diff --git a/sys/dev/rtwn/if_rtwn.c b/sys/dev/rtwn/if_rtwn.c --- a/sys/dev/rtwn/if_rtwn.c +++ b/sys/dev/rtwn/if_rtwn.c @@ -614,10 +614,12 @@ struct ieee80211com *ic = vap->iv_ic; struct rtwn_softc *sc = ic->ic_softc; struct rtwn_vap *uvp = RTWN_VAP(vap); + int i; /* Put vap into INIT state + stop device if needed. */ ieee80211_stop(vap); - ieee80211_draintask(ic, &vap->iv_nstate_task); + for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) + ieee80211_draintask(ic, &vap->iv_nstate_task[i]); ieee80211_draintask(ic, &ic->ic_parent_task); RTWN_LOCK(sc); diff --git a/sys/dev/usb/wlan/if_rum.c b/sys/dev/usb/wlan/if_rum.c --- a/sys/dev/usb/wlan/if_rum.c +++ b/sys/dev/usb/wlan/if_rum.c @@ -719,10 +719,12 @@ struct rum_vap *rvp = RUM_VAP(vap); struct ieee80211com *ic = vap->iv_ic; struct rum_softc *sc = ic->ic_softc; + int i; /* Put vap into INIT state. */ ieee80211_new_state(vap, IEEE80211_S_INIT, -1); - ieee80211_draintask(ic, &vap->iv_nstate_task); + for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) + ieee80211_draintask(ic, &vap->iv_nstate_task[i]); RUM_LOCK(sc); /* Cancel any unfinished Tx. */ diff --git a/sys/net80211/ieee80211.c b/sys/net80211/ieee80211.c --- a/sys/net80211/ieee80211.c +++ b/sys/net80211/ieee80211.c @@ -730,6 +730,7 @@ { struct ieee80211com *ic = vap->iv_ic; struct ifnet *ifp = vap->iv_ifp; + int i; CURVNET_SET(ifp->if_vnet); @@ -744,7 +745,8 @@ /* * Flush any deferred vap tasks. */ - ieee80211_draintask(ic, &vap->iv_nstate_task); + for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) + ieee80211_draintask(ic, &vap->iv_nstate_task[i]); ieee80211_draintask(ic, &vap->iv_swbmiss_task); ieee80211_draintask(ic, &vap->iv_wme_task); ieee80211_draintask(ic, &ic->ic_parent_task); diff --git a/sys/net80211/ieee80211_ddb.c b/sys/net80211/ieee80211_ddb.c --- a/sys/net80211/ieee80211_ddb.c +++ b/sys/net80211/ieee80211_ddb.c @@ -470,7 +470,8 @@ if (vap->iv_opmode == IEEE80211_M_MBSS) db_printf("(%p)", vap->iv_mesh); #endif - db_printf(" state %s", ieee80211_state_name[vap->iv_state]); + db_printf(" state %#x %s", vap->iv_state, + ieee80211_state_name[vap->iv_state]); db_printf(" ifp %p(%s)", vap->iv_ifp, if_name(vap->iv_ifp)); db_printf("\n"); @@ -482,6 +483,16 @@ struct sysctllog *iv_sysctl; /* dynamic sysctl context */ #endif db_printf("\n"); + + db_printf("\tiv_nstate %#x %s iv_nstate_b %d iv_nstate_n %d\n", + vap->iv_nstate, ieee80211_state_name[vap->iv_nstate], /* historic */ + vap->iv_nstate_b, vap->iv_nstate_n); + for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) { + db_printf("\t [%d] iv_nstates %#x %s _task %p _args %d\n", i, + vap->iv_nstates[i], ieee80211_state_name[vap->iv_nstates[i]], + &vap->iv_nstate_task[i], vap->iv_nstate_args[i]); + } + db_printf("\tdebug=%b\n", vap->iv_debug, IEEE80211_MSG_BITS); db_printf("\tflags=%b\n", vap->iv_flags, IEEE80211_F_BITS); diff --git a/sys/net80211/ieee80211_proto.c b/sys/net80211/ieee80211_proto.c --- a/sys/net80211/ieee80211_proto.c +++ b/sys/net80211/ieee80211_proto.c @@ -336,7 +336,8 @@ vap->iv_bmiss_max = IEEE80211_BMISS_MAX; callout_init_mtx(&vap->iv_swbmiss, IEEE80211_LOCK_OBJ(ic), 0); callout_init(&vap->iv_mgtsend, 1); - TASK_INIT(&vap->iv_nstate_task, 0, ieee80211_newstate_cb, vap); + for (i = 0; i < NET80211_IV_NSTATE_NUM; i++) + TASK_INIT(&vap->iv_nstate_task[i], 0, ieee80211_newstate_cb, vap); TASK_INIT(&vap->iv_swbmiss_task, 0, beacon_swmiss, vap); TASK_INIT(&vap->iv_wme_task, 0, vap_update_wme, vap); TASK_INIT(&vap->iv_slot_task, 0, vap_update_slot, vap); @@ -2493,6 +2494,51 @@ } } +static int +_ieee80211_newstate_get_next_empty_slot(struct ieee80211vap *vap) +{ + int nstate_num; + + IEEE80211_LOCK_ASSERT(vap->iv_ic); + + if (vap->iv_nstate_n >= NET80211_IV_NSTATE_NUM) + return (-1); + + nstate_num = vap->iv_nstate_b + vap->iv_nstate_n; + nstate_num %= NET80211_IV_NSTATE_NUM; + vap->iv_nstate_n++; + + return (nstate_num); +} + +static int +_ieee80211_newstate_get_next_pending_slot(struct ieee80211vap *vap) +{ + int nstate_num; + + IEEE80211_LOCK_ASSERT(vap->iv_ic); + + KASSERT(vap->iv_nstate_n > 0, ("%s: vap %p iv_nstate_n %d\n", + __func__, vap, vap->iv_nstate_n)); + + nstate_num = vap->iv_nstate_b; + vap->iv_nstate_b++; + if (vap->iv_nstate_b >= NET80211_IV_NSTATE_NUM) + vap->iv_nstate_b = 0; + vap->iv_nstate_n--; + + return (nstate_num); +} + +static int +_ieee80211_newstate_get_npending(struct ieee80211vap *vap) +{ + + IEEE80211_LOCK_ASSERT(vap->iv_ic); + + return (vap->iv_nstate_n); +} + /* * Handle post state change work common to all operating modes. */ @@ -2502,17 +2548,25 @@ struct ieee80211vap *vap = xvap; struct ieee80211com *ic = vap->iv_ic; enum ieee80211_state nstate, ostate; - int arg, rc; + int arg, rc, nstate_num; + KASSERT(npending == 1, ("%s: vap %p with npending %d != 1\n", + __func__, vap, npending)); IEEE80211_LOCK(ic); - nstate = vap->iv_nstate; - arg = vap->iv_nstate_arg; + nstate_num = _ieee80211_newstate_get_next_pending_slot(vap); + + /* + * Update the historic fields for now as they are used in some + * drivers and reduce code changes for now. + */ + vap->iv_nstate = nstate = vap->iv_nstates[nstate_num]; + arg = vap->iv_nstate_args[nstate_num]; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s:%d: running state update %s -> %s (%d)\n", __func__, __LINE__, ieee80211_state_name[vap->iv_state], - ieee80211_state_name[vap->iv_nstate], + ieee80211_state_name[nstate], npending); if (vap->iv_flags_ext & IEEE80211_FEXT_REINIT) { @@ -2523,9 +2577,10 @@ /* Deny any state changes while we are here. */ vap->iv_nstate = IEEE80211_S_INIT; IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, - "%s: %s -> %s arg %d\n", __func__, + "%s: %s -> %s arg %d -> %s arg %d\n", __func__, ieee80211_state_name[vap->iv_state], - ieee80211_state_name[vap->iv_nstate], arg); + ieee80211_state_name[vap->iv_nstate], 0, + ieee80211_state_name[nstate], arg); vap->iv_newstate(vap, vap->iv_nstate, 0); IEEE80211_LOCK_ASSERT(ic); vap->iv_flags_ext &= ~(IEEE80211_FEXT_REINIT | @@ -2666,7 +2721,7 @@ struct ieee80211com *ic = vap->iv_ic; struct ieee80211vap *vp; enum ieee80211_state ostate; - int nrunning, nscanning; + int nrunning, nscanning, nstate_num; IEEE80211_LOCK_ASSERT(ic); @@ -2688,14 +2743,6 @@ ieee80211_state_name[nstate], ieee80211_state_name[vap->iv_nstate]); return -1; - } else if (vap->iv_state != vap->iv_nstate) { - /* Warn if the previous state hasn't completed. */ - IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, - "%s:%d: pending %s -> %s (now to %s) transition lost\n", - __func__, __LINE__, - ieee80211_state_name[vap->iv_state], - ieee80211_state_name[vap->iv_nstate], - ieee80211_state_name[nstate]); } } @@ -2718,7 +2765,16 @@ nscanning++; } } - ostate = vap->iv_state; + /* + * Look ahead for the "old state" at that point when the last queued + * state transition is run. + */ + if (vap->iv_nstate_n == 0) { + ostate = vap->iv_state; + } else { + nstate_num = (vap->iv_nstate_b + vap->iv_nstate_n - 1) % NET80211_IV_NSTATE_NUM; + ostate = vap->iv_nstates[nstate_num]; + } IEEE80211_DPRINTF(vap, IEEE80211_MSG_STATE, "%s: %s -> %s (arg %d) (nrunning %d nscanning %d)\n", __func__, ieee80211_state_name[ostate], ieee80211_state_name[nstate], arg, @@ -2812,11 +2868,37 @@ default: break; } - /* defer the state change to a thread */ - vap->iv_nstate = nstate; - vap->iv_nstate_arg = arg; + /* + * Defer the state change to a thread. + * We support up-to NET80211_IV_NSTATE_NUM pending state changes + * using a separate task for each. Otherwise, if we enqueue + * more than one state change they will be folded together, + * npedning will be > 1 and we may run then out of sequence with + * other events. + * This is kind-of a hack after 10 years but we know how to provoke + * these cases now (and seen them in the wild). + */ + nstate_num = _ieee80211_newstate_get_next_empty_slot(vap); + if (nstate_num == -1) { + /* + * This is really bad and we should just go kaboom. + * Instead drop it. No one checks the return code anyway. + */ + ic_printf(ic, "%s:%d: pending %s -> %s (now to %s) " + "transition lost. %d/%d pending state changes:\n", + __func__, __LINE__, + ieee80211_state_name[vap->iv_state], + ieee80211_state_name[vap->iv_nstate], + ieee80211_state_name[nstate], + _ieee80211_newstate_get_npending(vap), + NET80211_IV_NSTATE_NUM); + + return (EAGAIN); + } + vap->iv_nstates[nstate_num] = nstate; + vap->iv_nstate_args[nstate_num] = arg; vap->iv_flags_ext |= IEEE80211_FEXT_STATEWAIT; - ieee80211_runtask(ic, &vap->iv_nstate_task); + ieee80211_runtask(ic, &vap->iv_nstate_task[nstate_num]); return EINPROGRESS; } diff --git a/sys/net80211/ieee80211_var.h b/sys/net80211/ieee80211_var.h --- a/sys/net80211/ieee80211_var.h +++ b/sys/net80211/ieee80211_var.h @@ -410,9 +410,16 @@ uint32_t iv_com_state; /* com usage / detached flag */ enum ieee80211_opmode iv_opmode; /* operation mode */ enum ieee80211_state iv_state; /* state machine state */ - enum ieee80211_state iv_nstate; /* pending state */ - int iv_nstate_arg; /* pending state arg */ - struct task iv_nstate_task; /* deferred state processing */ + + /* Deferred state processing. */ + enum ieee80211_state iv_nstate; /* next pending state (historic) */ +#define NET80211_IV_NSTATE_NUM 8 + int iv_nstate_b; /* First filled slot. */ + int iv_nstate_n; /* # of filled slots. */ + enum ieee80211_state iv_nstates[NET80211_IV_NSTATE_NUM]; /* queued pending state(s) */ + int iv_nstate_args[NET80211_IV_NSTATE_NUM]; /* queued pending state(s) arg */ + struct task iv_nstate_task[NET80211_IV_NSTATE_NUM]; + struct task iv_swbmiss_task;/* deferred iv_bmiss call */ struct callout iv_mgtsend; /* mgmt frame response timer */ /* inactivity timer settings */