diff --git a/recipes/firmware-tigerlake.toml b/recipes/firmware-tigerlake.toml new file mode 100644 index 00000000..7941e2db --- /dev/null +++ b/recipes/firmware-tigerlake.toml @@ -0,0 +1,125 @@ +# firmware-tigerlake — los blobs que pide EXACTAMENTE la laptop del usuario, y nada más. +# +# ══ QUÉ MÁQUINA ES ═════════════════════════════════════════════════════════════════════════════ +# Medido el 2026-09-16 sobre el metal real (`lspci -nn` + `lsmod`): +# +# 00:02.0 Intel TigerLake-LP GT2 [Iris Xe Graphics] [8086:9a49] ⇒ i915 / xe +# 00:14.3 Intel Wi-Fi 6 AX201 [8086:a0f0] ⇒ iwlwifi +# 00:1f.3 Intel 500 Series On-Package HD Audio [8086:a0c8] ⇒ snd_sof_pci_intel_tgl +# +# Es la misma máquina para la que ya se escribió `scripts/metal-firmware.sh`, que nombra estas tres +# familias y dice por qué: «son las tres familias de blobs que el metal necesita y ninguna se +# construye desde fuente». Esta receta es ese script convertido en artefacto sellado, con el +# firmware viniendo de `linux-firmware` pineado en vez de `/lib/firmware` del host. +# +# ══ POR QUÉ DERIVADA Y NO UN SEGUNDO FETCH ═════════════════════════════════════════════════════ +# El árbol completo son ~2 GB extraídos. Esta máquina usa unos 25 MB de eso. Derivar de +# `linux-firmware` cuesta segundos y no mueve su hash, igual que `atuq` sobre `firefox` (SDD 26). +# Otro metal = otra receta derivada de dos líneas, no otro tarball de 648 MB. +# +# ══ SIN COMPRIMIR, A PROPÓSITO ═════════════════════════════════════════════════════════════════ +# Los blobs se instalan planos (`.ucode`, `.bin`, `.ri`), no `.zst`. El kernel `linux-metal` **no +# habilita `FW_LOADER_COMPRESS`**, así que un firmware comprimido no se carga y el fallo aparece +# como «no WiFi» sin una línea que lo explique. Es la razón que ya está escrita en +# `metal-firmware.sh` y se repite acá porque es justo el tipo de cosa que se pierde al refactorizar. +# +# ⚠ **PUNTO DE PARTIDA, NO SELLADA.** Escrita desde el laptop, que no tiene lab ni store. Las +# aserciones de abajo están puestas para que el PRIMER BUILD falle ruidoso en vez de producir un +# artefacto flaco: un /lib/firmware a medias no falla al construir, falla meses después en el metal +# como «no hay WiFi» y nadie lo asocia con esta receta. +name = "firmware-tigerlake" +version = "20260910" # sigue a linux-firmware: no tiene versión propia +license = "LicenseRef-linux-firmware-various" + +[source] +# No tiene fuente propia: todo su contenido sale de la dep. Se apunta al árbol vacío del repo por +# la misma razón que `atuq` usa `source.dir` — la identidad del artefacto son sus fases + su dep, +# y el `ArtifactHash` ya las incluye. +dir = "firmware-tigerlake" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "static" +strip_debug = false +flags = [] + +[build.phases] +configure = "echo 'firmware-tigerlake: nada que configurar'" +compile = "echo 'firmware-tigerlake: nada que compilar — son blobs de la dep'" +install = ''' +set -e +SRC=/lib/firmware +DST=/out/lib/firmware + +# ── LAS ASERCIONES VAN PRIMERO (CLAUDE.md §3) ──────────────────────────────────────────────── +# Un derivado que no encuentra su base produciría un /lib/firmware con cuatro ficheros, `Store::has` +# lo daría por presente, y el fallo saldría el día del arranque en metal como «no hay WiFi». +[ -d "$SRC" ] || { echo "firmware-tigerlake: no está el árbol de linux-firmware en $SRC" >&2; exit 1; } +[ -f "$SRC/WHENCE" ] || { echo "firmware-tigerlake: falta WHENCE — la dep no se materializó entera" >&2; exit 1; } + +mkdir -p "$DST/intel/iwlwifi" "$DST/i915" "$DST/intel/sof" "$DST/intel/sof-tplg" + +# ── 1. WiFi: Intel AX201 ───────────────────────────────────────────────────────────────────── +# El driver elige la API MÁS ALTA que soporta, así que se copian TODAS las revisiones de las dos +# familias y no sólo la última: qué número pide depende del kernel, no de nosotros. +# Upstream movió los .ucode a intel/iwlwifi/ y dejó symlinks en la raíz; se cubren los dos sitios. +wifi=0 +for pat in "iwlwifi-QuZ-a0-hr-b0-*.ucode" "iwlwifi-cc-a0-*.ucode"; do + for d in "$SRC/intel/iwlwifi" "$SRC"; do + for f in "$d"/$pat; do + [ -f "$f" ] || continue + cp -L "$f" "$DST/intel/iwlwifi/" && wifi=$((wifi+1)) + done + done +done +# El driver busca en /lib/firmware antes que en intel/iwlwifi ⇒ se dejan los dos caminos. +for f in "$DST"/intel/iwlwifi/*.ucode; do + [ -f "$f" ] && cp -L "$f" "$DST/" || true +done +[ "$wifi" -gt 0 ] || { echo "firmware-tigerlake: 0 blobs de iwlwifi — el AX201 no va a asociar" >&2; exit 1; } + +# regulatory.db: cfg80211 lo verifica contra la clave built-in del kernel. Sin él el WiFi queda +# limitado al dominio world y en la práctica no ve la mitad de los canales. +for f in regulatory.db regulatory.db.p7s; do + [ -f "$SRC/$f" ] && cp -L "$SRC/$f" "$DST/" +done +[ -f "$DST/regulatory.db" ] || { echo "firmware-tigerlake: falta regulatory.db" >&2; exit 1; } + +# ── 2. Gráficos: i915 / xe en TigerLake ────────────────────────────────────────────────────── +# DMC = display microcontroller (sin él no hay ahorro de energía del display ni, en algunos +# kernels, modeset fiable). GuC/HuC = submission y decodificación por hardware. +gfx=0 +for f in "$SRC"/i915/tgl_*.bin; do + [ -f "$f" ] || continue + cp -L "$f" "$DST/i915/" && gfx=$((gfx+1)) +done +[ "$gfx" -gt 0 ] || { echo "firmware-tigerlake: 0 blobs de i915 tgl — el display va a ir sin DMC" >&2; exit 1; } + +# ── 3. Audio: SOF en el DSP de TigerLake ───────────────────────────────────────────────────── +# El SOF **no viene en linux-firmware** — se midió sobre el tarball (0 coincidencias con +# `intel/sof`) y por eso existe `recipes/sof-firmware.toml`, que es dep de esta receta. Acá sólo se +# recoge lo que aquélla ya dejó en su sitio: es la misma jugada que con la dep de linux-firmware. +snd=0 +if [ -d "$SRC/intel/sof" ]; then + mkdir -p "$DST/intel/sof/intel-signed" + for f in "$SRC"/intel/sof/intel-signed/sof-tgl*.ri; do + [ -f "$f" ] || continue + cp "$f" "$DST/intel/sof/intel-signed/" + ln -sf "intel-signed/$(basename "$f")" "$DST/intel/sof/$(basename "$f")" + snd=$((snd+1)) + done +fi +if [ -d "$SRC/intel/sof-tplg" ]; then + for f in "$SRC"/intel/sof-tplg/sof-hda-generic*.tplg; do + [ -f "$f" ] && cp -L "$f" "$DST/intel/sof-tplg/" && snd=$((snd+1)) + done +fi +[ "$snd" -gt 0 ] || { echo "firmware-tigerlake: 0 blobs de SOF — ¿se materializó la dep sof-firmware?" >&2; exit 1; } + +n=$(find "$DST" -type f | wc -l) +echo "firmware-tigerlake: $n ficheros (wifi=$wifi i915=$gfx sof=$snd)" +''' + +[deps] +build = ["linux-firmware", "sof-firmware", "busybox"] diff --git a/recipes/firmware-tigerlake/README.md b/recipes/firmware-tigerlake/README.md new file mode 100644 index 00000000..be9005af --- /dev/null +++ b/recipes/firmware-tigerlake/README.md @@ -0,0 +1,4 @@ +Esta receta no tiene fuente propia: su contenido sale enteramente de sus deps +(linux-firmware + sof-firmware). El directorio existe porque `source.dir` exige un +árbol que hashear — el mismo patrón que usa `recipes/atuq/`. Ver el encabezado de +../firmware-tigerlake.toml. diff --git a/recipes/intel-ucode.toml b/recipes/intel-ucode.toml new file mode 100644 index 00000000..5f3b03d2 --- /dev/null +++ b/recipes/intel-ucode.toml @@ -0,0 +1,75 @@ +# intel-ucode 20260812 — el microcódigo de la CPU. Va en el arranque, no en el userland. +# +# ══ POR QUÉ NO VIENE EN linux-firmware ═════════════════════════════════════════════════════════ +# El tarball de kernel.org trae `amd-ucode` pero **no el de Intel**: Intel lo publica por su cuenta +# en `intel/Intel-Linux-Processor-Microcode-Data-Files`, con su propia licencia. Medido sobre +# `linux-firmware-20260910.tar.xz` el 2026-09-16. +# +# ══ QUÉ CPU ═══════════════════════════════════════════════════════════════════════════════════ +# La laptop del usuario, leída de `/proc/cpuinfo`: +# +# 11th Gen Intel Core i7-11370H family 6 · model 140 (0x8c) · stepping 1 +# ⇒ le toca `intel-ucode/06-8c-01` (microcódigo corriendo hoy: 0xbe) +# +# La receta instala el árbol ENTERO igual, no sólo ese fichero: el cargador elige por signature en +# boot, y recortarlo ataría el artefacto a esta máquina exacta — que es justo lo que +# `firmware-tigerlake` hace a propósito y esto no debe hacer. Son 17 MB comprimidos. +# +# ══ LOS DOS CAMINOS DE CARGA, Y POR QUÉ HACEN FALTA LOS DOS ════════════════════════════════════ +# · **Tardía** (`/lib/firmware/intel-ucode/`): el kernel la aplica con el sistema ya arriba. +# Sirve, pero llega después de que las mitigaciones de CPU se hayan decidido. +# · **Temprana** (`kernel/x86/microcode/GenuineIntel.bin` dentro de un cpio SIN comprimir, al +# principio del initramfs): el kernel la lee antes de levantar los cores secundarios. Es la que +# de verdad importa, y es la que hay que enganchar en el armado de la imagen. +# +# Esta receta deja los dos: el árbol plano y el `GenuineIntel.bin` ya concatenado, listo para que +# el armador de la imagen lo prependa. **No arma el cpio** — eso es trabajo de la imagen, no del +# artefacto, y meterlo acá obligaría a esta receta a saber cómo se arma el initramfs. +# +# ⚠ **PUNTO DE PARTIDA, NO SELLADA.** Escrita desde el laptop, sin lab ni store. +name = "intel-ucode" +version = "20260812" +license = "LicenseRef-Intel-Software-License" # license/LICENSE en el árbol; NO es libre + +[source] +tarball = "https://github.com/intel/Intel-Linux-Processor-Microcode-Data-Files/archive/refs/tags/microcode-20260812.tar.gz" +# sha256 del tarball bajado el 2026-09-16. +sha256 = "7614616d7b2988c278060486f47de716ef44c19317928cb45ac9e288fafd5bd1" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "static" +strip_debug = false +flags = [] + +[build.phases] +configure = "echo 'intel-ucode: nada que configurar'" +compile = "echo 'intel-ucode: nada que compilar'" +install = ''' +set -e +[ -d intel-ucode ] || { echo "intel-ucode: no está el dir intel-ucode/ — ¿cambió el layout del release?" >&2; exit 1; } + +mkdir -p /out/lib/firmware/intel-ucode +cp intel-ucode/* /out/lib/firmware/intel-ucode/ + +# Aserción ruidosa: el árbol tiene ~130 signatures. Si llegan cuatro, algo se cortó. +n=$(find /out/lib/firmware/intel-ucode -type f | wc -l) +[ "$n" -gt 50 ] || { echo "intel-ucode: sólo $n signatures — el árbol llegó incompleto" >&2; exit 1; } + +# La signature de la laptop del usuario. No se recorta el árbol por ella, pero SÍ se comprueba que +# está: si un release futuro la dejara caer, el metal quedaría sin microcódigo en silencio. +[ -f /out/lib/firmware/intel-ucode/06-8c-01 ] || { + echo "intel-ucode: falta 06-8c-01 (TigerLake i7-11370H) — el metal del usuario se queda sin ucode" >&2; exit 1; } + +# Blob para carga TEMPRANA: todas las signatures concatenadas, que es exactamente el formato que el +# cargador del kernel espera en kernel/x86/microcode/GenuineIntel.bin. +mkdir -p /out/usr/share/intel-ucode +cat intel-ucode/* > /out/usr/share/intel-ucode/GenuineIntel.bin +[ -s /out/usr/share/intel-ucode/GenuineIntel.bin ] || { echo "intel-ucode: GenuineIntel.bin salió vacío" >&2; exit 1; } + +echo "intel-ucode: $n signatures + GenuineIntel.bin ($(wc -c < /out/usr/share/intel-ucode/GenuineIntel.bin) B)" +''' + +[deps] +build = ["busybox"] diff --git a/recipes/linux-firmware.toml b/recipes/linux-firmware.toml new file mode 100644 index 00000000..fb05e54d --- /dev/null +++ b/recipes/linux-firmware.toml @@ -0,0 +1,75 @@ +# 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; } +# Aserción ruidosa (CLAUDE.md §3): un árbol de firmware vacío pasaría por artefacto presente. +n=$(find /out/lib/firmware -type f | wc -l) +[ "$n" -gt 5000 ] || { echo "linux-firmware: sólo $n ficheros — el árbol llegó incompleto" >&2; exit 1; } +echo "linux-firmware: $n ficheros instalados" +''' + +[deps] +build = ["busybox"] diff --git a/recipes/sof-firmware.toml b/recipes/sof-firmware.toml new file mode 100644 index 00000000..1f125868 --- /dev/null +++ b/recipes/sof-firmware.toml @@ -0,0 +1,97 @@ +# sof-firmware 2025.12.2 — el DSP de audio de Intel. NO viene en linux-firmware, y eso se midió. +# +# ══ POR QUÉ ES UNA RECETA APARTE ═══════════════════════════════════════════════════════════════ +# Al escribir `firmware-tigerlake` se anotó como duda si `intel/sof*` venía dentro de +# `linux-firmware`. Se midió sobre el tarball real (`linux-firmware-20260910.tar.xz`, 2026-09-16): +# +# entradas del tarball ......... 5323 +# coincidencias con intel/sof .. 0 +# i915/tgl ..................... 15 ✓ +# iwlwifi ...................... 201 ✓ +# +# ⇒ **no viene.** Intel publica el SOF por su cuenta, en `thesofproject/sof-bin`. Sin esta receta +# una imagen de takana en el metal del usuario arranca muda: `snd_sof_pci_intel_tgl` carga, pide +# `intel/sof/sof-tgl.ri`, no lo encuentra y el DSP no levanta. Es un fallo que no se ve en QEMU, +# donde el audio es virtio — por eso no apareció en ninguno de los tres escritorios ya cerrados. +# +# ══ TIGERLAKE VA POR IPC3, NO POR IPC4 — Y LA INTUICIÓN ES LA CONTRARIA ════════════════════════ +# IPC4 es el camino nuevo y el default en las plataformas recientes, así que lo esperable sería +# tomar la serie más alta (`v2.14.x`). Medido sobre el tarball de sof-bin: **`sof-ipc4-v2.14.1` no +# contiene NINGÚN fichero `tgl`**. El firmware de TigerLake sólo existe en la rama IPC3, y el más +# nuevo que lo trae es `v2.2.x/sof-v2.2/sof-tgl.ri`. Las topologías van con él: `sof-tplg-v2.2`. +# +# tar -tzf sof-bin.tar.gz | grep "sof-ipc4-v2.14.1" | grep -i tgl ⇒ vacío +# tar -tzf sof-bin.tar.gz | grep -E "/sof-v[0-9.]+/sof-tgl\.ri$" ⇒ v2.1.1, v2.2 +# +# Si algún día se sube de serie, esto se re-mide: tomar «la más alta» es exactamente el error que +# esta nota existe para evitar. +# +# ══ intel-signed VS community ══════════════════════════════════════════════════════════════════ +# El árbol trae las dos firmas del mismo binario. Se instala **intel-signed**, que es el que la +# Artix del usuario tiene enlazado hoy (`sof-tgl.ri → intel-signed/sof-tgl.ri`) y el único que el +# DSP acepta con secure boot del audio activo. `community` es para DSP desbloqueados. +# +# ⚠ **PUNTO DE PARTIDA, NO SELLADA.** Escrita desde el laptop, sin lab ni store. Primer build en el +# worker. +name = "sof-firmware" +version = "2025.12.2" +license = "BSD-3-Clause" # LICENCE.Intel en la raíz del árbol + +[source] +tarball = "https://github.com/thesofproject/sof-bin/archive/refs/tags/v2025.12.2.tar.gz" +# sha256 del tarball bajado el 2026-09-16. +sha256 = "a5fb05464273a2963a35950be72e094c0ed89318a44b3c62cb0c93393436ae3c" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "static" +strip_debug = false # blobs firmados: tocarlos invalida la firma +flags = [] + +[build.phases] +configure = "echo 'sof-firmware: nada que configurar — son blobs firmados'" +compile = "echo 'sof-firmware: nada que compilar — son blobs firmados'" +# NO se usa el `install.sh` del árbol: hace rsync a `/lib/firmware` del sistema vivo y decide la +# serie por variables de entorno. Acá la serie es una DECISIÓN de la receta y entra en el hash. +install = ''' +set -e +FW=v2.2.x/sof-v2.2 +TPLG=v2.2.x/sof-tplg-v2.2 +DST=/out/lib/firmware/intel + +[ -d "$FW" ] || { echo "sof-firmware: no está $FW — ¿cambió el layout de sof-bin?" >&2; exit 1; } +[ -d "$TPLG" ] || { echo "sof-firmware: no está $TPLG" >&2; exit 1; } + +mkdir -p "$DST/sof/intel-signed" "$DST/sof-tplg" + +# ── firmware del DSP ───────────────────────────────────────────────────────────────────────── +# Sólo las variantes de TigerLake: `sof-tgl.ri` (LP, que es esta laptop) y `sof-tgl-h.ri` (H). +# El árbol entero son ~80 MB de plataformas que este metal no tiene. +n=0 +for v in sof-tgl.ri sof-tgl-h.ri; do + if [ -f "$FW/intel-signed/$v" ]; then + cp "$FW/intel-signed/$v" "$DST/sof/intel-signed/$v" + # El driver pide `intel/sof/sof-tgl.ri` a secas; el symlink es el que usa la distro de origen. + ln -sf "intel-signed/$v" "$DST/sof/$v" + n=$((n+1)) + fi +done +[ "$n" -gt 0 ] || { echo "sof-firmware: 0 blobs de tgl en $FW/intel-signed" >&2; exit 1; } + +# ── topologías ─────────────────────────────────────────────────────────────────────────────── +# `sof-hda-generic-*.tplg` es lo que monta el HD Audio on-package del 500 Series. Cuál de las +# variantes (1ch/2ch/4ch, con o sin kwd) elige el driver depende del códec detectado en boot, así +# que van todas las genéricas: son unos pocos KB cada una y elegir a mano sería adivinar. +t=0 +for f in "$TPLG"/sof-hda-generic*.tplg; do + [ -f "$f" ] || continue + cp "$f" "$DST/sof-tplg/" && t=$((t+1)) +done +[ "$t" -gt 0 ] || { echo "sof-firmware: 0 topologías hda-generic en $TPLG" >&2; exit 1; } + +echo "sof-firmware: $n firmware + $t topologías (serie IPC3 v2.2, TigerLake)" +''' + +[deps] +build = ["busybox"]