/*
 * tlnge_replica.c - deterministic replica of the callout/serializer protocol
 * used by if_tl.c and if_nge.c on this DragonFly tree.
 *
 * PROTOCOL UNDER TEST (identical shape in both drivers):
 *
 *   tl_stats_update()   sys/dev/netif/tl/if_tl.c:1666-1674
 *     - callout created with plain callout_init()        (if_tl.c:1197)
 *     - callback does lwkt_serialize_enter(if_serializer) BLOCKING
 *       (if_tl.c:1671 -> sys/kern/lwkt_serialize.c:234 tsleep "slize")
 *     - re-arms itself with callout_reset(..., hz, ...) INSIDE the
 *       serializer (if_tl.c:1722)
 *   tl_stop()           sys/dev/netif/tl/if_tl.c:2121
 *     - callout_stop(&sc->tl_stat_timer) while the caller HOLDS the same
 *       serializer (tl_stop runs under if_serializer from tl_ioctl
 *       if_tl.c:2062, tl_init :1916, tl_watchdog :2103, tl_detach :1258,
 *       tl_intvec_adchk :1571).
 *
 *   nge_tick()          sys/dev/netif/nge/if_nge.c:1366-1406
 *     - callout_init()                                   (if_nge.c:731)
 *     - lwkt_serialize_enter(ifp->if_serializer) BLOCKING (if_nge.c:1372)
 *     - callout_reset(..., hz, ...) inside the serializer (if_nge.c:1403)
 *   nge_stop()          sys/dev/netif/nge/if_nge.c:2094
 *     - callout_stop(&sc->nge_stat_timer) under the serializer (callers:
 *       nge_ioctl :2031, nge_init :1716, nge_watchdog :2067, nge_detach
 *       :934, nge_intr :1491).
 *
 * DragonFly callout_stop() synchronously waits for an in-progress callback
 * (sys/kern/kern_timeout.c:1091 -> _callout_cancel_or_stop(sync=1), ssleep
 * "costp" at :920).  A callback that was dequeued and is parked in
 * lwkt_serialize_enter on the stopper's serializer therefore deadlocks
 * against it (AB-BA) - the same mechanism PROVEN for em(4) as DF-3715.
 *
 * This module reproduces the protocol verbatim (plain callout_init, hz
 * period, blocking enter, re-arm inside the serializer) and exposes, for
 * each driver shape, three sysctls:
 *
 *   kern.<drv>replica.arm        =1  arm the replica callout
 *   kern.<drv>replica.stop_plain =1  callout_stop() WITHOUT the serializer
 *                                    (negative control - must return)
 *   kern.<drv>replica.stop_blk   =1  hold serializer, wait for the handler
 *                                    to park on it, callout_stop()
 *                                    -> if the analysis is right this
 *                                    NEVER returns
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/serialize.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

struct shape {
	struct lwkt_serialize ser;
	struct callout	 c;
	struct callout	 cf;		/* fix-shape handler */
	volatile int	 fired;
	volatile int	 ran;
	volatile int	 ffired, fran;	/* fix-shape counters */
	const char	*name;
};

static struct shape tl_sh = { .name = "tl" };
static struct shape nge_sh = { .name = "nge" };

/*
 * Exact tl_stats_update / nge_tick shape:
 * blocking enter -> (work) -> re-arm inside -> exit.
 */
static void
replica_tick(void *arg)
{
	struct shape *sh = arg;

	sh->fired++;
	/* if_tl.c:1671 / if_nge.c:1372 - BLOCKING acquire */
	lwkt_serialize_enter(&sh->ser);
	sh->ran++;
	/* if_tl.c:1722 / if_nge.c:1403 - self re-arm while serialized */
	callout_reset(&sh->c, hz, replica_tick, sh);
	lwkt_serialize_exit(&sh->ser);
}

static int
arm_sysctl(struct shape *sh, SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return error;
	if (v == 1) {
		sh->fired = sh->ran = 0;
		/* if_tl.c:1981 / if_nge.c:1717 */
		callout_reset(&sh->c, hz, replica_tick, sh);
		kprintf("replica[%s]: armed (period hz=%d)\n", sh->name, hz);
	}
	return 0;
}

static int
stop_plain_sysctl(struct shape *sh, SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

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

	/*
	 * Negative control: callout_stop() WITHOUT holding the serializer.
	 * The handler can always finish (it only blocks on us), so this
	 * must return promptly.
	 */
	kprintf("replica[%s]: stop_plain entering callout_stop()\n", sh->name);
	callout_stop(&sh->c);
	kprintf("replica[%s]: stop_plain callout_stop RETURNED pending=%d "
	    "(negative control ok)\n", sh->name, callout_pending(&sh->c));
	return 0;
}

static int
stop_blk_sysctl(struct shape *sh, SYSCTL_HANDLER_ARGS)
{
	int error, v = 0, spins;

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

	/*
	 * AB-BA test - exact tl_stop()/nge_stop() shape: take the serializer,
	 * wait until the handler is parked inside lwkt_serialize_enter
	 * (fired but not ran), then callout_stop().
	 *
	 * callout_stop (kern_timeout.c:1091, sync=1) sleeps on "costp" until
	 * the INPROG callback completes; the callback sleeps in
	 * lwkt_serialize_enter ("slize") until we release the serializer; we
	 * sleep in callout_stop holding it.  If the analysis is right the
	 * next kprintf NEVER executes - this sysctl thread AND a softclock
	 * thread wedge permanently.
	 */
	lwkt_serialize_enter(&sh->ser);
	for (spins = 0; spins < 5 * hz && sh->ran == sh->fired; spins++)
		tsleep(&sh->ran, 0, "blkw", 2);
	if (sh->ran == sh->fired) {
		kprintf("replica[%s]: handler never fired/parked\n", sh->name);
		lwkt_serialize_exit(&sh->ser);
		return 0;
	}
	kprintf("replica[%s]: handler PARKED on our serializer "
	    "(fired=%d ran=%d) - entering callout_stop()\n",
	    sh->name, sh->fired, sh->ran);
	kprintf("replica[%s]: IF THIS IS THE LAST replica LINE, "
	    "AB-BA DEADLOCK IS PROVEN\n", sh->name);
	callout_stop(&sh->c);
	kprintf("replica[%s]: callout_stop returned (NO deadlock)\n", sh->name);
	lwkt_serialize_exit(&sh->ser);
	return 0;
}

/*
 * FIX-pattern shape: handler never blocks - try-enter, on failure retry
 * in one tick (the em_txgc_timer / DF-3715 fix, proposed for tl_stats_
 * update and nge_tick).  callout_stop() under a held serializer must
 * return AND fully unload it despite the unconditional re-arms.
 */
static void
replica_fix_tick(void *arg)
{
	struct shape *sh = arg;

	sh->ffired++;
	if (lwkt_serialize_try(&sh->ser)) {
		sh->fran++;
		callout_reset(&sh->cf, hz, replica_fix_tick, sh);
		lwkt_serialize_exit(&sh->ser);
	} else {
		callout_reset(&sh->cf, 1, replica_fix_tick, sh);
	}
}

static int
stop_fix_sysctl(struct shape *sh, SYSCTL_HANDLER_ARGS)
{
	int error, v = 0, tries, f0;

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

	sh->ffired = sh->fran = 0;
	callout_reset(&sh->cf, 1, replica_fix_tick, sh);

	for (tries = 0; tries < 100; tries++) {
		lwkt_serialize_enter(&sh->ser);
		/* wait until the handler is missing against our serializer */
		while (sh->ffired == sh->fran)
			tsleep(&sh->ffired, 0, "fixw", 2);
		kprintf("replica[%s]: fix-shape missing (f=%d r=%d) - "
		    "callout_stop under serializer\n",
		    sh->name, sh->ffired, sh->fran);
		callout_stop(&sh->cf);
		kprintf("replica[%s]: fix-shape callout_stop RETURNED "
		    "(no deadlock)\n", sh->name);
		lwkt_serialize_exit(&sh->ser);
		if (callout_pending(&sh->cf) == 0)
			break;
		tsleep(&sh->fran, 0, "fixr", hz / 10);
	}
	f0 = sh->ffired;
	tsleep(&sh->ffired, 0, "fixs", hz / 2);
	kprintf("replica[%s]: fix-shape RESULT pending=%d fired-stalled=%s "
	    "(f=%d->%d r=%d)\n", sh->name, callout_pending(&sh->cf),
	    (sh->ffired == f0 && callout_pending(&sh->cf) == 0) ? "YES" : "NO",
	    f0, sh->ffired, sh->fran);
	return 0;
}

#define	SHAPE_SYSCTLS(prefix, var, shp)					\
static int								\
prefix##_arm(SYSCTL_HANDLER_ARGS)					\
{									\
	return arm_sysctl(shp, oidp, arg1, arg2, req);			\
}									\
static int								\
prefix##_plain(SYSCTL_HANDLER_ARGS)					\
{									\
	return stop_plain_sysctl(shp, oidp, arg1, arg2, req);		\
}									\
static int								\
prefix##_blk(SYSCTL_HANDLER_ARGS)					\
{									\
	return stop_blk_sysctl(shp, oidp, arg1, arg2, req);		\
}									\
static int								\
prefix##_fix(SYSCTL_HANDLER_ARGS)					\
{									\
	return stop_fix_sysctl(shp, oidp, arg1, arg2, req);		\
}									\
static SYSCTL_NODE(_kern, OID_AUTO, prefix##replica, CTLFLAG_RW, 0,	\
    "callout replica");							\
SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, arm,			\
    CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_arm, "I", "arm replica");	\
SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, stop_plain,		\
    CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_plain, "I",		\
    "stop without serializer (control)");				\
SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, stop_blk,		\
    CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_blk, "I",		\
    "stop under serializer (AB-BA)");					\
SYSCTL_PROC(_kern_##prefix##replica, OID_AUTO, stop_fix,		\
    CTLTYPE_INT | CTLFLAG_RW, 0, 0, prefix##_fix, "I",		\
    "try-enter fix shape: stop under serializer must return")

SHAPE_SYSCTLS(tl, tl_sh, &tl_sh);
SHAPE_SYSCTLS(nge, nge_sh, &nge_sh);

static int
tlnge_replica_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&tl_sh.ser);
		lwkt_serialize_init(&nge_sh.ser);
		/* if_tl.c:1197 / if_nge.c:731 - plain callout_init (MPSAFE) */
		callout_init(&tl_sh.c);
		callout_init(&nge_sh.c);
		callout_init(&tl_sh.cf);
		callout_init(&nge_sh.cf);
		kprintf("tlnge_replica: loaded (hz=%d)\n", hz);
		return 0;
	case MOD_UNLOAD:
		/* only safe if the AB-BA test was not run to the wedge */
		callout_stop(&tl_sh.c);
		callout_stop(&nge_sh.c);
		callout_stop(&tl_sh.cf);
		callout_stop(&nge_sh.cf);
		return 0;
	}
	return 0;
}

static moduledata_t tlnge_replica_mod = {
	"tlnge_replica",
	tlnge_replica_load,
	NULL
};
DECLARE_MODULE(tlnge_replica, tlnge_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
