/* $Id: DevTpmPpi.cpp $ */ /** @file * DevTpmPpi - Guest platform <-> VirtualBox TPM Physical Presence Interface Integration Framework. * * Based on: https://github.com/qemu/qemu/blob/master/docs/specs/tpm.rst (2024-06-26). */ /* * Copyright (C) 2024 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 . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DEV_TPM #include #include #include #include #include #include #include #include "VBoxDD.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** The TPM saved state version. */ #define TPM_PPI_SAVED_STATE_VERSION 1 /** The TPM PPI MMIO base default (compatible with qemu). */ #define TPM_PPI_MMIO_BASE_DEFAULT UINT64_C(0xfed45000) /** The size of the TPM PPI MMIO area. */ #define TPM_PPI_MMIO_SIZE _4K /** @name QEMU compatible PPI interface layout. * @{ */ /** * The PPI structure layout (yes, it really is misaligned). */ #pragma pack(1) typedef union QEMUTPMPPI { /** The byte view. */ uint8_t ab[0x400]; /** The structured view. */ struct { /** Supported operation flags set by the firmware for each operation. */ uint8_t abFunc[0x100]; /** SMI interrupt to use, set by firmware. Not supported. */ uint8_t bPpin; /** ACPI function index to pass to SMM code, set by ACPI. Not supported. */ uint32_t u32Ppip; /** Result of last executed operation, set by firmware. */ uint32_t u32Pprp; /** Operation request number to execute, set by ACPI. */ uint32_t u32Pprq; /** Operation request optional parameter, set by ACPI. */ uint32_t u32Pprm; /** Last executed operation request number, copied from QEMUTPMPPI::u32PPrq field by firmware. */ uint32_t u32Lppr; /** Result code from SMM function, not supported. */ uint32_t u32Fret; /** Reserved for future use. */ uint8_t abRsvd[0x40]; /** Operation to execute after reboot by firmware, used by firmware. */ uint8_t bNextStep; /** Memory overwrite variable. */ uint8_t bMovv; } s; } QEMUTPMPPI; #pragma pack() AssertCompileSize(QEMUTPMPPI, 0x400); /** Pointer to the QEMU PPI structure layout. */ typedef QEMUTPMPPI *PQEMUTPMPPI; /** Pointer to a const QEMU PPI structure layout. */ typedef const QEMUTPMPPI *PCQEMUTPMPPI; /** @} */ /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Shared TPM PPI device state. */ typedef struct DEVTPMPPI { /** Base MMIO address of the TPM PPI area. */ RTGCPHYS GCPhysMmio; /** The handle of the MMIO region. */ IOMMMIOHANDLE hMmio; /** The QEMU PPI state. */ QEMUTPMPPI Ppi; } DEVTPMPPI; /** Pointer to the shared TPM PPI device state. */ typedef DEVTPMPPI *PDEVTPMPPI; /** * TPM PPI device state for ring-3. */ typedef struct DEVTPMPPIR3 { /** Pointer to the device instance. */ PPDMDEVINS pDevIns; } DEVTPMPPIR3; /** Pointer to the TPM device state for ring-3. */ typedef DEVTPMPPIR3 *PDEVTPMPPIR3; /** * TPM PPI device state for ring-0. */ typedef struct DEVTPMPPIR0 { uint32_t u32Dummy; } DEVTPMPPIR0; /** Pointer to the TPM device state for ring-0. */ typedef DEVTPMPPIR0 *PDEVTPMPPIR0; /** * TPM PPI device state for raw-mode. */ typedef struct DEVTPMPPIRC { uint32_t u32Dummy; } DEVTPMPPIRC; /** Pointer to the TPM device state for raw-mode. */ typedef DEVTPMPPIRC *PDEVTPMPPIRC; /** The TPM PI device state for the current context. */ typedef CTX_SUFF(DEVTPMPPI) DEVTPMPPICC; /** Pointer to the TPM PPI device state for the current context. */ typedef CTX_SUFF(PDEVTPMPPI) PDEVTPMPPICC; /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ #ifdef IN_RING3 /** * SSM descriptor table for the TPM PPI structure. */ static SSMFIELD const g_aTpmPpiFields[] = { SSMFIELD_ENTRY(QEMUTPMPPI, s.abFunc), SSMFIELD_ENTRY(QEMUTPMPPI, s.bPpin), SSMFIELD_ENTRY(QEMUTPMPPI, s.u32Ppip), SSMFIELD_ENTRY(QEMUTPMPPI, s.u32Pprp), SSMFIELD_ENTRY(QEMUTPMPPI, s.u32Pprq), SSMFIELD_ENTRY(QEMUTPMPPI, s.u32Pprm), SSMFIELD_ENTRY(QEMUTPMPPI, s.u32Lppr), SSMFIELD_ENTRY(QEMUTPMPPI, s.u32Fret), SSMFIELD_ENTRY(QEMUTPMPPI, s.bNextStep), SSMFIELD_ENTRY(QEMUTPMPPI, s.bMovv), SSMFIELD_ENTRY_TERM() }; #endif /* -=-=-=-=-=- MMIO callbacks -=-=-=-=-=- */ /** * @callback_method_impl{FNIOMMMIONEWREAD} */ static DECLCALLBACK(VBOXSTRICTRC) tpmPpiMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) { PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); RT_NOREF(pvUser); LogFlowFunc(("off=%RGp cb=%u\n", off, cb)); if (off + cb > sizeof(pThis->Ppi)) return VINF_IOM_MMIO_UNUSED_FF; memcpy(pv, &pThis->Ppi.ab[off], cb); return VINF_SUCCESS; } /** * @callback_method_impl{FNIOMMMIONEWWRITE} */ static DECLCALLBACK(VBOXSTRICTRC) tpmPpiMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) { PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); RT_NOREF(pvUser); LogFlowFunc(("off=%RGp cb=%u\n", off, cb)); if (off + cb > sizeof(pThis->Ppi)) return VINF_SUCCESS; memcpy(&pThis->Ppi.ab[off], pv, cb); return VINF_SUCCESS; } #ifdef IN_RING3 /* -=-=-=-=-=-=-=-=- Saved State -=-=-=-=-=-=-=-=- */ /** * @callback_method_impl{FNSSMDEVLIVEEXEC} */ static DECLCALLBACK(int) tpmPpiR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass) { PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; RT_NOREF(uPass); /* Save the part of the config used for verification purposes when restoring. */ pHlp->pfnSSMPutGCPhys(pSSM, pThis->GCPhysMmio); return VINF_SSM_DONT_CALL_AGAIN; } /** * @callback_method_impl{FNSSMDEVSAVEEXEC} */ static DECLCALLBACK(int) tpmPpiR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) { PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; tpmPpiR3LiveExec(pDevIns, pSSM, SSM_PASS_FINAL); int rc = pHlp->pfnSSMPutStructEx(pSSM, &pThis->Ppi, sizeof(pThis->Ppi), 0 /*fFlags*/, &g_aTpmPpiFields[0], NULL); AssertRCReturn(rc, rc); return pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); /* sanity/terminator */ } /** * @callback_method_impl{FNSSMDEVLOADEXEC} */ static DECLCALLBACK(int) tpmPpiR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) { PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; RTGCPHYS GCPhysMmio; Assert(uPass == SSM_PASS_FINAL); RT_NOREF(uPass); AssertMsgReturn(uVersion == TPM_PPI_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); if (uPass == SSM_PASS_FINAL) { rc = pHlp->pfnSSMGetStructEx(pSSM, &pThis->Ppi, sizeof(pThis->Ppi), 0 /*fFlags*/, &g_aTpmPpiFields[0], NULL); AssertRCReturn(rc, rc); /* The marker. */ uint32_t u32; rc = pHlp->pfnSSMGetU32(pSSM, &u32); AssertRCReturn(rc, rc); AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); } return VINF_SUCCESS; } /** * @interface_method_impl{PDMDEVREG,pfnDestruct} */ static DECLCALLBACK(int) tpmPpiR3Destruct(PPDMDEVINS pDevIns) { PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); return VINF_SUCCESS; } /** * @interface_method_impl{PDMDEVREG,pfnConstruct} */ static DECLCALLBACK(int) tpmPpiR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) { PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); PDEVTPMPPICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVTPMPPICC); PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; int rc; RT_NOREF(iInstance); Assert(iInstance == 0); /* * Initalize the basic variables so that the destructor always works. */ pThisCC->pDevIns = pDevIns; /* * Validate and read the configuration. */ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "MmioBase", ""); rc = pHlp->pfnCFGMQueryU64Def(pCfg, "MmioBase", &pThis->GCPhysMmio, TPM_PPI_MMIO_BASE_DEFAULT); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: Failed to get the \"MmioBase\" value")); /* * Register the MMIO range, PDM API requests page aligned * addresses and sizes. */ rc = PDMDevHlpMmioCreateAndMap(pDevIns, pThis->GCPhysMmio, TPM_PPI_MMIO_SIZE, tpmPpiMmioWrite, tpmPpiMmioRead, IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU, "TPM PPI MMIO", &pThis->hMmio); AssertRCReturn(rc, rc); /* * Saved state. */ rc = PDMDevHlpSSMRegister3(pDevIns, TPM_PPI_SAVED_STATE_VERSION, sizeof(*pThis), tpmPpiR3LiveExec, tpmPpiR3SaveExec, tpmPpiR3LoadExec); AssertRCReturn(rc, rc); return VINF_SUCCESS; } #else /* !IN_RING3 */ /** * @callback_method_impl{PDMDEVREGR0,pfnConstruct} */ static DECLCALLBACK(int) tpmPpiRZConstruct(PPDMDEVINS pDevIns) { PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); PDEVTPMPPI pThis = PDMDEVINS_2_DATA(pDevIns, PDEVTPMPPI); int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, tpmPpiMmioWrite, tpmPpiMmioRead, NULL /*pvUser*/); AssertRCReturn(rc, rc); return VINF_SUCCESS; } #endif /* !IN_RING3 */ /** * The device registration structure. */ const PDMDEVREG g_DeviceTpmPpi = { /* .u32Version = */ PDM_DEVREG_VERSION, /* .uReserved0 = */ 0, /* .szName = */ "tpm-ppi", /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_NEW_STYLE, /* .fClass = */ PDM_DEVREG_CLASS_ARCH_BIOS, /* .cMaxInstances = */ 1, /* .uSharedVersion = */ 42, /* .cbInstanceShared = */ sizeof(DEVTPMPPI), /* .cbInstanceCC = */ sizeof(DEVTPMPPICC), /* .cbInstanceRC = */ sizeof(DEVTPMPPIRC), /* .cMaxPciDevices = */ 0, /* .cMaxMsixVectors = */ 0, /* .pszDescription = */ "Device implementing the TPM Physical Presence Interface (PPI)\n", #if defined(IN_RING3) /* .pszRCMod = */ "", /* .pszR0Mod = */ "", /* .pfnConstruct = */ tpmPpiR3Construct, /* .pfnDestruct = */ tpmPpiR3Destruct, /* .pfnRelocate = */ NULL, /* .pfnMemSetup = */ NULL, /* .pfnPowerOn = */ NULL, /* .pfnReset = */ NULL, /* .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 = */ tpmPpiRZConstruct, /* .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 = */ tpmPpiRZConstruct, /* .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 };