Commit 6b23c9fb by PLN (Algolia)

audio: a checker for the profile pin, and a fix that refuses to be a silence

Adds tools/check-audio.py and wires it into the gate as 'audio profiles',
advisory: a card pinned to Pro Audio is a preference, not a reason to refuse a
launch, but it IS the reason pavucontrol offers no Speaker / Headphones / HDMI.

The ranking is a faithful port of WirePlumber's own findBestProfile, including
its refusal to ever select pro-audio -- without that, a card someone pinned
there would read as correctly set.

--fix counts the sinks it would leave behind and stops at zero. On this box the
textbook repair (clear the pin, take WirePlumber's pick) would have switched off
the only four sinks present, since the laptop codec is pinned off and the USB
interface is unplugged. A fix that trades a wrong profile for no audio is not a
fix, and the fence has a test with that case in it.

Also records a finding the write-up turned up: the config fragment in
~/.config/wireplumber/wireplumber.conf.d/ is 0.5 format on an 0.4.17 install, so
neither it nor the DMIC guard written alongside it in July has ever been in
force. The tool flags that class -- a file that looks exactly like protection
and does nothing.
parent 7808c63f
...@@ -88,14 +88,57 @@ The guard written then — `~/.config/wireplumber/wireplumber.conf.d/51-disable- ...@@ -88,14 +88,57 @@ The guard written then — `~/.config/wireplumber/wireplumber.conf.d/51-disable-
there (checked: `grep -rn wireplumber tools/` finds only documentation and the there (checked: `grep -rn wireplumber tools/` finds only documentation and the
gig-up warning), so it was removed outside the tooling. gig-up warning), so it was removed outside the tooling.
### …and the guard was never in force at all
It is worse than missing. `~/.config/wireplumber/wireplumber.conf.d/*.conf` is
**WirePlumber 0.5's** config format and location; this box runs **0.4.17**,
which reads `~/.config/wireplumber/*.lua.d/*.lua` and knows nothing about
`.conf.d`. The one fragment still in that directory tries to set
```
api.alsa.period-size = 1024
api.alsa.headroom = 0
```
and no sink node carries either property (`pw-dump`, 2026-09-22) — so that
tuning has never applied either, and the shipped `50-alsa-config.lua` has the
same two keys sitting commented out. Which means the thing that has kept this
box safe since July is not the guard; it is the card being switched off.
That is the honest framing for turning it back on: it is a real change of risk
posture, not a restoration of a protection that was working.
The 0.4 form of the guard, for when that decision is made:
```lua
-- ~/.config/wireplumber/main.lua.d/51-disable-sof-dmic.lua
-- Keeps the internal DMIC capture node out of the graph. The SOF DSP stopped
-- answering IPC while this PCM was being probed (2026-07-26) and WirePlumber
-- went to uninterruptible D-state, taking scsynth with it. Playback is
-- untouched: Speaker, Jack Out and HDMI 1-3 all stay.
table.insert(alsa_monitor.rules, {
matches = {
{ { "node.name", "matches", "alsa_input.*sof_sdw*" },
{ "node.name", "matches", "*Mic*" } },
},
apply_properties = { ["node.disabled"] = true },
})
```
Both constraints inside one `matches` entry are ANDed, which is what keeps this
off the headset mic on the same card. **Verify the node name before trusting
it:** it cannot be read today, because the node only exists while the HiFi
profile is active. The first thing to do after the re-probe is
`pw-dump | grep -i 'alsa_input.*sof_sdw'` and confirm the pattern matched
something — a rule that matches nothing is indistinguishable from protection.
Order of operations, therefore: Order of operations, therefore:
1. rewrite the DMIC guard, matching the node **by name** (`HiFi__Mic__source`) — 1. install the Lua guard above, in `main.lua.d/`, **not** `wireplumber.conf.d/`;
the archive's "card3" is stale by its own "the ALSA card number is not 2. clear the `default-profile` pins — `tools/check-audio.py --fix`;
stable" learning;
2. clear the two `default-profile` pins;
3. restart WirePlumber once, on a calm evening, and check `HiFi` appears; 3. restart WirePlumber once, on a calm evening, and check `HiFi` appears;
4. only then set the SOF card to HiFi. 4. confirm the guard actually matched a node;
5. only then set the SOF card to HiFi.
## Gig relevance ## Gig relevance
...@@ -103,6 +146,33 @@ With the UMC202HD unplugged and the SOF card `off`, there is **no analog sink at ...@@ -103,6 +146,33 @@ With the UMC202HD unplugged and the SOF card `off`, there is **no analog sink at
all** on this box — `gig-up --headphones` has nothing to fold onto. That is fine all** on this box — `gig-up --headphones` has nothing to fold onto. That is fine
for a venue (the UMC is the gig interface) and blocking for a train rehearsal. for a venue (the UMC is the gig interface) and blocking for a train rehearsal.
## What shipped, 2026-09-22
`tools/check-audio.py` — check by default, `--fix` to repair, `--json` for the
gate, and `--dry-run` to see the commands without running them. It is probe
`audio profiles` in `tools/check-gig.py`, **ADVISE**, so every launch says this
out loud and no launch is ever refused over a card preference.
It reports the three causes above by name, and one more that this page found
while it was being written: **a config fragment in the other WirePlumber's
format**, which is the trap that hides the rest.
One fence is worth stating on its own, because the textbook fix was a trap.
WirePlumber's pick for the NVIDIA card with no display attached is `off`, and
its four raw Pro Audio outputs were **the only sinks on the box**. So "unstick
it from Pro Audio" would have left the machine silent, discovered the next
morning by someone who had not made the change. `--fix` therefore counts the
sinks it would leave behind and refuses at zero:
```
check-audio: REFUSING — applying this would leave the box with no sink at all.
alsa_card.pci-0000_01_00.1 -> off
Plug a display (HDMI profiles become available and outrank Pro Audio),
or plug the UMC202HD, then re-run. Nothing was changed.
```
A fix that trades a wrong profile for no audio is not a fix.
## What tooling can honestly promise ## What tooling can honestly promise
Following `check-preload.sh`'s shape — check by default, mutate only on an Following `check-preload.sh`'s shape — check by default, mutate only on an
......
...@@ -151,6 +151,19 @@ PROBES: tuple[Probe, ...] = ( ...@@ -151,6 +151,19 @@ PROBES: tuple[Probe, ...] = (
fix="tools/check-audio-graph.sh -v # recording integrity, " fix="tools/check-audio-graph.sh -v # recording integrity, "
"Master->UMC, is the interface clocking, is the prune live"), "Master->UMC, is the interface clocking, is the prune live"),
# ADVISE, never BLOCK. A card pinned to Pro Audio is a PREFERENCE — it is
# saved in WirePlumber's own state file and restored on every boot, which is
# exactly why it never self-corrects — and a gate that refuses to launch
# over a preference is a gate that gets ignored. But it is the reason
# pavucontrol offers no Speaker / Headphones / HDMI 1-3, and on 2026-09-22 it
# was also the reason this box had no ordinary stereo sink at all, which
# gig-up --headphones needs. Read-only here; the repair is `--fix`, which
# the gate must never pass (see gig_gate.mutations()).
Probe("audio profiles", [PY, "tools/check-audio.py"],
fix="tools/check-audio.py --fix # clears stale pins; REFUSES if the "
"result would leave the box with no sink at all",
kind=ADVISE),
# Cold, seeded, no rig required — and the slowest probe in the gate at ~12s # Cold, seeded, no rig required — and the slowest probe in the gate at ~12s
# of GHC, which is why `--fast` exists for the boot path. # of GHC, which is why `--fast` exists for the boot path.
Probe("setlist compiles", [PY, "tools/silent-eval.py", "--seeded"], Probe("setlist compiles", [PY, "tools/silent-eval.py", "--seeded"],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment