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;