diff --git a/Makefile b/Makefile index 5d0a0e65b..860403189 100644 --- a/Makefile +++ b/Makefile @@ -65,6 +65,11 @@ $(info **LOADAVG** (default: 1.5 times CPUS, e.g., 'make LOADAVG=54')) $(info **AVAILABLE_MEM_GB** (default: memory available on the system in GB, e.g., 'make AVAILABLE_MEM_GB=4')) $(info **MEM_PER_JOB_GB** (default: 1GB per job, e.g., 'make MEM_PER_JOB_GB=2')) $(info ----------------------------------------------------------------------) +ifeq "$(V)" "" +$(info Hint: If the build fails, re-run with V=1 for full configure + validation output) +$(info Verbose example: ./docker_repro.sh make BOARD=$(BOARD) V=1) +$(info ----------------------------------------------------------------------) +endif $(info !!!!!! Build starts !!!!!!) @@ -284,6 +289,34 @@ all payload: @sha256sum $< | tee -a "$(HASHES)" @stat -c "%8s:%n" $< | tee -a "$(SIZES)" +# Validate coreboot CBFS size against IFD BIOS region +validate_cbfs_ifd: +ifneq ($(CONFIG_COREBOOT),) + @echo "Validating $(BOARD) CBFS size against IFD BIOS region..." + @$(pwd)/bin/validate_cbfs_ifd_fit.sh \ + --coreboot-dir "$(build)/$(coreboot_dir)" \ + --board-dir "$(build)/$(BOARD)" \ + --config "$(pwd)/$(CONFIG_COREBOOT_CONFIG)" || exit 1 + @echo "✓ CBFS configuration is valid" +else + @echo "Board $(BOARD) does not use coreboot, skipping validation" +endif + +# Auto-fix coreboot CBFS size to match IFD BIOS region +fix_cbfs_ifd: +ifneq ($(CONFIG_COREBOOT),) + @echo "Auto-fixing $(BOARD) CBFS size to match IFD BIOS region..." + @$(pwd)/bin/validate_cbfs_ifd_fit.sh \ + --coreboot-dir "$(build)/$(coreboot_dir)" \ + --board-dir "$(build)/$(BOARD)" \ + --config "$(pwd)/$(CONFIG_COREBOOT_CONFIG)" \ + --fix || exit 1 + @echo "" + @echo "If CONFIG_CBFS_SIZE was adjusted, next build will use the new size." +else + @echo "Board $(BOARD) does not use coreboot, nothing to fix" +endif + # Disable all built in rules .INTERMEDIATE: .SUFFIXES: @@ -898,11 +931,27 @@ modules.clean: # PUBKEY_ASC. inject_gpg: $(board_build)/$(CB_OUTPUT_FILE_GPG_INJ) -$(board_build)/$(CB_OUTPUT_BASENAME)-gpg-injected.rom: $(board_build)/$(CB_OUTPUT_FILE) - cp "$(board_build)/$(CB_OUTPUT_FILE)" \ - "$(board_build)/$(CB_OUTPUT_FILE_GPG_INJ)" - ./bin/inject_gpg_key.sh --cbfstool "$(build)/$(coreboot_dir)/cbfstool" \ - "$(board_build)/$(CB_OUTPUT_FILE_GPG_INJ)" "$(PUBKEY_ASC)" +$(board_build)/$(CB_OUTPUT_BASENAME)-gpg-injected.rom: $(board_build)/$(CB_OUTPUT_FILE) $(PUBKEY_ASC) + @set -e; \ + src="$(board_build)/$(CB_OUTPUT_FILE)"; \ + tgt="$(board_build)/$(CB_OUTPUT_FILE_GPG_INJ)"; \ + key="$(PUBKEY_ASC)"; \ + meta="$(board_build)/$(CB_OUTPUT_FILE_GPG_INJ).meta"; \ + if [ -f "$$tgt" ] && [ -f "$$meta" ]; then \ + SRC_SHA256=$$(awk -F= '/^SRC_SHA256=/{print $$2}' "$$meta"); \ + KEY_SHA256=$$(awk -F= '/^KEY_SHA256=/{print $$2}' "$$meta"); \ + src_sum=$$(sha256sum "$$src" | awk '{print $$1}'); \ + key_sum=$$(sha256sum "$$key" | awk '{print $$1}'); \ + if [ "$$src_sum" = "$$SRC_SHA256" ] && [ "$$key_sum" = "$$KEY_SHA256" ]; then \ + echo "GPG injection up-to-date; skipping"; \ + exit 0; \ + fi; \ + fi; \ + cp "$$src" "$$tgt"; \ + ./bin/inject_gpg_key.sh --cbfstool "$(build)/$(coreboot_dir)/cbfstool" "$$tgt" "$$key"; \ + SRC_SHA256=$$(sha256sum "$$src" | awk '{print $$1}'); \ + KEY_SHA256=$$(sha256sum "$$key" | awk '{print $$1}'); \ + printf 'SRC_SHA256=%s\nKEY_SHA256=%s\n' "$$SRC_SHA256" "$$KEY_SHA256" > "$$meta" diff --git a/bin/validate_cbfs_ifd_fit.sh b/bin/validate_cbfs_ifd_fit.sh new file mode 100755 index 000000000..ecda19053 --- /dev/null +++ b/bin/validate_cbfs_ifd_fit.sh @@ -0,0 +1,321 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-3.0-or-later +# Validate that CBFS size fits within IFD BIOS region +# and report space usage statistics + +set -e + +usage() { + cat < --board-dir --config [--fix] + +Validates that CONFIG_CBFS_SIZE from coreboot config matches the BIOS region +size reported by the Intel Flash Descriptor (IFD), and provides space usage +statistics from cbfstool. + +Options: + --coreboot-dir Path to coreboot build directory + --board-dir Path to board build directory + --config Path to coreboot config file + --fix Automatically fix CONFIG_CBFS_SIZE to match IFD BIOS region + --help Show this help message + +Exit codes: + 0: Validation passed (or fix applied successfully, or tools not available yet) + 1: Validation failed - CONFIG_CBFS_SIZE exceeds IFD BIOS region +EOF + exit "${1:-0}" +} + +# Parse arguments +FIX_MODE=0 +while [ $# -gt 0 ]; do + case "$1" in + --coreboot-dir) + COREBOOT_DIR="$2" + shift 2 + ;; + --board-dir) + BOARD_DIR="$2" + shift 2 + ;; + --config) + CONFIG_FILE="$2" + shift 2 + ;; + --fix) + FIX_MODE=1 + shift + ;; + --help) + usage 0 + ;; + *) + echo "Unknown option: $1" >&2 + usage 1 + ;; + esac +done + +# Validate required arguments +if [ -z "$COREBOOT_DIR" ] || [ -z "$BOARD_DIR" ] || [ -z "$CONFIG_FILE" ]; then + echo "Error: Missing required arguments" >&2 + usage 1 +fi + +# Check if tools exist +CBFSTOOL="$COREBOOT_DIR/cbfstool" +IFDTOOL="$COREBOOT_DIR/util/ifdtool/ifdtool" + +if [ ! -x "$CBFSTOOL" ]; then + echo "Warning: cbfstool not found at $CBFSTOOL" >&2 + echo "Skipping CBFS analysis (coreboot not built yet)" >&2 + CBFSTOOL="" +fi + +if [ ! -x "$IFDTOOL" ]; then + echo "Warning: ifdtool not found at $IFDTOOL" >&2 + echo "Skipping IFD validation (coreboot not built yet)" >&2 + IFDTOOL="" +fi + +# Extract CONFIG_CBFS_SIZE from config +CBFS_SIZE=$(grep "^CONFIG_CBFS_SIZE=" "$CONFIG_FILE" | cut -d= -f2) +if [ -z "$CBFS_SIZE" ]; then + echo "Error: CONFIG_CBFS_SIZE not found in $CONFIG_FILE" >&2 + exit 1 +fi + +# Convert to decimal +CBFS_SIZE_DEC=$((CBFS_SIZE)) + +# Extract IFD path from config +IFD_PATH=$(grep "^CONFIG_IFD_BIN_PATH=" "$CONFIG_FILE" | cut -d'"' -f2) + +# Resolve relative IFD path to absolute, preferring coreboot base dir +if [ -n "$IFD_PATH" ] && [[ "$IFD_PATH" != /* ]] && [[ "$IFD_PATH" != *"@"* ]]; then + COREBOOT_BASE_DIR="$(dirname "$COREBOOT_DIR")" + if [ -d "$COREBOOT_BASE_DIR" ]; then + IFD_PATH="$COREBOOT_BASE_DIR/$IFD_PATH" + else + IFD_PATH="$PWD/$IFD_PATH" + fi +fi + +# If IFD path uses @BLOB_DIR@, resolve it +# @BLOB_DIR@ typically expands to blobs/ from the repo root +if [[ "$IFD_PATH" == *"@BLOB_DIR@"* ]]; then + # Try to find the Heads repo root (go up from coreboot-dir) + # COREBOOT_DIR is like /home/user/heads/build/x86/coreboot-25.09/BOARD + # So we need to go up 4 levels: BOARD -> coreboot-25.09 -> x86 -> build -> heads + if [ -d "$COREBOOT_DIR" ]; then + REPO_ROOT=$(cd "$COREBOOT_DIR/../../../../" 2>/dev/null && pwd || echo "") + if [ -n "$REPO_ROOT" ] && [ -d "$REPO_ROOT/blobs" ]; then + IFD_PATH="${IFD_PATH/@BLOB_DIR@/$REPO_ROOT/blobs}" + fi + fi +fi + +# If IFD path uses @BLOB_DIR@, we need to resolve it +# For now, skip validation if no IFD or if path is not resolved +if [ -z "$IFD_PATH" ] || [[ "$IFD_PATH" == *"@"* ]]; then + # Try to find the IFD in the coreboot build + BUILD_IFD="$COREBOOT_DIR/flashmap_descriptor.bin" + if [ ! -f "$BUILD_IFD" ]; then + echo "Info: No IFD validation possible (CONFIG_IFD_BIN_PATH=$IFD_PATH, build IFD not found)" + echo "Skipping IFD vs CBFS size validation" + IFD_VALIDATION_SKIPPED=1 + # Still report CBFS space usage even without IFD + if [ -n "$CBFSTOOL" ] && [ -f "$COREBOOT_DIR/coreboot.rom" ]; then + echo "" + CBFS_OUTPUT=$("$CBFSTOOL" "$COREBOOT_DIR/coreboot.rom" print 2>&1 || true) + FREE_BYTES=$(echo "$CBFS_OUTPUT" | awk '/\(empty\)/ {sum += $4} END {print sum+0}') + FREE_KB=$((FREE_BYTES / 1024)) + echo "CBFS configured size: $CBFS_SIZE ($CBFS_SIZE_DEC bytes)" + echo "CBFS Free Space: $FREE_BYTES bytes ($FREE_KB KiB)" + echo "" + fi + else + IFD_PATH="$BUILD_IFD" + fi +fi + +# Perform IFD validation if we have a path +if [ -z "$IFD_VALIDATION_SKIPPED" ] && [ -f "$IFD_PATH" ] && [ -n "$IFDTOOL" ]; then + echo "===================================================================" + echo "IFD vs CBFS Size Validation" + echo "===================================================================" + + # Try to get platform-specific ifdtool flag + PLATFORM="" + # First: check explicit CONFIG_IFD_CHIPSET + PLATFORM=$(grep '^CONFIG_IFD_CHIPSET=' "$CONFIG_FILE" | cut -d'"' -f2 || true) + # Second: auto-detect for Haswell/Broadwell (they need ifd2 flag) + if [ -z "$PLATFORM" ]; then + if grep -qE 'CONFIG_SOUTHBRIDGE_INTEL_LYNXPOINT|CONFIG_SOUTHBRIDGE_INTEL_WILDCATPOINT' "$CONFIG_FILE"; then + PLATFORM="ifd2" + echo "Auto-detected platform: ifd2 (Haswell/Broadwell)" + fi + fi + + # Run ifdtool to parse the descriptor + IFD_OUTPUT="" + if [ -n "$PLATFORM" ]; then + # Try with platform flag first + IFD_OUTPUT=$("$IFDTOOL" --platform "$PLATFORM" -d "$IFD_PATH" 2>/dev/null || true) + if [ -n "$IFD_OUTPUT" ]; then + echo "Using platform-specific parse: $PLATFORM" + else + # Platform flag failed, fall back to generic + echo "Warning: --platform $PLATFORM failed, using generic parse" + IFD_OUTPUT=$("$IFDTOOL" -d "$IFD_PATH" 2>/dev/null || true) + fi + else + # No platform needed (e.g., Sandy/Ivy Bridge), use generic parse + IFD_OUTPUT=$("$IFDTOOL" -d "$IFD_PATH" 2>/dev/null || true) + fi + + + # Extract BIOS region from IFD output + BIOS_REGION=$(echo "$IFD_OUTPUT" | grep "Flash Region 1 (BIOS):" | head -1) + if [ -z "$BIOS_REGION" ]; then + echo "Error: Could not find BIOS region in IFD" >&2 + exit 1 + fi + + # Parse BIOS region addresses (format: "00021000 - 00bfffff") + BIOS_START=$(echo "$BIOS_REGION" | awk '{print $(NF-2)}') + BIOS_END=$(echo "$BIOS_REGION" | awk '{print $NF}') + BIOS_SIZE=$(( 0x$BIOS_END - 0x$BIOS_START + 1 )) + BIOS_SIZE_KB=$((BIOS_SIZE / 1024)) + CBFS_SIZE_KB=$((CBFS_SIZE_DEC / 1024)) + + echo "IFD BIOS Region: 0x$BIOS_START - 0x$BIOS_END" + echo "IFD BIOS Size: 0x$(printf '%X' $BIOS_SIZE) ($BIOS_SIZE_KB KiB)" + echo "CONFIG_CBFS_SIZE: $CBFS_SIZE ($CBFS_SIZE_KB KiB)" + echo "" + + # CASE 1: CONFIG_CBFS_SIZE is too large + if [ $CBFS_SIZE_DEC -gt $BIOS_SIZE ]; then + OVERFLOW=$(( CBFS_SIZE_DEC - BIOS_SIZE )) + OVERFLOW_KB=$((OVERFLOW / 1024)) + + if [ $FIX_MODE -eq 1 ]; then + # Check if current CBFS content will fit after shrinking + if [ -n "$CBFSTOOL" ] && [ -f "$COREBOOT_DIR/coreboot.rom" ]; then + CBFS_PRINT=$("$CBFSTOOL" "$COREBOOT_DIR/coreboot.rom" print 2>/dev/null || true) + FREE_BYTES=$(echo "$CBFS_PRINT" | awk '/\(empty\)/ {sum += $4} END {print sum+0}') + USED_BYTES=$(( CBFS_SIZE_DEC - FREE_BYTES )) + USED_KB=$((USED_BYTES / 1024)) + + if [ $USED_BYTES -gt $BIOS_SIZE ]; then + echo "❌ Cannot shrink: Current CBFS content ($USED_KB KiB) won't fit in IFD BIOS region ($BIOS_SIZE_KB KiB)" >&2 + echo " Remove payloads/modules before retrying" >&2 + exit 1 + fi + fi + + # Perform shrink to exact IFD size + SHRINK_KB=$(( (CBFS_SIZE_DEC - BIOS_SIZE) / 1024 )) + echo "🔧 Shrinking CONFIG_CBFS_SIZE by $SHRINK_KB KiB" + sed -i "s/CONFIG_CBFS_SIZE=0x[0-9A-Fa-f]*/CONFIG_CBFS_SIZE=0x$(printf '%X' $BIOS_SIZE)/" "$CONFIG_FILE" + echo "✓ Updated: CONFIG_CBFS_SIZE=0x$(printf '%X' $BIOS_SIZE)" + exit 0 + else + # Report error + echo "❌ VALIDATION FAILED: CONFIG_CBFS_SIZE exceeds IFD BIOS region by $OVERFLOW_KB KiB" + echo "" + echo "Fix: Set CONFIG_CBFS_SIZE=0x$(printf '%X' $BIOS_SIZE) in $CONFIG_FILE" + if [ -n "$BOARD" ]; then + echo "Or run: make BOARD=$BOARD fix_cbfs_ifd" + fi + exit 1 + fi + fi + + # CASE 2: CONFIG_CBFS_SIZE equals IFD size + if [ $CBFS_SIZE_DEC -eq $BIOS_SIZE ]; then + echo "✓ CONFIG_CBFS_SIZE exactly matches IFD BIOS region" + fi + + # CASE 3: CONFIG_CBFS_SIZE is smaller than IFD (normal case) + if [ $CBFS_SIZE_DEC -lt $BIOS_SIZE ]; then + FREE_SPACE=$(( BIOS_SIZE - CBFS_SIZE_DEC )) + FREE_SPACE_KB=$((FREE_SPACE / 1024)) + FREE_BYTES=$(( FREE_SPACE % 1024 )) + echo "✓ CONFIG_CBFS_SIZE fits within IFD BIOS region" + + if [ $FREE_SPACE_KB -eq 0 ] && [ $FREE_SPACE -gt 0 ]; then + echo " Unused IFD capacity: $FREE_SPACE bytes (< 1 KiB)" + else + echo " Unused IFD capacity: $FREE_SPACE_KB KiB" + fi + + # Only expand if explicitly requested via fix_cbfs_ifd + if [ $FIX_MODE -eq 1 ]; then + # CRITICAL: Intel SPI flash architecture limitation + # Intel chipsets only memory-map the top 16 MiB of SPI flash to the fixed decode + # window at 0xFF000000-0xFFFFFFFF (just below 4GB boundary). This is where the CPU + # must execute XIP (Execute-In-Place) boot stages. + # + # cbfstool enforces DEFAULT_DECODE_WINDOW_MAX_SIZE = 16 MiB and will fail with + # "Assertion `IS_HOST_SPACE_ADDRESS(host_space_address)' failed" when converting + # XIP stages if CBFS_SIZE > 16 MiB, because the calculated addresses fall outside + # the memory-mapped region. + # + # Exceeding 16 MiB will brick hardware - the CPU cannot fetch boot code from + # addresses outside the decode window. + # + # References: + # - coreboot util/cbfstool/cbfstool.c: DEFAULT_DECODE_WINDOW_MAX_SIZE + # - coreboot util/cbfstool/fit.c: "FIT must reside in the top 16MiB" + # - IS_HOST_SPACE_ADDRESS macro: checks if address is in memory-mapped space + + MAX_CBFS_SIZE=0x1000000 # 16 MiB - Intel SPI decode window limit + + # Calculate safe expansion target: min(IFD BIOS size, 16 MiB limit) + if [ $BIOS_SIZE -gt $MAX_CBFS_SIZE ]; then + TARGET_SIZE=$MAX_CBFS_SIZE + TARGET_SIZE_KB=$((TARGET_SIZE / 1024)) + BIOS_SIZE_MB=$((BIOS_SIZE / 1024 / 1024)) + echo "" + echo "⚠️ IFD BIOS region ($BIOS_SIZE_MB MiB) exceeds Intel 16 MiB decode window limit" + echo " Capping CONFIG_CBFS_SIZE at 0x$(printf '%X' $MAX_CBFS_SIZE) ($TARGET_SIZE_KB KiB)" + echo " Reason: Intel chipsets only memory-map top 16 MiB of SPI flash" + echo " Exceeding this limit will brick hardware (CPU cannot execute boot code)" + else + TARGET_SIZE=$BIOS_SIZE + fi + + GAIN=$(( TARGET_SIZE - CBFS_SIZE_DEC )) + GAIN_KB=$((GAIN / 1024)) + + # Only expand if gain is > 128 KiB + if [ $GAIN -gt 131072 ]; then + echo "" + echo "🔧 Expanding CONFIG_CBFS_SIZE by $GAIN_KB KiB" + sed -i "s/CONFIG_CBFS_SIZE=0x[0-9A-Fa-f]*/CONFIG_CBFS_SIZE=0x$(printf '%X' $TARGET_SIZE)/" "$CONFIG_FILE" + echo "✓ Updated: CONFIG_CBFS_SIZE=0x$(printf '%X' $TARGET_SIZE)" + exit 0 + else + echo " Note: Expansion gain too small ($GAIN_KB KiB < 128 KiB threshold), keeping current size" + fi + fi + fi + + # Report CBFS free space + if [ -n "$CBFSTOOL" ] && [ -f "$COREBOOT_DIR/coreboot.rom" ]; then + echo "" + CBFS_OUTPUT=$("$CBFSTOOL" "$COREBOOT_DIR/coreboot.rom" print 2>&1 || true) + FREE_BYTES=$(echo "$CBFS_OUTPUT" | awk '/\(empty\)/ {sum += $4} END {print sum+0}') + FREE_KB=$((FREE_BYTES / 1024)) + echo "CBFS Free Space: $FREE_BYTES bytes ($FREE_KB KiB)" + fi + + echo "" + echo "===================================================================" + echo "✓ Validation complete" + echo "===================================================================" +fi + +exit 0 diff --git a/modules/coreboot b/modules/coreboot index 160d5d762..c76e4da8d 100644 --- a/modules/coreboot +++ b/modules/coreboot @@ -168,7 +168,15 @@ $(coreboot_module)_configure := \ -C "$(build)/$(coreboot_base_dir)" \ obj="$(build)/$(coreboot_dir)" \ DOTCONFIG="$(build)/$(coreboot_dir)/.config" \ - BUILD_TIMELESS=1 \ + BUILD_TIMELESS=1; \ + echo ""; \ + echo "Validating CONFIG_CBFS_SIZE vs IFD BIOS region..."; \ + $(pwd)/bin/validate_cbfs_ifd_fit.sh \ + --coreboot-dir "$(build)/$(coreboot_dir)" \ + --board-dir "$(build)/$(BOARD)" \ + --config "$(pwd)/$(CONFIG_COREBOOT_CONFIG)" \ + || exit 1; \ + echo "" # Create a dependency from coreboot to the toolchain. Use .heads-toolchain to # mark that the toolchain was built. @@ -239,9 +247,18 @@ $(build)/$(coreboot_dir)/.build: \ # This produces a ROM image that is written with the flashrom program ifneq ($(CONFIG_COREBOOT),) +# Let make rebuild the ROM only when inputs change; downstream logic +# will ensure expensive post-processing (like GPG injection) is skipped when up-to-date. $(build)/$(BOARD)/$(CB_OUTPUT_FILE): $(build)/$(coreboot_dir)/.build # Use coreboot.rom, because custom output files might not be processed by cbfstool "$(build)/$(coreboot_dir)/cbfstool" "$(dir $<)coreboot.rom" print + @echo "" + @echo "Validating final CBFS/IFD configuration..." + @$(pwd)/bin/validate_cbfs_ifd_fit.sh \ + --coreboot-dir "$(build)/$(coreboot_dir)" \ + --board-dir "$(build)/$(BOARD)" \ + --config "$(pwd)/$(CONFIG_COREBOOT_CONFIG)" || exit 1 + @echo "" $(call do-copy,$(dir $<)$(CONFIG_COREBOOT_ROM),$@) @touch $@ # update the time stamp