tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1173Shell 64Rust 62Markdown 441482 Definiciones

⎇ 5ac10fb3bab477cf6b9788aa5e4f0bab03d53ec1 scripts/lib/firmware-initramfs.sh Shell · 136 líneas · 7.6 KB Historia crudo

feat(instalador): el firmware que el kernel pide ANTES de montar la raiz lo decide la maquina 19df6c148 · sergio ·

1#!/bin/sh
2# lib/firmware-initramfs.sh — el firmware que el kernel pide ANTES de montar la raíz.
3#
4# ══ EL DEFECTO, MEDIDO EN METAL EL 2026-10-14/15 ═══════════════════════════════════════════════
5# «que ni ethernet funciona». La imagen ya trae el firmware completo (`firmware-metal`, 1047 MiB en
6# `/lib/firmware`) y **para el arranque no alcanza**, por una razón de tiempos:
7#
8#   iwlwifi 0000:00:14.3: Detected Intel(R) Wi-Fi 6 AX201 160MHz
9#   iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-QuZ-a0-hr-b0-77.ucode failed: -2
10#   iwlwifi 0000:00:14.3: no suitable firmware found!
11#
12# Esos mensajes salen a los **0,49 s de arranque**: el driver va COMPILADO EN EL KERNEL
13# (`linux-metal` no habilita `CONFIG_MODULES`), así que hace su `probe` cuando el único filesystem
14# que existe es el initramfs. `/lib/firmware` de la raíz todavía no está montada, y `request_firmware`
15# no reintenta nunca: el dispositivo queda sin inicializar para toda la sesión.
16#
17# ══ POR QUÉ ESTO VA EN EL INSTALADOR Y NO EN LA IMAGEN ═════════════════════════════════════════
18# Meter `/lib/firmware` entero en el initramfs es 1 GB cargado en RAM en cada arranque. Meter «el
19# wifi» tampoco sirve: la familia `iwlwifi` sola son 287 MB, y recortarla por generación es adivinar
20# —el nombre que el driver pide sale del chip Y de la versión de API que ese kernel soporta—.
21#
22# La salida es que lo decida **quien sabe en qué máquina está**: el instalador, que corre EN el metal
23# de destino. Y no tiene que adivinar nada, porque **el medio arranca con el MISMO problema**: su
24# initramfs tampoco trae firmware, así que su propio `dmesg` ya tiene la lista EXACTA de ficheros
25# que ese hardware pidió, con el nombre que el driver usó. Se leen de ahí y se copian; en una máquina
26# donde todo cargó bien, la lista sale vacía y esto no toca nada.
27#
28# Vive aparte para poder certificarla sin root ni hardware, igual que `lib/usuarios-del-home.sh` y
29# `lib/cuenta.sh`: `scripts/probar-firmware-initramfs.sh` le da un `dmesg` de mentira y un cpio de
30# juguete. Y viaja sola al initramfs del instalador porque los generadores derivan las librerías del
31# propio script que las sourcea.
32
33# El techo. La ESP del instalador son 64 MiB y ahí viven el kernel (~12 MiB) y este initramfs; un
34# injerto sin tope podría no entrar, y «no entra en la ESP» se descubre al final de una instalación.
35FW_IRD_TOPE_KIB="${FW_IRD_TOPE_KIB:-24576}"   # 24 MiB
36
37# firmware_pedido_del_dmesg [fichero] — las rutas de firmware que el kernel pidió y NO encontró, una
38# por línea, sin repetir. Sin argumento lee el `dmesg` de esta máquina.
39#
40# Se cubren los tres formatos que emite el kernel, porque cambian entre versiones y entre el camino
41# directo y el de respaldo — y mirar sólo uno da «no falta nada» sobre un wifi muerto:
42#   · `Direct firmware load for <X> failed with error -2`
43#   · `firmware: failed to load <X> (-2)`
44#   · `Falling back to sysfs fallback for: <X>`
45firmware_pedido_del_dmesg() {
46	{ if [ -n "${1:-}" ]; then cat "$1"; else dmesg 2>/dev/null; fi; } | sed -n \
47		-e 's/.*Direct firmware load for \([^ ]*\) failed.*/\1/p' \
48		-e 's/.*firmware: failed to load \([^ ]*\) .*/\1/p' \
49		-e 's/.*Falling back to sysfs fallback for: *\([^ ]*\).*/\1/p' \
50		| sed 's/[,:]$//' | sort -u
51}
52
53# firmware_al_initramfs <ird.cpio.gz> <dir-con-lib-firmware> [fichero-dmesg] — injerta en el
54# initramfs los blobs que el dmesg pidió. Devuelve 0 siempre que el initramfs quede USABLE: si no
55# hay nada que poner, lo deja intacto y lo dice.
56#
57# ⚠ Lo que NO hace: no falla la instalación porque falte un blob. Una máquina que se instala sin wifi
58# es recuperable; una que no arranca porque alguien reempaquetó mal su initramfs, no.
59firmware_al_initramfs() {
60	_fi_ird="$1"; _fi_src="$2"; _fi_dmesg="${3:-}"
61	[ -r "$_fi_ird" ] || { echo "    firmware: no encuentro el initramfs $_fi_ird — lo salteo" >&2; return 0; }
62
63	_fi_lista=$(firmware_pedido_del_dmesg "$_fi_dmesg")
64	if [ -z "$_fi_lista" ]; then
65		echo "    firmware: el kernel de este arranque no pidió ningún blob que le faltara — nada que injertar"
66		return 0
67	fi
68
69	_fi_tmp="${TMPDIR:-/tmp}/fw-ird.$$"
70	rm -rf "$_fi_tmp"; mkdir -p "$_fi_tmp/arbol" || return 0
71
72	# Desempaquetar. Si esto falla, el initramfs queda tal cual: no se toca el original hasta el final.
73	if ! ( cd "$_fi_tmp/arbol" && gzip -dc "$_fi_ird" | cpio -idm --quiet ) 2>/dev/null; then
74		echo "    firmware: no pude desempaquetar el initramfs; lo dejo como está" >&2
75		rm -rf "$_fi_tmp"; return 0
76	fi
77	# El `/init` es la razón de existir de este fichero. Si no está, algo entendí mal y me aparto.
78	[ -f "$_fi_tmp/arbol/init" ] || {
79		echo "    firmware: el initramfs no trae /init — no lo toco" >&2
80		rm -rf "$_fi_tmp"; return 0; }
81
82	_fi_n=0; _fi_kib=0; _fi_falta=0
83	for _fi_f in $_fi_lista; do
84		# Una ruta con `..` o absoluta saldría del árbol: el dmesg es texto del kernel, pero esto
85		# escribe ficheros y no se confía en una cadena parseada.
86		case "$_fi_f" in
87			/*|*..*) echo "    firmware: ⚠ descarto una ruta sospechosa: $_fi_f" >&2; continue ;;
88		esac
89		_fi_org=""
90		for _fi_c in "$_fi_src/lib/firmware/$_fi_f" "$_fi_src/$_fi_f"; do
91			[ -e "$_fi_c" ] && { _fi_org="$_fi_c"; break; }
92		done
93		if [ -z "$_fi_org" ]; then
94			# Se NOMBRA. Un blob que el hardware pide y la imagen no trae es una capacidad que esa
95			# máquina no va a tener, y el dueño merece saber cuál.
96			echo "    firmware: ⚠ el hardware pide «$_fi_f» y la imagen no lo trae"
97			_fi_falta=$((_fi_falta + 1))
98			continue
99		fi
100		_fi_peso=$(du -k "$(readlink -f "$_fi_org")" 2>/dev/null | cut -f1)
101		_fi_peso=${_fi_peso:-0}
102		if [ $((_fi_kib + _fi_peso)) -gt "$FW_IRD_TOPE_KIB" ]; then
103			echo "    firmware: ⚠ «$_fi_f» no entra en el tope de $((FW_IRD_TOPE_KIB / 1024)) MiB del initramfs: lo salteo"
104			continue
105		fi
106		mkdir -p "$_fi_tmp/arbol/lib/firmware/$(dirname "$_fi_f")"
107		# `-L`: si en la imagen el blob es un enlace (los 2415 que `linux-firmware` reconstruye desde
108		# WHENCE), al initramfs va el CONTENIDO — adentro no hay a dónde apuntar.
109		cp -L "$_fi_org" "$_fi_tmp/arbol/lib/firmware/$_fi_f" 2>/dev/null || continue
110		_fi_n=$((_fi_n + 1)); _fi_kib=$((_fi_kib + _fi_peso))
111	done
112
113	if [ "$_fi_n" = 0 ]; then
114		echo "    firmware: no pude poner ninguno de los $(echo "$_fi_lista" | wc -w) pedidos; el initramfs queda igual"
115		rm -rf "$_fi_tmp"; return 0
116	fi
117
118	# Reempaquetar y comprobar ANTES de pisar el original. Un initramfs a medio escribir es una
119	# máquina que no arranca, y eso no se diagnostica desde la máquina que no arranca.
120	( cd "$_fi_tmp/arbol" && find . | cpio -o -H newc --quiet --owner=root:root 2>/dev/null | gzip -9 > "$_fi_tmp/nuevo.cpio.gz" ) || {
121		echo "    firmware: falló el reempaquetado; dejo el initramfs original" >&2
122		rm -rf "$_fi_tmp"; return 0; }
123	# ⚠ `cpio -t` imprime las rutas SIN el `./` con que `find .` las guardó: el patrón acepta las dos
124	# formas. Costó tres «NO» sobre un initramfs correcto el 2026-10-14.
125	_fi_dentro=$(gzip -dc "$_fi_tmp/nuevo.cpio.gz" | cpio -t --quiet 2>/dev/null | grep -c "^\(\./\)\?init$" || true)
126	if [ "${_fi_dentro:-0}" -lt 1 ]; then
127		echo "    firmware: el initramfs nuevo no tiene /init — NO lo uso" >&2
128		rm -rf "$_fi_tmp"; return 0
129	fi
130	cp "$_fi_ird" "$_fi_ird.antes-del-firmware" 2>/dev/null || true
131	cp "$_fi_tmp/nuevo.cpio.gz" "$_fi_ird"
132	echo "    firmware: $_fi_n blob(s) injertados en el initramfs ($((_fi_kib)) KiB)$([ "$_fi_falta" -gt 0 ] && echo ", $_fi_falta sin encontrar")"
133	echo "              ahora pesa $(wc -c < "$_fi_ird") bytes"
134	rm -rf "$_fi_tmp"
135	return 0
136}