I would like to try this again: In iwm(4), process more than one frame per Rx interrupt, and enable monitor mode.
Monitor mode triggers "unhandled fimware response" errors without multi-Rx support. We have seen these infamous errors in other contexts as well. It is possible that the firmware decides to use multi-Rx in such cases which would of course confuse our driver. The fact is that Linux has not used single-Rx mode for years. One of their developers told me that Intel has stopped testing it entirely. The current code isn't future-proof and is likely to break with newer versions of firmware and/or hardware. These changes were attempted and reverted twice already. Last time because they were causing throughput issues (and I now see why). I have rebased my old diff and tweaked it slightly to avoid an unneeded mbuf copy and reworked all the offset/length calculations once again. Works fine here against several APs with no observable drop in throughput. Please test this on as many iwm devices as possible. Thanks! diff refs/heads/master refs/heads/iwm-multirx blob - 42cb00c62cc455d871da09560dcf82876db2f4c0 blob + c9a4dc36c3a7cf09c9836141b6a966370ac94824 --- sys/dev/pci/if_iwm.c +++ sys/dev/pci/if_iwm.c @@ -367,6 +367,7 @@ int iwm_get_signal_strength(struct iwm_softc *, struct void iwm_rx_rx_phy_cmd(struct iwm_softc *, struct iwm_rx_packet *, struct iwm_rx_data *); int iwm_get_noise(const struct iwm_statistics_rx_non_phy *); +int iwm_rx_frame(struct iwm_softc *, struct mbuf *, uint32_t); int iwm_ccmp_decap(struct iwm_softc *, struct mbuf *, struct ieee80211_node *); void iwm_rx_rx_mpdu(struct iwm_softc *, struct iwm_rx_packet *, @@ -431,7 +432,7 @@ uint8_t iwm_ridx2rate(struct ieee80211_rateset *, int) int iwm_rval2ridx(int); void iwm_ack_rates(struct iwm_softc *, struct iwm_node *, int *, int *); void iwm_mac_ctxt_cmd_common(struct iwm_softc *, struct iwm_node *, - struct iwm_mac_ctx_cmd *, uint32_t, int); + struct iwm_mac_ctx_cmd *, uint32_t); void iwm_mac_ctxt_cmd_fill_sta(struct iwm_softc *, struct iwm_node *, struct iwm_mac_data_sta *, int); int iwm_mac_ctxt_cmd(struct iwm_softc *, struct iwm_node *, uint32_t, int); @@ -476,6 +477,9 @@ const char *iwm_desc_lookup(uint32_t); void iwm_nic_error(struct iwm_softc *); void iwm_nic_umac_error(struct iwm_softc *); #endif +void iwm_rx_mpdu(struct iwm_softc *, struct mbuf *, size_t); +int iwm_rx_pkt_valid(struct iwm_rx_packet *); +void iwm_rx_pkt(struct iwm_softc *, struct iwm_rx_data *); void iwm_notif_intr(struct iwm_softc *); int iwm_intr(void *); int iwm_match(struct device *, void *, void *); @@ -1729,7 +1733,6 @@ iwm_nic_rx_init(struct iwm_softc *sc) IWM_FH_RCSR_RX_CONFIG_CHNL_EN_ENABLE_VAL | IWM_FH_RCSR_CHNL0_RX_IGNORE_RXF_EMPTY | /* HW bug */ IWM_FH_RCSR_CHNL0_RX_CONFIG_IRQ_DEST_INT_HOST_VAL | - IWM_FH_RCSR_CHNL0_RX_CONFIG_SINGLE_FRAME_MSK | (IWM_RX_RB_TIMEOUT << IWM_FH_RCSR_RX_CONFIG_REG_IRQ_RBTH_POS) | IWM_FH_RCSR_RX_CONFIG_REG_VAL_RB_SIZE_4K | IWM_RX_QUEUE_SIZE_LOG << IWM_FH_RCSR_RX_CONFIG_RBDCB_SIZE_POS); @@ -3514,57 +3517,23 @@ iwm_ccmp_decap(struct iwm_softc *sc, struct mbuf *m, s return 0; } -void -iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_packet *pkt, - struct iwm_rx_data *data) +int +iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, uint32_t rx_pkt_status) { struct ieee80211com *ic = &sc->sc_ic; - struct ifnet *ifp = IC2IFP(ic); struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_rxinfo rxi; struct ieee80211_channel *bss_chan; - struct mbuf *m; struct iwm_rx_phy_info *phy_info; - struct iwm_rx_mpdu_res_start *rx_res; int device_timestamp; - uint32_t len; - uint32_t rx_pkt_status; int rssi, chanidx; uint8_t saved_bssid[IEEE80211_ADDR_LEN] = { 0 }; - bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE, - BUS_DMASYNC_POSTREAD); - phy_info = &sc->sc_last_phy_info; - rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data; - wh = (struct ieee80211_frame *)(pkt->data + sizeof(*rx_res)); - len = le16toh(rx_res->byte_count); - if (len < IEEE80211_MIN_LEN) { - ic->ic_stats.is_rx_tooshort++; - IC2IFP(ic)->if_ierrors++; - return; - } - if (len > IWM_RBUF_SIZE - sizeof(*rx_res)) { - IC2IFP(ic)->if_ierrors++; - return; - } - rx_pkt_status = le32toh(*(uint32_t *)(pkt->data + - sizeof(*rx_res) + len)); - if (__predict_false(phy_info->cfg_phy_cnt > 20)) - return; + return EINVAL; - if (!(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) || - !(rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK)) - return; /* drop */ - - m = data->m; - if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur) != 0) - return; - m->m_data = pkt->data + sizeof(*rx_res); - m->m_pkthdr.len = m->m_len = len; - device_timestamp = le32toh(phy_info->system_timestamp); if (sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_RX_ENERGY_API) { @@ -3579,6 +3548,7 @@ iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_pac if (chanidx < 0 || chanidx >= nitems(ic->ic_channels)) chanidx = ieee80211_chan2ieee(ic, ic->ic_ibss_chan); + wh = mtod(m, struct ieee80211_frame *); ni = ieee80211_find_rxnode(ic, wh); if (ni == ic->ic_bss) { /* @@ -3603,7 +3573,6 @@ iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_pac if ((rx_pkt_status & IWM_RX_MPDU_RES_STATUS_SEC_ENC_MSK) != IWM_RX_MPDU_RES_STATUS_SEC_CCM_ENC) { ic->ic_stats.is_ccmp_dec_errs++; - ifp->if_ierrors++; m_freem(m); goto done; } @@ -3614,12 +3583,10 @@ iwm_rx_rx_mpdu(struct iwm_softc *sc, struct iwm_rx_pac (IWM_RX_MPDU_RES_STATUS_DEC_DONE | IWM_RX_MPDU_RES_STATUS_MIC_OK)) { ic->ic_stats.is_ccmp_dec_errs++; - ifp->if_ierrors++; m_freem(m); goto done; } if (iwm_ccmp_decap(sc, m, ni) != 0) { - ifp->if_ierrors++; m_freem(m); goto done; } @@ -3691,6 +3658,8 @@ done: if (ni == ic->ic_bss && IEEE80211_ADDR_EQ(saved_bssid, ni->ni_macaddr)) ni->ni_chan = bss_chan; ieee80211_release_node(ic, ni); + + return 0; } void @@ -4666,6 +4635,7 @@ void iwm_power_build_cmd(struct iwm_softc *sc, struct iwm_node *in, struct iwm_mac_power_cmd *cmd) { + struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = &in->in_ni; int dtim_period, dtim_msec, keep_alive; @@ -4687,7 +4657,8 @@ iwm_power_build_cmd(struct iwm_softc *sc, struct iwm_n keep_alive = roundup(keep_alive, 1000) / 1000; cmd->keep_alive_seconds = htole16(keep_alive); - cmd->flags = htole16(IWM_POWER_FLAGS_POWER_SAVE_ENA_MSK); + if (ic->ic_opmode != IEEE80211_M_MONITOR) + cmd->flags = htole16(IWM_POWER_FLAGS_POWER_SAVE_ENA_MSK); } int @@ -4714,13 +4685,15 @@ iwm_power_mac_update_mode(struct iwm_softc *sc, struct int iwm_power_update_device(struct iwm_softc *sc) { - struct iwm_device_power_cmd cmd = { - .flags = htole16(IWM_DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK), - }; + struct iwm_device_power_cmd cmd = { }; + struct ieee80211com *ic = &sc->sc_ic; if (!(sc->sc_capaflags & IWM_UCODE_TLV_FLAGS_DEVICE_PS_CMD)) return 0; + if (ic->ic_opmode != IEEE80211_M_MONITOR) + cmd.flags = htole16(IWM_DEVICE_POWER_FLAGS_POWER_SAVE_ENA_MSK); + return iwm_send_cmd_pdu(sc, IWM_POWER_TABLE_CMD, 0, sizeof(cmd), &cmd); } @@ -4782,7 +4755,12 @@ iwm_add_sta_cmd(struct iwm_softc *sc, struct iwm_node add_sta_cmd.tfd_queue_msk |= htole32(1 << iwm_ac_to_tx_fifo[ac]); } - IEEE80211_ADDR_COPY(&add_sta_cmd.addr, in->in_ni.ni_bssid); + if (ic->ic_opmode == IEEE80211_M_MONITOR) + IEEE80211_ADDR_COPY(&add_sta_cmd.addr, + etherbroadcastaddr); + else + IEEE80211_ADDR_COPY(&add_sta_cmd.addr, + in->in_ni.ni_bssid); } add_sta_cmd.add_modify = update ? 1 : 0; add_sta_cmd.station_flags_msk @@ -5452,7 +5430,7 @@ iwm_ack_rates(struct iwm_softc *sc, struct iwm_node *i void iwm_mac_ctxt_cmd_common(struct iwm_softc *sc, struct iwm_node *in, - struct iwm_mac_ctx_cmd *cmd, uint32_t action, int assoc) + struct iwm_mac_ctx_cmd *cmd, uint32_t action) { #define IWM_EXP2(x) ((1 << (x)) - 1) /* CWmin = 2^ECWmin - 1 */ struct ieee80211com *ic = &sc->sc_ic; @@ -5464,12 +5442,21 @@ iwm_mac_ctxt_cmd_common(struct iwm_softc *sc, struct i in->in_color)); cmd->action = htole32(action); - cmd->mac_type = htole32(IWM_FW_MAC_TYPE_BSS_STA); + if (ic->ic_opmode == IEEE80211_M_MONITOR) + cmd->mac_type = htole32(IWM_FW_MAC_TYPE_LISTENER); + else if (ic->ic_opmode == IEEE80211_M_STA) + cmd->mac_type = htole32(IWM_FW_MAC_TYPE_BSS_STA); + else + panic("unsupported operating mode %d\n", ic->ic_opmode); cmd->tsf_id = htole32(IWM_TSF_ID_A); IEEE80211_ADDR_COPY(cmd->node_addr, ic->ic_myaddr); - IEEE80211_ADDR_COPY(cmd->bssid_addr, ni->ni_bssid); + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + IEEE80211_ADDR_COPY(cmd->bssid_addr, etherbroadcastaddr); + return; + } + IEEE80211_ADDR_COPY(cmd->bssid_addr, ni->ni_bssid); iwm_ack_rates(sc, in, &cck_ack_rates, &ofdm_ack_rates); cmd->cck_rates = htole32(cck_ack_rates); cmd->ofdm_rates = htole32(ofdm_ack_rates); @@ -5560,6 +5547,7 @@ int iwm_mac_ctxt_cmd(struct iwm_softc *sc, struct iwm_node *in, uint32_t action, int assoc) { + struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = &in->in_ni; struct iwm_mac_ctx_cmd cmd; int active = (sc->sc_flags & IWM_FLAG_MAC_ACTIVE); @@ -5571,11 +5559,19 @@ iwm_mac_ctxt_cmd(struct iwm_softc *sc, struct iwm_node memset(&cmd, 0, sizeof(cmd)); - iwm_mac_ctxt_cmd_common(sc, in, &cmd, action, assoc); + iwm_mac_ctxt_cmd_common(sc, in, &cmd, action); - /* Allow beacons to pass through as long as we are not associated or we - * do not have dtim period information */ - if (!assoc || !ni->ni_associd || !ni->ni_dtimperiod) + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + cmd.filter_flags |= htole32(IWM_MAC_FILTER_IN_PROMISC | + IWM_MAC_FILTER_IN_CONTROL_AND_MGMT | + IWM_MAC_FILTER_IN_BEACON | + IWM_MAC_FILTER_IN_PROBE_REQUEST | + IWM_MAC_FILTER_IN_CRC32); + } else if (!assoc || !ni->ni_associd || !ni->ni_dtimperiod) + /* + * Allow beacons to pass through as long as we are not + * associated or we do not have dtim period information. + */ cmd.filter_flags |= htole32(IWM_MAC_FILTER_IN_BEACON); else iwm_mac_ctxt_cmd_fill_sta(sc, in, &cmd.sta, assoc); @@ -5792,7 +5788,10 @@ iwm_auth(struct iwm_softc *sc) splassert(IPL_NET); - sc->sc_phyctxt[0].channel = in->in_ni.ni_chan; + if (ic->ic_opmode == IEEE80211_M_MONITOR) + sc->sc_phyctxt[0].channel = ic->ic_ibss_chan; + else + sc->sc_phyctxt[0].channel = in->in_ni.ni_chan; err = iwm_phy_ctxt_cmd(sc, &sc->sc_phyctxt[0], 1, 1, IWM_FW_CTXT_ACTION_MODIFY, 0); if (err) { @@ -5826,6 +5825,9 @@ iwm_auth(struct iwm_softc *sc) } sc->sc_flags |= IWM_FLAG_STA_ACTIVE; + if (ic->ic_opmode == IEEE80211_M_MONITOR) + return 0; + /* * Prevent the FW from wandering off channel during association * by "protecting" the session with a time event. @@ -5956,8 +5958,16 @@ iwm_run(struct iwm_softc *sc) splassert(IPL_NET); + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + /* Add a MAC context and a sniffing STA. */ + err = iwm_auth(sc); + if (err) + return err; + } + /* Configure Rx chains for MIMO. */ - if ((in->in_ni.ni_flags & IEEE80211_NODE_HT) && + if ((ic->ic_opmode == IEEE80211_M_MONITOR || + (in->in_ni.ni_flags & IEEE80211_NODE_HT)) && !sc->sc_nvm.sku_cap_mimo_disable) { err = iwm_phy_ctxt_cmd(sc, &sc->sc_phyctxt[0], 2, 2, IWM_FW_CTXT_ACTION_MODIFY, 0); @@ -6025,6 +6035,11 @@ iwm_run(struct iwm_softc *sc) ieee80211_amrr_node_init(&sc->sc_amrr, &in->in_amn); ieee80211_mira_node_init(&in->in_mn); + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + iwm_led_blink_start(sc); + return 0; + } + /* Start at lowest available bit-rate, AMRR will raise. */ in->in_ni.ni_txrate = 0; in->in_ni.ni_txmcs = 0; @@ -6044,6 +6059,9 @@ iwm_run_stop(struct iwm_softc *sc) splassert(IPL_NET); + if (ic->ic_opmode == IEEE80211_M_MONITOR) + iwm_led_blink_stop(sc); + err = iwm_sf_config(sc, IWM_SF_INIT_OFF); if (err) return err; @@ -6715,6 +6733,12 @@ iwm_init(struct ifnet *ifp) ifq_clr_oactive(&ifp->if_snd); ifp->if_flags |= IFF_RUNNING; + if (ic->ic_opmode == IEEE80211_M_MONITOR) { + ic->ic_bss->ni_chan = ic->ic_ibss_chan; + ieee80211_new_state(ic, IEEE80211_S_RUN, -1); + return 0; + } + ieee80211_begin_scan(ifp); /* @@ -7194,37 +7218,104 @@ do { \ #define ADVANCE_RXQ(sc) (sc->rxq.cur = (sc->rxq.cur + 1) % IWM_RX_RING_COUNT); void -iwm_notif_intr(struct iwm_softc *sc) +iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, size_t maxlen) { - uint16_t hw; + struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = IC2IFP(ic); + struct iwm_rx_packet *pkt; + struct iwm_rx_mpdu_res_start *rx_res; + uint16_t len; + uint32_t rx_pkt_status; + int rxfail; - bus_dmamap_sync(sc->sc_dmat, sc->rxq.stat_dma.map, - 0, sc->rxq.stat_dma.size, BUS_DMASYNC_POSTREAD); + pkt = mtod(m, struct iwm_rx_packet *); + rx_res = (struct iwm_rx_mpdu_res_start *)pkt->data; + len = le16toh(rx_res->byte_count); + if (len < IEEE80211_MIN_LEN) { + ic->ic_stats.is_rx_tooshort++; + IC2IFP(ic)->if_ierrors++; + m_freem(m); + return; + } + if (len + sizeof(*rx_res) + sizeof(rx_pkt_status) > maxlen || + len > IEEE80211_MAX_LEN) { + IC2IFP(ic)->if_ierrors++; + m_freem(m); + return; + } - hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff; - hw &= (IWM_RX_RING_COUNT - 1); - while (sc->rxq.cur != hw) { - struct iwm_rx_data *data = &sc->rxq.data[sc->rxq.cur]; - struct iwm_rx_packet *pkt; - int qid, idx, code, handled = 1; + memcpy(&rx_pkt_status, pkt->data + sizeof(*rx_res) + len, + sizeof(rx_pkt_status)); + rx_pkt_status = le32toh(rx_pkt_status); + rxfail = ((rx_pkt_status & IWM_RX_MPDU_RES_STATUS_CRC_OK) == 0 || + (rx_pkt_status & IWM_RX_MPDU_RES_STATUS_OVERRUN_OK) == 0); + if (rxfail) { + ifp->if_ierrors++; + m_freem(m); + return; + } - bus_dmamap_sync(sc->sc_dmat, data->map, 0, sizeof(*pkt), - BUS_DMASYNC_POSTREAD); - pkt = mtod(data->m, struct iwm_rx_packet *); + /* Extract the 802.11 frame. */ + m->m_data = (caddr_t)pkt->data + sizeof(*rx_res); + m->m_pkthdr.len = m->m_len = len; + if (iwm_rx_frame(sc, m, rx_pkt_status) != 0) { + ifp->if_ierrors++; + m_freem(m); + } +} +int +iwm_rx_pkt_valid(struct iwm_rx_packet *pkt) +{ + int qid, idx, code; + + code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code); + qid = pkt->hdr.qid & ~0x80; + idx = pkt->hdr.idx; + + if ((code == 0 && qid == 0 && idx == 0) || + pkt->len_n_flags == htole32(IWM_FH_RSCSR_FRAME_INVALID)) + return 0; + + return 1; +} + +void +iwm_rx_pkt(struct iwm_softc *sc, struct iwm_rx_data *data) +{ + struct ifnet *ifp = IC2IFP(&sc->sc_ic); + struct iwm_rx_packet *pkt, *nextpkt; + uint32_t offset = 0, nextoff = 0, nmpdu = 0, len; + struct mbuf *m0, *m; + const size_t minsz = sizeof(pkt->len_n_flags) + sizeof(pkt->hdr); + size_t remain = IWM_RBUF_SIZE; + int qid, idx, code, handled = 1; + + bus_dmamap_sync(sc->sc_dmat, data->map, 0, IWM_RBUF_SIZE, + BUS_DMASYNC_POSTREAD); + + m0 = data->m; + while (m0 && offset + minsz < IWM_RBUF_SIZE) { + pkt = (struct iwm_rx_packet *)(m0->m_data + offset); + + if (!iwm_rx_pkt_valid(pkt)) + break; + + code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code); qid = pkt->hdr.qid & ~0x80; idx = pkt->hdr.idx; + len = sizeof(pkt->len_n_flags) + iwm_rx_packet_len(pkt); + if (len < sizeof(pkt->hdr) || + len > (IWM_RBUF_SIZE - offset - minsz)) + break; - code = IWM_WIDE_ID(pkt->hdr.flags, pkt->hdr.code); - - /* - * randomly get these from the firmware, no idea why. - * they at least seem harmless, so just ignore them for now - */ - if (__predict_false((pkt->hdr.code == 0 && qid == 0 && idx == 0) - || pkt->len_n_flags == htole32(0x55550000))) { - ADVANCE_RXQ(sc); - continue; + if (code == IWM_REPLY_RX_MPDU_CMD && ++nmpdu == 1) { + /* Take mbuf m0 off the RX ring. */ + if (iwm_rx_addbuf(sc, IWM_RBUF_SIZE, sc->rxq.cur)) { + ifp->if_ierrors++; + break; + } + KASSERT(data->m != m0); } switch (code) { @@ -7232,10 +7323,40 @@ iwm_notif_intr(struct iwm_softc *sc) iwm_rx_rx_phy_cmd(sc, pkt, data); break; - case IWM_REPLY_RX_MPDU_CMD: - iwm_rx_rx_mpdu(sc, pkt, data); - break; + case IWM_REPLY_RX_MPDU_CMD: { + nextoff = offset + + roundup(len, IWM_FH_RSCSR_FRAME_ALIGN); + nextpkt = (struct iwm_rx_packet *) + (m0->m_data + nextoff); + if (nextoff + minsz >= IWM_RBUF_SIZE || + !iwm_rx_pkt_valid(nextpkt)) { + /* No need to copy last frame in buffer. */ + if (offset > 0) + m_adj(m0, offset); + iwm_rx_mpdu(sc, m0, remain - minsz); + m0 = NULL; /* stack owns m0 now; abort loop */ + } else { + /* + * Create an mbuf which points to the current + * packet. Always copy from offset zero to + * preserve m_pkthdr. + */ + m = m_copym(m0, 0, M_COPYALL, M_DONTWAIT); + if (m == NULL) { + ifp->if_ierrors++; + break; + } + m_adj(m, offset); + iwm_rx_mpdu(sc, m, remain - minsz); + } + if (offset + minsz < remain) + remain -= offset; + else + remain = minsz; + break; + } + case IWM_TX_CMD: iwm_rx_tx_cmd(sc, pkt, data); break; @@ -7479,6 +7600,26 @@ iwm_notif_intr(struct iwm_softc *sc) iwm_cmd_done(sc, pkt); } + offset += roundup(len, IWM_FH_RSCSR_FRAME_ALIGN); + } + + if (m0 && m0 != data->m) + m_freem(m0); +} + +void +iwm_notif_intr(struct iwm_softc *sc) +{ + uint16_t hw; + + bus_dmamap_sync(sc->sc_dmat, sc->rxq.stat_dma.map, + 0, sc->rxq.stat_dma.size, BUS_DMASYNC_POSTREAD); + + hw = le16toh(sc->rxq.stat->closed_rb_num) & 0xfff; + hw &= (IWM_RX_RING_COUNT - 1); + while (sc->rxq.cur != hw) { + struct iwm_rx_data *data = &sc->rxq.data[sc->rxq.cur]; + iwm_rx_pkt(sc, data); ADVANCE_RXQ(sc); } @@ -7962,6 +8103,7 @@ iwm_attach(struct device *parent, struct device *self, IEEE80211_C_RSN | /* WPA/RSN */ IEEE80211_C_SCANALL | /* device scans all channels at once */ IEEE80211_C_SCANALLBAND | /* device scans all bands at once */ + IEEE80211_C_MONITOR | /* monitor mode supported */ IEEE80211_C_SHSLOT | /* short slot time supported */ IEEE80211_C_SHPREAMBLE; /* short preamble supported */