# DF-4643 — dce_virtual .set callback runs the sleeping DragonFly hrtimer_cancel
# under adev->irq.lock

- File: `sys/dev/drm/amd/amdgpu/dce_virtual.c:736` (via `:744-755`), chain into
  `sys/dev/drm/linux_hrtimer.c:121-150`
- Severity: Medium (local DoS / lock-protocol violation; config-gated)
- Status: **untested** — requires an AMD GPU, `kldload amdgpu`, and
  `amdgpu.virtual_display=1`. The audit guest has no AMD GPU (only QEMU virtual
  VGA `vgapci0`) and amdgpu is not in GENERIC.

## The chain (all user-triggerable once the driver is up)

Any DRM ioctl that enables/disables a CRTC vblank (page-flip, DRM_IOCTL_WAIT_VBLANK,
modeset/DPMS) on a virtual-display GPU:

```
amdgpu_enable_vblank_kms / amdgpu_disable_vblank_kms   amdgpu_kms.c:1172-1194
  -> amdgpu_irq_get/put(adev, &adev->crtc_irq, idx)    amdgpu_irq.c:499-524
    -> amdgpu_irq_update(adev, src, type)              amdgpu_irq.c:451
       spin_lock_irqsave(&adev->irq.lock)              amdgpu_irq.c:458   <-- crit_enter + lockmgr EXCLUSIVE
       -> src->funcs->set(...)                          amdgpu_irq.c:467
          = dce_virtual_set_crtc_irq_state              dce_virtual.c:744
          -> dce_virtual_set_crtc_vblank_interrupt_state dce_virtual.c:715
             -> hrtimer_cancel(&crtc->vblank_timer)     dce_virtual.c:736
                -> lwkt_setcpu_self(timer->gd)          linux_hrtimer.c:140 (CPU migration)
                -> systimer_del(&timer->st)             linux_hrtimer.c:137/141
                -> taskqueue_drain(tq, &timer->task)    linux_hrtimer.c:144-145
                   -> TQ_SLEEP/ssleep                   subr_taskqueue.c:490-495  (BLOCKS)
       spin_unlock_irqrestore(&adev->irq.lock)          amdgpu_irq.c:468
```

On Linux, `hrtimer_cancel()` is atomic-context safe (it spins, never sleeps), which
is why the amdgpu code can call it from the `.set` callback that the irq core
invokes under a raw spinlock with IRQs off. The DragonFly emulation
(`sys/dev/drm/linux_hrtimer.c`) violates that contract: cancel may
(a) `taskqueue_drain` → `ssleep` — a blocking sleep while the caller holds
`adev->irq.lock` (a lockmgr lock here) and sits in the crit section entered by
`spin_lock_irqsave` (`local_irq_save` = `crit_enter`, see
sys/dev/drm/include/linux/irqflags.h:34-37, spinlock.h:75-79); and
(b) `lwkt_setcpu_self()` — migrate the thread across CPUs while it holds that lock.
Every amdgpu interrupt-state update on all CRTCs serializes behind the sleeping
thread; combined with the already-broken cancel state machine (DF-2174: `active`
cleared by the task epilogue makes cancel return without stopping the systimer;
DF-2175: `hrtimer_start_range_ns` re-inits an armed systimer), the disable path can
sleep for an unbounded drain and migrate a lock holder, producing long system-wide
stalls of the display irq path and compounding the DF-2174 un-cancellable timer.

This is distinct from DF-2174/2175 (both about `active`-flag/queue corruption inside
the emulation): the defect here is the *context contract* — a callback the irq core
calls under a lock/irqsave is legally allowed (per Linux semantics every drm
driver relies on) to call hrtimer primitives, and the emulation's blocking/migrating
implementation of `hrtimer_cancel` is illegal in that context.

## Fix directions

Either (a) in `dce_virtual.c`: do not touch hrtimers from the `.set` callback —
record the desired state and arm/cancel the software vblank timer from a work
item (or only from hw_init/hw_fini/SMP-call context), or (b) in
`linux_hrtimer.c`: make `hrtimer_cancel` non-blocking in atomic context (spin for
completion / return -EAGAIN semantics) so the Linux atomic-context contract holds.
