/*
 * dc_callout_replica_fixed.c - protocol replica of the FIXED if_dc.c
 * callout protocol (the fix proposed for DF-3850's fix.diff):
 *
 *   dc_tick() becomes non-blocking:
 *     if (!lwkt_serialize_try(ifp->if_serializer)) {
 *             callout_reset(&sc->dc_stat_timer, 1, dc_tick, sc);
 *             return;
 *     }
 *
 * With this shape the callback is never INPROG-parked on the serializer,
 * so dc_stop()'s synchronous callout_stop() (if_dc.c:3489) returns, and
 * the STOP post-processing unloads the pending 1-tick retry (verified as
 * pending==0), so the timer is really dead afterwards.
 *
 *   sysctl kern.dcreplica_armfx=1   arm the fixed-shape tick
 *   sysctl kern.dcreplica_stopfx=1  hold the serializer 3s (tick retries
 *                                   several times), then callout_stop()
 *                                   under it -> MUST return, pending==0.
 */
#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;
static struct callout tick_c;		/* NON-MPSAFE, like if_dc.c:1847 */

static volatile int tick_fired, tick_ran, tick_retried;

static void
dc_tick_fixed(void *arg)
{
	tick_fired++;
	/* FIXED if_dc.c:2725 - try acquire, never block softclock */
	if (!lwkt_serialize_try(&ser)) {
		tick_retried++;
		callout_reset(&tick_c, 1, dc_tick_fixed, NULL);
		return;
	}
	tick_ran++;
	callout_reset(&tick_c, hz / 10, dc_tick_fixed, 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_fixed, NULL);
	kprintf("dcreplica-fx[arm]: fixed-shape dc_stat_timer armed\n");
	return (0);
}

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

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

	/* dc_stop context: serializer held across callout_stop(). */
	lwkt_serialize_enter(&ser);
	for (i = 0; i < 3000; i++)	/* hold 3s: tick must retry, not park */
		DELAY(1000);

	callout_stop(&tick_c);		/* if_dc.c:3489 with the fix applied */

	pending = !!callout_pending(&tick_c);
	kprintf("dcreplica-fx[stop]: FIXED shape: callout_stop RETURNED, "
	    "pending=%d fired=%d retried=%d ran=%d\n",
	    pending, tick_fired, tick_retried, tick_ran);
	lwkt_serialize_exit(&ser);
	return (0);
}

static int
fx_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&tick_c);
		tick_fired = tick_ran = tick_retried = 0;
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "dcreplica_armfx", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, arm_sysctl, "I", "arm fixed-shape tick");
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "dcreplica_stopfx", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, stop_sysctl, "I", "dc_stop under serializer (fixed)");
		kprintf("dcreplica-fx: loaded (FIXED if_dc.c protocol replica)\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);
	}
	return (EOPNOTSUPP);
}

static moduledata_t fx_mod = { "dcreplica_fx", fx_load, NULL };
DECLARE_MODULE(dcreplica_fx, fx_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
