# DF-5745 — apple_gmux: dual-instance attach clobbers the shared global softc pointer → NULL-deref panic in vga_switcheroo dispatch

## What

`apple_gmux_softc_data` is a single static global (apple-gmux.c:130). In
`apple_gmux_attach`:

* line 867: `apple_gmux_softc_data = sc;` — **unconditional**, even for a
  second gmux ACPI device;
* lines 880-884: `vga_switcheroo_register_handler()` fails with `-EINVAL`
  for the second instance (a handler is already registered,
  `sys/dev/video/vga/vga_switcheroo.c:242-245`);
* line 894 (error path): `apple_gmux_softc_data = NULL;` — **also
  unconditional**.

Net state after the second (failed) attach: **the first instance's handler is
still registered, but the global it dispatches through is NULL.** Every
vga_switcheroo dispatch then executes

```c
gmux_switchto():  apple_gmux_softc_data->switch_state_ddc = id;   /* :511 */
gmux_switch_ddc(): ...->switch_state_ddc                          /* :524 */
gmux_set_power_state(): gmux_set_discrete_state(apple_gmux_softc_data, ...) /* :581 */
```

→ write to NULL+offset → fatal page fault.

Natural trigger on affected systems: i915 load
(`sys/dev/drm/i915/i915_drv.c:742` — `if (vga_switcheroo_handler_flags() &
VGA_SWITCHEROO_CAN_SWITCH_DDC) vga_switcheroo_force_migd();` →
`vgasr_priv->handler->switchto` with no NULL guard,
vga_switcheroo.c:1225-1239). Also `/dev/vga_switcheroo` writes (root-only,
0600) and any DRM EDID probe via `vga_switcheroo_lock_ddc`.

Precondition: two ACPI devices with `_HID APP000B` (crafted DSDT/SSDT, or
duplicated gmux entries from a firmware bug).

## Rig

* `ssdt_b.aml`: AAA0 @ 0x8e + AAA2 @ 0x4cc, 4-port `_CRS` each (both reach
  full attach: version reads at base+4 hit readable QEMU ports 0x92 / ELCR
  0x4d0-0x4d1, so the classic path is taken).
* `ssdt_b2.aml`: same two devices with **valid-length** 0x78-port regions
  (AAA0 @ 0x4cc, AAA2 @ 0x8e) — proves the bug is independent of the
  DF-5744 length issue.
* `gmuxtrig.c` → `gmuxtrig.ko`: calls the same production entry point i915
  uses at boot: `vga_switcheroo_force_migd()`.

## Build / run (guest, root)

```
cd /root/gmuxtrig_b && make
# boot QEMU with: -acpitable file=ssdt_b.aml
kldload apple_gmux                       # AAA0 attaches; AAA2 fails at -22
kldload /root/gmuxtrig_b/gmuxtrig.ko     # -> panic
```

## Expected result (observed twice: 4-port rig and 0x78-length rig)

```
apple_gmux: Failed to register vga_switcheroo handler
device_probe_and_attach: apple_gmux1 attach returned -22
gmuxtrig: vga_switcheroo handler_flags = 0x1 (CAN_SWITCH_DDC=1)
gmuxtrig: calling vga_switcheroo_force_migd() ...
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x60
fault code = supervisor write data, page not present
Stopped at gmux_switchto+0x10: movl %edi,0x60(%rbx)      ; rbx == NULL global
```

Offset 0x60 == offsetof(struct apple_gmux_softc, switch_state_ddc) — the
exact store at apple-gmux.c:511.

## Fix validation (fix.diff, module-only rebuild)

* Guard `gmux_switchto`/`gmux_switch_ddc`/`gmux_set_power_state` against a
  NULL global (panic → `-ENODEV`).
* Clear the global on error/detach only when this softc owns it.
* Take ownership of the global only when it is still NULL (line 867).

Same 0x78-length rig, patched module, same trigger:

```
gmuxtrig: force_migd returned 0, NO PANIC     # dispatch drives the surviving instance
```

(Intermediate fix without the ownership hunk: `returned -19, NO PANIC` — no
crash but dead handler; the ownership hunk restores function.)

## Files

* `make_ssdt.py` (shared), `ssdt_b.aml`, `ssdt_b2.aml`
* `gmuxtrig.c` / `Makefile` — trigger KLD
* `panic.txt`, `panic.b2.txt` — panic signatures (both rigs)
* `run.log` — decisive run transcript
* `fix.diff` — 6 hunks; `fix_validation.log` — baseline/patched comparison
