# firmware-metal — el firmware de UNA MÁQUINA PERSONAL x86, completo. # # ══ POR QUÉ ESTA RECETA REEMPLAZA A `firmware-intel` ═══════════════════════════════════════════ # `firmware-intel` (publicada unas horas antes, el 2026-10-15) llevaba 120 MB: iwlwifi, i915, # regulatory y SOF. Dejaba afuera **el ethernet, el bluetooth y las GPU que no son Intel**, y el # operador lo dijo sin anestesia: # # *«que ni ethernet funciona. no sé por qué me lo pones tan pobre, como si no supieras lo que # necesita un sistema»* # # Tenía razón, y el error era de método: recorté por tamaño **antes de medir**. Con el árbol ya # sellado se puede medir, y lo que una PC puede necesitar es más de la mitad del tarball. La lista de # abajo sale de `du -sh` sobre el artefacto de `linux-firmware`, familia por familia, no de criterio. # # ══ LO QUE ENTRA, CON SU PESO MEDIDO (2026-10-15, linux-firmware 20260910) ═════════════════════ # # WIFI ─ 641 MB # intel/iwlwifi 287M · mrvl 81M · mediatek 71M · ath11k 70M · ath12k 47M · rtw89 34M # ath10k 20M · brcm 12M · ti-connectivity 9M · cypress 6M · libertas 2M · rtlwifi 1M # rtw88 0,9M · ath9k_htc 0,1M # BLUETOOTH ─ 33 MB intel/ibt-* 27M · qca 5M · rtl_bt 1,5M # ETHERNET ─ 8 MB cxgb4 4,6M · bnx2x 2,6M · rtl_nic 0,3M · bnx2 · cxgb3 · qlogic · tigon # GRÁFICOS ─ 309 MB nvidia 154M · amdgpu 115M · i915 27M · radeon 7,4M · xe 5,4M # PORTÁTIL ─ 49 MB intel/ipu 26M (cámara IPU6) · intel/vsc 11M · intel/ish 2,7M (sensor hub, # y por ahí pasa el touchpad I2C de muchos portátiles nuevos) # AUDIO cirrus 8,6M + el SOF que aporta la dep `sof-firmware` # regulatory.db sin él el supplicant no puede elegir canal # # ≈ 1,05 GB. El `product-rootfs` pesa 264–358 MB, así que esto multiplica el medio por cuatro. # Es el precio de que la imagen arranque con red en una máquina que nadie midió antes, y es la # decisión que el operador tomó explícitamente. # # ══ LO QUE QUEDA AFUERA, Y NO ES OLVIDO ════════════════════════════════════════════════════════ # qcom 525M SoC ARM (Adreno, Snapdragon). No hay PC x86 que lo pida. # netronome 140M · mellanox 107M · qed 20M · liquidio 5M · intel/ice 5,4M # NIC de datacenter de 25/100 G. Si alguna vez takana corre en un servidor con # una de ésas, es otra derivada de dos líneas — así está pensado el mecanismo. # intel/qat 3,7M · intel/vpu 4M acelerador de cripto y NPU: no hacen arrancar nada. # # ══ POR QUÉ LAS GPU NO-INTEL **SÍ** VAN, corrigiendo lo que escribí en `firmware-intel` ════════ # Ahí dije que `amdgpu`/`radeon` quedaban afuera «por un motivo técnico: takana pinta por # mesa-swrast, software puro». Eso estaba MAL y es el error más caro de los dos: sin su firmware # **`amdgpu` no inicializa el dispositivo**, y sin dispositivo no hay KMS ni `/dev/dri/card0` ⇒ el # compositor no tiene a qué abrirle nada y la pantalla queda muerta. Pintar por software no evita # necesitar que la GPU encienda. Lo mismo `nouveau` con el GSP de las NVIDIA nuevas. # # ══ SIN COMPRIMIR, Y CON LOS ENLACES ═══════════════════════════════════════════════════════════ # El kernel `linux-metal` no habilita `FW_LOADER_COMPRESS`: un `.zst` se lee igual que un fichero # ausente. Y se copia **preservando los symlinks** (`cp -a`), porque el nombre que el driver pide es # muchas veces el del ENLACE y no el del blob: `iwlwifi-QuZ-a0-hr-b0-77.ucode` cuelga de la raíz y # apunta a `intel/iwlwifi/…`. Son los 2415 enlaces que `linux-firmware` reconstruye desde WHENCE; # copiar con `cp -L` los resolvería y el driver volvería a no encontrar su nombre. name = "firmware-metal" version = "20260910" # sigue a linux-firmware: no tiene versión propia license = "LicenseRef-linux-firmware-various" [source] dir = "firmware-metal" [build] compiler = "zig-cc" target = "x86_64-linux-musl" link = "static" strip_debug = false flags = [] [build.phases] configure = "echo 'firmware-metal: nada que configurar'" compile = "echo 'firmware-metal: nada que compilar — son blobs de la dep'" install = ''' set -e SRC=/lib/firmware DST=/out/lib/firmware [ -d "$SRC" ] || { echo "firmware-metal: no está el árbol de linux-firmware en $SRC" >&2; exit 1; } [ -f "$SRC/WHENCE" ] || { echo "firmware-metal: falta WHENCE — la dep no se materializó entera" >&2; exit 1; } mkdir -p "$DST" # ── 1. Las familias, con sus enlaces internos intactos ──────────────────────────────────────── FAMILIAS="intel/iwlwifi rtw88 rtw89 rtlwifi ath9k_htc ath10k ath11k ath12k brcm mediatek mrvl ti-connectivity cypress libertas rtl_bt qca rtl_nic bnx2 bnx2x tigon cxgb3 cxgb4 qlogic i915 xe amdgpu radeon nvidia intel/ish intel/vsc intel/ipu cirrus" puestas=0 for d in $FAMILIAS; do if [ -d "$SRC/$d" ]; then mkdir -p "$DST/$(dirname "$d")" cp -a "$SRC/$d" "$DST/$(dirname "$d")/" puestas=$((puestas+1)) else # Se dice, no se calla: una familia que desaparece entre versiones de linux-firmware es # una capacidad que la imagen pierde en silencio. echo "firmware-metal: ⚠ $SRC/$d no existe en esta versión del árbol" >&2 fi done [ "$puestas" -ge 25 ] || { echo "firmware-metal: sólo $puestas familias de las 32 declaradas — el árbol no es el que se espera" >&2; exit 1; } # ── 2. Bluetooth Intel y la base regulatoria: ficheros, no directorios ──────────────────────── mkdir -p "$DST/intel" bt=0 for f in "$SRC"/intel/ibt-*; do [ -e "$f" ] || continue cp -a "$f" "$DST/intel/" && bt=$((bt+1)) done [ "$bt" -gt 0 ] || echo "firmware-metal: ⚠ 0 blobs de bluetooth Intel" >&2 for f in regulatory.db regulatory.db.p7s; do [ -e "$SRC/$f" ] && cp -a "$SRC/$f" "$DST/" done [ -e "$DST/regulatory.db" ] || { echo "firmware-metal: falta regulatory.db — ningún wifi podría elegir canal" >&2; exit 1; } # ── 3. Audio SOF (lo aporta la dep sof-firmware) ────────────────────────────────────────────── # Los `.ri` firmados van TODOS: el que carga lo decide el PCI id de la máquina, no nosotros. El # audio no aborta la receta —una máquina sin sonido es usable y una sin red no— pero se dice. snd=0 if [ -d "$SRC/intel/sof" ]; then cp -a "$SRC/intel/sof" "$DST/intel/" && snd=$((snd+1)) fi if [ -d "$SRC/intel/sof-tplg" ]; then cp -a "$SRC/intel/sof-tplg" "$DST/intel/" && snd=$((snd+1)) fi [ "$snd" -gt 0 ] || echo "firmware-metal: ⚠ sin SOF — esta imagen va a quedar sin audio HDA" >&2 # ── 4. LOS ENLACES DE LA RAÍZ: el nombre que el driver PIDE ─────────────────────────────────── # `iwlwifi-QuZ-a0-hr-b0-77.ucode` vive en la raíz como ENLACE a `intel/iwlwifi/…`. Sin esta vuelta, # el blob está adentro y el driver —que pide por el nombre de la raíz— no lo encuentra: es # exactamente el fallo que `linux-firmware` documenta en su propio encabezado. raiz=0 for l in "$SRC"/*; do [ -L "$l" ] || continue n=$(basename "$l") destino=$(readlink "$l") # Sólo si su destino cayó dentro de lo que copiamos: un enlace colgante es peor que no tenerlo, # porque `request_firmware` abre y falla en vez de buscar el siguiente candidato. if [ -e "$DST/$destino" ]; then ln -sf "$destino" "$DST/$n" raiz=$((raiz+1)) fi done echo "firmware-metal: $raiz enlaces de la raíz reconstruidos" # ── 5. Enlaces colgantes DENTRO de lo copiado: se resuelven desde el fuente ──────────────────── # Una familia puede enlazar a un blob de otra que no copiamos (`ath11k/… -> ../ath10k/…`). Si el # destino existe en el árbol original, se trae el fichero; si no, se retira el enlace. colgantes=0; traidos=0 for l in $(find "$DST" -type l); do [ -e "$l" ] && continue rel=$(echo "$l" | sed "s|^$DST/||") if [ -e "$SRC/$rel" ]; then rm -f "$l" mkdir -p "$(dirname "$l")" cp -L "$SRC/$rel" "$l" && traidos=$((traidos+1)) else rm -f "$l"; colgantes=$((colgantes+1)) fi done echo "firmware-metal: $traidos enlaces colgantes resueltos copiando el blob, $colgantes retirados" # ── 6. Los controles que importan para el metal de un desconocido ───────────────────────────── falta=0 for c in intel/iwlwifi rtw89 ath11k mediatek brcm rtl_nic i915 amdgpu nvidia regulatory.db; do [ -e "$DST/$c" ] || { echo "firmware-metal: ✗ falta $c" >&2; falta=$((falta+1)); } done # El wifi Intel es el caso más probable de todos: si no quedaron sus .ucode, esto no sirve. w=$(find "$DST/intel/iwlwifi" -name "*.ucode" 2>/dev/null | wc -l) [ "$w" -ge 40 ] || { echo "firmware-metal: sólo $w .ucode de iwlwifi (esperaba >40)" >&2; falta=$((falta+1)); } [ "$falta" = 0 ] || { echo "firmware-metal: $falta control(es) en rojo" >&2; exit 1; } n=$(find "$DST" \( -type f -o -type l \) | wc -l) k=$(du -sk "$DST" | cut -f1) echo "firmware-metal: $n entradas, $((k/1024)) MiB — wifi/bluetooth/ethernet/gráficos/audio de una PC x86" ''' [deps] build = ["linux-firmware", "sof-firmware", "wireless-regdb"]