While run_task() iterates through the host command queue during a scan, the interface may get reset via run_stop() and run_init() when switching bands (2GHz -> 5GHz) in run_media_change().
The list state gets corrupted because run_init() resets the list counters while run_task() is still iterating the list. run_task() will now attempt to call garbage or NULL callback pointers. run_newstate_cb SCAN -> INIT run_newstate_cb SCAN -> SCAN run_newstate_cb SCAN -> SCAN run_task ring->next=7 run_task ring->cur=0 run_task ring->queued=-7 <-- negative nonesense run_task cmd=0xffff800000651110 run_task cmd->cb=0x0 The kernel now explodes trying to call cmd->cb. This can be reproduced by running 'ifconfig run0 mediaopt monitor up'. Only happens with dual-band devices. run_init() can't know whether one or more run_task() tasks are scheduled. So I cannot think of a better fix but make run_task() abort itself in this situation, muck like iwm(4) does. Also complain about host command ring overflow in dmesg. ok? Index: if_run.c =================================================================== RCS file: /cvs/src/sys/dev/usb/if_run.c,v retrieving revision 1.109 diff -u -p -r1.109 if_run.c --- if_run.c 12 Jun 2015 15:47:31 -0000 1.109 +++ if_run.c 11 Jul 2015 07:51:29 -0000 @@ -1720,19 +1720,23 @@ run_task(void *arg) struct run_softc *sc = arg; struct run_host_cmd_ring *ring = &sc->cmdq; struct run_host_cmd *cmd; - int s; + int s, generation; if (usbd_is_dying(sc->sc_udev)) return; /* process host commands */ s = splusb(); + generation = ring->generation; while (ring->next != ring->cur) { cmd = &ring->cmd[ring->next]; splx(s); /* callback */ cmd->cb(sc, cmd->data); s = splusb(); + /* Abort this task if interface was stopped meanwhile. */ + if (generation != ring->generation) + break; ring->queued--; ring->next = (ring->next + 1) % RUN_HOST_CMD_RING_COUNT; } @@ -1751,6 +1755,11 @@ run_do_async(struct run_softc *sc, void return; s = splusb(); + if (ring->queued == RUN_HOST_CMD_RING_COUNT) { + printf("%s: host command ring overflow\n", sc->sc_dev.dv_xname); + splx(s); + return; + } cmd = &ring->cmd[ring->cur]; cmd->cb = cb; KASSERT(len <= sizeof (cmd->data)); @@ -4739,6 +4748,10 @@ run_stop(struct ifnet *ifp, int disable) timeout_del(&sc->calib_to); s = splusb(); + + /* Abort run_task thread. */ + sc->cmdq.generation++; + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); /* wait for all queued asynchronous commands to complete */ usb_wait_task(sc->sc_udev, &sc->sc_task); Index: if_runvar.h =================================================================== RCS file: /cvs/src/sys/dev/usb/if_runvar.h,v retrieving revision 1.10 diff -u -p -r1.10 if_runvar.h --- if_runvar.h 24 May 2014 10:10:17 -0000 1.10 +++ if_runvar.h 11 Jul 2015 07:26:41 -0000 @@ -123,6 +123,7 @@ struct run_host_cmd_ring { int cur; int next; int queued; + int generation; }; struct run_node {