From d35d926c826d49e55da166db82bff2f072843163 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Wed, 7 Oct 2026 15:09:13 +0900 Subject: [PATCH 01/10] sdhci: optional 8-bit bus and High Speed timing for eMMC SDHCI_EMMC_BUS_WIDTH (1/4/8, default 4) and SDHCI_EMMC_HS (CMD6 HS_TIMING, 50 MHz). Defaults are unchanged. --- include/sdhci.h | 4 ++++ src/sdhci.c | 53 ++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/include/sdhci.h b/include/sdhci.h index ba94c7486e..154d99ee31 100644 --- a/include/sdhci.h +++ b/include/sdhci.h @@ -412,6 +412,10 @@ /* eMMC-specific constants */ #define MMC_DW_CSD 0x03B70000U +/* CMD6 argument: Access=Write Byte, Index=185 (HS_TIMING), Value=1 (HS) */ +#define MMC_HS_TIMING_CSD 0x03B90100U +/* Card status (R1) bit 7: the device rejected the last CMD6 SWITCH */ +#define MMC_R1_SWITCH_ERROR (1U << 7) #define MMC_DEVICE_3_3V_VOLT_SET 0x40300000U #define MMC_OCR_BUSY_BIT 0x80000000U #define MMC_EMMC_RCA_DEFAULT 1 diff --git a/src/sdhci.c b/src/sdhci.c index fdf360763c..f63b76d5eb 100644 --- a/src/sdhci.c +++ b/src/sdhci.c @@ -1323,6 +1323,20 @@ static int sdcard_set_function(uint32_t function_number, uint32_t group_number) #ifdef DISK_EMMC +/* eMMC data bus width: 1, 4 (default) or 8 */ +#ifndef SDHCI_EMMC_BUS_WIDTH +#define SDHCI_EMMC_BUS_WIDTH 4 +#endif +#if SDHCI_EMMC_BUS_WIDTH == 8 +#define SDHCI_EMMC_BUS_WIDTH_CSD MMC_EXT_CSD_WIDTH_8BIT +#elif SDHCI_EMMC_BUS_WIDTH == 4 +#define SDHCI_EMMC_BUS_WIDTH_CSD MMC_EXT_CSD_WIDTH_4BIT +#elif SDHCI_EMMC_BUS_WIDTH == 1 +#define SDHCI_EMMC_BUS_WIDTH_CSD MMC_EXT_CSD_WIDTH_1BIT +#else +#error "SDHCI_EMMC_BUS_WIDTH must be 1, 4 or 8" +#endif + /* Send CMD1 (SEND_OP_COND) for eMMC and wait for device ready * Returns 0 on success, negative on error */ static int emmc_send_op_cond(uint32_t ocr_arg, uint32_t *ocr_reg) @@ -1434,12 +1448,31 @@ static int emmc_set_bus_width(uint32_t width) return 0; } +#ifdef SDHCI_EMMC_HS +/* Switch to High Speed timing (EXT_CSD HS_TIMING = 1). 0 on success. */ +static int emmc_set_high_speed(void) +{ + int status; + + status = sdhci_cmd(MMC_CMD6_SWITCH, MMC_HS_TIMING_CSD, SDHCI_RESP_R1B); + if (status == 0 || status == DEVICE_BUSY) { + /* Wait for the switch; SRS04 then holds the CMD13 status */ + status = sdhci_wait_busy(1); + } + if (status == 0 && (SDHCI_REG(SDHCI_SRS04) & MMC_R1_SWITCH_ERROR) != 0) { + status = -1; + } + return status; +} +#endif + /* Full eMMC card initialization sequence * Returns 0 on success */ static int emmc_card_full_init(void) { int status; uint32_t ocr_reg; + uint32_t clock_khz = SDHCI_CLK_25MHZ; /* Set power to 3.3v */ status = sdhci_set_power(SDHCI_SRS10_BVS_3_3V); @@ -1528,16 +1561,26 @@ static int emmc_card_full_init(void) return status; } - /* Set bus width to 4-bit */ - status = emmc_set_bus_width(MMC_EXT_CSD_WIDTH_4BIT); + /* Set bus width */ + status = emmc_set_bus_width(SDHCI_EMMC_BUS_WIDTH_CSD); if (status != 0) { wolfBoot_printf("eMMC: Set bus width failed, continuing with 1-bit\n"); /* Non-fatal, continue with 1-bit */ } - /* Set clock to 25MHz for legacy mode */ - if (sdhci_set_clock(SDHCI_CLK_25MHZ) == 0) { - wolfBoot_printf("eMMC: failed to set 25MHz clock\n"); +#ifdef SDHCI_EMMC_HS + /* High Speed: 50MHz, else 25MHz */ + if (emmc_set_high_speed() == 0) { + clock_khz = SDHCI_CLK_50MHZ; + } + else { + wolfBoot_printf("eMMC: High Speed switch failed, staying at 25MHz\n"); + } +#endif + + /* Set clock: 25MHz legacy, 50MHz High Speed */ + if (sdhci_set_clock(clock_khz) == 0) { + wolfBoot_printf("eMMC: failed to set %dkHz clock\n", clock_khz); return -1; } From 9544ef32c94e6e09b056c0346a4337bb3b70caf5 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Wed, 7 Oct 2026 15:09:14 +0900 Subject: [PATCH 02/10] Add NXP i.MX 8M Mini (imx8mm) target Boots a signed Linux kernel, either loaded into DRAM and started from U-Boot proper (RAM-boot, go) or started by ATF as BL33 reading a signed FIT from eMMC (uSDHC3, FAT32). Includes a test app and CI builds. --- .github/workflows/test-configs.yml | 24 + Makefile | 5 + config/examples/imx8mm-pico.config | 76 +++ hal/imx8mm.c | 790 +++++++++++++++++++++++++++++ hal/imx8mm.h | 138 +++++ hal/imx8mm.ld | 71 +++ src/boot_aarch64.c | 2 + src/boot_aarch64_start.S | 16 +- test-app/app_imx8mm.c | 105 ++++ 9 files changed, 1223 insertions(+), 4 deletions(-) create mode 100644 config/examples/imx8mm-pico.config create mode 100644 hal/imx8mm.c create mode 100644 hal/imx8mm.h create mode 100644 hal/imx8mm.ld create mode 100644 test-app/app_imx8mm.c diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 452ebc6a5f..d4d7b6fac5 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -834,6 +834,30 @@ jobs: config-file: ./config/examples/imx8qm-mek.config make-args: DISK_SDCARD=1 IMX8QM_SCU=0 wolfboot.bin test-app/image_v1_signed.bin + imx8mm_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # BL33: MMU, eMMC (8-bit HS), signed FIT on FAT32 + config-file: ./config/examples/imx8mm-pico.config + make-args: wolfboot.bin test-app/image_v1_signed.bin + + imx8mm_ramboot_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # RAM-boot: loaded and started by U-Boot proper ("go") + config-file: ./config/examples/imx8mm-pico.config + make-args: IMX8MM_BL33=0 wolfboot.bin test-app/image_v1_signed.bin + + imx8mm_bl33_ram_test: + uses: ./.github/workflows/test-build-aarch64.yml + with: + arch: aarch64 + # BL33: kernel placed in DRAM by SPL, MMU off + config-file: ./config/examples/imx8mm-pico.config + make-args: IMX8MM_BL33=1 DISK_EMMC=0 IMX8MM_MMU=0 wolfboot.bin test-app/image_v1_signed.bin + disk_boot_confirm_test: uses: ./.github/workflows/test-build-aarch64.yml with: diff --git a/Makefile b/Makefile index 95be9779a6..9655ce084f 100644 --- a/Makefile +++ b/Makefile @@ -489,6 +489,11 @@ ifeq ($(TARGET),imx8qm) MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin endif +# i.MX 8M Mini runs from DRAM; tools/scripts/imx8mm builds the boot images. +ifeq ($(TARGET),imx8mm) + MAIN_TARGET:=wolfboot.bin test-app/image_v1_signed.bin +endif + # i.MX95 M7 runs from ITCM (loaded by the Linux remoteproc driver); the payload # lives in DDR at 0x80100000, so there is no contiguous flash image to assemble. ifeq ($(TARGET),imx95_m7) diff --git a/config/examples/imx8mm-pico.config b/config/examples/imx8mm-pico.config new file mode 100644 index 0000000000..b1b4b64db1 --- /dev/null +++ b/config/examples/imx8mm-pico.config @@ -0,0 +1,76 @@ +# TechNexion PICO-IMX8MM (i.MX 8M Mini). See docs/Targets.md. +ARCH?=AARCH64 +TARGET?=imx8mm +SIGN?=RSA4096 +HASH?=SHA3 +DEBUG?=1 +VTOR?=1 +SPMATH?=1 +IMAGE_HEADER_SIZE?=1024 +PKA?=1 +WOLFTPM?=0 +# Console on UART2. DEBUG_UART=0 also needs -DWOLFBOOT_NO_PRINTF. +DEBUG_UART?=1 +# Linux-targeted toolchains: no glibc FORTIFY calls, no PIE +CFLAGS_EXTRA+=-U_FORTIFY_SOURCE -fno-pie +LDFLAGS_EXTRA+=-no-pie +NO_XIP?=1 +NO_QNX?=1 +WOLFBOOT_SECTOR_SIZE=0x400 +WOLFBOOT_NO_PARTITIONS=1 +# IMX8MM_BL33=1: started by ATF as BL33, in place of U-Boot proper +# IMX8MM_BL33=0: RAM-boot, loaded to DRAM and started by U-Boot ("go 0x40480000") +IMX8MM_BL33?=1 +ifeq ($(IMX8MM_BL33),1) + # BL33 address (U-Boot CONFIG_TEXT_BASE) + WOLFBOOT_LOAD_BASE?=0x40200000 + # DISK_EMMC=0: signed kernel placed by SPL + IMX8MM_KERNEL_ADDR?=0x48000000 + # 64 MB: up to 0x48000000 from the kernel run address + WOLFBOOT_RAMBOOT_MAX_SIZE?=0x4000000 + CFLAGS_EXTRA+=-DIMX8MM_BL33 + # Entered with the MMU off + CFLAGS_EXTRA+=-mstrict-align + # Identity MMU + D-cache while loading and verifying + IMX8MM_MMU?=1 + ifeq ($(IMX8MM_MMU),1) + CFLAGS_EXTRA+=-DIMX8MM_MMU + endif + # Signed FIT read from eMMC (uSDHC3) p1, FAT32 + DISK_EMMC?=1 + ifeq ($(DISK_EMMC),1) + CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT + # Print the eMMC bus and partitions at boot + CFLAGS_EXTRA+=-DIMX8MM_EMMC_PROBE + # 8-bit, High Speed (50 MHz) + IMX8MM_EMMC_HS?=1 + ifeq ($(IMX8MM_EMMC_HS),1) + CFLAGS_EXTRA+=-DSDHCI_EMMC_BUS_WIDTH=8 -DSDHCI_EMMC_HS + endif + DISK_FS?=fat32 + CFLAGS_EXTRA+=-DBOOT_PART_A=0 -DBOOT_PART_B=0 + CFLAGS_EXTRA+=-DBOOT_FILE_A='"/fitImage_A.itb"' + CFLAGS_EXTRA+=-DBOOT_FILE_B='"/fitImage_B.itb"' + # Whole FIT is read and verified here + WOLFBOOT_LOAD_ADDRESS?=0x48000000 + # 64 KB SDMA reads + DISK_BLOCK_SIZE?=65536 + CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=$(DISK_BLOCK_SIZE) + # Keep WOLFBOOT_FS_CACHE_SIZE at 512: larger is read by SDMA into an + # unaligned buffer + endif +else + # RAM-boot load and entry address + WOLFBOOT_LOAD_BASE?=0x40480000 + # Signed kernel at +0xC0000 in wolfboot_linux.bin + IMX8MM_KERNEL_ADDR?=0x40540000 + # 42 MB: the kernel must end below the DTB at 0x43000000 + WOLFBOOT_RAMBOOT_MAX_SIZE?=0x2A00000 +endif +CFLAGS_EXTRA+=-DIMX8MM_KERNEL_ADDR=$(IMX8MM_KERNEL_ADDR) +# Kernel run address +WOLFBOOT_LOAD_ADDRESS?=0x44000000 +# DTB address +WOLFBOOT_LOAD_DTS_ADDRESS?=0x43000000 +# Image read from DRAM (memcpy), RAM boot +EXT_FLASH?=1 diff --git a/hal/imx8mm.c b/hal/imx8mm.c new file mode 100644 index 0000000000..b6cb373a97 --- /dev/null +++ b/hal/imx8mm.c @@ -0,0 +1,790 @@ +/* imx8mm.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 + */ +#include +#include +#include +#include +#include "image.h" +#include "printf.h" +#include "hal/imx8mm.h" +#include "aarch64_arch.h" +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +#include "hal.h" +#include "sdhci.h" +#include "disk.h" +#endif +#ifndef ARCH_AARCH64 +# error "wolfBoot imx8mm HAL: wrong architecture selected. Please compile with ARCH=AARCH64." +#endif + +/* Fixed addresses */ +extern void *kernel_addr, *update_addr, *dts_addr; + +void* hal_get_primary_address(void) +{ +#ifdef IMX8MM_KERNEL_ADDR + /* Signed kernel placed by the previous stage */ + return (void*)IMX8MM_KERNEL_ADDR; +#else + return (void*)&kernel_addr; +#endif +} + +void* hal_get_update_address(void) +{ + return (void*)&update_addr; +} + +void* hal_get_dts_address(void) +{ + return (void*)&dts_addr; +} + +#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 + + +#ifdef DEBUG_UART +/* UART TX only; clocks, pins and baud rate are set by the earlier stages */ +#define IMX_UART_REG(off) (*(volatile uint32_t *)(IMX8MM_UART_BASE + (off))) +#define IMX_UART_UTXD IMX_UART_REG(IMX8MM_UART_UTXD) +#define IMX_UART_UCR1 IMX_UART_REG(IMX8MM_UART_UCR1) +#define IMX_UART_USR2 IMX_UART_REG(IMX8MM_UART_USR2) +#define IMX_UART_UTS IMX_UART_REG(IMX8MM_UART_UTS) + +void uart_init(void) +{ + IMX_UART_UCR1 |= UCR1_UARTEN; +} + +static void uart_putc(char c) +{ + while (IMX_UART_UTS & UTS_TXFULL) + ; + IMX_UART_UTXD = (uint32_t)(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') + uart_putc('\r'); + uart_putc(buf[i]); + } + /* Drain the FIFO before handing over */ + while (!(IMX_UART_USR2 & USR2_TXDC)) + ; +} +#endif /* DEBUG_UART */ + +void* hal_get_dts_update_address(void) +{ + return NULL; /* Not yet supported */ +} + +/* public HAL functions */ +#ifdef IMX8MM_BL33 +/* Enable the I-cache if the MMU is off (BL33 entry). do_boot() disables it. */ +static void imx8mm_icache_enable(void) +{ + uint64_t sctlr; + + __asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr)); + if ((sctlr & (1UL << 0)) == 0) { /* SCTLR_EL2.M: MMU off */ + __asm__ volatile("ic iallu\n" + "dsb sy\n" + "isb\n" ::: "memory"); + sctlr |= (1UL << 12); /* SCTLR_EL2.I */ + __asm__ volatile("msr sctlr_el2, %0\n" + "isb\n" :: "r"(sctlr) : "memory"); + } +} + +#ifdef IMX8MM_MMU +/* Identity MMU, 1 GB blocks: DRAM Normal cacheable, the rest Device (XN). + * do_boot() turns it off again before Linux. As hal/imx8qm.c. */ +#define MMU_BLOCK_NORMAL 0x0000000000000701ULL +#define MMU_BLOCK_DEVICE (0x0000000000000405ULL | (1ULL << 54) | (1ULL << 53)) + +static volatile uint64_t imx8mm_l1_table[512] __attribute__((aligned(4096))); + +static void imx8mm_mmu_enable(void) +{ + uint64_t sctlr, el; + int i; + + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + __asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr)); + if (((el >> 2) & 0x3) != 2 || (sctlr & (1UL << 0)) != 0) { + imx8mm_icache_enable(); + return; + } + + for (i = 0; i < 4; i++) { + uint64_t base = (uint64_t)i << 30; + imx8mm_l1_table[i] = base | + ((base >= IMX8MM_DRAM_BASE && base < IMX8MM_DRAM_END) ? + MMU_BLOCK_NORMAL : MMU_BLOCK_DEVICE); + } + + /* Attr0: Normal WB, Attr1: Device */ + __asm__ volatile("msr mair_el2, %0" :: "r"(0x00000000000000FFUL)); + __asm__ volatile("msr ttbr0_el2, %0" + :: "r"((uint64_t)(uintptr_t)imx8mm_l1_table)); + /* T0SZ=32, 4 KB granule, cacheable walks; bits 31 and 23 are RES1 */ + __asm__ volatile("msr tcr_el2, %0" + :: "r"(0x0000000000013520UL | (1UL << 31) | (1UL << 23))); + __asm__ volatile("isb"); + __asm__ volatile("tlbi alle2"); + __asm__ volatile("dsb sy"); + + /* Drop stale lines from earlier stages */ + aarch64_dcache_maint(0); + __asm__ volatile("ic iallu"); + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); + + sctlr |= (1UL << 0) | (1UL << 2) | (1UL << 12); /* M | C | I */ + __asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr)); + __asm__ volatile("isb"); +} + +#if defined(DISK_EMMC) && !defined(SDHCI_SDMA_DISABLED) +/* Cache maintenance by address range for SDMA */ +static void imx8mm_dcache_range(uintptr_t start, uint32_t sz, int invalidate) +{ + uintptr_t line, end; + uint64_t ctr; + + if (sz == 0) + return; + /* CTR_EL0.DminLine: log2 of the line size in words */ + __asm__ volatile("mrs %0, ctr_el0" : "=r"(ctr)); + line = (uintptr_t)4 << ((ctr >> 16) & 0xF); + + if (((start | sz) & (line - 1)) != 0) + wolfBoot_printf("imx8mm: DMA buffer %p+%u not cache-line aligned\n", + (void*)start, (unsigned)sz); + + end = (start + sz + line - 1) & ~(line - 1); + start &= ~(line - 1); + __asm__ volatile("dsb sy"); + for (; start < end; start += line) { + if (invalidate) + __asm__ volatile("dc ivac, %0" :: "r"(start) : "memory"); + else + __asm__ volatile("dc civac, %0" :: "r"(start) : "memory"); + } + __asm__ volatile("dsb sy"); + __asm__ volatile("isb"); +} + +void sdhci_platform_dma_prepare(void *buf, uint32_t sz, int is_write) +{ + /* Clean in both directions: a dirty line could evict onto DMA data */ + imx8mm_dcache_range((uintptr_t)buf, sz, 0); + (void)is_write; +} + +void sdhci_platform_dma_complete(void *buf, uint32_t sz, int is_write) +{ + if (!is_write) + imx8mm_dcache_range((uintptr_t)buf, sz, 1); +} +#endif /* DISK_EMMC && !SDHCI_SDMA_DISABLED */ +#endif /* IMX8MM_MMU */ +#endif /* IMX8MM_BL33 */ + +#if defined(IMX8MM_BL33) && defined(DEBUG) && defined(DEBUG_UART) +/* Image bounds (hal/imx8mm.ld) */ +extern uint8_t _start_text[]; +extern uint8_t _end[]; + +/* Called by the BL33 EL2 exception vectors (src/boot_aarch64_start.S) */ +void simple_el2_fault_handler(unsigned long esr, unsigned long elr, + unsigned long far, unsigned long vector); +void simple_el2_fault_handler(unsigned long esr, unsigned long elr, + unsigned long far, unsigned long vector) +{ + const char* from; + uintptr_t pc; + + from = (vector >= 8) ? "lower EL (payload)" : "current EL (wolfBoot)"; + pc = (uintptr_t)elr; + + wolfBoot_printf("\n*** i.MX8MM EL2 EXCEPTION ***\n"); + wolfBoot_printf("vector=%d from %s\n", (int)vector, from); + wolfBoot_printf("ESR=0x%08x EC=0x%02x ISS=0x%06x\n", + (uint32_t)esr, (uint32_t)((esr >> 26) & 0x3F), + (uint32_t)(esr & 0x1FFFFFUL)); + wolfBoot_printf("ELR=0x%08x%08x\n", + (uint32_t)(elr >> 32), (uint32_t)(elr & 0xFFFFFFFFUL)); + wolfBoot_printf("FAR=0x%08x%08x\n", + (uint32_t)(far >> 32), (uint32_t)(far & 0xFFFFFFFFUL)); + if (pc >= (uintptr_t)_start_text && pc < (uintptr_t)_end) { + wolfBoot_printf("ELR is inside wolfBoot (+0x%x from 0x%08x)\n", + (uint32_t)(pc - (uintptr_t)_start_text), + (uint32_t)(uintptr_t)_start_text); + } + else { + wolfBoot_printf("ELR is outside wolfBoot [0x%08x-0x%08x)\n", + (uint32_t)(uintptr_t)_start_text, (uint32_t)(uintptr_t)_end); + } +} +#endif /* IMX8MM_BL33 && DEBUG && DEBUG_UART */ + + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +/* eMMC (uSDHC3) for src/sdhci.c, BL33 mode only */ +#ifdef DISK_SDCARD +#error "imx8mm: only DISK_EMMC (uSDHC3) is supported" +#endif + +#define IMX8MM_USDHC_BASE IMX8MM_USDHC3_BASE + +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; } + +uint64_t hal_get_timer_us(void) +{ + return (timer_get_count() * 1000000ULL) / timer_get_freq(); +} + +void hal_delay_us(uint32_t us) +{ + uint64_t deadline = timer_deadline_us(us); + while (!timer_expired(deadline)) + ; +} + +/* uSDHC3 pins (PICO-IMX8MM eMMC), mux mode 2, as in U-Boot SPL */ +static const struct { + uint16_t mux; /* IOMUXC SW_MUX_CTL_PAD offset */ + uint16_t pad; /* IOMUXC SW_PAD_CTL_PAD offset */ + uint32_t mode; +} usdhc3_pads[] = { + { 0x138, 0x3A0, 2 | IOMUXC_MUX_SION }, /* NAND_WE_B -> USDHC3_CLK */ + { 0x13C, 0x3A4, 2 }, /* NAND_WP_B -> USDHC3_CMD */ + { 0x11C, 0x384, 2 }, /* NAND_DATA04 -> USDHC3_DATA0 */ + { 0x120, 0x388, 2 }, /* NAND_DATA05 -> USDHC3_DATA1 */ + { 0x124, 0x38C, 2 }, /* NAND_DATA06 -> USDHC3_DATA2 */ + { 0x128, 0x390, 2 }, /* NAND_DATA07 -> USDHC3_DATA3 */ + { 0x130, 0x398, 2 }, /* NAND_RE_B -> USDHC3_DATA4 */ + { 0x100, 0x368, 2 }, /* NAND_CE2_B -> USDHC3_DATA5 */ + { 0x104, 0x36C, 2 }, /* NAND_CE3_B -> USDHC3_DATA6 */ + { 0x108, 0x370, 2 }, /* NAND_CLE -> USDHC3_DATA7 */ +}; + +/* Clock root SYSTEM_PLL1_DIV2, gate on, pins (SPL skips this on USB boot) */ +static void imx8mm_usdhc3_setup(void) +{ + unsigned int i; + + wr32(CCM_CCGR_CLR(CCM_CCGR_USDHC3), CCM_CCGR_CLK_ON); + wr32(CCM_TARGET_ROOT(CCM_ROOT_USDHC3), + CCM_TARGET_ROOT_ENABLE | CCM_TARGET_ROOT_MUX(1)); + wr32(CCM_CCGR_SET(CCM_CCGR_USDHC3), CCM_CCGR_CLK_ON); + + for (i = 0; i < sizeof(usdhc3_pads) / sizeof(usdhc3_pads[0]); i++) { + wr32(IMX8MM_IOMUXC_BASE + usdhc3_pads[i].mux, usdhc3_pads[i].mode); + wr32(IMX8MM_IOMUXC_BASE + usdhc3_pads[i].pad, IOMUXC_PAD_USDHC); + } +} + +/* uSDHC -> SDHCI register shim, taken from hal/imx8qm.c */ + +#ifndef CADENCE_SRS_OFFSET +#define CADENCE_SRS_OFFSET 0x200 +#endif + +#ifndef USDHC_RESET_TIMEOUT_US +#define USDHC_RESET_TIMEOUT_US 1000000 +#endif +#ifndef USDHC_INIT_TIMEOUT_US +#define USDHC_INIT_TIMEOUT_US 100000 +#endif +#ifndef USDHC_CLK_STABLE_TIMEOUT_US +#define USDHC_CLK_STABLE_TIMEOUT_US 100000 +#endif + +/* Standard SDHCI offsets (after the Cadence SRS base) */ +#define STD_BLK 0x04 +#define STD_CMD 0x0C +#define STD_PRES_STATE 0x24 +#define STD_HOST_CTRL1 0x28 +#define STD_CLOCK_CTRL 0x2C +#define STD_INT_STATUS 0x30 +#define STD_INT_STATUS_EN 0x34 +#define STD_INT_SIGNAL_EN 0x38 +#define STD_HOST_CTRL2 0x3C +#define STD_CAPS1 0x40 +#define STD_CAPS2 0x44 +#define STD_MAX_CURRENT 0x48 +#define STD_ADMA_ADDR_LO 0x58 +#define STD_ADMA_ADDR_HI 0x5C + +/* uSDHC-only error bits */ +#define USDHC_INT_DMAE (1U << 28) +#define USDHC_INT_TNE (1U << 26) + +/* Registers uSDHC does not implement */ +static uint32_t srs10_shadow; /* bus power / bus voltage / high speed */ +static uint32_t srs15_shadow; /* host control 2 */ +/* Rate set by sdhci_platform_set_clock() */ +static uint32_t usdhc_achieved_clk_khz = IMX8MM_USDHC_PERCLK_HZ / 1000; + +uint32_t sdhci_reg_read(uint32_t offset) +{ + uintptr_t b = IMX8MM_USDHC_BASE; + uint32_t std, v, raw; + + if (offset < CADENCE_SRS_OFFSET) { + /* Cadence HRS: none on uSDHC; report the PHY handshake as done */ + return (offset == SDHCI_HRS04) ? SDHCI_HRS04_UIS_ACK : 0; + } + std = offset - CADENCE_SRS_OFFSET; + + switch (std) { + case STD_PRES_STATE: + v = rd32(b + USDHC_PRES_STATE); + v |= SDHCI_SRS09_CSS; + /* DAT0 level: bit 24 -> bit 20 */ + if ((v & USDHC_PRES_DLSL_DAT0) != 0) + v |= SDHCI_SRS09_DAT0_LVL; + else + v &= ~SDHCI_SRS09_DAT0_LVL; + return v; + + case STD_HOST_CTRL1: + v = srs10_shadow & ~(uint32_t)(SDHCI_SRS10_DTW | SDHCI_SRS10_EDTW); + switch (rd32(b + USDHC_PROT_CTRL) & USDHC_PROT_DTW_MASK) { + case USDHC_PROT_DTW_4BIT: v |= SDHCI_SRS10_DTW; break; + case USDHC_PROT_DTW_8BIT: v |= SDHCI_SRS10_EDTW; break; + default: break; + } + return v; + + case STD_CLOCK_CTRL: + /* No clock enable/stable bits on uSDHC */ + v = rd32(b + USDHC_SYS_CTRL) & + (uint32_t)(USDHC_SYS_DTOCV_MASK | USDHC_SYS_RSTA | + USDHC_SYS_RSTC | USDHC_SYS_RSTD); + return v | SDHCI_SRS11_ICE | SDHCI_SRS11_ICS | SDHCI_SRS11_SDCE; + + case STD_INT_STATUS: + case STD_INT_STATUS_EN: + case STD_INT_SIGNAL_EN: + raw = rd32(b + std); + v = raw; + if ((raw & USDHC_INT_DMAE) != 0) + v |= SDHCI_SRS12_EADMA; + /* Hide uSDHC-only bits; synthesize the error summary */ + v &= ~(uint32_t)(USDHC_INT_DMAE | USDHC_INT_TNE); + if (std == STD_INT_STATUS && + (((v & SDHCI_SRS12_ERR_STAT) != 0) || + ((raw & USDHC_INT_TNE) != 0))) { + v |= SDHCI_SRS12_EINT; + } + return v; + + case STD_HOST_CTRL2: + return srs15_shadow; + + case STD_CAPS1: + /* Synthesize the timeout clock (50 MHz) and base clock */ + v = rd32(b + USDHC_HOST_CTRL_CAP) & + (uint32_t)(USDHC_CAP_VS33 | USDHC_CAP_VS30 | USDHC_CAP_VS18); + v |= (50U << SDHCI_SRS16_TCF_SHIFT) & SDHCI_SRS16_TCF_MASK; + v |= SDHCI_SRS16_TCU; /* timeout clock is in MHz */ + v |= ((IMX8MM_USDHC_PERCLK_HZ / 1000000U) + << SDHCI_SRS16_BCSDCLK_SHIFT) & + SDHCI_SRS16_BCSDCLK_MASK; + return v; + + case STD_CAPS2: + return 0; + + case STD_ADMA_ADDR_LO: + /* SDMA address (DS_ADDR) */ + return rd32(b + USDHC_DS_ADDR); + + case STD_ADMA_ADDR_HI: + return 0; /* 32-bit ADMA only */ + + case STD_MAX_CURRENT: + /* MIX_CTRL on uSDHC; no max-current register */ + return 0; + + default: + return rd32(b + std); + } +} + +void sdhci_reg_write(uint32_t offset, uint32_t val) +{ + uintptr_t b = IMX8MM_USDHC_BASE; + uint32_t std, v, mix; + + if (offset < CADENCE_SRS_OFFSET) { + /* Cadence software reset -> uSDHC reset-all */ + if (offset == SDHCI_HRS00 && (val & SDHCI_HRS00_SWR) != 0) + wr32(b + USDHC_SYS_CTRL, + rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); + return; + } + std = offset - CADENCE_SRS_OFFSET; + + switch (std) { + case STD_BLK: + /* Drop the SDMA boundary bits */ + wr32(b + USDHC_BLK_ATT, + (val & 0xFFFF0000U) | (val & 0x0FFFU)); + return; + + case STD_CMD: + /* MIX_CTRL first: writing CMD_XFR_TYP starts the command */ + mix = rd32(b + USDHC_MIX_CTRL) & ~(uint32_t)USDHC_MIX_CTRL_XFER_MASK; + mix |= val & USDHC_MIX_CTRL_XFER_MASK; + wr32(b + USDHC_MIX_CTRL, mix); + + v = val & 0xFFFF0000U; + /* Data commands are R1, not R1b */ + if ((v & USDHC_XFR_DPSEL) != 0 && + (v & USDHC_XFR_RSPTYP_MASK) == USDHC_XFR_RSPTYP_48B) { + v = (v & ~(uint32_t)USDHC_XFR_RSPTYP_MASK) | + USDHC_XFR_RSPTYP_48; + } + wr32(b + USDHC_CMD_XFR_TYP, v); + return; + + case STD_HOST_CTRL1: + srs10_shadow = val; + v = rd32(b + USDHC_PROT_CTRL) & + ~(uint32_t)(USDHC_PROT_DTW_MASK | USDHC_PROT_DMASEL_MASK); + if ((val & SDHCI_SRS10_EDTW) != 0) + v |= USDHC_PROT_DTW_8BIT; + else if ((val & SDHCI_SRS10_DTW) != 0) + v |= USDHC_PROT_DTW_4BIT; + else + v |= USDHC_PROT_DTW_1BIT; + /* SDMA only */ + v |= USDHC_PROT_DMASEL_SIMPLE; + wr32(b + USDHC_PROT_CTRL, v); + return; + + case STD_CLOCK_CTRL: + /* Divider is set by sdhci_platform_set_clock() */ + v = rd32(b + USDHC_SYS_CTRL) & ~(uint32_t)USDHC_SYS_DTOCV_MASK; + v |= val & USDHC_SYS_DTOCV_MASK; + v |= val & (uint32_t)(USDHC_SYS_RSTA | USDHC_SYS_RSTC | + USDHC_SYS_RSTD); + wr32(b + USDHC_SYS_CTRL, v); + return; + + case STD_INT_STATUS: + /* W1C; also clear the uSDHC-only error bits */ + v = val; + if ((val & SDHCI_SRS12_EADMA) != 0) + v |= USDHC_INT_DMAE; + if ((val & (SDHCI_SRS12_ERR_STAT | SDHCI_SRS12_EINT)) != 0) + v |= USDHC_INT_TNE; + wr32(b + USDHC_INT_STATUS, v & ~(uint32_t)SDHCI_SRS12_EINT); + return; + + case STD_INT_STATUS_EN: + case STD_INT_SIGNAL_EN: + /* Also enable the uSDHC-only error bits */ + v = val & ~(uint32_t)SDHCI_SRS12_EINT; + if ((val & SDHCI_SRS12_ERR_STAT) != 0) + v |= USDHC_INT_DMAE | USDHC_INT_TNE; + wr32(b + std, v); + return; + + case STD_HOST_CTRL2: + srs15_shadow = val; + /* 1.8V signaling is in VEND_SPEC */ + v = rd32(b + USDHC_VEND_SPEC); + if ((val & SDHCI_SRS15_V18SE) != 0) + v |= USDHC_VEND_SPEC_VSELECT; + else + v &= ~(uint32_t)USDHC_VEND_SPEC_VSELECT; + wr32(b + USDHC_VEND_SPEC, v); + return; + + case STD_CAPS1: + case STD_CAPS2: + return; /* read-only */ + + case STD_ADMA_ADDR_LO: + wr32(b + USDHC_DS_ADDR, val); + return; + + case STD_ADMA_ADDR_HI: + return; /* 32-bit ADMA only */ + + default: + wr32(b + std, val); + return; + } +} + +void sdhci_platform_init(void) +{ + uintptr_t b = IMX8MM_USDHC_BASE; + uint64_t deadline; + uint32_t v; + + imx8mm_usdhc3_setup(); + + /* Reset all */ + wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); + deadline = timer_deadline_us(USDHC_RESET_TIMEOUT_US); + while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_RSTA) != 0) { + if (timer_expired(deadline)) { + wolfBoot_printf("imx8mm usdhc: reset-all did not clear\n"); + break; + } + } + + /* Little-endian data port, no DAT3 card detect */ + v = rd32(b + USDHC_PROT_CTRL); + v = (v & ~(uint32_t)USDHC_PROT_EMODE_MASK) | USDHC_PROT_EMODE_LE; + v &= ~(uint32_t)USDHC_PROT_D3CD; + wr32(b + USDHC_PROT_CTRL, v); + + /* One 512-byte block per watermark event */ + wr32(b + USDHC_WTMK_LVL, + ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_RD_SHIFT) | + ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_WR_SHIFT)); + + /* 80 init clocks before CMD0 */ + wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_INITA); + deadline = timer_deadline_us(USDHC_INIT_TIMEOUT_US); + while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_INITA) != 0) { + if (timer_expired(deadline)) + break; + } +} + +/* SD clock = PERCLK / (prescaler * divisor). Returns the rate in kHz. */ +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) +{ + uintptr_t b = IMX8MM_USDHC_BASE; + uint32_t target_hz, pre, dvs, v; + uint64_t deadline; + + (void)base_clk_khz; + + if (clock_khz == 0) + return 0; + +#ifdef IMX8MM_USDHC_MAX_CLK_KHZ + if (clock_khz > IMX8MM_USDHC_MAX_CLK_KHZ) + clock_khz = IMX8MM_USDHC_MAX_CLK_KHZ; +#endif + + target_hz = clock_khz * 1000U; + + /* Prescaler 1..256 (power of 2), divisor 1..16 */ + for (pre = 1; pre <= 256; pre <<= 1) { + for (dvs = 1; dvs <= 16; dvs++) { + if ((IMX8MM_USDHC_PERCLK_HZ / (pre * dvs)) <= target_hz) + goto found; + } + } + pre = 256; + dvs = 16; +found: + v = rd32(b + USDHC_SYS_CTRL) & + ~(uint32_t)(USDHC_SYS_DVS_MASK | USDHC_SYS_SDCLKFS_MASK); + /* SDCLKFS = prescaler / 2 */ + v |= ((pre >> 1) << USDHC_SYS_SDCLKFS_SHIFT) & USDHC_SYS_SDCLKFS_MASK; + v |= ((dvs - 1) << USDHC_SYS_DVS_SHIFT) & USDHC_SYS_DVS_MASK; + wr32(b + USDHC_SYS_CTRL, v); + + deadline = timer_deadline_us(USDHC_CLK_STABLE_TIMEOUT_US); + while ((rd32(b + USDHC_PRES_STATE) & USDHC_PRES_SDSTB) == 0) { + if (timer_expired(deadline)) { + wolfBoot_printf("imx8mm usdhc: SD clock never stabilized\n"); + return 0; + } + } + + usdhc_achieved_clk_khz = (IMX8MM_USDHC_PERCLK_HZ / (pre * dvs)) / 1000U; + return usdhc_achieved_clk_khz; +} + +void sdhci_platform_irq_init(void) +{ + /* Polled mode */ +} + +void sdhci_platform_set_bus_mode(int is_emmc) +{ + (void)is_emmc; +} + +#ifdef IMX8MM_EMMC_PROBE +static uint32_t le32(const uint8_t *p) +{ + return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24); +} + +/* Print the eMMC bus, the MBR partitions and the type of partition 1 */ +static void imx8mm_emmc_probe(void) +{ + static uint8_t sec[512] XALIGNED(4); + uint64_t t0, t1; + uint32_t lba1 = 0; + char name[9]; + int i, ret; + + t0 = hal_get_timer_us(); + ret = disk_init(0); + t1 = hal_get_timer_us(); + wolfBoot_printf("eMMC: init %s (%d), %d ms\n", (ret == 0) ? "OK" : "FAILED", + ret, (int)((t1 - t0) / 1000)); + if (ret != 0) + return; + switch (rd32(IMX8MM_USDHC_BASE + USDHC_PROT_CTRL) & USDHC_PROT_DTW_MASK) { + case USDHC_PROT_DTW_8BIT: i = 8; break; + case USDHC_PROT_DTW_4BIT: i = 4; break; + default: i = 1; break; + } + wolfBoot_printf("eMMC: %d-bit, %u kHz\n", i, usdhc_achieved_clk_khz); + + if (disk_read(0, 0, sizeof(sec), sec) < 0) { + wolfBoot_printf("eMMC: MBR read failed\n"); + return; + } + if (sec[510] != 0x55 || sec[511] != 0xAA) { + wolfBoot_printf("eMMC: no MBR signature (0x%x 0x%x)\n", + sec[510], sec[511]); + return; + } + wolfBoot_printf("eMMC: MBR partitions\n"); + for (i = 0; i < 4; i++) { + const uint8_t *e = sec + 446 + (16 * i); + uint32_t start = le32(e + 8); + uint32_t num = le32(e + 12); + if (e[4] == 0) + continue; + if (i == 0) + lba1 = start; + wolfBoot_printf(" %d: type 0x%x start %u size %u sectors (%u MB)\n", + i + 1, e[4], start, num, num / 2048); + } + + if (lba1 != 0 && + disk_read(0, (uint64_t)lba1 * 512, sizeof(sec), sec) == 0) { + wolfBoot_printf("eMMC: partition 1 boot sector: sig 0x%x%x", + sec[510], sec[511]); + memcpy(name, sec + 0x03, 8); /* BS_OEMName */ + name[8] = '\0'; + wolfBoot_printf(", OEM \"%s\"", name); + memcpy(name, sec + 0x36, 8); /* FAT12/FAT16 BS_FilSysType */ + if (memcmp(name, "FAT", 3) != 0) + memcpy(name, sec + 0x52, 8);/* FAT32 BS_FilSysType */ + if (memcmp(name, "FAT", 3) != 0) + memcpy(name, "unknown ", 8); + wolfBoot_printf(", type \"%s\"\n", name); + } +} +#endif /* IMX8MM_EMMC_PROBE */ + +#endif /* DISK_SDCARD || DISK_EMMC */ + +void hal_init(void) +{ +#if defined(IMX8MM_BL33) && defined(IMX8MM_MMU) + imx8mm_mmu_enable(); +#elif defined(IMX8MM_BL33) + imx8mm_icache_enable(); +#endif +#ifdef DEBUG_UART + uart_init(); +#endif +#if defined(DISK_EMMC) && defined(IMX8MM_EMMC_PROBE) + imx8mm_emmc_probe(); +#endif + #if defined(TEST_ENCRYPT) && defined (EXT_ENCRYPTED) + char enc_key[] = "0123456789abcdef0123456789abcdef" + "0123456789abcdef"; + wolfBoot_set_encrypt_key((uint8_t *)enc_key,(uint8_t *)(enc_key + 32)); + #endif +} + +/* do_boot() turns off the MMU and caches (EL2_HYPERVISOR) */ +void hal_prepare_boot(void) +{ +#if defined(DISK_SDCARD) || defined(DISK_EMMC) + /* Reset the controller for Linux */ + sdhci_shutdown(); +#endif +} + + +int RAMFUNCTION hal_flash_write(uintptr_t address, const uint8_t *data, int 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) +{ + return 0; +} diff --git a/hal/imx8mm.h b/hal/imx8mm.h new file mode 100644 index 0000000000..b592e38d98 --- /dev/null +++ b/hal/imx8mm.h @@ -0,0 +1,138 @@ +/* imx8mm.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 _IMX8MM_H_ +#define _IMX8MM_H_ + +#define USE_BUILTIN_STARTUP +#define USE_SIMPLE_STARTUP + +/* Entered at EL2; do_boot() turns the MMU and caches off before Linux */ +#ifndef EL2_HYPERVISOR +#define EL2_HYPERVISOR 1 +#endif + +/* Fallback only: ATF sets CNTFRQ_EL0 to 8 MHz */ +#define TIMER_CLK_FREQ 8000000 + +/* DRAM (PICO-IMX8MM: 2 GB), 1 GB aligned for the BL33 MMU */ +#ifndef IMX8MM_DRAM_BASE +#define IMX8MM_DRAM_BASE 0x40000000ULL +#endif +#ifndef IMX8MM_DRAM_END +#define IMX8MM_DRAM_END 0xC0000000ULL +#endif + +/* UART (console). Default: UART2, the PICO-PI console (ttymxc1). */ +#ifndef IMX8MM_UART_BASE +#define IMX8MM_UART_BASE 0x30890000UL +#endif +#define IMX8MM_UART_UTXD 0x40 /* Transmitter register */ +#define IMX8MM_UART_UCR1 0x80 /* Control register 1 */ +#define IMX8MM_UART_USR2 0x98 /* Status register 2 */ +#define IMX8MM_UART_UTS 0xB4 /* Test register */ +#define UCR1_UARTEN (1U << 0) +#define USR2_TXDC (1U << 3) /* Transmission complete */ +#define UTS_TXFULL (1U << 4) /* TxFIFO full */ + +/* Clock controller (CCM) */ +#define IMX8MM_CCM_BASE 0x30380000 +#define CCM_CCGR_SET(n) (IMX8MM_CCM_BASE + 0x4004 + ((n) * 0x10)) +#define CCM_CCGR_CLR(n) (IMX8MM_CCM_BASE + 0x4008 + ((n) * 0x10)) +#define CCM_CCGR_CLK_ON 0x3 +#define CCM_TARGET_ROOT(n) (IMX8MM_CCM_BASE + 0x8000 + ((n) * 0x80)) +#define CCM_TARGET_ROOT_ENABLE (1 << 28) +#define CCM_TARGET_ROOT_MUX(n) (((n) & 0x7) << 24) +#define CCM_CCGR_USDHC3 94 +#define CCM_ROOT_USDHC3 121 /* source 1 = SYSTEM_PLL1_DIV2 */ + +/* Pin mux controller (IOMUXC) */ +#define IMX8MM_IOMUXC_BASE 0x30330000 +#define IOMUXC_MUX_SION (1 << 4) +/* USDHC pad setting used by U-Boot: DSE6 | HYS | PUE | PE | FSEL2 */ +#define IOMUXC_PAD_USDHC 0x1D6 + +/* uSDHC3: eMMC (8-bit) on PICO-IMX8MM */ +#define IMX8MM_USDHC3_BASE 0x30B60000 +#ifndef IMX8MM_USDHC_PERCLK_HZ +#define IMX8MM_USDHC_PERCLK_HZ 400000000 /* SYSTEM_PLL1_DIV2, divider 1 */ +#endif + +/* uSDHC registers (same IP as i.MX8QM, see hal/imx8qm.h) */ +#define USDHC_DS_ADDR 0x00 +#define USDHC_BLK_ATT 0x04 +#define USDHC_CMD_ARG 0x08 +#define USDHC_CMD_XFR_TYP 0x0C +#define USDHC_CMD_RSP0 0x10 +#define USDHC_DATA_BUFF_ACC_PORT 0x20 +#define USDHC_PRES_STATE 0x24 +#define USDHC_PROT_CTRL 0x28 +#define USDHC_SYS_CTRL 0x2C +#define USDHC_INT_STATUS 0x30 +#define USDHC_INT_STATUS_EN 0x34 +#define USDHC_HOST_CTRL_CAP 0x40 +#define USDHC_WTMK_LVL 0x44 +#define USDHC_MIX_CTRL 0x48 +#define USDHC_VEND_SPEC 0xC0 + +#define USDHC_PRES_CIHB (1 << 0) /* command inhibit (CMD) */ +#define USDHC_PRES_SDSTB (1 << 3) /* SD clock stable */ +#define USDHC_PRES_DLSL_DAT0 (1 << 24) /* DAT0 line signal level */ + +#define USDHC_PROT_DTW_MASK (0x3 << 1) +#define USDHC_PROT_DTW_1BIT (0x0 << 1) +#define USDHC_PROT_DTW_4BIT (0x1 << 1) +#define USDHC_PROT_DTW_8BIT (0x2 << 1) +#define USDHC_PROT_D3CD (1 << 3) +#define USDHC_PROT_EMODE_MASK (0x3 << 4) +#define USDHC_PROT_EMODE_LE (0x2 << 4) /* little-endian data port */ +#define USDHC_PROT_DMASEL_MASK (0x3 << 8) +#define USDHC_PROT_DMASEL_SIMPLE (0x0 << 8) + +#define USDHC_SYS_DVS_SHIFT 4 +#define USDHC_SYS_DVS_MASK (0xF << 4) +#define USDHC_SYS_SDCLKFS_SHIFT 8 +#define USDHC_SYS_SDCLKFS_MASK (0xFF << 8) +#define USDHC_SYS_DTOCV_SHIFT 16 +#define USDHC_SYS_DTOCV_MASK (0xF << 16) +#define USDHC_SYS_RSTA (1 << 24) /* reset all */ +#define USDHC_SYS_RSTC (1 << 25) /* reset command line */ +#define USDHC_SYS_RSTD (1 << 26) /* reset data line */ +#define USDHC_SYS_INITA (1 << 27) /* send 80 init clocks */ + +#define USDHC_XFR_RSPTYP_MASK (0x3 << 16) +#define USDHC_XFR_RSPTYP_48 (0x2 << 16) +#define USDHC_XFR_RSPTYP_48B (0x3 << 16) +#define USDHC_XFR_DPSEL (1 << 21) /* data present */ + +#define USDHC_MIX_CTRL_XFER_MASK 0x3F + +#define USDHC_CAP_VS33 (1 << 24) +#define USDHC_CAP_VS30 (1 << 25) +#define USDHC_CAP_VS18 (1 << 26) + +#define USDHC_WTMK_RD_SHIFT 0 +#define USDHC_WTMK_WR_SHIFT 16 +#define USDHC_WTMK_BLOCK_WORDS 0x80 + +#define USDHC_VEND_SPEC_VSELECT (1 << 1) /* 1.8V signaling */ + +#endif /* _IMX8MM_H_ */ diff --git a/hal/imx8mm.ld b/hal/imx8mm.ld new file mode 100644 index 0000000000..e21b687170 --- /dev/null +++ b/hal/imx8mm.ld @@ -0,0 +1,71 @@ +MEMORY +{ + DDR_MEM(rwx): ORIGIN = @WOLFBOOT_LOAD_BASE@, LENGTH = 0x3c000000 +} +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 + + .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 + + /* Stack for BL33 mode (IMX8MM_BL33) */ + .stack (NOLOAD) : + { + . = ALIGN(16); + . += 0x40000; /* 256 KB */ + . = ALIGN(16); + END_STACK = .; + } > DDR_MEM + . = ALIGN(4); +} + +kernel_addr = 0x40540000; +update_addr = 0x46000000; +dts_addr = 0x43000000; +kernel_load_addr = 0x44000000; +dts_load_addr = 0x45000000; + +_wolfboot_partition_boot_address = kernel_addr; +_wolfboot_partition_update_address = update_addr; diff --git a/src/boot_aarch64.c b/src/boot_aarch64.c index ef4e7a7e54..3407776ab8 100644 --- a/src/boot_aarch64.c +++ b/src/boot_aarch64.c @@ -38,6 +38,8 @@ #include "hal/zynq.h" #elif defined(TARGET_nxp_ls1028a) #include "hal/nxp_ls1028a.h" +#elif defined(TARGET_imx8mm) +#include "hal/imx8mm.h" #endif #include "aarch64_arch.h" diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 11e1891fb4..2fbbfaf5a4 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -58,6 +58,10 @@ #include "hal/imx8qm.h" #endif +#ifdef TARGET_imx8mm +#include "hal/imx8mm.h" +#endif + /* VA region the TCR_EL{1,2,3} T0SZ values below cover. A high DDR window * must fit inside it; keep in step with those TCR literals. */ #ifndef WOLFBOOT_AARCH64_VA_LIMIT @@ -1339,7 +1343,8 @@ _cm4_entry: // EL == 2? mov x2, #3 << 20 msr cptr_el2, x2 /* Enable FP/SIMD */ -#if defined(TARGET_cm4) || defined(TARGET_imx8qm) +#if defined(TARGET_cm4) || defined(TARGET_imx8qm) || \ + (defined(TARGET_imx8mm) && defined(IMX8MM_BL33)) /* Always install a valid EL2 vector base: the reset VBAR_EL2 is UNKNOWN, so * without this a fault in the parsing-heavy boot code branches to an * undefined address. The table halts cleanly (and, under DEBUG+DEBUG_UART, @@ -1364,7 +1369,9 @@ _cm4_entry: b 7b 8: -#if defined(TARGET_tegra234) || defined(TARGET_imx8qm) || defined(TARGET_imx95_a55) +#if defined(TARGET_tegra234) || defined(TARGET_imx8qm) || \ + defined(TARGET_imx95_a55) || \ + (defined(TARGET_imx8mm) && defined(IMX8MM_BL33)) /* Reserved in-DRAM stack from the linker script: the generic path sets sp * to the image base, which here is the BL33 load address. */ ldr x1, =END_STACK @@ -1421,7 +1428,8 @@ _cm4_entry: bl boot_entry_C // boot_entry_C never returns b 7b // go to sleep anyhow in case. -#if defined(TARGET_cm4) || defined(TARGET_imx8qm) +#if defined(TARGET_cm4) || defined(TARGET_imx8qm) || \ + (defined(TARGET_imx8mm) && defined(IMX8MM_BL33)) /* Minimal always-on EL2 exception vectors. On any exception (data/instruction * abort, etc.) halt cleanly rather than branch to the UNKNOWN reset VBAR_EL2. * Under DEBUG+DEBUG_UART the handler additionally dumps ESR/ELR/FAR over UART. @@ -1480,7 +1488,7 @@ simple_el2_fault_stack: simple_el2_fault_stack_top: .section ".boot" #endif -#endif /* TARGET_cm4 || TARGET_imx8qm */ +#endif /* TARGET_cm4 || TARGET_imx8qm || (TARGET_imx8mm && IMX8MM_BL33) */ #if defined(TARGET_cm4) /* Firmware-provided DTB pointer, stored at _cm4_entry. Read by hal_get_boot_dts() * (hal/cm4.c) for the kernel-only FIT boot path. In .data so it is not subject to diff --git a/test-app/app_imx8mm.c b/test-app/app_imx8mm.c new file mode 100644 index 0000000000..e0c193fca3 --- /dev/null +++ b/test-app/app_imx8mm.c @@ -0,0 +1,105 @@ +/* app_imx8mm.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_imx8mm + +#include "hal/imx8mm.h" + +/* UART2 console, already set up by the earlier boot stages */ +#define UART_REG(off) \ + (*(volatile uint32_t*)(uintptr_t)(IMX8MM_UART_BASE + (off))) + +/* Bounded wait for TX FIFO space */ +#define UART_TX_SPIN_MAX 1000000 + +static void uart_putc(char c) +{ + unsigned int spin = 0; + + while ((UART_REG(IMX8MM_UART_UTS) & UTS_TXFULL) != 0) { + if (++spin > UART_TX_SPIN_MAX) + return; + } + UART_REG(IMX8MM_UART_UTXD) = (uint32_t)(uint8_t)c; +} + +static void uart_puts(const char* s) +{ + while (*s != '\0') { + if (*s == '\n') + uart_putc('\r'); + uart_putc(*s++); + } +} + +static void uart_puthex(uint64_t v) +{ + int i; + uart_puts("0x"); + for (i = 60; i >= 0; i -= 4) + uart_putc("0123456789abcdef"[(v >> i) & 0xF]); +} + +/* FDT magic 0xd00dfeed read little-endian */ +#define FDT_MAGIC_LE 0xedfe0dd0u + +/* dtb: x0 from wolfBoot (arm64 boot protocol) */ +void __attribute__((section(".boot"))) main(uint64_t dtb) { + uint64_t el; + + __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); + el >>= 2; + + uart_puts("\n*** wolfBoot i.MX 8M Mini payload: verified + booted ***\n"); + + uart_puts(" CurrentEL = EL"); + uart_putc('0' + (char)(el & 3)); + uart_puts("\n"); + + uart_puts(" DTB (x0) = "); + uart_puthex(dtb); + uart_puts("\n DTB magic = "); + /* MMU off: check range and alignment before reading */ + if (dtb < IMX8MM_DRAM_BASE || dtb > (IMX8MM_DRAM_END - 4) || + (dtb & 0x3) != 0) { + uart_puts("(pointer not in DRAM or misaligned)\n"); + } + else if (*((volatile uint32_t*)(uintptr_t)dtb) == FDT_MAGIC_LE) { + uart_puts("OK (0xd00dfeed)\n"); + } + else { + uart_puthex(*((volatile uint32_t*)(uintptr_t)dtb)); + uart_puts(" (BAD)\n"); + } + + uart_puts("*** payload complete: parked ***\n"); + + /* Wait for reboot */ + while(1) + ; +} +#endif /** TARGET_imx8mm **/ From 7907434584bedf4cf16e8ce500bf0917eac4e619 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Wed, 7 Oct 2026 15:09:15 +0900 Subject: [PATCH 03/10] imx8mm: add docs and image build scripts Scripts for flash.bin (BL33), the signed FIT and the RAM-boot image. --- .gitignore | 5 + docs/Targets.md | 199 ++++++++++++++++++++++ tools/scripts/imx8mm/imx8mm-mkfit.sh | 118 +++++++++++++ tools/scripts/imx8mm/imx8mm-mkflashbin.sh | 153 +++++++++++++++++ tools/scripts/imx8mm/imx8mm-mkramimage.sh | 47 +++++ 5 files changed, 522 insertions(+) create mode 100755 tools/scripts/imx8mm/imx8mm-mkfit.sh create mode 100755 tools/scripts/imx8mm/imx8mm-mkflashbin.sh create mode 100755 tools/scripts/imx8mm/imx8mm-mkramimage.sh diff --git a/.gitignore b/.gitignore index d33c896740..27d84bb2be 100644 --- a/.gitignore +++ b/.gitignore @@ -581,6 +581,11 @@ tools/scripts/cm4/*.bin tools/scripts/cm4/linux/ tools/scripts/cm4/rauc/ +# tools/scripts/imx8mm outputs +/imx8mm_flashbin/ +/imx8mm_fit/ +/fitImage_*.itb + # TI cl2000 (ARCH=C2000) intermediate assembly listings left in the repo root /*.asm diff --git a/docs/Targets.md b/docs/Targets.md index 53026cd31c..2a9902d964 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -26,6 +26,7 @@ This README describes configuration of supported targets. * [Nordic nRF5340](#nordic-nrf5340) * [Nordic nRF54L15](#nordic-nrf54l15) * [Nordic nRF54LM20](#nordic-nrf54lm20) +* [NXP i.MX 8M Mini](#nxp-imx-8m-mini) * [NXP i.MX 8QuadMax](#nxp-imx-8quadmax) * [NXP i.MX95 Cortex-M7](#nxp-imx95-cortex-m7) * [NXP i.MX95 Cortex-A55 (BL33)](#nxp-imx95-cortex-a55-bl33) @@ -9942,6 +9943,204 @@ Boot success marked. Version: 1 | `MAX3266X_OLD` | Build TPU acceleration against the older, deprecated Maxim SDK tree instead of the modern MSDK. | +## NXP i.MX 8M Mini + +wolfBoot runs on the NXP i.MX 8M Mini (MIMX8MM: 4x Cortex-A53) and boots a signed Linux kernel. Developed and tested on the TechNexion PICO-IMX8MM SoM (2 GB LPDDR4, eMMC) on the PICO-PI baseboard, with TechNexion's U-Boot 2023.04 (SPL) and NXP ATF. + +DDR training is done by U-Boot SPL, so wolfBoot never touches the DDR controller. Its HAL only reads the eMMC and writes to the UART. There are two ways to start it, selected with `IMX8MM_BL33`: + +``` +IMX8MM_BL33=1 (default): wolfBoot replaces U-Boot proper as BL33 + BootROM -> U-Boot SPL -> ATF (BL31) -> wolfBoot (BL33, EL2, 0x40200000) + -> read signed FIT from eMMC p1 -> verify -> Linux (EL2, DTB in x0) + +IMX8MM_BL33=0: RAM-boot, loaded into DRAM and started by U-Boot proper + BootROM -> U-Boot SPL -> ATF (BL31) -> U-Boot proper + -> load to DRAM, "go 0x40480000" -> wolfBoot -> verify -> Linux (EL2, DTB in x0) +``` + +In BL33 mode SPL loads wolfBoot in place of U-Boot proper, from the same FIT slot (`u-boot.itb`), so SPL and ATF stay unchanged. ATF enters wolfBoot at EL2 with the MMU and caches off. wolfBoot then turns on an identity MMU with the D-cache for the load and the verification. In RAM-boot mode U-Boot loads wolfBoot together with the signed kernel, and the DTB, into DRAM and starts it, for example with `go`. wolfBoot runs with the MMU and caches U-Boot left on. + +In both modes Linux is entered at EL2 with the MMU and D-cache off and the DTB address in `x0`. `do_boot()` cleans the caches to the point of coherency first (`EL2_HYPERVISOR=1`, set in `hal/imx8mm.h`). + +### Example configuration + +[/config/examples/imx8mm-pico.config](/config/examples/imx8mm-pico.config) covers both modes. The other variants are make options: + +| Build | Boot path | +|---|---| +| `make` | BL33 mode. Reads the signed FIT `/fitImage_A.itb` or `/fitImage_B.itb` from eMMC p1 (FAT32, uSDHC3, 8-bit, 50 MHz). | +| `make DISK_EMMC=0` | BL33 mode without a storage driver. SPL places the signed kernel and the DTB in DRAM as FIT loadables (USB download with `uuu`). | +| `make IMX8MM_BL33=0` | RAM-boot mode: loaded into DRAM and started by U-Boot proper (`go`). The signed kernel is bundled after wolfBoot in DRAM. | +| `... IMX8MM_MMU=0` | BL33 mode with the MMU off (I-cache only). Hashing a 27 MB kernel this way takes about 25 s instead of under 1 s. | +| `... IMX8MM_EMMC_HS=0` | eMMC at 4-bit, 25 MHz (the `src/sdhci.c` default) instead of 8-bit High Speed at 50 MHz. | +| `... BOOT_BENCHMARK=1` | Time the image read, the hash and the signature check. | + +The config uses RSA-4096 and SHA3-384 with a 1024-byte image header, and `DEBUG_UART=1` for the console on UART2 (`0x30890000`, `ttymxc1`). The UART clock, pin mux and baud rate are left as SPL set them. + +### Building + +No hardware is needed to compile: + +``` +cp config/examples/imx8mm-pico.config .config +make keytools +make CROSS_COMPILE=aarch64-linux-gnu- +``` + +This produces `wolfboot.bin` and `test-app/image_v1_signed.bin`. There is no contiguous `factory.bin`, because wolfBoot runs from DRAM. The test application is a small bare-metal payload. It prints the exception level and checks the DTB pointer it gets in `x0`. + +Notes on the build: + +- `aarch64-linux-gnu-gcc` defines `__linux__`, which makes `wolfBoot_printf` call the C library's `fprintf`. Keep `DEBUG_UART=1`, or add `CFLAGS_EXTRA+=-DWOLFBOOT_NO_PRINTF` when you turn it off. Otherwise wolfBoot jumps to an unresolved PLT entry at address 0. +- Distribution toolchains enable `_FORTIFY_SOURCE` and PIE by default. The config turns both off (`-U_FORTIFY_SOURCE -fno-pie`, `-no-pie`); with FORTIFY, `memcpy` becomes glibc's `__memcpy_chk`. `aarch64-linux-gnu-nm -u wolfboot.elf` must print nothing. +- `NO_QNX=1` is required. Without it the AArch64 startup code initializes the GIC at the ZynqMP address. +- The scripts below take the toolchain from `CROSS_COMPILE` in the environment: `export` it (default `aarch64-linux-gnu-`). +- Run `make clean` after changing options: the objects are not rebuilt when only the flags change. + +### Memory map + +| Address | Content | +|---|---| +| `0x00920000` | ATF BL31 | +| `0x40200000` | wolfBoot in BL33 mode (`WOLFBOOT_LOAD_BASE`, U-Boot's `CONFIG_TEXT_BASE`) | +| `0x40480000` | wolfBoot in RAM-boot mode (load and `go` address) | +| `0x40540000` | Signed kernel bundled after wolfBoot in RAM-boot mode (`kernel_addr`); it must end below the DTB, so at most 42 MB (`WOLFBOOT_RAMBOOT_MAX_SIZE`) | +| `0x43000000` | DTB (`WOLFBOOT_LOAD_DTS_ADDRESS`) | +| `0x44000000` | Kernel run address (`WOLFBOOT_LOAD_ADDRESS` in RAM-boot mode, the FIT `load` in BL33 mode) | +| `0x48000000` | BL33 mode: the whole signed FIT is read and verified here. With `DISK_EMMC=0`: the signed kernel placed by SPL. Max 64 MB (`WOLFBOOT_RAMBOOT_MAX_SIZE`) | + +### BL33 mode: building the boot image (flash.bin) + +`tools/scripts/imx8mm/imx8mm-mkflashbin.sh` builds wolfBoot with `IMX8MM_BL33=1` and creates the boot image with imx-mkimage. It uses the U-Boot SPL, DDR firmware and ATF of the regular U-Boot `flash.bin`, so build that first. + +Host packages (Ubuntu): `gcc-aarch64-linux-gnu`, `zlib1g-dev` (for imx-mkimage's `mkimage_imx8`) and `wget`. U-Boot's `mkimage` and `dtc` come from the U-Boot build (`tools/mkimage`, `scripts/dtc/dtc`); the examples put that `dtc` in `PATH`. + +TechNexion's `install_uboot_imx8.sh` in [u-boot-tn-imx](https://github.com/TechNexion/u-boot-tn-imx) collects everything: it clones [nxp-imx/imx-mkimage](https://github.com/nxp-imx/imx-mkimage) into `imx-mkimage/`, builds ATF from [nxp-imx/imx-atf](https://github.com/nxp-imx/imx-atf), downloads NXP's firmware-imx package for the LPDDR4 training firmware (it asks you to accept the NXP EULA), and makes U-Boot's `flash.bin`: + +``` +git clone -b tn-imx_v2023.04_6.1.55_2.2.0-stable https://github.com/TechNexion/u-boot-tn-imx.git +cd u-boot-tn-imx +export CROSS_COMPILE=aarch64-linux-gnu- +make pico-imx8mm_defconfig && make +PATH=$PWD/scripts/dtc:$PATH ./install_uboot_imx8.sh -b imx8mm-pico-pi.dtb -d /dev/null +``` + +`-d /dev/null` skips writing an SD card. A message about a missing `tee.bin` can be ignored. This leaves `u-boot-spl-ddr.bin`, `bl31.bin`, `mkimage_imx8`, `mkimage_uboot` and the U-Boot DTB in `u-boot-tn-imx/imx-mkimage/iMX8M`. Then, in wolfBoot: + +``` +UB= +export PATH=$UB/scripts/dtc:$PATH MKIMAGE=$UB/tools/mkimage +IMX_MKIMAGE=$UB/imx-mkimage tools/scripts/imx8mm/imx8mm-mkflashbin.sh +# -> flash.bin (about 525 KB) +``` + +The script writes a FIT with wolfBoot as `firmware` at `0x40200000`, the U-Boot DTB (SPL needs one, wolfBoot does not use it) and ATF. It puts the FIT at eMMC sector `0x300`, where SPL reads it, behind SPL and the DDR firmware. The image data starts 0x5000 into the FIT, as in imx-mkimage: newer `mkimage_imx8` versions write a HAB IVT and CSF right after the FIT header. + +To try it without writing the eMMC, start the board in serial download mode and run `uuu -b spl flash.bin`. To install it, write it to the eMMC user area at 33 KB (sector `0x42`), where the BootROM looks for the boot image. For example, from Linux on the board: + +``` +dd if=flash.bin of=/dev/mmcblk2 bs=512 seek=66 conv=fsync +``` + +This replaces U-Boot on the eMMC. Save the first 4 MB of the eMMC before writing it, so that U-Boot can be restored. + +With `DISK_EMMC=0`, pass the kernel and the DTB to the script. They are signed and added to the FIT as SPL loadables, and the image (about 35 MB) is for `uuu` only. A DTB from the Linux tree needs `bootargs` and `/memory` first (see the next section, `imx8mm_fit/board.dtb`): + +``` +DISK_EMMC=0 IMX_MKIMAGE=$UB/imx-mkimage \ + tools/scripts/imx8mm/imx8mm-mkflashbin.sh Image imx8mm-pico-pi.dtb +``` + +### BL33 mode: signed FIT image (kernel + DTB) + +In BL33 mode with eMMC, wolfBoot boots a FIT image (kernel + DTB) signed with the wolfBoot key. It reads the image as a file from a FAT32 partition. `tools/scripts/imx8mm/imx8mm-mkfit.sh` creates it, with `PATH` and `MKIMAGE` set as above: + +``` +tools/scripts/imx8mm/imx8mm-mkfit.sh Image imx8mm-pico-pi.dtb +# -> fitImage_A.itb, version 1 +SLOT=B VERSION=2 tools/scripts/imx8mm/imx8mm-mkfit.sh Image imx8mm-pico-pi.dtb +# -> fitImage_B.itb, version 2 +``` + +The FIT holds the kernel (`load`/`entry` `0x44000000`) and the DTB. The script makes the FIT with `mkimage` (data embedded, no `-E`), then signs the whole file with `tools/keytools/sign --rsa4096 --sha3`. Because the DTB is inside the signed image, it needs no separate digest. wolfBoot reads the file to `0x48000000` and verifies it. It then copies the kernel to its `load` address and the DTB to `0x43000000`. + +wolfBoot does not edit the DTB, and the DTB is covered by the signature, so the script prepares it before signing. It sets `root=` in `/chosen/bootargs` (`ROOTDEV`, default `/dev/mmcblk2p2`, the eMMC rootfs). A DTB from the Linux tree has neither `bootargs` nor a `/memory` node, because U-Boot normally adds them at boot; the script then adds `bootargs = "console=ttymxc1,115200 root=$ROOTDEV rootwait rw"` and `/memory` at `0x40000000` with `DRAM_SIZE` bytes (default `0x80000000`, 2 GB; set it for 1, 3 or 4 GB modules). Without `/memory` the kernel stops before printing anything. Set `ROOTDEV=` (empty) to use the DTB as it is. + +The script uses the `.config` and the `wolfboot_signing_private_key.der` of the tree, so run it in the same tree as the wolfBoot build. The image must be signed with the key that wolfBoot was built with. + +eMMC layout used on the PICO-IMX8MM (Linux `mmcblk2`, U-Boot `mmc 2`, MBR): + +| Partition | Content | +|---|---| +| (raw, from 33 KB) | `flash.bin` (SPL + DDR firmware, FIT with ATF and wolfBoot at sector `0x300`) | +| p1, FAT32 | `/fitImage_A.itb`, `/fitImage_B.itb` (the partition may also hold other boot files) | +| p2, ext4 | Linux root filesystem | + +wolfBoot's FAT32 driver reads FAT32 only, so p1 must be FAT32, not FAT16. To update an image from Linux: + +``` +mount /dev/mmcblk2p1 /mnt +cp fitImage_B.itb /mnt/ +sync +umount /mnt +``` + +Always `sync` and unmount before power-off. A file whose data has not reached the eMMC can be left with a size of 0, and wolfBoot then reports `No valid OS image`. Do not let the system automount p1, or unmount it before power-off. + +### A/B images and rollback + +Both files are on partition 0 (`BOOT_PART_A=0`, `BOOT_PART_B=0`, `BOOT_FILE_A`/`BOOT_FILE_B` in the config). `src/update_disk.c` reads the version from each header and verifies the newer image first. The results on the board: + +| `/fitImage_B.itb` (A is version 1) | Result | +|---|---| +| version 2, valid | boots B | +| header corrupted | B has no readable version, boots A | +| missing | boots A | +| version 2, payload corrupted | `Rollback to lower version not allowed`, PANIC | + +A newer image whose header is valid but whose payload is corrupted still sets the highest version. The older image is then refused, unless the build sets `ALLOW_DOWNGRADE`, which gives up rollback protection. An update must therefore never leave a half-written newer image under its final name. For example, copy it under a temporary name, `sync`, check it, then rename it. `DISK_BOOT_CONFIRM` (boot confirmation and fallback) only works with raw partitions, not with files. + +### RAM-boot mode: loading wolfBoot into DRAM from U-Boot proper + +In RAM-boot mode wolfBoot and the signed kernel are loaded into DRAM as one file and started there, for example with U-Boot's `fatload` and `go`. `tools/scripts/imx8mm/imx8mm-mkramimage.sh` builds wolfBoot with `IMX8MM_BL33=0` and signs the kernel. The DTB's SHA3-384 digest is bound into the signed header (`sign --dts`). The script then appends the signed kernel to wolfBoot at offset `0xC0000`: + +``` +tools/scripts/imx8mm/imx8mm-mkramimage.sh Image imx8mm-pico-pi.dtb +# -> wolfboot_linux.bin +``` + +Copy `wolfboot_linux.bin` and the same DTB to the SD card's first partition, then in U-Boot: + +``` +fatload mmc 1 0x40480000 wolfboot_linux.bin +fatload mmc 1 0x43000000 imx8mm-pico-pi.dtb +go 0x40480000 +``` + +wolfBoot verifies the kernel signature and checks the DTB at `0x43000000` against the digest in the header (`DTB digest verified`). If a different DTB is loaded, the boot stops. Sign the kernel again whenever the DTB changes. A DTB from the Linux tree needs `bootargs` and `/memory`, as for the FIT above: use the `imx8mm_fit/board.dtb` that `imx8mm-mkfit.sh` writes. + +### SD / eMMC driver + +The eMMC is on uSDHC3 (`0x30B60000`). The HAL maps the uSDHC registers to the SDHCI interface of `src/sdhci.c`, as `hal/imx8qm.c` does. It sets up the uSDHC3 clock root (SYSTEM_PLL1_DIV2, 400 MHz) and the pads itself, because SPL may not have initialized uSDHC3 (for example, after a USB download boot). + +- Bus: 8-bit, High Speed at 50 MHz (`SDHCI_EMMC_BUS_WIDTH=8`, `SDHCI_EMMC_HS`). `src/sdhci.c` switches the card with CMD6 (`HS_TIMING`) and stays at 25 MHz if the card reports a switch error. Other targets keep the 4-bit, 25 MHz default. +- Reads: SDMA in 64 KB blocks (`DISK_BLOCK_SIZE=65536`). With the D-cache on, the HAL cleans and invalidates the buffer around each transfer, and warns if a DMA buffer is not cache-line aligned. Keep `WOLFBOOT_FS_CACHE_SIZE` at its 512-byte default. A larger cache is read by SDMA into a buffer that shares a cache line with other data, which corrupts it. + +Measured with `BOOT_BENCHMARK=1` (27 MB FIT): read 0.99 s (about 29 MB/s), integrity check 0.68 s, signature 1 ms. Load and verification take about 1.7 s in total. + +### Known limitations + +- HABv4 is not set up. U-Boot SPL and ATF can authenticate the next stage with HABv4. Until then, nothing authenticates wolfBoot itself. +- BL33 mode boots Linux only from eMMC p1, or from DRAM with `uuu` (`DISK_EMMC=0`). The SD card slot (uSDHC2) is not supported in BL33 mode. +- No boot confirmation or automatic fallback with files on FAT32 (see above). + +### Tested with + +- TechNexion PICO-IMX8MM + PICO-PI, U-Boot 2023.04 ([u-boot-tn-imx](https://github.com/TechNexion/u-boot-tn-imx) branch `tn-imx_v2023.04_6.1.55_2.2.0-stable`, `pico-imx8mm_defconfig`), set up with its `install_uboot_imx8.sh`: imx-mkimage lf-6.1.55 (`c4365450`), ATF lf-6.1.55-2.2.2 (BL31 v2.8), firmware-imx 8.21. +- Linux 6.1.55 (`Image`, 27 MB) with `imx8mm-pico-pi.dtb`. +- Ubuntu GCC 13.3 and Linaro GCC 6.4.1 (`aarch64-linux-gnu-`). + ## NXP i.MX 8QuadMax wolfBoot runs on the NXP i.MX 8QuadMax (MIMX8QM: 2x Cortex-A72 + 4x Cortex-A53) as the bare-metal **BL33** firmware stage, replacing U-Boot inside the NXP boot container. Developed against the i.MX 8QuadMax Multisensory Enablement Kit (MCIMX8QM-CPU, "MEK"). diff --git a/tools/scripts/imx8mm/imx8mm-mkfit.sh b/tools/scripts/imx8mm/imx8mm-mkfit.sh new file mode 100755 index 0000000000..535b0d09fd --- /dev/null +++ b/tools/scripts/imx8mm/imx8mm-mkfit.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# Build and sign the FIT (kernel + DTB) that wolfBoot reads from eMMC p1 in +# BL33 mode. See "NXP i.MX 8M Mini" in docs/Targets.md. +# +# Usage: [SLOT=A|B] [VERSION=n] imx8mm-mkfit.sh Image DTB +# Env: ROOTDEV (root= in bootargs, default /dev/mmcblk2p2; bootargs is +# added if the DTB has none), DRAM_SIZE (/memory added if the DTB has +# none, default 0x80000000), MKIMAGE, DTC +set -e +# Paths from the caller are relative to the current directory +abspath() { case "$1" in /*) echo "$1" ;; *) echo "$PWD/$1" ;; esac; } +KERNEL="${1:+$(abspath "$1")}" +DTB="${2:+$(abspath "$2")}" +case "${MKIMAGE:-}" in */*) MKIMAGE="$(abspath "$MKIMAGE")" ;; esac +case "${DTC:-}" in */*) DTC="$(abspath "$DTC")" ;; esac +cd "$(dirname "$0")/../../.." + +KERNEL_ADDR=0x44000000 # kernel load/entry address in the FIT + +SLOT="${SLOT:-A}" +VERSION="${VERSION:-1}" +ROOTDEV="${ROOTDEV-/dev/mmcblk2p2}" +DRAM_SIZE="${DRAM_SIZE:-0x80000000}" +MKIMAGE="${MKIMAGE:-mkimage}" +DTC="${DTC:-dtc}" +KEY=wolfboot_signing_private_key.der +WORK=imx8mm_fit +OUT="fitImage_${SLOT}.itb" + +[ -f "$KERNEL" ] && [ -f "$DTB" ] || { + echo "Usage: $0 " >&2; exit 1; } +command -v "$DTC" >/dev/null && command -v dtc >/dev/null || { + echo "ERROR: dtc not in PATH (add U-Boot's scripts/dtc)" >&2; exit 1; } +case "$SLOT" in A|B) ;; *) echo "ERROR: SLOT must be A or B" >&2; exit 1;; esac +[ -f .config ] || { echo "ERROR: no .config (copy the wolfBoot one)" >&2; exit 1; } +[ -f "$KEY" ] || { echo "ERROR: $KEY not found (build wolfBoot first)" >&2; exit 1; } + +make keytools >/dev/null + +rm -rf "$WORK" && mkdir -p "$WORK" +cp "$KERNEL" "$WORK/Image" +if [ -n "$ROOTDEV" ]; then + "$DTC" -q -I dtb -O dts -o "$WORK/board.dts" "$DTB" + if grep -q "^[[:space:]]*bootargs = " "$WORK/board.dts"; then + sed -i "/^[[:space:]]*bootargs = /s#root=[^ \"]*#root=$ROOTDEV#" \ + "$WORK/board.dts" + else + # No bootargs (e.g. a kernel DTB): add one to /chosen + sed -i "0,/^[[:space:]]*chosen {/s##&\n\t\tbootargs = \"console=ttymxc1,115200 root=$ROOTDEV rootwait rw\";#" \ + "$WORK/board.dts" + fi + # No /memory (U-Boot normally adds it): add DRAM_SIZE at 0x40000000 + if ! grep -q 'device_type = "memory"' "$WORK/board.dts"; then + sz=$((DRAM_SIZE)) + printf '/ {\n\tmemory@40000000 {\n\t\tdevice_type = "memory";\n\t\treg = <0x0 0x40000000 0x%x 0x%x>;\n\t};\n};\n' \ + $((sz >> 32)) $((sz & 0xffffffff)) >> "$WORK/board.dts" + fi + grep -q "bootargs = .*root=$ROOTDEV[ \"]" "$WORK/board.dts" || \ + { echo "ERROR: cannot set root=$ROOTDEV in $DTB bootargs" >&2; exit 1; } + "$DTC" -q -I dts -O dtb -o "$WORK/board.dtb" "$WORK/board.dts" + grep "bootargs = " "$WORK/board.dts" +else + cp "$DTB" "$WORK/board.dtb" +fi + +cat > "$WORK/fitImage.its" <; + + images { + kernel-1 { + description = "Linux kernel"; + data = /incbin/("Image"); + type = "kernel"; + arch = "arm64"; + os = "linux"; + compression = "none"; + load = <$KERNEL_ADDR>; + entry = <$KERNEL_ADDR>; + hash-1 { + algo = "sha256"; + }; + }; + fdt-1 { + description = "Flattened Device Tree"; + data = /incbin/("board.dtb"); + type = "flat_dt"; + arch = "arm64"; + compression = "none"; + hash-1 { + algo = "sha256"; + }; + }; + }; + + configurations { + default = "conf-1"; + conf-1 { + description = "Linux kernel with FDT"; + kernel = "kernel-1"; + fdt = "fdt-1"; + }; + }; +}; +EOF + +# Embedded data: wolfBoot reads the "data" properties +(cd "$WORK" && "$MKIMAGE" -f fitImage.its fitImage.itb) +tools/keytools/sign --rsa4096 --sha3 "$WORK/fitImage.itb" "$KEY" "$VERSION" +cp "$WORK/fitImage_v${VERSION}_signed.bin" "$OUT" + +echo "$OUT: $(stat -c%s "$OUT") bytes, version $VERSION" +echo +echo "Copy it to eMMC p1 (FAT32) on the board, and sync before power-off:" +echo " mount /dev/mmcblk2p1 /mnt && cp $OUT /mnt/ && sync && umount /mnt" diff --git a/tools/scripts/imx8mm/imx8mm-mkflashbin.sh b/tools/scripts/imx8mm/imx8mm-mkflashbin.sh new file mode 100755 index 0000000000..6146945254 --- /dev/null +++ b/tools/scripts/imx8mm/imx8mm-mkflashbin.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash +# Build flash.bin for i.MX 8M Mini with wolfBoot as BL33 (replaces U-Boot +# proper). See "NXP i.MX 8M Mini" in docs/Targets.md. +# +# Usage: IMX_MKIMAGE= imx8mm-mkflashbin.sh +# DISK_EMMC=0 IMX_MKIMAGE= imx8mm-mkflashbin.sh Image DTB +# Env: CROSS_COMPILE, CONFIG, MAKE_ARGS, MKIMAGE, UBOOT_DTB, VERSION +# Overwrites .config and wolfboot.bin; uses the existing signing key. +set -e +# Paths from the caller are relative to the current directory +abspath() { case "$1" in /*) echo "$1" ;; *) echo "$PWD/$1" ;; esac; } +KERNEL="${1:+$(abspath "$1")}" +DTB="${2:+$(abspath "$2")}" +[ -z "${IMX_MKIMAGE:-}" ] || IMX_MKIMAGE="$(abspath "$IMX_MKIMAGE")" +[ -z "${CONFIG:-}" ] || CONFIG="$(abspath "$CONFIG")" +case "${MKIMAGE:-}" in */*) MKIMAGE="$(abspath "$MKIMAGE")" ;; esac +cd "$(dirname "$0")/../../.." + +BL33_BASE=0x40200000 # WOLFBOOT_LOAD_BASE with IMX8MM_BL33=1 +KERNEL_ADDR=0x48000000 # IMX8MM_KERNEL_ADDR with IMX8MM_BL33=1 +DTB_ADDR=0x43000000 # dts_addr (hal/imx8mm.ld) +ATF_ADDR=0x00920000 # BL31 entry +SPL_LOAD_ADDR=0x7E1000 # U-Boot SPL on i.MX 8M Mini +FIT_OFFSET=0x60000 # SPL reads the FIT from eMMC sector 0x300 + +CROSS_COMPILE="${CROSS_COMPILE:-aarch64-linux-gnu-}" +CONFIG="${CONFIG:-config/examples/imx8mm-pico.config}" +MAKE_ARGS="${MAKE_ARGS:-}" +MKIMAGE="${MKIMAGE:-mkimage}" +UBOOT_DTB="${UBOOT_DTB:-imx8mm-pico-pi.dtb}" +DISK_EMMC="${DISK_EMMC:-1}" +VERSION="${VERSION:-1}" +WORK=imx8mm_flashbin +OUT=flash.bin + +[ -n "${IMX_MKIMAGE:-}" ] || { echo "ERROR: set IMX_MKIMAGE" >&2; exit 1; } +command -v dtc >/dev/null || { + echo "ERROR: dtc not in PATH (add U-Boot's scripts/dtc)" >&2; exit 1; } +SOC_DIR="$(cd "$IMX_MKIMAGE/iMX8M" && pwd)" +for f in u-boot-spl-ddr.bin bl31.bin mkimage_imx8 "$UBOOT_DTB"; do + [ -f "$SOC_DIR/$f" ] || { echo "ERROR: missing $SOC_DIR/$f" >&2; exit 1; } +done +if [ "$DISK_EMMC" != "1" ]; then + [ -f "$KERNEL" ] && [ -f "$DTB" ] || { + echo "ERROR: DISK_EMMC=0 needs the kernel Image and the DTB" >&2; exit 1; } +fi + +cp "$CONFIG" .config +make clean >/dev/null +# DEBUG=0: -Os +# shellcheck disable=SC2086 # MAKE_ARGS must word-split into separate options +make CROSS_COMPILE="$CROSS_COMPILE" DEBUG="${DEBUG:-0}" IMX8MM_BL33=1 \ + DISK_EMMC="$DISK_EMMC" $MAKE_ARGS wolfboot.bin +"${CROSS_COMPILE}readelf" -h wolfboot.elf | \ + grep -q "Entry point address: *$BL33_BASE" || \ + { echo "ERROR: wolfboot.elf entry is not $BL33_BASE" >&2; exit 1; } + +rm -rf "$WORK" && mkdir -p "$WORK" +cp wolfboot.bin "$WORK/" +cp "$SOC_DIR/bl31.bin" "$SOC_DIR/u-boot-spl-ddr.bin" "$WORK/" +cp "$SOC_DIR/$UBOOT_DTB" "$WORK/uboot.dtb" + +LINUX_IMAGES="" +LOADABLES='"atf-1"' +if [ "$DISK_EMMC" != "1" ]; then + make keytools >/dev/null + tools/keytools/sign --rsa4096 --sha3 --dts "$DTB" "$KERNEL" \ + wolfboot_signing_private_key.der "$VERSION" + SIGNED="${KERNEL%.*}_v${VERSION}_signed.bin" + cp "$SIGNED" "$WORK/kernel_signed.bin" + cp "$DTB" "$WORK/linux.dtb" + LINUX_IMAGES=" + kernel-1 { + description = \"wolfBoot signed Linux kernel\"; + data = /incbin/(\"kernel_signed.bin\"); + type = \"firmware\"; + arch = \"arm64\"; + compression = \"none\"; + load = <$KERNEL_ADDR>; + }; + linuxdtb-1 { + description = \"Linux DTB\"; + data = /incbin/(\"linux.dtb\"); + type = \"firmware\"; + arch = \"arm64\"; + compression = \"none\"; + load = <$DTB_ADDR>; + };" + LOADABLES='"atf-1", "kernel-1", "linuxdtb-1"' +fi + +cat > "$WORK/wolfboot.its" <; + }; + fdt-1 { + description = "U-Boot DTB (required by SPL, unused)"; + data = /incbin/("uboot.dtb"); + type = "flat_dt"; + compression = "none"; + }; + atf-1 { + description = "ARM Trusted Firmware"; + data = /incbin/("bl31.bin"); + type = "firmware"; + arch = "arm64"; + compression = "none"; + load = <$ATF_ADDR>; + entry = <$ATF_ADDR>; + }; +$LINUX_IMAGES + }; + + configurations { + default = "config-1"; + config-1 { + description = "wolfBoot"; + firmware = "uboot-1"; + loadables = $LOADABLES; + fdt = "fdt-1"; + }; + }; +}; +EOF + +# External data at 0x5000, clear of the HAB IVT/CSF that mkimage_imx8 adds +(cd "$WORK" && "$MKIMAGE" -E -p 0x5000 -f wolfboot.its wolfboot.itb) + +# SPL + DDR firmware, then the FIT at FIT_OFFSET +(cd "$WORK" && "$SOC_DIR/mkimage_imx8" -version v1 -fit \ + -loader u-boot-spl-ddr.bin "$SPL_LOAD_ADDR" \ + -second_loader wolfboot.itb "$BL33_BASE" "$FIT_OFFSET" -out "../$OUT") + +echo "$OUT: $(stat -c%s "$OUT") bytes" +echo +if [ "$DISK_EMMC" = "1" ]; then + echo "Write it to the eMMC user area at 33 KB, e.g. from Linux on the board:" + echo " dd if=$OUT of=/dev/mmcblk2 bs=512 seek=66 conv=fsync" + echo "or boot it over USB (serial download mode): uuu -b spl $OUT" +else + echo "Boot it over USB (serial download mode): uuu -b spl $OUT" +fi diff --git a/tools/scripts/imx8mm/imx8mm-mkramimage.sh b/tools/scripts/imx8mm/imx8mm-mkramimage.sh new file mode 100755 index 0000000000..e0f2eca8a6 --- /dev/null +++ b/tools/scripts/imx8mm/imx8mm-mkramimage.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Build wolfBoot for RAM-boot (IMX8MM_BL33=0) and append the signed kernel. +# See "NXP i.MX 8M Mini" in docs/Targets.md. +# +# Usage: imx8mm-mkramimage.sh Image DTB -> wolfboot_linux.bin +# Env: CROSS_COMPILE, CONFIG, MAKE_ARGS, VERSION +# Overwrites .config and wolfboot.bin; uses the existing signing key. +set -e +# Paths from the caller are relative to the current directory +abspath() { case "$1" in /*) echo "$1" ;; *) echo "$PWD/$1" ;; esac; } +KERNEL="${1:+$(abspath "$1")}" +DTB="${2:+$(abspath "$2")}" +[ -z "${CONFIG:-}" ] || CONFIG="$(abspath "$CONFIG")" +cd "$(dirname "$0")/../../.." + +KERNEL_OFFSET=$((0xC0000)) # kernel_addr (0x40540000) - 0x40480000 + +CROSS_COMPILE="${CROSS_COMPILE:-aarch64-linux-gnu-}" +CONFIG="${CONFIG:-config/examples/imx8mm-pico.config}" +MAKE_ARGS="${MAKE_ARGS:-}" +VERSION="${VERSION:-1}" +OUT=wolfboot_linux.bin + +[ -f "$KERNEL" ] && [ -f "$DTB" ] || { + echo "Usage: $0 " >&2; exit 1; } + +cp "$CONFIG" .config +make clean >/dev/null +# shellcheck disable=SC2086 # MAKE_ARGS must word-split into separate options +make CROSS_COMPILE="$CROSS_COMPILE" IMX8MM_BL33=0 $MAKE_ARGS wolfboot.bin +make keytools >/dev/null + +core_sz=$(stat -c%s wolfboot.bin) +[ "$core_sz" -le "$KERNEL_OFFSET" ] || { + echo "ERROR: wolfboot.bin ($core_sz B) overlaps the kernel offset" >&2; exit 1; } + +tools/keytools/sign --rsa4096 --sha3 --dts "$DTB" "$KERNEL" \ + wolfboot_signing_private_key.der "$VERSION" +SIGNED="${KERNEL%.*}_v${VERSION}_signed.bin" + +cp wolfboot.bin "$OUT" +dd if=/dev/zero bs=1 count=$((KERNEL_OFFSET - core_sz)) >> "$OUT" 2>/dev/null +cat "$SIGNED" >> "$OUT" +[ "$(stat -c%s "$OUT")" -le $((0x43000000 - 0x40480000)) ] || { + echo "ERROR: $OUT overlaps the DTB at 0x43000000" >&2; exit 1; } + +echo "$OUT: $(stat -c%s "$OUT") bytes (signed kernel at +0x$(printf %x $KERNEL_OFFSET))" From 114b0eeaf9dcbfe620c3dd449437541582a66683 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 8 Oct 2026 11:04:06 +0900 Subject: [PATCH 04/10] imx8mm: RAM-boot image: check wolfBoot's _end against the kernel offset wolfboot.bin omits the BSS that startup clears. --- tools/scripts/imx8mm/imx8mm-mkramimage.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/scripts/imx8mm/imx8mm-mkramimage.sh b/tools/scripts/imx8mm/imx8mm-mkramimage.sh index e0f2eca8a6..7741bf81ae 100755 --- a/tools/scripts/imx8mm/imx8mm-mkramimage.sh +++ b/tools/scripts/imx8mm/imx8mm-mkramimage.sh @@ -31,8 +31,13 @@ make CROSS_COMPILE="$CROSS_COMPILE" IMX8MM_BL33=0 $MAKE_ARGS wolfboot.bin make keytools >/dev/null core_sz=$(stat -c%s wolfboot.bin) -[ "$core_sz" -le "$KERNEL_OFFSET" ] || { - echo "ERROR: wolfboot.bin ($core_sz B) overlaps the kernel offset" >&2; exit 1; } +core_end_hex=$("${CROSS_COMPILE}"nm -n wolfboot.elf | \ + awk '$3 == "_end" { print $1; exit }') +[ -n "$core_end_hex" ] || { + echo "ERROR: cannot find _end in wolfboot.elf" >&2; exit 1; } +core_end=$((16#$core_end_hex)) +[ "$core_end" -le $((0x40480000 + KERNEL_OFFSET)) ] || { + echo "ERROR: wolfBoot BSS overlaps the kernel offset" >&2; exit 1; } tools/keytools/sign --rsa4096 --sha3 --dts "$DTB" "$KERNEL" \ wolfboot_signing_private_key.der "$VERSION" From b57e98564b7b1d6c93319e5d99f19704f089a232 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 8 Oct 2026 11:20:58 +0900 Subject: [PATCH 05/10] imx8mm: mkfit: append root= when bootargs has none --- tools/scripts/imx8mm/imx8mm-mkfit.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/scripts/imx8mm/imx8mm-mkfit.sh b/tools/scripts/imx8mm/imx8mm-mkfit.sh index 535b0d09fd..c5734d3e24 100755 --- a/tools/scripts/imx8mm/imx8mm-mkfit.sh +++ b/tools/scripts/imx8mm/imx8mm-mkfit.sh @@ -42,8 +42,13 @@ cp "$KERNEL" "$WORK/Image" if [ -n "$ROOTDEV" ]; then "$DTC" -q -I dtb -O dts -o "$WORK/board.dts" "$DTB" if grep -q "^[[:space:]]*bootargs = " "$WORK/board.dts"; then - sed -i "/^[[:space:]]*bootargs = /s#root=[^ \"]*#root=$ROOTDEV#" \ - "$WORK/board.dts" + if grep -q "^[[:space:]]*bootargs = .*root=" "$WORK/board.dts"; then + sed -i "/^[[:space:]]*bootargs = /s#root=[^ \" ]*#root=$ROOTDEV#" \ + "$WORK/board.dts" + else + sed -i "/^[[:space:]]*bootargs = /s#\";# root=$ROOTDEV\";#" \ + "$WORK/board.dts" + fi else # No bootargs (e.g. a kernel DTB): add one to /chosen sed -i "0,/^[[:space:]]*chosen {/s##&\n\t\tbootargs = \"console=ttymxc1,115200 root=$ROOTDEV rootwait rw\";#" \ From 99158256d6131ae0328901d371cbe86bb81e6447 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 8 Oct 2026 11:41:23 +0900 Subject: [PATCH 06/10] imx8mm: RAM-boot image: check the kernel against WOLFBOOT_RAMBOOT_MAX_SIZE The limit is read from the build configuration. --- tools/scripts/imx8mm/imx8mm-mkramimage.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/scripts/imx8mm/imx8mm-mkramimage.sh b/tools/scripts/imx8mm/imx8mm-mkramimage.sh index 7741bf81ae..7e145656bf 100755 --- a/tools/scripts/imx8mm/imx8mm-mkramimage.sh +++ b/tools/scripts/imx8mm/imx8mm-mkramimage.sh @@ -38,6 +38,11 @@ core_end_hex=$("${CROSS_COMPILE}"nm -n wolfboot.elf | \ core_end=$((16#$core_end_hex)) [ "$core_end" -le $((0x40480000 + KERNEL_OFFSET)) ] || { echo "ERROR: wolfBoot BSS overlaps the kernel offset" >&2; exit 1; } +# shellcheck disable=SC2086 +max=$(make -s IMX8MM_BL33=0 $MAKE_ARGS \ + --eval='imx8mm-print-max: ; @echo $(WOLFBOOT_RAMBOOT_MAX_SIZE)' imx8mm-print-max) +[ "$(stat -c%s "$KERNEL")" -le $((max)) ] || { + echo "ERROR: kernel exceeds WOLFBOOT_RAMBOOT_MAX_SIZE ($max)" >&2; exit 1; } tools/keytools/sign --rsa4096 --sha3 --dts "$DTB" "$KERNEL" \ wolfboot_signing_private_key.der "$VERSION" From b1c7e4f64eda5b587d7b4a30f67bee48a95e7709 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 5 Nov 2026 13:35:22 +0900 Subject: [PATCH 07/10] imx8mm, imx8qm: share the uSDHC -> SDHCI shim --- arch.mk | 9 + docs/Targets.md | 6 +- hal/imx8mm.c | 343 +---------------- hal/imx8mm.h | 66 +--- hal/imx8qm.c | 375 +------------------ hal/imx8qm.h | 83 +---- hal/imx_usdhc_sdhci.c | 429 ++++++++++++++++++++++ hal/imx_usdhc_sdhci.h | 112 ++++++ tools/unit-tests/Makefile | 10 +- tools/unit-tests/unit-imx8qm-usdhc-shim.c | 13 +- 10 files changed, 595 insertions(+), 851 deletions(-) create mode 100644 hal/imx_usdhc_sdhci.c create mode 100644 hal/imx_usdhc_sdhci.h diff --git a/arch.mk b/arch.mk index 0da7cdd03f..7d21013e6e 100644 --- a/arch.mk +++ b/arch.mk @@ -324,6 +324,8 @@ ifeq ($(ARCH),AARCH64) # uSDHC board facts. Here rather than in the config because a command-line # CFLAGS_EXTRA= would replace the config's, whereas CFLAGS is additive. ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) + # uSDHC -> SDHCI shim, shared with imx8mm + OBJS+=hal/imx_usdhc_sdhci.o # Card detect is lsio_gpio5[22], so PRES_STATE bit 16 never sets. # SDMA is on: the read is the dominant boot cost and PIO caps it near # 1.6 MB/s regardless of bus clock. IMX8QM_SDHCI_PIO=1 falls back. @@ -357,6 +359,13 @@ ifeq ($(ARCH),AARCH64) endif endif + ifeq ($(TARGET),imx8mm) + # uSDHC -> SDHCI shim, shared with imx8qm + ifneq ($(filter 1,$(DISK_SDCARD) $(DISK_EMMC)),) + OBJS+=hal/imx_usdhc_sdhci.o + endif + endif + ifeq ($(TARGET),imx95_a55) # NXP i.MX95 Cortex-A55 cluster, running as BL33 in AHAB container 2. # -mstrict-align because this stage runs with the MMU off, so every access diff --git a/docs/Targets.md b/docs/Targets.md index 2a9902d964..2cb7037e5b 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -10122,7 +10122,7 @@ wolfBoot verifies the kernel signature and checks the DTB at `0x43000000` agains ### SD / eMMC driver -The eMMC is on uSDHC3 (`0x30B60000`). The HAL maps the uSDHC registers to the SDHCI interface of `src/sdhci.c`, as `hal/imx8qm.c` does. It sets up the uSDHC3 clock root (SYSTEM_PLL1_DIV2, 400 MHz) and the pads itself, because SPL may not have initialized uSDHC3 (for example, after a USB download boot). +The eMMC is on uSDHC3 (`0x30B60000`). The uSDHC registers are mapped to the SDHCI interface of `src/sdhci.c` by `hal/imx_usdhc_sdhci.c`, shared with the i.MX 8QuadMax. `hal/imx8mm.c` sets up the uSDHC3 clock root (SYSTEM_PLL1_DIV2, 400 MHz) and the pads itself in `imx_usdhc_board_init()`, because SPL may not have initialized uSDHC3 (for example, after a USB download boot). - Bus: 8-bit, High Speed at 50 MHz (`SDHCI_EMMC_BUS_WIDTH=8`, `SDHCI_EMMC_HS`). `src/sdhci.c` switches the card with CMD6 (`HS_TIMING`) and stays at 25 MHz if the card reports a switch error. Other targets keep the 4-bit, 25 MHz default. - Reads: SDMA in 64 KB blocks (`DISK_BLOCK_SIZE=65536`). With the D-cache on, the HAL cleans and invalidates the buffer around each transfer, and warns if a DMA buffer is not cache-line aligned. Keep `WOLFBOOT_FS_CACHE_SIZE` at its 512-byte default. A larger cache is read by SDMA into a buffer that shares a cache line with other data, which corrupts it. @@ -10345,7 +10345,7 @@ The MMU teardown before handoff must not be written in C. A set/way cache walk i ### SD / eMMC driver -i.MX uSDHC is the little-endian descendant of the QorIQ eSDHC and is close to, but not the same as, standard SDHCI. Rather than duplicating the card-initialization state machines, this target reuses the generic driver in `src/sdhci.c` and translates the register map in `hal/imx8qm.c` (`sdhci_reg_read`/`sdhci_reg_write`), as `hal/cm4.c` and `hal/tegra234.c` do for their controllers. Four differences are handled there: +i.MX uSDHC is the little-endian descendant of the QorIQ eSDHC and is close to, but not the same as, standard SDHCI. Rather than duplicating the card-initialization state machines, this target reuses the generic driver in `src/sdhci.c` and translates the register map in `hal/imx_usdhc_sdhci.c` (`sdhci_reg_read`/`sdhci_reg_write`, shared with the i.MX 8M Mini), as `hal/cm4.c` and `hal/tegra234.c` do for their controllers. Four differences are handled there: 1. The command register's transfer-mode half lives in `MIX_CTRL` (0x48), not in the low half of 0x0C. The command half at 0x0C is bit-identical to standard SDHCI. 2. `PROT_CTRL` encodes bus width as a 2-bit field and has no bus-power or bus-voltage fields. @@ -10410,7 +10410,7 @@ Register bases used by the HAL, from the upstream device tree (`imx8-ss-{lsio,co ### Known limitations -- **A warm reboot cannot recover an SD card left in UHS mode, and no software fix is possible on this board.** Once Linux has taken the card to 1.8V signalling (`timing spec: sd uhs SDR104`, `signal voltage: 1.80 V` in `/sys/kernel/debug/mmc1/ios`), a `reboot` performs no VDD cycle, so the card is still in its UHS state when wolfBoot starts and does not answer `CMD8`. A card that has negotiated UHS-I returns to the initial 3.3V state only when VDD is removed; `CMD0` does not do it. On the MEK the card supply is a fixed rail, and the uSDHC shim's bus-power and bus-voltage bits are shadowed rather than driven (`hal/imx8qm.c`), so `sdhci_set_power()` cannot produce a VDD cycle. Hardware-checked: switching the host to 1.8V to meet the card does not make it answer `CMD8` either, and initialization then fails at `CMD55`. +- **A warm reboot cannot recover an SD card left in UHS mode, and no software fix is possible on this board.** Once Linux has taken the card to 1.8V signalling (`timing spec: sd uhs SDR104`, `signal voltage: 1.80 V` in `/sys/kernel/debug/mmc1/ios`), a `reboot` performs no VDD cycle, so the card is still in its UHS state when wolfBoot starts and does not answer `CMD8`. A card that has negotiated UHS-I returns to the initial 3.3V state only when VDD is removed; `CMD0` does not do it. On the MEK the card supply is a fixed rail, and the uSDHC shim's bus-power and bus-voltage bits are shadowed rather than driven (`hal/imx_usdhc_sdhci.c`), so `sdhci_set_power()` cannot produce a VDD cycle. Hardware-checked: switching the host to 1.8V to meet the card does not make it answer `CMD8` either, and initialization then fails at `CMD55`. What wolfBoot does now is fail cleanly instead of hanging. `src/sdhci.c` used to wait for Command Inhibit with an unbounded loop, so the first failed command never returned and the boot stopped with no further output; the wait is bounded and resets the command line, so the failure is reported and reaches `wolfBoot_panic()`. Recovery is a **power cycle**, which always works, or `IMX8QM_SD_NO_UHS=1`, which avoids the situation by keeping Linux out of UHS. `src/sdhci.c` also carries an opt-in `SDHCI_UHS_RECOVER_ON_INIT`, which switches the host to 1.8V and retries `CMD8` once; it is off by default and does **not** help on this board, but is kept for hosts that can drive bus power. The eMMC has the same exposure at 1.8V for HS200/HS400. - **Failover cannot cross a version boundary downwards.** This is the anti-rollback policy rather than a gap: the retry is refused by the `ALLOW_DOWNGRADE` guard (`Rollback to lower version not allowed`) whenever the fallback slot carries a lower version, so equal-version slots are what make failover complete. - **AHAB has not been exercised against a closed part.** Signing is exercised and the signed container boots on an open part, but no board here has fused SRKs, so enforcement is unproven. Fusing is irreversible. diff --git a/hal/imx8mm.c b/hal/imx8mm.c index b6cb373a97..6b7be27196 100644 --- a/hal/imx8mm.c +++ b/hal/imx8mm.c @@ -285,8 +285,6 @@ void simple_el2_fault_handler(unsigned long esr, unsigned long elr, #error "imx8mm: only DISK_EMMC (uSDHC3) is supported" #endif -#define IMX8MM_USDHC_BASE IMX8MM_USDHC3_BASE - 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; } @@ -320,8 +318,9 @@ static const struct { { 0x108, 0x370, 2 }, /* NAND_CLE -> USDHC3_DATA7 */ }; -/* Clock root SYSTEM_PLL1_DIV2, gate on, pins (SPL skips this on USB boot) */ -static void imx8mm_usdhc3_setup(void) +/* Clock root SYSTEM_PLL1_DIV2, gate on, pins (SPL skips this on USB boot). + * Called by sdhci_platform_init() in hal/imx_usdhc_sdhci.c. */ +void imx_usdhc_board_init(void) { unsigned int i; @@ -336,338 +335,7 @@ static void imx8mm_usdhc3_setup(void) } } -/* uSDHC -> SDHCI register shim, taken from hal/imx8qm.c */ - -#ifndef CADENCE_SRS_OFFSET -#define CADENCE_SRS_OFFSET 0x200 -#endif - -#ifndef USDHC_RESET_TIMEOUT_US -#define USDHC_RESET_TIMEOUT_US 1000000 -#endif -#ifndef USDHC_INIT_TIMEOUT_US -#define USDHC_INIT_TIMEOUT_US 100000 -#endif -#ifndef USDHC_CLK_STABLE_TIMEOUT_US -#define USDHC_CLK_STABLE_TIMEOUT_US 100000 -#endif - -/* Standard SDHCI offsets (after the Cadence SRS base) */ -#define STD_BLK 0x04 -#define STD_CMD 0x0C -#define STD_PRES_STATE 0x24 -#define STD_HOST_CTRL1 0x28 -#define STD_CLOCK_CTRL 0x2C -#define STD_INT_STATUS 0x30 -#define STD_INT_STATUS_EN 0x34 -#define STD_INT_SIGNAL_EN 0x38 -#define STD_HOST_CTRL2 0x3C -#define STD_CAPS1 0x40 -#define STD_CAPS2 0x44 -#define STD_MAX_CURRENT 0x48 -#define STD_ADMA_ADDR_LO 0x58 -#define STD_ADMA_ADDR_HI 0x5C - -/* uSDHC-only error bits */ -#define USDHC_INT_DMAE (1U << 28) -#define USDHC_INT_TNE (1U << 26) - -/* Registers uSDHC does not implement */ -static uint32_t srs10_shadow; /* bus power / bus voltage / high speed */ -static uint32_t srs15_shadow; /* host control 2 */ -/* Rate set by sdhci_platform_set_clock() */ -static uint32_t usdhc_achieved_clk_khz = IMX8MM_USDHC_PERCLK_HZ / 1000; - -uint32_t sdhci_reg_read(uint32_t offset) -{ - uintptr_t b = IMX8MM_USDHC_BASE; - uint32_t std, v, raw; - - if (offset < CADENCE_SRS_OFFSET) { - /* Cadence HRS: none on uSDHC; report the PHY handshake as done */ - return (offset == SDHCI_HRS04) ? SDHCI_HRS04_UIS_ACK : 0; - } - std = offset - CADENCE_SRS_OFFSET; - - switch (std) { - case STD_PRES_STATE: - v = rd32(b + USDHC_PRES_STATE); - v |= SDHCI_SRS09_CSS; - /* DAT0 level: bit 24 -> bit 20 */ - if ((v & USDHC_PRES_DLSL_DAT0) != 0) - v |= SDHCI_SRS09_DAT0_LVL; - else - v &= ~SDHCI_SRS09_DAT0_LVL; - return v; - - case STD_HOST_CTRL1: - v = srs10_shadow & ~(uint32_t)(SDHCI_SRS10_DTW | SDHCI_SRS10_EDTW); - switch (rd32(b + USDHC_PROT_CTRL) & USDHC_PROT_DTW_MASK) { - case USDHC_PROT_DTW_4BIT: v |= SDHCI_SRS10_DTW; break; - case USDHC_PROT_DTW_8BIT: v |= SDHCI_SRS10_EDTW; break; - default: break; - } - return v; - - case STD_CLOCK_CTRL: - /* No clock enable/stable bits on uSDHC */ - v = rd32(b + USDHC_SYS_CTRL) & - (uint32_t)(USDHC_SYS_DTOCV_MASK | USDHC_SYS_RSTA | - USDHC_SYS_RSTC | USDHC_SYS_RSTD); - return v | SDHCI_SRS11_ICE | SDHCI_SRS11_ICS | SDHCI_SRS11_SDCE; - - case STD_INT_STATUS: - case STD_INT_STATUS_EN: - case STD_INT_SIGNAL_EN: - raw = rd32(b + std); - v = raw; - if ((raw & USDHC_INT_DMAE) != 0) - v |= SDHCI_SRS12_EADMA; - /* Hide uSDHC-only bits; synthesize the error summary */ - v &= ~(uint32_t)(USDHC_INT_DMAE | USDHC_INT_TNE); - if (std == STD_INT_STATUS && - (((v & SDHCI_SRS12_ERR_STAT) != 0) || - ((raw & USDHC_INT_TNE) != 0))) { - v |= SDHCI_SRS12_EINT; - } - return v; - - case STD_HOST_CTRL2: - return srs15_shadow; - - case STD_CAPS1: - /* Synthesize the timeout clock (50 MHz) and base clock */ - v = rd32(b + USDHC_HOST_CTRL_CAP) & - (uint32_t)(USDHC_CAP_VS33 | USDHC_CAP_VS30 | USDHC_CAP_VS18); - v |= (50U << SDHCI_SRS16_TCF_SHIFT) & SDHCI_SRS16_TCF_MASK; - v |= SDHCI_SRS16_TCU; /* timeout clock is in MHz */ - v |= ((IMX8MM_USDHC_PERCLK_HZ / 1000000U) - << SDHCI_SRS16_BCSDCLK_SHIFT) & - SDHCI_SRS16_BCSDCLK_MASK; - return v; - - case STD_CAPS2: - return 0; - - case STD_ADMA_ADDR_LO: - /* SDMA address (DS_ADDR) */ - return rd32(b + USDHC_DS_ADDR); - - case STD_ADMA_ADDR_HI: - return 0; /* 32-bit ADMA only */ - - case STD_MAX_CURRENT: - /* MIX_CTRL on uSDHC; no max-current register */ - return 0; - - default: - return rd32(b + std); - } -} - -void sdhci_reg_write(uint32_t offset, uint32_t val) -{ - uintptr_t b = IMX8MM_USDHC_BASE; - uint32_t std, v, mix; - - if (offset < CADENCE_SRS_OFFSET) { - /* Cadence software reset -> uSDHC reset-all */ - if (offset == SDHCI_HRS00 && (val & SDHCI_HRS00_SWR) != 0) - wr32(b + USDHC_SYS_CTRL, - rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); - return; - } - std = offset - CADENCE_SRS_OFFSET; - - switch (std) { - case STD_BLK: - /* Drop the SDMA boundary bits */ - wr32(b + USDHC_BLK_ATT, - (val & 0xFFFF0000U) | (val & 0x0FFFU)); - return; - - case STD_CMD: - /* MIX_CTRL first: writing CMD_XFR_TYP starts the command */ - mix = rd32(b + USDHC_MIX_CTRL) & ~(uint32_t)USDHC_MIX_CTRL_XFER_MASK; - mix |= val & USDHC_MIX_CTRL_XFER_MASK; - wr32(b + USDHC_MIX_CTRL, mix); - - v = val & 0xFFFF0000U; - /* Data commands are R1, not R1b */ - if ((v & USDHC_XFR_DPSEL) != 0 && - (v & USDHC_XFR_RSPTYP_MASK) == USDHC_XFR_RSPTYP_48B) { - v = (v & ~(uint32_t)USDHC_XFR_RSPTYP_MASK) | - USDHC_XFR_RSPTYP_48; - } - wr32(b + USDHC_CMD_XFR_TYP, v); - return; - - case STD_HOST_CTRL1: - srs10_shadow = val; - v = rd32(b + USDHC_PROT_CTRL) & - ~(uint32_t)(USDHC_PROT_DTW_MASK | USDHC_PROT_DMASEL_MASK); - if ((val & SDHCI_SRS10_EDTW) != 0) - v |= USDHC_PROT_DTW_8BIT; - else if ((val & SDHCI_SRS10_DTW) != 0) - v |= USDHC_PROT_DTW_4BIT; - else - v |= USDHC_PROT_DTW_1BIT; - /* SDMA only */ - v |= USDHC_PROT_DMASEL_SIMPLE; - wr32(b + USDHC_PROT_CTRL, v); - return; - - case STD_CLOCK_CTRL: - /* Divider is set by sdhci_platform_set_clock() */ - v = rd32(b + USDHC_SYS_CTRL) & ~(uint32_t)USDHC_SYS_DTOCV_MASK; - v |= val & USDHC_SYS_DTOCV_MASK; - v |= val & (uint32_t)(USDHC_SYS_RSTA | USDHC_SYS_RSTC | - USDHC_SYS_RSTD); - wr32(b + USDHC_SYS_CTRL, v); - return; - - case STD_INT_STATUS: - /* W1C; also clear the uSDHC-only error bits */ - v = val; - if ((val & SDHCI_SRS12_EADMA) != 0) - v |= USDHC_INT_DMAE; - if ((val & (SDHCI_SRS12_ERR_STAT | SDHCI_SRS12_EINT)) != 0) - v |= USDHC_INT_TNE; - wr32(b + USDHC_INT_STATUS, v & ~(uint32_t)SDHCI_SRS12_EINT); - return; - - case STD_INT_STATUS_EN: - case STD_INT_SIGNAL_EN: - /* Also enable the uSDHC-only error bits */ - v = val & ~(uint32_t)SDHCI_SRS12_EINT; - if ((val & SDHCI_SRS12_ERR_STAT) != 0) - v |= USDHC_INT_DMAE | USDHC_INT_TNE; - wr32(b + std, v); - return; - - case STD_HOST_CTRL2: - srs15_shadow = val; - /* 1.8V signaling is in VEND_SPEC */ - v = rd32(b + USDHC_VEND_SPEC); - if ((val & SDHCI_SRS15_V18SE) != 0) - v |= USDHC_VEND_SPEC_VSELECT; - else - v &= ~(uint32_t)USDHC_VEND_SPEC_VSELECT; - wr32(b + USDHC_VEND_SPEC, v); - return; - - case STD_CAPS1: - case STD_CAPS2: - return; /* read-only */ - - case STD_ADMA_ADDR_LO: - wr32(b + USDHC_DS_ADDR, val); - return; - - case STD_ADMA_ADDR_HI: - return; /* 32-bit ADMA only */ - - default: - wr32(b + std, val); - return; - } -} - -void sdhci_platform_init(void) -{ - uintptr_t b = IMX8MM_USDHC_BASE; - uint64_t deadline; - uint32_t v; - - imx8mm_usdhc3_setup(); - - /* Reset all */ - wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); - deadline = timer_deadline_us(USDHC_RESET_TIMEOUT_US); - while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_RSTA) != 0) { - if (timer_expired(deadline)) { - wolfBoot_printf("imx8mm usdhc: reset-all did not clear\n"); - break; - } - } - - /* Little-endian data port, no DAT3 card detect */ - v = rd32(b + USDHC_PROT_CTRL); - v = (v & ~(uint32_t)USDHC_PROT_EMODE_MASK) | USDHC_PROT_EMODE_LE; - v &= ~(uint32_t)USDHC_PROT_D3CD; - wr32(b + USDHC_PROT_CTRL, v); - - /* One 512-byte block per watermark event */ - wr32(b + USDHC_WTMK_LVL, - ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_RD_SHIFT) | - ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_WR_SHIFT)); - - /* 80 init clocks before CMD0 */ - wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_INITA); - deadline = timer_deadline_us(USDHC_INIT_TIMEOUT_US); - while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_INITA) != 0) { - if (timer_expired(deadline)) - break; - } -} - -/* SD clock = PERCLK / (prescaler * divisor). Returns the rate in kHz. */ -uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) -{ - uintptr_t b = IMX8MM_USDHC_BASE; - uint32_t target_hz, pre, dvs, v; - uint64_t deadline; - - (void)base_clk_khz; - - if (clock_khz == 0) - return 0; - -#ifdef IMX8MM_USDHC_MAX_CLK_KHZ - if (clock_khz > IMX8MM_USDHC_MAX_CLK_KHZ) - clock_khz = IMX8MM_USDHC_MAX_CLK_KHZ; -#endif - - target_hz = clock_khz * 1000U; - - /* Prescaler 1..256 (power of 2), divisor 1..16 */ - for (pre = 1; pre <= 256; pre <<= 1) { - for (dvs = 1; dvs <= 16; dvs++) { - if ((IMX8MM_USDHC_PERCLK_HZ / (pre * dvs)) <= target_hz) - goto found; - } - } - pre = 256; - dvs = 16; -found: - v = rd32(b + USDHC_SYS_CTRL) & - ~(uint32_t)(USDHC_SYS_DVS_MASK | USDHC_SYS_SDCLKFS_MASK); - /* SDCLKFS = prescaler / 2 */ - v |= ((pre >> 1) << USDHC_SYS_SDCLKFS_SHIFT) & USDHC_SYS_SDCLKFS_MASK; - v |= ((dvs - 1) << USDHC_SYS_DVS_SHIFT) & USDHC_SYS_DVS_MASK; - wr32(b + USDHC_SYS_CTRL, v); - - deadline = timer_deadline_us(USDHC_CLK_STABLE_TIMEOUT_US); - while ((rd32(b + USDHC_PRES_STATE) & USDHC_PRES_SDSTB) == 0) { - if (timer_expired(deadline)) { - wolfBoot_printf("imx8mm usdhc: SD clock never stabilized\n"); - return 0; - } - } - - usdhc_achieved_clk_khz = (IMX8MM_USDHC_PERCLK_HZ / (pre * dvs)) / 1000U; - return usdhc_achieved_clk_khz; -} - -void sdhci_platform_irq_init(void) -{ - /* Polled mode */ -} - -void sdhci_platform_set_bus_mode(int is_emmc) -{ - (void)is_emmc; -} +/* uSDHC -> SDHCI shim: hal/imx_usdhc_sdhci.c */ #ifdef IMX8MM_EMMC_PROBE static uint32_t le32(const uint8_t *p) @@ -697,7 +365,8 @@ static void imx8mm_emmc_probe(void) case USDHC_PROT_DTW_4BIT: i = 4; break; default: i = 1; break; } - wolfBoot_printf("eMMC: %d-bit, %u kHz\n", i, usdhc_achieved_clk_khz); + wolfBoot_printf("eMMC: %d-bit, %u kHz\n", i, + (unsigned)imx_usdhc_get_clock_khz()); if (disk_read(0, 0, sizeof(sec), sec) < 0) { wolfBoot_printf("eMMC: MBR read failed\n"); diff --git a/hal/imx8mm.h b/hal/imx8mm.h index b592e38d98..189b4e0ede 100644 --- a/hal/imx8mm.h +++ b/hal/imx8mm.h @@ -76,63 +76,13 @@ #define IMX8MM_USDHC_PERCLK_HZ 400000000 /* SYSTEM_PLL1_DIV2, divider 1 */ #endif -/* uSDHC registers (same IP as i.MX8QM, see hal/imx8qm.h) */ -#define USDHC_DS_ADDR 0x00 -#define USDHC_BLK_ATT 0x04 -#define USDHC_CMD_ARG 0x08 -#define USDHC_CMD_XFR_TYP 0x0C -#define USDHC_CMD_RSP0 0x10 -#define USDHC_DATA_BUFF_ACC_PORT 0x20 -#define USDHC_PRES_STATE 0x24 -#define USDHC_PROT_CTRL 0x28 -#define USDHC_SYS_CTRL 0x2C -#define USDHC_INT_STATUS 0x30 -#define USDHC_INT_STATUS_EN 0x34 -#define USDHC_HOST_CTRL_CAP 0x40 -#define USDHC_WTMK_LVL 0x44 -#define USDHC_MIX_CTRL 0x48 -#define USDHC_VEND_SPEC 0xC0 - -#define USDHC_PRES_CIHB (1 << 0) /* command inhibit (CMD) */ -#define USDHC_PRES_SDSTB (1 << 3) /* SD clock stable */ -#define USDHC_PRES_DLSL_DAT0 (1 << 24) /* DAT0 line signal level */ - -#define USDHC_PROT_DTW_MASK (0x3 << 1) -#define USDHC_PROT_DTW_1BIT (0x0 << 1) -#define USDHC_PROT_DTW_4BIT (0x1 << 1) -#define USDHC_PROT_DTW_8BIT (0x2 << 1) -#define USDHC_PROT_D3CD (1 << 3) -#define USDHC_PROT_EMODE_MASK (0x3 << 4) -#define USDHC_PROT_EMODE_LE (0x2 << 4) /* little-endian data port */ -#define USDHC_PROT_DMASEL_MASK (0x3 << 8) -#define USDHC_PROT_DMASEL_SIMPLE (0x0 << 8) - -#define USDHC_SYS_DVS_SHIFT 4 -#define USDHC_SYS_DVS_MASK (0xF << 4) -#define USDHC_SYS_SDCLKFS_SHIFT 8 -#define USDHC_SYS_SDCLKFS_MASK (0xFF << 8) -#define USDHC_SYS_DTOCV_SHIFT 16 -#define USDHC_SYS_DTOCV_MASK (0xF << 16) -#define USDHC_SYS_RSTA (1 << 24) /* reset all */ -#define USDHC_SYS_RSTC (1 << 25) /* reset command line */ -#define USDHC_SYS_RSTD (1 << 26) /* reset data line */ -#define USDHC_SYS_INITA (1 << 27) /* send 80 init clocks */ - -#define USDHC_XFR_RSPTYP_MASK (0x3 << 16) -#define USDHC_XFR_RSPTYP_48 (0x2 << 16) -#define USDHC_XFR_RSPTYP_48B (0x3 << 16) -#define USDHC_XFR_DPSEL (1 << 21) /* data present */ - -#define USDHC_MIX_CTRL_XFER_MASK 0x3F - -#define USDHC_CAP_VS33 (1 << 24) -#define USDHC_CAP_VS30 (1 << 25) -#define USDHC_CAP_VS18 (1 << 26) - -#define USDHC_WTMK_RD_SHIFT 0 -#define USDHC_WTMK_WR_SHIFT 16 -#define USDHC_WTMK_BLOCK_WORDS 0x80 - -#define USDHC_VEND_SPEC_VSELECT (1 << 1) /* 1.8V signaling */ +/* uSDHC registers and the shim interface (hal/imx_usdhc_sdhci.c) */ +#include "imx_usdhc_sdhci.h" +#define IMX8MM_USDHC_BASE IMX8MM_USDHC3_BASE +#define IMX_USDHC_BASE IMX8MM_USDHC_BASE +#define IMX_USDHC_PERCLK_HZ IMX8MM_USDHC_PERCLK_HZ +#ifdef IMX8MM_USDHC_MAX_CLK_KHZ +#define IMX_USDHC_MAX_CLK_KHZ IMX8MM_USDHC_MAX_CLK_KHZ +#endif #endif /* _IMX8MM_H_ */ diff --git a/hal/imx8qm.c b/hal/imx8qm.c index 1c996aa18c..283d118055 100644 --- a/hal/imx8qm.c +++ b/hal/imx8qm.c @@ -75,14 +75,6 @@ void hal_delay_us(uint32_t us) ; } -#if defined(DISK_SDCARD) || defined(DISK_EMMC) -#if defined(DISK_EMMC) -#define IMX8QM_USDHC_BASE IMX8QM_USDHC1_BASE /* eMMC, 8-bit */ -#else -#define IMX8QM_USDHC_BASE IMX8QM_USDHC2_BASE /* SD card, 4-bit */ -#endif -#endif - /* Set once imx8qm_scu_init() has brought the console's clock up, so * uart_init() knows the LPUART root clock rate and can program the divisor. */ static int imx8qm_scu_ready; @@ -965,372 +957,7 @@ int ext_flash_erase(uintptr_t address, int len) } #endif /* EXT_FLASH */ -/* uSDHC -> SDHCI shim (src/sdhci.c uses only sdhci_reg_read/write): transfer mode in - * MIX_CTRL, width in PROT_CTRL, DVS/SDCLKFS divider, no error summary, DMA err 25->28. */ - -#if defined(DISK_SDCARD) || defined(DISK_EMMC) - -/* Not exported by include/sdhci.h; every in-tree SDHCI platform defines it - * locally (hal/versal.c, hal/zynq7000.c, hal/cm4.h). */ -#ifndef CADENCE_SRS_OFFSET -#define CADENCE_SRS_OFFSET 0x200 -#endif - -#ifndef USDHC_RESET_TIMEOUT_US -#define USDHC_RESET_TIMEOUT_US 1000000 -#endif -#ifndef USDHC_INIT_TIMEOUT_US -#define USDHC_INIT_TIMEOUT_US 100000 -#endif -#ifndef USDHC_CLK_STABLE_TIMEOUT_US -#define USDHC_CLK_STABLE_TIMEOUT_US 100000 -#endif - -/* Standard-SDHCI offsets, as seen after the Cadence SRS base is removed. */ -#define STD_BLK 0x04 -#define STD_CMD 0x0C -#define STD_PRES_STATE 0x24 -#define STD_HOST_CTRL1 0x28 -#define STD_CLOCK_CTRL 0x2C -#define STD_INT_STATUS 0x30 -#define STD_INT_STATUS_EN 0x34 -#define STD_INT_SIGNAL_EN 0x38 -#define STD_HOST_CTRL2 0x3C -#define STD_CAPS1 0x40 -#define STD_CAPS2 0x44 -#define STD_MAX_CURRENT 0x48 -#define STD_ADMA_ADDR_LO 0x58 -#define STD_ADMA_ADDR_HI 0x5C - -/* uSDHC-only error bits with no standard-SDHCI position. */ -#define USDHC_INT_DMAE (1U << 28) -#define USDHC_INT_TNE (1U << 26) - -/* Fields the driver reads back that uSDHC does not implement. */ -static uint32_t srs10_shadow; /* bus power / bus voltage / high speed */ -static uint32_t srs15_shadow; /* host control 2 */ -/* The rate sdhci_platform_set_clock() achieved, reported as the base so the generic - * driver's divider is 1. NOT the CAPS1 base field, which stays the source rate. */ -static uint32_t usdhc_achieved_clk_khz = IMX8QM_USDHC_PERCLK_HZ / 1000; - -uint32_t sdhci_reg_read(uint32_t offset) -{ - uintptr_t b = IMX8QM_USDHC_BASE; - uint32_t std, v, raw; - - if (offset < CADENCE_SRS_OFFSET) { - /* Cadence HRS range: uSDHC has no equivalent. Report the PHY - * handshake as acknowledged so the driver's wait loops still exit. */ - return (offset == SDHCI_HRS04) ? SDHCI_HRS04_UIS_ACK : 0; - } - std = offset - CADENCE_SRS_OFFSET; - - switch (std) { - case STD_PRES_STATE: - v = rd32(b + USDHC_PRES_STATE); - /* Card state stable has no uSDHC bit; the card-inserted bit is - * already debounced, so report it as always stable. */ - v |= SDHCI_SRS09_CSS; - /* DAT0 level moves from the DLSL field (bit 24) to bit 20. */ - if ((v & USDHC_PRES_DLSL_DAT0) != 0) - v |= SDHCI_SRS09_DAT0_LVL; - else - v &= ~SDHCI_SRS09_DAT0_LVL; - return v; - - case STD_HOST_CTRL1: - v = srs10_shadow & ~(uint32_t)(SDHCI_SRS10_DTW | SDHCI_SRS10_EDTW); - switch (rd32(b + USDHC_PROT_CTRL) & USDHC_PROT_DTW_MASK) { - case USDHC_PROT_DTW_4BIT: v |= SDHCI_SRS10_DTW; break; - case USDHC_PROT_DTW_8BIT: v |= SDHCI_SRS10_EDTW; break; - default: break; - } - return v; - - case STD_CLOCK_CTRL: - /* No internal clock-enable or stable bit; report both satisfied. */ - v = rd32(b + USDHC_SYS_CTRL) & - (uint32_t)(USDHC_SYS_DTOCV_MASK | USDHC_SYS_RSTA | - USDHC_SYS_RSTC | USDHC_SYS_RSTD); - return v | SDHCI_SRS11_ICE | SDHCI_SRS11_ICS | SDHCI_SRS11_SDCE; - - case STD_INT_STATUS: - case STD_INT_STATUS_EN: - case STD_INT_SIGNAL_EN: - raw = rd32(b + std); - v = raw; - if ((raw & USDHC_INT_DMAE) != 0) - v |= SDHCI_SRS12_EADMA; - /* Hide uSDHC-only bits and synthesize the summary the driver polls. TNE has no standard - * bit, so test the raw value: otherwise a latched tuning error stalls the transfer. */ - v &= ~(uint32_t)(USDHC_INT_DMAE | USDHC_INT_TNE); - if (std == STD_INT_STATUS && - (((v & SDHCI_SRS12_ERR_STAT) != 0) || - ((raw & USDHC_INT_TNE) != 0))) { - v |= SDHCI_SRS12_EINT; - } - return v; - - case STD_HOST_CTRL2: - return srs15_shadow; - - case STD_CAPS1: - /* uSDHC zeroes these; synthesize a 50 MHz timeout clock and the - * real peripheral clock. 64-bit addressing masked off (PIO). */ - v = rd32(b + USDHC_HOST_CTRL_CAP) & - (uint32_t)(USDHC_CAP_VS33 | USDHC_CAP_VS30 | USDHC_CAP_VS18); - v |= (50U << SDHCI_SRS16_TCF_SHIFT) & SDHCI_SRS16_TCF_MASK; - v |= SDHCI_SRS16_TCU; /* timeout clock is in MHz */ - /* Constant source clock in MHz; the achieved rate would truncate to 0 MHz once the - * 400 kHz identification clock is programmed. */ - v |= ((IMX8QM_USDHC_PERCLK_HZ / 1000000U) - << SDHCI_SRS16_BCSDCLK_SHIFT) & - SDHCI_SRS16_BCSDCLK_MASK; - return v; - - case STD_CAPS2: - return 0; - - case STD_ADMA_ADDR_LO: - /* SDMA, not ADMA2: PROT_CTRL selects DMASEL=simple, so SRS22 is uSDHC's DS_ADDR (the - * running address the boundary handler rewrites to resume), not ADMA_SYS_ADDR. */ - return rd32(b + USDHC_DS_ADDR); - - case STD_ADMA_ADDR_HI: - return 0; /* 32-bit ADMA only */ - - case STD_MAX_CURRENT: - /* uSDHC has no max-current register, and this offset is its live - * MIX_CTRL. Passing that through would feed transfer-mode bits to - * the driver's XPC decision as if they were milliamps. Report no - * capability, as STD_CAPS2 does, which leaves XPC off. */ - return 0; - - default: - return rd32(b + std); - } -} - -void sdhci_reg_write(uint32_t offset, uint32_t val) -{ - uintptr_t b = IMX8QM_USDHC_BASE; - uint32_t std, v, mix; - - if (offset < CADENCE_SRS_OFFSET) { - /* The only Cadence host register the driver writes is the software - * reset; route it to the uSDHC reset-all bit. */ - if (offset == SDHCI_HRS00 && (val & SDHCI_HRS00_SWR) != 0) - wr32(b + USDHC_SYS_CTRL, - rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); - return; - } - std = offset - CADENCE_SRS_OFFSET; - - switch (std) { - case STD_BLK: - /* Bit 12 is block size here, SDMA boundary in standard SDHCI. */ - wr32(b + USDHC_BLK_ATT, - (val & 0xFFFF0000U) | (val & 0x0FFFU)); - return; - - case STD_CMD: - /* MIX_CTRL first: writing CMD_XFR_TYP starts the command. Bits - * above 5 of MIX_CTRL have no standard equivalent; preserve. */ - mix = rd32(b + USDHC_MIX_CTRL) & ~(uint32_t)USDHC_MIX_CTRL_XFER_MASK; - mix |= val & USDHC_MIX_CTRL_XFER_MASK; - wr32(b + USDHC_MIX_CTRL, mix); - - v = val & 0xFFFF0000U; - /* The Cadence response-check bits alias to "48-bit with busy". SD data commands are - * R1, not R1b, so a busy check would wait on DAT0 forever; downgrade. */ - if ((v & USDHC_XFR_DPSEL) != 0 && - (v & USDHC_XFR_RSPTYP_MASK) == USDHC_XFR_RSPTYP_48B) { - v = (v & ~(uint32_t)USDHC_XFR_RSPTYP_MASK) | - USDHC_XFR_RSPTYP_48; - } - wr32(b + USDHC_CMD_XFR_TYP, v); - return; - - case STD_HOST_CTRL1: - srs10_shadow = val; - v = rd32(b + USDHC_PROT_CTRL) & - ~(uint32_t)(USDHC_PROT_DTW_MASK | USDHC_PROT_DMASEL_MASK); - if ((val & SDHCI_SRS10_EDTW) != 0) - v |= USDHC_PROT_DTW_8BIT; - else if ((val & SDHCI_SRS10_DTW) != 0) - v |= USDHC_PROT_DTW_4BIT; - else - v |= USDHC_PROT_DTW_1BIT; - /* Standard SDHCI selects the DMA engine in bits 3:4; uSDHC uses - * bits 8:9. Only simple (SDMA) is ever requested here. */ - v |= USDHC_PROT_DMASEL_SIMPLE; - wr32(b + USDHC_PROT_CTRL, v); - return; - - case STD_CLOCK_CTRL: - /* Only the shared fields; the divider is owned by - * sdhci_platform_set_clock, so drop the standard divisor bits. */ - v = rd32(b + USDHC_SYS_CTRL) & ~(uint32_t)USDHC_SYS_DTOCV_MASK; - v |= val & USDHC_SYS_DTOCV_MASK; - v |= val & (uint32_t)(USDHC_SYS_RSTA | USDHC_SYS_RSTC | - USDHC_SYS_RSTD); - wr32(b + USDHC_SYS_CTRL, v); - return; - - case STD_INT_STATUS: - /* Write-1-to-clear. Map the standard ADMA error bit onto the uSDHC one and clear TNE - * with any error or the summary: the driver never sees TNE, so this is its only exit. */ - v = val; - if ((val & SDHCI_SRS12_EADMA) != 0) - v |= USDHC_INT_DMAE; - if ((val & (SDHCI_SRS12_ERR_STAT | SDHCI_SRS12_EINT)) != 0) - v |= USDHC_INT_TNE; - wr32(b + USDHC_INT_STATUS, v & ~(uint32_t)SDHCI_SRS12_EINT); - return; - - case STD_INT_STATUS_EN: - case STD_INT_SIGNAL_EN: - /* On uSDHC a status bit does not latch unless its enable is set, so the uSDHC-only - * error enables must be added to the mask the generic driver builds. */ - v = val & ~(uint32_t)SDHCI_SRS12_EINT; - if ((val & SDHCI_SRS12_ERR_STAT) != 0) - v |= USDHC_INT_DMAE | USDHC_INT_TNE; - wr32(b + std, v); - return; - - case STD_HOST_CTRL2: - srs15_shadow = val; - /* 1.8V signaling lives in VEND_SPEC on uSDHC. */ - v = rd32(b + USDHC_VEND_SPEC); - if ((val & SDHCI_SRS15_V18SE) != 0) - v |= USDHC_VEND_SPEC_VSELECT; - else - v &= ~(uint32_t)USDHC_VEND_SPEC_VSELECT; - wr32(b + USDHC_VEND_SPEC, v); - return; - - case STD_CAPS1: - case STD_CAPS2: - return; /* read-only */ - - case STD_ADMA_ADDR_LO: - wr32(b + USDHC_DS_ADDR, val); /* see the read side */ - return; - - case STD_ADMA_ADDR_HI: - return; /* 32-bit ADMA only */ - - default: - wr32(b + std, val); - return; - } -} - -void sdhci_platform_init(void) -{ - uintptr_t b = IMX8QM_USDHC_BASE; - uint64_t deadline; - uint32_t v; - - /* Reset the controller, then apply the settings that survive it. */ - wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); - deadline = timer_deadline_us(USDHC_RESET_TIMEOUT_US); - while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_RSTA) != 0) { - if (timer_expired(deadline)) { - wolfBoot_printf("imx8qm usdhc: reset-all did not clear\n"); - break; - } - } - - /* Little-endian data port, card-detect from DAT3 disabled (the MEK wires - * a dedicated card-detect GPIO, and on eMMC there is nothing to detect). */ - v = rd32(b + USDHC_PROT_CTRL); - v = (v & ~(uint32_t)USDHC_PROT_EMODE_MASK) | USDHC_PROT_EMODE_LE; - v &= ~(uint32_t)USDHC_PROT_D3CD; - wr32(b + USDHC_PROT_CTRL, v); - - /* Move a whole 512-byte block per watermark event, so the PIO loop sees one Buffer - * Read Ready per block rather than one every 32 bytes. */ - wr32(b + USDHC_WTMK_LVL, - ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_RD_SHIFT) | - ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_WR_SHIFT)); - - /* Send the 80 initialization clocks the card needs before CMD0. */ - wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_INITA); - deadline = timer_deadline_us(USDHC_INIT_TIMEOUT_US); - while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_INITA) != 0) { - if (timer_expired(deadline)) - break; - } -} - -/* uSDHC divides by (SDCLKFS prescaler) * (DVS + 1). Reporting the requested - * rate back as the base clock makes the generic driver's divider settle on 1. */ -uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) -{ - uintptr_t b = IMX8QM_USDHC_BASE; - uint32_t target_hz, pre, dvs, v; - uint64_t deadline; - - (void)base_clk_khz; - - if (clock_khz == 0) - return 0; - - /* Optional ceiling: sustained reads fail CRC at the 50 MHz the generic - * driver steps to. Raising it needs uSDHC delay-line tuning. */ -#ifdef IMX8QM_USDHC_MAX_CLK_KHZ - if (clock_khz > IMX8QM_USDHC_MAX_CLK_KHZ) - clock_khz = IMX8QM_USDHC_MAX_CLK_KHZ; -#endif - - target_hz = clock_khz * 1000U; - - /* Smallest prescaler/divisor pair not exceeding the requested rate; the prescaler is - * a power of two from 1 to 256 and the divisor runs 1..16. */ - for (pre = 1; pre <= 256; pre <<= 1) { - for (dvs = 1; dvs <= 16; dvs++) { - if ((IMX8QM_USDHC_PERCLK_HZ / (pre * dvs)) <= target_hz) - goto found; - } - } - pre = 256; - dvs = 16; -found: - v = rd32(b + USDHC_SYS_CTRL) & - ~(uint32_t)(USDHC_SYS_DVS_MASK | USDHC_SYS_SDCLKFS_MASK); - /* SDCLKFS is a one-hot prescaler code: 0x01 = divide by 2, and each - * further bit doubles it. A prescaler of 1 is encoded as 0. */ - v |= ((pre >> 1) << USDHC_SYS_SDCLKFS_SHIFT) & USDHC_SYS_SDCLKFS_MASK; - v |= ((dvs - 1) << USDHC_SYS_DVS_SHIFT) & USDHC_SYS_DVS_MASK; - wr32(b + USDHC_SYS_CTRL, v); - - /* Wait for the divided clock to settle before any command is issued. */ - deadline = timer_deadline_us(USDHC_CLK_STABLE_TIMEOUT_US); - while ((rd32(b + USDHC_PRES_STATE) & USDHC_PRES_SDSTB) == 0) { - if (timer_expired(deadline)) { - wolfBoot_printf("imx8qm usdhc: SD clock never stabilized\n"); - return 0; - } - } - - usdhc_achieved_clk_khz = (IMX8QM_USDHC_PERCLK_HZ / (pre * dvs)) / 1000U; - return usdhc_achieved_clk_khz; -} - -void sdhci_platform_irq_init(void) -{ - /* Polled mode: no GIC wiring needed for the boot path. */ -} - -void sdhci_platform_set_bus_mode(int is_emmc) -{ - /* The bus width is driven through Host Control 1, which the shim - * translates; there is no separate eMMC/SD mode select on uSDHC. */ - (void)is_emmc; -} - -#endif /* DISK_SDCARD || DISK_EMMC */ +/* uSDHC -> SDHCI shim: hal/imx_usdhc_sdhci.c */ /* Identity MMU (IMX8QM_MMU): MMU-off DRAM is uncached, so hashing is impractical. Maps diff --git a/hal/imx8qm.h b/hal/imx8qm.h index 0371407bd7..2df62ca63a 100644 --- a/hal/imx8qm.h +++ b/hal/imx8qm.h @@ -69,76 +69,8 @@ #define IMX8QM_USDHC1_BASE 0x5B010000 /* eMMC, 8-bit */ #define IMX8QM_USDHC2_BASE 0x5B020000 /* SD card, 4-bit */ -/* uSDHC keeps eSDHC's register layout and adds MIX_CTRL, which holds the - * transfer-mode half standard SDHCI keeps in the command register. */ -#define USDHC_DS_ADDR 0x00 -#define USDHC_BLK_ATT 0x04 -#define USDHC_CMD_ARG 0x08 -#define USDHC_CMD_XFR_TYP 0x0C -#define USDHC_CMD_RSP0 0x10 -#define USDHC_DATA_BUFF_ACC_PORT 0x20 -#define USDHC_PRES_STATE 0x24 -#define USDHC_PROT_CTRL 0x28 -#define USDHC_SYS_CTRL 0x2C -#define USDHC_INT_STATUS 0x30 -#define USDHC_INT_STATUS_EN 0x34 -#define USDHC_HOST_CTRL_CAP 0x40 -#define USDHC_WTMK_LVL 0x44 -#define USDHC_MIX_CTRL 0x48 -#define USDHC_VEND_SPEC 0xC0 - -/* PRES_STATE */ -#define USDHC_PRES_CIHB (1 << 0) /* command inhibit (CMD) */ -#define USDHC_PRES_SDSTB (1 << 3) /* SD clock stable */ -#define USDHC_PRES_DLSL_DAT0 (1 << 24) /* DAT0 line signal level */ - -/* PROT_CTRL */ -#define USDHC_PROT_DTW_MASK (0x3 << 1) -#define USDHC_PROT_DTW_1BIT (0x0 << 1) -#define USDHC_PROT_DTW_4BIT (0x1 << 1) -#define USDHC_PROT_DTW_8BIT (0x2 << 1) -#define USDHC_PROT_D3CD (1 << 3) -#define USDHC_PROT_EMODE_MASK (0x3 << 4) -#define USDHC_PROT_EMODE_LE (0x2 << 4) /* little-endian data port */ -#define USDHC_PROT_DMASEL_MASK (0x3 << 8) -#define USDHC_PROT_DMASEL_SIMPLE (0x0 << 8) - -/* SYS_CTRL. Reset and data-timeout sit at the same bits as SDHCI's SRS11 and pass - * through the shim; the DVS/SDCLKFS clock divider does not and is programmed directly. */ -#define USDHC_SYS_DVS_SHIFT 4 -#define USDHC_SYS_DVS_MASK (0xF << 4) -#define USDHC_SYS_SDCLKFS_SHIFT 8 -#define USDHC_SYS_SDCLKFS_MASK (0xFF << 8) -#define USDHC_SYS_DTOCV_SHIFT 16 -#define USDHC_SYS_DTOCV_MASK (0xF << 16) -#define USDHC_SYS_RSTA (1 << 24) /* reset all */ -#define USDHC_SYS_RSTC (1 << 25) /* reset command line */ -#define USDHC_SYS_RSTD (1 << 26) /* reset data line */ -#define USDHC_SYS_INITA (1 << 27) /* send 80 init clocks */ - -/* CMD_XFR_TYP (upper half is bit-identical to standard SDHCI's command half) */ -#define USDHC_XFR_RSPTYP_MASK (0x3 << 16) -#define USDHC_XFR_RSPTYP_48 (0x2 << 16) -#define USDHC_XFR_RSPTYP_48B (0x3 << 16) -#define USDHC_XFR_DPSEL (1 << 21) /* data present */ - -/* MIX_CTRL, the transfer-mode half. Bits 0,1,2,4,5 match standard SDHCI; the tuning - * and DDR bits above them have no equivalent and must be preserved. */ -#define USDHC_MIX_CTRL_XFER_MASK 0x3F - -/* HOST_CTRL_CAP: the voltage-support bits are at the standard positions. */ -#define USDHC_CAP_VS33 (1 << 24) -#define USDHC_CAP_VS30 (1 << 25) -#define USDHC_CAP_VS18 (1 << 26) - -/* WTMK_LVL burst watermarks, in 4-byte words. A full 512-byte block so the PIO loop - * sees one Buffer Read Ready per block rather than one per 32 bytes. */ -#define USDHC_WTMK_RD_SHIFT 0 -#define USDHC_WTMK_WR_SHIFT 16 -#define USDHC_WTMK_BLOCK_WORDS 0x80 - -/* VEND_SPEC */ -#define USDHC_VEND_SPEC_VSELECT (1 << 1) /* 1.8V signaling */ +/* uSDHC registers and the shim interface (hal/imx_usdhc_sdhci.c) */ +#include "imx_usdhc_sdhci.h" /* uSDHC per clock, owned by the SCU; 198 MHz is what the NXP BSP programs. With * IMX8QM_SCU=1 the real rate is read back and this is only the fallback. */ @@ -146,6 +78,17 @@ #define IMX8QM_USDHC_PERCLK_HZ 198000000 #endif +#if defined(DISK_EMMC) +#define IMX8QM_USDHC_BASE IMX8QM_USDHC1_BASE /* eMMC, 8-bit */ +#else +#define IMX8QM_USDHC_BASE IMX8QM_USDHC2_BASE /* SD card, 4-bit */ +#endif +#define IMX_USDHC_BASE IMX8QM_USDHC_BASE +#define IMX_USDHC_PERCLK_HZ IMX8QM_USDHC_PERCLK_HZ +#ifdef IMX8QM_USDHC_MAX_CLK_KHZ +#define IMX_USDHC_MAX_CLK_KHZ IMX8QM_USDHC_MAX_CLK_KHZ +#endif + /* FlexSPI0: MT35XU512ABA 64 MB octal NOR. AHB read window plus the LUT-driven IP * command path for erase/program; same IP as the Layerscape XSPI (nxp_ls1028a.c). */ #define IMX8QM_FLEXSPI0_BASE 0x5D120000 /* controller registers */ diff --git a/hal/imx_usdhc_sdhci.c b/hal/imx_usdhc_sdhci.c new file mode 100644 index 0000000000..ada6e6939f --- /dev/null +++ b/hal/imx_usdhc_sdhci.c @@ -0,0 +1,429 @@ +/* imx_usdhc_sdhci.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 + */ + +/* i.MX uSDHC -> SDHCI shim, shared by the i.MX 8QuadMax and i.MX 8M Mini HALs. + * src/sdhci.c uses only sdhci_reg_read/write: transfer mode in MIX_CTRL, width + * in PROT_CTRL, DVS/SDCLKFS divider, no error summary, DMA err 25->28. */ + +#include +#include + +#if defined(DEBUG_UART) + #define PRINTF_ENABLED +#endif + +#include "printf.h" +#include "wolfboot/wolfboot.h" + +#if defined(TARGET_imx8qm) +#include "hal/imx8qm.h" +#elif defined(TARGET_imx8mm) +#include "hal/imx8mm.h" +#else +#error "hal/imx_usdhc_sdhci.c: unsupported target" +#endif + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +#include "sdhci.h" +#include "aarch64_arch.h" + +#if !defined(IMX_USDHC_BASE) || !defined(IMX_USDHC_PERCLK_HZ) +#error "IMX_USDHC_BASE and IMX_USDHC_PERCLK_HZ must be set by the SoC header" +#endif + +/* MMIO is Device-nGnRnE (MMU off), so accesses are strongly ordered by the + * memory type and need no barriers. Kept as functions, not open-coded casts: + * the uSDHC unit test extracts the shim from this file and substitutes its own. */ +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; } + +#ifndef CADENCE_SRS_OFFSET +#define CADENCE_SRS_OFFSET 0x200 +#endif + +#ifndef USDHC_RESET_TIMEOUT_US +#define USDHC_RESET_TIMEOUT_US 1000000 +#endif +#ifndef USDHC_INIT_TIMEOUT_US +#define USDHC_INIT_TIMEOUT_US 100000 +#endif +#ifndef USDHC_CLK_STABLE_TIMEOUT_US +#define USDHC_CLK_STABLE_TIMEOUT_US 100000 +#endif + +/* Standard-SDHCI offsets, as seen after the Cadence SRS base is removed. */ +#define STD_BLK 0x04 +#define STD_CMD 0x0C +#define STD_PRES_STATE 0x24 +#define STD_HOST_CTRL1 0x28 +#define STD_CLOCK_CTRL 0x2C +#define STD_INT_STATUS 0x30 +#define STD_INT_STATUS_EN 0x34 +#define STD_INT_SIGNAL_EN 0x38 +#define STD_HOST_CTRL2 0x3C +#define STD_CAPS1 0x40 +#define STD_CAPS2 0x44 +#define STD_MAX_CURRENT 0x48 +#define STD_ADMA_ADDR_LO 0x58 +#define STD_ADMA_ADDR_HI 0x5C + +/* uSDHC-only error bits with no standard-SDHCI position. */ +#define USDHC_INT_DMAE (1U << 28) +#define USDHC_INT_TNE (1U << 26) + +/* Fields the driver reads back that uSDHC does not implement. */ +static uint32_t srs10_shadow; /* bus power / bus voltage / high speed */ +static uint32_t srs15_shadow; /* host control 2 */ +/* The rate sdhci_platform_set_clock() achieved, reported as the base so the generic + * driver's divider is 1. NOT the CAPS1 base field, which stays the source rate. */ +static uint32_t usdhc_achieved_clk_khz = IMX_USDHC_PERCLK_HZ / 1000; + +uint32_t sdhci_reg_read(uint32_t offset) +{ + uintptr_t b = IMX_USDHC_BASE; + uint32_t std, v, raw; + + if (offset < CADENCE_SRS_OFFSET) { + /* Cadence HRS range: uSDHC has no equivalent. Report the PHY + * handshake as acknowledged so the driver's wait loops still exit. */ + return (offset == SDHCI_HRS04) ? SDHCI_HRS04_UIS_ACK : 0; + } + std = offset - CADENCE_SRS_OFFSET; + + switch (std) { + case STD_PRES_STATE: + v = rd32(b + USDHC_PRES_STATE); + /* Card state stable has no uSDHC bit; the card-inserted bit is + * already debounced, so report it as always stable. */ + v |= SDHCI_SRS09_CSS; + /* DAT0 level moves from the DLSL field (bit 24) to bit 20. */ + if ((v & USDHC_PRES_DLSL_DAT0) != 0) + v |= SDHCI_SRS09_DAT0_LVL; + else + v &= ~SDHCI_SRS09_DAT0_LVL; + return v; + + case STD_HOST_CTRL1: + v = srs10_shadow & ~(uint32_t)(SDHCI_SRS10_DTW | SDHCI_SRS10_EDTW); + switch (rd32(b + USDHC_PROT_CTRL) & USDHC_PROT_DTW_MASK) { + case USDHC_PROT_DTW_4BIT: v |= SDHCI_SRS10_DTW; break; + case USDHC_PROT_DTW_8BIT: v |= SDHCI_SRS10_EDTW; break; + default: break; + } + return v; + + case STD_CLOCK_CTRL: + /* No internal clock-enable or stable bit; report both satisfied. */ + v = rd32(b + USDHC_SYS_CTRL) & + (uint32_t)(USDHC_SYS_DTOCV_MASK | USDHC_SYS_RSTA | + USDHC_SYS_RSTC | USDHC_SYS_RSTD); + return v | SDHCI_SRS11_ICE | SDHCI_SRS11_ICS | SDHCI_SRS11_SDCE; + + case STD_INT_STATUS: + case STD_INT_STATUS_EN: + case STD_INT_SIGNAL_EN: + raw = rd32(b + std); + v = raw; + if ((raw & USDHC_INT_DMAE) != 0) + v |= SDHCI_SRS12_EADMA; + /* Hide uSDHC-only bits and synthesize the summary the driver polls. TNE has no standard + * bit, so test the raw value: otherwise a latched tuning error stalls the transfer. */ + v &= ~(uint32_t)(USDHC_INT_DMAE | USDHC_INT_TNE); + if (std == STD_INT_STATUS && + (((v & SDHCI_SRS12_ERR_STAT) != 0) || + ((raw & USDHC_INT_TNE) != 0))) { + v |= SDHCI_SRS12_EINT; + } + return v; + + case STD_HOST_CTRL2: + return srs15_shadow; + + case STD_CAPS1: + /* uSDHC zeroes these; synthesize a 50 MHz timeout clock and the + * real peripheral clock. 64-bit addressing masked off (PIO). */ + v = rd32(b + USDHC_HOST_CTRL_CAP) & + (uint32_t)(USDHC_CAP_VS33 | USDHC_CAP_VS30 | USDHC_CAP_VS18); + v |= (50U << SDHCI_SRS16_TCF_SHIFT) & SDHCI_SRS16_TCF_MASK; + v |= SDHCI_SRS16_TCU; /* timeout clock is in MHz */ + /* Constant source clock in MHz; the achieved rate would truncate to 0 MHz once the + * 400 kHz identification clock is programmed. */ + v |= ((IMX_USDHC_PERCLK_HZ / 1000000U) + << SDHCI_SRS16_BCSDCLK_SHIFT) & + SDHCI_SRS16_BCSDCLK_MASK; + return v; + + case STD_CAPS2: + return 0; + + case STD_ADMA_ADDR_LO: + /* SDMA, not ADMA2: PROT_CTRL selects DMASEL=simple, so SRS22 is uSDHC's DS_ADDR (the + * running address the boundary handler rewrites to resume), not ADMA_SYS_ADDR. */ + return rd32(b + USDHC_DS_ADDR); + + case STD_ADMA_ADDR_HI: + return 0; /* 32-bit ADMA only */ + + case STD_MAX_CURRENT: + /* uSDHC has no max-current register, and this offset is its live + * MIX_CTRL. Passing that through would feed transfer-mode bits to + * the driver's XPC decision as if they were milliamps. Report no + * capability, as STD_CAPS2 does, which leaves XPC off. */ + return 0; + + default: + return rd32(b + std); + } +} + +void sdhci_reg_write(uint32_t offset, uint32_t val) +{ + uintptr_t b = IMX_USDHC_BASE; + uint32_t std, v, mix; + + if (offset < CADENCE_SRS_OFFSET) { + /* The only Cadence host register the driver writes is the software + * reset; route it to the uSDHC reset-all bit. */ + if (offset == SDHCI_HRS00 && (val & SDHCI_HRS00_SWR) != 0) + wr32(b + USDHC_SYS_CTRL, + rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); + return; + } + std = offset - CADENCE_SRS_OFFSET; + + switch (std) { + case STD_BLK: + /* Bit 12 is block size here, SDMA boundary in standard SDHCI. */ + wr32(b + USDHC_BLK_ATT, + (val & 0xFFFF0000U) | (val & 0x0FFFU)); + return; + + case STD_CMD: + /* MIX_CTRL first: writing CMD_XFR_TYP starts the command. Bits + * above 5 of MIX_CTRL have no standard equivalent; preserve. */ + mix = rd32(b + USDHC_MIX_CTRL) & ~(uint32_t)USDHC_MIX_CTRL_XFER_MASK; + mix |= val & USDHC_MIX_CTRL_XFER_MASK; + wr32(b + USDHC_MIX_CTRL, mix); + + v = val & 0xFFFF0000U; + /* The Cadence response-check bits alias to "48-bit with busy". SD data commands are + * R1, not R1b, so a busy check would wait on DAT0 forever; downgrade. */ + if ((v & USDHC_XFR_DPSEL) != 0 && + (v & USDHC_XFR_RSPTYP_MASK) == USDHC_XFR_RSPTYP_48B) { + v = (v & ~(uint32_t)USDHC_XFR_RSPTYP_MASK) | + USDHC_XFR_RSPTYP_48; + } + wr32(b + USDHC_CMD_XFR_TYP, v); + return; + + case STD_HOST_CTRL1: + srs10_shadow = val; + v = rd32(b + USDHC_PROT_CTRL) & + ~(uint32_t)(USDHC_PROT_DTW_MASK | USDHC_PROT_DMASEL_MASK); + if ((val & SDHCI_SRS10_EDTW) != 0) + v |= USDHC_PROT_DTW_8BIT; + else if ((val & SDHCI_SRS10_DTW) != 0) + v |= USDHC_PROT_DTW_4BIT; + else + v |= USDHC_PROT_DTW_1BIT; + /* Standard SDHCI selects the DMA engine in bits 3:4; uSDHC uses + * bits 8:9. Only simple (SDMA) is ever requested here. */ + v |= USDHC_PROT_DMASEL_SIMPLE; + wr32(b + USDHC_PROT_CTRL, v); + return; + + case STD_CLOCK_CTRL: + /* Only the shared fields; the divider is owned by + * sdhci_platform_set_clock, so drop the standard divisor bits. */ + v = rd32(b + USDHC_SYS_CTRL) & ~(uint32_t)USDHC_SYS_DTOCV_MASK; + v |= val & USDHC_SYS_DTOCV_MASK; + v |= val & (uint32_t)(USDHC_SYS_RSTA | USDHC_SYS_RSTC | + USDHC_SYS_RSTD); + wr32(b + USDHC_SYS_CTRL, v); + return; + + case STD_INT_STATUS: + /* Write-1-to-clear. Map the standard ADMA error bit onto the uSDHC one and clear TNE + * with any error or the summary: the driver never sees TNE, so this is its only exit. */ + v = val; + if ((val & SDHCI_SRS12_EADMA) != 0) + v |= USDHC_INT_DMAE; + if ((val & (SDHCI_SRS12_ERR_STAT | SDHCI_SRS12_EINT)) != 0) + v |= USDHC_INT_TNE; + wr32(b + USDHC_INT_STATUS, v & ~(uint32_t)SDHCI_SRS12_EINT); + return; + + case STD_INT_STATUS_EN: + case STD_INT_SIGNAL_EN: + /* On uSDHC a status bit does not latch unless its enable is set, so the uSDHC-only + * error enables must be added to the mask the generic driver builds. */ + v = val & ~(uint32_t)SDHCI_SRS12_EINT; + if ((val & SDHCI_SRS12_ERR_STAT) != 0) + v |= USDHC_INT_DMAE | USDHC_INT_TNE; + wr32(b + std, v); + return; + + case STD_HOST_CTRL2: + srs15_shadow = val; + /* 1.8V signaling lives in VEND_SPEC on uSDHC. */ + v = rd32(b + USDHC_VEND_SPEC); + if ((val & SDHCI_SRS15_V18SE) != 0) + v |= USDHC_VEND_SPEC_VSELECT; + else + v &= ~(uint32_t)USDHC_VEND_SPEC_VSELECT; + wr32(b + USDHC_VEND_SPEC, v); + return; + + case STD_CAPS1: + case STD_CAPS2: + return; /* read-only */ + + case STD_ADMA_ADDR_LO: + wr32(b + USDHC_DS_ADDR, val); /* see the read side */ + return; + + case STD_ADMA_ADDR_HI: + return; /* 32-bit ADMA only */ + + default: + wr32(b + std, val); + return; + } +} + +void sdhci_platform_init(void) +{ + uintptr_t b = IMX_USDHC_BASE; + uint64_t deadline; + uint32_t v; + + imx_usdhc_board_init(); + + /* Reset the controller, then apply the settings that survive it. */ + wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_RSTA); + deadline = timer_deadline_us(USDHC_RESET_TIMEOUT_US); + while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_RSTA) != 0) { + if (timer_expired(deadline)) { + wolfBoot_printf("usdhc: reset-all did not clear\n"); + break; + } + } + + /* Little-endian data port, card-detect from DAT3 disabled (the boards wire + * a dedicated card-detect GPIO, and on eMMC there is nothing to detect). */ + v = rd32(b + USDHC_PROT_CTRL); + v = (v & ~(uint32_t)USDHC_PROT_EMODE_MASK) | USDHC_PROT_EMODE_LE; + v &= ~(uint32_t)USDHC_PROT_D3CD; + wr32(b + USDHC_PROT_CTRL, v); + + /* Move a whole 512-byte block per watermark event, so the PIO loop sees one Buffer + * Read Ready per block rather than one every 32 bytes. */ + wr32(b + USDHC_WTMK_LVL, + ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_RD_SHIFT) | + ((uint32_t)USDHC_WTMK_BLOCK_WORDS << USDHC_WTMK_WR_SHIFT)); + + /* Send the 80 initialization clocks the card needs before CMD0. */ + wr32(b + USDHC_SYS_CTRL, rd32(b + USDHC_SYS_CTRL) | USDHC_SYS_INITA); + deadline = timer_deadline_us(USDHC_INIT_TIMEOUT_US); + while ((rd32(b + USDHC_SYS_CTRL) & USDHC_SYS_INITA) != 0) { + if (timer_expired(deadline)) + break; + } +} + +/* uSDHC divides by (SDCLKFS prescaler) * (DVS + 1). Reporting the requested + * rate back as the base clock makes the generic driver's divider settle on 1. */ +uint32_t sdhci_platform_set_clock(uint32_t clock_khz, uint32_t base_clk_khz) +{ + uintptr_t b = IMX_USDHC_BASE; + uint32_t target_hz, pre, dvs, v; + uint64_t deadline; + + (void)base_clk_khz; + + if (clock_khz == 0) + return 0; + + /* Optional ceiling: sustained reads fail CRC at the 50 MHz the generic + * driver steps to. Raising it needs uSDHC delay-line tuning. */ +#ifdef IMX_USDHC_MAX_CLK_KHZ + if (clock_khz > IMX_USDHC_MAX_CLK_KHZ) + clock_khz = IMX_USDHC_MAX_CLK_KHZ; +#endif + + target_hz = clock_khz * 1000U; + + /* Smallest prescaler/divisor pair not exceeding the requested rate; the prescaler is + * a power of two from 1 to 256 and the divisor runs 1..16. */ + for (pre = 1; pre <= 256; pre <<= 1) { + for (dvs = 1; dvs <= 16; dvs++) { + if ((IMX_USDHC_PERCLK_HZ / (pre * dvs)) <= target_hz) + goto found; + } + } + pre = 256; + dvs = 16; +found: + v = rd32(b + USDHC_SYS_CTRL) & + ~(uint32_t)(USDHC_SYS_DVS_MASK | USDHC_SYS_SDCLKFS_MASK); + /* SDCLKFS is a one-hot prescaler code: 0x01 = divide by 2, and each + * further bit doubles it. A prescaler of 1 is encoded as 0. */ + v |= ((pre >> 1) << USDHC_SYS_SDCLKFS_SHIFT) & USDHC_SYS_SDCLKFS_MASK; + v |= ((dvs - 1) << USDHC_SYS_DVS_SHIFT) & USDHC_SYS_DVS_MASK; + wr32(b + USDHC_SYS_CTRL, v); + + /* Wait for the divided clock to settle before any command is issued. */ + deadline = timer_deadline_us(USDHC_CLK_STABLE_TIMEOUT_US); + while ((rd32(b + USDHC_PRES_STATE) & USDHC_PRES_SDSTB) == 0) { + if (timer_expired(deadline)) { + wolfBoot_printf("usdhc: SD clock never stabilized\n"); + return 0; + } + } + + usdhc_achieved_clk_khz = (IMX_USDHC_PERCLK_HZ / (pre * dvs)) / 1000U; + return usdhc_achieved_clk_khz; +} + +void sdhci_platform_irq_init(void) +{ + /* Polled mode: no GIC wiring needed for the boot path. */ +} + +void sdhci_platform_set_bus_mode(int is_emmc) +{ + /* The bus width is driven through Host Control 1, which the shim + * translates; there is no separate eMMC/SD mode select on uSDHC. */ + (void)is_emmc; +} + +#endif /* DISK_SDCARD || DISK_EMMC */ + +#if defined(DISK_SDCARD) || defined(DISK_EMMC) +void WEAKFUNCTION imx_usdhc_board_init(void) +{ +} + +uint32_t imx_usdhc_get_clock_khz(void) +{ + return usdhc_achieved_clk_khz; +} +#endif /* DISK_SDCARD || DISK_EMMC */ diff --git a/hal/imx_usdhc_sdhci.h b/hal/imx_usdhc_sdhci.h new file mode 100644 index 0000000000..ac4b6a0cfc --- /dev/null +++ b/hal/imx_usdhc_sdhci.h @@ -0,0 +1,112 @@ +/* imx_usdhc_sdhci.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 + */ + +/* i.MX uSDHC registers, for the uSDHC -> SDHCI shim in hal/imx_usdhc_sdhci.c + * (i.MX 8QuadMax, i.MX 8M Mini). The SoC header sets IMX_USDHC_BASE and + * IMX_USDHC_PERCLK_HZ, and optionally IMX_USDHC_MAX_CLK_KHZ. Defines only: + * boot_aarch64_start.S includes the SoC headers. */ + +#ifndef _IMX_USDHC_SDHCI_H_ +#define _IMX_USDHC_SDHCI_H_ + +/* uSDHC keeps eSDHC's register layout and adds MIX_CTRL, which holds the + * transfer-mode half standard SDHCI keeps in the command register. */ +#define USDHC_DS_ADDR 0x00 +#define USDHC_BLK_ATT 0x04 +#define USDHC_CMD_ARG 0x08 +#define USDHC_CMD_XFR_TYP 0x0C +#define USDHC_CMD_RSP0 0x10 +#define USDHC_DATA_BUFF_ACC_PORT 0x20 +#define USDHC_PRES_STATE 0x24 +#define USDHC_PROT_CTRL 0x28 +#define USDHC_SYS_CTRL 0x2C +#define USDHC_INT_STATUS 0x30 +#define USDHC_INT_STATUS_EN 0x34 +#define USDHC_HOST_CTRL_CAP 0x40 +#define USDHC_WTMK_LVL 0x44 +#define USDHC_MIX_CTRL 0x48 +#define USDHC_VEND_SPEC 0xC0 + +/* PRES_STATE */ +#define USDHC_PRES_CIHB (1 << 0) /* command inhibit (CMD) */ +#define USDHC_PRES_SDSTB (1 << 3) /* SD clock stable */ +#define USDHC_PRES_DLSL_DAT0 (1 << 24) /* DAT0 line signal level */ + +/* PROT_CTRL */ +#define USDHC_PROT_DTW_MASK (0x3 << 1) +#define USDHC_PROT_DTW_1BIT (0x0 << 1) +#define USDHC_PROT_DTW_4BIT (0x1 << 1) +#define USDHC_PROT_DTW_8BIT (0x2 << 1) +#define USDHC_PROT_D3CD (1 << 3) +#define USDHC_PROT_EMODE_MASK (0x3 << 4) +#define USDHC_PROT_EMODE_LE (0x2 << 4) /* little-endian data port */ +#define USDHC_PROT_DMASEL_MASK (0x3 << 8) +#define USDHC_PROT_DMASEL_SIMPLE (0x0 << 8) + +/* SYS_CTRL. Reset and data-timeout sit at the same bits as SDHCI's SRS11 and pass + * through the shim; the DVS/SDCLKFS clock divider does not and is programmed directly. */ +#define USDHC_SYS_DVS_SHIFT 4 +#define USDHC_SYS_DVS_MASK (0xF << 4) +#define USDHC_SYS_SDCLKFS_SHIFT 8 +#define USDHC_SYS_SDCLKFS_MASK (0xFF << 8) +#define USDHC_SYS_DTOCV_SHIFT 16 +#define USDHC_SYS_DTOCV_MASK (0xF << 16) +#define USDHC_SYS_RSTA (1 << 24) /* reset all */ +#define USDHC_SYS_RSTC (1 << 25) /* reset command line */ +#define USDHC_SYS_RSTD (1 << 26) /* reset data line */ +#define USDHC_SYS_INITA (1 << 27) /* send 80 init clocks */ + +/* CMD_XFR_TYP (upper half is bit-identical to standard SDHCI's command half) */ +#define USDHC_XFR_RSPTYP_MASK (0x3 << 16) +#define USDHC_XFR_RSPTYP_48 (0x2 << 16) +#define USDHC_XFR_RSPTYP_48B (0x3 << 16) +#define USDHC_XFR_DPSEL (1 << 21) /* data present */ + +/* MIX_CTRL, the transfer-mode half. Bits 0,1,2,4,5 match standard SDHCI; the tuning + * and DDR bits above them have no equivalent and must be preserved. */ +#define USDHC_MIX_CTRL_XFER_MASK 0x3F + +/* HOST_CTRL_CAP: the voltage-support bits are at the standard positions. */ +#define USDHC_CAP_VS33 (1 << 24) +#define USDHC_CAP_VS30 (1 << 25) +#define USDHC_CAP_VS18 (1 << 26) + +/* WTMK_LVL burst watermarks, in 4-byte words. A full 512-byte block so the PIO loop + * sees one Buffer Read Ready per block rather than one per 32 bytes. */ +#define USDHC_WTMK_RD_SHIFT 0 +#define USDHC_WTMK_WR_SHIFT 16 +#define USDHC_WTMK_BLOCK_WORDS 0x80 + +/* VEND_SPEC */ +#define USDHC_VEND_SPEC_VSELECT (1 << 1) /* 1.8V signaling */ + +#ifndef __ASSEMBLER__ +#include + +/* Called first by sdhci_platform_init(): SoC clock and pad setup, if any. + * The default does nothing. */ +void imx_usdhc_board_init(void); + +/* SD clock rate last set by sdhci_platform_set_clock(), in kHz */ +uint32_t imx_usdhc_get_clock_khz(void); +#endif + +#endif /* _IMX_USDHC_SDHCI_H_ */ diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 12a69a11e7..cf10146660 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -1675,10 +1675,10 @@ versal_host.c: ../../hal/versal.c versal_host.h aarch64_arch_host.h @! grep -q '__asm__' $@ || { echo "$@: un-handled asm (an output operand needs an explicit rewrite above)"; grep -n '__asm__' $@; false; } # unit-imx8qm-usdhc-shim drives the real uSDHC-to-SDHCI translation from -# hal/imx8qm.c against an emulated register file. The shim block is extracted -# verbatim rather than host-copying the whole HAL: the rest of the file has -# multi-line asm (the cache maintenance in hal_prepare_boot) that a line-based -# rewrite cannot blank, while the shim itself is plain register access. +# hal/imx_usdhc_sdhci.c (shared by imx8qm and imx8mm) against an emulated +# register file, built with the imx8qm header. The shim block is extracted +# verbatim, without the includes and MMIO helpers above it, so the test +# substitutes its own rd32/wr32 and timer. # The trailing #endif of the enclosing "#if defined(DISK_SDCARD) ..." is # dropped, since its opening #if is outside the extracted range. # @@ -1689,7 +1689,7 @@ versal_host.c: ../../hal/versal.c versal_host.h aarch64_arch_host.h # rather than returning false, since a false in a for-body is discarded by the # next iteration's status and only the last item would fail the build; and it # matches the call syntax, since a bare name still matches a renamed one. -imx8qm_usdhc_extract.h: ../../hal/imx8qm.c +imx8qm_usdhc_extract.h: ../../hal/imx_usdhc_sdhci.c sed -n '/^#ifndef CADENCE_SRS_OFFSET$$/,/^#endif \/\* DISK_SDCARD || DISK_EMMC \*\/$$/p' $< \ | sed '/^#endif \/\* DISK_SDCARD || DISK_EMMC \*\/$$/d' > $@ test -s $@ diff --git a/tools/unit-tests/unit-imx8qm-usdhc-shim.c b/tools/unit-tests/unit-imx8qm-usdhc-shim.c index 3c6a7ef3af..e1853a6293 100644 --- a/tools/unit-tests/unit-imx8qm-usdhc-shim.c +++ b/tools/unit-tests/unit-imx8qm-usdhc-shim.c @@ -1,16 +1,17 @@ /* unit-imx8qm-usdhc-shim.c * - * Locks down the uSDHC-to-SDHCI register shim in hal/imx8qm.c. + * Locks down the uSDHC-to-SDHCI register shim in hal/imx_usdhc_sdhci.c, + * built with the i.MX 8QuadMax header. * * src/sdhci.c is written against the Cadence register naming (the SD standard * register set at 0x200 + the standard offset) and reaches the controller only * through sdhci_reg_read/sdhci_reg_write. i.MX uSDHC is the little-endian * descendant of the QorIQ eSDHC and is close to, but not the same as, standard - * SDHCI, so hal/imx8qm.c translates. Every translation is a place the boot can + * SDHCI, so hal/imx_usdhc_sdhci.c translates. Every translation is a place the boot can * fail silently on hardware, and none of it is exercised by a build, so it is * pinned here instead. * - * The shim functions are extracted verbatim from hal/imx8qm.c + * The shim functions are extracted verbatim from hal/imx_usdhc_sdhci.c * (imx8qm_usdhc_extract.h, generated by the Makefile) and driven against an * emulated register file, so the code under test is the shipped code rather * than a transcription of it. @@ -48,7 +49,8 @@ /* Emulated uSDHC register block, in place of 0x5B020000. */ static uint32_t g_usdhc[0x100]; -#define IMX8QM_USDHC_BASE ((uintptr_t)g_usdhc) +#undef IMX_USDHC_BASE +#define IMX_USDHC_BASE ((uintptr_t)g_usdhc) #define REG(off) g_usdhc[(off) / 4] @@ -86,6 +88,9 @@ static int timer_expired(uint64_t deadline) static int g_printf_calls; #define wolfBoot_printf(...) do { g_printf_calls++; } while (0) +/* SoC clock/pad hook called by sdhci_platform_init(); nothing to emulate */ +void imx_usdhc_board_init(void) { } + #include "imx8qm_usdhc_extract.h" #undef wolfBoot_printf From 176f6c9ca67f3c767828f03f64e397da02454a44 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 5 Nov 2026 14:06:52 +0900 Subject: [PATCH 08/10] imx8mm, imx8qm: move the identity MMU and SDMA cache maintenance to assembly --- docs/Targets.md | 2 +- hal/imx8mm.c | 89 ++--------------------------- hal/imx8mm.h | 5 ++ hal/imx8qm.c | 90 ++--------------------------- hal/imx8qm.h | 5 ++ hal/imx_usdhc_sdhci.c | 36 ++++++++++++ include/aarch64_arch.h | 8 +++ src/boot_aarch64_start.S | 119 +++++++++++++++++++++++++++++++++++++-- 8 files changed, 178 insertions(+), 176 deletions(-) diff --git a/docs/Targets.md b/docs/Targets.md index 2cb7037e5b..3a1d6be05e 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -10341,7 +10341,7 @@ BL33 also owns SMMU bring-up, in two parts. Power `SC_R_SMMU` and set `sCR0.CLIE `hal_dts_fixup()` supplies what U-Boot would have: `/chosen/bootargs`, the real two-bank `/memory` map (2 GB at `0x80000000`, 4 GB at `0x880000000`), and optionally `no-1-8-v` plus `max-frequency` on the SD node. Without it the OS comes up with 1 GB and an empty command line. RAM staging sits at `0xa0000000` and `0xa8000000`, in the gap between the stock DTB's carveouts (roughly `0x90000000` to `0x9c000000`) and the CMA pool at `0xc0000000`. An overlap raises no error: the OS reserves the device tree it was handed, so the colliding carveout's reservation fails and whichever driver owned that region faults later. -The MMU teardown before handoff must not be written in C. A set/way cache walk in C keeps its loop counters in stack slots and re-dirties lines behind itself; clearing `SCTLR_EL2.C` then strands them while later stack reads go to DRAM. `hal_prepare_boot()` calls the stack-free assembly `el2_flush_and_disable_mmu()` in `src/boot_aarch64_start.S`. +The MMU teardown before handoff must not be written in C. A set/way cache walk in C keeps its loop counters in stack slots and re-dirties lines behind itself; clearing `SCTLR_EL2.C` then strands them while later stack reads go to DRAM. `hal_prepare_boot()` calls the stack-free assembly `el2_flush_and_disable_mmu()` in `src/boot_aarch64_start.S`. The enable side, `el2_idmap_enable()`, and the range maintenance for SDMA (`flush_dcache_range()`, `invalidate_dcache_range()`) are assembly too, shared with the i.MX 8M Mini (`WOLFBOOT_AARCH64_EL2_IDMAP`). ### SD / eMMC driver diff --git a/hal/imx8mm.c b/hal/imx8mm.c index 6b7be27196..edc355b7a8 100644 --- a/hal/imx8mm.c +++ b/hal/imx8mm.c @@ -146,97 +146,20 @@ static void imx8mm_icache_enable(void) } #ifdef IMX8MM_MMU -/* Identity MMU, 1 GB blocks: DRAM Normal cacheable, the rest Device (XN). - * do_boot() turns it off again before Linux. As hal/imx8qm.c. */ -#define MMU_BLOCK_NORMAL 0x0000000000000701ULL -#define MMU_BLOCK_DEVICE (0x0000000000000405ULL | (1ULL << 54) | (1ULL << 53)) - -static volatile uint64_t imx8mm_l1_table[512] __attribute__((aligned(4096))); - +/* Identity MMU, 1 GB blocks: DRAM Normal cacheable, the rest Device (XN), + * stack-free in src/boot_aarch64_start.S. do_boot() turns it off again before + * Linux. SDMA cache maintenance is in hal/imx_usdhc_sdhci.c. */ static void imx8mm_mmu_enable(void) { - uint64_t sctlr, el; - int i; + uint64_t sctlr; - __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); __asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr)); - if (((el >> 2) & 0x3) != 2 || (sctlr & (1UL << 0)) != 0) { + if (current_el() != 2 || (sctlr & (1UL << 0)) != 0) { imx8mm_icache_enable(); return; } - - for (i = 0; i < 4; i++) { - uint64_t base = (uint64_t)i << 30; - imx8mm_l1_table[i] = base | - ((base >= IMX8MM_DRAM_BASE && base < IMX8MM_DRAM_END) ? - MMU_BLOCK_NORMAL : MMU_BLOCK_DEVICE); - } - - /* Attr0: Normal WB, Attr1: Device */ - __asm__ volatile("msr mair_el2, %0" :: "r"(0x00000000000000FFUL)); - __asm__ volatile("msr ttbr0_el2, %0" - :: "r"((uint64_t)(uintptr_t)imx8mm_l1_table)); - /* T0SZ=32, 4 KB granule, cacheable walks; bits 31 and 23 are RES1 */ - __asm__ volatile("msr tcr_el2, %0" - :: "r"(0x0000000000013520UL | (1UL << 31) | (1UL << 23))); - __asm__ volatile("isb"); - __asm__ volatile("tlbi alle2"); - __asm__ volatile("dsb sy"); - - /* Drop stale lines from earlier stages */ - aarch64_dcache_maint(0); - __asm__ volatile("ic iallu"); - __asm__ volatile("dsb sy"); - __asm__ volatile("isb"); - - sctlr |= (1UL << 0) | (1UL << 2) | (1UL << 12); /* M | C | I */ - __asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr)); - __asm__ volatile("isb"); -} - -#if defined(DISK_EMMC) && !defined(SDHCI_SDMA_DISABLED) -/* Cache maintenance by address range for SDMA */ -static void imx8mm_dcache_range(uintptr_t start, uint32_t sz, int invalidate) -{ - uintptr_t line, end; - uint64_t ctr; - - if (sz == 0) - return; - /* CTR_EL0.DminLine: log2 of the line size in words */ - __asm__ volatile("mrs %0, ctr_el0" : "=r"(ctr)); - line = (uintptr_t)4 << ((ctr >> 16) & 0xF); - - if (((start | sz) & (line - 1)) != 0) - wolfBoot_printf("imx8mm: DMA buffer %p+%u not cache-line aligned\n", - (void*)start, (unsigned)sz); - - end = (start + sz + line - 1) & ~(line - 1); - start &= ~(line - 1); - __asm__ volatile("dsb sy"); - for (; start < end; start += line) { - if (invalidate) - __asm__ volatile("dc ivac, %0" :: "r"(start) : "memory"); - else - __asm__ volatile("dc civac, %0" :: "r"(start) : "memory"); - } - __asm__ volatile("dsb sy"); - __asm__ volatile("isb"); -} - -void sdhci_platform_dma_prepare(void *buf, uint32_t sz, int is_write) -{ - /* Clean in both directions: a dirty line could evict onto DMA data */ - imx8mm_dcache_range((uintptr_t)buf, sz, 0); - (void)is_write; -} - -void sdhci_platform_dma_complete(void *buf, uint32_t sz, int is_write) -{ - if (!is_write) - imx8mm_dcache_range((uintptr_t)buf, sz, 1); + el2_idmap_enable(IMX8MM_DRAM_BASE, IMX8MM_DRAM_END); } -#endif /* DISK_EMMC && !SDHCI_SDMA_DISABLED */ #endif /* IMX8MM_MMU */ #endif /* IMX8MM_BL33 */ diff --git a/hal/imx8mm.h b/hal/imx8mm.h index 189b4e0ede..8715710b42 100644 --- a/hal/imx8mm.h +++ b/hal/imx8mm.h @@ -30,6 +30,11 @@ #define EL2_HYPERVISOR 1 #endif +/* BL33 identity MMU (el2_idmap_enable, src/boot_aarch64_start.S) */ +#if defined(IMX8MM_BL33) && defined(IMX8MM_MMU) +#define WOLFBOOT_AARCH64_EL2_IDMAP +#endif + /* Fallback only: ATF sets CNTFRQ_EL0 to 8 MHz */ #define TIMER_CLK_FREQ 8000000 diff --git a/hal/imx8qm.c b/hal/imx8qm.c index 283d118055..add20fb9bf 100644 --- a/hal/imx8qm.c +++ b/hal/imx8qm.c @@ -965,59 +965,7 @@ int ext_flash_erase(uintptr_t address, int len) #ifdef IMX8QM_MMU -/* Normal = AttrIdx0 + AF + inner-shareable; Device = AttrIdx1 + AF + XN, since - * an instruction fetch from Device memory is CONSTRAINED UNPREDICTABLE. */ -#define MMU_BLOCK_NORMAL 0x0000000000000701ULL -#define MMU_BLOCK_DEVICE (0x0000000000000405ULL | (1ULL << 54) | (1ULL << 53)) - -static volatile uint64_t imx8qm_l1_table[512] __attribute__((aligned(4096))); - -#if (defined(DISK_SDCARD) || defined(DISK_EMMC)) && !defined(SDHCI_SDMA_DISABLED) -/* DMA cache maintenance for the SDMA path by address range, not the set/way walk used - * at handoff, which would cost more per transfer than the DMA saves. No-op MMU-off. */ -static void imx8qm_dcache_range(uintptr_t start, uint32_t sz, int invalidate) -{ - uintptr_t line, end; - uint64_t ctr; - unsigned int dminline; - - if (sz == 0) - return; - /* CTR_EL0.DminLine is log2 of the smallest data cache line, in words. */ - __asm__ volatile("mrs %0, ctr_el0" : "=r"(ctr)); - dminline = (unsigned int)((ctr >> 16) & 0xF); - line = (uintptr_t)4 << dminline; - - end = (start + sz + line - 1) & ~(line - 1); - start &= ~(line - 1); - __asm__ volatile("dsb sy"); - for (; start < end; start += line) { - if (invalidate) - __asm__ volatile("dc ivac, %0" :: "r"(start) : "memory"); - else - __asm__ volatile("dc civac, %0" :: "r"(start) : "memory"); - } - __asm__ volatile("dsb sy"); - __asm__ volatile("isb"); -} - -void sdhci_platform_dma_prepare(void* buf, uint32_t sz, int is_write) -{ - /* Outbound data must reach memory before the controller reads it. Clean on inbound - * too: a dirty line over the buffer could evict onto data the controller wrote. */ - imx8qm_dcache_range((uintptr_t)buf, sz, 0); - (void)is_write; -} - -void sdhci_platform_dma_complete(void* buf, uint32_t sz, int is_write) -{ - /* Inbound data landed in memory behind the cache's back; drop any stale - * lines so the CPU reads what the controller wrote. */ - if (!is_write) - imx8qm_dcache_range((uintptr_t)buf, sz, 1); -} -#endif /* (DISK_SDCARD || DISK_EMMC) && !SDHCI_SDMA_DISABLED */ - +/* SDMA cache maintenance: hal/imx_usdhc_sdhci.c */ /* The registers below are EL2 ones. ATF enters BL33 at EL2 on this SoC, but * check rather than trap silently if that ever changes. */ @@ -1038,42 +986,12 @@ static int imx8qm_mmu_on; void imx8qm_mmu_enable(void) { - unsigned long sctlr; - int i; - if (!imx8qm_mmu_at_el2()) return; - /* Low 4 GB in 1 GB blocks: DRAM (>= 0x80000000) Normal, the rest Device. */ - for (i = 0; i < 4; i++) { - uint64_t base = (uint64_t)i << 30; - imx8qm_l1_table[i] = base | - ((base >= IMX8QM_DRAM_BASE) ? MMU_BLOCK_NORMAL : MMU_BLOCK_DEVICE); - } - - /* Attr0 = 0xFF Normal write-back write-allocate, Attr1 = 0x00 Device. */ - __asm__ volatile("msr mair_el2, %0" :: "r"(0x00000000000000FFUL)); - __asm__ volatile("msr ttbr0_el2, %0" - :: "r"((uint64_t)(uintptr_t)imx8qm_l1_table)); - /* T0SZ=32 (32-bit VA), 4 KB granule, cacheable inner-shareable table - * walks. Bits 31 and 23 are RES1 for TCR_EL2 when E2H is 0. */ - __asm__ volatile("msr tcr_el2, %0" - :: "r"(0x0000000000013520UL | (1UL << 31) | (1UL << 23))); - __asm__ volatile("isb"); - __asm__ volatile("tlbi alle2"); - __asm__ volatile("dsb sy"); - - /* Drop anything an earlier stage left in the caches before turning them - * on, so no stale line surfaces once caching is live. */ - aarch64_dcache_maint(0); - __asm__ volatile("ic iallu"); - __asm__ volatile("dsb sy"); - __asm__ volatile("isb"); - - __asm__ volatile("mrs %0, sctlr_el2" : "=r"(sctlr)); - sctlr |= (1UL << 0) | (1UL << 2) | (1UL << 12); /* M | C | I */ - __asm__ volatile("msr sctlr_el2, %0" :: "r"(sctlr)); - __asm__ volatile("isb"); + /* Low 4 GB in 1 GB blocks: DRAM (>= 0x80000000) Normal, the rest Device. + * Stack-free, in src/boot_aarch64_start.S like the teardown below. */ + el2_idmap_enable(IMX8QM_DRAM_BASE, IMX8QM_DRAM_END); imx8qm_mmu_on = 1; } diff --git a/hal/imx8qm.h b/hal/imx8qm.h index 2df62ca63a..53face9f00 100644 --- a/hal/imx8qm.h +++ b/hal/imx8qm.h @@ -44,6 +44,11 @@ * symbol: an ADRP against an absolute symbol this far out exceeds its +/-4GB reach. */ #define IMX8QM_UPDATE_ADDR 0x88000000 +/* Identity MMU (el2_idmap_enable, src/boot_aarch64_start.S) */ +#ifdef IMX8QM_MMU +#define WOLFBOOT_AARCH64_EL2_IDMAP +#endif + /* Generic timer fallback if firmware left CNTFRQ_EL0 unprogrammed; the i.MX8QM system * counter runs at 8 MHz. */ #ifndef TIMER_CLK_FREQ diff --git a/hal/imx_usdhc_sdhci.c b/hal/imx_usdhc_sdhci.c index ada6e6939f..737d124d27 100644 --- a/hal/imx_usdhc_sdhci.c +++ b/hal/imx_usdhc_sdhci.c @@ -426,4 +426,40 @@ uint32_t imx_usdhc_get_clock_khz(void) { return usdhc_achieved_clk_khz; } + +#if defined(WOLFBOOT_AARCH64_EL2_IDMAP) && !defined(SDHCI_SDMA_DISABLED) +/* SDMA cache maintenance with the identity MMU on, by address range rather than + * the set/way walk used at handoff, which would cost more per transfer than the + * DMA saves. The range routines are in src/boot_aarch64_start.S. */ +#ifndef IMX_USDHC_DMA_ALIGN +#define IMX_USDHC_DMA_ALIGN 64 /* Cortex-A53/A72 D-cache line */ +#endif + +void sdhci_platform_dma_prepare(void* buf, uint32_t sz, int is_write) +{ + uintptr_t start = (uintptr_t)buf; + + if (sz == 0) + return; + /* An unaligned buffer shares lines with other data, which the invalidate + * after the transfer would discard */ + if (((start | sz) & (IMX_USDHC_DMA_ALIGN - 1)) != 0) + wolfBoot_printf("usdhc: DMA buffer %p+%u not cache-line aligned\n", + buf, (unsigned)sz); + /* Outbound data must reach memory before the controller reads it. Clean on inbound + * too: a dirty line over the buffer could evict onto data the controller wrote. */ + flush_dcache_range(start, start + sz); + (void)is_write; +} + +void sdhci_platform_dma_complete(void* buf, uint32_t sz, int is_write) +{ + uintptr_t start = (uintptr_t)buf; + + /* Inbound data landed in memory behind the cache's back; drop any stale + * lines so the CPU reads what the controller wrote. */ + if (!is_write && sz != 0) + invalidate_dcache_range(start, start + sz); +} +#endif /* WOLFBOOT_AARCH64_EL2_IDMAP && !SDHCI_SDMA_DISABLED */ #endif /* DISK_SDCARD || DISK_EMMC */ diff --git a/include/aarch64_arch.h b/include/aarch64_arch.h index eb51c1f18a..e88283c715 100644 --- a/include/aarch64_arch.h +++ b/include/aarch64_arch.h @@ -114,4 +114,12 @@ static inline void aarch64_dcache_maint(int clean) __asm__ volatile("isb"); } +#ifdef WOLFBOOT_AARCH64_EL2_IDMAP +/* src/boot_aarch64_start.S: identity MMU at EL2 and D-cache maintenance by + * address range (end exclusive). Stack-free. */ +void el2_idmap_enable(uint64_t dram_base, uint64_t dram_end); +void flush_dcache_range(uintptr_t start, uintptr_t end); +void invalidate_dcache_range(uintptr_t start, uintptr_t end); +#endif + #endif /* _AARCH64_ARCH_H_ */ diff --git a/src/boot_aarch64_start.S b/src/boot_aarch64_start.S index 2fbbfaf5a4..7c300b900d 100644 --- a/src/boot_aarch64_start.S +++ b/src/boot_aarch64_start.S @@ -1401,7 +1401,7 @@ _cm4_entry: /* Invalidate rather than clean: out of cold reset the cache RAMs hold random * tags, the previous stage already cleaned what it owned, and a stale dirty * line would later be written back over live DRAM. */ - bl imx95_dcache_invalidate_all + bl aarch64_dcache_invalidate_all dsb sy isb @@ -1507,14 +1507,16 @@ _vector_table: bl boot_entry_C // boot_entry_C never returns #endif -#if defined(TARGET_imx95_a55) && !defined(NO_MMU) && \ - defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1 +#if (defined(TARGET_imx95_a55) && !defined(NO_MMU) && \ + defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1) || \ + defined(WOLFBOOT_AARCH64_EL2_IDMAP) /* Invalidate all data cache levels by set/way, once before the D-cache is * enabled: out of cold reset the cache RAMs hold random tags and dirty bits, and - * a stale dirty line would otherwise be written back over live DRAM. */ + * a stale dirty line would otherwise be written back over live DRAM. + * Clobbers x0-x9. */ .section ".boot" -.globl imx95_dcache_invalidate_all -imx95_dcache_invalidate_all: +.globl aarch64_dcache_invalidate_all +aarch64_dcache_invalidate_all: dmb ISH mrs x0, CLIDR_EL1 ubfx w2, w0, #24, #3 /* LoC */ @@ -1552,7 +1554,112 @@ imx95_dcache_invalidate_all: 9: dsb sy isb ret +#endif + +#if defined(WOLFBOOT_AARCH64_EL2_IDMAP) +/* + * void el2_idmap_enable(uint64_t dram_base, uint64_t dram_end) + * + * Identity-map the low 4 GB at EL2 in 1 GB blocks and turn the MMU, D-cache + * and I-cache on: [dram_base, dram_end) Normal write-back, the rest Device + * and XN. Call at EL2 with the MMU off. Uses no stack; clobbers x0-x15. + * el2_flush_and_disable_mmu() undoes it before the handoff. + */ +.set EL2_IDMAP_NORMAL, 0x701 /* AttrIdx0, AF, ISH */ +.set EL2_IDMAP_DEVICE, 0x405 | (1 << 53) | (1 << 54) /* AttrIdx1, AF, XN */ + +.global el2_idmap_enable +el2_idmap_enable: + mov x15, x30 + mov x11, x0 /* DRAM base */ + mov x12, x1 /* DRAM end */ + adrp x10, el2_idmap_l1 + add x10, x10, :lo12:el2_idmap_l1 + + mov x2, x10 /* clear all 512 entries */ + mov x3, #512 +1: str xzr, [x2], #8 + subs x3, x3, #1 + b.ne 1b + + ldr x6, =EL2_IDMAP_NORMAL + ldr x7, =EL2_IDMAP_DEVICE + mov x3, #0 +2: lsl x4, x3, #30 /* block base */ + orr x5, x4, x7 + cmp x4, x11 + b.lo 3f + cmp x4, x12 + b.hs 3f + orr x5, x4, x6 +3: str x5, [x10, x3, lsl #3] + add x3, x3, #1 + cmp x3, #4 + b.lo 2b + dsb sy + + /* Attr0 = 0xFF Normal WB RW-allocate, Attr1 = 0x00 Device-nGnRnE */ + mov x2, #0xFF + msr MAIR_EL2, x2 + msr TTBR0_EL2, x10 + /* T0SZ=32, 4 KB granule, inner-shareable WB walks; bits 31, 23 RES1 */ + ldr x2, =0x80813520 + msr TCR_EL2, x2 + isb + tlbi alle2 + dsb sy + + /* Drop stale lines from earlier stages before caching is on */ + bl aarch64_dcache_invalidate_all + ic iallu + dsb sy + isb + + mrs x2, SCTLR_EL2 + orr x2, x2, #(1 << 0) /* M */ + orr x2, x2, #(1 << 2) /* C */ + orr x2, x2, #(1 << 12) /* I */ + msr SCTLR_EL2, x2 + isb + mov x30, x15 + ret +.ltorg +/* + * void invalidate_dcache_range(start, end) + * + * invalidate data cache in the range, without writing it back + * (DMA reads; flush_dcache_range() is the clean & invalidate) + * + * x0: start address + * x1: end address + */ +.global invalidate_dcache_range +invalidate_dcache_range: + mrs x3, ctr_el0 + lsr x3, x3, #16 + and x3, x3, #0xf + mov x2, #4 + lsl x2, x2, x3 /* cache line size */ + + sub x3, x2, #1 + bic x0, x0, x3 +1: dc ivac, x0 + add x0, x0, x2 + cmp x0, x1 + b.lo 1b + dsb sy + ret + +.pushsection .bss.el2_idmap_l1, "aw", %nobits +.balign 4096 +el2_idmap_l1: + .skip 4096 +.popsection +#endif /* WOLFBOOT_AARCH64_EL2_IDMAP */ + +#if defined(TARGET_imx95_a55) && !defined(NO_MMU) && \ + defined(EL2_HYPERVISOR) && EL2_HYPERVISOR == 1 /* i.MX95 identity tables (4KB granule, 40-bit VA/PA, T0SZ=24). DRAM is Normal * cacheable so wolfcrypt's NEON loads do not fault; MU2's SCMI buffer stays * Device, as the M33 writes it outside this cluster's coherency. 0-0x7FFFFFFF From 586e5535d1b19f89d8cf9f7a929b044ba8b671f9 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 5 Nov 2026 16:52:59 +0900 Subject: [PATCH 09/10] imx8mm: move macros to headers, use the HAL UART in the test-app --- hal/imx8mm.c | 17 ++++------ hal/imx8mm.h | 2 ++ hal/imx_usdhc_sdhci.c | 38 --------------------- hal/imx_usdhc_sdhci.h | 41 +++++++++++++++++++++++ test-app/app_imx8mm.c | 69 +++++++++------------------------------ tools/unit-tests/Makefile | 9 ++--- 6 files changed, 70 insertions(+), 106 deletions(-) diff --git a/hal/imx8mm.c b/hal/imx8mm.c index edc355b7a8..35be7945fd 100644 --- a/hal/imx8mm.c +++ b/hal/imx8mm.c @@ -89,23 +89,18 @@ void ext_flash_unlock(void) #ifdef DEBUG_UART -/* UART TX only; clocks, pins and baud rate are set by the earlier stages */ -#define IMX_UART_REG(off) (*(volatile uint32_t *)(IMX8MM_UART_BASE + (off))) -#define IMX_UART_UTXD IMX_UART_REG(IMX8MM_UART_UTXD) -#define IMX_UART_UCR1 IMX_UART_REG(IMX8MM_UART_UCR1) -#define IMX_UART_USR2 IMX_UART_REG(IMX8MM_UART_USR2) -#define IMX_UART_UTS IMX_UART_REG(IMX8MM_UART_UTS) - +/* UART TX only; clocks, pins and baud rate are set by the earlier stages. + * Also linked into the test-app. */ void uart_init(void) { - IMX_UART_UCR1 |= UCR1_UARTEN; + IMX8MM_UART_REG(IMX8MM_UART_UCR1) |= UCR1_UARTEN; } static void uart_putc(char c) { - while (IMX_UART_UTS & UTS_TXFULL) + while (IMX8MM_UART_REG(IMX8MM_UART_UTS) & UTS_TXFULL) ; - IMX_UART_UTXD = (uint32_t)(uint8_t)c; + IMX8MM_UART_REG(IMX8MM_UART_UTXD) = (uint32_t)(uint8_t)c; } void uart_write(const char* buf, unsigned int sz) @@ -117,7 +112,7 @@ void uart_write(const char* buf, unsigned int sz) uart_putc(buf[i]); } /* Drain the FIFO before handing over */ - while (!(IMX_UART_USR2 & USR2_TXDC)) + while (!(IMX8MM_UART_REG(IMX8MM_UART_USR2) & USR2_TXDC)) ; } #endif /* DEBUG_UART */ diff --git a/hal/imx8mm.h b/hal/imx8mm.h index 8715710b42..025304aa63 100644 --- a/hal/imx8mm.h +++ b/hal/imx8mm.h @@ -57,6 +57,8 @@ #define UCR1_UARTEN (1U << 0) #define USR2_TXDC (1U << 3) /* Transmission complete */ #define UTS_TXFULL (1U << 4) /* TxFIFO full */ +#define IMX8MM_UART_REG(off) \ + (*(volatile uint32_t *)(uintptr_t)(IMX8MM_UART_BASE + (off))) /* Clock controller (CCM) */ #define IMX8MM_CCM_BASE 0x30380000 diff --git a/hal/imx_usdhc_sdhci.c b/hal/imx_usdhc_sdhci.c index 737d124d27..77a2ba9278 100644 --- a/hal/imx_usdhc_sdhci.c +++ b/hal/imx_usdhc_sdhci.c @@ -55,40 +55,6 @@ 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; } -#ifndef CADENCE_SRS_OFFSET -#define CADENCE_SRS_OFFSET 0x200 -#endif - -#ifndef USDHC_RESET_TIMEOUT_US -#define USDHC_RESET_TIMEOUT_US 1000000 -#endif -#ifndef USDHC_INIT_TIMEOUT_US -#define USDHC_INIT_TIMEOUT_US 100000 -#endif -#ifndef USDHC_CLK_STABLE_TIMEOUT_US -#define USDHC_CLK_STABLE_TIMEOUT_US 100000 -#endif - -/* Standard-SDHCI offsets, as seen after the Cadence SRS base is removed. */ -#define STD_BLK 0x04 -#define STD_CMD 0x0C -#define STD_PRES_STATE 0x24 -#define STD_HOST_CTRL1 0x28 -#define STD_CLOCK_CTRL 0x2C -#define STD_INT_STATUS 0x30 -#define STD_INT_STATUS_EN 0x34 -#define STD_INT_SIGNAL_EN 0x38 -#define STD_HOST_CTRL2 0x3C -#define STD_CAPS1 0x40 -#define STD_CAPS2 0x44 -#define STD_MAX_CURRENT 0x48 -#define STD_ADMA_ADDR_LO 0x58 -#define STD_ADMA_ADDR_HI 0x5C - -/* uSDHC-only error bits with no standard-SDHCI position. */ -#define USDHC_INT_DMAE (1U << 28) -#define USDHC_INT_TNE (1U << 26) - /* Fields the driver reads back that uSDHC does not implement. */ static uint32_t srs10_shadow; /* bus power / bus voltage / high speed */ static uint32_t srs15_shadow; /* host control 2 */ @@ -431,10 +397,6 @@ uint32_t imx_usdhc_get_clock_khz(void) /* SDMA cache maintenance with the identity MMU on, by address range rather than * the set/way walk used at handoff, which would cost more per transfer than the * DMA saves. The range routines are in src/boot_aarch64_start.S. */ -#ifndef IMX_USDHC_DMA_ALIGN -#define IMX_USDHC_DMA_ALIGN 64 /* Cortex-A53/A72 D-cache line */ -#endif - void sdhci_platform_dma_prepare(void* buf, uint32_t sz, int is_write) { uintptr_t start = (uintptr_t)buf; diff --git a/hal/imx_usdhc_sdhci.h b/hal/imx_usdhc_sdhci.h index ac4b6a0cfc..ce48ac570b 100644 --- a/hal/imx_usdhc_sdhci.h +++ b/hal/imx_usdhc_sdhci.h @@ -98,6 +98,47 @@ /* VEND_SPEC */ #define USDHC_VEND_SPEC_VSELECT (1 << 1) /* 1.8V signaling */ +/* Shim internals (hal/imx_usdhc_sdhci.c). CADENCE_SRS_OFFSET is not exported + * by include/sdhci.h; every in-tree SDHCI platform defines it locally. */ +#ifndef CADENCE_SRS_OFFSET +#define CADENCE_SRS_OFFSET 0x200 +#endif + +#ifndef USDHC_RESET_TIMEOUT_US +#define USDHC_RESET_TIMEOUT_US 1000000 +#endif +#ifndef USDHC_INIT_TIMEOUT_US +#define USDHC_INIT_TIMEOUT_US 100000 +#endif +#ifndef USDHC_CLK_STABLE_TIMEOUT_US +#define USDHC_CLK_STABLE_TIMEOUT_US 100000 +#endif + +/* Standard-SDHCI offsets, as seen after the Cadence SRS base is removed. */ +#define STD_BLK 0x04 +#define STD_CMD 0x0C +#define STD_PRES_STATE 0x24 +#define STD_HOST_CTRL1 0x28 +#define STD_CLOCK_CTRL 0x2C +#define STD_INT_STATUS 0x30 +#define STD_INT_STATUS_EN 0x34 +#define STD_INT_SIGNAL_EN 0x38 +#define STD_HOST_CTRL2 0x3C +#define STD_CAPS1 0x40 +#define STD_CAPS2 0x44 +#define STD_MAX_CURRENT 0x48 +#define STD_ADMA_ADDR_LO 0x58 +#define STD_ADMA_ADDR_HI 0x5C + +/* uSDHC-only error bits with no standard-SDHCI position. */ +#define USDHC_INT_DMAE (1U << 28) +#define USDHC_INT_TNE (1U << 26) + +/* SDMA buffers should be aligned to this (Cortex-A53/A72 D-cache line) */ +#ifndef IMX_USDHC_DMA_ALIGN +#define IMX_USDHC_DMA_ALIGN 64 +#endif + #ifndef __ASSEMBLER__ #include diff --git a/test-app/app_imx8mm.c b/test-app/app_imx8mm.c index e0c193fca3..289db3e434 100644 --- a/test-app/app_imx8mm.c +++ b/test-app/app_imx8mm.c @@ -24,79 +24,42 @@ #include #include "wolfboot/wolfboot.h" +#include "printf.h" #ifdef TARGET_imx8mm #include "hal/imx8mm.h" +#include "aarch64_arch.h" -/* UART2 console, already set up by the earlier boot stages */ -#define UART_REG(off) \ - (*(volatile uint32_t*)(uintptr_t)(IMX8MM_UART_BASE + (off))) - -/* Bounded wait for TX FIFO space */ -#define UART_TX_SPIN_MAX 1000000 - -static void uart_putc(char c) -{ - unsigned int spin = 0; - - while ((UART_REG(IMX8MM_UART_UTS) & UTS_TXFULL) != 0) { - if (++spin > UART_TX_SPIN_MAX) - return; - } - UART_REG(IMX8MM_UART_UTXD) = (uint32_t)(uint8_t)c; -} - -static void uart_puts(const char* s) -{ - while (*s != '\0') { - if (*s == '\n') - uart_putc('\r'); - uart_putc(*s++); - } -} - -static void uart_puthex(uint64_t v) -{ - int i; - uart_puts("0x"); - for (i = 60; i >= 0; i -= 4) - uart_putc("0123456789abcdef"[(v >> i) & 0xF]); -} +/* Console: uart_write() in hal/imx8mm.c, linked into the test-app + * (DEBUG_UART=1, the config default) */ /* FDT magic 0xd00dfeed read little-endian */ #define FDT_MAGIC_LE 0xedfe0dd0u /* dtb: x0 from wolfBoot (arm64 boot protocol) */ void __attribute__((section(".boot"))) main(uint64_t dtb) { - uint64_t el; + uint32_t magic; - __asm__ volatile("mrs %0, CurrentEL" : "=r"(el)); - el >>= 2; + wolfBoot_printf("\n*** wolfBoot i.MX 8M Mini payload: verified + booted ***\n"); + wolfBoot_printf(" CurrentEL = EL%d\n", (int)current_el()); + wolfBoot_printf(" DTB (x0) = 0x%08x%08x\n", + (uint32_t)(dtb >> 32), (uint32_t)dtb); - uart_puts("\n*** wolfBoot i.MX 8M Mini payload: verified + booted ***\n"); - - uart_puts(" CurrentEL = EL"); - uart_putc('0' + (char)(el & 3)); - uart_puts("\n"); - - uart_puts(" DTB (x0) = "); - uart_puthex(dtb); - uart_puts("\n DTB magic = "); /* MMU off: check range and alignment before reading */ if (dtb < IMX8MM_DRAM_BASE || dtb > (IMX8MM_DRAM_END - 4) || (dtb & 0x3) != 0) { - uart_puts("(pointer not in DRAM or misaligned)\n"); - } - else if (*((volatile uint32_t*)(uintptr_t)dtb) == FDT_MAGIC_LE) { - uart_puts("OK (0xd00dfeed)\n"); + wolfBoot_printf(" DTB magic = (pointer not in DRAM or misaligned)\n"); } else { - uart_puthex(*((volatile uint32_t*)(uintptr_t)dtb)); - uart_puts(" (BAD)\n"); + magic = *((volatile uint32_t*)(uintptr_t)dtb); + if (magic == FDT_MAGIC_LE) + wolfBoot_printf(" DTB magic = OK (0xd00dfeed)\n"); + else + wolfBoot_printf(" DTB magic = 0x%08x (BAD)\n", magic); } - uart_puts("*** payload complete: parked ***\n"); + wolfBoot_printf("*** payload complete: parked ***\n"); /* Wait for reboot */ while(1) diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index cf10146660..494a47ffab 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -1676,9 +1676,10 @@ versal_host.c: ../../hal/versal.c versal_host.h aarch64_arch_host.h # unit-imx8qm-usdhc-shim drives the real uSDHC-to-SDHCI translation from # hal/imx_usdhc_sdhci.c (shared by imx8qm and imx8mm) against an emulated -# register file, built with the imx8qm header. The shim block is extracted -# verbatim, without the includes and MMIO helpers above it, so the test -# substitutes its own rd32/wr32 and timer. +# register file, built with the imx8qm header (which brings the shim's macros +# in hal/imx_usdhc_sdhci.h). The shim block is extracted verbatim, without the +# includes and MMIO helpers above it, so the test substitutes its own rd32/wr32 +# and timer. # The trailing #endif of the enclosing "#if defined(DISK_SDCARD) ..." is # dropped, since its opening #if is outside the extracted range. # @@ -1690,7 +1691,7 @@ versal_host.c: ../../hal/versal.c versal_host.h aarch64_arch_host.h # next iteration's status and only the last item would fail the build; and it # matches the call syntax, since a bare name still matches a renamed one. imx8qm_usdhc_extract.h: ../../hal/imx_usdhc_sdhci.c - sed -n '/^#ifndef CADENCE_SRS_OFFSET$$/,/^#endif \/\* DISK_SDCARD || DISK_EMMC \*\/$$/p' $< \ + sed -n '/^\/\* Fields the driver reads back that uSDHC does not implement. \*\/$$/,/^#endif \/\* DISK_SDCARD || DISK_EMMC \*\/$$/p' $< \ | sed '/^#endif \/\* DISK_SDCARD || DISK_EMMC \*\/$$/d' > $@ test -s $@ @! grep -q '__asm__' $@ || { echo "$@: unexpected asm in the extracted shim"; false; } From bfaff432a6c86536a00737451542c3c319d970f6 Mon Sep 17 00:00:00 2001 From: Hideki Miyazaki Date: Thu, 5 Nov 2026 17:30:51 +0900 Subject: [PATCH 10/10] imx8mm: test-app: parse the DTB with fdt.c --- test-app/Makefile | 5 ++++ test-app/app_imx8mm.c | 65 ++++++++++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/test-app/Makefile b/test-app/Makefile index 1da5f5429e..14f15b4d27 100644 --- a/test-app/Makefile +++ b/test-app/Makefile @@ -1459,6 +1459,11 @@ ifeq ($(TARGET),zynq) LSCRIPT_TEMPLATE:=AARCH64.ld endif +ifeq ($(TARGET),imx8mm) + # Parses the DTB passed in x0 + APP_OBJS+=../src/fdt.o +endif + ifeq ($(TARGET),pic32ck) APP_OBJS+=../hal/pic32c.o endif diff --git a/test-app/app_imx8mm.c b/test-app/app_imx8mm.c index 289db3e434..9c7f42a3f9 100644 --- a/test-app/app_imx8mm.c +++ b/test-app/app_imx8mm.c @@ -21,6 +21,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ +#include #include #include "wolfboot/wolfboot.h" @@ -30,35 +31,61 @@ #include "hal/imx8mm.h" #include "aarch64_arch.h" +#include "fdt.h" /* Console: uart_write() in hal/imx8mm.c, linked into the test-app - * (DEBUG_UART=1, the config default) */ + * (DEBUG_UART=1, the config default). DTB parser: src/fdt.c. */ -/* FDT magic 0xd00dfeed read little-endian */ -#define FDT_MAGIC_LE 0xedfe0dd0u +/* Print a string property, if present and NUL-terminated */ +static void print_prop(const fdt_ctx* ctx, const char* path, + const char* name, const char* label) +{ + const char* s = NULL; + int len = 0; + int off = fdt_path_offset(ctx, path); -/* dtb: x0 from wolfBoot (arm64 boot protocol) */ -void __attribute__((section(".boot"))) main(uint64_t dtb) { - uint32_t magic; + if (off >= 0) + s = (const char*)fdt_getprop(ctx, off, name, &len); + if (s != NULL && len > 0 && s[len - 1] == '\0') + wolfBoot_printf(" %s = %s\n", label, s); + else + wolfBoot_printf(" %s = (none)\n", label); +} - wolfBoot_printf("\n*** wolfBoot i.MX 8M Mini payload: verified + booted ***\n"); - wolfBoot_printf(" CurrentEL = EL%d\n", (int)current_el()); - wolfBoot_printf(" DTB (x0) = 0x%08x%08x\n", - (uint32_t)(dtb >> 32), (uint32_t)dtb); +/* Parse the DTB with src/fdt.c */ +static void check_dtb(uint64_t dtb) +{ + fdt_ctx ctx; + uint32_t size = 0; + int rc; /* MMU off: check range and alignment before reading */ - if (dtb < IMX8MM_DRAM_BASE || dtb > (IMX8MM_DRAM_END - 4) || - (dtb & 0x3) != 0) { - wolfBoot_printf(" DTB magic = (pointer not in DRAM or misaligned)\n"); + if (dtb < IMX8MM_DRAM_BASE || + dtb > (IMX8MM_DRAM_END - FDT_HEADER_SIZE) || (dtb & 0x3) != 0) { + wolfBoot_printf(" DTB = pointer not in DRAM or misaligned\n"); + return; } - else { - magic = *((volatile uint32_t*)(uintptr_t)dtb); - if (magic == FDT_MAGIC_LE) - wolfBoot_printf(" DTB magic = OK (0xd00dfeed)\n"); - else - wolfBoot_printf(" DTB magic = 0x%08x (BAD)\n", magic); + rc = fdt_peek_size((const void*)(uintptr_t)dtb, FDT_HEADER_SIZE, &size); + if (rc == 0 && size > (IMX8MM_DRAM_END - dtb)) + rc = -FDT_ERR_BADLAYOUT; + if (rc == 0) + rc = fdt_open(&ctx, (void*)(uintptr_t)dtb, size); + if (rc != 0) { + wolfBoot_printf(" DTB = BAD (%d)\n", rc); + return; } + wolfBoot_printf(" DTB = OK, %u bytes\n", (unsigned)fdt_size(&ctx)); + print_prop(&ctx, "/", "model", "model "); + print_prop(&ctx, "/chosen", "bootargs", "bootargs "); +} +/* dtb: x0 from wolfBoot (arm64 boot protocol) */ +void __attribute__((section(".boot"))) main(uint64_t dtb) { + wolfBoot_printf("\n*** wolfBoot i.MX 8M Mini payload: verified + booted ***\n"); + wolfBoot_printf(" CurrentEL = EL%d\n", (int)current_el()); + wolfBoot_printf(" DTB (x0) = 0x%08x%08x\n", + (uint32_t)(dtb >> 32), (uint32_t)dtb); + check_dtb(dtb); wolfBoot_printf("*** payload complete: parked ***\n"); /* Wait for reboot */