/*
 * dc_callout_replica.c - deterministic replica of if_dc.c's callout protocol.
 *
 * if_dc.c arms ONE legacy callout for its 1 Hz / 10 Hz media tick:
 *
 *   dc_attach()   if_dc.c:1847   callout_init(&sc->dc_stat_timer)
 *                                (plain, NON-MPSAFE -> callback executes
 *                                 under the Giant MP lock, kern_timeout.c:583)
 *   dc_tick()     if_dc.c:2717   lwkt_serialize_enter(ifp->if_serializer)
 *                                (:2725, BLOCKING - lwkt_serialize.c:105 ->
 *                                 atomic_intr_cond_enter(..., sleep))
 *                                ... mii/PHY work under the serializer ...
 *                                self re-arm at :2786-2789, exit at :2791
 *   dc_stop()     if_dc.c:3489   callout_stop(&sc->dc_stat_timer)
 *
 * EVERY caller of dc_stop() holds ifp->if_serializer at that point:
 *   dc_detach :2213, dc_intr :2949 (via dc_stop when !RUNNING),
 *   dc_init :3196 (SIOCSIFFLAGS up, MTU/media via dc_ioctl->dc_init,
 *   watchdog :3468), dc_shutdown :3546, dc_suspend :3564.
 *
 * DragonFly callout_stop() is SYNCHRONOUS: _callout_cancel_or_stop(sync=1)
 * ssleep()'s on 'costp' (kern_timeout.c:920) until an INPROG callback
 * finishes (kern_timeout.c:567 sets INPROG around the call).
 *
 * => A dc_tick that has been dequeued and is parked in
 *    lwkt_serialize_enter() on the serializer held by the dc_stop caller
 *    deadlocks against it (AB-BA): the stopper sleeps forever in callout_stop,
 *    the tick sleeps forever on the serializer, the CPU's softclock thread
 *    stops delivering ALL callouts, and - because dc_stat_timer is a
 *    non-MPSAFE callout - the parked softclock thread holds the Giant MP
 *    lock while it sleeps.
 *
 * Sysctls (root):
 *   kern.dcreplica.arm = 1   arm the dc_tick-shaped timer (1 tick, so the
 *                            park is deterministic)
 *   kern.dcreplica.stop = 1  enter the serializer (dc_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;	/* ifp->if_serializer replica */
static struct callout tick_c;		/* sc->dc_stat_timer replica (NON-MPSAFE) */

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

static void
dc_tick_replica(void *arg)
{
	tick_fired++;
	/* if_dc.c:2725 - BLOCKING acquire (park point) */
	lwkt_serialize_enter(&ser);
	tick_ran++;
	/* if_dc.c:2786-2789 - self re-arm under the serializer */
	callout_reset(&tick_c, hz / 10, dc_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, dc_tick_replica, NULL);
	kprintf("dcreplica[arm]: dc_stat_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);

	/*
	 * dc_stop context: 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) {
		tick_parked_seen = 1;
		kprintf("dcreplica[stop]: tick INPROG and PARKED on the "
		    "serializer (fired=%d ran=%d)\n", tick_fired, tick_ran);
		kprintf("dcreplica[stop]: IF THIS IS THE LAST dcreplica LINE "
		    "callout_stop NEVER RETURNED (AB-BA deadlock)\n");
	} else {
		kprintf("dcreplica[stop]: no tick fired within 5s; "
		    "callout_stop will trivially return\n");
	}

	callout_stop(&tick_c);			/* if_dc.c:3489 */

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

static int
dcreplica_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_dc.c:1847 */
		tick_fired = tick_ran = tick_parked_seen = 0;
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "dcreplica_arm", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, arm_sysctl, "I", "arm dc_tick replica");
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "dcreplica_stop", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, stop_sysctl, "I", "dc_stop replica (callout_stop "
		    "under serializer)");
		kprintf("dcreplica: loaded (if_dc.c callout protocol replica)\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);		/* nothing to clean while wedged */
	}
	return (EOPNOTSUPP);
}

static moduledata_t dcreplica_mod = { "dcreplica", dcreplica_load, NULL };
DECLARE_MODULE(dcreplica, dcreplica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
