Add fwTPM example for Xilinx ZCU102 Cortex-R5 (lock-step)#3
Open
dgarske wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a new end-to-end fwTPM example targeting the Xilinx ZCU102 where Linux (A53) loads and communicates with a bare-metal Cortex-R5 lock-step fwTPM instance via Linux remoteproc + OpenAMP RPMsg, along with a PetaLinux project and a Linux-side smoke test.
Changes:
- Added ZCU102 R5 lock-step bare-metal fwTPM firmware (resource table, MPU setup, RPMsg transport, NV HALs, linker script, build system).
- Added a self-contained PetaLinux 2025.2 project (DT overlay carveouts/lock-step/IPI mailbox + recipes for firmware staging and smoke test + kernel config fragment).
- Added host-side helpers and documentation (SD deploy script, READMEs, repo top-level README entry, gitignore updates).
Reviewed changes
Copilot reviewed 46 out of 49 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| Xilinx/fwtpm-zcu102-r5/README.md | ZCU102 fwTPM architecture/build/boot documentation for the new example. |
| Xilinx/fwtpm-zcu102-r5/deploy-to-sdcard.sh | Host script to deploy PetaLinux artifacts to an SD card. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/Makefile | Build rules for the R5 bare-metal fwTPM firmware (Tier-1 objects, Tier-2 ELF link). |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/boot.S | R5 reset/vector entry and TCM stub for remoteproc reload robustness. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/lscript.ld | Linker script for rproc DDR carveout + TCM vectors + trace/resource sections. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/mpu_setup.c | Minimal ARMv7-R MPU programming for Normal-NC + Device regions. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/main.c | Firmware entry: MPU init, HAL registration, fwTPM init, RPMsg server loop. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/user_settings.h | wolfSSL/wolfTPM build-time configuration for bare-metal R5. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/fwtpm_clock_zynqmp.c | Clock HAL (PMU CCNT) and RNG seed callback. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/fwtpm_nv_ram.c | Volatile RAM-backed NV HAL (V1 fallback). |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/fwtpm_nv_qspi.c | QSPI-backed NV HAL skeleton using XQspiPsu. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/fwtpm_rpmsg.c | OpenAMP RPMsg endpoint bridging to FWTPM_ProcessCommand + poll loop. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/platform_info.c | libmetal device registration + remoteproc ops + IPI helpers for OpenAMP. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/rsc_table.c | remoteproc resource table (rpmsg vdev + optional trace ring). |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/bsp_stubs.c | Newlib/BSP stubs: _sbrk + outbyte() -> trace buffer ring. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/zcu102_r5.h | Local header for platform HAL APIs used across firmware sources. |
| Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/include/xparameters.h | Hand-rolled minimal platform constants and carveout addresses. |
| Xilinx/fwtpm-zcu102-r5/petalinux/README.md | Instructions for building and validating the included PetaLinux project. |
| Xilinx/fwtpm-zcu102-r5/petalinux/.gitignore | Ignores PetaLinux build outputs/imported hardware, preserves key configs. |
| Xilinx/fwtpm-zcu102-r5/petalinux/.petalinux/metadata | Captures PetaLinux version and project metadata for the committed project. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/attributes | PetaLinux project attributes (e.g., defconfig selection). |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/configs/config | Committed PetaLinux system configuration used as build input. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/configs/flash_parts.txt | Flash layout config for PetaLinux tooling. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/configs/rootfsconfigs/user-rootfsconfig | Rootfs config list (user-rootfsconfig) for the project. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/README | Meta-user layer README (currently template content). |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/COPYING.MIT | MIT license text for the meta-user layer template. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/conf/layer.conf | Yocto layer configuration for meta-user (scarthgap compatibility). |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/conf/petalinuxbsp.conf | Adds fwtpm-r5 and fwtpm-rpmsg-test packages to IMAGE_INSTALL. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/conf/user-rootfsconfig | Meta-user rootfs package listing file. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/device-tree/device-tree.bbappend | Hooks in system-user.dtsi overlay for the device-tree build. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/device-tree/device-tree-sdt.inc | SDT include wiring for extra DT include files. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi | DT overlay: lock-step cluster mode, carveouts, IPI mailbox, QSPI partition, GEM3 PHY. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/fwtpm-r5/fwtpm-r5_0.1.0.bb | Recipe to stage the prebuilt R5 ELF into firmware directory for remoteproc. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-apps/fwtpm-rpmsg-test/fwtpm-rpmsg-test_0.1.0.bb | Recipe to build/install the RPMsg smoke-test client. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-apps/fwtpm-rpmsg-test/files/fwtpm_rpmsg_test.c | Source for the smoke-test client (duplicate of linux-client version). |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-kernel/linux/linux-xlnx_%.bbappend | Adds kernel feature fragments (bsp.cfg + openamp.cfg). |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-kernel/linux/linux-xlnx/bsp.cfg | Empty kernel fragment placeholder referenced by bbappend. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-kernel/linux/linux-xlnx/openamp.cfg | Kernel config fragment enabling remoteproc/rpmsg/mailbox. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/u-boot/u-boot-xlnx_%.bbappend | U-Boot bbappend adding platform-top.h and bsp.cfg. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/u-boot/files/platform-top.h | U-Boot platform-top config header inclusion shim. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/u-boot/files/bsp.cfg | U-Boot config selection for platform-top. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/recipes-bsp/u-boot/files/0001-ubifs-distroboot-support.patch | U-Boot patch file (currently not wired into bbappend). |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/meta-xilinx-tools/recipes-bsp/uboot-device-tree/uboot-device-tree.bbappend | Adds a DT include for the U-Boot device-tree build. |
| Xilinx/fwtpm-zcu102-r5/petalinux/project-spec/meta-user/meta-xilinx-tools/recipes-bsp/uboot-device-tree/files/system-user.dtsi | Minimal U-Boot DT include overlay file. |
| Xilinx/fwtpm-zcu102-r5/linux-client/Makefile | Standalone build for the A53 Linux smoke-test client. |
| Xilinx/fwtpm-zcu102-r5/linux-client/fwtpm_rpmsg_test.c | Standalone A53 Linux RPMsg smoke-test client source. |
| README.md | Top-level repo README updated to mention the new ZCU102 example. |
| .gitignore | Repo gitignore updated for ZCU102 example build artifacts (BSP/hw/petalinux outputs/client binary). |
Comments suppressed due to low confidence (2)
Xilinx/fwtpm-zcu102-r5/README.md:211
- In the Memory Map table, the QSPI fwtpm-nv partition is listed at 0x07F00000, but system-user.dtsi defines it at 0x07FF0000. This discrepancy can cause readers to partition flash incorrectly and (if the QSPI HAL is enabled) can lead to writes to the wrong offset; please make the address consistent everywhere.
| RPMsg buffer pool | 0x3EE08000 | 256 KiB | shared payload buffers |
| QSPI fwtpm-nv | 0x07F00000 | 64 KiB | persistent NV (R5 owns) |
Xilinx/fwtpm-zcu102-r5/firmware/fwtpm-r5/fwtpm_nv_qspi.c:253
- This file uses printf() for QSPI init errors, but the port documentation indicates newlib stdio is not usable on this bare-metal R5 (no _write backend). If printf isn’t wired to outbyte/xil_printf, these diagnostics may hang precisely when flash init fails; prefer xil_printf/outbyte-based logging for error reporting here.
cfg = XQspiPsu_LookupConfig(XPAR_XQSPIPSU_0_BASEADDR);
if (cfg == NULL) {
printf("XQspiPsu LookupConfig failed\n");
return -1;
}
rc = XQspiPsu_CfgInitialize(&g_qspi, cfg, cfg->BaseAddress);
if (rc != XST_SUCCESS) {
printf("XQspiPsu CfgInitialize failed: %d\n", rc);
return rc;
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+73
to
+80
| /* Register NV storage HAL (QSPI-backed, persistent). */ | ||
| memset(&nvHal, 0, sizeof(nvHal)); | ||
| emit("step 3: nv ram init (V1: volatile)\r\n"); | ||
| rc = zynqmp_r5_nv_ram_init(&nvHal); | ||
| emit("step 4: nv init done\r\n"); | ||
| if (rc != 0) { | ||
| xil_printf("ERROR: NV QSPI init failed: %d\n", rc); | ||
| goto error; |
| #define ZCU102_R5_BUF_SIZE 0x00040000U /* 256 KiB rpmsg buffer pool */ | ||
|
|
||
| /* QSPI NV partition (R5 owns; Linux must NOT mount). */ | ||
| #define ZCU102_FWTPM_NV_QSPI_OFFSET 0x07F00000U /* last 1 MiB of 128 MiB */ |
Comment on lines
+4
to
+8
| branch) on the Xilinx Zynq UltraScale+ MPSoC ZCU102. The fwTPM server runs | ||
| bare-metal on the Cortex-R5 RPU pair in **lock-step mode** (RPU0+RPU1 paired | ||
| into a single safety-class processor, 256 KB merged TCM). Persistent NV | ||
| lives in a 64 KB QSPI flash partition. PetaLinux on the A53 APU acts as | ||
| TPM client over **OpenAMP RPMsg** loaded via Linux `remoteproc`. |
Comment on lines
+116
to
+133
| cmdBuf[0] = QSPI_CMD_FAST_READ_4B; | ||
| cmdBuf[1] = (uint8_t)(addr >> 24); | ||
| cmdBuf[2] = (uint8_t)(addr >> 16); | ||
| cmdBuf[3] = (uint8_t)(addr >> 8); | ||
| cmdBuf[4] = (uint8_t)(addr); | ||
|
|
||
| msg[0].TxBfrPtr = cmdBuf; | ||
| msg[0].RxBfrPtr = NULL; | ||
| msg[0].ByteCount = 5; | ||
| msg[0].BusWidth = XQSPIPSU_SELECT_MODE_SPI; | ||
| msg[0].Flags = XQSPIPSU_MSG_FLAG_TX; | ||
| msg[1].TxBfrPtr = NULL; | ||
| msg[1].RxBfrPtr = buf; | ||
| msg[1].ByteCount = size; | ||
| msg[1].BusWidth = XQSPIPSU_SELECT_MODE_SPI; | ||
| msg[1].Flags = XQSPIPSU_MSG_FLAG_RX; | ||
| /* FAST_READ has 1 dummy byte after address */ | ||
| return XQspiPsu_PolledTransfer(&g_qspi, msg, 2); |
Comment on lines
+79
to
+100
| rc = FWTPM_ProcessCommand(g_fwtpm_ctx, (const byte*)data, (int)len, | ||
| g_rsp_buf, &rspSize, /*locality*/0); | ||
| if (rc != 0) { | ||
| printf("FWTPM_ProcessCommand: rc=%d\n", rc); | ||
| /* Send a minimal TPM error response: tag=TPM_ST_NO_SESSIONS, | ||
| * size=10, rc=TPM_RC_FAILURE -- this lets the client see a | ||
| * structured failure rather than a transport timeout. */ | ||
| const uint8_t err[] = { | ||
| 0x80, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x01, 0x01 | ||
| }; | ||
| return rpmsg_send(ept, err, sizeof(err)); | ||
| } | ||
|
|
||
| return rpmsg_send(ept, g_rsp_buf, rspSize); | ||
| } | ||
|
|
||
| /* Linux-side endpoint deletion -- log only, fwTPM stays up. */ | ||
| static void rpmsg_unbind_cb(struct rpmsg_endpoint* ept) | ||
| { | ||
| (void)ept; | ||
| printf("rpmsg endpoint unbound (linux client closed)\n"); | ||
| } |
Comment on lines
+53
to
+54
| P1="${DEV}1" | ||
| P2="${DEV}2" |
Comment on lines
+6
to
+10
| (matching `firmware/fwtpm-r5/lscript.ld`), and installs: | ||
|
|
||
| - `/usr/lib/firmware/fwtpm_r5.elf` -- the R5 firmware blob | ||
| - `/usr/bin/fwtpm_rpmsg_test` -- the smoke-test client | ||
|
|
Comment on lines
+1
to
+5
| This README file contains information on the contents of the | ||
| meta-user layer. | ||
|
|
||
| Please see the corresponding sections below for details. | ||
|
|
| @@ -0,0 +1,5 @@ | |||
| PETALINUX_VER=2025.2 | |||
| VALIDATE_HW_CHKSUM=1 | |||
| HARDWARE_PATH=/home/davidgarske/GitHub/wolftpm-examples/Xilinx/fwtpm-zcu102-r5/hw/zcu102.xsa | |||
Comment on lines
+17
to
+24
| #define BOOTENV_DEV_QSPI(devtypeu, devtypel, instance) \ | ||
| "bootcmd_" #devtypel #instance "=sf probe " #instance " 0 0 && " \ | ||
| - "sf read $scriptaddr $script_offset_f $script_size_f && " \ | ||
| + "setenv mtdids 'nor0=nor0' && " \ | ||
| + "setenv mtdparts 'mtdparts=nor0:16m(raw),-(boot)' && " \ | ||
| + "mtdparts && " \ | ||
| + "ubi part boot; ubifsmount ubi0:boot; ubifsload $scriptaddr boot.scr; && " \ | ||
| "echo QSPI: Trying to boot script at ${scriptaddr} && " \ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds a new fwTPM example under
Xilinx/fwtpm-zcu102-r5/. The target is a stock ZCU102 development board with Linux running on the A53 APU and wolfTPM's fwTPM running bare-metal on the Cortex-R5 RPU in lock-step mode. Linux talks to the R5 fwTPM over OpenAMP RPMsg loaded via the upstream Linux remoteproc framework. The example is end-to-end verified on hardware -- the included Linux smoke test issues TPM2 Startup, SelfTest, GetRandom and GetCapability and gets clean responses from the R5.This is the third fwTPM port in
wolftpm-examples, joining STM32H5 (single-MCU, mssim over UART) and Microchip PolarFire SoC (RISC-V hart-4 under HSS, shared-memory TIS). It is the first port to:All work is public. No customer-specific hardware definitions or vendor-private assets are referenced -- the build uses the stock ZCU102
.xsafrom Vitis 2025.2, the stock ZCU102 PetaLinux 2025.2 BSP, and the public OpenAMP / libmetal sources shipped under/opt/Xilinx/2025.2/data/.What's included
End-to-end verification
After SD-card-flashing the PetaLinux image and writing the R5 ELF to
/lib/firmware/, the firmware comes up cleanly on cold boot. The remoteproc trace ring (/sys/kernel/debug/remoteproc/remoteproc0/trace0) shows the full init sequence;dmesgshows Linux registering the rpmsg channel; the smoke client issues four TPM2 commands and getsTPM_RC_SUCCESSfor all of them:R5 trace ring concurrently:
Linux dmesg:
Architecture highlights
The interesting bits worth a reviewer's attention:
Lock-step config from the device tree, not from a boot-time flag.
system-user.dtsisetsxlnx,cluster-mode = <1>andxlnx,tcm-mode = <1>on ther5fssnode, so the upstreamxlnx,zynqmp-r5fssdriver brings up the two R5 cores as one logical core with 256 KiB TCM at VA 0. Oneremoteproc0instance, one ELF.R5 firmware is rproc-loaded at runtime. No co-resident bootloader -- Linux's remoteproc framework parses the ELF (text in DDR carveout, vectors in TCM), loads it, kicks the R5 out of reset, and registers the rpmsg channel from the firmware's NS announce.
MPU programmed by hand, libxilstandalone
_bootbypassed. The BSP's_bootdoes MPU + cache init that assumes a peripheral layout that doesn't match the rproc-loaded layout.mpu_setup.cprograms just two regions -- 2 GiB Normal-NC covering TCM + DDR (so libmetal's LDREX/STREX atomics work), and 32 MiB Device covering IPI/UART/QSPI/CSU at0xFE000000. Caches stay off in V1 so the trace ring is coherent with the APU without explicit dcache flushes.boot.Shas a TCM-resident reset stub. ZynqMPremoteproc stop / startpreserves CP15 state across firmware reloads, so a previous run can leave an MPU map that prefetch-aborts the first DDR instruction fetch by_entry. The TCM stub clears SCTLR.M / C / I before jumping to DDR.DT memory-region naming matches what the kernel expects. The OpenAMP shared regions are named
vdev0vring0/vdev0vring1/vdev0buffer(matching the kernel'svdev%dvring%d/vdev%dbufferlookup pattern), andvdev0bufferhascompatible = "shared-dma-pool"soof_reserved_mem_device_init_by_idxsucceeds. With the obvious names (vring0,buffer) the kernel silently allocates fresh DMA pages at a different physical address and Linux + R5 talk to different memory.IPI mailbox channel-7 (PL3, OpenAMP "bare-metal-app" channel) is wired via
xlnx,zynqmp-ipi-mailboxandmboxesonr5f@0. The R5-side IPI bit mask inxparameters.his0x01000000(bit 24 = PL3), matching the host'sxlnx,ipi-id = 7.linux-client/fwtpm_rpmsg_test.cuses an explicitdst = 1024inRPMSG_CREATE_EPT_IOCTLbecause the in-kernelrpmsg_chrdevdriver only auto-probes channels named"rpmsg-raw"-- our"wolftpm"channel needs the userspace ioctl dance to materialize a/dev/rpmsg0device. 1024 is the first reserved endpoint address thatrpmsg_create_ept(src = ANY)assigns on the R5 side.Known limitations / follow-ons (called out in
README.md)fwtpm_nv_ram.c). The 64 KiB volatile NV image lives in the DDR carveout and does not survive power cycle or remoteproc stop/start. Persistent NV via the QSPI partition at0x07FF0000(fwtpm_nv_qspi.cskeleton included) needs Linux MTD to carve out (or skip) that range so the R5 owns it without conflict. An rpmsg-mediated NV proxy where Linux owns MTD and the R5 sends read/write requests over a second endpoint is the cleaner long-term answer for production.zynqmp_r5_rpmsg_poll. Adequate for the smoke test; the natural next step is enabling R5 IRQs afterFWTPM_Initand routing the IPI as a real exception.Xil_DCacheFlush(). Enabling caches with per-region attributes is a clear follow-on once the transport is hardened.user_settings.hto keep R5 text footprint small. Easy to flip on once the basic transport is stable.DEBUG_WOLFTPMfiled as an upstream wolfTPM cleanup: a few sites infwtpm_nv.croute diagnostic strings through plainprintf/fprintf(stderr, ...)which hang on bare-metal R5 (no_writebackend in newlib). README documents a candidate fix: route those throughWOLFSSL_MSG/WOLFSSL_MSG_EXso embedded ports can hook them viawolfSSL_SetLoggingCb.Bring-up notes (interesting bugs caught during dev)
For the curious -- six subtle issues we hit and resolved during hardware bring-up:
Init_MPUis notweakin the Xilinx 2025.2 BSP, so we couldn't override its peripheral map cleanly; we ship a customboot.S+mpu_setup.cinstead of using libxilstandalone's_boot.DEBUG_WOLFTPMhung at the first NV log line because wolfTPM uses bareprintfrather than the configurableWOLFSSL_MSGmacro.0x3EE00000+, outsiderproc_0_reserved.0x01000000). Linux never received the NS announce;/dev/rpmsg0never appeared.rpmsg_create_eptwasn't being called proactively -- we relied only onns_bind_cb(which fires the opposite direction). Adding an explicitrpmsg_create_eptafterrpmsg_init_vdevtriggers the NS_CREATE announce to Linux.metal_devicestatic-init withpage_shift = 12, page_mask = -1is internally inconsistent. The compiledvirtqueue_get_buffer_addrinline lookup readsphysmap[offset >> 12], indexing 32 entries past the single-entry physmap. Loop increment becomes(-1) + 1 = 0so the loop is infinite. Fix:page_shift = -1UL, page_mask = -1UL(libmetal's documented contiguous-region idiom). JTAG-localized via Xilinx Platform Cable II +xsdb.