# DF-3773 — ahci_beg_exclusive_access() drain loop never processes deferred timeouts → permanent port-thread wedge with the port lock held

File: `sys/dev/disk/ahci/ahci.c` (pass 2, GLM 5.3)

## What the bug is

```c
void
ahci_beg_exclusive_access(struct ahci_port *ap, struct ata_port *at)
{
	KKASSERT((ap->ap_flags & AP_F_EXCLUSIVE_ACCESS) == 0);
	ap->ap_flags |= AP_F_EXCLUSIVE_ACCESS;
	while (ap->ap_active || ap->ap_sactive) {         /* ahci.c:2266 */
		ahci_port_intr(ap, 1);
		ahci_os_softsleep();
	}
}
```

The loop assumes every active command will eventually complete via
`ahci_port_intr()`. That is false for a CCB whose timeout has fired but was
**deferred**: `ahci_ata_cmd_timeout()` (ahci.c:3873-3884) sets
`ap->ap_expired` and *returns without completing* whenever the port is still
running and other non-expired commands are active. The only other places a
deferred-expired CCB gets processed are `ahci_check_active_timeouts()`
(ahci.c:2192-2207) and the completion walk in `ahci_port_intr` (ahci.c:3186) —
neither of which can retire a CCB whose hardware CI/SACT bit is still set
(device hung: `~ci_saved & *active` never includes it, ahci.c:3152).

The driver knows this and handles it everywhere else it spins:
`ahci_poll()` calls `ahci_check_active_timeouts(ap)` inside its own wait loop
(ahci.c:2160-2163, comment: *"When polling we have to check if the currently
active CCB(s) have timed out as the callout will be deadlocked while we hold
the port lock"*). `ahci_beg_exclusive_access()` — which holds the same port
lock and can spin for the same reasons — **does not**.

## Consequence

`ahci_beg_exclusive_access()` is called from the PM state machine
(ahci.c:938) which runs under the port lock from `ahci_xpt_action`
XPT_ENG_EXEC (ahci_cam.c:888-890) or the port thread's initial probe
(ahci_dragonfly.c:375). If a command to a PM target is deferred-expired and
the device never completes it:

- the loop spins forever **holding `ap->ap_lock`**;
- every subsequent I/O to the port blocks on that lock;
- `ahci_intr()` can no longer take the lock non-blocking, so it defers all
  port interrupts to the port thread (ahci.c:2514-2517) — which also blocks on
  the lock ⇒ the port is permanently dead and a kernel thread is wedged.

Preconditions: port multiplier attached, a command to one target hangs
(timeout deferred), then a hot-plug/state-machine event on another target
enters `ahci_beg_exclusive_access`. Hardware-dependent DoS → Low.

## Why not reproduced on the guest

No AHCI controller and no PM on the audit guest (env.txt). Source-level logic
is definite; reachability requires a hung SATA device behind a PM.

## Reproduce on suitable hardware

PM with ≥2 targets; target 0 disk hangs mid-read (its timeout defers while
target 1 has an in-flight command — or vice versa); then hot-(re)plug target 1
so the state machine calls `ahci_beg_exclusive_access`. Observe the port thread
spin at 100% in the loop with `ap_expired != 0` and the port lock held.
