OP-TEE on Qualcomm Lemans › Development Branches › Discrete TPM (dTPM)

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.

Branch: 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
Secure-world ownership is the designOP-TEE takes the serial engine early in boot, talks TCG SPI-TIS to the chip, and publishes the PTA that the Linux TEE-backed TPM driver binds to. Linux's mainline tpm_tis_spi must stay off (it would contend for the same SE and fail with -110).
ComponentWhereWhat
OP-TEE pull requestOP-TEE/optee_os#8155dTPM support: the TCG PTP SPI (TIS) transport, the Qualcomm GENI SPI fixes, and the passthrough PTA.
Linux pull requestlore.kernel.org · linux-integrityThe tpm_dtpm_tee driver that binds the PTA by UUID and presents /dev/tpm0.
OP-TEE issueOP-TEE/optee_os#8160Locality 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.

ST33 TPM SPI-TIS chip QUP2 SE2 GENI SPI engine gpio 86-89 OP-TEE (secure world) qcom_geni_spi tpm2_spi (TIS) tpm2_chip Passthrough PTA (device-enum) REE-kernel only Linux /dev/tpm0 SPI wire raw TPM2 cmd/resp bytes
The full path. The chip and wire were always fine — every bug lived inside OP-TEE's SPI handling.
LayerWhat it isRole
ST33 TPMSTMicro discrete TPM 2.0, vendor id 0x104AThe security chip. On QUP2 SE2 = spi@888000, 20 MHz, CS0.
QUP2 SE2A Qualcomm GENI serial engine in SPI modeThe SoC's SPI controller for this bus. OP-TEE loads its firmware and drives it.
qcom_geni_spiOP-TEE's GENI SPI driverByte-level transfers, CS control, clocks, interconnect vote.
tpm2_spiTCG PTP SPI (TIS) transportTurns register reads/writes into SPI transactions with wait-state flow control.
tpm2_chipGeneric in-core TPM2 stackStartup, self-test, capabilities, the send/recv API.
Passthrough PTAPseudo Trusted ApplicationLets 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.

TPM core /dev/tpm0 tpm_dtpm_tee .send (sync) TEE shared mem cmd@0 · resp@4096 OP-TEE PTA REE_KERNEL gate tpm2_chip → ST33 SPI-TIS →  command bytes (invoke SUBMIT_COMMAND) ←  response bytes return through the same shared buffer
One command round-trip. The kernel driver is pure transport — it marshals bytes to OP-TEE and back; all the TPM and SPI logic lives in the secure world.
PropertyValue
Filedrivers/char/tpm/tpm_dtpm_tee.c
KconfigTCG_DTPM_TEE — depends on TEE && OPTEE
Binds byTEE device-bus UUID 50568cd9-…-d2ee (matches the PTA)
TransportSUBMIT_COMMAND — memref[0] command in, memref[1] response in/out
Shared memory4 KiB command + 4 KiB response
Chip flagsTPM2 · SYNC (send returns the response in place)
LoginTEE_IOCTL_LOGIN_REE_KERNEL — required by the PTA gate
Bounds checksresponse length validated against header size, 4 KiB cap, and caller buffer
Upstream shapeA single commit (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.
The lesson that cost a reflashThis driver and the PTA's login gate are a pair. The driver first opened with 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 roleOwner / transport onlyMeasurement root — replays log, extends PCRs
Who issues TPM commandsLinux onlySecure world (early) then Linux
PCR state at Linux bootPristine (zero, unless prior firmware touched it)Pre-seeded with the secure-boot measurements
NeedsNothing extraCFG_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.

Two measurers, one chipEnabling it puts the secure world and Linux on the same PCR bank. That is only coherent with a clear handoff: the secure world measures early and stops, Linux continues. Flipping the flag without that discipline gives races and PCR values neither side can explain. For the “Linux gets a working TPM” goal of this series, none of that is needed — which is why it stays off.

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.

The cruxOn a TIS TPM the locality is not a credential the chip verifies — it is just which register page the bus master addresses (on SPI, encoded in the address: 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):

bitnamemeaning
1requestUsewrite 1 → “I want this locality”
3Seizewrite 1 → forcibly take it from a lower locality
5activeLocalityread 1 → you own it; write 1 → release it
7tpmRegValidStsread 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:

  1. Request

    SPI-write requestUse (bit 1) to 0xD44000.
  2. Poll for grant

    SPI-read 0xD44000 until activeLocality (bit 5) reads 1. The TPM grants only once the current owner releases — or a higher locality sets Seize (bit 3) to preempt a lower one.
  3. Transact

    Write the command to the FIFO (0xD44024), drive it via STS (0xD44018), read the response — all on the 0xD44xxx page, so every byte is tagged locality 4.
  4. Release

    SPI-write activeLocality (bit 5) to 0xD44000; the lane is free again.
Why priority is the whole pointArbitration is by number: a higher locality can always seize a lower one, never the reverse. So whoever can address the locality-4 page can always preempt locality 0. The platform must guarantee only the trusted path can reach that page — on SPI there is no chipset address-decode to enforce it, so the guarantee is owning the bus. That is the hardware reason locality 4 equals “trusted hardware.”

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 / dTPMTrusted Platform Module; "discrete" = a real chip, as opposed to a firmware TPM (fTPM).
TCG PTP SPI / TISThe standard register protocol for talking to a TPM over SPI, including the wait-state handshake.
GENI SE / QUPQualcomm's generic serial engine; a configurable block that can be SPI, I2C, UART, etc.
NoC / interconnectThe on-chip network connecting blocks to memory and peripherals; bandwidth on it must be requested.
BCM / RPMh / cmd_dbBus Clock Manager, the resource-power-manager interface, and the command database used to place interconnect/clock votes.
CSChip Select: the SPI line that frames a transaction; must stay asserted across a multi-transfer TIS exchange.
FRAGMENTATIONA GENI command parameter bit meaning "this is one fragment of a larger transaction — keep CS asserted."
PTAPseudo Trusted Application: in-core OP-TEE service the normal world can call.
IMSG / DMSG / FMSG / EMSGOP-TEE log levels: Info, Debug, Flow (trace), Error.