# DF-5394 — usb_detach() races asynchronous usb_bus_attach(): NULL-softc
# guard bypassed with a wild softc (-8) → supervisor write fault in
# usb_callout_drain; teardown-skip variant frees the usb_bus under the live
# USB kthreads

## What (root cause)

`usb_bus_attach()` — the real USB bus bring-up (root-hub allocation,
probe/attach, `device_set_softc(dev, bus)` at usb_controller.c:791, watchdog
arm at :794) — runs ASYNCHRONOUSLY in the explore kthread, signalled from
`usb_attach_sub()` (usb_controller.c:900). `usb_detach()`
(usb_controller.c:188) uses `device_get_softc(dev)` NULL-ness as its ONLY
attach-state oracle:

```c
bus = device_get_softc(dev);          /* :191 — NULL until the async :791 */
if (bus == NULL) { return (0); }      /* :195 — "never setup properly"     */
usb_callout_drain(&bus->power_wdog);  /* :200 — dereferences `bus`         */
```

Between `kldload` returning (attach_msg just queued) and the explore kthread
reaching :791, a controller detach — kldunload (`devclass_delete_driver` →
`xhci_pci_detach` → `device_delete_children` → `usb_detach`), PCI hot-unplug,
or an HCD attach failure after the child attach (xhci_pci.c:339) — runs with
the async attach still in flight. Two failure modes:

1. **teardown-skip**: softc read as NULL → usb_detach returns having freed
   NOTHING (4 USB kthreads alive, usbpf ifp attached, intrhook linked,
   attach_msg queued) → the controller softc/embedded `struct usb_bus` is
   freed underneath the running explore kthread → use-after-free.
2. **wild-softc** (observed, deterministic once the interleaving lands):
   the racing mutation of the device object leaves garbage in `dev->softc`
   (observed `0xfffffffffffffff8`, non-NULL) → passes the :195 guard →
   `usb_callout_drain(&bus->power_wdog)` computes `&bus->power_wdog =
   -8+0x490 = 0x488` → `lock andl $0xffffefff,0x10(%rdi)` writes to
   **0x498** → trap 0xc, supervisor WRITE → panic + crash dump.

## Trigger (proven 5×, first-attempt hits with the socket serial console)

Guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC, QEMU `-device qemu-xhci`,
xhci.ko modular:

    root# kldunload xhci            # clean-unload the boot-attached HCD
    root# kldload xhci; kldunload xhci   # zero delay — race the async attach

On DragonFly the race is *wide*: `usb_attach_sub()` calls runtime
`config_intrhook_establish()` (:871) and `run_interrupt_driven_config_hooks()`
then sleeps ~10s ("conifhk", "**WARNING** waiting for the following device to
finish configuring … Giving up") before queueing attach_msg — so when
kldload() returns, `usb_bus_attach()` has only just begun. With the console
on a unix-socket serial (slow reader throttles every `device_printf` in the
attach path, e.g. the "5.0Gbps" print at :736), the crash lands on the first
pair, every time.

## Evidence

- `serial-run5-panic.log` — clean panic header + backtrace, dump #2
- `boot.log.df5394-run2-panic` — first dump's console (char-interleaved with
  the explore kthread's :736 printf = concurrency proof)
- `boot.log.df5394-run1-ddb` — first crash (DDB stop at the same site)
- `panic.txt` — decoded trap frames from BOTH dumps: bus = -8,
  fault VA 0x498 = &bus->power_wdog+0x10, tf_err = 0x2 (supervisor write),
  identical in dump #1 (frame 0xfffff8011846d5e8) and dump #2
  (frame 0xfffff8008e0b85e8)
- `kgdb.txt` — backtrace: `_callout_cancel_or_stop ← usb_detach+0x3c ←
  device_detach ← device_delete_child ← device_delete_children ←
  xhci_pci_detach` and the dump cross-check showing a usbus device object
  whose softc field == bus (the async :791 write landing in the device the
  detach path is tearing down)

## Build (fix validation)

    # on the guest, as root:
    cd /usr/src && patch -p1 < fix.diff
    make -j6 buildkernel KERNCONF=X86_64_GENERIC && make installkernel
    # reboot, then repeat the exact trigger (12 zero-delay pairs)

## Expected

Stock kernel: panic (trap 0xc, usb_detach+0x3c), crash dump, reboot —
within the first pair (socket serial) or a few pairs otherwise.
Patched kernel (fix.diff: set softc synchronously in usb_attach_sub BEFORE
signalling attach_msg + move the power_wdog drain AFTER the detach job):
no panic, clean `usbus0: detached` on every cycle, no leaked "usbus" kthreads.
