Los que faltaban son casi todos de la forma `ath11k/WCN6855/hw2.1/regdb.bin -> ../hw2.0/regdb.bin`: un `..` no resuelve si el directorio desde el que cuelga todavía no existe, y yo probaba el destino ANTES del mkdir. Con el directorio creado primero resuelven los 2415 de 2415 — medido sobre el árbol desempaquetado, no deducido. Queda dicho en la receta porque la lectura fácil era la contraria: «WHENCE declara enlaces a blobs que el tarball no trae», que habría llevado a bajar el umbral y sellar un artefacto con 337 firmwares inalcanzables. Y si el destino de verdad no existe, ahora se borra el directorio vacío que el mkdir haya dejado: un directorio vacío en el artefacto es exactamente lo que el §3 de CLAUDE.md prohíbe. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
123 lines
7.8 KiB
TOML
123 lines
7.8 KiB
TOML
# linux-firmware 20260910 — los BLOBS del hardware. La receta que le faltaba al metal.
|
|
#
|
|
# ══ POR QUÉ ESTA RECETA ROMPE LA REGLA, Y POR QUÉ ESTÁ BIEN ════════════════════════════════════
|
|
# takana compila todo desde fuente. Esto NO se compila desde fuente: es firmware binario que Intel,
|
|
# Realtek y el resto no liberan. La soberanía de «reproducir, no confiar» **no aplica** acá, igual
|
|
# que no aplica al microcódigo de la CPU: son DATOS FIJOS, no código que podamos auditar ni
|
|
# rebuildear. Lo que sí podemos hacer —y hacemos— es **fijarlos por contenido**: un tarball con su
|
|
# sha256, como cualquier fuente pineada (ADR 0006). El artefacto es reproducible aunque su
|
|
# contenido sea opaco.
|
|
#
|
|
# ══ QUÉ TODO CIERRA ════════════════════════════════════════════════════════════════════════════
|
|
# `scripts/metal-firmware.sh` lleva desde que se escribió con este comentario al pie:
|
|
#
|
|
# TODO(soberanía): reemplazar FWSRC=host por un pin a un commit de linux-firmware
|
|
# (recipe fetch+extract).
|
|
#
|
|
# Eso es esta receta. Hasta hoy el firmware del metal se COPIABA DEL HOST (`/lib/firmware` de la
|
|
# Artix del usuario), lo que significa que una imagen de takana sólo se podía armar desde una
|
|
# máquina que ya tuviera el firmware correcto instalado por otra distro. Un instalador que hay que
|
|
# construir desde la distro que viene a reemplazar no es un instalador.
|
|
#
|
|
# ══ PESA 648 MB COMPRIMIDO, Y POR ESO NO VA EN NINGÚN PERFIL ═══════════════════════════════════
|
|
# El árbol extraído son ~2 GB: firmware de todas las GPU, WiFi, bluetooth, DSP y controladoras que
|
|
# Linux conoce. Meter eso en una imagen de escritorio sería absurdo. **Esta receta es una BASE, no
|
|
# un miembro de perfil**: quien la consume es `firmware-tigerlake`, que extrae las cuatro familias
|
|
# que una laptop concreta pide. Si mañana hay otro metal, es otra receta derivada de dos líneas —
|
|
# no un segundo fetch de 648 MB.
|
|
#
|
|
# Por eso `perfiles` no la reclama nadie en `targets.toml`: entra por la dep de su derivada.
|
|
name = "linux-firmware"
|
|
version = "20260910"
|
|
license = "LicenseRef-linux-firmware-various" # cada blob trae su propia licencia en WHENCE
|
|
|
|
[source]
|
|
tarball = "https://cdn.kernel.org/pub/linux/kernel/firmware/linux-firmware-20260910.tar.xz"
|
|
# sha256 bajado y calculado el 2026-09-16, y **contrastado contra el `sha256sums.asc` que kernel.org
|
|
# publica firmado en el mismo directorio** — no sólo contra el fichero que bajamos, que sería
|
|
# comprobar que un fichero es igual a sí mismo. Coinciden.
|
|
sha256 = "f3937ca282ba256242e2b6dbe523df8a80007d29ffd61f56d270190865492ea8"
|
|
|
|
# ══ QUÉ TRAE Y QUÉ NO — MEDIDO SOBRE EL TARBALL, NO SUPUESTO ═══════════════════════════════════
|
|
# entradas ........ 5323
|
|
# i915/tgl ........ 15 ✓ (DMC, GuC, HuC de TigerLake)
|
|
# iwlwifi ......... 201 ✓ (en `intel/iwlwifi/`, no en la raíz)
|
|
# intel/sof ....... 0 ✗ ⇒ el audio va por `recipes/sof-firmware.toml`, aparte
|
|
# amd-ucode ....... sí (pero el microcódigo de Intel NO: lo publica Intel por su cuenta)
|
|
|
|
[build]
|
|
compiler = "zig-cc" # entra en el hash aunque no se compile nada; se deja el default del corpus
|
|
target = "x86_64-linux-musl"
|
|
link = "static"
|
|
strip_debug = false # son blobs: no hay secciones de depuración que separar
|
|
flags = []
|
|
|
|
[build.phases]
|
|
configure = "echo 'linux-firmware: nada que configurar — son blobs'"
|
|
compile = "echo 'linux-firmware: nada que compilar — son blobs'"
|
|
# El `install` del propio árbol (`make install`) usa un script que depende de rdfind/symlink
|
|
# rewriting. Se evita: copiar el árbol tal cual es más simple y no pierde los symlinks internos
|
|
# (cp -a los preserva), que es exactamente lo que el kernel espera encontrar.
|
|
install = '''
|
|
set -e
|
|
mkdir -p /out/lib/firmware
|
|
cp -a ./* /out/lib/firmware/
|
|
# WHENCE es el índice de licencias por blob. Se conserva: es la única trazabilidad legal que
|
|
# tienen estos ficheros y `scripts/licencias-*.sh` lo va a querer leer.
|
|
[ -f /out/lib/firmware/WHENCE ] || { echo "linux-firmware: falta WHENCE — el tarball no es el que se espera" >&2; exit 1; }
|
|
|
|
# ── LOS ENLACES, QUE EL TARBALL NO TRAE (medido 2026-09-17) ─────────────────────────────────────
|
|
# Esta receta decía «cp -a preserva los symlinks internos, que es lo que el kernel espera». La
|
|
# premisa es FALSA: `find -type l` sobre el árbol desempaquetado da **0**. Los ~2400 enlaces los
|
|
# crea `copy-firmware.sh` AL INSTALAR, leyéndolos de WHENCE, y esta receta evita ese script (pide
|
|
# rdfind). Resultado: un artefacto de 1,9 G, sellado y reproducible, en el que **iwlwifi no carga**
|
|
# — el driver pide `iwlwifi-QuZ-a0-hr-b0-77.ucode` en la raíz y el blob vive en `intel/iwlwifi/`.
|
|
# Justo el metal al que apunta `firmware-tigerlake` (TigerLake/AX201) se queda sin WiFi.
|
|
# El destino de cada `Link:` es relativo AL DIRECTORIO DEL ENLACE, no a /lib/firmware.
|
|
sed -n 's/^Link:[[:space:]]*//p' /out/lib/firmware/WHENCE \
|
|
| sed 's/[[:space:]]*->[[:space:]]*/\t/' > /tmp/whence-links.tsv
|
|
declarados=$(wc -l < /tmp/whence-links.tsv)
|
|
# TRES PASADAS, y el motivo es medido: WHENCE encadena enlaces —`a -> b` donde `b` es a su vez un
|
|
# enlace que se declara más abajo—. Con una sola pasada el destino todavía no existe cuando toca
|
|
# crear el primero, y quedaban 2078 de 2415 (86 %), que es justo lo que hizo saltar al guardián.
|
|
hechos=0
|
|
for pasada in 1 2 3; do
|
|
antes=$hechos
|
|
while IFS="$(printf '\t')" read -r dst src; do
|
|
[ -n "$dst" ] && [ -n "$src" ] || continue
|
|
dst_abs="/out/lib/firmware/$dst"
|
|
[ -e "$dst_abs" ] && continue # ya hecho (o es un fichero de verdad)
|
|
dir=$(dirname "$dst_abs")
|
|
# El directorio del enlace se crea ANTES de resolver el destino: muchos son de la forma
|
|
# `ath11k/WCN6855/hw2.1/regdb.bin -> ../hw2.0/regdb.bin`, y un `..` no resuelve si el
|
|
# directorio desde el que cuelga todavía no existe. Medido: con el test hecho antes del
|
|
# mkdir fallaban 337 de 2415 y parecía que el tarball venía roto; con el mkdir primero
|
|
# resuelven los 2415. El fallo estaba en la comprobación, no en los datos.
|
|
mkdir -p "$dir"
|
|
if [ ! -e "$dir/$src" ]; then
|
|
rmdir "$dir" 2>/dev/null || true # no dejar un directorio vacío de recuerdo
|
|
continue
|
|
fi
|
|
ln -sf "$src" "$dst_abs"
|
|
hechos=$((hechos+1))
|
|
done < /tmp/whence-links.tsv
|
|
echo "linux-firmware: pasada $pasada — $hechos/$declarados enlaces"
|
|
[ "$hechos" -eq "$antes" ] && break
|
|
done
|
|
[ "$hechos" -ge $((declarados * 9 / 10)) ] || { echo "linux-firmware: sólo $hechos de $declarados enlaces — el árbol no casa con su WHENCE" >&2; exit 1; }
|
|
|
|
# ── Aserción de completitud, CONTRA WHENCE y no contra un número mágico ─────────────────────────
|
|
# Antes decía `> 5000 ficheros`, medido sobre una versión vieja. La 20260910 trae 4889 y el guardián
|
|
# la rechazaba por completa: un umbral a mano envejece y miente en la dirección cara (frena lo bueno).
|
|
# WHENCE viaja DENTRO del tarball y dice cuántos blobs debería haber: se le pregunta a él.
|
|
n=$(find /out/lib/firmware -type f | wc -l)
|
|
decl=$(grep -c "^File:" /out/lib/firmware/WHENCE)
|
|
[ "$n" -ge "$decl" ] || { echo "linux-firmware: $n ficheros y WHENCE declara $decl — el árbol llegó incompleto" >&2; exit 1; }
|
|
echo "linux-firmware: $n ficheros instalados (WHENCE declara $decl)"
|
|
# El control que de verdad importa para el metal de esta casa:
|
|
[ -e /out/lib/firmware/i915 ] || { echo "linux-firmware: falta i915 — el gráfico de TigerLake no arrancaría" >&2; exit 1; }
|
|
'''
|
|
|
|
[deps]
|
|
build = ["busybox"]
|