/*
 * df4918.c — DF-4918: drain_workqueue() sleeps while HOLDING worker_lock
 *            (sys/dev/drm/linux_workqueue.c:276-281) — permanent
 *            kernel-thread + workqueue deadlock.
 *
 *	drain_workqueue(wq):
 *		for (i...) {
 *			lockmgr(&worker->worker_lock, LK_EXCLUSIVE);
 *			while (!STAILQ_EMPTY(&worker->ws_list_head)) {
 *				tsleep(&drain_workqueue, 0, "wkdrain", 1);
 *			}
 *			lockmgr(&worker->worker_lock, LK_RELEASE);
 *		}
 *
 * Plain tsleep() does NOT release held locks (only lksleep() does).
 * The worker thread needs that very lock to dequeue and run the remaining
 * works (process_all_work re-acquires at linux_workqueue.c:91), so once
 * drain_workqueue() acquires the lock of a worker whose queue is
 * non-empty:
 *
 *   - the drainer loops forever, one tick at a time, holding the lock;
 *   - the worker blocks forever in lockmgr() at :91;
 *   - every later queue_work() on the same workqueue blocks forever at
 *     :134 (cascade: any thread touching the wq wedges too).
 *
 * This is the OPPOSITE failure mode of the already-filed DF-1977 ("drain
 * does not wait for in-flight works" = returns too early); here drain
 * NEVER returns and permanently wedges the wq.  Root cause is distinct:
 * sleep-with-lock-held vs missing in-flight wait.
 *
 * Deterministic module test (no GPU needed):
 *   kldload drm
 *   kldload ./df4918.ko
 *   sysctl hw.df4918_test=1
 *     -> slow_fn() runs (worker released the lock for the func body),
 *        filler work still queued, drain_workqueue() grabs the lock and
 *        wedges.  After 12s the controller reports:
 *            slow_done=1 (func finished long ago)
 *            drain_done=0 filler_done=0  <- worker starved of its lock
 *            probe_started=1 probe_done=0 <- queue_work() wedged too
 *        sysctl hw.df4918_status re-prints the frozen state.
 *
 * With the fix (lksleep instead of tsleep) the identical run completes:
 *   drain_done=1 filler_done=1 probe_done=1, guest healthy.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/lock.h>
#include <sys/thread.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/module.h>

#include <linux/workqueue.h>

static struct workqueue_struct *g_wq;
static struct work_struct g_slow, g_filler, g_probe;
static volatile int g_slow_done, g_filler_done, g_probe_done;
static volatile int g_drain_started, g_drain_done;
static volatile int g_probe_started;
static volatile int g_ran;
static struct thread *g_drain_td, *g_probe_td;

static void
slow_fn(struct work_struct *w)
{
	/* worker released worker_lock to run this; sleep long enough for
	 * the drain to arrive while the filler is still queued */
	tsleep(slow_fn, 0, "df4918s", 3 * hz);
	g_slow_done = 1;
}

static void
filler_fn(struct work_struct *w)
{
	g_filler_done = 1;
}

static void
probe_fn(struct work_struct *w)
{
	g_probe_done = 1;
}

static void
drain_thread(void *arg)
{
	g_drain_started = 1;
	drain_workqueue(g_wq);		/* never returns on unpatched kernel */
	g_drain_done = 1;
	for (;;)
		tsleep(drain_thread, 0, "df4918d", 0);
}

static void
probe_thread(void *arg)
{
	g_probe_started = 1;
	/* wedges forever in lockmgr() at linux_workqueue.c:134 while the
	 * drainer holds the lock across its tsleep */
	queue_work(g_wq, &g_probe);
	g_probe_done = 1;
	for (;;)
		tsleep(probe_thread, 0, "df4918p", 0);
}

static int
df4918_run_test(void)
{
	int i, error;

	if (g_ran)
		return (EBUSY);		/* threads are wedged; no re-run */
	g_ran = 1;

	g_wq = alloc_workqueue("df4918wq", WQ_UNBOUND, 1);	/* 1 worker */
	if (g_wq == NULL) {
		kprintf("[DF4918] alloc_workqueue failed\n");
		return (ENOMEM);
	}
	INIT_WORK(&g_slow, slow_fn);
	INIT_WORK(&g_filler, filler_fn);
	INIT_WORK(&g_probe, probe_fn);

	queue_work(g_wq, &g_slow);	/* worker dequeues + runs (3s)  */
	queue_work(g_wq, &g_filler);	/* stays queued while slow runs */

	kprintf("[DF4918] queued slow(3s)+filler; waiting 1s for the worker "
	    "to enter slow_fn (lock released)...\n");
	tsleep(df4918_run_test, 0, "df4918w", 1 * hz);

	error = lwkt_create(drain_thread, NULL, &g_drain_td, NULL,
	    TDF_NOSTART, -1, "df4918drain");
	if (error == 0)
		lwkt_schedule(g_drain_td);
	kprintf("[DF4918] drain thread started: drain_workqueue() acquires "
	    "the worker lock and tsleep()s HOLDING it (linux_workqueue.c:"
	    "276-281)\n");

	/* let the deadlock establish itself, then probe the cascade */
	tsleep(df4918_run_test, 0, "df4918w", 2 * hz);
	error = lwkt_create(probe_thread, NULL, &g_probe_td, NULL,
	    TDF_NOSTART, -1, "df4918probe");
	if (error == 0)
		lwkt_schedule(g_probe_td);

	for (i = 0; i < 12; i++) {
		tsleep(df4918_run_test, 0, "df4918w", hz);
		kprintf("[DF4918] t+%2ds: drain_done=%d filler_done=%d "
		    "slow_done=%d probe_done=%d\n", 3 + i,
		    g_drain_done, g_filler_done, g_slow_done, g_probe_done);
		if (g_drain_done && g_filler_done && g_probe_done)
			break;
	}

	if (g_drain_done == 0 && g_filler_done == 0 && g_probe_started == 1 &&
	    g_probe_done == 0) {
		kprintf("[DF4918] DEADLOCK CONFIRMED: drain_workqueue() never "
		    "returned, the worker never drained the queued filler, "
		    "and queue_work() is wedged on the lock the drainer "
		    "sleeps on.\n");
	} else if (g_drain_done && g_filler_done) {
		kprintf("[DF4918] drain completed (no deadlock) — fixed "
		    "kernel?\n");
	}
	kprintf("[DF4918] done (threads park; module refuses unload)\n");
	return (0);
}

static int
sysctl_df4918_test(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 1)
		return (df4918_run_test());
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, df4918_test, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, sysctl_df4918_test, "I", "df4918 drain_workqueue deadlock test");

static int
sysctl_df4918_status(SYSCTL_HANDLER_ARGS)
{
	char buf[256];
	int len;

	len = ksnprintf(buf, sizeof(buf),
	    "ran=%d drain_started=%d drain_done=%d slow_done=%d "
	    "filler_done=%d probe_started=%d probe_done=%d "
	    "(drain_done=0&filler_done=0&probe_done=0 => WEDGED)\n",
	    g_ran, g_drain_started, g_drain_done, g_slow_done,
	    g_filler_done, g_probe_started, g_probe_done);
	return (SYSCTL_OUT(req, buf, len));
}
SYSCTL_PROC(_hw, OID_AUTO, df4918_status, CTLTYPE_STRING | CTLFLAG_RD,
    0, 0, sysctl_df4918_status, "A", "df4918 deadlock state");

static int
df4918_init(struct module *mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("[DF4918] loaded: sysctl hw.df4918_test=1 (needs "
		    "drm.ko loaded first: kldload drm)\n");
		break;
	default:
		break;
	}
	return (0);
}

static int
df4918_uninit(struct module *mod, int what, void *arg)
{
	switch (what) {
	case MOD_UNLOAD:
		if (g_ran) {
			kprintf("[DF4918] refusing unload: kernel threads are "
			    "wedged by the bug (reset the guest)\n");
			return (EBUSY);
		}
		break;
	default:
		break;
	}
	return (0);
}

static moduledata_t df4918_mod = { "df4918", df4918_init, df4918_uninit };
DECLARE_MODULE(df4918, df4918_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
