⬢ DragonFlyBSD Kernel Audit
DF-4025 / rereplica.c
← back to finding ↓ download raw
/*
 * rereplica.c - deterministic replica of if_re.c's re_timer callout protocol.
 *
 * if_re.c arms ONE MP-safe callout (callout_init_mp(&sc->re_timer),
 * if_re.c:604) whose handler takes the driver serializer by hand:
 *
 *   re_tick()              if_re.c:1354-1361  lwkt_serialize_enter (BLOCKING,
 *                                              :1358) then
 *   re_tick_serialized()   if_re.c:1363-1381  IFF_RUNNING gate (:1371) +
 *                                              self re-arm under the
 *                                              serializer (:1380)
 *
 * re_stop(sc, TRUE) (if_re.c:1988-2029) tears it down with
 * callout_stop(&sc->re_timer) (:2000) while holding that same serializer
 * (every caller holds it: re_detach :909-911, re_suspend :2045-2047,
 * re_shutdown :2116-2118, re_ioctl :1918, re_init :1871), and clears
 * IFF_RUNNING only AFTER the stop (:2001).
 *
 * DragonFly callout_stop() is synchronous (kern_timeout.c:1091 ->
 * _callout_cancel_or_stop(..., sync=1), ssleep "costp"): it waits for an
 * INPROG callback to finish.  A re_tick dequeued on another CPU that is
 * parked in lwkt_serialize_enter therefore deadlocks against the stopper
 * (AB-BA): both threads wedge permanently, the interface serializer is
 * never released, and the affected CPU's softclock thread stops delivering
 * ALL callouts on that CPU.
 *
 * Sysctls:
 *   kern.rereplica.arm  =1  arm the re_timer replica (short period for a
 *                            deterministic window; the real driver uses hz)
 *   kern.rereplica.stop =1  re_stop replica: hold serializer, wait until the
 *                            handler is parked, callout_stop() -> if the
 *                            AB-BA analysis is right this NEVER returns.
 */
#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;		/* sc->re_timer replica */
static volatile int running;		/* IFF_RUNNING replica */
static volatile int tick_fired, tick_ran;

static void	tick_timer(void *);

static void
tick_serialized(void *arg)		/* if_re.c:1363-1381 shape */
{
	/* if_re.c:1371 - IFF_RUNNING gate */
	if (!running)
		return;
	/* if_re.c:1380 - self re-arm UNDER the serializer */
	callout_reset(&tick_c, 1, tick_timer, arg);
}

static void
tick_timer(void *arg)			/* if_re.c:1354-1361 shape */
{
	tick_fired++;
	/* if_re.c:1358 - BLOCKING acquire */
	lwkt_serialize_enter(&ser);
	tick_ran++;
	tick_serialized(arg);
	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)
		return error;
	if (v == 1) {
		tick_fired = tick_ran = 0;
		running = 1;
		/* if_re.c:1881 callout_reset(&sc->re_timer, hz, re_tick, sc) */
		callout_reset(&tick_c, 1, tick_timer, NULL);
		kprintf("rereplica: armed (re_timer shape, 1-tick period)\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;

	/*
	 * re_stop(sc, TRUE) shape - if_re.c:1993 (ASSERT_SERIALIZED),
	 * :2000 callout_stop(), :2001 clear IFF_RUNNING, in that order,
	 * all while HOLDING the serializer.
	 */
	lwkt_serialize_enter(&ser);
	while (tick_ran == tick_fired)	/* wait until handler is parked */
		tsleep(&tick_ran, 0, "retkw", 2);
	kprintf("rereplica: re_tick parked on our serializer "
	    "(fired=%d ran=%d) - entering callout_stop()\n",
	    tick_fired, tick_ran);
	kprintf("rereplica: IF THIS IS THE LAST rereplica LINE, "
	    "AB-BA DEADLOCK IS PROVEN\n");
	callout_stop(&tick_c);		/* if_re.c:2000 */
	running = 0;			/* if_re.c:2001 */
	kprintf("rereplica: callout_stop returned (NO deadlock)\n");
	lwkt_serialize_exit(&ser);
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, rereplica, CTLFLAG_RW, 0,
    "re(4) callout replica");
SYSCTL_PROC(_kern_rereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    arm_sysctl, "I", "arm re_timer replica");
SYSCTL_PROC(_kern_rereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_sysctl, "I", "re_stop(TRUE) replica vs re_tick (AB-BA)");

static int
rereplica_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init_mp(&tick_c);
		running = 0;
		kprintf("rereplica: loaded (hz=%d)\n", hz);
		return 0;
	case MOD_UNLOAD:
		callout_stop_async(&tick_c);
		return 0;
	}
	return 0;
}

static moduledata_t rereplica_mod = {
	"rereplica",
	rereplica_load,
	NULL
};
DECLARE_MODULE(rereplica, rereplica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);