diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 6332801975..08eeb7757f 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -385,6 +385,34 @@ 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: bootloader + signed payload (RAM boot, no + # contiguous factory.bin), mirroring raspi3. This is the target's full + # MAIN_TARGET, and what the bundling scripts build. + config-file: ./config/examples/tegra234.config + make-args: wolfboot.bin test-app/image_v1_signed.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 test-app/image_v1_signed.bin CROSS_COMPILE=aarch64-linux-gnu- + + tegra234_sdcard_test: + uses: ./.github/workflows/test-build.yml + with: + arch: aarch64 + # Tegra234 microSD/SDHCI path. Hardware bring-up is still blocked, so + # this job exists to keep the Tegra SDHCI shim and the BPMP clock driver + # compiling. + config-file: ./config/examples/tegra234-sdcard.config + make-args: wolfboot.bin test-app/image_v1_signed.bin CROSS_COMPILE=aarch64-linux-gnu- + sim_tfm_smallstack_test: uses: ./.github/workflows/test-build.yml with: diff --git a/Makefile b/Makefile index b4ef4480d3..158e8655e1 100644 --- a/Makefile +++ b/Makefile @@ -369,6 +369,13 @@ ifeq ($(TARGET),raspi3) MAIN_TARGET:=wolfboot.bin endif +# Tegra234 bare-metal BL33 boots from RAM (loaded by an earlier stage), so +# there is no contiguous flash factory.bin. Build the bootloader plus the +# signed payload, which the bundling scripts concatenate into the BL33 image. +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 3676f5d7e0..867e9ac4f6 100644 --- a/arch.mk +++ b/arch.mk @@ -184,6 +184,41 @@ 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. + # -mstrict-align: wolfBoot runs with the MMU off, where every access is + # Device-nGnRnE and an unaligned access takes an alignment fault. Without + # it the compiler is free to emit unaligned accesses for struct copies. + ARCH_FLAGS=-mcpu=cortex-a78+crypto -march=armv8.2-a+crypto -mstrict-align + CFLAGS+=$(ARCH_FLAGS) -DCORTEX_A78 + # wolfBoot links and runs at the BL33 (cpubl) load address; must agree with + # ORIGIN in hal/tegra234.ld or the RAM-boot overlap guard misjudges where + # wolfBoot lives. + WOLFBOOT_ORIGIN=0x272000000 + # Bring-up: dump the state the prior stage handed wolfBoot (entry EL, + # SCTLR/MMU/cache bits, handoff x0/DTB pointer). Read-only; opt out for a + # quiet build. + ifeq ($(TEGRA234_HANDOFF_DUMP),1) + CFLAGS+=-DTEGRA234_HANDOFF_DUMP + endif + # SDMMC1 bring-up probe. Separate from the dump above because it MUTATES + # SoC state the booted OS inherits: it enables the SDMMC1 clock, deasserts + # its reset, and drives the SD power-rail GPIO. Off unless asked for. + ifeq ($(TEGRA234_SDMMC_PROBE),1) + CFLAGS+=-DTEGRA234_SDMMC_PROBE + endif + # MMU/WOLFBOOT_FDT/DUALBOOT + fdt.o come from the shared AARCH64 block. + # tegra234 stays MMU-off at runtime (1:1 physical); those flags only pull + # in the FDT/DTS codepath. EL2_HYPERVISOR+BOOT_EL1 add the EL2->EL1 drop + # with the DTB in x0 (config/examples/tegra234-linux.config). + ifeq ($(EL2_HYPERVISOR),1) + CFLAGS+=-DEL2_HYPERVISOR=1 + endif + # BOOT_EL1 itself is emitted by options.mk; nothing to add here. + 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..8c7a11772b --- /dev/null +++ b/config/examples/tegra234-linux.config @@ -0,0 +1,45 @@ +# 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 before booting the payload (Orin is GICv3; BL31 set it up). +SKIP_GIC_INIT?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 +# Payload slot in the BL33 bundle (see hal/tegra234.h): bounds the test-app link. +WOLFBOOT_PARTITION_SIZE?=0x100000 + +# --- Linux boot payload plumbing --------------------------------------------- +# MMU/WOLFBOOT_FDT come from arch.mk for every aarch64 target; 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. EL2_HYPERVISOR+BOOT_EL1 add 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 at BL33_BASE+0x200000 and the DTB at +# +0x300000 (see hal/tegra234.h). update_ram copies the kernel to LOAD_ADDRESS +# and relocates the DTB to LOAD_DTS_ADDRESS, both in low DRAM, 2 MB-aligned. +# LOAD_DTS_ADDRESS must stay above LOAD_ADDRESS + RAMBOOT_MAX_SIZE +# (0xA0000000 + 0x4000000 = 0xA4000000), otherwise the DTB relocation writes +# into the tail of a large image that already passed signature verification. +# The cap itself is enforced at runtime by wolfBoot_open_image_address() +# (src/image.c, no-partition branch). +WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000 +WOLFBOOT_LOAD_ADDRESS?=0xA0000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0xA5000000 diff --git a/config/examples/tegra234-sdcard.config b/config/examples/tegra234-sdcard.config new file mode 100644 index 0000000000..9fde288097 --- /dev/null +++ b/config/examples/tegra234-sdcard.config @@ -0,0 +1,39 @@ +# NVIDIA Jetson Orin / Tegra234 - bare-metal BL33, microSD (SDHCI) boot. +# +# WORK IN PROGRESS, does not boot from card yet: the SDMMC1 controller will not +# latch SD Clock Enable at BL33 because its functional (axicif) clock is brought +# up inside closed MB2/BPMP firmware only for the boot device (eMMC), not for +# microSD. See the SDHCI section of hal/tegra234.c. +# +# This config exists so the Tegra SDHCI shim and the BPMP clock driver stay +# compiled (and CI-checked) while that bring-up is blocked. For the paths that +# do work on hardware, use tegra234.config or tegra234-linux.config. +ARCH=AARCH64 +TARGET=tegra234 +SIGN?=ECC384 +HASH?=SHA384 +DEBUG?=1 +DEBUG_UART?=1 +TEGRA234_HANDOFF_DUMP?=1 +# Run the BPMP/SDMMC1 bring-up probe from hal_init. Unlike the handoff dump +# this mutates SoC state (clocks, resets, SD power GPIO), so it is opt-in and +# only enabled here, where the controller has to be brought up anyway. +TEGRA234_SDMMC_PROBE?=1 +SPMATH?=1 +NO_XIP?=1 +# Skip GIC init before booting the payload (Orin is GICv3; BL31 set it up). +SKIP_GIC_INIT?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 +# Payload slot in the BL33 bundle (see hal/tegra234.h): bounds the test-app link. +WOLFBOOT_PARTITION_SIZE?=0x100000 + +# Boot from the external microSD (SDMMC1) via the generic SDHCI driver. +DISK_SDCARD=1 + +# Upper bound on the image copied into RAM before verification. Keep +# LOAD_DTS_ADDRESS above LOAD_ADDRESS + RAMBOOT_MAX_SIZE so the DTB relocation +# cannot land inside the loaded image. +WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000 +WOLFBOOT_LOAD_ADDRESS?=0x90000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x95000000 diff --git a/config/examples/tegra234.config b/config/examples/tegra234.config new file mode 100644 index 0000000000..659907879a --- /dev/null +++ b/config/examples/tegra234.config @@ -0,0 +1,38 @@ +# NVIDIA Jetson Orin / Tegra234 - bare-metal wolfBoot as the BL33 firmware stage +# (the edk2 UEFI / cpu-bootloader slot that ARM Trusted Firmware hands off to). +# 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. +# To run under UEFI instead, use the aarch64_efi target. +# See docs/Targets.md, "NVIDIA Jetson Orin (NVIDIA Tegra234) BL33 firmware". +# +# Toolchain: bare-metal aarch64-none-elf-, or aarch64-linux-gnu- (wolfboot.bin +# links -ffreestanding -nostartfiles, so the Linux GNU toolchain works too; +# that is what CI uses). +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 before booting the payload (Orin is GICv3; BL31 set it up). +SKIP_GIC_INIT?=1 +WOLFBOOT_SECTOR_SIZE?=0x1000 +WOLFBOOT_NO_PARTITIONS=1 +# Hard upper bound on the image size accepted for a RAM load. Enforced at +# runtime by wolfBoot_open_image_address() (src/image.c, no-partition branch). +WOLFBOOT_RAMBOOT_MAX_SIZE=0x4000000 +# Payload slot in the BL33 bundle: BUNDLE_OFFSET..DTB_OFFSET (see +# hal/tegra234.h). Bounds the test-app link so an oversized payload fails to +# build rather than colliding with the bundled DTB. +WOLFBOOT_PARTITION_SIZE?=0x100000 +# DRAM staging. Keep the DTB target above LOAD_ADDRESS + RAMBOOT_MAX_SIZE +# (0x90000000 + 0x4000000 = 0x94000000), so update_ram's DTB relocation cannot +# land inside a large image that already passed signature verification. +WOLFBOOT_LOAD_ADDRESS?=0x90000000 +WOLFBOOT_LOAD_DTS_ADDRESS?=0x95000000 diff --git a/docs/Targets.md b/docs/Targets.md index 71dea72ce2..5256db9f58 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -35,6 +35,7 @@ This README describes configuration of supported targets. * [NXP T2080 PPC](#nxp-qoriq-t2080-ppc) * [Qemu x86-64 UEFI](#qemu-x86-64-uefi) * [NVIDIA Jetson Orin (aarch64_efi)](#nvidia-jetson-orin-aarch64_efi) +* [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) @@ -7966,6 +7967,73 @@ TCG2: PCR 9 (SHA256): `activeBanks=0x6` is the SHA-256 (0x2) + SHA-384 (0x4) PCR banks; the kernel, its command line and the platform device tree are extended into PCR 9 in both, and wolfBoot then reads the PCR back (`TPM2_PCR_Read`) and prints it. An attestation client can compare PCR 9 -- and the TCG2 event log -- against known-good values to confirm exactly which kernel, command line and device tree wolfBoot verified and booted. Choose `MEASURED_PCR_A` to fit the platform's PCR allocation (0-7 are firmware-owned; 8-15 are for OS/loader use). Note the edk2 firmware separately measures the loaded `wolfboot.efi` image itself into its own PCRs via `LoadImage`, so the firmware-verifies-wolfBoot and wolfBoot-measures-kernel events are distinct entries in the log. +## 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 test-app/image_v1_signed.bin CROSS_COMPILE=aarch64-linux-gnu- +``` + +Three example configs ship for this target: + +| Config | Boot path | +|---|---| +| `tegra234.config` | Verify the bundled payload and boot it at EL2 (no exception-level change). The simplest path. | +| `tegra234-linux.config` | Verify, drop EL2 -> EL1, hand off with the DTB in `x0` (the arm64 Linux boot contract). Validated on hardware. | +| `tegra234-sdcard.config` | microSD (SDHCI) boot. **Work in progress** - it compiles and probes but does not boot from card yet; see the SDMMC1 note above. | + +By default all three enable `TEGRA234_HANDOFF_DUMP`, which prints the entry state read-only. Only the microSD config additionally runs the BPMP/SDMMC1 bring-up probe, because enabling the SDMMC1 clock and releasing its reset is a lasting change to SoC state that the booted OS would inherit. + +### BL33 image layout + +There is no storage driver yet, so the signed payload and the device tree are bundled into the BL33 image itself at fixed offsets and read straight out of DRAM. MB2 loads the whole image at `0x272000000` and BL31 enters it there: + +``` +offset 0x000000 wolfBoot (must fit below 0x200000) +offset 0x200000 signed payload <- hal_get_primary_address() +offset 0x300000 raw DTB <- hal_get_dts_address() + 0x400000 MB2 cpubl size cap - the image must stay under this +``` + +The offsets are `TEGRA234_BL33_BASE`, `TEGRA234_BUNDLE_OFFSET` and `TEGRA234_DTB_OFFSET` in `hal/tegra234.h`; the two bundling scripts below use the same values and must be kept in sync with it. wolfBoot checks for the FDT magic at the DTB offset and reports no device tree if nothing was bundled there, so a plain `make wolfboot.bin` does not hand the payload a stale pointer. + +Note on what is signed: wolfBoot verifies the **payload** against its own key. The bundled **device tree** is not covered by that signature - it is protected only by whatever signs the BL33 image as a whole (on an unfused developer board, nothing). Treat the DTB as part of the firmware image's trust boundary, not the payload's. + +### Bundling scripts + +Both scripts take the same arguments and do the same work - copy a config into place, build wolfBoot and the signed test-app together (one `make` invocation, so both are signed with the same freshly generated key), check the size budget, then concatenate the pieces at the offsets above and overwrite `wolfboot.bin` with the finished BL33 image. They differ only in which config they build. + +The DTB is a required input: pass a path as the first argument, or set `L4T` to your `Linux_for_Tegra` directory and the script picks up `kernel/dtb/tegra234-p3768-0000+p3767-0005-nv.dtb` (Orin Nano dev kit) from it. Both scripts run `make keysclean` and `make clean` first, so **each run generates a new signing key** - build the bootloader and the payload from the same run. + +`tools/scripts/tegra234-mkbl33.sh` - uses `config/examples/tegra234.config`. wolfBoot verifies the bundled payload and boots it **at EL2**, i.e. at the same exception level it was entered at. The DTB pointer is still passed to the payload in `x0` (the FDT code path is enabled for every AArch64 target); what this config does not do is the EL2 -> EL1 drop. Useful for bringing up a new board or checking the console and the handoff dump. + +``` +tools/scripts/tegra234-mkbl33.sh /path/to/tegra234-.dtb +``` + +`tools/scripts/tegra234-mkpoc.sh` - uses `config/examples/tegra234-linux.config` (`EL2_HYPERVISOR=1`, `BOOT_EL1=1`). wolfBoot verifies the payload, **drops from EL2 to EL1, and enters it with the DTB pointer in `x0`** - the arm64 Linux boot contract. This is the one to use for the Linux boot path. + +``` +# Pass your board's kernel DTB, or set L4T=/path/to/Linux_for_Tegra: +tools/scripts/tegra234-mkpoc.sh /path/to/tegra234-.dtb +``` + +Both scripts abort if wolfBoot has grown past the payload offset, if the payload runs into the DTB offset, or if the finished bundle exceeds MB2's 4 MB cap - a size overrun is a build error rather than a silently corrupted image - and both print a size breakdown of each piece. + +Useful environment variables: `L4T` (as above) and `CROSS_COMPILE` (the scripts default to `aarch64-linux-gnu-`, which is what CI uses; a bare-metal `aarch64-none-elf-` toolchain also works). Set `TEGRA234_HANDOFF_DUMP=0` in the config for a quiet build once the handoff is characterized. + +wolfBoot is linked into the 2 MB below the payload offset, so an image that outgrows its slot fails at link time (`region DDR_MEM overflowed`) rather than being assembled into a broken bundle. + +The resulting `wolfboot.bin` is flashed into the BL33 (`A_cpu-bootloader`) partition, or - as used for the validation above - loaded non-persistently over USB with the L4T `flash.sh --rcm-boot` flow, which leaves the on-board firmware untouched. + ## 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..e2b2cd9ce4 --- /dev/null +++ b/hal/tegra234.c @@ -0,0 +1,805 @@ +/* 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. BL33 is the + * normal-world bootloader that ARM Trusted Firmware (BL31) hands off to at EL2 + * non-secure. This HAL owns the console (Tegra Combined UART), the generic + * timer, and BPMP IPC, and drives the BL33 -> EL1 handoff. + * + * Validated on hardware: 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. Storage (SDHCI, DISK_SDCARD) is in + * progress; see the SDHCI section. The board-agnostic Orin path that runs under + * UEFI is the aarch64_efi target (hal/aarch64_efi.c). Register definitions are + * in hal/tegra234.h. + */ + +#include +#include +#include +#include + +#if defined(DEBUG_UART) + #define PRINTF_ENABLED +#endif + +#include "image.h" +#include "printf.h" +#include "hal/tegra234.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 + +/* Handoff x0 captured at reset by the .boot stub (src/boot_aarch64_start.S): + * the DTB/params pointer the prior stage passed per the arm64 boot protocol. + * The nonzero initializer keeps it in .data, out of reach of the BSS clear. */ +volatile uint64_t boot_handoff_x0 = 0xFFFFFFFFFFFFFFFFULL; + +/* Flattened Device Tree magic 0xd00dfeed, stored big-endian -> reads back as + * 0xedfe0dd0 on this little-endian core. */ +#define FDT_MAGIC_LE 0xedfe0dd0u + +/* MMU is off throughout, so all MMIO below is Device/non-cacheable and + * strongly ordered: no cache maintenance is needed, and "dmb sy" covers the + * ordering the BPMP IVC protocol requires. */ +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; } + +/* -------------------------------------------------------------------------- + * ARMv8 generic timer (architectural; no SoC register needed) + * -------------------------------------------------------------------------- */ + +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(); +} + +/* Deadline helpers for the polling loops below: one division at setup, none + * in the loop itself. */ +static uint64_t timer_deadline_us(uint32_t us) +{ + return timer_get_count() + (((uint64_t)us * timer_get_freq()) / 1000000ULL); +} + +static int timer_expired(uint64_t deadline) +{ + return timer_get_count() > deadline; +} + +void hal_delay_us(uint32_t us) +{ + uint64_t deadline = timer_deadline_us(us); + + while (!timer_expired(deadline)) + ; +} + +/* -------------------------------------------------------------------------- + * Console: Tegra Combined UART (TCU) via the AON HSP shared mailbox. + * See hal/tegra234.h for the mailbox address derivation and the protocol. + * -------------------------------------------------------------------------- */ + +/* Give up on a mailbox write if the SPE stops draining, rather than hanging + * the boot inside printf. */ +#define TCU_TX_TIMEOUT_US 10000 + +#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. */ +} + +/* Send one mailbox message carrying 1..3 packed bytes. */ +static void tcu_tx_word(uint32_t bytes, unsigned int count) +{ + uint64_t deadline = timer_deadline_us(TCU_TX_TIMEOUT_US); + + while ((rd32(TEGRA_TCU_TX_MBOX) & TEGRA_TCU_MBOX_FULL) != 0) { + if (timer_expired(deadline)) + return; /* drop the characters; do not stall the boot */ + } + wr32(TEGRA_TCU_TX_MBOX, TEGRA_TCU_MBOX_FULL | + ((uint32_t)count << TEGRA_TCU_MBOX_NBYTES_SHIFT) | bytes); +} + +void uart_write(const char* buf, unsigned int sz) +{ + uint32_t bytes = 0; + unsigned int count = 0; + unsigned int i = 0; + int insert_nl = 0; + char c; + + while (i < sz) { + if (insert_nl) { + c = '\n'; + insert_nl = 0; + i++; + } + else if (buf[i] == '\n') { + c = '\r'; + insert_nl = 1; + } + else { + c = buf[i++]; + } + bytes |= (uint32_t)(uint8_t)c << (count * 8); + if (++count == TEGRA_TCU_MBOX_MAX_BYTES) { + tcu_tx_word(bytes, count); + bytes = 0; + count = 0; + } + } + if (count > 0) + tcu_tx_word(bytes, count); +} +#endif /* DEBUG_UART */ + +/* -------------------------------------------------------------------------- + * Partition / load addresses. The signed payload and, for a Linux boot, the + * DTB are bundled into the BL33 image at the offsets in hal/tegra234.h, so + * update_ram boots them straight out of DRAM with no storage driver. + * -------------------------------------------------------------------------- */ + +void* hal_get_primary_address(void) +{ + return (void*)(uintptr_t)(TEGRA234_BL33_BASE + TEGRA234_BUNDLE_OFFSET); +} + +/* No update partition is populated on this target yet: there is no storage + * driver, and the bundling scripts write only the boot payload. This returns + * the reserved DRAM staging address so the update API has a valid target once + * a storage path lands. */ +void* hal_get_update_address(void) +{ + return (void*)(uintptr_t)TEGRA234_UPDATE_ADDR; +} + +void* hal_get_dts_address(void) +{ + uintptr_t dtb = (uintptr_t)(TEGRA234_BL33_BASE + TEGRA234_DTB_OFFSET); + uint32_t totalsize; + + /* Only claim a DTB if one was actually bundled here: a plain + * "make wolfboot.bin" (no bundling script) leaves this offset empty, and + * update_ram would otherwise hand the payload a garbage pointer in x0. + * NOTE: the bundled DTB is covered by whatever signs the BL33 image, not + * by wolfBoot's payload signature. */ + if (rd32(dtb) != FDT_MAGIC_LE) + return NULL; + /* totalsize (big-endian, at offset 4) must fit in what is left of the + * BL33 image: update_ram copies that many bytes, and the bundled DTB is + * not covered by wolfBoot's payload signature. */ + totalsize = __builtin_bswap32(rd32(dtb + 4)); + if (totalsize < 8 || + totalsize > (TEGRA234_CPUBL_MAX_SIZE - TEGRA234_DTB_OFFSET)) + return NULL; + /* update_ram relocates this to WOLFBOOT_LOAD_DTS_ADDRESS and hands the + * pointer to the payload in x0. */ + return (void*)dtb; +} + +void* hal_get_dts_update_address(void) +{ + return NULL; /* Not yet supported */ +} + +/* -------------------------------------------------------------------------- + * External-flash hooks. Not reached by either shipped config (the payload is + * bundled in the BL33 image and read directly); provided as flat memory + * accessors for a build that turns EXT_FLASH on. + * -------------------------------------------------------------------------- */ + +#ifdef EXT_FLASH +int ext_flash_read(unsigned long address, uint8_t *data, int len) +{ + memcpy(data, (void *)address, len); + return len; +} + +int ext_flash_erase(unsigned long address, int len) +{ + memset((void *)address, 0xFF, len); + return len; +} + +int ext_flash_write(unsigned long address, const uint8_t *data, int len) +{ + memcpy((void *)address, data, len); + return len; +} + +void ext_flash_lock(void) +{ +} + +void ext_flash_unlock(void) +{ +} +#endif /* EXT_FLASH */ + +/* -------------------------------------------------------------------------- + * Handoff recon (bring-up, TEGRA234_HANDOFF_DUMP): print the machine state the + * prior stage handed wolfBoot - entry EL, SCTLR MMU/cache bits, the x0 + * DTB/params pointer - to confirm the BL33 entry contract on new BSP/silicon. + * -------------------------------------------------------------------------- */ + +#if defined(DEBUG_UART) && defined(TEGRA234_HANDOFF_DUMP) + +static inline uint64_t read_current_el(void) +{ + uint64_t v; + __asm__ volatile("mrs %0, CurrentEL" : "=r"(v)); + return (v >> 2) & 0x3; +} + +static inline uint64_t read_mpidr(void) +{ + uint64_t v; + __asm__ volatile("mrs %0, mpidr_el1" : "=r"(v)); + return v; +} + +/* The sysreg name is encoded in the instruction, so read the SCTLR that + * matches the EL we are actually at (a higher EL's 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 as two 32-bit halves: 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; + uint64_t cntfrq; + const uint8_t* p; + int i; + + /* Raw register, not timer_get_freq(): a zero here is exactly what the + * dump is meant to reveal, and the fallback would hide it. */ + __asm__ volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq)); + + 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: ", cntfrq); + dump64(" handoff x0: ", x0); + + /* Dump the head of the handoff pointer only if it lands in DRAM (avoid + * faulting on a stray value). A DTB starts with 0xd00dfeed big-endian. */ + if (x0 >= TEGRA234_DRAM_BASE && x0 <= (TEGRA234_DRAM_END - 16)) { + p = (const uint8_t*)(uintptr_t)x0; + 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 */ + +/* -------------------------------------------------------------------------- + * BPMP IPC: clocks and resets are BPMP-owned, so the SDMMC1 registers are + * inaccessible at BL33 (a bare read of the controller base faults with a CBB + * slave error) until the clock is enabled over the BPMP's CPU-NS IVC channel. + * Ported from Linux (drivers/firmware/tegra/{ivc.c,bpmp*.c}); channel layout + * and MRQ definitions are in hal/tegra234.h. + * -------------------------------------------------------------------------- */ + +/* Only the storage path needs the BPMP. Deliberately NOT built for the default + * (RAM boot) configs: enabling the SDMMC1 clock and releasing its reset is a + * lasting change to SoC state that the OS would inherit, so wolfBoot does not + * touch the BPMP unless it actually has to drive the controller. */ +#if defined(DISK_SDCARD) || defined(DISK_EMMC) || defined(TEGRA234_SDMMC_PROBE) + +#define BPMP_IVC_RESET_TIMEOUT_US 100000 /* handshake with the BPMP */ +#define BPMP_XFER_TIMEOUT_US 1000000 /* one MRQ round-trip */ +#define BPMP_ERR_TIMEOUT (-1000) +#define BPMP_ERR_BADARG (-1001) + +static uint32_t bpmp_rx_count; /* frames consumed from the BPMP */ +static int bpmp_ready; /* IVC channel established */ + +/* Ring the BPMP doorbell. The top0 HSP doorbell block base is computed from + * INT_DIMENSIONING; the trigger register is base + index*0x100, where the + * index for master BPMP (19) is TEGRA_HSP_DB_BPMP (3). */ +static void bpmp_ring_doorbell(void) +{ + uint32_t dim = rd32(TEGRA_HSP_TOP0 + TEGRA_HSP_INT_DIMENSIONING); + uint32_t nsm = dim & 0xf, nss = (dim >> 4) & 0xf, nas = (dim >> 8) & 0xf; + uintptr_t db = TEGRA_HSP_TOP0 + + (uintptr_t)(1u + nsm / 2u + nss + nas) * 0x10000UL + + (uintptr_t)TEGRA_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) +{ + uint64_t deadline; + uint32_t peer, mine; + + wr32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_STATE, TEGRA_IVC_SYNC); + dmb_sy(); + bpmp_ring_doorbell(); + + deadline = timer_deadline_us(BPMP_IVC_RESET_TIMEOUT_US); + while (!timer_expired(deadline)) { + dmb_sy(); + peer = rd32(TEGRA_BPMP_RXCH + TEGRA_IVC_TX_STATE); + mine = rd32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_STATE); + if (mine == TEGRA_IVC_ESTABLISHED) + return 0; + if (peer == TEGRA_IVC_SYNC || (mine == TEGRA_IVC_SYNC && + peer == TEGRA_IVC_ACK)) { + wr32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_COUNT, 0); + wr32(TEGRA_BPMP_RXCH + TEGRA_IVC_RX_COUNT, 0); + bpmp_rx_count = 0; + dmb_sy(); + wr32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_STATE, + (peer == TEGRA_IVC_SYNC) ? TEGRA_IVC_ACK : + TEGRA_IVC_ESTABLISHED); + bpmp_ring_doorbell(); + } + else if (mine == TEGRA_IVC_ACK) { + wr32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_STATE, TEGRA_IVC_ESTABLISHED); + bpmp_ring_doorbell(); + } + } + return -1; +} + +/* Establish the channel once; subsequent callers reuse it. */ +static int bpmp_init(void) +{ + if (bpmp_ready) + return 0; + if (bpmp_ivc_reset() != 0) + return -1; + bpmp_ready = 1; + return 0; +} + +/* One synchronous MRQ round-trip. Returns the BPMP response code, or + * BPMP_ERR_TIMEOUT. num_frames==1, so the frame is always at channel+HDR. */ +static int32_t bpmp_transfer(uint32_t mrq, const void* tx, unsigned txsz, + void* rx, unsigned rxsz) +{ + uintptr_t ob = TEGRA_BPMP_TXCH + TEGRA_IVC_HDR_SZ; + uintptr_t ib = TEGRA_BPMP_RXCH + TEGRA_IVC_HDR_SZ; + uint64_t deadline; + int32_t ret; + + /* Fail fast on a channel that was never established, rather than paying + * the full round-trip timeout per call. */ + if (!bpmp_ready) + return BPMP_ERR_TIMEOUT; + if (txsz > TEGRA_BPMP_MB_DATA_SZ || rxsz > TEGRA_BPMP_MB_DATA_SZ) + return BPMP_ERR_BADARG; + + wr32(ob + TEGRA_BPMP_MB_CODE, mrq); + wr32(ob + TEGRA_BPMP_MB_FLAGS, TEGRA_BPMP_MSG_ACK); + /* Clear the whole payload first: the frame is reused across MRQs, so a + * short request would otherwise inherit the tail of a longer one. */ + memset((void*)(ob + TEGRA_BPMP_MB_DATA), 0, TEGRA_BPMP_MB_DATA_SZ); + if (tx != NULL && txsz > 0) + memcpy((void*)(ob + TEGRA_BPMP_MB_DATA), tx, txsz); + dmb_sy(); + wr32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_COUNT, + rd32(TEGRA_BPMP_TXCH + TEGRA_IVC_TX_COUNT) + 1); + bpmp_ring_doorbell(); + + deadline = timer_deadline_us(BPMP_XFER_TIMEOUT_US); + do { + dmb_sy(); + if (rd32(TEGRA_BPMP_RXCH + TEGRA_IVC_TX_COUNT) != bpmp_rx_count) + break; + if (timer_expired(deadline)) { + /* We incremented the TX count but never consumed a response, so + * the channel is now out of step with the BPMP. Drop it: every + * later call fails fast instead of misreading a stale frame. */ + bpmp_ready = 0; + return BPMP_ERR_TIMEOUT; + } + } while (1); + + ret = (int32_t)rd32(ib + TEGRA_BPMP_MB_CODE); + if (rx != NULL && rxsz > 0) + memcpy(rx, (void*)(ib + TEGRA_BPMP_MB_DATA), rxsz); + bpmp_rx_count++; + wr32(TEGRA_BPMP_RXCH + TEGRA_IVC_RX_COUNT, bpmp_rx_count); + dmb_sy(); + return ret; +} + +static int32_t bpmp_clk_enable(uint32_t clk_id) +{ + uint32_t req = (TEGRA_BPMP_CMD_CLK_ENABLE << 24) | (clk_id & 0xFFFFFF); + return bpmp_transfer(TEGRA_BPMP_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 = (TEGRA_BPMP_CMD_CLK_SET_RATE << 24) | (clk_id & 0xFFFFFF); + + memset(req, 0, sizeof(req)); + memcpy(&req[0], &cmd, sizeof(cmd)); /* cmd_and_id */ + memcpy(&req[8], &rate, sizeof(rate));/* [4]=unused, [8]=rate */ + return bpmp_transfer(TEGRA_BPMP_MRQ_CLK, req, sizeof(req), NULL, 0); +} + +static int64_t bpmp_clk_get_rate(uint32_t clk_id) +{ + uint32_t req = (TEGRA_BPMP_CMD_CLK_GET_RATE << 24) | (clk_id & 0xFFFFFF); + int64_t rate = -1; + + if (bpmp_transfer(TEGRA_BPMP_MRQ_CLK, &req, sizeof(req), + &rate, sizeof(rate)) != 0) + return -1; + return rate; +} + +static int32_t bpmp_reset_deassert(uint32_t reset_id) +{ + uint32_t req[2]; + + req[0] = TEGRA_BPMP_CMD_RESET_DEASSERT; + req[1] = reset_id; + return bpmp_transfer(TEGRA_BPMP_MRQ_RESET, req, sizeof(req), NULL, 0); +} + +/* Keep the first failure while still running the whole sequence. */ +static int32_t bpmp_first_err(int32_t err, int32_t ret) +{ + return (err != 0) ? err : ret; +} + +/* Enable + un-reset the SDMMC1 controller so its SDHCI registers become + * accessible. Idempotent. Order: rate (BPMP picks a parent), enable, then + * release the reset. The controller advertises a 208 MHz base in CAPS, so the + * module clock matches it and the SDHCI internal divider produces the 400 kHz + * init clock. tmclk is separate and must also run (see hal/tegra234.h). */ +static int tegra234_sdmmc1_clock_on(void) +{ + int32_t err = 0; + + if (bpmp_init() != 0) + return -1; + err = bpmp_first_err(err, bpmp_clk_set_rate(TEGRA234_CLK_SDMMC1, + TEGRA234_SDMMC1_BASE_RATE)); + err = bpmp_first_err(err, bpmp_clk_enable(TEGRA234_CLK_SDMMC1)); + err = bpmp_first_err(err, + bpmp_clk_set_rate(TEGRA234_CLK_SDMMC_LEGACY_TM, + TEGRA234_SDMMC_TMCLK_RATE)); + err = bpmp_first_err(err, bpmp_clk_enable(TEGRA234_CLK_SDMMC_LEGACY_TM)); + err = bpmp_first_err(err, bpmp_reset_deassert(TEGRA234_RESET_SDMMC1)); + return (err == 0) ? 0 : -1; +} +#endif /* BPMP transport */ + +#if defined(DEBUG_UART) && defined(TEGRA234_SDMMC_PROBE) +/* 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; + uintptr_t sd = TEGRA_SDMMC1_BASE; + int64_t rate; + int32_t r; + + wolfBoot_printf("BPMP: ivc reset...\n"); + if (bpmp_init() != 0) { + wolfBoot_printf("BPMP: ivc reset TIMEOUT (channel not established)\n"); + return; + } + wolfBoot_printf("BPMP: established\n"); + + r = bpmp_transfer(TEGRA_BPMP_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 == BPMP_ERR_TIMEOUT) + return; + + /* Must not fall through to the register probe on failure: reading the + * controller with its clock off faults with a CBB slave error that powers + * the core down - the very failure this dump exists to diagnose. */ + if (tegra234_sdmmc1_clock_on() != 0) { + wolfBoot_printf("BPMP: SDMMC1 clock enable FAILED (skipping probe)\n"); + return; + } + rate = bpmp_clk_get_rate(TEGRA234_CLK_SDMMC1); + if (rate < 0) + wolfBoot_printf("BPMP: SDMMC1 rate query FAILED\n"); + else + wolfBoot_printf("BPMP: SDMMC1 actual rate = 0x%08x%08x Hz\n", + (uint32_t)((uint64_t)rate >> 32), (uint32_t)rate); + + wolfBoot_printf("SDMMC1 probe @0x%08x (should NOT fault now):\n", + (uint32_t)sd); + wolfBoot_printf(" CAPS1=0x%08x CAPS2=0x%08x\n", + rd32(sd + TEGRA_STD_CAPS1), rd32(sd + TEGRA_STD_CAPS2)); + wolfBoot_printf(" HOSTVER=0x%08x PRESENT=0x%08x\n", + rd32(sd + TEGRA_STD_HOST_VERSION), rd32(sd + TEGRA_STD_PRESENT_STATE)); + wolfBoot_printf("SDMMC1 probe survived -- clock is ON\n"); + + /* Card I/O is deliberately not exercised here: the disk boot path runs + * sdhci_init() itself, and calling it twice leaves the driver's cached + * clock stale across its software reset. */ +} +#endif /* DEBUG_UART && TEGRA234_SDMMC_PROBE */ + +/* -------------------------------------------------------------------------- + * microSD via SDHCI/MMC (DISK_SDCARD) - WIP, off by default. + * + * DISK_SDCARD=1 compiles the generic Cadence-model driver (src/sdhci.c); the + * hooks below translate its SRS register model to Tegra234 SDMMC1, 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 + * (axicif) clock bring-up happens inside closed MB2/BPMP firmware only for the + * boot device (eMMC), not microSD. Do not expect card I/O yet. + * -------------------------------------------------------------------------- */ + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) + +static inline uint8_t rd8(uintptr_t a) { return *(volatile uint8_t*)a; } +static inline void wr8(uintptr_t a, uint8_t v) { *(volatile uint8_t*)a = v; } + +/* Set once the BPMP has clocked and un-reset SDMMC1. Until then the + * controller's registers MUST NOT be touched: a bare access faults with a CBB + * slave error (RAS uncorrectable) that powers the core off. The generic driver + * has no way to learn that platform init failed (sdhci_platform_init is void), + * so the accessors below go quiet instead. sdhci_init() then reads 0 from the + * card-detect register and returns -1 through its normal error path. */ +static int sdmmc_ready; + +uint32_t sdhci_reg_read(uint32_t offset) +{ + if (!sdmmc_ready) { + /* Answer the PHY handshake so the driver's wait loops still exit. */ + return (offset == SDHCI_HRS04) ? SDHCI_HRS04_UIS_ACK : 0; + } + if (offset >= TEGRA_SDHCI_SRS_OFFSET) { + /* Cadence SRS (0x200+) -> Tegra standard-SDHCI (regs at 0). */ + return rd32(TEGRA_SDHCI_BASE + offset - TEGRA_SDHCI_SRS_OFFSET); + } + /* Cadence HRS range: emulate what the generic driver reads. */ + switch (offset) { + case SDHCI_HRS00: /* -> standard SW-reset-for-all bit */ + return (rd8(TEGRA_SDHCI_BASE + TEGRA_STD_SW_RESET) + & TEGRA_STD_SW_RESET_ALL) ? 1u : 0u; + case SDHCI_HRS04: /* report ACK so the wait loops exit */ + return SDHCI_HRS04_UIS_ACK; + default: + return 0; + } +} + +void sdhci_reg_write(uint32_t offset, uint32_t val) +{ + if (!sdmmc_ready) + return; + if (offset >= TEGRA_SDHCI_SRS_OFFSET) { + /* Tegra234 accepts 32-bit access to the whole standard block and + * (unlike the Arasan v3.0 on ZynqMP) requires the SRS11 clock/reset + * word to be written 32-bit for SD-clock-enable to latch, so there is + * no byte/half-word decomposition here. */ + wr32(TEGRA_SDHCI_BASE + offset - TEGRA_SDHCI_SRS_OFFSET, val); + return; + } + if (offset == SDHCI_HRS00 && (val & SDHCI_HRS00_SWR) != 0) + wr8(TEGRA_SDHCI_BASE + TEGRA_STD_SW_RESET, TEGRA_STD_SW_RESET_ALL); +} + +/* MB1 boots from QSPI and leaves the microSD 3.3V rail (VDD_3V3_SD) off, so + * the card is unpowered and SDMMC1 refuses to enable its SD clock. Drive the + * enable GPIO high (output, not floated); see hal/tegra234.h. */ +static void tegra234_sd_power_on(void) +{ + wr32(TEGRA_MAIN_GPIO_A0 + TEGRA_GPIO_OUTPUT_VALUE, 1u); + wr32(TEGRA_MAIN_GPIO_A0 + TEGRA_GPIO_OUTPUT_CONTROL, + rd32(TEGRA_MAIN_GPIO_A0 + TEGRA_GPIO_OUTPUT_CONTROL) & ~1u); + wr32(TEGRA_MAIN_GPIO_A0 + TEGRA_GPIO_ENABLE_CONFIG, + rd32(TEGRA_MAIN_GPIO_A0 + TEGRA_GPIO_ENABLE_CONFIG) | + TEGRA_GPIO_ENABLE_OUT); + hal_delay_us(2000); /* let the rail ramp before the card is clocked */ +} + +void sdhci_platform_init(void) +{ + tegra234_sd_power_on(); + + /* MB1 has already applied the SDMMC1 pinmux and pad voltage (3.3V). The + * vendor clock/pad setup 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) { + sdmmc_ready = 1; + } + else { +#if defined(DEBUG_UART) + wolfBoot_printf("tegra234 sdhci: BPMP SDMMC1 clock enable FAILED; " + "leaving the controller untouched\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 divider the controller will not latch SD Clock Enable), and + * report the achieved module rate as the base. */ +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) +{ + uintptr_t b = TEGRA_SDHCI_BASE; + int64_t actual; + uint32_t v; + + if (clock_khz == 0 || !sdmmc_ready) + return 0; + + /* Tegra prerequisites for SD Clock Enable to latch. This hook runs inside + * sdhci_set_clock, i.e. after the software-reset-for-all and before SDCE + * is set - where the Linux driver also (re)applies them. */ + v = rd32(b + TEGRA_VENDOR_CLOCK_CTRL); + v &= ~(TEGRA_VCC_TAP_MASK | TEGRA_VCC_TRIM_MASK | + TEGRA_VCC_SDR50_TUNING_OVERRIDE | TEGRA_VCC_PADPIPE_CLKEN_OVERRIDE); + v |= (TEGRA_SDMMC1_DEFAULT_TRIM << TEGRA_VCC_TRIM_SHIFT) | + (TEGRA_SDMMC1_DEFAULT_TAP << TEGRA_VCC_TAP_SHIFT); + wr32(b + TEGRA_VENDOR_CLOCK_CTRL, v); + + /* default speed */ + v = rd32(b + TEGRA_VENDOR_MISC_CTRL); + v &= ~(TEGRA_VMC_ENABLE_SPEC_300 | TEGRA_VMC_ENABLE_SDR50 | + TEGRA_VMC_ENABLE_SDR104); + wr32(b + TEGRA_VENDOR_MISC_CTRL, v); + + /* power the pads */ + v = rd32(b + TEGRA_SDMEM_COMP_PADCTRL); + v &= ~TEGRA_PADCTRL_VREF_SEL_MASK; + v |= TEGRA_PADCTRL_VREF_SEL_VAL | TEGRA_PADCTRL_E_INPUT_E_PWRD; + wr32(b + TEGRA_SDMEM_COMP_PADCTRL, v); + + if (bpmp_clk_set_rate(TEGRA234_CLK_SDMMC1, + (int64_t)clock_khz * 1000) != 0) + return base_clk_khz; /* fall back to the CAPS base */ + actual = bpmp_clk_get_rate(TEGRA234_CLK_SDMMC1); + if (actual <= 0) + return base_clk_khz; + 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(); /* read-only: prints state, changes none */ +#endif +#if defined(TEGRA234_SDMMC_PROBE) + tegra234_bpmp_bringup(); /* MUTATES SoC state: clocks/resets/GPIO */ +#endif +#endif +} + +void hal_prepare_boot(void) +{ + /* The payload was just copied into DRAM as data. The MMU is off, so no + * D-cache maintenance is needed (with the MMU off, data accesses are + * Device-nGnRnE and never allocate in the D-cache), but + * SCTLR_ELx.I is IMPLEMENTATION DEFINED at BL33 entry: with the I-cache + * on, instruction fetches can still hit stale lines covering the load + * address (e.g. after a warm reset or a repeated RCM load). Invalidate + * before branching, as TF-A and U-Boot do at their handoffs. */ +#if defined(DISK_SDCARD) || defined(DISK_EMMC) + /* Quiesce SDMMC1 so the OS does not inherit a controller this firmware + * left clocked and running. */ + if (sdmmc_ready) + sdhci_shutdown(); +#endif + __asm__ volatile( + "dsb sy \n" + "ic iallu \n" + "dsb sy \n" + "isb \n" + ::: "memory"); +} + +/* No internal flash on this target: wolfBoot is loaded into DRAM by an earlier + * stage. 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..a7244280b8 --- /dev/null +++ b/hal/tegra234.h @@ -0,0 +1,189 @@ +/* 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 + */ + +/* NVIDIA Jetson Orin (Tegra234) hardware definitions. Register bases are from + * the public Tegra234 memory map / upstream device tree (tegra234.dtsi). + * + * src/boot_aarch64_start.S includes this header for the USE_*_STARTUP + * selection below, so nothing here may expand to C-only syntax on the path the + * assembler sees. Everything is a plain integer expression; the pointer + * casting lives in the HAL (hal/tegra234.c). + */ + +#ifndef _TEGRA234_H_ +#define _TEGRA234_H_ + +/* Simple AArch64 startup (src/boot_aarch64_start.S): a .boot reset stub that + * detects the EL, sets sp, and calls boot_entry_C. MB1/MB2 already brought up + * DRAM/clocks, so no builtin EL3/MMU init is needed (same model as raspi3). */ +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +/* --- BL33 image layout --------------------------------------------------- + * MB2 loads the cpu-bootloader (cpubl) slot at BL33_BASE and BL31 enters it + * there; confirmed on hardware. The signed payload and the DTB are bundled + * into the same image at fixed offsets so update_ram boots straight out of + * DRAM with no storage driver. Offsets must clear wolfBoot's runtime + * footprint (~600 KB incl. the 256 KB .stack) and the total must stay under + * MB2's 4 MB cpubl cap. Keep in sync with hal/tegra234.ld and + * tools/scripts/tegra234-mk*.sh. */ +#define TEGRA234_BL33_BASE 0x272000000 +/* Non-ECC DRAM window, used to sanity-check handoff pointers */ +#define TEGRA234_DRAM_BASE 0x80000000 +#define TEGRA234_DRAM_END 0x280000000 +#define TEGRA234_BUNDLE_OFFSET 0x200000 /* 2 MB: signed payload */ +#define TEGRA234_DTB_OFFSET 0x300000 /* 3 MB: device tree */ +#define TEGRA234_CPUBL_MAX_SIZE 0x400000 /* MB2 cap on the image */ + +/* Update partition, staged in low DRAM. A constant rather than a linker + * symbol: an ADRP against an absolute symbol this far from the image base does + * not fit its +/-4GB reach. */ +#define TEGRA234_UPDATE_ADDR 0x81100000 + +/* --- Generic timer ------------------------------------------------------- + * Fallback if firmware left CNTFRQ_EL0 unprogrammed. Tegra234's timestamp + * counter runs at 31.25 MHz (CNTFRQ_EL0 = 0x01DCD650, read on silicon). */ +#ifndef TIMER_CLK_FREQ +#define TIMER_CLK_FREQ 31250000 +#endif + +/* --- Console: Tegra Combined UART (TCU) ---------------------------------- + * At BL33 the console is the TCU (Linux ttyTCU0), not a raw NS16550: a + * physical UART such as UARTA (0x03100000) is neither clocked nor routed + * here, and touching it faults with a CBB slave error (RAS uncorrectable) + * that powers the core off. Confirmed on hardware 2026-07-27. + * + * TX is an AON HSP shared mailbox the SPE drains and forwards to the debug + * UART. Per the platform device tree the TCU "tx" mailbox is SM1 of the AON + * HSP (hsp@c150000); Tegra HSP lays shared mailboxes out at + * 0x10000 + (n/2)*0x10000 + (n%2)*0x8000, so SM1 = 0x0c150000 + 0x18000 + * (this equals ARM Trusted Firmware's TEGRA_CONSOLE_SPE_BASE). + * + * Protocol (matches tegra-tcu / ATF console_spe): wait for FULL to clear, + * then write FULL | count<<24 | up to 3 bytes packed little-endian. */ +#define TEGRA_TCU_TX_MBOX 0x0c168000 +#define TEGRA_TCU_MBOX_FULL (1u << 31) +#define TEGRA_TCU_MBOX_NBYTES_SHIFT 24 +#define TEGRA_TCU_MBOX_MAX_BYTES 3 + +/* --- BPMP IPC: IVC channel + HSP doorbell -------------------------------- + * Clocks and resets are owned by the BPMP. 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 -> doorbell register index 3). */ +#define TEGRA_BPMP_TXCH 0x40070300 /* TX SysRAM + cpu_tx(3)*256 */ +#define TEGRA_BPMP_RXCH 0x40071300 /* RX SysRAM + 0x300 */ +#define TEGRA_IVC_HDR_SZ 128 /* tx{count,state} + rx{count} */ +#define TEGRA_IVC_TX_COUNT 0x00 +#define TEGRA_IVC_TX_STATE 0x04 +#define TEGRA_IVC_RX_COUNT 0x40 +#define TEGRA_IVC_SYNC 0 +#define TEGRA_IVC_ACK 1 +#define TEGRA_IVC_ESTABLISHED 2 + +#define TEGRA_HSP_TOP0 0x03c00000 +#define TEGRA_HSP_INT_DIMENSIONING 0x380 +#define TEGRA_HSP_DB_BPMP 3 + +/* BPMP mb_data frame: u32 code, u32 flags, u8 data[120] */ +#define TEGRA_BPMP_MB_CODE 0x00 +#define TEGRA_BPMP_MB_FLAGS 0x04 +#define TEGRA_BPMP_MB_DATA 0x08 +#define TEGRA_BPMP_MB_DATA_SZ 120 +#define TEGRA_BPMP_MSG_ACK 1 + +/* MRQ requests and the clock/reset sub-commands we issue */ +#define TEGRA_BPMP_MRQ_PING 0 +#define TEGRA_BPMP_MRQ_RESET 20 +#define TEGRA_BPMP_MRQ_CLK 22 +#define TEGRA_BPMP_CMD_CLK_GET_RATE 1 +#define TEGRA_BPMP_CMD_CLK_SET_RATE 2 +#define TEGRA_BPMP_CMD_CLK_ENABLE 7 +#define TEGRA_BPMP_CMD_RESET_DEASSERT 2 + +#define TEGRA234_CLK_SDMMC1 120 +#define TEGRA234_RESET_SDMMC1 82 +/* SDMMC legacy timeout clock (tmclk). t234 SDHCI (NVQUIRK_HAS_TMCLK) keeps + * this 12 MHz clock on at all times for data-timeout, and advertises it in + * CAPS; without it the SD clock path never completes. */ +#define TEGRA234_CLK_SDMMC_LEGACY_TM 219 +#define TEGRA234_SDMMC_TMCLK_RATE 12000000 +/* SDMMC1 base clock: the controller advertises 208 MHz in CAPS */ +#define TEGRA234_SDMMC1_BASE_RATE 208000000 + +/* --- SDMMC / SDHCI ------------------------------------------------------- + * SDMMC1 (external microSD on the Orin Nano dev kit) is sdhci@3400000 in + * tegra234.dtsi; SDMMC4 (eMMC) is at 0x03460000. Tegra234 SDMMC1 is a v4+ + * standard-SDHCI controller (HOSTVER 0x0505, A64S set). */ +#define TEGRA_SDMMC1_BASE 0x03400000 +#define TEGRA_SDMMC4_BASE 0x03460000 +#ifndef TEGRA_SDHCI_BASE +#define TEGRA_SDHCI_BASE TEGRA_SDMMC1_BASE +#endif + +/* Offset at which the generic (Cadence) driver in src/sdhci.c places the + * standard SD Host Controller "SRS" block; subtract it to reach Tegra's + * standard-SDHCI registers, which begin at offset 0. */ +#define TEGRA_SDHCI_SRS_OFFSET 0x200 + +/* Standard SDHCI registers the shim reaches directly (the Cadence HRS + * definitions come from include/sdhci.h) */ +#define TEGRA_STD_SW_RESET 0x2F /* 8-bit */ +#define TEGRA_STD_SW_RESET_ALL 0x01 +#define TEGRA_STD_CAPS1 0x40 +#define TEGRA_STD_CAPS2 0x44 +#define TEGRA_STD_PRESENT_STATE 0x24 +#define TEGRA_STD_HOST_VERSION 0xFC + +/* Tegra SDMMC vendor registers (outside the standard SDHCI range, so they + * survive the driver's Software-Reset-for-All) */ +#define TEGRA_VENDOR_CLOCK_CTRL 0x100 +#define TEGRA_VCC_TAP_MASK 0x00FF0000 +#define TEGRA_VCC_TAP_SHIFT 16 +#define TEGRA_VCC_TRIM_MASK 0x1F000000 +#define TEGRA_VCC_TRIM_SHIFT 24 +#define TEGRA_VCC_PADPIPE_CLKEN_OVERRIDE (1u << 3) +#define TEGRA_VCC_SDR50_TUNING_OVERRIDE (1u << 5) +#define TEGRA_VENDOR_MISC_CTRL 0x120 +#define TEGRA_VMC_ENABLE_SDR104 0x08 +#define TEGRA_VMC_ENABLE_SDR50 0x10 +#define TEGRA_VMC_ENABLE_SPEC_300 0x20 +#define TEGRA_SDMEM_COMP_PADCTRL 0x1e0 +#define TEGRA_PADCTRL_VREF_SEL_MASK 0x0000000F +#define TEGRA_PADCTRL_VREF_SEL_VAL 0x7 +#define TEGRA_PADCTRL_E_INPUT_E_PWRD (1u << 31) + +/* SDMMC1 (microSD) default pad tap/trim from the platform device tree */ +#define TEGRA_SDMMC1_DEFAULT_TAP 0x0E +#define TEGRA_SDMMC1_DEFAULT_TRIM 0x08 + +/* --- Main GPIO: microSD 3.3V rail ---------------------------------------- + * VDD_3V3_SD is a GPIO-controlled fixed regulator that MB1 leaves off (it + * boots from QSPI). Per the platform device tree the enable is main-GPIO port + * A pin 0 (regulator-vdd-3v3-sd, enable-active-high). Per-pin registers are + * at base + bank*0x1000 + port*0x200 + pin*0x20, and port A is bank 0 / + * port 0 / pin 0. */ +#define TEGRA_MAIN_GPIO_A0 0x02210000 +#define TEGRA_GPIO_ENABLE_CONFIG 0x00 /* bit0 ENABLE, bit1 OUT */ +#define TEGRA_GPIO_ENABLE_OUT 0x03 /* ENABLE | OUT */ +#define TEGRA_GPIO_OUTPUT_CONTROL 0x0C /* bit0 FLOATED (0 = driven) */ +#define TEGRA_GPIO_OUTPUT_VALUE 0x10 /* bit0 HIGH */ + +#endif /* _TEGRA234_H_ */ diff --git a/hal/tegra234.ld b/hal/tegra234.ld new file mode 100644 index 0000000000..5ad5a512e6 --- /dev/null +++ b/hal/tegra234.ld @@ -0,0 +1,84 @@ +/* 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). + * Keep the addresses here in sync with hal/tegra234.h. + */ +MEMORY +{ + /* MB2 loads the cpubl (BL33) slot at 0x272000000 and BL31 enters it there, + * near the top of the 8GB DRAM map (non-ECC 0x80000000..0x280000000); + * confirmed on hardware 2026-07-27. wolfBoot is not position-independent, + * so it must link/run at that base. + * + * LENGTH is deliberately the payload offset (TEGRA234_BUNDLE_OFFSET, 2 MB) + * rather than the whole window: growing wolfBoot into the bundled payload + * is then a link error instead of an image the bundling scripts silently + * assemble wrong. */ + DDR_MEM(rwx): ORIGIN = 0x272000000, LENGTH = 0x200000 +} +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 : + { + PROVIDE(_stored_data = .); /* XIP builds copy .data from here */ + _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__ = .; + } > DDR_MEM + + /* Dedicated stack region, image-relative so the stack top is valid + * wherever the prior stage loads wolfBoot. Used by the tegra234 sp fixup + * in src/boot_aarch64_start.S. */ + .stack (NOLOAD) : + { + . = ALIGN(16); + _stack_bottom = .; + . += 0x40000; /* 256 KB */ + . = ALIGN(16); + END_STACK = .; + /* _end marks the top of wolfBoot's runtime footprint, so it sits + * above the stack rather than at the end of .bss. src/update_ram.c + * compares against it in the RAM-boot overlap guard (built only with + * EXT_FLASH+NO_XIP), and it is the conventional heap start. */ + _end = .; + } > DDR_MEM + . = ALIGN(4); +} + +/* No partition symbols here: this target builds WOLFBOOT_NO_PARTITIONS, so the + * boot/update/DTB addresses come from hal_get_*_address() in hal/tegra234.c + * (constants in hal/tegra234.h) and the RAM staging targets come from + * WOLFBOOT_LOAD_ADDRESS / WOLFBOOT_LOAD_DTS_ADDRESS in the config. Defining + * them twice only invites the two copies to disagree. */ diff --git a/include/sdhci.h b/include/sdhci.h index 21e89ea40c..a686a04890 100644 --- a/include/sdhci.h +++ b/include/sdhci.h @@ -471,6 +471,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 no-op default in src/sdhci.c). Called + * from sdhci_set_clock with the requested card clock and the CAPS-derived + * base; returns the base clock (kHz) to divide from, or 0 on error. Platforms + * whose clock tree is owned by a PMC/BPMP (Tegra) set the module clock equal + * to the card clock and return that, keeping the internal divider ~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 e3ddb9259e..9fbff4c4b8 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 @@ -1257,7 +1261,21 @@ _vector_table: 7: wfi // infinite sleep b 7b -8: mov sp, x1 // set stack pointer +8: +#ifdef TARGET_tegra234 + /* Use the reserved in-DRAM stack from hal/tegra234.ld. The generic path + * sets sp to the image base, which on this target is the BL33 load address + * - growing down from there runs into memory wolfBoot does not own. */ + ldr x1, =END_STACK +#endif + mov sp, x1 // set stack pointer +#ifdef TARGET_tegra234 + /* Stash the handoff x0 (DTB pointer, saved in x21 at reset) in a .data + * global so C can read it after boot_entry_C clears BSS. */ + 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 60ae4d32cb..eda3d6603a 100644 --- a/src/sdhci.c +++ b/src/sdhci.c @@ -373,15 +373,25 @@ static int sdhci_set_power(uint32_t voltage) * Clock Control * ============================================================================ */ +/* Weak default: keep the CAPS-derived base clock and let the controller's + * internal divider produce the card clock. See include/sdhci.h. */ +__attribute__((weak)) +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) +{ + (void)clock_khz; + return base_clk_khz; +} + /* returns actual frequency in kHz */ static uint32_t sdhci_set_clock(uint32_t clock_khz) { static uint32_t last_clock_khz = 0; - uint32_t reg, base_clk_khz, i, mclk, freq_khz; + uint32_t reg, base_clk_khz, i, mclk, freq_khz, to; if (last_clock_khz != 0 && last_clock_khz == clock_khz) { - /* clock already set */ - return 0; + /* Already at this frequency. Return it (not 0) so that a 0 return is + * unambiguously an error for any caller that starts checking. */ + return last_clock_khz; } /* disable clock */ @@ -390,11 +400,24 @@ static uint32_t sdhci_set_clock(uint32_t clock_khz) /* get base clock */ reg = SDHCI_REG(SDHCI_SRS16); base_clk_khz = (reg & SDHCI_SRS16_BCSDCLK_MASK) >> SDHCI_SRS16_BCSDCLK_SHIFT; + base_clk_khz *= 1000; /* convert MHz to kHz */ + + /* Let the platform drive its own clock tree if it needs to. Called before + * the base-clock check so a platform whose CAPS report 0 (because the + * module clock is owned by a PMC/BPMP and not yet running) can supply the + * real base instead of failing here. */ + base_clk_khz = sdhci_platform_set_clock(clock_khz, base_clk_khz); if (base_clk_khz == 0) { - /* error getting base clock */ - return -1; + /* No usable base clock. The SD clock was already disabled above, so + * the controller is left idle. NOTE: 0 is also what the "clock already + * set" path above returns, so callers cannot currently tell these + * apart - see the DEBUG_SDHCI log for which one happened. */ +#ifdef DEBUG_SDHCI + wolfBoot_printf("sdhci_set_clock: no usable base clock " + "(CAPS and platform hook both 0)\n"); +#endif + return 0; } - base_clk_khz *= 1000; /* convert MHz to kHz */ /* calculate divider */ for (i=1; i<2046; i++) { @@ -415,8 +438,19 @@ static uint32_t sdhci_set_clock(uint32_t clock_khz) SDHCI_REG_SET(SDHCI_SRS11, reg); freq_khz = base_clk_khz / i; - /* wait for clock to stabilize */ - while ((SDHCI_REG(SDHCI_SRS11) & SDHCI_SRS11_ICS) == 0); + /* wait for clock to stabilize (bounded: a controller whose base clock is + * supplied by a platform hook may never assert ICS if that clock is not + * actually running, and an unbounded spin here hangs the boot) */ + to = 100000U; + while ((SDHCI_REG(SDHCI_SRS11) & SDHCI_SRS11_ICS) == 0 && to > 0U) { + to--; + } + if ((SDHCI_REG(SDHCI_SRS11) & SDHCI_SRS11_ICS) == 0) { +#ifdef DEBUG_SDHCI + wolfBoot_printf("sdhci_set_clock: internal clock never stabilized\n"); +#endif + return 0; + } /* enable clock */ sdhci_reg_or(SDHCI_SRS11, SDHCI_SRS11_SDCE); diff --git a/test-app/app_tegra234.c b/test-app/app_tegra234.c new file mode 100644 index 0000000000..d620e08f3c --- /dev/null +++ b/test-app/app_tegra234.c @@ -0,0 +1,115 @@ +/* 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 "wolfboot/wolfboot.h" + +#ifdef TARGET_tegra234 + +#include "hal/tegra234.h" + +/* Same TCU console the bootloader chain and wolfBoot use (see hal/tegra234.h). + * The MMU is off here, so the mailbox register is driven directly. One byte + * per message keeps this payload trivial. */ +#define TCU_TX_MBOX ((volatile uint32_t*)(uintptr_t)TEGRA_TCU_TX_MBOX) + +/* Bounded spin: no timer is set up in this payload, so a plain counter is + * enough to keep a stalled SPE from hanging the PoC mid-message. */ +#define TCU_TX_SPIN_MAX 1000000 + +static void tcu_putc(char c) +{ + unsigned int spin = 0; + + while ((*TCU_TX_MBOX & TEGRA_TCU_MBOX_FULL) != 0) { + if (++spin > TCU_TX_SPIN_MAX) + return; /* drop the character rather than stall forever */ + } + *TCU_TX_MBOX = TEGRA_TCU_MBOX_FULL | + (1u << TEGRA_TCU_MBOX_NBYTES_SHIFT) | (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]); +} + +/* FDT magic 0xd00dfeed, stored big-endian -> reads as 0xedfe0dd0 here */ +#define FDT_MAGIC_LE 0xedfe0dd0u + +/* The DTB pointer arrives as the first argument: wolfBoot's el2_to_el1_boot() + * leaves it in x0 per the arm64 boot protocol, and boot_arm64_start.S forwards + * it to main per AAPCS. Taking it as a parameter rather than reading x0 with + * inline asm keeps this correct at any optimization level. */ +void __attribute__((section(".boot"))) main(uint64_t dtb) { + uint64_t el; + + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + el >>= 2; + + tcu_puts("\n*** wolfBoot Tegra234 PoC payload: verified + booted ***\n"); + + /* Prove the EL2->EL1 drop happened, and that a valid FDT arrived in x0: + * the full arm64 boot contract for a signed payload. */ + tcu_puts(" CurrentEL = EL"); + tcu_putc('0' + (char)(el & 3)); + tcu_puts("\n"); + + tcu_puts(" DTB (x0) = "); + tcu_puthex(dtb); + tcu_puts("\n DTB magic = "); + /* Range- and alignment-check before dereferencing: with the MMU off this + * is Device memory, where an unaligned access faults. A stray pointer + * should be reported, not taken. */ + if (dtb < TEGRA234_DRAM_BASE || dtb > (TEGRA234_DRAM_END - 4) || + (dtb & 0x3) != 0) { + tcu_puts("(pointer not in DRAM or misaligned)\n"); + } + else if (*((volatile uint32_t*)(uintptr_t)dtb) == FDT_MAGIC_LE) { + tcu_puts("OK (0xd00dfeed)\n"); + } + else { + tcu_puthex(*((volatile uint32_t*)(uintptr_t)dtb)); + tcu_puts(" (BAD)\n"); + } + + tcu_puts("*** PoC complete: parked ***\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 2fd37d2c4d..0c76fff905 100644 --- a/test-app/boot_arm64_start.S +++ b/test-app/boot_arm64_start.S @@ -26,6 +26,10 @@ .type _start, @function _start: + /* Preserve the handoff x0 (DTB pointer, per the arm64 boot protocol) so + * the data/bss init below can use x0; restored before the jump to main. */ + mov x19, x0 + /* Set up stack pointer */ ldr x0, =__stack mov sp, x0 @@ -56,7 +60,9 @@ _start: b.lt 3b 4: - /* Jump to main - never returns */ + /* Restore x0 and jump to main - never returns. main(void) ignores it, but + * the tegra234 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..476484c87e --- /dev/null +++ b/tools/scripts/tegra234-mkbl33.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# +# tegra234-mkbl33.sh - build the Tegra234 bare-metal BL33 image. +# +# 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 the hal/tegra234.h offsets and read straight out of DRAM: +# +# [ wolfBoot | pad 0x200000 | signed test-app | pad 0x300000 | DTB ] +# +# The result overwrites wolfboot.bin: flash it to the A_cpu-bootloader +# partition, or load it non-persistently with the L4T "flash.sh --rcm-boot" +# flow. For the EL2->EL1 + DTB-in-x0 Linux boot use tegra234-mkpoc.sh instead. +set -e +cd "$(dirname "$0")/../.." + +# NOTE: this overwrites .config, wolfboot.bin, and the signing keys in the +# working tree (it runs "make keysclean" so wolfBoot and the payload are built +# against one freshly generated key). + +PAYLOAD_OFFSET=$((0x200000)) # TEGRA234_BUNDLE_OFFSET (hal/tegra234.h) +DTB_OFFSET=$((0x300000)) # TEGRA234_DTB_OFFSET (hal/tegra234.h) +MB2_CPUBL_MAX=$((0x400000)) # TEGRA234_CPUBL_MAX_SIZE (hal/tegra234.h) +CROSS_COMPILE="${CROSS_COMPILE:-aarch64-linux-gnu-}" + +# DTB path: pass as arg 1, or set L4T to your Linux_for_Tegra directory. +DTB="${1:-${L4T:+$L4T/kernel/dtb/tegra234-p3768-0000+p3767-0005-nv.dtb}}" +if [ -z "$DTB" ]; then + echo "ERROR: no DTB given. Pass a DTB path as arg 1, or set L4T to your" >&2 + echo " Linux_for_Tegra directory." >&2 + exit 1 +fi +[ -f "$DTB" ] || { echo "ERROR: DTB not found: $DTB" >&2; exit 1; } +# An FDT starts with the magic d0 0d fe ed; catch a wrong file before it is +# baked into the image and only fails on the board. +if [ "$(od -An -tx1 -N4 "$DTB" | tr -d ' \n')" != "d00dfeed" ]; then + echo "ERROR: not a device tree blob (bad FDT magic): $DTB" >&2; exit 1 +fi + +# Portable file size (GNU coreutils vs BSD/macOS stat). +filesize() { + stat -c%s "$1" 2>/dev/null || stat -f%z "$1" +} + +# Pad a file out to $2 bytes ("truncate" is GNU-only). +pad_to() { + dd if=/dev/null of="$1" bs=1 seek="$2" 2>/dev/null +} + +cp config/examples/tegra234.config .config +# A failure here would silently reuse stale keys, so do not swallow it. +make keysclean >/dev/null +make clean >/dev/null +# Build wolfBoot and the signed test-app together so they share one key. +make CROSS_COMPILE="$CROSS_COMPILE" wolfboot.bin test-app/image_v1_signed.bin + +core_sz=$(filesize wolfboot.bin) +[ "$core_sz" -le "$PAYLOAD_OFFSET" ] || { echo "ERROR: wolfBoot ($core_sz B) > 0x$(printf %x $PAYLOAD_OFFSET)" >&2; exit 1; } +app_sz=$(filesize test-app/image_v1_signed.bin) +[ $((PAYLOAD_OFFSET + app_sz)) -le "$DTB_OFFSET" ] || { echo "ERROR: payload overruns DTB offset" >&2; exit 1; } + +cp wolfboot.bin bundle.bin +pad_to bundle.bin "$PAYLOAD_OFFSET" +cat test-app/image_v1_signed.bin >> bundle.bin +pad_to bundle.bin "$DTB_OFFSET" +cat "$DTB" >> bundle.bin + +# Check the size budget before overwriting wolfboot.bin, so a rejected bundle +# leaves the plain bootloader binary intact. +total=$(filesize bundle.bin) +if [ "$total" -gt "$MB2_CPUBL_MAX" ]; then + rm -f bundle.bin + echo "ERROR: bundle ($total B) exceeds MB2 CPUBL cap (0x$(printf %x $MB2_CPUBL_MAX))" >&2; exit 1 +fi +mv bundle.bin 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): $(filesize "$DTB") B" +echo " (under MB2 4 MB CPUBL cap: OK)" diff --git a/tools/scripts/tegra234-mkpoc.sh b/tools/scripts/tegra234-mkpoc.sh new file mode 100755 index 0000000000..4b6473d64e --- /dev/null +++ b/tools/scripts/tegra234-mkpoc.sh @@ -0,0 +1,79 @@ +#!/usr/bin/env bash +# +# tegra234-mkpoc.sh - build the bare-metal BL33 image for the DRAM-staged boot: +# wolfBoot verifies a signed payload, drops from EL2 to EL1, and hands it a +# device tree in x0 (the arm64 Linux boot contract) with no storage driver. The +# payload is the tegra234 test-app, which reports its EL and validates the DTB +# magic. Use tegra234-mkbl33.sh for the plain EL2 boot. +# +# 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")/../.." + +# NOTE: this overwrites .config, wolfboot.bin, and the signing keys in the +# working tree (it runs "make keysclean" so wolfBoot and the payload are built +# against one freshly generated key). + +PAYLOAD_OFFSET=$((0x200000)) # TEGRA234_BUNDLE_OFFSET (hal/tegra234.h) +DTB_OFFSET=$((0x300000)) # TEGRA234_DTB_OFFSET (hal/tegra234.h) +MB2_CPUBL_MAX=$((0x400000)) # TEGRA234_CPUBL_MAX_SIZE (hal/tegra234.h) +CROSS_COMPILE="${CROSS_COMPILE:-aarch64-linux-gnu-}" + +# DTB path: pass as arg 1, or set L4T to your Linux_for_Tegra directory. +DTB="${1:-${L4T:+$L4T/kernel/dtb/tegra234-p3768-0000+p3767-0005-nv.dtb}}" +if [ -z "$DTB" ]; then + echo "ERROR: no DTB given. Pass a DTB path as arg 1, or set L4T to your" >&2 + echo " Linux_for_Tegra directory." >&2 + exit 1 +fi +[ -f "$DTB" ] || { echo "ERROR: DTB not found: $DTB" >&2; exit 1; } +# An FDT starts with the magic d0 0d fe ed; catch a wrong file before it is +# baked into the image and only fails on the board. +if [ "$(od -An -tx1 -N4 "$DTB" | tr -d ' \n')" != "d00dfeed" ]; then + echo "ERROR: not a device tree blob (bad FDT magic): $DTB" >&2; exit 1 +fi + +# Portable file size (GNU coreutils vs BSD/macOS stat). +filesize() { + stat -c%s "$1" 2>/dev/null || stat -f%z "$1" +} + +# Pad a file out to $2 bytes ("truncate" is GNU-only). +pad_to() { + dd if=/dev/null of="$1" bs=1 seek="$2" 2>/dev/null +} + +echo "== 1. build wolfBoot + signed test-app (shared key) ==" +cp config/examples/tegra234-linux.config .config +# A failure here would silently reuse stale keys, so do not swallow it. +make keysclean >/dev/null +make clean >/dev/null +make CROSS_COMPILE="$CROSS_COMPILE" wolfboot.bin test-app/image_v1_signed.bin + +core_sz=$(filesize wolfboot.bin) +[ "$core_sz" -le "$PAYLOAD_OFFSET" ] || { echo "ERROR: wolfBoot ($core_sz B) > 0x$(printf %x $PAYLOAD_OFFSET)" >&2; exit 1; } +app_sz=$(filesize test-app/image_v1_signed.bin) +[ $((PAYLOAD_OFFSET + app_sz)) -le "$DTB_OFFSET" ] || { echo "ERROR: payload overruns DTB offset" >&2; exit 1; } + +echo "== 2. assemble the BL33 bundle ==" +cp wolfboot.bin bundle.bin +pad_to bundle.bin "$PAYLOAD_OFFSET" +cat test-app/image_v1_signed.bin >> bundle.bin +pad_to bundle.bin "$DTB_OFFSET" +cat "$DTB" >> bundle.bin + +# Check the size budget before overwriting wolfboot.bin, so a rejected bundle +# leaves the plain bootloader binary intact. +total=$(filesize bundle.bin) +if [ "$total" -gt "$MB2_CPUBL_MAX" ]; then + rm -f bundle.bin + echo "ERROR: bundle ($total B) exceeds MB2 CPUBL cap (0x$(printf %x $MB2_CPUBL_MAX))" >&2; exit 1 +fi +mv bundle.bin 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): $(filesize "$DTB") B" +echo " (under MB2 4 MB CPUBL cap: OK)"