# DF-3775 — DHRS-error path bypasses the blockable filter and runs blocking error recovery (tsleep, 1–2 s polls) in hard-interrupt context

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

## What the bug is

`ahci_port_intr(ap, blockable)` is called with `blockable == 0` from the real
interrupt handler (`ahci_intr()` → `ahci_os_lock_port_nb()` success,
ahci.c:2511-2513). For that case the driver carefully filters the interrupt
sources that need **blocking** recovery and defers them to the port thread:

```c
	if (blockable == 0) {
		...
		if (is & blockable_mask) {           /* TFES|IFS|PCS|PRCS|HBFS|OFS|UFS */
			ahci_pwrite(ap, AHCI_PREG_IE, 0);
			ahci_os_signal_port_thread(ap, AP_SIGF_PORTINT);
			return;                          /* ahci.c:2602-2606 — defer */
		}
	}
```

`AHCI_PREG_IS_DHRS` is deliberately **not** in `blockable_mask` (ahci.c:2565-2571)
because the normal DHRS handling is non-blocking. However the DHRS *error*
sub-path is fully blocking:

```c
	} else if (is & AHCI_PREG_IS_DHRS) {
		...
		if ((tfd & AHCI_PREG_TFD_STS_ERR) &&
		    (cmd & AHCI_PREG_CMD_ST) == 0) {
			err_slot = ...;
			goto process_error;              /* ahci.c:2851 */
		}
```

`process_error` (ahci.c:2649-2807) then executes, **in the PCI interrupt
thread**:

- `ahci_port_stop()` (2671) → `ahci_pwait_clr(CR)` → `ahci_pwait_eq()`
  (ahci.c:3612-3632): after a 100 µs hard loop it calls
  **`ahci_os_softsleep()` = `tsleep()`** for up to AHCI_PWAIT_TIMEOUT (1000 ms);
- `ahci_os_hardsleep(10)` (2672) and possibly `ahci_port_clo()` (2675, another
  1 s pwait);
- for NCQ: `ahci_port_start()` (2717, up to 2000 ms pwait) then
  `ahci_port_read_ncq_error()` (2718) → `ahci_poll()` with 1000 ms of
  softsleeps, then `ahci_port_stop()` again (2719).

DragonFly's `tsleep()` has no interrupt-context guard (verified:
sys/kern/kern_synch.c — no TDF_INTTHREAD check), so the interrupt thread is
simply **descheduled for seconds** with the port lock held. On a shared IRQ
vector every other device on that vector is stalled for the duration; if the
timed-out register never clears, each pwait burns its full 1–2 s budget.

The design intent (the whole point of `blockable_mask`) is that none of this
runs in ISR context — the DHRS-error goto silently violates it.

## Impact

Interrupt-thread stalls of up to ~5 s per occurrence with the port lock held;
repeatable by any SATA device that raises a D2H FIS with ERR while the command
processor is stopped (e.g. a device error coincident with a port stop — the
same conditions CAM error recovery produces). Degraded-but-recoverable DoS /
latency hazard, not memory unsafety → Low.

## Why not reproduced on the guest

No AHCI controller on the audit guest (env.txt) — the interrupt path cannot be
entered. Source analysis is line-accurate.
