From e284546658301714095468f89d200354b755c389 Mon Sep 17 00:00:00 2001 From: David Garske Date: Wed, 29 Jul 2026 11:55:17 -0700 Subject: [PATCH] tegra234: bare-metal BL33 target with verified EL2->EL1 + device-tree boot --- .github/workflows/test-configs.yml | 17 + Makefile | 6 + arch.mk | 24 + config/examples/tegra234-linux.config | 38 ++ config/examples/tegra234.config | 36 ++ docs/Targets.md | 22 + hal/tegra234.c | 824 ++++++++++++++++++++++++++ hal/tegra234.h | 33 ++ hal/tegra234.ld | 86 +++ include/sdhci.h | 7 + src/boot_aarch64_start.S | 21 +- src/sdhci.c | 22 + test-app/app_tegra234.c | 103 ++++ test-app/boot_arm64_start.S | 11 +- tools/scripts/tegra234-mkbl33.sh | 47 ++ tools/scripts/tegra234-mkpoc.sh | 50 ++ 16 files changed, 1345 insertions(+), 2 deletions(-) create mode 100644 config/examples/tegra234-linux.config create mode 100644 config/examples/tegra234.config create mode 100644 hal/tegra234.c create mode 100644 hal/tegra234.h create mode 100644 hal/tegra234.ld create mode 100644 test-app/app_tegra234.c create mode 100755 tools/scripts/tegra234-mkbl33.sh create mode 100755 tools/scripts/tegra234-mkpoc.sh diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 411299218a..7f63706044 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -359,6 +359,23 @@ jobs: config-file: ./config/examples/raspi3-encrypted.config make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- + tegra234_test: + uses: ./.github/workflows/test-build.yml + with: + arch: aarch64 + # Tegra234 bare-metal BL33: build the bootloader only (RAM boot, no + # factory.bin), mirroring raspi3. + config-file: ./config/examples/tegra234.config + make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- + + tegra234_linux_test: + uses: ./.github/workflows/test-build.yml + with: + arch: aarch64 + # Tegra234 DRAM-staged Linux boot (MMU/EL2->EL1/DTB code path). + config-file: ./config/examples/tegra234-linux.config + make-args: wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- + sim_tfm_smallstack_test: uses: ./.github/workflows/test-build.yml with: diff --git a/Makefile b/Makefile index dec0224b8f..33019ba3f7 100644 --- a/Makefile +++ b/Makefile @@ -314,6 +314,12 @@ ifeq ($(TARGET),raspi3) MAIN_TARGET:=wolfboot.bin endif +# Tegra234 bare-metal BL33 boots from RAM (loaded by an earlier stage), +# like raspi3: build the bootloader binary, no contiguous flash factory.bin. +ifeq ($(TARGET),tegra234) + MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin +endif + ifeq ($(TARGET),sim) CFLAGS+=-fno-pie LDFLAGS+=-no-pie diff --git a/arch.mk b/arch.mk index 42a08fc19f..724e08e05d 100644 --- a/arch.mk +++ b/arch.mk @@ -169,6 +169,30 @@ ifeq ($(ARCH),AARCH64) SPI_TARGET=nxp endif + ifeq ($(TARGET),tegra234) + # NVIDIA Jetson Orin (Tegra234, Cortex-A78AE): bare-metal wolfBoot as the + # BL33 firmware stage. The UEFI-application alternative is the aarch64_efi + # target. See hal/tegra234.c. + ARCH_FLAGS=-mcpu=cortex-a78+crypto -march=armv8.2-a+crypto + CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A78 + # Bring-up: dump the state the prior stage handed wolfBoot (entry EL, + # SCTLR/MMU/cache bits, handoff x0/DTB pointer). Opt-out for a quiet build. + ifeq ($(TEGRA234_HANDOFF_DUMP),1) + CFLAGS+=-DTEGRA234_HANDOFF_DUMP + endif + # MMU/WOLFBOOT_FDT/DUALBOOT + fdt.o come from the shared AARCH64 block (all + # aarch64 targets enable them). tegra234 stays MMU-off at runtime (1:1 + # physical); the flags only pull in the FDT/DTS codepath. For a Linux boot, + # drop from EL2 to EL1 with the DTB in x0 when the config requests it + # (config/examples/tegra234-linux.config). + ifeq ($(EL2_HYPERVISOR),1) + CFLAGS+=-DEL2_HYPERVISOR=1 + endif + ifeq ($(BOOT_EL1),1) + CFLAGS+=-DBOOT_EL1 + endif + endif + # Default ARM ASM setting for unrecognized AARCH64 targets ifeq ($(filter zynq versal nxp_ls1028a,$(TARGET)),) NO_ARM_ASM?=1 diff --git a/config/examples/tegra234-linux.config b/config/examples/tegra234-linux.config new file mode 100644 index 0000000000..905b36aaae --- /dev/null +++ b/config/examples/tegra234-linux.config @@ -0,0 +1,38 @@ +# NVIDIA Jetson Orin / Tegra234 - bare-metal BL33, DRAM-staged Linux boot. +# +# wolfBoot runs as the bare-metal BL33 (replacing edk2 UEFI), verifies a signed +# Linux kernel bundled into the BL33 image in DRAM, and boots it EL2->EL1 with a +# device tree - no storage driver required. This sidesteps the SDMMC1 controller +# bring-up (still open; see hal/tegra234.c). The kernel + DTB are bundled by +# tools/scripts/tegra234-mkpoc.sh. +# +# For the storage-based (SDHCI) variant, see tegra234.config + DISK_SDCARD. +ARCH=AARCH64 +TARGET=tegra234 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +DEBUG_UART?=1 +TEGRA234_HANDOFF_DUMP?=1 +SPMATH?=1 +NO_XIP?=1 +# Skip GIC init in the shared bare-metal startup (Orin is GICv3; BL31 set it up). +NO_QNX?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 + +# --- Linux boot payload plumbing --------------------------------------------- +# MMU/WOLFBOOT_FDT are enabled for all aarch64 targets by arch.mk (they only +# switch on the FDT/DTS code path - the two-argument do_boot that forwards a +# DTB pointer; tegra234 stays MMU-off at runtime, 1:1 physical, which is what +# the arm64 Linux boot protocol wants - wolfBoot builds no page tables). +# This config adds the EL2->EL1 drop with the DTB in x0 (the Linux boot ABI). +EL2_HYPERVISOR=1 +BOOT_EL1=1 + +# RAM staging: the bundled payload lives high (at BL33_BASE+0x80000). update_ram +# copies the kernel to LOAD_ADDRESS and relocates the DTB to LOAD_DTS_ADDRESS, +# both in low DRAM, 2 MB-aligned, clear of the 43 MB kernel footprint. +WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000 +WOLFBOOT_LOAD_ADDRESS?=0xA0000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0xA4000000 diff --git a/config/examples/tegra234.config b/config/examples/tegra234.config new file mode 100644 index 0000000000..47d80cf3c7 --- /dev/null +++ b/config/examples/tegra234.config @@ -0,0 +1,36 @@ +# NVIDIA Jetson Orin / Tegra234 - bare-metal wolfBoot as the BL33 firmware stage. +# +# BL33 is the normal-world bootloader that ARM Trusted Firmware (BL31) hands off +# to; wolfBoot replaces the edk2 UEFI / cpu-bootloader slot. This HAL brings up +# the TCU console + ARMv8 generic timer + BPMP IPC on the shared AArch64 startup. +# This base config boots a bundled signed payload at EL2; for the EL2->EL1 + +# device-tree Linux boot contract use config/examples/tegra234-linux.config. +# Validated on hardware via non-persistent RCM boot (see docs/Targets.md, +# "NVIDIA Jetson Orin (NVIDIA Tegra234) BL33 firmware"). +# +# To run wolfBoot under UEFI instead (board-agnostic) use the aarch64_efi target +# (config/examples/aarch64_efi.config). +# +# Toolchain: a bare-metal aarch64-none-elf- toolchain, or aarch64-linux-gnu- +# for the freestanding bootloader-only build (the wolfboot.bin target links +# -ffreestanding -nostartfiles, so the Linux GNU toolchain works too -- this is +# what CI uses via CROSS_COMPILE=aarch64-linux-gnu-). +ARCH=AARCH64 +TARGET=tegra234 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +DEBUG_UART?=1 +# Bring-up: print the handoff state (entry EL, SCTLR/MMU/cache, x0/DTB pointer) +# from hal_init. Set to 0 for a quiet build once the handoff is characterized. +TEGRA234_HANDOFF_DUMP?=1 +SPMATH?=1 +# Boot media is external (SD/eMMC/NVMe via ext_flash); no internal flash. +NO_XIP?=1 +# Skip GIC init in the shared bare-metal startup (Orin is GICv3; BL31 set it up). +NO_QNX?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 +# DRAM load addresses (mirror hal/tegra234.ld). +WOLFBOOT_LOAD_ADDRESS?=0x90000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x91000000 diff --git a/docs/Targets.md b/docs/Targets.md index e41b99f4e2..4ca0bae310 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -33,6 +33,7 @@ This README describes configuration of supported targets. * [NXP T10xx PPC (T1024 / T1040)](#nxp-qoriq-t10xx-ppc-t1024--t1040) * [NXP T2080 PPC](#nxp-qoriq-t2080-ppc) * [Qemu x86-64 UEFI](#qemu-x86-64-uefi) +* [NVIDIA Jetson Orin (NVIDIA Tegra234) BL33 firmware](#nvidia-jetson-orin-nvidia-tegra234-bl33-firmware) * [Raspberry Pi pico 2 (rp2350)](#raspberry-pi-pico-rp2350) * [RealTek RTL8735B (AmebaPro2)](#realtek-rtl8735b-amebapro2) * [Renesas RA6M4](#renesas-ra6m4) @@ -7865,6 +7866,27 @@ You can `Ctrl-C` or login as `root` and power off qemu with `poweroff` +## NVIDIA Jetson Orin (NVIDIA Tegra234) BL33 firmware + +wolfBoot can run on the NVIDIA Jetson Orin two ways: as an `aarch64_efi` UEFI application (documented separately), or - this `tegra234` target - as **bare-metal firmware** that replaces the **BL33** stage. BL33 is the normal-world bootloader that ARM Trusted Firmware (BL31) hands off to at EL2 non-secure with the MMU off; on Jetson it is the edk2 UEFI / cpu-bootloader (cpubl) slot. Running bare-metal instead of under UEFI puts wolfBoot much closer to the root of trust, with a far smaller trusted surface beneath it - wolfBoot owns its own console, clocks, and boot handoff. + +On an unfused developer board the BL33 slot is directly replaceable: MB2 and the earlier stages are inside NVIDIA's signed/fused root of trust and would require NVIDIA signing tooling, but BL33 is not signature-enforced. The bare-metal HAL (`hal/tegra234.c`) provides the Tegra Combined UART (TCU) console, the ARMv8 generic timer, a BPMP IPC driver (clocks/resets over the CPU-NS IVC channel), and a "handoff dump" (entry EL, SCTLR/MMU/cache bits, handoff `x0`) enabled with `TEGRA234_HANDOFF_DUMP=1`. + +Validated on hardware (Jetson Orin Nano dev kit, non-persistent RCM boot): wolfBoot runs as BL33 at EL2, verifies a signed payload with wolfCrypt (ECC384/SHA384), drops from EL2 to EL1, and hands off with a device tree in `x0` - the arm64 Linux boot contract - straight out of DRAM with no storage driver. The payload and DTB are bundled into the BL33 image (see `tools/scripts/tegra234-mkpoc.sh` and `config/examples/tegra234-linux.config`). Loading a full kernel from storage is still in progress, bounded by two limits: MB2 caps the BL33 image at 4 MB (so a full kernel cannot be bundled - it must be loaded from storage or a pre-staged DRAM location), and microSD (SDHCI, `DISK_SDCARD`) is blocked on the closed SDMMC1 controller bring-up. See `hal/tegra234.c` for the full boot-chain map and open questions. + +Build the bootloader binary (no hardware needed to compile): + +``` +cp config/examples/tegra234.config .config +make wolfboot.bin CROSS_COMPILE=aarch64-linux-gnu- +``` + +Build the BL33 image for the DRAM-staged verify + EL2->EL1 + device-tree boot: + +``` +tools/scripts/tegra234-mkpoc.sh +``` + ## Intel x86_64 with Intel FSP support This setup is more complex than the UEFI approach described earlier, but allows diff --git a/hal/tegra234.c b/hal/tegra234.c new file mode 100644 index 0000000000..dacc52c236 --- /dev/null +++ b/hal/tegra234.c @@ -0,0 +1,824 @@ +/* tegra234.c + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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. + * + * wolfBoot 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-1335, USA + */ + +/* + * Bare-metal HAL for the NVIDIA Jetson Orin (Tegra234, Cortex-A78AE): wolfBoot + * as the BL33 firmware stage, replacing the edk2 UEFI payload rather than + * running as a UEFI app. BL33 is the normal-world bootloader that ARM Trusted + * Firmware (BL31) hands off to at EL2 non-secure; on Jetson it is the edk2 + * UEFI / cpu-bootloader (cpubl) slot. This HAL owns the console (Tegra Combined + * UART) and the ARMv8 generic timer, and drives the BL33 -> EL1 handoff. + * + * Validated on hardware: wolfBoot verifies a signed payload with wolfCrypt and + * boots it EL2 -> EL1 with a device tree in x0 (the arm64 Linux boot contract), + * straight out of DRAM with no storage driver (see the tegra234 bundling + * scripts and config/examples/tegra234-linux.config). Register bases are from + * the public Tegra234 memory map / upstream device tree (tegra234.dtsi). + * + * Storage (SDHCI, DISK_SDCARD, below) is in progress: blocked on the SDMMC1 + * controller bring-up (see the comment there). The board-agnostic Orin path + * that runs under UEFI is the aarch64_efi target (hal/aarch64_efi.c). + */ + +#include +#include +#include +#include +#include "image.h" +#include "printf.h" +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +#include "sdhci.h" +#endif + +#ifndef ARCH_AARCH64 +# error "wolfBoot tegra234 HAL: wrong architecture. Compile with ARCH=AARCH64." +#endif + +#if defined(DEBUG_UART) + #define PRINTF_ENABLED +#endif + +/* Linker-provided partition/load addresses (see hal/tegra234.ld). */ +extern uint32_t kernel_addr; +extern uint32_t update_addr; +extern uint32_t dts_addr; + +/* Handoff x0 captured at reset by the .boot stub (src/boot_aarch64_start.S, + * USE_SIMPLE_STARTUP path) - the DTB/params pointer the prior stage passed per + * the arm64 boot protocol. Nonzero initializer keeps it in .data so the BSS + * clear does not wipe it before hal_init reads it. Used by the handoff dump. */ +volatile uint64_t boot_handoff_x0 = 0xFFFFFFFFFFFFFFFFULL; + +/* -------------------------------------------------------------------------- + * ARMv8 generic timer (architectural; no SoC register needed) + * -------------------------------------------------------------------------- */ + +/* Fallback if firmware left CNTFRQ_EL0 unprogrammed. Tegra234's timestamp + * counter runs at 31.25 MHz (CNTFRQ_EL0 = 0x01DCD650, confirmed on silicon). */ +#ifndef TIMER_CLK_FREQ +#define TIMER_CLK_FREQ 31250000ULL +#endif + +static inline uint64_t timer_get_count(void) +{ + uint64_t cntpct; + __asm__ volatile("mrs %0, cntpct_el0" : "=r" (cntpct)); + return cntpct; +} + +static inline uint64_t timer_get_freq(void) +{ + uint64_t cntfrq; + __asm__ volatile("mrs %0, cntfrq_el0" : "=r" (cntfrq)); + return cntfrq ? cntfrq : TIMER_CLK_FREQ; +} + +uint64_t hal_get_timer_us(void) +{ + return (timer_get_count() * 1000000ULL) / timer_get_freq(); +} + +void hal_delay_us(uint32_t us) +{ + uint64_t freq = timer_get_freq(); + uint64_t target = timer_get_count() + ((uint64_t)us * freq) / 1000000ULL; + + while (timer_get_count() < target) + ; +} + +/* -------------------------------------------------------------------------- + * Console: Tegra Combined UART (TCU) via the AON HSP shared mailbox. + * -------------------------------------------------------------------------- */ + +/* + * When wolfBoot runs as BL33 (the "cpubl" slot) the debug console is the + * Tegra Combined UART (TCU, Linux ttyTCU0), NOT a raw NS16550. Every earlier + * stage (MB1/MB2/BL31/OP-TEE) prints through it, and a physical UART like + * UARTA (0x03100000) is neither clocked nor routed at this point -- touching + * it faults with a CBB slave error (RAS uncorrectable) and powers the core + * off. CONFIRMED on hardware 2026-07-27. + * + * The TCU TX path is a shared mailbox that the SPE (AON cluster) drains and + * forwards to the physical debug UART. Per the platform device tree the TCU + * "tx" mailbox is shared-mailbox SM1 of the AON HSP (hsp@c150000). The Tegra + * HSP lays shared mailboxes out at 0x10000 + (n/2)*0x10000 + (n%2)*0x8000, so + * SM1 = 0x0c150000 + 0x18000 = 0x0c168000 (this equals ARM Trusted Firmware's + * TEGRA_CONSOLE_SPE_BASE). Linux's tegra-tcu driver writes the same register + * from EL1 non-secure, so EL2-NS access from wolfBoot is permitted. + * + * Protocol (matches tegra-tcu / ATF console_spe): the writer waits until the + * mailbox FULL bit (31) clears, then writes FULL | (num_bytes<<24) | bytes, + * low byte first. We send one byte per message for simplicity. + */ +#define TEGRA_TCU_TX_MBOX ((volatile uint32_t*)0x0c168000UL) +#define TCU_MBOX_FULL (1u << 31) /* mailbox occupied */ +#define TCU_MBOX_NBYTES(n) ((uint32_t)(n) << 24) + +#if defined(DEBUG_UART) +void uart_init(void) +{ + /* The TCU/SPE mailbox is already live from the earlier boot stages; no + * clock, pinmux, or baud setup is needed (or possible) here. */ +} + +static void tcu_tx(char c) +{ + /* Wait until the SPE has drained the previous byte (FULL clear). */ + while ((*TEGRA_TCU_TX_MBOX & TCU_MBOX_FULL) != 0) + ; + *TEGRA_TCU_TX_MBOX = TCU_MBOX_FULL | TCU_MBOX_NBYTES(1) | (uint8_t)c; +} + +void uart_write(const char* buf, unsigned int sz) +{ + unsigned int i; + for (i = 0; i < sz; i++) { + if (buf[i] == '\n') + tcu_tx('\r'); + tcu_tx(buf[i]); + } +} +#endif /* DEBUG_UART */ + +/* -------------------------------------------------------------------------- + * Partition / load addresses + * -------------------------------------------------------------------------- */ + +/* The signed payload (and, for a Linux boot, the DTB) is bundled into the BL33 + * image at fixed offsets, so update_ram verifies and boots it straight out of + * DRAM with no storage driver. Offsets must clear wolfBoot's runtime footprint + * (~600 KB: text + bss + the 256 KB .stack) and stay under MB2's 4 MB CPUBL + * size cap. BL33 base is the confirmed cpubl load address; keep these in sync + * with hal/tegra234.ld and the bundling scripts (tools/scripts/tegra234-*.sh). */ +#define TEGRA234_BL33_BASE 0x272000000ULL +#define TEGRA234_BUNDLE_OFFSET 0x200000ULL /* 2 MB: signed payload */ +#define TEGRA234_DTB_OFFSET 0x300000ULL /* 3 MB: device tree */ + +void* hal_get_primary_address(void) +{ + return (void*)(uintptr_t)(TEGRA234_BL33_BASE + TEGRA234_BUNDLE_OFFSET); +} + +void* hal_get_update_address(void) +{ + return (void*)&update_addr; +} + +void* hal_get_dts_address(void) +{ +#if defined(MMU) && defined(WOLFBOOT_FDT) + /* DRAM-staged Linux boot: the DTB is bundled into the BL33 image just above + * the signed kernel; update_ram relocates it to WOLFBOOT_LOAD_DTS_ADDRESS + * and hands its pointer to the kernel in x0. */ + return (void*)(uintptr_t)(TEGRA234_BL33_BASE + TEGRA234_DTB_OFFSET); +#else + return (void*)&dts_addr; +#endif +} + +void* hal_get_dts_update_address(void) +{ + return NULL; /* Not yet supported */ +} + +/* -------------------------------------------------------------------------- + * External storage (boot media). Stubbed: booting from storage needs the + * SDHCI/MMC driver (src/sdhci.c + the platform hooks in include/sdhci.h, cf. + * hal/zynq.c Arasan controller), which is in progress (see the SDHCI section). + * -------------------------------------------------------------------------- */ + +#ifdef EXT_FLASH +int ext_flash_read(unsigned long address, uint8_t *data, int len) +{ + XMEMCPY(data, (void *)address, len); + return len; +} + +int ext_flash_erase(unsigned long address, int len) +{ + XMEMSET((void *)address, 0xFF, len); + return len; +} + +int ext_flash_write(unsigned long address, const uint8_t *data, int len) +{ + XMEMCPY((void *)address, data, len); + return len; +} + +void ext_flash_lock(void) +{ +} + +void ext_flash_unlock(void) +{ +} +#endif /* EXT_FLASH */ + +/* -------------------------------------------------------------------------- + * Handoff recon (bring-up) + * + * Prints the machine state the prior boot stage handed wolfBoot: the current + * exception level, the current-EL SCTLR (MMU/I/D-cache enable bits), the + * handoff x0 pointer (candidate DTB/params base) with a short hexdump, plus + * MPIDR/CNTFRQ. Used to confirm the BL33 entry contract (entry EL, whether + * MMU/caches are on, whether a DTB pointer arrives in x0) on new BSP/silicon. + * Enabled with TEGRA234_HANDOFF_DUMP. + * -------------------------------------------------------------------------- */ + +#if defined(DEBUG_UART) && defined(TEGRA234_HANDOFF_DUMP) + +extern volatile uint64_t boot_handoff_x0; + +static inline uint64_t read_current_el(void) +{ + uint64_t v; + __asm__ volatile("mrs %0, CurrentEL" : "=r"(v)); + return (v >> 2) & 0x3; /* EL number */ +} + +static inline uint64_t read_mpidr(void) +{ + uint64_t v; + __asm__ volatile("mrs %0, mpidr_el1" : "=r"(v)); + return v; +} + +/* SCTLR at the current EL. The sysreg name is encoded in the instruction, so + * read the one that matches the level we are actually running at (reading a + * higher EL's SCTLR would trap). */ +static uint64_t read_current_sctlr(uint64_t el) +{ + uint64_t v = 0; + switch (el) { + case 3: __asm__ volatile("mrs %0, sctlr_el3" : "=r"(v)); break; + case 2: __asm__ volatile("mrs %0, sctlr_el2" : "=r"(v)); break; + default: __asm__ volatile("mrs %0, sctlr_el1" : "=r"(v)); break; + } + return v; +} + +/* Print a 64-bit value as two 32-bit halves so it is correct whether or not + * the small UART printf was built with PRINTF_LONG_LONG. */ +static void dump64(const char* name, uint64_t v) +{ + wolfBoot_printf("%s0x%08x%08x\n", name, + (uint32_t)(v >> 32), (uint32_t)(v & 0xFFFFFFFFUL)); +} + +static void tegra234_handoff_dump(void) +{ + uint64_t el = read_current_el(); + uint64_t sctlr = read_current_sctlr(el); + uint64_t x0 = boot_handoff_x0; + + wolfBoot_printf("TEGRA234 handoff recon:\n"); + wolfBoot_printf(" CurrentEL: EL%d\n", (int)el); + dump64(" SCTLR_ELx: ", sctlr); + wolfBoot_printf(" MMU=%d I$=%d D$=%d\n", + (int)(sctlr & 0x1), (int)((sctlr >> 12) & 0x1), + (int)((sctlr >> 2) & 0x1)); + dump64(" MPIDR_EL1: ", read_mpidr()); + dump64(" CNTFRQ_EL0: ", timer_get_freq()); + dump64(" handoff x0: ", x0); + + /* Hexdump the first 64 bytes at the handoff pointer if it looks like a + * sane DRAM address (avoid faulting on a stray value). A DTB would start + * with the magic 0xd00dfeed (big-endian in memory). */ + if (x0 >= 0x80000000ULL && x0 < 0x280000000ULL) { + const uint8_t* p = (const uint8_t*)(uintptr_t)x0; + int i; + wolfBoot_printf(" [x0] first 16 bytes:\n "); + for (i = 0; i < 16; i++) + wolfBoot_printf("%02x ", p[i]); + wolfBoot_printf("\n"); + } + else { + wolfBoot_printf(" [x0] not a plausible DRAM pointer; skipping dump\n"); + } +} +#endif /* DEBUG_UART && TEGRA234_HANDOFF_DUMP (handoff dump) */ + +/* -------------------------------------------------------------------------- + * BPMP IPC (bring-up): enable the SDMMC1 clock/reset so the SDHCI registers + * become accessible. On Tegra234 clocks/resets are owned by the BPMP; the SD + * controller is un-clocked at BL33 (a bare register read of 0x03400000 faults + * with a CBB slave error). We talk to the BPMP over its CPU-NS IVC channel, + * ported from the Linux driver (drivers/firmware/tegra/{ivc.c,bpmp*.c}). + * + * MMU is OFF here, so the BPMP SysRAM is Device/non-cacheable and strongly + * ordered: the kernel driver's cache flush/invalidate steps are no-ops for us + * and a plain "dmb sy" covers the ordering. T234 uses the tegra186 BPMP layout + * (cpu_tx channel at queue index 3, 256-byte queues), signalled by the top0 + * HSP doorbell for master BPMP (19). + * -------------------------------------------------------------------------- */ + +#define BPMP_TXCH 0x40070300UL /* TX SysRAM + cpu_tx(3)*256: our requests */ +#define BPMP_RXCH 0x40071300UL /* RX SysRAM + 0x300: BPMP responses */ +#define IVC_HDR 128 /* header: tx{count,state} + rx{count} */ +#define IVC_TX_COUNT 0x00 +#define IVC_TX_STATE 0x04 +#define IVC_RX_COUNT 0x40 +#define IVC_SYNC 0u +#define IVC_ACK 1u +#define IVC_ESTABLISHED 2u +#define MB_CODE 0x00 /* mb_data: u32 code, u32 flags, u8 data[120] */ +#define MB_FLAGS 0x04 +#define MB_DATA 0x08 +#define MSG_ACK 1u + +#define HSP_TOP0 0x03c00000UL +#define HSP_INT_DIMENSIONING 0x380 +#define HSP_DB_BPMP 3 /* doorbell register index (master 19 -> idx 3) */ + +#define MRQ_PING 0u +#define MRQ_RESET 20u +#define MRQ_CLK 22u +#define CMD_CLK_SET_RATE 2u +#define CMD_CLK_ENABLE 7u +#define CMD_RESET_DEASSERT 2u +#define TEGRA234_CLK_SDMMC1 120u +#define TEGRA234_RESET_SDMMC1 82u +/* SDMMC legacy timeout clock (tmclk). t234 SDHCI (NVQUIRK_HAS_TMCLK) keeps this + * 12 MHz clock enabled all the time; the controller uses it for data-timeout + * (USE_TMCLK_FOR_DATA_TIMEOUT default 1) and advertises it in CAPS. Enabling + * only the main SDMMC1 clock leaves the SD clock path incomplete. */ +#define TEGRA234_CLK_SDMMC_LEGACY_TM 219u +#define TEGRA234_SDMMC_TMCLK_RATE 12000000 + +/* The BPMP transport is needed both by the SDHCI clock-enable (DISK_SDCARD) + * and the standalone handoff/clock bring-up dump. */ +#if defined(DISK_SDCARD) || defined(DISK_EMMC) || \ + (defined(DEBUG_UART) && defined(TEGRA234_HANDOFF_DUMP)) + +static inline void dmb_sy(void) { __asm__ volatile("dmb sy" ::: "memory"); } +static inline uint32_t rd32(uintptr_t a) { return *(volatile uint32_t*)a; } +static inline void wr32(uintptr_t a, uint32_t v) { *(volatile uint32_t*)a = v; } + +static uint32_t bpmp_rx_count; /* frames we have consumed from the BPMP */ + +/* Ring the BPMP doorbell: top0 HSP doorbell block base is computed from the + * INT_DIMENSIONING register; trigger reg for master 19 = base + 19*0x100. */ +static void bpmp_ring_doorbell(void) +{ + uint32_t dim = rd32(HSP_TOP0 + HSP_INT_DIMENSIONING); + uint32_t nsm = dim & 0xf, nss = (dim >> 4) & 0xf, nas = (dim >> 8) & 0xf; + uintptr_t db = HSP_TOP0 + (uintptr_t)(1u + nsm / 2u + nss + nas) * 0x10000UL + + (uintptr_t)HSP_DB_BPMP * 0x100UL; + dmb_sy(); + wr32(db, 1); /* HSP_DB_TRIGGER */ +} + +/* Drive the IVC SYNC->ACK->ESTABLISHED handshake with the BPMP. */ +static int bpmp_ivc_reset(void) +{ + int i; + wr32(BPMP_TXCH + IVC_TX_STATE, IVC_SYNC); + dmb_sy(); + bpmp_ring_doorbell(); + for (i = 0; i < 200000; i++) { + uint32_t peer, mine; + dmb_sy(); + peer = rd32(BPMP_RXCH + IVC_TX_STATE); + mine = rd32(BPMP_TXCH + IVC_TX_STATE); + if (mine == IVC_ESTABLISHED) + return 0; + if (peer == IVC_SYNC) { + wr32(BPMP_TXCH + IVC_TX_COUNT, 0); + wr32(BPMP_RXCH + IVC_RX_COUNT, 0); + bpmp_rx_count = 0; + dmb_sy(); + wr32(BPMP_TXCH + IVC_TX_STATE, IVC_ACK); + bpmp_ring_doorbell(); + } + else if (mine == IVC_SYNC && peer == IVC_ACK) { + wr32(BPMP_TXCH + IVC_TX_COUNT, 0); + wr32(BPMP_RXCH + IVC_RX_COUNT, 0); + bpmp_rx_count = 0; + dmb_sy(); + wr32(BPMP_TXCH + IVC_TX_STATE, IVC_ESTABLISHED); + bpmp_ring_doorbell(); + } + else if (mine == IVC_ACK) { + wr32(BPMP_TXCH + IVC_TX_STATE, IVC_ESTABLISHED); + bpmp_ring_doorbell(); + } + } + return -1; +} + +/* One synchronous MRQ round-trip. Returns the BPMP response code, or -1000 on + * transport timeout. num_frames==1, so the frame is always at channel+IVC_HDR. */ +static int32_t bpmp_transfer(uint32_t mrq, const void* tx, unsigned txsz, + void* rx, unsigned rxsz) +{ + uintptr_t ob = BPMP_TXCH + IVC_HDR; + uintptr_t ib = BPMP_RXCH + IVC_HDR; + uint32_t txc; + int i; + + wr32(ob + MB_CODE, mrq); + wr32(ob + MB_FLAGS, MSG_ACK); + if (tx && txsz) + memcpy((void*)(ob + MB_DATA), tx, txsz); + dmb_sy(); + txc = rd32(BPMP_TXCH + IVC_TX_COUNT); + wr32(BPMP_TXCH + IVC_TX_COUNT, txc + 1); + bpmp_ring_doorbell(); + + for (i = 0; i < 2000000; i++) { + dmb_sy(); + if (rd32(BPMP_RXCH + IVC_TX_COUNT) != bpmp_rx_count) + break; + } + if (i >= 2000000) + return -1000; + + { + int32_t ret = (int32_t)rd32(ib + MB_CODE); + if (rx && rxsz) + memcpy(rx, (void*)(ib + MB_DATA), rxsz); + bpmp_rx_count++; + wr32(BPMP_RXCH + IVC_RX_COUNT, bpmp_rx_count); + dmb_sy(); + return ret; + } +} + +static int32_t bpmp_clk_enable(uint32_t clk_id) +{ + uint32_t req = (CMD_CLK_ENABLE << 24) | (clk_id & 0xFFFFFF); + return bpmp_transfer(MRQ_CLK, &req, sizeof(req), NULL, 0); +} + +static int32_t bpmp_clk_set_rate(uint32_t clk_id, int64_t rate) +{ + uint8_t req[16]; + uint32_t cmd = (CMD_CLK_SET_RATE << 24) | (clk_id & 0xFFFFFF); + memset(req, 0, sizeof(req)); + memcpy(&req[0], &cmd, 4); /* cmd_and_id */ + memcpy(&req[8], &rate, 8); /* [4]=unused, [8]=rate */ + return bpmp_transfer(MRQ_CLK, req, sizeof(req), NULL, 0); +} + +static int32_t bpmp_reset_deassert(uint32_t reset_id) +{ + uint32_t req[2]; + req[0] = CMD_RESET_DEASSERT; + req[1] = reset_id; + return bpmp_transfer(MRQ_RESET, req, sizeof(req), NULL, 0); +} + +static int64_t bpmp_clk_get_rate(uint32_t clk_id) +{ + uint32_t req = (1u << 24) | (clk_id & 0xFFFFFF); /* CMD_CLK_GET_RATE=1 */ + int64_t rate = -1; + bpmp_transfer(MRQ_CLK, &req, sizeof(req), &rate, sizeof(rate)); + return rate; +} + +/* Enable + un-reset the SDMMC1 controller via the BPMP so its SDHCI registers + * become accessible. Safe to call more than once. */ +static int tegra234_sdmmc1_clock_on(void) +{ + if (bpmp_ivc_reset() != 0) + return -1; + /* Set the module clock rate first (BPMP picks a parent), then enable it, + * then release the controller reset. The SDHCI advertises a 208 MHz base + * (CAPS), so drive the module clock to match; the SDHCI internal divider + * then produces the 400 kHz init clock. */ + bpmp_clk_set_rate(TEGRA234_CLK_SDMMC1, 208000000); + bpmp_clk_enable(TEGRA234_CLK_SDMMC1); + /* The SDMMC legacy timeout clock (tmclk) must also be running: t234 SDHCI + * uses it for data-timeout and advertises it in CAPS. Kept always-on at + * 12 MHz (matches sdhci-tegra.c probe). Without it the SD clock path never + * completes and SDCE will not latch. */ + bpmp_clk_set_rate(TEGRA234_CLK_SDMMC_LEGACY_TM, TEGRA234_SDMMC_TMCLK_RATE); + bpmp_clk_enable(TEGRA234_CLK_SDMMC_LEGACY_TM); + bpmp_reset_deassert(TEGRA234_RESET_SDMMC1); + return 0; +} +#endif /* BPMP transport */ + +#if defined(DEBUG_UART) && defined(TEGRA234_HANDOFF_DUMP) +/* Bring-up: establish the BPMP channel, PING it, then clock+un-reset SDMMC1 + * and re-probe its SDHCI registers (which faulted before the clock was on). */ +static void tegra234_bpmp_bringup(void) +{ + uint32_t chal = 0x5a5a5a5a, reply = 0; + int32_t r; + volatile uint32_t* sd = (volatile uint32_t*)0x03400000UL; + + wolfBoot_printf("BPMP: ivc reset...\n"); + if (bpmp_ivc_reset() != 0) { + wolfBoot_printf("BPMP: ivc reset TIMEOUT (channel not established)\n"); + return; + } + wolfBoot_printf("BPMP: established\n"); + + r = bpmp_transfer(MRQ_PING, &chal, sizeof(chal), &reply, sizeof(reply)); + wolfBoot_printf("BPMP: ping ret=%d reply=0x%08x\n", (int)r, (uint32_t)reply); + if (r == -1000) + return; + + (void)r; + tegra234_sdmmc1_clock_on(); + { + int64_t rate = bpmp_clk_get_rate(TEGRA234_CLK_SDMMC1); + wolfBoot_printf("BPMP: SDMMC1 actual rate = %d Hz (hi=%d)\n", + (uint32_t)rate, (uint32_t)((uint64_t)rate >> 32)); + } + + wolfBoot_printf("SDMMC1 probe @0x03400000 (should NOT fault now):\n"); + wolfBoot_printf(" CAPS1(0x40)=0x%08x CAPS2(0x44)=0x%08x\n", + (uint32_t)sd[0x40 / 4], (uint32_t)sd[0x44 / 4]); + wolfBoot_printf(" HOSTVER(0xFC)=0x%08x PRESENT(0x24)=0x%08x\n", + (uint32_t)sd[0xFC / 4], (uint32_t)sd[0x24 / 4]); + wolfBoot_printf("SDMMC1 probe survived -- clock is ON\n"); + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) + /* Exercise the generic SDHCI driver over the Tegra shim: init the card and + * read block 0 (the MBR/GPT protective sector -> should end in 0x55 0xAA). */ + { + static uint32_t blk[128]; /* 512 bytes */ + int rc; + wolfBoot_printf("SDHCI: init...\n"); + rc = sdhci_init(); + wolfBoot_printf("SDHCI: init rc=%d\n", rc); + if (rc == 0) { + memset(blk, 0, sizeof(blk)); + rc = sdhci_read(MMC_CMD17_READ_SINGLE, 0, blk, 512); + wolfBoot_printf("SDHCI: read blk0 rc=%d w0=0x%08x w127=0x%08x\n", + rc, (uint32_t)blk[0], (uint32_t)blk[127]); + } + } +#endif +} +#endif /* DEBUG_UART && TEGRA234_HANDOFF_DUMP */ + +/* -------------------------------------------------------------------------- + * microSD via SDHCI/MMC (DISK_SDCARD) - WIP, off by default. + * + * DISK_SDCARD=1 compiles the generic Cadence-model driver (src/sdhci.c), which + * calls the HAL hooks below. The hooks translate its SRS (0x200+) register + * model to Tegra234 SDMMC1 (sdhci@3400000, a standard-SDHCI controller with + * Tegra vendor registers), enable clocks via the BPMP, and drive the SD-power + * GPIO. STATUS: blocked - the controller will not latch SD Clock Enable at + * BL33; the SDMMC1 functional-clock (axicif) bring-up is done inside closed + * MB2/BPMP firmware only for the boot device (eMMC), not microSD. This is the + * open question for storage boot. Do not expect card I/O yet. + * -------------------------------------------------------------------------- */ + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) + +#include "sdhci.h" + +/* Tegra234 SDMMC1 (external microSD on the Orin Nano dev kit): sdhci@3400000 + * in tegra234.dtsi. SDMMC4 (eMMC) is at 0x03460000. */ +#ifndef TEGRA_SDMMC1_BASE +#define TEGRA_SDMMC1_BASE 0x03400000UL +#endif +#ifndef TEGRA_SDHCI_BASE +#define TEGRA_SDHCI_BASE TEGRA_SDMMC1_BASE +#endif + +/* Offset at which the generic (Cadence) driver places the standard SD Host + * Controller "SRS" register block. Subtract it to reach Tegra's standard-SDHCI + * registers, which begin at offset 0 from the controller base. */ +#define TEGRA_SDHCI_SRS_OFFSET 0x200U + +/* Standard SDHCI byte offsets that the Cadence SRS10/SRS11 32-bit words span. + * Tegra234 SDMMC1 is a v4+ standard-SDHCI controller (HOSTVER 0x0505, A64S + * set), so unlike the Arasan v3.0 on ZynqMP no legacy-SDMA or HV4E masking is + * needed -- just the plain SRS->standard translation with correct access + * widths for the byte/half-word sub-registers. */ +#define STD_HOST_CTRL1 0x28 /* 8-bit */ +#define STD_POWER_CTRL 0x29 /* 8-bit */ +#define STD_BLKGAP_CTRL 0x2A /* 8-bit */ +#define STD_WAKEUP_CTRL 0x2B /* 8-bit */ +#define STD_CLK_CTRL 0x2C /* 16-bit */ +#define STD_TIMEOUT_CTRL 0x2E /* 8-bit */ +#define STD_SW_RESET 0x2F /* 8-bit */ +#define STD_SW_RESET_ALL 0x01 + +uint32_t sdhci_reg_read(uint32_t offset) +{ + volatile uint8_t* base = (volatile uint8_t*)TEGRA_SDHCI_BASE; + + if (offset >= TEGRA_SDHCI_SRS_OFFSET) { + /* Cadence SRS (0x200+) -> Tegra standard-SDHCI (regs at 0). */ + return *((volatile uint32_t*)(base + (offset - TEGRA_SDHCI_SRS_OFFSET))); + } + /* Cadence HRS range: emulate what the generic driver reads. */ + switch (offset) { + case 0x000: /* HRS00 software reset -> standard SW-reset-for-all bit */ + return (*((volatile uint8_t*)(base + STD_SW_RESET)) + & STD_SW_RESET_ALL) ? 1u : 0u; + case 0x010: /* HRS04 PHY access -> report ACK so wait loops exit */ + return (1u << 26); /* SDHCI_HRS04_UIS_ACK */ + default: + return 0; + } +} + +void sdhci_reg_write(uint32_t offset, uint32_t val) +{ + volatile uint8_t* base = (volatile uint8_t*)TEGRA_SDHCI_BASE; + + if (offset >= TEGRA_SDHCI_SRS_OFFSET) { + /* Tegra234 SDMMC accepts 32-bit access to the whole standard register + * block, and (unlike the Arasan v3.0 on ZynqMP) actually requires the + * SRS11 clock/reset word to be written 32-bit for SD-clock-enable to + * latch -- so no byte/half-word decomposition. */ + *((volatile uint32_t*)(base + (offset - TEGRA_SDHCI_SRS_OFFSET))) = val; + return; + } + /* HRS00 write requesting reset -> standard SW-reset-for-all. */ + if (offset == 0x000 && (val & 1u) != 0) + *((volatile uint8_t*)(base + STD_SW_RESET)) = STD_SW_RESET_ALL; +} + +/* Tegra234 SDMMC vendor registers (outside the standard SDHCI range; survive + * the standard Software-Reset-for-All the generic driver issues). */ +#define TEGRA_SDMEM_COMP_PADCTRL 0x1e0 +#define PADCTRL_VREF_SEL_MASK 0x0000000F +#define PADCTRL_VREF_SEL_VAL 0x7 +#define PADCTRL_E_INPUT_E_PWRD (1u << 31) +#define TEGRA_AUTO_CAL_CONFIG 0x1e4 +#define AUTO_CAL_ENABLE (1u << 29) +#define AUTO_CAL_START (1u << 31) +#define TEGRA_AUTO_CAL_STATUS 0x1ec +#define AUTO_CAL_ACTIVE (1u << 31) + +/* Tegra234 SDMMC vendor registers (outside the standard SDHCI range). */ +#define TEGRA_VENDOR_CLOCK_CTRL 0x100 +#define VCC_TAP_MASK 0x00FF0000 +#define VCC_TAP_SHIFT 16 +#define VCC_TRIM_MASK 0x1F000000 +#define VCC_TRIM_SHIFT 24 +#define VCC_SDR50_TUNING_OVERRIDE (1u << 5) +#define VCC_PADPIPE_CLKEN_OVERRIDE (1u << 3) +#define TEGRA_VENDOR_MISC_CTRL 0x120 +#define VMC_ENABLE_SDR104 0x08 +#define VMC_ENABLE_SDR50 0x10 +#define VMC_ENABLE_SPEC_300 0x20 +/* SDMMC1 (microSD) default pad tap/trim from the platform device tree. */ +#define TEGRA_SDMMC1_DEFAULT_TAP 0x0E +#define TEGRA_SDMMC1_DEFAULT_TRIM 0x08 + +/* The microSD 3.3V rail (VDD_3V3_SD) is a GPIO-controlled fixed regulator that + * MB1 leaves off (it boots from QSPI), so the card is unpowered and the SDMMC1 + * controller refuses to enable its SD clock. Per the platform device tree the + * enable is main-GPIO port A pin 0 (regulator-vdd-3v3-sd, enable-active-high). + * The Tegra234 GPIO "gpio" aperture is at 0x02210000; per-pin registers are at + * base + bank*0x1000 + port*0x200 + pin*0x20, and port A is bank 0 / port 0 / + * pin 0 -> base + 0. Drive it high (output, not floated). */ +#define TEGRA_MAIN_GPIO_A0 0x02210000UL +#define GPIO_ENABLE_CONFIG 0x00 /* bit0 ENABLE, bit1 OUT */ +#define GPIO_OUTPUT_CONTROL 0x0C /* bit0 FLOATED (0 = driven) */ +#define GPIO_OUTPUT_VALUE 0x10 /* bit0 HIGH */ + +static void tegra234_sd_power_on(void) +{ + volatile uint8_t* g = (volatile uint8_t*)TEGRA_MAIN_GPIO_A0; + *((volatile uint32_t*)(g + GPIO_OUTPUT_VALUE)) = 1u; /* drive HIGH */ + *((volatile uint32_t*)(g + GPIO_OUTPUT_CONTROL)) &= ~1u; /* not floated */ + *((volatile uint32_t*)(g + GPIO_ENABLE_CONFIG)) = 0x3u; /* ENABLE | OUT */ + /* let the rail ramp before the card is clocked */ + hal_delay_us(2000); +} + +void sdhci_platform_init(void) +{ + /* Power the microSD rail via its enable GPIO before anything else. */ + tegra234_sd_power_on(); + + /* Clocks/resets are BPMP-owned and off at BL33; enable SDMMC1 before the + * generic driver touches any register. MB1 has already applied the SDMMC1 + * pinmux and pad voltage (3.3V for SD init). The vendor clock/pad setup + * that SD Clock Enable depends on is (re)applied in sdhci_platform_set_clock + * below, because it must land after the driver's software-reset-for-all. */ + if (tegra234_sdmmc1_clock_on() != 0) { +#if defined(DEBUG_UART) + wolfBoot_printf("tegra234 sdhci: BPMP SDMMC1 clock enable FAILED\n"); +#endif + } +} + +/* Tegra owns the SD clock in its BPMP/CAR tree: set the SDMMC1 module clock + * equal to the requested card clock so the SDHCI internal divider stays ~1. + * With a large internal divider the Tegra controller will not latch SD Clock + * Enable; matching the module clock to the card clock (as the Linux driver + * does) fixes that. Report the actually-achieved module rate as the base. */ +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) +{ + volatile uint8_t* b = (volatile uint8_t*)TEGRA_SDHCI_BASE; + int64_t actual; + uint32_t v; + + if (clock_khz == 0) + return 0; + + /* Tegra vendor prerequisites for SD Clock Enable to latch. This hook runs + * inside sdhci_set_clock -- i.e. after the driver's software-reset-for-all + * and before it sets SDCE -- which is exactly where the Linux driver + * (re)applies these on reset: + * - VENDOR_CLOCK_CTRL: program tap/trim, clear the override bits + * - VENDOR_MISC_CTRL: default speed (clear SPEC300/SDR50/SDR104) + * - SDMEM_COMP_PADCTRL: power the pads (VREF_SEL + E_INPUT_E_PWRD) */ + v = *((volatile uint32_t*)(b + TEGRA_VENDOR_CLOCK_CTRL)); + v &= ~(VCC_TAP_MASK | VCC_TRIM_MASK | VCC_SDR50_TUNING_OVERRIDE | + VCC_PADPIPE_CLKEN_OVERRIDE); + v |= (TEGRA_SDMMC1_DEFAULT_TRIM << VCC_TRIM_SHIFT) | + (TEGRA_SDMMC1_DEFAULT_TAP << VCC_TAP_SHIFT); + *((volatile uint32_t*)(b + TEGRA_VENDOR_CLOCK_CTRL)) = v; + + v = *((volatile uint32_t*)(b + TEGRA_VENDOR_MISC_CTRL)); + v &= ~(VMC_ENABLE_SPEC_300 | VMC_ENABLE_SDR50 | VMC_ENABLE_SDR104); + *((volatile uint32_t*)(b + TEGRA_VENDOR_MISC_CTRL)) = v; + + v = *((volatile uint32_t*)(b + TEGRA_SDMEM_COMP_PADCTRL)); + v &= ~PADCTRL_VREF_SEL_MASK; + v |= PADCTRL_VREF_SEL_VAL | PADCTRL_E_INPUT_E_PWRD; + *((volatile uint32_t*)(b + TEGRA_SDMEM_COMP_PADCTRL)) = v; + + /* Module clock = card clock, so the SDHCI internal divider stays ~1. */ + bpmp_clk_set_rate(TEGRA234_CLK_SDMMC1, (int64_t)clock_khz * 1000); + actual = bpmp_clk_get_rate(TEGRA234_CLK_SDMMC1); + if (actual <= 0) + return base_clk_khz; /* fall back to the CAPS base */ + return (uint32_t)((uint64_t)actual / 1000); +} + +void sdhci_platform_irq_init(void) +{ + /* Polled-mode: no GIC wiring needed. */ +} + +void sdhci_platform_set_bus_mode(int is_emmc) +{ + (void)is_emmc; /* SDMMC1 is the external microSD (SD, not eMMC). */ +} + +#endif /* DISK_SDCARD || DISK_EMMC */ + +/* -------------------------------------------------------------------------- + * Public HAL entry points + * -------------------------------------------------------------------------- */ + +void hal_init(void) +{ +#if defined(DEBUG_UART) + uart_init(); + wolfBoot_printf("wolfBoot Tegra234 (bare-metal BL33)\n"); +#if defined(TEGRA234_HANDOFF_DUMP) + tegra234_handoff_dump(); + tegra234_bpmp_bringup(); +#endif +#endif +} + +void hal_prepare_boot(void) +{ +} + +/* Internal flash is not used on this target (boot media is SD/eMMC/NVMe via + * the external-flash path). Provide the required no-op HAL flash surface. */ +int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int len) +{ + (void)address; (void)data; (void)len; + return 0; +} + +void RAMFUNCTION hal_flash_unlock(void) +{ +} + +void RAMFUNCTION hal_flash_lock(void) +{ +} + +int RAMFUNCTION hal_flash_erase(uintptr_t address, int len) +{ + (void)address; (void)len; + return 0; +} diff --git a/hal/tegra234.h b/hal/tegra234.h new file mode 100644 index 0000000000..843d3ff62a --- /dev/null +++ b/hal/tegra234.h @@ -0,0 +1,33 @@ +/* tegra234.h + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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. + * + * wolfBoot 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-1335, USA + */ + +#ifndef _TEGRA234_H_ +#define _TEGRA234_H_ + +/* Use the simple AArch64 startup in src/boot_aarch64_start.S: a small .boot + * reset stub that detects the current EL, sets the stack, and calls + * boot_entry_C. The NVIDIA earlier boot stage (MB1/MB2) already brings up + * DRAM/clocks, so wolfBoot does not need the full builtin EL3/MMU init path + * (same model as raspi3). */ +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +#endif /* _TEGRA234_H_ */ diff --git a/hal/tegra234.ld b/hal/tegra234.ld new file mode 100644 index 0000000000..2e49d352f6 --- /dev/null +++ b/hal/tegra234.ld @@ -0,0 +1,86 @@ +/* tegra234.ld - bare-metal link map for the Tegra234 BL33 firmware target. + * + * DRAM-resident, boot-from-DRAM AArch64 map (same style as hal/raspi3.ld). + * wolfBoot links and runs at the confirmed BL33 (cpu-bootloader) load address; + * the kernel/update/dts load addresses below are the low-DRAM staging ranges + * used by the RAM-boot path. Confirmed on hardware (see the MEMORY note). + */ +MEMORY +{ + /* wolfBoot runs as BL33 (the "cpubl" slot). CONFIRMED on hardware + * (2026-07-27): MB2 loads cpubl at 0x272000000 and BL31 enters it there, + * near the top of the 8GB DRAM map (non-ECC 0x80000000..0x280000000). + * wolfBoot is not position-independent, so it must link/run at that base; + * the downstream kernel/DTB are staged separately in low DRAM (see the + * *_load_addr symbols below). */ + DDR_MEM(rwx): ORIGIN = 0x272000000, LENGTH = 0x4000000 +} +ENTRY(_vector_table); + +SECTIONS +{ + .text : + { + _start_text = .; + KEEP(*(.boot*)) + *(.text*) + *(.rodata*) + *(.note.*) + . = ALIGN(4); + _end_text = .; + } > DDR_MEM + .edidx : + { + . = ALIGN(4); + *(.ARM.exidx*) + } > DDR_MEM + + .data : + { + _start_data = .; + KEEP(*(.data*)) + . = ALIGN(4); + KEEP(*(.ramcode)) + . = ALIGN(4); + _end_data = .; + } > DDR_MEM + + .bss (NOLOAD) : + { + _start_bss = .; + __bss_start__ = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _end_bss = .; + __bss_end__ = .; + _end = .; + } > DDR_MEM + + /* Dedicated stack region, reserved immediately above the image's BSS so + * the stack top is valid no matter where the prior stage loads wolfBoot + * (it is image-relative, not tied to DRAM base or LENGTH). The old + * "END_STACK = ORIGIN + LENGTH" put the stack at the top of DDR_MEM, but + * the simple-startup stub actually set sp to the image base (grows down + * BELOW DRAM on this target). This region + the tegra234 sp fixup in + * src/boot_aarch64_start.S give a real, in-DRAM stack. */ + .stack (NOLOAD) : + { + . = ALIGN(16); + _stack_bottom = .; + . += 0x40000; /* 256 KB */ + . = ALIGN(16); + END_STACK = .; + } > DDR_MEM + . = ALIGN(4); +} + +/* Low-DRAM staging addresses used by the RAM-boot path. */ +kernel_addr = 0x80100000; +update_addr = 0x81100000; +dts_addr = 0x800a0000; +kernel_load_addr = 0x90000000; +dts_load_addr = 0x91000000; + +_wolfboot_partition_boot_address = kernel_addr; +_wolfboot_partition_update_address = update_addr; diff --git a/include/sdhci.h b/include/sdhci.h index 35415026cc..21ea0d8a35 100644 --- a/include/sdhci.h +++ b/include/sdhci.h @@ -466,6 +466,13 @@ void sdhci_reg_write(uint32_t offset, uint32_t val); /* Platform initialization (clocks, resets, pin mux, debounce) */ void sdhci_platform_init(void); +/* Optional platform clock hook (weak default in src/sdhci.c is a no-op). + * Called from sdhci_set_clock with the requested card clock and the + * CAPS-derived base; returns the base clock (kHz) to divide from. Platforms + * with a PMC/BPMP clock tree (Tegra) set the module clock = card clock and + * return that so the internal divider stays ~1. */ +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz); + /* Platform interrupt setup (PLIC/NVIC/GIC/etc.) */ void sdhci_platform_irq_init(void); diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 58066d1424..f1ad698d91 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -38,6 +38,10 @@ #include "hal/raspi3.h" #endif +#ifdef TARGET_tegra234 +#include "hal/tegra234.h" +#endif + /* GICv2 Register Offsets */ #ifndef GICD_BASE #define GICD_BASE 0xF9010000 @@ -1185,7 +1189,22 @@ _vector_table: 7: wfi // infinite sleep b 7b -8: mov sp, x1 // set stack pointer +8: +#ifdef TARGET_tegra234 + /* Use the dedicated in-DRAM stack region from hal/tegra234.ld rather than + * the image base (x1), which on this target would grow below DRAM. */ + ldr x1, =END_STACK +#endif + mov sp, x1 // set stack pointer +#ifdef TARGET_tegra234 + /* Capture the handoff x0 (DTB/params pointer, stashed in x21 at reset) + * into a .data global so C (tegra234_handoff_dump) can read it after + * boot_entry_C clears BSS. MMU is off here, so this is a flat, coherent + * store to the image's link address (target is NO_XIP). */ + adrp x2, boot_handoff_x0 + add x2, x2, :lo12:boot_handoff_x0 + str x21, [x2] +#endif bl boot_entry_C // boot_entry_C never returns b 7b // go to sleep anyhow in case. #if 0 diff --git a/src/sdhci.c b/src/sdhci.c index f474184d0a..1f5bfba467 100644 --- a/src/sdhci.c +++ b/src/sdhci.c @@ -356,6 +356,19 @@ static int sdhci_set_power(uint32_t voltage) * ============================================================================ */ /* returns actual frequency in kHz */ +/* Platform clock hook. Weak default: keep the CAPS-derived base clock and let + * the controller's internal divider produce the card clock. A platform whose + * clock is owned by a PMC/BPMP (Tegra) overrides this to set the module clock + * equal to the requested card clock and returns that as the base, so the + * internal divider stays ~1. Returns the base clock (kHz) to divide from, or 0 + * on error. */ +__attribute__((weak)) +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) +{ + (void)clock_khz; + return base_clk_khz; +} + static uint32_t sdhci_set_clock(uint32_t clock_khz) { static uint32_t last_clock_khz = 0; @@ -378,6 +391,15 @@ static uint32_t sdhci_set_clock(uint32_t clock_khz) } base_clk_khz *= 1000; /* convert MHz to kHz */ + /* Let the platform drive its own clock tree if it needs to (e.g. Tegra, + * where the module clock is set equal to the card clock and the internal + * divider stays ~1). The default (weak) implementation is a no-op that + * returns the CAPS-derived base unchanged. */ + base_clk_khz = sdhci_platform_set_clock(clock_khz, base_clk_khz); + if (base_clk_khz == 0) { + return -1; + } + /* calculate divider */ for (i=1; i<2046; i++) { if (((base_clk_khz / i) < clock_khz) || diff --git a/test-app/app_tegra234.c b/test-app/app_tegra234.c new file mode 100644 index 0000000000..e3e96d091d --- /dev/null +++ b/test-app/app_tegra234.c @@ -0,0 +1,103 @@ +/* app_tegra234.c + * + * Test bare-metal boot application + * + * Copyright (C) 2026 wolfSSL Inc. + * + * This file is part of wolfBoot. + * + * wolfBoot 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. + * + * wolfBoot 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-1335, USA + */ + +#include +#include +#include + +#include "wolfboot/wolfboot.h" + +#ifdef TARGET_tegra234 + +/* Tegra Combined UART (TCU) TX shared mailbox (AON HSP SM1). Same console the + * bootloader chain and wolfBoot use; see hal/tegra234.c for the derivation. + * This test-app runs at EL2 with the MMU off (wolfBoot do_boot br's straight + * here), so it can drive the mailbox register directly. */ +#define TCU_TX_MBOX ((volatile uint32_t*)0x0c168000UL) +#define TCU_FULL (1u << 31) + +static void tcu_putc(char c) +{ + while ((*TCU_TX_MBOX & TCU_FULL) != 0) + ; + *TCU_TX_MBOX = TCU_FULL | (1u << 24) | (uint8_t)c; +} + +static void tcu_puts(const char* s) +{ + while (*s != '\0') { + if (*s == '\n') + tcu_putc('\r'); + tcu_putc(*s++); + } +} + +static void tcu_puthex(uint64_t v) +{ + int i; + tcu_puts("0x"); + for (i = 60; i >= 0; i -= 4) + tcu_putc("0123456789abcdef"[(v >> i) & 0xF]); +} + +/* Flattened Device Tree magic (0xd00dfeed, stored big-endian -> reads as + * 0xedfe0dd0 on this little-endian core). */ +#define FDT_MAGIC_LE 0xedfe0dd0u + +void __attribute__((section(".boot"))) main(void) { + uint64_t dtb, el; + + /* wolfBoot's el2_to_el1_boot() passes the DTB pointer in x0; capture it as + * the very first thing, before the compiler can reuse the register. */ + __asm__ volatile("mov %0, x0" : "=r"(dtb)); + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + el >>= 2; + + tcu_puts("\r\n*** wolfBoot Tegra234 PoC payload: verified + booted ***\r\n"); + + /* Prove the EL2->EL1 drop actually happened. */ + tcu_puts(" CurrentEL = EL"); + tcu_putc('0' + (char)(el & 3)); + tcu_puts("\r\n"); + + /* Prove the device tree pointer was handed to us in x0 and points at a + * valid FDT (magic 0xd00dfeed). This is the full bare-metal boot contract: + * a signed payload entered at EL1 with a DTB, exactly as the arm64 Linux + * boot protocol requires. */ + tcu_puts(" DTB (x0) = "); + tcu_puthex(dtb); + tcu_puts("\r\n DTB magic = "); + if (dtb != 0 && *((volatile uint32_t*)(uintptr_t)dtb) == FDT_MAGIC_LE) + tcu_puts("OK (0xd00dfeed)\r\n"); + else { + tcu_puthex(dtb ? *((volatile uint32_t*)(uintptr_t)dtb) : 0); + tcu_puts(" (BAD)\r\n"); + } + + tcu_puts("*** PoC complete: parked ***\r\n"); + + /* Wait for reboot */ + while(1) + ; +} +#endif /** TARGET_tegra234 **/ diff --git a/test-app/boot_arm64_start.S b/test-app/boot_arm64_start.S index d370552c1c..17c92d2fb5 100644 --- a/test-app/boot_arm64_start.S +++ b/test-app/boot_arm64_start.S @@ -26,6 +26,12 @@ .type _start, @function _start: + /* Preserve the handoff argument the prior stage left in x0 (e.g. the DTB + * pointer passed by wolfBoot's el2_to_el1_boot, per the arm64 boot + * protocol) into a callee-saved register, so the data/bss init below can + * freely use x0. It is restored to x0 just before the jump to main. */ + mov x19, x0 + /* Set up stack pointer */ ldr x0, =__stack mov sp, x0 @@ -56,7 +62,10 @@ _start: b.lt 3b 4: - /* Jump to main - never returns */ + /* Restore the handoff argument (x0) and jump to main - never returns. + * main(void) ignores it, but tegra234's PoC payload reads x0 to recover + * the DTB pointer. */ + mov x0, x19 bl main /* If main returns, loop forever */ diff --git a/tools/scripts/tegra234-mkbl33.sh b/tools/scripts/tegra234-mkbl33.sh new file mode 100755 index 0000000000..83b71007d4 --- /dev/null +++ b/tools/scripts/tegra234-mkbl33.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# +# tegra234-mkbl33.sh - build the Tegra234 bare-metal BL33 image for RCM boot. +# +# wolfBoot runs as BL33 (the "cpubl" slot, loaded at 0x272000000) and boots a +# signed test-app at EL2 (the base tegra234.config; no EL2->EL1 drop). There is +# no storage driver, so the payload and the device tree are bundled into the +# BL33 image at fixed offsets and read straight out of DRAM: +# +# [ wolfBoot | pad 0x200000 | signed test-app | pad 0x300000 | DTB ] +# +# hal_get_primary_address() -> BL33_BASE(0x272000000)+0x200000 (payload) and +# hal_get_dts_address() -> +0x300000 (DTB). The result overwrites wolfboot.bin, +# which tegra234_rcmboot.sh flashes as BL33. For the EL2->EL1 + DTB-in-x0 Linux +# boot use tegra234-mkpoc.sh instead. +set -e +cd "$(dirname "$0")/../.." + +PAYLOAD_OFFSET=$((0x200000)) # TEGRA234_BUNDLE_OFFSET (hal) +DTB_OFFSET=$((0x300000)) # TEGRA234_DTB_OFFSET (hal) + +L4T=/home/davidgarske/l4t/Linux_for_Tegra +DTB="${1:-$L4T/kernel/dtb/tegra234-p3768-0000+p3767-0005-nv.dtb}" +[ -f "$DTB" ] || { echo "ERROR: DTB not found: $DTB"; exit 1; } + +cp config/examples/tegra234.config .config +make keysclean >/dev/null 2>&1 || true +make clean >/dev/null 2>&1 || true +# Build wolfBoot and the signed test-app together so they share one key. +make CROSS_COMPILE=aarch64-linux-gnu- wolfboot.bin test-app/image_v1_signed.bin + +core_sz=$(stat -c%s wolfboot.bin) +[ "$core_sz" -le "$PAYLOAD_OFFSET" ] || { echo "ERROR: wolfBoot ($core_sz B) > 0x$(printf %x $PAYLOAD_OFFSET)"; exit 1; } +app_sz=$(stat -c%s test-app/image_v1_signed.bin) +[ $((PAYLOAD_OFFSET + app_sz)) -le "$DTB_OFFSET" ] || { echo "ERROR: payload overruns DTB offset"; exit 1; } + +cp wolfboot.bin bundle.bin +truncate -s "$PAYLOAD_OFFSET" bundle.bin +cat test-app/image_v1_signed.bin >> bundle.bin +truncate -s "$DTB_OFFSET" bundle.bin +cat "$DTB" >> bundle.bin +mv bundle.bin wolfboot.bin + +echo "BL33 bundle -> wolfboot.bin: $(stat -c%s wolfboot.bin) bytes" +echo " wolfBoot core: $core_sz B" +echo " signed test-app @ +0x$(printf %x $PAYLOAD_OFFSET): $app_sz B" +echo " DTB @ +0x$(printf %x $DTB_OFFSET): $(stat -c%s "$DTB") B" diff --git a/tools/scripts/tegra234-mkpoc.sh b/tools/scripts/tegra234-mkpoc.sh new file mode 100755 index 0000000000..341b7e509e --- /dev/null +++ b/tools/scripts/tegra234-mkpoc.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# +# tegra234-mkpoc.sh - build the bare-metal BL33 image for the DRAM-staged boot +# that exercises the security-relevant core: wolfBoot verifies a signed payload, +# drops from EL2 to EL1, and hands it a device tree in x0 - the exact arm64 +# Linux boot contract - without any storage driver. The payload is the tegra234 +# test-app (reports its EL and validates the DTB magic), kept small so the whole +# BL33 bundle stays under MB2's 4 MB CPUBL size cap. +# +# BL33 image layout (loaded at 0x272000000, must be < 4 MB): +# [ wolfBoot | pad to 0x200000 | signed test-app | pad to 0x300000 | raw DTB ] +set -e +cd "$(dirname "$0")/../.." + +PAYLOAD_OFFSET=$((0x200000)) # TEGRA234_BUNDLE_OFFSET (hal) +DTB_OFFSET=$((0x300000)) # TEGRA234_DTB_OFFSET (hal) +MB2_CPUBL_MAX=$((0x400000)) # MB2 hard cap on the BL33 image + +L4T=/home/davidgarske/l4t/Linux_for_Tegra +DTB="${1:-$L4T/kernel/dtb/tegra234-p3768-0000+p3767-0005-nv.dtb}" +[ -f "$DTB" ] || { echo "ERROR: DTB not found: $DTB"; exit 1; } + +echo "== 1. build wolfBoot + signed test-app (shared key) ==" +cp config/examples/tegra234-linux.config .config +make keysclean >/dev/null 2>&1 || true +make clean >/dev/null 2>&1 || true +make CROSS_COMPILE=aarch64-linux-gnu- wolfboot.bin test-app/image_v1_signed.bin + +core_sz=$(stat -c%s wolfboot.bin) +[ "$core_sz" -le "$PAYLOAD_OFFSET" ] || { echo "ERROR: wolfBoot ($core_sz B) > 0x$(printf %x $PAYLOAD_OFFSET)"; exit 1; } +app_sz=$(stat -c%s test-app/image_v1_signed.bin) +[ $((PAYLOAD_OFFSET + app_sz)) -le "$DTB_OFFSET" ] || { echo "ERROR: payload overruns DTB offset"; exit 1; } + +echo "== 2. assemble the BL33 bundle ==" +cp wolfboot.bin bundle.bin +truncate -s "$PAYLOAD_OFFSET" bundle.bin +cat test-app/image_v1_signed.bin >> bundle.bin +truncate -s "$DTB_OFFSET" bundle.bin +cat "$DTB" >> bundle.bin +mv bundle.bin wolfboot.bin + +total=$(stat -c%s wolfboot.bin) +echo "BL33 bundle -> wolfboot.bin: $total bytes" +echo " wolfBoot core: $core_sz B" +echo " signed test-app @ +0x$(printf %x $PAYLOAD_OFFSET): $app_sz B" +echo " DTB @ +0x$(printf %x $DTB_OFFSET): $(stat -c%s "$DTB") B" +if [ "$total" -gt "$MB2_CPUBL_MAX" ]; then + echo "ERROR: bundle ($total B) exceeds MB2 CPUBL cap (0x$(printf %x $MB2_CPUBL_MAX))"; exit 1 +fi +echo " (under MB2 4 MB CPUBL cap: OK)"