134 lines
5.6 KiB
Markdown
134 lines
5.6 KiB
Markdown
|
|
---
|
|||
|
|
# 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:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
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 `Vec`s
|
|||
|
|
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.:
|
|||
|
|
|
|||
|
|
```rust
|
|||
|
|
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.
|