On Sun, Jul 19, 2015 at 04:32:39AM +0200, Stefan Sperling wrote: > 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.
Please test this updated diff instead. The previous one had races between the ioctl handler and the newstate function, pointed out by kettenis@. I believe claudio@ has already hit them. I got help from mpi@, too. 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 20 Jul 2015 22:33:16 -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); @@ -425,9 +417,9 @@ int iwm_match(struct device *, void *, v int iwm_preinit(struct iwm_softc *); 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 *); @@ -5250,40 +5242,27 @@ iwm_media_change(struct ifnet *ifp) sc->sc_fixed_ridx = ridx; } - if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == - (IFF_UP | IFF_RUNNING)) { - iwm_stop(ifp, 0); - error = iwm_init(ifp); - } - return error; + /* + * No need to do anything on the hardware side. + * Channel changes will be applied during the + * association sequence in iwm_auth(). + */ + + return (0); } 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 ifnet *ifp = IC2IFP(&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)); + sc->sc_newstate_errno = 0; if (ic->ic_state == IEEE80211_S_SCAN && nstate != ic->ic_state) iwm_led_blink_stop(sc); @@ -5292,6 +5271,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 +5291,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; } @@ -5319,10 +5301,25 @@ iwm_newstate_cb(void *wk) switch (nstate) { case IEEE80211_S_INIT: - sc->sc_scanband = 0; + if (ifp->if_flags & IFF_RUNNING) { + /* Stop the device. */ + sc->sc_scanband = 0; + sc->sc_auth_prot = 0; + iwm_stop(ifp); + } + + if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP) + iwm_init(ifp); break; case IEEE80211_S_SCAN: + /* Allow scan only if the interface is up */ + if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) != + (IFF_UP | IFF_RUNNING)) { + sc->sc_newstate_errno = ENXIO; + return; + } + if (sc->sc_scanband) break; @@ -5330,11 +5327,13 @@ iwm_newstate_cb(void *wk) ic->ic_des_esslen != 0, ic->ic_des_essid, ic->ic_des_esslen)) != 0) { printf("%s: could not initiate scan\n", DEVNAME(sc)); - return; + sc->sc_newstate_errno = EIO; + ieee80211_end_scan(&ic->ic_if); + break; } - ic->ic_state = nstate; + iwm_led_blink_start(sc); - return; + break; case IEEE80211_S_AUTH: if ((error = iwm_auth(sc)) != 0) { @@ -5374,36 +5373,30 @@ iwm_newstate_cb(void *wk) timeout_add_msec(&sc->sc_calib_to, 500); iwm_mvm_led_enable(sc); break; } - - default: - break; } - sc->sc_newstate(ic, nstate, arg); + sc->sc_newstate(ic, nstate, task_arg->arg); + + /* Wake up sleeping process waiting for state transition completion. */ + wakeup(&sc->sc_newstate); + + if (nstate == IEEE80211_S_INIT && + (ifp->if_flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) + ieee80211_begin_scan(ifp); } 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); + task_arg->state = nstate; + task_arg->arg = arg; + task_add(systq, &sc->sc_newstate_task); return 0; } @@ -5432,11 +5425,8 @@ iwm_endscan_cb(void *arg) } if (done) { - if (!sc->sc_scanband) { - ic->ic_scan_lock = IEEE80211_SCAN_UNLOCKED; - } else { + if (sc->sc_scanband) ieee80211_end_scan(&ic->ic_if); - } sc->sc_scanband = 0; } } @@ -5541,24 +5531,18 @@ iwm_allow_mcast(struct iwm_softc *sc) return error; } -/* - * ifnet interfaces - */ - +/* Initialize the hardware and mark the interface as RUNNING. */ int 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; } @@ -5569,9 +5553,6 @@ iwm_init(struct ifnet *ifp) ifp->if_flags &= ~IFF_OACTIVE; ifp->if_flags |= IFF_RUNNING; - ieee80211_begin_scan(ifp); - sc->sc_flags |= IWM_FLAG_HW_INITED; - return 0; } @@ -5648,25 +5629,15 @@ 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); + sc->sc_flags |= IWM_FLAG_STOPPED; + 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); } @@ -5674,6 +5645,7 @@ void iwm_watchdog(struct ifnet *ifp) { struct iwm_softc *sc = ifp->if_softc; + struct ieee80211com *ic = &sc->sc_ic; ifp->if_timer = 0; if (sc->sc_tx_timer > 0) { @@ -5682,7 +5654,7 @@ iwm_watchdog(struct ifnet *ifp) #ifdef IWM_DEBUG iwm_nic_error(sc); #endif - task_add(systq, &sc->init_task); + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); ifp->if_oerrors++; return; } @@ -5703,18 +5675,6 @@ iwm_ioctl(struct ifnet *ifp, u_long cmd, s = splnet(); - /* - * Prevent processes from entering this function while another - * process is tsleep'ing in it. - */ - while ((sc->sc_flags & IWM_FLAG_BUSY) && error == 0) - error = tsleep(&sc->sc_flags, PCATCH, "iwmioc", 0); - if (error != 0) { - splx(s); - return error; - } - sc->sc_flags |= IWM_FLAG_BUSY; - switch (cmd) { case SIOCSIFADDR: ifp->if_flags |= IFF_UP; @@ -5725,12 +5685,16 @@ iwm_ioctl(struct ifnet *ifp, u_long cmd, case SIOCSIFFLAGS: if (ifp->if_flags & IFF_UP) { if (!(ifp->if_flags & IFF_RUNNING)) { - if ((error = iwm_init(ifp)) != 0) - ifp->if_flags &= ~IFF_UP; + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); + tsleep(&sc->sc_newstate, 0, "iwmstart", 0); + error = sc->sc_newstate_errno; } } else { - if (ifp->if_flags & IFF_RUNNING) - iwm_stop(ifp, 1); + if (ifp->if_flags & IFF_RUNNING) { + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); + tsleep(&sc->sc_newstate, 0, "iwmstop", 0); + error = sc->sc_newstate_errno; + } } break; @@ -5748,17 +5712,9 @@ iwm_ioctl(struct ifnet *ifp, u_long cmd, error = ieee80211_ioctl(ifp, cmd, data); } - if (error == ENETRESET) { + if (error == ENETRESET) error = 0; - if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == - (IFF_UP | IFF_RUNNING)) { - iwm_stop(ifp, 0); - error = iwm_init(ifp); - } - } - sc->sc_flags &= ~IWM_FLAG_BUSY; - wakeup(&sc->sc_flags); splx(s); return error; } @@ -6158,6 +6114,7 @@ iwm_intr(void *arg) { struct iwm_softc *sc = arg; struct ifnet *ifp = IC2IFP(&sc->sc_ic); + struct ieee80211com *ic = &sc->sc_ic; int handled = 0; int r1, r2, rv = 0; int isperiodic = 0; @@ -6227,7 +6184,7 @@ iwm_intr(void *arg) printf("%s: fatal firmware error\n", DEVNAME(sc)); ifp->if_flags &= ~IFF_UP; - iwm_stop(ifp, 1); + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); rv = 1; goto out; @@ -6237,7 +6194,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); + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); rv = 1; goto out; } @@ -6257,7 +6214,7 @@ iwm_intr(void *arg) DPRINTF(("%s: rfkill switch, disabling interface\n", DEVNAME(sc))); ifp->if_flags &= ~IFF_UP; - iwm_stop(ifp, 1); + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); } } @@ -6533,9 +6490,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); @@ -6586,7 +6540,7 @@ iwm_attach(struct device *parent, struct #endif 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 @@ -6631,37 +6585,37 @@ iwm_radiotap_attach(struct iwm_softc *sc #endif void -iwm_init_task(void *arg1) +iwm_suspend(struct iwm_softc *sc) { - struct iwm_softc *sc = arg1; struct ifnet *ifp = &sc->sc_ic.ic_if; - int s; + struct ieee80211com *ic = &sc->sc_ic; - s = splnet(); - while (sc->sc_flags & IWM_FLAG_BUSY) - tsleep(&sc->sc_flags, 0, "iwmpwr", 0); - sc->sc_flags |= IWM_FLAG_BUSY; - - iwm_stop(ifp, 0); - if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == IFF_UP) - iwm_init(ifp); + /* Remove any already scheduled transition. */ + task_del(systq, &sc->sc_newstate_task); - sc->sc_flags &= ~IWM_FLAG_BUSY; - wakeup(&sc->sc_flags); - splx(s); + /* 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); + + iwm_stop(ifp); + + /* Reset net80211 state machine. */ + sc->sc_newstate(ic, IEEE80211_S_INIT, -1); } void -iwm_wakeup(struct iwm_softc *sc) +iwm_resume(struct iwm_softc *sc) { pcireg_t reg; + struct ieee80211com *ic = &sc->sc_ic; /* 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); - + ieee80211_new_state(ic, IEEE80211_S_INIT, -1); } int @@ -6673,10 +6627,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 20 Jul 2015 22:11:16 -0000 @@ -288,10 +288,8 @@ struct iwm_rx_ring { }; #define IWM_FLAG_USE_ICT 0x01 -#define IWM_FLAG_HW_INITED 0x02 -#define IWM_FLAG_STOPPED 0x04 -#define IWM_FLAG_RFKILL 0x08 -#define IWM_FLAG_BUSY 0x10 +#define IWM_FLAG_STOPPED 0x02 +#define IWM_FLAG_RFKILL 0x04 struct iwm_ucode_status { uint32_t uc_error_event_table; @@ -353,18 +351,23 @@ 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; + int sc_newstate_errno; struct ieee80211_amrr sc_amrr; struct timeout sc_calib_to; struct timeout sc_led_blink_to; - struct task init_task; - bus_space_tag_t sc_st; bus_space_handle_t sc_sh; bus_size_t sc_sz; @@ -448,7 +451,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;