diff --git a/hardware/005-xps13-9350.qd b/hardware/005-xps13-9350.qd index 9ffb796..dbde34d 100644 --- a/hardware/005-xps13-9350.qd +++ b/hardware/005-xps13-9350.qd @@ -47,7 +47,8 @@ running in the background is enough. Before the workaround the controller spent uptime runtime-suspended, so the race window was being hit constantly. Pin the audio controller out of runtime power management so the SoundWire links stay powered and the -window never opens. +window never opens. This alone is not sufficient — see "Docking re-arms it" below for the second +trigger this misses. .box {Do not do this with udev} type:{warning} A udev rule writing `ATTR{power/control}="on"` on `add|bind` looks like the obvious lever and @@ -109,6 +110,79 @@ awk '{printf "suspended %d%%\n", 100*$1/($1+$2)}' \ <(paste /sys/bus/pci/devices/0000:00:1f.3/power/runtime_{suspended,active}_time) ``` +### Docking re-arms it + +Pinning the PCI controller is not the end of it. A panic on 2026-08-21 happened with `sof_pci_debug=1` +active and `runtime_suspended_time` flat at `0` for the whole session — the PCI-level fix was +verified working right up to the crash. What was different: the Dell U2724DE dock had been unplugged +and replugged 72 minutes earlier. + +The dock's DP monitors drive an ASoC card-level jack/ELD reconfiguration on every replug — this is +card-wide, not SoundWire-specific, since the internal speakers and the DP outputs share the same +`sof-soundwire` ALSA card. That reconfiguration independently resumes whichever SoundWire links the +card touches. Confirmed by sampling `runtime_active_time` across a reproduced replug: `link.1` and +`link.2` (the two `rt1318` speaker amps) went from suspended-and-static to active and climbing within +under a minute, while `link.0` (the unused `rt715` mic) stayed suspended throughout. Boot alone never +produces this — every link starts out uniformly suspended and stays that way until something asks for +audio. + +That asymmetric state — some links live and taking interrupts, one still parked — is exactly the +precondition `sdw_intel_thread`'s walk over `cdns == NULL` needs. The PCI-level pin has no visibility +into it, because it only prevents the *controller* from ever entering runtime PM; the link and codec +devices underneath are separate PM domains that a jack event can wake regardless. + +Pinning those nine devices individually with a udev `add|bind` rule would repeat the mistake already +made once with the PCI controller: it would look correct and might still be silently reverted by a +driver-internal call this doc hasn't found. Instead, assert the invariant repeatedly rather than trust +a single write to hold: + +`/usr/local/bin/pin-sof-soundwire-pm.sh` walks the PCI device, its three `soundwire_intel.link.*`, +their `sdw-master-*` children, and every `/sys/bus/soundwire/devices/sdw:*` codec, writing `on` to +`power/control` wherever it isn't already set. It's idempotent and only logs when it actually changes +something. + +`/etc/systemd/system/pin-sof-soundwire-pm.timer` runs it every 15 seconds, forever, so any revert this +doc hasn't identified self-heals within one tick regardless of the mechanism: + +```txt +[Timer] +OnBootSec=10s +OnUnitActiveSec=15s +``` + +`/etc/udev/rules.d/99-sof-soundwire-links-pm.rules` reasserts it immediately on the dock's own +Thunderbolt hotplug event, closing the gap between replug and the next timer tick, since the risky +moment is the resume race itself: + +```txt +ACTION=="add", SUBSYSTEM=="thunderbolt", ATTR{vendor}=="0xd4", ATTR{device}=="0xc045", \ + TAG+="systemd", ENV{SYSTEMD_WANTS}+="pin-sof-soundwire-pm.service" +``` + +.box {Use `+=`, not `=`, for `SYSTEMD_WANTS`} type:{warning} + `/usr/lib/udev/rules.d/90-bolt.rules` already sets `ENV{SYSTEMD_WANTS}+="bolt.service"` on the + same `thunderbolt` subsystem. A first draft of this rule used `=` instead of `+=`; being numbered + `99-` it loaded after bolt's rule and silently clobbered it, so every dock replug stopped + requesting `bolt.service` at all. `udevadm test --action=add /sys/bus/thunderbolt/devices/1-1` + catches this — check the merged `SYSTEMD_WANTS` line lists both services, not just one. + +Update the Thunderbolt vendor/device match if the dock is ever replaced: + +```sh +cat /sys/bus/thunderbolt/devices/*/vendor /sys/bus/thunderbolt/devices/*/device \ + /sys/bus/thunderbolt/devices/*/device_name +``` + +Verify all nine devices are pinned: + +```sh +for d in /sys/bus/pci/devices/0000:00:1f.3 /sys/bus/pci/devices/0000:00:1f.3/soundwire_intel.link.* \ + /sys/bus/pci/devices/0000:00:1f.3/soundwire_intel.link.*/sdw-master-* \ + /sys/bus/soundwire/devices/sdw:*; do + printf '%-70s %s\n' "$d" "$(cat "$d/power/control")" +done +``` + ### Recognising it after the fact The panic leaves nothing behind, so identify it by shape rather than by trace. `CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE` @@ -156,6 +230,14 @@ for the entire session — `journalctl -b -1 -k -p 4` shows only the usual boot- (`charge_now == charge_full` on the next boot) and `battery-charge-watchdog` logs only its startup line, never a `FAULT`. + The two did co-occur on 2026-08-21 — the fault fired for 101 minutes that morning, resolving + itself 8 seconds before the dock was unplugged, with the panic following 72 minutes later — but + the mechanisms are unrelated. Same dock event, two independent bugs. + + A dock replug on the same day also reproduced `pcieport 0000:39:00.0: not ready 1023ms after + resume; giving up`. Not yet investigated; noted here since it fires on the same trigger as the + SoundWire re-arm above and could be mistaken for a symptom of it. + Capturing a fresh trace would need `efi_pstore.pstore_disable=0` on the kernel command line, which means creating `/etc/kernel/cmdline`, regenerating the unified kernel image and re-signing it. `CONFIG_EFI_VARS_PSTORE_DEFAULT_DISABLE` is set, so panics are not persisted by default.