Add background scanning support to iwn(4), as was done for iwm(4) recently.
authorstsp <stsp@openbsd.org>
Thu, 14 Dec 2017 14:21:11 +0000 (14:21 +0000)
committerstsp <stsp@openbsd.org>
Thu, 14 Dec 2017 14:21:11 +0000 (14:21 +0000)
Tested on following devices by various people: 6300, 6200, 6205, 2200, 5100

ok phessler@ tb@

sys/dev/pci/if_iwn.c
sys/dev/pci/if_iwnreg.h
sys/dev/pci/if_iwnvar.h

index a92be22..6c799fb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwn.c,v 1.194 2017/10/26 15:00:28 mpi Exp $        */
+/*     $OpenBSD: if_iwn.c,v 1.195 2017/12/14 14:21:11 stsp Exp $       */
 
 /*-
  * Copyright (c) 2007-2010 Damien Bergamini <damien.bergamini@free.fr>
@@ -222,7 +222,9 @@ int         iwn_config(struct iwn_softc *);
 uint16_t       iwn_get_active_dwell_time(struct iwn_softc *, uint16_t, uint8_t);
 uint16_t       iwn_limit_dwell(struct iwn_softc *, uint16_t);
 uint16_t       iwn_get_passive_dwell_time(struct iwn_softc *, uint16_t);
-int            iwn_scan(struct iwn_softc *, uint16_t);
+int            iwn_scan(struct iwn_softc *, uint16_t, int);
+void           iwn_scan_abort(struct iwn_softc *);
+int            iwn_bgscan(struct ieee80211com *);
 int            iwn_auth(struct iwn_softc *, int);
 int            iwn_run(struct iwn_softc *);
 int            iwn_set_key(struct ieee80211com *, struct ieee80211_node *,
@@ -516,6 +518,7 @@ iwn_attach(struct device *parent, struct device *self, void *aux)
        if_attach(ifp);
        ieee80211_ifattach(ifp);
        ic->ic_node_alloc = iwn_node_alloc;
+       ic->ic_bgscan_start = iwn_bgscan;
        ic->ic_newassoc = iwn_newassoc;
        ic->ic_updateedca = iwn_updateedca;
        ic->ic_set_key = iwn_set_key;
@@ -1761,18 +1764,20 @@ iwn_newstate(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
        struct iwn_node *wn = (void *)ni;
        int error;
 
-       timeout_del(&sc->calib_to);
-
-       if (ic->ic_state == IEEE80211_S_RUN &&
-           (ni->ni_flags & IEEE80211_NODE_HT))
+       if (ic->ic_state == IEEE80211_S_RUN) {
                ieee80211_mira_cancel_timeouts(&wn->mn);
+               timeout_del(&sc->calib_to);
+               sc->calib.state = IWN_CALIB_STATE_INIT;
+               if (sc->sc_flags & IWN_FLAG_BGSCAN)
+                       iwn_scan_abort(sc);
+       }
 
        switch (nstate) {
        case IEEE80211_S_SCAN:
                /* Make the link LED blink while we're scanning. */
                iwn_set_led(sc, IWN_LED_LINK, 10, 10);
 
-               if ((error = iwn_scan(sc, IEEE80211_CHAN_2GHZ)) != 0) {
+               if ((error = iwn_scan(sc, IEEE80211_CHAN_2GHZ, 0)) != 0) {
                        printf("%s: could not initiate scan\n",
                            sc->sc_dev.dv_xname);
                        return error;
@@ -1971,11 +1976,13 @@ iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
        struct ieee80211_frame *wh;
        struct ieee80211_rxinfo rxi;
        struct ieee80211_node *ni;
+       struct ieee80211_channel *bss_chan = NULL;
        struct mbuf *m, *m1;
        struct iwn_rx_stat *stat;
        caddr_t head;
        uint32_t flags;
        int error, len, rssi;
+       uint8_t chan;
 
        if (desc->type == IWN_MPDU_RX_DONE) {
                /* Check for prior RX_PHY notification. */
@@ -2131,6 +2138,16 @@ iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
 
        rssi = ops->get_rssi(stat);
 
+       chan = stat->chan;
+       if (chan > IEEE80211_CHAN_MAX)
+               chan = IEEE80211_CHAN_MAX;
+
+       if (ni == ic->ic_bss) {
+               bss_chan = ni->ni_chan;
+               /* Fix current channel. */
+               ni->ni_chan = &ic->ic_channels[chan];
+       }
+
 #if NBPFILTER > 0
        if (sc->sc_drvbpf != NULL) {
                struct mbuf mb;
@@ -2140,9 +2157,8 @@ iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
                tap->wr_flags = 0;
                if (stat->flags & htole16(IWN_STAT_FLAG_SHPREAMBLE))
                        tap->wr_flags |= IEEE80211_RADIOTAP_F_SHORTPRE;
-               tap->wr_chan_freq =
-                   htole16(ic->ic_channels[stat->chan].ic_freq);
-               chan_flags = ic->ic_channels[stat->chan].ic_flags;
+               tap->wr_chan_freq = htole16(ic->ic_channels[chan].ic_freq);
+               chan_flags = ic->ic_channels[chan].ic_flags;
                if (ic->ic_curmode != IEEE80211_MODE_11N)
                        chan_flags &= ~IEEE80211_CHAN_HT;
                tap->wr_chan_flags = htole16(chan_flags);
@@ -2187,6 +2203,10 @@ iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
        rxi.rxi_tstamp = 0;     /* unused */
        ieee80211_input(ifp, m, ni, &rxi);
 
+       /* Restore BSS channel. */
+       if (ni == ic->ic_bss)
+               ni->ni_chan = bss_chan;
+
        /* Node is no longer needed. */
        ieee80211_release_node(ic, ni);
 }
@@ -2586,6 +2606,9 @@ iwn_notif_intr(struct iwn_softc *sc)
                        DPRINTFN(2, ("scanning channel %d status %x\n",
                            scan->chan, letoh32(scan->status)));
 
+                       if (sc->sc_flags & IWN_FLAG_BGSCAN)
+                               break;
+
                        /* Fix current channel. */
                        ic->ic_bss->ni_chan = &ic->ic_channels[scan->chan];
                        break;
@@ -2602,14 +2625,18 @@ iwn_notif_intr(struct iwn_softc *sc)
 
                        if (scan->status == 1 && scan->chan <= 14 &&
                            (sc->sc_flags & IWN_FLAG_HAS_5GHZ)) {
+                               int error;
                                /*
                                 * We just finished scanning 2GHz channels,
                                 * start scanning 5GHz ones.
                                 */
-                               if (iwn_scan(sc, IEEE80211_CHAN_5GHZ) == 0)
+                               error = iwn_scan(sc, IEEE80211_CHAN_5GHZ,
+                                   (sc->sc_flags & IWN_FLAG_BGSCAN) ? 1 : 0);
+                               if (error == 0)
                                        break;
                        }
                        ieee80211_end_scan(ifp);
+                       sc->sc_flags &= ~IWN_FLAG_BGSCAN;
                        break;
                }
                case IWN5000_CALIBRATION_RESULT:
@@ -4642,9 +4669,8 @@ iwn_limit_dwell(struct iwn_softc *sc, uint16_t dwell_time)
         * XXX Yes, the math should take into account that bintval
         * is 1.024mS, not 1mS..
         */
-       if (bintval > 0) {
+       if (ic->ic_state == IEEE80211_S_RUN && bintval > 0)
                return (MIN(IWN_PASSIVE_DWELL_BASE, ((bintval * 85) / 100)));
-       }
 
        /* No association context? Default */
        return (IWN_PASSIVE_DWELL_BASE);
@@ -4665,7 +4691,7 @@ iwn_get_passive_dwell_time(struct iwn_softc *sc, uint16_t flags)
 }
 
 int
-iwn_scan(struct iwn_softc *sc, uint16_t flags)
+iwn_scan(struct iwn_softc *sc, uint16_t flags, int bgscan)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwn_scan_hdr *hdr;
@@ -4695,6 +4721,18 @@ iwn_scan(struct iwn_softc *sc, uint16_t flags)
        hdr->quiet_time = htole16(10);          /* timeout in milliseconds */
        hdr->quiet_threshold = htole16(1);      /* min # of packets */
 
+       if (bgscan) {
+               int bintval;
+
+               /* Set maximum off-channel time. */
+               hdr->max_out = htole32(200 * 1024);
+
+               /* Configure scan pauses which service on-channel traffic. */
+               bintval = ic->ic_bss->ni_intval ? ic->ic_bss->ni_intval : 100;
+               hdr->pause_scan = htole32(((100 / bintval) << 22) |
+                   ((100 % bintval) * 1024));
+       }
+
        /* Select antennas for scanning. */
        rxchain =
            IWN_RXCHAIN_VALID(sc->rxchainmask) |
@@ -4852,11 +4890,39 @@ iwn_scan(struct iwn_softc *sc, uint16_t flags)
        hdr->len = htole16(buflen);
 
        DPRINTF(("sending scan command nchan=%d\n", hdr->nchan));
+       if (bgscan)
+               sc->sc_flags |= IWN_FLAG_BGSCAN;
        error = iwn_cmd(sc, IWN_CMD_SCAN, buf, buflen, 1);
+       if (bgscan && error)
+               sc->sc_flags &= ~IWN_FLAG_BGSCAN;
        free(buf, M_DEVBUF, IWN_SCAN_MAXSZ);
        return error;
 }
 
+void
+iwn_scan_abort(struct iwn_softc *sc)
+{
+       iwn_cmd(sc, IWN_CMD_SCAN_ABORT, NULL, 0, 1);
+
+       /* XXX Cannot wait for status response in interrupt context. */
+       DELAY(100);
+
+       sc->sc_flags &= ~IWN_FLAG_BGSCAN;
+}
+
+int
+iwn_bgscan(struct ieee80211com *ic) 
+{
+       struct iwn_softc *sc = ic->ic_softc;
+       int error; 
+
+       error = iwn_scan(sc, IEEE80211_CHAN_2GHZ, 1);
+       if (error)
+               printf("%s: could not initiate background scan\n",
+                   sc->sc_dev.dv_xname);
+       return error;
+}
+
 int
 iwn_auth(struct iwn_softc *sc, int arg)
 {
@@ -4864,6 +4930,9 @@ iwn_auth(struct iwn_softc *sc, int arg)
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_node *ni = ic->ic_bss;
        int error, ridx;
+       int bss_switch = 
+           (!IEEE80211_ADDR_EQ(sc->bss_node_addr, etheranyaddr) &&
+           !IEEE80211_ADDR_EQ(sc->bss_node_addr, ni->ni_macaddr));
 
        /* Update adapter configuration. */
        IEEE80211_ADDR_COPY(sc->rxon.bssid, ni->ni_bssid);
@@ -4878,8 +4947,12 @@ iwn_auth(struct iwn_softc *sc, int arg)
        }
        if (ic->ic_flags & IEEE80211_F_SHSLOT)
                sc->rxon.flags |= htole32(IWN_RXON_SHSLOT);
+       else
+               sc->rxon.flags &= ~htole32(IWN_RXON_SHSLOT);
        if (ic->ic_flags & IEEE80211_F_SHPREAMBLE)
                sc->rxon.flags |= htole32(IWN_RXON_SHPREAMBLE);
+       else
+               sc->rxon.flags &= ~htole32(IWN_RXON_SHPREAMBLE);
        switch (ic->ic_curmode) {
        case IEEE80211_MODE_11A:
                sc->rxon.cck_mask  = 0;
@@ -4923,12 +4996,19 @@ iwn_auth(struct iwn_softc *sc, int arg)
         * Make sure the firmware gets to see a beacon before we send
         * the auth request. Otherwise the Tx attempt can fail due to
         * the firmware's built-in regulatory domain enforcement.
+        * Delaying here for every incoming deauth frame can result in a DoS.
         * Don't delay if we're here because of an incoming frame (arg != -1)
         * or if we're already waiting for a response (ic_mgt_timer != 0).
+        * If we are switching APs after a background scan then net80211 has
+        * just faked the reception of a deauth frame from our old AP, so it
+        * is safe to delay in that case.
         */
-       if (arg == -1 && ic->ic_mgt_timer == 0)
+       if ((arg == -1 || bss_switch) && ic->ic_mgt_timer == 0)
                DELAY(ni->ni_intval * 3 * IEEE80211_DUR_TU);
 
+       /* We can now clear the cached address of our previous AP. */
+       memset(sc->bss_node_addr, 0, sizeof(sc->bss_node_addr));
+
        return 0;
 }
 
@@ -4938,6 +5018,7 @@ iwn_run(struct iwn_softc *sc)
        struct iwn_ops *ops = &sc->ops;
        struct ieee80211com *ic = &sc->sc_ic;
        struct ieee80211_node *ni = ic->ic_bss;
+       struct iwn_node *wn = (void *)ni;
        struct iwn_node_info node;
        int error;
 
@@ -5028,6 +5109,10 @@ iwn_run(struct iwn_softc *sc)
                printf("%s: could not add BSS node\n", sc->sc_dev.dv_xname);
                return error;
        }
+
+       /* Cache address of AP in case it changes after a background scan. */
+       IEEE80211_ADDR_COPY(sc->bss_node_addr, ni->ni_macaddr);
+
        DPRINTF(("setting link quality for node %d\n", node.id));
        if ((error = iwn_set_link_quality(sc, ni)) != 0) {
                printf("%s: could not setup link quality for node %d\n",
@@ -5045,10 +5130,7 @@ iwn_run(struct iwn_softc *sc)
        sc->calib_cnt = 0;
        timeout_add_msec(&sc->calib_to, 500);
 
-       if (ni->ni_flags & IEEE80211_NODE_HT) {
-               struct iwn_node *wn = (void *)ni;
-               ieee80211_mira_node_init(&wn->mn);
-       }
+       ieee80211_mira_node_init(&wn->mn);
 
        /* Link LED always on while associated. */
        iwn_set_led(sc, IWN_LED_LINK, 0, 1);
@@ -6444,6 +6526,8 @@ iwn_init(struct ifnet *ifp)
        struct ieee80211com *ic = &sc->sc_ic;
        int error;
 
+       memset(sc->bss_node_addr, 0, sizeof(sc->bss_node_addr));
+
        if ((error = iwn_hw_prepare(sc)) != 0) {
                printf("%s: hardware not ready\n", sc->sc_dev.dv_xname);
                goto fail;
index b4569ea..ded19f3 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwnreg.h,v 1.54 2017/08/12 09:13:06 stsp Exp $     */
+/*     $OpenBSD: if_iwnreg.h,v 1.55 2017/12/14 14:21:11 stsp Exp $     */
 
 /*-
  * Copyright (c) 2007, 2008
@@ -437,6 +437,7 @@ struct iwn_tx_cmd {
 #define IWN5000_CMD_CALIB_CONFIG       101
 #define IWN_CMD_SET_POWER_MODE         119
 #define IWN_CMD_SCAN                   128
+#define IWN_CMD_SCAN_ABORT             129
 #define IWN_CMD_TXPOWER_DBM            149
 #define IWN_CMD_TXPOWER                        151
 #define IWN5000_CMD_TX_ANT_CONFIG      152
@@ -823,8 +824,8 @@ struct iwn_scan_hdr {
        uint16_t        quiet_threshold;
        uint16_t        crc_threshold;
        uint16_t        rxchain;
-       uint32_t        max_svc;        /* background scans */
-       uint32_t        pause_svc;      /* background scans */
+       uint32_t        max_out;        /* (in usec) background scans */
+       uint32_t        pause_scan;     /* (in usec) background scans */
        uint32_t        flags;
        uint32_t        filter;
 
index 019ca96..82a3693 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: if_iwnvar.h,v 1.32 2016/12/07 15:48:44 stsp Exp $     */
+/*     $OpenBSD: if_iwnvar.h,v 1.33 2017/12/14 14:21:11 stsp Exp $     */
 
 /*-
  * Copyright (c) 2007, 2008
@@ -202,6 +202,7 @@ struct iwn_softc {
 #define IWN_FLAG_HAS_11N       (1 << 6)
 #define IWN_FLAG_ENH_SENS      (1 << 7)
 #define IWN_FLAG_ADV_BT_COEX   (1 << 8)
+#define IWN_FLAG_BGSCAN                (1 << 9)
 
        uint8_t                 hw_type;
 
@@ -257,6 +258,8 @@ struct iwn_softc {
        struct iwn_calib_info   calibcmd[5];
        uint32_t                errptr;
 
+       uint8_t                 bss_node_addr[IEEE80211_ADDR_LEN];
+
        struct iwn_rx_stat      last_rx_stat;
        int                     last_rx_valid;
 #define IWN_LAST_RX_VALID      0x01