⬢ DragonFlyBSD Kernel Audit
DF-3906 / xl_callout_replica.c
← back to finding ↓ download raw
/*
 * xl_callout_replica.c - deterministic replica of if_xl.c's callout protocol.
 *
 * if_xl.c arms ONE legacy callout for its 1 Hz stats/media tick:
 *
 *   xl_attach()          if_xl.c:1320  callout_init(&sc->xl_stat_timer)
 *                                     (plain, NON-MPSAFE -> callback
 *                                      executes under the Giant MP lock)
 *   xl_stats_update()    if_xl.c:2372  lwkt_serialize_enter(
 *                                       sc->arpcom.ac_if.if_serializer)
 *                                       (:2377, BLOCKING)
 *                                       -> xl_stats_update_serialized()
 *                                       ... 16 CSR reads + mii_tick ...
 *                                       self re-arm at :2429
 *                                       (callout_reset, 1 Hz, guarded only
 *                                       by xl_stats_no_timeout),
 *                                       exit at :2379
 *   xl_stop()            if_xl.c:3136  ASSERT_SERIALIZED (:3142) ...
 *                                       callout_stop(&sc->xl_stat_timer)
 *                                       (:3169)
 *
 * EVERY caller of xl_stop() holds ifp->if_serializer at that point:
 *   xl_detach :1573, xl_ioctl SIOCSIFFLAGS-down :3065, xl_init :2712
 *   (SIOCSIFFLAGS-up, media change via xl_ifmedia_upd :2955 -> xl_init,
 *   watchdog :3125 via xl_init), xl_shutdown :3212, xl_suspend :3222.
 *
 * DragonFly callout_stop() is SYNCHRONOUS (kern_timeout.c:1091 ->
 * :859-932, ssleep 'costp' at :920) - it waits for an INPROG callback.
 *
 * => An xl_stats_update dequeued and parked in lwkt_serialize_enter()
 *    on the serializer held by the xl_stop caller deadlocks against it
 *    (AB-BA): the stopper sleeps forever in callout_stop, the tick sleeps
 *    forever on the serializer, that CPU's softclock thread stops
 *    delivering ALL callouts, and - because xl_stat_timer is a
 *    non-MPSAFE callout - the parked softclock thread holds the Giant
 *    MP lock while it sleeps.  10th member of the DF-3715 family.
 *
 * Sysctls (root):
 *   kern.xlreplica.arm = 1   arm the xl_stats_update-shaped timer (1 tick)
 *   kern.xlreplica.stop = 1  enter the serializer (xl_stop context), wait
 *                            until the tick is parked on it, then
 *                            callout_stop() -- NEVER RETURNS on the AB-BA.
 */
#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;	/* ifp->if_serializer replica */
static struct callout tick_c;		/* sc->xl_stat_timer replica (NON-MPSAFE) */

static volatile int tick_fired;		/* bumped before the park point */
static volatile int tick_ran;		/* bumped only when acquired */

static void
xl_tick_replica(void *arg)
{
	tick_fired++;
	/* if_xl.c:2377 - BLOCKING acquire (park point) */
	lwkt_serialize_enter(&ser);
	tick_ran++;
	/* if_xl.c:2429 - self re-arm inside the serialized body */
	callout_reset(&tick_c, hz, xl_tick_replica, 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, xl_tick_replica, NULL);
	kprintf("xlreplica[arm]: xl_stat_timer armed (1 tick), waiting\n");
	return (0);
}

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

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

	/*
	 * xl_stop context (if_xl.c:3136-3169): serializer held while
	 * calling callout_stop().
	 */
	lwkt_serialize_enter(&ser);
	base = tick_fired;
	for (i = 0; i < 5000; i++) {		/* wait <= 5s for a tick fire */
		if (tick_fired != base && tick_ran == base)
			break;			/* fired but parked on us */
		DELAY(1000);
	}
	if (tick_fired != base) {
		kprintf("xlreplica[stop]: tick INPROG and PARKED on the "
		    "serializer (fired=%d ran=%d)\n", tick_fired, tick_ran);
		kprintf("xlreplica[stop]: IF THIS IS THE LAST xlreplica LINE "
		    "callout_stop NEVER RETURNED (AB-BA deadlock)\n");
	} else {
		kprintf("xlreplica[stop]: no tick fired within 5s; "
		    "callout_stop will trivially return\n");
	}

	callout_stop(&tick_c);			/* if_xl.c:3169 */

	kprintf("xlreplica[stop]: callout_stop RETURNED (no deadlock in "
	    "this interleave)\n");
	lwkt_serialize_exit(&ser);
	return (0);
}

static int
xlreplica_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		lwkt_serialize_init(&ser);
		callout_init(&tick_c);	/* plain: NON-MPSAFE, like if_xl.c:1320 */
		tick_fired = tick_ran = 0;
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "xlreplica_arm", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, arm_sysctl, "I", "arm xl tick replica");
		sysctl_add_oid(NULL, SYSCTL_STATIC_CHILDREN(_kern),
		    OID_AUTO, "xlreplica_stop", CTLTYPE_INT|CTLFLAG_RW,
		    NULL, 0, stop_sysctl, "I", "xl_stop replica (callout_stop "
		    "under serializer)");
		kprintf("xlreplica: loaded (if_xl.c callout protocol replica)\n");
		return (0);
	case MOD_UNLOAD:
		return (EBUSY);		/* nothing to clean while wedged */
	}
	return (EOPNOTSUPP);
}

static moduledata_t xlreplica_mod = { "xlreplica", xlreplica_load, NULL };
DECLARE_MODULE(xlreplica, xlreplica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);