/*
 * DF-2728 / DF-2729 trigger module for sys/kern/lwkt_thread.c (DragonFly 6.5-DEVELOPMENT)
 *
 * Mode 1 (DF-2728): remotely schedule a *preemptable* (interrupt-style) thread via
 *   the generic _lwkt_schedule() remote branch (sys/kern/lwkt_thread.c:1310).
 *   On the target cpu the IPI is processed by lwkt_process_ipiq_frame() with a
 *   frame, lwkt_schedule_remote() (lwkt_thread.c:1336) deliberately drops the
 *   critical section and calls _lwkt_schedule() -> _lwkt_schedule_post() ->
 *   ntd->td_preemptable(ntd, 1) == lwkt_preempt().
 *   lwkt_preempt() line 1026 then executes:
 *       KKASSERT(gd->gd_processing_ipiq == 0);
 *   while gd->gd_processing_ipiq == 1 (incremented by lwkt_process_ipiq_frame
 *   at sys/kern/lwkt_ipiq.c:605)  ->  INVARIANTS panic.
 *   On a stock (non-INVariANTS) kernel the exact same sequence is the *designed*
 *   preemption path and runs cleanly (fully balanced crit/intr-nesting counts).
 *
 * Mode 2 (DF-2729): simulate the push/pull migration window (lwkt_setcpu_self /
 *   lwkt_giveaway set TDF_MIGRATING before the thread is inert; a racing wakeup
 *   IPI lands on the old home cpu) by setting TDF_MIGRATING on a thread homed
 *   on the target cpu, then calling lwkt_schedule() from this cpu.
 *   The IPI handler runs lwkt_schedule_remote() -> _lwkt_schedule() which first
 *   hits:
 *       KKASSERT((td->td_flags & TDF_MIGRATING) == 0);   (lwkt_thread.c:1293)
 *   even though _lwkt_enqueue() (lwkt_thread.c:188) explicitly and *by design*
 *   swallows enqueues of TDF_MIGRATING threads.  INVARIANTS panic; stock kernel
 *   gracefully skips the enqueue (the designed behavior the assert forbids).
 *
 * Mode 3 (control): plain remote schedule of a non-migrating, non-preemptable
 *   thread - must NOT panic.  Proves the module machinery itself is sound.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/globaldata.h>
#include <machine/smp.h>
#include <machine/cpu.h>	/* cpu_set_thread_handler */

/* no prototype in a clean header on this branch; exported kernel symbol */
void cpu_set_thread_handler(thread_t td, void (*rfunc)(void),
			    void (*func)(void *), void *arg);

static struct thread *g_td1;	/* preemptable target (mode 1) */
static struct thread *g_td2;	/* migrating target (mode 2)   */
static int g_tcpu;

static void
df2728_sleeper(void *arg __unused)
{
	/* ithread-style body: never runs before the triggers fire anyway */
	for (;;) {
		lwkt_deschedule_self(curthread);
		lwkt_switch();
	}
}

static int
df2728_mode_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0;
	int error;

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

	switch (mode) {
	case 1:
		kprintf("df2728: mode1(DF-2728): lwkt_schedule(td1=%p) "
			"cpu%d -> cpu%d, td_preemptable=lwkt_preempt\n",
			g_td1, mycpuid, g_tcpu);
		lwkt_schedule(g_td1);
		kprintf("df2728: mode1: returned WITHOUT panic "
			"(non-INVariANTS semantics: designed preempt)\n");
		break;
	case 2:
		kprintf("df2728: mode2(DF-2729): TDF_MIGRATING window + "
			"lwkt_schedule(td2=%p) cpu%d -> cpu%d\n",
			g_td2, mycpuid, g_tcpu);
		atomic_set_int(&g_td2->td_flags, TDF_MIGRATING);
		lwkt_schedule(g_td2);
		atomic_clear_int(&g_td2->td_flags, TDF_MIGRATING);
		kprintf("df2728: mode2: returned WITHOUT panic "
			"(non-INVariANTS semantics: enqueue swallowed by design)\n");
		break;
	case 3:
		kprintf("df2728: mode3(control): plain remote schedule td2=%p\n",
			g_td2);
		lwkt_schedule(g_td2);
		kprintf("df2728: mode3: ok, no panic (control)\n");
		break;
	default:
		return (EINVAL);
	}
	return (0);
}

SYSCTL_PROC(_debug, OID_AUTO, df2728, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, df2728_mode_sysctl, "I", "DF-2728/DF-2729 trigger: 1|2|3");

static int
df2728_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD:
		g_tcpu = (mycpuid + 1) % ncpus;

		/* interrupt-thread analogue: never started, preemptable */
		g_td1 = lwkt_alloc_thread(NULL, LWKT_THREAD_STACK,
					  g_tcpu, TDF_NOSTART);
		g_td1->td_preemptable = lwkt_preempt;
		g_td1->td_pri = TDPRI_INT_MED;
		cpu_set_thread_handler(g_td1, lwkt_exit, df2728_sleeper, NULL);

		/* plain kernel-thread analogue: never started */
		g_td2 = lwkt_alloc_thread(NULL, LWKT_THREAD_STACK,
					  g_tcpu, TDF_NOSTART);
		g_td2->td_pri = TDPRI_KERN_DAEMON;
		cpu_set_thread_handler(g_td2, lwkt_exit, df2728_sleeper, NULL);

		kprintf("df2728: loaded on cpu%d, target cpu%d, td1=%p td2=%p\n",
			mycpuid, g_tcpu, g_td1, g_td2);
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t df2728_mod = { "df2728", df2728_modevent, 0 };
DECLARE_MODULE(df2728, df2728_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
