⬢ DragonFlyBSD Kernel Audit
DF-3935 / age_replica.c
← back to finding ↓ download raw
/*
 * age_replica.c - deterministic replica of if_age.c's callout protocol
 * (DF-3715 family, age instance).
 *
 * age(4) uses ONE callout, initialized NON-MP:
 *   callout_init(&sc->age_tick_ch);                    if_age.c:517
 * whose handler takes the driver serializer by hand and self re-arms:
 *   age_tick()                                          if_age.c:2188-2200
 *     lwkt_serialize_enter(ifp->if_serializer);         :2194  (BLOCKING)
 *     mii_tick(mii);
 *     callout_reset(&sc->age_tick_ch, hz, age_tick, sc);:2197  (self re-arm)
 *     lwkt_serialize_exit(ifp->if_serializer);          :2199
 *
 * age_stop() tears it down while HOLDING that same serializer:
 *   age_stop()                                          if_age.c:2516-2534
 *     ASSERT_SERIALIZED(ifp->if_serializer);            :2524
 *     callout_stop(&sc->age_tick_ch);                   :2534
 * (every age_stop caller holds the serializer: age_ioctl SIOCSIFFLAGS
 *  :1673-1674, age_init :2242+2249, age_detach :686-688, age_suspend :1393)
 *
 * DragonFly callout_stop() is synchronous (kern_timeout.c:1074-1077 ->
 * _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.
 *
 * Same protocol shape as the PROVEN em(4) instance (DF-3715) except em
 * uses callout_init_mp callouts; age uses a plain (non-MP) callout, which
 * this replica matches.
 */
#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;		/* age_tick_ch replica (non-MP) */
static volatile int tick_fired, tick_ran;

static void
age_tick_replica(void *arg)
{
	tick_fired++;
	lwkt_serialize_enter(&ser);			/* if_age.c:2194 (BLOCKING) */
	tick_ran++;
	callout_reset(&tick_c, hz / 10, age_tick_replica, NULL);
	lwkt_serialize_exit(&ser);			/* if_age.c:2199 */
}

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, age_tick_replica, NULL);
		kprintf("age_replica: armed age_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);

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

static SYSCTL_NODE(_kern, OID_AUTO, agereplica, CTLFLAG_RW, 0,
    "age callout replica");
SYSCTL_PROC(_kern_agereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm age_tick replica");
SYSCTL_PROC(_kern_agereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_sysctl, "I", "age_stop vs age_tick (AB-BA)");

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

static moduledata_t age_replica_mod = { "age_replica", age_replica_load, NULL };
DECLARE_MODULE(age_replica, age_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);