|
| 1 | +#!/bin/bash |
| 2 | +# SPDX-License-Identifier: GPL-3.0-or-later |
| 3 | +# Validate that CBFS size fits within IFD BIOS region |
| 4 | +# and report space usage statistics |
| 5 | + |
| 6 | +set -e |
| 7 | + |
| 8 | +usage() { |
| 9 | + cat <<EOF |
| 10 | +Usage: $0 --coreboot-dir <path> --board-dir <path> --config <path> [--fix] |
| 11 | +
|
| 12 | +Validates that CONFIG_CBFS_SIZE from coreboot config matches the BIOS region |
| 13 | +size reported by the Intel Flash Descriptor (IFD), and provides space usage |
| 14 | +statistics from cbfstool. |
| 15 | +
|
| 16 | +Options: |
| 17 | + --coreboot-dir Path to coreboot build directory |
| 18 | + --board-dir Path to board build directory |
| 19 | + --config Path to coreboot config file |
| 20 | + --fix Automatically fix CONFIG_CBFS_SIZE to match IFD BIOS region |
| 21 | + --help Show this help message |
| 22 | +
|
| 23 | +Exit codes: |
| 24 | + 0: Validation passed (or fix applied successfully, or tools not available yet) |
| 25 | + 1: Validation failed - CONFIG_CBFS_SIZE exceeds IFD BIOS region |
| 26 | +EOF |
| 27 | + exit "${1:-0}" |
| 28 | +} |
| 29 | + |
| 30 | +# Parse arguments |
| 31 | +FIX_MODE=0 |
| 32 | +while [ $# -gt 0 ]; do |
| 33 | + case "$1" in |
| 34 | + --coreboot-dir) |
| 35 | + COREBOOT_DIR="$2" |
| 36 | + shift 2 |
| 37 | + ;; |
| 38 | + --board-dir) |
| 39 | + BOARD_DIR="$2" |
| 40 | + shift 2 |
| 41 | + ;; |
| 42 | + --config) |
| 43 | + CONFIG_FILE="$2" |
| 44 | + shift 2 |
| 45 | + ;; |
| 46 | + --fix) |
| 47 | + FIX_MODE=1 |
| 48 | + shift |
| 49 | + ;; |
| 50 | + --help) |
| 51 | + usage 0 |
| 52 | + ;; |
| 53 | + *) |
| 54 | + echo "Unknown option: $1" >&2 |
| 55 | + usage 1 |
| 56 | + ;; |
| 57 | + esac |
| 58 | +done |
| 59 | + |
| 60 | +# Validate required arguments |
| 61 | +if [ -z "$COREBOOT_DIR" ] || [ -z "$BOARD_DIR" ] || [ -z "$CONFIG_FILE" ]; then |
| 62 | + echo "Error: Missing required arguments" >&2 |
| 63 | + usage 1 |
| 64 | +fi |
| 65 | + |
| 66 | +# Check if tools exist |
| 67 | +CBFSTOOL="$COREBOOT_DIR/cbfstool" |
| 68 | +IFDTOOL="$COREBOOT_DIR/util/ifdtool/ifdtool" |
| 69 | + |
| 70 | +if [ ! -x "$CBFSTOOL" ]; then |
| 71 | + echo "Warning: cbfstool not found at $CBFSTOOL" >&2 |
| 72 | + echo "Skipping CBFS analysis (coreboot not built yet)" >&2 |
| 73 | + CBFSTOOL="" |
| 74 | +fi |
| 75 | + |
| 76 | +if [ ! -x "$IFDTOOL" ]; then |
| 77 | + echo "Warning: ifdtool not found at $IFDTOOL" >&2 |
| 78 | + echo "Skipping IFD validation (coreboot not built yet)" >&2 |
| 79 | + IFDTOOL="" |
| 80 | +fi |
| 81 | + |
| 82 | +# Extract CONFIG_CBFS_SIZE from config |
| 83 | +CBFS_SIZE=$(grep "^CONFIG_CBFS_SIZE=" "$CONFIG_FILE" | cut -d= -f2) |
| 84 | +if [ -z "$CBFS_SIZE" ]; then |
| 85 | + echo "Error: CONFIG_CBFS_SIZE not found in $CONFIG_FILE" >&2 |
| 86 | + exit 1 |
| 87 | +fi |
| 88 | + |
| 89 | +# Convert to decimal |
| 90 | +CBFS_SIZE_DEC=$((CBFS_SIZE)) |
| 91 | + |
| 92 | +# Extract IFD path from config |
| 93 | +IFD_PATH=$(grep "^CONFIG_IFD_BIN_PATH=" "$CONFIG_FILE" | cut -d'"' -f2) |
| 94 | + |
| 95 | +# Resolve relative IFD path to absolute, preferring coreboot base dir |
| 96 | +if [ -n "$IFD_PATH" ] && [[ "$IFD_PATH" != /* ]] && [[ "$IFD_PATH" != *"@"* ]]; then |
| 97 | + COREBOOT_BASE_DIR="$(dirname "$COREBOOT_DIR")" |
| 98 | + if [ -d "$COREBOOT_BASE_DIR" ]; then |
| 99 | + IFD_PATH="$COREBOOT_BASE_DIR/$IFD_PATH" |
| 100 | + else |
| 101 | + IFD_PATH="$PWD/$IFD_PATH" |
| 102 | + fi |
| 103 | +fi |
| 104 | + |
| 105 | +# If IFD path uses @BLOB_DIR@, resolve it |
| 106 | +# @BLOB_DIR@ typically expands to blobs/ from the repo root |
| 107 | +if [[ "$IFD_PATH" == *"@BLOB_DIR@"* ]]; then |
| 108 | + # Try to find the Heads repo root (go up from coreboot-dir) |
| 109 | + # COREBOOT_DIR is like /home/user/heads/build/x86/coreboot-25.09/BOARD |
| 110 | + # So we need to go up 4 levels: BOARD -> coreboot-25.09 -> x86 -> build -> heads |
| 111 | + if [ -d "$COREBOOT_DIR" ]; then |
| 112 | + REPO_ROOT=$(cd "$COREBOOT_DIR/../../../../" 2>/dev/null && pwd || echo "") |
| 113 | + if [ -n "$REPO_ROOT" ] && [ -d "$REPO_ROOT/blobs" ]; then |
| 114 | + IFD_PATH="${IFD_PATH/@BLOB_DIR@/$REPO_ROOT/blobs}" |
| 115 | + fi |
| 116 | + fi |
| 117 | +fi |
| 118 | + |
| 119 | +# If IFD path uses @BLOB_DIR@, we need to resolve it |
| 120 | +# For now, skip validation if no IFD or if path is not resolved |
| 121 | +if [ -z "$IFD_PATH" ] || [[ "$IFD_PATH" == *"@"* ]]; then |
| 122 | + # Try to find the IFD in the coreboot build |
| 123 | + BUILD_IFD="$COREBOOT_DIR/flashmap_descriptor.bin" |
| 124 | + if [ ! -f "$BUILD_IFD" ]; then |
| 125 | + echo "Info: No IFD validation possible (CONFIG_IFD_BIN_PATH=$IFD_PATH, build IFD not found)" |
| 126 | + echo "Skipping IFD vs CBFS size validation" |
| 127 | + IFD_VALIDATION_SKIPPED=1 |
| 128 | + # Still report CBFS space usage even without IFD |
| 129 | + if [ -n "$CBFSTOOL" ] && [ -f "$COREBOOT_DIR/coreboot.rom" ]; then |
| 130 | + echo "" |
| 131 | + CBFS_OUTPUT=$("$CBFSTOOL" "$COREBOOT_DIR/coreboot.rom" print 2>&1 || true) |
| 132 | + FREE_BYTES=$(echo "$CBFS_OUTPUT" | awk '/\(empty\)/ {sum += $4} END {print sum+0}') |
| 133 | + FREE_KB=$((FREE_BYTES / 1024)) |
| 134 | + echo "CBFS configured size: $CBFS_SIZE ($CBFS_SIZE_DEC bytes)" |
| 135 | + echo "CBFS Free Space: $FREE_BYTES bytes ($FREE_KB KiB)" |
| 136 | + echo "" |
| 137 | + fi |
| 138 | + else |
| 139 | + IFD_PATH="$BUILD_IFD" |
| 140 | + fi |
| 141 | +fi |
| 142 | + |
| 143 | +# Perform IFD validation if we have a path |
| 144 | +if [ -z "$IFD_VALIDATION_SKIPPED" ] && [ -f "$IFD_PATH" ] && [ -n "$IFDTOOL" ]; then |
| 145 | + echo "===================================================================" |
| 146 | + echo "IFD vs CBFS Size Validation" |
| 147 | + echo "===================================================================" |
| 148 | + |
| 149 | + # Try to get platform-specific ifdtool flag |
| 150 | + PLATFORM="" |
| 151 | + # First: check explicit CONFIG_IFD_CHIPSET |
| 152 | + PLATFORM=$(grep '^CONFIG_IFD_CHIPSET=' "$CONFIG_FILE" | cut -d'"' -f2 || true) |
| 153 | + # Second: auto-detect for Haswell/Broadwell (they need ifd2 flag) |
| 154 | + if [ -z "$PLATFORM" ]; then |
| 155 | + if grep -qE 'CONFIG_SOUTHBRIDGE_INTEL_LYNXPOINT|CONFIG_SOUTHBRIDGE_INTEL_WILDCATPOINT' "$CONFIG_FILE"; then |
| 156 | + PLATFORM="ifd2" |
| 157 | + echo "Auto-detected platform: ifd2 (Haswell/Broadwell)" |
| 158 | + fi |
| 159 | + fi |
| 160 | + |
| 161 | + # Run ifdtool to parse the descriptor |
| 162 | + IFD_OUTPUT="" |
| 163 | + if [ -n "$PLATFORM" ]; then |
| 164 | + # Try with platform flag first |
| 165 | + IFD_OUTPUT=$("$IFDTOOL" --platform "$PLATFORM" -d "$IFD_PATH" 2>/dev/null || true) |
| 166 | + if [ -n "$IFD_OUTPUT" ]; then |
| 167 | + echo "Using platform-specific parse: $PLATFORM" |
| 168 | + else |
| 169 | + # Platform flag failed, fall back to generic |
| 170 | + echo "Warning: --platform $PLATFORM failed, using generic parse" |
| 171 | + IFD_OUTPUT=$("$IFDTOOL" -d "$IFD_PATH" 2>/dev/null || true) |
| 172 | + fi |
| 173 | + else |
| 174 | + # No platform needed (e.g., Sandy/Ivy Bridge), use generic parse |
| 175 | + IFD_OUTPUT=$("$IFDTOOL" -d "$IFD_PATH" 2>/dev/null || true) |
| 176 | + fi |
| 177 | + |
| 178 | + |
| 179 | + # Extract BIOS region from IFD output |
| 180 | + BIOS_REGION=$(echo "$IFD_OUTPUT" | grep "Flash Region 1 (BIOS):" | head -1) |
| 181 | + if [ -z "$BIOS_REGION" ]; then |
| 182 | + echo "Error: Could not find BIOS region in IFD" >&2 |
| 183 | + exit 1 |
| 184 | + fi |
| 185 | + |
| 186 | + # Parse BIOS region addresses (format: "00021000 - 00bfffff") |
| 187 | + BIOS_START=$(echo "$BIOS_REGION" | awk '{print $(NF-2)}') |
| 188 | + BIOS_END=$(echo "$BIOS_REGION" | awk '{print $NF}') |
| 189 | + BIOS_SIZE=$(( 0x$BIOS_END - 0x$BIOS_START + 1 )) |
| 190 | + BIOS_SIZE_KB=$((BIOS_SIZE / 1024)) |
| 191 | + CBFS_SIZE_KB=$((CBFS_SIZE_DEC / 1024)) |
| 192 | + |
| 193 | + echo "IFD BIOS Region: 0x$BIOS_START - 0x$BIOS_END" |
| 194 | + echo "IFD BIOS Size: 0x$(printf '%X' $BIOS_SIZE) ($BIOS_SIZE_KB KiB)" |
| 195 | + echo "CONFIG_CBFS_SIZE: $CBFS_SIZE ($CBFS_SIZE_KB KiB)" |
| 196 | + echo "" |
| 197 | + |
| 198 | + # CASE 1: CONFIG_CBFS_SIZE is too large |
| 199 | + if [ $CBFS_SIZE_DEC -gt $BIOS_SIZE ]; then |
| 200 | + OVERFLOW=$(( CBFS_SIZE_DEC - BIOS_SIZE )) |
| 201 | + OVERFLOW_KB=$((OVERFLOW / 1024)) |
| 202 | + |
| 203 | + if [ $FIX_MODE -eq 1 ]; then |
| 204 | + # Check if current CBFS content will fit after shrinking |
| 205 | + if [ -n "$CBFSTOOL" ] && [ -f "$COREBOOT_DIR/coreboot.rom" ]; then |
| 206 | + CBFS_PRINT=$("$CBFSTOOL" "$COREBOOT_DIR/coreboot.rom" print 2>/dev/null || true) |
| 207 | + FREE_BYTES=$(echo "$CBFS_PRINT" | awk '/\(empty\)/ {sum += $4} END {print sum+0}') |
| 208 | + USED_BYTES=$(( CBFS_SIZE_DEC - FREE_BYTES )) |
| 209 | + USED_KB=$((USED_BYTES / 1024)) |
| 210 | + |
| 211 | + if [ $USED_BYTES -gt $BIOS_SIZE ]; then |
| 212 | + echo "❌ Cannot shrink: Current CBFS content ($USED_KB KiB) won't fit in IFD BIOS region ($BIOS_SIZE_KB KiB)" >&2 |
| 213 | + echo " Remove payloads/modules before retrying" >&2 |
| 214 | + exit 1 |
| 215 | + fi |
| 216 | + fi |
| 217 | + |
| 218 | + # Perform shrink to exact IFD size |
| 219 | + SHRINK_KB=$(( (CBFS_SIZE_DEC - BIOS_SIZE) / 1024 )) |
| 220 | + echo "🔧 Shrinking CONFIG_CBFS_SIZE by $SHRINK_KB KiB" |
| 221 | + sed -i "s/CONFIG_CBFS_SIZE=0x[0-9A-Fa-f]*/CONFIG_CBFS_SIZE=0x$(printf '%X' $BIOS_SIZE)/" "$CONFIG_FILE" |
| 222 | + echo "✓ Updated: CONFIG_CBFS_SIZE=0x$(printf '%X' $BIOS_SIZE)" |
| 223 | + exit 0 |
| 224 | + else |
| 225 | + # Report error |
| 226 | + echo "❌ VALIDATION FAILED: CONFIG_CBFS_SIZE exceeds IFD BIOS region by $OVERFLOW_KB KiB" |
| 227 | + echo "" |
| 228 | + echo "Fix: Set CONFIG_CBFS_SIZE=0x$(printf '%X' $BIOS_SIZE) in $CONFIG_FILE" |
| 229 | + if [ -n "$BOARD" ]; then |
| 230 | + echo "Or run: make BOARD=$BOARD fix_cbfs_ifd" |
| 231 | + fi |
| 232 | + exit 1 |
| 233 | + fi |
| 234 | + fi |
| 235 | + |
| 236 | + # CASE 2: CONFIG_CBFS_SIZE equals IFD size |
| 237 | + if [ $CBFS_SIZE_DEC -eq $BIOS_SIZE ]; then |
| 238 | + echo "✓ CONFIG_CBFS_SIZE exactly matches IFD BIOS region" |
| 239 | + fi |
| 240 | + |
| 241 | + # CASE 3: CONFIG_CBFS_SIZE is smaller than IFD (normal case) |
| 242 | + if [ $CBFS_SIZE_DEC -lt $BIOS_SIZE ]; then |
| 243 | + FREE_SPACE=$(( BIOS_SIZE - CBFS_SIZE_DEC )) |
| 244 | + FREE_SPACE_KB=$((FREE_SPACE / 1024)) |
| 245 | + FREE_BYTES=$(( FREE_SPACE % 1024 )) |
| 246 | + echo "✓ CONFIG_CBFS_SIZE fits within IFD BIOS region" |
| 247 | + |
| 248 | + if [ $FREE_SPACE_KB -eq 0 ] && [ $FREE_SPACE -gt 0 ]; then |
| 249 | + echo " Unused IFD capacity: $FREE_SPACE bytes (< 1 KiB)" |
| 250 | + else |
| 251 | + echo " Unused IFD capacity: $FREE_SPACE_KB KiB" |
| 252 | + fi |
| 253 | + |
| 254 | + # Only expand if explicitly requested via fix_cbfs_ifd |
| 255 | + if [ $FIX_MODE -eq 1 ]; then |
| 256 | + # CRITICAL: Intel SPI flash architecture limitation |
| 257 | + # Intel chipsets only memory-map the top 16 MiB of SPI flash to the fixed decode |
| 258 | + # window at 0xFF000000-0xFFFFFFFF (just below 4GB boundary). This is where the CPU |
| 259 | + # must execute XIP (Execute-In-Place) boot stages. |
| 260 | + # |
| 261 | + # cbfstool enforces DEFAULT_DECODE_WINDOW_MAX_SIZE = 16 MiB and will fail with |
| 262 | + # "Assertion `IS_HOST_SPACE_ADDRESS(host_space_address)' failed" when converting |
| 263 | + # XIP stages if CBFS_SIZE > 16 MiB, because the calculated addresses fall outside |
| 264 | + # the memory-mapped region. |
| 265 | + # |
| 266 | + # Exceeding 16 MiB will brick hardware - the CPU cannot fetch boot code from |
| 267 | + # addresses outside the decode window. |
| 268 | + # |
| 269 | + # References: |
| 270 | + # - coreboot util/cbfstool/cbfstool.c: DEFAULT_DECODE_WINDOW_MAX_SIZE |
| 271 | + # - coreboot util/cbfstool/fit.c: "FIT must reside in the top 16MiB" |
| 272 | + # - IS_HOST_SPACE_ADDRESS macro: checks if address is in memory-mapped space |
| 273 | + |
| 274 | + MAX_CBFS_SIZE=0x1000000 # 16 MiB - Intel SPI decode window limit |
| 275 | + |
| 276 | + # Calculate safe expansion target: min(IFD BIOS size, 16 MiB limit) |
| 277 | + if [ $BIOS_SIZE -gt $MAX_CBFS_SIZE ]; then |
| 278 | + TARGET_SIZE=$MAX_CBFS_SIZE |
| 279 | + TARGET_SIZE_KB=$((TARGET_SIZE / 1024)) |
| 280 | + BIOS_SIZE_MB=$((BIOS_SIZE / 1024 / 1024)) |
| 281 | + echo "" |
| 282 | + echo "⚠️ IFD BIOS region ($BIOS_SIZE_MB MiB) exceeds Intel 16 MiB decode window limit" |
| 283 | + echo " Capping CONFIG_CBFS_SIZE at 0x$(printf '%X' $MAX_CBFS_SIZE) ($TARGET_SIZE_KB KiB)" |
| 284 | + echo " Reason: Intel chipsets only memory-map top 16 MiB of SPI flash" |
| 285 | + echo " Exceeding this limit will brick hardware (CPU cannot execute boot code)" |
| 286 | + else |
| 287 | + TARGET_SIZE=$BIOS_SIZE |
| 288 | + fi |
| 289 | + |
| 290 | + GAIN=$(( TARGET_SIZE - CBFS_SIZE_DEC )) |
| 291 | + GAIN_KB=$((GAIN / 1024)) |
| 292 | + |
| 293 | + # Only expand if gain is > 128 KiB |
| 294 | + if [ $GAIN -gt 131072 ]; then |
| 295 | + echo "" |
| 296 | + echo "🔧 Expanding CONFIG_CBFS_SIZE by $GAIN_KB KiB" |
| 297 | + sed -i "s/CONFIG_CBFS_SIZE=0x[0-9A-Fa-f]*/CONFIG_CBFS_SIZE=0x$(printf '%X' $TARGET_SIZE)/" "$CONFIG_FILE" |
| 298 | + echo "✓ Updated: CONFIG_CBFS_SIZE=0x$(printf '%X' $TARGET_SIZE)" |
| 299 | + exit 0 |
| 300 | + else |
| 301 | + echo " Note: Expansion gain too small ($GAIN_KB KiB < 128 KiB threshold), keeping current size" |
| 302 | + fi |
| 303 | + fi |
| 304 | + fi |
| 305 | + |
| 306 | + # Report CBFS free space |
| 307 | + if [ -n "$CBFSTOOL" ] && [ -f "$COREBOOT_DIR/coreboot.rom" ]; then |
| 308 | + echo "" |
| 309 | + CBFS_OUTPUT=$("$CBFSTOOL" "$COREBOOT_DIR/coreboot.rom" print 2>&1 || true) |
| 310 | + FREE_BYTES=$(echo "$CBFS_OUTPUT" | awk '/\(empty\)/ {sum += $4} END {print sum+0}') |
| 311 | + FREE_KB=$((FREE_BYTES / 1024)) |
| 312 | + echo "CBFS Free Space: $FREE_BYTES bytes ($FREE_KB KiB)" |
| 313 | + fi |
| 314 | + |
| 315 | + echo "" |
| 316 | + echo "===================================================================" |
| 317 | + echo "✓ Validation complete" |
| 318 | + echo "===================================================================" |
| 319 | +fi |
| 320 | + |
| 321 | +exit 0 |
0 commit comments