From e38d2b44a40bbf911fa9bf913fbed33e0e9c556d Mon Sep 17 00:00:00 2001 From: stsp Date: Thu, 14 Dec 2017 14:21:11 +0000 Subject: [PATCH] Add background scanning support to iwn(4), as was done for iwm(4) recently. Tested on following devices by various people: 6300, 6200, 6205, 2200, 5100 ok phessler@ tb@ --- sys/dev/pci/if_iwn.c | 122 +++++++++++++++++++++++++++++++++------- sys/dev/pci/if_iwnreg.h | 7 ++- sys/dev/pci/if_iwnvar.h | 5 +- 3 files changed, 111 insertions(+), 23 deletions(-) diff --git a/sys/dev/pci/if_iwn.c b/sys/dev/pci/if_iwn.c index a92be2287ac..6c799fbf996 100644 --- a/sys/dev/pci/if_iwn.c +++ b/sys/dev/pci/if_iwn.c @@ -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 @@ -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; diff --git a/sys/dev/pci/if_iwnreg.h b/sys/dev/pci/if_iwnreg.h index b4569eacd5b..ded19f3c037 100644 --- a/sys/dev/pci/if_iwnreg.h +++ b/sys/dev/pci/if_iwnreg.h @@ -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; diff --git a/sys/dev/pci/if_iwnvar.h b/sys/dev/pci/if_iwnvar.h index 019ca96f8bd..82a3693439d 100644 --- a/sys/dev/pci/if_iwnvar.h +++ b/sys/dev/pci/if_iwnvar.h @@ -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 -- 2.20.1