# DF-4961 — amdgpu_i2c.c bit-bang adapters have NULL `adapter->lock_ops` → NULL function-pointer call in `i2c_transfer()` → kernel panic

## What

`amdgpu_i2c_create()`'s bit-bang branch (`sys/dev/drm/amd/amdgpu/amdgpu_i2c.c:193-212`)
registers the adapter with `i2c_bit_add_bus()` only. In DragonFly's linux-compat
i2c core, ONLY `i2c_add_adapter()` installs `adapter->lock_ops`
(`sys/dev/drm/linux_i2c.c:51-52`); `i2c_bit_add_bus()` does not (it only sets
`algo` and `retries`, `linux_i2c.c:128-134`). Every subsequent
`i2c_transfer()` on such an adapter executes
`adapter->lock_ops->lock_bus(adapter, I2C_LOCK_SEGMENT)` at `linux_i2c.c:99`
— a call through a NULL pointer → kernel panic.

radeon's identical bit branch carries an explicit
`#ifdef __DragonFly__ i2c_add_adapter(&i2c->adapter); #endif` with the comment
"setup lock_ops" (`radeon_i2c.c:970-972`), and the DP aux core assigns its own
`lock_ops` (`drm_dp_helper.c:1124`) — only amdgpu_i2c.c's bit branch omits it.

## Reachability (production)

* Every `!rec->hw_capable` GPIO DDC bus (legacy/VGA-class connectors; the
  `hw_capable && amdgpu_hw_i2c` branch at amdgpu_i2c.c:184 is not taken for
  them — `amdgpu_hw_i2c` defaults to 1, `amdgpu_drv.c:88`, irrelevant here).
* First `i2c_transfer()` on the adapter: EDID probe via
  `drm_get_edid(..., &ddc_bus->adapter)` (`amdgpu_connectors.c:308-313`) →
  `drm_do_probe_ddc_edid` (`drm_edid.c:1552`), or this file's own
  `amdgpu_i2c_router_select_ddc_port()` (`amdgpu_display.c:463`,
  `amdgpu_connectors.c:292`) / `..._cd_port()` (`dce_v8_0.c:3229`,
  `dce_v10_0.c:3341`, `dce_v11_0.c:3467`).
* Trigger is kernel-driven (display detect / HPD poll) once the amdgpu driver
  is bound — no user interaction needed; panic on driver load/probe.

## PoC (this directory)

The probe KLD compiles the REAL, UNMODIFIED audited files from the guest's
own `/usr/src/sys` (sha256-verified identical to the audit tree) —

* `sys/dev/drm/amd/amdgpu/amdgpu_i2c.c` (the audited file)
* `sys/dev/drm/linux_i2c.c` (the compat core with the deref)

— plus `agi2c_probe.c` (fake drm_device/pci_dev/bus_rec/connector + linkage
stubs for the never-reached atombios hw algo / mmio accessors / logging).
The NULL deref happens in `i2c_transfer()` BEFORE any hardware access, so no
AMD GPU is required.

## Build

```
./build.sh        # or manually: transfer agi2c_probe.c + Makefile, then
                  #   cd /tmp/df4961 && make      (-Werror, bsd.kmod.mk)
```

## Run

```
./run.sh          # phase 1: baseline  -> PANIC
                  # phase 2: fix.diff applied to /usr/src copy, rebuild
                  #         -> lock_ops set, no panic, clean kldunload
```

## Expected output

Baseline (`run.log`):
```
agi2c: chan=0xfffff8008de6f580 adapter.name='AMDGPU i2c bit bus DF4961-probe'
agi2c: adapter.algo=0xffffffff826025a0 (== &i2c_bit_algo: YES - bit bus) master_xfer=0xffffffff82600d10
agi2c: adapter.lock_ops=0  <- i2c_transfer() needs this non-NULL
agi2c: FIRING - REAL amdgpu_i2c_router_select_ddc_port(&fconn)...
Fatal trap 12: page fault while in kernel mode
fault virtual address = 0x0
Stopped at i2c_transfer+0x2b: movq (%rax),%rax        <- loading lock_bus from NULL
```
Guest dies (`vm.sh status` → down).

Patched (`run.2.log`):
```
agi2c: adapter.lock_ops=0xffffffff826025d0  <- i2c_transfer() needs this non-NULL
agi2c: lock_ops set - fix applied, firing anyway (no panic expected)
agi2c: FIRING - REAL amdgpu_i2c_router_select_ddc_port(&fconn)...
agi2c: [drm_dbg stub amdgpu_i2c_get_byte] i2c 0x03 0x00 read failed
... (4 get/put ops complete via the compat bit_xfer stub)
agi2c: survived amdgpu_i2c_router_select_ddc_port (unfixed build panics ...)
agi2c: unloaded
```
KLDLOAD2_OK + KLDUNLOAD_OK, guest stays up.

## Fix

`fix.diff` — mirror radeon: call `i2c_add_adapter()` after
`i2c_bit_add_bus()` in the bit branch (git-apply-able against the repo root).
