On Wed, Dec 06, 2017 at 09:25:48AM -0700, Base Pr1me wrote:
> Sorry for the anecdotal information. I've enabled debugging and if I see
> any relevant data, I'll pass it along.

I found one bug which rendered the driver unable to make progress after
an association failure. This new version fixes that problem for me.

Note that if you're watching the extra dmesg output created by
'ifconfig iwm0 debug' on the console, flooding the list of scan results
to the framebuffer results in delays which can cause some frames to be
received too late, and this makes association failures more likely.

There is nothing we can do about that. Either turn off the debug output,
or run in X and watch with 'tail -f /var/log/messages'. However, I could
make sure the failure code path is actually working by flooding the
console to make association fail after a scan. The driver now keeps
scanning when it fails to roam, as expected. It would hang with the
previous version of this diff.

Index: dev/pci/if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.217
diff -u -p -r1.217 if_iwm.c
--- dev/pci/if_iwm.c    26 Oct 2017 15:00:28 -0000      1.217
+++ dev/pci/if_iwm.c    6 Dec 2017 17:11:13 -0000
@@ -420,9 +420,9 @@ uint32_t iwm_scan_rate_n_flags(struct iw
 uint8_t        iwm_lmac_scan_fill_channels(struct iwm_softc *,
            struct iwm_scan_channel_cfg_lmac *, int);
 int    iwm_fill_probe_req(struct iwm_softc *, struct iwm_scan_probe_req *);
-int    iwm_lmac_scan(struct iwm_softc *);
+int    iwm_lmac_scan(struct iwm_softc *, int);
 int    iwm_config_umac_scan(struct iwm_softc *);
-int    iwm_umac_scan(struct iwm_softc *);
+int    iwm_umac_scan(struct iwm_softc *, int);
 uint8_t        iwm_ridx2rate(struct ieee80211_rateset *, int);
 int    iwm_rval2ridx(int);
 void   iwm_ack_rates(struct iwm_softc *, struct iwm_node *, int *, int *);
@@ -435,6 +435,10 @@ int        iwm_update_quotas(struct iwm_softc *
 void   iwm_add_task(struct iwm_softc *, struct taskq *, struct task *);
 void   iwm_del_task(struct iwm_softc *, struct taskq *, struct task *);
 int    iwm_scan(struct iwm_softc *);
+int    iwm_bgscan(struct ieee80211com *);
+int    iwm_umac_scan_abort(struct iwm_softc *);
+int    iwm_lmac_scan_abort(struct iwm_softc *);
+int    iwm_scan_abort(struct iwm_softc *);
 int    iwm_auth(struct iwm_softc *);
 int    iwm_deauth(struct iwm_softc *);
 int    iwm_assoc(struct iwm_softc *);
@@ -4868,7 +4872,7 @@ iwm_fill_probe_req(struct iwm_softc *sc,
 }
 
 int
-iwm_lmac_scan(struct iwm_softc *sc)
+iwm_lmac_scan(struct iwm_softc *sc, int bgscan)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_host_cmd hcmd = {
@@ -4879,28 +4883,34 @@ iwm_lmac_scan(struct iwm_softc *sc)
        };
        struct iwm_scan_req_lmac *req;
        size_t req_len;
-       int err;
+       int err, async = bgscan;
 
        req_len = sizeof(struct iwm_scan_req_lmac) +
            (sizeof(struct iwm_scan_channel_cfg_lmac) *
            sc->sc_capa_n_scan_channels) + sizeof(struct iwm_scan_probe_req);
        if (req_len > IWM_MAX_CMD_PAYLOAD_SIZE)
                return ENOMEM;
-       req = malloc(req_len, M_DEVBUF, M_WAIT | M_CANFAIL | M_ZERO);
+       req = malloc(req_len, M_DEVBUF,
+           (async ? M_NOWAIT : M_WAIT) | M_CANFAIL | M_ZERO);
        if (req == NULL)
                return ENOMEM;
 
        hcmd.len[0] = (uint16_t)req_len;
        hcmd.data[0] = (void *)req;
+       hcmd.flags |= async ? IWM_CMD_ASYNC : 0;
 
        /* These timings correspond to iwlwifi's UNASSOC scan. */
        req->active_dwell = 10;
        req->passive_dwell = 110;
        req->fragmented_dwell = 44;
        req->extended_dwell = 90;
-       req->max_out_time = 0;
-       req->suspend_time = 0;
-
+       if (bgscan) {
+               req->max_out_time = htole32(120);
+               req->suspend_time = htole32(120);
+       } else {
+               req->max_out_time = htole32(0);
+               req->suspend_time = htole32(0);
+       }
        req->scan_prio = htole32(IWM_SCAN_PRIORITY_HIGH);
        req->rx_chain_select = iwm_scan_rx_chain(sc);
        req->iter_num = htole32(1);
@@ -5048,7 +5058,7 @@ iwm_config_umac_scan(struct iwm_softc *s
 }
 
 int
-iwm_umac_scan(struct iwm_softc *sc)
+iwm_umac_scan(struct iwm_softc *sc, int bgscan)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_host_cmd hcmd = {
@@ -5060,7 +5070,7 @@ iwm_umac_scan(struct iwm_softc *sc)
        struct iwm_scan_req_umac *req;
        struct iwm_scan_req_umac_tail *tail;
        size_t req_len;
-       int err;
+       int err, async = bgscan;
 
        req_len = sizeof(struct iwm_scan_req_umac) +
            (sizeof(struct iwm_scan_channel_cfg_umac) *
@@ -5068,20 +5078,27 @@ iwm_umac_scan(struct iwm_softc *sc)
            sizeof(struct iwm_scan_req_umac_tail);
        if (req_len > IWM_MAX_CMD_PAYLOAD_SIZE)
                return ENOMEM;
-       req = malloc(req_len, M_DEVBUF, M_WAIT | M_CANFAIL | M_ZERO);
+       req = malloc(req_len, M_DEVBUF,
+           (async ? M_NOWAIT : M_WAIT) | M_CANFAIL | M_ZERO);
        if (req == NULL)
                return ENOMEM;
 
        hcmd.len[0] = (uint16_t)req_len;
        hcmd.data[0] = (void *)req;
+       hcmd.flags |= async ? IWM_CMD_ASYNC : 0;
 
        /* These timings correspond to iwlwifi's UNASSOC scan. */
        req->active_dwell = 10;
        req->passive_dwell = 110;
        req->fragmented_dwell = 44;
        req->extended_dwell = 90;
-       req->max_out_time = 0;
-       req->suspend_time = 0;
+       if (bgscan) {
+               req->max_out_time = htole32(120);
+               req->suspend_time = htole32(120);
+       } else {
+               req->max_out_time = htole32(0);
+               req->suspend_time = htole32(0);
+       }
 
        req->scan_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
        req->ooc_priority = htole32(IWM_SCAN_PRIORITY_HIGH);
@@ -5461,13 +5478,18 @@ iwm_scan(struct iwm_softc *sc)
        struct ieee80211com *ic = &sc->sc_ic;
        int err;
 
-       if (sc->sc_flags & IWM_FLAG_SCANNING)
-               return 0;
+       if (sc->sc_flags & IWM_FLAG_SCANNING) {
+               err = iwm_scan_abort(sc);
+               if (err) {
+                       printf("%s: could not abort scan\n", DEVNAME(sc));
+                       return err;
+               }
+       }
 
        if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
-               err = iwm_umac_scan(sc);
+               err = iwm_umac_scan(sc, 0);
        else
-               err = iwm_lmac_scan(sc);
+               err = iwm_lmac_scan(sc, 0);
        if (err) {
                printf("%s: could not initiate scan\n", DEVNAME(sc));
                return err;
@@ -5482,6 +5504,79 @@ iwm_scan(struct iwm_softc *sc)
 }
 
 int
+iwm_bgscan(struct ieee80211com *ic) 
+{
+       struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+       int err;
+
+       if (sc->sc_flags & IWM_FLAG_SCANNING)
+               return 0;
+
+       if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
+               err = iwm_umac_scan(sc, 1);
+       else
+               err = iwm_lmac_scan(sc, 1);
+       if (err) {
+               printf("%s: could not initiate scan\n", DEVNAME(sc));
+               return err;
+       }
+
+       sc->sc_flags |= IWM_FLAG_SCANNING;
+       return 0;
+}
+
+int
+iwm_umac_scan_abort(struct iwm_softc *sc)
+{
+       struct iwm_umac_scan_abort cmd = { 0 };
+
+       return iwm_send_cmd_pdu(sc,
+           IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_ABORT_UMAC),
+           0, sizeof(cmd), &cmd);
+}
+
+int
+iwm_lmac_scan_abort(struct iwm_softc *sc)
+{
+       struct iwm_host_cmd cmd = {
+               .id = IWM_SCAN_OFFLOAD_ABORT_CMD,
+       };
+       int err, status;
+
+       err = iwm_send_cmd_status(sc, &cmd, &status);
+       if (err)
+               return err;
+
+       if (status != IWM_CAN_ABORT_STATUS) {
+               /*
+                * The scan abort will return 1 for success or
+                * 2 for "failure".  A failure condition can be
+                * due to simply not being in an active scan which
+                * can occur if we send the scan abort before the
+                * microcode has notified us that a scan is completed.
+                */
+               return EBUSY;
+       }
+
+       return 0;
+}
+
+int
+iwm_scan_abort(struct iwm_softc *sc)
+{
+       int err;
+
+       if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_UMAC_SCAN))
+               err = iwm_umac_scan_abort(sc);
+       else
+               err = iwm_lmac_scan_abort(sc);
+
+       if (err == 0)
+               sc->sc_flags &= ~IWM_FLAG_SCANNING;
+       return err;
+}
+
+int
 iwm_auth(struct iwm_softc *sc)
 {
        struct ieee80211com *ic = &sc->sc_ic;
@@ -6056,8 +6151,12 @@ iwm_newstate_task(void *psc)
        }
 
 out:
-       if (err == 0 && (sc->sc_flags & IWM_FLAG_SHUTDOWN) == 0)
-               sc->sc_newstate(ic, nstate, arg);
+       if ((sc->sc_flags & IWM_FLAG_SHUTDOWN) == 0) {
+               if (err)
+                       task_add(systq, &sc->init_task);
+               else
+                       sc->sc_newstate(ic, nstate, arg);
+       }
        refcnt_rele_wake(&sc->task_refs);
        splx(s);
 }
@@ -6524,9 +6623,10 @@ iwm_start(struct ifnet *ifp)
                        ni = m->m_pkthdr.ph_cookie;
                        goto sendit;
                }
-               if (ic->ic_state != IEEE80211_S_RUN) {
+
+               if (ic->ic_state != IEEE80211_S_RUN ||
+                   (ic->ic_xflags & IEEE80211_F_TX_MGMT_ONLY))
                        break;
-               }
 
                IFQ_DEQUEUE(&ifp->if_snd, m);
                if (!m)
@@ -7106,7 +7206,9 @@ iwm_notif_intr(struct iwm_softc *sc)
                case IWM_BINDING_CONTEXT_CMD:
                case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_CFG_CMD):
                case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_REQ_UMAC):
+               case IWM_WIDE_ID(IWM_ALWAYS_LONG_GROUP, IWM_SCAN_ABORT_UMAC):
                case IWM_SCAN_OFFLOAD_REQUEST_CMD:
+               case IWM_SCAN_OFFLOAD_ABORT_CMD:
                case IWM_REPLY_BEACON_FILTERING_CMD:
                case IWM_MAC_PM_POWER_TABLE:
                case IWM_TIME_QUOTA_CMD:
@@ -7772,6 +7874,7 @@ iwm_attach(struct device *parent, struct
        task_set(&sc->htprot_task, iwm_htprot_task, sc);
 
        ic->ic_node_alloc = iwm_node_alloc;
+       ic->ic_bgscan_start = iwm_bgscan;
 
        /* Override 802.11 state transition machine. */
        sc->sc_newstate = ic->ic_newstate;
Index: net80211/ieee80211.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211.c,v
retrieving revision 1.63
diff -u -p -r1.63 ieee80211.c
--- net80211/ieee80211.c        5 Sep 2017 12:02:21 -0000       1.63
+++ net80211/ieee80211.c        6 Dec 2017 17:07:10 -0000
@@ -72,6 +72,32 @@ void ieee80211_setbasicrates(struct ieee
 int ieee80211_findrate(struct ieee80211com *, enum ieee80211_phymode, int);
 
 void
+ieee80211_begin_bgscan(struct ifnet *ifp)
+{
+       struct ieee80211com *ic = (void *)ifp;
+
+       if ((ic->ic_flags & IEEE80211_F_BGSCAN) ||
+           ic->ic_state != IEEE80211_S_RUN)
+               return;
+
+       if (ic->ic_bgscan_start != NULL && ic->ic_bgscan_start(ic) == 0) {
+               ic->ic_flags |= IEEE80211_F_BGSCAN;
+               if (ifp->if_flags & IFF_DEBUG)
+                       printf("%s: begin background scan\n", ifp->if_xname);
+
+               /* Driver calls ieee80211_end_scan() when done. */
+       }
+}
+
+void
+ieee80211_bgscan_timeout(void *arg)
+{
+       struct ifnet *ifp = arg;
+
+       ieee80211_begin_bgscan(ifp);
+}
+
+void
 ieee80211_channel_init(struct ifnet *ifp)
 {
        struct ieee80211com *ic = (void *)ifp;
@@ -158,6 +184,8 @@ ieee80211_ifattach(struct ifnet *ifp)
        ifp->if_priority = IF_WIRELESS_DEFAULT_PRIORITY;
 
        ieee80211_set_link_state(ic, LINK_STATE_DOWN);
+
+       timeout_set(&ic->ic_bgscan_timeout, ieee80211_bgscan_timeout, ifp);
 }
 
 void
Index: net80211/ieee80211_input.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_input.c,v
retrieving revision 1.196
diff -u -p -r1.196 ieee80211_input.c
--- net80211/ieee80211_input.c  4 Sep 2017 09:11:46 -0000       1.196
+++ net80211/ieee80211_input.c  28 Nov 2017 18:54:19 -0000
@@ -267,6 +267,14 @@ ieee80211_input(struct ifnet *ifp, struc
                ni->ni_rssi = rxi->rxi_rssi;
                ni->ni_rstamp = rxi->rxi_tstamp;
                ni->ni_inact = 0;
+
+               /* Cancel or start background scan based on RSSI. */
+               if ((*ic->ic_node_checkrssi)(ic, ni))
+                       timeout_del(&ic->ic_bgscan_timeout);
+               else if (!timeout_pending(&ic->ic_bgscan_timeout) &&
+                   (ic->ic_flags & IEEE80211_F_BGSCAN) == 0)
+                       timeout_add_msec(&ic->ic_bgscan_timeout,
+                           500 * (ic->ic_bgscan_fail + 1));
        }
 
 #ifndef IEEE80211_STA_ONLY
@@ -1462,6 +1470,7 @@ ieee80211_recv_probe_resp(struct ieee802
        }
        if ((ic->ic_state != IEEE80211_S_SCAN ||
             !(ic->ic_caps & IEEE80211_C_SCANALL)) &&
+           (ic->ic_flags & IEEE80211_F_BGSCAN) == 0 &&
            chan != bchan) {
                /*
                 * Frame was received on a channel different from the
@@ -1504,7 +1513,8 @@ ieee80211_recv_probe_resp(struct ieee802
 
 #ifdef IEEE80211_DEBUG
        if (ieee80211_debug > 1 &&
-           (ni == NULL || ic->ic_state == IEEE80211_S_SCAN)) {
+           (ni == NULL || ic->ic_state == IEEE80211_S_SCAN ||
+           (ic->ic_flags & IEEE80211_F_BGSCAN))) {
                printf("%s: %s%s on chan %u (bss chan %u) ",
                    __func__, (ni == NULL ? "new " : ""),
                    isprobe ? "probe response" : "beacon",
@@ -1589,7 +1599,8 @@ ieee80211_recv_probe_resp(struct ieee802
                 * This probe response indicates the AP is still serving us
                 * so don't allow ieee80211_watchdog() to move us into SCAN.
                 */
-                ic->ic_mgt_timer = 0;
+                if ((ic->ic_flags & IEEE80211_F_BGSCAN) == 0)
+                       ic->ic_mgt_timer = 0;
        }
        /*
         * We do not try to update EDCA parameters if QoS was not negotiated
@@ -1606,7 +1617,8 @@ ieee80211_recv_probe_resp(struct ieee802
                        ni->ni_flags &= ~IEEE80211_NODE_QOS;
        }
 
-       if (ic->ic_state == IEEE80211_S_SCAN) {
+       if (ic->ic_state == IEEE80211_S_SCAN ||
+           (ic->ic_flags & IEEE80211_F_BGSCAN)) {
                struct ieee80211_rsnparams rsn, wpa;
 
                ni->ni_rsnprotos = IEEE80211_PROTO_NONE;
@@ -2361,9 +2373,13 @@ ieee80211_recv_deauth(struct ieee80211co
 
        ic->ic_stats.is_rx_deauth++;
        switch (ic->ic_opmode) {
-       case IEEE80211_M_STA:
-               ieee80211_new_state(ic, IEEE80211_S_AUTH,
-                   IEEE80211_FC0_SUBTYPE_DEAUTH);
+       case IEEE80211_M_STA: {
+               int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+                   ic->ic_state == IEEE80211_S_RUN);
+               if (!bgscan) /* ignore deauth during bgscan */
+                       ieee80211_new_state(ic, IEEE80211_S_AUTH,
+                           IEEE80211_FC0_SUBTYPE_DEAUTH);
+               }
                break;
 #ifndef IEEE80211_STA_ONLY
        case IEEE80211_M_HOSTAP:
@@ -2407,9 +2423,13 @@ ieee80211_recv_disassoc(struct ieee80211
 
        ic->ic_stats.is_rx_disassoc++;
        switch (ic->ic_opmode) {
-       case IEEE80211_M_STA:
-               ieee80211_new_state(ic, IEEE80211_S_ASSOC,
-                   IEEE80211_FC0_SUBTYPE_DISASSOC);
+       case IEEE80211_M_STA: {
+               int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+                   ic->ic_state == IEEE80211_S_RUN);
+               if (!bgscan) /* ignore disassoc during bgscan */
+                       ieee80211_new_state(ic, IEEE80211_S_ASSOC,
+                           IEEE80211_FC0_SUBTYPE_DISASSOC);
+               }
                break;
 #ifndef IEEE80211_STA_ONLY
        case IEEE80211_M_HOSTAP:
Index: net80211/ieee80211_node.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.c,v
retrieving revision 1.121
diff -u -p -r1.121 ieee80211_node.c
--- net80211/ieee80211_node.c   5 Sep 2017 14:56:59 -0000       1.121
+++ net80211/ieee80211_node.c   6 Dec 2017 17:12:42 -0000
@@ -65,12 +65,16 @@ void ieee80211_node_copy(struct ieee8021
 void ieee80211_choose_rsnparams(struct ieee80211com *);
 u_int8_t ieee80211_node_getrssi(struct ieee80211com *,
     const struct ieee80211_node *);
+int ieee80211_node_checkrssi(struct ieee80211com *,
+    const struct ieee80211_node *);
 void ieee80211_setup_node(struct ieee80211com *, struct ieee80211_node *,
     const u_int8_t *);
 void ieee80211_free_node(struct ieee80211com *, struct ieee80211_node *);
 void ieee80211_ba_del(struct ieee80211_node *);
 struct ieee80211_node *ieee80211_alloc_node_helper(struct ieee80211com *);
 void ieee80211_node_cleanup(struct ieee80211com *, struct ieee80211_node *);
+void ieee80211_node_switch_bss(struct ieee80211com *, struct ieee80211_node *);
+void ieee80211_node_join_bss(struct ieee80211com *, struct ieee80211_node *);
 void ieee80211_needs_auth(struct ieee80211com *, struct ieee80211_node *);
 #ifndef IEEE80211_STA_ONLY
 void ieee80211_node_join_ht(struct ieee80211com *, struct ieee80211_node *);
@@ -128,6 +132,7 @@ ieee80211_node_attach(struct ifnet *ifp)
        ic->ic_node_free = ieee80211_node_free;
        ic->ic_node_copy = ieee80211_node_copy;
        ic->ic_node_getrssi = ieee80211_node_getrssi;
+       ic->ic_node_checkrssi = ieee80211_node_checkrssi;
        ic->ic_scangen = 1;
        ic->ic_max_nnodes = ieee80211_cache_size;
 
@@ -546,6 +551,113 @@ ieee80211_match_bss(struct ieee80211com 
        return fail;
 }
 
+struct ieee80211_node_switch_bss_arg {
+       u_int8_t cur_macaddr[IEEE80211_ADDR_LEN];
+       u_int8_t sel_macaddr[IEEE80211_ADDR_LEN];
+};
+
+/* Implements ni->ni_unref_cb(). */
+void
+ieee80211_node_switch_bss(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+       struct ifnet *ifp = &ic->ic_if;
+       struct ieee80211_node_switch_bss_arg *sba = ni->ni_unref_arg;
+       struct ieee80211_node *curbs, *selbs;
+
+       splassert(IPL_NET);
+
+       if ((ic->ic_flags & IEEE80211_F_BGSCAN) == 0) {
+               free(sba, M_DEVBUF, sizeof(*sba));
+               return;
+       }
+
+       ic->ic_xflags &= ~IEEE80211_F_TX_MGMT_ONLY;
+
+       selbs = ieee80211_find_node(ic, sba->sel_macaddr);
+       if (selbs == NULL) {
+               free(sba, M_DEVBUF, sizeof(*sba));
+               ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+               ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+               return;
+       }
+
+       curbs = ieee80211_find_node(ic, sba->cur_macaddr);
+       if (curbs == NULL) {
+               free(sba, M_DEVBUF, sizeof(*sba));
+               ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+               ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+               return;
+       }
+
+       if (ifp->if_flags & IFF_DEBUG) {
+               printf("%s: roaming from %s chan %d ",
+                   ifp->if_xname, ether_sprintf(curbs->ni_macaddr),
+                   ieee80211_chan2ieee(ic, curbs->ni_chan));
+               printf("to %s chan %d\n", ether_sprintf(selbs->ni_macaddr),
+                   ieee80211_chan2ieee(ic, selbs->ni_chan));
+       }
+       ieee80211_node_newstate(curbs, IEEE80211_STA_CACHE);
+       ieee80211_node_join_bss(ic, selbs); /* frees arg and ic->ic_bss */
+}
+
+void
+ieee80211_node_join_bss(struct ieee80211com *ic, struct ieee80211_node *selbs)
+{
+       enum ieee80211_phymode mode;
+       struct ieee80211_node *ni;
+
+       /* Reinitialize media mode and channels if needed. */
+       mode = ieee80211_chan2mode(ic, selbs->ni_chan);
+       if (mode != ic->ic_curmode)
+               ieee80211_setmode(ic, mode);
+
+       (*ic->ic_node_copy)(ic, ic->ic_bss, selbs);
+       ni = ic->ic_bss;
+
+       ic->ic_curmode = ieee80211_chan2mode(ic, ni->ni_chan);
+
+       /* Make sure we send valid rates in an association request. */
+       if (ic->ic_opmode == IEEE80211_M_STA)
+               ieee80211_fix_rate(ic, ni,
+                   IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE |
+                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
+
+       if (ic->ic_flags & IEEE80211_F_RSNON)
+               ieee80211_choose_rsnparams(ic);
+       else if (ic->ic_flags & IEEE80211_F_WEPON)
+               ni->ni_rsncipher = IEEE80211_CIPHER_USEGROUP;
+
+       ieee80211_node_newstate(selbs, IEEE80211_STA_BSS);
+#ifndef IEEE80211_STA_ONLY
+       if (ic->ic_opmode == IEEE80211_M_IBSS) {
+               ieee80211_fix_rate(ic, ni, IEEE80211_F_DOFRATE |
+                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
+               if (ni->ni_rates.rs_nrates == 0) {
+                       ieee80211_new_state(ic, IEEE80211_S_SCAN, -1);
+                       return;
+               }
+               ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
+       } else
+#endif
+       {
+               int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+                   ic->ic_opmode == IEEE80211_M_STA &&
+                   ic->ic_state == IEEE80211_S_RUN);
+
+               timeout_del(&ic->ic_bgscan_timeout);
+               ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+
+               /* 
+                * After a background scan, we have now switched APs.
+                * Pretend we were just de-authed, which makes
+                * ieee80211_new_state() try to re-auth and thus send
+                * an AUTH frame to our newly selected AP.
+                */
+               ieee80211_new_state(ic, IEEE80211_S_AUTH,
+                   bgscan ? IEEE80211_FC0_SUBTYPE_DEAUTH : -1);
+       }
+}
+
 /*
  * Complete a scan of potential channels.
  */
@@ -553,12 +665,16 @@ void
 ieee80211_end_scan(struct ifnet *ifp)
 {
        struct ieee80211com *ic = (void *)ifp;
-       struct ieee80211_node *ni, *nextbs, *selbs;
+       struct ieee80211_node *ni, *nextbs, *selbs, *curbs;
+       int bgscan = ((ic->ic_flags & IEEE80211_F_BGSCAN) &&
+           ic->ic_opmode == IEEE80211_M_STA &&
+           ic->ic_state == IEEE80211_S_RUN);
 
        if (ifp->if_flags & IFF_DEBUG)
                printf("%s: end %s scan\n", ifp->if_xname,
-                       (ic->ic_flags & IEEE80211_F_ASCAN) ?
-                               "active" : "passive");
+                   bgscan ? "background" :
+                   ((ic->ic_flags & IEEE80211_F_ASCAN) ?
+                   "active" : "passive"));
 
        if (ic->ic_scan_count)
                ic->ic_flags &= ~IEEE80211_F_ASCAN;
@@ -634,6 +750,7 @@ ieee80211_end_scan(struct ifnet *ifp)
                return;
        }
        selbs = NULL;
+       curbs = NULL;
 
        for (; ni != NULL; ni = nextbs) {
                nextbs = RBT_NEXT(ieee80211_tree, ni);
@@ -647,6 +764,10 @@ ieee80211_end_scan(struct ifnet *ifp)
                                ieee80211_free_node(ic, ni);
                        continue;
                }
+
+               if (ni->ni_state == IEEE80211_STA_BSS)
+                       curbs = ni;
+
                if (ieee80211_match_bss(ic, ni) != 0)
                        continue;
 
@@ -657,21 +778,17 @@ ieee80211_end_scan(struct ifnet *ifp)
                    IEEE80211_IS_CHAN_5GHZ(selbs->ni_chan) &&
                    IEEE80211_IS_CHAN_2GHZ(ni->ni_chan) &&
                    ni->ni_rssi > selbs->ni_rssi) {
-                       uint8_t min_rssi = 0, max_rssi = ic->ic_max_rssi;
+                       uint8_t min_rssi;
 
                        /* 
                         * Prefer 5GHz (with reasonable RSSI) over 2GHz since
                         * the 5GHz band is usually less saturated.
                         */
-                       if (max_rssi) {
-                               /* Driver reports RSSI relative to maximum. */
-                               if (ni->ni_rssi > max_rssi / 3)
-                                       min_rssi = ni->ni_rssi - (max_rssi / 3);
-                       } else {
-                               /* Driver reports RSSI value in dBm. */
-                               if (ni->ni_rssi > 10) /* XXX magic number */
-                                       min_rssi = ni->ni_rssi - 10;
-                       }
+                       if (ic->ic_max_rssi)
+                               min_rssi = IEEE80211_RSSI_THRES_RATIO_5GHZ;
+                       else
+                               min_rssi = (uint8_t)IEEE80211_RSSI_THRES_5GHZ;
+
                        if (selbs->ni_rssi >= min_rssi)
                                continue;
                }
@@ -679,35 +796,65 @@ ieee80211_end_scan(struct ifnet *ifp)
                if (ni->ni_rssi > selbs->ni_rssi)
                        selbs = ni;
        }
-       if (selbs == NULL)
-               goto notfound;
-       (*ic->ic_node_copy)(ic, ic->ic_bss, selbs);
-       ni = ic->ic_bss;
 
-       ic->ic_curmode = ieee80211_chan2mode(ic, ni->ni_chan);
+       if (bgscan) {
+               struct ieee80211_node_switch_bss_arg *arg;
 
-       /* Make sure we send valid rates in an association request. */
-       if (ic->ic_opmode == IEEE80211_M_STA)
-               ieee80211_fix_rate(ic, ni,
-                   IEEE80211_F_DOSORT | IEEE80211_F_DOFRATE |
-                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
+               /* AP disappeared? Should not happen. */
+               if (selbs == NULL || curbs == NULL) {
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto notfound;
+               }
 
-       if (ic->ic_flags & IEEE80211_F_RSNON)
-               ieee80211_choose_rsnparams(ic);
-       else if (ic->ic_flags & IEEE80211_F_WEPON)
-               ni->ni_rsncipher = IEEE80211_CIPHER_USEGROUP;
+               /* 
+                * After a background scan we might end up choosing the
+                * same AP again. Do not change ic->ic_bss in this case,
+                * and make background scans less frequent.
+                */
+               if (selbs == curbs) {
+                       if (ic->ic_bgscan_fail < IEEE80211_BGSCAN_FAIL_MAX)
+                               ic->ic_bgscan_fail++;
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto wakeup;
+               }
+       
+               arg = malloc(sizeof(*arg), M_DEVBUF, M_NOWAIT | M_ZERO);
+               if (arg == NULL) {
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto wakeup;
+               }
 
-       ieee80211_node_newstate(selbs, IEEE80211_STA_BSS);
-#ifndef IEEE80211_STA_ONLY
-       if (ic->ic_opmode == IEEE80211_M_IBSS) {
-               ieee80211_fix_rate(ic, ni, IEEE80211_F_DOFRATE |
-                   IEEE80211_F_DONEGO | IEEE80211_F_DODEL);
-               if (ni->ni_rates.rs_nrates == 0)
-                       goto notfound;
-               ieee80211_new_state(ic, IEEE80211_S_RUN, -1);
-       } else
-#endif
-               ieee80211_new_state(ic, IEEE80211_S_AUTH, -1);
+               ic->ic_bgscan_fail = 0;
+
+               /* 
+                * We are going to switch APs.
+                * Queue a de-auth frame addressed to our current AP.
+                */
+               if (IEEE80211_SEND_MGMT(ic, ic->ic_bss,
+                   IEEE80211_FC0_SUBTYPE_DEAUTH,
+                   IEEE80211_REASON_AUTH_LEAVE) != 0) {
+                       ic->ic_flags &= ~IEEE80211_F_BGSCAN;
+                       goto wakeup;
+               }
+
+               /* Prevent dispatch of additional data frames to hardware. */
+               ic->ic_xflags |= IEEE80211_F_TX_MGMT_ONLY;
+
+               /* 
+                * Install a callback which will switch us to the new AP once
+                * all dispatched frames have been processed by hardware.
+                */
+               IEEE80211_ADDR_COPY(arg->cur_macaddr, curbs->ni_macaddr);
+               IEEE80211_ADDR_COPY(arg->sel_macaddr, selbs->ni_macaddr);
+               ic->ic_bss->ni_unref_arg = arg;
+               ic->ic_bss->ni_unref_arg_size = sizeof(*arg);
+               ic->ic_bss->ni_unref_cb = ieee80211_node_switch_bss;
+               /* F_BGSCAN flag gets cleared in ieee80211_node_join_bss(). */
+               goto wakeup;
+       } else if (selbs == NULL)
+               goto notfound;
+
+       ieee80211_node_join_bss(ic, selbs);
 
  wakeup:
        if (ic->ic_scan_lock & IEEE80211_SCAN_REQUEST) {
@@ -808,6 +955,9 @@ ieee80211_node_cleanup(struct ieee80211c
                ni->ni_rsnie = NULL;
        }
        ieee80211_ba_del(ni);
+       free(ni->ni_unref_arg, M_DEVBUF, ni->ni_unref_arg_size);
+       ni->ni_unref_arg = NULL;
+       ni->ni_unref_arg_size = 0;
 }
 
 void
@@ -835,6 +985,25 @@ ieee80211_node_getrssi(struct ieee80211c
        return ni->ni_rssi;
 }
 
+int
+ieee80211_node_checkrssi(struct ieee80211com *ic,
+    const struct ieee80211_node *ni)
+{
+       uint8_t thres;
+
+       if (ic->ic_max_rssi) {
+               thres = (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) ?
+                   IEEE80211_RSSI_THRES_RATIO_2GHZ :
+                   IEEE80211_RSSI_THRES_RATIO_5GHZ;
+               return ((ni->ni_rssi * 100) / ic->ic_max_rssi >= thres);
+       }
+
+       thres = (IEEE80211_IS_CHAN_2GHZ(ni->ni_chan)) ?
+           IEEE80211_RSSI_THRES_2GHZ :
+           IEEE80211_RSSI_THRES_5GHZ;
+       return (ni->ni_rssi >= (u_int8_t)thres);
+}
+
 void
 ieee80211_setup_node(struct ieee80211com *ic,
        struct ieee80211_node *ni, const u_int8_t *macaddr)
@@ -1177,9 +1346,16 @@ ieee80211_release_node(struct ieee80211c
        DPRINTF(("%s refcnt %u\n", ether_sprintf(ni->ni_macaddr),
            ni->ni_refcnt));
        s = splnet();
-       if (ieee80211_node_decref(ni) == 0 &&
-           ni->ni_state == IEEE80211_STA_COLLECT) {
-               ieee80211_free_node(ic, ni);
+       if (ieee80211_node_decref(ni) == 0) {
+               if (ni->ni_unref_cb) {
+                       (*ni->ni_unref_cb)(ic, ni);
+                       ni->ni_unref_cb = NULL;
+                       /* Freed by callback if necessary: */
+                       ni->ni_unref_arg = NULL;
+                       ni->ni_unref_arg_size = 0;
+               }
+               if (ni->ni_state == IEEE80211_STA_COLLECT)
+                       ieee80211_free_node(ic, ni);
        }
        splx(s);
 }
Index: net80211/ieee80211_node.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_node.h,v
retrieving revision 1.69
diff -u -p -r1.69 ieee80211_node.h
--- net80211/ieee80211_node.h   17 Aug 2017 06:01:05 -0000      1.69
+++ net80211/ieee80211_node.h   29 Nov 2017 17:35:35 -0000
@@ -297,6 +297,12 @@ struct ieee80211_node {
 #define IEEE80211_NODE_SA_QUERY                0x0800  /* SA Query in progress 
*/
 #define IEEE80211_NODE_SA_QUERY_FAILED 0x1000  /* last SA Query failed */
 #define IEEE80211_NODE_RSN_NEW_PTK     0x2000  /* expecting a new PTK */
+
+       /* If not NULL, this function gets called when ni_refcnt hits zero. */
+       void                    (*ni_unref_cb)(struct ieee80211com *,
+                                       struct ieee80211_node *);
+       void *                  ni_unref_arg;
+       size_t                  ni_unref_arg_size;
 };
 
 RBT_HEAD(ieee80211_tree, ieee80211_node);
Index: net80211/ieee80211_proto.c
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_proto.c,v
retrieving revision 1.80
diff -u -p -r1.80 ieee80211_proto.c
--- net80211/ieee80211_proto.c  18 Aug 2017 17:30:12 -0000      1.80
+++ net80211/ieee80211_proto.c  6 Dec 2017 17:07:31 -0000
@@ -853,6 +853,7 @@ ieee80211_newstate(struct ieee80211com *
        ic->ic_state = nstate;                  /* state transition */
        ni = ic->ic_bss;                        /* NB: no reference held */
        ieee80211_set_link_state(ic, LINK_STATE_DOWN);
+       ic->ic_xflags &= ~IEEE80211_F_TX_MGMT_ONLY;
        switch (nstate) {
        case IEEE80211_S_INIT:
                /*
@@ -919,6 +920,8 @@ justcleanup:
                        if (ic->ic_opmode == IEEE80211_M_HOSTAP)
                                timeout_del(&ic->ic_rsn_timeout);
 #endif
+                       timeout_del(&ic->ic_bgscan_timeout);
+                       ic->ic_bgscan_fail = 0;
                        ic->ic_mgt_timer = 0;
                        mq_purge(&ic->ic_mgtq);
                        mq_purge(&ic->ic_pwrsaveq);
@@ -968,6 +971,8 @@ justcleanup:
                                    " rescanning\n", ifp->if_xname,
                                    ether_sprintf(ic->ic_bss->ni_bssid));
                        }
+                       timeout_del(&ic->ic_bgscan_timeout);
+                       ic->ic_bgscan_fail = 0;
                        ieee80211_free_allnodes(ic);
                        /* FALLTHROUGH */
                case IEEE80211_S_AUTH:
@@ -1008,6 +1013,8 @@ justcleanup:
                        }
                        break;
                case IEEE80211_S_RUN:
+                       timeout_del(&ic->ic_bgscan_timeout);
+                       ic->ic_bgscan_fail = 0;
                        switch (mgt) {
                        case IEEE80211_FC0_SUBTYPE_AUTH:
                                IEEE80211_SEND_MGMT(ic, ni,
Index: net80211/ieee80211_var.h
===================================================================
RCS file: /cvs/src/sys/net80211/ieee80211_var.h,v
retrieving revision 1.81
diff -u -p -r1.81 ieee80211_var.h
--- net80211/ieee80211_var.h    6 Nov 2017 11:34:29 -0000       1.81
+++ net80211/ieee80211_var.h    29 Nov 2017 18:06:56 -0000
@@ -57,6 +57,13 @@
 #define        IEEE80211_TXPOWER_MAX   100     /* max power */
 #define        IEEE80211_TXPOWER_MIN   -50     /* kill radio (if possible) */
 
+#define IEEE80211_RSSI_THRES_2GHZ              (-40)   /* in dBm */
+#define IEEE80211_RSSI_THRES_5GHZ              (-50)   /* in dBm */
+#define IEEE80211_RSSI_THRES_RATIO_2GHZ                60      /* in percent */
+#define IEEE80211_RSSI_THRES_RATIO_5GHZ                50      /* in percent */
+
+#define IEEE80211_BGSCAN_FAIL_MAX              360     /* units of 500 msec */
+
 enum ieee80211_phytype {
        IEEE80211_T_DS,                 /* direct sequence spread spectrum */
        IEEE80211_T_OFDM,               /* frequency division multiplexing */
@@ -220,6 +227,9 @@ struct ieee80211com {
                                    struct ieee80211_node *, u_int8_t);
        void                    (*ic_update_htprot)(struct ieee80211com *,
                                        struct ieee80211_node *);
+       int                     (*ic_bgscan_start)(struct ieee80211com *);
+       struct timeout          ic_bgscan_timeout;
+       uint32_t                ic_bgscan_fail;
        u_int8_t                ic_myaddr[IEEE80211_ADDR_LEN];
        struct ieee80211_rateset ic_sup_rates[IEEE80211_MODE_MAX];
        struct ieee80211_channel ic_channels[IEEE80211_CHAN_MAX+1];
@@ -231,6 +241,7 @@ struct ieee80211com {
        u_int                   ic_scan_lock;   /* user-initiated scan */
        u_int8_t                ic_scan_count;  /* count scans */
        u_int32_t               ic_flags;       /* state flags */
+       u_int32_t               ic_xflags;      /* more flags */
        u_int32_t               ic_caps;        /* capabilities */
        u_int16_t               ic_modecaps;    /* set of mode capabilities */
        u_int16_t               ic_curmode;     /* current mode */
@@ -256,6 +267,8 @@ struct ieee80211com {
                                        const struct ieee80211_node *);
        u_int8_t                (*ic_node_getrssi)(struct ieee80211com *,
                                        const struct ieee80211_node *);
+       int                     (*ic_node_checkrssi)(struct ieee80211com *,
+                                       const struct ieee80211_node *);
        u_int8_t                ic_max_rssi;
        struct ieee80211_tree   ic_tree;
        int                     ic_nnodes;      /* length of ic_nnodes */
@@ -352,7 +365,11 @@ extern struct ieee80211com_head ieee8021
 #define IEEE80211_F_MFPR       0x01000000      /* CONF: MFP required */
 #define        IEEE80211_F_HTON        0x02000000      /* CONF: HT enabled */
 #define        IEEE80211_F_PBAR        0x04000000      /* CONF: PBAC required 
*/
+#define        IEEE80211_F_BGSCAN      0x08000000      /* STATUS: background 
scan */
 #define IEEE80211_F_USERMASK   0xf0000000      /* CONF: ioctl flag mask */
+
+/* ic_xflags */
+#define        IEEE80211_F_TX_MGMT_ONLY 0x00000001     /* leave data frames on 
ifq */
 
 /* ic_caps */
 #define        IEEE80211_C_WEP         0x00000001      /* CAPABILITY: WEP 
available */

Reply via email to