/*
 * sqe2e.c - DF-2915 end-to-end trigger + DF-2917 demonstrator.
 *
 * Round 1 (nowake): a kthread does the real API sequence
 *	sleepq_lock()   -> subr_sleepqueue.c:177  (first use on the chain:
 *	                     objcache_get() garbage -> KKASSERT :209 panics
 *	                     on an INVARIANTS kernel without the DF-2915 fix)
 *	sleepq_add()    -> subr_sleepqueue.c:252
 *	sleepq_wait()   -> subr_sleepqueue.c:387   (NO waker is ever issued)
 *
 *   - kernel w/o DF-2915 fix: panics at :209 (0xAA-polluted M_SLEEPQ).
 *   - kernel with DF-2915 fix only: sleepq_wait() returns IMMEDIATELY
 *     with no wakeup ever issued -> DF-2917 (tsleep flags lost the
 *     PDOMAIN_FBSD0 domain -> kern_synch.c:660-663 mismatch fast-path).
 *   - kernel with both fixes: sleeper actually blocks; the main thread
 *     observes it still blocked, then sleepq_broadcast() wakes it.
 *
 * Round 2 (broadcast): normal lock/add/broadcast/wait round trip.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sleepqueue.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/kthread.h>
#include <sys/module.h>

extern struct malloc_type M_SLEEPQ[1];	/* subr_sleepqueue.c:107 */
/* sbticks is declared in <sys/kernel.h> (sys/kernel.h:86) */

#define WC_SIZE		48
#define SPRAY		8192

static int sq_wchan_obj;
static const void *sq_wchan = &sq_wchan_obj;
static volatile int sq_phase;		/* 0=init 1=interlocked 2=woke */
static volatile sbintime_t sq_t0, sq_t1;
static volatile int sq_ret;

static void
pollute_msleepq(void)
{
	void **m;
	int i;

	m = kmalloc(SPRAY * sizeof(void *), M_TEMP, M_WAITOK | M_ZERO);
	for (i = 0; i < SPRAY; ++i) {
		m[i] = kmalloc(WC_SIZE, M_SLEEPQ, M_WAITOK);
		memset(m[i], 0xAA, WC_SIZE);
	}
	for (i = 0; i < SPRAY; ++i)
		kfree(m[i], M_SLEEPQ);
	kfree(m, M_TEMP);
}

static void
sq_sleeper(void *arg)
{
	/*
	 * sleepq_lock() - FIRST use of this hash chain.  On a kernel with
	 * the DF-0139 hash bug this panics inside spin_lock() on a wild
	 * chain pointer; with the DF-0139 mask test-patch but without the
	 * DF-2915 fix this panics at the KKASSERT (:209).
	 */
	sleepq_lock(sq_wchan);				/* :177 */
	sleepq_add(sq_wchan, NULL, "sqdemo", SLEEPQ_SLEEP, 0); /* :252 */

	sq_t0 = sbticks;
	sq_phase = 1;			/* interlocked on sq_wchan now */
	wakeup(&sq_phase);

	sq_ret = 0;
	sleepq_wait(sq_wchan, 0);	/* :387 - NO wakeup will be issued */
	sq_t1 = sbticks;

	sq_phase = 2;
	wakeup(&sq_phase);
	kprintf("SQDEMO e2e: round1 (nowake): sleepq_wait() returned "
		"ret=%d after %jd sbticks with NO wakeup issued\n",
		sq_ret, (intmax_t)(sq_t1 - sq_t0));
	/* return -> kthread_exit via handler trampoline */
}

static void
sq_sleeper2(void *arg)
{
	sleepq_lock(sq_wchan);
	sleepq_add(sq_wchan, NULL, "sqdemo2", SLEEPQ_SLEEP, 0);
	sq_t0 = sbticks;
	sq_phase = 3;
	wakeup(&sq_phase);
	sleepq_wait(sq_wchan, 0);
	sq_t1 = sbticks;
	sq_phase = 4;
	wakeup(&sq_phase);
	kprintf("SQDEMO e2e: round2 (broadcast): sleepq_wait() returned "
		"after %jd sbticks (broadcast was issued)\n",
		(intmax_t)(sq_t1 - sq_t0));
}

static int
sqe2e_run(void)
{
	int i;

	pollute_msleepq();
	kprintf("SQDEMO e2e: M_SLEEPQ polluted with 0xAA, running "
		"sleepq lock/add/wait round trip\n");

	sq_phase = 0;
	kthread_create(sq_sleeper, NULL, NULL, "sq_sleeper");

	/* wait (bounded) for the sleeper to become interlocked */
	for (i = 0; i < 100 && sq_phase < 1; ++i)
		tsleep(&sq_phase, 0, "sqrdy", 1);
	if (sq_phase < 1) {
		kprintf("SQDEMO e2e: sleeper never reached sleepq_add "
			"(panicked?)\n");
		return 0;
	}

	/*
	 * Deliberately do NOT wake it.  If sleepq_wait() really sleeps we
	 * will still see phase==1 after ~2s; if DF-2917 is present the
	 * sleeper completes instantly and phase becomes 2.
	 */
	for (i = 0; i < 50 && sq_phase < 2; ++i)
		tsleep(&sq_phase, 0, "sqwait", 4);	/* 4 ticks */

	if (sq_phase < 2) {
		kprintf("SQDEMO e2e: round1: sleeper STILL BLOCKED after "
			"~200 ticks with no wakeup - sleepq_wait() really "
			"sleeps (DF-2917 fixed); issuing broadcast\n");
		sleepq_broadcast(sq_wchan, 0, 0, 0);	/* :505 */
		for (i = 0; i < 100 && sq_phase < 2; ++i)
			tsleep(&sq_phase, 0, "sqwake", 1);
		if (sq_phase < 2)
			kprintf("SQDEMO e2e: round1: broadcast failed to "
				"wake the sleeper!\n");
	} else {
		kprintf("SQDEMO e2e: round1: DF-2917 CONFIRMED - "
			"sleepq_wait() returned without any wakeup\n");
	}

	/* round 2: normal broadcast round trip */
	sq_phase = 2;
	kthread_create(sq_sleeper2, NULL, NULL, "sq_sleeper2");
	for (i = 0; i < 100 && sq_phase < 3; ++i)
		tsleep(&sq_phase, 0, "sqrd2", 1);
	if (sq_phase < 3) {
		kprintf("SQDEMO e2e: round2: sleeper2 never interlocked\n");
		return 0;
	}
	tsleep(&sq_phase, 0, "sqgap", 2);	/* let it block (or not) */
	sleepq_broadcast(sq_wchan, 0, 0, 0);
	for (i = 0; i < 100 && sq_phase < 4; ++i)
		tsleep(&sq_phase, 0, "sqdn2", 1);
	kprintf("SQDEMO e2e: done (phase=%d)\n", sq_phase);
	return 0;
}

static int
sqe2e_handler(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		sqe2e_run();
		break;
	default:
		break;
	}
	return 0;
}

static moduledata_t sqe2e_mod = { "sqe2e", sqe2e_handler, NULL };
DECLARE_MODULE(sqe2e, sqe2e_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
