Watch
1
0
Fork
You've already forked souveraine
0
souveraine/.handoff-from-phone/usb-signaller-boot-panic/README.md
Fimeg 19fe6b1d0e shell: dock reorder, fullscreen detection fix, idle-power, sessiond, misc shell work
- Dock drag-to-reorder for pinned apps (insertion gap, quick-slide vs dwell)
- Fullscreen detection: scan all windows via HyprlandData.windowList
- IdleCoordinator, GlobalStates, Session.qml updates
- Deploy script, qmldir, settings, wallpaper, visualizer fixes
- sessiond server, memory module updates

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 22:18:20 -04:00

5.6 KiB
Raw Blame History


usb-signaller — boot-time panic in UDC::load, index out of bounds

Status: 2026-07-20 — root cause identified, not yet fixed. Read-only device audit found this; nothing restarted, no patch applied yet.

Symptom

Both usb-signaller.service and usb-signaller-default-mode.service fail on every boot (confirmed across 5 consecutive boots in journal, going back to at least 2026-07-20 01:21). Journal:

usb-signaller[NNN]:   Pre-flight checks
usb-signaller[NNN]: 🔍 Discovering URSs
usb-signaller[NNN]: thread 'main' (NNN) panicked at src/udc.rs:388:37:
usb-signaller[NNN]: index out of bounds: the len is 0 but the index is 0
usb-signaller.service: Main process exited, code=exited, status=101/n/a
...
usb-signaller-default-mode[NNN]: usb-signaller never appeared on the bus
usb-signaller-default-mode.service: Failed with result 'exit-code'.

usb-signaller-default-mode.service ("Default USB mode at boot (developer)") fails as a downstream consequence — it waits ~21s for usb-signaller on D-Bus, which never appears since the main process already panicked.

Not a hardware/UDC-absence issue: /sys/class/udc/ does contain a real controller (a600000.usb) at the time of the audit. So discovery has something to enumerate; the crash is in how it's indexed, not in finding zero UDCs.

Root cause

Package: usb-signaller 0.3.1-2 (upstream, by Dylan Van Assche — the usb-moded replacement for mainline Linux Mobile). Source checked out locally at ~/build/src/usb-signaller and ~/build/usb-signaller/src/usb-signaller for build/patching.

src/udc.rs, UDC::load(), lines 382-388:

for entry in discover_gadgets() {
    let entry = ConfigFsGadget::load(&entry)?;
    if udc_name == entry.udc {
        if entry.enabled {
            /* USB Ethernet NCM gadget */
            if entry.configs[0].functions[0].name.starts_with("ncm.") {

ConfigFsGadget.configs and each config's .functions are plain Vecs populated in gadget.rs by scanning the gadget's configs/ directory in configfs — there is no guarantee either Vec is non-empty. The code assumes any gadget where entry.enabled == true already has at least one config with at least one function, and indexes [0] unchecked on both.

At early boot (this runs right after "Pre-flight checks", i.e. very early in the service's life), a gadget can be enabled in sysfs terms while its configs/ subtree hasn't finished being populated yet, or a stale/partial gadget is left over from a previous session — configfs itself doesn't enforce "enabled implies non-empty configs" atomically. Either scenario produces entry.configs (or entry.configs[0].functions) with len 0, and the unchecked index panics the whole process before any UDC mode gets set.

Fix sketch (not applied — read-only audit)

Guard both indexes, e.g.:

if entry.enabled {
    if let Some(cfg) = entry.configs.first() {
        if let Some(func) = cfg.functions.first() {
            if func.name.starts_with("ncm.") { ... }
            else if func.name.starts_with("ffs.mtp") { ... }
            else if func.name.starts_with("ffs.accessory") { ... }
            else { mode = UDCMode::Unknown; eprintln!(...); }
        } else {
            mode = UDCMode::Unknown; // enabled gadget, no functions yet
        }
    } else {
        mode = UDCMode::Unknown; // enabled gadget, no configs yet
    }
}

i.e. treat "enabled but not yet fully populated" as UDCMode::Unknown rather than crashing — matches how the existing else branch (line 408-409) already handles an unrecognized function name.

Impact

Because the process panics, USB mode never gets set at boot at all (not even a fallback), and usb-signaller-default-mode.service (developer mode at boot) never applies. Practically: USB currently comes up in whatever mode the kernel/gadget defaults to, not the configured "developer" default, until something else (manual usb-signallerctl call, or a later retry) sets it — needs to be verified once a fix lands.

Next steps

  1. Patch udc.rs:388 per sketch above (or upstream-style .get(0) / .first() guards).
  2. Rebuild, reboot, confirm both units go green and default-mode actually applies (check usb-signallerctl status or equivalent post-boot).
  3. Consider filing upstream against Dylan Van Assche's usb-signaller repo — this is likely a stock bug, not something introduced by local patches (worth diffing ~/build/src/usb-signaller against upstream HEAD to confirm before filing).

Status update — 2026-07-21

Fix applied and verified at runtime:

  • Patched per the sketch (.first() guards, fall through to UDCMode::Unknown), cross-built on archdev, installed at /usr/local/bin/usb-signaller with a systemd drop-in (/etc/systemd/system/usb-signaller.service.d/local-fix.conf) overriding ExecStart — pacman-owned /usr/bin binary untouched.
  • Patch + pkgrel=3 vendored in Pixel3Arch/pkgs/usb-signaller/ (0001-udc-guard-empty-configs-functions.patch), commit a970742.
  • Runtime restart: both units went green, default-mode applied Developer for the first time ("Setting mode of UDC a600000.usb from Charging to Developer").
  • Trap discovered: the mode apply recreates the gadget, the new function enumerates as usb1, and the stale usb0 keeps a linkdown route that eats every reply — USB ssh dies. Fixed the networkd match to Name=usb* (live + rootfs-overlay) and flushed the stale usb0.

Remaining:

  1. Cold-boot verify (Casey, at device): both units green from boot, usb-signallerctl status shows developer, USB net comes up as usb0.
  2. Diff against upstream HEAD and file the panic upstream — the guard is a clean candidate patch.