/*
 * jme_callout_replica.c - deterministic replica of if_jme.c's timer/serializer
 * protocol plus jme_rxeof's descriptor-walk edge case.
 *
 * PART 1 - the DF-3715/3738/3760 AB-BA family, jme(4) instance.
 *
 * if_jme.c arms one MP-safe callout pinned to CPU0
 * (callout_init_mp(&sc->jme_tick_ch), if_jme.c:772; armed with
 * callout_reset_bycpu(..., JME_TICK_CPUID), if_jme.c:2881/:483):
 *
 *   jme_tick()  if_jme.c:2566-2582
 *       lwkt_serialize_enter(&sc->jme_serialize)   BLOCKING   (:2571)
 *       sc->jme_in_tick = TRUE; mii_tick(mii); ...             (:2575-2577)
 *         [mii_tick -> jme_miibus_statchg: jme_serialize_skipmain()
 *          = lwkt_serialize_array_enter(arr, cnt, 1)  (:3971/:362-363)
 *          ... jme_deserialize_skipmain() ...        (:3978/:493-494)]
 *       callout_reset(&sc->jme_tick_ch, hz, jme_tick, sc)      (:2579)
 *       lwkt_serialize_exit(&sc->jme_serialize)                (:2581)
 *
 *   jme_stop()  if_jme.c:2889-2958 - runs with ALL driver serializers
 *   held (ASSERT_IFNET_SERIALIZED_ALL :2898; entered via
 *   ifnet_serialize_all at jme_detach :1092, jme_suspend :1589,
 *   jme_ioctl :1913 [SIOCSIFFLAGS down :1954, SIOCSIFMTU -> jme_init
 *   :1939 -> jme_stop :2672], watchdog :1885/:1900):
 *       callout_stop(&sc->jme_tick_ch)                          (:2907)
 *   (also jme_miibus_statchg:409 with all serializers held.)
 *
 * DragonFly callout_stop() is the SYNCHRONOUS variant
 * (sys/kern/kern_timeout.c:1091 -> _callout_cancel_or_stop(..., sync=1));
 * when the callback is INPROG it ssleep()s on "costp" (kern_timeout.c:920)
 * until the callback completes.  If jme_tick is INPROG and parked inside
 * lwkt_serialize_enter on the serializer held by the jme_stop() caller,
 * neither thread can make progress: PERMANENT AB-BA DEADLOCK - and the
 * wedged CPU0 softclock thread stops delivering every callout queued
 * on CPU0.
 *
 * This module reproduces exactly that protocol with two callouts:
 *   "jme tick (as shipped)"     - blocking acquire + skipmain dance +
 *                                 self re-arm at hz
 *   "jme tick (fix.diff shape)" - lwkt_serialize_try + 1-tick retry
 * and exposes sysctls to run the interleavings:
 *
 *   kern.jmereplica.arm      =1  arm both replicas
 *   kern.jmereplica.stop_try =1  hold ALL serializers, callout_stop() the
 *                                try-based (fixed) shape -> must return,
 *                                pending=0 (validates the fix mechanism)
 *   kern.jmereplica.stop_blk =1  hold ALL serializers, wait for the
 *                                shipped-shape handler to be BLOCKED on
 *                                the main serializer, callout_stop()
 *                                -> if the analysis is right this NEVER
 *                                returns (sysctl thread D3 wchan "costp").
 *
 * PART 2 - jme_rxeof() (nsegs=0,pktlen=0) edge (if_jme.c:2494-2562).
 *
 *   The descriptor consistency check at :2513 is
 *       nsegs != howmany(pktlen, MCLBYTES)
 *   which admits the pair (nsegs==0, pktlen==0) since howmany(0,2048)==0.
 *   jme_rxpkt() with nsegs==0 (if_jme.c:2304-2486) executes neither the
 *   jme_newbuf() repost path nor jme_discard_rxbufs(), and advances
 *   jme_rx_cons by 0 (:2484-2485): the descriptor is never re-armed, so
 *   jme_rxeof()'s unbounded `for(;;)` re-reads the same OWN-clear,
 *   VALID-set descriptor forever (interrupt-thread / netisr spin).
 *   kern.jmereplica.rxspin   =1  runs the verbatim loop logic against a
 *                                simulated ring holding a (0,0) descriptor
 *                                with a large guard counter and reports
 *                                iterations consumed, reposts performed
 *                                (0) and rx_cons delta (0) - i.e. the
 *                                loop provably never terminates.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/serialize.h>
#include <sys/serialize2.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/systm.h>

/* ---- jme_softc serializer topology (if_jmevar.h:285-287, if_jme.c:730-746) */
static struct lwkt_serialize ser_main;	/* == sc->jme_serialize     */
static struct lwkt_serialize ser_tx;	/* == jme_tx_serialize      */
static struct lwkt_serialize ser_rx0;	/* == jme_rx_data[0]        */
static struct lwkt_serialize *ser_arr[3] = { &ser_main, &ser_tx, &ser_rx0 };
#define SER_CNT		3
#define JME_TICK_CPUID	0		/* if_jme.c:77 */

static struct callout tick_blk_c;	/* jme_tick as shipped */
static struct callout tick_try_c;	/* jme_tick with fix.diff */

static volatile int blk_fired, blk_ran;
static volatile int try_fired, try_ran;

/* if_jme.c:2566-2582 - jme_tick() EXACT protocol (blocking) */
static void
jme_tick_blk(void *arg)
{
	blk_fired++;
	/* if_jme.c:2571 - BLOCKING acquire of the main serializer */
	lwkt_serialize_enter(&ser_main);
	/*
	 * mii_tick() body; jme_miibus_statchg() may run here and does
	 * jme_serialize_skipmain()/jme_deserialize_skipmain()
	 * (if_jme.c:3971-3982 == array_enter/array_exit from index 1).
	 */
	lwkt_serialize_array_enter(ser_arr, SER_CNT, 1);
	lwkt_serialize_array_exit(ser_arr, SER_CNT, 1);
	blk_ran++;
	/* if_jme.c:2579 - self re-arm at hz (same CPU) */
	callout_reset(&tick_blk_c, hz, jme_tick_blk, NULL);
	lwkt_serialize_exit(&ser_main);
}

/* jme_tick() with the proposed fix: try-enter, 1-tick retry (pinned) */
static void
jme_tick_try(void *arg)
{
	try_fired++;
	if (lwkt_serialize_try(&ser_main)) {
		lwkt_serialize_array_enter(ser_arr, SER_CNT, 1);
		lwkt_serialize_array_exit(ser_arr, SER_CNT, 1);
		try_ran++;
		callout_reset_bycpu(&tick_try_c, hz, jme_tick_try, NULL,
		    JME_TICK_CPUID);
		lwkt_serialize_exit(&ser_main);
	} else {
		/* serializer held (e.g. by jme_stop) - retry next tick */
		callout_reset_bycpu(&tick_try_c, 1, jme_tick_try, NULL,
		    JME_TICK_CPUID);
	}
}

static int
arm_sysctl(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) {
		blk_fired = blk_ran = try_fired = try_ran = 0;
		callout_reset_bycpu(&tick_try_c, 1, jme_tick_try, NULL,
		    JME_TICK_CPUID);
		callout_reset_bycpu(&tick_blk_c, 1, jme_tick_blk, NULL,
		    JME_TICK_CPUID);
		kprintf("jmereplica: armed (hz=%d)\n", hz);
	}
	return 0;
}

/*
 * Fix-shape test: jme_stop() vs the try-based jme_tick.
 * Hold ALL serializers (ifnet_serialize_all), then callout_stop() -
 * must return (handler never blocks) with pending=0.
 */
static int
stop_try_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0, tries;

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

	for (tries = 0; tries < 100; tries++) {
		lwkt_serialize_array_enter(ser_arr, SER_CNT, 0);
		if (try_fired == try_ran &&
		    callout_pending(&tick_try_c) == 0)
			tsleep(&try_fired, 0, "tryw", 1);
		kprintf("jmereplica[try]: stop (fired=%d ran=%d)\n",
		    try_fired, try_ran);
		callout_stop(&tick_try_c);		/* if_jme.c:2907 */
		kprintf("jmereplica[try]: callout_stop returned, "
		    "pending=%d\n", callout_pending(&tick_try_c));
		lwkt_serialize_array_exit(ser_arr, SER_CNT, 0);
		if (callout_pending(&tick_try_c) == 0)
			break;
		tsleep(&try_ran, 0, "tryr", hz / 10);
	}
	kprintf("jmereplica[try]: RESULT pending=%d "
	    "(0 == try-shape fully stopped, no deadlock)\n",
	    callout_pending(&tick_try_c));
	return 0;
}

/*
 * Shipped-shape test: jme_stop() vs the blocking jme_tick.
 * Take ALL serializers (as jme_stop does via ifnet_serialize_all),
 * wait until the handler is parked inside lwkt_serialize_enter
 * (fired but not ran), then callout_stop().  If the AB-BA analysis is
 * right this never prints the next line - the sysctl thread AND the
 * CPU0 softclock thread are wedged permanently (D-state, wchan "costp"
 * == kern_timeout.c:920).
 */
static int
stop_blk_sysctl(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;

	lwkt_serialize_array_enter(ser_arr, SER_CNT, 0); /* :1092/:1913 */
	while (blk_ran == blk_fired)
		tsleep(&blk_ran, 0, "blkw", 2);
	kprintf("jmereplica[blk]: jme_tick handler BLOCKED on our "
	    "main serializer (fired=%d ran=%d) - entering callout_stop()\n",
	    blk_fired, blk_ran);
	kprintf("jmereplica[blk]: IF THIS IS THE LAST jmereplica LINE, "
	    "THE if_jme.c AB-BA DEADLOCK IS PROVEN\n");
	callout_stop(&tick_blk_c);			/* :2907 */
	kprintf("jmereplica[blk]: callout_stop returned (NO deadlock)\n");
	lwkt_serialize_array_exit(ser_arr, SER_CNT, 0);
	return 0;
}

/* ---- Part 2: jme_rxeof() (nsegs=0,pktlen=0) verbatim-loop replica ---- */

/* if_jmereg.h:955-1010 */
#define REPL_RD_OWN			0x80000000
#define REPL_RD_VALID			0x80000000
#define REPL_RD_CNT_MASK		0x7F000000
#define REPL_RD_CNT_SHIFT		24
#define REPL_RD_FRAME_LEN_MASK		0x0000FFFF
#define REPL_RX_NSEGS(x)	(((x) & REPL_RD_CNT_MASK) >> REPL_RD_CNT_SHIFT)
#define REPL_RX_BYTES(x)	((x) & REPL_RD_FRAME_LEN_MASK)
#define REPL_MCLBYTES			2048
#define REPL_NDESC			16	/* minimum legal ring size */

struct repl_desc {
	uint32_t	flags;
	uint32_t	buflen;
	uint32_t	addr_hi;
	uint32_t	addr_lo;
};

static struct repl_desc repl_ring[REPL_NDESC];
static int repl_rx_cons;
static int repl_reposts;	/* jme_newbuf()/jme_discard_rxbufs() count */
static long repl_spin_iters;

/*
 * jme_rxpkt() verbatim for the nsegs==0 path (if_jme.c:2304-2486):
 * with nsegs==0 the descriptor loop body (count = 0; count < nsegs)
 * never executes - neither jme_newbuf() nor jme_discard_rxbufs()
 * reposts the descriptor - and rx_cons advances by nsegs == 0.
 */
static void
repl_rxpkt(void)
{
	int nsegs, cons, count;

	cons = repl_rx_cons;
	nsegs = REPL_RX_NSEGS(repl_ring[cons].buflen);

	/* if_jme.c:2337 - clearing OWN of following descs (nsegs > 1 only) */
	/* if_jme.c:2361 - for (count = 0; count < nsegs; count++)
	 *     { jme_newbuf() repost; chain mbuf }  -> 0 iterations */
	count = 0;
	repl_reposts += count * 0;	/* nsegs == 0 -> no repost */

	repl_rx_cons += nsegs;			/* if_jme.c:2484 */
	repl_rx_cons %= REPL_NDESC;		/* if_jme.c:2485 */
}

/*
 * jme_rxeof() verbatim loop logic (if_jme.c:2494-2562), count == -1
 * (interrupt-path shape, e.g. jme_msix_rx:3845 / jme_rx_intr:3500),
 * against the simulated ring.  A guard counter stands in for "forever".
 */
static void
repl_rxeof_spin(void)
{
	struct repl_desc *desc;
	int nsegs, pktlen;
	long guard = 200000000;	/* ~seconds; stands in for infinity */
	int cons_in = repl_rx_cons;

	for (;;) {
		if (guard-- <= 0)
			break;	/* observability only: WOULD NOT TERMINATE */
		repl_spin_iters++;

		desc = &repl_ring[repl_rx_cons];
		if ((desc->flags & REPL_RD_OWN) == REPL_RD_OWN)
			break;				/* :2500 */
		if ((desc->buflen & REPL_RD_VALID) == 0)
			break;				/* :2502 */

		nsegs = REPL_RX_NSEGS(desc->buflen);	/* :2511 */
		pktlen = REPL_RX_BYTES(desc->buflen);	/* :2512 */
		if (nsegs != howmany(pktlen, REPL_MCLBYTES)) {
			/* :2513 - admits (0,0): 0 == howmany(0,2048) */
			break;
		}

		/* RSS check skipped: single-ring config (JME_ENABLE_HWRSS
		 * is FALSE for jme_rx_ring_cnt == 1, if_jmevar.h:333-334) */

		repl_rxpkt();				/* :2561 */
	}
	kprintf("jmereplica[rxspin]: guard=%ld iterations=%ld "
	    "reposts=%d rx_cons %d -> %d (unchanged == descriptor never "
	    "re-armed: loop provably unbounded)\n",
	    guard, repl_spin_iters, repl_reposts, cons_in, repl_rx_cons);
}

static int
rxspin_sysctl(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;

	/* Simulated completed descriptor: OWN clear, VALID set,
	 * nsegs == 0, pktlen == 0 - passes the :2513 consistency check. */
	bzero(repl_ring, sizeof(repl_ring));
	repl_ring[0].flags = 0;			/* OWN clear */
	repl_ring[0].buflen = REPL_RD_VALID |	/* :2502 passes */
	    (0 << REPL_RD_CNT_SHIFT) |		/* nsegs = 0 */
	    0;				/* pktlen = 0 */
	repl_rx_cons = 0;
	repl_reposts = 0;
	repl_spin_iters = 0;
	repl_rxeof_spin();
	return 0;
}

/*
 * Fixed-shape walk (DF-3777 fix.diff): nsegs < 1 -> repost this one
 * descriptor and advance before the howmany() check.  Against the same
 * simulated ring the walk must terminate after ONE iteration (desc[1]
 * is bzero'd -> VALID clear -> break at :2502).
 */
static void
repl_rxeof_fixed(void)
{
	struct repl_desc *desc;
	int nsegs, pktlen, iters = 0;
	int cons_in = repl_rx_cons;

	for (;;) {
		iters++;
		desc = &repl_ring[repl_rx_cons];
		if ((desc->flags & REPL_RD_OWN) == REPL_RD_OWN)
			break;
		if ((desc->buflen & REPL_RD_VALID) == 0)
			break;
		nsegs = REPL_RX_NSEGS(desc->buflen);
		pktlen = REPL_RX_BYTES(desc->buflen);
		if (nsegs < 1) {
			/* fix: jme_discard_rxbufs(..., 1) */
			repl_reposts += 1;
			repl_rx_cons += 1;
			repl_rx_cons %= REPL_NDESC;
			continue;
		}
		if (nsegs != howmany(pktlen, REPL_MCLBYTES))
			break;
		repl_rxpkt();
	}
	kprintf("jmereplica[rxspin_fix]: iterations=%d reposts=%d "
	    "rx_cons %d -> %d (terminates; degenerate desc reposted)\n",
	    iters, repl_reposts, cons_in, repl_rx_cons);
}

static int
rxspin_fix_sysctl(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;

	bzero(repl_ring, sizeof(repl_ring));
	repl_ring[0].flags = 0;
	repl_ring[0].buflen = REPL_RD_VALID;
	repl_rx_cons = 0;
	repl_reposts = 0;
	repl_spin_iters = 0;
	repl_rxeof_fixed();
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, jmereplica, CTLFLAG_RW, 0,
    "jme callout/rxeof replica");
SYSCTL_PROC(_kern_jmereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm replica callouts");
SYSCTL_PROC(_kern_jmereplica, OID_AUTO, stop_try, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, stop_try_sysctl, "I", "jme_stop vs try-based (fixed) jme_tick");
SYSCTL_PROC(_kern_jmereplica, OID_AUTO, stop_blk, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, stop_blk_sysctl, "I", "jme_stop vs shipped (blocking) jme_tick");
SYSCTL_PROC(_kern_jmereplica, OID_AUTO, rxspin, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, rxspin_sysctl, "I", "jme_rxeof (nsegs=0,pktlen=0) spin demo");
SYSCTL_PROC(_kern_jmereplica, OID_AUTO, rxspin_fix, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, rxspin_fix_sysctl, "I", "jme_rxeof with DF-3777 fix.diff shape");

static int
jme_replica_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser_main);
		lwkt_serialize_init(&ser_tx);
		lwkt_serialize_init(&ser_rx0);
		callout_init_mp(&tick_try_c);
		callout_init_mp(&tick_blk_c);
		kprintf("jme_callout_replica: loaded (hz=%d ncpus=%d)\n",
		    hz, ncpus);
		return 0;
	case MOD_UNLOAD:
		/* only safe if the AB-BA test was not run */
		callout_stop(&tick_try_c);
		callout_stop(&tick_blk_c);
		return 0;
	}
	return 0;
}

static moduledata_t jme_replica_mod = {
	"jme_callout_replica",
	jme_replica_load,
	NULL
};
DECLARE_MODULE(jme_callout_replica, jme_replica_mod, SI_SUB_DRIVERS,
    SI_ORDER_ANY);
