TPM on QEMU Q35
This document describes the TPM 2.0 architecture for the QEMU Q35 platform. Q35 uses a direct CRB/FIFO path between firmware and the TPM device.
Table of Contents
- Requirements
- Build Configuration
- Platform Memory Layout
- Architecture Overview
- TPM Device Library Stack
- Hash Library Architecture
- Physical Presence Interface
- swtpm Setup
- Communication Flow
- PCDs Reference
Requirements
| Requirement | Notes |
|---|---|
| Host OS | Linux (native) or WSL on Windows. Native Windows is not supported. |
| swtpm | TPM 2.0 emulator. Install via your distro's package manager (e.g. apt install swtpm swtpm-tools). |
| QEMU | Built with tpm-tis device support (standard upstream QEMU includes this). |
| Build host | Same Linux/WSL environment used to run stuart_build and launch QEMU. |
See swtpm Setup for the full setup commands.
Build Configuration
The TPM is disabled by default. To enable it, set BLD_*_TPM2_ENABLE=TRUE on the command line or in a
BuildConfig.conf file placed at the root level of the repo:
stuart_build -c Platforms/QemuQ35Pkg/PlatformBuild.py --FlashRom BLD_*_TPM2_ENABLE=TRUE
The following defines control TPM behavior in QemuQ35Pkg.dsc:
| Define | Default | Purpose |
|---|---|---|
TPM2_ENABLE | FALSE | Master switch. Guards all TPM drivers, libraries, and PCDs. |
TPM_CONFIG_ENABLE | FALSE | Enables Tcg2ConfigDxe HII configuration UI. |
TPM_REPLAY_ENABLED | FALSE | Enables TPM Replay overrides (uses TpmTestingPkg variants of Tcg2Dxe and DxeTpm2MeasureBootLib). |
Platform Memory Layout
Q35 uses the standard x86 TPM memory-mapped I/O region:
| Region | Address | Size | Interface |
|---|---|---|---|
| TPM TIS/CRB | 0xFED40000 | 0x5000 (20 KiB) | CRB or TIS (auto-detected) |
The firmware communicates directly with the TPM device via MMIO, and QEMU forwards the I/O to swtpm over a Unix socket.
The base address comes from the SecurityPkg package declaration default
(PcdTpmBaseAddress = 0xFED40000). The Q35 DSC does not override it explicitly. The
Tcg2ConfigPei driver detects the TPM at this address during PEI.
Architecture Overview
┌──────────────────────────────────────────────────────────────────────────────────┐
│ UEFI Firmware (x86_64) │
│ │
│ ┌─── PEI Phase ──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Tcg2ConfigPei │ │
│ │ │ 1. Detect TPM 1.2 vs 2.0 at 0xFED40000 │ │
│ │ │ 2. Set PcdTpmInstanceGuid │ │
│ │ ▼ │ │
│ │ HashLibBaseCryptoRouterPei + HashInstanceLib* │ │
│ │ │ 1. Constructors register each enabled hash algorithm │ │
│ │ │ 2. Filtered by PcdTpm2HashMask → PcdTcg2HashAlgorithmBitmap │ │
│ │ ▼ │ │
│ │ Tcg2Pei │ │
│ │ │ 1. Tpm2RequestUseTpm() │ │
│ │ │ 2. Tpm2Startup(TPM_SU_CLEAR) │ │
│ │ │ 3. SyncPcrAllocationsAndPcrMask() │ │
│ │ │ 4. Tpm2SelfTest() │ │
│ │ │ 5. Measure firmware volumes (CRTM) into PCR[0-7] │ │
│ │ │ 6. Install TpmInitializedPpi │ │
│ └────┼───────────────────────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─── DXE Phase ──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ HashLibBaseCryptoRouterDxe + HashInstanceLib* │ │
│ │ │ 1. Constructors register each enabled hash algorithm │ │
│ │ │ 2. Filtered by PcdTpm2HashMask → PcdTcg2HashAlgorithmBitmap │ │
│ │ ▼ │ │
│ │ Tcg2Dxe │ │
│ │ │ 1. Verify PcdTpmInstanceGuid is TPM 2.0 │ │
│ │ │ 2. Verify no TpmErrorHob is present │ │
│ │ │ 3. Tpm2RequestUseTpm() │ │
│ │ │ 4. Query TPM capabilities │ │
│ │ │ ├── Manufacturer │ │
│ │ │ ├── Firmware version │ │
│ │ │ └── Max cmd/resp size │ │
│ │ │ 5. Get supported/active PCR banks filtered by → HashAlgorithmBitmap │ │
│ │ │ 6. Decide SupportedEventLogs (TCG_1_2 only if SHA1 active) │ │
│ │ │ 7. SetupEventLog │ │
│ │ │ ├── Allocate log area(s) │ │
│ │ │ └── Acquire and log pre-DXE HOB(s) │ │
│ │ │ 8. Register events │ │
│ │ │ ├── ReadyToBoot │ │
│ │ │ ├── ExitBootServices │ │
│ │ │ └── ExitBootServices Failed │ │
│ │ │ 9. Register protocol notifies │ │
│ │ │ ├── VariableWriteArch (SecureBoot) │ │
│ │ │ └── ResetNotification (TPM shutdown) │ │
│ │ │ 10. Install Tcg2Protocol │ │
│ └────┼───────────────────────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─── BDS Phase ──────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ DeviceBootManagerAfterConsole │ │
│ │ │ 1. Tcg2PhysicalPresenceLibProcessRequest (NULL) │ │
│ │ │ 2. Process any pending PP request before shell launch │ │
│ │ │ 3. Create TCG2_PHYSICAL_PRESENCE_VARIABLE if it doesn't exist │ │
│ └────┼───────────────────────────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌─── UEFI Shell ─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ UEFI Shell / OS / TpmShellApp │ │
│ │ │ 1. gBS->LocateProtocol(&gEfiTcg2ProtocolGuid) │ │
│ │ │ 2. Tcg2Protocol->GetCapability / SetActivePcrBanks / etc. │ │
│ └────┼───────────────────────────────────────────────────────────────────────┘ │
│ ▼ │
│ Tpm2DeviceLibDTpm ─ direct MMIO reads/writes to 0xFED40000 │
│ │ │
├───────┼──────────────────────────────────────────────────────────────────────────┤
│ ▼ │
│ QEMU TPM TIS device (-device tpm-tis,tpmdev=tpm0) │
│ │ │
│ ▼ │
│ Unix socket ─ swtpm process (--tpm2) │
└──────────────────────────────────────────────────────────────────────────────────┘
TPM Device Library Stack
Q35 uses two different patterns for accessing the TPM, depending on the firmware phase:
PEI Phase: Direct MMIO (Tpm2DeviceLibDTpm)
Tcg2Pei
│ Tpm2SubmitCommand()
▼
Tpm2DeviceLibDTpm
│ Auto-detects CRB vs TIS vs FIFO via InterfaceId register
│ DTpm2SubmitCommand() dispatches:
│ CRB → PtpCrbTpmCommand()
│ FIFO → Tpm2TisTpmCommand()
│ TIS → Tpm2TisTpmCommand()
▼
Direct MMIO to 0xFED40000
Tpm2DeviceLibDTpm reads the InterfaceId register at PcdTpmBaseAddress + 0x30 to
determine the interface type. QEMU's tpm-tis device presents a TIS/FIFO interface.
DXE Phase: Router Pattern (Tpm2DeviceLibRouter)
Tcg2Dxe
│ Tpm2SubmitCommand()
▼
Tpm2DeviceLibRouterDxe
│ Delegates to registered TPM2_DEVICE_INTERFACE
▼
Tpm2InstanceLibDTpm (constructor registers with router)
│ DTpm2SubmitCommand() — same dispatch as PEI
▼
Direct MMIO to 0xFED40000
The router pattern exists to support future scenarios where multiple TPM device types
could coexist. The router accepts only the instance whose ProviderGuid matches
PcdTpmInstanceGuid.
Other DXE Drivers: TCG2 Protocol (Tpm2DeviceLibTcg2)
Most DXE drivers that need TPM access use Tpm2DeviceLibTcg2, which goes through the
TCG2 Protocol rather than direct MMIO. Only Tcg2Dxe itself uses Tpm2DeviceLibRouter
with direct MMIO.
CRB Register Layout
If the TPM presents a CRB interface (as opposed to TIS/FIFO), the register layout is defined by the TCG PC Client Platform TPM Profile (PTP) specification. See:
- TCG PC Client Platform TPM Profile (PTP) Specification —
Section 6 "Command Response Buffer Interface" describes
LocalityState,LocalityControl,InterfaceId,CrbControlRequest,CrbControlStart,CrbControlCommand*/CrbControlResponse*, and the sharedCrbDataBuffer.
The key register for this platform is InterfaceId at PcdTpmBaseAddress + 0x30, which
Tpm2DeviceLibDTpm reads to decide between CRB and TIS/FIFO dispatch paths.
TIS Register Layout
QEMU's tpm-tis device presents a TIS (TPM Interface Specification) / FIFO interface.
The register layout is defined by:
- TCG PC Client Specific TPM Interface Specification (TIS) —
defines
Access,IntEnable/IntVector,STS(withcommandReady,tpmGo,dataAvail,burstCount),DataFifo, and theVid/Did/Rididentification registers.
The TIS flow uses BurstCount from the STS register to pace reads and writes through
the DataFifo register, one burst at a time.
Hash Library Architecture
Tcg2Dxe uses HashLibBaseCryptoRouterDxe while Tcg2Pei uses HashLibBaseCryptoRouterPei
with all hash instance libraries included.
Registration Flow
HashLibBaseCryptoRouterConstructorresetsPcdTcg2HashAlgorithmBitmapto 0.- Each
HashInstanceLibconstructor callsRegisterHashInterfaceLib(). RegisterHashInterfaceLib()checks the algorithm againstPcdTpm2HashMask(0x02= SHA256 only). Algorithms not in the mask returnEFI_UNSUPPORTED.
Hash Algorithm Bitmask Values
The bit positions used in PcdTpm2HashMask, PcdTcg2HashAlgorithmBitmap, and the
EFI_TCG2_BOOT_SERVICE_CAPABILITY.HashAlgorithmBitmap field are defined by the EFI
TCG2 protocol and the TCG algorithm registry:
- UEFI TCG2 Protocol Specification — see
EFI_TCG2_BOOT_HASH_ALG_*(SHA1= BIT0,SHA256= BIT1,SHA384= BIT2,SHA512= BIT3,SM3_256= BIT4). - TCG Algorithm Registry — canonical list of TPM hash algorithm IDs.
For this platform, PcdTpm2HashMask = 0x02 enables SHA256 only.
Filtering Chain
PcdTpm2HashMask (0x02)
│
▼
RegisterHashInterfaceLib() ── gates which HashInstanceLibs register
│
▼
PcdTcg2HashAlgorithmBitmap ── result of all successful registrations
│
▼
Tcg2Dxe intersects with TPM-reported capabilities
│
▼
Final ActivePcrBanks / HashAlgorithmBitmap in EFI_TCG2_BOOT_SERVICE_CAPABILITY
Physical Presence Interface
Library Selection
TPM2_ENABLE | Library | Behavior |
|---|---|---|
FALSE | Tcg2PhysicalPresenceLibNull | All functions stubbed |
TRUE | DxeTcg2PhysicalPresenceMinimumLib | Auto-confirms Clear; rejects all other operations |
The MinimumLib implementation:
- Auto-confirms TPM Clear operations without user prompting.
- Rejects SET_PCR_BANKS, LOG_ALL_DIGESTS, and other operations with
TCG_PP_RETURN_TPM_OPERATION_RESPONSE_FAILURE. - Does not create or use
TCG2_PHYSICAL_PRESENCE_FLAGS_VARIABLE.
ProcessRequest in BDS
Tcg2PhysicalPresenceLibProcessRequest() is invoked from the platform's
DeviceBootManagerLib during DeviceBootManagerAfterConsole(), before the shell
launches. It:
- Reads the
Tcg2PhysicalPresenceNV variable (creates it if missing). - Executes any pending PP request stored in the variable.
- Stores the result back for
ReturnOperationResponseToOsFunction()to report.
swtpm Setup
Installation
swtpm requires Unix sockets, so it must run in a Linux environment. On Windows, use WSL (Windows Subsystem for Linux).
# Windows (from a WSL terminal)
wsl --install # if WSL is not yet enabled
wsl # enter the WSL environment
# Ubuntu/Debian (native or WSL)
sudo apt install swtpm swtpm-tools
# Fedora
sudo dnf install swtpm swtpm-tools
Manual Setup
Create the TPM state directory and start swtpm before launching QEMU:
mkdir -p /tmp/mytpm1
swtpm socket \
--tpmstate dir=/tmp/mytpm1 \
--ctrl type=unixio,path=/tmp/mytpm1/swtpm-sock \
--tpm2 \
--log level=20
Automatic Setup (QemuRunner)
When SWTPM_ENABLE=TRUE, QemuRunner.py automatically starts swtpm as a subprocess before
launching QEMU. The swtpm state directory is set to BUILD_OUTPUT_BASE and the Unix socket
is placed at {BUILD_OUTPUT_BASE}/swtpm-sock:
# Platforms/QemuQ35Pkg/Plugins/QemuRunner/QemuRunner.py
@staticmethod
def StartSwTpm(tpm_dir, tpm_sock):
"""Starts the swtpm emulator and returns its Popen handle."""
cmd = [
"swtpm", "socket",
"--tpmstate", f"dir={tpm_dir}",
"--ctrl", f"type=unixio,path={tpm_sock}",
"--tpm2",
"--log", "level=1",
]
return subprocess.Popen(cmd)
swtpm is started before QEMU launches. QemuRunner then waits (up to 30 seconds) for the
Unix socket to appear before starting QEMU, and terminates the swtpm process so it doesn't
outlive the run. SWTPM is enabled by default. Disable it by setting SWTPM_ENABLE=FALSE on
the command line or in the BuildConfig.conf file.
SWTPM is only available on Linux builds. QemuRunner automatically disables it on Windows
hosts even if SWTPM_ENABLE=TRUE.
QEMU Arguments
When SWTPM_ENABLE=TRUE, QemuRunner.py adds the following to the QEMU command line
(with the socket path under BUILD_OUTPUT_BASE):
-chardev socket,id=chrtpm,path={BUILD_OUTPUT_BASE}/swtpm-sock
-tpmdev emulator,id=tpm0,chardev=chrtpm
-device tpm-tis,tpmdev=tpm0
The -device tpm-tis argument is Q35-specific — it attaches a TIS-compatible TPM device
to the Q35 chipset at the standard address 0xFED40000.
Communication Flow
Complete path from a shell application to swtpm:
TpmShellApp (UEFI Shell)
│ gBS->LocateProtocol(&gEfiTcg2ProtocolGuid)
│ Tcg2Protocol->SetActivePcrBanks(0x02)
▼
Tcg2Dxe (EFI_TCG2_PROTOCOL)
│ Validates bank mask against HashAlgorithmBitmap
│ Calls Tcg2PhysicalPresenceLibSubmitRequestToPreOSFunction()
│ ├── MinimumLib: rejects SET_PCR_BANKS → returns EFI_UNSUPPORTED
│ └── MinimumLib: NO_ACTION (already-active) → writes NV variable → EFI_SUCCESS
▼
Tpm2CommandLib (for direct TPM commands like GetCapability)
│ Serializes TPM2 command structure into byte buffer
│ Calls Tpm2SubmitCommand(cmdBuffer, cmdSize, rspBuffer, &rspSize)
▼
Tpm2DeviceLibRouter → Tpm2InstanceLibDTpm
│ DTpm2SubmitCommand() — detects interface type (TIS on QEMU)
▼
Tpm2TisTpmCommand (Tpm2Tis.c)
│ 1. TisPcPrepareCommand: set TIS_PC_STS_COMMAND_READY
│ 2. Write command bytes to DataFifo (paced by BurstCount)
│ 3. Set TIS_PC_STS_GO to start execution
│ 4. Poll STS for DataAvail (90 second timeout)
│ 5. Read response header from DataFifo
│ 6. Read remaining response bytes (paced by BurstCount)
│ 7. Set TIS_PC_STS_READY (return to idle)
▼
MMIO to 0xFED40000 (QEMU TIS device)
│
▼
QEMU tpm-tis device ──── Unix socket ──── swtpm process
PCDs Reference
Required PCDs (set when TPM2_ENABLE=TRUE)
| PCD | Value | Type | Purpose |
|---|---|---|---|
PcdTpmBaseAddress | 0xFED40000 (package default) | DynamicDefault | TPM TIS/CRB MMIO base address |
PcdTpm2HashMask | 0x02 | DynamicDefault | Hash algorithm filter (SHA256 only) |
PcdTpmInstanceGuid | gEfiTpmDeviceInstanceTpm20DtpmGuid | DynamicDefault | Selects discrete TPM 2.0 device type (set by Tcg2ConfigPei at runtime) |
PcdTpm2AcpiTableRev | 4 | DynamicHii | ACPI TPM2 table revision |
PcdUserPhysicalPresence | FALSE | FixedAtBuild | No physical user presence assertion |
Memory Type PCDs
| PCD | Value (pages) | Purpose |
|---|---|---|
PcdMemoryTypeEfiACPIReclaimMemory | 0x2B (43) | Includes TPM ACPI tables |
PcdMemoryTypeEfiACPIMemoryNVS | 0x80 (128) | ACPI NVS memory |
PcdMemoryTypeEfiReservedMemoryType | 0x510 | Reserved memory |
PcdMemoryTypeEfiRuntimeServicesCode | 0x100 | Runtime code |
PcdMemoryTypeEfiRuntimeServicesData | 0x700 | Runtime data |
Conditional PCDs (TPM_CONFIG_ENABLE=TRUE)
| PCD | Value | Purpose |
|---|---|---|
PcdTcgPhysicalPresenceInterfaceVer | "1.3" | TCG PPI specification version reported to OS |