/*
 * sk_callout_replica.c - deterministic replica of if_sk.c's callout protocol.
 *
 * if_sk.c arms ONE legacy callout per port for its 1 Hz tick:
 *
 *   sk_attach()    if_sk.c:1311  callout_init(&sc_if->sk_tick_timer)
 *                               (plain, NON-MPSAFE -> callback executes
 *                                under the Giant MP lock)
 *   sk_yukon_tick() if_sk.c:2081 lwkt_serialize_enter(ifp->if_serializer)
 *                               (:2087, BLOCKING)
 *                               mii_tick(mii) ... under the serializer
 *                               self re-arm at :2089 (UNCONDITIONAL),
 *                               exit at :2090
 *   sk_tick()      if_sk.c:2032  lwkt_serialize_enter (:2041, BLOCKING),
 *                               conditional self re-arm at :2067 while the
 *                               link is down (GPIO poll loop :2061-2070)
 *   sk_stop()      if_sk.c:2770  ASSERT_SERIALIZED (:2778) then
 *                               callout_stop(&sc_if->sk_tick_timer) (:2782)
 *
 * EVERY caller of sk_stop() holds ifp->if_serializer (= &sc->sk_serializer,
 * the one card-global serializer both ports share, ether_ifattach at :1281):
 *   sk_ioctl SIOCSIFFLAGS-down :992, SIOCSIFMTU -> sk_init :977 ->
 *   sk_stop :2606, sk_watchdog :1758, sk_detach :1540, skc_shutdown path.
 * Additional re-arm sites under the same serializer: sk_intr_bcom :2138,
 * sk_intr_xmac :2159 (ISR context).
 *
 * DragonFly callout_stop() is SYNCHRONOUS (kern_timeout.c:1091 ->
 * :859-932, ssleep 'costp' at :920) - it waits for an INPROG callback.
 *
 * => A sk_yukon_tick/sk_tick dequeued and parked in
 *    lwkt_serialize_enter() on the serializer held by the sk_stop caller
 *    deadlocks against it (AB-BA): the stopper sleeps forever in
 *    callout_stop, the tick sleeps forever on the serializer, that CPU's
 *    softclock thread stops delivering ALL callouts, and - because
 *    sk_tick_timer is a non-MPSAFE callout - the parked softclock thread
 *    holds the Giant MP lock while it sleeps.
 *
 * Sysctls (root):
 *   kern.skreplica.arm = 1   arm the sk_yukon_tick-shaped timer (1 tick)
 *   kern.skreplica.stop = 1  enter the serializer (sk_stop context), wait
 *                            until the tick is parked on it, then
 *                            callout_stop() -- NEVER RETURNS on the AB-BA.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/serialize.h>
#include <sys/callout.h>

static struct lwkt_serialize ser;	/* sc->sk_serializer replica */
static struct callout tick_c;		/* sc_if->sk_tick_timer replica (NON-MPSAFE) */

static volatile int tick_fired;		/* bumped before the park point */
static volatile int tick_ran;		/* bumped only when acquired */

static void
sk_tick_replica(void *arg)
{
	tick_fired++;
	/* if_sk.c:2087 - BLOCKING acquire (park point) */
	lwkt_serialize_enter(&ser);
	tick_ran++;
	/* if_sk.c:2089 - unconditional self re-arm under the serializer */
	callout_reset(&tick_c, hz, sk_tick_replica, NULL);
	lwkt_serialize_exit(&ser);
}

static int
arm_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

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

	callout_reset(&tick_c, 1, sk_tick_replica, NULL);
	kprintf("skreplica[arm]: sk_tick_timer armed (1 tick), waiting\n");
	return (0);
}

static int
stop_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;
	int i, base;

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

	/*
	 * sk_stop context (if_sk.c:2770-2782): serializer held while
	 * calling callout_stop().
	 */
	lwkt_serialize_enter(&ser);
	base = tick_fired;
	for (i = 0; i < 5000; i++) {		/* wait <= 5s for a tick fire */
		if (tick_fired != base && tick_ran == base)
			break;			/* fired but parked on us */
		DELAY(1000);
	}
	if (tick_fired != base) {
		kprintf("skreplica[stop]: tick INPROG and PARKED on the "
		    "serializer (fired=%d ran=%d)\n", tick_fired, tick_ran);
		kprintf("skreplica[stop]: IF THIS IS THE LAST skreplica LINE "
		    "callout_stop NEVER RETURNED (AB-BA deadlock)\n");
	} else {
		kprintf("skreplica[stop]: no tick fired within 5s; "
		    "callout_stop will trivially return\n");
	}

	callout_stop(&tick_c);			/* if_sk.c:2782 */

	kprintf("skreplica[stop]: callout_stop RETURNED (no deadlock in "
	    "this interleave)\n");
	lwkt_serialize_exit(&ser);
	return (0);
}

static int
skreplica_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&tick_c);	/* plain: NON-MPSAFE, like if_sk.c:1311 */
		tick_fired = tick_ran = 0;
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "skreplica_arm", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, arm_sysctl, "I", "arm sk tick replica");
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "skreplica_stop", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, stop_sysctl, "I", "sk_stop replica (callout_stop "
		    "under serializer)");
		kprintf("skreplica: loaded (if_sk.c callout protocol replica)\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);		/* nothing to clean while wedged */
	}
	return (EOPNOTSUPP);
}

static moduledata_t skreplica_mod = { "skreplica", skreplica_load, NULL };
DECLARE_MODULE(skreplica, skreplica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
