diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Security | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Security')
-rw-r--r-- | src/VBox/Devices/Security/DevTpm.cpp | 1874 | ||||
-rw-r--r-- | src/VBox/Devices/Security/DrvTpmEmu.cpp | 1021 | ||||
-rw-r--r-- | src/VBox/Devices/Security/DrvTpmEmuTpms.cpp | 534 | ||||
-rw-r--r-- | src/VBox/Devices/Security/DrvTpmHost.cpp | 403 |
4 files changed, 3832 insertions, 0 deletions
diff --git a/src/VBox/Devices/Security/DevTpm.cpp b/src/VBox/Devices/Security/DevTpm.cpp new file mode 100644 index 00000000..936a62aa --- /dev/null +++ b/src/VBox/Devices/Security/DevTpm.cpp @@ -0,0 +1,1874 @@ +/* $Id: DevTpm.cpp $ */ +/** @file + * DevTpm - Trusted Platform Module emulation. + * + * This emulation is based on the spec available under (as of 2021-08-02): + * https://trustedcomputinggroup.org/wp-content/uploads/PC-Client-Specific-Platform-TPM-Profile-for-TPM-2p0-v1p05p_r14_pub.pdf + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_TPM +#include <VBox/vmm/pdmdev.h> +#include <VBox/vmm/pdmtpmifs.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include <iprt/formats/tpm.h> + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** The TPM saved state version. */ +#define TPM_SAVED_STATE_VERSION 1 + +/** Default vendor ID. */ +#define TPM_VID_DEFAULT 0x1014 +/** Default device ID. */ +#define TPM_DID_DEFAULT 0x0001 +/** Default revision ID. */ +#define TPM_RID_DEFAULT 0x01 +/** Maximum size of the data buffer in bytes. */ +#define TPM_DATA_BUFFER_SIZE_MAX 3968 + +/** The TPM MMIO base default as defined in chapter 5.2. */ +#define TPM_MMIO_BASE_DEFAULT 0xfed40000 +/** The size of the TPM MMIO area. */ +#define TPM_MMIO_SIZE 0x5000 + +/** Number of localities as mandated by the TPM spec. */ +#define TPM_LOCALITY_COUNT 5 +/** Size of each locality in the TPM MMIO area (chapter 6.5.2).*/ +#define TPM_LOCALITY_MMIO_SIZE 0x1000 + +/** @name TPM locality register related defines for the FIFO interface. + * @{ */ +/** Ownership management for a particular locality. */ +#define TPM_FIFO_LOCALITY_REG_ACCESS 0x00 +/** Indicates whether a dynamic OS has been established on this platform before.. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_ESTABLISHMENT RT_BIT(0) +/** On reads indicates whether the locality requests use of the TPM (1) or not or is already active locality (0), + * writing a 1 requests the locality to be granted getting the active locality.. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_REQUEST_USE RT_BIT(1) +/** Indicates whether another locality is requesting usage of the TPM. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_PENDING_REQUEST RT_BIT(2) +/** Writing a 1 forces the TPM to give control to the locality if it has a higher priority. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_SEIZE RT_BIT(3) +/** On reads indicates whether this locality has been seized by a higher locality (1) or not (0), writing a 1 clears this bit. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_BEEN_SEIZED RT_BIT(4) +/** On reads indicates whether this locality is active (1) or not (0), writing a 1 relinquishes control for this locality. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_ACTIVE RT_BIT(5) +/** Set bit indicates whether all other bits in this register have valid data. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_VALID RT_BIT(7) +/** Writable mask. */ +# define TPM_FIFO_LOCALITY_REG_ACCESS_WR_MASK 0x3a + +/** Interrupt enable register. */ +#define TPM_FIFO_LOCALITY_REG_INT_ENABLE 0x08 +/** Data available interrupt enable bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_DATA_AVAIL RT_BIT_32(0) +/** Status valid interrupt enable bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_STS_VALID RT_BIT_32(1) +/** Locality change interrupt enable bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_LOCALITY_CHANGE RT_BIT_32(2) +/** Interrupt polarity configuration. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_MASK 0x18 +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_SHIFT 3 +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_INT_POLARITY_SHIFT) +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_GET(a) (((a) & TPM_FIFO_LOCALITY_REG_INT_POLARITY_MASK) >> TPM_FIFO_LOCALITY_REG_INT_POLARITY_SHIFT) +/** High level interrupt trigger. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_HIGH 0 +/** Low level interrupt trigger. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_LOW 1 +/** Rising edge interrupt trigger. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_RISING 2 +/** Falling edge interrupt trigger. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_POLARITY_FALLING 3 +/** Command ready enable bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_CMD_RDY RT_BIT_32(7) +/** Global interrupt enable/disable bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_ENABLE_GLOBAL RT_BIT_32(31) + +/** Configured interrupt vector register. */ +#define TPM_FIFO_LOCALITY_REG_INT_VEC 0x0c + +/** Interrupt status register. */ +#define TPM_FIFO_LOCALITY_REG_INT_STS 0x10 +/** Data available interrupt occured bit, writing a 1 clears the bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_STS_DATA_AVAIL RT_BIT_32(0) +/** Status valid interrupt occured bit, writing a 1 clears the bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_STS_STS_VALID RT_BIT_32(1) +/** Locality change interrupt occured bit, writing a 1 clears the bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_STS_LOCALITY_CHANGE RT_BIT_32(2) +/** Command ready occured bit, writing a 1 clears the bit. */ +# define TPM_FIFO_LOCALITY_REG_INT_STS_CMD_RDY RT_BIT_32(7) +/** Writable mask. */ +# define TPM_FIFO_LOCALITY_REG_INT_STS_WR_MASK UINT32_C(0x87) + +/** Interfacce capabilities register. */ +#define TPM_FIFO_LOCALITY_REG_IF_CAP 0x14 +/** Flag whether the TPM supports the data avilable interrupt. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_DATA_AVAIL RT_BIT(0) +/** Flag whether the TPM supports the status valid interrupt. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_STS_VALID RT_BIT(1) +/** Flag whether the TPM supports the data avilable interrupt. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_LOCALITY_CHANGE RT_BIT(2) +/** Flag whether the TPM supports high level interrupts. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_LVL_HIGH RT_BIT(3) +/** Flag whether the TPM supports low level interrupts. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_LVL_LOW RT_BIT(4) +/** Flag whether the TPM supports rising edge interrupts. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_RISING_EDGE RT_BIT(5) +/** Flag whether the TPM supports falling edge interrupts. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_FALLING_EDGE RT_BIT(6) +/** Flag whether the TPM supports the command ready interrupt. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_INT_CMD_RDY RT_BIT(7) +/** Flag whether the busrt count field is static or dynamic. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_BURST_CNT_STATIC RT_BIT(8) +/** Maximum transfer size support. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_MASK 0x600 +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_SHIFT 9 +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_SHIFT) +/** Only legacy transfers supported. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_LEGACY 0x0 +/** 8B maximum transfer size. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_8B 0x1 +/** 32B maximum transfer size. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_32B 0x2 +/** 64B maximum transfer size. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_64B 0x3 +/** Interface version. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_MASK UINT32_C(0x70000000) +# define TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_SHIFT 28 +# define TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_SHIFT) +/** Interface 1.21 or ealier. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_IF_1_21 0 +/** Interface 1.3. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_IF_1_3 2 +/** Interface 1.3 for TPM 2.0. */ +# define TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_IF_1_3_TPM2 3 + +/** TPM status register. */ +#define TPM_FIFO_LOCALITY_REG_STS 0x18 +/** Writing a 1 forces the TPM to re-send the response. */ +# define TPM_FIFO_LOCALITY_REG_STS_RESPONSE_RETRY RT_BIT_32(1) +/** Indicating whether the TPM has finished a self test. */ +# define TPM_FIFO_LOCALITY_REG_STS_SELF_TEST_DONE RT_BIT_32(2) +/** Flag indicating whether the TPM expects more data for the command. */ +# define TPM_FIFO_LOCALITY_REG_STS_EXPECT RT_BIT_32(3) +/** Flag indicating whether the TPM has more response data available. */ +# define TPM_FIFO_LOCALITY_REG_STS_DATA_AVAIL RT_BIT_32(4) +/** Written by software to cause the TPM to execute a previously transfered command. */ +# define TPM_FIFO_LOCALITY_REG_STS_TPM_GO RT_BIT_32(5) +/** On reads indicates whether the TPM is ready to receive a new command (1) or not (0), + * a write of 1 causes the TPM to transition to this state. */ +# define TPM_FIFO_LOCALITY_REG_STS_CMD_RDY RT_BIT_32(6) +/** Indicates whether the Expect and data available bits are valid. */ +# define TPM_FIFO_LOCALITY_REG_STS_VALID RT_BIT_32(7) +/** Sets the burst count. */ +# define TPM_FIFO_LOCALITY_REG_STS_BURST_CNT_MASK UINT32_C(0xffff00) +# define TPM_FIFO_LOCALITY_REG_STS_BURST_CNT_SHIFT UINT32_C(8) +# define TPM_FIFO_LOCALITY_REG_STS_BURST_CNT_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_STS_BURST_CNT_SHIFT) +/** Cancels the active command. */ +# define TPM_FIFO_LOCALITY_REG_STS_CMD_CANCEL RT_BIT_32(24) +/** Reset establishment bit. */ +# define TPM_FIFO_LOCALITY_REG_STS_RST_ESTABLISHMENT RT_BIT_32(25) +/** Sets the TPM family. */ +# define TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_MASK UINT32_C(0x0c000000) +# define TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_SHIFT UINT32_C(26) +# define TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_SHIFT) +# define TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_1_2 UINT32_C(0) +# define TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_2_0 UINT32_C(1) + + +/** TPM end of HASH operation signal register for locality 4. */ +#define TPM_FIFO_LOCALITY_REG_HASH_END 0x20 +/** Data FIFO read/write register. */ +#define TPM_FIFO_LOCALITY_REG_DATA_FIFO 0x24 +/** TPM start of HASH operation signal register for locality 4. */ +#define TPM_FIFO_LOCALITY_REG_HASH_START 0x28 + +/** Locality interface ID register. */ +#define TPM_FIFO_LOCALITY_REG_INTF_ID 0x30 +/** Interface type field. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_MASK UINT32_C(0xf) +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_SHIFT 0 +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_SHIFT) +/** FIFO interface as defined in PTP for TPM 2.0 is active. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_FIFO_TPM20 0x0 +/** CRB interface is active. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_CRB 0x1 +/** FIFO interface as defined in TIS 1.3 is active. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_TIS1_3 0xf +/** Interface type field. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_MASK UINT32_C(0xf) +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_SHIFT 4 +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_SHIFT) +/** FIFO interface for TPM 2.0 */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_FIFO 0 +/** CRB interface version 0. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_CRB 1 +/** Only locality 0 is supported when clear, set if 5 localities are supported. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_LOCALITY RT_BIT(8) +/** Maximum transfer size support. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_MASK 0x1800 +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SHIFT 11 +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SHIFT) +/** Only legacy transfers supported. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_LEGACY 0x0 +/** 8B maximum transfer size. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_8B 0x1 +/** 32B maximum transfer size. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_32B 0x2 +/** 64B maximum transfer size. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_64B 0x3 +/** FIFO interface is supported and may be selected. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_FIFO RT_BIT(13) +/** CRB interface is supported and may be selected. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_CRB RT_BIT(14) +/** Interrupt polarity configuration. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_MASK 0x60000 +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_SHIFT 17 +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_SET(a) ((a) << TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_SHIFT) +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_GET(a) (((a) & TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_MASK) >> TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_SHIFT) +/** Selects the FIFO interface, takes effect on next _TPM_INIT. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_FIFO 0 +/** Selects the CRB interface, takes effect on next _TPM_INIT. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_CRB 1 +/** Locks the interface selector field and prevents further changes. */ +# define TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_LOCK RT_BIT(19) + + +/** Extended data FIFO read/write register. */ +#define TPM_FIFO_LOCALITY_REG_XDATA_FIFO 0x80 +/** TPM device and vendor ID. */ +#define TPM_FIFO_LOCALITY_REG_DID_VID 0xf00 +/** TPM revision ID. */ +#define TPM_FIFO_LOCALITY_REG_RID 0xf04 +/** @} */ + + +/** @name TPM locality register related defines for the CRB interface. + * @{ */ +/** Locality state register. */ +#define TPM_CRB_LOCALITY_REG_STATE 0x00 +/** Indicates whether a dynamic OS has been established on this platform before.. */ +# define TPM_CRB_LOCALITY_REG_ESTABLISHMENT RT_BIT(0) +/** Flag whether the host has a locality assigned (1) or not (0). */ +# define TPM_CRB_LOCALITY_REG_STATE_LOC_ASSIGNED RT_BIT(1) +/** Indicates the currently active locality. */ +# define TPM_CRB_LOCALITY_REG_STATE_ACTIVE_LOC_MASK UINT32_C(0x1c) +# define TPM_CRB_LOCALITY_REG_STATE_ACTIVE_LOC_SHIFT 2 +# define TPM_CRB_LOCALITY_REG_STATE_ACTIVE_LOC_SET(a) ((a) << TPM_CRB_LOCALITY_REG_STATE_ACTIVE_LOC_SHIFT) +/** Flag whether the register contains valid values. */ +# define TPM_CRB_LOCALITY_REG_STATE_VALID RT_BIT(7) + +/** Locality control register. */ +#define TPM_CRB_LOCALITY_REG_CTRL 0x08 +/** Request TPM access from this locality. */ +# define TPM_CRB_LOCALITY_REG_CTRL_REQ_ACCESS RT_BIT(0) +/** Release TPM access from this locality. */ +# define TPM_CRB_LOCALITY_REG_CTRL_RELINQUISH RT_BIT(1) +/** Seize TPM access in favor of this locality if it has a higher priority. */ +# define TPM_CRB_LOCALITY_REG_CTRL_SEIZE RT_BIT(2) +/** Resets the established bit if written from locality 3 or 4. */ +# define TPM_CRB_LOCALITY_REG_CTRL_RST_ESTABLISHMENT RT_BIT(3) + +/** Locality status register. */ +#define TPM_CRB_LOCALITY_REG_STS 0x0c +/** Locality has been granted access to the TPM. */ +# define TPM_CRB_LOCALITY_REG_STS_GRANTED RT_BIT(0) +/** A higher locality has seized the TPM from this locality. */ +# define TPM_CRB_LOCALITY_REG_STS_SEIZED RT_BIT(1) + +/** Locality interface ID register. */ +#define TPM_CRB_LOCALITY_REG_INTF_ID 0x30 +/** Interface type field. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_MASK UINT32_C(0xf) +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_SHIFT 0 +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_SET(a) ((a) << TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_SHIFT) +/** FIFO interface as defined in PTP for TPM 2.0 is active. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_FIFO_TPM20 0x0 +/** CRB interface is active. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_CRB 0x1 +/** FIFO interface as defined in TIS 1.3 is active. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_TIS1_3 0xf +/** Interface type field. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_MASK UINT32_C(0xf) +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_SHIFT 4 +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_SET(a) ((a) << TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_SHIFT) +/** FIFO interface for TPM 2.0 */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_FIFO 0 +/** CRB interface version 0. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_CRB 1 +/** Only locality 0 is supported when clear, set if 5 localities are supported. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_LOCALITY RT_BIT(8) +/** @todo TPM supports ... */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_CRB_IDLE_BYPASS RT_BIT(9) +/** Maximum transfer size support. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_MASK 0x1800 +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SHIFT 11 +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SET(a) ((a) << TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SHIFT) +/** Only legacy transfers supported. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_LEGACY 0x0 +/** 8B maximum transfer size. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_8B 0x1 +/** 32B maximum transfer size. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_32B 0x2 +/** 64B maximum transfer size. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_64B 0x3 +/** FIFO interface is supported and may be selected. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_FIFO RT_BIT(13) +/** CRB interface is supported and may be selected. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_CAP_CRB RT_BIT(14) +/** Interrupt polarity configuration. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_MASK 0x60000 +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_SHIFT 17 +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_SET(a) ((a) << TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_SHIFT) +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_GET(a) (((a) & TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_MASK) >> TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_SHIFT) +/** Selects the FIFO interface, takes effect on next _TPM_INIT. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_FIFO 0 +/** Selects the CRB interface, takes effect on next _TPM_INIT. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_CRB 1 +/** Locks the interface selector field and prevents further changes. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_LOCK RT_BIT(19) +/** Revision ID field. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_RID_SHIFT 17 +# define TPM_CRB_LOCALITY_REG_INTF_ID_RID_SET(a) ((uint64_t)(a) << TPM_CRB_LOCALITY_REG_INTF_ID_RID_SHIFT) +/** Vendor ID field. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_VID_SHIFT 32 +# define TPM_CRB_LOCALITY_REG_INTF_ID_VID_SET(a) ((uint64_t)(a) << TPM_CRB_LOCALITY_REG_INTF_ID_VID_SHIFT) +/** Device ID field. */ +# define TPM_CRB_LOCALITY_REG_INTF_ID_DID_SHIFT 48 +# define TPM_CRB_LOCALITY_REG_INTF_ID_DID_SET(a) ((uint64_t)(a) << TPM_CRB_LOCALITY_REG_INTF_ID_DID_SHIFT) + +/** Locality CRB extension register (optional and locality 0 only). */ +#define TPM_CRB_LOCALITY_REG_CTRL_EXT 0x38 + +/** Locality CRB request register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_REQ 0x40 +/** The TPM should transition to the ready state to receive a new command. */ +# define TPM_CRB_LOCALITY_REG_CTRL_REQ_CMD_RDY RT_BIT(0) +/** The TPM should transition to the idle state. */ +# define TPM_CRB_LOCALITY_REG_CTRL_REQ_IDLE RT_BIT(1) + +/** Locality CRB status register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_STS 0x44 +/** This bit indicates that the TPM ran into a fatal error if set. */ +# define TPM_CRB_LOCALITY_REG_CTRL_STS_TPM_FATAL_ERR RT_BIT(0) +/** This bit indicates that the TPM is in the idle state. */ +# define TPM_CRB_LOCALITY_REG_CTRL_STS_TPM_IDLE RT_BIT(1) + +/** Locality CRB cancel register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_CANCEL 0x48 +/** Locality CRB start register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_START 0x4c + +/** Locality interrupt enable register. */ +#define TPM_CRB_LOCALITY_REG_INT_ENABLE 0x50 +/** Enable the "TPM has executed a reqeust and response is available" interrupt. */ +# define TPM_CRB_LOCALITY_REG_INT_ENABLE_START RT_BIT(0) +/** Enable the "TPM has transitioned to the command ready state" interrupt. */ +# define TPM_CRB_LOCALITY_REG_INT_CMD_RDY RT_BIT(1) +/** Enable the "TPM has cleared the establishment flag" interrupt. */ +# define TPM_CRB_LOCALITY_REG_INT_ESTABLISHMENT_CLR RT_BIT(2) +/** Enable the "active locality has changed" interrupt. */ +# define TPM_CRB_LOCALITY_REG_INT_LOC_CHANGED RT_BIT(3) +/** Enables interrupts globally as defined by the individual bits in this register. */ +# define TPM_CRB_LOCALITY_REG_INT_GLOBAL_ENABLE RT_BIT(31) + +/** Locality interrupt status register. */ +#define TPM_CRB_LOCALITY_REG_INT_STS 0x54 +/** Indicates that the TPM as executed a command and the response is available for reading, writing a 1 clears the bit. */ +# define TPM_CRB_LOCALITY_REG_INT_STS_START RT_BIT(0) +/** Indicates that the TPM has finished the transition to the ready state, writing a 1 clears this bit. */ +# define TPM_CRB_LOCALITY_REG_INT_STS_CMD_RDY RT_BIT(1) +/** Indicates that the TPM has cleared the establishment flag, writing a 1 clears this bit. */ +# define TPM_CRB_LOCALITY_REG_INT_STS_ESTABLISHMENT_CLR RT_BIT(2) +/** Indicates that a locality change has occurrec, writing a 1 clears this bit. */ +# define TPM_CRB_LOCALITY_REG_INT_STS_LOC_CHANGED RT_BIT(3) + +/** Locality command buffer size register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_CMD_SZ 0x58 +/** Locality command buffer low address register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_CMD_LADDR 0x5c +/** Locality command buffer low address register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_CMD_HADDR 0x60 +/** Locality response buffer size register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_RSP_SZ 0x64 +/** Locality response buffer address register. */ +#define TPM_CRB_LOCALITY_REG_CTRL_RSP_ADDR 0x68 +/** Locality data buffer. */ +#define TPM_CRB_LOCALITY_REG_DATA_BUFFER 0x80 +/** @} */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Possible TPM states + * (see chapter 5.6.12.1 Figure 3 State Transition Diagram). + */ +typedef enum DEVTPMSTATE +{ + /** Invalid state, do not use. */ + DEVTPMSTATE_INVALID = 0, + /** Idle state. */ + DEVTPMSTATE_IDLE, + /** Ready to accept command data. */ + DEVTPMSTATE_READY, + /** Command data being transfered. */ + DEVTPMSTATE_CMD_RECEPTION, + /** Command is being executed by the TPM. */ + DEVTPMSTATE_CMD_EXEC, + /** Command has completed and data can be read. */ + DEVTPMSTATE_CMD_COMPLETION, + /** Command is being canceled. */ + DEVTPMSTATE_CMD_CANCEL, + /** TPM ran into a fatal error and is not operational. */ + DEVTPMSTATE_FATAL_ERROR, + /** Last valid state (used for saved state sanity check). */ + DEVTPMSTATE_LAST_VALID = DEVTPMSTATE_FATAL_ERROR, + /** 32bit hack. */ + DEVTPMSTATE_32BIT_HACK = 0x7fffffff +} DEVTPMSTATE; + + +/** + * Locality state. + */ +typedef struct DEVTPMLOCALITY +{ + /** The interrupt enable register. */ + uint32_t uRegIntEn; + /** The interrupt status register. */ + uint32_t uRegIntSts; +} DEVTPMLOCALITY; +/** Pointer to a locality state. */ +typedef DEVTPMLOCALITY *PDEVTPMLOCALITY; +/** Pointer to a const locality state. */ +typedef const DEVTPMLOCALITY *PCDEVTPMLOCALITY; + + +/** + * Shared TPM device state. + */ +typedef struct DEVTPM +{ + /** Base MMIO address of the TPM device. */ + RTGCPHYS GCPhysMmio; + /** The handle of the MMIO region. */ + IOMMMIOHANDLE hMmio; + /** The handle for the ring-3 task. */ + PDMTASKHANDLE hTpmCmdTask; + /** The vendor ID configured. */ + uint16_t uVenId; + /** The device ID configured. */ + uint16_t uDevId; + /** The revision ID configured. */ + uint8_t bRevId; + /** The IRQ value. */ + uint8_t uIrq; + /** Flag whether CRB access mode is used. */ + bool fCrb; + /** Flag whether the TPM driver below supportes other localities than 0. */ + bool fLocChangeSup; + /** Flag whether the establishment bit is set. */ + bool fEstablishmentSet; + + /** Currently selected locality. */ + uint8_t bLoc; + /** States of the implemented localities. */ + DEVTPMLOCALITY aLoc[TPM_LOCALITY_COUNT]; + /** Bitmask of localities having requested access to the TPM. */ + uint32_t bmLocReqAcc; + /** Bitmask of localities having been seized access from the TPM. */ + uint32_t bmLocSeizedAcc; + /** The current state of the TPM. */ + DEVTPMSTATE enmState; + /** The TPM version being emulated. */ + TPMVERSION enmTpmVers; + + /** Size of the command/response buffer. */ + uint32_t cbCmdResp; + /** Offset into the Command/Response buffer. */ + uint32_t offCmdResp; + /** Command/Response buffer. */ + uint8_t abCmdResp[TPM_DATA_BUFFER_SIZE_MAX]; +} DEVTPM; +/** Pointer to the shared TPM device state. */ +typedef DEVTPM *PDEVTPM; + +/** The special no current locality selected value. */ +#define TPM_NO_LOCALITY_SELECTED 0xff + + +/** + * TPM device state for ring-3. + */ +typedef struct DEVTPMR3 +{ + /** Pointer to the device instance. */ + PPDMDEVINS pDevIns; + /** The base interface for LUN\#0. */ + PDMIBASE IBase; + /** The base interface below. */ + R3PTRTYPE(PPDMIBASE) pDrvBase; + /** The TPM connector interface below. */ + R3PTRTYPE(PPDMITPMCONNECTOR) pDrvTpm; +} DEVTPMR3; +/** Pointer to the TPM device state for ring-3. */ +typedef DEVTPMR3 *PDEVTPMR3; + + +/** + * TPM device state for ring-0. + */ +typedef struct DEVTPMR0 +{ + uint32_t u32Dummy; +} DEVTPMR0; +/** Pointer to the TPM device state for ring-0. */ +typedef DEVTPMR0 *PDEVTPMR0; + + +/** + * TPM device state for raw-mode. + */ +typedef struct DEVTPMRC +{ + uint32_t u32Dummy; +} DEVTPMRC; +/** Pointer to the TPM device state for raw-mode. */ +typedef DEVTPMRC *PDEVTPMRC; + +/** The TPM device state for the current context. */ +typedef CTX_SUFF(DEVTPM) DEVTPMCC; +/** Pointer to the TPM device state for the current context. */ +typedef CTX_SUFF(PDEVTPM) PDEVTPMCC; + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef IN_RING3 +/** + * SSM descriptor table for the TPM structure. + */ +static SSMFIELD const g_aTpmFields[] = +{ + SSMFIELD_ENTRY(DEVTPM, fEstablishmentSet), + SSMFIELD_ENTRY(DEVTPM, bLoc), + SSMFIELD_ENTRY(DEVTPM, aLoc[0].uRegIntEn), + SSMFIELD_ENTRY(DEVTPM, aLoc[0].uRegIntSts), + SSMFIELD_ENTRY(DEVTPM, aLoc[1].uRegIntEn), + SSMFIELD_ENTRY(DEVTPM, aLoc[1].uRegIntSts), + SSMFIELD_ENTRY(DEVTPM, aLoc[2].uRegIntEn), + SSMFIELD_ENTRY(DEVTPM, aLoc[2].uRegIntSts), + SSMFIELD_ENTRY(DEVTPM, aLoc[3].uRegIntEn), + SSMFIELD_ENTRY(DEVTPM, aLoc[3].uRegIntSts), + SSMFIELD_ENTRY(DEVTPM, aLoc[4].uRegIntEn), + SSMFIELD_ENTRY(DEVTPM, aLoc[4].uRegIntSts), + SSMFIELD_ENTRY(DEVTPM, bmLocReqAcc), + SSMFIELD_ENTRY(DEVTPM, bmLocSeizedAcc), + SSMFIELD_ENTRY(DEVTPM, enmState), + SSMFIELD_ENTRY(DEVTPM, offCmdResp), + SSMFIELD_ENTRY(DEVTPM, abCmdResp), + SSMFIELD_ENTRY_TERM() +}; +#endif + + +/** + * Sets the IRQ line of the given device to the given state. + * + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param iLvl The interrupt level to set. + */ +DECLINLINE(void) tpmIrqReq(PPDMDEVINS pDevIns, PDEVTPM pThis, int iLvl) +{ + PDMDevHlpISASetIrqNoWait(pDevIns, pThis->uIrq, iLvl); +} + + +/** + * Updates the IRQ status of the given locality. + * + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param pLoc The locality state. + */ +static void tpmLocIrqUpdate(PPDMDEVINS pDevIns, PDEVTPM pThis, PDEVTPMLOCALITY pLoc) +{ + if ( (pLoc->uRegIntEn & TPM_CRB_LOCALITY_REG_INT_GLOBAL_ENABLE) /* Aliases with TPM_FIFO_LOCALITY_REG_INT_ENABLE_GLOBAL */ + && (pLoc->uRegIntEn & pLoc->uRegIntSts)) + tpmIrqReq(pDevIns, pThis, 1); + else + tpmIrqReq(pDevIns, pThis, 0); +} + + +/** + * Sets the interrupt status for the given locality, firing an interrupt if necessary. + * + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param pLoc The locality state. + * @param uSts The interrupt status bit to set. + */ +static void tpmLocSetIntSts(PPDMDEVINS pDevIns, PDEVTPM pThis, PDEVTPMLOCALITY pLoc, uint32_t uSts) +{ + pLoc->uRegIntSts |= uSts; + tpmLocIrqUpdate(pDevIns, pThis, pLoc); +} + + +/** + * Selects the next locality which has requested access. + * + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + */ +static void tpmLocSelectNext(PPDMDEVINS pDevIns, PDEVTPM pThis) +{ + Assert(pThis->bmLocReqAcc); + Assert(pThis->bLoc == TPM_NO_LOCALITY_SELECTED); + pThis->bLoc = (uint8_t)ASMBitLastSetU32(pThis->bmLocReqAcc) - 1; /* Select one with highest priority. */ + + tpmLocSetIntSts(pDevIns, pThis, &pThis->aLoc[pThis->bLoc], TPM_CRB_LOCALITY_REG_INT_STS_LOC_CHANGED); +} + + +/** + * Returns the given locality being accessed from the given TPM MMIO offset. + * + * @returns Locality number. + * @param off The offset into the TPM MMIO region. + */ +DECLINLINE(uint8_t) tpmGetLocalityFromOffset(RTGCPHYS off) +{ + return off / TPM_LOCALITY_MMIO_SIZE; +} + + +/** + * Returns the given register of a particular locality being accessed from the given TPM MMIO offset. + * + * @returns Register index being accessed. + * @param off The offset into the TPM MMIO region. + */ +DECLINLINE(uint32_t) tpmGetRegisterFromOffset(RTGCPHYS off) +{ + return off % TPM_LOCALITY_MMIO_SIZE; +} + + +/** + * Read from a FIFO interface register. + * + * @returns VBox strict status code. + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param pLoc The locality state being read from. + * @param bLoc The locality index. + * @param uReg The register offset being accessed. + * @param pu64 Where to store the read data. + * @param cb Number of bytes to read. + */ +static VBOXSTRICTRC tpmMmioFifoRead(PPDMDEVINS pDevIns, PDEVTPM pThis, PDEVTPMLOCALITY pLoc, + uint8_t bLoc, uint32_t uReg, uint64_t *pu64, size_t cb) +{ + RT_NOREF(pDevIns); + VBOXSTRICTRC rc = VINF_SUCCESS; + + /* Special path for the data buffer. */ + if ( ( ( uReg >= TPM_FIFO_LOCALITY_REG_DATA_FIFO + && uReg < TPM_FIFO_LOCALITY_REG_DATA_FIFO + sizeof(uint32_t)) + || ( uReg >= TPM_FIFO_LOCALITY_REG_XDATA_FIFO + && uReg < TPM_FIFO_LOCALITY_REG_XDATA_FIFO + sizeof(uint32_t))) + && bLoc == pThis->bLoc + && pThis->enmState == DEVTPMSTATE_CMD_COMPLETION) + { + if (pThis->offCmdResp <= pThis->cbCmdResp - cb) + { + memcpy(pu64, &pThis->abCmdResp[pThis->offCmdResp], cb); + pThis->offCmdResp += (uint32_t)cb; + } + else + memset(pu64, 0xff, cb); + return VINF_SUCCESS; + } + + uint64_t u64; + switch (uReg) + { + case TPM_FIFO_LOCALITY_REG_ACCESS: + u64 = TPM_FIFO_LOCALITY_REG_ACCESS_VALID; + if (pThis->bLoc == bLoc) + u64 |= TPM_FIFO_LOCALITY_REG_ACCESS_ACTIVE; + if (pThis->bmLocSeizedAcc & RT_BIT_32(bLoc)) + u64 |= TPM_FIFO_LOCALITY_REG_ACCESS_BEEN_SEIZED; + if (pThis->bmLocReqAcc & ~RT_BIT_32(bLoc)) + u64 |= TPM_FIFO_LOCALITY_REG_ACCESS_PENDING_REQUEST; + if ( pThis->bLoc != bLoc + && pThis->bmLocReqAcc & RT_BIT_32(bLoc)) + u64 |= TPM_FIFO_LOCALITY_REG_ACCESS_REQUEST_USE; + if (pThis->fEstablishmentSet) + u64 |= TPM_FIFO_LOCALITY_REG_ACCESS_ESTABLISHMENT; + break; + case TPM_FIFO_LOCALITY_REG_INT_ENABLE: + u64 = pLoc->uRegIntEn; + break; + case TPM_FIFO_LOCALITY_REG_INT_VEC: + u64 = pThis->uIrq; + break; + case TPM_FIFO_LOCALITY_REG_INT_STS: + u64 = pLoc->uRegIntSts; + break; + case TPM_FIFO_LOCALITY_REG_IF_CAP: + u64 = TPM_FIFO_LOCALITY_REG_IF_CAP_INT_DATA_AVAIL + | TPM_FIFO_LOCALITY_REG_IF_CAP_INT_STS_VALID + | TPM_FIFO_LOCALITY_REG_IF_CAP_INT_LOCALITY_CHANGE + | TPM_FIFO_LOCALITY_REG_IF_CAP_INT_LVL_LOW + | TPM_FIFO_LOCALITY_REG_IF_CAP_INT_CMD_RDY + | TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_SET(TPM_FIFO_LOCALITY_REG_IF_CAP_DATA_XFER_SZ_64B) + | TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_SET(TPM_FIFO_LOCALITY_REG_IF_CAP_IF_VERSION_IF_1_3); /** @todo Make some of them configurable? */ + break; + case TPM_FIFO_LOCALITY_REG_STS: + if (bLoc != pThis->bLoc) + { + u64 = UINT64_MAX; + break; + } + + u64 = TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_SET( pThis->enmTpmVers == TPMVERSION_1_2 + ? TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_1_2 + : TPM_FIFO_LOCALITY_REG_STS_TPM_FAMILY_2_0) + | TPM_FIFO_LOCALITY_REG_STS_BURST_CNT_SET(_1K) + | TPM_FIFO_LOCALITY_REG_STS_VALID; + if (pThis->enmState == DEVTPMSTATE_READY) + u64 |= TPM_FIFO_LOCALITY_REG_STS_CMD_RDY; + else if (pThis->enmState == DEVTPMSTATE_CMD_RECEPTION) /* When in the command reception state check whether all of the command data has been received. */ + { + if ( pThis->offCmdResp < sizeof(TPMREQHDR) + || pThis->offCmdResp < RTTpmReqGetSz((PCTPMREQHDR)&pThis->abCmdResp[0])) + u64 |= TPM_FIFO_LOCALITY_REG_STS_EXPECT; + } + else if (pThis->enmState == DEVTPMSTATE_CMD_COMPLETION) /* Check whether there is more response data available. */ + { + if (pThis->offCmdResp < RTTpmRespGetSz((PCTPMRESPHDR)&pThis->abCmdResp[0])) + u64 |= TPM_FIFO_LOCALITY_REG_STS_DATA_AVAIL; + } + break; + case TPM_FIFO_LOCALITY_REG_INTF_ID: + u64 = TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_SET(TPM_FIFO_LOCALITY_REG_INTF_ID_IF_VERS_FIFO) + | TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SET(TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_64B) + | TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_GET(TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_FIFO) + | TPM_FIFO_LOCALITY_REG_INTF_ID_IF_SEL_LOCK; + if (pThis->enmTpmVers == TPMVERSION_1_2) + u64 |= TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_SET(TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_TIS1_3); + else + u64 |= TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_SET(TPM_FIFO_LOCALITY_REG_INTF_ID_IF_TYPE_FIFO_TPM20); + + if (pThis->fLocChangeSup) /* Only advertise the locality capability if the driver below supports it. */ + u64 |= TPM_FIFO_LOCALITY_REG_INTF_ID_CAP_LOCALITY; + break; + case TPM_FIFO_LOCALITY_REG_DID_VID: + u64 = RT_H2BE_U32(RT_MAKE_U32(pThis->uVenId, pThis->uDevId)); + break; + case TPM_FIFO_LOCALITY_REG_RID: + u64 = pThis->bRevId; + break; + default: /* Return ~0. */ + u64 = UINT64_MAX; + break; + } + + *pu64 = u64; + + return rc; +} + + +/** + * Read to a FIFO interface register. + * + * @returns VBox strict status code. + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param pLoc The locality state being written to. + * @param bLoc The locality index. + * @param uReg The register offset being accessed. + * @param u64 The value to write. + * @param cb Number of bytes to write. + */ +static VBOXSTRICTRC tpmMmioFifoWrite(PPDMDEVINS pDevIns, PDEVTPM pThis, PDEVTPMLOCALITY pLoc, + uint8_t bLoc, uint32_t uReg, uint64_t u64, size_t cb) +{ +#ifdef IN_RING3 + PDEVTPMR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMR3); +#endif + + /* Special path for the data buffer. */ + if ( ( ( uReg >= TPM_FIFO_LOCALITY_REG_DATA_FIFO + && uReg < TPM_FIFO_LOCALITY_REG_DATA_FIFO + sizeof(uint32_t)) + || ( uReg >= TPM_FIFO_LOCALITY_REG_XDATA_FIFO + && uReg < TPM_FIFO_LOCALITY_REG_XDATA_FIFO + sizeof(uint32_t))) + && bLoc == pThis->bLoc + && ( pThis->enmState == DEVTPMSTATE_READY + || pThis->enmState == DEVTPMSTATE_CMD_RECEPTION)) + { + pThis->enmState = DEVTPMSTATE_CMD_RECEPTION; + if (pThis->offCmdResp <= pThis->cbCmdResp - cb) + { + memcpy(&pThis->abCmdResp[pThis->offCmdResp], &u64, cb); + pThis->offCmdResp += (uint32_t)cb; + } + return VINF_SUCCESS; + } + + VBOXSTRICTRC rc = VINF_SUCCESS; + uint32_t u32 = (uint32_t)u64; + + switch (uReg) + { + case TPM_FIFO_LOCALITY_REG_ACCESS: + u32 &= TPM_FIFO_LOCALITY_REG_ACCESS_WR_MASK; + /* + * Chapter 5.6.11, 2 states that writing to this register with more than one + * bit set to '1' is vendor specific, we decide to ignore such writes to make the logic + * below simpler. + */ + if (!RT_IS_POWER_OF_TWO(u32)) + break; + + /* Seize access only if this locality has a higher priority than the currently selected one. */ + if ( (u32 & TPM_FIFO_LOCALITY_REG_ACCESS_SEIZE) + && pThis->bLoc != TPM_NO_LOCALITY_SELECTED + && bLoc > pThis->bLoc) + { + pThis->bmLocSeizedAcc |= RT_BIT_32(pThis->bLoc); + /** @todo Abort command. */ + pThis->bLoc = bLoc; + } + + if ( (u64 & TPM_FIFO_LOCALITY_REG_ACCESS_REQUEST_USE) + && !(pThis->bmLocReqAcc & RT_BIT_32(bLoc))) + { + pThis->bmLocReqAcc |= RT_BIT_32(bLoc); + if (pThis->bLoc == TPM_NO_LOCALITY_SELECTED) + { + pThis->bLoc = bLoc; /* Doesn't fire an interrupt. */ + pThis->bmLocSeizedAcc &= ~RT_BIT_32(bLoc); + } + } + + if ( (u64 & TPM_FIFO_LOCALITY_REG_ACCESS_ACTIVE) + && (pThis->bmLocReqAcc & RT_BIT_32(bLoc))) + { + pThis->bmLocReqAcc &= ~RT_BIT_32(bLoc); + if (pThis->bLoc == bLoc) + { + pThis->bLoc = TPM_NO_LOCALITY_SELECTED; + if (pThis->bmLocReqAcc) + tpmLocSelectNext(pDevIns, pThis); /* Select the next locality. */ + } + } + break; + case TPM_FIFO_LOCALITY_REG_INT_ENABLE: + if (bLoc != pThis->bLoc) + break; + pLoc->uRegIntEn = u32; + tpmLocIrqUpdate(pDevIns, pThis, pLoc); + break; + case TPM_FIFO_LOCALITY_REG_INT_STS: + if (bLoc != pThis->bLoc) + break; + pLoc->uRegIntSts &= ~(u32 & TPM_FIFO_LOCALITY_REG_INT_STS_WR_MASK); + tpmLocIrqUpdate(pDevIns, pThis, pLoc); + break; + case TPM_FIFO_LOCALITY_REG_STS: + /* + * Writes are ignored completely if the locality being accessed is not the + * current active one or if the value has multiple bits set (not a power of two), + * see chapter 5.6.12.1. + */ + if ( bLoc != pThis->bLoc + || !RT_IS_POWER_OF_TWO(u64)) + break; + + if ( (u64 & TPM_FIFO_LOCALITY_REG_STS_CMD_RDY) + && ( pThis->enmState == DEVTPMSTATE_IDLE + || pThis->enmState == DEVTPMSTATE_CMD_COMPLETION)) + { + pThis->enmState = DEVTPMSTATE_READY; + pThis->offCmdResp = 0; + tpmLocSetIntSts(pDevIns, pThis, pLoc, TPM_FIFO_LOCALITY_REG_INT_STS_CMD_RDY); + } + + if ( (u64 & TPM_FIFO_LOCALITY_REG_STS_TPM_GO) + && pThis->enmState == DEVTPMSTATE_CMD_RECEPTION) + { + pThis->enmState = DEVTPMSTATE_CMD_EXEC; + rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hTpmCmdTask); + } + + if ( (u64 & TPM_FIFO_LOCALITY_REG_STS_RST_ESTABLISHMENT) + && pThis->bLoc >= 3 + && ( pThis->enmState == DEVTPMSTATE_IDLE + || pThis->enmState == DEVTPMSTATE_CMD_COMPLETION)) + { +#ifndef IN_RING3 + rc = VINF_IOM_R3_MMIO_WRITE; + break; +#else + if (pThisCC->pDrvTpm) + { + int rc2 = pThisCC->pDrvTpm->pfnResetEstablishedFlag(pThisCC->pDrvTpm, pThis->bLoc); + if (RT_SUCCESS(rc2)) + pThis->fEstablishmentSet = false; + else + pThis->enmState = DEVTPMSTATE_FATAL_ERROR; + } + else + pThis->fEstablishmentSet = false; +#endif + } + + if ( (u64 & TPM_FIFO_LOCALITY_REG_STS_CMD_CANCEL) + && pThis->enmState == DEVTPMSTATE_CMD_EXEC) + { +#ifndef IN_RING3 + rc = VINF_IOM_R3_MMIO_WRITE; + break; +#else + if (pThisCC->pDrvTpm) + { + pThis->enmState = DEVTPMSTATE_CMD_CANCEL; + int rc2 = pThisCC->pDrvTpm->pfnCmdCancel(pThisCC->pDrvTpm); + if (RT_FAILURE(rc2)) + pThis->enmState = DEVTPMSTATE_FATAL_ERROR; + } +#endif + } + + break; + case TPM_FIFO_LOCALITY_REG_INT_VEC: + case TPM_FIFO_LOCALITY_REG_IF_CAP: + case TPM_FIFO_LOCALITY_REG_DID_VID: + case TPM_FIFO_LOCALITY_REG_RID: + default: /* Ignore. */ + break; + } + + return rc; +} + + +/** + * Read from a CRB interface register. + * + * @returns VBox strict status code. + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param pLoc The locality state being read from. + * @param bLoc The locality index. + * @param uReg The register offset being accessed. + * @param pu64 Where to store the read data. + * @param cb Size of the read in bytes. + */ +static VBOXSTRICTRC tpmMmioCrbRead(PPDMDEVINS pDevIns, PDEVTPM pThis, PDEVTPMLOCALITY pLoc, + uint8_t bLoc, uint32_t uReg, uint64_t *pu64, size_t cb) +{ + RT_NOREF(pDevIns); + + /* Special path for the data buffer. */ + if ( uReg >= TPM_CRB_LOCALITY_REG_DATA_BUFFER + && uReg < TPM_CRB_LOCALITY_REG_DATA_BUFFER + pThis->cbCmdResp + && bLoc == pThis->bLoc + && pThis->enmState == DEVTPMSTATE_CMD_COMPLETION) + { + memcpy(pu64, &pThis->abCmdResp[uReg - TPM_CRB_LOCALITY_REG_DATA_BUFFER], cb); + return VINF_SUCCESS; + } + + VBOXSTRICTRC rc = VINF_SUCCESS; + uint64_t u64 = UINT64_MAX; + switch (uReg) + { + case TPM_CRB_LOCALITY_REG_STATE: + u64 = TPM_CRB_LOCALITY_REG_STATE_VALID + | ( pThis->bLoc != TPM_NO_LOCALITY_SELECTED + ? TPM_CRB_LOCALITY_REG_STATE_ACTIVE_LOC_SET(pThis->bLoc) | TPM_CRB_LOCALITY_REG_STATE_LOC_ASSIGNED + : TPM_CRB_LOCALITY_REG_STATE_ACTIVE_LOC_SET(0)); + if (pThis->fEstablishmentSet) + u64 |= TPM_CRB_LOCALITY_REG_ESTABLISHMENT; + break; + case TPM_CRB_LOCALITY_REG_STS: + u64 = pThis->bLoc == bLoc + ? TPM_CRB_LOCALITY_REG_STS_GRANTED + : 0; + u64 |= pThis->bmLocSeizedAcc & RT_BIT_32(bLoc) + ? TPM_CRB_LOCALITY_REG_STS_SEIZED + : 0; + break; + case TPM_CRB_LOCALITY_REG_INTF_ID: + u64 = TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_SET(TPM_CRB_LOCALITY_REG_INTF_ID_IF_TYPE_CRB) + | TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_SET(TPM_CRB_LOCALITY_REG_INTF_ID_IF_VERS_CRB) + | TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_SET(TPM_CRB_LOCALITY_REG_INTF_ID_CAP_DATA_XFER_SZ_64B) + | TPM_CRB_LOCALITY_REG_INTF_ID_CAP_CRB + | TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_GET(TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_CRB) + | TPM_CRB_LOCALITY_REG_INTF_ID_IF_SEL_LOCK + | TPM_CRB_LOCALITY_REG_INTF_ID_RID_SET(pThis->bRevId) + | TPM_CRB_LOCALITY_REG_INTF_ID_VID_SET(pThis->uVenId) + | TPM_CRB_LOCALITY_REG_INTF_ID_DID_SET(pThis->uDevId); + + if (pThis->fLocChangeSup) /* Only advertise the locality capability if the driver below supports it. */ + u64 |= TPM_CRB_LOCALITY_REG_INTF_ID_CAP_LOCALITY; + + break; + case TPM_CRB_LOCALITY_REG_CTRL_REQ: + if (bLoc != pThis->bLoc) + break; + /* + * Command ready and go idle are always 0 upon read + * as we don't need time to transition to this state + * when written by the guest. + */ + u64 = 0; + break; + case TPM_CRB_LOCALITY_REG_CTRL_STS: + if (bLoc != pThis->bLoc) + break; + if (pThis->enmState == DEVTPMSTATE_FATAL_ERROR) + u64 = TPM_CRB_LOCALITY_REG_CTRL_STS_TPM_FATAL_ERR; + else if (pThis->enmState == DEVTPMSTATE_IDLE) + u64 = TPM_CRB_LOCALITY_REG_CTRL_STS_TPM_IDLE; + else + u64 = 0; + break; + case TPM_CRB_LOCALITY_REG_CTRL_CANCEL: + if (bLoc != pThis->bLoc) + break; + if (pThis->enmState == DEVTPMSTATE_CMD_CANCEL) + u64 = 0x1; + else + u64 = 0; + break; + case TPM_CRB_LOCALITY_REG_CTRL_START: + if (bLoc != pThis->bLoc) + break; + if (pThis->enmState == DEVTPMSTATE_CMD_EXEC) + u64 = 0x1; + else + u64 = 0; + break; + case TPM_CRB_LOCALITY_REG_INT_ENABLE: + u64 = pLoc->uRegIntEn; + break; + case TPM_CRB_LOCALITY_REG_INT_STS: + u64 = pLoc->uRegIntSts; + break; + case TPM_CRB_LOCALITY_REG_CTRL_CMD_LADDR: + u64 = pThis->GCPhysMmio + (bLoc * TPM_LOCALITY_MMIO_SIZE) + TPM_CRB_LOCALITY_REG_DATA_BUFFER; + break; + case TPM_CRB_LOCALITY_REG_CTRL_CMD_HADDR: + u64 = (pThis->GCPhysMmio + (bLoc * TPM_LOCALITY_MMIO_SIZE) + TPM_CRB_LOCALITY_REG_DATA_BUFFER) >> 32; + break; + case TPM_CRB_LOCALITY_REG_CTRL_CMD_SZ: + case TPM_CRB_LOCALITY_REG_CTRL_RSP_SZ: + u64 = pThis->cbCmdResp; + break; + case TPM_CRB_LOCALITY_REG_CTRL_RSP_ADDR: + u64 = pThis->GCPhysMmio + (bLoc * TPM_LOCALITY_MMIO_SIZE) + TPM_CRB_LOCALITY_REG_DATA_BUFFER; + break; + case TPM_CRB_LOCALITY_REG_CTRL: /* Writeonly */ + u64 = 0; + break; + case TPM_CRB_LOCALITY_REG_CTRL_EXT: + default: + break; /* Return ~0 */ + } + + *pu64 = u64; + return rc; +} + + +/** + * Read to a CRB interface register. + * + * @returns VBox strict status code. + * @param pDevIns Pointer to the PDM device instance data. + * @param pThis Pointer to the shared TPM device. + * @param pLoc The locality state being written to. + * @param bLoc The locality index. + * @param uReg The register offset being accessed. + * @param u64 The value to write. + * @param cb Size of the write in bytes. + */ +static VBOXSTRICTRC tpmMmioCrbWrite(PPDMDEVINS pDevIns, PDEVTPM pThis, PDEVTPMLOCALITY pLoc, + uint8_t bLoc, uint32_t uReg, uint64_t u64, size_t cb) +{ +#ifdef IN_RING3 + PDEVTPMR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMR3); +#endif + + VBOXSTRICTRC rc = VINF_SUCCESS; + uint32_t u32 = (uint32_t)u64; + + /* Special path for the data buffer. */ + if ( uReg >= TPM_CRB_LOCALITY_REG_DATA_BUFFER + && uReg < TPM_CRB_LOCALITY_REG_DATA_BUFFER + pThis->cbCmdResp + && bLoc == pThis->bLoc + && ( pThis->enmState == DEVTPMSTATE_READY + || pThis->enmState == DEVTPMSTATE_CMD_RECEPTION)) + { + pThis->enmState = DEVTPMSTATE_CMD_RECEPTION; + memcpy(&pThis->abCmdResp[uReg - TPM_CRB_LOCALITY_REG_DATA_BUFFER], &u64, cb); + return VINF_SUCCESS; + } + + switch (uReg) + { + case TPM_CRB_LOCALITY_REG_CTRL: + { + /* See chapter 6.5.3.2.2.1. */ + if ( (u64 & TPM_CRB_LOCALITY_REG_CTRL_RST_ESTABLISHMENT) + && pThis->bLoc >= 3 + && ( pThis->enmState == DEVTPMSTATE_IDLE + || pThis->enmState == DEVTPMSTATE_CMD_COMPLETION)) + { +#ifndef IN_RING3 + rc = VINF_IOM_R3_MMIO_WRITE; + break; +#else + if (pThisCC->pDrvTpm) + { + int rc2 = pThisCC->pDrvTpm->pfnResetEstablishedFlag(pThisCC->pDrvTpm, pThis->bLoc); + if (RT_SUCCESS(rc2)) + pThis->fEstablishmentSet = false; + else + pThis->enmState = DEVTPMSTATE_FATAL_ERROR; + } + else + pThis->fEstablishmentSet = false; +#endif + } + + /* + * The following three checks should be mutually exclusive as the writer shouldn't + * request, relinquish and seize access in the same write. + */ + /* Seize access only if this locality has a higher priority than the currently selected one. */ + if ( (u64 & TPM_CRB_LOCALITY_REG_CTRL_SEIZE) + && pThis->bLoc != TPM_NO_LOCALITY_SELECTED + && bLoc > pThis->bLoc) + { + if (pThis->enmState == DEVTPMSTATE_CMD_EXEC) + { +#ifndef IN_RING3 + rc = VINF_IOM_R3_MMIO_WRITE; + break; +#else + pThis->enmState = DEVTPMSTATE_CMD_CANCEL; + if (pThisCC->pDrvTpm) + { + int rc2 = pThisCC->pDrvTpm->pfnCmdCancel(pThisCC->pDrvTpm); + if (RT_FAILURE(rc2)) + pThis->enmState = DEVTPMSTATE_FATAL_ERROR; + else + { + pThis->enmState = DEVTPMSTATE_CMD_COMPLETION; + tpmLocSetIntSts(pDevIns, pThis, pLoc, TPM_CRB_LOCALITY_REG_INT_STS_START); + } + } +#endif + } + + pThis->bmLocSeizedAcc |= RT_BIT_32(pThis->bLoc); + pThis->bLoc = bLoc; + } + + if ( (u64 & TPM_CRB_LOCALITY_REG_CTRL_REQ_ACCESS) + && !(pThis->bmLocReqAcc & RT_BIT_32(bLoc))) + { + pThis->bmLocReqAcc |= RT_BIT_32(bLoc); + if (pThis->bLoc == TPM_NO_LOCALITY_SELECTED) + { + pThis->bLoc = bLoc; /* Doesn't fire an interrupt. */ + pThis->bmLocSeizedAcc &= ~RT_BIT_32(bLoc); + } + } + + if ( (u64 & TPM_CRB_LOCALITY_REG_CTRL_RELINQUISH) + && (pThis->bmLocReqAcc & RT_BIT_32(bLoc))) + { + pThis->bmLocReqAcc &= ~RT_BIT_32(bLoc); + if (pThis->bLoc == bLoc) + { + pThis->bLoc = TPM_NO_LOCALITY_SELECTED; + if (pThis->bmLocReqAcc) + tpmLocSelectNext(pDevIns, pThis); /* Select the next locality. */ + } + } + break; + } + case TPM_CRB_LOCALITY_REG_CTRL_REQ: + if ( bLoc != pThis->bLoc + || !RT_IS_POWER_OF_TWO(u32)) /* Ignore if multiple bits are set. */ + break; + if ( (u32 & TPM_CRB_LOCALITY_REG_CTRL_REQ_CMD_RDY) + && ( pThis->enmState == DEVTPMSTATE_IDLE + || pThis->enmState == DEVTPMSTATE_CMD_COMPLETION)) + { + pThis->enmState = DEVTPMSTATE_READY; + tpmLocSetIntSts(pDevIns, pThis, pLoc, TPM_CRB_LOCALITY_REG_INT_STS_CMD_RDY); + } + else if ( (u32 & TPM_CRB_LOCALITY_REG_CTRL_REQ_IDLE) + && pThis->enmState != DEVTPMSTATE_CMD_EXEC) + { + /* Invalidate the command/response buffer. */ + RT_ZERO(pThis->abCmdResp); + pThis->offCmdResp = 0; + pThis->enmState = DEVTPMSTATE_IDLE; + } + break; + case TPM_CRB_LOCALITY_REG_CTRL_CANCEL: + if (bLoc != pThis->bLoc) + break; + if ( pThis->enmState == DEVTPMSTATE_CMD_EXEC + && u32 == 0x1) + { +#ifndef IN_RING3 + rc = VINF_IOM_R3_MMIO_WRITE; + break; +#else + pThis->enmState = DEVTPMSTATE_CMD_CANCEL; + if (pThisCC->pDrvTpm) + { + int rc2 = pThisCC->pDrvTpm->pfnCmdCancel(pThisCC->pDrvTpm); + if (RT_FAILURE(rc2)) + pThis->enmState = DEVTPMSTATE_FATAL_ERROR; + else + { + pThis->enmState = DEVTPMSTATE_CMD_COMPLETION; + tpmLocSetIntSts(pDevIns, pThis, pLoc, TPM_CRB_LOCALITY_REG_INT_STS_START); + } + } +#endif + } + break; + case TPM_CRB_LOCALITY_REG_CTRL_START: + if (bLoc != pThis->bLoc) + break; + if ( pThis->enmState == DEVTPMSTATE_CMD_RECEPTION + && u32 == 0x1) + { + pThis->enmState = DEVTPMSTATE_CMD_EXEC; + rc = PDMDevHlpTaskTrigger(pDevIns, pThis->hTpmCmdTask); + } + break; + case TPM_CRB_LOCALITY_REG_INT_ENABLE: + pLoc->uRegIntEn = u32; + tpmLocIrqUpdate(pDevIns, pThis, pLoc); + break; + case TPM_CRB_LOCALITY_REG_INT_STS: + pLoc->uRegIntSts &= ~u32; + tpmLocIrqUpdate(pDevIns, pThis, pLoc); + break; + case TPM_CRB_LOCALITY_REG_CTRL_EXT: /* Not implemented. */ + case TPM_CRB_LOCALITY_REG_STATE: /* Readonly */ + case TPM_CRB_LOCALITY_REG_INTF_ID: + case TPM_CRB_LOCALITY_REG_CTRL_STS: + case TPM_CRB_LOCALITY_REG_CTRL_CMD_LADDR: + case TPM_CRB_LOCALITY_REG_CTRL_CMD_HADDR: + case TPM_CRB_LOCALITY_REG_CTRL_CMD_SZ: + case TPM_CRB_LOCALITY_REG_CTRL_RSP_SZ: + case TPM_CRB_LOCALITY_REG_CTRL_RSP_ADDR: + default: /* Ignore. */ + break; + } + + return rc; +} + + +/* -=-=-=-=-=- MMIO callbacks -=-=-=-=-=- */ + +/** + * @callback_method_impl{FNIOMMMIONEWREAD} + */ +static DECLCALLBACK(VBOXSTRICTRC) tpmMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + RT_NOREF(pvUser); + + AssertReturn(cb <= sizeof(uint64_t), VERR_INTERNAL_ERROR); + + RTGCPHYS offAligned = off & ~UINT64_C(0x3); + uint8_t cBitsShift = (off & 0x3) * 8; + + VBOXSTRICTRC rc = VINF_SUCCESS; + uint32_t uReg = tpmGetRegisterFromOffset(offAligned); + uint8_t bLoc = tpmGetLocalityFromOffset(offAligned); + PDEVTPMLOCALITY pLoc = &pThis->aLoc[bLoc]; + + uint64_t u64; + if (pThis->fCrb) + rc = tpmMmioCrbRead(pDevIns, pThis, pLoc, bLoc, uReg, &u64, cb); + else + rc = tpmMmioFifoRead(pDevIns, pThis, pLoc, bLoc, uReg, &u64, cb); + + LogFlowFunc((": %RGp %#x %#llx\n", off, cb, u64)); + + if (rc == VINF_SUCCESS) + { + switch (cb) + { + case 1: *(uint8_t *)pv = (uint8_t)(u64 >> cBitsShift); break; + case 2: *(uint16_t *)pv = (uint16_t)(u64 >> cBitsShift); break; + case 4: *(uint32_t *)pv = (uint32_t)(u64 >> cBitsShift); break; + case 8: *(uint64_t *)pv = u64; break; + default: AssertFailedBreakStmt(rc = VERR_INTERNAL_ERROR); + } + } + + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ +static DECLCALLBACK(VBOXSTRICTRC) tpmMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + RT_NOREF(pvUser); + + Assert(!(off & (cb - 1))); + + uint64_t u64; + switch (cb) + { + case 1: u64 = *(const uint8_t *)pv; break; + case 2: u64 = *(const uint16_t *)pv; break; + case 4: u64 = *(const uint32_t *)pv; break; + case 8: u64 = *(const uint64_t *)pv; break; + default: AssertFailedReturn(VERR_INTERNAL_ERROR); + } + + LogFlowFunc((": %RGp %#llx\n", off, u64)); + + VBOXSTRICTRC rc = VINF_SUCCESS; + uint32_t uReg = tpmGetRegisterFromOffset(off); + uint8_t bLoc = tpmGetLocalityFromOffset(off); + PDEVTPMLOCALITY pLoc = &pThis->aLoc[bLoc]; + + if (pThis->fCrb) + rc = tpmMmioCrbWrite(pDevIns, pThis, pLoc, bLoc, uReg, u64, cb); + else + rc = tpmMmioFifoWrite(pDevIns, pThis, pLoc, bLoc, uReg, u64, cb); + + return rc; +} + + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNPDMTASKDEV, Execute a command in ring-3} + */ +static DECLCALLBACK(void) tpmR3CmdExecWorker(PPDMDEVINS pDevIns, void *pvUser) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PDEVTPMR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMR3); + RT_NOREF(pvUser); + LogFlowFunc(("\n")); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + if (pThisCC->pDrvTpm) + { + size_t cbCmd = RTTpmReqGetSz((PCTPMREQHDR)&pThis->abCmdResp[0]); + int rc = pThisCC->pDrvTpm->pfnCmdExec(pThisCC->pDrvTpm, pThis->bLoc, &pThis->abCmdResp[0], cbCmd, + &pThis->abCmdResp[0], sizeof(pThis->abCmdResp)); + if (RT_SUCCESS(rc)) + { + pThis->enmState = DEVTPMSTATE_CMD_COMPLETION; + pThis->offCmdResp = 0; + if (pThis->fCrb) + tpmLocSetIntSts(pThisCC->pDevIns, pThis, &pThis->aLoc[pThis->bLoc], TPM_CRB_LOCALITY_REG_INT_STS_START); + else + tpmLocSetIntSts(pThisCC->pDevIns, pThis, &pThis->aLoc[pThis->bLoc], TPM_FIFO_LOCALITY_REG_INT_STS_DATA_AVAIL | TPM_FIFO_LOCALITY_REG_INT_STS_STS_VALID); + } + else + { + /* Set fatal error. */ + pThis->enmState = DEVTPMSTATE_FATAL_ERROR; + } + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); +} + + +/** + * Resets the shared hardware TPM state. + * + * @param pThis Pointer to the shared TPM device. + */ +static void tpmR3HwReset(PDEVTPM pThis) +{ + pThis->enmState = DEVTPMSTATE_IDLE; + pThis->bLoc = TPM_NO_LOCALITY_SELECTED; + pThis->bmLocReqAcc = 0; + pThis->bmLocSeizedAcc = 0; + pThis->offCmdResp = 0; + RT_ZERO(pThis->abCmdResp); + + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aLoc); i++) + { + PDEVTPMLOCALITY pLoc = &pThis->aLoc[i]; + pLoc->uRegIntEn = 0; + pLoc->uRegIntSts = 0; + } +} + + +/* -=-=-=-=-=-=-=-=- Saved State -=-=-=-=-=-=-=-=- */ + +/** + * @callback_method_impl{FNSSMDEVLIVEEXEC} + */ +static DECLCALLBACK(int) tpmR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + RT_NOREF(uPass); + + /* Save the part of the config used for verification purposes when restoring. */ + pHlp->pfnSSMPutGCPhys(pSSM, pThis->GCPhysMmio); + pHlp->pfnSSMPutU16( pSSM, pThis->uVenId); + pHlp->pfnSSMPutU16( pSSM, pThis->uDevId); + pHlp->pfnSSMPutU8( pSSM, pThis->bRevId); + pHlp->pfnSSMPutU8( pSSM, pThis->uIrq); + pHlp->pfnSSMPutBool( pSSM, pThis->fLocChangeSup); + pHlp->pfnSSMPutU32( pSSM, (uint32_t)pThis->enmTpmVers); + pHlp->pfnSSMPutU32( pSSM, pThis->cbCmdResp); + + return VINF_SSM_DONT_CALL_AGAIN; +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) tpmR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + + tpmR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL); + + int rc = pHlp->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aTpmFields[0], NULL); + AssertRCReturn(rc, rc); + + return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */ +} + + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) tpmR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint8_t u8; + uint16_t u16; + uint32_t u32; + bool f; + RTGCPHYS GCPhysMmio; + TPMVERSION enmTpmVers; + + Assert(uPass == SSM_PASS_FINAL); RT_NOREF(uPass); + AssertMsgReturn(uVersion == TPM_SAVED_STATE_VERSION, ("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + + /* Verify the config first. */ + int rc = pHlp->pfnSSMGetGCPhys(pSSM, &GCPhysMmio); + AssertRCReturn(rc, rc); + if (GCPhysMmio != pThis->GCPhysMmio) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved GCPhysMmio=%#RGp; configured GCPhysMmio=%#RGp"), + GCPhysMmio, pThis->GCPhysMmio); + + rc = pHlp->pfnSSMGetU16(pSSM, &u16); + AssertRCReturn(rc, rc); + if (u16 != pThis->uVenId) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved uVenId=%#RX16; configured uVenId=%#RX16"), + u16, pThis->uVenId); + + rc = pHlp->pfnSSMGetU16(pSSM, &u16); + AssertRCReturn(rc, rc); + if (u16 != pThis->uDevId) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved uDevId=%#RX16; configured uDevId=%#RX16"), + u16, pThis->uDevId); + + rc = pHlp->pfnSSMGetU8(pSSM, &u8); + AssertRCReturn(rc, rc); + if (u8 != pThis->bRevId) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved bRevId=%#RX8; configured bDevId=%#RX8"), + u8, pThis->bRevId); + + rc = pHlp->pfnSSMGetU8(pSSM, &u8); + AssertRCReturn(rc, rc); + if (u8 != pThis->uIrq) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved uIrq=%#RX8; configured uIrq=%#RX8"), + u8, pThis->uIrq); + + rc = pHlp->pfnSSMGetBool(pSSM, &f); + AssertRCReturn(rc, rc); + if (f != pThis->fLocChangeSup) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved fLocChangeSup=%RTbool; configured fLocChangeSup=%RTbool"), + f, pThis->fLocChangeSup); + + rc = pHlp->pfnSSMGetU32(pSSM, (uint32_t *)&enmTpmVers); + AssertRCReturn(rc, rc); + if (enmTpmVers != pThis->enmTpmVers) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved enmTpmVers=%RU32; configured enmTpmVers=%RU32"), + enmTpmVers, pThis->enmTpmVers); + + rc = pHlp->pfnSSMGetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + if (u32 != pThis->cbCmdResp) + return pHlp->pfnSSMSetCfgError(pSSM, RT_SRC_POS, + N_("Config mismatch - saved cbCmdResp=%RU32; configured cbCmdResp=%RU32"), + u32, pThis->cbCmdResp); + + if (uPass == SSM_PASS_FINAL) + { + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aTpmFields[0], NULL); + + /* The marker. */ + rc = pHlp->pfnSSMGetU32(pSSM, &u32); + AssertRCReturn(rc, rc); + AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + /* Verify device state sanity. */ + AssertLogRelMsgReturn( pThis->enmState > DEVTPMSTATE_INVALID + && pThis->enmState <= DEVTPMSTATE_LAST_VALID, + ("Invalid TPM state loaded from saved state: %#x\n", pThis->enmState), + VERR_SSM_UNEXPECTED_DATA); + + AssertLogRelMsgReturn(pThis->offCmdResp <= pThis->cbCmdResp, + ("Invalid TPM command/response buffer offset loaded from saved state: %#x\n", pThis->offCmdResp), + VERR_SSM_UNEXPECTED_DATA); + } + + return VINF_SUCCESS; +} + + +/* -=-=-=-=-=-=-=-=- PDMIBASE -=-=-=-=-=-=-=-=- */ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) tpmR3QueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PDEVTPMCC pThisCC = RT_FROM_MEMBER(pInterface, DEVTPMCC, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + //PDMIBASE_RETURN_INTERFACE(pszIID, PDMITPMPORT, &pThisCC->ITpmPort); + return NULL; +} + + +/* -=-=-=-=-=-=-=-=- PDMDEVREG -=-=-=-=-=-=-=-=- */ + +/** + * @interface_method_impl{PDMDEVREG,pfnPowerOn} + */ +static DECLCALLBACK(void) tpmR3PowerOn(PPDMDEVINS pDevIns) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PDEVTPMCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMCC); + + if (pThisCC->pDrvTpm) + pThis->fEstablishmentSet = pThisCC->pDrvTpm->pfnGetEstablishedFlag(pThisCC->pDrvTpm); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) tpmR3Reset(PPDMDEVINS pDevIns) +{ + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PDEVTPMCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMCC); + + tpmR3HwReset(pThis); + if (pThisCC->pDrvTpm) + pThis->fEstablishmentSet = pThisCC->pDrvTpm->pfnGetEstablishedFlag(pThisCC->pDrvTpm); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) tpmR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + + /** @todo */ + RT_NOREF(pThis); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct} + */ +static DECLCALLBACK(int) tpmR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + PDEVTPMCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMCC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + + RT_NOREF(iInstance); + + pThis->hTpmCmdTask = NIL_PDMTASKHANDLE; + + pThisCC->pDevIns = pDevIns; + + /* IBase */ + pThisCC->IBase.pfnQueryInterface = tpmR3QueryInterface; + + /* + * Validate and read the configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Irq" + "|MmioBase" + "|VendorId" + "|DeviceId" + "|RevisionId" + "|Crb", + ""); + + rc = pHlp->pfnCFGMQueryU8Def(pCfg, "Irq", &pThis->uIrq, 10); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"Irq\" value")); + + rc = pHlp->pfnCFGMQueryU64Def(pCfg, "MmioBase", &pThis->GCPhysMmio, TPM_MMIO_BASE_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("Configuration error: Failed to get the \"MmioBase\" value")); + + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "VendorId", &pThis->uDevId, TPM_VID_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"VendorId\" value")); + + rc = pHlp->pfnCFGMQueryU16Def(pCfg, "DeviceId", &pThis->uDevId, TPM_DID_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"DeviceId\" value")); + + rc = pHlp->pfnCFGMQueryU8Def(pCfg, "RevisionId", &pThis->bRevId, TPM_RID_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"RevisionId\" value")); + + rc = pHlp->pfnCFGMQueryBoolDef(pCfg, "Crb", &pThis->fCrb, false); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"Crb\" value")); + + /* + * Register the MMIO range, PDM API requests page aligned + * addresses and sizes. + */ + rc = PDMDevHlpMmioCreateAndMap(pDevIns, pThis->GCPhysMmio, TPM_MMIO_SIZE, tpmMmioWrite, tpmMmioRead, + IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU, + "TPM MMIO", &pThis->hMmio); + AssertRCReturn(rc, rc); + + /* + * Attach any TPM driver below. + */ + rc = PDMDevHlpDriverAttach(pDevIns, 0 /*iLUN*/, &pThisCC->IBase, &pThisCC->pDrvBase, "TPM"); + if (RT_SUCCESS(rc)) + { + pThisCC->pDrvTpm = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMITPMCONNECTOR); + AssertLogRelMsgReturn(pThisCC->pDrvTpm, ("TPM#%d: Driver is missing the TPM interface.\n", iInstance), VERR_PDM_MISSING_INTERFACE); + + pThis->cbCmdResp = RT_MIN(pThisCC->pDrvTpm->pfnGetBufferSize(pThisCC->pDrvTpm), TPM_DATA_BUFFER_SIZE_MAX); + pThis->fLocChangeSup = pThisCC->pDrvTpm->pfnGetLocalityMax(pThisCC->pDrvTpm) > 0; + + pThis->enmTpmVers = pThisCC->pDrvTpm->pfnGetVersion(pThisCC->pDrvTpm); + if (pThis->enmTpmVers == TPMVERSION_UNKNOWN) + return PDMDEV_SET_ERROR(pDevIns, VERR_NOT_SUPPORTED, N_("The emulated TPM version is not supported")); + } + else if (rc == VERR_PDM_NO_ATTACHED_DRIVER) + { + pThis->fLocChangeSup = false; + pThis->fEstablishmentSet = false; + pThis->cbCmdResp = TPM_DATA_BUFFER_SIZE_MAX; + + pThisCC->pDrvBase = NULL; + pThisCC->pDrvTpm = NULL; + LogRel(("TPM#%d: no unit\n", iInstance)); + } + else + AssertLogRelMsgRCReturn(rc, ("TPM#%d: Failed to attach to TPM driver. rc=%Rrc\n", iInstance, rc), rc); + + /* Create task for executing requests in ring-3. */ + rc = PDMDevHlpTaskCreate(pDevIns, PDMTASK_F_RZ, "TPMCmdWrk", + tpmR3CmdExecWorker, NULL /*pvUser*/, &pThis->hTpmCmdTask); + AssertRCReturn(rc,rc); + + /* + * Saved state. + */ + rc = PDMDevHlpSSMRegister3(pDevIns, TPM_SAVED_STATE_VERSION, sizeof(*pThis), + tpmR3LiveExec, tpmR3SaveExec, tpmR3LoadExec); + AssertRCReturn(rc, rc); + + tpmR3HwReset(pThis); + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) tpmRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PDEVTPM pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPM); + + int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, tpmMmioWrite, tpmMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +/** + * The device registration structure. + */ +const PDMDEVREG g_DeviceTpm = +{ + /* .u32Version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "tpm", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_SERIAL, + /* .cMaxInstances = */ 1, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(DEVTPM), + /* .cbInstanceCC = */ sizeof(DEVTPMCC), + /* .cbInstanceRC = */ sizeof(DEVTPMRC), + /* .cMaxPciDevices = */ 0, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "Trusted Platform Module", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ tpmR3Construct, + /* .pfnDestruct = */ tpmR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ tpmR3PowerOn, + /* .pfnReset = */ tpmR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ NULL, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ NULL, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ tpmRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ tpmRZConstruct, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ + diff --git a/src/VBox/Devices/Security/DrvTpmEmu.cpp b/src/VBox/Devices/Security/DrvTpmEmu.cpp new file mode 100644 index 00000000..8cadd338 --- /dev/null +++ b/src/VBox/Devices/Security/DrvTpmEmu.cpp @@ -0,0 +1,1021 @@ +/* $Id: DrvTpmEmu.cpp $ */ +/** @file + * TPM emulator using a TCP/socket interface to talk to swtpm (https://github.com/stefanberger/swtpm). + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_TPM_EMU +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmtpmifs.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/stream.h> +#include <iprt/alloc.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/socket.h> +#include <iprt/tcp.h> +#include <iprt/uuid.h> +#include <iprt/json.h> + +#include <iprt/formats/tpm.h> + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** @name Protocol definitions to communicate with swtpm, taken from https://github.com/stefanberger/swtpm/blob/master/include/swtpm/tpm_ioctl.h + * @{ */ +/** + * Commands going over the control channel (big endian). + */ +typedef enum SWTPMCMD +{ + /** Not used. */ + SWTPMCMD_INVALID = 0, + SWTPMCMD_GET_CAPABILITY, + SWTPMCMD_INIT, + SWTPMCMD_SHUTDOWN, + SWTPMCMD_GET_TPMESTABLISHED, + SWTPMCMD_SET_LOCALITY, + SWTPMCMD_HASH_START, + SWTPMCMD_HASH_DATA, + SWTPMCMD_HASH_END, + SWTPMCMD_CANCEL_TPM_CMD, + SWTPMCMD_STORE_VOLATILE, + SWTPMCMD_RESET_TPMESTABLISHED, + SWTPMCMD_GET_STATEBLOB, + SWTPMCMD_SET_STATEBLOB, + SWTPMCMD_STOP, + SWTPMCMD_GET_CONFIG, + SWTPMCMD_SET_DATAFD, + SWTPMCMD_SET_BUFFERSIZE, + SWTPMCMD_GET_INFO, + /** Blow the enum up to a 32bit size. */ + SWTPMCMD_32BIT_HACK = 0x7fffffff +} SWTPMCMD; + + +/** + * Command/Response header. + */ +typedef union SWTPMHDR +{ + /** The command opcode. */ + SWTPMCMD enmCmd; + /** The response result. */ + uint32_t u32Resp; +} SWTPMHDR; +AssertCompileSize(SWTPMHDR, sizeof(uint32_t)); +/** Pointer to a command/response header. */ +typedef SWTPMHDR *PSWTPMHDR; +/** Pointer to a const command/response header. */ +typedef const SWTPMHDR *PCSWTPMHDR; + + +/** + * Additional command data for SWTPMCMD_INIT. + */ +typedef struct SWTPMCMDTPMINIT +{ + /** Additional flags */ + uint32_t u32Flags; +} SWTPMCMDTPMINIT; +/** Pointer to a command data struct for SWTPMCMD_INIT. */ +typedef SWTPMCMDTPMINIT *PSWTPMCMDTPMINIT; +/** Pointer to a const command data struct for SWTPMCMD_INIT. */ +typedef const SWTPMCMDTPMINIT *PCSWTPMCMDTPMINIT; + + +/** @name Capabilities as returned by SWTPMCMD_INIT. + * @{ */ +#define SWTPMCMD_INIT_F_DELETE_VOLATILE RT_BIT_32(0); +/** @} */ + + +/** + * Response data for a SWTPMCMD_GET_CAPABILITY command. + */ +typedef struct SWTPMRESPGETCAPABILITY +{ + /** The capabilities supported. */ + uint32_t u32Caps; +} SWTPMRESPGETCAPABILITY; +/** Pointer to a response data struct for SWTPMCMD_GET_CAPABILITY. */ +typedef SWTPMRESPGETCAPABILITY *PSWTPMRESPGETCAPABILITY; +/** Pointer to a const response data struct for SWTPMCMD_GET_CAPABILITY. */ +typedef const SWTPMRESPGETCAPABILITY *PCSWTPMRESPGETCAPABILITY; + + +/** @name Capabilities as returned by SWTPMCMD_GET_CAPABILITY. + * @{ */ +#define SWTPM_CAP_INIT RT_BIT_32(0) +#define SWTPM_CAP_SHUTDOWN RT_BIT_32(1) +#define SWTPM_CAP_GET_TPMESTABLISHED RT_BIT_32(2) +#define SWTPM_CAP_SET_LOCALITY RT_BIT_32(3) +#define SWTPM_CAP_HASHING RT_BIT_32(4) +#define SWTPM_CAP_CANCEL_TPM_CMD RT_BIT_32(5) +#define SWTPM_CAP_STORE_VOLATILE RT_BIT_32(6) +#define SWTPM_CAP_RESET_TPMESTABLISHED RT_BIT_32(7) +#define SWTPM_CAP_GET_STATEBLOB RT_BIT_32(8) +#define SWTPM_CAP_SET_STATEBLOB RT_BIT_32(9) +#define SWTPM_CAP_STOP RT_BIT_32(10) +#define SWTPM_CAP_GET_CONFIG RT_BIT_32(11) +#define SWTPM_CAP_SET_DATAFD RT_BIT_32(12) +#define SWTPM_CAP_SET_BUFFERSIZE RT_BIT_32(13) +#define SWTPM_CAP_GET_INFO RT_BIT_32(14) +#define SWTPM_CAP_SEND_COMMAND_HEADER RT_BIT_32(15) +/** @} */ + + +/** + * Additional command data for SWTPMCMD_SET_LOCALITY. + */ +typedef struct SWTPMCMDSETLOCALITY +{ + /** The locality to set */ + uint8_t bLoc; +} SWTPMCMDSETLOCALITY; +/** Pointer to a command data struct for SWTPMCMD_SET_LOCALITY. */ +typedef SWTPMCMDSETLOCALITY *PSWTPMCMDSETLOCALITY; +/** Pointer to a const command data struct for SWTPMCMD_SET_LOCALITY. */ +typedef const SWTPMCMDSETLOCALITY *PCSWTPMCMDSETLOCALITY; + + +/** + * Additional command data for SWTPMCMD_GET_CONFIG. + */ +typedef struct SWTPMCMDGETCONFIG +{ + /** Combination of SWTPM_GET_CONFIG_F_XXX. */ + uint64_t u64Flags; + /** The offset where to start reading from. */ + uint32_t u32Offset; + /** Some padding to a 8 byte alignment. */ + uint32_t u32Padding; +} SWTPMCMDGETCONFIG; +/** Pointer to a response data struct for SWTPMCMD_GET_CONFIG. */ +typedef SWTPMCMDGETCONFIG *PSWTPMCMDGETCONFIG; +/** Pointer to a const response data struct for SWTPMCMD_GET_CONFIG. */ +typedef const SWTPMCMDGETCONFIG *PCSWTPMCMDGETCONFIG; + + +/** @name Flags for SWTPMCMDGETCONFIG::u64Flags. + * @{ */ +/** Return the TPM specification JSON object. */ +#define SWTPM_GET_CONFIG_F_TPM_SPECIFICATION RT_BIT_64(0) +/** Return the TPM attributes JSON object. */ +#define SWTPM_GET_CONFIG_F_TPM_ATTRIBUTES RT_BIT_64(1) +/** @} */ + + +/** + * Response data for a SWTPMCMD_GET_CONFIG command. + */ +typedef struct SWTPMRESPGETCONFIG +{ + /** Total size of the object in bytes. */ + uint32_t cbTotal; + /** Size of the chunk returned in this response. */ + uint32_t cbThis; +} SWTPMRESPGETCONFIG; +/** Pointer to a response data struct for SWTPMCMD_GET_CONFIG. */ +typedef SWTPMRESPGETCONFIG *PSWTPMRESPGETCONFIG; +/** Pointer to a const response data struct for SWTPMCMD_GET_CONFIG. */ +typedef const SWTPMRESPGETCONFIG *PCSWTPMRESPGETCONFIG; + + +/** + * Response data for a SWTPMCMD_GET_TPMESTABLISHED command. + */ +typedef struct SWTPMRESPGETTPMEST +{ + /** Flag whether the TPM established bit is set for the TPM. */ + uint8_t fEst; +} SWTPMRESPGETTPMEST; +/** Pointer to a response data struct for SWTPMCMD_GET_TPMESTABLISHED. */ +typedef SWTPMRESPGETTPMEST *PSWTPMRESPGETTPMEST; +/** Pointer to a const response data struct for SWTPMCMD_GET_TPMESTABLISHED. */ +typedef const SWTPMRESPGETTPMEST *PCSWTPMRESPGETTPMEST; + + +/** + * Additional command data for SWTPMCMD_RESET_TPMESTABLISHED. + */ +typedef struct SWTPMCMDRSTEST +{ + /** The locality resetting trying to reset the established bit. */ + uint8_t bLoc; +} SWTPMCMDRSTEST; +/** Pointer to a response data struct for SWTPMCMD_GET_TPMESTABLISHED. */ +typedef SWTPMCMDRSTEST *PSWTPMCMDRSTEST; +/** Pointer to a const response data struct for SWTPMCMD_GET_TPMESTABLISHED. */ +typedef const SWTPMCMDRSTEST *PCSWTPMCMDRSTEST; + + +/** + * Additional command data for SWTPMCMD_SET_BUFFERSIZE. + */ +typedef struct SWTPMCMDSETBUFSZ +{ + /** The buffer size to set, 0 to query for the currently used buffer size. */ + uint32_t cbBuffer; +} SWTPMCMDSETBUFSZ; +/** Pointer to a command data struct for SWTPMCMD_SET_BUFFERSIZE. */ +typedef SWTPMCMDSETBUFSZ *PSWTPMCMDSETBUFSZ; +/** Pointer to a const command data struct for SWTPMCMD_SET_BUFFERSIZE. */ +typedef const SWTPMCMDSETBUFSZ *PCSWTPMCMDSETBUFSZ; + + +/** + * Response data for a SWTPMCMD_SET_BUFFERSIZE command. + */ +typedef struct SWTPMRESPSETBUFSZ +{ + /** Buffer size in use. */ + uint32_t cbBuffer; + /** Minimum supported buffer size. */ + uint32_t cbBufferMin; + /** Maximum supported buffer size. */ + uint32_t cbBufferMax; +} SWTPMRESPSETBUFSZ; +/** Pointer to a response data struct for SWTPMCMD_SET_BUFFERSIZE. */ +typedef SWTPMRESPSETBUFSZ *PSWTPMRESPSETBUFSZ; +/** Pointer to a const response data struct for SWTPMCMD_SET_BUFFERSIZE. */ +typedef const SWTPMRESPSETBUFSZ *PCSWTPMRESPSETBUFSZ; + + +/** + * TPM emulator driver instance data. + * + * @implements PDMITPMCONNECTOR + */ +typedef struct DRVTPMEMU +{ + /** The stream interface. */ + PDMITPMCONNECTOR ITpmConnector; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + + /** Socket handle for the control connection. */ + RTSOCKET hSockCtrl; + /** Socket handle for the data connection. */ + RTSOCKET hSockData; + + /** Currently set locality. */ + uint8_t bLoc; + + /** TPM version offered by the emulator. */ + TPMVERSION enmTpmVers; + /** Capabilities offered by the TPM emulator. */ + uint32_t fCaps; + /** Buffer size for the emulated TPM. */ + uint32_t cbBuffer; +} DRVTPMEMU; +/** Pointer to the TPM emulator instance data. */ +typedef DRVTPMEMU *PDRVTPMEMU; + +/** The special no current locality selected value. */ +#define TPM_NO_LOCALITY_SELECTED 0xff + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Executes the given command over the control connection to the TPM emulator. + * + * @returns VBox status code. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param enmCmd The command to execute. + * @param pvCmd Additional command data to send. + * @param cbCmd Size of the additional command data in bytes. + * @param pu32Resp Where to store the response code from the reply. + * @param pvResp Where to store additional resposne data. + * @param cbResp Size of the Response data in bytes (excluding the response status code which is implicit). + * @param cMillies Number of milliseconds to wait before aborting the command with a timeout error. + * + * @note This method can return success even though the request at such failed, check the content of pu32Resp! + */ +static int drvTpmEmuExecCtrlCmdEx(PDRVTPMEMU pThis, SWTPMCMD enmCmd, const void *pvCmd, size_t cbCmd, uint32_t *pu32Resp, + void *pvResp, size_t cbResp, RTMSINTERVAL cMillies) +{ + SWTPMHDR Hdr; + RTSGBUF SgBuf; + RTSGSEG aSegs[2]; + uint32_t cSegs = 1; + + Hdr.enmCmd = (SWTPMCMD)RT_H2BE_U32(enmCmd); + aSegs[0].pvSeg = &Hdr; + aSegs[0].cbSeg = sizeof(Hdr); + if (cbCmd) + { + cSegs++; + aSegs[1].pvSeg = (void *)pvCmd; + aSegs[1].cbSeg = cbCmd; + } + + RTSgBufInit(&SgBuf, &aSegs[0], cSegs); + int rc = RTSocketSgWrite(pThis->hSockCtrl, &SgBuf); + if (RT_SUCCESS(rc)) + { + rc = RTSocketSelectOne(pThis->hSockCtrl, cMillies); + if (RT_SUCCESS(rc)) + { + uint32_t u32Resp = 0; + rc = RTSocketRead(pThis->hSockCtrl, &u32Resp, sizeof(u32Resp), NULL /*pcbRead*/); + if (RT_SUCCESS(rc)) + { + *pu32Resp = RT_BE2H_U32(u32Resp); + if (*pu32Resp == 0) + { + if (cbResp) + rc = RTSocketRead(pThis->hSockCtrl, pvResp, cbResp, NULL /*pcbRead*/); + } + else + rc = VERR_NET_IO_ERROR; + } + } + } + + return rc; +} + + +/** + * Continue receiving a response from a previous call of drvTpmEmuExecCtrlCmdEx() or + * drvTpmEmuExecCtrlCmdNoPayload(). + * + * @param pThis Pointer to the TPM emulator driver instance data. + * @param enmCmd The command to execute. + * @param pvResp Where to store additional resposne data. + * @param cbResp Size of the additional response data in bytes. + * @param cMillies Number of milliseconds to wait before aborting the receive with a timeout error. + */ +static int drvTpmEmuExecCtrlCmdRespCont(PDRVTPMEMU pThis, void *pvResp, size_t cbResp, RTMSINTERVAL cMillies) +{ + int rc = RTSocketSelectOne(pThis->hSockCtrl, cMillies); + if (RT_SUCCESS(rc)) + rc = RTSocketRead(pThis->hSockCtrl, pvResp, cbResp, NULL /*pcbRead*/); + + return rc; +} + + +/** + * Executes the given command over the control connection to the TPM emulator - variant with no command payload. + * + * @returns VBox status code. + * @retval VERR_NET_IO_ERROR if the executed command returned an error in the response status field. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param enmCmd The command to execute. + * @param pvResp Where to store additional resposne data. + * @param cbResp Size of the Response data in bytes (excluding the response status code which is implicit). + * @param cMillies Number of milliseconds to wait before aborting the command with a timeout error. + */ +static int drvTpmEmuExecCtrlCmdNoPayload(PDRVTPMEMU pThis, SWTPMCMD enmCmd, void *pvResp, size_t cbResp, RTMSINTERVAL cMillies) +{ + uint32_t u32Resp = 0; + int rc = drvTpmEmuExecCtrlCmdEx(pThis, enmCmd, NULL /*pvCmd*/, 0 /*cbCmd*/, &u32Resp, + pvResp, cbResp, cMillies); + if (RT_SUCCESS(rc)) + { + if (u32Resp != 0) + rc = VERR_NET_IO_ERROR; + } + + return rc; +} + + +/** + * Executes the given command over the control connection to the TPM emulator - variant with no response payload other than the result. + * + * @returns VBox status code. + * @retval VERR_NET_IO_ERROR if the executed command returned an error in the response status field. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param enmCmd The command to execute. + * @param pvCmd Additional command data to send. + * @param cbCmd Size of the additional command data in bytes. + * @param pu32Resp Where to store the response code from the reply. + * @param cMillies Number of milliseconds to wait before aborting the command with a timeout error. + */ +static int drvTpmEmuExecCtrlCmdNoResp(PDRVTPMEMU pThis, SWTPMCMD enmCmd, const void *pvCmd, size_t cbCmd, uint32_t *pu32Resp, + RTMSINTERVAL cMillies) +{ + return drvTpmEmuExecCtrlCmdEx(pThis, enmCmd, pvCmd, cbCmd, pu32Resp, + NULL /*pvResp*/, 0 /*cbResp*/, cMillies); +} + + +/** + * Executes the given command over the control connection to the TPM emulator - variant with no command and response payload. + * + * @returns VBox status code. + * @retval VERR_NET_IO_ERROR if the executed command returned an error in the response status field. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param enmCmd The command to execute. + * @param cMillies Number of milliseconds to wait before aborting the command with a timeout error. + */ +static int drvTpmEmuExecCtrlCmdNoPayloadAndResp(PDRVTPMEMU pThis, SWTPMCMD enmCmd, RTMSINTERVAL cMillies) +{ + uint32_t u32Resp = 0; + int rc = drvTpmEmuExecCtrlCmdEx(pThis, enmCmd, NULL /*pvCmd*/, 0 /*cbCmd*/, &u32Resp, + NULL /*pvResp*/, 0 /*cbResp*/, cMillies); + if (RT_SUCCESS(rc)) + { + if (u32Resp != 0) + rc = VERR_NET_IO_ERROR; + } + + return rc; +} + + +/** + * Queries the version of the TPM offered by the remote emulator. + * + * @returns VBox status code. + * @param pThis Pointer to the TPM emulator driver instance data. + */ +static int drvTpmEmuQueryTpmVersion(PDRVTPMEMU pThis) +{ + SWTPMCMDGETCONFIG Cmd; + SWTPMRESPGETCONFIG Resp; + uint8_t abData[_4K]; + uint32_t u32Resp = 0; + + RT_ZERO(Cmd); RT_ZERO(Resp); + Cmd.u64Flags = RT_H2BE_U64(SWTPM_GET_CONFIG_F_TPM_SPECIFICATION); + Cmd.u32Offset = 0; + int rc = drvTpmEmuExecCtrlCmdEx(pThis, SWTPMCMD_GET_INFO, &Cmd, sizeof(Cmd), &u32Resp, + &Resp, sizeof(Resp), RT_MS_10SEC); + if (RT_SUCCESS(rc)) + { + /* + * Currently it is not necessary to get the information in chunks, a single + * transaction is enough. To fend off future versions of swtpm requiring this + * we return an error here if the total length is not equal to the length of the chunk. + */ + if (RT_BE2H_U32(Resp.cbTotal) == RT_BE2H_U32(Resp.cbThis)) + { + /* Fetch the response body. */ + rc = drvTpmEmuExecCtrlCmdRespCont(pThis, &abData[0], RT_BE2H_U32(Resp.cbThis), RT_MS_10SEC); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonVal = NIL_RTJSONVAL; + rc = RTJsonParseFromBuf(&hJsonVal, &abData[0], RT_BE2H_U32(Resp.cbThis), NULL /*pErrInfo*/); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonTpmSpec = NIL_RTJSONVAL; + rc = RTJsonValueQueryByName(hJsonVal, "TPMSpecification", &hJsonTpmSpec); + if (RT_SUCCESS(rc)) + { + RTJSONVAL hJsonTpmFam = NIL_RTJSONVAL; + rc = RTJsonValueQueryByName(hJsonTpmSpec, "family", &hJsonTpmFam); + if (RT_SUCCESS(rc)) + { + const char *pszFam = NULL; + rc = RTJsonValueQueryString(hJsonTpmFam, &pszFam); + if (RT_SUCCESS(rc)) + { + if (!RTStrCmp(pszFam, "1.2")) + pThis->enmTpmVers = TPMVERSION_1_2; + else if (!RTStrCmp(pszFam, "2.0")) + pThis->enmTpmVers = TPMVERSION_2_0; + else + pThis->enmTpmVers = TPMVERSION_UNKNOWN; + } + + RTJsonValueRelease(hJsonTpmFam); + } + + RTJsonValueRelease(hJsonTpmSpec); + } + + RTJsonValueRelease(hJsonVal); + } + } + } + else + rc = VERR_NOT_SUPPORTED; + } + + return rc; +} + + +/** + * Queries the capabilities of the remote TPM emulator and verifies that + * it offers everything we require for operation. + * + * @returns VBox status code. + * @param pThis Pointer to the TPM emulator driver instance data. + */ +static int drvTpmEmuQueryCaps(PDRVTPMEMU pThis) +{ + SWTPMRESPGETCAPABILITY Resp; + int rc = drvTpmEmuExecCtrlCmdNoPayload(pThis, SWTPMCMD_GET_CAPABILITY, &Resp, sizeof(Resp), RT_MS_10SEC); + if (RT_SUCCESS(rc)) + pThis->fCaps = RT_BE2H_U32(Resp.u32Caps); + + return rc; +} + + +/** + * Queries the maximum supported buffer size by the emulation. + * + * @returns VBox status code. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param pcbBufferMax Where to store the maximum supported buffer size on success. + */ +static int drvTpmEmuQueryBufferSzMax(PDRVTPMEMU pThis, uint32_t *pcbBufferMax) +{ + SWTPMCMDSETBUFSZ Cmd; + SWTPMRESPSETBUFSZ Resp; + uint32_t u32Resp = 0; + + RT_ZERO(Cmd); RT_ZERO(Resp); + Cmd.cbBuffer = RT_H2BE_U32(0); + int rc = drvTpmEmuExecCtrlCmdEx(pThis, SWTPMCMD_SET_BUFFERSIZE, &Cmd, sizeof(Cmd), &u32Resp, + &Resp, sizeof(Resp), RT_MS_10SEC); + if (RT_SUCCESS(rc)) + { + if (u32Resp == 0) + *pcbBufferMax = RT_BE2H_U32(Resp.cbBufferMax); + else + rc = VERR_NET_IO_ERROR; + } + + return rc; +} + + +/** + * Queries the maximum supported buffer size by the emulation. + * + * @returns VBox status code. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param cbBuffer The buffer size to set. + */ +static int drvTpmEmuSetBufferSz(PDRVTPMEMU pThis, uint32_t cbBuffer) +{ + SWTPMCMDSETBUFSZ Cmd; + SWTPMRESPSETBUFSZ Resp; + uint32_t u32Resp = 0; + + RT_ZERO(Cmd); RT_ZERO(Resp); + Cmd.cbBuffer = RT_H2BE_U32(cbBuffer); + int rc = drvTpmEmuExecCtrlCmdEx(pThis, SWTPMCMD_SET_BUFFERSIZE, &Cmd, sizeof(Cmd), &u32Resp, + &Resp, sizeof(Resp), RT_MS_10SEC); + if ( RT_SUCCESS(rc) + && u32Resp != 0) + rc = VERR_NET_IO_ERROR; + + return rc; +} + + +/** + * Sets the given locality for the emulated TPM. + * + * @returns VBox status code. + * @param pThis Pointer to the TPM emulator driver instance data. + * @param bLoc The locality to set. + */ +static int drvTpmEmuSetLocality(PDRVTPMEMU pThis, uint8_t bLoc) +{ + SWTPMCMDSETLOCALITY Cmd; + uint32_t u32Resp = 0; + + Cmd.bLoc = bLoc; + int rc = drvTpmEmuExecCtrlCmdNoResp(pThis, SWTPMCMD_SET_LOCALITY, &Cmd, sizeof(Cmd), &u32Resp, RT_MS_10SEC); + if ( RT_SUCCESS(rc) + && u32Resp != 0) + rc = VERR_NET_IO_ERROR; + + if (RT_SUCCESS(rc)) + pThis->bLoc = bLoc; + + return rc; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetVersion} */ +static DECLCALLBACK(TPMVERSION) drvTpmEmuGetVersion(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + return pThis->enmTpmVers; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetLocalityMax} */ +static DECLCALLBACK(uint32_t) drvTpmEmuGetLocalityMax(PPDMITPMCONNECTOR pInterface) +{ + RT_NOREF(pInterface); + return 4; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetBufferSize} */ +static DECLCALLBACK(uint32_t) drvTpmEmuGetBufferSize(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + return pThis->cbBuffer; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetEstablishedFlag} */ +static DECLCALLBACK(bool) drvTpmEmuGetEstablishedFlag(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + + SWTPMRESPGETTPMEST Resp; + int rc = drvTpmEmuExecCtrlCmdNoPayload(pThis, SWTPMCMD_GET_TPMESTABLISHED, &Resp, sizeof(Resp), RT_MS_10SEC); + if (RT_SUCCESS(rc) + && Resp.fEst != 0) + return true; + + return false; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnResetEstablishedFlag} */ +static DECLCALLBACK(int) drvTpmEmuResetEstablishedFlag(PPDMITPMCONNECTOR pInterface, uint8_t bLoc) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + + SWTPMCMDRSTEST Cmd; + uint32_t u32Resp = 0; + + Cmd.bLoc = bLoc; + int rc = drvTpmEmuExecCtrlCmdNoResp(pThis, SWTPMCMD_RESET_TPMESTABLISHED, &Cmd, sizeof(Cmd), &u32Resp, RT_MS_10SEC); + if ( RT_SUCCESS(rc) + && u32Resp != 0) + rc = VERR_NET_IO_ERROR; + + return rc; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnCmdExec} */ +static DECLCALLBACK(int) drvTpmEmuCmdExec(PPDMITPMCONNECTOR pInterface, uint8_t bLoc, const void *pvCmd, size_t cbCmd, void *pvResp, size_t cbResp) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + + int rc = VINF_SUCCESS; + if (pThis->bLoc != bLoc) + rc = drvTpmEmuSetLocality(pThis, bLoc); + + if (RT_SUCCESS(rc)) + { + rc = RTSocketWrite(pThis->hSockData, pvCmd, cbCmd); + if (RT_SUCCESS(rc)) + { + rc = RTSocketSelectOne(pThis->hSockData, RT_MS_10SEC); + if (RT_SUCCESS(rc)) + { + /* Read the response header in first. */ + TPMRESPHDR RespHdr; + rc = RTSocketRead(pThis->hSockData, &RespHdr, sizeof(RespHdr), NULL /*pcbRead*/); + if (RT_SUCCESS(rc)) + { + size_t cbHdrResp = RTTpmRespGetSz(&RespHdr); + if (cbHdrResp <= cbResp - sizeof(RespHdr)) + { + memcpy(pvResp, &RespHdr, sizeof(RespHdr)); + + if (cbHdrResp > sizeof(RespHdr)) + rc = RTSocketRead(pThis->hSockData, (uint8_t *)pvResp + sizeof(RespHdr), cbHdrResp - sizeof(RespHdr), + NULL /*pcbRead*/); + } + else + rc = VERR_BUFFER_OVERFLOW; + } + } + } + } + + return rc; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnCmdCancel} */ +static DECLCALLBACK(int) drvTpmEmuCmdCancel(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + + return drvTpmEmuExecCtrlCmdNoPayloadAndResp(pThis, SWTPMCMD_CANCEL_TPM_CMD, RT_MS_10SEC); +} + + +/** @interface_method_impl{PDMIBASE,pfnQueryInterface} */ +static DECLCALLBACK(void *) drvTpmEmuQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMITPMCONNECTOR, &pThis->ITpmConnector); + return NULL; +} + + +/* -=-=-=-=- PDMDRVREG -=-=-=-=- */ + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOn} + */ +static DECLCALLBACK(void) drvTpmEmuPowerOn(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + + SWTPMCMDTPMINIT Cmd; + uint32_t u32Resp = 0; + + RT_ZERO(Cmd); + Cmd.u32Flags = 0; + int rc = drvTpmEmuExecCtrlCmdEx(pThis, SWTPMCMD_INIT, &Cmd, sizeof(Cmd), &u32Resp, + NULL, 0, RT_MS_10SEC); + if (RT_FAILURE(rc)) + PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, "Failed to startup the TPM with %Rrc", rc); +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) drvTpmEmuPowerOff(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + + int rc = drvTpmEmuExecCtrlCmdNoPayload(pThis, SWTPMCMD_SHUTDOWN, NULL, 0, RT_MS_10SEC); + if (RT_FAILURE(rc)) + PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, "Failed to shutdown the TPM with %Rrc", rc); +} + + +/** @copydoc FNPDMDRVDESTRUCT */ +static DECLCALLBACK(void) drvTpmEmuDestruct(PPDMDRVINS pDrvIns) +{ + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + LogFlow(("%s\n", __FUNCTION__)); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + if (pThis->hSockCtrl != NIL_RTSOCKET) + { + int rc = RTSocketShutdown(pThis->hSockCtrl, true /* fRead */, true /* fWrite */); + AssertRC(rc); + + rc = RTSocketClose(pThis->hSockCtrl); + AssertRC(rc); RT_NOREF(rc); + + pThis->hSockCtrl = NIL_RTSOCKET; + } + + if (pThis->hSockData != NIL_RTSOCKET) + { + int rc = RTSocketShutdown(pThis->hSockData, true /* fRead */, true /* fWrite */); + AssertRC(rc); + + rc = RTSocketClose(pThis->hSockData); + AssertRC(rc); RT_NOREF(rc); + + pThis->hSockCtrl = NIL_RTSOCKET; + } +} + + +/** @copydoc FNPDMDRVCONSTRUCT */ +static DECLCALLBACK(int) drvTpmEmuConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + pThis->hSockCtrl = NIL_RTSOCKET; + pThis->hSockData = NIL_RTSOCKET; + pThis->enmTpmVers = TPMVERSION_UNKNOWN; + pThis->bLoc = TPM_NO_LOCALITY_SELECTED; + + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvTpmEmuQueryInterface; + /* ITpmConnector */ + pThis->ITpmConnector.pfnGetVersion = drvTpmEmuGetVersion; + pThis->ITpmConnector.pfnGetLocalityMax = drvTpmEmuGetLocalityMax; + pThis->ITpmConnector.pfnGetBufferSize = drvTpmEmuGetBufferSize; + pThis->ITpmConnector.pfnGetEstablishedFlag = drvTpmEmuGetEstablishedFlag; + pThis->ITpmConnector.pfnResetEstablishedFlag = drvTpmEmuResetEstablishedFlag; + pThis->ITpmConnector.pfnCmdExec = drvTpmEmuCmdExec; + pThis->ITpmConnector.pfnCmdCancel = drvTpmEmuCmdCancel; + + /* + * Validate and read the configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Location|BufferSize", ""); + + char szLocation[_1K]; + int rc = pHlp->pfnCFGMQueryString(pCfg, "Location", &szLocation[0], sizeof(szLocation)); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: querying \"Location\" resulted in %Rrc"), rc); + + /* + * Create/Open the socket. + */ + char *pszPort = strchr(szLocation, ':'); + if (!pszPort) + return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_FOUND, RT_SRC_POS, + N_("DrvTpmEmu#%d: The location misses the port to connect to"), + pDrvIns->iInstance); + + *pszPort = '\0'; /* Overwrite temporarily to avoid copying the hostname into a temporary buffer. */ + uint32_t uPort = 0; + rc = RTStrToUInt32Ex(pszPort + 1, NULL, 10, &uPort); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d: The port part of the location is not a numerical value"), + pDrvIns->iInstance); + + rc = RTTcpClientConnect(szLocation, uPort, &pThis->hSockCtrl); + *pszPort = ':'; /* Restore delimiter before checking the status. */ + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d failed to connect to control socket %s"), + pDrvIns->iInstance, szLocation); + + rc = drvTpmEmuQueryCaps(pThis); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d failed to query capabilities offered by %s"), + pDrvIns->iInstance, szLocation); + + if (!(pThis->fCaps & SWTPM_CAP_GET_CONFIG)) + return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_SUPPORTED, RT_SRC_POS, + N_("DrvTpmEmu#%d Emulated TPM at '%s' misses the GET_CONFIG capability"), + pDrvIns->iInstance, szLocation); + + rc = drvTpmEmuQueryTpmVersion(pThis); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d failed to query TPM version from %s"), + pDrvIns->iInstance, szLocation); + + if (pThis->enmTpmVers == TPMVERSION_UNKNOWN) + return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_SUPPORTED, RT_SRC_POS, + N_("DrvTpmEmu#%d Emulated TPM version of %s is not supported"), + pDrvIns->iInstance, szLocation); + + const char *pszTpmVers = NULL; + uint32_t fCapsReq = SWTPM_CAP_INIT | SWTPM_CAP_SHUTDOWN | SWTPM_CAP_GET_TPMESTABLISHED + | SWTPM_CAP_SET_LOCALITY | SWTPM_CAP_CANCEL_TPM_CMD | SWTPM_CAP_GET_STATEBLOB + | SWTPM_CAP_SET_STATEBLOB | SWTPM_CAP_STOP | SWTPM_CAP_SET_BUFFERSIZE; + switch (pThis->enmTpmVers) + { + case TPMVERSION_1_2: + /* No additional capabilities needed. */ + pszTpmVers = "1.2"; + break; + case TPMVERSION_2_0: + fCapsReq |= SWTPM_CAP_RESET_TPMESTABLISHED; + pszTpmVers = "2.0"; + break; + default: + AssertMsgFailedReturn(("DrvTpmEmu#%d Emulated TPM version %d is not correctly handled", pDrvIns->iInstance, pThis->enmTpmVers), + VERR_INVALID_STATE); + } + + if ((pThis->fCaps & fCapsReq) != fCapsReq) + return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_SUPPORTED, RT_SRC_POS, + N_("DrvTpmEmu#%d Emulated TPM version of %s does not offer required set of capabilities (%#x requested vs. %#x offered)"), + pDrvIns->iInstance, szLocation, fCapsReq, pThis->fCaps); + + uint32_t cbBufferMax = 0; + rc = drvTpmEmuQueryBufferSzMax(pThis, &cbBufferMax); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d failed to query maximum buffer size from %s"), + pDrvIns->iInstance, szLocation); + + /* Configure the buffer size. */ + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "BufferSize", &pThis->cbBuffer, cbBufferMax); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: querying \"BufferSize\" resulted in %Rrc"), rc); + + /* Set the buffer size. */ + rc = drvTpmEmuSetBufferSz(pThis, pThis->cbBuffer); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d failed to set buffer size to %u for %s"), + pDrvIns->iInstance, pThis->cbBuffer, szLocation); + + /* Connect the data channel now. */ + /** @todo Allow configuring a different port. */ + *pszPort = '\0'; /* Overwrite temporarily to avoid copying the hostname into a temporary buffer. */ + rc = RTTcpClientConnect(szLocation, uPort + 1, &pThis->hSockData); + *pszPort = ':'; /* Restore delimiter before checking the status. */ + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmEmu#%d failed to connect to data socket %s"), + pDrvIns->iInstance, szLocation); + + LogRel(("DrvTpmEmu#%d: Connected to %s, emulating TPM version %s\n", pDrvIns->iInstance, szLocation, pszTpmVers)); + return VINF_SUCCESS; +} + + +/** + * TPM emulator driver registration record. + */ +const PDMDRVREG g_DrvTpmEmu = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "TpmEmu", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "TPM emulator driver.", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_STREAM, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVTPMEMU), + /* pfnConstruct */ + drvTpmEmuConstruct, + /* pfnDestruct */ + drvTpmEmuDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + drvTpmEmuPowerOn, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + drvTpmEmuPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Security/DrvTpmEmuTpms.cpp b/src/VBox/Devices/Security/DrvTpmEmuTpms.cpp new file mode 100644 index 00000000..2059c780 --- /dev/null +++ b/src/VBox/Devices/Security/DrvTpmEmuTpms.cpp @@ -0,0 +1,534 @@ +/* $Id: DrvTpmEmuTpms.cpp $ */ +/** @file + * TPM emulation driver based on libtpms. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_TPM_EMU +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmtpmifs.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/uuid.h> +#include <iprt/vfs.h> +#include <iprt/zip.h> + +#include <libtpms/tpm_library.h> +#include <libtpms/tpm_error.h> +#include <libtpms/tpm_tis.h> +#include <libtpms/tpm_nvfilename.h> + +#include <iprt/formats/tpm.h> + +#if 0 +#include <unistd.h> +#endif +#include <stdlib.h> + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * TPM emulation driver instance data. + * + * @implements PDMITPMCONNECTOR + */ +typedef struct DRVTPMEMU +{ + /** The stream interface. */ + PDMITPMCONNECTOR ITpmConnector; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** The VFS interface of the driver below for NVRAM/TPM state loading and storing. */ + PPDMIVFSCONNECTOR pDrvVfs; + + /** The TPM version we are emulating. */ + TPMVERSION enmVersion; + /** The buffer size the TPM advertises. */ + uint32_t cbBuffer; + /** Currently set locality. */ + uint8_t bLoc; +} DRVTPMEMU; +/** Pointer to the TPM emulator instance data. */ +typedef DRVTPMEMU *PDRVTPMEMU; + +/** The special no current locality selected value. */ +#define TPM_NO_LOCALITY_SELECTED 0xff + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Pointer to the (only) instance data in this driver. */ +static PDRVTPMEMU g_pDrvTpmEmuTpms = NULL; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/* -=-=-=-=- PDMITPMCONNECTOR interface callabcks. -=-=-=-=- */ + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetVersion} */ +static DECLCALLBACK(TPMVERSION) drvTpmEmuTpmsGetVersion(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + return pThis->enmVersion; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetLocalityMax} */ +static DECLCALLBACK(uint32_t) drvTpmEmuGetLocalityMax(PPDMITPMCONNECTOR pInterface) +{ + RT_NOREF(pInterface); + return 4; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetBufferSize} */ +static DECLCALLBACK(uint32_t) drvTpmEmuGetBufferSize(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + return pThis->cbBuffer; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetEstablishedFlag} */ +static DECLCALLBACK(bool) drvTpmEmuTpmsGetEstablishedFlag(PPDMITPMCONNECTOR pInterface) +{ + RT_NOREF(pInterface); + + TPM_BOOL fTpmEst = FALSE; + TPM_RESULT rcTpm = TPM_IO_TpmEstablished_Get(&fTpmEst); + if (RT_LIKELY(rcTpm == TPM_SUCCESS)) + return RT_BOOL(fTpmEst); + + return false; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnResetEstablishedFlag} */ +static DECLCALLBACK(int) drvTpmEmuTpmsResetEstablishedFlag(PPDMITPMCONNECTOR pInterface, uint8_t bLoc) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + uint8_t bLocOld = pThis->bLoc; + + pThis->bLoc = bLoc; + TPM_RESULT rcTpm = TPM_IO_TpmEstablished_Reset(); + pThis->bLoc = bLocOld; + + if (RT_LIKELY(rcTpm == TPM_SUCCESS)) + return VINF_SUCCESS; + + LogRelMax(10, ("DrvTpmEmuTpms#%u: Failed to reset the established flag with %#x\n", + pThis->pDrvIns->iInstance, rcTpm)); + return VERR_DEV_IO_ERROR; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnCmdExec} */ +static DECLCALLBACK(int) drvTpmEmuTpmsCmdExec(PPDMITPMCONNECTOR pInterface, uint8_t bLoc, const void *pvCmd, size_t cbCmd, void *pvResp, size_t cbResp) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + + pThis->bLoc = bLoc; + + uint8_t *pbRespBuf = NULL; + uint32_t cbRespBuf = 0; + uint32_t cbRespActual = 0; + TPM_RESULT rcTpm = TPMLIB_Process(&pbRespBuf, &cbRespActual, &cbRespBuf, (uint8_t *)pvCmd, (uint32_t)cbCmd); + if (RT_LIKELY(rcTpm == TPM_SUCCESS)) + { + memcpy(pvResp, pbRespBuf, RT_MIN(cbResp, cbRespActual)); + free(pbRespBuf); + return VINF_SUCCESS; + } + + LogRelMax(10, ("DrvTpmEmuTpms#%u: Failed to execute command with %#x\n", + pThis->pDrvIns->iInstance, rcTpm)); + return VERR_DEV_IO_ERROR; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnCmdCancel} */ +static DECLCALLBACK(int) drvTpmEmuTpmsCmdCancel(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMEMU pThis = RT_FROM_MEMBER(pInterface, DRVTPMEMU, ITpmConnector); + + TPM_RESULT rcTpm = TPMLIB_CancelCommand(); + if (RT_LIKELY(rcTpm == TPM_SUCCESS)) + return VINF_SUCCESS; + + LogRelMax(10, ("DrvTpmEmuTpms#%u: Failed to cancel outstanding command with %#x\n", + pThis->pDrvIns->iInstance, rcTpm)); + return VERR_DEV_IO_ERROR; +} + + +/** @interface_method_impl{PDMIBASE,pfnQueryInterface} */ +static DECLCALLBACK(void *) drvTpmEmuTpmsQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMITPMCONNECTOR, &pThis->ITpmConnector); + return NULL; +} + + +/* -=-=-=-=- libtpms_callbacks -=-=-=-=- */ + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkNvRamInit(void) +{ + PDRVTPMEMU pThis = g_pDrvTpmEmuTpms; + RT_NOREF(pThis); + + return TPM_SUCCESS; +} + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkNvRamLoadData(uint8_t **ppvData, uint32_t *pcbLength, + uint32_t idTpm, const char *pszName) +{ + PDRVTPMEMU pThis = g_pDrvTpmEmuTpms; + + AssertReturn(idTpm == 0, TPM_FAIL); + + uint64_t cbState = 0; + int rc = pThis->pDrvVfs->pfnQuerySize(pThis->pDrvVfs, pThis->pDrvIns->pReg->szName, pszName, &cbState); + if ( RT_SUCCESS(rc) + && cbState == (uint32_t)cbState) + { + void *pvData = malloc(cbState); + if (RT_LIKELY(pvData)) + { + rc = pThis->pDrvVfs->pfnReadAll(pThis->pDrvVfs, pThis->pDrvIns->pReg->szName, pszName, + pvData, cbState); + if (RT_SUCCESS(rc)) + { + *ppvData = (uint8_t *)pvData; + *pcbLength = (uint32_t)cbState; + return VINF_SUCCESS; + } + + free(pvData); + } + } + else if (rc == VERR_NOT_FOUND) + return TPM_RETRY; /* This is fine for the first start of a new VM. */ + + return TPM_FAIL; +} + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkNvRamStoreData(const uint8_t *pvData, uint32_t cbLength, + uint32_t idTpm, const char *pszName) +{ + PDRVTPMEMU pThis = g_pDrvTpmEmuTpms; + + AssertReturn(idTpm == 0, TPM_FAIL); + + int rc = pThis->pDrvVfs->pfnWriteAll(pThis->pDrvVfs, pThis->pDrvIns->pReg->szName, pszName, + pvData, cbLength); + if (RT_SUCCESS(rc)) + return TPM_SUCCESS; + + return TPM_FAIL; +} + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkNvRamDeleteName(uint32_t idTpm, const char *pszName, TPM_BOOL fMustExist) +{ + PDRVTPMEMU pThis = g_pDrvTpmEmuTpms; + + AssertReturn(idTpm == 0, TPM_FAIL); + + int rc = pThis->pDrvVfs->pfnDelete(pThis->pDrvVfs, pThis->pDrvIns->pReg->szName, pszName); + if ( RT_SUCCESS(rc) + || ( rc == VERR_NOT_FOUND + && !fMustExist)) + return TPM_SUCCESS; + + return TPM_FAIL; +} + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkIoInit(void) +{ + return TPM_SUCCESS; +} + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkIoGetLocality(TPM_MODIFIER_INDICATOR *pLocalityModifier, uint32_t idTpm) +{ + PDRVTPMEMU pThis = g_pDrvTpmEmuTpms; + + AssertReturn(idTpm == 0, TPM_FAIL); + + *pLocalityModifier = pThis->bLoc; + return TPM_SUCCESS; +} + + +static DECLCALLBACK(TPM_RESULT) drvTpmEmuTpmsCbkIoGetPhysicalPresence(TPM_BOOL *pfPhysicalPresence, uint32_t idTpm) +{ + AssertReturn(idTpm == 0, TPM_FAIL); + + *pfPhysicalPresence = TRUE; + return TPM_SUCCESS; +} + + +/* -=-=-=-=- PDMDRVREG -=-=-=-=- */ + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOn} + */ +static DECLCALLBACK(void) drvTpmEmuTpmsPowerOn(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + TPM_RESULT rcTpm = TPMLIB_MainInit(); + if (RT_UNLIKELY(rcTpm != TPM_SUCCESS)) + { + LogRel(("DrvTpmEmuTpms#%u: Failed to initialize TPM emulation with %#x\n", + pDrvIns->iInstance, rcTpm)); + PDMDrvHlpVMSetError(pDrvIns, VERR_INVALID_PARAMETER, RT_SRC_POS, "Failed to startup the TPM with %u", rcTpm); + } +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnReset} + */ +static DECLCALLBACK(void) drvTpmEmuTpmsReset(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + TPMLIB_Terminate(); + TPM_RESULT rcTpm = TPMLIB_MainInit(); + if (RT_UNLIKELY(rcTpm != TPM_SUCCESS)) + { + LogRel(("DrvTpmEmuTpms#%u: Failed to reset TPM emulation with %#x\n", + pDrvIns->iInstance, rcTpm)); + PDMDrvHlpVMSetError(pDrvIns, VERR_INVALID_PARAMETER, RT_SRC_POS, "Failed to startup the TPM with %u", rcTpm); + } +} + + +/** + * @interface_method_impl{PDMDRVREG,pfnPowerOff} + */ +static DECLCALLBACK(void) drvTpmEmuTpmsPowerOff(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + TPMLIB_Terminate(); +} + + +/** @copydoc FNPDMDRVCONSTRUCT */ +static DECLCALLBACK(int) drvTpmEmuTpmsConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVTPMEMU pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMEMU); + PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + pThis->enmVersion = TPMVERSION_UNKNOWN; + pThis->bLoc = TPM_NO_LOCALITY_SELECTED; + + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvTpmEmuTpmsQueryInterface; + /* ITpmConnector */ + pThis->ITpmConnector.pfnGetVersion = drvTpmEmuTpmsGetVersion; + pThis->ITpmConnector.pfnGetLocalityMax = drvTpmEmuGetLocalityMax; + pThis->ITpmConnector.pfnGetBufferSize = drvTpmEmuGetBufferSize; + pThis->ITpmConnector.pfnGetEstablishedFlag = drvTpmEmuTpmsGetEstablishedFlag; + pThis->ITpmConnector.pfnResetEstablishedFlag = drvTpmEmuTpmsResetEstablishedFlag; + pThis->ITpmConnector.pfnCmdExec = drvTpmEmuTpmsCmdExec; + pThis->ITpmConnector.pfnCmdCancel = drvTpmEmuTpmsCmdCancel; + + /* + * Validate and read the configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "TpmVersion|BufferSize", ""); + +#if 0 + TPMLIB_SetDebugFD(STDERR_FILENO); + TPMLIB_SetDebugLevel(~0); +#endif + + /* + * Try attach the VFS driver below and query it's VFS interface. + */ + PPDMIBASE pBase = NULL; + int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Failed to attach driver below us! %Rrc"), rc); + pThis->pDrvVfs = PDMIBASE_QUERY_INTERFACE(pBase, PDMIVFSCONNECTOR); + if (!pThis->pDrvVfs) + return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, + N_("No VFS interface below")); + + TPMLIB_TPMVersion enmVersion = TPMLIB_TPM_VERSION_2; + uint32_t uTpmVersion = 0; + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "TpmVersion", &uTpmVersion, 2); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: querying \"TpmVersion\" resulted in %Rrc"), rc); + + switch (uTpmVersion) + { + case 1: + enmVersion = TPMLIB_TPM_VERSION_1_2; + pThis->enmVersion = TPMVERSION_1_2; + break; + case 2: + enmVersion = TPMLIB_TPM_VERSION_2; + pThis->enmVersion = TPMVERSION_2_0; + break; + default: + return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_SUPPORTED, RT_SRC_POS, + N_("Configuration error: \"TpmVersion\" %u is not supported"), uTpmVersion); + } + + TPM_RESULT rcTpm = TPMLIB_ChooseTPMVersion(enmVersion); + if (rcTpm != TPM_SUCCESS) + return PDMDrvHlpVMSetError(pDrvIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Failed to set the TPM version for the emulated TPM with %d"), rcTpm); + + int cbBufferMax = 0; + rcTpm = TPMLIB_GetTPMProperty(TPMPROP_TPM_BUFFER_MAX, &cbBufferMax); + if (rcTpm != TPM_SUCCESS) + return PDMDrvHlpVMSetError(pDrvIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Querying the maximum supported buffer size failed with %u"), rcTpm); + + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "BufferSize", &pThis->cbBuffer, (uint32_t)cbBufferMax); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: querying \"BufferSize\" resulted in %Rrc"), rc); + + uint32_t cbBufferMin = 0; + uint32_t cbBuffer = TPMLIB_SetBufferSize(pThis->cbBuffer, &cbBufferMin, NULL /*max_size*/); + if (pThis->cbBuffer != cbBuffer) + return PDMDrvHlpVMSetError(pDrvIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Failed to set buffer size (%u) of the emulated TPM with %u (min %u, max %d)"), + pThis->cbBuffer, cbBuffer, cbBufferMin, cbBufferMax); + + struct libtpms_callbacks Callbacks; + Callbacks.sizeOfStruct = sizeof(Callbacks); + Callbacks.tpm_nvram_init = drvTpmEmuTpmsCbkNvRamInit; + Callbacks.tpm_nvram_loaddata = drvTpmEmuTpmsCbkNvRamLoadData; + Callbacks.tpm_nvram_storedata = drvTpmEmuTpmsCbkNvRamStoreData; + Callbacks.tpm_nvram_deletename = drvTpmEmuTpmsCbkNvRamDeleteName; + Callbacks.tpm_io_init = drvTpmEmuTpmsCbkIoInit; + Callbacks.tpm_io_getlocality = drvTpmEmuTpmsCbkIoGetLocality; + Callbacks.tpm_io_getphysicalpresence = drvTpmEmuTpmsCbkIoGetPhysicalPresence; + rcTpm = TPMLIB_RegisterCallbacks(&Callbacks); + if (rcTpm != TPM_SUCCESS) + return PDMDrvHlpVMSetError(pDrvIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("Failed to register callbacks with the TPM emulation: %u"), + rcTpm); + + /* We can only have one instance of the TPM emulation and require the global variable for the callbacks unfortunately. */ + g_pDrvTpmEmuTpms = pThis; + return VINF_SUCCESS; +} + + +/** + * TPM libtpms emulator driver registration record. + */ +const PDMDRVREG g_DrvTpmEmuTpms = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "TpmEmuTpms", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "TPM emulation driver based on libtpms.", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_STREAM, + /* cMaxInstances */ + 1, + /* cbInstance */ + sizeof(DRVTPMEMU), + /* pfnConstruct */ + drvTpmEmuTpmsConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + drvTpmEmuTpmsPowerOn, + /* pfnReset */ + drvTpmEmuTpmsReset, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + drvTpmEmuTpmsPowerOff, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + diff --git a/src/VBox/Devices/Security/DrvTpmHost.cpp b/src/VBox/Devices/Security/DrvTpmHost.cpp new file mode 100644 index 00000000..e9625f57 --- /dev/null +++ b/src/VBox/Devices/Security/DrvTpmHost.cpp @@ -0,0 +1,403 @@ +/* $Id: DrvTpmHost.cpp $ */ +/** @file + * TPM host access driver. + */ + +/* + * Copyright (C) 2021-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_TPM_HOST +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmtpmifs.h> +#include <iprt/assert.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/uuid.h> +#include <iprt/tpm.h> + +#include <iprt/formats/tpm.h> + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * TPM 1.2 buffer size capability response. + */ +#pragma pack(1) +typedef struct TPMRESPGETBUFSZ +{ + TPMRESPHDR Hdr; + uint32_t u32Length; + uint32_t cbBuf; +} TPMRESPGETBUFSZ; +#pragma pack() +typedef TPMRESPGETBUFSZ *PTPMRESPGETBUFSZ; +typedef const TPMRESPGETBUFSZ *PCTPMRESPGETBUFSZ; + + +/** + * TPM 2.0 buffer size capability response. + */ +#pragma pack(1) +typedef struct TPM2RESPGETBUFSZ +{ + TPMRESPHDR Hdr; + uint8_t fMore; + uint32_t u32Cap; + uint32_t u32Count; + uint32_t u32Prop; + uint32_t u32Value; +} TPM2RESPGETBUFSZ; +#pragma pack() +typedef TPM2RESPGETBUFSZ *PTPM2RESPGETBUFSZ; +typedef const TPM2RESPGETBUFSZ *PCTPM2RESPGETBUFSZ; + + +/** + * TPM Host driver instance data. + * + * @implements PDMITPMCONNECTOR + */ +typedef struct DRVTPMHOST +{ + /** The stream interface. */ + PDMITPMCONNECTOR ITpmConnector; + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + + /** Handle to the host TPM. */ + RTTPM hTpm; + /** Cached TPM version. */ + TPMVERSION enmTpmVersion; + /** Cached buffer size of the host TPM. */ + uint32_t cbBuffer; +} DRVTPMHOST; +/** Pointer to the TPM emulator instance data. */ +typedef DRVTPMHOST *PDRVTPMHOST; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Queries the buffer size of the host TPM. + * + * @returns VBox status code. + * @param pThis The host TPM driver instance data. + */ +static int drvTpmHostQueryBufferSize(PDRVTPMHOST pThis) +{ + uint8_t abResp[_1K]; + int rc = VINF_SUCCESS; + + switch (pThis->enmTpmVersion) + { + case TPMVERSION_1_2: + { + TPMREQGETCAPABILITY Req; + + Req.Hdr.u16Tag = RT_H2BE_U16(TPM_TAG_RQU_COMMAND); + Req.Hdr.cbReq = RT_H2BE_U32(sizeof(Req)); + Req.Hdr.u32Ordinal = RT_H2BE_U32(TPM_ORD_GETCAPABILITY); + Req.u32Cap = RT_H2BE_U32(TPM_CAP_PROPERTY); + Req.u32Length = RT_H2BE_U32(sizeof(uint32_t)); + Req.u32SubCap = RT_H2BE_U32(TPM_CAP_PROP_INPUT_BUFFER); + rc = RTTpmReqExec(pThis->hTpm, 0 /*bLoc*/, &Req, sizeof(Req), &abResp[0], sizeof(abResp), NULL /*pcbResp*/); + break; + } + case TPMVERSION_2_0: + { + TPM2REQGETCAPABILITY Req; + + Req.Hdr.u16Tag = RT_H2BE_U16(TPM2_ST_NO_SESSIONS); + Req.Hdr.cbReq = RT_H2BE_U32(sizeof(Req)); + Req.Hdr.u32Ordinal = RT_H2BE_U32(TPM2_CC_GET_CAPABILITY); + Req.u32Cap = RT_H2BE_U32(TPM2_CAP_TPM_PROPERTIES); + Req.u32Property = RT_H2BE_U32(TPM2_PT_INPUT_BUFFER); + Req.u32Count = RT_H2BE_U32(1); + rc = RTTpmReqExec(pThis->hTpm, 0 /*bLoc*/, &Req, sizeof(Req), &abResp[0], sizeof(abResp), NULL /*pcbResp*/); + break; + } + default: + AssertFailed(); + } + + if (RT_SUCCESS(rc)) + { + switch (pThis->enmTpmVersion) + { + case TPMVERSION_1_2: + { + PCTPMRESPGETBUFSZ pResp = (PCTPMRESPGETBUFSZ)&abResp[0]; + + if ( RTTpmRespGetSz(&pResp->Hdr) == sizeof(*pResp) + && RT_BE2H_U32(pResp->u32Length) == sizeof(uint32_t)) + pThis->cbBuffer = RT_BE2H_U32(pResp->cbBuf); + else + rc = VERR_INVALID_PARAMETER; + break; + } + case TPMVERSION_2_0: + { + PCTPM2RESPGETBUFSZ pResp = (PCTPM2RESPGETBUFSZ)&abResp[0]; + + if ( RTTpmRespGetSz(&pResp->Hdr) == sizeof(*pResp) + && RT_BE2H_U32(pResp->u32Count) == 1) + pThis->cbBuffer = RT_BE2H_U32(pResp->u32Value); + else + rc = VERR_INVALID_PARAMETER; + break; + } + default: + AssertFailed(); + } + } + + return rc; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetVersion} */ +static DECLCALLBACK(TPMVERSION) drvTpmHostGetVersion(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMHOST pThis = RT_FROM_MEMBER(pInterface, DRVTPMHOST, ITpmConnector); + return pThis->enmTpmVersion; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetLocalityMax} */ +static DECLCALLBACK(uint32_t) drvTpmHostGetLocalityMax(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMHOST pThis = RT_FROM_MEMBER(pInterface, DRVTPMHOST, ITpmConnector); + return RTTpmGetLocalityMax(pThis->hTpm); +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetBufferSize} */ +static DECLCALLBACK(uint32_t) drvTpmHostGetBufferSize(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMHOST pThis = RT_FROM_MEMBER(pInterface, DRVTPMHOST, ITpmConnector); + return pThis->cbBuffer; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnGetEstablishedFlag} */ +static DECLCALLBACK(bool) drvTpmHostGetEstablishedFlag(PPDMITPMCONNECTOR pInterface) +{ + RT_NOREF(pInterface); + return false; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnResetEstablishedFlag} */ +static DECLCALLBACK(int) drvTpmHostResetEstablishedFlag(PPDMITPMCONNECTOR pInterface, uint8_t bLoc) +{ + RT_NOREF(pInterface, bLoc); + return VINF_SUCCESS; +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnCmdExec} */ +static DECLCALLBACK(int) drvTpmHostCmdExec(PPDMITPMCONNECTOR pInterface, uint8_t bLoc, const void *pvCmd, size_t cbCmd, void *pvResp, size_t cbResp) +{ + RT_NOREF(bLoc); + PDRVTPMHOST pThis = RT_FROM_MEMBER(pInterface, DRVTPMHOST, ITpmConnector); + + return RTTpmReqExec(pThis->hTpm, 0 /*bLoc*/, pvCmd, cbCmd, pvResp, cbResp, NULL /*pcbResp*/); +} + + +/** @interface_method_impl{PDMITPMCONNECTOR,pfnCmdCancel} */ +static DECLCALLBACK(int) drvTpmHostCmdCancel(PPDMITPMCONNECTOR pInterface) +{ + PDRVTPMHOST pThis = RT_FROM_MEMBER(pInterface, DRVTPMHOST, ITpmConnector); + + return RTTpmReqCancel(pThis->hTpm); +} + + +/** @interface_method_impl{PDMIBASE,pfnQueryInterface} */ +static DECLCALLBACK(void *) drvTpmHostQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVTPMHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMHOST); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMITPMCONNECTOR, &pThis->ITpmConnector); + return NULL; +} + + +/* -=-=-=-=- PDMDRVREG -=-=-=-=- */ + +/** @copydoc FNPDMDRVDESTRUCT */ +static DECLCALLBACK(void) drvTpmHostDestruct(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + PDRVTPMHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMHOST); + LogFlow(("%s\n", __FUNCTION__)); + + if (pThis->hTpm != NIL_RTTPM) + { + int rc = RTTpmClose(pThis->hTpm); + AssertRC(rc); + + pThis->hTpm = NIL_RTTPM; + } +} + + +/** @copydoc FNPDMDRVCONSTRUCT */ +static DECLCALLBACK(int) drvTpmHostConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVTPMHOST pThis = PDMINS_2_DATA(pDrvIns, PDRVTPMHOST); + PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; + + /* + * Init the static parts. + */ + pThis->pDrvIns = pDrvIns; + pThis->hTpm = NIL_RTTPM; + + /* IBase */ + pDrvIns->IBase.pfnQueryInterface = drvTpmHostQueryInterface; + /* ITpmConnector */ + pThis->ITpmConnector.pfnGetVersion = drvTpmHostGetVersion; + pThis->ITpmConnector.pfnGetLocalityMax = drvTpmHostGetLocalityMax; + pThis->ITpmConnector.pfnGetBufferSize = drvTpmHostGetBufferSize; + pThis->ITpmConnector.pfnGetEstablishedFlag = drvTpmHostGetEstablishedFlag; + pThis->ITpmConnector.pfnResetEstablishedFlag = drvTpmHostResetEstablishedFlag; + pThis->ITpmConnector.pfnCmdExec = drvTpmHostCmdExec; + pThis->ITpmConnector.pfnCmdCancel = drvTpmHostCmdCancel; + + /* + * Validate and read the configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "TpmId", ""); + + uint32_t idTpm = RTTPM_ID_DEFAULT; + int rc = pHlp->pfnCFGMQueryU32Def(pCfg, "TpmId", &idTpm, RTTPM_ID_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: querying \"TpmId\" resulted in %Rrc"), rc); + + rc = RTTpmOpen(&pThis->hTpm, idTpm); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmHost%d: Opening TPM with id %u failed with %Rrc"), pDrvIns->iInstance, idTpm, rc); + + RTTPMVERSION enmVersion = RTTpmGetVersion(pThis->hTpm); + + switch (enmVersion) + { + case RTTPMVERSION_1_2: + pThis->enmTpmVersion = TPMVERSION_1_2; + break; + case RTTPMVERSION_2_0: + pThis->enmTpmVersion = TPMVERSION_2_0; + break; + case RTTPMVERSION_UNKNOWN: + default: + return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_SUPPORTED, RT_SRC_POS, + N_("DrvTpmHost%d: TPM version %u of TPM id %u is not supported"), + pDrvIns->iInstance, enmVersion, idTpm); + } + + rc = drvTpmHostQueryBufferSize(pThis); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("DrvTpmHost%d: Querying input buffer size of TPM with id %u failed with %Rrc"), + pDrvIns->iInstance, idTpm, rc); + + LogRel(("DrvTpmHost#%d: Connected to TPM %u.\n", pDrvIns->iInstance, idTpm)); + return VINF_SUCCESS; +} + + +/** + * TPM host driver registration record. + */ +const PDMDRVREG g_DrvTpmHost = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "TpmHost", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "TPM host driver.", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_STREAM, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVTPMHOST), + /* pfnConstruct */ + drvTpmHostConstruct, + /* pfnDestruct */ + drvTpmHostDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + |