⬢ DragonFlyBSD Kernel Audit
DF-3717 / mxge_callout_replica.c
← back to finding ↓ download raw
/*
 * mxge_callout_replica.c - deterministic replica of if_mxge.c's
 * callout/serializer teardown protocol (DF-3717).
 *
 * mxge protocol under test (sys/dev/netif/mxge/if_mxge.c):
 *
 *   mxge_tick()   :3712-3750  self-re-arming MP callout (co_hdl, armed
 *                              :4445 callout_reset_bycpu); callback's FIRST
 *                              act is a BLOCKING lwkt_serialize_enter of
 *                              sc->main_serialize (:3720), re-arm via
 *                              callout_reset INSIDE the serializer (:3747).
 *
 *   mxge_detach() :4463-4474  ifnet_serialize_all(ifp) (takes main_serialize
 *                              first, :4463) then callout_stop(&sc->co_hdl)
 *                              (:4468) - a SYNC stop while holding the very
 *                              serializer the parked callback needs.
 *                              (callout_terminate at :4474 is outside the
 *                              serializers - the right pattern - but the
 *                              deadlock happens first at :4468.)
 *
 * DragonFly callout_stop() is synchronous: kern_timeout.c:1091 ->
 * _callout_cancel_or_stop(..., sync=1) ssleep's on "costp" (:920) until an
 * INPROG callback completes; a parked mxge_tick replica sleeps inside
 * lwkt_serialize_enter (lwkt_serialize.c:86 atomic_intr_cond_enter ->
 * lwkt_serialize_sleep) until the stopper releases the serializer.
 * AB-BA: permanent wedge of the detaching thread AND the softclock thread
 * of the callback's CPU (all callouts on that CPU stop firing).
 *
 * Two INDEPENDENT callouts (mxge never re-arms after terminate; neither do
 * we) and three sysctls:
 *   kern.mxgereplica.armfix =1 arm the fix-test callout
 *   kern.mxgereplica.fix    =1 mxge_detach WITH the recommended fix:
 *                             callout_stop_async() under the serializer +
 *                             callout_terminate() after release -> must
 *                             return, pending must be 0
 *   kern.mxgereplica.stop   =1 mxge_detach EXACTLY as if_mxge.c:4463-4468:
 *                             sync callout_stop() under the serializer ->
 *                             if the 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;	/* == sc->main_serialize */
static struct callout fix_c;		/* fix-test instance of co_hdl */
static struct callout stop_c;		/* AB-BA-test instance of co_hdl */

static volatile int fix_started, fix_ran;
static volatile int stop_started, stop_ran;

/* mxge_tick replica (if_mxge.c:3712-3750) */
static void
tick_fix(void *arg)
{
	fix_started++;			/* callback entered */

	/* if_mxge.c:3720 - BLOCKING acquire, first act of the callback */
	lwkt_serialize_enter(&ser);

	fix_ran++;
	/* if_mxge.c:3747 - self re-arm INSIDE the serializer */
	callout_reset(&fix_c, hz / 4, tick_fix, NULL);

	lwkt_serialize_exit(&ser);
}

static void
tick_stop(void *arg)
{
	stop_started++;

	/* if_mxge.c:3720 - BLOCKING acquire */
	lwkt_serialize_enter(&ser);

	stop_ran++;
	/* if_mxge.c:3747 - self re-arm INSIDE the serializer */
	callout_reset(&stop_c, hz / 4, tick_stop, NULL);

	lwkt_serialize_exit(&ser);
}

static int
armfix_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) {
		fix_started = fix_ran = 0;
		callout_reset(&fix_c, 1, tick_fix, NULL);
		kprintf("mxge_replica: armed FIX-test tick (hz/4)\n");
	}
	return 0;
}

static int
armstop_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) {
		stop_started = stop_ran = 0;
		callout_reset(&stop_c, 1, tick_stop, NULL);
		kprintf("mxge_replica: armed STOP-test tick (hz/4)\n");
	}
	return 0;
}

/*
 * Wait until a callback is INPROG and parked on our serializer.
 *
 * NB: deliberately a BUSY-WAIT.  A tsleep()-timeout based poll would rely
 * on the callout subsystem itself (_callout_setup_quick) - but once the
 * tick callback parks the softclock thread of the test CPU ('slize'),
 * tsleep timeouts serviced by that same softclock never fire and the
 * observer itself freezes (observed live: softclock N in B2 'slize' while
 * the sysctl waiter sat in D2 'mxgw' forever).
 */
static void
wait_parked(volatile int *ran, volatile int *started)
{
	long spins = 0;

	while (*ran == *started) {
		cpu_ccfence();
		if (++spins > 500000000L) {
			kprintf("mxge_replica: wait_parked TIMEOUT "
			    "(started=%d ran=%d)\n", *started, *ran);
			break;
		}
	}
}

static int
fix_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;

	/* mxge_detach WITH the fix: stop_async under, terminate outside */
	lwkt_serialize_enter(&ser);			/* :4463 */
	wait_parked(&fix_ran, &fix_started);
	kprintf("mxge_replica[fix]: tick parked (started=%d ran=%d); "
	    "callout_stop_async + release + callout_terminate\n",
	    fix_started, fix_ran);
	callout_stop_async(&fix_c);			/* fixed :4468 */
	lwkt_serialize_exit(&ser);			/* release  */
	callout_terminate(&fix_c);			/* :4474 */
	kprintf("mxge_replica[fix]: returned; pending=%d ran=%d "
	    "(0 == cleanly stopped, NO deadlock)\n",
	    callout_pending(&fix_c), fix_ran);
	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;

	/* mxge_detach EXACTLY as shipped (if_mxge.c:4463-4468) */
	lwkt_serialize_enter(&ser);			/* :4463 */
	wait_parked(&stop_ran, &stop_started);
	kprintf("mxge_replica[stop]: tick BLOCKED on our serializer "
	    "(started=%d ran=%d) - entering callout_stop() :4468\n",
	    stop_started, stop_ran);
	kprintf("mxge_replica[stop]: IF THIS IS THE LAST mxge_replica LINE, "
	    "THE mxge_detach AB-BA DEADLOCK IS PROVEN\n");
	callout_stop(&stop_c);				/* :4468 - sync */
	kprintf("mxge_replica[stop]: callout_stop returned (NO deadlock)\n");
	lwkt_serialize_exit(&ser);
	return 0;
}

static SYSCTL_NODE(_kern, OID_AUTO, mxgereplica, CTLFLAG_RW, 0,
    "mxge callout teardown replica");
SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, armfix, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    armfix_sysctl, "I", "arm fix-test callout");
SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, fix, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    fix_sysctl, "I", "detach shape WITH recommended fix");
SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, arm, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    armstop_sysctl, "I", "arm AB-BA-test callout");
SYSCTL_PROC(_kern_mxgereplica, OID_AUTO, stop, CTLTYPE_INT | CTLFLAG_RW, 0, 0,
    stop_sysctl, "I", "mxge_detach shape as shipped (AB-BA)");

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

static moduledata_t mxge_replica_mod = {
	"mxge_callout_replica",
	mxge_replica_load,
	NULL
};
DECLARE_MODULE(mxge_callout_replica, mxge_replica_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);