From 414cfb318cbd314c4fe5a7b2550403ae4469491f Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Sun, 15 Mar 2026 20:52:14 -0400 Subject: [PATCH 1/3] flashtools/cbfs: support >16MB flash via Intel EXT_BIOS_WIN On Intel PCH platforms with flash chips larger than 16MB, only the top 16MB of the BIOS region is mapped in the fixed decode window at 0xFF000000. The remaining lower portion is accessible through a second "extended BIOS window" (EXT_BIOS_WIN) programmed by coreboot into the SPI controller's PCI config space: SPI_BIOS_CONTROL (PCI 0xdc): bit 27 = EXT_BIOS_ENABLE bits 12-26 = EXT_BIOS_LIMIT (size) SPI_CFG_BAR1 (PCI 0xe0): host base of the 32MB EXT_BIOS_WIN When cbfs runs without -o (live flash), it now calls try_ext_bios_win() which reads these registers from /sys/bus/pci/devices/0000:00:1f.5/config. If the extended window is active, both memory windows are mapped and assembled into a contiguous ROM buffer; FMAP+CBFS search is then used to locate the CBFS master header within the full image. Falls back to the original 16MB delta-pointer path when EXT_BIOS_WIN is absent. Fixes: https://github.com/osresearch/flashtools/issues/10 Signed-off-by: Thierry Laurion --- ...f12568cb23387144a4b7a6535fe1bc1e79b1.patch | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch diff --git a/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch b/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch new file mode 100644 index 000000000..14476acaf --- /dev/null +++ b/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch @@ -0,0 +1,240 @@ +--- a/cbfs.c 2023-03-10 04:51:40.000000000 -0500 ++++ b/cbfs.c 2026-03-15 20:49:13.697175119 -0400 +@@ -21,6 +21,20 @@ + #include "pnor.h" + #include "util.h" + ++/* Intel PCH SPI controller: bus 0, device 31, function 5 */ ++#define INTEL_SPI_PCI_PATH "/sys/bus/pci/devices/0000:00:1f.5/config" ++/* SPI BIOS Control PCI config register (offset 0xdc) */ ++#define SPI_BIOS_CONTROL 0xdc ++#define SPI_BIOS_CONTROL_EXT_BIOS_ENABLE (1u << 27) ++/* SPI CFG BAR1: host base address of the 32MB extended BIOS window (offset 0xe0) */ ++#define SPI_CFG_BAR1 0xe0 ++ ++/* The fixed decode window always maps the top 16MB of BIOS at 0xFF000000 */ ++#define FIXED_WIN_BASE UINT64_C(0xFF000000) ++#define FIXED_BIOS_WIN_SIZE (16u * 1024u * 1024u) ++/* The extended decode window is a fixed 32MB region in host address space */ ++#define EXT_BIOS_WIN_TOTAL (32u * 1024u * 1024u) ++ + #define CBFS_HEADER_MAGIC 0x4F524243 + #define CBFS_HEADER_VERSION1 0x31313131 + #define CBFS_HEADER_VERSION2 0x31313132 +@@ -394,6 +408,155 @@ + return hbi; + } + ++/* ++ * Read a 32-bit little-endian value from a PCI device's config space. ++ * pci_path: e.g. "/sys/bus/pci/devices/0000:00:1f.5/config" ++ * offset: byte offset within PCI config space ++ */ ++static int ++read_pci_config32(const char *pci_path, unsigned offset, uint32_t *val) ++{ ++ int fd = open(pci_path, O_RDONLY); ++ if (fd < 0) ++ return -1; ++ if (pread(fd, val, sizeof(*val), offset) != (ssize_t)sizeof(*val)) { ++ close(fd); ++ return -1; ++ } ++ close(fd); ++ return 0; ++} ++ ++ ++/* ++ * For flash chips > 16MB, Intel PCH SPI controllers provide an extended BIOS ++ * decode window (EXT_BIOS_WIN) alongside the standard 16MB fixed decode window: ++ * ++ * Fixed decode window: top 16MB of BIOS mapped at 0xFF000000-0xFFFFFFFF ++ * Extended decode window: lower portion of BIOS mapped via SPI_CFG_BAR1 ++ * ++ * The extended window occupies a fixed 32MB slot in host address space. ++ * Flash data is placed at the TOP of this 32MB slot. ++ * ++ * SPI_BIOS_CONTROL (PCI 0xdc): bit 27 = EXT_BIOS_ENABLE ++ * bits 12-26 = EXT_BIOS_LIMIT (ext size, 4KB aligned) ++ * SPI_CFG_BAR1 (PCI 0xe0): host base of the 32MB EXT_BIOS_WIN region ++ * ++ * References: ++ * coreboot/src/soc/intel/common/block/fast_spi/mmap_boot.c ++ * coreboot/src/soc/intel/common/block/fast_spi/fast_spi.c (line ~361) ++ * https://github.com/osresearch/flashtools/issues/10 ++ * ++ * Returns a malloc'd buffer containing the full ROM (caller must free), ++ * or NULL if the extended window is not present / not supported. ++ * On success *size_out is set to the total ROM size in bytes. ++ */ ++static void * ++try_ext_bios_win(uint64_t *size_out) ++{ ++ uint32_t bios_control = 0, bar1 = 0; ++ ++ if (read_pci_config32(INTEL_SPI_PCI_PATH, SPI_BIOS_CONTROL, &bios_control) < 0) { ++ if (verbose) ++ fprintf(stderr, ++ "Could not read SPI BIOS_CONTROL from %s; " ++ "no extended window support\n", ++ INTEL_SPI_PCI_PATH); ++ return NULL; ++ } ++ ++ if (!(bios_control & SPI_BIOS_CONTROL_EXT_BIOS_ENABLE)) { ++ if (verbose) ++ fprintf(stderr, ++ "Extended BIOS window not enabled " ++ "(BIOS_CONTROL=0x%08x)\n", bios_control); ++ return NULL; ++ } ++ ++ if (read_pci_config32(INTEL_SPI_PCI_PATH, SPI_CFG_BAR1, &bar1) < 0) { ++ fprintf(stderr, "Failed to read SPI_CFG_BAR1 from %s\n", ++ INTEL_SPI_PCI_PATH); ++ return NULL; ++ } ++ ++ /* Clear PCI BAR type bits (bottom 4) to get the host base address */ ++ const uint64_t ext_win_base = (uint64_t)(bar1 & ~0xfU); ++ if (ext_win_base == 0) { ++ fprintf(stderr, ++ "SPI_CFG_BAR1 is zero - extended window not configured\n"); ++ return NULL; ++ } ++ ++ /* ++ * EXT_BIOS_LIMIT (bits 12-26 of BIOS_CONTROL) is the size in bytes ++ * of the extended (non-fixed) portion, stored 4KB-aligned. ++ */ ++ const size_t ext_size = bios_control & 0x07FFF000U; ++ if (ext_size == 0) { ++ fprintf(stderr, "EXT_BIOS_LIMIT is zero\n"); ++ return NULL; ++ } ++ if (ext_size > EXT_BIOS_WIN_TOTAL) { ++ fprintf(stderr, ++ "EXT_BIOS_LIMIT 0x%zx exceeds EXT_BIOS_WIN_TOTAL 0x%x\n", ++ ext_size, EXT_BIOS_WIN_TOTAL); ++ return NULL; ++ } ++ ++ const size_t total_size = FIXED_BIOS_WIN_SIZE + ext_size; ++ ++ /* ++ * Within the 32MB EXT_BIOS_WIN region, flash data occupies the top ++ * ext_size bytes: ++ * ext_flash_host = ext_win_base + EXT_BIOS_WIN_TOTAL - ext_size ++ */ ++ const uint64_t ext_flash_host = ext_win_base + EXT_BIOS_WIN_TOTAL - ext_size; ++ ++ if (verbose) { ++ fprintf(stderr, "Extended BIOS window detected:\n"); ++ fprintf(stderr, " BIOS_CONTROL=0x%08x BAR1=0x%08x\n", ++ bios_control, bar1); ++ fprintf(stderr, " Total ROM: %zu MB (fixed 16 MB + ext %zu MB)\n", ++ total_size >> 20, ext_size >> 20); ++ fprintf(stderr, " Fixed decode: host 0x%016llx 16 MB\n", ++ (unsigned long long)FIXED_WIN_BASE); ++ fprintf(stderr, " Extended decode: host 0x%016llx %zu MB\n", ++ (unsigned long long)ext_flash_host, ext_size >> 20); ++ } ++ ++ uint8_t *rom = malloc(total_size); ++ if (!rom) { ++ perror("malloc"); ++ return NULL; ++ } ++ ++ /* Top 16MB of BIOS from the fixed decode window */ ++ void *fixed = map_physical(FIXED_WIN_BASE, FIXED_BIOS_WIN_SIZE); ++ if (!fixed) { ++ fprintf(stderr, ++ "Failed to map fixed BIOS window at 0x%016llx\n", ++ (unsigned long long)FIXED_WIN_BASE); ++ free(rom); ++ return NULL; ++ } ++ memcpy(rom + ext_size, fixed, FIXED_BIOS_WIN_SIZE); ++ ++ /* Lower portion of BIOS from the extended decode window */ ++ void *ext = map_physical(ext_flash_host, ext_size); ++ if (!ext) { ++ fprintf(stderr, ++ "Failed to map extended BIOS window at 0x%016llx\n", ++ (unsigned long long)ext_flash_host); ++ free(rom); ++ return NULL; ++ } ++ memcpy(rom, ext, ext_size); ++ ++ *size_out = (uint64_t)total_size; ++ return rom; ++} ++ ++ + static int64_t find_cbfs(const char *romname, const uint8_t *rom, size_t size) + { + long int fmap_offset = fmap_find(rom, size); +@@ -492,12 +655,13 @@ + return EXIT_FAILURE; + } + +- int32_t header_delta; ++ int32_t header_delta = 0; + struct cbfs_header header; + void *rom = NULL, *off = NULL; +- uint64_t size, cb_size; ++ uint64_t size = 0, cb_size; + const uint64_t mem_end = 0x100000000; + void *cb_map; ++ void *ext_rom = NULL; /* malloc'd full ROM from extended BIOS window */ + + if (use_file) { + int readonly = do_add || do_delete ? 0 : 1; +@@ -529,8 +693,29 @@ + + memcpy(&header, cb_map + offset, sizeof(header)); + } else { +- copy_physical(mem_end - 4, sizeof(header_delta), &header_delta); +- copy_physical(mem_end + header_delta, sizeof(header), &header); ++ /* ++ * For flash chips > 16MB the CBFS master header may lie ++ * below the fixed 16MB decode window. Try the extended ++ * BIOS window first; fall back to the legacy delta pointer ++ * for 16MB (and smaller) chips. ++ */ ++ uint64_t ext_size = 0; ++ ext_rom = try_ext_bios_win(&ext_size); ++ if (ext_rom != NULL) { ++ int64_t offset = find_cbfs("live flash", ++ ext_rom, ext_size); ++ if (offset < 0) { ++ free(ext_rom); ++ return EXIT_FAILURE; ++ } ++ size = ext_size; ++ memcpy(&header, ext_rom + offset, sizeof(header)); ++ } else { ++ copy_physical(mem_end - 4, sizeof(header_delta), ++ &header_delta); ++ copy_physical(mem_end + header_delta, ++ sizeof(header), &header); ++ } + } + } + +@@ -559,7 +744,11 @@ + } + + if (!use_file) { +- if (cb_map == NULL) { ++ if (ext_rom != NULL) { ++ /* Full ROM already in memory from extended BIOS window */ ++ rom = ext_rom; ++ /* size already set when ext_rom was assembled */ ++ } else if (cb_map == NULL) { + size = (uint64_t) header.romsize; + rom = map_physical(mem_end - size, size); + } else { From bd146a76c585542c4c71e2514f972c3e2caac94d Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Sun, 15 Mar 2026 20:56:04 -0400 Subject: [PATCH 2/3] boards/msi*: drop CONFIG_CBFS_VIA_FLASHPROG workaround The cbfs tool now natively supports >16MB BIOS regions on Intel ADL+ platforms by reading the extended BIOS decode window (EXT_BIOS_WIN) directly from the SPI controller's PCI config space, via the patch in patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch. Comment out CONFIG_CBFS_VIA_FLASHPROG=y on all four MSI boards (msi_z790p_ddr5, UNTESTED_msi_z790p_ddr4, UNTESTED_msi_z690a_ddr5, UNTESTED_msi_z690a_ddr4) since the flashprog-based workaround is no longer needed. Signed-off-by: Thierry Laurion --- .../UNTESTED_msi_z690a_ddr4/UNTESTED_msi_z690a_ddr4.config | 5 +++-- .../UNTESTED_msi_z690a_ddr5/UNTESTED_msi_z690a_ddr5.config | 5 +++-- .../UNTESTED_msi_z790p_ddr4/UNTESTED_msi_z790p_ddr4.config | 5 +++-- boards/msi_z790p_ddr5/msi_z790p_ddr5.config | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/boards/UNTESTED_msi_z690a_ddr4/UNTESTED_msi_z690a_ddr4.config b/boards/UNTESTED_msi_z690a_ddr4/UNTESTED_msi_z690a_ddr4.config index 893bafe46..238c77d5b 100644 --- a/boards/UNTESTED_msi_z690a_ddr4/UNTESTED_msi_z690a_ddr4.config +++ b/boards/UNTESTED_msi_z690a_ddr4/UNTESTED_msi_z690a_ddr4.config @@ -47,5 +47,6 @@ export CONFIG_BOOT_DEV="/dev/nvme0n1" export CONFIG_BOARD_NAME="MSI PRO Z690-A DDR4" export CONFIG_FLASH_OPTIONS="flashprog --progress --programmer internal" -# Workaround to access > 16MiB BIOS region on ADL+ -export CONFIG_CBFS_VIA_FLASHPROG=y +# cbfs now supports > 16MiB BIOS regions natively via Intel EXT_BIOS_WIN +# (see patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch) +# export CONFIG_CBFS_VIA_FLASHPROG=y diff --git a/boards/UNTESTED_msi_z690a_ddr5/UNTESTED_msi_z690a_ddr5.config b/boards/UNTESTED_msi_z690a_ddr5/UNTESTED_msi_z690a_ddr5.config index 7fb97e23d..40b7c88a1 100644 --- a/boards/UNTESTED_msi_z690a_ddr5/UNTESTED_msi_z690a_ddr5.config +++ b/boards/UNTESTED_msi_z690a_ddr5/UNTESTED_msi_z690a_ddr5.config @@ -47,5 +47,6 @@ export CONFIG_BOOT_DEV="/dev/nvme0n1" export CONFIG_BOARD_NAME="MSI PRO Z690-A" export CONFIG_FLASH_OPTIONS="flashprog --progress --programmer internal" -# Workaround to access > 16MiB BIOS region on ADL+ -export CONFIG_CBFS_VIA_FLASHPROG=y +# cbfs now supports > 16MiB BIOS regions natively via Intel EXT_BIOS_WIN +# (see patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch) +# export CONFIG_CBFS_VIA_FLASHPROG=y diff --git a/boards/UNTESTED_msi_z790p_ddr4/UNTESTED_msi_z790p_ddr4.config b/boards/UNTESTED_msi_z790p_ddr4/UNTESTED_msi_z790p_ddr4.config index a95719090..36dc61791 100644 --- a/boards/UNTESTED_msi_z790p_ddr4/UNTESTED_msi_z790p_ddr4.config +++ b/boards/UNTESTED_msi_z790p_ddr4/UNTESTED_msi_z790p_ddr4.config @@ -47,5 +47,6 @@ export CONFIG_BOOT_DEV="/dev/nvme0n1" export CONFIG_BOARD_NAME="MSI PRO Z790-P DDR4" export CONFIG_FLASH_OPTIONS="flashprog --progress --programmer internal" -# Workaround to access > 16MiB BIOS region on ADL+ -export CONFIG_CBFS_VIA_FLASHPROG=y +# cbfs now supports > 16MiB BIOS regions natively via Intel EXT_BIOS_WIN +# (see patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch) +# export CONFIG_CBFS_VIA_FLASHPROG=y diff --git a/boards/msi_z790p_ddr5/msi_z790p_ddr5.config b/boards/msi_z790p_ddr5/msi_z790p_ddr5.config index 0b2e9671c..85fb136ed 100644 --- a/boards/msi_z790p_ddr5/msi_z790p_ddr5.config +++ b/boards/msi_z790p_ddr5/msi_z790p_ddr5.config @@ -47,5 +47,6 @@ export CONFIG_BOOT_DEV="/dev/nvme0n1" export CONFIG_BOARD_NAME="MSI PRO Z790-P" export CONFIG_FLASH_OPTIONS="flashprog --progress --programmer internal" -# Workaround to access > 16MiB BIOS region on ADL+ -export CONFIG_CBFS_VIA_FLASHPROG=y +# cbfs now supports > 16MiB BIOS regions natively via Intel EXT_BIOS_WIN +# (see patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch) +# export CONFIG_CBFS_VIA_FLASHPROG=y From 303037703a0b5534574546355423c9c9bf028a3e Mon Sep 17 00:00:00 2001 From: Thierry Laurion Date: Sun, 15 Mar 2026 21:17:34 -0400 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Thierry Laurion --- ...ols-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch b/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch index 14476acaf..6c8975dc6 100644 --- a/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch +++ b/patches/flashtools-d1e6f12568cb23387144a4b7a6535fe1bc1e79b1.patch @@ -1,9 +1,11 @@ --- a/cbfs.c 2023-03-10 04:51:40.000000000 -0500 +++ b/cbfs.c 2026-03-15 20:49:13.697175119 -0400 -@@ -21,6 +21,20 @@ +@@ -21,6 +21,21 @@ #include "pnor.h" #include "util.h" ++#include ++ +/* Intel PCH SPI controller: bus 0, device 31, function 5 */ +#define INTEL_SPI_PCI_PATH "/sys/bus/pci/devices/0000:00:1f.5/config" +/* SPI BIOS Control PCI config register (offset 0xdc) */ @@ -157,6 +159,7 @@ + return NULL; + } + memcpy(rom + ext_size, fixed, FIXED_BIOS_WIN_SIZE); ++ unmap_physical(fixed, FIXED_BIOS_WIN_SIZE); + + /* Lower portion of BIOS from the extended decode window */ + void *ext = map_physical(ext_flash_host, ext_size); @@ -164,10 +167,16 @@ + fprintf(stderr, + "Failed to map extended BIOS window at 0x%016llx\n", + (unsigned long long)ext_flash_host); ++ munmap(fixed, FIXED_BIOS_WIN_SIZE); + free(rom); + return NULL; + } + memcpy(rom, ext, ext_size); ++ unmap_physical(ext, ext_size); ++ ++ /* Mappings are no longer needed after copying into the ROM buffer. */ ++ munmap(ext, ext_size); ++ munmap(fixed, FIXED_BIOS_WIN_SIZE); + + *size_out = (uint64_t)total_size; + return rom;