# DF-5527 Verification Verdict

**Target:** `sys/bus/gpio/gpio_intel/gpio_cherryview.c`
**Class:** interrupt-lifecycle defect (missing hardware mask-clear on
free) → latent interrupt-storm DoS / hard lockup
**Severity:** Info (latent — no in-tree caller misuses the order today)
**Verification:** reproduced on the QEMU guest with the stock kernel and
the DF-5518 planted-ACPI rig (production-code harness; no kernel
modifications for the baseline); fix validated on a rebuilt kernel.

## Verdict: REPRODUCED (state-level), FIX VALIDATED

`gpio_cherryview_unmap_intr` (gpio_cherryview.c:380-414) destroys the
mapping (`map->pin = -1` at :393, `map->intidx = -1` at :394,
`map->is_level = 0` at :395) and restores the pad's INTCFG/GPIOCFG, but
**never touches `CHV_GPIO_REG_MASK` (0x380)**. The only code that ever
clears a MASK bit is `gpio_cherryview_disable_intr` (:441-444), reached
solely via `GPIO_TEARDOWN_INTR`. The `gpio_if.m` contract does not
require teardown-before-free, and `gpio_intel_free_intr`
(gpio_intel.c:250-263) calls `unmap_intr` directly with no disable.

Consequence for a caller that frees without teardown: the community IRQ
line stays **unmasked with no mapping**. If the pad then asserts
(level source), `gpio_cherryview_intr` (:219-235) reads GPIIS, sees the
bit, reads `mapping->is_level` — which `unmap_intr` just zeroed — takes
the *edge* clear path (W1C at :225), the level source re-asserts
GPIIS immediately, the IRQ re-fires: endless interrupt storm → hard
lockup / DoS. (The QEMU rig cannot inject ISA IRQ 5, so the storm loop
itself is argued from the ISR code; the armed-line *state* — MASK bit
set while `intrmaps[i].pin == -1` — is what was reproduced live.)

## Harness

`gpiofree.c` (kmod, stock kernel, DF-5518 rig): finds `gpio_intel0`
(devclass unit 0), plants two pads in the RAM register image
(pin 8 → INTSEL line 5, pin 15 → INTSEL line 6; CTL0 = INTSEL<<28 |
GPIOEN, GPIOCFG=0; CTL1 = INTCFG 2 = edge/rising to satisfy
`map_intr`'s sanity checks at :260-329), then:

* **A (defect path)**: `GPIO_ALLOC_INTR(8)` → `GPIO_SETUP_INTR` →
  `GPIO_FREE_INTR` — teardown deliberately skipped.
* **B (control)**: `GPIO_ALLOC_INTR(15)` → `GPIO_SETUP_INTR` →
  `GPIO_TEARDOWN_INTR` → `GPIO_FREE_INTR`.

State is dumped after every step from inside the kernel (PHYS_TO_DMAP
reads of IS/MASK + `sc->intrmaps[]`) and cross-checked from userland
with `/dev/mem` reads at 0xD0300/0xD0380.

## Baseline run (stock kernel, run.log)

```
gpiofree[A-setup]: IS=00000020 MASK=00000020 (bit5=1 bit6=0) map5{pin=8,intidx=5,is_level=0}
gpiofree[A-free]:  IS=00000020 MASK=00000020 (bit5=1 bit6=0) map5{pin=-1,intidx=-1,is_level=0}
gpiofree[B-done]:  IS=00000040 MASK=00000020 (bit5=1 bit6=0) map6{pin=-1,...}
gpiofree: SUMMARY MASK=00000020: line5 armed-with-no-mapping=1 (DEFECT if 1), line6 left-armed=0
```
Userland cross-check: `/dev/mem` @0xD0380 = `20 00 00 00`.

After `GPIO_FREE_INTR` on line 5 the MASK bit is **still set** while
`intrmaps[5].pin == -1` — the orphaned armed line, verbatim. Control
arm B (teardown first) never leaves its bit set.

## Fix validation (run.fixed.log, fix_build.log)

`fix.diff` makes `unmap_intr` capture `map->intidx` *before* destroying
the mapping and, after destroying it, clear the MASK bit and W1C the IS
bit for that line. Applied to the guest's `/usr/src` copy
(`patch -p1`, 2/2 hunks), kernel rebuilt
(`make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel`,
RC=0, kern.version `#1: Tue Oct 6 23:06:02`), rig rebooted, **identical
harness re-run**:

```
gpiofree[A-setup]: IS=00000020 MASK=00000020 (bit5=1 bit6=0) map5{pin=8,intidx=5,is_level=0}
gpiofree[A-free]:  IS=00000020 MASK=00000000 (bit5=0 bit6=0) map5{pin=-1,intidx=-1,is_level=0}
gpiofree: SUMMARY MASK=00000000: line5 armed-with-no-mapping=0 (DEFECT if 1), line6 left-armed=0
```
Userland cross-check: `/dev/mem` @0xD0380 = `00 00 00 00`.

The only variable between the two runs is `fix.diff`: baseline
`armed-with-no-mapping=1` → patched `0`. Guest healthy after both runs.

## Why Info

The sole in-tree free path, `gpio_acpi_unmap_aei` (gpio_acpi.c:447-454),
always does `GPIO_TEARDOWN_INTR` before `GPIO_FREE_INTR`, so nothing in
the tree triggers the storm today; the provider's gpio_if contract
simply doesn't defend against the natural misuse. Defense-in-depth fix,
one hunk, validated.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
