โฌข DragonFlyBSD Kernel Audit
DF-4218 / ae_replica.c
โ† back to finding โ†“ download raw
/*
 * ae_replica.c โ€” deterministic replica of if_ae.c's callout protocol
 * (DF-3715 family, ae instance; adapted from the PROVEN ale replica
 * DF-3991 / age replica DF-3935 โ€” same vendor driver family, identical
 * protocol shape, character-for-character the same dance as ae(4)).
 *
 * ae(4) uses ONE callout, initialized NON-MP:
 *   callout_init(&sc->ae_tick_ch);                     if_ae.c:1262
 * whose handler takes the driver serializer by hand and self re-arms:
 *   ae_tick()                                          if_ae.c:988-999
 *     lwkt_serialize_enter(ifp->if_serializer);        :995   (BLOCKING)
 *     mii_tick(mii);
 *     callout_reset(&sc->ae_tick_ch, hz, ae_tick, sc); :997   (self re-arm)
 *     lwkt_serialize_exit(ifp->if_serializer);         :998
 *
 * ae_stop() tears it down while HOLDING that same serializer:
 *   ae_stop()                                          if_ae.c:219-262
 *     ASSERT_SERIALIZED(ifp->if_serializer);           :225
 *     callout_stop(&sc->ae_tick_ch);                   :232
 * (every ae_stop caller holds the serializer: ae_ioctl SIOCSIFFLAGS
 *  "down" :1220-1221, ae_init :799 โ€” also reached from the ae_intr
 *  error path :770-775 IN INTERRUPT CONTEXT (DMAR/DMAW timeout,
 *  PHY linkdown) โ€”, ae_watchdog :983, ae_detach :1417,
 *  ae_suspend :1786, ae_shutdown :1801.)
 *
 * 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 (and, being a NON-MP callout,
 *             it parked there holding the Giant/mplock)
 * Neither side can progress; the interface serializer is never released
 * and the affected CPU's softclock thread stops delivering callouts.
 *
 * Replica acceleration: ae_tick's real period is hz (1 s); the replica
 * ticks at hz/10 purely to make the park observable immediately.  The
 * protocol (blocking enter + self re-arm + sync stop under serializer)
 * is identical.
 */
#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;		/* ae_tick_ch replica (non-MP) */
static volatile int tick_fired, tick_ran;

static void
ae_tick_replica(void *arg)
{
	tick_fired++;
	lwkt_serialize_enter(&ser);			/* if_ae.c:995 (BLOCKING) */
	tick_ran++;
	callout_reset(&tick_c, hz / 10, ae_tick_replica, NULL); /* :997 */
	lwkt_serialize_exit(&ser);			/* if_ae.c:998 */
}

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, ae_tick_replica, NULL);
		kprintf("ae_replica: armed ae_tick replica "
		    "(non-mp callout, ticks hz/10; real ae_tick is hz)\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);

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

static SYSCTL_NODE(_kern, OID_AUTO, aereplica, CTLFLAG_RW, 0,
    "ae callout replica");
SYSCTL_PROC(_kern_aereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm ae_tick replica");
SYSCTL_PROC(_kern_aereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_sysctl, "I", "ae_stop vs ae_tick (AB-BA)");

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

static moduledata_t ae_replica_mod = { "ae_replica", ae_replica_load, NULL };
DECLARE_MODULE(ae_replica, ae_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);