diff --git a/.github/workflows/test-configs.yml b/.github/workflows/test-configs.yml index 452ebc6a5f..9ff4eabde6 100644 --- a/.github/workflows/test-configs.yml +++ b/.github/workflows/test-configs.yml @@ -624,6 +624,21 @@ jobs: config-file: ./config/examples/polarfire_mpfs250_m.config pre-build: sh tools/ci/gen_mpfs_libero_stub.sh tools/ci/mpfs_libero_stub make-args: LIBERO_FPGA_CONFIG_DIR=tools/ci/mpfs_libero_stub + # The Icicle Kit System Controller probe (no DDR, no image source). + microchip_mpfs250_icicle_probe_test: + uses: ./.github/workflows/test-build-riscv.yml + with: + arch: riscv64 + config-file: ./config/examples/polarfire_mpfs250_icicle.config + # The Icicle Kit variant: per-board BCLK phase, fabric SD/eMMC demux select + # and the signed FIT read from a file on the card's FAT32 partition. + microchip_mpfs250_icicle_m_test: + uses: ./.github/workflows/test-build-riscv.yml + with: + arch: riscv64 + config-file: ./config/examples/polarfire_mpfs250_icicle_m.config + pre-build: sh tools/ci/gen_mpfs_libero_stub.sh tools/ci/mpfs_libero_stub + make-args: LIBERO_FPGA_CONFIG_DIR=tools/ci/mpfs_libero_stub # The HSS replacement booting from the other two stores: eMMC (same SDHCI # driver as SD, different pads) and QSPI into DDR (src/update_ram.c rather # than src/update_disk.c). diff --git a/config/examples/polarfire_mpfs250_icicle.config b/config/examples/polarfire_mpfs250_icicle.config new file mode 100644 index 0000000000..c810bfcbab --- /dev/null +++ b/config/examples/polarfire_mpfs250_icicle.config @@ -0,0 +1,74 @@ +# PolarFire SoC MPFS250T Icicle Kit, M-mode System Controller probe +# +# The MPFS-ICICLE-KIT carries a non-secure die (MPFS250T_ES / MPFS250T) with +# no Athena/TeraFire user crypto co-processor, where the Video Kit configs in +# this directory assume an MPFS250TS secure die. This configuration exists to +# establish which System Controller services that die actually serves: the +# sNVM page survey, the device serial number, the SRAM-PUF and the TRNG. +# +# It carries no image source on purpose. wolfBoot runs hal_init(), which is +# where the survey happens, and then has nothing to boot - that is expected. +# MPFS_ATHENA is deliberately absent: there is no co-processor to drive. +# +# The service probe (MPFS_SC_PROBE: serial number, TRNG nonce, PUF emulation) +# is on by default in every M-mode build. The sNVM page survey is a make +# argument, not a config setting: +# cp config/examples/polarfire_mpfs250_icicle.config .config +# make CROSS_COMPILE=riscv64-unknown-elf- MPFS_SNVM_DUMP=1 \ +# MPFS_SNVM_DUMP_FIRST=0 MPFS_SNVM_DUMP_COUNT=221 +# +# Both need RISCV_MMODE=1 (options.mk): under HSS the System Controller +# mailbox belongs to HSS, so an S-mode build is refused. +# +# No DDR: LIBERO_FPGA_CONFIG_DIR is deliberately unset, so MPFS_DDR_INIT is +# off and hal/mpfs250_ddr.o is not linked. The Icicle's own Libero export is +# a later step; the System Controller does not need DDR. + +ARCH?=RISCV64 +TARGET?=mpfs250 +SIGN?=ECC384 +HASH?=SHA384 +IMAGE_HEADER_SIZE=512 +WOLFBOOT_VERSION?=1 +ARMORED?=0 +DEBUG?=0 +DEBUG_SYMBOLS?=1 +DEBUG_UART?=1 +VTOR?=1 +NO_XIP?=1 +NVM_FLASH_WRITEONCE?=0 +UART_FLASH?=0 +V?=0 +NO_MPU?=1 +RAM_CODE?=0 +SPMATH?=0 +SPMATHALL?=1 +DUALBANK_SWAP?=0 +PKA?=0 +ENCRYPT=0 +WOLFTPM?=0 +ELF?=1 +STRIP_ELF?=1 +OPTIMIZATION_LEVEL=1 +RISCV_MMODE?=1 +STACK_SIZE_PER_HART=0 +NO_ASM?=1 + +# The Video Kit's flash hangs off the System Controller QSPI and its configs +# set EXT_FLASH=1 with -DMPFS_SC_SPI. The Icicle's is on the MSS QSPI +# controller instead, so neither applies here, and the probe needs no flash. +EXT_FLASH?=0 +SPI_FLASH?=0 +DISK_SDCARD?=0 +DISK_EMMC?=0 + +# wolfBoot executes from the L2 scratchpad, as on the Video Kit: the four +# pinned scratchpad ways the startup in src/boot_riscv_start.S sets up. +WOLFBOOT_ORIGIN?=0x0A000000 +WOLFBOOT_LOAD_ADDRESS?=0x0A020200 +WOLFBOOT_SECTOR_SIZE?=0x10000 +WOLFBOOT_PARTITION_SIZE?=0x1FE0000 +WOLFBOOT_PARTITION_BOOT_ADDRESS?=0x20000 +WOLFBOOT_PARTITION_UPDATE_ADDRESS?=0x2000000 +WOLFBOOT_PARTITION_SWAP_ADDRESS?=0x4000000 +CFLAGS_EXTRA+=-DWOLFBOOT_SHA_BLOCK_SIZE=4096 diff --git a/config/examples/polarfire_mpfs250_icicle_m.config b/config/examples/polarfire_mpfs250_icicle_m.config new file mode 100644 index 0000000000..e672551a86 --- /dev/null +++ b/config/examples/polarfire_mpfs250_icicle_m.config @@ -0,0 +1,178 @@ +# PolarFire SoC MPFS250T Icicle Kit, M-Mode (Machine Mode) with LPDDR4 + SD card +# +# The Icicle Kit variant of polarfire_mpfs250_m.config: the same standalone +# wolfBoot replacing HSS, with the three things that differ on this board: +# - the DDR PLL BCLK phase HSS measures on the Icicle is 4 (Video Kit: 3) +# - the SD/eMMC demux select is a fabric register at 0x4FFFFF00 +# - the Libero design config must be an Icicle export (ICICLE_MSS), e.g. +# an HSS build/boards/mpfs-icicle-kit/fpga_design_config regenerated +# from the Icicle XML, not the Video Kit's +# The card keeps the stock HSS/U-Boot layout: a FAT32 first partition with +# the Yocto fitImage, so the signed image is read from a file on it rather +# than from the raw partition start. +# +# Flash via mpfsBootmodeProgrammer (bootmode 1): +# java -jar mpfsBootmodeProgrammer.jar --bootmode 1 --die MPFS250T \ +# --package FCVG484 --workdir $PWD wolfboot.elf + +ARCH?=RISCV64 +TARGET?=mpfs250 +SIGN?=ECC384 +HASH?=SHA384 +IMAGE_HEADER_SIZE=512 +WOLFBOOT_VERSION?=1 +ARMORED?=0 +DEBUG?=0 +DEBUG_SYMBOLS?=1 +DEBUG_UART?=1 +VTOR?=1 +EXT_FLASH?=0 +SPI_FLASH?=0 +NO_XIP?=1 +NVM_FLASH_WRITEONCE?=0 +UART_FLASH?=0 +V?=0 +NO_MPU?=1 +RAM_CODE?=0 +SPMATH?=0 +SPMATHALL?=1 +DUALBANK_SWAP?=0 +PKA?=0 +ENCRYPT=0 +WOLFTPM?=0 +ELF?=1 +#DEBUG_ELF?=1 + +OPTIMIZATION_LEVEL=1 + +# M-Mode configuration: runs on E51 from L2 SRAM +RISCV_MMODE?=1 + +# Stack size per hart (L2 SRAM constraints). Single source of truth: this +# Makefile var feeds both the startup asm (-DSTACK_SIZE_PER_HART) and the +# linker script (@STACK_SIZE_PER_HART@ in mpfs250-m.ld). +STACK_SIZE_PER_HART=4096 + +# E51 core lacks RISC-V crypto extensions (Zknh), use portable C +# RISC-V SHA256/384/512 assembly (wolfcrypt port/riscv) enabled by default. +# Verified on the MPFS250 E51 (rv64imac); requires wolfSSL with the RISC-V +# unaligned-access fix (PR #10530, present in the lib/wolfssl submodule). +# The E51 handles unaligned access so the default (faster) asm path is used; +# add -DWOLFSSL_RISCV_ASM_NO_UNALIGNED for RISC-V cores that trap on it. +NO_ASM?=0 + +# Enable LPDDR4 init in hal_init() by pointing at the Libero/HSS-generated +# fpga_design_config directory for this board. The directory must contain +# fpga_design_config.h and its sub-headers (memory_map/, ddr/, clocks/, ...). +# Typical sources: +# - HSS Icicle build: +# /build/boards/mpfs-icicle-kit/fpga_design_config +# (check fpga_design_config.h says ICICLE_MSS: the HSS generator only +# reruns when the XML changes, so a directory left from a Video Kit +# build keeps the Video Kit's SEG and IOMUX values) +# - Libero MSS Configurator export for the design. +# The -I path is added and -DMPFS_DDR_INIT is set automatically when this is +# non-empty (see arch.mk). Override on the command line for one-off builds: +# make LIBERO_FPGA_CONFIG_DIR=/path/to/fpga_design_config +LIBERO_FPGA_CONFIG_DIR?= + +# DDR PLL BCLK/SCLK phase. HSS measures it with its software sweep; the +# Icicle Kit trains to 4, the Video Kit to 3 (the driver default). With the +# wrong value TIP training passes and no DRAM write ever completes. +MPFS_DDR_BCLK_PHASE=4 + +# SD/eMMC demux: on the Icicle Kit the select line is a register in the +# fabric (HSS CONFIG_SERVICE_MMC_FABRIC_SD_EMMC_DEMUX_SELECT_ADDRESS). +MPFS_SD_EMMC_DEMUX_ADDR=0x4FFFFF00 + +# Boot Linux: drop to S-mode after wolfBoot verifies kernel +CFLAGS_EXTRA+=-DWOLFBOOT_MMODE_SMODE_BOOT + +# SD card storage for kernel image (no QSPI flash) +DISK_SDCARD?=1 +DISK_EMMC?=0 + +# wolfBoot in L2 SRAM: all 4 scratchpad ways (0x0A000000 - 0x0A07FFFF) +WOLFBOOT_ORIGIN?=0x0A000000 +WOLFBOOT_L2SCRATCH_SIZE?=512k + +# Boot-hart stack, sized for ML-DSA-87 verify on top of the two +# IMAGE_HEADER_SIZE buffers update_disk.c puts on the stack. ECC384 needs less. +STACK_SIZE?=64k + +# 4KB sector size (SD card flow is partition-based, not flash-erase-based) +WOLFBOOT_SECTOR_SIZE?=0x1000 + +# Scratch address where the signed FIT image is staged before signature +# verification + FIT parse. 0x82000000 is 32 MB into the cached DDR window +# at 0x80000000, which is 1 GB wide - 0xC0000000 up is the non-cached alias +# onto the same DDR, so staging must stay below it. It is clear of the +# kernel destination below and 128 MB below the DTB region, which is the +# headroom the staged-image bound further down depends on. +# After the FIT is parsed: +# kernel is copied to 0x80200000 (FIT-internal "load") +# DTB is copied to WOLFBOOT_LOAD_DTS_ADDRESS (0x8A000000) +# Layout: +# 0x80200000 - 0x814FFFFF : kernel (~19 MB after parse) +# 0x82000000 - 0x832FFFFF : FIT scratch (~19 MB - overwritten on next boot) +# 0x8A000000 - 0x8A004FFF : DTB +WOLFBOOT_LOAD_ADDRESS?=0x82000000 + +# DTB load address in DDR +WOLFBOOT_LOAD_DTS_ADDRESS?=0x8A000000 + +# Bound the UNAUTHENTICATED FIT image length before it drives the disk read +# into the DDR staging region (required by src/update_disk.c). The FIT is +# staged at WOLFBOOT_LOAD_ADDRESS (0x82000000); the DTB load region begins +# 128 MB above at 0x8A000000, so cap the staged image at 128 MB so an +# attacker-declared fw_size cannot overrun the DTB region before the +# signature check runs. +WOLFBOOT_RAMBOOT_MAX_SIZE=0x08000000 + +# The stock Icicle card (HSS/U-Boot layout): index 0 is the FAT32 BOOT +# partition carrying fitImage and boot.scr, index 1 the HSS payload, index 2 +# the rootfs. The signed FIT is a file on the FAT32 partition, so HSS and +# U-Boot keep booting from the same card: +# ./tools/keytools/sign --ecc384 --sha384 fitImage \ +# wolfboot_signing_private_key.der 1 +# cp fitImage_v1_signed.bin /wolfboot/ +WOLFBOOT_NO_PARTITIONS=1 +CFLAGS_EXTRA+=-DBOOT_PART_A=0 +CFLAGS_EXTRA+=-DBOOT_PART_B=0 +DISK_FS=fat32 +CFLAGS_EXTRA+=-DBOOT_FILE_A='"/wolfboot/fitImage_v1_signed.bin"' +CFLAGS_EXTRA+=-DBOOT_FILE_B='"/wolfboot/fitImage_v1_signed.bin"' + +# The Yocto fitImage carries a gzip-compressed kernel and an initramfs. +GZIP=1 +FIT_RAMDISK=1 + +# The Icicle base design's CAN controller hangs the stock DTB's CAN driver. +CFLAGS_EXTRA+=-DLINUX_BOOTARGS='"earlycon=sbi initcall_blacklist=mpfs_can_driver_init"' + +# Speed up disk partition read (512KB chunks - max DMA size) +CFLAGS_EXTRA+=-DDISK_BLOCK_SIZE=0x80000 + +# Card detect is not wired to the SDHCI controller's CI/CDPL inputs on the +# kits, so assume a card is present. +CFLAGS_EXTRA+=-DSDHCI_FORCE_CARD_DETECT + +# Optional encryption (kernel signed+encrypted with AES-256) +#CUSTOM_ENCRYPT_KEY=1 +#ENCRYPT=1 +#ENCRYPT_WITH_AES256=1 +#OBJS_EXTRA=src/my_custom_encrypt_key.o + +# Used by test-application/ELF wrapper +WOLFBOOT_PARTITION_BOOT_ADDRESS=0x80200000 +WOLFBOOT_PARTITION_SIZE=0x4000000 + +# Debug options (useful for initial M-mode + DDR bring-up) +CFLAGS_EXTRA+=-DDEBUG_BOOT +#CFLAGS_EXTRA+=-DDEBUG_SDHCI +#CFLAGS_EXTRA+=-DDEBUG_DISK +#CFLAGS_EXTRA+=-DDISK_TEST +# Verbose register-level DDR tracing, the post-training register dump and +# the DDR pattern test (development aids). +#CFLAGS_EXTRA+=-DDEBUG_DDR +#MPFS_DDR_MEMTEST=1 diff --git a/docs/PolarFire.md b/docs/PolarFire.md index a8ca7c3c6f..8dce082334 100644 --- a/docs/PolarFire.md +++ b/docs/PolarFire.md @@ -48,7 +48,7 @@ Path 1 needs no FPGA design files and never writes the device, so it is the righ |---|---| | RISC-V bare-metal toolchain | SoftConsole (`/opt/Microchip/SoftConsole-v2022.2-RISC-V-747/riscv-unknown-elf-gcc/bin`), or any `riscv64-unknown-elf-gcc` | | `mpfsBootmodeProgrammer.jar` + a JRE | SoftConsole (`extras/mpfs/`, `eclipse/jre/bin/java`); paths 2 and 3 only | -| FlashPro programmer | on-board on the Video Kit, over the same USB cable as the consoles | +| FlashPro programmer | on-board on the Video Kit and the Icicle Kit (close J9 on the Icicle), over the same USB cable as the consoles | | `dtc`, `mkimage` | `device-tree-compiler`, `u-boot-tools` | | `hss-payload-generator` | `tools/hss-payload-generator` in the [hart-software-services](https://github.com/polarfire-soc/hart-software-services) tree; path 1 only | | `fpga_design_config/` for the board | an HSS build (`build/boards//fpga_design_config`) or a Libero MSS Configurator export; paths 2 and 3 only | @@ -145,7 +145,7 @@ $SC_INSTALL_DIR/eclipse/jre/bin/java -jar \ --bootmode 1 --die MPFS250T --package FCG1152 --workdir $PWD wolfboot.elf ``` -`--bootmode 1` is non-secure boot from eNVM: the System Controller releases the harts at the eNVM reset vector. The die and package must match the part; `--die` defaults to `MPFS250T_ES` and `--package` to `FCVG484`, neither of which is the Video Kit. +`--bootmode 1` is non-secure boot from eNVM: the System Controller releases the harts at the eNVM reset vector. The die and package must match the part; `--die` defaults to `MPFS250T_ES` and `--package` to `FCVG484`, neither of which is the Video Kit (the Icicle Kit is `--die MPFS250T --package FCVG484`). #### Boot @@ -200,6 +200,9 @@ The programmer's `--die` list covers `MPFS025T` through `MPFS460T` and their `_E | banner, then silence | the FIT loaded but Linux has no console: the dtb's own `/chosen/bootargs` wins unless `LINUX_BOOTARGS` is set at build time. See [bootargs](compile.md#linux-kernel-command-line-bootargs) | | banner, then silence, and no `[SBI]` output at all | the image was built with `make CFLAGS_EXTRA=...`, which overrode the config's `-DWOLFBOOT_MMODE_SMODE_BOOT`. Check with `nm wolfboot.elf | grep sbi`; an M-mode build that boots an S-mode OS must have those symbols | | DDR training stops or reports a failure | build with `CFLAGS_EXTRA+=-DDEBUG_DDR` and compare against the board's Libero settings | +| DDR trains but the OS misbehaves later | build with `MPFS_DDR_MEMTEST=1` for a CPU pattern test over the mapped DDR windows before the OS is loaded | +| TIP training passes (`train_stat=0x1F`) but `MTC WRCALIB TIMEOUT` on every retry, or a PDMA fill of DDR hangs | the DDR PLL BCLK/SCLK phase is another board's. Dump the PHY under HSS (tinyCLI `debug hexdump 0x20007000 0x1000`) and read `PLL_DIV_0_1_MAIN`/`PLL_DIV_2_3_MAIN` VCO phase selects, then set `MPFS_DDR_BCLK_PHASE` to match | +| Linux boots but userspace segfaults or the kernel reports an illegal instruction at valid code, more often with more CPUs | DRAM content corruption from a mode-register mismatch: the mode registers must be written masked so the controller supplies them from its CFG_* values (the driver does this now); check `MR13` data mask against `CFG_DATA_MASK` if it recurs | ### Next @@ -219,7 +222,7 @@ The programmer's `--die` list covers `MPFS025T` through `MPFS460T` and their `_E ## Boot configurations -Nine ready-to-use config templates cover all supported boot mode / storage / memory combinations: +Ten ready-to-use config templates cover all supported boot mode / storage / memory combinations: | Configuration | Config File | Boot Mode | Storage | Memory | HSS | |---------------|-------------|-----------|---------|--------|-----| @@ -231,6 +234,7 @@ Nine ready-to-use config templates cover all supported boot mode / storage / mem | **M-Mode + DDR** | `polarfire_mpfs250_m.config` | M-mode (E51, no HSS) | SD Card | LPDDR4 (DDR) | No | | **M-Mode + DDR, eMMC** | `polarfire_mpfs250_m_emmc.config` | M-mode (E51, no HSS) | eMMC | LPDDR4 (DDR) | No | | **M-Mode + DDR, QSPI** | `polarfire_mpfs250_m_qspi_ddr.config` | M-mode (E51, no HSS) | SC QSPI | LPDDR4 (DDR) | No | +| **M-Mode + DDR, Icicle Kit** | `polarfire_mpfs250_icicle_m.config` | M-mode (E51, no HSS) | SD Card (FAT32 file) | LPDDR4 (DDR) | No | | **Boot mode 2 UBL** | `polarfire_mpfs250_ubl.config` | System Controller -> sNVM stub | eNVM (verifies it) | L2-LIM | No | The **M-Mode + DDR** configuration brings up the LPDDR4 controller from @@ -252,6 +256,16 @@ still produces a working M-mode wolfBoot without DDR. Add `-DDEBUG_DDR` to `CFLAGS_EXTRA` for verbose register-level traces during bring-up. +The **Icicle Kit** variant (`polarfire_mpfs250_icicle_m.config`) is the same M-mode + DDR boot on the MPFS-ICICLE-KIT (MPFS250T, a non-secure die, package `FCVG484`). Three things are board-specific and are set in that config rather than detected: + +- **DDR PLL BCLK/SCLK phase**, `MPFS_DDR_BCLK_PHASE`: HSS measures it per board with a software sweep; the Icicle trains at 4 where the Video Kit trains at 3. With the other board's value TIP training reports success and no DRAM write ever completes. +- **SD/eMMC demux select**, `MPFS_SD_EMMC_DEMUX_ADDR=0x4FFFFF00`: on the Icicle the demux is a fabric register (HSS's `CONFIG_SERVICE_MMC_FABRIC_SD_EMMC_DEMUX_SELECT_ADDRESS`), not the Video Kit's IOMUX override. +- **Libero design config**, `LIBERO_FPGA_CONFIG_DIR`: must be an Icicle export (`ICICLE_MSS`). An HSS `build/boards/mpfs-icicle-kit/fpga_design_config` is only regenerated when the XML changes, so one left behind by a Video Kit build silently carries the Video Kit's SEG and IOMUX values; the SEG difference leaves the 64-bit DDR windows unmapped and Linux dies at its first high-memory access. + +The config reads the signed FIT from `/wolfboot/fitImage_v1_signed.bin` on the card's FAT32 partition (`DISK_FS=fat32`), so the stock HSS/U-Boot card layout stays usable, and sets `GZIP=1` and `FIT_RAMDISK=1` for the Yocto fitImage. + +Validated on the Icicle: DDR training completes on every cold boot, and Linux 6.12 (4 CPUs) reaches login and survives a memory stress. + The eMMC and QSPI variants are the same configuration with a different image store. One SDHCI controller reaches both the SD slot and the eMMC through an external demux, so the SD and eMMC builds are mutually exclusive: @@ -301,6 +315,7 @@ These flags apply to `polarfire_mpfs250_m_qspi.config` and are added via `CFLAGS | `WATCHDOG` | undefined | Has no timeout to set on this target: the MSS watchdogs are armed by the bitstream and their window cannot be changed from software. See "Watchdog policy" below. wolfBoot refreshes all five in `hal_init()` and again in `hal_prepare_boot()` either way. | | `MPFS_WDT_NODES_KEEP` (make variable, `=1`) | undefined | Linux boot only: keep the five MSS watchdog nodes enabled in the dtb and stop the parked E51 from refreshing them, so the OS owns the watchdogs. See "Watchdog policy" below. | | `MPFS_QSPI_CLK_DIV` (make variable) | `QSPI_CLK_DIV_8` | QSPI SCK divider, a `QSPI_CLK_DIV_*` name from `hal/mpfs250.h`. See "PolarFire SoC QSPI". | +| `MPFS_SC_PROBE` (make variable) | `1` on M-mode builds | Read-only: at boot, report whether the System Controller serves the device serial number, the TRNG nonce and PUF emulation, with two stability checks (nonce differs across calls, PUF response repeats). No value is printed, only a checksum of the PUF response for comparing boots. `MPFS_SC_PROBE=0` disables it. M-mode only. | | `MPFS_SNVM_DUMP` (make variable, `=1`) | undefined | Read-only: at boot, print the admin word, state and a checksum of `MPFS_SNVM_DUMP_COUNT` sNVM pages from `MPFS_SNVM_DUMP_FIRST` (defaults 2 from 200, the keystore and the wrapped image key). Page contents are never printed. M-mode only. | ### Stack overflow detection diff --git a/docs/Targets.md b/docs/Targets.md index ac736baf21..018306e2a0 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -900,6 +900,7 @@ The PolarFire SoC is a 64-bit RISC-V SoC with a five-core CPU cluster (1x E51 mo | **QSPI + L2-LIM** | `polarfire_mpfs250_hss_l2lim.config` | S-mode (U54 via HSS) | SC QSPI | L2-LIM (no DDR) | Yes | | **M-Mode (no HSS)** | `polarfire_mpfs250_m_qspi.config` | M-mode (E51, no HSS) | SC QSPI | L2 Scratchpad | No | | **M-Mode + DDR** | `polarfire_mpfs250_m.config` | M-mode (E51, no HSS) | SD Card | LPDDR4 (DDR) | No | +| **M-Mode + DDR, Icicle Kit** | `polarfire_mpfs250_icicle_m.config` | M-mode (E51, no HSS) | SD Card (FAT32 file) | LPDDR4 (DDR) | No | | **Boot mode 2 UBL** | `polarfire_mpfs250_ubl.config` | System Controller -> sNVM stub | eNVM (verifies it) | L2-LIM | No | ### PolarFire SoC build options @@ -909,6 +910,10 @@ Make variables, on the command line or in the `.config`. See [PolarFire.md](Pola | Option | Purpose | |--------|---------| | `LIBERO_FPGA_CONFIG_DIR` | Path to the board's Libero/HSS `fpga_design_config` headers. Enables LPDDR4 init (`MPFS_DDR_INIT`); required for the M-Mode + DDR build. | +| `MPFS_DDR_BCLK_PHASE` | DDR PLL BCLK/SCLK phase (0-7) that HSS measures per board with its software sweep: 3 (default) on the Video Kit, 4 on the Icicle Kit. | +| `MPFS_SD_EMMC_DEMUX_ADDR` | Fabric register that selects the SD slot or the eMMC on designs with the demux in the fabric (Icicle Kit: `0x4FFFFF00`); unset, the Video Kit's IOMUX override is used. | +| `MPFS_SC_PROBE` | Read-only System Controller service survey at boot (device serial, TRNG nonce, PUF emulation): status and stability only, never a value. On by default for M-mode builds; `MPFS_SC_PROBE=0` disables it. | +| `MPFS_DDR_MEMTEST` | `=1`: CPU pattern test over the DDR windows the design's SEG registers map, after training, reporting the error count per 64 MB chunk; independent of `DEBUG_DDR`. | | `MPFS_ATHENA` (+ `MPFS_ATHENA_CAL_DIR`) | SHA-384 and AES-256-CTR offload to the Athena F5200 (TeraFire), with a mandatory self-check. | | `SNVM_KEYSTORE`, `SNVM_KEYSTORE_PROVISION`, `SNVM_KEYSTORE_REQUIRE_ROM` | Serve the verification keystore from secure NVM; the one-time writer; refuse to boot unless every keystore page is ROM-flagged. | | `SNVM_KEK`, `SNVM_ENCKEY_PROVISION` | PUF-derived KEK with RFC 3394 key-wrap for the sNVM-stored image-encryption key, and its one-time writer. | @@ -928,6 +933,15 @@ Make variables, on the command line or in the `.config`. See [PolarFire.md](Pola > **Filesystem boot:** this target can also read its signed image from a **file** on a read-only FAT32 or ext4 partition instead of from raw offset 0. Build with `DISK_FS=fat32|ext4|both` and set `BOOT_FILE_A` / `BOOT_FILE_B`. See [Disk boot from a read-only filesystem (FAT32 / ext4)](compile.md#disk-boot-from-a-read-only-filesystem-fat32--ext4). +### PolarFire SoC Icicle Kit + +The MPFS-ICICLE-KIT carries an MPFS250T, a non-secure die in the FCVG484 package, where the Video Kit configurations above assume an MPFS250TS. `polarfire_mpfs250_icicle_m.config` is the M-mode + LPDDR4 + SD boot for it; `polarfire_mpfs250_icicle.config` is the same M-mode build without DDR or an image source, for surveying the System Controller services on an unfamiliar board. Three things are board-specific and are set in the config rather than detected: + +- `MPFS_DDR_BCLK_PHASE=4`: the DDR PLL BCLK/SCLK phase. With the Video Kit's value TIP training reports success but no DRAM write completes. +- `MPFS_SD_EMMC_DEMUX_ADDR=0x4FFFFF00`: the SD/eMMC demux select is a fabric register on this design, not an IOMUX override. +- `LIBERO_FPGA_CONFIG_DIR`: must be an Icicle (`ICICLE_MSS`) export. A Video Kit export carries other SEG and IOMUX values and leaves the 64-bit DDR windows unmapped. + +The config reads the signed FIT from a file on the card's FAT32 partition (`DISK_FS=fat32`), so the stock HSS/U-Boot card layout stays usable. The eNVM is programmed with `--die MPFS250T --package FCVG484` over the on-board FlashPro (close J9). The sNVM, PUF and TRNG services answer on this die; the authenticated sNVM clients and boot mode 2 need a secure die. See [PolarFire.md](PolarFire.md#m-mode--ddr-booting-linux-minimal-sbi) for the bring-up details and troubleshooting. ## STM32F7 diff --git a/hal/mpfs250.c b/hal/mpfs250.c index 545565f5c3..6cbf085a48 100644 --- a/hal/mpfs250.c +++ b/hal/mpfs250.c @@ -424,6 +424,82 @@ static void mpfs_ddr_bringup(void) #endif /* MPFS_DDR_INIT */ #endif /* WOLFBOOT_RISCV_MMODE */ +#ifdef MPFS_SC_PROBE +/* Report which System Controller services this die serves. The secure ("S") + * parts differ from the plain ones in what the co-processor backs, and the + * difference does not show until a service is asked for, so a new board is + * surveyed rather than assumed. Read-only, and no value is printed: the PUF + * response and the serial number are device-unique, so only the service + * status and two stability comparisons are reported. */ +static void mpfs_sc_scrub(void *p, unsigned int len) +{ + volatile uint8_t *b = (volatile uint8_t *)p; + unsigned int i; + + for (i = 0; i < len; i++) { + b[i] = 0; + } +} + +static void mpfs_sc_probe(void) +{ + uint8_t serial[DEVICE_SERIAL_NUMBER_SIZE]; + uint8_t challenge[MPFS_PUF_CHALLENGE_LEN]; + uint8_t resp[MPFS_PUF_RESPONSE_LEN]; + uint8_t resp2[MPFS_PUF_RESPONSE_LEN]; + uint8_t nonce[MPFS_NONCE_LEN]; + uint8_t nonce2[MPFS_NONCE_LEN]; + unsigned int i; + uint32_t sum; + int ret; + + for (i = 0; i < sizeof(challenge); i++) { + challenge[i] = (uint8_t)i; + } + + memset(serial, 0, sizeof(serial)); + ret = mpfs_read_serial_number(serial); + wolfBoot_printf("sc probe: serial (0x00) ret %d\n", ret); + + memset(nonce, 0, sizeof(nonce)); + ret = mpfs_nonce(nonce); + wolfBoot_printf("sc probe: nonce (0x21) ret %d\n", ret); + if (ret == 0) { + memset(nonce2, 0, sizeof(nonce2)); + if (mpfs_nonce(nonce2) == 0) { + wolfBoot_printf("sc probe: nonce random across calls: %d\n", + memcmp(nonce, nonce2, sizeof(nonce)) != 0); + } + } + + memset(resp, 0, sizeof(resp)); + ret = mpfs_puf_emulation(challenge, 0, resp); + wolfBoot_printf("sc probe: puf (0x20) ret %d\n", ret); + if (ret == 0) { + memset(resp2, 0, sizeof(resp2)); + if (mpfs_puf_emulation(challenge, 0, resp2) == 0) { + wolfBoot_printf("sc probe: puf stable in boot: %d\n", + memcmp(resp, resp2, sizeof(resp)) == 0); + } + /* FNV-1a of the response, as mpfs_snvm_dump() does for a page: it + * lets two boots be compared without putting a device-unique + * secret on the console. Not a security primitive. */ + sum = 0x811C9DC5u; + for (i = 0; i < sizeof(resp); i++) { + sum ^= resp[i]; + sum *= 0x01000193u; + } + wolfBoot_printf("sc probe: puf sum %08x\n", (unsigned)sum); + } + + mpfs_sc_scrub(serial, sizeof(serial)); + mpfs_sc_scrub(resp, sizeof(resp)); + mpfs_sc_scrub(resp2, sizeof(resp2)); + mpfs_sc_scrub(nonce, sizeof(nonce)); + mpfs_sc_scrub(nonce2, sizeof(nonce2)); +} +#endif /* MPFS_SC_PROBE */ + void hal_init(void) { #ifdef WOLFBOOT_RISCV_MMODE @@ -490,6 +566,10 @@ void hal_init(void) mpfs_snvm_dump(MPFS_SNVM_DUMP_FIRST, MPFS_SNVM_DUMP_COUNT); #endif +#ifdef MPFS_SC_PROBE + mpfs_sc_probe(); +#endif + #ifdef SNVM_KEYSTORE_PROVISION /* One-time: write the compiled-in trust anchor into sNVM. Stop on failure * so a provisioning run cannot look successful. */ @@ -2243,17 +2323,33 @@ void mpfs_iomux_init(void) SYSREG_REG(SYSREG_IOMUX2_CR_OFFSET) = MPFS_IOMUX2_CR; SYSREG_REG(SYSREG_IOMUX3_CR_OFFSET) = MPFS_IOMUX3_CR; - /* Board GPIO drive overrides that steer the external SD/eMMC demux to the - * selected device; HSS does this in switch_demux_using_fabric_ip(). */ iomux4 = MPFS_IOMUX4_CR; iomux5 = MPFS_IOMUX5_CR; +#ifndef MPFS_SD_EMMC_DEMUX_ADDR + /* Board GPIO drive overrides that steer the external SD/eMMC demux to the + * selected device; HSS does this in switch_demux_using_fabric_ip(). */ iomux4 &= ~(0xFu << 16); iomux4 |= (0xDu << 16); iomux5 &= ~((0xFu << 0) | (0xFu << 16) | (0xFu << 28)); iomux5 |= MPFS_DEMUX_SELECT; +#endif SYSREG_REG(SYSREG_IOMUX4_CR_OFFSET) = iomux4; SYSREG_REG(SYSREG_IOMUX5_CR_OFFSET) = iomux5; SYSREG_REG(SYSREG_IOMUX6_CR_OFFSET) = MPFS_IOMUX6_CR; +#ifdef MPFS_SD_EMMC_DEMUX_ADDR + /* A design with the demux select in the fabric (the Icicle Kit's, at + * 0x4FFFFF00) takes it through that register instead, once the FICs are + * clocked and out of reset: 1 selects the SD slot, 0 the eMMC. */ + SYSREG_SUBBLK_CLOCK_CR |= MSS_PERIPH_FIC0 | MSS_PERIPH_FIC1 | + MSS_PERIPH_FIC3; + SYSREG_SOFT_RESET_CR &= ~(MSS_PERIPH_FIC0 | MSS_PERIPH_FIC1 | + MSS_PERIPH_FIC3 | SYSREG_SOFT_RESET_CR_FPGA); +#if defined(DISK_EMMC) + *(volatile uint32_t *)MPFS_SD_EMMC_DEMUX_ADDR = 0U; +#else + *(volatile uint32_t *)MPFS_SD_EMMC_DEMUX_ADDR = 1U; +#endif +#endif /* Bank-wide config goes via SCB; per-pad IO_CFG goes via SYSREG. */ SYSREGSCB_REG(SCB_MSSIO_BANK4_CFG_CR_OFFSET) = diff --git a/hal/mpfs250.h b/hal/mpfs250.h index 01dcdd8fe7..1dfe6181ec 100644 --- a/hal/mpfs250.h +++ b/hal/mpfs250.h @@ -93,6 +93,7 @@ /* Peripheral Soft Reset Control Register (offset 0x88) */ #define SYSREG_SOFT_RESET_CR (*((volatile uint32_t*)(SYSREG_BASE + 0x88))) #define SYSREG_SOFT_RESET_CR_QSPI (1U << 19) +#define SYSREG_SOFT_RESET_CR_FPGA (1U << 17) /* eNVM Control Register (offset 0xB8). Bits [5:0] set the AHB-to-eNVM * clock divider (period = (value+1) * AHB period); bit 6 (clock-okay) @@ -117,6 +118,9 @@ #define MSS_PERIPH_GPIO1 (1U << 21) #define MSS_PERIPH_GPIO2 (1U << 22) #define MSS_PERIPH_DDRC (1U << 23) +#define MSS_PERIPH_FIC0 (1U << 24) +#define MSS_PERIPH_FIC1 (1U << 25) +#define MSS_PERIPH_FIC3 (1U << 27) #define MSS_PERIPH_ATHENA (1U << 28) /* Crypto hardware accelerator */ /* MSS Watchdog Timer (per-hart) */ diff --git a/hal/mpfs250_ddr.c b/hal/mpfs250_ddr.c index 7c838608ac..b2fea1ffba 100644 --- a/hal/mpfs250_ddr.c +++ b/hal/mpfs250_ddr.c @@ -37,8 +37,14 @@ #ifdef MPFS_DDR_INIT /* DQ/DQS init offset (HSS rpc_156). 6 is the Libero Video Kit value; HSS - * allows 1..9 (TUNE_RPC_156_DQDQS_INIT_VALUE) but it only shifts the window. */ -static uint32_t mpfs_phy_rpc156_val = 6U; + * allows 1..9 (TUNE_RPC_156_DQDQS_INIT_VALUE) and it shifts the write window, + * so it is a per-board tuning value rather than a constant: override it with + * -DMPFS_PHY_RPC156_VAL=n when a board's write calibration will not cover + * every lane. */ +#ifndef MPFS_PHY_RPC156_VAL +#define MPFS_PHY_RPC156_VAL 6U +#endif +static uint32_t mpfs_phy_rpc156_val = MPFS_PHY_RPC156_VAL; #if defined(WOLFBOOT_RISCV_MMODE) && defined(MPFS_DDR_INIT) /* DDR-init busy-loop delay. The argument is NOT a real microsecond: at 600 MHz @@ -1479,19 +1485,20 @@ static void training_bclk_rotate(void) mb(); } -/* Phase: apply BCLK phase from Libero settings. Extracted verbatim from - * run_training; no shared state. */ +/* BCLK phase of the DDR PLL (PLL_PHADJ REG_OUT2_PHSINIT), with BCLK90 two + * phases behind it. HSS measures this per board with its software + * BCLK/SCLK sweep (DDR_TRAINING_IP_SM_BCLKSCLK_SW); its readback is 3 on the + * Video Kit and 4 on the Icicle Kit. The wrong value leaves TIP training + * passing while no DRAM write ever completes, so a board whose write + * calibration times out on every lane needs -DMPFS_DDR_BCLK_PHASE=n. */ +#ifndef MPFS_DDR_BCLK_PHASE +#define MPFS_DDR_BCLK_PHASE 3U +#endif static void training_apply_bclk_phase(void) { - /* Force HSS's software-clock-training result on this board: - * bclk_phase=0x300 (field 3) with paired bclk90=0x2800 (field 5). - * Earlier this was tried BEFORE the auto-init reorder (when WRLVL - * ran prematurely, so the phase could not affect the trained - * wl_dly). Now WRLVL trains AFTER the manual prep, so the - * BCLK<->SCLK phase it aligns DQS against actually matters. PHADJ - * load = 0x4003/0x0003/0x4003 toggle of bit 14 (LOADPHS_B). */ - uint32_t bclk_phase = 0x300UL; - uint32_t bclk90_phase = 0x2800UL; + uint32_t bclk_phase = (MPFS_DDR_BCLK_PHASE & 0x7UL) << 8; + uint32_t bclk90_phase = ((MPFS_DDR_BCLK_PHASE + 2U) & 0x7UL) << 11; + DDR_PLL_REG(PLL_PHADJ) = 0x00004003UL | bclk_phase | bclk90_phase; mb(); DDR_PLL_REG(PLL_PHADJ) = 0x00000003UL | bclk_phase | bclk90_phase; @@ -1505,51 +1512,18 @@ static void training_apply_bclk_phase(void) * from run_training; no shared state. */ static void training_mr_writes(void) { - struct mr_write_s { - uint8_t mr; - uint8_t val; - }; - /* MR2 = 0x12: OP[2:0]=010 -> RL=14, OP[5:3]=010 -> WL=8, WLS=0. - * This MUST match the controller's CFG_RL=14 and CFG_WL=8 (Libero - * hw_ddrc.h). The previous 0x2D set OP[2:0]=101 (RL=28) and - * OP[5:3]=101 (WL=14): with the DRAM at WL=14 but the controller - * launching write data at WL=8, every write lands six cycles off - * and no wrcalib offset on any lane can recover it -- the observed - * MTC WRCALIB status_lower=0x0 (all lanes fail) + memory_test fail. - * 0x12 is also what the controller auto-init derives from CFG_*. - * (An earlier "0x12 -> reads hang" note predates the non-cached - * DDR read-alias fix and is no longer expected to apply.) */ - struct mr_write_s mr_writes[] = { - {1, 0x56}, {2, 0x12}, {3, 0x31}, {11, 0x31}, - {12, 0x32}, {13, 0x20}, {14, 0x0F}, {22, 0x06} - }; - int i, j; - uint32_t ack_cnt = 0, err_cnt = 0; + /* HSS lpddr4_manual_training: masked writes (MR_WR_MASK 0xFFFFF) so the + * controller supplies every mode-register field from its own CFG_* + * values, ten times each. A literal written here can disagree with + * the controller (MR13 data mask and FSP-OP, MR2 read/write latency). */ + static const uint8_t mrs[] = { 1, 2, 3, 4, 11, 16, 17, 22, 13 }; + uint32_t i; + uint32_t err_cnt = 0; - for (i = 0; i < (int)(sizeof(mr_writes)/sizeof(mr_writes[0])); i++) { - for (j = 0; j < 10; j++) { /* 10 retries per MR */ - DDRCFG_REG(MC_INIT_CS) = 0x01; - /* MR_WR_MASK convention: 1 = mask off (preserve), 0 = include - * in write. HSS uses 0 (mode_register_write at mss_ddr.c:3259) - * to actually write data. Our previous 0xFF was masking off - * the low 8 bits (the entire LPDDR4 MR data field), so the - * MR writes were silently no-op'd and DRAM stayed at default - * MR values - causing TIP to stall after BCLK_SCLK because - * DRAM was not configured for the LPDDR4 training sequence. */ - DDRCFG_REG(MC_INIT_MR_WR_MASK) = 0x00; - DDRCFG_REG(MC_INIT_MR_ADDR) = mr_writes[i].mr; - DDRCFG_REG(MC_INIT_MR_WR_DATA) = mr_writes[i].val; - DDRCFG_REG(MC_INIT_MR_W_REQ) = 0x01; - DDRCFG_REG(MC_INIT_MR_W_REQ) = 0x00; - mb(); - ddr_delay(500); /* 5us delay */ - if (DDRCFG_REG(MC_INIT_ACK) != 0) - ack_cnt++; - else - err_cnt++; - } + for (i = 0; i < sizeof(mrs) / sizeof(mrs[0]); i++) { + err_cnt += ddr_cadence_mr_masked_write_x10(mrs[i]); } - DBG_DDR("ack=%d err=%d...", ack_cnt, err_cnt); + DBG_DDR("MR masked writes err=%u...", (unsigned)err_cnt); } /* Phase: configure PHY for WRLVL before training reset release. @@ -2238,10 +2212,144 @@ static void training_posttip_dump(void) /* TIP relies on lane_select being non-zero between iterations (see * training_pretip_lane_snapshot), so leave it where the loop ended. */ } + +/* Raw dump of the DDRC, csr_custom and PHY blocks, for diffing against an + * HSS tinyCLI "debug hexdump" of the same ranges on the same board. */ +static void ddr_regdump(const char *tag) +{ + static const struct { uint32_t base; uint32_t len; } ranges[] = { + { DDRCFG_BASE + 0x2400U, 0x40U }, + { DDRCFG_BASE + 0x2800U, 0x40U }, + { DDRCFG_BASE + 0x3C00U, 0x1A0U }, + { DDRCFG_BASE + 0x4000U, 0x420U }, + { DDRCFG_BASE + 0x4C00U, 0x20U }, + { DDRCFG_BASE + 0x5000U, 0x30U }, + { DDRCFG_BASE + 0x5400U, 0x470U }, + { DDRCFG_BASE + 0x5C00U, 0x40U }, + { DDRCFG_BASE + 0x6400U, 0x160U }, + { DDRCFG_BASE + 0x7C00U, 0x10U }, + { DDRCFG_BASE + 0x8000U, 0x20U }, + { DDRCFG_BASE + 0x10000U, 0x60U }, + { DDRCFG_BASE + 0x12C00U, 0xAA0U }, + { DDRCFG_BASE + 0x3C000U, 0x50U }, + { CFG_DDR_SGMII_PHY_BASE, 0x1000U }, + }; + uint32_t i; + uint32_t off; + uint32_t n = 0; + volatile uint32_t *p; + + wolfBoot_printf("REGDUMP begin %s\n", tag); + for (i = 0; i < sizeof(ranges) / sizeof(ranges[0]); i++) { + for (off = 0; off < ranges[i].len; off += 16U) { + p = (volatile uint32_t *)(uintptr_t)(ranges[i].base + off); + wolfBoot_printf("REGDUMP %08x: %08x %08x %08x %08x\n", + (unsigned)(ranges[i].base + off), + (unsigned)p[0], (unsigned)p[1], (unsigned)p[2], + (unsigned)p[3]); + n++; + if ((n & 0x1FU) == 0U) { + MPFS_DDR_PET_WDT(); + } + } + } + wolfBoot_printf("REGDUMP end %s\n", tag); +} #else #define training_posttip_dump() do { } while (0) +#define ddr_regdump(tag) do { } while (0) #endif +#ifdef MPFS_DDR_MEMTEST +/* CPU pattern test over a DDR window in 64 MB chunks: write an address-derived + * 64-bit pattern, read it back, and report the error count and first failing + * address per chunk. The windows are read through the L2 (cached) or the + * non-cached alias as given, so a cache artefact shows up as a difference + * between the two. */ +static void ddr_memtest_window(uintptr_t base, uint64_t len, const char *name) +{ + const uint64_t chunk = 64ULL << 20; + uint64_t off; + uint64_t i; + uint64_t n = chunk / sizeof(uint64_t); + uint64_t total_err = 0; + + wolfBoot_printf("MEMTEST %s: 0x%lx len 0x%lx\n", name, + (unsigned long)base, (unsigned long)len); + for (off = 0; off < len; off += chunk) { + volatile uint64_t *p = (volatile uint64_t *)(base + off); + uint64_t err = 0; + uint64_t first = 0; + uint64_t got = 0, exp = 0; + for (i = 0; i < n; i++) { + uint64_t a = (uint64_t)(base + off) + (i << 3); + p[i] = a ^ 0xA5A5A5A55A5A5A5AULL ^ (a << 32); + } + __asm__ volatile("fence iorw,iorw" ::: "memory"); + for (i = 0; i < n; i++) { + uint64_t a = (uint64_t)(base + off) + (i << 3); + uint64_t e = a ^ 0xA5A5A5A55A5A5A5AULL ^ (a << 32); + uint64_t g = p[i]; + if (g != e) { + if (err == 0) { + first = a; got = g; exp = e; + } + err++; + } + } + total_err += err; + if (err != 0) { + wolfBoot_printf("MEMTEST %s +0x%lx: %lu errors, first 0x%lx got " + "0x%lx%08lx exp 0x%lx%08lx\n", name, (unsigned long)off, + (unsigned long)err, (unsigned long)first, + (unsigned long)(got >> 32), (unsigned long)(got & 0xFFFFFFFFUL), + (unsigned long)(exp >> 32), (unsigned long)(exp & 0xFFFFFFFFUL)); + } + MPFS_DDR_PET_WDT(); + } + wolfBoot_printf("MEMTEST %s: done, %lu errors\n", name, + (unsigned long)total_err); +} + +/* A SEG register with a zero ADDRESS_OFFSET leaves its window unmapped, and a + * CPU access there stalls the bus instead of faulting. The lengths are the + * MSS map's: 1 GB cached at 0x80000000, 256 MB non-cached at 0xC0000000 + * (0xD0000000 is the write-combining window and 0xE0000000 is FIC1), and the + * whole 2 GB through the 64-bit cached window. */ +#define MPFS_SEG_MAPPED(v) (((v) & 0x7FFFUL) != 0UL) + +static void ddr_memtest(void) +{ + if (MPFS_SEG_MAPPED(LIBERO_SETTING_SEG0_0)) { + ddr_memtest_window(0x80000000UL, 1024ULL << 20, "cached32"); + } + if (MPFS_SEG_MAPPED(LIBERO_SETTING_SEG1_2)) { + ddr_memtest_window(0xC0000000UL, 256ULL << 20, "noncached32"); + } + if (MPFS_SEG_MAPPED(LIBERO_SETTING_SEG0_1)) { + ddr_memtest_window(0x1000000000UL, 2048ULL << 20, "cached64"); + } +} + +#endif /* MPFS_DDR_MEMTEST */ + +/* HSS load_dq() for every lane: the prelude HSS runs before each + * write_calibration_using_mtc(), leaving the DFI status override at 7. */ +static void training_load_dq_all(void) +{ + uint32_t lane; + + for (lane = 0; lane < 4U; lane++) { + DDRPHY_REG(PHY_EXPERT_DLYCNT_MOVE0) = 0x00UL; + DDRPHY_REG(PHY_EXPERT_DFI_STATUS_TO_SHIM) = 0x07UL; + DDRPHY_REG(PHY_EXPERT_MODE_EN) = 0x21UL; + DDRPHY_REG(PHY_EXPERT_DLYCNT_LOAD0) = (0xFFUL << (lane * 8UL)); + DDRPHY_REG(PHY_EXPERT_DLYCNT_LOAD0) = 0x00UL; + DDRPHY_REG(PHY_EXPERT_MODE_EN) = 0x08UL; + } + mb(); + udelay(50); +} static int run_training(uint32_t retry_count) { @@ -2270,16 +2378,6 @@ static int run_training(uint32_t retry_count) */ training_bclk_rotate(); - /* - * Apply BCLK phase from Libero settings. - * NOTE: a faithful port of HSS's software BCLK_SCLK sweep - * (DDR_TRAINING_IP_SM_BCLKSCLK_SW) plus a direct force of HSS's - * observed phase (bclk_phase=0x300/bclk90=0x2800) were both tested on - * a clean cold boot: neither moved wl_dly off the ~+13-tap gap vs HSS - * (wl_dly stayed 0x25-0x2C), and the sweep destabilized WRCALIB. So - * the BCLK/SCLK phase is NOT the source of the wl_dly divergence -- - * reverted to the Libero default apply. - */ training_apply_bclk_phase(); ddr_delay(1000); @@ -2648,21 +2746,10 @@ static int run_training(uint32_t retry_count) DDRPHY_REG(PHY_RPC3_ODT) = 0x03U; mb(); - /* Note: tested re-running expert_dfi_status_override_to_shim - * sequence here (HSS DDR_TRAINING_ROTATE_CLK pattern) to force - * dfi_training_complete=1 -- no effect. STAT_DFI_TRAINING_ - * COMPLETE stays at 0 regardless of write timing or sequence. */ - - /* Re-pulse PHY_DFI_INIT_START to re-establish DFI init handshake. - * INIT_COMPLETE has been cleared somewhere in the training flow; - * this re-asserts the start signal so the controller can - * re-handshake DFI INIT (and hopefully TRAINING) completion. */ - DDRCFG_REG(MC_DFI_INIT_START) = 0x00000000UL; - mb(); - udelay(10); - DDRCFG_REG(MC_DFI_INIT_START) = 0x00000001UL; - mb(); - udelay(1000); /* let DFI re-handshake */ + /* PHY_DFI_INIT_START stays asserted from here on: HSS only drops it on + * its failure path, and re-pulsing it over a trained PHY restarts the + * DFI handshake and leaves the controller unable to complete the write + * calibration and PDMA traffic that follow. */ /* Check final training status */ train_stat = DDRPHY_REG(PHY_TRAINING_STATUS); @@ -2738,6 +2825,8 @@ static int run_training(uint32_t retry_count) (unsigned)((100000U - dfi_to) * 10U)); DBG_DDR(" MTC WRCALIB (HSS-style) tstat=0x%x...\n", train_stat); + training_load_dq_all(); + ddr_regdump("pre-wrcalib"); wrcal_res = mpfs_write_calibration_using_mtc(4U); if (wrcal_res == MPFS_MTC_TIMEOUT_ERROR) { wolfBoot_printf(" MTC WRCALIB TIMEOUT\n"); @@ -2934,7 +3023,6 @@ static int mpfs_ddr_post_initialization(void) { uint32_t timeout; uint32_t mr_err; - uint32_t mr2_err; DDRPHY_REG(PHY_EXPERT_MODE_EN) = 0x08UL; DDRPHY_REG(PHY_EXPERT_DFI_STATUS_TO_SHIM) = 0x0UL; @@ -2949,18 +3037,9 @@ static int mpfs_ddr_post_initialization(void) mb(); udelay(500); /* HSS DELAY_CYCLES_500_MICRO */ - /* CRITICAL: do an UNMASKED MR2 write first to clear LPDDR4 - * MR2 OP[7] (write-leveling enable). TIP's WRLVL phase may - * set MR2[7]=1 to enter WRLVL mode; the polar-fire-guide rule - * "MUST clear the write-leveling bit in MR2 after DFI_WRLVL_ - * RESP=1" applies. If left set, DRAM stays in WRLVL mode and - * subsequent burst writes corrupt on lanes that don't see the - * expected WRLVL response (lanes 1, 2, 3 in our case). - * MR2=0x2D = WL Set 5 / RL Set 5, MR2[7]=0 = WRLVL disabled. */ - mr2_err = ddr_cadence_mr_unmasked_write(2U, 0x2DUL); - DBG_DDR(" MR2 explicit clear (=0x2D) ack=%u\n", - mr2_err == 0U ? 1U : 0U); - (void)mr2_err; + /* Masked writes only: the controller's own MR2 value has the + * write-leveling bit clear, so this also takes the DRAM out of WRLVL + * mode after TIP without a literal that can disagree with CFG_RL/WL. */ mr_err = ddr_cadence_mr_masked_write_x10(1U); mr_err |= ddr_cadence_mr_masked_write_x10(2U); mr_err |= ddr_cadence_mr_masked_write_x10(3U); @@ -3030,7 +3109,7 @@ int mpfs_ddr_init(unsigned int outer_retry) wolfBoot_printf("\n========================================\n"); - mpfs_phy_rpc156_val = 6U; + mpfs_phy_rpc156_val = MPFS_PHY_RPC156_VAL; (void)outer_retry; @@ -3135,16 +3214,7 @@ int mpfs_ddr_init(unsigned int outer_retry) * does this regardless of train_stat. */ DDRPHY_REG(PHY_RPC220) = 0x0CUL; /* HSS-captured (2026-05-15) */ mb(); - for (lane = 0; lane < 4; lane++) { - DDRPHY_REG(PHY_EXPERT_DLYCNT_MOVE0) = 0x00UL; - DDRPHY_REG(PHY_EXPERT_DFI_STATUS_TO_SHIM) = 0x07UL; - DDRPHY_REG(PHY_EXPERT_MODE_EN) = 0x21UL; - DDRPHY_REG(PHY_EXPERT_DLYCNT_LOAD0) = - (0xFFUL << (lane * 8UL)); - DDRPHY_REG(PHY_EXPERT_DLYCNT_LOAD0) = 0x00UL; - DDRPHY_REG(PHY_EXPERT_MODE_EN) = 0x08UL; - } - mb(); + training_load_dq_all(); /* HSS DDR_TRAINING_WRITE_CALIBRATION (mss_ddr.c:1740-1750): * after SET_FINAL_MODE (DDRPHY_MODE final, above) + rpc220 @@ -3170,6 +3240,10 @@ int mpfs_ddr_init(unsigned int outer_retry) * through and the 19 MB load then hard-failed every block. * Retrain instead -- the non-deterministic WRLVL converges * to all-4-lane within a few attempts. */ + if (wrcal == MPFS_MTC_TIMEOUT_ERROR) { + wolfBoot_printf(" WRCALIB MTC timeout -- retraining\n"); + break; + } if (wrcal != 0U) { wolfBoot_printf( " WRCALIB not all lanes (result=%u) -- retraining\n", @@ -3316,6 +3390,8 @@ int mpfs_ddr_init(unsigned int outer_retry) return -4; } + ddr_regdump("post-init"); + /* PDMA pre-fill, the HSS clear_bootup_cache_ways equivalent: primes the * DDRC row buffers before the memory test. 1 MB is enough for that and * keeps the boot short. */ @@ -3334,6 +3410,9 @@ int mpfs_ddr_init(unsigned int outer_retry) (void)prime_sink; wolfBoot_printf("DDR: Initialization COMPLETE\n"); +#ifdef MPFS_DDR_MEMTEST + ddr_memtest(); +#endif return 0; } diff --git a/options.mk b/options.mk index 21ef956977..57a19e17b9 100644 --- a/options.mk +++ b/options.mk @@ -199,6 +199,24 @@ ifneq ($(MPFS_QSPI_CLK_DIV),) CFLAGS+=-DMPFS_QSPI_CLK_DIV=$(MPFS_QSPI_CLK_DIV) endif +# Per-board LPDDR4 and SD values; unset, the HAL's Video Kit defaults apply. +ifneq ($(MPFS_DDR_BCLK_PHASE),) + CFLAGS+=-DMPFS_DDR_BCLK_PHASE=$(MPFS_DDR_BCLK_PHASE) +endif +ifneq ($(MPFS_PHY_RPC156_VAL),) + CFLAGS+=-DMPFS_PHY_RPC156_VAL=$(MPFS_PHY_RPC156_VAL) +endif +ifneq ($(MPFS_SD_EMMC_DEMUX_ADDR),) + CFLAGS+=-DMPFS_SD_EMMC_DEMUX_ADDR=$(MPFS_SD_EMMC_DEMUX_ADDR) +endif +# CPU pattern test over the mapped DDR windows after training; its only output is the console. +ifeq ($(MPFS_DDR_MEMTEST),1) + ifneq ($(DEBUG_UART),1) + $(error MPFS_DDR_MEMTEST=1 needs DEBUG_UART=1: the test reports through the UART) + endif + CFLAGS+=-D"MPFS_DDR_MEMTEST" +endif + # Read-only survey of a range of sNVM pages at boot: each page's admin word (ROM flag), state and # checksum, never the contents, since one of the default range's pages holds the wrapped image key. MPFS_SNVM_DUMP_FIRST?=200 @@ -215,6 +233,23 @@ ifeq ($(MPFS_SNVM_DUMP),1) -DMPFS_SNVM_DUMP_COUNT=$(MPFS_SNVM_DUMP_COUNT) endif +# Read-only survey of the System Controller services this die serves: serial number, nonce/TRNG and +# PUF emulation. Prints each service's status and two stability comparisons, never a value, since the +# PUF response and the serial number are device-unique. On by default for the M-mode mpfs250 target +# (MPFS_SC_PROBE=0 disables it); under HSS the mailbox belongs to HSS, so an S-mode build refuses it. +ifeq ($(TARGET)$(RISCV_MMODE),mpfs2501) + MPFS_SC_PROBE?=1 +endif +ifeq ($(MPFS_SC_PROBE),1) + ifeq ($(WOLFBOOT_TARGET_BUILD),1) + ifneq ($(RISCV_MMODE),1) + $(error MPFS_SC_PROBE=1 needs the M-mode target (RISCV_MMODE=1): \ + only M-mode reaches the System Controller mailbox) + endif + endif + CFLAGS+=-D"MPFS_SC_PROBE" +endif + # Leave the MSS watchdog nodes in the dtb and stop the parked E51 refreshing them, so the OS owns # the watchdogs after the hand-off and must service them itself. ifeq ($(MPFS_WDT_NODES_KEEP),1) diff --git a/tools/config.mk b/tools/config.mk index b51fc07ce2..298453249b 100644 --- a/tools/config.mk +++ b/tools/config.mk @@ -170,7 +170,8 @@ CONFIG_VARS:= ARCH TARGET SIGN HASH MCUXSDK MCUXPRESSO MCUXPRESSO_CPU MCUXPRESSO CERT_CHAIN_GEN_CA_ALGO \ CERT_CHAIN_GEN_CA_HASH \ CERT_CHAIN_NAME_CONSTRAINTS \ - ENCRYPT_CACHE + ENCRYPT_CACHE \ + MPFS_DDR_BCLK_PHASE MPFS_SD_EMMC_DEMUX_ADDR MPFS_PHY_RPC156_VAL MPFS_DDR_MEMTEST MPFS_SC_PROBE # ?= treats a defined-but-empty value as already set, so a stale # LSCRIPT_IN?= line in .config (or an exported empty env var) would skip