DF-4275 / df4275_rep.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | /* * DF-4275 protocol replica โ the epic_stop()/epic_stats_update() AB-BA * deadlock in sys/dev/netif/tx/if_tx.c (DF-3715 family instance). * * Replicated protocol (identical lock discipline to the driver): * * epic_stats_update() (if_tx.c:796) epic_ifioctl()/epic_ifwatchdog() * ----------------------------------- -------------------------------- * lwkt_serialize_enter(if_serializer); lwkt_serialize_enter(if_serializer); * mii_tick(mii); epic_stop(sc); (if_tx.c:1337) * callout_reset(...) callout_stop(&sc->tx_stat_timer); * lwkt_serialize_exit(); * * In this kernel callout_stop() is SYNCHRONOUS: * sys/kern/kern_timeout.c:1091 -> _callout_cancel_or_stop(cc, CALLOUT_STOP, 1) * -> ssleep(c, &c->spin, 0, "costp", 0) waiting for CALLOUT_INPROG to clear. * * If the tick callback has been dequeued by softclock (CALLOUT_INPROG set at * kern_timeout.c:567) and is blocked in lwkt_serialize_enter() on the very * serializer the stopping thread holds, neither thread can ever make * progress: the stopper sleeps in 'costp' waiting for the callback, the * callback waits for the serializer. * * if_tx.c entry points that hold ifp->if_serializer across * epic_stop() -> callout_stop(): * - epic_ifioctl SIOCSIFMTU (if_tx.c:360-375: epic_stop+epic_init) * - epic_ifioctl SIOCSIFFLAGS down (if_tx.c:389-391) * - epic_ifwatchdog (if_tx.c:764-790: epic_stop+epic_init) * - epic_intr PCI-fatal (if_tx.c:691-705) * - epic_detach (if_tx.c:300-304) * * Because if_tx.c uses plain callout_init() (if_tx.c:200, non-MPSAFE), the * wedged softclock thread also holds the MP lock. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/kthread.h> #include <sys/proc.h> #include <sys/serialize.h> #include <sys/callout.h> #include <sys/thread.h> #include <sys/thread2.h> #include <sys/mplock2.h> static struct lwkt_serialize ser; /* ifp->if_serializer analog */ static struct callout timer; /* sc->tx_stat_timer analog */ static volatile int tick_inprog; /* callback dequeued + parked */ static struct thread *stopper_td; static volatile int done; /* ---- epic_stats_update() analog (softclock context) ---- */ static void tick(void *arg) { tick_inprog = 1; /* INPROG + about to block */ kprintf("df4275: tick() dequeued; entering serializer " "(blocks: stopper holds it)\n"); lwkt_serialize_enter(&ser); /* == epic_stats_update:803 */ kprintf("df4275: tick() ACQUIRED serializer (DEADLOCK DID NOT " "HAPPEN -- unexpected)\n"); lwkt_serialize_exit(&ser); } /* ---- the epic_stop()-under-serializer analog ---- */ static void stopper(void *arg) { get_mplock(); stopper_td = curthread; /* arm the callout, then take the serializer โ exactly what * epic_ifioctl() does before calling epic_stop(). */ callout_reset(&timer, 1, tick, NULL); lwkt_serialize_enter(&ser); /* Wait until softclock dequeued the callback and it is parked on the * serializer (in the real driver this is epic_stats_update blocked on * ifp->if_serializer while an ioctl/watchdog holds it). NB: yield-wait, * not a timeout sleep โ the tsleep timeout would itself depend on the * softclock wheel that this deadlock wedges โ and not a busy DELAY, * which would starve softclock on this CPU. */ while (!tick_inprog) { if (tick_inprog) break; lwkt_yield(); } kprintf("df4275: stopper HOLDS serializer; tick() INPROG+blocked; " "calling callout_stop() [epic_stop analog] ...\n"); kprintf("df4275: expecting permanent 'costp' sleep -- if this returns," " no deadlock\n"); callout_stop(&timer); /* == epic_stop:1341 */ kprintf("df4275: callout_stop RETURNED (no deadlock)\n"); lwkt_serialize_exit(&ser); done = 1; wakeup(&done); rel_mplock(); kthread_exit(); } /* ---- reporter: proves the stopper is parked in 'costp' ---- */ static void reporter(void *arg) { int i; get_mplock(); for (i = 0; i < 5 && !done; i++) { tsleep(&done, 0, "df4275r", hz); kprintf("df4275: stopper_td=%p wchan=%p wmesg=\"%s\" " "(tick_inprog=%d)\n", stopper_td, stopper_td->td_wchan, stopper_td->td_wmesg ? stopper_td->td_wmesg : "(null)", tick_inprog); } if (done) kprintf("df4275: RESULT: no deadlock\n"); else kprintf("df4275: RESULT: DEADLOCK REPRODUCED -- stopper " "permanently asleep in \"%s\" while tick() is blocked on " "the serializer the stopper holds\n", stopper_td->td_wmesg ? stopper_td->td_wmesg : "?"); rel_mplock(); kthread_exit(); } static int df4275_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: lwkt_serialize_init(&ser); callout_init(&timer); /* non-MPSAFE, same as if_tx.c:200 */ kthread_create(stopper, NULL, NULL, "df4275stop"); kthread_create(reporter, NULL, NULL, "df4275rep"); kprintf("df4275: replica armed\n"); break; default: break; } return (0); } static moduledata_t df4275rep_mod = { "df4275rep", df4275_load, NULL }; DECLARE_MODULE(df4275rep, df4275rep_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(df4275rep, 1); |