diff --git a/.gitignore b/.gitignore index d33c896740..45bce6158f 100644 --- a/.gitignore +++ b/.gitignore @@ -625,6 +625,9 @@ tools/unit-tests/unit-hifive1-flash-write tools/unit-tests/unit-hifive1-hfrosccfg tools/unit-tests/unit-keygen-keystore tools/unit-tests/unit-kontron-tgl-spi +tools/unit-tests/unit-fsp-nvs +tools/unit-tests/unit-fsp-nvs-save +tools/unit-tests/unit-tgl-spi-hwseq tools/unit-tests/unit-nrf5340-flash-protect tools/unit-tests/unit-nrf5340-uart-crlf tools/unit-tests/unit-nrf54l-uart-write @@ -670,6 +673,7 @@ tools/unit-tests/keystore_gen tools/unit-tests/kinetis_erase_extract.h tools/unit-tests/kontron_spi_extract.h tools/unit-tests/kontron_spi_fn_extract.h +tools/unit-tests/tgl_spi_hwseq_extract.h tools/unit-tests/nrf5340_protect_fn_extract.h tools/unit-tests/nrf54l_uart_write_extract.h tools/unit-tests/p1021_erase_extract.h diff --git a/arch.mk b/arch.mk index 0da7cdd03f..14a292af2a 100644 --- a/arch.mk +++ b/arch.mk @@ -2500,6 +2500,9 @@ ifeq ("${FSP}", "1") OBJS+=src/x86/tgl_fsp.o OBJS+=src/ucode0.o CFLAGS += -DUCODE0_ADDRESS=$(UCODE0_BASE) + ifeq ($(FSP_NVS_CACHE),1) + OBJS+=src/x86/fsp_nvs.o + endif endif else # building wolfBoot @@ -2546,6 +2549,9 @@ ifeq ("${FSP}", "1") endif ifeq ($(FSP_TGL), 1) OBJS+=src/x86/tgl_fsp.o + ifeq ($(FSP_NVS_CACHE),1) + OBJS+=src/x86/fsp_nvs.o + endif endif endif ifeq ($(64BIT),1) diff --git a/config/examples/nai_68int6.config b/config/examples/nai_68int6.config index 286f0301e4..457c59f723 100644 --- a/config/examples/nai_68int6.config +++ b/config/examples/nai_68int6.config @@ -41,6 +41,12 @@ BIOS_REGION_SIZE=0xc00000 UCODE0_BASE=0xffd90000 UCODE0_BIN=src/x86/ucode0.bin +# FSP memory training data cache: 128 KB of the BIOS region just below the +# wolfBoot window, so it survives a reflash of the window. +FSP_NVS_CACHE=1 +FSP_NVS_BASE=0xff9e0000 +FSP_NVS_SIZE=0x20000 + FSP_T_BIN=./src/x86/fsp_t.bin FSP_M_BIN=./src/x86/fsp_m.bin FSP_S_BIN=./src/x86/fsp_s.bin diff --git a/docs/Targets.md b/docs/Targets.md index 53026cd31c..380cf0d122 100644 --- a/docs/Targets.md +++ b/docs/Targets.md @@ -9096,7 +9096,8 @@ Notable options in [config/examples/nai_68int6.config](../config/examples/nai_68 | Option | Meaning | |---|---| | `X86_UART_NUMBER=2` | LPSS UART2 for all three FSP phases (Kontron uses 0) | -| `ACPI=1` | Required. Generates ACPI tables (RSDP/XSDT/FADT/MADT/MCFG and a minimal DSDT); the board's PCI interrupt routing is only in the DSDT `_PRT`, since the MP table is the Kontron one | +| `ACPI=1` | Required. Generates ACPI tables (RSDP/XSDT/FADT/MADT/MCFG and a minimal DSDT); the board's PCI interrupt routing is only in the DSDT `_PRT`, since the MP table is the Kontron one. The DSDT also declares the board devices on LPSS I2C0 (EEPROMs, RTC/watchdog) and GSPI0 (the PRC, for NAI's `naigspi` driver) | +| `FSP_NVS_CACHE=1`, `FSP_NVS_BASE`, `FSP_NVS_SIZE` | Keeps FSP-M's memory training data (its non-volatile storage HOB) in a 128 KB area of the BIOS region below the wolfBoot window, so later boots skip full memory training. Stage 1 passes a valid copy back through `NvsBufferPtr`; stage 2 rewrites it, before FSP-S and before the BIOS region is locked, only when it changes. The record is keyed on the FSP-M revision and UPDs, so a change to either falls back to full training | | `ANTI_ROLLBACK=1` | Persistent version floor and boot audit counters in TPM NV | | `MEASURED_BOOT=1`, `MEASURED_PCR_OS=4` | Firmware into PCR 0, the verified OS image into PCR 4 | | `WOLFBOOT_BOOT_DISK=0` | Boot from GPT partitions; the A/B indices (sda3/sda4) are set in `src/update_disk.c` | @@ -9117,7 +9118,26 @@ The booted OS cannot lower the floor. The index is platform-created and writable ### Boot, programming and recovery -wolfBoot boots the highest-versioned signed 64-bit `bzImage` that verifies from the two GPT boot partitions. It falls back to the other slot only if that is not a downgrade (see Anti-rollback), and halts if neither boots. Program the flash with a Dediprog SF100 on JP4 while the board is off; the BIOS region lock blocks `flashrom` from the booted OS. JP36 selects which bank boots and which one the programmer sees; move it only with the board off. +wolfBoot boots the highest-versioned signed 64-bit `bzImage` or Multiboot2 ELF that verifies from the two GPT boot partitions. It falls back to the other slot only if that is not a downgrade (see Anti-rollback), and halts if neither boots. Program the flash with a Dediprog SF100 on JP4 while the board is off; the BIOS region lock blocks `flashrom` from the booted OS. JP36 selects which bank boots and which one the programmer sees; move it only with the board off. + +### Multiboot2 ELF payloads + +The configuration builds both payload paths (`LINUX_PAYLOAD=1`, `ELF=1`, `MULTIBOOT2=1`), and `do_boot()` picks one per image: an image that starts with the ELF magic takes the Multiboot2 path, and anything else boots as Linux. A Multiboot2 RTOS image such as Green Hills INTEGRITY-178 is signed and written to an A/B slot the same way as a kernel: + +``` +./tools/keytools/sign --ecc384 --sha384 wolfboot_signing_private_key.der +``` + +The two paths differ only in the handoff: + +| | Linux `bzImage` | Multiboot2 ELF | +|---|---|---| +| Entry | 64-bit Linux boot protocol, `boot_params` and the command line | ELF segments loaded at their physical addresses, virtual addresses mapped in wolfBoot's page tables, jump to `e_entry` in 64-bit mode with EAX=`0x36d76289` and EBX pointing to the Multiboot2 information | +| Memory map | E820 table in `boot_params` | Multiboot2 memory map and basic memory information tags, from the FSP HOBs | +| ACPI tables | Generated (`ACPI=1`) | None; they are only built on the Linux path | +| RTC | The legacy CMOS RTC is ignored under the hardware-reduced ACPI tables; the I2C0 RTC is declared in the DSDT | The CMOS RTC is usable directly; I2C0 is enabled for the board RTC | + +Everything else is shared: FSP silicon setup (VT-d at the stock BIOS addresses, Hyper-Threading off), the halted PCH TCO watchdog, the BIOS region lock, measured boot, anti-rollback and A/B selection. ## Infineon PSOC Control C3 diff --git a/hal/x86_fsp_tgl.c b/hal/x86_fsp_tgl.c index b0f5889d43..8dae2231e6 100644 --- a/hal/x86_fsp_tgl.c +++ b/hal/x86_fsp_tgl.c @@ -21,6 +21,7 @@ #include "wolfboot/wolfboot.h" #include +#include #ifdef __WOLFBOOT @@ -190,6 +191,156 @@ int hal_flash_protect(haladdr_t address, int len) * hook's address/len are the same range FREG1 describes. */ return tgl_lock_bios_region(); } + +#ifdef WOLFBOOT_FSP_NVS_CACHE +/* Hardware-sequencing flash cycles, used to save the FSP memory training + * data before the BIOS region is locked. Runs from RAM (stage 2) only. */ +#define SPI_BIOS_CNTL (0xdc) +#define SPI_BIOS_CNTL_WPD (1U << 0) +#define SPI_FADDR (0x8) +#define SPI_FDATA0 (0x10) +#define SPI_HSFSTS_FDONE (1U << 0) +#define SPI_HSFSTS_FCERR (1U << 1) +#define SPI_HSFSTS_AEL (1U << 2) +#define SPI_HSFSTS_SCIP (1U << 5) +#define SPI_HSFSTS_FGO (1U << 16) +#define SPI_HSFSTS_FCYCLE_SHIFT (17) +#define SPI_HSFSTS_FDBC_SHIFT (24) +#define SPI_FCYCLE_WRITE (2) +#define SPI_FCYCLE_ERASE_4K (3) +#define SPI_HWSEQ_MAX_DATA (64) +#define SPI_ERASE_SIZE (0x1000) +#define SPI_HWSEQ_TIMEOUT_MS (1000) + +static uint32_t tgl_spi_begin(uint32_t *cmd) +{ + uint32_t spi_bar, cntl; + + spi_bar = pci_config_read32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_BAR0_OFFSET); + spi_bar &= PCI_BAR_MASK; + *cmd = pci_config_read32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET); + pci_config_write32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET, + *cmd | PCI_COMMAND_MEM_SPACE); + cntl = pci_config_read8(0, SPI_PCI_DEV, SPI_PCI_FUN, SPI_BIOS_CNTL); + pci_config_write8(0, SPI_PCI_DEV, SPI_PCI_FUN, SPI_BIOS_CNTL, + cntl | SPI_BIOS_CNTL_WPD); + if ((pci_config_read8(0, SPI_PCI_DEV, SPI_PCI_FUN, SPI_BIOS_CNTL) & + SPI_BIOS_CNTL_WPD) == 0) + return 0; + return spi_bar; +} + +static void tgl_spi_end(uint32_t cmd) +{ + uint8_t cntl; + + cntl = pci_config_read8(0, SPI_PCI_DEV, SPI_PCI_FUN, SPI_BIOS_CNTL); + pci_config_write8(0, SPI_PCI_DEV, SPI_PCI_FUN, SPI_BIOS_CNTL, + cntl & ~SPI_BIOS_CNTL_WPD); + pci_config_write32(0, SPI_PCI_DEV, SPI_PCI_FUN, PCI_COMMAND_OFFSET, cmd); +} + +/* The BIOS region is mapped so that its last byte sits at 4 GB - 1; + * returns the flash linear address of the memory-mapped address, or -1 + * when the range is outside the BIOS region. */ +static int64_t tgl_spi_linear(uint32_t spi_bar, uint32_t address, + uint32_t len) +{ + uint32_t reg, base, top; + uint64_t lin; + + reg = mmio_read32(spi_bar + SPI_FREG1); + base = (reg & SPI_FREG_BASE_MASK) << SPI_FREG_ADDR_SHIFT; + top = (((reg & SPI_FREG_LIMIT_MASK) >> SPI_FREG_LIMIT_SHIFT) + << SPI_FREG_ADDR_SHIFT) | 0xfffU; + if (top < base || len == 0) + return -1; + lin = (uint64_t)top - (0xffffffffULL - address); + if (lin < base || lin + len - 1 > top) + return -1; + return (int64_t)lin; +} + +static int tgl_spi_cycle(uint32_t spi_bar, uint32_t cycle, uint32_t faddr, + uint32_t len) +{ + uint32_t sts = 0; + int ms; + + for (ms = 0; ms < SPI_HWSEQ_TIMEOUT_MS; ms++) { + if ((mmio_read32(spi_bar + SPI_BIOS_HSFSTS_CTL) & + SPI_HSFSTS_SCIP) == 0) + break; + delay(1); + } + if (ms == SPI_HWSEQ_TIMEOUT_MS) + return -1; + mmio_write32(spi_bar + SPI_BIOS_HSFSTS_CTL, + SPI_HSFSTS_FDONE | SPI_HSFSTS_FCERR | SPI_HSFSTS_AEL); + mmio_write32(spi_bar + SPI_FADDR, faddr); + mmio_write32(spi_bar + SPI_BIOS_HSFSTS_CTL, + ((len - 1) << SPI_HSFSTS_FDBC_SHIFT) | + (cycle << SPI_HSFSTS_FCYCLE_SHIFT) | SPI_HSFSTS_FGO); + for (ms = 0; ms < SPI_HWSEQ_TIMEOUT_MS; ms++) { + sts = mmio_read32(spi_bar + SPI_BIOS_HSFSTS_CTL); + if (sts & (SPI_HSFSTS_FDONE | SPI_HSFSTS_FCERR)) + break; + delay(1); + } + mmio_write32(spi_bar + SPI_BIOS_HSFSTS_CTL, + SPI_HSFSTS_FDONE | SPI_HSFSTS_FCERR | SPI_HSFSTS_AEL); + if (ms == SPI_HWSEQ_TIMEOUT_MS || (sts & SPI_HSFSTS_FCERR)) + return -1; + return 0; +} + +int tgl_spi_flash_erase(uint32_t address, uint32_t len) +{ + uint32_t spi_bar, cmd, off; + int64_t lin; + int ret = 0; + + if ((address | len) & (SPI_ERASE_SIZE - 1)) + return -1; + spi_bar = tgl_spi_begin(&cmd); + lin = (spi_bar != 0) ? tgl_spi_linear(spi_bar, address, len) : -1; + if (lin < 0) + ret = -1; + for (off = 0; ret == 0 && off < len; off += SPI_ERASE_SIZE) + ret = tgl_spi_cycle(spi_bar, SPI_FCYCLE_ERASE_4K, + (uint32_t)lin + off, 1); + tgl_spi_end(cmd); + return ret; +} + +int tgl_spi_flash_write(uint32_t address, const uint8_t *buf, uint32_t len) +{ + uint32_t spi_bar, cmd, off, chunk, word, i; + int64_t lin; + int ret = 0; + + spi_bar = tgl_spi_begin(&cmd); + lin = (spi_bar != 0) ? tgl_spi_linear(spi_bar, address, len) : -1; + if (lin < 0) + ret = -1; + for (off = 0; ret == 0 && off < len; off += chunk) { + /* 64-byte aligned cycles never cross a flash page */ + chunk = SPI_HWSEQ_MAX_DATA - (((uint32_t)lin + off) & + (SPI_HWSEQ_MAX_DATA - 1)); + if (chunk > len - off) + chunk = len - off; + for (i = 0; i < chunk; i += 4) { + word = 0xffffffffU; + memcpy(&word, buf + off + i, (chunk - i < 4) ? chunk - i : 4); + mmio_write32(spi_bar + SPI_FDATA0 + i, word); + } + ret = tgl_spi_cycle(spi_bar, SPI_FCYCLE_WRITE, (uint32_t)lin + off, + chunk); + } + tgl_spi_end(cmd); + return ret; +} +#endif /* WOLFBOOT_FSP_NVS_CACHE */ #endif /* WOLFBOOT_TGL */ #endif diff --git a/include/stage2_params.h b/include/stage2_params.h index ef3c6e2c81..cb172bcc0c 100644 --- a/include/stage2_params.h +++ b/include/stage2_params.h @@ -31,6 +31,9 @@ struct stage2_parameter { /* Verified payload length, set by the disk loader before do_boot so the * Linux loader can bound the signed container header against the image. */ uint32_t payload_size; +#ifdef WOLFBOOT_FSP_NVS_CACHE + uint32_t fsp_nvs_key; +#endif #ifdef WOLFBOOT_TPM_SEAL uint32_t tpm_policy; uint16_t tpm_policy_size; diff --git a/include/x86/acpi_dsdt.h b/include/x86/acpi_dsdt.h index 71b4ebbc20..af6aa92c94 100644 --- a/include/x86/acpi_dsdt.h +++ b/include/x86/acpi_dsdt.h @@ -22,10 +22,10 @@ * Regenerate: iasl src/x86/acpi_dsdt.asl && * xxd -i -n acpi_dsdt_aml src/x86/acpi_dsdt.aml (append below this header). */ static const unsigned char acpi_dsdt_aml[] = { - 0x44, 0x53, 0x44, 0x54, 0x1e, 0x02, 0x00, 0x00, 0x02, 0x48, 0x57, 0x4f, + 0x44, 0x53, 0x44, 0x54, 0x29, 0x05, 0x00, 0x00, 0x02, 0xa7, 0x57, 0x4f, 0x4c, 0x46, 0x42, 0x54, 0x4e, 0x41, 0x49, 0x54, 0x47, 0x4c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x49, 0x4e, 0x54, 0x4c, 0x28, 0x06, 0x23, 0x20, - 0x10, 0x49, 0x1f, 0x5f, 0x53, 0x42, 0x5f, 0x5b, 0x82, 0x41, 0x1f, 0x50, + 0x10, 0x44, 0x50, 0x5f, 0x53, 0x42, 0x5f, 0x5b, 0x82, 0x4c, 0x4f, 0x50, 0x43, 0x49, 0x30, 0x08, 0x5f, 0x48, 0x49, 0x44, 0x0c, 0x41, 0xd0, 0x0a, 0x08, 0x08, 0x5f, 0x43, 0x49, 0x44, 0x0c, 0x41, 0xd0, 0x0a, 0x03, 0x08, 0x5f, 0x53, 0x45, 0x47, 0x00, 0x08, 0x5f, 0x42, 0x42, 0x4e, 0x00, 0x08, @@ -67,6 +67,71 @@ static const unsigned char acpi_dsdt_aml[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, - 0x79, 0x00 + 0x79, 0x00, 0x5b, 0x82, 0x49, 0x25, 0x49, 0x32, 0x43, 0x30, 0x08, 0x5f, + 0x41, 0x44, 0x52, 0x0c, 0x00, 0x00, 0x15, 0x00, 0x5b, 0x82, 0x43, 0x0c, + 0x45, 0x45, 0x50, 0x32, 0x08, 0x5f, 0x48, 0x49, 0x44, 0x0d, 0x31, 0x35, + 0x41, 0x43, 0x30, 0x30, 0x30, 0x32, 0x00, 0x08, 0x5f, 0x43, 0x49, 0x44, + 0x0d, 0x50, 0x52, 0x50, 0x30, 0x30, 0x30, 0x31, 0x00, 0x08, 0x5f, 0x44, + 0x44, 0x4e, 0x0d, 0x4e, 0x41, 0x49, 0x20, 0x4d, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, 0x45, 0x45, 0x50, 0x52, 0x4f, + 0x4d, 0x00, 0x08, 0x5f, 0x43, 0x52, 0x53, 0x11, 0x26, 0x0a, 0x23, 0x8e, + 0x1e, 0x00, 0x02, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0xa0, + 0x86, 0x01, 0x00, 0x56, 0x00, 0x5c, 0x5f, 0x53, 0x42, 0x2e, 0x50, 0x43, + 0x49, 0x30, 0x2e, 0x49, 0x32, 0x43, 0x30, 0x00, 0x79, 0x00, 0x08, 0x5f, + 0x44, 0x53, 0x44, 0x12, 0x41, 0x05, 0x02, 0x11, 0x13, 0x0a, 0x10, 0x14, + 0xd8, 0xff, 0xda, 0xba, 0x6e, 0x8c, 0x4d, 0x8a, 0x91, 0xbc, 0x9b, 0xbf, + 0x4a, 0xa3, 0x01, 0x12, 0x39, 0x03, 0x12, 0x1b, 0x02, 0x0d, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, 0x65, 0x00, 0x0d, 0x61, 0x74, + 0x6d, 0x65, 0x6c, 0x2c, 0x32, 0x34, 0x63, 0x30, 0x32, 0x00, 0x12, 0x0b, + 0x02, 0x0d, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x0b, 0x00, 0x01, 0x12, 0x0e, + 0x02, 0x0d, 0x70, 0x61, 0x67, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x0a, + 0x08, 0x5b, 0x82, 0x46, 0x0d, 0x45, 0x45, 0x50, 0x33, 0x08, 0x5f, 0x48, + 0x49, 0x44, 0x0d, 0x31, 0x35, 0x41, 0x43, 0x30, 0x30, 0x30, 0x33, 0x00, + 0x08, 0x5f, 0x43, 0x49, 0x44, 0x0d, 0x50, 0x52, 0x50, 0x30, 0x30, 0x30, + 0x31, 0x00, 0x08, 0x5f, 0x44, 0x44, 0x4e, 0x0d, 0x4e, 0x41, 0x49, 0x20, + 0x4d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, + 0x45, 0x45, 0x50, 0x52, 0x4f, 0x4d, 0x20, 0x53, 0x4e, 0x00, 0x08, 0x5f, + 0x43, 0x52, 0x53, 0x11, 0x26, 0x0a, 0x23, 0x8e, 0x1e, 0x00, 0x02, 0x00, + 0x01, 0x02, 0x00, 0x00, 0x01, 0x06, 0x00, 0xa0, 0x86, 0x01, 0x00, 0x5e, + 0x00, 0x5c, 0x5f, 0x53, 0x42, 0x2e, 0x50, 0x43, 0x49, 0x30, 0x2e, 0x49, + 0x32, 0x43, 0x30, 0x00, 0x79, 0x00, 0x08, 0x5f, 0x44, 0x53, 0x44, 0x12, + 0x41, 0x06, 0x02, 0x11, 0x13, 0x0a, 0x10, 0x14, 0xd8, 0xff, 0xda, 0xba, + 0x6e, 0x8c, 0x4d, 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01, 0x12, + 0x49, 0x04, 0x04, 0x12, 0x1c, 0x02, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x74, 0x69, 0x62, 0x6c, 0x65, 0x00, 0x0d, 0x61, 0x74, 0x6d, 0x65, 0x6c, + 0x2c, 0x32, 0x34, 0x63, 0x73, 0x30, 0x32, 0x00, 0x12, 0x0a, 0x02, 0x0d, + 0x73, 0x69, 0x7a, 0x65, 0x00, 0x0a, 0x10, 0x12, 0x0e, 0x02, 0x0d, 0x70, + 0x61, 0x67, 0x65, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x0a, 0x08, 0x12, 0x0e, + 0x02, 0x0d, 0x72, 0x65, 0x61, 0x64, 0x2d, 0x6f, 0x6e, 0x6c, 0x79, 0x00, + 0x01, 0x5b, 0x82, 0x4a, 0x0a, 0x52, 0x54, 0x43, 0x31, 0x08, 0x5f, 0x48, + 0x49, 0x44, 0x0d, 0x31, 0x35, 0x41, 0x43, 0x30, 0x30, 0x30, 0x34, 0x00, + 0x08, 0x5f, 0x43, 0x49, 0x44, 0x0d, 0x50, 0x52, 0x50, 0x30, 0x30, 0x30, + 0x31, 0x00, 0x08, 0x5f, 0x44, 0x44, 0x4e, 0x0d, 0x4e, 0x41, 0x49, 0x20, + 0x4d, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x62, 0x6f, 0x61, 0x72, 0x64, 0x20, + 0x57, 0x44, 0x54, 0x20, 0x52, 0x54, 0x43, 0x00, 0x08, 0x5f, 0x43, 0x52, + 0x53, 0x11, 0x26, 0x0a, 0x23, 0x8e, 0x1e, 0x00, 0x02, 0x00, 0x01, 0x02, + 0x00, 0x00, 0x01, 0x06, 0x00, 0xa0, 0x86, 0x01, 0x00, 0x68, 0x00, 0x5c, + 0x5f, 0x53, 0x42, 0x2e, 0x50, 0x43, 0x49, 0x30, 0x2e, 0x49, 0x32, 0x43, + 0x30, 0x00, 0x79, 0x00, 0x08, 0x5f, 0x44, 0x53, 0x44, 0x12, 0x37, 0x02, + 0x11, 0x13, 0x0a, 0x10, 0x14, 0xd8, 0xff, 0xda, 0xba, 0x6e, 0x8c, 0x4d, + 0x8a, 0x91, 0xbc, 0x9b, 0xbf, 0x4a, 0xa3, 0x01, 0x12, 0x20, 0x01, 0x12, + 0x1d, 0x02, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x6c, + 0x65, 0x00, 0x0d, 0x64, 0x61, 0x6c, 0x6c, 0x61, 0x73, 0x2c, 0x64, 0x73, + 0x31, 0x33, 0x37, 0x34, 0x00, 0x5b, 0x82, 0x4e, 0x0a, 0x53, 0x50, 0x49, + 0x30, 0x08, 0x5f, 0x41, 0x44, 0x52, 0x0c, 0x02, 0x00, 0x1e, 0x00, 0x5b, + 0x82, 0x4c, 0x09, 0x50, 0x52, 0x43, 0x30, 0x08, 0x5f, 0x48, 0x49, 0x44, + 0x0d, 0x31, 0x35, 0x41, 0x43, 0x30, 0x30, 0x30, 0x31, 0x00, 0x08, 0x5f, + 0x53, 0x54, 0x52, 0x11, 0x42, 0x05, 0x0a, 0x4e, 0x4e, 0x00, 0x41, 0x00, + 0x49, 0x00, 0x20, 0x00, 0x47, 0x00, 0x53, 0x00, 0x50, 0x00, 0x49, 0x00, + 0x20, 0x00, 0x50, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x73, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x20, 0x00, 0x52, 0x00, + 0x65, 0x00, 0x67, 0x00, 0x69, 0x00, 0x73, 0x00, 0x74, 0x00, 0x65, 0x00, + 0x72, 0x00, 0x20, 0x00, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x74, 0x00, + 0x72, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x72, 0x00, + 0x00, 0x00, 0x08, 0x5f, 0x43, 0x52, 0x53, 0x11, 0x29, 0x0a, 0x26, 0x8e, + 0x21, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x09, 0x00, 0x20, + 0xa1, 0x07, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x5f, 0x53, 0x42, + 0x2e, 0x50, 0x43, 0x49, 0x30, 0x2e, 0x53, 0x50, 0x49, 0x30, 0x00, 0x79, + 0x00 }; -static const unsigned int acpi_dsdt_aml_len = 542; +static const unsigned int acpi_dsdt_aml_len = 1321; diff --git a/include/x86/fsp_nvs.h b/include/x86/fsp_nvs.h new file mode 100644 index 0000000000..b8860ed07a --- /dev/null +++ b/include/x86/fsp_nvs.h @@ -0,0 +1,46 @@ +/* fsp_nvs.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 FSP_NVS_H +#define FSP_NVS_H + +#include + +/* Flash record holding the FSP-M non-volatile storage (memory training) + * data. The key covers the FSP-M revision and the FSP-M UPDs, so a change + * to either invalidates the record. */ +#define FSP_NVS_MAGIC 0x5356464BU /* "KFVS" */ + +struct fsp_nvs_record { + uint32_t magic; + uint32_t key; + uint32_t len; + uint32_t crc; + uint32_t reserved[4]; + uint8_t data[]; +} __attribute__((packed)); + +uint32_t fsp_nvs_crc32(uint32_t crc, const uint8_t *buf, uint32_t len); +int fsp_nvs_record_valid(const struct fsp_nvs_record *rec, uint32_t key, + uint32_t area_size); +void fsp_nvs_apply(uint8_t *udp_m, uint32_t udp_size, uint32_t fsp_m_revision); +void fsp_nvs_save(void); + +#endif /* FSP_NVS_H */ diff --git a/include/x86/gpio_config.h b/include/x86/gpio_config.h index 73bcd468fe..9b60d90c2b 100644 --- a/include/x86/gpio_config.h +++ b/include/x86/gpio_config.h @@ -39,6 +39,10 @@ #define GPIO_GPPC_B9_CFG_OFF (0x790) #define GPIO_GPPC_B10_CFG_OFF (0x7a0) +#define GPIO_GPPC_B15_CFG_OFF (0x7f0) +#define GPIO_GPPC_B16_CFG_OFF (0x800) +#define GPIO_GPPC_B17_CFG_OFF (0x810) +#define GPIO_GPPC_B18_CFG_OFF (0x820) #define GPIO_GPPC_C6_CFG_OFF (0x760) #define GPIO_GPPC_C7_CFG_OFF (0x770) #define GPIO_GPPC_C8_CFG_OFF (0x780) @@ -49,6 +53,8 @@ #define GPIO_GPPC_C13_CFG_OFF (0x7d0) #define GPIO_GPPC_C14_CFG_OFF (0x7e0) #define GPIO_GPPC_C15_CFG_OFF (0x7f0) +#define GPIO_GPPC_C16_CFG_OFF (0x800) +#define GPIO_GPPC_C17_CFG_OFF (0x810) #define GPIO_GPPC_C20_CFG_OFF (0x840) #define GPIO_GPPC_C21_CFG_OFF (0x850) #define GPIO_GPPC_C22_CFG_OFF (0x860) @@ -688,6 +694,109 @@ static const struct tgl_gpio_conf gpio_table_presilicon[] = { .gpio_rxinv = 0x00, .gpio_rxevconf = 0x02, .gpio_reset = GPIO_RESET_PLTRST}, + /* GSPI0 (B15-B18) and I2C0 (C16/C17) in native mode, as stock. */ + /* PAD B15 (DW0=0x44000601 DW1=0x00000027) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B15_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD B16 (DW0=0x84000601 DW1=0x00000028) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B16_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD B17 (DW0=0x44000702 DW1=0x00010029) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B17_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD B18 (DW0=0x84000600 DW1=0x0000002A) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_0_PORT_ID, + .cfg_offset = GPIO_GPPC_B18_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = GPIO_DIR_OUTPUT, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_PLTRST}, + /* PAD C16 (DW0=0x44000702 DW1=0x0000001E) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C16_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, + /* PAD C17 (DW0=0x44000702 DW1=0x0000001F) */ + {.gpio = + { + .comm_port_id = GPIO_COMM_4_PORT_ID, + .cfg_offset = GPIO_GPPC_C17_CFG_OFF, + }, + .flags = + (GPIO_SET_MODE | GPIO_SET_DIRECTION | GPIO_SET_RESET | + GPIO_SET_TERM | GPIO_SET_INTERRUPT | GPIO_SET_RXINV | + GPIO_SET_RXEVCONF), + .gpio_mode = GPIO_MODE_NATIVE_1, + .gpio_dir = 0x03, + .gpio_term = GPIO_TERM_NONE, + .gpio_interrupt = 0x00, + .gpio_rxinv = 0x00, + .gpio_rxevconf = 0x02, + .gpio_reset = GPIO_RESET_HOSTDEEPRESET}, }; #else /* TARGET_kontron_vx3060_s2 */ diff --git a/include/x86/tgl_fsp.h b/include/x86/tgl_fsp.h index 5145fbc185..2dfcf380ea 100644 --- a/include/x86/tgl_fsp.h +++ b/include/x86/tgl_fsp.h @@ -25,5 +25,7 @@ int x86_fsp_tgl_init_sata(uint32_t *bar); int tgl_lock_bios_region(void); +int tgl_spi_flash_erase(uint32_t address, uint32_t len); +int tgl_spi_flash_write(uint32_t address, const uint8_t *buf, uint32_t len); #endif /* TGL_FSP_H */ diff --git a/options.mk b/options.mk index 0592944472..ec1a72e499 100644 --- a/options.mk +++ b/options.mk @@ -1937,6 +1937,17 @@ ifeq ($(FSP), 1) # set CFLAGS defines for each x86_fsp option $(foreach option,$(X86_FSP_OPTIONS),$(if $($(option)), $(eval CFLAGS += -D$(option)=$($(option))))) + + ifeq ($(FSP_NVS_CACHE),1) + ifeq ($(and $(FSP_NVS_BASE),$(FSP_NVS_SIZE)),) + $(error FSP_NVS_CACHE=1 needs FSP_NVS_BASE and FSP_NVS_SIZE) + endif + ifeq ($(filter $(TARGET),kontron_vx3060_s2 nai_68int6),) + $(error FSP_NVS_CACHE=1 is only supported on the Tiger Lake targets) + endif + CFLAGS+=-DWOLFBOOT_FSP_NVS_CACHE + CFLAGS+=-DFSP_NVS_BASE=$(FSP_NVS_BASE) -DFSP_NVS_SIZE=$(FSP_NVS_SIZE) + endif endif ifeq ($(FLASH_MULTI_SECTOR_ERASE),1) diff --git a/src/boot_x86_fsp.c b/src/boot_x86_fsp.c index 1c13dc0c5e..258c969a0a 100644 --- a/src/boot_x86_fsp.c +++ b/src/boot_x86_fsp.c @@ -42,6 +42,9 @@ #include #include #endif +#ifdef WOLFBOOT_FSP_NVS_CACHE +#include +#endif #define WOLFBOOT_X86_STACK_SIZE 0x10000 @@ -586,6 +589,10 @@ void start(uint32_t stack_base, uint32_t stack_top, uint64_t timestamp, if (status != 0) { panic(); } +#if defined(WOLFBOOT_FSP_NVS_CACHE) && defined(BUILD_LOADER_STAGE1) + fsp_nvs_apply(udp_m_parameter, fsp_m_info_header->CfgRegionSize, + fsp_m_info_header->ImageRevision); +#endif #if defined(WOLFBOOT_DUMP_FSP_UPD) wolfBoot_printf("Dumping fspm udp (%d bytes)" ENDLINE, (int)fsp_m_info_header->CfgRegionSize); diff --git a/src/boot_x86_fsp_payload.c b/src/boot_x86_fsp_payload.c index c976687fc3..ecdbe6c418 100644 --- a/src/boot_x86_fsp_payload.c +++ b/src/boot_x86_fsp_payload.c @@ -94,29 +94,16 @@ void jump(uintptr_t entry) : "g"(entry)); } +#if defined(WOLFBOOT_ELF) /** - * @brief Perform the boot process for the given application. - * - * This function performs the boot process for the specified application. - * It either loads and boots a Linux payload or an ELF binary, depending on the - * configuration. + * @brief Load an ELF image and boot it, through Multiboot2 when enabled. * - * @param app Pointer to the start of the application. + * @param app Pointer to the start of the ELF image. + * @param stage2_params Stage 2 parameters (memory layout and FSP HOBs). */ -void do_boot(const uint32_t *app) +static void boot_elf(const uint32_t *app, + struct stage2_parameter *stage2_params) { - struct stage2_parameter *stage2_params; - -#ifdef WOLFBOOT_TPM - wolfBoot_tpm2_deinit(); -#endif /* WOLFBOOT_TPM */ - - stage2_params = stage2_get_parameters(); -#if defined(WOLFBOOT_LINUX_PAYLOAD) - mptable_setup(); - load_linux((uint8_t *)app, stage2_params->payload_size, stage2_params, - cmdline); -#elif defined(WOLFBOOT_ELF) int r; uint64_t e; uint8_t *mb2_header; @@ -163,7 +150,40 @@ void do_boot(const uint32_t *app) #else jump(e); #endif /* WOLFBOOT_MULTIBOOT2 */ +} +#endif /* WOLFBOOT_ELF */ + +/** + * @brief Perform the boot process for the given application. + * + * This function performs the boot process for the specified application. + * It either loads and boots a Linux payload or an ELF binary, depending on the + * configuration. With both built in, an image starting with the ELF magic + * takes the ELF path and anything else boots as Linux. + * + * @param app Pointer to the start of the application. + */ +void do_boot(const uint32_t *app) +{ + struct stage2_parameter *stage2_params; + +#ifdef WOLFBOOT_TPM + wolfBoot_tpm2_deinit(); +#endif /* WOLFBOOT_TPM */ + stage2_params = stage2_get_parameters(); +#if defined(WOLFBOOT_LINUX_PAYLOAD) && defined(WOLFBOOT_ELF) + if (memcmp(app, ELF_IDENT_STR, 4) == 0) { + boot_elf(app, stage2_params); + return; + } +#endif +#if defined(WOLFBOOT_LINUX_PAYLOAD) + mptable_setup(); + load_linux((uint8_t *)app, stage2_params->payload_size, stage2_params, + cmdline); +#elif defined(WOLFBOOT_ELF) + boot_elf(app, stage2_params); #else #error "No payload compiled in" #endif /* WOLFBOOT_LINUX_PAYLOAD */ diff --git a/src/x86/acpi_dsdt.asl b/src/x86/acpi_dsdt.asl index 0a9b66f121..0516aa6333 100644 --- a/src/x86/acpi_dsdt.asl +++ b/src/x86/acpi_dsdt.asl @@ -1,7 +1,7 @@ /* acpi_dsdt.asl * * Minimal DSDT for the NAI 68INT6: the PCI root bus with its _CRS and the - * _PRT INTx routing. Compiled offline with iasl and committed as a C array. + * _PRT INTx routing, plus the board devices on LPSS I2C0 and GSPI0. Compiled offline with iasl and committed as a C array. * * Regenerate after editing: * iasl src/x86/acpi_dsdt.asl @@ -84,6 +84,95 @@ DefinitionBlock ("", "DSDT", 2, "WOLFBT", "NAITGL", 0x00000001) 0x0000000000000000, 0x0000004000000000, 0x0000007FFFFFFFFF, 0x0000000000000000, 0x0000004000000000) }) + + /* LPSS I2C0 and GSPI0 with the board devices the stock BIOS + * declares, so NAI's Linux drivers (naigspi, at24) bind. */ + Device (I2C0) + { + Name (_ADR, 0x00150000) + + Device (EEP2) + { + Name (_HID, "15AC0002") + Name (_CID, "PRP0001") + Name (_DDN, "NAI Motherboard EEPROM") + Name (_CRS, ResourceTemplate () + { + I2cSerialBusV2 (0x0056, ControllerInitiated, 100000, + AddressingMode7Bit, "\\_SB.PCI0.I2C0") + }) + Name (_DSD, Package () + { + ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + Package () + { + Package () { "compatible", "atmel,24c02" }, + Package () { "size", 256 }, + Package () { "pagesize", 8 } + } + }) + } + + Device (EEP3) + { + Name (_HID, "15AC0003") + Name (_CID, "PRP0001") + Name (_DDN, "NAI Motherboard EEPROM SN") + Name (_CRS, ResourceTemplate () + { + I2cSerialBusV2 (0x005E, ControllerInitiated, 100000, + AddressingMode7Bit, "\\_SB.PCI0.I2C0") + }) + Name (_DSD, Package () + { + ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + Package () + { + Package () { "compatible", "atmel,24cs02" }, + Package () { "size", 16 }, + Package () { "pagesize", 8 }, + Package () { "read-only", One } + } + }) + } + + Device (RTC1) + { + Name (_HID, "15AC0004") + Name (_CID, "PRP0001") + Name (_DDN, "NAI Motherboard WDT RTC") + Name (_CRS, ResourceTemplate () + { + I2cSerialBusV2 (0x0068, ControllerInitiated, 100000, + AddressingMode7Bit, "\\_SB.PCI0.I2C0") + }) + Name (_DSD, Package () + { + ToUUID ("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"), + Package () + { + Package () { "compatible", "dallas,ds1374" } + } + }) + } + } + + Device (SPI0) + { + Name (_ADR, 0x001E0002) + + Device (PRC0) + { + Name (_HID, "15AC0001") + Name (_STR, Unicode ("NAI GSPI Processor Register Controller")) + Name (_CRS, ResourceTemplate () + { + SpiSerialBusV2 (0x0000, PolarityLow, FourWireMode, 8, + ControllerInitiated, 500000, ClockPolarityLow, + ClockPhaseFirst, "\\_SB.PCI0.SPI0") + }) + } + } } } } diff --git a/src/x86/fsp.c b/src/x86/fsp.c index 0ed59ec439..4158f32c65 100644 --- a/src/x86/fsp.c +++ b/src/x86/fsp.c @@ -25,6 +25,9 @@ #include #include #include +#ifdef WOLFBOOT_FSP_NVS_CACHE +#include +#endif #define FSP_INFO_HEADER_OFFSET 0x94 #define EFI_SUCCESS 0x0 @@ -162,6 +165,9 @@ void fsp_init_silicon(void) panic(); } wolfBoot_printf("success"); +#if defined(WOLFBOOT_FSP_NVS_CACHE) && !defined(BUILD_LOADER_STAGE1) + fsp_nvs_save(); +#endif memcpy(_fsp_s_base_start, _start_fsp_s, _end_fsp_s - _start_fsp_s); fsp_s_hdr = (struct fsp_info_header*)(_fsp_s_base_start + FSP_INFO_HEADER_OFFSET); silicon_init_off = fsp_s_hdr->FspSiliconInitEntryOffset; diff --git a/src/x86/fsp_nvs.c b/src/x86/fsp_nvs.c new file mode 100644 index 0000000000..f3350b9fb8 --- /dev/null +++ b/src/x86/fsp_nvs.c @@ -0,0 +1,176 @@ +/* fsp_nvs.c + * + * Cache of the FSP-M memory training data in SPI flash. Stage 1 hands a + * valid saved copy back to FspMemInit through NvsBufferPtr so the memory + * reference code takes its fast path; stage 2 saves the data FSP-M returned + * in its non-volatile storage HOB whenever it differs from the saved copy. + * + * 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 +#include +#include +#include + +#if !defined(FSP_NVS_BASE) || !defined(FSP_NVS_SIZE) +#error "FSP_NVS_CACHE needs FSP_NVS_BASE and FSP_NVS_SIZE" +#endif + +#define FSP_NVS_SECTOR_SIZE 0x1000U +#if (FSP_NVS_SIZE % FSP_NVS_SECTOR_SIZE) != 0 +#error "FSP_NVS_SIZE must be a multiple of the 4 KB erase size" +#endif + +#ifndef FSP_NVS_CACHE_FLUSH +/* The flash window is cached; drop stale lines before the read-back */ +#define FSP_NVS_CACHE_FLUSH() __asm__ volatile ("wbinvd" ::: "memory") +#endif + +uint32_t fsp_nvs_crc32(uint32_t crc, const uint8_t *buf, uint32_t len) +{ + int i; + + crc = ~crc; + while (len-- > 0) { + crc ^= *buf++; + for (i = 0; i < 8; i++) + crc = (crc >> 1) ^ (0xEDB88320U & (0U - (crc & 1U))); + } + return ~crc; +} + +int fsp_nvs_record_valid(const struct fsp_nvs_record *rec, uint32_t key, + uint32_t area_size) +{ + if (area_size <= sizeof(*rec)) + return 0; + if (rec->magic != FSP_NVS_MAGIC || rec->key != key) + return 0; + if (rec->len == 0 || rec->len > area_size - sizeof(*rec)) + return 0; + return fsp_nvs_crc32(0, rec->data, rec->len) == rec->crc; +} + +#ifdef BUILD_LOADER_STAGE1 +void fsp_nvs_apply(uint8_t *udp_m, uint32_t udp_size, uint32_t fsp_m_revision) +{ + const struct fsp_nvs_record *rec = + (const struct fsp_nvs_record *)(uintptr_t)FSP_NVS_BASE; + FSPM_ARCH_UPD *arch = (FSPM_ARCH_UPD *)(udp_m + sizeof(FSP_UPD_HEADER)); + uint32_t key; + + /* The key is taken before NvsBufferPtr and BootMode are changed, so + * stage 2 can match it against the UPDs this boot trained with. */ + key = fsp_nvs_crc32(0, (const uint8_t *)&fsp_m_revision, + sizeof(fsp_m_revision)); + key = fsp_nvs_crc32(key, udp_m, udp_size); + stage2_get_parameters()->fsp_nvs_key = key; + + if (!fsp_nvs_record_valid(rec, key, FSP_NVS_SIZE)) { + wolfBoot_printf("FSP NVS: no saved training data\r\n"); + return; + } + arch->NvsBufferPtr = (uint32_t)(uintptr_t)rec->data; + arch->BootMode = BOOT_ASSUMING_NO_CONFIGURATION_CHANGES; + wolfBoot_printf("FSP NVS: using saved training data (%d bytes)\r\n", + (int)rec->len); +} +#else +/* FSP_NON_VOLATILE_STORAGE_HOB_GUID */ +static const struct efi_guid fsp_nvs_hob_guid = { + 0x721acf02, 0x4d77, 0x4c2a, + { 0xb3, 0xdc, 0x27, 0x0b, 0x7b, 0xa9, 0xe4, 0xb0 } +}; + +static struct efi_hob_guid_type *fsp_nvs_find_hob(struct efi_hob *hob) +{ + while (hob != NULL && + hob_get_type(hob) != EFI_HOB_TYPE_END_OF_HOB_LIST) { + if (hob_get_length(hob) < sizeof(struct efi_hob_generic_header)) + return NULL; + if (hob_get_type(hob) == EFI_HOB_TYPE_GUID_EXTENSION && + hob_get_length(hob) > sizeof(struct efi_hob_guid_type) && + hob_guid_equals(&hob->u.guid.name, + (struct efi_guid *)&fsp_nvs_hob_guid)) + return &hob->u.guid; + hob = hob_get_next(hob); + } + return NULL; +} + +static int fsp_nvs_is_current(const struct fsp_nvs_record *rec, uint32_t key, + const uint8_t *data, uint32_t len) +{ + return fsp_nvs_record_valid(rec, key, FSP_NVS_SIZE) && rec->len == len && + memcmp(rec->data, data, len) == 0; +} + +void fsp_nvs_save(void) +{ + const struct fsp_nvs_record *rec = + (const struct fsp_nvs_record *)(uintptr_t)FSP_NVS_BASE; + struct stage2_parameter *params = stage2_get_parameters(); + struct efi_hob_guid_type *hob; + struct fsp_nvs_record hdr; + const uint8_t *data; + uint32_t len, erase_len; + + hob = fsp_nvs_find_hob((struct efi_hob *)(uintptr_t)params->hobList); + if (hob == NULL) { + wolfBoot_printf("FSP NVS: no training data HOB\r\n"); + return; + } + data = (const uint8_t *)(hob + 1); + len = hob->header.hob_length - sizeof(*hob); + if (len > FSP_NVS_SIZE - sizeof(hdr)) { + wolfBoot_printf("FSP NVS: training data (%d bytes) exceeds the cache\r\n", + (int)len); + return; + } + if (fsp_nvs_is_current(rec, params->fsp_nvs_key, data, len)) + return; + + memset(&hdr, 0, sizeof(hdr)); + hdr.magic = FSP_NVS_MAGIC; + hdr.key = params->fsp_nvs_key; + hdr.len = len; + hdr.crc = fsp_nvs_crc32(0, data, len); + erase_len = (sizeof(hdr) + len + FSP_NVS_SECTOR_SIZE - 1) & + ~(FSP_NVS_SECTOR_SIZE - 1); + /* The header goes last, so an interrupted save leaves no valid record */ + if (tgl_spi_flash_erase(FSP_NVS_BASE, erase_len) != 0 || + tgl_spi_flash_write(FSP_NVS_BASE + sizeof(hdr), data, len) != 0 || + tgl_spi_flash_write(FSP_NVS_BASE, (const uint8_t *)&hdr, + sizeof(hdr)) != 0) { + wolfBoot_printf("FSP NVS: saving training data failed\r\n"); + return; + } + FSP_NVS_CACHE_FLUSH(); + if (!fsp_nvs_is_current(rec, hdr.key, data, len)) { + wolfBoot_printf("FSP NVS: training data read-back mismatch\r\n"); + return; + } + wolfBoot_printf("FSP NVS: saved %d bytes of training data\r\n", (int)len); +} +#endif /* BUILD_LOADER_STAGE1 */ diff --git a/src/x86/tgl_fsp.c b/src/x86/tgl_fsp.c index 03066e6f82..29cb99f000 100644 --- a/src/x86/tgl_fsp.c +++ b/src/x86/tgl_fsp.c @@ -677,7 +677,13 @@ static void fsp_set_silicon_cfg(FSPS_UPD *fsps) upd->Usb2PhyPehalfbit[15] = 0; upd->PchTsnEnable[0] = 0; upd->PchTsnEnable[1] = 0; +#ifdef TARGET_nai_68int6 + /* No CNVi module is fitted; forcing Disable makes FSP-S request a global + * reset on every cold boot, while Auto finds nothing and does not */ + upd->CnviMode = 1; +#else upd->CnviMode = 0; +#endif upd->CnviBtCore = 0; upd->SataLedEnable = 1; upd->IomTypeCPortPadCfg[0] = 0; @@ -1035,6 +1041,8 @@ int fsp_machine_update_s_parameters(uint8_t *default_s_params) upd->Cx = FSP_C_STATES; upd->TxtEnable = FSP_TXT; upd->ThermalMonitor = FSP_THERMAL_THROTTLING; + /* I2C0 (PCI mode) carries the board EEPROMs and the watchdog RTC */ + upd->SerialIoI2cMode[0] = 1; #endif return 0; } diff --git a/tools/unit-tests/Makefile b/tools/unit-tests/Makefile index 12a69a11e7..d4a6dffce5 100644 --- a/tools/unit-tests/Makefile +++ b/tools/unit-tests/Makefile @@ -156,6 +156,9 @@ TESTS+=unit-t10xx-flash-status TESTS+=unit-p1021-erase-advance TESTS+=unit-p1021-read-badblock TESTS+=unit-kontron-tgl-spi +TESTS+=unit-fsp-nvs +TESTS+=unit-fsp-nvs-save +TESTS+=unit-tgl-spi-hwseq TESTS+=unit-nrf5340-flash-protect TESTS+=unit-keygen-keystore TESTS+=unit-samr21-erase-advance @@ -2125,6 +2128,32 @@ unit-kontron-tgl-spi: unit-kontron-tgl-spi.c kontron_spi_extract.h \ kontron_spi_fn_extract.h gcc -o $@ unit-kontron-tgl-spi.c $(CFLAGS) $(LDFLAGS) +# unit-tgl-spi-hwseq runs the real SPI hardware-sequencing erase/write from +# hal/x86_fsp_tgl.c (FSP memory training data cache) against a model of the +# SPI controller registers and a RAM-backed flash window. +tgl_spi_hwseq_extract.h: ../../hal/x86_fsp_tgl.c + sed -n '/^#ifdef WOLFBOOT_FSP_NVS_CACHE/,/^#endif \/\* WOLFBOOT_FSP_NVS_CACHE \*\//p' $< > $@ + +unit-tgl-spi-hwseq:CFLAGS+=-DWOLFBOOT_FSP_NVS_CACHE +unit-tgl-spi-hwseq: unit-tgl-spi-hwseq.c kontron_spi_extract.h \ + tgl_spi_hwseq_extract.h + gcc -o $@ unit-tgl-spi-hwseq.c $(CFLAGS) $(LDFLAGS) + +# unit-fsp-nvs runs the FSP-M memory training data cache record check and +# the stage 1 hand-off (src/x86/fsp_nvs.c) against a RAM-backed area. +unit-fsp-nvs:CFLAGS+=-DWOLFBOOT_FSP -DWOLFBOOT_FSP_NVS_CACHE \ + -DBUILD_LOADER_STAGE1 -DPRINTF_ENABLED +unit-fsp-nvs: unit-fsp-nvs.c ../../src/x86/fsp_nvs.c + gcc -o $@ unit-fsp-nvs.c $(CFLAGS) $(LDFLAGS) + +# unit-fsp-nvs-save runs the stage 2 save path against a synthetic HOB list +# and a RAM-backed flash area; built non-PIE so the HOB list sits below 4 GB +# (stage2_parameter.hobList is 32-bit). +unit-fsp-nvs-save:CFLAGS+=-DWOLFBOOT_FSP -DWOLFBOOT_FSP_NVS_CACHE \ + -DPRINTF_ENABLED -fno-pie +unit-fsp-nvs-save: unit-fsp-nvs-save.c ../../src/x86/fsp_nvs.c ../../src/x86/hob.c + gcc -o $@ unit-fsp-nvs-save.c $(CFLAGS) -no-pie $(LDFLAGS) + # unit-nrf5340-flash-protect runs the real hal_flash_protect() from # hal/nrf5340.c against a mock SPU region-permission array (F-13623: the # region count truncated, so a sub-block or partial-tail len left flash diff --git a/tools/unit-tests/unit-fsp-nvs-save.c b/tools/unit-tests/unit-fsp-nvs-save.c new file mode 100644 index 0000000000..91e7d7c6b6 --- /dev/null +++ b/tools/unit-tests/unit-fsp-nvs-save.c @@ -0,0 +1,252 @@ +/* unit-fsp-nvs-save.c + * + * Unit tests for the stage 2 half of the FSP-M memory training data cache + * (src/x86/fsp_nvs.c): finding the non-volatile storage HOB, deciding + * whether the saved copy is current, and the erase/write sequence, against + * a RAM-backed flash area and a synthetic HOB list. + * + * 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 + +#define AREA_SIZE 0x2000U +static uint8_t g_area[AREA_SIZE] __attribute__((aligned(4096))); + +#define FSP_NVS_BASE ((uintptr_t)g_area) +#define FSP_NVS_SIZE AREA_SIZE +#define FSP_NVS_CACHE_FLUSH() do { } while (0) + +#include "stage2_params.h" + +static struct stage2_parameter g_params; +static int g_erase_calls, g_write_calls, g_fail_write_at; + +struct stage2_parameter *stage2_get_parameters(void) +{ + return &g_params; +} + +int tgl_spi_flash_erase(uint32_t address, uint32_t len) +{ + uintptr_t off = (uintptr_t)address - (uintptr_t)(uint32_t)FSP_NVS_BASE; + + g_erase_calls++; + ck_assert_uint_eq(address & 0xfff, 0); + ck_assert_uint_eq(len & 0xfff, 0); + ck_assert_uint_le(off + len, AREA_SIZE); + memset(g_area + off, 0xff, len); + return 0; +} + +int tgl_spi_flash_write(uint32_t address, const uint8_t *buf, uint32_t len) +{ + uintptr_t off = (uintptr_t)address - (uintptr_t)(uint32_t)FSP_NVS_BASE; + + g_write_calls++; + if (g_fail_write_at == g_write_calls) + return -1; + ck_assert_uint_le(off + len, AREA_SIZE); + memcpy(g_area + off, buf, len); + return 0; +} + +#include "../../src/x86/hob.c" +#include "../../src/x86/fsp_nvs.c" + +/* HOB list in static storage: the non-PIE build keeps it below 4 GB, as + * stage2_parameter.hobList is 32-bit. */ +static uint8_t g_hobs[0x4000] __attribute__((aligned(8))); + +static uint8_t *add_guid_hob(uint8_t *p, const struct efi_guid *guid, + uint32_t data_len, uint8_t seed) +{ + struct efi_hob_guid_type *h = (struct efi_hob_guid_type *)p; + uint32_t i, total = (uint32_t)sizeof(*h) + data_len; + + total = (total + 7U) & ~7U; + h->header.hob_type = EFI_HOB_TYPE_GUID_EXTENSION; + h->header.hob_length = (uint16_t)total; + h->header.reserved = 0; + memcpy(&h->name, guid, sizeof(*guid)); + for (i = 0; i < data_len; i++) + p[sizeof(*h) + i] = (uint8_t)(seed + i); + return p + total; +} + +static void end_hobs(uint8_t *p) +{ + struct efi_hob_generic_header *h = (struct efi_hob_generic_header *)p; + + h->hob_type = EFI_HOB_TYPE_END_OF_HOB_LIST; + h->hob_length = sizeof(*h); + h->reserved = 0; +} + +static const struct efi_guid other_guid = { + 0x11111111, 0x2222, 0x3333, { 1, 2, 3, 4, 5, 6, 7, 8 } +}; + +/* An unrelated GUID HOB, then the NVS HOB with data_len bytes. */ +static void build_hobs(uint32_t data_len, uint8_t seed) +{ + uint8_t *p = g_hobs; + + memset(g_hobs, 0, sizeof(g_hobs)); + p = add_guid_hob(p, &other_guid, 24, 0x55); + p = add_guid_hob(p, &fsp_nvs_hob_guid, data_len, seed); + end_hobs(p); +} + +static void setup(uint32_t key) +{ + memset(&g_params, 0, sizeof(g_params)); + g_params.hobList = (uint32_t)(uintptr_t)g_hobs; + g_params.fsp_nvs_key = key; + g_erase_calls = g_write_calls = g_fail_write_at = 0; +} + +static const struct fsp_nvs_record *area(void) +{ + return (const struct fsp_nvs_record *)g_area; +} + +START_TEST(test_save_into_empty_area) +{ + ck_assert_uint_lt((uintptr_t)g_hobs, 0x100000000ULL); + memset(g_area, 0xff, sizeof(g_area)); + setup(0xabcd); + build_hobs(1000, 3); + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 1); + ck_assert_int_eq(fsp_nvs_record_valid(area(), 0xabcd, AREA_SIZE), 1); + ck_assert_uint_eq(area()->len, 1000); + /* the unrelated HOB is 48 bytes, then the NVS HOB header */ + ck_assert_int_eq(memcmp(area()->data, + g_hobs + 48 + sizeof(struct efi_hob_guid_type), 1000), 0); +} +END_TEST + +START_TEST(test_current_copy_is_not_rewritten) +{ + memset(g_area, 0xff, sizeof(g_area)); + setup(0xabcd); + build_hobs(1000, 3); + fsp_nvs_save(); + setup(0xabcd); + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 0); + ck_assert_int_eq(g_write_calls, 0); +} +END_TEST + +START_TEST(test_changed_data_or_key_is_rewritten) +{ + memset(g_area, 0xff, sizeof(g_area)); + setup(0xabcd); + build_hobs(1000, 3); + fsp_nvs_save(); + + setup(0xabcd); + build_hobs(1000, 4); + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 1); + ck_assert_uint_eq(area()->data[0], 4); + + setup(0x1234); + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 1); + ck_assert_int_eq(fsp_nvs_record_valid(area(), 0x1234, AREA_SIZE), 1); +} +END_TEST + +START_TEST(test_missing_or_oversized_hob) +{ + uint8_t *p; + + memset(g_area, 0xff, sizeof(g_area)); + setup(1); + memset(g_hobs, 0, sizeof(g_hobs)); + p = add_guid_hob(g_hobs, &other_guid, 24, 0); + end_hobs(p); + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 0); + + setup(1); + build_hobs(AREA_SIZE - sizeof(struct fsp_nvs_record) + 1, 0); + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 0); +} +END_TEST + +START_TEST(test_zero_length_hob_stops_the_walk) +{ + struct efi_hob_generic_header *h = + (struct efi_hob_generic_header *)g_hobs; + + memset(g_area, 0xff, sizeof(g_area)); + setup(1); + memset(g_hobs, 0, sizeof(g_hobs)); + h->hob_type = EFI_HOB_TYPE_GUID_EXTENSION; + h->hob_length = 0; + fsp_nvs_save(); + ck_assert_int_eq(g_erase_calls, 0); +} +END_TEST + +START_TEST(test_failed_header_write_leaves_no_record) +{ + memset(g_area, 0xff, sizeof(g_area)); + setup(7); + build_hobs(1000, 3); + /* the data is written first, then the header; fail the header */ + g_fail_write_at = 2; + fsp_nvs_save(); + ck_assert_int_eq(fsp_nvs_record_valid(area(), 7, AREA_SIZE), 0); +} +END_TEST + +static Suite *fsp_nvs_save_suite(void) +{ + Suite *s = suite_create("fsp-nvs-save"); + TCase *tc = tcase_create("fsp-nvs-save"); + + tcase_add_test(tc, test_save_into_empty_area); + tcase_add_test(tc, test_current_copy_is_not_rewritten); + tcase_add_test(tc, test_changed_data_or_key_is_rewritten); + tcase_add_test(tc, test_missing_or_oversized_hob); + tcase_add_test(tc, test_zero_length_hob_stops_the_walk); + tcase_add_test(tc, test_failed_header_write_leaves_no_record); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = fsp_nvs_save_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +} diff --git a/tools/unit-tests/unit-fsp-nvs.c b/tools/unit-tests/unit-fsp-nvs.c new file mode 100644 index 0000000000..61d5d1e2a0 --- /dev/null +++ b/tools/unit-tests/unit-fsp-nvs.c @@ -0,0 +1,209 @@ +/* unit-fsp-nvs.c + * + * Unit tests for the FSP-M memory training data cache (src/x86/fsp_nvs.c): + * the record check that decides whether saved data is handed back to + * FspMemInit, and the stage 1 hand-off itself. + * + * 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 + +#define AREA_SIZE 0x1000U +static uint8_t g_area[AREA_SIZE] __attribute__((aligned(16))); + +#define FSP_NVS_BASE ((uintptr_t)g_area) +#define FSP_NVS_SIZE AREA_SIZE + +#include "stage2_params.h" + +static struct stage2_parameter g_params; + +struct stage2_parameter *stage2_get_parameters(void) +{ + return &g_params; +} + +#include "../../src/x86/fsp_nvs.c" + +#define UDP_SIZE 0x100U +#define REVISION 0x0a077e46U + +static uint8_t g_udp[UDP_SIZE]; + +static FSPM_ARCH_UPD *arch(void) +{ + return (FSPM_ARCH_UPD *)(g_udp + sizeof(FSP_UPD_HEADER)); +} + +static uint32_t expected_key(void) +{ + uint32_t rev = REVISION; + uint32_t key = fsp_nvs_crc32(0, (const uint8_t *)&rev, sizeof(rev)); + + return fsp_nvs_crc32(key, g_udp, UDP_SIZE); +} + +static struct fsp_nvs_record *write_record(uint32_t key, uint32_t len) +{ + struct fsp_nvs_record *rec = (struct fsp_nvs_record *)g_area; + uint32_t i; + + memset(g_area, 0xff, sizeof(g_area)); + rec->magic = FSP_NVS_MAGIC; + rec->key = key; + rec->len = len; + for (i = 0; i < len; i++) + rec->data[i] = (uint8_t)(i * 7 + 1); + rec->crc = fsp_nvs_crc32(0, rec->data, len); + return rec; +} + +static void reset_udp(void) +{ + uint32_t i; + + for (i = 0; i < UDP_SIZE; i++) + g_udp[i] = (uint8_t)i; + arch()->NvsBufferPtr = 0; + arch()->BootMode = BOOT_WITH_FULL_CONFIGURATION; + memset(&g_params, 0, sizeof(g_params)); +} + +START_TEST(test_crc32_check_value) +{ + ck_assert_uint_eq(fsp_nvs_crc32(0, (const uint8_t *)"123456789", 9), + 0xCBF43926U); + /* chaining two halves equals one pass */ + ck_assert_uint_eq(fsp_nvs_crc32(fsp_nvs_crc32(0, + (const uint8_t *)"1234", 4), + (const uint8_t *)"56789", 5), 0xCBF43926U); +} +END_TEST + +START_TEST(test_record_valid) +{ + struct fsp_nvs_record *rec = write_record(0x1234, 100); + + ck_assert_int_eq(fsp_nvs_record_valid(rec, 0x1234, AREA_SIZE), 1); + ck_assert_int_eq(fsp_nvs_record_valid(rec, 0x1235, AREA_SIZE), 0); +} +END_TEST + +START_TEST(test_record_rejects_bad_fields) +{ + struct fsp_nvs_record *rec; + + rec = write_record(1, 100); + rec->magic ^= 1; + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, AREA_SIZE), 0); + + rec = write_record(1, 100); + rec->data[50] ^= 0x80; + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, AREA_SIZE), 0); + + rec = write_record(1, 100); + rec->len = 0; + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, AREA_SIZE), 0); + + /* data exactly filling the area is accepted, one byte more is not */ + rec = write_record(1, AREA_SIZE - sizeof(*rec)); + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, AREA_SIZE), 1); + rec->len = AREA_SIZE - sizeof(*rec) + 1; + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, AREA_SIZE), 0); + rec->len = 0xffffffffU; + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, AREA_SIZE), 0); + + /* an area too small for the header is never valid */ + rec = write_record(1, 4); + ck_assert_int_eq(fsp_nvs_record_valid(rec, 1, sizeof(*rec)), 0); +} +END_TEST + +START_TEST(test_apply_without_record) +{ + reset_udp(); + memset(g_area, 0xff, sizeof(g_area)); + fsp_nvs_apply(g_udp, UDP_SIZE, REVISION); + ck_assert_uint_eq(arch()->NvsBufferPtr, 0); + ck_assert_uint_eq(arch()->BootMode, BOOT_WITH_FULL_CONFIGURATION); + ck_assert_uint_eq(g_params.fsp_nvs_key, expected_key()); +} +END_TEST + +START_TEST(test_apply_with_matching_record) +{ + struct fsp_nvs_record *rec; + + reset_udp(); + rec = write_record(expected_key(), 200); + fsp_nvs_apply(g_udp, UDP_SIZE, REVISION); + ck_assert_uint_eq(arch()->NvsBufferPtr, + (uint32_t)(uintptr_t)rec->data); + ck_assert_uint_eq(arch()->BootMode, + BOOT_ASSUMING_NO_CONFIGURATION_CHANGES); +} +END_TEST + +START_TEST(test_apply_rejects_changed_config) +{ + reset_udp(); + write_record(expected_key(), 200); + + /* a different FSP-M revision invalidates the record */ + fsp_nvs_apply(g_udp, UDP_SIZE, REVISION + 1); + ck_assert_uint_eq(arch()->NvsBufferPtr, 0); + + /* so does any change to the UPDs */ + reset_udp(); + g_udp[UDP_SIZE - 1] ^= 1; + fsp_nvs_apply(g_udp, UDP_SIZE, REVISION); + ck_assert_uint_eq(arch()->NvsBufferPtr, 0); + ck_assert_uint_eq(arch()->BootMode, BOOT_WITH_FULL_CONFIGURATION); +} +END_TEST + +static Suite *fsp_nvs_suite(void) +{ + Suite *s = suite_create("fsp-nvs"); + TCase *tc = tcase_create("fsp-nvs"); + + tcase_add_test(tc, test_crc32_check_value); + tcase_add_test(tc, test_record_valid); + tcase_add_test(tc, test_record_rejects_bad_fields); + tcase_add_test(tc, test_apply_without_record); + tcase_add_test(tc, test_apply_with_matching_record); + tcase_add_test(tc, test_apply_rejects_changed_config); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = fsp_nvs_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +} diff --git a/tools/unit-tests/unit-tgl-spi-hwseq.c b/tools/unit-tests/unit-tgl-spi-hwseq.c new file mode 100644 index 0000000000..f24c2e0896 --- /dev/null +++ b/tools/unit-tests/unit-tgl-spi-hwseq.c @@ -0,0 +1,289 @@ +/* unit-tgl-spi-hwseq.c + * + * Unit tests for the Tiger Lake SPI hardware-sequencing erase/write used to + * save the FSP memory training data (hal/x86_fsp_tgl.c, extracted by the + * Makefile): BIOS-region address translation, 64-byte cycle splitting, the + * BIOS_CNTL write-enable handling and the error paths, against a model of + * the SPI controller registers and a RAM-backed flash window. + * + * 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 + +#define MMIO_BASE 0xFED40000UL +#define SPI_BIOS_CNTL_EXPECTED 0xdc + +#include "kontron_spi_extract.h" + +/* BIOS region: linear 0x01400000-0x01FFFFFF, the top 12 MB of 32 MB */ +#define FREG1_INIT 0x1FFF1400U +#define REG_HSFSTS 0x04 +#define REG_FADDR 0x08 +#define REG_FDATA0 0x10 +#define REG_FREG1 0x58 + +/* Modeled flash window: linear 0x019E0000 + 128 KB */ +#define WIN_LINEAR 0x019E0000U +#define WIN_SIZE 0x20000U +#define WIN_MMIO 0xFF9E0000U + +static uint32_t g_mmio[64]; +static uint32_t g_pci_cmd; +static uint8_t g_bios_cntl; +static int g_wpd_stuck_low; +static int g_fail_cycle; +static int g_never_done; +static uint8_t g_flash[WIN_SIZE]; +static struct { + uint32_t cycle, faddr, len; +} g_log[64]; +static int g_ncycles; + +static void sim_reset(void) +{ + memset(g_mmio, 0, sizeof(g_mmio)); + g_mmio[REG_FREG1 / 4] = FREG1_INIT; + g_pci_cmd = 0x0001; + g_bios_cntl = 0; + g_wpd_stuck_low = g_fail_cycle = g_never_done = 0; + g_ncycles = 0; + memset(g_flash, 0x5a, sizeof(g_flash)); +} + +uint32_t pci_config_read32(uint8_t bus, uint8_t dev, uint8_t fun, uint8_t off) +{ + (void)bus; (void)dev; (void)fun; + if (off == PCI_BAR0_OFFSET) + return (uint32_t)MMIO_BASE; + if (off == PCI_COMMAND_OFFSET) + return g_pci_cmd; + return 0; +} + +void pci_config_write32(uint8_t bus, uint8_t dev, uint8_t fun, uint8_t off, + uint32_t val) +{ + (void)bus; (void)dev; (void)fun; + if (off == PCI_COMMAND_OFFSET) + g_pci_cmd = val; +} + +uint8_t pci_config_read8(uint8_t bus, uint8_t dev, uint8_t fun, uint8_t off) +{ + (void)bus; (void)dev; (void)fun; + ck_assert_uint_eq(off, SPI_BIOS_CNTL_EXPECTED); + return g_bios_cntl; +} + +void pci_config_write8(uint8_t bus, uint8_t dev, uint8_t fun, uint8_t off, + uint8_t val) +{ + (void)bus; (void)dev; (void)fun; + ck_assert_uint_eq(off, SPI_BIOS_CNTL_EXPECTED); + g_bios_cntl = g_wpd_stuck_low ? (uint8_t)(val & ~1U) : val; +} + +void delay(int msec) +{ + (void)msec; +} + +static void run_cycle(uint32_t ctl) +{ + uint32_t cycle = (ctl >> 17) & 0xf; + uint32_t len = ((ctl >> 24) & 0x3f) + 1; + uint32_t faddr = g_mmio[REG_FADDR / 4]; + uint32_t i; + + /* a cycle only runs with flash writes enabled */ + ck_assert_uint_eq(g_bios_cntl & 1U, 1U); + if (g_ncycles < (int)(sizeof(g_log) / sizeof(g_log[0]))) { + g_log[g_ncycles].cycle = cycle; + g_log[g_ncycles].faddr = faddr; + g_log[g_ncycles].len = len; + } + g_ncycles++; + if (faddr >= WIN_LINEAR && faddr < WIN_LINEAR + WIN_SIZE) { + if (cycle == 3) + memset(g_flash + (faddr - WIN_LINEAR), 0xff, 0x1000); + else if (cycle == 2) + for (i = 0; i < len; i++) + g_flash[faddr - WIN_LINEAR + i] = + ((uint8_t *)&g_mmio[REG_FDATA0 / 4])[i]; + } + if (g_never_done) + return; + g_mmio[REG_HSFSTS / 4] |= (g_fail_cycle ? 0x2U : 0x1U); +} + +static void mmio_write32(uintptr_t address, uint32_t value) +{ + uint32_t off = (uint32_t)(address - MMIO_BASE); + + if (off == REG_HSFSTS) { + /* status bits are write-1-to-clear */ + g_mmio[REG_HSFSTS / 4] &= ~(value & 0x7U); + if (value & (1U << 16)) + run_cycle(value); + return; + } + g_mmio[off / 4] = value; +} + +static uint32_t mmio_read32(uintptr_t address) +{ + return g_mmio[(address - MMIO_BASE) / 4]; +} + +#include "tgl_spi_hwseq_extract.h" + +START_TEST(test_linear_mapping) +{ + sim_reset(); + ck_assert_int_eq(tgl_spi_linear(MMIO_BASE, WIN_MMIO, 0x1000), + WIN_LINEAR); + ck_assert_int_eq(tgl_spi_linear(MMIO_BASE, 0xFFFFFFFFU, 1), + 0x01FFFFFF); + ck_assert_int_eq(tgl_spi_linear(MMIO_BASE, 0xFF400000U, 0x1000), + 0x01400000); + /* below the BIOS region, past its top, or empty */ + ck_assert_int_eq(tgl_spi_linear(MMIO_BASE, 0xFF3FF000U, 0x1000), -1); + ck_assert_int_eq(tgl_spi_linear(MMIO_BASE, 0xFFFFF000U, 0x2000), -1); + ck_assert_int_eq(tgl_spi_linear(MMIO_BASE, WIN_MMIO, 0), -1); +} +END_TEST + +START_TEST(test_erase_sectors) +{ + sim_reset(); + ck_assert_int_eq(tgl_spi_flash_erase(WIN_MMIO, 0x2000), 0); + ck_assert_int_eq(g_ncycles, 2); + ck_assert_uint_eq(g_log[0].cycle, 3); + ck_assert_uint_eq(g_log[0].faddr, WIN_LINEAR); + ck_assert_uint_eq(g_log[1].faddr, WIN_LINEAR + 0x1000); + ck_assert_uint_eq(g_flash[0], 0xff); + ck_assert_uint_eq(g_flash[0x1fff], 0xff); + ck_assert_uint_eq(g_flash[0x2000], 0x5a); + /* write enable dropped and PCI command restored afterwards */ + ck_assert_uint_eq(g_bios_cntl & 1U, 0); + ck_assert_uint_eq(g_pci_cmd, 0x0001); +} +END_TEST + +START_TEST(test_erase_rejects_unaligned) +{ + sim_reset(); + ck_assert_int_eq(tgl_spi_flash_erase(WIN_MMIO + 0x10, 0x1000), -1); + ck_assert_int_eq(tgl_spi_flash_erase(WIN_MMIO, 0x800), -1); + ck_assert_int_eq(g_ncycles, 0); +} +END_TEST + +START_TEST(test_write_splits_on_64_byte_boundaries) +{ + uint8_t buf[150]; + uint32_t i; + + sim_reset(); + for (i = 0; i < sizeof(buf); i++) + buf[i] = (uint8_t)(i + 1); + ck_assert_int_eq(tgl_spi_flash_write(WIN_MMIO + 0x10, buf, sizeof(buf)), + 0); + ck_assert_int_eq(g_ncycles, 3); + ck_assert_uint_eq(g_log[0].faddr, WIN_LINEAR + 0x10); + ck_assert_uint_eq(g_log[0].len, 48); + ck_assert_uint_eq(g_log[1].faddr, WIN_LINEAR + 0x40); + ck_assert_uint_eq(g_log[1].len, 64); + ck_assert_uint_eq(g_log[2].faddr, WIN_LINEAR + 0x80); + ck_assert_uint_eq(g_log[2].len, 38); + ck_assert_uint_eq(g_log[0].cycle, 2); + ck_assert_int_eq(memcmp(g_flash + 0x10, buf, sizeof(buf)), 0); + ck_assert_uint_eq(g_flash[0x10 + sizeof(buf)], 0x5a); + ck_assert_uint_eq(g_bios_cntl & 1U, 0); +} +END_TEST + +START_TEST(test_write_outside_bios_region) +{ + uint8_t buf[4] = { 0 }; + + sim_reset(); + ck_assert_int_eq(tgl_spi_flash_write(0xFF000000U, buf, sizeof(buf)), -1); + ck_assert_int_eq(g_ncycles, 0); + ck_assert_uint_eq(g_bios_cntl & 1U, 0); +} +END_TEST + +START_TEST(test_cycle_error_and_timeout) +{ + uint8_t buf[8] = { 0 }; + + sim_reset(); + g_fail_cycle = 1; + ck_assert_int_eq(tgl_spi_flash_write(WIN_MMIO, buf, sizeof(buf)), -1); + + sim_reset(); + g_never_done = 1; + ck_assert_int_eq(tgl_spi_flash_erase(WIN_MMIO, 0x1000), -1); +} +END_TEST + +START_TEST(test_write_enable_refused) +{ + uint8_t buf[8] = { 0 }; + + sim_reset(); + g_wpd_stuck_low = 1; + ck_assert_int_eq(tgl_spi_flash_write(WIN_MMIO, buf, sizeof(buf)), -1); + ck_assert_int_eq(tgl_spi_flash_erase(WIN_MMIO, 0x1000), -1); + ck_assert_int_eq(g_ncycles, 0); +} +END_TEST + +static Suite *hwseq_suite(void) +{ + Suite *s = suite_create("tgl-spi-hwseq"); + TCase *tc = tcase_create("tgl-spi-hwseq"); + + tcase_add_test(tc, test_linear_mapping); + tcase_add_test(tc, test_erase_sectors); + tcase_add_test(tc, test_erase_rejects_unaligned); + tcase_add_test(tc, test_write_splits_on_64_byte_boundaries); + tcase_add_test(tc, test_write_outside_bios_region); + tcase_add_test(tc, test_cycle_error_and_timeout); + tcase_add_test(tc, test_write_enable_refused); + suite_add_tcase(s, tc); + return s; +} + +int main(void) +{ + int fails; + Suite *s = hwseq_suite(); + SRunner *sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + fails = srunner_ntests_failed(sr); + srunner_free(sr); + return fails; +}