# DF-5617 — ig4_acpi.c attach-error unwinder re-enters the leaked serializer (ACPI twin of DF-5601)

**Status: reproduced (kernel panic through production code) | fix validated (A/B)**

## What

`ig4iic_acpi_attach()` sets `sc->pci_attached = 1` at `ig4_acpi.c:127` *before*
calling `ig4iic_attach(sc)` (`ig4_acpi.c:132`). When `ig4iic_attach()` fails on
its `bus_setup_intr()` path — which returns **with the serializer still held**
(`ig4_iic.c:648-655` has no `lwkt_serialize_exit()` on the error branch — the
root defect filed as DF-1049/DF-5601) — the unwinder
`ig4iic_acpi_detach(dev)` (`ig4_acpi.c:134` → `:146-151`) calls
`ig4iic_detach(sc)`, whose first action is `lwkt_serialize_enter(&sc->slz)`
(`ig4_iic.c:678`) — a re-entry of a self-held, explicitly non-recursive
serializer (`sys/serialize.h:41-42`): **KKASSERT panic on INVARIANTS kernels,
hard self-deadlock otherwise**.

## Rig — two halves

### (A) Real-path proof: planted ig4 ACPI device (no harness code in the path)

`ssdt_ig4p.asl` (generated by `gen_ssdt.py`, compiled with the guest's iasl)
declares `\_SB.I2C8`/`I2C9` with `HID INT33C2` (first entry of
`ig4iic_ids[]`, `ig4_acpi.c:71`) and a `_CRS` whose Memory32Fixed window
points **into the SSDT's own AML pad buffer** (QEMU places the appended SSDT
at `0xBFFE1CAC` — guest-RAM-backed but outside SMAP "System RAM", so
`ram_attach()` (`nexus.c:653-705`) has not reserved it and the allocation
succeeds; SMAP-resident windows are refused by the rman — observed live).
The pad carries `COMP_VER=0x3131352A` so the `IG4_ATOM` compat gate passes.

```
QEMU_EXTRA="-acpitable file=$PWD/ssdt_ig4p.aml" dfbsd-qemu/vm.sh up
vm.sh run_root 'kldload ig4'
```

Observed (run.log / dance logs): the REAL `ig4iic_acpi_probe` matches the HID,
the ACPI `_CRS` translation yields `iomem ... irq N on acpi0`, MMIO alloc
succeeds, and `ig4iic_attach()` (`ig4_acpi.c:132`) runs the full register
dance on the crafted device — reaching `bus_setup_intr()` with the latch
supplied by `latch_dance.py` (the DW ENABLE_STATUS 0x6C→0x9C latch does not
exist in RAM; the host plays it via QEMU monitor `gpa2hva` +
`/proc/<qemu>/mem`):

```
ig4iic0: <Designware I2C Controller> iomem 0xbffe1cdc-0xbffe2cdb irq 17 on acpi0
ig4iic0: version 1 type 33c2d0c0 params 00000000 general 00000000 comp_version 3131352a
ig4iic0: <Designware I2C Controller> [attached!]      <- full attach, all gates passed
```

Every step of `ig4_acpi.c:99-132` is real and live on this guest. The only
input that never occurs *naturally* on the QEMU rig is a `bus_setup_intr()`
**failure** (every reachable GSI deterministically avoids an exclusive-owner
conflict: the `_CRS` parser drops GSIs above the legacy map —
`acpi_resource.c:599` `machintr_legacy_intr_find` — and the IOAPIC auto-router
picks handler-free (cpu, gsi) slots). On real hardware the failure is any
`rman_activate_resource()` error or `register_int() == NULL`
(`nexus.c:519-532`, e.g. an exclusive-owner GSI conflict), which is exactly
what DF-5601 cited for the PCI twin.

### (B) The panic: production ig4iic_attach/ig4iic_detach through the ig4_acpi.c unwinder

`df5617.c` (built by `build_kld.sh` with `df56.c`, the DF-5601 pseudo-bus
whose `bus_setup_intr` returns EINVAL — the failure injection) replays
`ig4iic_acpi_attach()`'s prologue line-for-line (`ig4_acpi.c:104-127`, no
bzero — the DF-5600 twin-check; softc is M_ZERO-allocated by newbus,
`subr_bus.c:1953`), calls the REAL `ig4iic_attach()` (stock `ig4.ko`) on a
fake register file with a mirror kthread emulating the ENABLE_STATUS latch,
then replays `ig4_acpi.c:133-134`'s unwinder into the REAL `ig4iic_detach()`.

```
vm.sh run_root 'kldload ig4 && kldload df56.ko && kldload df5617.ko'
```

Baseline (stock kernel, run.log / panic.txt):

```
df5617ig40: Unable to setup irq: error 22                     <- ig4_iic.c:652
DF5617: ig4iic_attach() = 22 (EINVAL from bus_setup_intr)
DF5617: serializer is HELD by the ig4_iic.c:648-655 error path (IS_SERIALIZED: 1)
DF5617: replaying ig4_acpi.c:133-134 unwinder -> ig4iic_acpi_detach() -> ig4_acpi.c:146-147 ig4iic_detach()
panic: assertion "!IS_SERIALIZED((s))" failed in lwkt_serialize_enter at lwkt_serialize.c:107
lwkt_serialize_enter() at lwkt_serialize_enter+0x8c
ig4iic_detach() at ig4iic_detach+0x1f
df5617_attach() at df5617_attach+0x1d8
device_doattach() at device_doattach+0x3af
```

Guest wedged in DDB (`vm.sh status` ⇒ down) — boot/kldload-time kernel panic.

## Fix validation (A/B, same kernel config)

`fix.diff` (identical to DF-5601's verified one-liner) adds the missing
`lwkt_serialize_exit(&sc->slz)` on `ig4_iic.c`'s setup-intr error path — the
root of the chain both files' unwinders trip on. Patched `ig4.ko` built
in the guest (`fix_build.log`), identical scenario re-run (run-fixed.log):

```
DF5617: serializer is released by the ig4_iic.c:648-655 error path (IS_SERIALIZED: 0)
DF5617: ig4iic_acpi_detach replay = 0 -- returned cleanly, NO PANIC (fix effective)
```

Guest stayed up. Baseline panic vs patched clean return: **fix_status = fixed**.

## Impact

Boot-time or `kldload ig4`-time kernel panic / hard hang on any system where
the ACPI-enumerated ig4 controller's IRQ setup fails once (exclusive-owner GSI
conflict, rman activation failure). Availability bug — not
unprivileged-triggerable (needs the driver attached to a failing GSI /
crafted firmware): **Low**, consistent with DF-1049/DF-5601.

## Rig lessons

1. SMAP RAM-backed `_CRS` MMIO windows are refused by the nexus rman
   (`ram_attach` reserves all System RAM) — point planted windows into the
   fw_cfg ACPI table area (`0xBFFC0000-0xBFFFFFFF`), which is RAM but not
   SMAP; the appended SSDT's own bytes are the natural deterministic backing.
2. The window must be ≥ 0x810 bytes: `ig4_iic.c:504-508` unconditionally
   reads `IG4_REG_GENERAL` at +0x808; a 0x200 window page-faults the kernel
   (observed live — `Fatal trap 12 ... ig4iic_attach+0x12c: movl 0x808(%rdx)`).
3. `iasl` places AML buffer data at file offsets that shift with content —
   locate the register file with an embedded marker, recompute the window,
   recompile; the SSDT base itself (`0xBFFE1CAC`) is stable across boots.
4. QEMU monitor `gpa2hva` + `/proc/<qemu-pid>/mem` (ptrace_scope=0) gives
   sub-millisecond physical writes — resolve the hva *before* the trigger;
   `memwrite` latency (0.3-0.6 s per monitor round-trip) is too slow for
   the 100-tick poll windows.

## 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.
