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;