Add wolfTPM fwTPM on AMD Zynq-7000 Cortex-A9 (ZC702) with wolfCrypt SRAM PUF - #7
Conversation
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Adds a bare-metal wolfTPM fwTPM server port for AMD Zynq-7000 (ZC702) Cortex-A9, including a sibling “hello” bring-up image and host-side UART tooling, with optional QSPI-backed NV storage and an SRAM-PUF–derived NV integrity key.
Changes:
- Introduces Zynq-7000 bare-metal HAL (startup, MMU, UART, timers, newlib retarget) plus a hello-world heartbeat image.
- Adds fwTPM firmware (UART swtpm/mssim framing), SRAM PUF integration (incl. synthetic selftest), and NV backends (RAM default, QSPI optional).
- Adds host-side Python utilities (swtpm TCP↔UART bridge, UART functional test, NV persistence test) and documentation.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| Xilinx/fwtpm-zc702-a9/firmware/hello/zynq7000-ddr.ld | Adds DDR linker script for hello-world image. |
| Xilinx/fwtpm-zc702-a9/firmware/hello/main.c | Implements UART banner + global-timer heartbeat sanity test. |
| Xilinx/fwtpm-zc702-a9/firmware/hello/Makefile | Builds the hello-world image with the common HAL. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/zynq7000-fwtpm.ld | Adds DDR linker script sized for fwTPM (larger heap/stack). |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/user_settings.h | Defines wolfSSL/wolfTPM build configuration for bare-metal + PUF/MemUse. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/main.c | Implements fwTPM init, self-test, and UART swtpm/mssim command server. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/swtpm_uart_bridge.py | Adds TCP mssim ↔ UART bridge for existing wolfTPM swtpm clients. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_uart_test.py | Adds direct UART functional test client (caps/PCR/random). |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_nv_persist_test.py | Adds TPM NV persistence test over UART. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf_selftest.c | Adds on-target synthetic SRAM PUF regression (build-time opt-in). |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.h | Declares PUF init/key hook + helper persistence seam. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.c | Implements OCM SRAM PUF enroll/reconstruct and integrity-key derivation. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_ram.c | Implements volatile RAM-backed NV HAL. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_qspi.c | Implements QSPI-backed NV HAL + persistent PUF helper store. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_clock_zynq.c | Implements fwTPM clock HAL + sleep shim + entropy timer. |
| Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/Makefile | Builds fwTPM image from sibling wolfSSL/wolfTPM source trees. |
| Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.h | Declares minimal polled Cadence UART API. |
| Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.c | Implements polled Cadence UART driver (+ optional baud configuration). |
| Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.h | Declares Global Timer + PMU cycle-counter timing API. |
| Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.c | Implements Global Timer ticks/ms/delays and PMU cycle counter enable/read. |
| Xilinx/fwtpm-zc702-a9/firmware/common/zynq7000.h | Adds Zynq-7000 address/clock definitions (UART, timers, QSPI, OCM). |
| Xilinx/fwtpm-zc702-a9/firmware/common/startup.S | Adds bare-metal A9 reset/startup: vectors, stacks, cache/MMU/VFP, BSS, main. |
| Xilinx/fwtpm-zc702-a9/firmware/common/retarget.c | Adds newlib syscall stubs (stdio→UART, _sbrk heap, gettimeofday). |
| Xilinx/fwtpm-zc702-a9/firmware/common/mmu.c | Adds minimal 1MB-section MMU mapping enabling Normal DDR for printf correctness. |
| Xilinx/fwtpm-zc702-a9/README.md | Adds ZC702 fwTPM build/run docs and design notes. |
| Xilinx/fwtpm-zc702-a9/.gitignore | Ignores build artifacts and Python cache files for the new port. |
| README.md | Links the new ZC702 port from the repo root README. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 31 changed files in this pull request and generated no new comments.
Suppressed comments (4)
Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.c:136
- In the enroll path, the return value of fwtpm_puf_helper_store() is ignored, so a flash/program failure can silently proceed with a derived NV integrity key that won’t be reconstructible on the next boot (breaking persistent NV and key stability). Propagate helper-store failures as an error and only report/enroll-success when the helper data is actually persisted.
if (ret == 0) {
(void)fwtpm_puf_helper_store(helper, (unsigned int)WC_PUF_HELPER_BYTES,
(unsigned int)WC_PUF_PROFILE_ID);
}
didEnroll = 1;
Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.c:148
- wc_PufDeriveKey() is passed sizeof(PUF_INFO), which includes the terminating NUL from the string literal. Using the NUL in HKDF info is easy to do accidentally and can lead to subtle mismatches if other implementations/tests use strlen() semantics. Prefer using (sizeof(PUF_INFO) - 1) to make the info length unambiguous.
if (ret == 0) {
ret = wc_PufDeriveKey(&ctx, PUF_INFO, (word32)sizeof(PUF_INFO),
g_pufKey, sizeof(g_pufKey));
Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_qspi.c:167
- This QSPI RX wait loop has no timeout. If the controller is misconfigured or the flash doesn’t respond, the firmware can hang forever inside qspi_xfer4(), potentially wedging the TPM server. Consider adding a bounded wait using zynq_millis() (and propagating an error up) similar to the WIP poll timeout used for erase/program.
while (!(QSPI_ISR & ISR_RXNEMPTY)) {
/* wait for the RX word */
}
Xilinx/fwtpm-zc702-a9/firmware/bench/zynq7000-ddr.ld:5
- This linker script header still says it is for the "hello-world" image, but it lives under firmware/bench and is used for the benchmark target. Updating the comment avoids confusion when grepping for the right linker script.
* Linker script for the Zynq-7000 Cortex-A9 hello-world image, linked into DDR
* at 0x04000000 (the FSBL brings DDR up via ps7_init; we load over the top with
* JTAG). Vector table first so VBAR lands on it.
Summary
A bare-metal firmware-TPM 2.0 server (wolfTPM
fwtpm) running on a single Cortex-A9 of an AMD Zynq-7000 (ZC702), driven from a host over UART with the same raw swtpm + Microsoft-simulator ("mssim") framing as the existing STM32H5 and Mi-V ports, so the stock wolfTPM swtpm client drives it unmodified.Distinguishing feature: PUF-derived NV integrity key
The TPM NV-journal integrity key is a device-unique key derived from the Cortex-A9 on-chip-memory (OCM) SRAM power-on state, using wolfCrypt's configurable SRAM PUF (a BCH(127,k,t) fuzzy extractor plus HKDF). No root key is stored in flash - it is regenerated from silicon on each boot. Persistent NV and the PUF helper data live in QSPI flash (
-DFWTPM_NV_QSPI).What is included
-DFWTPM_PUF_SELFTEST) with selectable BCH profile.hellosanity image.-DFWTPM_ENABLE_PQC) for an ECC + ML-DSA / ML-KEM TPM instead of the default RSA + ECC.Hardware validation
Validated on the ZC702 bench over JTAG (prebuilt FSBL does
ps7_init, app loaded over the top): fwTPM boots, self-test passes, and the SRAM PUF regenerates a stable device-unique integrity key across boots.Notes
Self-contained bare-metal server model (the dual-A9 second core and PetaLinux are not used; the coprocessor + Linux-client model is the separate ZCU102 R5 example). No vendor BSP is redistributed; wolfSSL and wolfTPM are consumed as sibling source trees.