On Thu, Jun 18, 2015 at 04:23:43PM +0200, Stefan Sperling wrote:
> The net80211 stack assumes drivers will switch IEEE80211_S_* states in
> interrupt context. iwm(4) does not follow this rule. Since it insists on 
> responses from firmware commands to look for success or failure and it
> uses tsleep() to wait for responses it cannot switch state in interrupt
> context. So currently, the entire state machine is deferred to process
> context (big hammer solution) :-/
> 
> Complications arise in the suspend/resume path because of this, such as
> http://marc.info/?l=openbsd-tech&m=143438073018743&w=2 apart from several
> other such issues where a failure on part of the firmware to respond will
> deadlock the driver in an endless tsleep.
> 
> I would very much like iwm_newstate() to be interrupt safe and get rid of
> the pesky newstate_cb task which wraps it. It makes debugging and following
> the control flow difficult. And I hope the driver will be more stable overall.
> 
> There are two ways to approach this:
> 
>  - Simply don't care about answers from firmware when in interrupt
>    (note that this is what iwn(4) does)
> 
>  - Busy-wait for replies from the firmware when in interrupt

Here's a diff implementing a third approach, discussed with mpi@.

 - Keep the newstate transitions in a task thread, but only ever
   schedule one 80211 state transition at a time.

Requires a tweak for suspend/resume, which wants to run two state
transitions at resume time if the interface was up during suspend
(back to INIT, then INIT -> SCAN).

Please test this if you use iwm(4). It should make the driver more
reliable, e.g. when bringing the interface up which sometimes fails
because of... reasons.

Index: if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.45
diff -u -p -r1.45 if_iwm.c
--- if_iwm.c    15 Jun 2015 08:06:11 -0000      1.45
+++ if_iwm.c    19 Jul 2015 02:13:13 -0000
@@ -195,14 +195,6 @@ const struct iwm_rate {
 #define IWM_RIDX_IS_CCK(_i_) ((_i_) < IWM_RIDX_OFDM)
 #define IWM_RIDX_IS_OFDM(_i_) ((_i_) >= IWM_RIDX_OFDM)
 
-struct iwm_newstate_state {
-       struct task ns_wk;
-       struct ieee80211com *ns_ic;
-       enum ieee80211_state ns_nstate;
-       int ns_arg;
-       int ns_generation;
-};
-
 int    iwm_store_cscheme(struct iwm_softc *, uint8_t *, size_t);
 int    iwm_firmware_store_section(struct iwm_softc *, enum iwm_ucode_type,
                                        uint8_t *, size_t);
@@ -406,13 +398,13 @@ struct ieee80211_node *iwm_node_alloc(st
 void   iwm_calib_timeout(void *);
 void   iwm_setrates(struct iwm_node *);
 int    iwm_media_change(struct ifnet *);
-void   iwm_newstate_cb(void *);
+void   iwm_newstate_task(void *);
 int    iwm_newstate(struct ieee80211com *, enum ieee80211_state, int);
 void   iwm_endscan_cb(void *);
 int    iwm_init_hw(struct iwm_softc *);
 int    iwm_init(struct ifnet *);
 void   iwm_start(struct ifnet *);
-void   iwm_stop(struct ifnet *, int);
+void   iwm_stop(struct ifnet *);
 void   iwm_watchdog(struct ifnet *);
 int    iwm_ioctl(struct ifnet *, u_long, iwm_caddr_t);
 const char *iwm_desc_lookup(uint32_t);
@@ -427,7 +419,8 @@ void        iwm_attach_hook(iwm_hookarg_t);
 void   iwm_attach(struct device *, struct device *, void *);
 void   iwm_init_task(void *);
 int    iwm_activate(struct device *, int);
-void   iwm_wakeup(struct iwm_softc *);
+void   iwm_suspend(struct iwm_softc *);
+void   iwm_resume(struct iwm_softc *);
 
 #if NBPFILTER > 0
 void   iwm_radiotap_attach(struct iwm_softc *);
@@ -5252,38 +5245,25 @@ iwm_media_change(struct ifnet *ifp)
 
        if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
            (IFF_UP | IFF_RUNNING)) {
-               iwm_stop(ifp, 0);
+               iwm_stop(ifp);
                error = iwm_init(ifp);
        }
        return error;
 }
 
 void
-iwm_newstate_cb(void *wk)
+iwm_newstate_task(void *arg)
 {
-       struct iwm_newstate_state *iwmns = (void *)wk;
-       struct ieee80211com *ic = iwmns->ns_ic;
-       enum ieee80211_state nstate = iwmns->ns_nstate;
-       int generation = iwmns->ns_generation;
+       struct iwm_softc *sc = arg;
+       struct ieee80211com *ic = &sc->sc_ic;
+       struct iwm_newstate_task_arg *task_arg = &sc->sc_newstate_task_arg;
+       enum ieee80211_state nstate = task_arg->state;
        struct iwm_node *in;
-       int arg = iwmns->ns_arg;
-       struct ifnet *ifp = IC2IFP(ic);
-       struct iwm_softc *sc = ifp->if_softc;
        int error;
 
-       free(iwmns, M_DEVBUF, sizeof(*iwmns));
-
-       DPRINTF(("Prepare to switch state %d->%d\n", ic->ic_state, nstate));
-       if (sc->sc_generation != generation) {
-               DPRINTF(("newstate_cb: someone pulled the plug meanwhile\n"));
-               if (nstate == IEEE80211_S_INIT) {
-                       DPRINTF(("newstate_cb: nstate == IEEE80211_S_INIT: 
calling sc_newstate()\n"));
-                       sc->sc_newstate(ic, nstate, arg);
-               }
-               return;
-       }
-
-       DPRINTF(("switching state %d->%d\n", ic->ic_state, nstate));
+       DPRINTF(("%s: %s->%s\n", __func__,
+           ieee80211_state_name[ic->ic_state],
+           ieee80211_state_name[nstate]));
 
        if (ic->ic_state == IEEE80211_S_SCAN && nstate != ic->ic_state)
                iwm_led_blink_stop(sc);
@@ -5292,6 +5272,8 @@ iwm_newstate_cb(void *wk)
        if (ic->ic_state == IEEE80211_S_RUN && nstate != ic->ic_state) {
                iwm_mvm_disable_beacon_filter(sc, (void *)ic->ic_bss);
 
+               timeout_del(&sc->sc_calib_to);
+
                if (((in = (void *)ic->ic_bss) != NULL))
                        in->in_assoc = 0;
                iwm_release(sc, NULL);
@@ -5310,8 +5292,9 @@ iwm_newstate_cb(void *wk)
                if (nstate == IEEE80211_S_SCAN ||
                    nstate == IEEE80211_S_AUTH ||
                    nstate == IEEE80211_S_ASSOC) {
-                       DPRINTF(("Force transition to INIT; MGT=%d\n", arg));
-                       sc->sc_newstate(ic, IEEE80211_S_INIT, arg);
+                       DPRINTF(("Force transition to INIT; MGT=%d\n",
+                           task_arg->arg));
+                       sc->sc_newstate(ic, IEEE80211_S_INIT, task_arg->arg);
                        DPRINTF(("Going INIT->SCAN\n"));
                        nstate = IEEE80211_S_SCAN;
                }
@@ -5320,6 +5303,10 @@ iwm_newstate_cb(void *wk)
        switch (nstate) {
        case IEEE80211_S_INIT:
                sc->sc_scanband = 0;
+               sc->sc_auth_prot = 0;
+               ic->ic_scan_lock = IEEE80211_SCAN_UNLOCKED;
+               sc->sc_flags |= IWM_FLAG_STOPPED;
+               iwm_stop_device(sc);
                break;
 
        case IEEE80211_S_SCAN:
@@ -5379,31 +5366,27 @@ iwm_newstate_cb(void *wk)
                break;
        }
 
-       sc->sc_newstate(ic, nstate, arg);
+       sc->sc_newstate(ic, nstate, task_arg->arg);
+
+       if (nstate == IEEE80211_S_INIT && (sc->sc_flags & IWM_FLAG_RESET)) {
+               sc->sc_flags &= ~IWM_FLAG_RESET;
+               iwm_init(IC2IFP(&sc->sc_ic));
+       }
 }
 
 int
 iwm_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
 {
-       struct iwm_newstate_state *iwmns;
-       struct ifnet *ifp = IC2IFP(ic);
-       struct iwm_softc *sc = ifp->if_softc;
-
-       timeout_del(&sc->sc_calib_to);
-
-       iwmns = malloc(sizeof(*iwmns), M_DEVBUF, M_NOWAIT);
-       if (!iwmns) {
-               DPRINTF(("%s: allocating state cb mem failed\n", DEVNAME(sc)));
-               return ENOMEM;
-       }
+       struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+       struct iwm_newstate_task_arg *task_arg = &sc->sc_newstate_task_arg;
 
-       iwmns->ns_ic = ic;
-       iwmns->ns_nstate = nstate;
-       iwmns->ns_arg = arg;
-       iwmns->ns_generation = sc->sc_generation;
+       /* Remove any already scheduled transition. */
+       task_del(systq, &sc->sc_newstate_task);
 
-       task_set(&iwmns->ns_wk, iwm_newstate_cb, iwmns);
-       task_add(sc->sc_nswq, &iwmns->ns_wk);
+       /* Schedule state transition in a process context. */
+       task_arg->state = nstate;
+       task_arg->arg = arg;
+       task_add(systq, &sc->sc_newstate_task);
 
        return 0;
 }
@@ -5551,14 +5534,11 @@ iwm_init(struct ifnet *ifp)
        struct iwm_softc *sc = ifp->if_softc;
        int error;
 
-       if (sc->sc_flags & IWM_FLAG_HW_INITED) {
-               return 0;
-       }
        sc->sc_generation++;
        sc->sc_flags &= ~IWM_FLAG_STOPPED;
 
        if ((error = iwm_init_hw(sc)) != 0) {
-               iwm_stop(ifp, 1);
+               iwm_stop(ifp);
                return error;
        }
 
@@ -5570,7 +5550,6 @@ iwm_init(struct ifnet *ifp)
        ifp->if_flags |= IFF_RUNNING;
 
        ieee80211_begin_scan(ifp);
-       sc->sc_flags |= IWM_FLAG_HW_INITED;
 
        return 0;
 }
@@ -5648,26 +5627,17 @@ iwm_start(struct ifnet *ifp)
 }
 
 void
-iwm_stop(struct ifnet *ifp, int disable)
+iwm_stop(struct ifnet *ifp)
 {
        struct iwm_softc *sc = ifp->if_softc;
        struct ieee80211com *ic = &sc->sc_ic;
 
-       sc->sc_flags &= ~IWM_FLAG_HW_INITED;
-       sc->sc_flags |= IWM_FLAG_STOPPED;
-       sc->sc_generation++;
-       sc->sc_scanband = 0;
-       sc->sc_auth_prot = 0;
-       ic->ic_scan_lock = IEEE80211_SCAN_UNLOCKED;
        ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+       ifp->if_timer = 0;
+       sc->sc_tx_timer = 0; /* cancel watchdog */
 
        if (ic->ic_state != IEEE80211_S_INIT)
                ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
-
-       timeout_del(&sc->sc_calib_to);
-       iwm_led_blink_stop(sc);
-       ifp->if_timer = sc->sc_tx_timer = 0;
-       iwm_stop_device(sc);
 }
 
 void
@@ -5730,7 +5700,7 @@ iwm_ioctl(struct ifnet *ifp, u_long cmd,
                        }
                } else {
                        if (ifp->if_flags & IFF_RUNNING)
-                               iwm_stop(ifp, 1);
+                               iwm_stop(ifp);
                }
                break;
 
@@ -5752,7 +5722,7 @@ iwm_ioctl(struct ifnet *ifp, u_long cmd,
                error = 0;
                if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) ==
                    (IFF_UP | IFF_RUNNING)) {
-                       iwm_stop(ifp, 0);
+                       iwm_stop(ifp);
                        error = iwm_init(ifp);
                }
        }
@@ -6227,7 +6197,7 @@ iwm_intr(void *arg)
 
                printf("%s: fatal firmware error\n", DEVNAME(sc));
                ifp->if_flags &= ~IFF_UP;
-               iwm_stop(ifp, 1);
+               iwm_stop(ifp);
                rv = 1;
                goto out;
 
@@ -6237,7 +6207,7 @@ iwm_intr(void *arg)
                handled |= IWM_CSR_INT_BIT_HW_ERR;
                printf("%s: hardware error, stopping device \n", DEVNAME(sc));
                ifp->if_flags &= ~IFF_UP;
-               iwm_stop(ifp, 1);
+               iwm_stop(ifp);
                rv = 1;
                goto out;
        }
@@ -6257,7 +6227,7 @@ iwm_intr(void *arg)
                        DPRINTF(("%s: rfkill switch, disabling interface\n",
                            DEVNAME(sc)));
                        ifp->if_flags &= ~IFF_UP;
-                       iwm_stop(ifp, 1);
+                       iwm_stop(ifp);
                }
        }
 
@@ -6533,9 +6503,6 @@ iwm_attach(struct device *parent, struct
        sc->sc_eswq = taskq_create("iwmes", 1, IPL_NET, 0);
        if (sc->sc_eswq == NULL)
                goto fail4;
-       sc->sc_nswq = taskq_create("iwmns", 1, IPL_NET, 0);
-       if (sc->sc_nswq == NULL)
-               goto fail4;
 
        /* Clear pending interrupts. */
        IWM_WRITE(sc, IWM_CSR_INT, 0xffffffff);
@@ -6587,6 +6554,7 @@ iwm_attach(struct device *parent, struct
        timeout_set(&sc->sc_calib_to, iwm_calib_timeout, sc);
        timeout_set(&sc->sc_led_blink_to, iwm_led_blink_timeout, sc);
        task_set(&sc->init_task, iwm_init_task, sc);
+       task_set(&sc->sc_newstate_task, iwm_newstate_task, sc);
 
        /*
         * We cannot read the MAC address without loading the
@@ -6634,34 +6602,62 @@ void
 iwm_init_task(void *arg1)
 {
        struct iwm_softc *sc = arg1;
+       struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &sc->sc_ic.ic_if;
-       int s;
 
-       s = splnet();
-       while (sc->sc_flags & IWM_FLAG_BUSY)
-               tsleep(&sc->sc_flags, 0, "iwmpwr", 0);
-       sc->sc_flags |= IWM_FLAG_BUSY;
+       if (ic->ic_state == IEEE80211_S_INIT)
+               return;
 
-       iwm_stop(ifp, 0);
-       if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP)
-               iwm_init(ifp);
+       if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) != IFF_UP)
+               return;
 
-       sc->sc_flags &= ~IWM_FLAG_BUSY;
-       wakeup(&sc->sc_flags);
-       splx(s);
+       /* Schedule state transition to INIT followed by INIT -> SCAN. */
+       sc->sc_flags |= IWM_FLAG_RESET;
+       ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+}
+
+void
+iwm_suspend(struct iwm_softc *sc)
+{
+       struct ifnet *ifp = &sc->sc_ic.ic_if;
+       struct ieee80211com *ic = &sc->sc_ic;
+
+       ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+       ifp->if_timer = 0;
+       sc->sc_tx_timer = 0; /* cancel watchdog */
+
+       task_del(systq, &sc->init_task);
+
+       /* Remove any already scheduled transition. */
+       task_del(systq, &sc->sc_newstate_task);
+
+       /* Cancel pending timeouts. */
+       if (ic->ic_state == IEEE80211_S_SCAN)
+               iwm_led_blink_stop(sc);
+       if (ic->ic_state == IEEE80211_S_RUN)
+               timeout_del(&sc->sc_calib_to);
+
+       /* Stop hardware. */
+       iwm_stop_device(sc);
 }
 
 void
-iwm_wakeup(struct iwm_softc *sc)
+iwm_resume(struct iwm_softc *sc)
 {
        pcireg_t reg;
+       struct ieee80211com *ic = &sc->sc_ic;
+       struct ifnet *ifp = &sc->sc_ic.ic_if;
 
        /* Clear device-specific "PCI retry timeout" register (41h). */
        reg = pci_conf_read(sc->sc_pct, sc->sc_pcitag, 0x40);
        pci_conf_write(sc->sc_pct, sc->sc_pcitag, 0x40, reg & ~0xff00);
 
-       iwm_init_task(sc);
-
+       if (ic->ic_state != IEEE80211_S_INIT &&
+           (ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP) {
+               /* Schedule transition to INIT followed by INIT -> SCAN. */
+               sc->sc_flags |= IWM_FLAG_RESET;
+               ieee80211_new_state(ic, IEEE80211_S_INIT, -1);
+       }
 }
 
 int
@@ -6673,10 +6669,10 @@ iwm_activate(struct device *self, int ac
        switch (act) {
        case DVACT_SUSPEND:
                if (ifp->if_flags & IFF_RUNNING)
-                       iwm_stop(ifp, 0);
+                       iwm_suspend(sc);
                break;
-       case DVACT_WAKEUP:
-               iwm_wakeup(sc);
+       case DVACT_RESUME:
+               iwm_resume(sc);
                break;
        }
 
Index: if_iwmvar.h
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwmvar.h,v
retrieving revision 1.9
diff -u -p -r1.9 if_iwmvar.h
--- if_iwmvar.h 15 Jun 2015 08:06:12 -0000      1.9
+++ if_iwmvar.h 16 Jul 2015 00:56:37 -0000
@@ -288,7 +288,7 @@ struct iwm_rx_ring {
 };
 
 #define IWM_FLAG_USE_ICT       0x01
-#define IWM_FLAG_HW_INITED     0x02
+#define IWM_FLAG_RESET         0x02
 #define IWM_FLAG_STOPPED       0x04
 #define IWM_FLAG_RFKILL                0x08
 #define IWM_FLAG_BUSY          0x10
@@ -353,11 +353,17 @@ struct iwm_bf_data {
        int last_cqm_event;
 };
 
+struct iwm_newstate_task_arg {
+       enum ieee80211_state state;
+       int arg;
+};
+
 struct iwm_softc {
        struct device sc_dev;
        struct ieee80211com sc_ic;
        int (*sc_newstate)(struct ieee80211com *, enum ieee80211_state, int);
-       int sc_newstate_pending;
+       struct task sc_newstate_task;
+       struct iwm_newstate_task_arg sc_newstate_task_arg;
 
        struct ieee80211_amrr sc_amrr;
        struct timeout sc_calib_to;
@@ -448,7 +454,7 @@ struct iwm_softc {
        uint8_t sc_cmd_resp[IWM_CMD_RESP_MAX];
        int sc_wantresp;
 
-       struct taskq *sc_nswq, *sc_eswq;
+       struct taskq *sc_eswq;
        struct task sc_eswk;
 
        struct iwm_rx_phy_info sc_last_phy_info;

Reply via email to