# DF-5789 — newbus detach cascade is destructive-irreversible: `devclass_delete_driver()` aborts mid-loop on the first detach veto, leaving already-detached devices permanently dead while the driver stays registered and module unload is wedged

## Root cause

`sys/kern/subr_bus.c:888-897` (`devclass_delete_driver`):

```c
	for (i = 0; i < dc->maxunit; i++)
		if (dc->devices[i]) {
			dev = dc->devices[i];
			if (dev->driver == driver && dev->parent &&
			    dev->parent->devclass == busclass) {
				if ((error = device_detach(dev)) != 0)
					return(error);            /* <-- mid-loop abort */
				device_set_driver(dev, NULL);
		    	}
		}
```

`device_detach()` (`sys/kern/subr_bus.c:2128-2154`) is **destructive and
irreversible per device**: on success it frees the softc
(`device_set_driver(dev, NULL)` at 2150 → `kfree(dev->softc)` at 1944),
tears down the device's sysctl tree (2151), removes the devclass entry for
non-fixed-class devices (2146-2147) and sets `state = DS_NOTPRESENT`
(2149).  `devclass_delete_driver()` has **no rollback**: when a *later*
unit of the same driver vetoes detach (returns non-zero — e.g. `EBUSY`
from an open umass device, a mounted filesystem on the driver's disk, an
up network interface, or the driver bugs proven in DF-5544/5562/5635),
the function returns the error immediately, so:

1. every unit detached **before** the veto is now permanently dead — no
   driver, no softc, no sysctl node — while the driver **remains
   registered** and the kldunload fails;
2. the module unload is wedged: retries reach the same veto (earlier
   units are skipped because `dev->driver == NULL`), so the dead units
   can never be re-attached (`bus_generic_driver_added` only re-probes
   `DS_NOTPRESENT` children when a *new* driver is added,
   `sys/kern/subr_bus.c:3007-3016`, and no new driver-added event can
   occur while the module stays loaded);
3. the dead units linger as zombie entries in `bus_data_devices`, visible
   to unprivileged users through `hw.bus.devices`.

This is the in-`subr_bus.c` root of the detach-veto family proven this
session: DF-5544 (uhci), DF-5562 (ohci), DF-5635 (iicbus) are the
driver-side variants that *swallow* this same error.

## Build

```
scp rig.c walker.c Makefile run.sh dfbsd:/root/df5789/
vm.sh run_root 'cd /root/df5789 && mv run.sh run5789.sh &&
                make KERNBUILDDIR=/usr/obj/usr/src/sys/X86_64_GENERIC &&
                cc -O -o walker walker.c'
```

## Run

`sh run5789.sh` (root; leaves the module half-loaded on purpose, then
cooperates and unloads so the guest stays usable).

## Expected (stock kernel)

* `kldunload rig` → `kldunload: can't unload file: Device busy`
  AFTER dmesg shows `dfveto0: detached` followed by
  `dfveto1: DETACH VETO (EBUSY)`.
* In-kernel status read: `dfveto0 ... state=0 driver=0 ... softc=0`
  (DS_NOTPRESENT, driver NULL, softc freed) while the module is still
  loaded.
* `sysctl dev.dfveto` shows only unit `.1`; `dev.dfveto.0` subtree gone.
* Unprivileged `./walker` lists the zombie:
  `[110] dfveto0 drv= state=NOTPRESENT parent=0xfffff8008bb7f800`.
* Second `kldunload` fails identically (permanent wedge for unit 1).
* After `sysctl hw.df5789.cooperate=1` + `kldunload rig`, the module
  unloads but dfveto0 was skipped (`driver==NULL`) — it only comes back
  if the module is loaded again (partial self-heal via
  `bus_generic_driver_added`); during any persistent veto the hardware
  unit is dead until reboot.

## Fixed kernel (fix.diff: snapshot + rollback)

* `kldunload` still properly fails with `Device busy` (the veto is
  honored), **but** `dev.dfveto.0` is alive again: `state=3` (DS_ATTACHED),
  driver bound, sysctl node present — the rollback re-attached the units
  the aborted loop had detached.

## Files

* `rig.c` — KLD: driver `dfveto` on nexus (2 units, unit 1 vetoes until
  `hw.df5789.cooperate=1`) + driver `dfleak` (softc 8192, used by DF-5790).
* `walker.c` — unprivileged `hw.bus.devices` walker (libdevctl method).
* `run5789.sh` / `run5789.log` — baseline run.
* `fixrun5789.log` — run against the fixed kernel.
* `fix.diff` — git-apply-able fix (also fixes DF-5790).
* `build.log`, `run5790.log`, `fixrun5790.log`, `kbuild.log`,
  `kinstall.log` — full logs. `env.txt`, `rig.txt` — kernel log excerpts.
