Discrete TPM (dTPM) integration
A discrete TPM 2.0 (STMicro ST33, SPI-TIS) on QUP2 SE2, owned by the secure world (OP-TEE) and exposed to the normal world through a passthrough pseudo-TA. Linux gets a standard /dev/tpm0 and never touches the SPI bus.
features/lemans/security/dtpm — a feature branch of
qcom-buildroot. It repoints the Linux
and OP-TEE manifest revisions to the trees carrying the dTPM transport, the passthrough PTA and the
Lemans SPI/TPM enablement.
Building it
Initialize the workspace against the features/lemans/security/dtpm branch of the build manifest, then build and flash exactly as in the Quick Start.
mkdir sandbox.lemans && cd sandbox.lemans
repo init -u https://github.com/qualcomm-linux/qcom-buildroot \
-b features/lemans/security/dtpm -m manifests/lemans.xml
repo sync -j4 --no-clone-bundle
cd build
make -j8 toolchains
make -j8 all # OP-TEE (TPM2 SPI + passthrough PTA) + kernel (optee-dtpm)
# package and flash as usual — see Quick Start for the full procedure
make bootimage # tz.mbn + uefi.elf (OP-TEE brings the TPM up)
make efi # efi.bin (kernel with the optee-dtpm driver)
make flash-bootimage
make flash-uki
tpm_tis_spi must stay off (it would contend for the same SE and fail with -110).Upstream links
| Component | Where | What |
|---|---|---|
| OP-TEE pull request | OP-TEE/optee_os#8155 | dTPM support: the TCG PTP SPI (TIS) transport, the Qualcomm GENI SPI fixes, and the passthrough PTA. |
| Linux pull request | lore.kernel.org · linux-integrity | The tpm_dtpm_tee driver that binds the PTA by UUID and presents /dev/tpm0. |
| OP-TEE issue | OP-TEE/optee_os#8160 | Locality support on dTPM — driving TIS locality 4 from the secure world (design follow-up, detailed below). |
The goal & the stack
A discrete TPM is physically wired to the SoC's SPI bus. We want the secure world (OP-TEE) to own it, and the normal world (Linux) to reach it through a thin pass-through.
Unlike the usual arrangement where Linux drives the TPM directly, here OP-TEE takes the serial engine (SE) early in boot, talks TCG SPI-TIS to the chip, and publishes a pseudo-TA that the Linux TEE-backed TPM driver binds to. Everything the TPM sees therefore flows through OP-TEE's own SPI driver.
| Layer | What it is | Role |
|---|---|---|
| ST33 TPM | STMicro discrete TPM 2.0, vendor id 0x104A | The security chip. On QUP2 SE2 = spi@888000, 20 MHz, CS0. |
| QUP2 SE2 | A Qualcomm GENI serial engine in SPI mode | The SoC's SPI controller for this bus. OP-TEE loads its firmware and drives it. |
qcom_geni_spi | OP-TEE's GENI SPI driver | Byte-level transfers, CS control, clocks, interconnect vote. |
tpm2_spi | TCG PTP SPI (TIS) transport | Turns register reads/writes into SPI transactions with wait-state flow control. |
tpm2_chip | Generic in-core TPM2 stack | Startup, self-test, capabilities, the send/recv API. |
| Passthrough PTA | Pseudo Trusted Application | Lets the Linux kernel pipe raw TPM2 commands in; REE-kernel only. |
The kernel side
The normal world reaches the TPM through one small Linux driver, tpm_dtpm_tee, that binds to the OP-TEE pass-through TA and presents an ordinary /dev/tpm0.
It is the deliberate sibling of the upstream firmware-TPM driver tpm_ftpm_tee: the same TEE-client plumbing and the same SUBMIT_COMMAND wire shape, but a different UUID — because this TPM is a real chip behind OP-TEE, not software running inside it. The kernel never touches the SPI bus; it hands command bytes to OP-TEE through shared memory and gets the response back the same way.
| Property | Value |
|---|---|
| File | drivers/char/tpm/tpm_dtpm_tee.c |
| Kconfig | TCG_DTPM_TEE — depends on TEE && OPTEE |
| Binds by | TEE device-bus UUID 50568cd9-…-d2ee (matches the PTA) |
| Transport | SUBMIT_COMMAND — memref[0] command in, memref[1] response in/out |
| Shared memory | 4 KiB command + 4 KiB response |
| Chip flags | TPM2 · SYNC (send returns the response in place) |
| Login | TEE_IOCTL_LOGIN_REE_KERNEL — required by the PTA gate |
| Bounds checks | response length validated against header size, 4 KiB cap, and caller buffer |
c611cd4c9), modeled on the accepted tpm_ftpm_tee driver: match/probe/remove/id_table, goto-based error unwind, kernel-doc, and a tight two-paragraph message. The REE_KERNEL login is part of it from the first line, so there is no introduce-then-fix seam.LOGIN_PUBLIC; after the PTA was hardened to require REE_KERNEL, the probe failed with ACCESS_DENIED until the driver was changed to match. Change the gate and its one caller together.Who drives the TPM — and what the event log would change
In this series OP-TEE owns and transports the chip; Linux runs the TPM software stack. The secure world does not measure anything into the TPM — which is exactly why the TCG event-log path is left off.
Today: OP-TEE owns, Linux measures
OP-TEE brings the serial engine up, registers the chip, and exposes it through the pass-through PTA. That is the whole of its involvement: it never issues a TPM command of its own. Every TPM2_Startup, PCR extend, quote, or key operation originates in Linux — IMA, trusted keys, measured boot, tpm2-tools — and travels as opaque command bytes through the PTA. From the chip's point of view there is one master of TPM semantics: the normal world.
Because of that, the in-core TCG PCR provider (core/kernel/tcg.c, bridged by tpm2_tcg.c) has no job here. It is gated behind CFG_CORE_TPM_EVENT_LOG, which expects earlier firmware to have performed measured boot and handed OP-TEE a crypto-agile event log at a fixed address to replay. The Lemans EVK has no such log, so the provider is off — and commit 219fe54c9 makes chip registration succeed without it: the stub returns NOT_IMPLEMENTED, which is read as “no provider built in,” not as a failure.
If the event log were enabled
Turning on CFG_CORE_TPM_EVENT_LOG (and with it CFG_CORE_TCG_PROVIDER) changes OP-TEE's role from transport to measurement root. OP-TEE would parse the firmware event log, replay it into the TPM — extending PCRs from the secure world — and continue the boot-measurement chain before Linux ever runs. The bridge (tpm2_tcg.c) that plugs our chip into that framework is already present (restored by the revert); only the config flags and an actual event log are missing.
| This series (event log off) | Event log on | |
|---|---|---|
| OP-TEE's role | Owner / transport only | Measurement root — replays log, extends PCRs |
| Who issues TPM commands | Linux only | Secure world (early) then Linux |
| PCR state at Linux boot | Pristine (zero, unless prior firmware touched it) | Pre-seeded with the secure-boot measurements |
| Needs | Nothing extra | CFG_CORE_TPM_EVENT_LOG + a firmware event log at a fixed PA |
What it would mean for Linux
Linux would still reach the same /dev/tpm0 through the same PTA — the driver and wire format do not change. What changes is what the PCRs already contain: instead of starting from zero, the lower PCRs would hold the measurements the secure world replayed, and Linux's own IMA / measured-boot extends would build on that chain rather than beginning it. That is the point — a root of trust for measurement anchored before the normal world.
OP-TEE issue — locality 4
Filed as OP-TEE/optee_os#8160: why the TPM lives on a bus the secure world owns, and why that ownership is what makes TIS locality 4 meaningful.
0xD4_0000 + locality×0x1000). So locality 4 only means “trusted hardware” if nothing untrusted can reach that page. A secure-owned bus is exactly that guarantee: OP-TEE is the sole master, pins Linux to locality 0, and keeps the higher localities for itself.Primer: what a locality is, and how one is requested
A locality is the TPM's notion of who is asking — a privilege level (0–4, higher = more trusted) attached to every command. One physical chip, five privilege lanes. The TPM never authenticates it: it believes whatever the bus presents, so the platform must ensure only the right software can reach each lane. Two mechanisms combine.
1 · Addressing selects the lane. Every locality has its own copy of the TIS registers — same offsets, a different 4 KB page. On SPI the page is just address bits [15:12]:
register for locality L = 0xD4_0000 | (L << 12) | offset
ACCESS (off 0x00) loc 0 -> 0xD40000 loc 4 -> 0xD44000
STS (off 0x18) loc 0 -> 0xD40018 loc 4 -> 0xD44018
FIFO (off 0x24) loc 0 -> 0xD40024 loc 4 -> 0xD44024
The current TIS backend hard-codes L=0 (the 0xD40xxx page). Driving locality 4 means forming the address with L=4.
2 · The TPM_ACCESS register is the request/grant handshake. Only one locality owns the TPM at a time; ownership is arbitrated through ACCESS (offset 0x00 of each page):
| bit | name | meaning |
|---|---|---|
| 1 | requestUse | write 1 → “I want this locality” |
| 3 | Seize | write 1 → forcibly take it from a lower locality |
| 5 | activeLocality | read 1 → you own it; write 1 → release it |
| 7 | tpmRegValidSts | read 1 → register contents are valid |
So “requesting a locality” is not a pin or a signal — it is this register conversation, addressed to the page you want. To run a command at locality 4:
Request
SPI-writerequestUse(bit 1) to0xD44000.Poll for grant
SPI-read0xD44000untilactiveLocality(bit 5) reads 1. The TPM grants only once the current owner releases — or a higher locality setsSeize(bit 3) to preempt a lower one.Transact
Write the command to the FIFO (0xD44024), drive it viaSTS(0xD44018), read the response — all on the0xD44xxxpage, so every byte is tagged locality 4.Release
SPI-writeactiveLocality(bit 5) to0xD44000; the lane is free again.
The filed issue
The text as filed on #8160:
## Summary
Add first-class support in OP-TEE for a *discrete* TPM 2.0 that lives on a bus
the secure world owns exclusively, exposed to the normal world through a
passthrough pseudo-TA. The transport (TCG PTP / TIS over SPI) and the PTA are in
PR #8155. This issue covers the design rationale and one capability the design
depends on: letting the secure world drive the TPM at an elevated TIS
**locality**, specifically **locality 4**.
## Use case
- A real TPM chip (e.g. ST33, SPI-TIS) is wired to a serial engine only OP-TEE
can program. Linux has no path to the controller.
- OP-TEE brings the engine up, registers the chip, and relays opaque TPM2
command bytes for the normal world through a passthrough PTA.
- Linux presents a standard `/dev/tpm0` and runs the full TPM stack (IMA,
trusted keys, measured boot, tpm2-tools) unchanged.
- Net: the normal world gets a working hardware TPM but never touches the wire;
every byte is mediated by the secure world.
## Why the bus must be owned by OP-TEE
Localities only mean something if the entity asserting them is trusted. On a TIS
TPM the locality is not a credential the TPM verifies — it is simply which
register page the master addresses (on SPI the locality is encoded in the
register address, `0xD4_0000 + locality*0x1000`). The TPM trusts that the
platform only lets privileged software reach the privileged pages.
If the normal world drives the controller, that assumption is gone: whoever
programs the SPI master picks the locality, so locality 4 is worthless as a
trust signal — a compromised REE could assert it.
Making the bus secure-only restores the guarantee in software:
- OP-TEE is the *sole* SPI master, so it is the sole arbiter of which locality
reaches the wire.
- The passthrough PTA pins the normal world to locality 0 while OP-TEE reserves
the higher localities for itself.
- Locality 4 regains its meaning — "the trusted-hardware path" —
because nothing outside the secure world can physically assert it.
## What locality 4 is
TIS defines five localities (0-4), each a separate register page, as a
privilege/source indicator; higher is more privileged.
- **0** - legacy / normal OS and applications.
- **1-2** - OS / dynamically-launched trusted OS (D-RTM runtime).
- **3** - auxiliary components (ACM).
- **4** - *trusted hardware*, the highest. It is the only locality allowed to
drive the `_TPM_Hash_Start` / `_TPM_Hash_Data` / `_TPM_Hash_End` sequence that
establishes a Dynamic Root of Trust for Measurement, and it is the reset
authority for the D-RTM PCRs (PCR 17 is resettable only from locality 4).
Objects and NV indices can be bound to a locality via `TPM2_PolicyLocality`,
so a secret sealed to locality 4 is unsealable only from locality 4.
## Why we want locality 4 here
With a secure-owned bus, OP-TEE can legitimately be that trusted-hardware
component:
- **Measurement root.** OP-TEE can reset/extend the D-RTM PCRs and anchor
measured boot in PCRs the normal world can neither reset nor forge.
- **Sealing to the secure world.** Secrets sealed to locality 4 can only be
unsealed by OP-TEE, even though the chip is shared with Linux.
- **Tamper-evident split.** Linux (locality 0) and OP-TEE (locality 1 and up,
through 4) share one chip, but the normal world cannot spoof or replay the
secure world's measurements because it cannot reach the privileged localities.
None of this is possible on a normal-world-driven TPM; it is a direct
consequence of the secure world owning the transport.
Scope of the "need for locality 4": only D-RTM (the `_TPM_Hash_*` sequence and
PCR-17 reset) strictly requires locality 4. The sealing / secure-NV /
measurement-root cases only need *a locality the normal world cannot assert* --
locality 4 is simply the natural choice. Either way these privileged commands
are ones OP-TEE constructs and issues itself; the passthrough path is never run
above locality 0, so the normal world cannot borrow a privileged locality by
submitting a command through the PTA.
## Ask
- Extend the OP-TEE TIS/TPM2 stack to select the TIS locality (request /
relinquish, address encoding) instead of assuming locality 0.
- Let the secure world drive locality 4 and reserve the higher localities from
the passthrough path, keeping the normal world on locality 0.
- Decide how this should surface: a build/config option, a per-chip property, or
a PTA-level policy for which localities the normal world may use.
## References
- OP-TEE transport + passthrough PTA: OP-TEE/optee_os#8155
- Linux TEE driver (`tpm_dtpm_tee`):
https://lore.kernel.org/linux-integrity/20261026163259.1900439-1-jorge.ramirez@oss.qualcomm.com/
Verification
Secure-world and normal-world, both confirmed on hardware.
OP-TEE boot (secure world)
I/TC: QUP SPI 15: voted BCM 'QUP2' (addr=0x50078 data=0x60004001)
I/TC: QUP SPI 15: fw loaded (protocol=1 fw_version=0xb02 cfg_version=0x9)
I/TC: TPM2 SPI: TPM detected, DID_VID=0x104a
I/TC: TPM2 SPI: ACCESS ... (VALID=1)
I/TC: TPM2 SPI chip registered
Linux (normal world)
# ls -la /dev/tpm*
crw------- 1 root root 10, 224 /dev/tpm0
crw------- 1 root root 249, 65536 /dev/tpmrm0
# cat /sys/class/tpm/tpm0/tpm_version_major
2
End to endBoth device nodes present, resource-manager too, reporting TPM 2.0. ST33 ⇄ GENI SE ⇄ OP-TEE ⇄ PTA ⇄ Linux tpm0.
Userspace: tpm2-tools
The Buildroot rootfs ships the tpm2-tools package (BR2_PACKAGE_TPM2_TOOLS in lemans.mk, also wired as the tpm qcom-test), so the chip can be driven from the target over the in-kernel resource manager (/dev/tpmrm0, the default TCTI). A few checks that put real TPM2 traffic through the stack:
# identity — manufacturer reads back as STM, plus firmware and buffer sizes
tpm2_getcap properties-fixed
# full self-test of every implemented algorithm (returns 0, silent == pass)
tpm2_selftest -f
# hardware RNG — 16 bytes from the chip
tpm2_getrandom 16 --hex
# read the SHA-256 PCR bank (the measured-boot state)
tpm2_pcrread sha256
# extend PCR 23 and confirm it changed — a write round-trip
tpm2_pcrextend 23:sha256=$(printf 'test' | sha256sum | cut -d' ' -f1)
tpm2_pcrread sha256:23
# create a primary key under the owner hierarchy — a heavier round-trip
tpm2_createprimary -C o -c /tmp/primary.ctx
Reading itA clean tpm2_getcap and a silent tpm2_selftest -f prove the whole path — ST33 ⇄ GENI SE ⇄ OP-TEE ⇄ PTA ⇄ tpm0 — carries real TPM2 commands, not merely that the device node exists. tpm2-tools talks to /dev/tpmrm0 by default; point it at the raw chip with -T device:/dev/tpm0 if you need to bypass the kernel RM.
Glossary
| TPM / dTPM | Trusted Platform Module; "discrete" = a real chip, as opposed to a firmware TPM (fTPM). |
| TCG PTP SPI / TIS | The standard register protocol for talking to a TPM over SPI, including the wait-state handshake. |
| GENI SE / QUP | Qualcomm's generic serial engine; a configurable block that can be SPI, I2C, UART, etc. |
| NoC / interconnect | The on-chip network connecting blocks to memory and peripherals; bandwidth on it must be requested. |
| BCM / RPMh / cmd_db | Bus Clock Manager, the resource-power-manager interface, and the command database used to place interconnect/clock votes. |
| CS | Chip Select: the SPI line that frames a transaction; must stay asserted across a multi-transfer TIS exchange. |
| FRAGMENTATION | A GENI command parameter bit meaning "this is one fragment of a larger transaction — keep CS asserted." |
| PTA | Pseudo Trusted Application: in-core OP-TEE service the normal world can call. |
| IMSG / DMSG / FMSG / EMSG | OP-TEE log levels: Info, Debug, Flow (trace), Error. |