The attached patch implements usb_rem_task_wait and uses it in various
drivers' detach routines so that they have a chance of guaranteeing
not to leave pending tasks floating around using memory after free.
Also changes various callout_stops near usb_rem_task to callout_halt
if they seemed relevant in 30sec of analysis.

Compile-tested only so far.  Run-testers invited.  Comments?
diff --git a/sys/dev/usb/if_athn_usb.c b/sys/dev/usb/if_athn_usb.c
index 7861fc3f6dc9..8c889fb54953 100644
--- a/sys/dev/usb/if_athn_usb.c
+++ b/sys/dev/usb/if_athn_usb.c
@@ -335,7 +335,7 @@ athn_usb_attach(device_t parent, device_t self, void *aux)
        athn_usb_free_tx_cmd(usc);
        athn_usb_free_tx_msg(usc);
        athn_usb_close_pipes(usc);
-       usb_rem_task(usc->usc_udev, &usc->usc_task);
+       usb_rem_task_wait(usc->usc_udev, &usc->usc_task, USB_TASKQ_DRIVER);
 
        cv_destroy(&usc->usc_cmd_cv);
        cv_destroy(&usc->usc_msg_cv);
@@ -501,7 +501,7 @@ athn_usb_detach(device_t self, int flags)
 
        athn_usb_wait_async(usc);
 
-       usb_rem_task(usc->usc_udev, &usc->usc_task);
+       usb_rem_task_wait(usc->usc_udev, &usc->usc_task, USB_TASKQ_DRIVER);
 
        /* Abort Tx/Rx pipes. */
        athn_usb_abort_pipes(usc);
diff --git a/sys/dev/usb/if_atu.c b/sys/dev/usb/if_atu.c
index f526038a0037..87b369c4e0b8 100644
--- a/sys/dev/usb/if_atu.c
+++ b/sys/dev/usb/if_atu.c
@@ -2235,7 +2235,7 @@ atu_stop(struct ifnet *ifp, int disable)
        ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
        ifp->if_timer = 0;
 
-       usb_rem_task(sc->atu_udev, &sc->sc_task);
+       usb_rem_task_wait(sc->atu_udev, &sc->sc_task, USB_TASKQ_DRIVER);
        ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
 
        /* Stop transfers. */
diff --git a/sys/dev/usb/if_aue.c b/sys/dev/usb/if_aue.c
index 56f872141dec..309df518d005 100644
--- a/sys/dev/usb/if_aue.c
+++ b/sys/dev/usb/if_aue.c
@@ -886,13 +886,16 @@ aue_detach(device_t self, int flags)
                return 0;
        }
 
-       callout_stop(&sc->aue_stat_ch);
        /*
-        * Remove any pending tasks.  They cannot be executing because they run
-        * in the same thread as detach.
+        * XXX Halting callout guarantees no more tick tasks.  What
+        * guarantees no more stop tasks?  What guarantees no more
+        * calls to aue_send?  Don't we need to wait for if_detach or
+        * something?  Should we set sc->aue_dying here?  Is device
+        * deactivation guaranteed to have already happened?
         */
-       usb_rem_task(sc->aue_udev, &sc->aue_tick_task);
-       usb_rem_task(sc->aue_udev, &sc->aue_stop_task);
+       callout_halt(&sc->aue_stat_ch, NULL);
+       usb_rem_task_wait(sc->aue_udev, &sc->aue_tick_task, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->aue_udev, &sc->aue_stop_task, USB_TASKQ_DRIVER);
 
        sc->aue_closing = 1;
        cv_signal(&sc->aue_domc);
diff --git a/sys/dev/usb/if_axe.c b/sys/dev/usb/if_axe.c
index 35ee22469064..a8b5c0b94875 100644
--- a/sys/dev/usb/if_axe.c
+++ b/sys/dev/usb/if_axe.c
@@ -1111,11 +1111,8 @@ axe_detach(device_t self, int flags)
        if (sc->axe_ep[AXE_ENDPT_INTR] != NULL)
                usbd_abort_pipe(sc->axe_ep[AXE_ENDPT_INTR]);
 
-       /*
-        * Remove any pending tasks.  They cannot be executing because they run
-        * in the same thread as detach.
-        */
-       usb_rem_task(sc->axe_udev, &sc->axe_tick_task);
+       callout_halt(&sc->axe_stat_ch, NULL);
+       usb_rem_task_wait(sc->axe_udev, &sc->axe_tick_task, USB_TASKQ_DRIVER);
 
        s = splusb();
 
diff --git a/sys/dev/usb/if_axen.c b/sys/dev/usb/if_axen.c
index 571d5cdce7d8..379b85ed2230 100644
--- a/sys/dev/usb/if_axen.c
+++ b/sys/dev/usb/if_axen.c
@@ -826,11 +826,9 @@ axen_detach(device_t self, int flags)
 
        sc->axen_dying = true;
 
-       /*
-        * Remove any pending tasks.  They cannot be executing because they run
-        * in the same thread as detach.
-        */
-       usb_rem_task(sc->axen_udev, &sc->axen_tick_task);
+       callout_halt(&sc->axen_stat_ch, NULL);
+       usb_rem_task_wait(sc->axen_udev, &sc->axen_tick_task,
+           USB_TASKQ_DRIVER);
 
        s = splusb();
 
diff --git a/sys/dev/usb/if_cue.c b/sys/dev/usb/if_cue.c
index 7a91adb6a121..3eb4856329d0 100644
--- a/sys/dev/usb/if_cue.c
+++ b/sys/dev/usb/if_cue.c
@@ -571,13 +571,16 @@ cue_detach(device_t self, int flags)
 
        DPRINTFN(2,("%s: %s: enter\n", device_xname(sc->cue_dev), __func__));
 
-       callout_stop(&sc->cue_stat_ch);
        /*
-        * Remove any pending task.  It cannot be executing because it run
-        * in the same thread as detach.
+        * XXX Halting callout guarantees no more tick tasks.  What
+        * guarantees no more stop tasks?  What guarantees no more
+        * calls to cue_send?  Don't we need to wait for if_detach or
+        * something?  Should we set sc->cue_dying here?  Is device
+        * deactivation guaranteed to have already happened?
         */
-       usb_rem_task(sc->cue_udev, &sc->cue_tick_task);
-       usb_rem_task(sc->cue_udev, &sc->cue_stop_task);
+       callout_halt(&sc->cue_stat_ch, NULL);
+       usb_rem_task_wait(sc->cue_udev, &sc->cue_tick_task, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->cue_udev, &sc->cue_stop_task, USB_TASKQ_DRIVER);
 
        if (!sc->cue_attached) {
                /* Detached before attached finished, so just bail out. */
diff --git a/sys/dev/usb/if_otus.c b/sys/dev/usb/if_otus.c
index 809d3a3e08c1..0876d4d641d1 100644
--- a/sys/dev/usb/if_otus.c
+++ b/sys/dev/usb/if_otus.c
@@ -701,7 +701,7 @@ otus_detach(device_t self, int flags)
        if (ifp != NULL)        /* Failed to attach properly */
                otus_stop(ifp);
 
-       usb_rem_task(sc->sc_udev, &sc->sc_task);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
        callout_destroy(&sc->sc_scan_to);
        callout_destroy(&sc->sc_calib_to);
 
diff --git a/sys/dev/usb/if_rum.c b/sys/dev/usb/if_rum.c
index f38f9b914c42..e14483f588aa 100644
--- a/sys/dev/usb/if_rum.c
+++ b/sys/dev/usb/if_rum.c
@@ -496,9 +496,9 @@ rum_detach(device_t self, int flags)
        s = splusb();
 
        rum_stop(ifp, 1);
-       usb_rem_task(sc->sc_udev, &sc->sc_task);
-       callout_stop(&sc->sc_scan_ch);
-       callout_stop(&sc->sc_amrr_ch);
+       callout_halt(&sc->sc_scan_ch, NULL);
+       callout_halt(&sc->sc_amrr_ch, NULL);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
 
        bpf_detach(ifp);
        ieee80211_ifdetach(ic); /* free all nodes */
@@ -735,6 +735,11 @@ rum_newstate(struct ieee80211com *ic, enum ieee80211_state 
nstate, int arg)
 {
        struct rum_softc *sc = ic->ic_ifp->if_softc;
 
+       /*
+        * XXXSMP: This does not wait for the task, if it is in flight,
+        * to complete.  If this code works at all, it must rely on the
+        * kernel lock to serialize with the USB task thread.
+        */
        usb_rem_task(sc->sc_udev, &sc->sc_task);
        callout_stop(&sc->sc_scan_ch);
        callout_stop(&sc->sc_amrr_ch);
diff --git a/sys/dev/usb/if_run.c b/sys/dev/usb/if_run.c
index 2207549804ab..abddf1b4e505 100644
--- a/sys/dev/usb/if_run.c
+++ b/sys/dev/usb/if_run.c
@@ -759,8 +759,10 @@ run_detach(device_t self, int flags)
        sc->sc_flags |= RUN_DETACHING;
 
        if (ifp->if_flags & IFF_RUNNING) {
-               usb_rem_task(sc->sc_udev, &sc->sc_task);
                run_stop(ifp, 0);
+               callout_halt(&sc->scan_to, NULL);
+               callout_halt(&sc->calib_to, NULL);
+               usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
        }
 
        ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
diff --git a/sys/dev/usb/if_smsc.c b/sys/dev/usb/if_smsc.c
index 43c9b3e4a5a0..bb582ab822b3 100644
--- a/sys/dev/usb/if_smsc.c
+++ b/sys/dev/usb/if_smsc.c
@@ -1140,7 +1140,7 @@ smsc_detach(device_t self, int flags)
        struct ifnet *ifp = &sc->sc_ec.ec_if;
        int s;
 
-       callout_stop(&sc->sc_stat_ch);
+       callout_halt(&sc->sc_stat_ch, NULL);
 
        if (sc->sc_ep[SMSC_ENDPT_TX] != NULL)
                usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_TX]);
@@ -1149,12 +1149,8 @@ smsc_detach(device_t self, int flags)
        if (sc->sc_ep[SMSC_ENDPT_INTR] != NULL)
                usbd_abort_pipe(sc->sc_ep[SMSC_ENDPT_INTR]);
 
-       /*
-        * Remove any pending tasks.  They cannot be executing because they run
-        * in the same thread as detach.
-        */
-       usb_rem_task(sc->sc_udev, &sc->sc_tick_task);
-       usb_rem_task(sc->sc_udev, &sc->sc_stop_task);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_tick_task, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_stop_task, USB_TASKQ_DRIVER);
 
        s = splusb();
 
diff --git a/sys/dev/usb/if_udav.c b/sys/dev/usb/if_udav.c
index 84afa2502960..175301d3ab08 100644
--- a/sys/dev/usb/if_udav.c
+++ b/sys/dev/usb/if_udav.c
@@ -352,11 +352,11 @@ udav_detach(device_t self, int flags)
        if (!sc->sc_attached)
                return 0;
 
-       callout_stop(&sc->sc_stat_ch);
+       callout_halt(&sc->sc_stat_ch, NULL);
 
        /* Remove any pending tasks */
-       usb_rem_task(sc->sc_udev, &sc->sc_tick_task);
-       usb_rem_task(sc->sc_udev, &sc->sc_stop_task);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_tick_task, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_stop_task, USB_TASKQ_DRIVER);
 
        s = splusb();
 
diff --git a/sys/dev/usb/if_upgt.c b/sys/dev/usb/if_upgt.c
index 21da0680be09..dc92c446a7b3 100644
--- a/sys/dev/usb/if_upgt.c
+++ b/sys/dev/usb/if_upgt.c
@@ -504,8 +504,10 @@ upgt_detach(device_t self, int flags)
                upgt_stop(sc);
 
        /* remove tasks and timeouts */
-       usb_rem_task(sc->sc_udev, &sc->sc_task_newstate);
-       usb_rem_task(sc->sc_udev, &sc->sc_task_tx);
+       callout_halt(&sc->scan_to, NULL);
+       callout_halt(&sc->led_to, NULL);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task_newstate, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task_tx, USB_TASKQ_DRIVER);
        callout_destroy(&sc->scan_to);
        callout_destroy(&sc->led_to);
 
@@ -1346,6 +1348,11 @@ upgt_newstate(struct ieee80211com *ic, enum 
ieee80211_state nstate, int arg)
 {
        struct upgt_softc *sc = ic->ic_ifp->if_softc;
 
+       /*
+        * XXXSMP: This does not wait for the task, if it is in flight,
+        * to complete.  If this code works at all, it must rely on the
+        * kernel lock to serialize with the USB task thread.
+        */
        usb_rem_task(sc->sc_udev, &sc->sc_task_newstate);
        callout_stop(&sc->scan_to);
 
diff --git a/sys/dev/usb/if_ural.c b/sys/dev/usb/if_ural.c
index 9ea1dc1b6e0c..93f42769c329 100644
--- a/sys/dev/usb/if_ural.c
+++ b/sys/dev/usb/if_ural.c
@@ -534,9 +534,9 @@ ural_detach(device_t self, int flags)
        s = splusb();
 
        ural_stop(ifp, 1);
-       usb_rem_task(sc->sc_udev, &sc->sc_task);
-       callout_stop(&sc->sc_scan_ch);
-       callout_stop(&sc->sc_amrr_ch);
+       callout_halt(&sc->sc_scan_ch, NULL);
+       callout_halt(&sc->sc_amrr_ch, NULL);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
 
        bpf_detach(ifp);
        ieee80211_ifdetach(ic);
@@ -784,6 +784,11 @@ ural_newstate(struct ieee80211com *ic, enum 
ieee80211_state nstate,
 {
        struct ural_softc *sc = ic->ic_ifp->if_softc;
 
+       /*
+        * XXXSMP: This does not wait for the task, if it is in flight,
+        * to complete.  If this code works at all, it must rely on the
+        * kernel lock to serialize with the USB task thread.
+        */
        usb_rem_task(sc->sc_udev, &sc->sc_task);
        callout_stop(&sc->sc_scan_ch);
        callout_stop(&sc->sc_amrr_ch);
diff --git a/sys/dev/usb/if_url.c b/sys/dev/usb/if_url.c
index c56b1278de62..15a7fa775a12 100644
--- a/sys/dev/usb/if_url.c
+++ b/sys/dev/usb/if_url.c
@@ -346,11 +346,18 @@ url_detach(device_t self, int flags)
        if (!sc->sc_attached)
                return 0;
 
-       callout_stop(&sc->sc_stat_ch);
+       /*
+        * XXX Halting callout guarantees no more tick tasks.  What
+        * guarantees no more stop tasks?  What guarantees no more
+        * calls to url_send?  Don't we need to wait for if_detach or
+        * something?  Should set sc->sc_dying here?  Is device
+        * deactivation guaranteed to have already happened?
+        */
+       callout_halt(&sc->sc_stat_ch, NULL);
 
        /* Remove any pending tasks */
-       usb_rem_task(sc->sc_udev, &sc->sc_tick_task);
-       usb_rem_task(sc->sc_udev, &sc->sc_stop_task);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_tick_task, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_stop_task, USB_TASKQ_DRIVER);
 
        s = splusb();
 
diff --git a/sys/dev/usb/if_urtw.c b/sys/dev/usb/if_urtw.c
index f8c2b51c7978..8572b25abf54 100644
--- a/sys/dev/usb/if_urtw.c
+++ b/sys/dev/usb/if_urtw.c
@@ -774,11 +774,13 @@ urtw_detach(device_t self, int flags)
 
        sc->sc_dying = true;
 
+       callout_halt(&sc->scan_to, NULL);
+       callout_halt(&sc->sc_led_ch, NULL);
        callout_destroy(&sc->scan_to);
        callout_destroy(&sc->sc_led_ch);
 
-       usb_rem_task(sc->sc_udev, &sc->sc_task);
-       usb_rem_task(sc->sc_udev, &sc->sc_ledtask);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_ledtask, USB_TASKQ_DRIVER);
 
        if (ifp->if_softc != NULL) {
                bpf_detach(ifp);
@@ -1042,6 +1044,11 @@ urtw_newstate(struct ieee80211com *ic, enum 
ieee80211_state nstate, int arg)
 {
        struct urtw_softc *sc = ic->ic_ifp->if_softc;
 
+       /*
+        * XXXSMP: This does not wait for the task, if it is in flight,
+        * to complete.  If this code works at all, it must rely on the
+        * kernel lock to serialize with the USB task thread.
+        */
        usb_rem_task(sc->sc_udev, &sc->sc_task);
        callout_stop(&sc->scan_to);
 
diff --git a/sys/dev/usb/if_urtwn.c b/sys/dev/usb/if_urtwn.c
index 2a104787a9a0..6f55a881841d 100644
--- a/sys/dev/usb/if_urtwn.c
+++ b/sys/dev/usb/if_urtwn.c
@@ -539,12 +539,12 @@ urtwn_detach(device_t self, int flags)
 
        sc->sc_dying = 1;
 
-       callout_stop(&sc->sc_scan_to);
-       callout_stop(&sc->sc_calib_to);
+       callout_halt(&sc->sc_scan_to, NULL);
+       callout_halt(&sc->sc_calib_to, NULL);
 
        if (ISSET(sc->sc_flags, URTWN_FLAG_ATTACHED)) {
-               usb_rem_task(sc->sc_udev, &sc->sc_task);
                urtwn_stop(ifp, 0);
+               usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
 
                ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
                bpf_detach(ifp);
diff --git a/sys/dev/usb/if_zyd.c b/sys/dev/usb/if_zyd.c
index e45a50f71d76..04123de313b8 100644
--- a/sys/dev/usb/if_zyd.c
+++ b/sys/dev/usb/if_zyd.c
@@ -466,9 +466,9 @@ zyd_detach(device_t self, int flags)
        s = splusb();
 
        zyd_stop(ifp, 1);
-       usb_rem_task(sc->sc_udev, &sc->sc_task);
-       callout_stop(&sc->sc_scan_ch);
-       callout_stop(&sc->sc_amrr_ch);
+       callout_halt(&sc->sc_scan_ch, NULL);
+       callout_halt(&sc->sc_amrr_ch, NULL);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_task, USB_TASKQ_DRIVER);
 
        /* Abort, etc. done by zyd_stop */
        zyd_close_pipes(sc);
@@ -761,6 +761,11 @@ zyd_newstate(struct ieee80211com *ic, enum ieee80211_state 
nstate, int arg)
        if (!sc->attached)
                return ENXIO;
 
+       /*
+        * XXXSMP: This does not wait for the task, if it is in flight,
+        * to complete.  If this code works at all, it must rely on the
+        * kernel lock to serialize with the USB task thread.
+        */
        usb_rem_task(sc->sc_udev, &sc->sc_task);
        callout_stop(&sc->sc_scan_ch);
        callout_stop(&sc->sc_amrr_ch);
diff --git a/sys/dev/usb/uatp.c b/sys/dev/usb/uatp.c
index fcbbc6eac23e..cda7c802658c 100644
--- a/sys/dev/usb/uatp.c
+++ b/sys/dev/usb/uatp.c
@@ -1362,7 +1362,8 @@ geyser34_finalize(struct uatp_softc *sc)
 {
 
        DPRINTF(sc, UATP_DEBUG_MISC, ("finalizing\n"));
-       usb_rem_task(sc->sc_hdev.sc_parent->sc_udev, &sc->sc_reset_task);
+       usb_rem_task_wait(sc->sc_hdev.sc_parent->sc_udev, &sc->sc_reset_task,
+           USB_TASKQ_DRIVER);
 
        return 0;
 }
diff --git a/sys/dev/usb/umcs.c b/sys/dev/usb/umcs.c
index c4ad74c6e355..b8f20401189d 100644
--- a/sys/dev/usb/umcs.c
+++ b/sys/dev/usb/umcs.c
@@ -517,7 +517,7 @@ umcs7840_detach(device_t self, int flags)
                kmem_free(sc->sc_intr_buf, sc->sc_intr_buflen);
                sc->sc_intr_pipe = NULL;
        }
-       usb_rem_task(sc->sc_udev, &sc->sc_change_task);
+       usb_rem_task_wait(sc->sc_udev, &sc->sc_change_task, USB_TASKQ_DRIVER);
 
        /* detach children */
        for (i = 0; i < sc->sc_numports; i++) {
diff --git a/sys/dev/usb/usb.c b/sys/dev/usb/usb.c
index 5f691f8e76a4..dc4759ebb20c 100644
--- a/sys/dev/usb/usb.c
+++ b/sys/dev/usb/usb.c
@@ -146,6 +146,7 @@ struct usb_taskq {
        kcondvar_t cv;
        struct lwp *task_thread_lwp;
        const char *name;
+       struct usb_task *current_task;
 };
 
 static struct usb_taskq usb_taskq[USB_NUM_TASKQS];
@@ -294,6 +295,7 @@ usb_once_init(void)
                mutex_init(&taskq->lock, MUTEX_DEFAULT, IPL_USB);
                cv_init(&taskq->cv, "usbtsk");
                taskq->name = taskq_names[i];
+               taskq->current_task = NULL;
                if (kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL,
                    usb_task_thread, taskq, &taskq->task_thread_lwp,
                    "%s", taskq->name)) {
@@ -426,8 +428,14 @@ usb_add_task(struct usbd_device *dev, struct usb_task 
*task, int queue)
 }
 
 /*
- * XXX This does not wait for completion!  Most uses need such an
- * operation.  Urgh...
+ * usb_rem_task(dev, task)
+ *
+ *     If task is queued to run, remove it from the queue.
+ *
+ *     Caller is _not_ guaranteed that the task is not running when
+ *     this is done.
+ *
+ *     Never sleeps.
  */
 void
 usb_rem_task(struct usbd_device *dev, struct usb_task *task)
@@ -449,6 +457,85 @@ usb_rem_task(struct usbd_device *dev, struct usb_task 
*task)
        }
 }
 
+/*
+ * usb_taskq_wait(taskq, task)
+ *
+ *     Wait for taskq to finish executing task, if it is executing
+ *     task.  Caller must hold the taskq lock.
+ */
+static void
+usb_taskq_wait(struct usb_taskq *taskq, struct usb_task *task)
+{
+
+       KASSERT(mutex_owned(&taskq->lock));
+
+       while (taskq->current_task == task)
+               cv_wait(&taskq->cv, &taskq->lock);
+
+       KASSERT(taskq->current_task != task);
+}
+
+/*
+ * usb_rem_task_wait(dev, task, queue)
+ *
+ *     If task is scheduled to run, remove it from the queue.  If it
+ *     may have already begun to run, wait for it to complete.
+ *
+ *     Caller MUST guarantee that task will not be scheduled on a
+ *     _different_ queue, at least until after this returns.
+ *
+ *     If caller guarantees that task will not be scheduled on the
+ *     same queue before this returns, then caller is guaranteed that
+ *     the task is not running at all when this returns.
+ *
+ *     May sleep.
+ */
+void
+usb_rem_task_wait(struct usbd_device *dev, struct usb_task *task, int queue)
+{
+       struct usb_taskq *taskq;
+       int queue1;
+
+       USBHIST_FUNC(); USBHIST_CALLED(usbdebug);
+       ASSERT_SLEEPABLE();
+       KASSERT(0 <= queue);
+       KASSERT(queue < USB_NUM_TASKQS);
+
+       taskq = &usb_taskq[queue];
+
+       if ((queue1 = task->queue) == USB_NUM_TASKQS) {
+               /*
+                * It is not on the queue, but it may have already run.
+                * Wait for it.
+                */
+               mutex_enter(&taskq->lock);
+               usb_taskq_wait(taskq, task);
+               mutex_exit(&taskq->lock);
+       } else {
+               /*
+                * It may be on the queue (and not another one), but
+                * the state may have changed by now because we don't
+                * have the queue locked.  Lock and reload.
+                */
+               KASSERTMSG(queue1 == queue,
+                   "task %p on q%d expected on q%d", task, queue1, queue);
+               mutex_enter(&taskq->lock);
+               queue1 = task->queue;
+               if (queue1 == queue) {
+                       /* Still queued, not run.  Just remove it.  */
+                       TAILQ_REMOVE(&taskq->tasks, task, next);
+                       task->queue = USB_NUM_TASKQS;
+               } else {
+                       /* Already ran.  Wait for it.  */
+                       KASSERTMSG(queue1 == USB_NUM_TASKQS,
+                           "task %p on q%d expected on q%d",
+                           task, queue1, queue);
+                       usb_taskq_wait(taskq, task);
+               }
+               mutex_exit(&taskq->lock);
+       }
+}
+
 void
 usb_event_thread(void *arg)
 {
@@ -517,6 +604,7 @@ usb_task_thread(void *arg)
                        mpsafe = ISSET(task->flags, USB_TASKQ_MPSAFE);
                        TAILQ_REMOVE(&taskq->tasks, task, next);
                        task->queue = USB_NUM_TASKQS;
+                       taskq->current_task = task;
                        mutex_exit(&taskq->lock);
 
                        if (!mpsafe)
@@ -527,6 +615,10 @@ usb_task_thread(void *arg)
                                KERNEL_UNLOCK_ONE(curlwp);
 
                        mutex_enter(&taskq->lock);
+                       KASSERTMSG(taskq->current_task == task,
+                           "somebody scribbled on usb taskq %p", taskq);
+                       taskq->current_task = NULL;
+                       cv_broadcast(&taskq->cv);
                }
        }
        mutex_exit(&taskq->lock);
diff --git a/sys/dev/usb/usb_subr.c b/sys/dev/usb/usb_subr.c
index 4115f9932ab0..af41f6558100 100644
--- a/sys/dev/usb/usb_subr.c
+++ b/sys/dev/usb/usb_subr.c
@@ -823,7 +823,7 @@ usbd_kill_pipe(struct usbd_pipe *pipe)
        usbd_lock_pipe(pipe);
        pipe->up_methods->upm_close(pipe);
        usbd_unlock_pipe(pipe);
-       usb_rem_task(pipe->up_dev, &pipe->up_async_task);
+       usb_rem_task_wait(pipe->up_dev, &pipe->up_async_task, USB_TASKQ_DRIVER);
        pipe->up_endpoint->ue_refcnt--;
        kmem_free(pipe, pipe->up_dev->ud_bus->ub_pipesize);
 }
diff --git a/sys/dev/usb/usbdi.h b/sys/dev/usb/usbdi.h
index 911d3f96a2c5..ae88d2c612f5 100644
--- a/sys/dev/usb/usbdi.h
+++ b/sys/dev/usb/usbdi.h
@@ -219,6 +219,7 @@ struct usb_task {
 
 void usb_add_task(struct usbd_device *, struct usb_task *, int);
 void usb_rem_task(struct usbd_device *, struct usb_task *);
+void usb_rem_task_wait(struct usbd_device *, struct usb_task *, int);
 #define usb_init_task(t, f, a, fl) ((t)->fun = (f), (t)->arg = (a), (t)->queue 
= USB_NUM_TASKQS, (t)->flags = (fl))
 
 struct usb_devno {

Reply via email to