Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

RequirementNotes
Host OSLinux (native) or WSL on Windows. Native Windows is not supported.
swtpmTPM 2.0 emulator. Install via your distro's package manager (e.g. apt install swtpm swtpm-tools).
QEMUBuilt with tpm-tis device support (standard upstream QEMU includes this).
Build hostSame 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:

DefineDefaultPurpose
TPM2_ENABLEFALSEMaster switch. Guards all TPM drivers, libraries, and PCDs.
TPM_CONFIG_ENABLEFALSEEnables Tcg2ConfigDxe HII configuration UI.
TPM_REPLAY_ENABLEDFALSEEnables TPM Replay overrides (uses TpmTestingPkg variants of Tcg2Dxe and DxeTpm2MeasureBootLib).

Platform Memory Layout

Q35 uses the standard x86 TPM memory-mapped I/O region:

RegionAddressSizeInterface
TPM TIS/CRB0xFED400000x5000 (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:

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:

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

  1. HashLibBaseCryptoRouterConstructor resets PcdTcg2HashAlgorithmBitmap to 0.
  2. Each HashInstanceLib constructor calls RegisterHashInterfaceLib().
  3. RegisterHashInterfaceLib() checks the algorithm against PcdTpm2HashMask (0x02 = SHA256 only). Algorithms not in the mask return EFI_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:

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_ENABLELibraryBehavior
FALSETcg2PhysicalPresenceLibNullAll functions stubbed
TRUEDxeTcg2PhysicalPresenceMinimumLibAuto-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:

  1. Reads the Tcg2PhysicalPresence NV variable (creates it if missing).
  2. Executes any pending PP request stored in the variable.
  3. 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.

Note

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)

PCDValueTypePurpose
PcdTpmBaseAddress0xFED40000 (package default)DynamicDefaultTPM TIS/CRB MMIO base address
PcdTpm2HashMask0x02DynamicDefaultHash algorithm filter (SHA256 only)
PcdTpmInstanceGuidgEfiTpmDeviceInstanceTpm20DtpmGuidDynamicDefaultSelects discrete TPM 2.0 device type (set by Tcg2ConfigPei at runtime)
PcdTpm2AcpiTableRev4DynamicHiiACPI TPM2 table revision
PcdUserPhysicalPresenceFALSEFixedAtBuildNo physical user presence assertion

Memory Type PCDs

PCDValue (pages)Purpose
PcdMemoryTypeEfiACPIReclaimMemory0x2B (43)Includes TPM ACPI tables
PcdMemoryTypeEfiACPIMemoryNVS0x80 (128)ACPI NVS memory
PcdMemoryTypeEfiReservedMemoryType0x510Reserved memory
PcdMemoryTypeEfiRuntimeServicesCode0x100Runtime code
PcdMemoryTypeEfiRuntimeServicesData0x700Runtime data

Conditional PCDs (TPM_CONFIG_ENABLE=TRUE)

PCDValuePurpose
PcdTcgPhysicalPresenceInterfaceVer"1.3"TCG PPI specification version reported to OS