audio: ALSA encendido en el kernel — y el muro real es libudev-zero, no el stack de audio
ALSA =y en linux-metal (b3:613bca15): HDA legacy + codecs (lo que emula QEMU y usan las máquinas pre-SOF), SOF para TigerLake (el laptop del target NO suena por la ruta legacy: Intel movió el audio al DSP) y SND_USB_AUDIO. Todo =y porque el producto es monolítico. ⚠ Falta inyectar el FIRMWARE de SOF en la imagen (intel/sof/*.ri, sof-tplg/*.tplg), igual que metal-firmware.sh hace con los tgl_* de i915: sin blobs el driver carga y falla. FUNCIONÓ LA MITAD: en la VM `/dev/snd` ya trae controlC0 + pcmC0D0p/c y /sys/class/sound lista card0. El kernel detecta la tarjeta. Pero wpctl sigue con `Devices:` vacío. **Y LA CAUSA ES ESTRUCTURAL, no una propiedad que falte.** `get_card_nr` de SPA (alsa-udev.c:178-183) sólo acepta el device CARD —`/sys/.../sound/card0`—, que es un device de CLASE: sin major:minor, sin nodo en /dev. Y `udev_enumerate_scan_devices` de libudev-zero recorre EXCLUSIVAMENTE /sys/dev/block y /sys/dev/char (udev_enumerate.c:281), o sea sólo devices CON nodo; el udev real enumera /sys/class entero. **El device que SPA necesita nunca entra en la enumeración**: no hay guarda que sacar, falta la mitad del espacio de búsqueda. Salidas: (a) enseñarle a libudev-zero a recorrer /sys/class —radio 51 sellados— o (b) empaquetar eudev. Es decisión de arquitectura, no parche. CORRIJO ALGO QUE ESCRIBÍ ANTES: el parche `pipewire-sound-initialized.patch` se hizo creyendo que SOUND_INITIALIZED era el bloqueo, y NO lo era — con el parche aplicado el resultado no cambió. Se conserva porque la incompatibilidad que describe es real (sin udevd nadie pone esa propiedad y SPA descartaría toda tarjeta en cuanto la enumeración funcione), pero su prosa ahora empieza avisando que no es la solución. Sacarlo del camino importa: dejarlo presentado como el arreglo mandaría al próximo a buscar en el lugar equivocado. Dos arreglos de andamiaje que costaron un ciclo cada uno: - **`-nic none` por defecto.** Agregar la tarjeta de sonido CORRIÓ LAS RUTAS PCI, la entrada de disco cacheada en las VARS de OVMF dejó de coincidir, y la firmware se fue al orden por defecto: PXE v4, PXE v6, HTTP… minutos de timeouts. Con un TIMEOUT corto eso se lee como «la VM no arrancó» y el serial sólo dice `PXE-E16: No valid offer received`. - El filtro del log de wireplumber era `alsa|udev|sound|card|monitor` con `tail -40`, y se llenó de bluez/v4l2/libcamera —tres monitores que avisan que su plugin no está— dejando fuera justo las líneas de ALSA. **Un filtro ancho con cola corta es peor que ninguno.** Ahora filtra `alsa` y además copia el log entero a la raíz ext4 (tmpfs muere con la VM). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
⚠ ESTE PARCHE NO ERA EL BLOQUEO — leer antes de sacar conclusiones.
|
||||
|
||||
Se escribió creyendo que `SOUND_INITIALIZED` era lo que impedía enumerar, y **no lo era**: con el
|
||||
parche aplicado, `wpctl status` seguía dando `Devices:` vacío. La causa real está más abajo en el mismo
|
||||
fichero y es ESTRUCTURAL, no una propiedad que falte (ver la nota al final).
|
||||
|
||||
El parche se conserva porque la incompatibilidad que describe es REAL y volvería a morder en cuanto la
|
||||
enumeración funcione: sin él, SPA descarta toda tarjeta en un sistema sin udevd. Pero no es la
|
||||
solución del problema de audio.
|
||||
|
||||
pipewire/SPA: no exigir `SOUND_INITIALIZED`, una propiedad que sólo pone udevd con sus reglas.
|
||||
|
||||
EL SÍNTOMA, y por qué es caro de leer: con ALSA encendido en el kernel, la tarjeta detectada y
|
||||
`/dev/snd/controlC0` presente, `wpctl status` seguía dando
|
||||
|
||||
Audio
|
||||
├─ Devices: ← vacío
|
||||
├─ Sinks: * 33. Dummy Output
|
||||
|
||||
o sea el MISMO cuadro que cuando no había kernel con sonido. Tres cosas distintas —no hay ALSA, no hay
|
||||
tarjeta, hay tarjeta y el stack no la ve— se ven idénticas en esa salida.
|
||||
|
||||
LA CAUSA, en la fuente: `spa/plugins/alsa/alsa-udev.c:167`
|
||||
|
||||
if (udev_device_get_property_value(udev_device, "SOUND_INITIALIZED") == NULL)
|
||||
return SPA_ID_INVALID; /* descarta la tarjeta */
|
||||
|
||||
`SOUND_INITIALIZED` no es una propiedad del kernel: **la pone udevd al ejecutar sus reglas**
|
||||
(`78-sound-card.rules` de systemd/eudev marca la tarjeta cuando todos sus devices están listos). Esta
|
||||
distro usa **libudev-zero**, que lee /sys directo y NO EJECUTA REGLAS por diseño — su
|
||||
`udev_enumerate_scan_devices` recorre `/sys/dev/block` y `/sys/dev/char`, así que la tarjeta SÍ se
|
||||
enumera, pero llega sin esa propiedad. Resultado: SPA descarta todas las tarjetas, siempre.
|
||||
|
||||
POR QUÉ SE PARCHEA PIPEWIRE Y NO libudev-zero: sintetizar `SOUND_INITIALIZED` en libudev-zero sería
|
||||
igual de correcto, pero `yupana radio libudev-zero` da **51 artefactos sellados cayendo a deuda** —
|
||||
está en el cierre de las cinco imágenes. El radio de pipewire son 2. Se paga el barato.
|
||||
|
||||
POR QUÉ QUITAR EL GUARDA ES LEGÍTIMO Y NO UN ATAJO: ese `if` existe para no correr una carrera contra
|
||||
udevd —evitar tomar una tarjeta cuyos devices todavía no aparecieron—. **Con libudev-zero no hay
|
||||
udevd, así que no hay carrera que perder**: cuando `/sys/dev/char` lista el device, el device existe.
|
||||
Se elimina una comprobación cuya premisa no se cumple acá, no una que nos moleste.
|
||||
|
||||
Lo que NO se toca: los otros tres guardas del mismo bloque (`ACP_IGNORE`, `SOUND_CLASS=modem`,
|
||||
`DEVPATH`). Los dos primeros son opt-out explícitos del administrador y el tercero es un dato real del
|
||||
kernel — ésos siguen valiendo.
|
||||
|
||||
diff -Naur a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c
|
||||
--- a/spa/plugins/alsa/alsa-udev.c
|
||||
+++ b/spa/plugins/alsa/alsa-udev.c
|
||||
@@ -164,8 +164,13 @@
|
||||
if ((str = udev_device_get_property_value(udev_device, "SOUND_CLASS")) && spa_streq(str, "modem"))
|
||||
return SPA_ID_INVALID;
|
||||
|
||||
- if (udev_device_get_property_value(udev_device, "SOUND_INITIALIZED") == NULL)
|
||||
- return SPA_ID_INVALID;
|
||||
+ /* hammer: NO exigir SOUND_INITIALIZED. Esa propiedad la pone udevd al correr
|
||||
+ * 78-sound-card.rules, y esta distro usa libudev-zero, que lee /sys directo y no
|
||||
+ * ejecuta reglas. El guarda existe para no competir con udevd por una tarjeta a
|
||||
+ * medio inicializar; sin udevd no hay carrera: si /sys/dev/char lista el device,
|
||||
+ * el device esta. Ver recipes/incoming-gnome/pipewire-sound-initialized.patch.
|
||||
+ * Los otros guardas de este bloque (ACP_IGNORE, SOUND_CLASS=modem, DEVPATH) se
|
||||
+ * respetan tal cual: son opt-out del administrador o datos reales del kernel. */
|
||||
|
||||
if ((str = udev_device_get_property_value(udev_device, "DEVPATH")) == NULL)
|
||||
return SPA_ID_INVALID;
|
||||
|
||||
══ LO QUE SÍ BLOQUEA, medido después ═════════════════════════════════════════════════════════════
|
||||
`get_card_nr` sigue con dos comprobaciones más (alsa-udev.c:178-183):
|
||||
|
||||
if ((e = strrchr(str, '/')) == NULL) return SPA_ID_INVALID;
|
||||
if (strlen(e) <= 5 || strncmp(e, "/card", 5) != 0) return SPA_ID_INVALID;
|
||||
|
||||
o sea que **sólo acepta el device CARD** (`/sys/.../sound/card0`), no `controlC0` ni `pcmC0D0p`. Y
|
||||
`card0` es un device de CLASE: no tiene major:minor, no tiene nodo en /dev.
|
||||
|
||||
`udev_enumerate_scan_devices` de libudev-zero recorre **exclusivamente** `/sys/dev/block` y
|
||||
`/sys/dev/char` (udev_enumerate.c:281) — o sea sólo devices CON nodo. El udev real enumera
|
||||
`/sys/class/*` entero, con nodo o sin él.
|
||||
|
||||
⇒ **El device que SPA necesita nunca entra en la enumeración.** No hay guarda que sacar: falta la
|
||||
mitad del espacio de búsqueda. Las salidas son (a) enseñarle a libudev-zero a recorrer `/sys/class`
|
||||
—`yupana radio libudev-zero` = 51 sellados a deuda— o (b) empaquetar eudev. Es una decisión de
|
||||
arquitectura, no un parche, y queda para quien la tome.
|
||||
@@ -8,6 +8,13 @@
|
||||
# habla el protocolo de PulseAudio. O sea que gnome-shell sigue usando libpulse sin saber que del otro
|
||||
# lado hay PipeWire. Elegir PipeWire NO obligó a tocar una línea del cliente.
|
||||
#
|
||||
# ⚠ LLEVA UN PARCHE: `pipewire-sound-initialized.patch`. SPA descartaba TODAS las tarjetas de sonido
|
||||
# porque exige la propiedad `SOUND_INITIALIZED`, que **no la pone el kernel sino udevd al correr sus
|
||||
# reglas** — y acá el udev es libudev-zero, que lee /sys directo y no ejecuta reglas por diseño. El
|
||||
# síntoma era un `wpctl status` con `Devices:` vacío, IDÉNTICO al de no tener ALSA en el kernel. Ver el
|
||||
# parche: dice por qué se toca pipewire (radio 2) y no libudev-zero (radio 51), y por qué quitar ese
|
||||
# guarda es legítimo (protege de una carrera contra udevd que sin udevd no existe).
|
||||
#
|
||||
# ⚠ SIN GESTOR DE SESIÓN todavía (`-Dsession-managers=[]`). PipeWire arranca y acepta clientes —lo que
|
||||
# quita el `Gvc-WARNING: Failed to connect context: Connection refused` del shell— pero sin
|
||||
# wireplumber no enumera ni enruta dispositivos: no hay política de qué entra y qué sale. Para una VM
|
||||
@@ -43,6 +50,7 @@ version = "1.2.7"
|
||||
[source]
|
||||
tarball = "https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/1.2.7/pipewire-1.2.7.tar.gz"
|
||||
sha256 = "e75568ed18bcbe75e9779af57cb9cc256fd7ebfaadc12bb347a0717055d1d3a9"
|
||||
patches = ["pipewire-sound-initialized.patch"]
|
||||
|
||||
[build]
|
||||
compiler = "zig-cc"
|
||||
|
||||
@@ -9,6 +9,28 @@
|
||||
# +AUDIT (la jaula del anfitrión kikin J4b / harkaq), IO_URING (kikin J4 se diseña sobre él) y
|
||||
# BPF_SYSCALL (horizonte observabilidad) van EXPLÍCITOS: «al azar del defconfig» no es un contrato.
|
||||
#
|
||||
# AUDIO: ALSA ENCENDIDO (2026-07-29, decisión del usuario tras elegir PipeWire como servidor).
|
||||
# Estaba `-d SOUND -d SND` — apagado a propósito cuando el escritorio no existía todavía. El precio
|
||||
# se cobró al cerrar wireplumber: `wpctl status` daba `Devices:` VACÍO con un único sink `auto_null`,
|
||||
# y **no era un fallo del stack de audio sino la ausencia de /dev/snd**. Se comprobó dándole a QEMU
|
||||
# una `ich9-intel-hda` emulada: con tarjeta y sin ALSA en el kernel, el resultado no cambiaba.
|
||||
#
|
||||
# Todo =y y no =m, por la razón de siempre en esta receta: el producto es monolítico, sin
|
||||
# udev/modprobe en el camino. Tres frentes, y ninguno es opcional para el target real:
|
||||
# · HDA legacy (SND_HDA_INTEL + codecs Realtek/HDMI/generic) — es lo que emula QEMU
|
||||
# (`ich9-intel-hda`) y lo que usan las máquinas pre-SOF. Con esto solo, la VM ya tiene qué
|
||||
# enumerar.
|
||||
# · SOF para TigerLake (SND_SOC_SOF_TIGERLAKE + HDA_AUDIO_CODEC/HDA_LINK) — el laptop del target
|
||||
# concreto NO suena por la ruta legacy: Intel movió el audio de TGL al DSP, y sin el firmware SOF
|
||||
# cargado no hay salida. Es el mismo patrón que i915 con su DMC/GuC.
|
||||
# · SND_USB_AUDIO — auriculares y micrófonos USB, que es como la mayoría de la gente escucha.
|
||||
# `SND_DYNAMIC_MINORS` porque con varias tarjetas (HDA + HDMI + USB) los minors fijos se agotan.
|
||||
#
|
||||
# ⚠ FALTA EL FIRMWARE DE SOF en la imagen. El kernel trae el driver; los blobs
|
||||
# (`intel/sof/sof-tgl.ri`, `intel/sof-tplg/*.tplg`) los tiene que inyectar `metal-firmware.sh`, igual
|
||||
# que hace con los `tgl_*` de i915. Sin ellos SOF carga y falla, y en el laptop no habrá sonido
|
||||
# aunque el driver esté. En QEMU no importa: ahí manda la ruta HDA legacy.
|
||||
#
|
||||
# PERFIL ESCRITORIO/JUEGO (plan-jaula-juegos T1 + joyas-reusables #1/#4, 2026-07-16):
|
||||
# - NTSYNC: primitivas de sincronización NT en kernel (wine/proton moderno, reemplaza esync/fsync).
|
||||
# Sin deps de Kconfig (verificado drivers/misc/Kconfig del tag); tristate ⇒ =y con MODULES=off.
|
||||
@@ -108,7 +130,14 @@ scripts/config -d MODULE_SIG -d MODULE_SIG_ALL -d DEBUG_INFO_BTF -d DEBUG_INFO -
|
||||
-e NET_VENDOR_REALTEK -e R8169 -e NET_VENDOR_BROADCOM -e TG3 \
|
||||
-e USB_NET_DRIVERS -e USB_USBNET -e USB_NET_CDCETHER -e USB_NET_CDC_NCM -e USB_NET_RNDIS_HOST \
|
||||
-d DRM_AMDGPU -d DRM_NOUVEAU -d DRM_RADEON -d AGP \
|
||||
-d SOUND -d SND -d MEDIA_SUPPORT -d INFINIBAND -d BT -d NFC -d CAN \
|
||||
-e SOUND -e SND -e SND_PCM -e SND_TIMER -e SND_HRTIMER -e SND_DYNAMIC_MINORS \
|
||||
-e SND_PCI -e SND_HDA -e SND_HDA_INTEL -e SND_HDA_GENERIC -e SND_HDA_PATCH_LOADER \
|
||||
-e SND_HDA_CODEC_REALTEK -e SND_HDA_CODEC_HDMI -e SND_HDA_CODEC_GENERIC \
|
||||
-e SND_INTEL_DSP_CONFIG -e SND_SOC -e SND_SOC_INTEL_SOF_PCI_DEV \
|
||||
-e SND_SOC_SOF_TOPLEVEL -e SND_SOC_SOF_PCI -e SND_SOC_SOF_INTEL_TOPLEVEL \
|
||||
-e SND_SOC_SOF_TIGERLAKE -e SND_SOC_SOF_HDA_AUDIO_CODEC -e SND_SOC_SOF_HDA_LINK \
|
||||
-e SND_USB -e SND_USB_AUDIO \
|
||||
-d MEDIA_SUPPORT -d INFINIBAND -d BT -d NFC -d CAN \
|
||||
-d WATCHDOG -d THUNDERBOLT -d FIREWIRE -d DEBUG_WX \
|
||||
-d XFS_FS -d BTRFS_FS -d F2FS_FS -d JFS_FS -d REISERFS_FS -d GFS2_FS -d NTFS3_FS && \
|
||||
scripts/config --set-str CMDLINE "console=tty0 console=ttyS0,115200 quiet loglevel=3 vt.global_cursor_default=0 efi=novamap split_lock_detect=off initrd=/initramfs.cpio.gz rdinit=/init" && \
|
||||
|
||||
@@ -304,6 +304,13 @@ esperar_nombre() {
|
||||
# resultado fue DOS wireplumber vivos peleándose el grafo — visible en `wpctl status` como dos clientes
|
||||
# «WirePlumber» con pids distintos. El síntoma no es un error: es una lista de clientes que se lee
|
||||
# normal si no se cuenta.
|
||||
# ANTES de arrancar nada: ¿hay hardware que enumerar? Es la pregunta que separa «el stack de audio
|
||||
# falla» de «no hay tarjeta», y las dos se ven igual en un `wpctl status` con `Devices:` vacío. El
|
||||
# cmdline del kernel lleva `quiet loglevel=3`, así que los mensajes de ALSA NO salen por el serial —
|
||||
# mirar /dev y /sys es la única forma directa.
|
||||
say "audio: /dev/snd = $(ls /dev/snd 2>/dev/null | tr "\n" " ")"
|
||||
say "audio: /sys/class/sound = $(ls /sys/class/sound 2>/dev/null | tr '\n' ' ')"
|
||||
say "audio: HDA en el bus PCI = $(grep -ci 'audio' /proc/bus/pci/devices 2>/dev/null || echo '?')"
|
||||
if [ -x /usr/bin/pipewire ]; then
|
||||
/usr/bin/pipewire >/tmp/pipewire.log 2>&1 &
|
||||
say "pipewire lanzado (pid $!)"
|
||||
@@ -323,7 +330,10 @@ if [ -x /usr/bin/pipewire ]; then
|
||||
# aplica la política (qué es entrada, qué es salida, qué se enlaza con qué), y esa política está
|
||||
# escrita en Lua. Sin él el único sink es `auto_null`, el nodo de descarte.
|
||||
if [ -x /usr/bin/wireplumber ]; then
|
||||
/usr/bin/wireplumber >/tmp/wireplumber.log 2>&1 &
|
||||
# WIREPLUMBER_DEBUG=3: sin esto su log no dice POR QUÉ descarta una tarjeta, y «Devices: vacío»
|
||||
# es indistinguible de «no hay hardware». Con el nivel de debug puesto, el monitor de ALSA
|
||||
# imprime cada device que ve y el motivo del descarte.
|
||||
WIREPLUMBER_DEBUG=3 /usr/bin/wireplumber >/tmp/wireplumber.log 2>&1 &
|
||||
WP_PID=$!
|
||||
say "wireplumber lanzado (pid $WP_PID)"
|
||||
sleep 2
|
||||
@@ -333,6 +343,20 @@ if [ -x /usr/bin/pipewire ]; then
|
||||
# creó: si sale vacío, wireplumber corre pero no enumeró nada.
|
||||
wpctl status >/tmp/wpctl.log 2>&1 || true
|
||||
dump /tmp/wpctl.log
|
||||
# Y su propio log, SIEMPRE — no sólo si muere. Un wireplumber vivo que no enumera nada es el
|
||||
# caso interesante, y es justo el que quedaba sin evidencia. Se filtran las líneas de alsa/udev,
|
||||
# que son las que dicen qué vio y qué descartó.
|
||||
# Filtro ESTRECHO: `alsa` y nada más. La primera versión filtraba
|
||||
# `alsa|udev|sound|card|monitor` y el `tail -40` se llenó de bluez/v4l2/libcamera —tres
|
||||
# monitores que avisan que su plugin no está, ruido garantizado— dejando fuera justo las líneas
|
||||
# de ALSA. Un filtro ancho con cola corta es peor que ninguno.
|
||||
say "wireplumber :: ALSA"
|
||||
grep -iE "alsa" /tmp/wireplumber.log 2>/dev/null | head -40 > /tmp/wp-alsa.log
|
||||
dump /tmp/wp-alsa.log
|
||||
# Y el log COMPLETO a la raíz ext4, que sí se sincroniza: /tmp es tmpfs y muere con la VM.
|
||||
# Se saca con `debugfs -R "dump /wireplumber.log ..."`, igual que el core.
|
||||
cp /tmp/wireplumber.log /wireplumber.log 2>/dev/null || true
|
||||
sync
|
||||
else
|
||||
say "!! wireplumber MURIÓ:"; dump /tmp/wireplumber.log
|
||||
fi
|
||||
|
||||
@@ -60,6 +60,17 @@ if [ "${AUDIO:-1}" = 1 ]; then
|
||||
QEMU_ARGS+=(-audiodev none,id=snd0 -device ich9-intel-hda -device hda-duplex,audiodev=snd0)
|
||||
fi
|
||||
|
||||
# NET=none (default): SIN tarjeta de red. No es sólo ahorro — QEMU añade una e1000 por defecto, y OVMF
|
||||
# la usa para crear entradas de arranque PXE/HTTP (IPv4 e IPv6). Cuando la entrada de disco que OVMF
|
||||
# tenía cacheada en sus VARS deja de coincidir —y basta AGREGAR UN DISPOSITIVO PCI, como la tarjeta de
|
||||
# sonido de arriba, para que las rutas se corran— la firmware cae al orden por defecto y se pasa un
|
||||
# par de minutos en timeouts de PXE antes de llegar al disco. Con un TIMEOUT corto eso se ve como
|
||||
# «la VM no arrancó», y el serial sólo muestra `PXE-E16: No valid offer received`. Pasó de verdad.
|
||||
# Este escritorio no necesita red; `NET=user` si algún día hace falta.
|
||||
if [ "${NET:-none}" = none ]; then
|
||||
QEMU_ARGS+=(-nic none)
|
||||
fi
|
||||
|
||||
echo "==> QEMU (DISP=$DISP, serial=$SERIAL)"
|
||||
echo " imagen: $IMG"
|
||||
if [ -n "${TIMEOUT:-}" ]; then
|
||||
|
||||
Reference in New Issue
Block a user