/*
 * ale_replica.c — deterministic replica of if_ale.c's callout protocol
 * (DF-3715 family, ale instance; adapted from the PROVEN age replica
 * DF-3935, same vendor driver family, identical protocol shape).
 *
 * ale(4) uses ONE callout, initialized NON-MP:
 *   callout_init(&sc->ale_tick_ch);                    if_ale.c:455
 * whose handler takes the driver serializer by hand and self re-arms:
 *   ale_tick()                                          if_ale.c:2257-2272
 *     lwkt_serialize_enter(ifp->if_serializer);         :2263  (BLOCKING)
 *     mii_tick(mii); ale_stats_update(sc);
 *     callout_reset(&sc->ale_tick_ch, hz, ale_tick, sc);:2269  (self re-arm)
 *     lwkt_serialize_exit(ifp->if_serializer);          :2271
 *
 * ale_stop() tears it down while HOLDING that same serializer:
 *   ale_stop()                                          if_ale.c:2544-2599
 *     ASSERT_SERIALIZED(ifp->if_serializer);            :2551
 *     callout_stop(&sc->ale_tick_ch);                   :2560
 * (every ale_stop caller holds the serializer: ale_ioctl SIOCSIFFLAGS
 *  down :1762-1764 and MTU-change ale_init :1745-1748, ale_init :2319
 *  (also reached from the ale_intr error path :1981-1985 IN INTERRUPT
 *  CONTEXT), ale_detach :660-662, ale_suspend :1455.)
 *
 * DragonFly callout_stop() is synchronous (kern_timeout.c:1091-1094 ->
 * _callout_cancel_or_stop(..., sync=1) -> ssleep(c, ..., "costp", 0) at
 * kern_timeout.c:923).  A callback dequeued and parked inside
 * lwkt_serialize_enter therefore deadlocks against the stopper (AB-BA):
 *   stopper: holds serializer -> sleeps in callout_stop waiting for the
 *            INPROG callback
 *   callback: INPROG -> blocked in lwkt_serialize_enter waiting for the
 *            serializer the stopper holds
 * Neither side can progress; the interface serializer is never released
 * and the affected CPU's softclock thread stops delivering callouts.
 */
#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 tick_c;		/* ale_tick_ch replica (non-MP) */
static volatile int tick_fired, tick_ran;

static void
ale_tick_replica(void *arg)
{
	tick_fired++;
	lwkt_serialize_enter(&ser);			/* if_ale.c:2263 (BLOCKING) */
	tick_ran++;
	callout_reset(&tick_c, hz / 10, ale_tick_replica, NULL);
	lwkt_serialize_exit(&ser);			/* if_ale.c:2271 */
}

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) {
		tick_fired = tick_ran = 0;
		callout_reset(&tick_c, 1, ale_tick_replica, NULL);
		kprintf("ale_replica: armed ale_tick replica "
		    "(non-mp callout, ticks hz/10)\n");
	}
	return (0);
}

static int
stop_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);

	/* ale_stop context: serializer held (if_ale.c:2551) */
	lwkt_serialize_enter(&ser);
	while (tick_ran == tick_fired)
		tsleep(&tick_ran, 0, "alew", 2);
	kprintf("ale_replica: tick handler BLOCKED on our serializer "
	    "(fired=%d ran=%d) - entering callout_stop()\n",
	    tick_fired, tick_ran);
	kprintf("ale_replica: IF THIS IS THE LAST ale_replica LINE, "
	    "AB-BA DEADLOCK PROVEN (DF-3715 family / ale instance)\n");
	callout_stop(&tick_c);				/* if_ale.c:2560 */
	kprintf("ale_replica: callout_stop returned (NO deadlock)\n");
	lwkt_serialize_exit(&ser);
	return (0);
}

static SYSCTL_NODE(_kern, OID_AUTO, alereplica, CTLFLAG_RW, 0,
    "ale callout replica");
SYSCTL_PROC(_kern_alereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm ale_tick replica");
SYSCTL_PROC(_kern_alereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_sysctl, "I", "ale_stop vs ale_tick (AB-BA)");

static int
ale_replica_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&tick_c);		/* if_ale.c:455: NON-mp */
		kprintf("ale_replica: loaded (hz=%d)\n", hz);
		return (0);
	case MOD_UNLOAD:
		callout_stop(&tick_c);
		return (0);
	}
	return (0);
}

static moduledata_t ale_replica_mod = { "ale_replica", ale_replica_load, NULL };
DECLARE_MODULE(ale_replica, ale_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
