# Inside UEFI: TPM initialization, PCRs and the event log

Tracing EDK II's TPM 2.0 initialization, PCR usage, measured byte streams, crypto-agile event logs, replay and the firmware-to-OS handoff.

Author: Berkan YILDIZ
Published: 2026-10-24
Tags: UEFI, TPM 2.0, EDK II, Measured boot, Firmware security
Canonical: <https://blog.berkan.fr/uefi-tpm-initialization>


A Trusted Platform Module (TPM) becomes usable during boot through a sequence of decisions and state transitions. Platform firmware selects an implementation, acquires its command interface, starts or resumes the TPM, reconciles PCR-bank policy, and exposes measurement services. The operating system later receives a device description and a record of the measurements made before it gained control.

EDK II makes these boundaries visible. The same `Tpm2RequestUseTpm()` name can acquire a hardware locality in one module and locate a UEFI protocol in another. A PEI marker can announce that initialization finished even when no TPM was found. A successful command transfer can still contain a TPM error response.

This article follows those distinctions through the TPM 2.0 implementation, from the platform's device selection to the loader and OS interfaces. It also reconstructs what the PCRs accumulate, which bytes firmware measures, how event records are serialized, and how a consumer can replay that history. TPM 1.2 appears where it affects detection and log compatibility.

## Source revision and scope

The source reference is [TianoCore EDK II commit `02c1141a0e5b5c958630e81ac6ebb4b03c1e836f`][revision], dated **October 24, 2026**. The inspected `SecurityPkg`, `MdePkg` and `OvmfPkg` files match that revision. All EDK II source links below are pinned to it.

The main path is the PC-style TPM 2.0 stack in `SecurityPkg`, with OVMF as a concrete example of how a platform selects its components and libraries. PCR assignments are compared with the [PC Client Platform Firmware Profile 1.06, revision 52][pc-firmware-profile] and Windows' documented usage. This is source analysis: no physical TPM, firmware image or S3-resume sequence was exercised. Code listings are reduced reconstructions, with descriptive local names and omitted branches identified in the text. The worked event record below is constructed from the format and calculated hashes, not taken from a captured boot.

The board-level prerequisites are platform-specific. Chipset register decoding, device power and reset, and initialization of a security processor backing a firmware TPM must make the selected interface available. The generic TPM driver assumes that work is done.

![TPM boot flow: PEI selects an implementation, initializes its command interface and startup state, then measures firmware and records event HOBs. DXE exposes the TCG2 protocol and event log; platform ACPI code describes the device for the OS.](/images/blog/uefi-tpm-boot-flow.svg)

## Device implementation and host interface are separate choices

The TPM implementation and its host-facing interface describe different properties.

| Property | Examples | What firmware needs to establish |
| --- | --- | --- |
| TPM implementation | Discrete TPM, firmware TPM, virtual TPM | Which provider backs the platform's TPM service. |
| Command family | TPM 1.2 or TPM 2.0 | Which command and response formats are supported. |
| Host interface | Legacy TIS, PTP FIFO, PTP CRB, or a platform-specific transport | How command bytes and responses cross the firmware/device boundary. |
| UEFI service | `EFI_TCG2_PROTOCOL` | How later firmware components and an OS loader access the established service. |

Intel describes [PTT as an integrated firmware TPM implementing TPM 2.0][intel-ptt]. That identifies the backing technology. A firmware module's use of a CRB register block, by itself, describes the host interface rather than proving whether the implementation is a separate chip or firmware in a security processor.

The [TCG Platform TPM Profile][ptp-spec] specifies the PC-facing interfaces. EDK II's `Tpm2DeviceLibDTpm` implements the ordinary TIS/FIFO/CRB paths. Its library name and `gEfiTpmDeviceInstanceTpm20DtpmGuid` are software selection identifiers; interpreting a particular machine still requires its platform build configuration and transport implementation.

### Library resolution determines the actual call path

EDK II resolves a library class to an implementation through platform `.dsc` files and component overrides. Consequently, finding a call to `Tpm2SubmitCommand()` establishes only the library interface until its binding is known.

The OVMF [library mapping][ovmf-libraries] assigns PEIMs an SVSM-aware TPM device library. Its constructor first checks whether the confidential-guest environment provides an SVSM vTPM. If selected, requests use that backend; otherwise it initializes the regular PTP implementation. The MMIO path below follows that fallback, not the SVSM command mechanism. The branch is explicit in the [SVSM shim][svsm-shim].

DXE has another distinction. Ordinary DXE consumers use `Tpm2DeviceLibTcg2`, which forwards commands to an installed `EFI_TCG2_PROTOCOL`. The [component override for `Tcg2Dxe`][ovmf-dxe-components] instead supplies the router and a registered device instance, so the protocol provider can reach the device beneath it.

```text Ordinary DXE command path · regular PTP backend
DXE consumer
  Tpm2CommandLib
    Tpm2DeviceLibTcg2
      EFI_TCG2_PROTOCOL.SubmitCommand
        Tcg2Dxe
          Tpm2DeviceLibRouter + selected device instance
            DTpm2SubmitCommand
              FIFO/TIS or CRB transfer
```

The [protocol-backed library][protocol-device-lib] also implements `Tpm2RequestUseTpm()` by locating the protocol and returning success when it is available. It assumes the provider already acquired access. The regular device implementation uses the same API name to perform locality arbitration against registers.

## PEI selects the TPM before dispatching its initializer

PEI, the Pre-EFI Initialization phase, uses PPIs to publish services and dependency markers. HOBs, or hand-off blocks, carry state into later boot phases. Platform Configuration Database values, or PCDs, provide another part of the configuration contract.

`PcdTpmInstanceGuid` identifies the selected TPM provider. The standard [instance definitions][instance-guids] include an all-zero GUID for no instance, a TPM 1.2 GUID, and a TPM 2.0 dTPM GUID.

The generic [`Tcg2ConfigPeimEntryPoint()`][generic-config] reads and validates the `Tcg2Config` setup variable. With auto-detection enabled it invokes `DetectTpmDevice()`, then converts the detected device type into `PcdTpmInstanceGuid`. The code distinguishes the requested setup type from the detected provider, builds an instance HOB, and installs `gEfiTpmDeviceSelectedGuid`.

That last GUID is a scheduling boundary. [`Tcg2Pei.inf`][pei-inf] requires both the master boot-mode PPI and the TPM-device-selected marker in its dependency expression. Device selection therefore precedes the TPM 2.0 initializer's dispatch.

### The generic detector uses a TPM 1.2 probe

The [`SecurityPkg` detector][generic-detection] first requests access through `Tpm12RequestUseTpm()`. Failure returns the no-device selection. If access succeeds, it issues a TPM 1.2 startup command: `TPM_ST_CLEAR` for an ordinary boot, or `TPM_ST_STATE` on S3 resume.

Successful TPM 1.2 startup selects TPM 1.2 and sets `PcdTpmInitializationPolicy` to zero, avoiding a second initialization. An error from that startup probe selects the TPM 2.0 candidate.

```c Generic detector · reduced normal-boot branch
if (EFI_ERROR (Tpm12RequestUseTpm ())) {
    return TPM_DEVICE_NULL;
}

if (EFI_ERROR (Tpm12Startup (TPM_ST_CLEAR))) {
    return TPM_DEVICE_2_0_DTPM;
}

PcdSet8S (PcdTpmInitializationPolicy, 0);
return TPM_DEVICE_1_2;
```

This is a classification heuristic in this detector. The failing TPM 1.2 probe does not itself establish successful TPM 2.0 startup. The selected TPM 2.0 path must still acquire its own interface and execute its initialization sequence.

On S3, the generic detector first tries the saved device-detection variable. That preserves the normal-boot selection when valid detection state is available.

### OVMF uses a different detector

The [OVMF configuration PEIM][ovmf-config] performs hardware detection without the generic setup-variable policy. When TPM 1.2 support is included, `InternalTpm12Detect()` requests access and sends `TPM_ORD_GetTicks`. The TPM 2.0-only variant substitutes a helper that returns `EFI_UNSUPPORTED`.

If the TPM 1.2 path does not succeed, OVMF calls `Tpm2RequestUseTpm()`. Success selects the TPM 2.0 instance GUID. Failure leaves the no-instance selection and publishes the initialization-complete marker so dependent PEIMs can proceed. It publishes the device-selected marker afterward.

These are alternative platform configurations. The OVMF `GetTicks` probe and the generic `Startup` probe should not be combined into a single universal detection sequence.

## Interface detection starts at the configured register window

For the regular PTP backend, the register base comes from `PcdTpmBaseAddress`. [`SecurityPkg.dec`][security-pcds] declares a default of **`0xFED40000`**. A platform can override it; this is a configured base, not a range discovered by the generic library through PCI enumeration.

The [common device-library constructor][device-constructor] initializes two cached configuration values:

- If `PcdActiveTpmInterfaceType` is `0xFF`, it calls `Tpm2GetPtpInterface()` and records the result.
- For CRB, if `PcdCRBIdleByPass` is `0xFF`, it reads and caches the idle-bypass capability.

[`Tpm2IsPtpPresence()`][ptp-device] reads the first byte of the configured window and treats `0xFF` as absent. This is a register-availability test. Interface classification then reads the identifier at **base `+ 0x30`** and the FIFO capability register at **base `+ 0x14`**.

| Classification | Conditions checked by `Tpm2GetPtpInterface()` |
| --- | --- |
| CRB | CRB interface type, a recognized CRB interface version, and `CapCRB` set. |
| PTP FIFO | FIFO interface type, the FIFO interface version, `CapFIFO` set, and the PTP capability-register version. |
| Legacy TIS | The legacy TIS interface type. |
| Unavailable or unsupported | Presence test fails, or none of the supported combinations matches. |

The result drives both `DTpm2RequestUseTpm()` and `DTpm2SubmitCommand()`. An unsupported interface therefore surfaces as `EFI_NOT_FOUND` at the API boundary.

The firmware observes the interface already selected by the platform. The classification helper does not negotiate a different interface merely because the identifier advertises another supported mode.

## Acquiring a locality is separate from starting the TPM

A locality identifies an access context at the TPM interface. It is separate from UEFI privilege levels and from the TPM's storage, endorsement and platform hierarchies.

With the default PC register base, the paths here operate on the locality-zero window. Higher-locality operations depend on the platform and transport. The [register definitions][ptp-registers] describe the access and control bits used by the library.

### FIFO and legacy TIS access

[`TisPcRequestUseTpm()`][tis-device] checks presence, writes the request-use bit to the access register, and polls for valid, active access. Its request is a register write, not a TPM command packet.

```text FIFO/TIS locality acquisition
base + 0x00  ACCESS.requestUse = 1
             poll ACCESS.valid and ACCESS.activeLocality
             stop on success or the access timeout
```

The command transfer then follows a different state machine: set command-ready, obtain the FIFO burst count, write command bytes, wait for the TPM to stop expecting input, assert `GO`, and wait for response data. Response bytes are read from the FIFO using the advertised burst count.

| FIFO register | Offset | Role in this implementation |
| --- | --- | --- |
| Access | `0x00` | Request and observe locality ownership. |
| Interface capability | `0x14` | Help classify the interface. |
| Status | `0x18` | Command-ready, input expectation and response availability. |
| Burst count | `0x19` | Number of bytes transferable in a burst. |
| Data FIFO | `0x24` | Command and response byte stream. |
| Interface identifier | `0x30` | Active interface type and capabilities. |

The inspected transfer path polls registers with bounded delays. An interrupt-enable register in the device definition does not imply this firmware library uses an interrupt-driven command-completion path.

### CRB access and command submission

For CRB, `PtpCrbRequestUseTpm()` writes `REQUEST_ACCESS` to the locality-control register at **`+0x08`**, then polls the granted bit in locality status at **`+0x0C`**.

`PtpCrbTpmCommand()` subsequently manages the command-response buffer:

1. Enforce the required idle transition, accounting for the cached idle-bypass capability.
2. Request command-ready and wait for the ready transition.
3. Copy the command bytes into the buffer and program command/response addresses and sizes.
4. Set the start bit and wait for the TPM to clear it.
5. Read the response header, check the caller's response capacity, and copy the remaining response bytes.
6. Request the return to idle. Timeout paths attempt cancellation and recovery.

| CRB register or area | Offset from locality base |
| --- | --- |
| Locality control / status | `0x08` / `0x0C` |
| Interface identifier | `0x30` |
| Control request / status | `0x40` / `0x44` |
| Cancel / start | `0x48` / `0x4C` |
| Command size / address | `0x58` / `0x5C` and `0x60` |
| Response size / address | `0x64` / `0x68` |
| Data buffer in the EDK II register structure | `0x80` |

This implementation uses the data area embedded in its mapped CRB structure. Its state machine and buffer programming are concrete source behavior; other CRB backends can have different buffer-placement requirements.

At this layer, successful submission means that firmware obtained a response through the transport. Interpreting the TPM command's result belongs to the command wrapper above it.

## TPM2_Startup establishes the boot state

The main PEI initializer is [`PeimEntryMA()` in `Tcg2Pei.c`][tcg2-pei]. It first rejects a no-instance or TPM 1.2 selection and checks for an existing `gTpmErrorHobGuid`. On an ordinary boot it also registers for shadowing into permanent memory.

In its initial execution, before the in-memory measurement pass, it requests TPM access. When `PcdTpm2InitializationPolicy` is `1`, it sends:

| Boot path | Startup command |
| --- | --- |
| Ordinary boot | `TPM2_Startup(TPM_SU_CLEAR)` |
| S3 resume | `TPM2_Startup(TPM_SU_STATE)` |
| S3 state startup fails | Retry with `TPM_SU_CLEAR`; record the recovered-error condition if that succeeds. |
| Initialization policy is `0` | Skip this startup command; initialization is delegated to an earlier/platform path. |

The PCD default is `1`. Its declaration explicitly allows a platform to skip initialization when it was performed earlier, such as by a chipset SRTM solution.

The [TPM Library architecture][tpm-startup-architecture] defines startup as the transition into operational state, with behavior depending on the startup type and preceding shutdown. `Startup(CLEAR)` and `Startup(STATE)` express different boot-state requirements. `TPM_SU_CLEAR` is a startup parameter; it is not an invocation of the separate `TPM2_Clear` command.

### The command packet is a big-endian byte stream

[`Tpm2Startup()`][startup-command] constructs a packed header and startup parameter, converts the multi-byte fields to big endian, and submits the bytes through `Tpm2DeviceLib`.

```text TPM2_Startup(TPM_SU_CLEAR) · command bytes
80 01  00 00 00 0C  00 00 01 44  00 00
-----  -----------  -----------  -----
 tag      size       command     type
```

| Field | Value |
| --- | --- |
| Tag | `TPM_ST_NO_SESSIONS`, `0x8001` |
| Total size | `12` bytes |
| Command code | `TPM_CC_Startup`, `0x00000144` |
| Startup type | `TPM_SU_CLEAR`, `0x0000`; `TPM_SU_STATE` is `0x0001`. |

The wrapper examines the returned TPM response code after transport success. It maps `TPM_RC_SUCCESS` to `EFI_SUCCESS` and also accepts `TPM_RC_INITIALIZE`, accommodating the already-initialized case. Other response codes become `EFI_DEVICE_ERROR`.

Thus an `EFI_SUCCESS` return from this wrapper does not necessarily mean a fresh startup was performed at that call site. The mapping deliberately includes an already-started TPM state.

### S3 recovery changes the measurement history

S3 resume follows its own path. The PEIM skips the ordinary shadow/firmware-measurement sequence and does not request the optional self-test.

If `Startup(STATE)` fails but `Startup(CLEAR)` succeeds, the initializer emits error separator measurements into **PCRs 0 through 7**. The event payload is the 32-bit value `1`; the measurement path hashes that payload before extension. It is not a direct write of the integer `1` into the PCR.

[`Tcg2PlatformPei`][platform-pei] also registers an end-of-PEI callback on S3 resume. The callback looks for the event HOBs used by this recovery path and invokes platform-hierarchy configuration when the error was recorded. A successful fallback startup therefore continues with an explicit error history and additional platform handling.

## A PCR accumulates a measurement history

A Platform Configuration Register holds a fixed-size digest inside the TPM. Its address has two parts: a **bank algorithm and a PCR index**. SHA-256 PCR 7 and SHA-1 PCR 7 are separate registers. Neither contains a filename, an executable, a certificate database or a list of prior measurements.

In the ordinary EDK II path, firmware first computes an event digest from selected bytes. It then submits that digest to the TPM, which extends the addressed PCR:

```text Ordinary measurement and extension · H is the bank's hash
eventDigest = H(measuredByteStream)
PCRnext     = H(PCRprevious || eventDigest)
```

There are two hash operations here. Hashing a firmware volume produces an input to the extend operation; extending hashes that input together with the old PCR. A final PCR value therefore differs from the hash of the last image measured into it.

Order matters within each PCR. Measuring A and then B normally produces a different result from measuring B and then A. Events directed to PCR 4 do not alter PCR 7, although a log can interleave records for both. Repetition also matters: extending an identical digest twice advances the register twice.

The [PEI hash router][hash-router] and the [TPM integrity command library][integrity-commands] expose the division of work. `Tpm2PcrExtend()` serializes a list of algorithm-tagged digests into a `TPM2_PCR_Extend` command. `TPM2_PCR_Event` is a separate command that can hash supplied event bytes inside the TPM; the ordinary router path does not use it to send firmware images to the TPM.

### Initialization, reset and locality affect replay

PCR reset behavior comes from the TPM's platform profile and PCR attributes. Static boot PCRs cannot be arbitrarily cleared by ordinary OS software midway through a boot. Debug, application and dynamic-launch PCRs have different reset rules. `TPM2_PCR_Reset` is therefore not a general instruction for returning any PCR to zero.

For ordinary cold-boot measurements, PCR 0 starts at zero, but a replay engine must use the actual startup state. The [TPM Library architecture][tpm-startup-architecture] sets PCR 0 to the locality that sent `TPM2_Startup(TPM_SU_CLEAR)`, as a digest-sized integer. A startup from locality 3 therefore leaves every bank's PCR 0 at `00…03` before the first extend. An H-CRTM sequence before startup takes precedence: the TPM sets PCR 0 to `00…04` and immediately extends the hashed H-CRTM data, so `PCR[0] = H(00…04 || H(hash_data))`. Resume and dynamic launch have their own rules, which the same document defines along with the role of PCR attributes.

EDK II preserves startup context through `gTpm2StartupLocalityHobGuid`. `SetupEventLog()` can convert that HOB into a `StartupLocality` information event, the [PC firmware profile's][pc-firmware-profile] `TCG_EfiStartupLocalityEvent`: the 16-byte signature `StartupLocality\0` followed by one byte, `0`, `3` or `4` for an H-CRTM. The profile requires it when the value is 3 or 4 and places it before any event that extends PCR 0. It tells a replay engine which starting value to use; it is not itself a digest to extend. Initializing every PCR to zero and extending every log record is wrong on these platforms.

### A PCR selection also identifies its bank

`Tpm2PcrRead()` accepts a `TPML_PCR_SELECTION`. Each selection contains an algorithm identifier and a bitmap of PCR indices. In a three-byte bitmap, bit 7 of the first byte selects PCR 7, and bit 3 of the second byte selects PCR 11:

```text Example selection · SHA-256 PCRs 7 and 11
algorithm identifier = 0x000B
sizeofSelect          = 3
pcrSelect             = 80 08 00
```

That bitmap has no expected digest values in it. It identifies registers to read or include in an operation. Policy evaluation additionally needs the relevant state and authorization conditions. The response to a PCR read includes the returned selection, values and a PCR update counter; code should interpret those returned fields rather than assume every requested value fit into one response.

## PCR-bank policy is part of initialization

TPM 2.0 maintains Platform Configuration Registers (PCRs) in banks associated with different hash algorithms. A supported bank, an allocated active bank and a hashing implementation linked into firmware are separate pieces of state.

`SyncPcrAllocationsAndPcrMask()` queries the TPM's supported and active PCR selections, then reconciles them with two firmware masks:

| Value | Meaning in this path |
| --- | --- |
| TPM supported algorithms | Bank capability reported by the device. |
| TPM active banks | Banks currently allocated with PCR selections. |
| `PcdTpm2HashMask` | Platform measurement-mask policy. A zero value is initialized from the active banks. |
| `PcdTcg2HashAlgorithmBitmap` | Algorithms available to the firmware hashing implementation. |

When active banks exceed the platform mask or firmware hashing support, the helper derives a reduced set:

```text PCR-bank reconciliation · reduced expression
newActive = activeBanks & platformMask & firmwareHashAlgorithms
```

For a nonempty reduced set, it requests `Tpm2PcrAllocateBanks()` and calls `ResetCold()`. Allocation changes require a subsequent boot. The helper also removes unsupported algorithms from the platform mask.

The OVMF [PEI component configuration][ovmf-pei-components] links a hash router and separate hash instances into `Tcg2Pei`. That build-time composition contributes to the bitmap the policy code consumes. Device capability alone is insufficient to establish which measurements this firmware can produce.

The helper returns `VOID`; capability and allocation failures have debug/assertion handling rather than a status returned directly to `PeimEntryMA()`. This matters when interpreting a boot trace: the initialization marker must be read alongside the bank configuration, command responses and build's assertion behavior.

### Self-test has its own policy and result contract

On a non-S3 boot, `PcdTpm2SelfTestPolicy == 1` causes `Tpm2SelfTest(NO)`. The `NO` parameter requests testing of functions not previously tested, rather than an unconditional full retest. A platform can suppress this call through policy, including configurations where testing was already performed.

There is also a return-value detail in this revision: [`Tpm2SelfTest()`][self-test-command] constructs and submits the command but returns the transport status without separately decoding `Res.Header.responseCode`. `Tpm2Startup()` does decode that response code. A successful EFI return must therefore be interpreted according to the specific wrapper, not treated as a uniform certificate of TPM health.

## Initialization markers describe different outcomes

The PEI dependency markers use NULL interfaces. Their presence is the signal; they do not provide a status method.

| Marker or record | Meaning established by the inspected paths |
| --- | --- |
| `gEfiTpmDeviceSelectedGuid` | Platform device selection has completed. |
| `gPeiTpmInitializedPpiGuid` | The initializer reached its successful initialization branch. |
| `gPeiTpmInitializationDonePpiGuid` | The initialization attempt completed, or configuration declared no TPM. This is not a success flag. |
| `gTpmErrorHobGuid` | An error was recorded for later consumers, including DXE. |

In `PeimEntryMA()`, the common error path builds the error HOB and reports an interface error. Its completion path installs the done PPI on both success and failure. The configuration modules also install the done marker when no TPM is selected, because the TPM initializer will not provide it in that case.

This gives dependent modules a way to wait for an outcome without waiting forever for success. The successful-initialization marker, error HOB and selected instance provide the additional context. None of these software records independently authenticates the TPM or proves complete measurement coverage.

## What the PCR indices typically measure

PCR numbers are conventions imposed by a platform and its boot software. The TPM processes a register index and digest; it does not intrinsically recognize that PCR 4 concerns a boot application or that PCR 7 concerns Secure Boot.

For PC Client firmware, the [firmware-profile allocation][pc-firmware-profile] separates code, configuration, boot selection and authorization state. The concrete examples below follow the pinned EDK II implementation. Separators and other phase markers can also enter these registers, so the final value is rarely just one component's hash.

### Firmware-owned PCRs 0 through 7

| PCR | Typical PC firmware category | Measured material and EDK II examples |
| --- | --- | --- |
| **0** | Platform firmware code and root-of-trust history | S-CRTM version and firmware-volume contents. Embedded modules can be covered by their containing measured volume. This is not necessarily a hash of the complete motherboard flash chip. |
| **1** | Platform configuration | Configuration affecting platform behavior. `Tcg2Dxe` measures `BootOrder` and referenced `Boot####` variable data here; processor-location information can also produce events here. |
| **2** | Additional executable code | EFI boot-service and runtime drivers handled by the image-measurement path, including relevant add-in/option-ROM code. Manufacturer-controlled code already covered by a measured firmware volume can follow a different coverage path. |
| **3** | Configuration associated with additional code | Driver/option-ROM configuration and data under the platform's measurement policy. A code event in PCR 2 does not imply a matching configuration event in PCR 3. |
| **4** | Boot application code and boot attempts | PE/COFF boot applications such as Windows Boot Manager, plus actions marking calls to and returns from boot applications. Several attempted boot targets can contribute to one history. |
| **5** | Boot metadata and transition data | The GPT measurement contains the partition header and valid entries. EDK II also records boot-services exit actions here. This is not a recursive digest of ESP files. |
| **6** | Manufacturer-specific state | The current PC firmware profile leaves the purpose to the platform manufacturer. Older descriptions centered on power-state transitions are not a universal modern definition. |
| **7** | Secure Boot policy and image-authority decisions | Configuration variables such as `SecureBoot`, `PK`, `KEK`, `db` and `dbx`, a policy separator, and the authority used to authorize relevant images. Image bytes themselves are measured through the code paths above. |

The ownership and measuring component matter as much as the category. A DXE driver embedded in an already-measured firmware volume need not appear as another PCR 2 image event. [`DxeTpm2MeasureBootHandler()`][image-measurements] consults measured/excluded firmware-volume information and distinguishes applications from drivers. Absence of an individual image event can reflect aggregate coverage; it can also reflect a coverage gap. The containing-volume record and policy resolve the difference.

There is also a documentation-profile difference: the [Windows PCR reference][windows-pcr-profile] labels PCR 6 for S4/S5 resume events, whereas the cited PC firmware profile makes it manufacturer-specific. The table above follows the latter allocation; the platform's actual events determine what entered its PCR 6.

### PCRs 8 through 15 belong to the static OS profile

The firmware profile assigns this range to the static operating-system environment. The meanings below are **Windows' documented native-UEFI usage**, rather than portable names every OS must follow. [Windows PCR descriptions][windows-pcr-profile]

| PCR | Typical Windows description | Interpretation |
| --- | --- | --- |
| **8** | Reserved; initially zero with no normal extends in the documented profile | Other boot stacks can use it. |
| **9** | Reserved; initially zero with no normal extends in the documented profile | Other boot stacks can use it. |
| **10** | Reserved; initially zero with no normal extends in the documented profile | Do not transfer a legacy BIOS PCR-10 boot-manager description to native UEFI. |
| **11** | BitLocker access control | Boot-phase events constrain when a TPM protector can be used. It is not the volume's sector-data hash. |
| **12** | Data and highly volatile events | Boot-stack data events; not a universal hash of one fixed file. |
| **13** | Boot-module details | Details recorded by the Windows boot measurement stack. |
| **14** | Boot authorities | Authorization information recorded by that stack, separate from firmware's PCR 7 policy history. |
| **15** | Reserved in the cited Windows profile | The PC firmware allocation still leaves its use to the static OS. |

For comparison, [GRUB's measurement profile][grub-measured-boot] places commands and kernel command lines in PCR 8 and files read by GRUB in PCR 9. That is a different measuring component with a different policy. “PCR 9 is unused” is meaningful only with the Windows profile qualified as above.

The [BitLocker boot-chain analysis](/bitlocker-tpm-boot-chain) follows the Windows PCR 11 capping implementation and TPM-protector selection. A measurement being present in a PCR does not imply that BitLocker selected that PCR for its protector.

### PCR 16, the dynamic range and PCR 23

| PCR | Profile role | Consequence for analysis |
| --- | --- | --- |
| **16** | Debug | Resettable; unsuitable as a durable record of production boot history. |
| **17–22** | Dynamic root-of-trust measurement range | Dynamic-launch hardware and software define the measurements and locality restrictions. A register can represent launch components, policy or later dynamic-OS state, depending on the selected scheme. |
| **23** | Application support | Resettable application/OS use; interpret it within that application's protocol. |

The [PC firmware profile][pc-firmware-profile] describes PCRs 16 and 23 and leaves PCRs 17–22 to the dynamic OS. The [TCG D-RTM architecture][drtm-pcrs] defines how a dynamic launch uses them: the launch event makes a locality 4 access that resets PCRs 17–22 to zero, and the CPU's first measurement goes into PCR 17. Beyond that, assignments vary between dynamic-launch implementations. For a concrete Windows-related example, Microsoft describes [SMM protection components and policy being bound to PCR 17 during Secure Launch][secure-launch-measurements].

An unused or reset PCR must also be distinguished from a missing log event: resettable registers, dynamic launch and resume each need their own replay rules.

## The in-memory PEI pass begins firmware measurement

On an ordinary boot, shadowing dispatches `Tcg2Pei` again from memory. `RegisterForShadow()` returning `EFI_ALREADY_STARTED` identifies that pass, and the entry point calls `PeimEntryMP()` instead of repeating device startup.

The in-memory pass installs `gEdkiiTcgPpiGuid`, optionally measures the S-CRTM version, measures the main boot firmware volume, and registers callbacks for additional firmware-volume information and the end of PEI.

The core-root-of-trust version measurement uses PCR 0 and `EV_S_CRTM_VERSION`. Firmware-volume measurement also targets PCR 0 in the inspected path, with event representations selected according to the platform firmware-profile revision. Coverage depends on the platform: the code supports excluded volumes, already-measured volumes and precomputed digests supplied through PPIs.

The regular measurement path connects hashing, TPM state and firmware memory:

```text PEI firmware measurement · ordinary hashing path
MeasureMainBios / firmware-volume notification
  MeasureFvImage
    HashAndExtend
      firmware hash instances compute digests
      Tpm2PcrExtend submits the digest list
    LogHashEvent
      build event HOBs for the DXE handoff
```

The TPM retains the accumulated PCR state. Firmware retains the event records describing the measurements. The log is not a copy of the final PCR value; a verifier replays it from the bank's starting value.

`LogHashEvent()` builds `gTcgEventEntryHobGuid` and `gTcgEvent2EntryHobGuid` records for the applicable log representations. The [hash router][hash-router] supplies the host-side digest computation and PCR extension. The data handed to the TPM is normally the digest list rather than an entire firmware image.

These mechanics also locate the trust boundary: the TPM accumulates the values submitted to it; the platform's measurement code determines which bytes those values describe. The root of trust and measurement coverage must be assessed separately from whether `TPM2_Startup` succeeded.

## The measured bytes and the event description can differ

The measurement API carries two distinct inputs: `DataToHash` with its length, and an event structure containing the description to retain. The digest can describe bytes that are absent from the log. Conversely, some event payloads are the exact serialized bytes that were hashed.

![Firmware hashes selected bytes and extends the resulting digest into a TPM PCR. It separately records the digest and event description in RAM. A consumer replays the digest records, then compares the reconstructed state with a TPM read or authenticated quote.](/images/blog/tpm-pcr-event-log.svg)

### Firmware volumes: contents versus an address-and-length record

In `MeasureFvImage()`, the ordinary path hashes the firmware-volume range at `FvDataBase` for `FvLength` bytes. The associated `EV_EFI_PLATFORM_FIRMWARE_BLOB` or `BLOB2` payload describes a base address, length and, in the newer format, a description.

Hashing that short log payload would hash the location descriptor, not the firmware volume. Recomputing the measurement requires the original measured contents, including the relevant migration or pre-hash path. The code explicitly tracks the original FV address separately from the data address used for measurement.

### EFI images: structured PE/COFF measurement

[`Tcg2MeasurePeImage()`][image-measurements] records the image location, size, link-time base and device path, but passes the image bytes to `HashLogExtendEvent()` with `PE_COFF_IMAGE`. The protocol dispatches that flag to [`MeasurePeImageAndExtend()`][pe-measurement].

That routine hashes a structured PE/COFF byte stream. It skips the optional-header checksum and the Certificate Table directory entry, processes nonempty sections in raw-file order, and excludes certificate content through its remaining-data calculation. Consequently, three values must be kept separate:

- The whole-file hash used to identify a downloaded `.efi` sample.
- The image digest produced by the PE/COFF measurement algorithm.
- The PCR value after that image digest and preceding events have been extended.

The device path in the event payload identifies the image for interpretation. It is not a substitute for the measured image, and hashing the path will not reproduce its PE digest.

### Variables: the wrapper sometimes matters

`Tcg2Dxe.MeasureVariable()` builds a `UEFI_VARIABLE_DATA` payload containing the vendor GUID, name length, data length, UTF-16 variable name and variable data. Name length counts 16-bit characters; data length counts bytes. The serialized name excludes its terminator.

For `EV_EFI_VARIABLE_DRIVER_CONFIG`, the routine hashes that **complete serialized wrapper**. For ordinary boot-variable events, its alternate branch hashes the variable's data bytes while still logging the richer wrapper. Thus the event type changes the relationship between the digest and payload.

Image-authorization events have another producer. [`DxeImageVerificationLib/Measurement.c`][authority-measurement] constructs a variable-data wrapper for the `EFI_SIGNATURE_DATA` authority used to validate an image, then measures and logs the full wrapper into PCR 7 as `EV_EFI_VARIABLE_AUTHORITY`.

An authority event therefore does not mean “hash the entire executable certificate table.” It records a particular authorization input. The image's code measurement and the authority measurement answer different questions.

### GPT and separators have directly reproducible payloads

The GPT producer constructs `EFI_GPT_DATA` from the partition header, the count of valid entries, and those entries. It hashes and logs that constructed data into PCR 5. It does not hash free space, every disk sector or every ESP file.

`MeasureSeparatorEvent()` constructs a four-byte zero value, hashes it and records `EV_SEPARATOR`. The S3 recovery path described earlier uses a four-byte error value of one. A separator advances the PCR; it neither clears the register nor creates an empty boundary outside the hash history.

The important question for reproducing an event is therefore: **which producer selected which byte stream under which event semantics?** A general `H(EventData)` check is valid only for event types whose producer actually hashes that payload.

## DXE exposes capabilities and reconstructs the event log

During DXE, the Driver Execution Environment, [`Tcg2Dxe.DriverEntry()`][tcg2-dxe] checks the selected instance and rejects an existing TPM-error HOB before requesting access through its bound device library. The ordinary startup command belongs to the earlier PEI path; DXE's access request does not imply another startup.

The driver queries manufacturer information, maximum command/response sizes, and supported and active PCR banks. It intersects the exposed algorithm information with firmware hashing support and prepares the `EFI_TCG2_BOOT_SERVICE_CAPABILITY` structure. The protocol version set here is **1.1**, a version of the firmware service interface rather than the TPM command-family number.

When the SHA-1 bank is absent from the exposed active banks, it removes support for the legacy TCG 1.2 event-log format. A TPM 2.0 device can therefore expose the crypto-agile log without the SHA-1 log representation.

[`SetupEventLog()`][log-setup] allocates the DXE log areas and copies the PEI event HOBs into them. For crypto-agile records it filters the PEI digest lists against the exposed active-bank mask and reconstructs their serialized sizes. This is log reconstruction, not a second extension of those PEI measurements. The function also establishes the final-events table.

The driver registers callbacks for later boot measurements. Its Secure Boot callback measures the policy variables and then a PCR 7 separator. On the first ready-to-boot notification, it measures handoff tables and boot variables, records a boot-attempt action in PCR 4, and emits separators for PCRs 0 through 6. Later boot attempts add their own return/call actions. Boot-services exit actions go to PCR 5. This event ordering is concrete source behavior; it should not be replaced by an assumed alphabetical list of measured files.

The resulting [TCG2 protocol][tcg2-protocol] supplies these main operations:

| Operation | Role |
| --- | --- |
| `GetCapability()` | Report firmware/TPM capability information and presence state. |
| `GetEventLog()` | Return log locations, the last entry and truncation state. |
| `HashLogExtendEvent()` | Hash or measure data, extend the relevant TPM state, and optionally record an event. |
| `SubmitCommand()` | Submit a raw TPM command through the selected backend. |
| PCR-bank services | Query active banks and request a bank change through the supported policy path. |

The [TCG EFI Protocol specification][efi-tcg-spec] defines this loader-facing service boundary. It is a boot-services protocol, not the operating system's permanent runtime TPM driver.

## Event logs live outside the TPM

The TPM stores the current PCR digests. Firmware retains the event records in host memory, first as PEI HOBs and then as DXE log buffers. The OS can copy or persist the history after the handoff. The log is not an audit file kept inside the TPM, nor is the TPM responsible for recording which filename supposedly produced an incoming digest.

An ordinary event record identifies the destination PCR, event type, digest or digest list, and event-data length and bytes. Its digest field normally contains the **input digest extended into the PCR**, not the resulting PCR value. The latter must be calculated from the previous state.

The layouts and event-type constants are defined in [`UefiTcgPlatform.h`][event-definitions]. The TCG2 API's `EFI_TCG2_EVENT` input structure is not itself the on-log record layout: the provider computes digests and serializes the chosen log representation.

### The legacy SHA-1 representation

The legacy `TCG_PCR_EVENT` layout is a packed, little-endian header followed by variable-length event data:

```text TCG_PCR_EVENT · serialized record
UINT32 PCRIndex
UINT32 EventType
BYTE   Digest[20]       // SHA-1 input to the extend
UINT32 EventSize
BYTE   Event[EventSize]
```

The fixed header occupies 32 bytes. A TPM 2.0 implementation can expose this representation for compatibility when the relevant SHA-1 bank is available. The format name does not mean the device is using the TPM 1.2 command family.

### The crypto-agile log starts with a different record shape

For the TCG 2 format, EDK II writes a first record using the legacy-shaped header, PCR index 0, type `EV_NO_ACTION` and a zero SHA-1-sized digest field. Its payload is a `TCG_EfiSpecIDEventStruct` carrying the signature `Spec ID Event03`.

That payload describes the platform class, specification version fields, `uintnSize`, the number of algorithms, an `(algorithmId, digestSize)` table and optional vendor information. `uintnSize` encodes the platform's `UINTN` width; it is not permission to reinterpret every subsequent length field as a native pointer-sized integer.

The algorithm table is the bootstrap information for parsing the remaining digest lists. In this implementation, entries can describe SHA-1, SHA-256, SHA-384, SHA-512 and SM3-256 according to active banks. The first record's 20-byte placeholder does **not** prove that SHA-1 is an active bank: its legacy-shaped encoding is used to introduce a crypto-agile log even without SHA-1 measurements.

Subsequent records use the densely packed `TCG_PCR_EVENT2` serialization:

```text TCG_PCR_EVENT2 · wire layout, not sizeof a C structure
UINT32 PCRIndex
UINT32 EventType
UINT32 DigestCount
repeat DigestCount times:
    UINT16 AlgorithmId
    BYTE   Digest[DigestSizeForAlgorithm]
UINT32 EventSize
BYTE   Event[EventSize]
```

The digest length comes from the algorithm's declared size; there is no digest-length integer before each digest in this stream. The next algorithm identifier follows immediately after the preceding digest. Native `TPML_DIGEST_VALUES` storage includes fixed-capacity arrays/unions, so advancing by `sizeof(TCG_PCR_EVENT2)` or by the largest possible digest union skips the wrong bytes.

EDK II's `GetDigestListBinSize()` and `CopyDigestListBinToBuffer()` walk these compact lists explicitly. Log integers are little endian, while TPM command integers are generally big endian. Sharing a TPM command unmarshaller with a log parser without accounting for that difference produces incorrect counts and algorithm IDs.

### Worked record: a separator with two digests

Consider a constructed PCR 7 separator event with SHA-1 and SHA-256 digests for the four bytes `00 00 00 00`. Its complete record occupies **76 bytes**:

| Offset | Size | Value or field |
| --- | --- | --- |
| `0x00` | 4 | PCR index: `07 00 00 00`. |
| `0x04` | 4 | `EV_SEPARATOR`: `04 00 00 00`. |
| `0x08` | 4 | Digest count: `02 00 00 00`. |
| `0x0C` | 2 | SHA-1 algorithm ID: `04 00`. |
| `0x0E` | 20 | SHA-1 digest of the separator payload. |
| `0x22` | 2 | SHA-256 algorithm ID: `0B 00`. |
| `0x24` | 32 | SHA-256 digest of that same payload. |
| `0x44` | 4 | Event-data size: `04 00 00 00`. |
| `0x48` | 4 | Separator payload: `00 00 00 00`. |

```text Calculated event digests · not final PCR values
SHA-1:
9069ca78e7450a285173431b3e52c5c25299e473

SHA-256:
df3f619804a92fdb4057192dc43dd748ea778adc52bc498ce80524c014b81119
```

Even though the payload is zero, both digests are nonzero. Extending the SHA-256 digest into a synthetic all-zero PCR produces `3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969`. A real PCR 7 separator normally follows policy measurements, so its actual resulting PCR value has a different preceding state.

### Information events must not be extended during replay

For ordinary PCR indices, `TcgDxeHashLogExtendEvent()` handles `EV_NO_ACTION` through its logging branch without calling `HashAndExtend()`. Examples include the Spec ID header, startup-locality information and platform-identification records. These records can be essential for interpretation even though they do not advance a PCR.

This is different from an `EV_SEPARATOR`, which is extended. Event names and an apparent digest field are insufficient to decide whether to replay a record; event semantics determine the operation. The inspected helper also has a separate NV-index path for an out-of-PCR-range destination, so its entire behavior should not be reduced to one rule for all possible indices.

### Parsing must preserve the original stream boundaries

A parser needs a bounded buffer and explicit checks for each fixed field, digest count, algorithm ID, digest length and event-data length. Length additions must not wrap, and an unknown algorithm must not be assigned an assumed 32-byte digest. A complete, recognized Spec ID algorithm-size table can support skipping an algorithm the parser cannot evaluate; an unavailable or inconsistent size cannot.

Decoding the nested event data requires another layer of bounds checks. Variable-name counts are not byte counts; device paths contain their own node lengths; a firmware-blob event describes memory not included in the record. Preserve the original bytes and the order of extending events. Normalizing strings or rebuilding a variable wrapper with different padding can change the data whose digest is being checked.

### Common event types provide the interpretation layer

These identifiers come from the pinned [event definitions][event-definitions]. The destination PCR is a separate field; an event type alone does not fix its destination for every producer.

| Type | Value | Typical interpretation |
| --- | --- | --- |
| `EV_NO_ACTION` | `0x00000003` | Log metadata; do not perform an ordinary PCR extend. |
| `EV_SEPARATOR` | `0x00000004` | A measured phase/error marker; it advances the destination PCR. |
| `EV_S_CRTM_VERSION` | `0x00000008` | Root-of-trust version measurement. |
| `EV_TABLE_OF_DEVICES` | `0x0000000B` | Platform configuration; on server-class platforms, `Tcg2Dxe` measures the processors' `EFI_CPU_PHYSICAL_LOCATION` table into PCR 1 with it. |
| `EV_EFI_VARIABLE_DRIVER_CONFIG` | `0x80000001` | Variable configuration, including Secure Boot policy; EDK II hashes its serialized wrapper. |
| `EV_EFI_VARIABLE_BOOT` | `0x80000002` | Boot-variable measurement; distinguish logged wrapper from measured variable data. |
| `EV_EFI_BOOT_SERVICES_APPLICATION` | `0x80000003` | EFI application image measurement; the payload describes the image. |
| `EV_EFI_BOOT_SERVICES_DRIVER` | `0x80000004` | EFI boot-service driver image measurement. |
| `EV_EFI_RUNTIME_SERVICES_DRIVER` | `0x80000005` | EFI runtime driver image measurement. |
| `EV_EFI_GPT_EVENT` | `0x80000006` | Constructed GPT-header/partition-entry measurement. |
| `EV_EFI_ACTION` | `0x80000007` | A measured boot action, such as a boot attempt or boot-services transition. |
| `EV_EFI_PLATFORM_FIRMWARE_BLOB` / `BLOB2` | `0x80000008` / `0x8000000A` | Measured firmware range; payload describes the range. |
| `EV_EFI_VARIABLE_AUTHORITY` | `0x800000E0` | Authority used for an image-authorization decision. |
| `EV_EFI_SPDM_DEVICE_POLICY` | `0x800000E3` | The SPDM device signature database, measured into PCR 7 like a policy variable when SPDM device authentication is enabled. |

## GetEventLog and the final-events handoff

[`Tcg2GetEventLog()`][get-event-log] returns the selected log's starting physical address, the **start of its last record**, and a truncation flag. The last-entry pointer is not a one-past-the-end address or a byte length. To copy through that record, a consumer must parse its complete serialized size. Empty/no-log cases also need to be handled; this implementation can return success with zero addresses when no TPM is exposed.

The call does not stop measurement. In EDK II it marks the requested log format as having been retrieved. Subsequent ordinary log writes also enter the final-events area, published under `gEfiTcg2FinalEventsTableGuid` in the UEFI configuration tables.

```text EFI_TCG2_FINAL_EVENTS_TABLE · crypto-agile representation
UINT64 Version          // 1 in this implementation
UINT64 NumberOfEvents
TCG_PCR_EVENT2 records  // packed variable-length stream
```

This table has its own header. It is not another complete log beginning with a Spec ID record. The algorithm-size context comes from the corresponding main log.

### The overlap starts at the first retrieval

The normal DXE log can continue growing while new events are mirrored to the final-events table. If another component called `GetEventLog()` earlier, some mirrored records can already be present in a later consumer's main-log snapshot:

```text Illustrative handoff · letters denote extending events
Main log copied by loader:      A B C D E
Final-events table at copy:           D E
Final-events table after exit:        D E F G

Combined history:              A B C D E F G
```

Appending the complete final table would extend D and E twice. Ignoring it would omit F and G. The consumer must track the overlap at its snapshot boundary and append only the later suffix. Removing all equal-looking records is also wrong because two identical extending events can be legitimate.

The [Linux TPM event-log documentation][linux-log-handoff] describes its EFI stub saving the final-table size at the snapshot boundary so the kernel can skip that prefix during concatenation. It is a concrete example of the handoff problem, not a claim that Windows uses the same private table layout.

### Extension and logging can fail independently

`EFI_TCG2_EXTEND_ONLY`, flag `0x1`, requests extension without ordinary event logging. Raw TPM submission can also bypass the firmware log. Even without those paths, extension can succeed before the record is stored: `Tcg2HashLogExtendEvent()` documents `EFI_VOLUME_FULL` for a successful extend whose record could not be written to one or more logs.

The pinned DXE implementation attempts bounded dynamic log growth in its logging machinery and tracks truncation. Its final-events handling can append an `EV_NO_ACTION` truncation marker when space runs out. A marker explains incompleteness; it cannot reconstruct a missing extend input.

These conditions mean a readable, syntactically valid log can still be insufficient for replay. Keep the API's truncation result, main/final-table boundary, boot mode and capture time with the bytes being analyzed.

## Replaying a log and interpreting the result

For each bank and PCR, replay maintains one accumulator. It uses the recorded event digests in chronological order, with the correct initial state and event semantics. It does not hash the entire log file or concatenate all event payloads and hash once.

The following reduced example covers **SHA-256 PCR 7 on a normal cold boot**, after the full log has already been decoded and the final-event overlap resolved:

```python Replay only one ordinary PCR · parsing and appraisal omitted
from hashlib import sha256

TPM_ALG_SHA256 = 0x000B
EV_NO_ACTION = 0x00000003

pcr = bytes(32)

for event in decoded_events:
    if event.event_type == EV_NO_ACTION or event.pcr_index != 7:
        continue

    digest = event.digests[TPM_ALG_SHA256]
    if len(digest) != 32:
        raise ValueError("Invalid SHA-256 digest length")

    pcr = sha256(pcr + digest).digest()
```

An absent required digest is an error here, not permission to use a zero digest. A general implementation additionally handles PCR 0 startup context, resume, resettable PCRs, dynamic launch, incomplete logs and the selected platform profile.

### Replay consistency and measurement appraisal are separate checks

There are at least three distinct questions:

| Check | Evidence it establishes |
| --- | --- |
| Replay recorded digests and compare with PCR state | Whether those digest inputs explain that accumulated state. |
| Recompute a digest from the measured object under the producer's rules | Whether an available object matches that event's claimed measurement. |
| Appraise the resulting history against an accepted policy | Whether those versions, authorities, settings and transitions are acceptable. |

Replaying a PE event can succeed without possessing the PE file, because replay consumes its recorded digest. That alone does not authenticate the path string in its descriptive payload. An attacker who can edit only descriptive text may leave digest replay unchanged. A verifier must know which event fields are bound by the measurement and obtain reference objects or trusted manifests where needed.

Likewise, a matching digest history is not a proof that measured code contains no vulnerabilities, that all executable components were measured, or that firmware's measuring code reported honest inputs. Those are root-of-trust, coverage and appraisal questions.

### A PCR read and a quote provide different evidence

A local `TPM2_PCR_Read` supplies current register values. A remote verifier additionally needs an authenticated statement of the selected state and freshness. `TPM2_Quote` signs an attestation structure containing the PCR selection and a digest of selected PCR values, together with caller-supplied qualifying data used for a challenge. The [TPM command specification][tpm-quote] defines that operation; EDK II's [`TPMS_QUOTE_INFO` and `TPMS_ATTEST` definitions][tpm-data-structures] show the separation of selection, digest and attestation context.

A verifier checks the attestation signature and trusted key identity, verifies the challenge and selection, and matches the replay result to the attested PCR digest under the quote's algorithm rules. It then applies its acceptance policy. Signing a quote does not directly sign every byte of the external event log.

The capture point matters: a firmware-only log should not be expected to reproduce PCR values after the OS has added further measurements. Gather the matching history and PCR/quote state, or identify the boundary at which the comparison is valid.

## Following the history into Windows

Firmware's TCG log, Windows' persisted measured-boot files and Windows Event Log diagnostics are related artifacts with different formats. A TPM error event in an `.evtx` file is not itself a `TCG_PCR_EVENT2` record to extend during replay.

Microsoft documents persisted measurement histories under `C:\Windows\Logs\MeasuredBoot` and tools such as `TBSLogGenerator.exe` and `PCPTool.exe` for decoding collected logs. [Windows measured-boot decoding][windows-log-decoding]

[`tpmtool gatherlogs`][tpmtool] can collect an analysis bundle, including available SRTM/DRTM boot and resume artifacts and TPM diagnostics. The following commands illustrate collection into a chosen local folder and inspection of the bank-selection metadata; they are not operations performed for this source analysis:

```powershell Local collection example · does not clear the TPM or change PCR-bank allocation
tpmtool getdeviceinformation
tpmtool gatherlogs C:\TpmResearch

Get-ItemProperty `
    'HKLM:\SYSTEM\CurrentControlSet\Control\IntegrityServices' `
    -Name TPMActivePCRBanks, TPMDigestAlgID
```

`TPMActivePCRBanks` is a bank bitmap; `TPMDigestAlgID` is the algorithm identifier Windows selected. Microsoft documents Windows preferring SHA-256 when firmware/log prerequisites are met, with SHA-1 fallback and a single bank used to continue Windows measurements. [Windows bank selection][windows-pcr-banks]

To diagnose a changed PCR, compare extending events for the same boot mode, bank and PCR in sequence. The first divergent digest often identifies the relevant measurement category; its type and producer identify how to interpret it. Comparing only the two final PCR strings reveals a difference but loses the information needed to explain it.

## Platform policy and ACPI complete the handoff

TPM startup leaves additional platform responsibilities. [`Tcg2PlatformDxe`][platform-dxe] registers a callback for the SMM-ready-to-lock protocol and invokes `ConfigureTpmPlatformHierarchy()` when that boundary is reached.

The [hierarchy library][hierarchy-lib] bound by OVMF chooses its behavior through `PcdRandomizePlatformHierarchy`. One branch changes platform authorization to generated random bytes; the other disables the platform hierarchy. This is platform-hierarchy policy, separate from ordinary PCR extension and from OS provisioning of keys and storage.

### The OS receives a device description

Platform ACPI code describes the TPM for the OS's own driver. The path examined here is `SecurityPkg/Tcg/Tcg2Acpi`; its inclusion and dependencies are platform choices, separate from the OVMF component overrides discussed earlier. In [`Tcg2Acpi.PublishTpm2()`][tcg2-acpi], the cached active interface selects the ACPI start method:

| Regular interface | ACPI start method in this implementation |
| --- | --- |
| FIFO or legacy TIS | `6`, the TIS start method. |
| CRB | `7`, command-response buffer interface. |

For CRB, this code publishes the control area at **`PcdTpmBaseAddress + 0x40`** and configures command and response buffers at **base `+ 0x80`**, each with size **`0xF80`**. The [ACPI definitions][acpi-definitions] also enumerate other start methods; these values describe the regular branches inspected here, not all TPM transports.

This publication is distinct from early firmware detection. PEI uses platform configuration and its bound device library to acquire the TPM. The loader consumes `EFI_TCG2_PROTOCOL` and the measurement log. After the boot-services handoff, the OS uses its native TPM stack and the platform's device description.

An ACPI table therefore shows only that firmware published a description, not that every earlier startup and measurement succeeded.

## Reconstructing the path in a firmware image

The source provides several independent anchors for binary analysis:

| Boundary | Useful source or binary anchors |
| --- | --- |
| Platform selection | Instance GUIDs, setup-variable access, device-selected PPI installation. |
| Device dispatch | PPI dependency expressions and the selected library/provider. |
| Transport | Configured MMIO base, access at `+0x30`, FIFO burst-count loops or CRB control writes. |
| Startup | Big-endian command code `0x00000144`, the boot-mode branch and startup parameter. |
| Measurement policy | PCR capability queries, mask intersections, allocation requests and reset. |
| Phase handoff | TPM error HOB, event HOBs and shadowed PEIM entry. |
| DXE publication | TCG2 protocol installation, capability structure and log allocation. |
| OS discovery | ACPI `TPM2` signature, start method and control-area address. |

The implementation is therefore best understood as a sequence of linked contracts: **select a provider, obtain its transport, establish the appropriate startup state, configure usable measurement banks, record the measured boot, and publish the interfaces for the next boot stage**. The platform bindings and boot mode decide which branches run.

[revision]: https://github.com/tianocore/edk2/commit/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f
[intel-ptt]: https://www.intel.com/content/www/us/en/support/articles/000094205/processors/intel-core-processors.html
[ptp-spec]: https://www.trustedcomputinggroup.org/resources/documents/pc-client-platform-tpm-profile-ptp-specification
[tpm-startup-architecture]: https://medias.cdn.trustedcomputinggroup.org/production/Trusted_Platform_Module_2_0_Library_Part_1_Architecture_Version_185_pub_2a3392dc49.pdf
[efi-tcg-spec]: https://www.trustedcomputinggroup.org/resources/documents/tcg-efi-protocol-specification
[ovmf-libraries]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/OvmfPkg/Include/Dsc/OvmfTpmLibs.dsc.inc
[ovmf-dxe-components]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/OvmfPkg/Include/Dsc/OvmfTpmComponentsDxe.dsc.inc
[ovmf-pei-components]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/OvmfPkg/Include/Dsc/OvmfTpmComponentsPei.dsc.inc
[svsm-shim]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2DeviceLibDTpm/Tpm2PtpSvsmShim.c
[protocol-device-lib]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2DeviceLibTcg2/Tpm2DeviceLibTcg2.c
[instance-guids]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Include/Guid/TpmInstance.h
[generic-config]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Config/Tcg2ConfigPeim.c
[generic-detection]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Config/TpmDetection.c
[ovmf-config]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/OvmfPkg/Tcg/Tcg2Config/Tcg2ConfigPeim.c
[pei-inf]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Pei/Tcg2Pei.inf
[security-pcds]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/SecurityPkg.dec
[device-constructor]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2DeviceLibDTpm/Tpm2DeviceLibDTpmBase.c
[ptp-device]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2DeviceLibDTpm/Tpm2Ptp.c
[ptp-registers]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/MdePkg/Include/IndustryStandard/TpmPtp.h
[tis-device]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2DeviceLibDTpm/Tpm2Tis.c
[tcg2-pei]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Pei/Tcg2Pei.c#L1135
[startup-command]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2CommandLib/Tpm2Startup.c
[self-test-command]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2CommandLib/Tpm2Test.c
[platform-pei]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2PlatformPei/Tcg2PlatformPei.c
[hash-router]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/HashLibBaseCryptoRouter/HashLibBaseCryptoRouterPei.c
[tcg2-dxe]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c#L3039
[tcg2-protocol]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/MdePkg/Include/Protocol/Tcg2Protocol.h
[platform-dxe]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2PlatformDxe/Tcg2PlatformDxe.c
[hierarchy-lib]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/PeiDxeTpmPlatformHierarchyLib/PeiDxeTpmPlatformHierarchyLib.c
[tcg2-acpi]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Acpi/Tcg2Acpi.c
[acpi-definitions]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/MdePkg/Include/IndustryStandard/Tpm2Acpi.h
[pc-firmware-profile]: https://medias.cdn.trustedcomputinggroup.org/production/TCG_PC_Client_Platform_Firmware_Profile_Version_1_06_Revision_52_pub_3_1184065dfc.pdf
[integrity-commands]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/Tpm2CommandLib/Tpm2Integrity.c
[windows-pcr-profile]: https://learn.microsoft.com/en-us/windows/security/operating-system-security/data-protection/bitlocker/configure#configure-tpm-platform-validation-profile-for-native-uefi-firmware-configurations
[image-measurements]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/DxeTpm2MeasureBootLib/DxeTpm2MeasureBootLib.c#L415
[grub-measured-boot]: https://www.gnu.org/software/grub/manual/grub/html_node/Measured-Boot.html
[drtm-pcrs]: https://medias.cdn.trustedcomputinggroup.org/production/TCG_D_RTM_Architecture_v1_0_Published_06172013_d4f30ceb4f.pdf
[secure-launch-measurements]: https://www.microsoft.com/en-us/security/blog/2020/09/01/force-firmware-code-to-be-measured-and-attested-by-secure-launch-on-windows-10/
[pe-measurement]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Dxe/MeasureBootPeCoff.c#L96
[authority-measurement]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Library/DxeImageVerificationLib/Measurement.c#L216
[log-setup]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c#L1822
[event-definitions]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/MdePkg/Include/IndustryStandard/UefiTcgPlatform.h
[get-event-log]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/SecurityPkg/Tcg/Tcg2Dxe/Tcg2Dxe.c#L661
[linux-log-handoff]: https://docs.kernel.org/security/tpm/tpm_event_log.html
[tpm-data-structures]: https://github.com/tianocore/edk2/blob/02c1141a0e5b5c958630e81ac6ebb4b03c1e836f/MdePkg/Include/IndustryStandard/Tpm20.h#L1158
[tpm-quote]: https://medias.cdn.trustedcomputinggroup.org/production/Trusted_Platform_Module_2_0_Library_Part_3_Commands_Version_185_pub_87a5c10230.pdf#page=157
[windows-log-decoding]: https://learn.microsoft.com/en-us/troubleshoot/windows-client/windows-security/decode-measured-boot-logs-to-track-pcr-changes
[tpmtool]: https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/tpmtool
[windows-pcr-banks]: https://learn.microsoft.com/en-us/windows/security/hardware-security/tpm/switch-pcr-banks-on-tpm-2-0-devices

