/*
 * dfv_2969.c — suspend_kproc lifetime protocol violation.
 *   dfv.s2969=1: spawn kthread A that immediately kthread_exit()s, then
 *   call suspend_kproc(tdA, 0). The target is gone: wakeup()/tsleep() use
 *   a stale identity and the loop reads/writes td_mpflags on a parked
 *   (freed) struct thread -> unkillable sleeper holding kpsus_token.
 *   dfv.s2969x=1: second suspend_kproc on a LIVE kthread -> blocks forever
 *   acquiring kpsus_token (proves the wedge extends system-wide).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/kthread.h>
#include <sys/thread.h>
#include <sys/thread2.h>

static struct thread *dead_td;
static struct thread *live_thr;

static void
exiter_thread(void *arg)
{
	kthread_exit();
}

static void
live_thread(void *arg)
{
	int i = 0;
	for (;;)
		tsleep(&i, 0, "dfvlive", hz);
}

static int
dfv_s2969(SYSCTL_HANDLER_ARGS)
{
	int error, go = 0;

	error = sysctl_handle_int(oidp, &go, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (go != 1)
		return (0);

	kprintf("DF2969: creating kthread that immediately exits...\n");
	kthread_create(exiter_thread, NULL, &dead_td, "dfvdead");
	tsleep(&go, 0, "dfvw", hz);		/* let it exit and park */
	kprintf("DF2969: target td=%p exited (parked); calling "
		"suspend_kproc(td, 0) on it now\n", dead_td);
	kprintf("DF2969: entering suspend_kproc...\n");
	suspend_kproc(dead_td, 0);		/* expected: never returns */
	kprintf("DF2969: suspend_kproc returned?!\n");
	return (0);
}

static int
dfv_s2969x(SYSCTL_HANDLER_ARGS)
{
	int error, go = 0;

	error = sysctl_handle_int(oidp, &go, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (go != 1)
		return (0);
	if (live_thr == NULL)
		kthread_create(live_thread, NULL, &live_thr, "dfvlive");
	kprintf("DF2969: calling suspend_kproc on LIVE kthread %p -> should "
		"wedge on kpsus_token held by the stuck suspender\n", live_thr);
	suspend_kproc(live_thr, 1);
	kprintf("DF2969: live suspend_kproc returned (kpsus_token not held?)\n");
	return (0);
}

static SYSCTL_NODE(, OID_AUTO, dfv69, CTLFLAG_RW, 0, "DF 2969 rig");
SYSCTL_PROC(_dfv69, OID_AUTO, s2969, CTLTYPE_INT|CTLFLAG_RW, 0, 0,
    dfv_s2969, "I", "suspend dead kthread");
SYSCTL_PROC(_dfv69, OID_AUTO, s2969x, CTLTYPE_INT|CTLFLAG_RW, 0, 0,
    dfv_s2969x, "I", "suspend live kthread (wedge probe)");

static int
dfv69_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("dfv_2969 loaded\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);		/* threads may be wedged */
	}
	return (EOPNOTSUPP);
}
static moduledata_t dfv69_mod = { "dfv_2969", dfv69_modevent, 0 };
DECLARE_MODULE(dfv_2969, dfv69_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
