# DF-4976 — flr_work never canceled/flushed at mailbox teardown (put_irq): queued work outlives the device

File: `sys/dev/drm/amd/amdgpu/mxgpu_ai.c` (pass 2, GLM 5.3)

## What

`xgpu_ai_mailbox_put_irq()` (mxgpu_ai.c:365-369) — the designated teardown
hook, invoked from `soc15_common_hw_fini` (soc15.c:843-846) — only drops the
two irq sources. It never `cancel_work_sync()`/`flush_work()`s
`adev->virt.flr_work`, and no other teardown path in the tree does either
(the only references to `flr_work` are mxgpu_ai.c:235/237/296/360 and
amdgpu_virt.h:246).

A `flr_work` that was queued by `xgpu_ai_mailbox_rcv_irq` (mxgpu_ai.c:296)
shortly before teardown survives `put_irq`:

- the handler `xgpu_ai_mailbox_flr_work` (mxgpu_ai.c:235-271) immediately
  touches the device: `mutex_trylock(&adev->lock_reset)` (:250),
  `RREG32_NO_KIQ` mailbox peeks in a `msleep(10)` loop for up to 500 ms
  (`AI_MAILBOX_POLL_FLR_TIMEDOUT`, mxgpu_ai.h:239/…), then
  `amdgpu_device_gpu_recover(adev, NULL)` (:270).

Consequences, in increasing severity:

1. **During GPU recovery / suspend** (put_irq runs per IP hw_fini): the
   surviving work re-enters `amdgpu_device_gpu_recover` concurrently with
   the recovery/suspend cycle that disabled its irq — the mutex serializes
   them into an unintended back-to-back double recovery racing the
   fini/resume state machine.
2. **During driver detach** (`amdgpu_driver_unload_kms`, amdgpu_kms.c:75 →
   `amdgpu_device_fini` → per-IP hw_fini → put_irq, then
   `drm_dev_put(dev)` amdgpu_drv.c:974 frees `dev->dev_private` == `adev`):
   the queued work dereferences freed memory — `container_of` on the dead
   `flr_work` (mxgpu_ai.c:237-238), lock/unlock of a freed
   `struct lock`, MMIO reads through freed register mappings, and a call
   into `amdgpu_device_gpu_recover` on the freed device. **Use-after-free.**

The DragonFly shim provides the missing idiom — `cancel_work_sync` exists
(sys/dev/drm/include/linux/workqueue.h:131) and is already used in this
very subsystem's teardown (amdgpu_irq.c:279/310
`cancel_work_sync(&adev->reset_work)`), so the omission is plainly a bug,
not a shim limitation.

Aggravating note (functional, same root): `get_irq` runs only in
`late_init` (probe/resume). GPU recovery runs hw_fini→put_irq but never
re-runs late_init, so after the first recovery the mailbox RCV irq stays
disabled — FLR notifications are then silently lost, which both widens the
stale-work hazard window at the *next* resume and degrades host FLR
coordination.

## Threat model

SR-IOV VF guest (Vega20-class). Queuing requires an `IDH_FLR_NOTIFICATION`
from the host/PF (mxgpu_ai.c:294-296) — host-timed, like DF-4755/4975 —
landing just before hw_fini/put_irq of a detach, suspend, or recovery.
A guest-local user can widen the recovery-path variant by causing GPU job
timeouts (any user with /dev/dri access can hang the ring long enough for
TDR), which forces the recovery teardown that put_irq's without checking
for a pending FLR work. Impact ceiling: use-after-free at detach; race at
recovery. **Low** (host-timed, teardown window, VF-only), consistent with
the family's ratings.

## Recommended fix (fix.diff in this pack)

```diff
 void xgpu_ai_mailbox_put_irq(struct amdgpu_device *adev)
 {
 	amdgpu_irq_put(adev, &adev->virt.ack_irq, 0);
 	amdgpu_irq_put(adev, &adev->virt.rcv_irq, 0);
+	cancel_work_sync(&adev->virt.flr_work);
 }
```

## Reproduce / verification status

Not runnable on the audit guest (no AMD GPU / SR-IOV; see env.txt in the
DF-4975 pack, identical guest). Verification is by code trace
(VERDICT.md).
