iwm(4) background scan

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

iwm(4) background scan

Stefan Sperling-5
This is an initial implementation of background scanning for iwm(4).

The intention is to transparently roam between access points with
the same SSID. This implementation is functionally complete, but it
may still need some tuning.

Background scans are triggered if the received signal strength
indicator for our current AP falls below a fixed threshold.
This is a simple heuristic. We could tweak this heuristic,
and add, or replace it with, other heuristics in the future.

If a better AP is found, we deauth from our current AP and then
try to associate with our new AP of choice. If we can successfully
associate, we roam with virtually no interruption of link.
If association fails, we drop into the usual scan loop as a fallback.

If no better AP is found, background scans will occur less and less
frequently over time. When a better AP is found, this backoff resets.
The longest interval is hard-coded to 3 minutes (if I got my math right).

Most changes are in the net80211 stack. Once this feature has proven
itself with iwm(4) we could add support for other drivers.

Slightly tested in the hack hut.

OK?

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 29 Nov 2017 18:53:27 -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,7 @@ 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_auth(struct iwm_softc *);
 int iwm_deauth(struct iwm_softc *);
 int iwm_assoc(struct iwm_softc *);
@@ -4868,7 +4869,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 +4880,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 +5055,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 +5067,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 +5075,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);
@@ -5465,9 +5479,9 @@ iwm_scan(struct iwm_softc *sc)
  return 0;
 
  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 +5496,28 @@ 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_auth(struct iwm_softc *sc)
 {
  struct ieee80211com *ic = &sc->sc_ic;
@@ -6524,9 +6560,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)
@@ -7772,6 +7809,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 28 Nov 2017 23:26:51 -0000
@@ -72,6 +72,31 @@ 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)
+ 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 +183,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 29 Nov 2017 18:39:24 -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);
+
+ if (bgscan)
+ 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 29 Nov 2017 18:07:38 -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 | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Base Pr1me
Testing environment: 3 APUs with same SSID, 2 5GHz channels and 1 2.4GHz channel.

Test 1: Boot lappy, connected to 2.4GHz spot that I was closest to. Walked to
5GHz spot and waited. It switched fine. Walked to second 5GHz spot and waited.
It did not switch. Waited longer. It did not switch. I had to start manually
scanning APs to get it to switch. I was not checking routing. I should have.

Test 2: Reboot lappy next to 5GHz switch. It connected to the 2.4GHz spot, which
had ~35% less signal strength. Commenced checking web mail. Waited. It switched
to the AP I was standing by. Kept checking email. Walked back by 2.4GHz AP and
waited. It switched. Routing data stopped. Waited. Initiated `route show` and it
just hung there. Had to do a manual scan and routing started working again.

Anything else you'd like tested? The 2.4GHz AP was ~75% by the 5GHz APs, while
the 5GHz APs appeared to be ~35% near the 2.4GHz AP, falling in line with 2.4's
better range.

Thanks,
Tracey

On 11/29/17 12:05 PM, Stefan Sperling wrote:

> This is an initial implementation of background scanning for iwm(4).
>
> The intention is to transparently roam between access points with
> the same SSID. This implementation is functionally complete, but it
> may still need some tuning.
>
> Background scans are triggered if the received signal strength
> indicator for our current AP falls below a fixed threshold.
> This is a simple heuristic. We could tweak this heuristic,
> and add, or replace it with, other heuristics in the future.
>
> If a better AP is found, we deauth from our current AP and then
> try to associate with our new AP of choice. If we can successfully
> associate, we roam with virtually no interruption of link.
> If association fails, we drop into the usual scan loop as a fallback.
>
> If no better AP is found, background scans will occur less and less
> frequently over time. When a better AP is found, this backoff resets.
> The longest interval is hard-coded to 3 minutes (if I got my math right).
>
> Most changes are in the net80211 stack. Once this feature has proven
> itself with iwm(4) we could add support for other drivers.
>
> Slightly tested in the hack hut.
>
> OK?
>
> 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 29 Nov 2017 18:53:27 -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,7 @@ 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_auth(struct iwm_softc *);
>   int iwm_deauth(struct iwm_softc *);
>   int iwm_assoc(struct iwm_softc *);
> @@ -4868,7 +4869,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 +4880,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 +5055,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 +5067,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 +5075,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);
> @@ -5465,9 +5479,9 @@ iwm_scan(struct iwm_softc *sc)
>   return 0;
>  
>   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 +5496,28 @@ 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_auth(struct iwm_softc *sc)
>   {
>   struct ieee80211com *ic = &sc->sc_ic;
> @@ -6524,9 +6560,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)
> @@ -7772,6 +7809,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 28 Nov 2017 23:26:51 -0000
> @@ -72,6 +72,31 @@ 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)
> + 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 +183,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 29 Nov 2017 18:39:24 -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);
> +
> + if (bgscan)
> + 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 29 Nov 2017 18:07:38 -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 | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Stefan Sperling-5
On Tue, Dec 05, 2017 at 09:38:07AM -0700, Base Pr1me wrote:

> Testing environment: 3 APUs with same SSID, 2 5GHz channels and 1 2.4GHz channel.
>
> Test 1: Boot lappy, connected to 2.4GHz spot that I was closest to. Walked
> to 5GHz spot and waited. It switched fine. Walked to second 5GHz spot and
> waited. It did not switch. Waited longer. It did not switch. I had to start
> manually scanning APs to get it to switch. I was not checking routing. I
> should have.
>
> Test 2: Reboot lappy next to 5GHz switch. It connected to the 2.4GHz spot,
> which had ~35% less signal strength. Commenced checking web mail. Waited. It
> switched to the AP I was standing by. Kept checking email. Walked back by
> 2.4GHz AP and waited. It switched. Routing data stopped. Waited. Initiated
> `route show` and it just hung there. Had to do a manual scan and routing
> started working again.

There seems to be an unrelated bug which is rearing its head during
these tests. 'route show' doing nothing indicates there may be some
deeper issue involved. Other parts of the network stack are likely
taking part in the observed behaviour.

Similar issues have been reported before I published this diff, where
an iwm interface got stuck doing nothing until ifconfig down/up.

It's unfortunate that this problem shows up at the same time as I
start working on background scan because it interferes with the
primary goal of this diff (seamless operation).

Still unsure what's going on, but I will try to track it down.
Can you show me a way to reproduce the hangs reliably?

Does it also happen on GENERIC instead of GENERIC.MP?

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Base Pr1me
I'll see if I can suss anything out this week. I'll compile and test on
GENERIC as well.

On Tue, Dec 5, 2017 at 10:13 AM, Stefan Sperling <[hidden email]> wrote:

> On Tue, Dec 05, 2017 at 09:38:07AM -0700, Base Pr1me wrote:
> > Testing environment: 3 APUs with same SSID, 2 5GHz channels and 1 2.4GHz
> channel.
> >
> > Test 1: Boot lappy, connected to 2.4GHz spot that I was closest to.
> Walked
> > to 5GHz spot and waited. It switched fine. Walked to second 5GHz spot and
> > waited. It did not switch. Waited longer. It did not switch. I had to
> start
> > manually scanning APs to get it to switch. I was not checking routing. I
> > should have.
> >
> > Test 2: Reboot lappy next to 5GHz switch. It connected to the 2.4GHz
> spot,
> > which had ~35% less signal strength. Commenced checking web mail.
> Waited. It
> > switched to the AP I was standing by. Kept checking email. Walked back by
> > 2.4GHz AP and waited. It switched. Routing data stopped. Waited.
> Initiated
> > `route show` and it just hung there. Had to do a manual scan and routing
> > started working again.
>
> There seems to be an unrelated bug which is rearing its head during
> these tests. 'route show' doing nothing indicates there may be some
> deeper issue involved. Other parts of the network stack are likely
> taking part in the observed behaviour.
>
> Similar issues have been reported before I published this diff, where
> an iwm interface got stuck doing nothing until ifconfig down/up.
>
> It's unfortunate that this problem shows up at the same time as I
> start working on background scan because it interferes with the
> primary goal of this diff (seamless operation).
>
> Still unsure what's going on, but I will try to track it down.
> Can you show me a way to reproduce the hangs reliably?
>
> Does it also happen on GENERIC instead of GENERIC.MP?
>
Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Base Pr1me
Two questions:

1) In the heuristics for deciding on the AP, what is used to decide the
best AP.
2) Does the background scan eventually timeout completely requiring a
reboot?

Just attached to an AP from an ethernet connection and can't get it to
change, period. So, need to know if I have to keep rebooting in to the
future for testing.

On Tue, Dec 5, 2017 at 10:15 AM, Base Pr1me <[hidden email]> wrote:

> I'll see if I can suss anything out this week. I'll compile and test on
> GENERIC as well.
>
> On Tue, Dec 5, 2017 at 10:13 AM, Stefan Sperling <[hidden email]> wrote:
>
>> On Tue, Dec 05, 2017 at 09:38:07AM -0700, Base Pr1me wrote:
>> > Testing environment: 3 APUs with same SSID, 2 5GHz channels and 1
>> 2.4GHz channel.
>> >
>> > Test 1: Boot lappy, connected to 2.4GHz spot that I was closest to.
>> Walked
>> > to 5GHz spot and waited. It switched fine. Walked to second 5GHz spot
>> and
>> > waited. It did not switch. Waited longer. It did not switch. I had to
>> start
>> > manually scanning APs to get it to switch. I was not checking routing. I
>> > should have.
>> >
>> > Test 2: Reboot lappy next to 5GHz switch. It connected to the 2.4GHz
>> spot,
>> > which had ~35% less signal strength. Commenced checking web mail.
>> Waited. It
>> > switched to the AP I was standing by. Kept checking email. Walked back
>> by
>> > 2.4GHz AP and waited. It switched. Routing data stopped. Waited.
>> Initiated
>> > `route show` and it just hung there. Had to do a manual scan and routing
>> > started working again.
>>
>> There seems to be an unrelated bug which is rearing its head during
>> these tests. 'route show' doing nothing indicates there may be some
>> deeper issue involved. Other parts of the network stack are likely
>> taking part in the observed behaviour.
>>
>> Similar issues have been reported before I published this diff, where
>> an iwm interface got stuck doing nothing until ifconfig down/up.
>>
>> It's unfortunate that this problem shows up at the same time as I
>> start working on background scan because it interferes with the
>> primary goal of this diff (seamless operation).
>>
>> Still unsure what's going on, but I will try to track it down.
>> Can you show me a way to reproduce the hangs reliably?
>>
>> Does it also happen on GENERIC instead of GENERIC.MP?
>>
>
>
Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Stefan Sperling-5
On Tue, Dec 05, 2017 at 02:49:49PM -0700, Base Pr1me wrote:
> Two questions:
>
> 1) In the heuristics for deciding on the AP, what is used to decide the
> best AP.

Signal strength with which beacons from the AP were received is the
final differentiator. AP selection happens only when a scan ends (run
'ifconfig iwm0 debug' and 'tail -f /var/log/messages' to see it live).

Note that a switch happens if our current AP drops below a fixed threshold.
It does not happen if another AP shows up which is also above that threshold,
because then we'd ping-pong between "good enough" APs all the time.

> 2) Does the background scan eventually timeout completely requiring a
> reboot?

No reboot is required. I don't know what happened on your system
to cause this.

> Just attached to an AP from an ethernet connection and can't get it to
> change, period. So, need to know if I have to keep rebooting in to the
> future for testing.

Sorry, but this descriptive statement does not help.
In order to fix a bug, I need *data* I can rely on.

I'll be doing more testing myself today. I hope I'll be able to reproduce
the problem (I am hopeful because apparently several people have seen it).

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Base Pr1me
Sorry for the anecdotal information. I've enabled debugging and if I see
any relevant data, I'll pass it along.

On Wed, Dec 6, 2017 at 4:43 AM, Stefan Sperling <[hidden email]> wrote:

> On Tue, Dec 05, 2017 at 02:49:49PM -0700, Base Pr1me wrote:
> > Two questions:
> >
> > 1) In the heuristics for deciding on the AP, what is used to decide the
> > best AP.
>
> Signal strength with which beacons from the AP were received is the
> final differentiator. AP selection happens only when a scan ends (run
> 'ifconfig iwm0 debug' and 'tail -f /var/log/messages' to see it live).
>
> Note that a switch happens if our current AP drops below a fixed threshold.
> It does not happen if another AP shows up which is also above that
> threshold,
> because then we'd ping-pong between "good enough" APs all the time.
>
> > 2) Does the background scan eventually timeout completely requiring a
> > reboot?
>
> No reboot is required. I don't know what happened on your system
> to cause this.
>
> > Just attached to an AP from an ethernet connection and can't get it to
> > change, period. So, need to know if I have to keep rebooting in to the
> > future for testing.
>
> Sorry, but this descriptive statement does not help.
> In order to fix a bug, I need *data* I can rely on.
>
> I'll be doing more testing myself today. I hope I'll be able to reproduce
> the problem (I am hopeful because apparently several people have seen it).
>
Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Stefan Sperling-5
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 | Threaded
Open this post in threaded view
|

Re: iwm(4) background scan

Stefan Sperling-5
On Wed, Dec 06, 2017 at 06:28:28PM +0100, Stefan Sperling wrote:
> I found one bug which rendered the driver unable to make progress after
> an association failure. This new version fixes that problem for me.

New diff against -current.

The fixes I committed to the driver today make the scan abort code
from the previous diff unnecessary.

Index: dev/pci/if_iwm.c
===================================================================
RCS file: /cvs/src/sys/dev/pci/if_iwm.c,v
retrieving revision 1.219
diff -u -p -r1.219 if_iwm.c
--- dev/pci/if_iwm.c 7 Dec 2017 14:13:05 -0000 1.219
+++ dev/pci/if_iwm.c 7 Dec 2017 14:39:07 -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,7 @@ 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_auth(struct iwm_softc *);
 int iwm_deauth(struct iwm_softc *);
 int iwm_assoc(struct iwm_softc *);
@@ -4868,7 +4869,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 +4880,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 +5055,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 +5067,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 +5075,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);
@@ -5465,9 +5479,9 @@ iwm_scan(struct iwm_softc *sc)
  return 0;
 
  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 +5496,28 @@ 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_auth(struct iwm_softc *sc)
 {
  struct ieee80211com *ic = &sc->sc_ic;
@@ -6539,9 +6575,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)
@@ -7787,6 +7824,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 21:55:18 -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 6 Dec 2017 21:55:18 -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 21:55:18 -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 6 Dec 2017 21:55:18 -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 21:55:18 -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 6 Dec 2017 22:24:51 -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 (-60) /* in dBm */
+#define IEEE80211_RSSI_THRES_5GHZ (-70) /* 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 */