This diff makes iwm_stop() always run in a process context.
I want iwm_stop() to be able to sleep so that it can wait for asynchronous
driver tasks, and perhaps even wait for firmware commands, in the future.

If the interrupt handler detects a fatal condition, instead of calling
iwm_stop() directly, defer to the init task. The init task looks at flags
to understand what happened and restarts or stops the device as appropriate.

I found that toggling the RF kill switch can trigger a fatal firmware error.
Hence I am letting the interrupt handler check RF kill before checking for
fatal firmware error. Provides better error reporting when the kill switch
is used.

During suspend, bring the device down during the QUIESCE stage which
is allowed to sleep.

dhclient/down/scan in a loop still works (as far as it always has, with
various errors reported in dmesg). Suspend/resume still works.

ok?

Index: if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.211
diff -u -p -r1.211 if_iwm.c
--- if_iwm.c    13 Aug 2017 18:08:03 -0000      1.211
+++ if_iwm.c    14 Aug 2017 06:44:59 -0000
@@ -6517,6 +6517,7 @@ iwm_stop(struct ifnet *ifp)
        sc->sc_flags &= ~IWM_FLAG_BINDING_ACTIVE;
        sc->sc_flags &= ~IWM_FLAG_STA_ACTIVE;
        sc->sc_flags &= ~IWM_FLAG_TE_ACTIVE;
+       sc->sc_flags &= ~IWM_FLAG_HW_ERR;
 
        sc->sc_newstate(ic, IEEE80211_S_INIT, -1);
 
@@ -7169,7 +7170,6 @@ int
 iwm_intr(void *arg)
 {
        struct iwm_softc *sc = arg;
-       struct ifnet *ifp = IC2IFP(&sc->sc_ic);
        int handled = 0;
        int r1, r2, rv = 0;
        int isperiodic = 0;
@@ -7218,6 +7218,15 @@ iwm_intr(void *arg)
        /* ignored */
        handled |= (r1 & (IWM_CSR_INT_BIT_ALIVE /*| IWM_CSR_INT_BIT_SCD*/));
 
+       if (r1 & IWM_CSR_INT_BIT_RF_KILL) {
+               handled |= IWM_CSR_INT_BIT_RF_KILL;
+               if (iwm_check_rfkill(sc)) {
+                       task_add(systq, &sc->init_task);
+                       rv = 1;
+                       goto out;
+               }
+       }
+
        if (r1 & IWM_CSR_INT_BIT_SW_ERR) {
 #ifdef IWM_DEBUG
                int i;
@@ -7238,7 +7247,6 @@ iwm_intr(void *arg)
 #endif
 
                printf("%s: fatal firmware error\n", DEVNAME(sc));
-               iwm_stop(ifp);
                task_add(systq, &sc->init_task);
                rv = 1;
                goto out;
@@ -7248,7 +7256,8 @@ iwm_intr(void *arg)
        if (r1 & IWM_CSR_INT_BIT_HW_ERR) {
                handled |= IWM_CSR_INT_BIT_HW_ERR;
                printf("%s: hardware error, stopping device \n", DEVNAME(sc));
-               iwm_stop(ifp);
+               sc->sc_flags |= IWM_FLAG_HW_ERR;
+               task_add(systq, &sc->init_task);
                rv = 1;
                goto out;
        }
@@ -7262,13 +7271,6 @@ iwm_intr(void *arg)
                wakeup(&sc->sc_fw);
        }
 
-       if (r1 & IWM_CSR_INT_BIT_RF_KILL) {
-               handled |= IWM_CSR_INT_BIT_RF_KILL;
-               if (iwm_check_rfkill(sc) && (ifp->if_flags & IFF_UP)) {
-                       iwm_stop(ifp);
-               }
-       }
-
        if (r1 & IWM_CSR_INT_BIT_RX_PERIODIC) {
                handled |= IWM_CSR_INT_BIT_RX_PERIODIC;
                IWM_WRITE(sc, IWM_CSR_INT, IWM_CSR_INT_BIT_RX_PERIODIC);
@@ -7739,23 +7741,27 @@ iwm_init_task(void *arg1)
 {
        struct iwm_softc *sc = arg1;
        struct ifnet *ifp = &sc->sc_ic.ic_if;
-       int s;
+       int s = splnet();
        int generation = sc->sc_generation;
+       int fatal = (sc->sc_flags & (IWM_FLAG_HW_ERR | IWM_FLAG_RFKILL));
 
        rw_enter_write(&sc->ioctl_rwl);
        if (generation != sc->sc_generation) {
                rw_exit(&sc->ioctl_rwl);
+               splx(s);
                return;
        }
-       s = splnet();
 
        if (ifp->if_flags & IFF_RUNNING)
                iwm_stop(ifp);
-       if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP)
+       else if (sc->sc_flags & IWM_FLAG_HW_ERR)
+               sc->sc_flags &= ~IWM_FLAG_HW_ERR;
+
+       if (!fatal && (ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP)
                iwm_init(ifp);
 
-       splx(s);
        rw_exit(&sc->ioctl_rwl);
+       splx(s);
 }
 
 int
@@ -7778,9 +7784,12 @@ iwm_activate(struct device *self, int ac
        int err = 0;
 
        switch (act) {
-       case DVACT_SUSPEND:
-               if (ifp->if_flags & IFF_RUNNING)
+       case DVACT_QUIESCE:
+               if (ifp->if_flags & IFF_RUNNING) {
+                       rw_enter_write(&sc->ioctl_rwl);
                        iwm_stop(ifp);
+                       rw_exit(&sc->ioctl_rwl);
+               }
                break;
        case DVACT_RESUME:
                err = iwm_resume(sc);
Index: if_iwmvar.h
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwmvar.h,v
retrieving revision 1.33
diff -u -p -r1.33 if_iwmvar.h
--- if_iwmvar.h 13 Aug 2017 15:34:54 -0000      1.33
+++ if_iwmvar.h 14 Aug 2017 06:00:16 -0000
@@ -286,6 +286,7 @@ struct iwm_rx_ring {
 #define IWM_FLAG_BINDING_ACTIVE        0x10    /* MAC->PHY binding added to 
firmware */
 #define IWM_FLAG_STA_ACTIVE    0x20    /* AP added to firmware station table */
 #define IWM_FLAG_TE_ACTIVE     0x40    /* time event is scheduled */
+#define IWM_FLAG_HW_ERR                0x80    /* hardware error occurred */
 
 struct iwm_ucode_status {
        uint32_t uc_error_event_table;

Reply via email to