/*
 * sk_callout_replica_fixed.c - fix-shape replica for if_sk.c (DF-3903).
 *
 * Same protocol as sk_callout_replica.c, but the tick uses the fix shape
 * proposed in fix.diff for if_sk.c:
 *
 *   sk_tick()/sk_yukon_tick() -> lwkt_serialize_try(); on failure re-arm
 *   for 1 tick and return (NEVER park the softclock thread on the
 *   serializer), on success do the work, re-arm at 1 Hz and exit.
 *
 * With this shape, sk_stop()'s synchronous callout_stop() under the
 * serializer always returns: the callback either completes or re-arms
 * to a later tick without blocking, so no AB-BA.
 *
 * Sysctls (root):
 *   kern.skrepfix.arm = 1   arm the fixed tick (1 Hz, busy serializer)
 *   kern.skrepfix.stop = 1  hold the serializer for 3s, then callout_stop
 *                           under it (must RETURN, pending must be 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;

static volatile int tick_fired, tick_ran, tick_retry;

static void
sk_tick_fixed(void *arg)
{
	tick_fired++;
	/* fix shape: never block softclock on the serializer */
	if (!lwkt_serialize_try(&ser)) {
		tick_retry++;
		callout_reset(&tick_c, 1, sk_tick_fixed, NULL);
		return;
	}
	tick_ran++;
	callout_reset(&tick_c, hz, sk_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, sk_tick_fixed, NULL);
	kprintf("skrepfix[arm]: fixed-shape tick armed\n");
	return (0);
}

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

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

	/* hold the serializer ~3s so the tick must retry past us */
	lwkt_serialize_enter(&ser);
	for (i = 0; i < 3000; i++)
		DELAY(1000);

	callout_stop(&tick_c);		/* must RETURN now */

	kprintf("skrepfix[stop]: callout_stop RETURNED (fixed shape): "
	    "fired=%d ran=%d retry=%d pending=%d\n",
	    tick_fired, tick_ran, tick_retry, callout_pending(&tick_c));
	lwkt_serialize_exit(&ser);
	return (0);
}

static int
skrepfix_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_retry = 0;
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "skrepfix_arm", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, arm_sysctl, "I", "arm fixed sk tick");
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "skrepfix_stop", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, stop_sysctl, "I", "sk_stop shape with fix");
		kprintf("skrepfix: loaded (if_sk.c fix-shape replica)\n");
		return (0);
	case MOD_UNLOAD:
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t skrepfix_mod = { "skrepfix", skrepfix_load, NULL };
DECLARE_MODULE(skrepfix, skrepfix_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
