# DF-4300 — vkbd_dev_close check-then-sleep TASK handshake can miss the drain task's wakeup while pinning the kernel mplock

## Finding
`vkbd_dev_close()` (sys/dev/misc/vkbd/vkbd.c:307-308) waits for the drain
task with a plain check-then-sleep loop:

```c
	while (state->ks_flags & TASK)
		VKBD_SLEEP(state, ks_task, "vkbdc", 0);   /* tsleep, PCATCH */
```

and the task side (`vkbd_dev_intr`, vkbd.c:627-632) clears the flag and
wakes the sleeper *after* draining:

```c
	kbd_intr(kbd, NULL);
	state->ks_flags &= ~TASK;
	wakeup(&state->ks_task);
```

There is no interlock (no tsleep_interlock/critsec between the flag test
and the sleep), so a wakeup that lands between the close path's flag
test and its tsleep() insertion is lost: close sleeps indefinitely.

Aggravating factor: vkbd's `dev_ops` is not `D_MPSAFE` (vkbd.c:179-190,
flags 0), so `d_close` is dispatched under the kernel mplock
(sys/kern/kern_device.c:174-189, `dev_needmplock()` at :119-121), and
DragonFly's `tsleep()` does **not** release the mplock
(sys/kern/kern_synch.c — no mplock handling). A close that blocks here
pins the mplock, stalling every other non-MPSAFE `dev_d*()` call in the
system until the sleeper receives a signal (PCATCH) or forever.

## Status
Not reproduced live: ~90,000 open/write/close cycles racing the drain
task against close (`vkbd_wedge.c`, log in ../DF-4302 session) never
observed a stuck close — the check→tsleep window is empirically very
narrow. The race is code-real (no interlock) but stochastic; filed as
speculative.

## Fix
Use an interlocked sleep (test-and-sleep under a critical section /
tsleep_interlock pattern), or have the task hold off clearing TASK until
it can re-check sleepers; simplest: perform the wait with the standard
`tsleep_interlock()+tsleep()` pairing around the flag check.
