/*
 * sf_callout_harness.c - replica of if_sf.c's callout/serializer protocol
 * (DF-4327, DF-3715 family).
 *
 * if_sf.c arms one callout:
 *
 *   sf_stats_update()  if_sf.c:1387-1424
 *       lwkt_serialize_enter(ifp->if_serializer);   [BLOCKING, :1396]
 *       ... stats + mii_tick ...
 *       callout_reset(&sc->sf_stat_timer, hz, sf_stats_update, sc); [:1421]
 *       lwkt_serialize_exit(...);
 *
 *   sf_stop()          if_sf.c:1337-1377   - always called WITH the
 *                                          serializer held (ioctl, detach,
 *                                          watchdog, abnormal-IRQ reinit):
 *       callout_stop(&sc->sf_stat_timer);   [:1344]  BLOCKING stop
 *
 * DragonFly callout_stop() = _callout_cancel_or_stop(cc, CALLOUT_STOP, 1)
 * (sys/kern/kern_timeout.c:1091) which ssleep("costp")s until an INPROG
 * callback completes (kern_timeout.c:923).  If the 1s stat timer has fired
 * on another CPU and is blocked acquiring the serializer that the thread
 * inside sf_stop() holds, callout_stop() waits for the callback while the
 * callback waits for the serializer held by the stopper:  AB-BA, permanent
 * wedge of the sysctl/ioctl thread *and* of that CPU's softclock thread.
 *
 * This module reproduces exactly that protocol:
 *
 *   sysctl kern.sfcal.arm =1  arm the sf_stats_update-shaped timer
 *   sysctl kern.sfcal.stop=1  (RUN IN BACKGROUND) hold the serializer,
 *                             wait until the callback is provably blocked
 *                             on it, then callout_stop() -> NEVER RETURNS.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/serialize.h>
#include <sys/callout.h>
#include <sys/sysctl.h>
#include <sys/thread.h>

static struct lwkt_serialize ser;
static struct callout c;
static volatile int fired, ran;
static volatile int stop_entered, stop_returned, stop_ret;

static void
sf_timer(void *arg)
{
	fired++;
	/* if_sf.c:1396 - BLOCKING acquire, exactly like sf_stats_update */
	lwkt_serialize_enter(&ser);
	ran++;
	/* if_sf.c:1421 - self re-arm under the serializer */
	callout_reset(&c, hz, sf_timer, 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)
		return (error);
	if (v == 1) {
		fired = ran = 0;
		stop_entered = stop_returned = 0;
		callout_reset(&c, 1, sf_timer, NULL);
		kprintf("sfcal: armed sf_stats_update-shaped timer (1 tick)\n");
	}
	return (0);
}

static int
stop_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;
	int spins, f0;

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

	/* == sf_stop() entry: serializer held == */
	stop_entered = 1;
	lwkt_serialize_enter(&ser);
	kprintf("sfcal[stop]: serializer held; waiting for callback to be "
	    "provably blocked on it...\n");

	/* wait until the handler has fired again but is blocked on ser */
	f0 = fired;
	{
		int ran0 = ran;
		spins = 0;
		while (fired == f0 || ran != ran0) {
			if (++spins > 2000) {	/* ~seconds-scale safety */
				kprintf("sfcal[stop]: FAILED to catch blocked "
				    "state (fired=%d ran=%d)\n", fired, ran);
				lwkt_serialize_exit(&ser);
				stop_returned = 1;
				return (0);
			}
			tsleep(&ran, 0, "sfcalw", 1);
			if (fired != f0 && ran == ran0)
				break;
		}
	}
	kprintf("sfcal[stop]: callback fired=%d ran=%d -> BLOCKED on "
	    "serializer. calling callout_stop() under serializer "
	    "(if_sf.c:1344 protocol)...\n", fired, ran);

	/* == if_sf.c:1344 - expected to NEVER return == */
	stop_ret = callout_stop(&c);
	stop_returned = 1;			/* only reached if no deadlock */
	kprintf("sfcal[stop]: UNEXPECTED: callout_stop returned %d\n",
	    stop_ret);
	lwkt_serialize_exit(&ser);
	return (0);
}

static SYSCTL_NODE(_kern, OID_AUTO, sfcal, CTLFLAG_RW, 0,
    "sf callout replica");
SYSCTL_PROC(_kern_sfcal, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, arm_sysctl, "I", "arm the sf timer replica");
SYSCTL_PROC(_kern_sfcal, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, stop_sysctl, "I", "deadlock replica (blocks)");

static int
sfcal_modevent(module_t mod, int type, void *data)
{
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&c);
		kprintf("sfcal: loaded (kern.sfcal.arm / kern.sfcal.stop)\n");
		break;
	case MOD_UNLOAD:
		callout_stop_async(&c);
		break;
	default:
		break;
	}
	return (error);
}

static struct moduledata sf_callout_harness_mod = { "sf_callout_harness", sfcal_modevent, NULL };
DECLARE_MODULE(sf_callout_harness, sf_callout_harness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(sf_callout_harness, 1);
