/*
 * DF-4606 mechanism PoC: ata_timeout() mid-callback lock window vs
 * synchronous callout_cancel() -> permanent channel/kernel deadlock.
 *
 * Reproduces the exact kernel-primitive interleaving cited in the finding,
 * using the same kernel machinery the nata timeout path uses:
 *
 *   ata-queue.c:93    callout_init_lk(&request->callout, &ch->state_mtx)
 *   ata-queue.c:555-561  ata_timeout: LK_RELEASE -> ATA_LOCKING(UNLOCK) ->
 *                        ata_finish() -> LK_EXCLUSIVE (re-acquire)
 *   ata-lowlevel.c:442   ata_end_transaction: callout_cancel() while holding
 *                        ch->state_mtx   (also ata-all.c:208 ata_reinit,
 *                        ata-queue.c:578 ata_fail_requests)
 *
 * kern_timeout.c:609-620 executes the callback via
 *   lockmgr(c->lk, LK_EXCLUSIVE|LK_CANCELABLE); qfunc(); lockmgr(c->lk, LK_RELEASE);
 * kern_timeout.c:888-925 _callout_cancel_or_stop(sync=1): if INPROG, issues
 *   lockmgr(c->lk, LK_CANCEL_BEG) and ssleep()s until the callback returns.
 * kern_lock.c:   LKC_CANCEL only aborts LK_CANCELABLE acquisitions; a plain
 *   LK_EXCLUSIVE re-acquire (ata-queue.c:560) blocks normally.
 *
 * => canceller holds lk and sleeps waiting for the callback; the callback is
 *    blocked re-acquiring lk held by the canceller.  Circular wait.
 *
 * This module deterministically stages that interleaving:
 *   [softclock]  cb:        holds lk (AUTOLOCK), LK_RELEASE, window opens,
 *                           then re-acquires (== ata_timeout shape)
 *   [kthread]    canceller: takes lk inside the window, callout_cancel()
 *
 * Expected (finding TRUE): "callout_cancel RETURNED" is NEVER printed, the
 * softclock thread and the canceller sleep forever, and the system wedges
 * (all timeout-based sleeps stop firing) -- i.e. local DoS.
 * Expected (finding FALSE): the RETURNED line prints and the module reports
 * no deadlock.
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/lock.h>
#include <sys/callout.h>
#include <sys/kthread.h>
#include <sys/thread.h>
#include <sys/sysctl.h>

static struct lock lk;
static struct callout co;
static volatile int phase;	/* 0 armed, 1 window open, 2 canceller holds lk,
				 * 3 finished */
/* progress probe (sysctl df4606.progress) so the blocking point of the
 * softclock thread is observable even when console drains are wedged:
 * 1 window opened (after LK_RELEASE), 2 spin passed, 3 pre-lockmgr,
 * 4 re-acquired (never in the deadlock case) */
static int cb_progress = 0;
SYSCTL_INT(_hw, OID_AUTO, df4606_progress, CTLFLAG_RW, &cb_progress, 0,
	   "softclock callback progress");

static void	df4606_canceller(void *);

/* plays the role of ata_timeout() (ata-queue.c:539-562) */
static void
df4606_cb(void *arg)
{
	kprintf("df4606: [softclock] callback: framework holds lk, "
		"dropping it (== ata-queue.c:557)\n");
	lockmgr(&lk, LK_RELEASE);
	cb_progress = 1;
	phase = 1;
	/*
	 * Keep the window open until the canceller has taken lk (ata_timeout
	 * keeps it open for ATA_LOCKING(UNLOCK)+ata_finish(); we only widen
	 * it deterministically).  Busy-rendezvous: never sleep in a softclock
	 * callback (its own timeouts are serviced by itself).
	 */
	while (phase != 2)
		cpu_ccfence();
	cb_progress = 2;
	kprintf("df4606: [softclock] callback: re-acquiring lk "
		"(== ata-queue.c:560) ... last line before wedge if the "
		"finding holds\n");
	cb_progress = 3;
	lockmgr(&lk, LK_EXCLUSIVE);	/* blocks on the canceller */
	cb_progress = 4;
	kprintf("df4606: [softclock] callback: re-acquired (no deadlock)\n");
	phase = 3;
	wakeup(&phase);
}

/* plays the role of ata_interrupt()/ata_reinit()/ata_fail_requests() calling
 * callout_cancel() while holding the channel state lock */
static void
df4606_canceller(void *arg)
{
	while (phase != 1)
		tsleep(&phase, 0, "df4606c", 1);
	/*
	 * Take lk while the callback is inside its window, exactly like a
	 * late ata_interrupt() acquiring ch->state_mtx after ata_timeout()
	 * dropped it at ata-queue.c:557.
	 */
	lockmgr(&lk, LK_EXCLUSIVE);
	kprintf("df4606: [canceller] acquired lk inside the window "
		"(== late ata_interrupt with state_mtx)\n");
	phase = 2;		/* release the callback into its re-acquire */
	kprintf("df4606: [canceller] calling callout_cancel() "
		"(== ata-lowlevel.c:442) ...\n");
	callout_cancel(&co);
	kprintf("df4606: [canceller] callout_cancel RETURNED -- "
		"mechanism refuted, no deadlock\n");
	phase = 3;
	wakeup(&phase);
	lockmgr(&lk, LK_RELEASE);
	kthread_exit();
}

static int
df4606_load(void)
{
	lockinit(&lk, "df4606lk", 0, 0);
	callout_init_lk(&co, &lk);
	phase = 0;
	kthread_create(df4606_canceller, NULL, NULL, "df4606c");
	callout_reset(&co, 2, df4606_cb, NULL);
	kprintf("df4606: armed; watch for the wedge\n");
	return (0);
}

static int
df4606_handler(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		return (df4606_load());
	case MOD_UNLOAD:
		/* the callout/thread never finish if the deadlock hit */
		return (EBUSY);
	default:
		return (0);
	}
}

static moduledata_t df4606_mod = {
	"df4606",
	df4606_handler,
	NULL
};
DECLARE_MODULE(df4606, df4606_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
