/*
 * DF-2849 PoC variant A — mode 1: enqueue racing taskqgroup_detach() in the
 * window where gtask->gt_taskqueue is already NULL
 * (subr_gtaskqueue.c:746) but the task object is still shared.
 *
 * grouptaskqueue_enqueue(queue==NULL) hits the INVARIANTS
 * panic("queue == NULL") (subr_gtaskqueue.c:203-207); without INVARIANTS
 * it is TQ_LOCK(NULL) -> lockmgr on a NULL-derived address -> kernel trap.
 *
 * The plain GROUPTASK_ENQUEUE macro is exactly what any in-tree-style
 * interrupt handler would do.
 */
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/gtaskqueue.h>
#include <sys/thread.h>
#include <sys/thread2.h>

static struct taskqgroup *rg;
static struct grouptask gt;
static volatile int stop;
static struct thread *racer_td, *det_td;

static void
gt_fn(void *ctx)
{
}

static void
racer(void *arg)
{
	while (!stop) {
		GROUPTASK_ENQUEUE(&gt);
		lwkt_yield();	/* avoid starving the CPU under test */
	}
	lwkt_exit();
}

static void
detacher(void *arg)
{
	int i;

	for (i = 0; i < 2000000 && !stop; i++) {
		taskqgroup_attach(rg, &gt, NULL, NULL, NULL, "gt");
		taskqgroup_detach(rg, &gt);
	}
	stop = 1;
	lwkt_exit();
}

static int
gtq_null_ev(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		rg = taskqgroup_create("gtq_null", 2, 1);
		if (rg == NULL)
			return (ENOMEM);
		GROUPTASK_INIT(&gt, 0, gt_fn, NULL);
		gt.gt_taskqueue = NULL;
		taskqgroup_attach(rg, &gt, NULL, NULL, NULL, "gt");
		lwkt_create(racer, NULL, &racer_td, NULL, TDF_NOSTART, 1,
		    "gtnrace");
		lwkt_setpri_initial(racer_td, TDPRI_KERN_DAEMON);
		lwkt_schedule(racer_td);
		lwkt_create(detacher, NULL, &det_td, NULL, TDF_NOSTART, 2,
		    "gtndet");
		lwkt_setpri_initial(det_td, TDPRI_KERN_DAEMON);
		lwkt_schedule(det_td);
		kprintf("gtq_null: racer/detacher running, expect panic\n");
		return (0);
	default:
		break;
	}
	return (EOPNOTSUPP);
}
DEV_MODULE(gtq_null, gtq_null_ev, NULL);
