/*
 * fxp_callout_replica.c - deterministic replica of if_fxp.c's callout
 * protocol (pass-2, DF-4064; adapted from DF-3738's emx replica).
 *
 * if_fxp.c arms ONE NON-MPSAFE callout (plain callout_init at :399):
 *
 *   fxp_tick()   if_fxp.c:1441-1555   lwkt_serialize_enter(
 *                                         sc->arpcom.ac_if.if_serializer)
 *					  BLOCKING at :1449
 *                                        + self re-arm at :1552
 *                                        (callout_reset(&sc->fxp_stat_timer,
 *                                         hz, fxp_tick, sc) INSIDE the
 *                                         serializer, before exit :1554)
 *
 * fxp_stop() tears it down with callout_stop(&sc->fxp_stat_timer) at :1577
 * while holding the ifnet serializer (ASSERT_SERIALIZED :1568; callers:
 * fxp_detach :738, fxp_shutdown :787, fxp_suspend :805, fxp_init :1651,
 * fxp_ioctl SIOCSIFFLAGS :2078).  Because the callout is NON-MPSAFE, the
 * callback also executes while holding the MP lock (kern_timeout.c:575-580),
 * so a parked fxp_tick wedges every BGL-dependent path system-wide.
 *
 * DragonFly callout_stop() is SYNCHRONOUS (kern_timeout.c:1091 ->
 * _callout_cancel_or_stop sync=1; ssleep "costp" at :920).  A callback
 * dequeued and parked on the serializer deadlocks against the stopper
 * (AB-BA): stopper sleeps in callout_stop holding the serializer; the
 * softclock thread is parked inside the callback; and (fxp-only twist) the
 * MP lock is held by the parked softclock thread.
 *
 * sysctls:
 *   kern.fxr.arm     =1  arm the replica timer (hz period, fxp shape)
 *   kern.fxr.stop_blk=1  hold serializer, wait until the handler is parked
 *                        on it (fired-but-not-ran), callout_stop() ->
 *                        must NEVER return if the AB-BA analysis is right
 *                        (fxp_stop shape; wedges the writer + a softclock
 *                        thread + the MP lock).
 */
#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>

static struct lwkt_serialize ser;	/* == ifp->if_serializer replica */
static struct callout blk_c;		/* == sc->fxp_stat_timer replica */

static volatile int blk_fired, blk_ran;

static void
blk_timer(void *arg)
{
	blk_fired++;
	/* if_fxp.c:1449 - BLOCKING acquire of the ifnet serializer */
	lwkt_serialize_enter(&ser);
	blk_ran++;
	/* if_fxp.c:1552 - self re-arm INSIDE the serializer */
	callout_reset(&blk_c, hz, blk_timer, NULL);
	/* if_fxp.c:1554 - exit */
	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)
		return error;
	if (v == 1) {
		blk_fired = blk_ran = 0;
		callout_reset(&blk_c, 1, blk_timer, NULL);
		kprintf("fxr: armed (fxp_stat_timer shape, hz period)\n");
	}
	return 0;
}

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;

	/*
	 * AB-BA test (fxp_stop shape): take the serializer, wait until the
	 * blocking handler is parked inside lwkt_serialize_enter (fired but
	 * not ran), then callout_stop().
	 */
	lwkt_serialize_enter(&ser);
	while (blk_ran == blk_fired)
		tsleep(&blk_ran, 0, "blkw", 2);
	kprintf("fxr: handler BLOCKED on our serializer (fired=%d ran=%d)"
	    " - entering callout_stop() [fxp_stop:1577 shape]\n",
	    blk_fired, blk_ran);
	kprintf("fxr: IF THIS IS THE LAST fxr LINE, AB-BA DEADLOCK IS PROVEN"
	    " (writer stuck D-'costp', softclock B-'slize', MP lock held)\n");
	callout_stop(&blk_c);
	kprintf("fxr: callout_stop returned (NO deadlock)\n");
	lwkt_serialize_exit(&ser);
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, fxr, CTLFLAG_RW, 0,
    "fxp callout replica");
SYSCTL_PROC(_kern_fxr, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm fxp_stat_timer replica");
SYSCTL_PROC(_kern_fxr, OID_AUTO, stop_blk, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_blk_sysctl, "I", "fxp_stop vs blocking fxp_tick (AB-BA)");

static int
fxp_callout_replica_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&blk_c);	/* NON-MP: exactly if_fxp.c:399 */
		kprintf("fxp_callout_replica: loaded (hz=%d)\n", hz);
		return 0;
	case MOD_UNLOAD:
		/* only safe if the AB-BA test was not run to the deadlock */
		callout_stop(&blk_c);
		return 0;
	}
	return 0;
}

static moduledata_t fxp_callout_replica_mod = {
	"fxp_callout_replica",
	fxp_callout_replica_load,
	NULL
};
DECLARE_MODULE(fxp_callout_replica, fxp_callout_replica_mod,
    SI_SUB_DRIVERS, SI_ORDER_ANY);
