diff --git a/README.md b/README.md index fb9d0b2..a6b9328 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,17 @@ Firmware TPM 2.0 running bare-metal on the Zynq UltraScale+ MPSoC R5 RPU in lock-step mode. PetaLinux on the A53 APU acts as TPM client over OpenAMP RPMsg via Linux remoteproc. Persistent NV in QSPI flash. +## AMD Zynq-7000 fwTPM on Cortex-A9 with SRAM PUF + +See [Xilinx/fwtpm-zc702-a9](Xilinx/fwtpm-zc702-a9). + +Firmware TPM 2.0 running bare-metal on a single Cortex-A9 of an AMD/Xilinx +Zynq-7000 (ZC702), served to a host over UART with the raw swtpm/mssim framing. +The TPM's NV-journal integrity key is a device-unique key derived from the +Cortex-A9 on-chip-memory (OCM) SRAM power-on state via wolfCrypt's SRAM PUF +(BCH fuzzy extractor + HKDF) - no root key is stored in flash. Entropy is +wolfCrypt MemUse (the Zynq-7000 PS has no hardware TRNG). + ## Microchip PolarFire SoC fwTPM on a RISC-V hart (AMP) See [Microchip/fwtpm-polarfire-miv](Microchip/fwtpm-polarfire-miv). diff --git a/Xilinx/fwtpm-zc702-a9/.gitignore b/Xilinx/fwtpm-zc702-a9/.gitignore new file mode 100644 index 0000000..129d33f --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/.gitignore @@ -0,0 +1,16 @@ +# Build artifacts +build/ +*.o +*.d +*.elf +*.hex +*.bin +*.lst +*.map + +# Local-only notes / draft scratch (never committed) +*.local.md + +# Python +__pycache__/ +*.pyc diff --git a/Xilinx/fwtpm-zc702-a9/README.md b/Xilinx/fwtpm-zc702-a9/README.md new file mode 100644 index 0000000..24fbbac --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/README.md @@ -0,0 +1,174 @@ +# fwTPM on AMD Zynq-7000 Cortex-A9 (ZC702) with SRAM PUF + +Firmware TPM 2.0 (from [wolfTPM](https://github.com/wolfSSL/wolfTPM) `fwtpm`) running bare-metal on a single **Cortex-A9** (ARMv7-A) of an AMD/Xilinx **Zynq-7000** (ZC702). The fwTPM server is driven from a host PC over UART using the same raw swtpm + Microsoft-simulator ("mssim") framing as the STM32H5 and Mi-V ports, so the stock wolfTPM swtpm client drives it unmodified. + +Its distinguishing feature is that the TPM's 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 + HKDF). No root key is stored in flash: it is regenerated from silicon each boot. + +## Architecture + +``` ++-----------------------------------------------------------+ +| Zynq-7000 (ZC702) | +| | +| Cortex-A9 core 0 (SVC, bare-metal) | +| +---------------------------------------------------+ | +| | wolfTPM fwTPM engine (FWTPM_ProcessCommand) | | +| | NV journal --> volatile RAM (default) | | +| | QSPI flash (opt-in) | | +| | integrity key <-- SRAM PUF (OCM power-on) | | +| | clock <-- MPCore Global Timer | | +| | entropy <-- wolfCrypt MemUse (no HW TRNG) | | +| +---------------------------------------------------+ | +| ^ raw swtpm / mssim framing | +| | Cadence UART1 (0xE0001000) | ++------------|----------------------------------------------+ + v + Host PC: swtpm_uart_bridge.py <-> wolfTPM examples +``` + +The dual-A9 second core and PetaLinux are not used; this is a self-contained bare-metal server (the coprocessor + Linux-client model is the ZCU102 R5 example instead). + +## Layout + +``` +firmware/ + common/ shared bare-metal A9 HAL (wolfSSL-authored, no vendor BSP) + zynq7000.h address book (UART, Global Timer, SLCR, QSPI, OCM, DDR) + zynq_uart.c/.h polled Cadence UART console driver + zynq_time.c/.h MPCore Global Timer time base + A9 PMU cycle counter + startup.S A9 SVC reset: vectors, cache/VFP bring-up, BSS, main + mmu.c flat MMU map (DDR Normal cacheable) - required for printf + retarget.c newlib stubs (printf -> UART, _sbrk heap) + hello/ sanity image: banner + Global-Timer heartbeat + fwtpm-a9/ the fwTPM server + main.c HAL registration + UART swtpm/mssim command loop + fwtpm_clock_zynq.c clock HAL (Global Timer) + entropy hi-res timer (PMCCNTR) + fwtpm_nv_ram.c volatile NV backend (default) + fwtpm_nv_qspi.c persistent NV + PUF helper store in QSPI (-DFWTPM_NV_QSPI) + fwtpm_puf.c/.h OCM SRAM PUF -> device-unique NV integrity key + fwtpm_puf_selftest.c synthetic PUF regression (build -DFWTPM_PUF_SELFTEST) + user_settings.h wolfSSL + wolfTPM configuration + zynq7000-fwtpm.ld linker (DDR @ 0x04000000) + host-client/ PC-side drivers (swtpm bridge, caps/PCR/random, NV persist) + bench/ standalone wolfCrypt benchmark (no TPM), runs from DDR + main.c UART/timer bring-up + current_time() + benchmark_test() + user_settings.h full RSA-2048 + ECC config + Makefile builds wolfcrypt/benchmark bare-metal +``` + +## Prerequisites + +- `arm-none-eabi-gcc` toolchain (13.x verified). +- wolfSSL source tree (default `../../../../../wolfssl`) with SRAM PUF support (`wolfcrypt/src/puf.c`). +- wolfTPM source tree (default `../../../../../wolftpm`) with the `fwtpm` engine. +- A prebuilt Zynq-7000 FSBL (does `ps7_init`: DDR, clocks, MIO/UART) - e.g. `soc-prebuilt-firmware/zc702-zynq/zynq_fsbl.elf`. +- Xilinx `xsdb` / `hw_server` (Vitis) for the JTAG load, plus a serial terminal on the ZC702 USB-UART (a CP210x, UART1, 115200 8N1). + +## Build + +```bash +cd firmware/hello && make # sanity image (zc702-hello.elf) +cd firmware/fwtpm-a9 && make # fwTPM server (zc702-fwtpm.elf) +cd firmware/bench && make # wolfCrypt benchmark (zc702-bench.elf) + +# Override the wolfSSL / wolfTPM source paths: +make WOLFTPM_DIR=/path/to/wolftpm WOLFSSL_DIR=/path/to/wolfssl +``` + +Optional `fwtpm-a9` build flags (`EXTRA_CFLAGS` / knobs): + +- `-DFWTPM_ENABLE_PQC` - ECC + post-quantum (ML-DSA / ML-KEM) TPM instead of the default RSA + ECC. Pair with a host wolfTPM built `--enable-v185 --enable-mldsa --enable-mlkem`. +- `-DFWTPM_PUF_SELFTEST` - run the synthetic SRAM PUF regression at boot (also enables `WOLFSSL_PUF_TEST`). Set the BCH profile with `PUF_T` (7/10/13/15) and `PUF_CW` (codeword count), e.g. `make PUF_T=13 PUF_CW=32 EXTRA_CFLAGS="-DFWTPM_PUF_SELFTEST"`. + +## Load and run (JTAG over FSBL) + +The A9 has no PLM/PMU boot help: a prebuilt FSBL does `ps7_init` and parks, then the app is loaded over the top. Set SW10 to on-board JTAG and SW16 to JTAG boot mode, power-cycle, then with `xsdb`: + +```tcl +connect +targets -set -filter {name =~ "ARM Cortex-A9 MPCore #0"} +rst -system +after 1500 +targets -set -filter {name =~ "ARM Cortex-A9 MPCore #0"} +dow zynq_fsbl.elf ;# ps7_init (DDR/UART/clocks), then parks +con +after 3000 +stop +dow firmware/fwtpm-a9/zc702-fwtpm.elf +con +``` + +The board prints the banner, the SRAM PUF identity/profile, then the self-test (`TPM2_Startup` / `TPM2_GetRandom` `rc=0`), then serves TPM2 over UART1. + +Drive it from the host (adjust the serial device): + +```bash +cd firmware/fwtpm-a9/host-client +python3 fwtpm_uart_test.py /dev/ttyUSB0 # caps / PCR / GetRandom + +# Or bridge to the stock wolfTPM swtpm client: +python3 swtpm_uart_bridge.py /dev/ttyUSB0 2321 & +# then from a wolfTPM build: +./examples/wrap/caps # over TPM_INTERFACE=swtpm +``` + +## SRAM PUF: test and use + +- **Test (synthetic).** The `-DFWTPM_PUF_SELFTEST` build injects deterministic synthetic SRAM (`WOLFSSL_PUF_TEST`) and runs enroll -> clean reconstruct -> reconstruct at the BCH correction limit (t flips) -> over-limit (t+1 flips must fail or differ) -> bad-argument -> zeroize, printing per-step results and `Result: 0 (PASS)`. This proves the fuzzy-extractor math on the A9 silicon independent of the physical OCM. Sweep `PUF_T` / `PUF_CW` to characterize a profile. + +- **Use (physical).** The default build reads an uninitialized OCM carve-out (`FWTPM_PUF_OCM_ADDR`, near the top of the high-mapped 256 KB OCM) as the PUF source. On first boot it enrolls (generating helper data + a device identity); later boots reconstruct the same stable bits from the persisted helper data, correcting the SRAM noise. The reconstructed bits HKDF-derive a 32-byte key that backs the fwTPM NV journal's integrity HMAC (`FWTPM_NV_HAL.get_integrity_key`). The BCH profile's `WC_PUF_PROFILE_ID` is persisted with the helper data and checked on reconstruct, so a build mismatch is rejected rather than silently producing a wrong key. + +A **stable key across power cycles** requires persisting the helper data in non-volatile storage. With the default volatile RAM NV the helper data does not survive a reload, so each boot enrolls afresh; build with `-DFWTPM_NV_QSPI` to persist both the helper data and the NV journal in QSPI flash (see below). + +## Persistent NV in QSPI (`-DFWTPM_NV_QSPI`) + +`fwtpm_nv_qspi.c` stores the fwTPM NV journal and the SRAM-PUF helper data in the top two 64 KB sectors of the board's QSPI NOR flash (the same 16 MB Micron MT25Q that wolfBoot boots from): NV at `0x00FE0000`, PUF helper at `0x00FF0000`. The wolfBoot partitions end well below this, and a hard runtime guard refuses any erase/program below `0x00F00000`, so the boot image cannot be touched. NV is a RAM shadow loaded from flash at init; a write updates the shadow and rewrites the touched sector. The QSPI controller access (I/O mode for commands, Linear/XIP mode at `0xFC000000` for reads) is shared with the wolfBoot Zynq-7000 HAL. + +With this backend the PUF-derived key is stable across boots: the first boot enrolls and stores the helper data; later boots reconstruct the same device identity from it. On the ZC702 this is hardware-verified end to end: boot 1 reports `(enrolled)`, boot 2 (after a reload) reports `(reconstructed)` with the same identity, and a TPM NV index written on one boot (`fwtpm_nv_persist_test.py`) reads back after a reload - the NV journal validating under the reconstructed PUF integrity key. + +## Platform notes + +- **No hardware TRNG.** The Zynq-7000 PS has no TRNG, so the Hash-DRBG is seeded by wolfCrypt's MemUse entropy (memory-timing jitter conditioned through SHA3-256, gated fail-closed by SP800-90B health tests). The high-resolution sampler uses the A9 PMU cycle counter (the A9 has no ARMv7 generic timer). The I-cache is enabled in `startup.S` to keep SHA3-heavy seeding fast. +- **Clock.** The MPCore 64-bit Global Timer (0xF8F00200, 333.333 MHz on the ZC702) is the monotonic ms time base. +- **Caches / MMU.** `startup.S` enables the MMU via `mmu.c` (flat identity map: DDR Normal write-back cacheable, MMIO Device, OCM Normal non-cacheable) with the I-cache, D-cache and branch prediction on. The MMU is required: with it off the A9 treats all data as Strongly-Ordered, so the unaligned accesses newlib's `printf` emits abort. There is no DMA in this build (polled UART; the optional QSPI NV backend is CPU PIO / XIP, not DMA), so full caching is safe. + +## Status + +| Item | Status | +|------|--------| +| A9 HAL + hello (UART, Global Timer, MMU/cache/VFP) | Hardware-validated (ZC702) | +| fwTPM over UART (self-test + swtpm/mssim server) | Hardware-validated (ZC702): manufacturer "WOLF", PCR read, GetRandom | +| SRAM PUF synthetic regression | Hardware-validated (ZC702): Result 0 (PASS) | +| SRAM PUF -> NV integrity key (physical OCM) | Hardware-validated (ZC702): enrolls a device identity, backs NV integrity | +| Persistent NV + PUF helper data in QSPI flash | Hardware-validated (ZC702): PUF reconstructs across reload, NV value persists | + +Note: the A9 runs with the MMU enabled (flat map, DDR Normal write-back cacheable). This is required, not optional - with the MMU off the A9 treats all data as Strongly-Ordered, and the unaligned accesses newlib's `printf` emits fault. See `firmware/common/mmu.c`. + +## Performance (measured on hardware) + +Direct wolfCrypt benchmark from `firmware/bench`, run on one Cortex-A9 of the ZC702 (ARMv7-A @ 667 MHz, 32-bit portable-C SP math, `-O2`, `BENCH_EMBEDDED` 1 KB buffers). This is the full RSA-2048 + ECC set the fwTPM uses. + +| Operation | Result | +|-----------|--------| +| RSA-2048 keygen | 0.14 ops/sec (7.19 s) | +| RSA-2048 sign (private) | 6.31 ops/sec (158 ms) | +| RSA-2048 verify (public) | 363.6 ops/sec (2.75 ms) | +| ECC P-256 keygen | 92.5 ops/sec (10.8 ms) | +| ECDHE P-256 agree | 92.7 ops/sec (10.8 ms) | +| ECDSA P-256 sign | 82.9 ops/sec (12.1 ms) | +| ECDSA P-256 verify | 46.0 ops/sec (21.8 ms) | +| SHA-256 | 19.3 MiB/s | +| SHA-1 | 42.7 MiB/s | +| SHA3-256 | 7.3 MiB/s | +| HMAC-SHA256 | 19.6 MiB/s | +| AES-128-CBC | 13.1 MiB/s | +| AES-256-GCM | 3.4 MiB/s | +| RNG (SHA-256 DRBG) | 7.5 MiB/s | + +The benchmark image uses a deterministic bench-only RNG seed. As expected for a hardened core, this is roughly an order of magnitude faster than the SCU35 MicroBlaze V soft core (e.g. ECDSA P-256 sign 82.9 vs 8.1 ops/sec, SHA-256 19.3 vs 2.0 MiB/s). This measures raw wolfCrypt throughput; end-to-end TPM command latency additionally includes the 115200-baud UART transport. + +## See also + +- `Xilinx/fwtpm-zcu102-r5` - fwTPM on the ZynqMP Cortex-R5 (OpenAMP RPMsg, Linux client). +- `Microchip/miv-mpf300-splash` - fwTPM on a soft Mi-V RV32 core (UART), the standalone-UART template for this port. +- `STM32/fwtpm-stm32h5` - fwTPM on Cortex-M33 (UART). diff --git a/Xilinx/fwtpm-zc702-a9/firmware/bench/Makefile b/Xilinx/fwtpm-zc702-a9/firmware/bench/Makefile new file mode 100644 index 0000000..ef6deda --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/bench/Makefile @@ -0,0 +1,75 @@ +# Makefile - ZC702 Cortex-A9 wolfCrypt benchmark +# +# Standalone wolfCrypt (no wolfTPM) benchmark image for a single Cortex-A9 of the +# Zynq-7000 (ZC702). Measures raw core crypto throughput and reports over the +# Cadence UART. Requires the wolfSSL source tree as a sibling of wolftpm-examples +# (override with WOLFSSL_DIR=...). Runs from DDR; loaded over JTAG on top of a +# prebuilt FSBL (ps7_init), like the hello and fwTPM images. +# +# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+ (see source headers). + +CROSS_COMPILE ?= arm-none-eabi- +CC = $(CROSS_COMPILE)gcc +OBJCOPY = $(CROSS_COMPILE)objcopy +SIZE = $(CROSS_COMPILE)size + +WOLFSSL_DIR ?= ../../../../../wolfssl +COMMON_DIR = ../common +TARGET = zc702-bench + +ifeq ($(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/aes.c),) +$(error wolfSSL sources not found at WOLFSSL_DIR=$(WOLFSSL_DIR) - set WOLFSSL_DIR) +endif + +# Cortex-A9 with VFPv3-D16 hard-float (startup.S enables VFP). +ARCHFLAGS = -mcpu=cortex-a9 -mfpu=vfpv3-d16 -mfloat-abi=hard -marm +OPT ?= -O2 +EXTRA_CFLAGS ?= + +CFLAGS = $(ARCHFLAGS) $(OPT) -g3 -MMD -MP -ffunction-sections -fdata-sections \ + -ffreestanding -DWOLFSSL_USER_SETTINGS \ + -I. -I$(COMMON_DIR) -I$(WOLFSSL_DIR) $(EXTRA_CFLAGS) +APP_WARN = -Wall -Wextra + +ASFLAGS = $(ARCHFLAGS) +LDSCRIPT = zynq7000-ddr.ld +# -u _printf_float pulls in newlib float printf (benchmark prints MB/s, ops/sec). +LDFLAGS = $(ARCHFLAGS) $(OPT) -T $(LDSCRIPT) -nostartfiles --specs=nano.specs \ + -Wl,--gc-sections -Wl,-Map=$(TARGET).map -u _printf_float + +WOLF_SRCS = $(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/*.c) \ + $(WOLFSSL_DIR)/wolfcrypt/benchmark/benchmark.c +APP_SRCS = main.c \ + $(COMMON_DIR)/zynq_uart.c $(COMMON_DIR)/zynq_time.c \ + $(COMMON_DIR)/retarget.c $(COMMON_DIR)/mmu.c +ASM_SRCS = $(COMMON_DIR)/startup.S + +BUILD = build +WOLF_OBJS = $(patsubst %.c,$(BUILD)/wolf/%.o,$(notdir $(WOLF_SRCS))) +APP_OBJS = $(patsubst %.c,$(BUILD)/%.o,$(notdir $(APP_SRCS))) \ + $(patsubst %.S,$(BUILD)/%.o,$(notdir $(ASM_SRCS))) + +VPATH = $(COMMON_DIR):$(WOLFSSL_DIR)/wolfcrypt/src:$(WOLFSSL_DIR)/wolfcrypt/benchmark + +all: $(TARGET).elf + $(SIZE) $(TARGET).elf + +$(BUILD)/wolf/%.o: %.c | $(BUILD)/wolf + $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/%.o: %.c | $(BUILD) + $(CC) $(CFLAGS) $(APP_WARN) -c $< -o $@ +$(BUILD)/%.o: %.S | $(BUILD) + $(CC) $(ASFLAGS) -c $< -o $@ + +$(BUILD) $(BUILD)/wolf: + mkdir -p $@ + +$(TARGET).elf: $(APP_OBJS) $(WOLF_OBJS) $(LDSCRIPT) + $(CC) $(LDFLAGS) $(APP_OBJS) $(WOLF_OBJS) -o $@ + +clean: + rm -rf $(BUILD) $(TARGET).elf $(TARGET).map + +-include $(APP_OBJS:.o=.d) $(WOLF_OBJS:.o=.d) + +.PHONY: all clean diff --git a/Xilinx/fwtpm-zc702-a9/firmware/bench/main.c b/Xilinx/fwtpm-zc702-a9/firmware/bench/main.c new file mode 100644 index 0000000..f02381a --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/bench/main.c @@ -0,0 +1,81 @@ +/* main.c + * + * wolfCrypt benchmark harness for a single Cortex-A9 of the AMD Zynq-7000 + * (ZC702). Brings up the console UART and the MPCore Global Timer, provides the + * benchmark time source and a deterministic (bench-only) RNG seed, then runs + * wolfCrypt's benchmark_test() and reports over the UART. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include + +#include "zynq7000.h" +#include "zynq_uart.h" +#include "zynq_time.h" + +#include +#include + +extern int benchmark_test(void* args); + +/* Benchmark time source: fractional seconds from the free-running 64-bit MPCore + * Global Timer (ZYNQ_GLOBAL_TIMER_FREQ). */ +double current_time(int reset) +{ + (void)reset; + return (double)zynq_global_ticks() / (double)ZYNQ_GLOBAL_TIMER_FREQ; +} + +/* Deterministic bench-only RNG seed (an LCG). This is NOT an entropy source and + * must never be used to generate real keys; it only makes the benchmark's + * key-generation and DRBG paths run reproducibly. */ +int bench_seed(unsigned char* out, unsigned int sz) +{ + static uint32_t s = 0x2468ACE1u; + unsigned int i; + + for (i = 0; i < sz; i++) { + s = (s * 1103515245u) + 12345u; + out[i] = (unsigned char)(s >> 16); + } + return 0; +} + +int main(void) +{ + zynq_uart_init(ZYNQ_CONSOLE_UART_BASE); + zynq_timer_init(); + + printf("\r\n"); + printf("========================================================\r\n"); + printf(" wolfCrypt benchmark on AMD Zynq-7000 Cortex-A9 (ZC702)\r\n"); + printf(" ARMv7-A @ 667 MHz, 32-bit SP math (RSA-2048 + ECC)\r\n"); + printf("========================================================\r\n"); + + (void)wolfCrypt_Init(); + benchmark_test(NULL); + (void)wolfCrypt_Cleanup(); + + printf("=== benchmark complete ===\r\n"); + for (;;) { + } + return 0; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/bench/user_settings.h b/Xilinx/fwtpm-zc702-a9/firmware/bench/user_settings.h new file mode 100644 index 0000000..02dfc3e --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/bench/user_settings.h @@ -0,0 +1,123 @@ +/* user_settings.h + * + * wolfCrypt benchmark configuration for a single Cortex-A9 (ARMv7-A) of the AMD + * Zynq-7000 (ZC702). Standalone wolfCrypt (no wolfTPM): measures raw crypto + * throughput on the A9 core. The ZC702 has DDR, so this benchmarks the full set + * the fwTPM uses (RSA-2048, ECC P-256/P-384, AES, SHA-2/3, HMAC). + * + * The RNG seed here is a deterministic bench-only source (CUSTOM_RAND_GENERATE_ + * SEED); it is NOT an entropy source and must never be used for real keys. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef WOLFSSL_USER_SETTINGS_H +#define WOLFSSL_USER_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---- Platform (bare-metal, no OS/filesystem) ---- */ +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define WOLFSSL_SMALL_STACK +#define WOLFSSL_GENERAL_ALIGNMENT 4 +#define SIZEOF_LONG_LONG 8 +#define NO_FILESYSTEM +#define NO_WRITEV +#define NO_ASN_TIME +#define WOLFSSL_ASN_TEMPLATE +#define LITTLE_ENDIAN_ORDER + +/* ---- Benchmark driver ---- */ +#define BENCH_EMBEDDED /* small buffers, short run per algorithm */ +#define WOLFSSL_USER_CURRTIME /* we supply double current_time(int) */ +#define NO_MAIN_DRIVER /* benchmark_test() is called from main.c */ + +/* ---- Single-precision math (portable C, 32-bit A9) ---- */ +#define WOLFSSL_SP_MATH_ALL +#define WOLFSSL_SP_SMALL +#define SP_WORD_SIZE 32 +#define WOLFSSL_SP_384 +#define WOLFSSL_HAVE_SP_ECC +#define WOLFSSL_HAVE_SP_RSA +#define WOLFSSL_PUBLIC_MP +#define WOLFSSL_KEY_GEN + +/* ---- RSA-2048 ---- */ +#define WC_RSA_BLINDING +#define WC_RSA_PSS +#define WC_RSA_NO_PADDING + +/* ---- ECC P-256 + P-384 (keygen / ECDSA / ECDHE) ---- */ +#define HAVE_ECC +#define ECC_USER_CURVES +#undef NO_ECC256 +#define HAVE_ECC384 +#define ECC_SHAMIR +#define ECC_TIMING_RESISTANT +#define HAVE_ECC_KEY_EXPORT + +/* ---- AES (GCM / CBC / CTR / CMAC) ---- */ +#define HAVE_AESGCM +#define HAVE_AES_DECRYPT +#define WOLFSSL_AES_COUNTER +#define WOLFSSL_AES_CFB +#define WOLFSSL_AES_DIRECT +#define HAVE_AES_KEYWRAP +#define WOLFSSL_CMAC + +/* ---- Hashing: SHA-1, SHA-2, SHA-3 ---- */ +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +#define WOLFSSL_SHA3 +#define HAVE_HKDF +#define HAVE_HMAC + +/* ---- RNG: Hash-DRBG seeded by a deterministic bench-only source ---- + * NOT entropy - bench only. See bench_seed() in main.c. */ +#define HAVE_HASHDRBG +#define WC_NO_RNG_SEED_FALLBACK +#define CUSTOM_RAND_GENERATE_SEED bench_seed +#ifndef __ASSEMBLER__ +extern int bench_seed(unsigned char* out, unsigned int sz); +#endif +#define NO_OLD_RNGNAME + +/* ---- Disabled (not benchmarked here) ---- */ +#define NO_DSA +#define NO_DH +#define NO_OLD_TLS +#define NO_RC4 +#define NO_MD4 +#define NO_MD5 +#define NO_DES3 +#define NO_PSK +#define NO_PWDBASED +#define NO_PKCS12 +#define NO_SESSION_CACHE +#define WOLFSSL_NO_SHAKE128 +#define WOLFSSL_NO_SHAKE256 + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSL_USER_SETTINGS_H */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/bench/zynq7000-ddr.ld b/Xilinx/fwtpm-zc702-a9/firmware/bench/zynq7000-ddr.ld new file mode 100644 index 0000000..3d50d97 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/bench/zynq7000-ddr.ld @@ -0,0 +1,115 @@ +/* zynq7000-ddr.ld + * + * 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. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +OUTPUT_ARCH(arm) +ENTRY(_start) + +__stack_size = DEFINED(__stack_size) ? __stack_size : 16K; +__irq_stack_size = DEFINED(__irq_stack_size) ? __irq_stack_size : 4K; +__abt_stack_size = DEFINED(__abt_stack_size) ? __abt_stack_size : 1K; +__und_stack_size = DEFINED(__und_stack_size) ? __und_stack_size : 1K; +__heap_size = DEFINED(__heap_size) ? __heap_size : 64K; + +MEMORY +{ + ddr (rwx) : ORIGIN = 0x04000000, LENGTH = 64M +} + +SECTIONS +{ + .vectors : ALIGN(32) { KEEP(*(.vectors)) } > ddr + .entry : ALIGN(4) { KEEP(*(.entry)) } > ddr + + .text : ALIGN(4) + { + *(.text .text.*) + *(.gnu.linkonce.t.*) + *(.rodata .rodata.*) + *(.gnu.linkonce.r.*) + . = ALIGN(4); + } > ddr + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > ddr + .ARM.extab : ALIGN(4) { *(.ARM.extab* .gnu.linkonce.armextab.*) } > ddr + + .preinit_array : ALIGN(4) + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } > ddr + .init_array : ALIGN(4) + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } > ddr + .fini_array : ALIGN(4) + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + } > ddr + + .data : ALIGN(8) + { + _data_start = .; + *(.data .data.*) + *(.gnu.linkonce.d.*) + . = ALIGN(8); + _data_end = .; + } > ddr + /* RAM image: LMA follows VMA in the one region, so _data_load == _data_start + * and startup.S's copy loop is a no-op. */ + _data_load = LOADADDR(.data); + + .bss (NOLOAD) : ALIGN(8) + { + _bss_start = .; + *(.bss .bss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(8); + _bss_end = .; + } > ddr + + . = ALIGN(8); + PROVIDE (end = .); + PROVIDE (_end = .); + + .heap (NOLOAD) : ALIGN(8) + { + . = . + __heap_size; + . = ALIGN(8); + _heap_end = .; + } > ddr + + /* Exception-mode stacks then the SVC/main stack (top of each region). */ + .stacks (NOLOAD) : ALIGN(16) + { + . = . + __irq_stack_size; . = ALIGN(16); _irq_stack_top = .; + . = . + __abt_stack_size; . = ALIGN(16); _abt_stack_top = .; + . = . + __und_stack_size; . = ALIGN(16); _und_stack_top = .; + . = . + __stack_size; . = ALIGN(16); _stack_top = .; + } > ddr +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/mmu.c b/Xilinx/fwtpm-zc702-a9/firmware/common/mmu.c new file mode 100644 index 0000000..767b886 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/mmu.c @@ -0,0 +1,101 @@ +/* mmu.c + * + * Minimal ARMv7-A MMU setup for the Zynq-7000 Cortex-A9: a flat (identity) + * first-level translation table using 1 MB sections. It exists so that DDR is + * mapped as Normal memory - with the MMU disabled the A9 treats all data as + * Strongly-Ordered, where unaligned accesses always fault (newlib's printf + * emits unaligned stack stores, so it aborts without this). Mapping DDR as + * Normal write-back cacheable also enables the D-cache for a large speed-up. + * + * Map: + * DDR 0x00000000-0x3FFFFFFF Normal, write-back cacheable + * OCM 0xFFF00000-0xFFFFFFFF Normal, non-cacheable (true SRAM PUF reads) + * else Device (UART/SLCR/QSPI/Global Timer/GIC MMIO) + * + * Called from startup.S after the stacks and .bss are set up and before the C + * library init / main, so all C code above it runs with the MMU on. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#define MMU_NUM_SECTIONS 4096U + +/* Short-descriptor 1 MB section attributes (AP[1:0]=11 full access; domain 0; + * DACR sets domain 0 to manager so AP is not actually checked). Memory type is + * (TEX[2:0],C,B): + * Normal WB cacheable TEX=000 C=1 B=1 -> 0x00C + * Normal non-cacheable TEX=001 C=0 B=0 -> 0x1000 + * Device TEX=000 C=0 B=1 -> 0x004 + * plus AP=11 (0xC00) and the section type bits[1:0]=10 (0x2). */ +#define MMU_SEC_TYPE 0x00000002U +#define MMU_SEC_AP_FULL 0x00000C00U +#define MMU_SEC_NORMAL_WB (0x0000000CU | MMU_SEC_AP_FULL | MMU_SEC_TYPE) +#define MMU_SEC_NORMAL_NC (0x00001000U | MMU_SEC_AP_FULL | MMU_SEC_TYPE) +#define MMU_SEC_DEVICE (0x00000004U | MMU_SEC_AP_FULL | MMU_SEC_TYPE) + +/* 16 KB first-level table (4096 x 4 B), 16 KB aligned as required by TTBR0. */ +static uint32_t mmu_l1[MMU_NUM_SECTIONS] __attribute__((aligned(16384))); + +void mmu_init(void) +{ + uint32_t i; + uint32_t base; + uint32_t desc; + uint32_t sctlr; + + for (i = 0; i < MMU_NUM_SECTIONS; i++) { + base = i << 20; + if (i < 0x400U) { + desc = base | MMU_SEC_NORMAL_WB; /* DDR (1 GB) */ + } + else if (i == 0xFFFU) { + desc = base | MMU_SEC_NORMAL_NC; /* OCM high (SRAM PUF source) */ + } + else { + desc = base | MMU_SEC_DEVICE; /* MMIO */ + } + mmu_l1[i] = desc; + } + + /* DACR: domain 0 = manager (0b11), so section AP permissions are not + * checked - appropriate for this single-privilege bare-metal image. */ + __asm__ volatile("mcr p15, 0, %0, c3, c0, 0" : : "r"(0xFFFFFFFFU)); + /* TTBCR = 0: use TTBR0 only, 16 KB table. */ + __asm__ volatile("mcr p15, 0, %0, c2, c0, 2" : : "r"(0U)); + /* TTBR0 = table base (non-cacheable table walks; table is never modified + * after this, and it is written before the D-cache is enabled below). */ + __asm__ volatile("mcr p15, 0, %0, c2, c0, 0" + : : "r"((uint32_t)(uintptr_t)mmu_l1)); + /* Invalidate the unified TLB. */ + __asm__ volatile("mcr p15, 0, %0, c8, c7, 0" : : "r"(0U)); + __asm__ volatile("dsb" ::: "memory"); + __asm__ volatile("isb" ::: "memory"); + + /* Enable MMU (M) and D-cache (C), keep I-cache (I), and clear strict + * alignment checking (A) so unaligned Normal-memory accesses are allowed. */ + __asm__ volatile("mrc p15, 0, %0, c1, c0, 0" : "=r"(sctlr)); + sctlr |= (1U << 0); /* M: MMU enable */ + sctlr |= (1U << 2); /* C: data cache enable */ + sctlr |= (1U << 12); /* I: instruction cache enable */ + sctlr &= ~(1U << 1); /* A: alignment fault checking off */ + __asm__ volatile("mcr p15, 0, %0, c1, c0, 0" : : "r"(sctlr)); + __asm__ volatile("isb" ::: "memory"); +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/retarget.c b/Xilinx/fwtpm-zc702-a9/firmware/common/retarget.c new file mode 100644 index 0000000..a5994c7 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/retarget.c @@ -0,0 +1,177 @@ +/* retarget.c + * + * Newlib bare-metal syscall stubs for the Zynq-7000 Cortex-A9 firmware. Routes + * stdout and stderr to the console Cadence UART and provides a simple _sbrk + * heap that grows from the linker 'end' symbol up to _heap_end. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#include +#include +#include +#include +#include + +#include "zynq7000.h" +#include "zynq_uart.h" +#include "zynq_time.h" + +/* Linker-provided heap bounds. */ +extern char end[]; /* start of heap (after .bss) */ +extern char _heap_end[]; + +static char* heap_ptr = NULL; + +int _write(int fd, const char* buf, int len) +{ + static char prev = 0; /* last byte emitted, kept across calls */ + int i; + + (void)fd; + if (buf == NULL) { + return -1; + } + for (i = 0; i < len; i++) { + /* Translate LF to CRLF, but do not double a CR the caller already sent + * (avoids "\r\r\n" for the common explicit "\r\n" format strings). */ + if (buf[i] == '\n' && prev != '\r') { + zynq_uart_putc(ZYNQ_CONSOLE_UART_BASE, '\r'); + } + zynq_uart_putc(ZYNQ_CONSOLE_UART_BASE, buf[i]); + prev = buf[i]; + } + return len; +} + +int _read(int fd, char* buf, int len) +{ + int count = 0; + uint8_t c; + + (void)fd; + if (buf == NULL) { + return -1; + } + if (len <= 0) { + return (len == 0) ? 0 : -1; /* zero-length read returns 0 (POSIX) */ + } + /* Block for at least one byte, then drain what is available. */ + while (count < len) { + if (zynq_uart_getc(ZYNQ_CONSOLE_UART_BASE, &c)) { + buf[count++] = (char)c; + if (c == '\n' || c == '\r') { + break; + } + } + else if (count > 0) { + break; + } + } + return count; +} + +void* _sbrk(ptrdiff_t incr) +{ + char* prev; + char* next; + + if (heap_ptr == NULL) { + heap_ptr = end; + } + /* Reject growth past the heap top and, since incr is signed, any negative + * increment that would rewind the cursor below the heap base. */ + next = heap_ptr + incr; + if (next < end || next > _heap_end) { + errno = ENOMEM; + return (void*)-1; + } + prev = heap_ptr; + heap_ptr = next; + return (void*)prev; +} + +int _close(int fd) +{ + (void)fd; + return -1; +} + +int _fstat(int fd, struct stat* st) +{ + (void)fd; + if (st == NULL) { + return -1; + } + st->st_mode = S_IFCHR; + return 0; +} + +int _isatty(int fd) +{ + (void)fd; + return 1; +} + +off_t _lseek(int fd, off_t offset, int whence) +{ + (void)fd; + (void)offset; + (void)whence; + return 0; +} + +int _getpid(void) +{ + return 1; +} + +int _kill(int pid, int sig) +{ + (void)pid; + (void)sig; + errno = EINVAL; + return -1; +} + +void _exit(int code) +{ + (void)code; + for (;;) { + /* park */ + } +} + +/* No RTC on this board; report a monotonic time since boot (from the Global + * Timer) so the output object is always defined rather than stack garbage. Not + * wall-clock, so the wolfCrypt builds also define NO_ASN_TIME. */ +int _gettimeofday(struct timeval* tv, void* tz) +{ + uint64_t ms; + + (void)tz; + if (tv == NULL) { + return -1; + } + ms = zynq_millis(); + tv->tv_sec = (time_t)(ms / 1000u); + tv->tv_usec = (suseconds_t)((ms % 1000u) * 1000u); + return 0; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/startup.S b/Xilinx/fwtpm-zc702-a9/firmware/common/startup.S new file mode 100644 index 0000000..9859955 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/startup.S @@ -0,0 +1,165 @@ +/* startup.S + * + * Minimal bare-metal Cortex-A9 (ARMv7-A) startup for the Zynq-7000 PS. The + * prebuilt FSBL does ps7_init (DDR, clocks, MIO/UART) and parks; this image is + * then loaded over JTAG at ZYNQ_LOAD_ADDR and entered at _start in SVC mode + * with IRQ/FIQ masked (the JTAG script sets pc and cpsr = 0xD3). + * + * _start puts the core into a known state independent of what the FSBL left + * behind: it disables the MMU and D-cache, invalidates the caches / TLB / + * branch predictor, then enables the I-cache and branch prediction. VFP/NEON is + * enabled for the hard-float ABI. It sets the stacks, installs the vector table, + * and zeroes .bss. It then calls mmu_init() (mmu.c), which installs a flat map + * and enables the MMU and D-cache - required so newlib printf's unaligned + * accesses do not abort (data is Strongly-Ordered while the MMU is off). There + * is no DMA in this build, so full caching is coherency-safe. Finally it runs + * the C library init array and calls main(). + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + .equ CPSR_MODE_FIQ, 0x11 + .equ CPSR_MODE_IRQ, 0x12 + .equ CPSR_MODE_SVC, 0x13 + .equ CPSR_MODE_ABT, 0x17 + .equ CPSR_MODE_UND, 0x1B + .equ CPSR_MODE_SYS, 0x1F + .equ CPSR_I_BIT, 0x80 + .equ CPSR_F_BIT, 0x40 + + /* Exception vector table (VBAR points here). Reset dispatches to _start; + * every other exception parks in a self-loop for halt-and-inspect. */ + .section .vectors, "ax" + .globl _vectors +_vectors: + b _start /* reset */ + b _hang /* undefined instruction */ + b _hang /* supervisor call */ + b _hang /* prefetch abort */ + b _hang /* data abort */ + b _hang /* reserved */ + b _hang /* IRQ */ + b _hang /* FIQ */ +_hang: + b _hang + + .section .entry, "ax" + .globl _start + .type _start, %function +_start: + /* Ensure SVC mode, IRQ/FIQ masked. */ + msr cpsr_c, #(CPSR_MODE_SVC | CPSR_I_BIT | CPSR_F_BIT) + + /* Install the vector base. */ + ldr r0, =_vectors + mcr p15, 0, r0, c12, c0, 0 /* VBAR = _vectors */ + + /* Read SCTLR, force MMU (M) and D-cache (C) off for a known state. */ + mrc p15, 0, r0, c1, c0, 0 + bic r0, r0, #0x00000001 /* M = 0 (MMU off) */ + bic r0, r0, #0x00000004 /* C = 0 (D-cache off) */ + mcr p15, 0, r0, c1, c0, 0 + dsb + isb + + /* Invalidate I-cache, branch predictor and unified TLB. */ + mov r0, #0 + mcr p15, 0, r0, c7, c5, 0 /* ICIALLU: invalidate I-cache */ + mcr p15, 0, r0, c7, c5, 6 /* BPIALL: invalidate branch pred */ + mcr p15, 0, r0, c8, c7, 0 /* TLBIALL: invalidate TLB */ + dsb + isb + + /* Enable I-cache (I, bit12) and program-flow prediction (Z, bit11). */ + mrc p15, 0, r0, c1, c0, 0 + orr r0, r0, #0x00001000 /* I = 1 (I-cache) */ + orr r0, r0, #0x00000800 /* Z = 1 (branch prediction) */ + mcr p15, 0, r0, c1, c0, 0 + isb + + /* Enable VFP/NEON for the hard-float ABI: full CP10/CP11 access, then set + * the FPEXC EN bit. */ + mrc p15, 0, r0, c1, c0, 2 /* CPACR */ + orr r0, r0, #(0xF << 20) /* cp10 + cp11 full access */ + mcr p15, 0, r0, c1, c0, 2 + isb + mov r0, #(1 << 30) /* FPEXC.EN */ + vmsr fpexc, r0 + + /* Per-mode stacks. Switch to each mode, set sp, return to SVC last. */ + msr cpsr_c, #(CPSR_MODE_IRQ | CPSR_I_BIT | CPSR_F_BIT) + ldr sp, =_irq_stack_top + msr cpsr_c, #(CPSR_MODE_ABT | CPSR_I_BIT | CPSR_F_BIT) + ldr sp, =_abt_stack_top + msr cpsr_c, #(CPSR_MODE_UND | CPSR_I_BIT | CPSR_F_BIT) + ldr sp, =_und_stack_top + msr cpsr_c, #(CPSR_MODE_SVC | CPSR_I_BIT | CPSR_F_BIT) + ldr sp, =_stack_top + + /* Zero .bss. */ + ldr r0, =_bss_start + ldr r1, =_bss_end + mov r2, #0 +1: + cmp r0, r1 + bhs 2f + str r2, [r0], #4 + b 1b +2: + /* Copy .data from LMA to VMA (no-op for a JTAG image where LMA == VMA). */ + ldr r0, =_data_load + ldr r1, =_data_start + ldr r2, =_data_end +3: + cmp r1, r2 + bhs 4f + ldr r3, [r0], #4 + str r3, [r1], #4 + b 3b +4: + /* Enable the MMU (flat map, DDR Normal cacheable) before any C library or + * application code: with the MMU off all data is Strongly-Ordered and the + * unaligned accesses newlib emits (e.g. in printf) would fault. */ + bl mmu_init + + /* C library constructors, then main(0, NULL). */ + bl __libc_init_array + mov r0, #0 + mov r1, #0 + bl main + + /* main should not return; park the core if it does. */ +5: + wfi + b 5b + .size _start, . - _start + + /* Empty _init/_fini for __libc_init_array (crti/crtn are omitted under + * -nostartfiles; there are no C++ static constructors to run here). */ + .globl _init + .type _init, %function +_init: + bx lr + .size _init, . - _init + + .globl _fini + .type _fini, %function +_fini: + bx lr + .size _fini, . - _fini diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/zynq7000.h b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq7000.h new file mode 100644 index 0000000..0c5a438 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq7000.h @@ -0,0 +1,94 @@ +/* zynq7000.h + * + * Board definition for the AMD/Xilinx Zynq-7000 (ZC702 / ZedBoard / MicroZed) + * Cortex-A9 processing system (PS). Peripheral base addresses and the fixed PS + * clocks below match the stock Zynq-7000 memory map; the prebuilt FSBL does the + * ps7_init (DDR, MIO/pinmux, PLLs, UART) before this firmware is loaded over + * JTAG, so the values here describe the running PS rather than program it. + * + * If a board routes the console to a different UART or clocks the PS + * differently, override the values below. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef ZYNQ7000_H +#define ZYNQ7000_H + +#include + +/* --- PS peripheral base addresses (Zynq-7000 TRM UG585) --- */ + +/* Cadence (Zynq) UART controllers. The ZC702 USB-UART is wired to UART1. */ +#ifndef ZYNQ_UART0_BASE +#define ZYNQ_UART0_BASE 0xE0000000UL +#endif +#ifndef ZYNQ_UART1_BASE +#define ZYNQ_UART1_BASE 0xE0001000UL +#endif + +/* Console UART used by the drivers and newlib retarget. */ +#ifndef ZYNQ_CONSOLE_UART_BASE +#define ZYNQ_CONSOLE_UART_BASE ZYNQ_UART1_BASE +#endif +#ifndef ZYNQ_CONSOLE_BAUD +#define ZYNQ_CONSOLE_BAUD 115200UL +#endif + +/* Cortex-A9 MPCore private/global peripherals (SCU region at 0xF8F00000). The + * 64-bit Global Timer is the free-running time base (the A9 has no ARMv7 + * generic timer / CNTPCT). It is clocked at CPU_3x2x = 1/2 the CPU clock: + * 333.333 MHz on the ZC702 (667 MHz A9). Override for other CPU frequencies. */ +#ifndef ZYNQ_GLOBAL_TIMER_BASE +#define ZYNQ_GLOBAL_TIMER_BASE 0xF8F00200UL +#endif +#ifndef ZYNQ_GLOBAL_TIMER_FREQ +#define ZYNQ_GLOBAL_TIMER_FREQ 333333333UL +#endif + +/* System Level Control Registers (clocks, resets, OCM_CFG remap). */ +#ifndef ZYNQ_SLCR_BASE +#define ZYNQ_SLCR_BASE 0xF8000000UL +#endif + +/* Quad-SPI controller (linear/IO mode) for persistent NV (opt-in). */ +#ifndef ZYNQ_QSPI_BASE +#define ZYNQ_QSPI_BASE 0xE000D000UL +#endif + +/* On-Chip Memory (256 KB). After the SLCR OCM_CFG remap all four banks sit + * high at 0xFFFC0000..0xFFFFFFFF. A carve-out near the top serves as the + * physical SRAM PUF source (uninitialized at cold power-on). */ +#ifndef ZYNQ_OCM_HIGH_BASE +#define ZYNQ_OCM_HIGH_BASE 0xFFFC0000UL +#endif +#ifndef ZYNQ_OCM_SIZE +#define ZYNQ_OCM_SIZE 0x00040000UL /* 256 KB */ +#endif + +/* DDR base and the load/link address for this firmware. The FSBL brings up + * DDR; we link into it well above the FSBL's low-DDR usage. */ +#ifndef ZYNQ_DDR_BASE +#define ZYNQ_DDR_BASE 0x00000000UL +#endif +#ifndef ZYNQ_LOAD_ADDR +#define ZYNQ_LOAD_ADDR 0x04000000UL +#endif + +#endif /* ZYNQ7000_H */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.c b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.c new file mode 100644 index 0000000..057a68a --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.c @@ -0,0 +1,129 @@ +/* zynq_time.c + * + * Timekeeping for the Zynq-7000 Cortex-A9. + * + * MPCore Global Timer register map (offsets from ZYNQ_GLOBAL_TIMER_BASE, the + * SCU private-peripheral region; UG585 ch. "Global Timer"): + * 0x00 COUNTER0 lower 32 bits of the 64-bit up-counter + * 0x04 COUNTER1 upper 32 bits + * 0x08 CONTROL bit0 Timer Enable, bit3 Auto-increment, [15:8] prescaler + * 0x0C ISR interrupt status + * + * The Global Timer is a free-running 64-bit up-counter shared by both A9 cores, + * clocked at ZYNQ_GLOBAL_TIMER_FREQ. A correct 64-bit read reads the high word, + * the low word, then the high word again and retries if the high word changed. + * + * The A9 PMU cycle counter (PMCCNTR, CP15 c9) provides a fast free-running + * counter for entropy timing; it is enabled once and read via MRC. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "zynq_time.h" +#include "zynq7000.h" + +#define GT_COUNTER0 0x00u +#define GT_COUNTER1 0x04u +#define GT_CONTROL 0x08u + +#define GT_CTRL_ENABLE 0x01u + +static volatile uint32_t* gt_reg(uint32_t offset) +{ + return (volatile uint32_t*)(ZYNQ_GLOBAL_TIMER_BASE + offset); +} + +void zynq_timer_init(void) +{ + /* Enable the Global Timer (harmless if the FSBL already started it): + * no prescaler, no auto-increment, no interrupt. */ + *gt_reg(GT_CONTROL) = GT_CTRL_ENABLE; +} + +uint64_t zynq_global_ticks(void) +{ + uint32_t hi, lo, hi2; + + do { + hi = *gt_reg(GT_COUNTER1); + lo = *gt_reg(GT_COUNTER0); + hi2 = *gt_reg(GT_COUNTER1); + } while (hi != hi2); + + return ((uint64_t)hi << 32) | (uint64_t)lo; +} + +uint64_t zynq_millis(void) +{ + uint64_t t = zynq_global_ticks(); + /* Split whole-seconds from the remainder so we keep sub-kHz precision (exact + * even when FREQ is not a whole number of kHz) WITHOUT the range-shortening + * (ticks*1000) pre-multiply, which would overflow 64 bits in ~1.75 years and + * send the monotonic clock (and UART frame-timeout deadlines) backwards. The + * remainder is < FREQ, so remainder*1000 stays far below 2^64; the raw 64-bit + * tick counter itself only wraps after centuries. */ + return (t / (uint64_t)ZYNQ_GLOBAL_TIMER_FREQ) * 1000u + + ((t % (uint64_t)ZYNQ_GLOBAL_TIMER_FREQ) * 1000u) + / (uint64_t)ZYNQ_GLOBAL_TIMER_FREQ; +} + +void zynq_delay_us(uint32_t us) +{ + uint64_t start = zynq_global_ticks(); + uint64_t ticks = ((uint64_t)us * (uint64_t)ZYNQ_GLOBAL_TIMER_FREQ) + / 1000000u; + + while ((zynq_global_ticks() - start) < ticks) { + /* busy wait */ + } +} + +void zynq_delay_ms(uint32_t ms) +{ + uint64_t start = zynq_global_ticks(); + uint64_t ticks = ((uint64_t)ms * (uint64_t)ZYNQ_GLOBAL_TIMER_FREQ) + / 1000u; + + while ((zynq_global_ticks() - start) < ticks) { + /* busy wait */ + } +} + +void zynq_pmu_init(void) +{ + uint32_t val; + + /* PMCR (c9,c12,0): set E (enable, bit0), P (reset event counters, bit1), + * C (reset cycle counter, bit2). */ + val = (1u << 0) | (1u << 1) | (1u << 2); + __asm__ volatile("mcr p15, 0, %0, c9, c12, 0" :: "r"(val)); + + /* PMCNTENSET (c9,c12,1): bit31 enables the cycle counter (PMCCNTR). */ + val = (1u << 31); + __asm__ volatile("mcr p15, 0, %0, c9, c12, 1" :: "r"(val)); +} + +uint32_t zynq_cycle_count(void) +{ + uint32_t val; + + /* PMCCNTR (c9,c13,0). */ + __asm__ volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(val)); + return val; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.h b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.h new file mode 100644 index 0000000..4303a8e --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_time.h @@ -0,0 +1,52 @@ +/* zynq_time.h + * + * Timekeeping for the Zynq-7000 Cortex-A9 using the MPCore 64-bit Global Timer + * as the monotonic time base, plus the A9 PMU cycle counter for a high- + * resolution entropy timer. The A9 has no ARMv7 generic timer (CNTPCT is + * undefined and traps), so the Global Timer is used instead. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef ZYNQ_TIME_H +#define ZYNQ_TIME_H + +#include + +/* Enable and snapshot the Global Timer. Call once at startup. */ +void zynq_timer_init(void); + +/* Raw 64-bit Global Timer count (at ZYNQ_GLOBAL_TIMER_FREQ). */ +uint64_t zynq_global_ticks(void); + +/* Milliseconds since power-on (Global Timer is free-running from reset). */ +uint64_t zynq_millis(void); + +/* Busy-wait delays built on the Global Timer. */ +void zynq_delay_ms(uint32_t ms); +void zynq_delay_us(uint32_t us); + +/* Enable the A9 performance monitor cycle counter (PMCCNTR). Call once. */ +void zynq_pmu_init(void); + +/* 32-bit CPU cycle counter snapshot (PMCCNTR), for high-resolution entropy + * timing. Wraps; only inter-sample deltas are meaningful. */ +uint32_t zynq_cycle_count(void); + +#endif /* ZYNQ_TIME_H */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.c b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.c new file mode 100644 index 0000000..ba34950 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.c @@ -0,0 +1,146 @@ +/* zynq_uart.c + * + * Minimal polled Cadence (Zynq-7000) UART console driver. + * + * Cadence UART register map (offsets from the controller base, UG585 ch. 19): + * 0x00 CR Control (RXEN/TXEN, FIFO resets) + * 0x04 MR Mode (parity, char length, stop bits) + * 0x18 BAUDGEN Baud rate generator (CD divisor) + * 0x2C SR Channel Status + * 0x30 FIFO TX / RX data FIFO (write = TX, read = RX) + * 0x34 BDIV Baud rate divider (BDIV, actual = ref/(CD*(BDIV+1))) + * + * SR bits used here: RXEMPTY (bit 1) = RX FIFO empty, TXFULL (bit 4) = TX FIFO + * full. baud = ref_clk / (CD * (BDIV + 1)). + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "zynq_uart.h" + +#define ZUART_CR 0x00u +#define ZUART_MR 0x04u +#define ZUART_BAUDGEN 0x18u +#define ZUART_SR 0x2Cu +#define ZUART_FIFO 0x30u +#define ZUART_BDIV 0x34u + +/* CR bits. */ +#define ZUART_CR_RXRST 0x00000001u /* reset RX FIFO (self-clearing) */ +#define ZUART_CR_TXRST 0x00000002u /* reset TX FIFO (self-clearing) */ +#define ZUART_CR_RXEN 0x00000004u +#define ZUART_CR_RXDIS 0x00000008u +#define ZUART_CR_TXEN 0x00000010u +#define ZUART_CR_TXDIS 0x00000020u + +/* SR bits. */ +#define ZUART_SR_RXEMPTY 0x00000002u +#define ZUART_SR_TXFULL 0x00000010u + +/* MR: 8 data bits, no parity, 1 stop bit (PAR field = 100b = 0x20). */ +#define ZUART_MR_8N1 0x00000020u + +static volatile uint32_t* zuart_reg(uintptr_t base, uint32_t offset) +{ + return (volatile uint32_t*)(base + offset); +} + +void zynq_uart_init(uintptr_t base) +{ + /* Reset the FIFOs (bits self-clear), then enable TX and RX while clearing + * the disable bits. Baud generator and mode are left as the FSBL set them. */ + *zuart_reg(base, ZUART_CR) = ZUART_CR_TXRST | ZUART_CR_RXRST; + *zuart_reg(base, ZUART_CR) = ZUART_CR_RXEN | ZUART_CR_TXEN; +} + +void zynq_uart_set_baud(uintptr_t base, uint32_t ref_clk_hz, uint32_t baud) +{ + uint32_t bestCd = 0u, bestBdiv = 0u, bestErr = 0xFFFFFFFFu; + uint32_t bdiv, cd, actual, err; + + if (baud == 0u || ref_clk_hz == 0u) { + return; + } + /* Search BDIV in [4,255] (hardware minimum 4) for the CD that lands closest + * to the target rate: CD = ref / (baud * (BDIV+1)). */ + for (bdiv = 4u; bdiv <= 255u; bdiv++) { + cd = ref_clk_hz / (baud * (bdiv + 1u)); + if (cd < 1u || cd > 0xFFFFu) { + continue; + } + actual = ref_clk_hz / (cd * (bdiv + 1u)); + err = (actual > baud) ? (actual - baud) : (baud - actual); + if (err < bestErr) { + bestErr = err; + bestCd = cd; + bestBdiv = bdiv; + } + } + if (bestCd == 0u) { + return; + } + *zuart_reg(base, ZUART_CR) = ZUART_CR_TXRST | ZUART_CR_RXRST; + *zuart_reg(base, ZUART_MR) = ZUART_MR_8N1; + *zuart_reg(base, ZUART_BAUDGEN) = bestCd; + *zuart_reg(base, ZUART_BDIV) = bestBdiv; + *zuart_reg(base, ZUART_CR) = ZUART_CR_RXEN | ZUART_CR_TXEN; +} + +void zynq_uart_putc(uintptr_t base, char c) +{ + while ((*zuart_reg(base, ZUART_SR) & ZUART_SR_TXFULL) != 0u) { + /* wait for TX FIFO space */ + } + *zuart_reg(base, ZUART_FIFO) = (uint32_t)(uint8_t)c; +} + +void zynq_uart_write(uintptr_t base, const uint8_t* buf, size_t len) +{ + size_t i; + + if (buf == NULL) { + return; + } + for (i = 0; i < len; i++) { + zynq_uart_putc(base, (char)buf[i]); + } +} + +void zynq_uart_puts(uintptr_t base, const char* str) +{ + if (str == NULL) { + return; + } + while (*str != '\0') { + zynq_uart_putc(base, *str); + str++; + } +} + +int zynq_uart_getc(uintptr_t base, uint8_t* out) +{ + if (out == NULL) { + return 0; + } + if ((*zuart_reg(base, ZUART_SR) & ZUART_SR_RXEMPTY) != 0u) { + return 0; + } + *out = (uint8_t)(*zuart_reg(base, ZUART_FIFO) & 0xFFu); + return 1; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.h b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.h new file mode 100644 index 0000000..835105e --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/common/zynq_uart.h @@ -0,0 +1,54 @@ +/* zynq_uart.h + * + * Minimal polled Cadence (Zynq-7000) UART console driver. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef ZYNQ_UART_H +#define ZYNQ_UART_H + +#include +#include + +/* Enable the given UART's transmitter and receiver and reset its FIFOs. The + * prebuilt FSBL has already configured the mode (8N1) and baud generator via + * ps7_init, so this does NOT reprogram them - it only (re)enables the paths so + * the console works after we take the core. Use zynq_uart_set_baud() only if a + * design needs a baud rate the FSBL did not set. */ +void zynq_uart_init(uintptr_t base); + +/* Program the baud generator for the requested rate from a known UART + * reference clock (Hz). Optional; not needed when inheriting the FSBL setup. */ +void zynq_uart_set_baud(uintptr_t base, uint32_t ref_clk_hz, uint32_t baud); + +/* Blocking single character transmit (waits for TX FIFO space). */ +void zynq_uart_putc(uintptr_t base, char c); + +/* Blocking write of len bytes. */ +void zynq_uart_write(uintptr_t base, const uint8_t* buf, size_t len); + +/* Blocking write of a NUL-terminated string. */ +void zynq_uart_puts(uintptr_t base, const char* str); + +/* Non-blocking receive: returns 1 and stores a byte in *out if one is + * available, else returns 0. */ +int zynq_uart_getc(uintptr_t base, uint8_t* out); + +#endif /* ZYNQ_UART_H */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/Makefile b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/Makefile new file mode 100644 index 0000000..65dcae2 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/Makefile @@ -0,0 +1,120 @@ +# Makefile - Zynq-7000 Cortex-A9 wolfTPM fwTPM (ZC702) +# +# Builds the wolfTPM firmware TPM (fwTPM) served over the Cadence UART. Requires +# the wolfSSL and wolfTPM source trees as siblings of wolftpm-examples (override +# with WOLFSSL_DIR=... / WOLFTPM_DIR=...). Toolchain: arm-none-eabi GCC (13.x +# verified). The firmware is JTAG-loaded over a prebuilt FSBL (see README). +# +# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+ (see source headers). + +CROSS_COMPILE ?= arm-none-eabi- +CC = $(CROSS_COMPILE)gcc +OBJCOPY = $(CROSS_COMPILE)objcopy +SIZE = $(CROSS_COMPILE)size + +WOLFSSL_DIR ?= ../../../../../wolfssl +WOLFTPM_DIR ?= ../../../../../wolftpm +COMMON_DIR = ../common +TARGET = zc702-fwtpm + +# Fail early with a clear message if the sibling source trees are missing. +ifeq ($(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/aes.c),) +$(error wolfSSL sources not found at WOLFSSL_DIR=$(WOLFSSL_DIR) - set WOLFSSL_DIR to your wolfssl checkout) +endif +ifeq ($(wildcard $(WOLFTPM_DIR)/src/tpm2_packet.c),) +$(error wolfTPM sources not found at WOLFTPM_DIR=$(WOLFTPM_DIR) - set WOLFTPM_DIR to your wolftpm checkout) +endif + +# Cortex-A9 with VFPv3-D16 hard-float (startup.S enables VFP). +ARCHFLAGS = -mcpu=cortex-a9 -mfpu=vfpv3-d16 -mfloat-abi=hard -marm +OPT ?= -O2 + +# Optional build flags (pass via EXTRA_CFLAGS): +# -DFWTPM_ENABLE_PQC ECC + post-quantum (ML-DSA / ML-KEM) TPM instead of the +# default RSA + ECC (see user_settings.h). Pair with a +# host wolfTPM built --enable-v185 --enable-mldsa +# --enable-mlkem. +# -DFWTPM_PUF_SELFTEST run the synthetic SRAM PUF regression at boot (also +# enables WOLFSSL_PUF_TEST). Set the BCH profile with +# PUF_T (7/10/13/15) and PUF_CW (codeword count), e.g. +# make PUF_T=13 PUF_CW=32 EXTRA_CFLAGS="-DFWTPM_PUF_SELFTEST" +# -DFWTPM_NV_QSPI persistent NV + PUF helper data in the QSPI flash (top +# two 64 KB sectors) instead of the default volatile RAM +# NV. Makes the PUF-derived key stable across power cycles. +EXTRA_CFLAGS ?= + +# SRAM PUF BCH profile knobs (override the puf.h defaults t=10, cw=16). +ifdef PUF_T +EXTRA_CFLAGS += -DWC_PUF_BCH_T=$(PUF_T) +endif +ifdef PUF_CW +EXTRA_CFLAGS += -DWC_PUF_NUM_CODEWORDS=$(PUF_CW) +endif + +CFLAGS = $(ARCHFLAGS) $(OPT) -g3 -MMD -MP -ffunction-sections -fdata-sections \ + -ffreestanding -DWOLFSSL_USER_SETTINGS -DWOLFTPM_USER_SETTINGS \ + -I. -I$(COMMON_DIR) -I$(WOLFSSL_DIR) -I$(WOLFTPM_DIR) \ + $(EXTRA_CFLAGS) +# Warnings on the port/common code only (the wolfCrypt/wolfTPM globs are noisy). +APP_WARN = -Wall -Wextra + +ASFLAGS = $(ARCHFLAGS) +LDSCRIPT = zynq7000-fwtpm.ld +# The fwTPM prints no floating point (integer-only printf), so leave the float +# printf support out to save code space. +LDFLAGS = $(ARCHFLAGS) $(OPT) -T $(LDSCRIPT) -nostartfiles --specs=nano.specs \ + -Wl,--gc-sections -Wl,-Map=$(TARGET).map + +# wolfCrypt +WOLF_SRCS = $(wildcard $(WOLFSSL_DIR)/wolfcrypt/src/*.c) +# wolfTPM fwTPM engine + TPM2 packet/util (no server/socket/TIS build) +TPM_SRCS = $(WOLFTPM_DIR)/src/fwtpm/fwtpm.c \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm_command.c \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm_nv.c \ + $(WOLFTPM_DIR)/src/fwtpm/fwtpm_crypto.c \ + $(WOLFTPM_DIR)/src/tpm2_util.c \ + $(WOLFTPM_DIR)/src/tpm2_packet.c \ + $(WOLFTPM_DIR)/src/tpm2_param_enc.c \ + $(WOLFTPM_DIR)/src/tpm2_crypto.c +# Port + common +APP_SRCS = main.c fwtpm_nv_ram.c fwtpm_nv_qspi.c fwtpm_clock_zynq.c \ + fwtpm_puf.c fwtpm_puf_selftest.c \ + $(COMMON_DIR)/zynq_uart.c $(COMMON_DIR)/zynq_time.c \ + $(COMMON_DIR)/retarget.c $(COMMON_DIR)/mmu.c +ASM_SRCS = $(COMMON_DIR)/startup.S + +BUILD = build +WOLF_OBJS = $(patsubst %.c,$(BUILD)/wolf/%.o,$(notdir $(WOLF_SRCS))) +TPM_OBJS = $(patsubst %.c,$(BUILD)/tpm/%.o,$(notdir $(TPM_SRCS))) +APP_OBJS = $(patsubst %.c,$(BUILD)/%.o,$(notdir $(APP_SRCS))) \ + $(patsubst %.S,$(BUILD)/%.o,$(notdir $(ASM_SRCS))) + +VPATH = $(COMMON_DIR):$(WOLFSSL_DIR)/wolfcrypt/src:$(WOLFTPM_DIR)/src:$(WOLFTPM_DIR)/src/fwtpm + +all: $(TARGET).elf $(TARGET).bin + $(SIZE) $(TARGET).elf + +$(BUILD)/wolf/%.o: %.c | $(BUILD)/wolf + $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/tpm/%.o: %.c | $(BUILD)/tpm + $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/%.o: %.c | $(BUILD) + $(CC) $(CFLAGS) $(APP_WARN) -c $< -o $@ +$(BUILD)/%.o: %.S | $(BUILD) + $(CC) $(ASFLAGS) -c $< -o $@ + +$(BUILD) $(BUILD)/wolf $(BUILD)/tpm: + mkdir -p $@ + +$(TARGET).elf: $(APP_OBJS) $(TPM_OBJS) $(WOLF_OBJS) $(LDSCRIPT) + $(CC) $(LDFLAGS) $(APP_OBJS) $(TPM_OBJS) $(WOLF_OBJS) -o $@ + +$(TARGET).bin: $(TARGET).elf + $(OBJCOPY) -O binary $< $@ + +clean: + rm -rf $(BUILD) $(TARGET).elf $(TARGET).bin $(TARGET).map + +-include $(APP_OBJS:.o=.d) $(TPM_OBJS:.o=.d) $(WOLF_OBJS:.o=.d) + +.PHONY: all clean diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_clock_zynq.c b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_clock_zynq.c new file mode 100644 index 0000000..fbe8d9c --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_clock_zynq.c @@ -0,0 +1,68 @@ +/* fwtpm_clock_zynq.c + * + * FWTPM_CLOCK_HAL implementation for the Zynq-7000 Cortex-A9. Uses the MPCore + * 64-bit Global Timer for monotonic millisecond time, and the A9 PMU cycle + * counter for the high-resolution timestamp that wolfCrypt's MemUse entropy + * source samples via CUSTOM_ENTROPY_TIMEHIRES. + * + * The Global Timer is a true 64-bit up-counter, so unlike the ZCU102 R5 port + * (32-bit TTC + software accumulator) no wrap handling is needed here. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "user_settings.h" + +#include + +#include + +#include "zynq_time.h" + +/* FWTPM_CLOCK_HAL get_ms callback: milliseconds since power-on. */ +static UINT64 fwtpm_zynq_get_ms(void* halCtx) +{ + (void)halCtx; + return (UINT64)zynq_millis(); +} + +/* Enable the PMU cycle counter (entropy time source) and the Global Timer + * (monotonic ms), then register the clock HAL. Called from main() before + * FWTPM_Init(), so the timer is live before the first RNG use. */ +int FWTPM_Clock_ZYNQ_Init(FWTPM_CTX* ctx) +{ + zynq_pmu_init(); + zynq_timer_init(); + return FWTPM_Clock_SetHAL(ctx, fwtpm_zynq_get_ms, (void*)0); +} + +/* XSLEEP_MS shim (see user_settings.h): busy-wait ms on the Global Timer. */ +void fwtpm_sleep_ms(unsigned int ms) +{ + zynq_delay_ms((uint32_t)ms); +} + +/* High-resolution time source for MemUse entropy (see user_settings.h). Returns + * the A9 PMU cycle counter, which advances every CPU clock (~667 MHz). MemUse + * conditions this jitter through SHA3-256 and gates it behind SP800-90B health + * tests; this only supplies the raw timestamp. */ +unsigned long long fwtpm_entropy_timer(void) +{ + return (unsigned long long)zynq_cycle_count(); +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_qspi.c b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_qspi.c new file mode 100644 index 0000000..c77e319 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_qspi.c @@ -0,0 +1,566 @@ +/* fwtpm_nv_qspi.c + * + * Persistent FWTPM_NV_HAL for the Zynq-7000 A9, backed by the QSPI NOR flash + * (the same device wolfBoot boots from). Opt-in: build with -DFWTPM_NV_QSPI + * (the default build uses the volatile fwtpm_nv_ram.c). Also provides the + * persistent SRAM-PUF helper-data store (fwtpm_puf_helper_load/store), so the + * PUF-derived NV integrity key is stable across real power cycles. + * + * The wolfTPM core owns a log-structured NV journal on top of this flat store. + * Here NV is a RAM shadow loaded from flash at init; reads are served from the + * shadow, and a write updates the shadow then rewrites only the touched 64 KB + * sector(s) (erase + page program). The PUF helper data lives in its own top + * sector. + * + * SAFETY: the wolfBoot partitions on this board occupy up to ~0x00E10000 of the + * 16 MB flash. NV and the PUF helper live in the top two sectors (0x00FE0000, + * 0x00FF0000) and a hard runtime guard refuses any erase/program below + * FWTPM_QSPI_MIN_SAFE (0x00F00000), so a miscomputed address can never touch + * the boot image. + * + * The QSPI controller access is ported from the wolfBoot Zynq-7000 HAL + * (hal/zynq7000.c, also wolfSSL Inc.): I/O mode for commands/erase/program and + * Linear/XIP mode (0xFC000000+) for bulk reads. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "user_settings.h" + +#ifdef FWTPM_NV_QSPI + +#include +#include + +#include +#include +#include + +#include "zynq7000.h" +#include "zynq_time.h" + +/* ---- QSPI controller registers (Zynq-7000 "Linear/Static" QSPI, UG585 ch.12; + * facts from the wolfBoot HAL). ---- */ +#define QSPI_REG(off) (*(volatile uint32_t*)(uintptr_t)(ZYNQ_QSPI_BASE + (off))) +#define QSPI_CR QSPI_REG(0x00) +#define QSPI_ISR QSPI_REG(0x04) +#define QSPI_IDR QSPI_REG(0x0C) +#define QSPI_EN QSPI_REG(0x14) +#define QSPI_TXD0 QSPI_REG(0x1C) +#define QSPI_RXD QSPI_REG(0x20) +#define QSPI_TXTHR QSPI_REG(0x28) +#define QSPI_RXTHR QSPI_REG(0x2C) +#define QSPI_TXD1 QSPI_REG(0x80) +#define QSPI_TXD2 QSPI_REG(0x84) +#define QSPI_TXD3 QSPI_REG(0x88) +#define QSPI_LQSPI_CR QSPI_REG(0xA0) + +#define QSPI_LINEAR_BASE 0xFC000000UL /* XIP window for linear-mode reads */ + +/* CR bits. */ +#define CR_IFMODE 0x80000000U +#define CR_HOLD_B 0x00080000U +#define CR_SSFORCE 0x00004000U +#define CR_PCS_NONE 0x00003C00U +#define CR_PCS_CS0 0x00003800U +#define CR_FIFO_WIDTH 0x000000C0U +#define CR_BAUD_DIV_4 0x00000008U +#define CR_BAUD_DIV_8 0x00000010U +#define CR_MSTREN 0x00000001U + +#define ISR_RXNEMPTY 0x00000010U +#define ISR_MASK 0x0000007DU +#define EN_VAL 0x00000001U + +/* SPI NOR commands / status. */ +#define SPI_CMD_RDID 0x9F +#define SPI_CMD_RDSR 0x05 +#define SPI_CMD_WREN 0x06 +#define SPI_CMD_PAGE_PROGRAM 0x02 +#define SPI_CMD_SECTOR_ERASE 0xD8 /* 64 KB */ +#define SPI_STATUS_WIP 0x01 +#define SPI_STATUS_WEL 0x02 +#define SPI_NOR_PAGE_SIZE 256U +#define SPI_NOR_SECTOR_SIZE 0x10000U + +/* ---- NV / PUF-helper flash layout (top of the 16 MB flash) ---- */ +#define FWTPM_QSPI_NV_BASE 0x00FE0000UL /* NV journal sector (64 KB) */ +#define FWTPM_QSPI_PUF_BASE 0x00FF0000UL /* PUF helper-data sector (64 KB) */ +#define FWTPM_QSPI_MIN_SAFE 0x00F00000UL /* refuse writes below this */ +#define FWTPM_QSPI_MAX_SAFE 0x01000000UL /* ...and at/above this (16 MB top) */ +#define FWTPM_NV_SIZE SPI_NOR_SECTOR_SIZE /* 64 KB NV region */ + +/* Bound the WIP (write-in-progress) poll so a stuck flash/controller cannot hang + * the fwTPM forever. A 64 KB sector erase is well under this. */ +#ifndef FWTPM_QSPI_WIP_TIMEOUT_MS +#define FWTPM_QSPI_WIP_TIMEOUT_MS 5000U +#endif + +/* ---- low-level QSPI I/O (ported from wolfBoot hal/zynq7000.c) ---- */ +static void qspi_drain_rxfifo(void) +{ + while (QSPI_ISR & ISR_RXNEMPTY) { + (void)QSPI_RXD; + } +} + +static void qspi_cs_assert(void) +{ + QSPI_CR = (QSPI_CR & ~CR_PCS_NONE) | CR_PCS_CS0; +} + +static void qspi_cs_release(void) +{ + QSPI_CR |= CR_PCS_NONE; +} + +/* Clock up to 4 bytes: the byte count is encoded by which TXDn register is + * written (TXD0 for 4 or when reading, TXD1/2/3 for 1/2/3 tx-only bytes). */ +static void qspi_xfer4(const uint8_t* tx, uint8_t* rx, unsigned int nbytes) +{ + uint32_t txw = 0xFFFFFFFFU; + uint32_t rxw; + unsigned int i; + + if (nbytes > 4) { + nbytes = 4; + } + if (nbytes == 0) { + return; + } + if (tx != NULL) { + for (i = 0; i < nbytes; i++) { + txw &= ~((uint32_t)0xFFU << (i * 8)); + txw |= ((uint32_t)tx[i]) << (i * 8); + } + } + qspi_drain_rxfifo(); + if (rx != NULL || nbytes == 4) { + QSPI_TXD0 = txw; + } + else { + switch (nbytes) { + case 1: QSPI_TXD1 = txw; break; + case 2: QSPI_TXD2 = txw; break; + case 3: QSPI_TXD3 = txw; break; + default: QSPI_TXD0 = txw; break; + } + } + while (!(QSPI_ISR & ISR_RXNEMPTY)) { + /* wait for the RX word */ + } + rxw = QSPI_RXD; + if (rx != NULL) { + for (i = 0; i < nbytes; i++) { + rx[i] = (uint8_t)(rxw >> (i * 8)); + } + } +} + +static int qspi_xfer(const uint8_t* tx, uint8_t* rx, unsigned int len) +{ + unsigned int off = 0; + unsigned int chunk; + + qspi_cs_assert(); + while (off < len) { + chunk = len - off; + if (chunk > 4) { + chunk = 4; + } + qspi_xfer4((tx != NULL) ? &tx[off] : NULL, + (rx != NULL) ? &rx[off] : NULL, chunk); + off += chunk; + } + qspi_cs_release(); + return 0; +} + +static void qspi_io_mode_setup(void) +{ + QSPI_EN = 0; + QSPI_IDR = ISR_MASK; + qspi_drain_rxfifo(); + QSPI_ISR = ISR_MASK; + QSPI_LQSPI_CR = 0; /* leave linear mode */ + QSPI_TXTHR = 1; + QSPI_RXTHR = 1; + QSPI_CR = CR_IFMODE | CR_HOLD_B | CR_SSFORCE | CR_PCS_NONE + | CR_FIFO_WIDTH | CR_BAUD_DIV_8 | CR_MSTREN; + QSPI_EN = EN_VAL; +} + +static void qspi_linear_mode_setup(void) +{ + QSPI_EN = 0; + QSPI_IDR = ISR_MASK; + qspi_drain_rxfifo(); + QSPI_ISR = ISR_MASK; + QSPI_CR = CR_IFMODE | CR_HOLD_B | CR_SSFORCE | CR_PCS_CS0 + | CR_FIFO_WIDTH | CR_BAUD_DIV_4 | CR_MSTREN; + QSPI_LQSPI_CR = 0x8000010BU; /* single-bit FAST_READ (0x0B), 1 dummy */ + QSPI_EN = EN_VAL; +} + +static int spi_flash_read_id(uint8_t out[3]) +{ + uint8_t cmd[4] = { SPI_CMD_RDID, 0, 0, 0 }; + uint8_t rx[4] = { 0, 0, 0, 0 }; + int rc = qspi_xfer(cmd, rx, sizeof(cmd)); + if (rc == 0) { + out[0] = rx[1]; + out[1] = rx[2]; + out[2] = rx[3]; + } + return rc; +} + +static int spi_flash_status(uint8_t* status) +{ + uint8_t cmd[2] = { SPI_CMD_RDSR, 0 }; + uint8_t rx[2] = { 0, 0 }; + int rc = qspi_xfer(cmd, rx, sizeof(cmd)); + if (rc == 0) { + *status = rx[1]; + } + return rc; +} + +static int spi_flash_wait_ready(void) +{ + uint8_t status = 0xFF; + uint64_t deadline = zynq_millis() + FWTPM_QSPI_WIP_TIMEOUT_MS; + + do { + if (spi_flash_status(&status) != 0) { + return -1; + } + if (zynq_millis() >= deadline) { + /* WIP stuck: fail with a distinct code rather than hang forever, so + * the caller can surface the error and continue degraded. */ + return -2; + } + } while ((status & SPI_STATUS_WIP) != 0); + return 0; +} + +static int spi_flash_write_enable(void) +{ + uint8_t cmd = SPI_CMD_WREN; + uint8_t status = 0; + int rc; + + rc = qspi_xfer(&cmd, NULL, 1); + if (rc != 0) { + return rc; + } + if (spi_flash_status(&status) != 0) { + return -1; + } + if ((status & SPI_STATUS_WEL) == 0) { + return -1; + } + return 0; +} + +/* Hard guard: the whole [address, address+len) span must lie within the reserved + * NV/PUF window [MIN_SAFE, MAX_SAFE) - never the boot region below, never past + * the device. The len check (with an overflow-safe upper bound) blocks an + * erase/program from spilling out of the window if a layout constant changes. */ +static int qspi_addr_safe(uint32_t address, uint32_t len) +{ + if (address < FWTPM_QSPI_MIN_SAFE) { + return 0; + } + if (len == 0U || len > (FWTPM_QSPI_MAX_SAFE - FWTPM_QSPI_MIN_SAFE)) { + return 0; + } + if (address > FWTPM_QSPI_MAX_SAFE - len) { + return 0; + } + return 1; +} + +static int spi_flash_sector_erase(uint32_t address) +{ + uint8_t cmd[4]; + int rc; + + if (!qspi_addr_safe(address, SPI_NOR_SECTOR_SIZE)) { + return -1; + } + rc = spi_flash_write_enable(); + if (rc != 0) { + return rc; + } + cmd[0] = SPI_CMD_SECTOR_ERASE; + cmd[1] = (uint8_t)((address >> 16) & 0xFFU); + cmd[2] = (uint8_t)((address >> 8) & 0xFFU); + cmd[3] = (uint8_t)((address >> 0) & 0xFFU); + rc = qspi_xfer(cmd, NULL, sizeof(cmd)); + if (rc != 0) { + return rc; + } + return spi_flash_wait_ready(); +} + +static int spi_flash_page_program(uint32_t address, const uint8_t* data, + unsigned int len) +{ + uint8_t hdr[4]; + unsigned int off; + unsigned int chunk; + int rc; + + if (len == 0 || len > SPI_NOR_PAGE_SIZE) { + return -1; + } + if (!qspi_addr_safe(address, len)) { + return -1; + } + rc = spi_flash_write_enable(); + if (rc != 0) { + return rc; + } + hdr[0] = SPI_CMD_PAGE_PROGRAM; + hdr[1] = (uint8_t)((address >> 16) & 0xFFU); + hdr[2] = (uint8_t)((address >> 8) & 0xFFU); + hdr[3] = (uint8_t)((address >> 0) & 0xFFU); + + qspi_cs_assert(); + qspi_xfer4(hdr, NULL, 4); + off = 0; + while (off < len) { + chunk = len - off; + if (chunk > 4) { + chunk = 4; + } + qspi_xfer4(&data[off], NULL, chunk); + off += chunk; + } + qspi_cs_release(); + + return spi_flash_wait_ready(); +} + +/* Bulk read via Linear/XIP mode: word reads from 0xFC000000+addr, decomposed to + * bytes so any address/length alignment works. Restores I/O mode after. */ +static int spi_flash_read(uint32_t address, uint8_t* data, unsigned int len) +{ + const volatile uint32_t* xipw; + uint32_t aligned; + uint32_t w; + unsigned int byteOff; + unsigned int i; + + if (len == 0) { + return 0; + } + qspi_linear_mode_setup(); + + aligned = address & ~3U; + byteOff = address & 3U; + xipw = (const volatile uint32_t*)(uintptr_t)(QSPI_LINEAR_BASE + aligned); + (void)xipw[0]; /* prime the controller pipeline */ + + i = 0; + if (byteOff != 0) { + w = *xipw++; + for (; byteOff < 4U && i < len; byteOff++, i++) { + data[i] = (uint8_t)(w >> (byteOff * 8U)); + } + } + while (i + 4U <= len) { + w = *xipw++; + data[i++] = (uint8_t)(w >> 0); + data[i++] = (uint8_t)(w >> 8); + data[i++] = (uint8_t)(w >> 16); + data[i++] = (uint8_t)(w >> 24); + } + if (i < len) { + w = *xipw; + for (byteOff = 0; i < len; byteOff++, i++) { + data[i] = (uint8_t)(w >> (byteOff * 8U)); + } + } + + qspi_io_mode_setup(); + return 0; +} + +/* Erase a 64 KB sector then program it from the given buffer (must be one full + * SPI_NOR_SECTOR_SIZE). */ +static int spi_flash_rewrite_sector(uint32_t base, const uint8_t* buf) +{ + unsigned int off; + int rc; + + rc = spi_flash_sector_erase(base); + if (rc != 0) { + return rc; + } + for (off = 0; off < SPI_NOR_SECTOR_SIZE; off += SPI_NOR_PAGE_SIZE) { + rc = spi_flash_page_program(base + off, &buf[off], SPI_NOR_PAGE_SIZE); + if (rc != 0) { + return rc; + } + } + return 0; +} + +/* ---- FWTPM_NV_HAL: RAM shadow + write-through of the touched sector ---- */ +static uint8_t g_nv[FWTPM_NV_SIZE]; + +static int nv_in_bounds(word32 offset, word32 size) +{ + return (size <= FWTPM_NV_SIZE && offset <= FWTPM_NV_SIZE - size); +} + +static int nv_read(void* halCtx, word32 offset, byte* buf, word32 size) +{ + (void)halCtx; + if (!nv_in_bounds(offset, size)) { + return -1; + } + memcpy(buf, &g_nv[offset], size); + return 0; +} + +/* NV is one 64 KB sector, so any write rewrites the whole sector from the + * updated shadow. Writes are infrequent (NV_DefineSpace / NV_Write), so the + * full-sector rewrite is acceptable and keeps the mapping trivial. */ +static int nv_flush(void) +{ + return spi_flash_rewrite_sector(FWTPM_QSPI_NV_BASE, g_nv); +} + +static int nv_write(void* halCtx, word32 offset, const byte* buf, word32 size) +{ + (void)halCtx; + if (!nv_in_bounds(offset, size)) { + return -1; + } + memcpy(&g_nv[offset], buf, size); + return nv_flush(); +} + +static int nv_erase(void* halCtx, word32 offset, word32 size) +{ + (void)halCtx; + if (!nv_in_bounds(offset, size)) { + return -1; + } + memset(&g_nv[offset], 0xFF, size); + return nv_flush(); +} + +int FWTPM_NV_QSPI_Init(FWTPM_NV_HAL* hal) +{ + uint8_t id[3]; + + qspi_io_mode_setup(); + + if (spi_flash_read_id(id) == 0) { + printf("QSPI NV: flash JEDEC ID %02X %02X %02X, NV @ 0x%06lX (64 KB)\r\n", + id[0], id[1], id[2], (unsigned long)FWTPM_QSPI_NV_BASE); + } + + /* Load the NV region from flash into the shadow. A blank (erased) region + * reads 0xFF, which the wolfTPM core treats as unformatted (formats on + * first boot). */ + if (spi_flash_read(FWTPM_QSPI_NV_BASE, g_nv, FWTPM_NV_SIZE) != 0) { + return -1; + } + + hal->read = nv_read; + hal->write = nv_write; + hal->erase = nv_erase; + hal->ctx = NULL; + hal->maxSize = FWTPM_NV_SIZE; + return 0; +} + +/* ---- Persistent SRAM-PUF helper-data store (overrides the weak no-ops in + * fwtpm_puf.c). Record at the top of the PUF sector: + * [0..3] magic 'P','U','F','1' + * [4..7] profileId (LE) + * [8..11] helperLen (LE) + * [12..] helper bytes + * ---- */ +#define PUF_MAGIC0 'P' +#define PUF_MAGIC1 'U' +#define PUF_MAGIC2 'F' +#define PUF_MAGIC3 '1' +#define PUF_HDR_LEN 12U + +int fwtpm_puf_helper_load(unsigned char* helper, unsigned int helperSz, + unsigned int* profileId) +{ + uint8_t hdr[PUF_HDR_LEN]; + uint32_t storedLen; + + if (helper == NULL || profileId == NULL) { + return -1; + } + if (spi_flash_read(FWTPM_QSPI_PUF_BASE, hdr, PUF_HDR_LEN) != 0) { + return -1; + } + if (hdr[0] != PUF_MAGIC0 || hdr[1] != PUF_MAGIC1 || + hdr[2] != PUF_MAGIC2 || hdr[3] != PUF_MAGIC3) { + return -1; /* no record (blank/erased sector) -> caller enrolls */ + } + *profileId = (uint32_t)hdr[4] | ((uint32_t)hdr[5] << 8) | + ((uint32_t)hdr[6] << 16) | ((uint32_t)hdr[7] << 24); + storedLen = (uint32_t)hdr[8] | ((uint32_t)hdr[9] << 8) | + ((uint32_t)hdr[10] << 16) | ((uint32_t)hdr[11] << 24); + if (storedLen != helperSz) { + return -1; /* profile/size mismatch -> re-enroll */ + } + if (spi_flash_read(FWTPM_QSPI_PUF_BASE + PUF_HDR_LEN, helper, helperSz) + != 0) { + return -1; + } + return 0; +} + +int fwtpm_puf_helper_store(const unsigned char* helper, unsigned int helperSz, + unsigned int profileId) +{ + static uint8_t sector[SPI_NOR_SECTOR_SIZE]; + + if (helper == NULL || (PUF_HDR_LEN + helperSz) > SPI_NOR_SECTOR_SIZE) { + return -1; + } + memset(sector, 0xFF, sizeof(sector)); + sector[0] = PUF_MAGIC0; sector[1] = PUF_MAGIC1; + sector[2] = PUF_MAGIC2; sector[3] = PUF_MAGIC3; + sector[4] = (uint8_t)(profileId & 0xFF); + sector[5] = (uint8_t)((profileId >> 8) & 0xFF); + sector[6] = (uint8_t)((profileId >> 16) & 0xFF); + sector[7] = (uint8_t)((profileId >> 24) & 0xFF); + sector[8] = (uint8_t)(helperSz & 0xFF); + sector[9] = (uint8_t)((helperSz >> 8) & 0xFF); + sector[10] = (uint8_t)((helperSz >> 16) & 0xFF); + sector[11] = (uint8_t)((helperSz >> 24) & 0xFF); + memcpy(§or[PUF_HDR_LEN], helper, helperSz); + + return spi_flash_rewrite_sector(FWTPM_QSPI_PUF_BASE, sector); +} + +#endif /* FWTPM_NV_QSPI */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_ram.c b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_ram.c new file mode 100644 index 0000000..e5e6a70 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_nv_ram.c @@ -0,0 +1,86 @@ +/* fwtpm_nv_ram.c + * + * Volatile RAM-backed FWTPM_NV_HAL for the Zynq-7000 A9 fwTPM bring-up. A + * 64 KiB DDR buffer whose contents are lost across power cycles. Drop-in + * replacement for fwtpm_nv_qspi.c (persistent) - select with the default build; + * the wolfTPM core owns the log-structured NV journal on top of this flat store. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "user_settings.h" + +#include +#include + +#include +#include + +#define NV_RAM_SIZE 0x10000U /* 64 KiB */ + +static uint8_t g_nv_ram[NV_RAM_SIZE]; + +/* Overflow-safe bounds check: reject an oversized size first, then the offset, + * without forming offset+size (which can wrap). */ +static int nv_ram_in_bounds(word32 offset, word32 size) +{ + return (size <= NV_RAM_SIZE && offset <= NV_RAM_SIZE - size); +} + +static int nv_ram_read(void* halCtx, word32 offset, byte* buf, word32 size) +{ + (void)halCtx; + if (!nv_ram_in_bounds(offset, size)) { + return -1; + } + memcpy(buf, &g_nv_ram[offset], size); + return 0; +} + +static int nv_ram_write(void* halCtx, word32 offset, const byte* buf, + word32 size) +{ + (void)halCtx; + if (!nv_ram_in_bounds(offset, size)) { + return -1; + } + memcpy(&g_nv_ram[offset], buf, size); + return 0; +} + +static int nv_ram_erase(void* halCtx, word32 offset, word32 size) +{ + (void)halCtx; + if (!nv_ram_in_bounds(offset, size)) { + return -1; + } + memset(&g_nv_ram[offset], 0xFF, size); + return 0; +} + +int zynq_nv_ram_init(FWTPM_NV_HAL* hal) +{ + memset(g_nv_ram, 0xFF, sizeof(g_nv_ram)); + hal->read = nv_ram_read; + hal->write = nv_ram_write; + hal->erase = nv_ram_erase; + hal->ctx = NULL; + hal->maxSize = NV_RAM_SIZE; + return 0; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.c b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.c new file mode 100644 index 0000000..104ce91 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.c @@ -0,0 +1,190 @@ +/* fwtpm_puf.c + * + * wolfCrypt SRAM PUF integration for the Zynq-7000 fwTPM. Uses the power-on + * state of a carve-out of the Cortex-A9 on-chip memory (OCM) as the PUF source. + * On first boot it enrolls (generating helper data + a device identity); on + * later boots it reconstructs the same stable bits from the persisted helper + * data, correcting the SRAM's power-on noise. From the reconstructed stable + * bits it HKDF-derives a 32-byte device-unique key which backs the fwTPM NV + * journal's integrity HMAC (get_integrity_key). No root key is stored: it is + * regenerated from silicon each boot. + * + * The PUF profile (BCH strength WC_PUF_BCH_T, codeword count + * WC_PUF_NUM_CODEWORDS) is fixed at build time and its WC_PUF_PROFILE_ID is + * persisted next to the helper data; a mismatch on reconstruct is rejected + * rather than silently producing a wrong key. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "user_settings.h" + +#include +#include + +#include +#include +#include + +#include "zynq7000.h" +#include "fwtpm_puf.h" + +/* OCM PUF source address. A carve-out near the top of the 256 KB OCM (mapped + * high after the SLCR OCM_CFG remap) that the FSBL does not use, so its + * power-on SRAM state survives to the first read here. Override for a board + * whose FSBL clears this region. Must provide at least WC_PUF_RAW_BYTES. */ +#ifndef FWTPM_PUF_OCM_ADDR +#define FWTPM_PUF_OCM_ADDR (ZYNQ_OCM_HIGH_BASE + 0x3F000UL) +#endif + +/* HKDF context/info string binding the derived key to this use. */ +static const unsigned char PUF_INFO[] = "wolfTPM-fwTPM-NV-integrity"; + +/* Local secret scrub via volatile writes. ForceZero is inline-compiled in this + * build (no linkable symbol from misc.c), so use our own to zero key material. */ +static void puf_scrub(void* p, unsigned int n) +{ + volatile unsigned char* v = (volatile unsigned char*)p; + unsigned int i; + for (i = 0; i < n; i++) { + v[i] = 0; + } +} + +/* Module state retained after init (the wc_PufCtx, which holds the raw SRAM, is + * zeroized once the key is derived). */ +static unsigned char g_pufKey[WC_PUF_KEY_SZ]; +static unsigned char g_pufId[WC_PUF_ID_SZ]; +static int g_pufReady = 0; + +/* Default (weak) persistence: no store. load reports "not found" so every boot + * enrolls; the QSPI NV backend overrides both to persist across power cycles. */ +__attribute__((weak)) +int fwtpm_puf_helper_load(unsigned char* helper, unsigned int helperSz, + unsigned int* profileId) +{ + (void)helper; + (void)helperSz; + (void)profileId; + return -1; /* not found */ +} + +__attribute__((weak)) +int fwtpm_puf_helper_store(const unsigned char* helper, unsigned int helperSz, + unsigned int profileId) +{ + (void)helper; + (void)helperSz; + (void)profileId; + return 0; /* no-op (volatile) */ +} + +int FwTPM_Puf_Init(int* enrolled) +{ + wc_PufCtx ctx; + unsigned char helper[WC_PUF_HELPER_BYTES]; + unsigned int profileId = 0; + int didEnroll = 0; + int ret; + + g_pufReady = 0; + + ret = wc_PufInit(&ctx); + if (ret != 0) { + return ret; + } + + /* Read the raw OCM power-on state into the PUF context. */ + ret = wc_PufReadSram(&ctx, (const byte*)(uintptr_t)FWTPM_PUF_OCM_ADDR, + WC_PUF_RAW_BYTES); + if (ret != 0) { + wc_PufZeroize(&ctx); + return ret; + } + + /* Reconstruct from persisted helper data if present and the profile matches; + * otherwise enroll this boot and persist the new helper data. */ + if (fwtpm_puf_helper_load(helper, (unsigned int)WC_PUF_HELPER_BYTES, + &profileId) == 0) { + ret = wc_PufReconstructEx(&ctx, helper, WC_PUF_HELPER_BYTES, profileId); + } + else { + ret = wc_PufEnroll(&ctx); + if (ret == 0) { + ret = wc_PufGetHelperData(&ctx, helper, WC_PUF_HELPER_BYTES); + } + if (ret == 0) { + (void)fwtpm_puf_helper_store(helper, (unsigned int)WC_PUF_HELPER_BYTES, + (unsigned int)WC_PUF_PROFILE_ID); + } + didEnroll = 1; + } + if (ret != 0) { + wc_PufZeroize(&ctx); + return ret; + } + + /* Capture the device identity and derive the integrity key. */ + ret = wc_PufGetIdentity(&ctx, g_pufId, sizeof(g_pufId)); + if (ret == 0) { + ret = wc_PufDeriveKey(&ctx, PUF_INFO, (word32)sizeof(PUF_INFO), + g_pufKey, sizeof(g_pufKey)); + } + + /* Scrub the context (raw SRAM, stable bits) regardless of outcome. */ + wc_PufZeroize(&ctx); + puf_scrub(helper, sizeof(helper)); + + if (ret != 0) { + return ret; + } + + g_pufReady = 1; + if (enrolled != NULL) { + *enrolled = didEnroll; + } + return 0; +} + +int FwTPM_Puf_GetIntegrityKey(void* halCtx, unsigned char* key, + unsigned int* keySz) +{ + (void)halCtx; + if (!g_pufReady || key == NULL || keySz == NULL) { + return -1; + } + memcpy(key, g_pufKey, sizeof(g_pufKey)); + *keySz = (unsigned int)sizeof(g_pufKey); + return 0; +} + +void FwTPM_Puf_PrintInfo(int enrolled) +{ + int i; + + printf("SRAM PUF: source OCM 0x%08lX, profile t=%d cw=%d id=0x%08lX (%s)\r\n", + (unsigned long)FWTPM_PUF_OCM_ADDR, (int)WC_PUF_BCH_T, + (int)WC_PUF_NUM_CODEWORDS, (unsigned long)WC_PUF_PROFILE_ID, + enrolled ? "enrolled" : "reconstructed"); + printf("SRAM PUF: device identity "); + for (i = 0; i < 8 && i < (int)sizeof(g_pufId); i++) { + printf("%02X", g_pufId[i]); + } + printf("...\r\n"); +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.h b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.h new file mode 100644 index 0000000..7dbf9b2 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf.h @@ -0,0 +1,65 @@ +/* fwtpm_puf.h + * + * wolfCrypt SRAM PUF integration for the Zynq-7000 fwTPM: derives a silicon- + * unique key from the power-on state of on-chip memory (OCM) and supplies it as + * the fwTPM NV-journal integrity key (FWTPM_NV_HAL.get_integrity_key), so the + * TPM's NV integrity root is regenerated from the device rather than stored. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef FWTPM_PUF_H +#define FWTPM_PUF_H + +#include + +/* Read the OCM PUF source, enroll (first boot) or reconstruct (later boots, + * using persisted helper data), and derive the device-unique integrity key. + * On success *enrolled is set to 1 if this boot enrolled (no stored helper was + * found) or 0 if it reconstructed from stored helper data. Returns 0 on + * success, a wolfCrypt error otherwise. */ +int FwTPM_Puf_Init(int* enrolled); + +/* FWTPM_NV_HAL.get_integrity_key hook: returns the 32-byte PUF-derived key. + * FwTPM_Puf_Init() must have run first. */ +int FwTPM_Puf_GetIntegrityKey(void* halCtx, unsigned char* key, + unsigned int* keySz); + +/* Print the device identity fingerprint and PUF profile to the console. */ +void FwTPM_Puf_PrintInfo(int enrolled); + +/* Weak helper-data persistence seam. The default (RAM NV) build provides no-op + * versions (load reports "not found", so every boot enrolls); the QSPI NV + * backend overrides these to persist helper data across power cycles. + * load: fill helper[0..helperSz) and *profileId; return 0 if found, else -1. + * store: persist helper + profileId; return 0 on success. */ +int fwtpm_puf_helper_load(unsigned char* helper, unsigned int helperSz, + unsigned int* profileId); +int fwtpm_puf_helper_store(const unsigned char* helper, unsigned int helperSz, + unsigned int profileId); + +#ifdef FWTPM_PUF_SELFTEST +/* Synthetic SRAM PUF regression (enroll -> clean reconstruct -> t-flip + * reconstruct -> over-t must-fail -> bad-arg -> zeroize), mirroring the + * wolfCrypt puf_test. Prints per-step results and returns 0 on PASS. Requires a + * build with -DFWTPM_PUF_SELFTEST (which also enables WOLFSSL_PUF_TEST). */ +int FwTPM_Puf_SelfTest(void); +#endif + +#endif /* FWTPM_PUF_H */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf_selftest.c b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf_selftest.c new file mode 100644 index 0000000..5fc242f --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/fwtpm_puf_selftest.c @@ -0,0 +1,191 @@ +/* fwtpm_puf_selftest.c + * + * Synthetic wolfCrypt SRAM PUF regression for the Zynq-7000 A9, run on target. + * Mirrors the wolfCrypt puf_test: enroll -> clean reconstruct (identity and + * derived key must match) -> reconstruct at the BCH correction limit (t flips, + * must still match) -> over-limit (t+1 flips, must fail or differ, never + * silently reproduce the key) -> bad-argument checks -> zeroize. It injects + * deterministic synthetic SRAM (WOLFSSL_PUF_TEST), so it proves the fuzzy- + * extractor math on this ARMv7-A silicon with no dependence on the physical + * OCM. Built only with -DFWTPM_PUF_SELFTEST (which also enables + * WOLFSSL_PUF_TEST); the BCH profile is set by PUF_T / PUF_CW. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "user_settings.h" + +#ifdef FWTPM_PUF_SELFTEST + +#include +#include + +#include +#include +#include + +#include "fwtpm_puf.h" + +/* Deterministic synthetic SRAM fill (a simple LCG so the pattern is stable + * across the enroll/reconstruct passes but not all-zero). */ +static void puf_fill_sram(unsigned char* buf, unsigned int sz) +{ + unsigned int i; + unsigned int x = 0x1234567u; + + for (i = 0; i < sz; i++) { + x = x * 1103515245u + 12345u; + buf[i] = (unsigned char)(x >> 16); + } +} + +/* Flip the first nbits bits of the 128-bit (16-byte) codeword block. */ +static void puf_flip_bits(unsigned char* buf, int block, int nbits) +{ + int i; + int base = block * 16; + + for (i = 0; i < nbits; i++) { + buf[base + (i >> 3)] ^= (unsigned char)(1u << (i & 7)); + } +} + +static void step(const char* name, int ok) +{ + printf(" %-40s %s\r\n", name, ok ? "PASS" : "FAIL"); +} + +/* Local secret scrub via volatile writes (ForceZero is inline-only here). */ +static void puf_scrub(void* p, unsigned int n) +{ + volatile unsigned char* v = (volatile unsigned char*)p; + unsigned int i; + for (i = 0; i < n; i++) { + v[i] = 0; + } +} + +int FwTPM_Puf_SelfTest(void) +{ + wc_PufCtx ctx; + unsigned char key1[WC_PUF_KEY_SZ]; + unsigned char key2[WC_PUF_KEY_SZ]; + unsigned char id1[WC_PUF_ID_SZ]; + unsigned char id2[WC_PUF_ID_SZ]; + static unsigned char testSram[WC_PUF_RAW_BYTES]; + static unsigned char noisySram[WC_PUF_RAW_BYTES]; + static unsigned char helperBuf[WC_PUF_HELPER_BYTES]; + const unsigned char info[] = "puf-test-context"; + int block, nblocks; + int ret; + + printf("SRAM PUF synthetic self-test (t=%d cw=%d):\r\n", + (int)WC_PUF_BCH_T, (int)WC_PUF_NUM_CODEWORDS); + + puf_fill_sram(testSram, (unsigned int)sizeof(testSram)); + nblocks = (WC_PUF_NUM_CODEWORDS < 3) ? WC_PUF_NUM_CODEWORDS : 3; + + /* Enroll. */ + if ((ret = wc_PufInit(&ctx)) != 0) goto fail; + if ((ret = wc_PufSetTestData(&ctx, testSram, sizeof(testSram))) != 0) + goto fail; + if ((ret = wc_PufEnroll(&ctx)) != 0) goto fail; + memcpy(helperBuf, ctx.helperData, WC_PUF_HELPER_BYTES); + if ((ret = wc_PufGetIdentity(&ctx, id1, sizeof(id1))) != 0) goto fail; + if ((ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key1, sizeof(key1))) + != 0) goto fail; + step("enroll", 1); + + /* Clean reconstruct: identity and key must match. */ + if ((ret = wc_PufInit(&ctx)) != 0) goto fail; + if ((ret = wc_PufSetTestData(&ctx, testSram, sizeof(testSram))) != 0) + goto fail; + if ((ret = wc_PufReadSram(&ctx, testSram, sizeof(testSram))) != 0) goto fail; + if ((ret = wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES)) != 0) + goto fail; + if ((ret = wc_PufGetIdentity(&ctx, id2, sizeof(id2))) != 0) goto fail; + if (memcmp(id1, id2, WC_PUF_ID_SZ) != 0) { ret = -1; goto fail; } + if ((ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key2, sizeof(key2))) + != 0) goto fail; + if (memcmp(key1, key2, WC_PUF_KEY_SZ) != 0) { ret = -1; goto fail; } + step("clean reconstruct (identity + key match)", 1); + + /* Reconstruct at the correction limit: t flips per block, still matches. */ + memcpy(noisySram, testSram, sizeof(testSram)); + for (block = 0; block < nblocks; block++) { + puf_flip_bits(noisySram, block, WC_PUF_BCH_T); + } + if ((ret = wc_PufInit(&ctx)) != 0) goto fail; + if ((ret = wc_PufSetTestData(&ctx, noisySram, sizeof(noisySram))) != 0) + goto fail; + if ((ret = wc_PufReadSram(&ctx, noisySram, sizeof(noisySram))) != 0) + goto fail; + if ((ret = wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES)) != 0) + goto fail; + if ((ret = wc_PufGetIdentity(&ctx, id2, sizeof(id2))) != 0) goto fail; + if (memcmp(id1, id2, WC_PUF_ID_SZ) != 0) { ret = -1; goto fail; } + if ((ret = wc_PufDeriveKey(&ctx, info, sizeof(info), key2, sizeof(key2))) + != 0) goto fail; + if (memcmp(key1, key2, WC_PUF_KEY_SZ) != 0) { ret = -1; goto fail; } + step("reconstruct at correction limit (t flips)", 1); + + /* Over the limit: t+1 flips in block 0 must fail or yield a DIFFERENT + * identity - never silently reproduce the enrolled key. */ + memcpy(noisySram, testSram, sizeof(testSram)); + puf_flip_bits(noisySram, 0, WC_PUF_BCH_T + 1); + if ((ret = wc_PufInit(&ctx)) != 0) goto fail; + if ((ret = wc_PufSetTestData(&ctx, noisySram, sizeof(noisySram))) != 0) + goto fail; + if ((ret = wc_PufReadSram(&ctx, noisySram, sizeof(noisySram))) != 0) + goto fail; + ret = wc_PufReconstruct(&ctx, helperBuf, WC_PUF_HELPER_BYTES); + if (ret == 0) { + if (wc_PufGetIdentity(&ctx, id2, sizeof(id2)) == 0 && + memcmp(id1, id2, WC_PUF_ID_SZ) == 0) { + ret = -1; /* reproduced the enrolled key past the limit: fail */ + goto fail; + } + } + else if (ret != WC_NO_ERR_TRACE(PUF_RECONSTRUCT_E)) { + goto fail; /* unexpected error */ + } + step("over-limit reconstruct rejected (t+1 flips)", 1); + + /* Bad-argument checks. */ + if (wc_PufInit(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { ret = -1; goto fail; } + if (wc_PufEnroll(NULL) != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { ret = -1; goto fail; } + step("bad-argument checks", 1); + + /* Zeroize; derive must then fail (not ready). */ + if ((ret = wc_PufZeroize(&ctx)) != 0) goto fail; + if (wc_PufDeriveKey(&ctx, info, sizeof(info), key1, sizeof(key1)) + != WC_NO_ERR_TRACE(PUF_DERIVE_KEY_E)) { ret = -1; goto fail; } + step("zeroize", 1); + + puf_scrub(key1, sizeof(key1)); + puf_scrub(key2, sizeof(key2)); + printf("Result: 0 (PASS)\r\n"); + return 0; + +fail: + printf("Result: %d (FAIL)\r\n", ret); + return ret; +} + +#endif /* FWTPM_PUF_SELFTEST */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_nv_persist_test.py b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_nv_persist_test.py new file mode 100644 index 0000000..a13fe98 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_nv_persist_test.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +# fwtpm_nv_persist_test.py +# +# TPM-level NV persistence test for the wolfTPM fwTPM. Defines an +# owner NV index, writes a marker value, and reads it back. Run once to define + +# write; reload the firmware (RAM is wiped, QSPI flash is not) and run again: the +# read returns the same value and prints PERSISTED, proving the TPM's own NV +# survived in the QSPI flash NV sectors (build with -DFWTPM_NV_QSPI). +# +# Usage: fwtpm_nv_persist_test.py [/dev/ttyUSBx | /path/to/pty] [hexvalue] +# +# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+. + +import sys +import struct +import serial + +PORT = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyUSB0" +VALUE = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0xDEADBEEF +BAUD = 115200 +# Bound the device-announced response size (>= the device FWTPM_MAX_COMMAND_SIZE) +# so a bogus 32-bit length cannot make the client block/allocate unboundedly. +MAX_RSP = 8192 + +TPM_ST_SESSIONS = 0x8002 +TPM_ST_NO_SESS = 0x8001 +TPM_RH_OWNER = 0x40000001 +TPM_RS_PW = 0x40000009 +ALG_SHA256 = 0x000B +NV_INDEX = 0x01000010 + +CC_STARTUP = 0x00000144 +CC_NV_DEFINE = 0x0000012A +CC_NV_WRITE = 0x00000137 +CC_NV_READ = 0x0000014E + +# TPMA_NV: OWNERWRITE | OWNERREAD | NO_DA (non-orderly => NV-backed, persistent) +NVA_OWNERWRITE = 0x00000002 +NVA_OWNERREAD = 0x00020000 +NVA_NO_DA = 0x02000000 +NV_ATTR = NVA_OWNERWRITE | NVA_OWNERREAD | NVA_NO_DA + +RC_SUCCESS = 0x000 + + +def pw_auth(): + """Empty-password TPM_RS_PW session authorization area (9 bytes).""" + return struct.pack(">I", TPM_RS_PW) + struct.pack(">H", 0) + \ + bytes([0x00]) + struct.pack(">H", 0) + + +def read_exact(ser, n): + buf = b"" + while len(buf) < n: + chunk = ser.read(n - len(buf)) + if not chunk: + raise TimeoutError(f"timeout ({len(buf)}/{n})") + buf += chunk + return buf + + +def xfer(ser, tag, cc, handles=b"", auth=b"", params=b""): + if tag == TPM_ST_SESSIONS: + body = handles + struct.pack(">I", len(auth)) + auth + params + else: + body = handles + params + pkt = struct.pack(">HII", tag, 10 + len(body), cc) + body + ser.write(pkt) + hdr = read_exact(ser, 10) + rtag, size, rc = struct.unpack(">HII", hdr) + if size < 10 or size > MAX_RSP: + raise ValueError(f"bad response size {size} (max {MAX_RSP})") + rbody = read_exact(ser, size - 10) if size > 10 else b"" + return rc, rtag, rbody + + +def resp_params(rtag, rbody): + """Strip the leading parameterSize (present when the response is sessioned).""" + if rtag == TPM_ST_SESSIONS and len(rbody) >= 4: + (psize,) = struct.unpack(">I", rbody[:4]) + return rbody[4:4 + psize] + return rbody + + +def drain_banner(ser): + """Discard the firmware boot banner / self-test text still queued on the + port before the first TPM exchange, so it is not mis-parsed as a response.""" + saved = ser.timeout + ser.timeout = 0.5 + ser.reset_input_buffer() + while ser.read(4096): + pass + ser.timeout = saved + + +def main(): + ser = serial.Serial(PORT, BAUD, timeout=5) + print(f"[fwTPM NV persistence test on {PORT}] index=0x{NV_INDEX:08X}") + drain_banner(ser) + + rc, _, _ = xfer(ser, TPM_ST_NO_SESS, CC_STARTUP, params=struct.pack(">H", 0)) + print(f"TPM2_Startup rc=0x{rc:08X} " + f"({'OK' if rc in (0, 0x100) else 'FAIL'})") + + # First try to read the index (proves persistence if it already exists). + handles = struct.pack(">I", TPM_RH_OWNER) + struct.pack(">I", NV_INDEX) + params = struct.pack(">HH", 4, 0) # readSize=4, offset=0 + rc, rtag, rbody = xfer(ser, TPM_ST_SESSIONS, CC_NV_READ, + handles, pw_auth(), params) + + if rc == RC_SUCCESS: + p = resp_params(rtag, rbody) + (dsz,) = struct.unpack(">H", p[:2]) + val = struct.unpack(">I", p[2:2 + dsz])[0] + print(f"TPM2_NV_Read rc=0x{rc:08X} value=0x{val:08X} " + f"<= PERSISTED from a previous run") + ser.close() + return + + print(f"TPM2_NV_Read rc=0x{rc:08X} (index not defined yet - " + f"defining and writing 0x{VALUE:08X})") + + # Define the index: TPM2B_AUTH (empty) + TPM2B_NV_PUBLIC. + nvpub = struct.pack(">I", NV_INDEX) + struct.pack(">H", ALG_SHA256) + \ + struct.pack(">I", NV_ATTR) + struct.pack(">H", 0) + \ + struct.pack(">H", 4) # authPolicy size 0, dataSize 4 + params = struct.pack(">H", 0) + struct.pack(">H", len(nvpub)) + nvpub + rc, _, _ = xfer(ser, TPM_ST_SESSIONS, CC_NV_DEFINE, + struct.pack(">I", TPM_RH_OWNER), pw_auth(), params) + print(f"TPM2_NV_DefineSpace rc=0x{rc:08X} " + f"({'OK' if rc == 0 else 'FAIL'})") + if rc != 0: + ser.close() + return + + # Write the marker value. + params = struct.pack(">H", 4) + struct.pack(">I", VALUE) + struct.pack(">H", 0) + rc, _, _ = xfer(ser, TPM_ST_SESSIONS, CC_NV_WRITE, handles, pw_auth(), params) + print(f"TPM2_NV_Write rc=0x{rc:08X} " + f"({'OK' if rc == 0 else 'FAIL'}) value=0x{VALUE:08X}") + + # Read back in the same session. + params = struct.pack(">HH", 4, 0) + rc, rtag, rbody = xfer(ser, TPM_ST_SESSIONS, CC_NV_READ, + handles, pw_auth(), params) + if rc == 0: + p = resp_params(rtag, rbody) + (dsz,) = struct.unpack(">H", p[:2]) + val = struct.unpack(">I", p[2:2 + dsz])[0] + print(f"TPM2_NV_Read rc=0x{rc:08X} value=0x{val:08X} " + f"(reload the firmware and re-run to prove persistence)") + else: + print(f"TPM2_NV_Read rc=0x{rc:08X} FAIL") + ser.close() + + +if __name__ == "__main__": + try: + main() + except TimeoutError as e: + print(f"timeout: {e} - is the device running and serving TPM commands? " + f"Reload the firmware and retry.") + sys.exit(1) diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_uart_test.py b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_uart_test.py new file mode 100644 index 0000000..4214051 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/fwtpm_uart_test.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +# fwtpm_uart_test.py +# +# Host-side driver for the wolfTPM fwTPM (firmware/fwtpm-a9). Speaks the raw swtpm +# framing that FwTPM_UartCommandLoop() implements: send a TPM 2.0 command +# packet (tag 0x8001) over the serial port; the device replies with the raw +# TPM response packet. Exercises GetCapability, PCR_Read and GetRandom. +# +# Usage: fwtpm_uart_test.py [/dev/ttyUSBx | /path/to/pty] +# +# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+. + +import sys +import struct +import serial + +PORT = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyUSB0" +BAUD = 115200 +# Bound the device-announced response size (>= the device FWTPM_MAX_COMMAND_SIZE) +# so a bogus 32-bit length cannot make the client block/allocate unboundedly. +MAX_RSP = 8192 +ST_NO_SESSIONS = 0x8001 + +# command codes +CC_STARTUP = 0x00000144 +CC_GET_CAPABILITY = 0x0000017A +CC_PCR_READ = 0x0000017E +CC_GET_RANDOM = 0x0000017B + +# capabilities / properties +TPM_CAP_TPM_PROPERTIES = 0x00000006 +PT_MANUFACTURER = 0x00000105 +PT_FIRMWARE_VERSION_1 = 0x0000010B +PT_FIRMWARE_VERSION_2 = 0x0000010C +ALG_SHA256 = 0x000B + + +def cmd(cc, payload=b""): + return struct.pack(">HII", ST_NO_SESSIONS, 10 + len(payload), cc) + payload + + +def read_exact(ser, n): + buf = b"" + while len(buf) < n: + chunk = ser.read(n - len(buf)) + if not chunk: + raise TimeoutError(f"timeout ({len(buf)}/{n} bytes)") + buf += chunk + return buf + + +def xfer(ser, c): + ser.write(c) + hdr = read_exact(ser, 10) + tag, size, rc = struct.unpack(">HII", hdr) + if size < 10 or size > MAX_RSP: + raise ValueError(f"bad response size {size} (max {MAX_RSP})") + body = read_exact(ser, size - 10) if size > 10 else b"" + return rc, body + + +def drain_banner(ser): + """Discard the firmware boot banner / self-test text still queued on the + port before the first TPM exchange, so it is not mis-parsed as a response.""" + saved = ser.timeout + ser.timeout = 0.5 + ser.reset_input_buffer() + while ser.read(4096): + pass + ser.timeout = saved + + +def main(): + ser = serial.Serial(PORT, BAUD, timeout=5) + print(f"[fwTPM UART client on {PORT}]") + drain_banner(ser) + + rc, _ = xfer(ser, cmd(CC_STARTUP, struct.pack(">H", 0x0000))) # SU_CLEAR + print(f"TPM2_Startup rc=0x{rc:08X} " + f"({'OK' if rc in (0, 0x100) else 'FAIL'})") # 0x100=already started + + # GetCapability: manufacturer + p = struct.pack(">III", TPM_CAP_TPM_PROPERTIES, PT_MANUFACTURER, 1) + rc, b = xfer(ser, cmd(CC_GET_CAPABILITY, p)) + man = b[-4:] if rc == 0 and len(b) >= 4 else b"" + print(f"TPM2_GetCapability MAN rc=0x{rc:08X} manufacturer=" + f"{man!r} ({man.hex()})") + + # GetCapability: firmware version + for pt, nm in ((PT_FIRMWARE_VERSION_1, "FW1"), (PT_FIRMWARE_VERSION_2, "FW2")): + p = struct.pack(">III", TPM_CAP_TPM_PROPERTIES, pt, 1) + rc, b = xfer(ser, cmd(CC_GET_CAPABILITY, p)) + val = struct.unpack(">I", b[-4:])[0] if rc == 0 and len(b) >= 4 else 0 + print(f"TPM2_GetCapability {nm} rc=0x{rc:08X} value=0x{val:08X}") + + # PCR_Read PCR0 (SHA-256): TPML_PCR_SELECTION{count=1,{alg,sizeSel=3,sel}} + sel = struct.pack(">I", 1) + struct.pack(">HB", ALG_SHA256, 3) + bytes([0x01, 0, 0]) + rc, b = xfer(ser, cmd(CC_PCR_READ, sel)) + print(f"TPM2_PCR_Read PCR0 rc=0x{rc:08X} ({len(b)} bytes)" + f"{' '+b.hex() if rc==0 else ''}") + + # GetRandom 16 + rc, b = xfer(ser, cmd(CC_GET_RANDOM, struct.pack(">H", 16))) + rnd = b[2:] if rc == 0 and len(b) >= 2 else b"" + print(f"TPM2_GetRandom 16 rc=0x{rc:08X} {rnd.hex()}") + + ser.close() + + +if __name__ == "__main__": + try: + main() + except TimeoutError as e: + print(f"timeout: {e} - is the device running and serving TPM commands? " + f"Reload the firmware and retry.") + sys.exit(1) diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/swtpm_uart_bridge.py b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/swtpm_uart_bridge.py new file mode 100644 index 0000000..e8956e2 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/host-client/swtpm_uart_bridge.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# swtpm_uart_bridge.py +# +# Bridges wolfTPM's swtpm socket transport (TCP, mssim framing) to the device +# fwTPM's UART command loop, so the standard wolfTPM example suite (wrap_test, +# caps, ...) can drive the device. wolfTPM connects to localhost:2321 and speaks +# the Microsoft-simulator framing; this forwards each frame to/from the serial +# port, which the device already speaks. +# +# Usage: swtpm_uart_bridge.py [serial-device] [tcp-port] +# defaults: /dev/ttyUSB0 2321 +# +# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+. + +import socket +import struct +import sys +import time +import serial + +DEV = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyUSB0" +PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 2321 + +# Reject frames larger than the device command buffer (FWTPM_MAX_COMMAND_SIZE, +# with headroom) so a bogus 32-bit size cannot make the single-threaded bridge +# block on a body read that never completes. +MAX_CMD = 8192 +# Also bound the device-announced response size, so a malfunctioning device +# cannot make the bridge block on a serial read that never completes. +MAX_RSP = 8192 +# Once a frame has started, bound how long to wait for the rest of it; an +# incomplete frame then closes the connection instead of blocking forever. +FRAME_TO = 60 + +# mssim platform command codes +SEND_COMMAND = 8 +POWER_ON = 1 +POWER_OFF = 2 +NV_ON = 11 +RESET = 17 +SESSION_END = 20 +STOP = 21 + +# Moderate per-read timeout; ser_read() below tolerates long silences (a slow +# TPM keygen can compute for minutes with no UART traffic) via a cumulative +# deadline, so an in-progress command is never mistaken for a dead link. +ser = serial.Serial(DEV, 115200, timeout=5) +RESP_DEADLINE = 900 # seconds to wait for a full response (RSA keygen is slow) +time.sleep(0.3) +ser.reset_input_buffer() +# drain any boot banner still queued on the port +ser.timeout = 0.5 +while ser.read(4096): + pass +ser.timeout = 5 + + +def ser_read(n): + buf = b"" + t0 = time.time() + while len(buf) < n: + c = ser.read(n - len(buf)) + if c: + buf += c + t0 = time.time() # reset deadline on any progress + continue + if time.time() - t0 > RESP_DEADLINE: + raise IOError("serial timeout (%d/%d after %ds)" + % (len(buf), n, RESP_DEADLINE)) + return buf + + +def sock_read(conn, n): + buf = b"" + while len(buf) < n: + c = conn.recv(n - len(buf)) + if not c: + return None + buf += c + return buf + + +srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +srv.bind(("localhost", PORT)) +srv.listen(1) +print("swtpm-UART bridge: localhost:%d <-> %s" % (PORT, DEV), flush=True) + +cmdno = 0 +while True: + conn, _ = srv.accept() + conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + try: + while True: + conn.settimeout(None) # block for the next command + hdr = sock_read(conn, 4) + if hdr is None: + break + cmd = struct.unpack(">I", hdr)[0] + conn.settimeout(FRAME_TO) # bound the rest of this frame + + if cmd == SEND_COMMAND: + loc = sock_read(conn, 1) + szb = sock_read(conn, 4) + if loc is None or szb is None: + break # client disconnected mid-frame + sz = struct.unpack(">I", szb)[0] + if sz < 10 or sz > MAX_CMD: + print("reject frame with bad size %d (max %d)" + % (sz, MAX_CMD), flush=True) + break + body = sock_read(conn, sz) + if body is None: + break + cc = struct.unpack(">I", body[6:10])[0] if sz >= 10 else 0 + cmdno += 1 + t0 = time.time() + ser.write(hdr + loc + szb + body) + ser.flush() + rspb = ser_read(4) + rsp_sz = struct.unpack(">I", rspb)[0] + if rsp_sz > MAX_RSP: + print("device announced oversize response %d (max %d)" + % (rsp_sz, MAX_RSP), flush=True) + break + rsp = ser_read(rsp_sz) if rsp_sz else b"" + ack = ser_read(4) + rc = struct.unpack(">I", rsp[6:10])[0] if rsp_sz >= 10 else 0xFFFFFFFF + dt = time.time() - t0 + print("#%-3d cc=0x%03X -> rc=0x%08X (%dB, %.1fs)" + % (cmdno, cc, rc, rsp_sz, dt), flush=True) + conn.sendall(rspb + rsp + ack) + + elif cmd in (POWER_ON, POWER_OFF, NV_ON, RESET, STOP): + ser.write(hdr) + ser.flush() + conn.sendall(ser_read(4)) + if cmd == STOP: + break + + elif cmd == SESSION_END: + ser.write(hdr) + ser.flush() + # device sends no response to SESSION_END + + else: + ser.write(hdr) + ser.flush() + conn.sendall(ser_read(4)) + except Exception as e: + print("conn closed: %s" % e, flush=True) + finally: + conn.close() diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/main.c b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/main.c new file mode 100644 index 0000000..51f8a41 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/main.c @@ -0,0 +1,390 @@ +/* main.c + * + * wolfTPM firmware TPM (fwTPM) on the Zynq-7000 Cortex-A9 (ZC702). Registers the + * NV / clock HALs, initializes the fwTPM engine, runs a small standalone + * self-test (TPM2_Startup -> TPM2_GetRandom), then serves TPM2 commands over the + * Cadence UART using the same framing as the STM32H5 and Mi-V ports (raw swtpm + + * Microsoft-simulator "mssim"), so the wolfTPM swtpm client can drive it from a + * host. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "user_settings.h" +#include +#include +#include + +#include +#include +#include + +#include "zynq7000.h" +#include "zynq_uart.h" +#include "zynq_time.h" +#include "fwtpm_puf.h" + +/* Port HAL initializers (this example). NV is volatile RAM by default; build + * -DFWTPM_NV_QSPI for persistent NV + PUF helper data in the QSPI flash. */ +#ifdef FWTPM_NV_QSPI +extern int FWTPM_NV_QSPI_Init(FWTPM_NV_HAL* hal); +#else +extern int zynq_nv_ram_init(FWTPM_NV_HAL* hal); +#endif +extern int FWTPM_Clock_ZYNQ_Init(FWTPM_CTX* ctx); + +/* Static fwTPM context (large - keep off the stack). */ +static FWTPM_CTX g_ctx; + +/* mssim platform command codes (Microsoft TPM simulator protocol). */ +#define MSSIM_SIGNAL_POWER_ON 1 +#define MSSIM_SIGNAL_POWER_OFF 2 +#define MSSIM_SEND_COMMAND 8 +#define MSSIM_SIGNAL_RESET 17 +#define MSSIM_SESSION_END 20 +#define MSSIM_STOP 21 + +/* Once a frame has started, the rest of it must arrive within this window; on + * expiry the parser resets rather than blocking forever on a partial frame. */ +#define FWTPM_FRAME_TIMEOUT_MS 2000U + +/* Minimal well-formed TPM_RC_FAILURE response (10-byte header) used to unblock + * a raw-transport host after a malformed or stalled frame. */ +static const uint8_t g_tpmRcFailure[10] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x01, 0x01 +}; + +/* ---- raw UART byte transport over the Cadence UART ---- */ +static int UartRecv(uint8_t* buf, uint32_t sz) +{ + uint32_t i; + for (i = 0; i < sz; i++) { + while (zynq_uart_getc(ZYNQ_CONSOLE_UART_BASE, &buf[i]) == 0) { + /* spin until a byte arrives (Global Timer is free-running HW) */ + } + } + return 0; +} + +/* Bounded receive for the remainder of an in-progress frame: like UartRecv but + * gives up with -1 if no byte arrives within timeoutMs (inter-byte deadline), + * so a sender that stops mid-frame cannot wedge the command loop. */ +static int UartRecvTO(uint8_t* buf, uint32_t sz, uint32_t timeoutMs) +{ + uint32_t i; + uint64_t deadline; + + for (i = 0; i < sz; i++) { + deadline = zynq_millis() + timeoutMs; + while (zynq_uart_getc(ZYNQ_CONSOLE_UART_BASE, &buf[i]) == 0) { + if (zynq_millis() >= deadline) { + return -1; + } + } + } + return 0; +} + +static int UartSend(const uint8_t* buf, uint32_t sz) +{ + zynq_uart_write(ZYNQ_CONSOLE_UART_BASE, buf, sz); + return 0; +} + +/* Discard up to maxBytes of pending input, stopping early once the stream is + * idle for ~10 ms - resyncs the raw transport after a malformed frame without + * blocking indefinitely on a bogus declared length. */ +static void UartDrain(uint32_t maxBytes) +{ + uint8_t b; + uint32_t got = 0U; + uint64_t idleStart = zynq_millis(); + + /* Cap the resync drain regardless of the (untrusted) declared frame length, + * so a peer that keeps the line busy cannot force an unbounded drain. */ + if (maxBytes > FWTPM_MAX_COMMAND_SIZE) { + maxBytes = FWTPM_MAX_COMMAND_SIZE; + } + while (got < maxBytes) { + if (zynq_uart_getc(ZYNQ_CONSOLE_UART_BASE, &b) != 0) { + got++; + idleStart = zynq_millis(); + } + else if ((zynq_millis() - idleStart) > 10U) { + break; + } + } +} + +static uint32_t LoadU32BE(const uint8_t* p) +{ + return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | + ((uint32_t)p[2] << 8) | (uint32_t)p[3]; +} + +static void StoreU32BE(uint8_t* p, uint32_t v) +{ + p[0] = (uint8_t)(v >> 24); + p[1] = (uint8_t)(v >> 16); + p[2] = (uint8_t)(v >> 8); + p[3] = (uint8_t)v; +} + +static void UartSendAck(void) +{ + uint8_t ack[4] = { 0, 0, 0, 0 }; + UartSend(ack, 4); +} + +/* ---- standalone self-test: drive a couple of TPM2 commands directly ---- */ +static void FwTPM_SelfTest(FWTPM_CTX* ctx) +{ + /* TPM2_Startup(SU_CLEAR) */ + static const uint8_t cmdStartup[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00 + }; + /* TPM2_GetRandom(16) */ + static const uint8_t cmdGetRandom[] = { + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x01, 0x7B, 0x00, 0x10 + }; + int rspSize; + uint32_t rc; + int i; + + printf("Self-test:\r\n"); + + rspSize = FWTPM_MAX_COMMAND_SIZE; + FWTPM_ProcessCommand(ctx, cmdStartup, (int)sizeof(cmdStartup), + ctx->rspBuf, &rspSize, 0); + rc = (rspSize >= 10) ? LoadU32BE(ctx->rspBuf + 6) : 0xFFFFFFFFu; + printf(" TPM2_Startup rc=0x%08lX %s\r\n", (unsigned long)rc, + (rc == 0) ? "OK" : "FAIL"); + + rspSize = FWTPM_MAX_COMMAND_SIZE; + FWTPM_ProcessCommand(ctx, cmdGetRandom, (int)sizeof(cmdGetRandom), + ctx->rspBuf, &rspSize, 0); + rc = (rspSize >= 10) ? LoadU32BE(ctx->rspBuf + 6) : 0xFFFFFFFFu; + printf(" TPM2_GetRandom rc=0x%08lX %s", (unsigned long)rc, + (rc == 0) ? "OK bytes=" : "FAIL"); + /* response: hdr(10) + TPM2B size(2) + random bytes */ + if (rc == 0 && rspSize >= 12) { + int n = (int)(((uint16_t)ctx->rspBuf[10] << 8) | ctx->rspBuf[11]); + for (i = 0; i < n && (12 + i) < rspSize; i++) { + printf("%02X", ctx->rspBuf[12 + i]); + } + } + printf("\r\n"); +} + +/* ---- TPM2 command server over UART (raw swtpm + mssim framing) ---- */ +static void FwTPM_UartCommandLoop(FWTPM_CTX* ctx) +{ + uint8_t hdr[4]; + uint16_t tag; + uint32_t mssimCmd; + uint8_t locality; + uint32_t cmdSize, remaining; + int rspSize; + uint32_t rspSzOut; + uint8_t rspHdr[4]; + + for (;;) { + if (UartRecv(hdr, 4) != 0) { + continue; + } + + /* Raw swtpm: TPM command begins with tag 0x8001 or 0x8002. */ + tag = ((uint16_t)hdr[0] << 8) | (uint16_t)hdr[1]; + if (tag == 0x8001 || tag == 0x8002) { + memcpy(ctx->cmdBuf, hdr, 4); + if (UartRecvTO(ctx->cmdBuf + 4, 6, FWTPM_FRAME_TIMEOUT_MS) != 0) { + UartSend(g_tpmRcFailure, sizeof(g_tpmRcFailure)); + continue; + } + cmdSize = LoadU32BE(ctx->cmdBuf + 2); + if (cmdSize < 10 || cmdSize > FWTPM_MAX_COMMAND_SIZE) { + if (cmdSize > 10U) { + UartDrain(cmdSize - 10U); + } + UartSend(g_tpmRcFailure, sizeof(g_tpmRcFailure)); + continue; + } + remaining = cmdSize - 10; + if (remaining > 0) { + if (UartRecvTO(ctx->cmdBuf + 10, remaining, + FWTPM_FRAME_TIMEOUT_MS) != 0) { + UartSend(g_tpmRcFailure, sizeof(g_tpmRcFailure)); + continue; + } + } + rspSize = FWTPM_MAX_COMMAND_SIZE; + FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize, + ctx->rspBuf, &rspSize, 0); + if (rspSize > 0) { + UartSend(ctx->rspBuf, (uint32_t)rspSize); + } + else { + UartSend(g_tpmRcFailure, sizeof(g_tpmRcFailure)); + } + continue; + } + + /* mssim: first 4 bytes are a big-endian platform command code. */ + mssimCmd = LoadU32BE(hdr); + if (mssimCmd == MSSIM_SESSION_END) { + continue; + } + if (mssimCmd == MSSIM_STOP) { + UartSendAck(); + return; + } + if (mssimCmd == MSSIM_SIGNAL_POWER_ON) { + ctx->powerOn = 1; + UartSendAck(); + continue; + } + if (mssimCmd == MSSIM_SIGNAL_POWER_OFF) { + ctx->powerOn = 0; + ctx->wasStarted = 0; + UartSendAck(); + continue; + } + if (mssimCmd == MSSIM_SIGNAL_RESET) { + ctx->wasStarted = 0; + UartSendAck(); + continue; + } + if (mssimCmd != MSSIM_SEND_COMMAND) { + UartSendAck(); + continue; + } + + /* SEND_COMMAND: locality(1) + cmdSize(4) + payload */ + if (UartRecvTO(&locality, 1, FWTPM_FRAME_TIMEOUT_MS) != 0) { + StoreU32BE(rspHdr, 0); + UartSend(rspHdr, 4); + UartSendAck(); + continue; + } + if (UartRecvTO(hdr, 4, FWTPM_FRAME_TIMEOUT_MS) != 0) { + StoreU32BE(rspHdr, 0); + UartSend(rspHdr, 4); + UartSendAck(); + continue; + } + cmdSize = LoadU32BE(hdr); + if (cmdSize == 0 || cmdSize > FWTPM_MAX_COMMAND_SIZE) { + if (cmdSize > 0U) { + UartDrain(cmdSize); + } + StoreU32BE(rspHdr, 0); + UartSend(rspHdr, 4); + UartSendAck(); + continue; + } + if (UartRecvTO(ctx->cmdBuf, cmdSize, FWTPM_FRAME_TIMEOUT_MS) != 0) { + StoreU32BE(rspHdr, 0); + UartSend(rspHdr, 4); + UartSendAck(); + continue; + } + + rspSize = FWTPM_MAX_COMMAND_SIZE; + FWTPM_ProcessCommand(ctx, ctx->cmdBuf, (int)cmdSize, + ctx->rspBuf, &rspSize, (int)locality); + + rspSzOut = (rspSize > 0) ? (uint32_t)rspSize : 0; + StoreU32BE(rspHdr, rspSzOut); + UartSend(rspHdr, 4); + if (rspSzOut != 0) { + UartSend(ctx->rspBuf, rspSzOut); + } + UartSendAck(); + } +} + +int main(void) +{ + FWTPM_CTX* ctx = &g_ctx; + FWTPM_NV_HAL nvHal; + int pufEnrolled = 0; + int rc; + + zynq_uart_init(ZYNQ_CONSOLE_UART_BASE); + + printf("\r\n========================================================\r\n"); + printf(" wolfTPM fwTPM on AMD Zynq-7000 (ZC702)\r\n"); + printf(" Cortex-A9 bare-metal, served over UART\r\n"); + printf("========================================================\r\n"); + +#ifdef FWTPM_PUF_SELFTEST + /* Synthetic SRAM PUF regression (proves the fuzzy-extractor math on this + * silicon; does not touch the physical OCM). */ + FwTPM_Puf_SelfTest(); +#endif + + memset(ctx, 0, sizeof(*ctx)); + memset(&nvHal, 0, sizeof(nvHal)); + +#ifdef FWTPM_NV_QSPI + rc = FWTPM_NV_QSPI_Init(&nvHal); +#else + rc = zynq_nv_ram_init(&nvHal); +#endif + + /* Derive the device-unique NV integrity key from the OCM SRAM PUF and wire + * it into the NV HAL before it is registered. If the PUF is unavailable the + * fwTPM still runs, but without a silicon-bound NV integrity key. */ + if (rc == 0) { + int prc = FwTPM_Puf_Init(&pufEnrolled); + if (prc == 0) { + nvHal.get_integrity_key = FwTPM_Puf_GetIntegrityKey; + FwTPM_Puf_PrintInfo(pufEnrolled); + } + else { + printf("SRAM PUF init failed: %d " + "(NV integrity uses no device key)\r\n", prc); + } + } + + if (rc == 0) { + rc = FWTPM_NV_SetHAL(ctx, &nvHal); + } + if (rc == 0) { + rc = FWTPM_Clock_ZYNQ_Init(ctx); + } + if (rc == 0) { + rc = FWTPM_Init(ctx); + } + if (rc != 0) { + printf("fwTPM init failed: %d\r\n", rc); + for (;;) { } + } + printf("fwTPM %s initialized (CTX %u bytes)\r\n", + FWTPM_GetVersionString(), (unsigned int)sizeof(FWTPM_CTX)); + + FwTPM_SelfTest(ctx); + + printf("Serving TPM2 over UART (swtpm/mssim). No more console output.\r\n"); + FwTPM_UartCommandLoop(ctx); + + FWTPM_Cleanup(ctx); + for (;;) { } + return 0; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/user_settings.h b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/user_settings.h new file mode 100644 index 0000000..89ad1b2 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/user_settings.h @@ -0,0 +1,191 @@ +/* user_settings.h + * + * Combined wolfSSL + wolfTPM settings for the wolfTPM firmware TPM (fwTPM) on + * the Zynq-7000 Cortex-A9 (ZC702), served over UART. Modeled on the STM32H5 and + * Mi-V standalone-UART ports for the transport, and on the ZCU102 R5 port for + * the entropy source: the Zynq-7000 PS has no hardware TRNG, so the Hash-DRBG + * is seeded by wolfCrypt's MemUse entropy (memory-timing jitter conditioned + * through SHA3-256, gated by SP800-90B health tests). SP math is 32-bit + * portable C for the ARMv7-A core. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef WOLFSSL_USER_SETTINGS_H +#define WOLFSSL_USER_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* ---- wolfTPM fwTPM ---- */ +#define WOLFTPM_FWTPM +#define WOLFTPM_SMALL_STACK + +/* No POSIX sleep on bare-metal A9; back XSLEEP_MS with a shim over the Global + * Timer delay (fwtpm_sleep_ms -> zynq_delay_ms) so TPM retry waits are real + * time. The shim keeps a stable prototype independent of the HAL's uint32_t. */ +#ifndef __ASSEMBLER__ +extern void fwtpm_sleep_ms(unsigned int ms); +#endif +#define XSLEEP_MS(ms) fwtpm_sleep_ms((unsigned int)(ms)) + +/* ---- Platform (bare-metal, no OS/filesystem) ---- */ +#define WOLFCRYPT_ONLY +#define SINGLE_THREADED +#define WOLFSSL_SMALL_STACK +#define WOLFSSL_GENERAL_ALIGNMENT 4 +#define SIZEOF_LONG_LONG 8 +#define WOLFSSL_USER_IO +#define WOLFSSL_NO_SOCK +#define NO_FILESYSTEM +#define NO_MAIN_DRIVER +#define NO_WRITEV +#define NO_ASN_TIME /* no RTC */ +#define WOLFSSL_ASN_TEMPLATE +#define LITTLE_ENDIAN_ORDER + +/* ---- Single-precision math (portable C, 32-bit, arbitrary sizes for TPM) ---- */ +#define WOLFSSL_SP_MATH_ALL +#define WOLFSSL_SP_SMALL +#define SP_WORD_SIZE 32 +#define WOLFSSL_SP_384 +#define WOLFSSL_HAVE_SP_ECC + +#define WOLFSSL_PUBLIC_MP +#define WOLFSSL_KEY_GEN + +/* ---- RSA vs PQC (mutually exclusive) ---- + * Default: RSA + ECC. Build with -DFWTPM_ENABLE_PQC for an ECC + post-quantum + * (ML-DSA / ML-KEM) TPM instead; that drops RSA and adds SHA-3 / SHAKE. */ +#ifdef FWTPM_ENABLE_PQC +#define NO_RSA +#else +#define WOLFSSL_HAVE_SP_RSA +#define WC_RSA_BLINDING +#define WC_RSA_PSS +#define WC_RSA_NO_PADDING +#endif + +/* ---- ECC P-256 / P-384 ---- */ +#define HAVE_ECC +#define ECC_USER_CURVES +#undef NO_ECC256 +#define HAVE_ECC384 +#define ECC_SHAMIR +#define ECC_TIMING_RESISTANT +#define HAVE_ECC_KEY_EXPORT + +/* ---- AES (CFB + keywrap + GCM for the TPM) ---- */ +#define HAVE_AESGCM +#define GCM_SMALL +#define HAVE_AES_DECRYPT +#define WOLFSSL_AES_CFB +#define WOLFSSL_AES_DIRECT +#define HAVE_AES_KEYWRAP +#define WOLFSSL_CMAC + +/* ---- Hashing (SHA-1 kept: RSA OAEP MGF1 default) ---- */ +#define WOLFSSL_SHA384 +#define WOLFSSL_SHA512 +#define WOLFSSL_SHA3 /* required by the MemUse entropy conditioner (SHA3-256) */ +#define HAVE_HKDF +#define HAVE_HMAC + +/* ---- SRAM PUF: device-unique NV integrity key ---- */ +/* Derives the fwTPM NV-journal integrity key from the OCM power-on SRAM state + * (BCH fuzzy extractor + HKDF; see fwtpm_puf.c). The BCH profile comes from the + * Makefile PUF_T / PUF_CW (-DWC_PUF_BCH_T / -DWC_PUF_NUM_CODEWORDS); puf.h + * defaults to t=10, cw=16 when unset. Building -DFWTPM_PUF_SELFTEST additionally + * enables the synthetic on-target regression (WOLFSSL_PUF_TEST). */ +#define WOLFSSL_PUF +#ifdef FWTPM_PUF_SELFTEST +#define WOLFSSL_PUF_TEST +#endif + +/* ---- RNG: Hash-DRBG seeded by wolfCrypt MemUse entropy ---- + * The Zynq-7000 PS has no hardware TRNG. MemUse samples memory-access timing + * jitter, conditions it through SHA3-256, and enforces SP800-90B RCT/APT health + * tests fail-closed (seeding, and FWTPM_Init, fail rather than emit weak keys). + * NO_DEV_RANDOM is intentionally NOT defined (it would select a bare-metal seed + * branch without MemUse support); ENTROPY_MEMUSE_FORCE_FAILURE compiles out the + * /dev/urandom fallback so no OS file APIs are referenced. + * + * The A9 has no ARMv7 generic timer, so the high-resolution time source for the + * jitter sampler is supplied via CUSTOM_ENTROPY_TIMEHIRES, backed by the A9 PMU + * cycle counter (fwtpm_entropy_timer in fwtpm_clock_zynq.c). The I-cache (on) + * keeps the SHA3-heavy seeding fast enough to be usable. + * + * ENTROPY_NUM_WORDS_BITS=13 -> 64 KiB state array (8192 x word64). With + * ENTROPY_NUM_UPDATES=16 the max state index (255<<5)+(15<<1)=8190 stays in + * bounds (the default 18 updates would write 4 words past the end at BITS=13). */ +#define HAVE_HASHDRBG +#define WC_NO_RNG_SEED_FALLBACK +#define HAVE_ENTROPY_MEMUSE +#define ENTROPY_MEMUSE_FORCE_FAILURE +#define ENTROPY_NUM_WORDS_BITS 13 +#define ENTROPY_NUM_UPDATES 16 +#define CUSTOM_ENTROPY_TIMEHIRES() fwtpm_entropy_timer() +#ifndef __ASSEMBLER__ +extern unsigned long long fwtpm_entropy_timer(void); +#endif +#define NO_OLD_RNGNAME + +/* ---- Disabled legacy/unused ---- */ +#define NO_OLD_TLS +#define NO_DSA +#define NO_DH +#define NO_RC4 +#define NO_MD4 +#define NO_MD5 +#define NO_DES3 +#define NO_PSK +#define NO_PWDBASED +#define NO_PKCS12 +#define NO_SESSION_CACHE + +#ifdef FWTPM_ENABLE_PQC +/* Post-quantum: ML-DSA (sign) + ML-KEM (encap). Both need SHA-3 / SHAKE and the + * wolfTPM v1.85 spec support. Trimmed to the small-memory paths. */ +#define WOLFTPM_V185 +#define WOLFSSL_EXPERIMENTAL_SETTINGS +#define WOLFSSL_SHAKE128 +#define WOLFSSL_SHAKE256 +#define WOLFSSL_HAVE_MLDSA +#define WOLFSSL_WC_DILITHIUM +#define WOLFSSL_DILITHIUM_NO_LARGE_CODE +#define WOLFSSL_MLDSA_SIGN_SMALL_MEM +#define WOLFSSL_MLDSA_SIGN_SMALL_MEM_PRECALC +#define WOLFSSL_MLDSA_VERIFY_SMALL_MEM +#define WOLFSSL_MLDSA_MAKE_KEY_SMALL_MEM +#define WOLFSSL_HAVE_MLKEM +#define WOLFSSL_WC_MLKEM +#define WOLFSSL_MLKEM_SMALL +#define WOLFSSL_NO_ML_KEM_512 +#define WOLFSSL_NO_ML_KEM_1024 +#else +#define WOLFSSL_NO_SHAKE128 +#define WOLFSSL_NO_SHAKE256 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* WOLFSSL_USER_SETTINGS_H */ diff --git a/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/zynq7000-fwtpm.ld b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/zynq7000-fwtpm.ld new file mode 100644 index 0000000..9d3375b --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/fwtpm-a9/zynq7000-fwtpm.ld @@ -0,0 +1,114 @@ +/* zynq7000-fwtpm.ld + * + * Linker script for the Zynq-7000 Cortex-A9 wolfTPM fwTPM image, linked into + * DDR at 0x04000000 (the FSBL brings DDR up via ps7_init; we load over the top + * with JTAG). Larger heap/stack than the hello image for the SP math and the + * MemUse entropy state. WOLFSSL_SMALL_STACK routes large temporaries to the + * heap, so the heap is generous. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +OUTPUT_ARCH(arm) +ENTRY(_start) + +__stack_size = DEFINED(__stack_size) ? __stack_size : 64K; +__irq_stack_size = DEFINED(__irq_stack_size) ? __irq_stack_size : 4K; +__abt_stack_size = DEFINED(__abt_stack_size) ? __abt_stack_size : 1K; +__und_stack_size = DEFINED(__und_stack_size) ? __und_stack_size : 1K; +__heap_size = DEFINED(__heap_size) ? __heap_size : 1M; + +MEMORY +{ + ddr (rwx) : ORIGIN = 0x04000000, LENGTH = 128M +} + +SECTIONS +{ + .vectors : ALIGN(32) { KEEP(*(.vectors)) } > ddr + .entry : ALIGN(4) { KEEP(*(.entry)) } > ddr + + .text : ALIGN(4) + { + *(.text .text.*) + *(.gnu.linkonce.t.*) + *(.rodata .rodata.*) + *(.gnu.linkonce.r.*) + . = ALIGN(4); + } > ddr + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > ddr + .ARM.extab : ALIGN(4) { *(.ARM.extab* .gnu.linkonce.armextab.*) } > ddr + + .preinit_array : ALIGN(4) + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } > ddr + .init_array : ALIGN(4) + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } > ddr + .fini_array : ALIGN(4) + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + } > ddr + + .data : ALIGN(8) + { + _data_start = .; + *(.data .data.*) + *(.gnu.linkonce.d.*) + . = ALIGN(8); + _data_end = .; + } > ddr + _data_load = LOADADDR(.data); + + .bss (NOLOAD) : ALIGN(8) + { + _bss_start = .; + *(.bss .bss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(8); + _bss_end = .; + } > ddr + + . = ALIGN(8); + PROVIDE (end = .); + PROVIDE (_end = .); + + .heap (NOLOAD) : ALIGN(8) + { + . = . + __heap_size; + . = ALIGN(8); + _heap_end = .; + } > ddr + + .stacks (NOLOAD) : ALIGN(16) + { + . = . + __irq_stack_size; . = ALIGN(16); _irq_stack_top = .; + . = . + __abt_stack_size; . = ALIGN(16); _abt_stack_top = .; + . = . + __und_stack_size; . = ALIGN(16); _und_stack_top = .; + . = . + __stack_size; . = ALIGN(16); _stack_top = .; + } > ddr +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/hello/Makefile b/Xilinx/fwtpm-zc702-a9/firmware/hello/Makefile new file mode 100644 index 0000000..6f83541 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/hello/Makefile @@ -0,0 +1,62 @@ +# Makefile - Zynq-7000 Cortex-A9 hello-world (ZC702) +# +# Bare-metal A9 sanity image (banner + Global-Timer heartbeat) that proves the +# JTAG-over-FSBL load flow and the common HAL before the wolfTPM fwTPM build. +# Uses the arm-none-eabi GCC toolchain (13.x verified). +# +# Copyright (C) 2006-2026 wolfSSL Inc. GPLv2+ (see source headers). + +CROSS_COMPILE ?= arm-none-eabi- +CC = $(CROSS_COMPILE)gcc +OBJCOPY = $(CROSS_COMPILE)objcopy +SIZE = $(CROSS_COMPILE)size + +COMMON_DIR = ../common +TARGET = zc702-hello + +# Cortex-A9 with VFPv3-D16 hard-float (startup.S enables VFP). NEON is present +# on the A9 too but not required here. +ARCHFLAGS = -mcpu=cortex-a9 -mfpu=vfpv3-d16 -mfloat-abi=hard -marm +OPT ?= -O2 + +CFLAGS = $(ARCHFLAGS) $(OPT) -g3 -MMD -MP -ffunction-sections -fdata-sections \ + -ffreestanding -Wall -Wextra -I. -I$(COMMON_DIR) +ASFLAGS = $(ARCHFLAGS) +LDSCRIPT = zynq7000-ddr.ld +LDFLAGS = $(ARCHFLAGS) $(OPT) -T $(LDSCRIPT) -nostartfiles --specs=nano.specs \ + -Wl,--gc-sections -Wl,-Map=$(TARGET).map + +APP_SRCS = main.c \ + $(COMMON_DIR)/zynq_uart.c $(COMMON_DIR)/zynq_time.c \ + $(COMMON_DIR)/mmu.c +ASM_SRCS = $(COMMON_DIR)/startup.S + +BUILD = build +APP_OBJS = $(patsubst %.c,$(BUILD)/%.o,$(notdir $(APP_SRCS))) \ + $(patsubst %.S,$(BUILD)/%.o,$(notdir $(ASM_SRCS))) + +VPATH = $(COMMON_DIR) + +all: $(TARGET).elf $(TARGET).bin + $(SIZE) $(TARGET).elf + +$(BUILD)/%.o: %.c | $(BUILD) + $(CC) $(CFLAGS) -c $< -o $@ +$(BUILD)/%.o: %.S | $(BUILD) + $(CC) $(ASFLAGS) -c $< -o $@ + +$(BUILD): + mkdir -p $@ + +$(TARGET).elf: $(APP_OBJS) $(LDSCRIPT) + $(CC) $(LDFLAGS) $(APP_OBJS) -o $@ + +$(TARGET).bin: $(TARGET).elf + $(OBJCOPY) -O binary $< $@ + +clean: + rm -rf $(BUILD) $(TARGET).elf $(TARGET).bin $(TARGET).map + +-include $(APP_OBJS:.o=.d) + +.PHONY: all clean diff --git a/Xilinx/fwtpm-zc702-a9/firmware/hello/main.c b/Xilinx/fwtpm-zc702-a9/firmware/hello/main.c new file mode 100644 index 0000000..da1664a --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/hello/main.c @@ -0,0 +1,103 @@ +/* main.c + * + * Hello-world over the Cadence UART with a Global-Timer heartbeat on the + * Zynq-7000 Cortex-A9 (ZC702). Proves the JTAG-over-FSBL load flow, the console + * UART, the 64-bit Global Timer and the cache/VFP bring-up before layering + * wolfCrypt and the wolfTPM fwTPM on top. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * wolfTPM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include "zynq7000.h" +#include "zynq_uart.h" +#include "zynq_time.h" + +/* Direct UART output helpers. This bring-up build avoids newlib printf. */ +static void uputs(const char* s) +{ + while (*s != '\0') { + zynq_uart_putc(ZYNQ_CONSOLE_UART_BASE, *s++); + } +} + +static void uputu(uint32_t v) +{ + char buf[10]; + int i = 0; + + if (v == 0u) { + zynq_uart_putc(ZYNQ_CONSOLE_UART_BASE, '0'); + return; + } + while (v > 0u) { + buf[i++] = (char)('0' + (v % 10u)); + v /= 10u; + } + while (i > 0) { + zynq_uart_putc(ZYNQ_CONSOLE_UART_BASE, buf[--i]); + } +} + +static void uputhex32(uint32_t v) +{ + static const char hexd[] = "0123456789ABCDEF"; + int i; + + for (i = 28; i >= 0; i -= 4) { + zynq_uart_putc(ZYNQ_CONSOLE_UART_BASE, hexd[(v >> i) & 0xFu]); + } +} + +int main(void) +{ + uint32_t counter = 0; + + zynq_uart_init(ZYNQ_CONSOLE_UART_BASE); + zynq_timer_init(); + + uputs("\r\n"); + uputs("========================================================\r\n"); + uputs(" wolfSSL / wolfTPM on AMD Zynq-7000 (ZC702)\r\n"); + uputs(" Cortex-A9 bare-metal - Hello World\r\n"); + uputs("========================================================\r\n"); + uputs("Console : Cadence UART @ 0x"); + uputhex32((uint32_t)ZYNQ_CONSOLE_UART_BASE); + uputs(", "); + uputu((uint32_t)ZYNQ_CONSOLE_BAUD); + uputs(" 8N1\r\n"); + uputs("Global Timer : "); + uputu((uint32_t)ZYNQ_GLOBAL_TIMER_FREQ); + uputs(" Hz\r\n"); + uputs("A heartbeat prints each second.\r\n\r\n"); + + for (;;) { + uputs("heartbeat "); + uputu(counter); + uputs(" (uptime "); + uputu((uint32_t)zynq_millis()); + uputs(" ms)\r\n"); + counter++; + + zynq_delay_ms(1000); + } + + return 0; +} diff --git a/Xilinx/fwtpm-zc702-a9/firmware/hello/zynq7000-ddr.ld b/Xilinx/fwtpm-zc702-a9/firmware/hello/zynq7000-ddr.ld new file mode 100644 index 0000000..3d50d97 --- /dev/null +++ b/Xilinx/fwtpm-zc702-a9/firmware/hello/zynq7000-ddr.ld @@ -0,0 +1,115 @@ +/* zynq7000-ddr.ld + * + * 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. + * + * Copyright (C) 2006-2026 wolfSSL Inc. + * + * This file is part of wolfTPM. + * + * wolfTPM is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +OUTPUT_ARCH(arm) +ENTRY(_start) + +__stack_size = DEFINED(__stack_size) ? __stack_size : 16K; +__irq_stack_size = DEFINED(__irq_stack_size) ? __irq_stack_size : 4K; +__abt_stack_size = DEFINED(__abt_stack_size) ? __abt_stack_size : 1K; +__und_stack_size = DEFINED(__und_stack_size) ? __und_stack_size : 1K; +__heap_size = DEFINED(__heap_size) ? __heap_size : 64K; + +MEMORY +{ + ddr (rwx) : ORIGIN = 0x04000000, LENGTH = 64M +} + +SECTIONS +{ + .vectors : ALIGN(32) { KEEP(*(.vectors)) } > ddr + .entry : ALIGN(4) { KEEP(*(.entry)) } > ddr + + .text : ALIGN(4) + { + *(.text .text.*) + *(.gnu.linkonce.t.*) + *(.rodata .rodata.*) + *(.gnu.linkonce.r.*) + . = ALIGN(4); + } > ddr + + .ARM.exidx : ALIGN(4) + { + __exidx_start = .; + *(.ARM.exidx* .gnu.linkonce.armexidx.*) + __exidx_end = .; + } > ddr + .ARM.extab : ALIGN(4) { *(.ARM.extab* .gnu.linkonce.armextab.*) } > ddr + + .preinit_array : ALIGN(4) + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } > ddr + .init_array : ALIGN(4) + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE_HIDDEN (__init_array_end = .); + } > ddr + .fini_array : ALIGN(4) + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT(.fini_array.*))) + KEEP (*(.fini_array)) + PROVIDE_HIDDEN (__fini_array_end = .); + } > ddr + + .data : ALIGN(8) + { + _data_start = .; + *(.data .data.*) + *(.gnu.linkonce.d.*) + . = ALIGN(8); + _data_end = .; + } > ddr + /* RAM image: LMA follows VMA in the one region, so _data_load == _data_start + * and startup.S's copy loop is a no-op. */ + _data_load = LOADADDR(.data); + + .bss (NOLOAD) : ALIGN(8) + { + _bss_start = .; + *(.bss .bss.*) + *(.gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(8); + _bss_end = .; + } > ddr + + . = ALIGN(8); + PROVIDE (end = .); + PROVIDE (_end = .); + + .heap (NOLOAD) : ALIGN(8) + { + . = . + __heap_size; + . = ALIGN(8); + _heap_end = .; + } > ddr + + /* Exception-mode stacks then the SVC/main stack (top of each region). */ + .stacks (NOLOAD) : ALIGN(16) + { + . = . + __irq_stack_size; . = ALIGN(16); _irq_stack_top = .; + . = . + __abt_stack_size; . = ALIGN(16); _abt_stack_top = .; + . = . + __und_stack_size; . = ALIGN(16); _und_stack_top = .; + . = . + __stack_size; . = ALIGN(16); _stack_top = .; + } > ddr +}