This patch adds A-MSDU rx offloading support for both iwm(4) and iwx(4) and re-enables net80211's software A-MSDU Rx support for all 11n drivers.
Meaning iwn(4) and athn(4) will also be receiving A-MSDUs again. This feature has been turned off since July 2019: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/net80211/ieee80211_input.c#rev1.207 The root cause of the problem I saw at that time was related to dlg's Rx queue back-pressure mechanism. Once we figured this out, all wireless drivers were fixed to call if_input() only once per interrupt so this is no longer an issue. For a very brief period we tried to enable A-MSDUs again in -current but we found out that iwm/iwx needed additional work for the new devices which received support while A-MSDU was disabled in-tree: https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/sys/net80211/ieee80211_input.c#rev1.226 The patch below completes the missing bits to make A-MSDUs work on these new devices. My previous iwm-only patch and related test reports are here: https://marc.info/?t=161703907100001&r=1&w=2 The iwm changes have already been extensively tested. For iwm, this new patch only adds an additional range check: @@ -4640,7 +4640,8 @@ iwm_rx_reorder(struct iwm_softc *sc, struct mbuf *m, i baid = (reorder_data & IWM_RX_MPDU_REORDER_BAID_MASK) >> IWM_RX_MPDU_REORDER_BAID_SHIFT; - if (baid == IWM_RX_REORDER_DATA_INVALID_BAID) + if (baid == IWM_RX_REORDER_DATA_INVALID_BAID || + baid >= nitems(sc->sc_rxba_data)) return 0; rxba = &sc->sc_rxba_data[baid]; I have tested on the following devices: iwm0 at pci2 dev 0 function 0 "Intel Dual Band Wireless-AC 8265" rev 0x78, msi iwm0: hw rev 0x230, fw ver 34.0.1, address 7c:11:xx:xx:xx:xx iwx0 at pci5 dev 0 function 0 "Intel Wi-Fi 6 AX200" rev 0x1a, msix iwx0: hw rev 0x340, fw ver 48.1335886879.0, address d0:ab:xx:xx:xx:xx iwx0 at pci0 dev 20 function 3 "Intel Wi-Fi 6 AX201" rev 0x00, msix iwx0: hw rev 0x350, fw ver 48.1335886879.0, address 0c:7a:xx:xx:xx:xx ok? diff refs/heads/master refs/heads/amsdu blob - 00bf20b37ed33a652232885349c2f3dfa0d666d3 blob + 05cc01334b522b7b537fba6df4b4fd3e0c5d8eb9 --- sys/dev/pci/if_iwm.c +++ sys/dev/pci/if_iwm.c @@ -144,6 +144,8 @@ #include <net80211/ieee80211_amrr.h> #include <net80211/ieee80211_ra.h> #include <net80211/ieee80211_radiotap.h> +#include <net80211/ieee80211_priv.h> /* for SEQ_LT */ +#undef DPRINTF /* defined in ieee80211_priv.h */ #define DEVNAME(_s) ((_s)->sc_dev.dv_xname) @@ -328,12 +330,17 @@ int iwm_mimo_enabled(struct iwm_softc *); void iwm_setup_ht_rates(struct iwm_softc *); void iwm_htprot_task(void *); void iwm_update_htprot(struct ieee80211com *, struct ieee80211_node *); +void iwm_init_reorder_buffer(struct iwm_reorder_buffer *, uint16_t, + uint16_t); +void iwm_clear_reorder_buffer(struct iwm_softc *, struct iwm_rxba_data *); int iwm_ampdu_rx_start(struct ieee80211com *, struct ieee80211_node *, uint8_t); void iwm_ampdu_rx_stop(struct ieee80211com *, struct ieee80211_node *, uint8_t); +void iwm_rx_ba_session_expired(void *); +void iwm_reorder_timer_expired(void *); void iwm_sta_rx_agg(struct iwm_softc *, struct ieee80211_node *, uint8_t, - uint16_t, uint16_t, int); + uint16_t, uint16_t, int, int); #ifdef notyet int iwm_ampdu_tx_start(struct ieee80211com *, struct ieee80211_node *, uint8_t); @@ -372,8 +379,10 @@ int iwm_rxmq_get_signal_strength(struct iwm_softc *, s 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_hwdecrypt(struct iwm_softc *, struct mbuf *, uint32_t, + struct ieee80211_rxinfo *); int iwm_ccmp_decap(struct iwm_softc *, struct mbuf *, - struct ieee80211_node *); + struct ieee80211_node *, struct ieee80211_rxinfo *); void iwm_rx_frame(struct iwm_softc *, struct mbuf *, int, uint32_t, int, int, uint32_t, struct ieee80211_rxinfo *, struct mbuf_list *); void iwm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *, @@ -490,6 +499,20 @@ void iwm_nic_umac_error(struct iwm_softc *); #endif void iwm_rx_mpdu(struct iwm_softc *, struct mbuf *, void *, size_t, struct mbuf_list *); +void iwm_flip_address(uint8_t *); +int iwm_detect_duplicate(struct iwm_softc *, struct mbuf *, + struct iwm_rx_mpdu_desc *, struct ieee80211_rxinfo *); +int iwm_is_sn_less(uint16_t, uint16_t, uint16_t); +void iwm_release_frames(struct iwm_softc *, struct ieee80211_node *, + struct iwm_rxba_data *, struct iwm_reorder_buffer *, uint16_t, + struct mbuf_list *); +int iwm_oldsn_workaround(struct iwm_softc *, struct ieee80211_node *, + int, struct iwm_reorder_buffer *, uint32_t, uint32_t); +int iwm_rx_reorder(struct iwm_softc *, struct mbuf *, int, + struct iwm_rx_mpdu_desc *, int, int, uint32_t, + struct ieee80211_rxinfo *, struct mbuf_list *); +void iwm_rx_mpdu_mq(struct iwm_softc *, struct mbuf *, void *, size_t, + struct mbuf_list *); int iwm_rx_pkt_valid(struct iwm_rx_packet *); void iwm_rx_pkt(struct iwm_softc *, struct iwm_rx_data *, struct mbuf_list *); @@ -2902,11 +2925,139 @@ iwm_setup_ht_rates(struct iwm_softc *sc) ic->ic_sup_mcs[1] = 0xff; /* MCS 8-15 */ } +void +iwm_init_reorder_buffer(struct iwm_reorder_buffer *reorder_buf, + uint16_t ssn, uint16_t buf_size) +{ + reorder_buf->head_sn = ssn; + reorder_buf->num_stored = 0; + reorder_buf->buf_size = buf_size; + reorder_buf->last_amsdu = 0; + reorder_buf->last_sub_index = 0; + reorder_buf->removed = 0; + reorder_buf->valid = 0; + reorder_buf->consec_oldsn_drops = 0; + reorder_buf->consec_oldsn_ampdu_gp2 = 0; + reorder_buf->consec_oldsn_prev_drop = 0; +} + +void +iwm_clear_reorder_buffer(struct iwm_softc *sc, struct iwm_rxba_data *rxba) +{ + int i; + struct iwm_reorder_buffer *reorder_buf = &rxba->reorder_buf; + struct iwm_reorder_buf_entry *entry; + + for (i = 0; i < reorder_buf->buf_size; i++) { + entry = &rxba->entries[i]; + ml_purge(&entry->frames); + timerclear(&entry->reorder_time); + } + + reorder_buf->removed = 1; + timeout_del(&reorder_buf->reorder_timer); + timerclear(&rxba->last_rx); + timeout_del(&rxba->session_timer); + rxba->baid = IWM_RX_REORDER_DATA_INVALID_BAID; +} + +#define RX_REORDER_BUF_TIMEOUT_MQ_USEC (100000ULL) + +void +iwm_rx_ba_session_expired(void *arg) +{ + struct iwm_rxba_data *rxba = arg; + struct iwm_softc *sc = rxba->sc; + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_node *ni = ic->ic_bss; + struct timeval now, timeout, expiry; + int s; + + s = splnet(); + if ((sc->sc_flags & IWM_FLAG_SHUTDOWN) == 0 && + ic->ic_state == IEEE80211_S_RUN && + rxba->baid != IWM_RX_REORDER_DATA_INVALID_BAID) { + getmicrouptime(&now); + USEC_TO_TIMEVAL(RX_REORDER_BUF_TIMEOUT_MQ_USEC, &timeout); + timeradd(&rxba->last_rx, &timeout, &expiry); + if (timercmp(&now, &expiry, <)) { + timeout_add_usec(&rxba->session_timer, rxba->timeout); + } else { + ic->ic_stats.is_ht_rx_ba_timeout++; + ieee80211_delba_request(ic, ni, + IEEE80211_REASON_TIMEOUT, 0, rxba->tid); + } + } + splx(s); +} + +void +iwm_reorder_timer_expired(void *arg) +{ + struct mbuf_list ml = MBUF_LIST_INITIALIZER(); + struct iwm_reorder_buffer *buf = arg; + struct iwm_rxba_data *rxba = iwm_rxba_data_from_reorder_buf(buf); + struct iwm_reorder_buf_entry *entries = &rxba->entries[0]; + struct iwm_softc *sc = rxba->sc; + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_node *ni = ic->ic_bss; + int i, s; + uint16_t sn = 0, index = 0; + int expired = 0; + int cont = 0; + struct timeval now, timeout, expiry; + + if (!buf->num_stored || buf->removed) + return; + + s = splnet(); + getmicrouptime(&now); + USEC_TO_TIMEVAL(RX_REORDER_BUF_TIMEOUT_MQ_USEC, &timeout); + + for (i = 0; i < buf->buf_size ; i++) { + index = (buf->head_sn + i) % buf->buf_size; + + if (ml_empty(&entries[index].frames)) { + /* + * If there is a hole and the next frame didn't expire + * we want to break and not advance SN. + */ + cont = 0; + continue; + } + timeradd(&entries[index].reorder_time, &timeout, &expiry); + if (!cont && timercmp(&now, &expiry, <)) + break; + + expired = 1; + /* continue until next hole after this expired frame */ + cont = 1; + sn = (buf->head_sn + (i + 1)) & 0xfff; + } + + if (expired) { + /* SN is set to the last expired frame + 1 */ + iwm_release_frames(sc, ni, rxba, buf, sn, &ml); + if_input(&sc->sc_ic.ic_if, &ml); + ic->ic_stats.is_ht_rx_ba_window_gap_timeout++; + } else { + /* + * If no frame expired and there are stored frames, index is now + * pointing to the first unexpired frame - modify reorder timeout + * accordingly. + */ + timeout_add_usec(&buf->reorder_timer, + RX_REORDER_BUF_TIMEOUT_MQ_USEC); + } + + splx(s); +} + #define IWM_MAX_RX_BA_SESSIONS 16 void iwm_sta_rx_agg(struct iwm_softc *sc, struct ieee80211_node *ni, uint8_t tid, - uint16_t ssn, uint16_t winsize, int start) + uint16_t ssn, uint16_t winsize, int timeout_val, int start) { struct ieee80211com *ic = &sc->sc_ic; struct iwm_add_sta_cmd cmd; @@ -2914,9 +3065,14 @@ iwm_sta_rx_agg(struct iwm_softc *sc, struct ieee80211_ int err, s; uint32_t status; size_t cmdsize; + struct iwm_rxba_data *rxba = NULL; + uint8_t baid = 0; + s = splnet(); + if (start && sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS) { ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); return; } @@ -2945,16 +3101,71 @@ iwm_sta_rx_agg(struct iwm_softc *sc, struct ieee80211_ err = iwm_send_cmd_pdu_status(sc, IWM_ADD_STA, cmdsize, &cmd, &status); - s = splnet(); - if (!err && (status & IWM_ADD_STA_STATUS_MASK) == IWM_ADD_STA_SUCCESS) { + if (err || (status & IWM_ADD_STA_STATUS_MASK) != IWM_ADD_STA_SUCCESS) { + if (start) + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + + if (sc->sc_mqrx_supported) { + /* Deaggregation is done in hardware. */ if (start) { - sc->sc_rx_ba_sessions++; - ieee80211_addba_req_accept(ic, ni, tid); - } else if (sc->sc_rx_ba_sessions > 0) - sc->sc_rx_ba_sessions--; - } else if (start) - ieee80211_addba_req_refuse(ic, ni, tid); + if (!(status & IWM_ADD_STA_BAID_VALID_MASK)) { + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + baid = (status & IWM_ADD_STA_BAID_MASK) >> + IWM_ADD_STA_BAID_SHIFT; + if (baid == IWM_RX_REORDER_DATA_INVALID_BAID || + baid >= nitems(sc->sc_rxba_data)) { + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + rxba = &sc->sc_rxba_data[baid]; + if (rxba->baid != IWM_RX_REORDER_DATA_INVALID_BAID) { + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + rxba->sta_id = IWM_STATION_ID; + rxba->tid = tid; + rxba->baid = baid; + rxba->timeout = timeout_val; + getmicrouptime(&rxba->last_rx); + iwm_init_reorder_buffer(&rxba->reorder_buf, ssn, + winsize); + if (timeout_val != 0) { + struct ieee80211_rx_ba *ba; + timeout_add_usec(&rxba->session_timer, + timeout_val); + /* XXX disable net80211's BA timeout handler */ + ba = &ni->ni_rx_ba[tid]; + ba->ba_timeout_val = 0; + } + } else { + int i; + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + rxba = &sc->sc_rxba_data[i]; + if (rxba->baid == + IWM_RX_REORDER_DATA_INVALID_BAID) + continue; + if (rxba->tid != tid) + continue; + iwm_clear_reorder_buffer(sc, rxba); + break; + } + } + } + if (start) { + sc->sc_rx_ba_sessions++; + ieee80211_addba_req_accept(ic, ni, tid); + } else if (sc->sc_rx_ba_sessions > 0) + sc->sc_rx_ba_sessions--; + splx(s); } @@ -3002,18 +3213,20 @@ iwm_ba_task(void *arg) struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = ic->ic_bss; int s = splnet(); + int tid; - if (sc->sc_flags & IWM_FLAG_SHUTDOWN) { - refcnt_rele_wake(&sc->task_refs); - splx(s); - return; + for (tid = 0; tid < IWM_MAX_TID_COUNT; tid++) { + if (sc->sc_flags & IWM_FLAG_SHUTDOWN) + break; + if (sc->ba_start_tidmask & (1 << tid)) { + iwm_sta_rx_agg(sc, ni, tid, sc->ba_ssn[tid], + sc->ba_winsize[tid], sc->ba_timeout_val[tid], 1); + sc->ba_start_tidmask &= ~(1 << tid); + } else if (sc->ba_stop_tidmask & (1 << tid)) { + iwm_sta_rx_agg(sc, ni, tid, 0, 0, 0, 0); + sc->ba_stop_tidmask &= ~(1 << tid); + } } - - if (sc->ba_start) - iwm_sta_rx_agg(sc, ni, sc->ba_tid, sc->ba_ssn, - sc->ba_winsize, 1); - else - iwm_sta_rx_agg(sc, ni, sc->ba_tid, 0, 0, 0); refcnt_rele_wake(&sc->task_refs); splx(s); @@ -3030,13 +3243,14 @@ iwm_ampdu_rx_start(struct ieee80211com *ic, struct iee struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid]; struct iwm_softc *sc = IC2IFP(ic)->if_softc; - if (sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS) + if (sc->sc_rx_ba_sessions >= IWM_MAX_RX_BA_SESSIONS || + tid > IWM_MAX_TID_COUNT || (sc->ba_start_tidmask & (1 << tid))) return ENOSPC; - sc->ba_start = 1; - sc->ba_tid = tid; - sc->ba_ssn = htole16(ba->ba_winstart); - sc->ba_winsize = htole16(ba->ba_winsize); + sc->ba_start_tidmask |= (1 << tid); + sc->ba_ssn[tid] = ba->ba_winstart; + sc->ba_winsize[tid] = ba->ba_winsize; + sc->ba_timeout_val[tid] = ba->ba_timeout_val; iwm_add_task(sc, systq, &sc->ba_task); return EBUSY; @@ -3052,8 +3266,10 @@ iwm_ampdu_rx_stop(struct ieee80211com *ic, struct ieee { struct iwm_softc *sc = IC2IFP(ic)->if_softc; - sc->ba_start = 0; - sc->ba_tid = tid; + if (tid > IWM_MAX_TID_COUNT || sc->ba_stop_tidmask & (1 << tid)) + return; + + sc->ba_stop_tidmask = (1 << tid); iwm_add_task(sc, systq, &sc->ba_task); } @@ -3907,7 +4123,8 @@ iwm_get_noise(const struct iwm_statistics_rx_non_phy * } int -iwm_ccmp_decap(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +iwm_ccmp_decap(struct iwm_softc *sc, struct mbuf *m, struct ieee80211_node *ni, + struct ieee80211_rxinfo *rxi) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_key *k = &ni->ni_pairwise_key; @@ -3936,7 +4153,12 @@ iwm_ccmp_decap(struct iwm_softc *sc, struct mbuf *m, s (uint64_t)ivp[5] << 24 | (uint64_t)ivp[6] << 32 | (uint64_t)ivp[7] << 40; - if (pn <= *prsc) { + if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) { + if (pn < *prsc) { + ic->ic_stats.is_ccmp_replays++; + return 1; + } + } else if (pn <= *prsc) { ic->ic_stats.is_ccmp_replays++; return 1; } @@ -3953,6 +4175,60 @@ iwm_ccmp_decap(struct iwm_softc *sc, struct mbuf *m, s return 0; } +int +iwm_rx_hwdecrypt(struct iwm_softc *sc, struct mbuf *m, uint32_t rx_pkt_status, + struct ieee80211_rxinfo *rxi) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = IC2IFP(ic); + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + int ret = 0; + uint8_t type, subtype; + + wh = mtod(m, struct ieee80211_frame *); + + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + if (type == IEEE80211_FC0_TYPE_CTL) + return 0; + + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + if (ieee80211_has_qos(wh) && (subtype & IEEE80211_FC0_SUBTYPE_NODATA)) + return 0; + + if (IEEE80211_IS_MULTICAST(wh->i_addr1) || + !(wh->i_fc[1] & IEEE80211_FC1_PROTECTED)) + return 0; + + ni = ieee80211_find_rxnode(ic, wh); + /* Handle hardware decryption. */ + if ((ni->ni_flags & IEEE80211_NODE_RXPROT) && + ni->ni_pairwise_key.k_cipher == IEEE80211_CIPHER_CCMP) { + 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++; + ret = 1; + goto out; + } + /* Check whether decryption was successful or not. */ + if ((rx_pkt_status & + (IWM_RX_MPDU_RES_STATUS_DEC_DONE | + IWM_RX_MPDU_RES_STATUS_MIC_OK)) != + (IWM_RX_MPDU_RES_STATUS_DEC_DONE | + IWM_RX_MPDU_RES_STATUS_MIC_OK)) { + ic->ic_stats.is_ccmp_dec_errs++; + ret = 1; + goto out; + } + rxi->rxi_flags |= IEEE80211_RXI_HWDEC; + } +out: + if (ret) + ifp->if_ierrors++; + ieee80211_release_node(ic, ni); + return ret; +} + void iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, int chanidx, uint32_t rx_pkt_status, int is_shortpre, int rate_n_flags, @@ -3960,11 +4236,11 @@ iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, int struct mbuf_list *ml) { struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = IC2IFP(ic); struct ieee80211_frame *wh; struct ieee80211_node *ni; struct ieee80211_channel *bss_chan; uint8_t saved_bssid[IEEE80211_ADDR_LEN] = { 0 }; - struct ifnet *ifp = IC2IFP(ic); if (chanidx < 0 || chanidx >= nitems(ic->ic_channels)) chanidx = ieee80211_chan2ieee(ic, ic->ic_ibss_chan); @@ -3981,39 +4257,12 @@ iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, int } ni->ni_chan = &ic->ic_channels[chanidx]; - /* Handle hardware decryption. */ - if (((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_CTL) - && (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && - !IEEE80211_IS_MULTICAST(wh->i_addr1) && - (ni->ni_flags & IEEE80211_NODE_RXPROT) && - ni->ni_pairwise_key.k_cipher == IEEE80211_CIPHER_CCMP) { - 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); - ieee80211_release_node(ic, ni); - return; - } - /* Check whether decryption was successful or not. */ - if ((rx_pkt_status & - (IWM_RX_MPDU_RES_STATUS_DEC_DONE | - IWM_RX_MPDU_RES_STATUS_MIC_OK)) != - (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); - ieee80211_release_node(ic, ni); - return; - } - if (iwm_ccmp_decap(sc, m, ni) != 0) { - ifp->if_ierrors++; - m_freem(m); - ieee80211_release_node(ic, ni); - return; - } - rxi->rxi_flags |= IEEE80211_RXI_HWDEC; + if ((rxi->rxi_flags & IEEE80211_RXI_HWDEC) && + iwm_ccmp_decap(sc, m, ni, rxi) != 0) { + ifp->if_ierrors++; + m_freem(m); + ieee80211_release_node(ic, ni); + return; } #if NBPFILTER > 0 @@ -4089,6 +4338,8 @@ iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, void uint32_t rx_pkt_status; int rssi, chanidx, rate_n_flags; + memset(&rxi, 0, sizeof(rxi)); + phy_info = &sc->sc_last_phy_info; rx_res = (struct iwm_rx_mpdu_res_start *)pktdata; len = le16toh(rx_res->byte_count); @@ -4127,6 +4378,11 @@ iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, void m->m_data = pktdata + sizeof(*rx_res); m->m_pkthdr.len = m->m_len = len; + if (iwm_rx_hwdecrypt(sc, m, rx_pkt_status, &rxi)) { + m_freem(m); + return; + } + chanidx = letoh32(phy_info->channel); device_timestamp = le32toh(phy_info->system_timestamp); phy_flags = letoh16(phy_info->phy_flags); @@ -4136,7 +4392,6 @@ iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, void rssi = (0 - IWM_MIN_DBM) + rssi; /* normalize */ rssi = MIN(rssi, ic->ic_max_rssi); /* clip to max. 100% */ - memset(&rxi, 0, sizeof(rxi)); rxi.rxi_rssi = rssi; rxi.rxi_tstamp = device_timestamp; @@ -4146,6 +4401,386 @@ iwm_rx_mpdu(struct iwm_softc *sc, struct mbuf *m, void } void +iwm_flip_address(uint8_t *addr) +{ + int i; + uint8_t mac_addr[ETHER_ADDR_LEN]; + + for (i = 0; i < ETHER_ADDR_LEN; i++) + mac_addr[i] = addr[ETHER_ADDR_LEN - i - 1]; + IEEE80211_ADDR_COPY(addr, mac_addr); +} + +/* + * Drop duplicate 802.11 retransmissions + * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") + * and handle pseudo-duplicate frames which result from deaggregation + * of A-MSDU frames in hardware. + */ +int +iwm_detect_duplicate(struct iwm_softc *sc, struct mbuf *m, + struct iwm_rx_mpdu_desc *desc, struct ieee80211_rxinfo *rxi) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct iwm_node *in = (void *)ic->ic_bss; + struct iwm_rxq_dup_data *dup_data = &in->dup_data; + uint8_t tid = IWM_MAX_TID_COUNT, subframe_idx; + struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); + uint8_t type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + int hasqos = ieee80211_has_qos(wh); + uint16_t seq; + + if (type == IEEE80211_FC0_TYPE_CTL || + (hasqos && (subtype & IEEE80211_FC0_SUBTYPE_NODATA)) || + IEEE80211_IS_MULTICAST(wh->i_addr1)) + return 0; + + if (hasqos) { + tid = (ieee80211_get_qos(wh) & IEEE80211_QOS_TID); + if (tid > IWM_MAX_TID_COUNT) + tid = IWM_MAX_TID_COUNT; + } + + /* If this wasn't a part of an A-MSDU the sub-frame index will be 0 */ + subframe_idx = desc->amsdu_info & + IWM_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + + seq = letoh16(*(u_int16_t *)wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; + if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && + dup_data->last_seq[tid] == seq && + dup_data->last_sub_frame[tid] >= subframe_idx) + return 1; + + /* + * Allow the same frame sequence number for all A-MSDU subframes + * following the first subframe. + * Otherwise these subframes would be discarded as replays. + */ + if (dup_data->last_seq[tid] == seq && + subframe_idx > dup_data->last_sub_frame[tid] && + (desc->mac_flags2 & IWM_RX_MPDU_MFLG2_AMSDU)) { + rxi->rxi_flags |= IEEE80211_RXI_SAME_SEQ; + } + + dup_data->last_seq[tid] = seq; + dup_data->last_sub_frame[tid] = subframe_idx; + + return 0; +} + +/* + * Returns true if sn2 - buffer_size < sn1 < sn2. + * To be used only in order to compare reorder buffer head with NSSN. + * We fully trust NSSN unless it is behind us due to reorder timeout. + * Reorder timeout can only bring us up to buffer_size SNs ahead of NSSN. + */ +int +iwm_is_sn_less(uint16_t sn1, uint16_t sn2, uint16_t buffer_size) +{ + return SEQ_LT(sn1, sn2) && !SEQ_LT(sn1, sn2 - buffer_size); +} + +void +iwm_release_frames(struct iwm_softc *sc, struct ieee80211_node *ni, + struct iwm_rxba_data *rxba, struct iwm_reorder_buffer *reorder_buf, + uint16_t nssn, struct mbuf_list *ml) +{ + struct iwm_reorder_buf_entry *entries = &rxba->entries[0]; + uint16_t ssn = reorder_buf->head_sn; + + /* ignore nssn smaller than head sn - this can happen due to timeout */ + if (iwm_is_sn_less(nssn, ssn, reorder_buf->buf_size)) + goto set_timer; + + while (iwm_is_sn_less(ssn, nssn, reorder_buf->buf_size)) { + int index = ssn % reorder_buf->buf_size; + struct mbuf *m; + int chanidx, is_shortpre; + uint32_t rx_pkt_status, rate_n_flags, device_timestamp; + struct ieee80211_rxinfo *rxi; + + /* This data is the same for all A-MSDU subframes. */ + chanidx = entries[index].chanidx; + rx_pkt_status = entries[index].rx_pkt_status; + is_shortpre = entries[index].is_shortpre; + rate_n_flags = entries[index].rate_n_flags; + device_timestamp = entries[index].device_timestamp; + rxi = &entries[index].rxi; + + /* + * Empty the list. Will have more than one frame for A-MSDU. + * Empty list is valid as well since nssn indicates frames were + * received. + */ + while ((m = ml_dequeue(&entries[index].frames)) != NULL) { + iwm_rx_frame(sc, m, chanidx, rx_pkt_status, is_shortpre, + rate_n_flags, device_timestamp, rxi, ml); + reorder_buf->num_stored--; + + /* + * Allow the same frame sequence number and CCMP PN for + * all A-MSDU subframes following the first subframe. + * Otherwise they would be discarded as replays. + */ + rxi->rxi_flags |= IEEE80211_RXI_SAME_SEQ; + rxi->rxi_flags |= IEEE80211_RXI_HWDEC_SAME_PN; + } + + ssn = (ssn + 1) & 0xfff; + } + reorder_buf->head_sn = nssn; + +set_timer: + if (reorder_buf->num_stored && !reorder_buf->removed) { + timeout_add_usec(&reorder_buf->reorder_timer, + RX_REORDER_BUF_TIMEOUT_MQ_USEC); + } else + timeout_del(&reorder_buf->reorder_timer); +} + +int +iwm_oldsn_workaround(struct iwm_softc *sc, struct ieee80211_node *ni, int tid, + struct iwm_reorder_buffer *buffer, uint32_t reorder_data, uint32_t gp2) +{ + struct ieee80211com *ic = &sc->sc_ic; + + if (gp2 != buffer->consec_oldsn_ampdu_gp2) { + /* we have a new (A-)MPDU ... */ + + /* + * reset counter to 0 if we didn't have any oldsn in + * the last A-MPDU (as detected by GP2 being identical) + */ + if (!buffer->consec_oldsn_prev_drop) + buffer->consec_oldsn_drops = 0; + + /* either way, update our tracking state */ + buffer->consec_oldsn_ampdu_gp2 = gp2; + } else if (buffer->consec_oldsn_prev_drop) { + /* + * tracking state didn't change, and we had an old SN + * indication before - do nothing in this case, we + * already noted this one down and are waiting for the + * next A-MPDU (by GP2) + */ + return 0; + } + + /* return unless this MPDU has old SN */ + if (!(reorder_data & IWM_RX_MPDU_REORDER_BA_OLD_SN)) + return 0; + + /* update state */ + buffer->consec_oldsn_prev_drop = 1; + buffer->consec_oldsn_drops++; + + /* if limit is reached, send del BA and reset state */ + if (buffer->consec_oldsn_drops == IWM_AMPDU_CONSEC_DROPS_DELBA) { + ieee80211_delba_request(ic, ni, IEEE80211_REASON_UNSPECIFIED, + 0, tid); + buffer->consec_oldsn_prev_drop = 0; + buffer->consec_oldsn_drops = 0; + return 1; + } + + return 0; +} + +/* + * Handle re-ordering of frames which were de-aggregated in hardware. + * Returns 1 if the MPDU was consumed (buffered or dropped). + * Returns 0 if the MPDU should be passed to upper layer. + */ +int +iwm_rx_reorder(struct iwm_softc *sc, struct mbuf *m, int chanidx, + struct iwm_rx_mpdu_desc *desc, int is_shortpre, int rate_n_flags, + uint32_t device_timestamp, struct ieee80211_rxinfo *rxi, + struct mbuf_list *ml) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct iwm_rxba_data *rxba; + struct iwm_reorder_buffer *buffer; + uint32_t reorder_data = le32toh(desc->reorder_data); + int is_amsdu = (desc->mac_flags2 & IWM_RX_MPDU_MFLG2_AMSDU); + int last_subframe = + (desc->amsdu_info & IWM_RX_MPDU_AMSDU_LAST_SUBFRAME); + uint8_t tid; + uint8_t subframe_idx = (desc->amsdu_info & + IWM_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK); + struct iwm_reorder_buf_entry *entries; + int index; + uint16_t nssn, sn; + uint8_t baid, type, subtype; + int hasqos; + + wh = mtod(m, struct ieee80211_frame *); + hasqos = ieee80211_has_qos(wh); + tid = hasqos ? ieee80211_get_qos(wh) & IEEE80211_QOS_TID : 0; + + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + ni = ieee80211_find_rxnode(ic, wh); + + /* + * We are only interested in Block Ack requests and unicast QoS data. + */ + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + return 0; + if (hasqos) { + if (subtype & IEEE80211_FC0_SUBTYPE_NODATA) + return 0; + } else { + if (type != IEEE80211_FC0_TYPE_CTL || + subtype != IEEE80211_FC0_SUBTYPE_BAR) + return 0; + } + + baid = (reorder_data & IWM_RX_MPDU_REORDER_BAID_MASK) >> + IWM_RX_MPDU_REORDER_BAID_SHIFT; + if (baid == IWM_RX_REORDER_DATA_INVALID_BAID || + baid >= nitems(sc->sc_rxba_data)) + return 0; + + rxba = &sc->sc_rxba_data[baid]; + if (rxba == NULL || tid != rxba->tid || rxba->sta_id != IWM_STATION_ID) + return 0; + + /* Bypass A-MPDU re-ordering in net80211. */ + rxi->rxi_flags |= IEEE80211_RXI_AMPDU_DONE; + + nssn = reorder_data & IWM_RX_MPDU_REORDER_NSSN_MASK; + sn = (reorder_data & IWM_RX_MPDU_REORDER_SN_MASK) >> + IWM_RX_MPDU_REORDER_SN_SHIFT; + + buffer = &rxba->reorder_buf; + entries = &rxba->entries[0]; + + if (!buffer->valid) { + if (reorder_data & IWM_RX_MPDU_REORDER_BA_OLD_SN) + return 0; + buffer->valid = 1; + } + + if (type == IEEE80211_FC0_TYPE_CTL && + subtype == IEEE80211_FC0_SUBTYPE_BAR) { + iwm_release_frames(sc, ni, rxba, buffer, nssn, ml); + goto drop; + } + + /* + * If there was a significant jump in the nssn - adjust. + * If the SN is smaller than the NSSN it might need to first go into + * the reorder buffer, in which case we just release up to it and the + * rest of the function will take care of storing it and releasing up to + * the nssn. + */ + if (!iwm_is_sn_less(nssn, buffer->head_sn + buffer->buf_size, + buffer->buf_size) || + !SEQ_LT(sn, buffer->head_sn + buffer->buf_size)) { + uint16_t min_sn = SEQ_LT(sn, nssn) ? sn : nssn; + ic->ic_stats.is_ht_rx_frame_above_ba_winend++; + iwm_release_frames(sc, ni, rxba, buffer, min_sn, ml); + } + + if (iwm_oldsn_workaround(sc, ni, tid, buffer, reorder_data, + device_timestamp)) { + /* BA session will be torn down. */ + ic->ic_stats.is_ht_rx_ba_window_jump++; + goto drop; + + } + + /* drop any outdated packets */ + if (SEQ_LT(sn, buffer->head_sn)) { + ic->ic_stats.is_ht_rx_frame_below_ba_winstart++; + goto drop; + } + + /* release immediately if allowed by nssn and no stored frames */ + if (!buffer->num_stored && SEQ_LT(sn, nssn)) { + if (iwm_is_sn_less(buffer->head_sn, nssn, buffer->buf_size) && + (!is_amsdu || last_subframe)) + buffer->head_sn = nssn; + return 0; + } + + /* + * release immediately if there are no stored frames, and the sn is + * equal to the head. + * This can happen due to reorder timer, where NSSN is behind head_sn. + * When we released everything, and we got the next frame in the + * sequence, according to the NSSN we can't release immediately, + * while technically there is no hole and we can move forward. + */ + if (!buffer->num_stored && sn == buffer->head_sn) { + if (!is_amsdu || last_subframe) + buffer->head_sn = (buffer->head_sn + 1) & 0xfff; + return 0; + } + + index = sn % buffer->buf_size; + + /* + * Check if we already stored this frame + * As AMSDU is either received or not as whole, logic is simple: + * If we have frames in that position in the buffer and the last frame + * originated from AMSDU had a different SN then it is a retransmission. + * If it is the same SN then if the subframe index is incrementing it + * is the same AMSDU - otherwise it is a retransmission. + */ + if (!ml_empty(&entries[index].frames)) { + if (!is_amsdu) { + ic->ic_stats.is_ht_rx_ba_no_buf++; + goto drop; + } else if (sn != buffer->last_amsdu || + buffer->last_sub_index >= subframe_idx) { + ic->ic_stats.is_ht_rx_ba_no_buf++; + goto drop; + } + } else { + /* This data is the same for all A-MSDU subframes. */ + entries[index].chanidx = chanidx; + entries[index].is_shortpre = is_shortpre; + entries[index].rate_n_flags = rate_n_flags; + entries[index].device_timestamp = device_timestamp; + memcpy(&entries[index].rxi, rxi, sizeof(entries[index].rxi)); + } + + /* put in reorder buffer */ + ml_enqueue(&entries[index].frames, m); + buffer->num_stored++; + getmicrouptime(&entries[index].reorder_time); + + if (is_amsdu) { + buffer->last_amsdu = sn; + buffer->last_sub_index = subframe_idx; + } + + /* + * We cannot trust NSSN for AMSDU sub-frames that are not the last. + * The reason is that NSSN advances on the first sub-frame, and may + * cause the reorder buffer to advance before all the sub-frames arrive. + * Example: reorder buffer contains SN 0 & 2, and we receive AMSDU with + * SN 1. NSSN for first sub frame will be 3 with the result of driver + * releasing SN 0,1, 2. When sub-frame 1 arrives - reorder buffer is + * already ahead and it will be dropped. + * If the last sub-frame is not on this queue - we will get frame + * release notification with up to date NSSN. + */ + if (!is_amsdu || last_subframe) + iwm_release_frames(sc, ni, rxba, buffer, nssn, ml); + + return 1; + +drop: + m_freem(m); + return 1; +} + +void iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, void *pktdata, size_t maxlen, struct mbuf_list *ml) { @@ -4157,6 +4792,8 @@ iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, v uint8_t chanidx; uint16_t phy_info; + memset(&rxi, 0, sizeof(rxi)); + desc = (struct iwm_rx_mpdu_desc *)pktdata; if (!(desc->status & htole16(IWM_RX_MPDU_RES_STATUS_CRC_OK)) || @@ -4219,6 +4856,55 @@ iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, v m_adj(m, 2); } + /* + * Hardware de-aggregates A-MSDUs and copies the same MAC header + * in place for each subframe. But it leaves the 'A-MSDU present' + * bit set in the frame header. We need to clear this bit ourselves. + * + * And we must allow the same CCMP PN for subframes following the + * first subframe. Otherwise they would be discarded as replays. + */ + if (desc->mac_flags2 & IWM_RX_MPDU_MFLG2_AMSDU) { + struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); + uint8_t subframe_idx = (desc->amsdu_info & + IWM_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK); + if (subframe_idx > 0) + rxi.rxi_flags |= IEEE80211_RXI_HWDEC_SAME_PN; + if (ieee80211_has_qos(wh) && ieee80211_has_addr4(wh) && + m->m_len >= sizeof(struct ieee80211_qosframe_addr4)) { + struct ieee80211_qosframe_addr4 *qwh4 = mtod(m, + struct ieee80211_qosframe_addr4 *); + qwh4->i_qos[0] &= htole16(~IEEE80211_QOS_AMSDU); + + /* HW reverses addr3 and addr4. */ + iwm_flip_address(qwh4->i_addr3); + iwm_flip_address(qwh4->i_addr4); + } else if (ieee80211_has_qos(wh) && + m->m_len >= sizeof(struct ieee80211_qosframe)) { + struct ieee80211_qosframe *qwh = mtod(m, + struct ieee80211_qosframe *); + qwh->i_qos[0] &= htole16(~IEEE80211_QOS_AMSDU); + + /* HW reverses addr3. */ + iwm_flip_address(qwh->i_addr3); + } + } + + /* + * Verify decryption before duplicate detection. The latter uses + * the TID supplied in QoS frame headers and this TID is implicitly + * verified as part of the CCMP nonce. + */ + if (iwm_rx_hwdecrypt(sc, m, le16toh(desc->status), &rxi)) { + m_freem(m); + return; + } + + if (iwm_detect_duplicate(sc, m, desc, &rxi)) { + m_freem(m); + return; + } + phy_info = le16toh(desc->phy_info); rate_n_flags = le32toh(desc->v1.rate_n_flags); chanidx = desc->v1.channel; @@ -4228,10 +4914,14 @@ iwm_rx_mpdu_mq(struct iwm_softc *sc, struct mbuf *m, v rssi = (0 - IWM_MIN_DBM) + rssi; /* normalize */ rssi = MIN(rssi, ic->ic_max_rssi); /* clip to max. 100% */ - memset(&rxi, 0, sizeof(rxi)); rxi.rxi_rssi = rssi; rxi.rxi_tstamp = le64toh(desc->v1.tsf_on_air_rise); + if (iwm_rx_reorder(sc, m, chanidx, desc, + (phy_info & IWM_RX_MPDU_PHY_SHORT_PREAMBLE), + rate_n_flags, device_timestamp, &rxi, ml)) + return; + iwm_rx_frame(sc, m, chanidx, le16toh(desc->status), (phy_info & IWM_RX_MPDU_PHY_SHORT_PREAMBLE), rate_n_flags, device_timestamp, &rxi, ml); @@ -6691,6 +7381,8 @@ iwm_deauth(struct iwm_softc *sc) } sc->sc_flags &= ~IWM_FLAG_STA_ACTIVE; sc->sc_rx_ba_sessions = 0; + sc->ba_start_tidmask = 0; + sc->ba_stop_tidmask = 0; } tfd_queue_msk = 0; @@ -6769,6 +7461,8 @@ iwm_disassoc(struct iwm_softc *sc) } sc->sc_flags &= ~IWM_FLAG_STA_ACTIVE; sc->sc_rx_ba_sessions = 0; + sc->ba_start_tidmask = 0; + sc->ba_stop_tidmask = 0; } return 0; @@ -7327,11 +8021,16 @@ iwm_newstate(struct ieee80211com *ic, enum ieee80211_s { struct ifnet *ifp = IC2IFP(ic); struct iwm_softc *sc = ifp->if_softc; + int i; if (ic->ic_state == IEEE80211_S_RUN) { timeout_del(&sc->sc_calib_to); iwm_del_task(sc, systq, &sc->ba_task); iwm_del_task(sc, systq, &sc->htprot_task); + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + struct iwm_rxba_data *rxba = &sc->sc_rxba_data[i]; + iwm_clear_reorder_buffer(sc, rxba); + } } sc->ns_nstate = nstate; @@ -8137,10 +8836,19 @@ iwm_stop(struct ifnet *ifp) sc->sc_flags &= ~IWM_FLAG_SHUTDOWN; sc->sc_rx_ba_sessions = 0; + sc->ba_start_tidmask = 0; + sc->ba_stop_tidmask = 0; + memset(sc->ba_ssn, 0, sizeof(sc->ba_ssn)); + memset(sc->ba_winsize, 0, sizeof(sc->ba_winsize)); + memset(sc->ba_timeout_val, 0, sizeof(sc->ba_timeout_val)); sc->sc_newstate(ic, IEEE80211_S_INIT, -1); timeout_del(&sc->sc_calib_to); /* XXX refcount? */ + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + struct iwm_rxba_data *rxba = &sc->sc_rxba_data[i]; + iwm_clear_reorder_buffer(sc, rxba); + } iwm_led_blink_stop(sc); ifp->if_timer = sc->sc_tx_timer = 0; @@ -9217,7 +9925,7 @@ iwm_attach(struct device *parent, struct device *self, struct ifnet *ifp = &ic->ic_if; const char *intrstr; int err; - int txq_i, i; + int txq_i, i, j; sc->sc_pct = pa->pa_pc; sc->sc_pcitag = pa->pa_tag; @@ -9528,6 +10236,17 @@ iwm_attach(struct device *parent, struct device *self, #endif timeout_set(&sc->sc_calib_to, iwm_calib_timeout, sc); timeout_set(&sc->sc_led_blink_to, iwm_led_blink_timeout, sc); + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + struct iwm_rxba_data *rxba = &sc->sc_rxba_data[i]; + rxba->baid = IWM_RX_REORDER_DATA_INVALID_BAID; + rxba->sc = sc; + timeout_set(&rxba->session_timer, iwm_rx_ba_session_expired, + rxba); + timeout_set(&rxba->reorder_buf.reorder_timer, + iwm_reorder_timer_expired, &rxba->reorder_buf); + for (j = 0; j < nitems(rxba->entries); j++) + ml_init(&rxba->entries[j].frames); + } task_set(&sc->init_task, iwm_init_task, sc); task_set(&sc->newstate_task, iwm_newstate_task, sc); task_set(&sc->ba_task, iwm_ba_task, sc); blob - 201ce69014b9422335a6d698cd4a3cc3f314b2b5 blob + b2c61cbc20324f3871a7dc6fab9d68de97795a17 --- sys/dev/pci/if_iwmreg.h +++ sys/dev/pci/if_iwmreg.h @@ -3137,6 +3137,9 @@ struct iwm_rx_mpdu_res_start { #define IWM_RX_MPDU_MFLG2_PAD 0x20 #define IWM_RX_MPDU_MFLG2_AMSDU 0x40 +#define IWM_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK 0x7f +#define IWM_RX_MPDU_AMSDU_LAST_SUBFRAME 0x80 + #define IWM_RX_MPDU_PHY_AMPDU (1 << 5) #define IWM_RX_MPDU_PHY_AMPDU_TOGGLE (1 << 6) #define IWM_RX_MPDU_PHY_SHORT_PREAMBLE (1 << 7) @@ -3167,6 +3170,15 @@ struct iwm_rx_mpdu_desc_v1 { }; } __packed; +#define IWM_RX_REORDER_DATA_INVALID_BAID 0x7f + +#define IWM_RX_MPDU_REORDER_NSSN_MASK 0x00000fff +#define IWM_RX_MPDU_REORDER_SN_MASK 0x00fff000 +#define IWM_RX_MPDU_REORDER_SN_SHIFT 12 +#define IWM_RX_MPDU_REORDER_BAID_MASK 0x7f000000 +#define IWM_RX_MPDU_REORDER_BAID_SHIFT 24 +#define IWM_RX_MPDU_REORDER_BA_OLD_SN 0x80000000 + struct iwm_rx_mpdu_desc { uint16_t mpdu_len; uint8_t mac_flags1; @@ -4627,6 +4639,7 @@ struct iwm_lq_cmd { /* * TID for non QoS frames - to be written in tid_tspec */ +#define IWM_MAX_TID_COUNT 8 #define IWM_TID_NON_QOS IWM_MAX_TID_COUNT /* blob - 24c965b1a8e0e97c47b00f1a80a26bd50c1d46e9 blob + 14c16d3a321a9c10496a90a582886f9de8853570 --- sys/dev/pci/if_iwmvar.h +++ sys/dev/pci/if_iwmvar.h @@ -361,6 +361,99 @@ struct iwm_bf_data { int last_cqm_event; }; +/** + * struct iwm_reorder_buffer - per ra/tid/queue reorder buffer + * @head_sn: reorder window head sn + * @num_stored: number of mpdus stored in the buffer + * @buf_size: the reorder buffer size as set by the last addba request + * @queue: queue of this reorder buffer + * @last_amsdu: track last ASMDU SN for duplication detection + * @last_sub_index: track ASMDU sub frame index for duplication detection + * @reorder_timer: timer for frames are in the reorder buffer. For AMSDU + * it is the time of last received sub-frame + * @removed: prevent timer re-arming + * @valid: reordering is valid for this queue + * @consec_oldsn_drops: consecutive drops due to old SN + * @consec_oldsn_ampdu_gp2: A-MPDU GP2 timestamp to track + * when to apply old SN consecutive drop workaround + * @consec_oldsn_prev_drop: track whether or not an MPDU + * that was single/part of the previous A-MPDU was + * dropped due to old SN + */ +struct iwm_reorder_buffer { + uint16_t head_sn; + uint16_t num_stored; + uint16_t buf_size; + uint16_t last_amsdu; + uint8_t last_sub_index; + struct timeout reorder_timer; + int removed; + int valid; + unsigned int consec_oldsn_drops; + uint32_t consec_oldsn_ampdu_gp2; + unsigned int consec_oldsn_prev_drop; +#define IWM_AMPDU_CONSEC_DROPS_DELBA 10 +}; + +/** + * struct iwm_reorder_buf_entry - reorder buffer entry per frame sequence number + * @frames: list of mbufs stored (A-MSDU subframes share a sequence number) + * @reorder_time: time the packet was stored in the reorder buffer + */ +struct iwm_reorder_buf_entry { + struct mbuf_list frames; + struct timeval reorder_time; + uint32_t rx_pkt_status; + int chanidx; + int is_shortpre; + uint32_t rate_n_flags; + uint32_t device_timestamp; + struct ieee80211_rxinfo rxi; +}; + +/** + * struct iwm_rxba_data - BA session data + * @sta_id: station id + * @tid: tid of the session + * @baid: baid of the session + * @timeout: the timeout set in the addba request + * @entries_per_queue: # of buffers per queue + * @last_rx: last rx timestamp, updated only if timeout passed from last update + * @session_timer: timer to check if BA session expired, runs at 2 * timeout + * @sc: softc pointer, needed for timer context + * @reorder_buf: reorder buffer + * @reorder_buf_data: buffered frames, one entry per sequence number + */ +struct iwm_rxba_data { + uint8_t sta_id; + uint8_t tid; + uint8_t baid; + uint16_t timeout; + uint16_t entries_per_queue; + struct timeval last_rx; + struct timeout session_timer; + struct iwm_softc *sc; + struct iwm_reorder_buffer reorder_buf; + struct iwm_reorder_buf_entry entries[IEEE80211_BA_MAX_WINSZ]; +}; + +static inline struct iwm_rxba_data * +iwm_rxba_data_from_reorder_buf(struct iwm_reorder_buffer *buf) +{ + return (void *)((uint8_t *)buf - + offsetof(struct iwm_rxba_data, reorder_buf)); +} + +/** + * struct iwm_rxq_dup_data - per station per rx queue data + * @last_seq: last sequence per tid for duplicate packet detection + * @last_sub_frame: last subframe packet + */ +struct iwm_rxq_dup_data { + uint16_t last_seq[IWM_MAX_TID_COUNT + 1]; + uint8_t last_sub_frame[IWM_MAX_TID_COUNT + 1]; +}; + struct iwm_softc { struct device sc_dev; struct ieee80211com sc_ic; @@ -379,10 +472,11 @@ struct iwm_softc { /* Task for firmware BlockAck setup/teardown and its arguments. */ struct task ba_task; - int ba_start; - int ba_tid; - uint16_t ba_ssn; - uint16_t ba_winsize; + uint32_t ba_start_tidmask; + uint32_t ba_stop_tidmask; + uint16_t ba_ssn[IWM_MAX_TID_COUNT]; + uint16_t ba_winsize[IWM_MAX_TID_COUNT]; + int ba_timeout_val[IWM_MAX_TID_COUNT]; /* Task for HT protection updates. */ struct task htprot_task; @@ -495,6 +589,8 @@ struct iwm_softc { struct iwm_rx_phy_info sc_last_phy_info; int sc_ampdu_ref; +#define IWM_MAX_BAID 32 + struct iwm_rxba_data sc_rxba_data[IWM_MAX_BAID]; uint32_t sc_time_event_uid; @@ -548,6 +644,8 @@ struct iwm_node { struct ieee80211_amrr_node in_amn; struct ieee80211_ra_node in_rn; int lq_rate_mismatch; + + struct iwm_rxq_dup_data dup_data; }; #define IWM_STATION_ID 0 #define IWM_AUX_STA_ID 1 blob - cdb3bafe26a9c3096a5d5daa679210adea87eed6 blob + 821be5930004c36a46ae84c139cd3b6c46e29934 --- sys/dev/pci/if_iwx.c +++ sys/dev/pci/if_iwx.c @@ -129,6 +129,8 @@ #include <net80211/ieee80211_var.h> #include <net80211/ieee80211_radiotap.h> +#include <net80211/ieee80211_priv.h> /* for SEQ_LT */ +#undef DPRINTF /* defined in ieee80211_priv.h */ #define DEVNAME(_s) ((_s)->sc_dev.dv_xname) @@ -300,12 +302,17 @@ void iwx_setup_ht_rates(struct iwx_softc *); int iwx_mimo_enabled(struct iwx_softc *); void iwx_htprot_task(void *); void iwx_update_htprot(struct ieee80211com *, struct ieee80211_node *); +void iwx_init_reorder_buffer(struct iwx_reorder_buffer *, uint16_t, + uint16_t); +void iwx_clear_reorder_buffer(struct iwx_softc *, struct iwx_rxba_data *); int iwx_ampdu_rx_start(struct ieee80211com *, struct ieee80211_node *, uint8_t); void iwx_ampdu_rx_stop(struct ieee80211com *, struct ieee80211_node *, uint8_t); +void iwx_rx_ba_session_expired(void *); +void iwx_reorder_timer_expired(void *); void iwx_sta_rx_agg(struct iwx_softc *, struct ieee80211_node *, uint8_t, - uint16_t, uint16_t, int); + uint16_t, uint16_t, int, int); #ifdef notyet int iwx_ampdu_tx_start(struct ieee80211com *, struct ieee80211_node *, uint8_t); @@ -331,8 +338,10 @@ int iwx_rxmq_get_signal_strength(struct iwx_softc *, s void iwx_rx_rx_phy_cmd(struct iwx_softc *, struct iwx_rx_packet *, struct iwx_rx_data *); int iwx_get_noise(const struct iwx_statistics_rx_non_phy *); +int iwx_rx_hwdecrypt(struct iwx_softc *, struct mbuf *, uint32_t, + struct ieee80211_rxinfo *); int iwx_ccmp_decap(struct iwx_softc *, struct mbuf *, - struct ieee80211_node *); + struct ieee80211_node *, struct ieee80211_rxinfo *); void iwx_rx_frame(struct iwx_softc *, struct mbuf *, int, uint32_t, int, int, uint32_t, struct ieee80211_rxinfo *, struct mbuf_list *); void iwx_rx_tx_cmd_single(struct iwx_softc *, struct iwx_rx_packet *, @@ -427,6 +436,19 @@ int iwx_ioctl(struct ifnet *, u_long, caddr_t); const char *iwx_desc_lookup(uint32_t); void iwx_nic_error(struct iwx_softc *); void iwx_nic_umac_error(struct iwx_softc *); +int iwx_detect_duplicate(struct iwx_softc *, struct mbuf *, + struct iwx_rx_mpdu_desc *, struct ieee80211_rxinfo *); +int iwx_is_sn_less(uint16_t, uint16_t, uint16_t); +void iwx_release_frames(struct iwx_softc *, struct ieee80211_node *, + struct iwx_rxba_data *, struct iwx_reorder_buffer *, uint16_t, + struct mbuf_list *); +int iwx_oldsn_workaround(struct iwx_softc *, struct ieee80211_node *, + int, struct iwx_reorder_buffer *, uint32_t, uint32_t); +int iwx_rx_reorder(struct iwx_softc *, struct mbuf *, int, + struct iwx_rx_mpdu_desc *, int, int, uint32_t, + struct ieee80211_rxinfo *, struct mbuf_list *); +void iwx_rx_mpdu_mq(struct iwx_softc *, struct mbuf *, void *, size_t, + struct mbuf_list *); int iwx_rx_pkt_valid(struct iwx_rx_packet *); void iwx_rx_pkt(struct iwx_softc *, struct iwx_rx_data *, struct mbuf_list *); @@ -2680,20 +2702,153 @@ iwx_setup_ht_rates(struct iwx_softc *sc) ic->ic_sup_mcs[1] = 0xff; /* MCS 8-15 */ } +void +iwx_init_reorder_buffer(struct iwx_reorder_buffer *reorder_buf, + uint16_t ssn, uint16_t buf_size) +{ + reorder_buf->head_sn = ssn; + reorder_buf->num_stored = 0; + reorder_buf->buf_size = buf_size; + reorder_buf->last_amsdu = 0; + reorder_buf->last_sub_index = 0; + reorder_buf->removed = 0; + reorder_buf->valid = 0; + reorder_buf->consec_oldsn_drops = 0; + reorder_buf->consec_oldsn_ampdu_gp2 = 0; + reorder_buf->consec_oldsn_prev_drop = 0; +} + +void +iwx_clear_reorder_buffer(struct iwx_softc *sc, struct iwx_rxba_data *rxba) +{ + int i; + struct iwx_reorder_buffer *reorder_buf = &rxba->reorder_buf; + struct iwx_reorder_buf_entry *entry; + + for (i = 0; i < reorder_buf->buf_size; i++) { + entry = &rxba->entries[i]; + ml_purge(&entry->frames); + timerclear(&entry->reorder_time); + } + + reorder_buf->removed = 1; + timeout_del(&reorder_buf->reorder_timer); + timerclear(&rxba->last_rx); + timeout_del(&rxba->session_timer); + rxba->baid = IWX_RX_REORDER_DATA_INVALID_BAID; +} + +#define RX_REORDER_BUF_TIMEOUT_MQ_USEC (100000ULL) + +void +iwx_rx_ba_session_expired(void *arg) +{ + struct iwx_rxba_data *rxba = arg; + struct iwx_softc *sc = rxba->sc; + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_node *ni = ic->ic_bss; + struct timeval now, timeout, expiry; + int s; + + s = splnet(); + if ((sc->sc_flags & IWX_FLAG_SHUTDOWN) == 0 && + ic->ic_state == IEEE80211_S_RUN && + rxba->baid != IWX_RX_REORDER_DATA_INVALID_BAID) { + getmicrouptime(&now); + USEC_TO_TIMEVAL(RX_REORDER_BUF_TIMEOUT_MQ_USEC, &timeout); + timeradd(&rxba->last_rx, &timeout, &expiry); + if (timercmp(&now, &expiry, <)) { + timeout_add_usec(&rxba->session_timer, rxba->timeout); + } else { + ic->ic_stats.is_ht_rx_ba_timeout++; + ieee80211_delba_request(ic, ni, + IEEE80211_REASON_TIMEOUT, 0, rxba->tid); + } + } + splx(s); +} + +void +iwx_reorder_timer_expired(void *arg) +{ + struct mbuf_list ml = MBUF_LIST_INITIALIZER(); + struct iwx_reorder_buffer *buf = arg; + struct iwx_rxba_data *rxba = iwx_rxba_data_from_reorder_buf(buf); + struct iwx_reorder_buf_entry *entries = &rxba->entries[0]; + struct iwx_softc *sc = rxba->sc; + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_node *ni = ic->ic_bss; + int i, s; + uint16_t sn = 0, index = 0; + int expired = 0; + int cont = 0; + struct timeval now, timeout, expiry; + + if (!buf->num_stored || buf->removed) + return; + + s = splnet(); + getmicrouptime(&now); + USEC_TO_TIMEVAL(RX_REORDER_BUF_TIMEOUT_MQ_USEC, &timeout); + + for (i = 0; i < buf->buf_size ; i++) { + index = (buf->head_sn + i) % buf->buf_size; + + if (ml_empty(&entries[index].frames)) { + /* + * If there is a hole and the next frame didn't expire + * we want to break and not advance SN. + */ + cont = 0; + continue; + } + timeradd(&entries[index].reorder_time, &timeout, &expiry); + if (!cont && timercmp(&now, &expiry, <)) + break; + + expired = 1; + /* continue until next hole after this expired frame */ + cont = 1; + sn = (buf->head_sn + (i + 1)) & 0xfff; + } + + if (expired) { + /* SN is set to the last expired frame + 1 */ + iwx_release_frames(sc, ni, rxba, buf, sn, &ml); + if_input(&sc->sc_ic.ic_if, &ml); + ic->ic_stats.is_ht_rx_ba_window_gap_timeout++; + } else { + /* + * If no frame expired and there are stored frames, index is now + * pointing to the first unexpired frame - modify reorder timeout + * accordingly. + */ + timeout_add_usec(&buf->reorder_timer, + RX_REORDER_BUF_TIMEOUT_MQ_USEC); + } + + splx(s); +} + #define IWX_MAX_RX_BA_SESSIONS 16 void iwx_sta_rx_agg(struct iwx_softc *sc, struct ieee80211_node *ni, uint8_t tid, - uint16_t ssn, uint16_t winsize, int start) + uint16_t ssn, uint16_t winsize, int timeout_val, int start) { struct ieee80211com *ic = &sc->sc_ic; struct iwx_add_sta_cmd cmd; struct iwx_node *in = (void *)ni; int err, s; uint32_t status; + struct iwx_rxba_data *rxba = NULL; + uint8_t baid = 0; + + s = splnet(); if (start && sc->sc_rx_ba_sessions >= IWX_MAX_RX_BA_SESSIONS) { ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); return; } @@ -2718,16 +2873,69 @@ iwx_sta_rx_agg(struct iwx_softc *sc, struct ieee80211_ err = iwx_send_cmd_pdu_status(sc, IWX_ADD_STA, sizeof(cmd), &cmd, &status); - s = splnet(); - if (!err && (status & IWX_ADD_STA_STATUS_MASK) == IWX_ADD_STA_SUCCESS) { - if (start) { - sc->sc_rx_ba_sessions++; - ieee80211_addba_req_accept(ic, ni, tid); - } else if (sc->sc_rx_ba_sessions > 0) - sc->sc_rx_ba_sessions--; - } else if (start) - ieee80211_addba_req_refuse(ic, ni, tid); + if (err || (status & IWX_ADD_STA_STATUS_MASK) != IWX_ADD_STA_SUCCESS) { + if (start) + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + /* Deaggregation is done in hardware. */ + if (start) { + if (!(status & IWX_ADD_STA_BAID_VALID_MASK)) { + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + baid = (status & IWX_ADD_STA_BAID_MASK) >> + IWX_ADD_STA_BAID_SHIFT; + if (baid == IWX_RX_REORDER_DATA_INVALID_BAID || + baid >= nitems(sc->sc_rxba_data)) { + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + rxba = &sc->sc_rxba_data[baid]; + if (rxba->baid != IWX_RX_REORDER_DATA_INVALID_BAID) { + ieee80211_addba_req_refuse(ic, ni, tid); + splx(s); + return; + } + rxba->sta_id = IWX_STATION_ID; + rxba->tid = tid; + rxba->baid = baid; + rxba->timeout = timeout_val; + getmicrouptime(&rxba->last_rx); + iwx_init_reorder_buffer(&rxba->reorder_buf, ssn, + winsize); + if (timeout_val != 0) { + struct ieee80211_rx_ba *ba; + timeout_add_usec(&rxba->session_timer, + timeout_val); + /* XXX disable net80211's BA timeout handler */ + ba = &ni->ni_rx_ba[tid]; + ba->ba_timeout_val = 0; + } + } else { + int i; + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + rxba = &sc->sc_rxba_data[i]; + if (rxba->baid == + IWX_RX_REORDER_DATA_INVALID_BAID) + continue; + if (rxba->tid != tid) + continue; + iwx_clear_reorder_buffer(sc, rxba); + break; + } + } + + if (start) { + sc->sc_rx_ba_sessions++; + ieee80211_addba_req_accept(ic, ni, tid); + } else if (sc->sc_rx_ba_sessions > 0) + sc->sc_rx_ba_sessions--; + splx(s); } @@ -2775,18 +2983,20 @@ iwx_ba_task(void *arg) struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_node *ni = ic->ic_bss; int s = splnet(); + int tid; - if (sc->sc_flags & IWX_FLAG_SHUTDOWN) { - refcnt_rele_wake(&sc->task_refs); - splx(s); - return; + for (tid = 0; tid < IWX_MAX_TID_COUNT; tid++) { + if (sc->sc_flags & IWX_FLAG_SHUTDOWN) + break; + if (sc->ba_start_tidmask & (1 << tid)) { + iwx_sta_rx_agg(sc, ni, tid, sc->ba_ssn[tid], + sc->ba_winsize[tid], sc->ba_timeout_val[tid], 1); + sc->ba_start_tidmask &= ~(1 << tid); + } else if (sc->ba_stop_tidmask & (1 << tid)) { + iwx_sta_rx_agg(sc, ni, tid, 0, 0, 0, 0); + sc->ba_stop_tidmask &= ~(1 << tid); + } } - - if (sc->ba_start) - iwx_sta_rx_agg(sc, ni, sc->ba_tid, sc->ba_ssn, - sc->ba_winsize, 1); - else - iwx_sta_rx_agg(sc, ni, sc->ba_tid, 0, 0, 0); refcnt_rele_wake(&sc->task_refs); splx(s); @@ -2803,13 +3013,14 @@ iwx_ampdu_rx_start(struct ieee80211com *ic, struct iee struct ieee80211_rx_ba *ba = &ni->ni_rx_ba[tid]; struct iwx_softc *sc = IC2IFP(ic)->if_softc; - if (sc->sc_rx_ba_sessions >= IWX_MAX_RX_BA_SESSIONS) + if (sc->sc_rx_ba_sessions >= IWX_MAX_RX_BA_SESSIONS || + tid > IWX_MAX_TID_COUNT || (sc->ba_start_tidmask & (1 << tid))) return ENOSPC; - sc->ba_start = 1; - sc->ba_tid = tid; - sc->ba_ssn = htole16(ba->ba_winstart); - sc->ba_winsize = htole16(ba->ba_winsize); + sc->ba_start_tidmask |= (1 << tid); + sc->ba_ssn[tid] = ba->ba_winstart; + sc->ba_winsize[tid] = ba->ba_winsize; + sc->ba_timeout_val[tid] = ba->ba_timeout_val; iwx_add_task(sc, systq, &sc->ba_task); return EBUSY; @@ -2825,8 +3036,10 @@ iwx_ampdu_rx_stop(struct ieee80211com *ic, struct ieee { struct iwx_softc *sc = IC2IFP(ic)->if_softc; - sc->ba_start = 0; - sc->ba_tid = tid; + if (tid > IWX_MAX_TID_COUNT || sc->ba_stop_tidmask & (1 << tid)) + return; + + sc->ba_stop_tidmask = (1 << tid); iwx_add_task(sc, systq, &sc->ba_task); } @@ -3249,7 +3462,8 @@ iwx_get_noise(const struct iwx_statistics_rx_non_phy * } int -iwx_ccmp_decap(struct iwx_softc *sc, struct mbuf *m, struct ieee80211_node *ni) +iwx_ccmp_decap(struct iwx_softc *sc, struct mbuf *m, struct ieee80211_node *ni, + struct ieee80211_rxinfo *rxi) { struct ieee80211com *ic = &sc->sc_ic; struct ieee80211_key *k; @@ -3283,7 +3497,12 @@ iwx_ccmp_decap(struct iwx_softc *sc, struct mbuf *m, s (uint64_t)ivp[5] << 24 | (uint64_t)ivp[6] << 32 | (uint64_t)ivp[7] << 40; - if (pn <= *prsc) { + if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) { + if (pn < *prsc) { + ic->ic_stats.is_ccmp_replays++; + return 1; + } + } else if (pn <= *prsc) { ic->ic_stats.is_ccmp_replays++; return 1; } @@ -3300,34 +3519,28 @@ iwx_ccmp_decap(struct iwx_softc *sc, struct mbuf *m, s return 0; } -void -iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int chanidx, - uint32_t rx_pkt_status, int is_shortpre, int rate_n_flags, - uint32_t device_timestamp, struct ieee80211_rxinfo *rxi, - struct mbuf_list *ml) +int +iwx_rx_hwdecrypt(struct iwx_softc *sc, struct mbuf *m, uint32_t rx_pkt_status, + struct ieee80211_rxinfo *rxi) { struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = IC2IFP(ic); struct ieee80211_frame *wh; struct ieee80211_node *ni; - struct ieee80211_channel *bss_chan; - uint8_t saved_bssid[IEEE80211_ADDR_LEN] = { 0 }; - struct ifnet *ifp = IC2IFP(ic); + int ret = 0; + uint8_t type, subtype; - if (chanidx < 0 || chanidx >= nitems(ic->ic_channels)) - chanidx = ieee80211_chan2ieee(ic, ic->ic_ibss_chan); - wh = mtod(m, struct ieee80211_frame *); + + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + if (type == IEEE80211_FC0_TYPE_CTL) + return 0; + + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + if (ieee80211_has_qos(wh) && (subtype & IEEE80211_FC0_SUBTYPE_NODATA)) + return 0; + ni = ieee80211_find_rxnode(ic, wh); - if (ni == ic->ic_bss) { - /* - * We may switch ic_bss's channel during scans. - * Record the current channel so we can restore it later. - */ - bss_chan = ni->ni_chan; - IEEE80211_ADDR_COPY(&saved_bssid, ni->ni_macaddr); - } - ni->ni_chan = &ic->ic_channels[chanidx]; - /* Handle hardware decryption. */ if (((wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) != IEEE80211_FC0_TYPE_CTL) && (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) && @@ -3339,10 +3552,8 @@ iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int if ((rx_pkt_status & IWX_RX_MPDU_RES_STATUS_SEC_ENC_MSK) != IWX_RX_MPDU_RES_STATUS_SEC_CCM_ENC) { ic->ic_stats.is_ccmp_dec_errs++; - ifp->if_ierrors++; - m_freem(m); - ieee80211_release_node(ic, ni); - return; + ret = 1; + goto out; } /* Check whether decryption was successful or not. */ if ((rx_pkt_status & @@ -3351,20 +3562,54 @@ iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int (IWX_RX_MPDU_RES_STATUS_DEC_DONE | IWX_RX_MPDU_RES_STATUS_MIC_OK)) { ic->ic_stats.is_ccmp_dec_errs++; - ifp->if_ierrors++; - m_freem(m); - ieee80211_release_node(ic, ni); - return; + ret = 1; + goto out; } - if (iwx_ccmp_decap(sc, m, ni) != 0) { - ifp->if_ierrors++; - m_freem(m); - ieee80211_release_node(ic, ni); - return; - } rxi->rxi_flags |= IEEE80211_RXI_HWDEC; } +out: + if (ret) + ifp->if_ierrors++; + ieee80211_release_node(ic, ni); + return ret; +} +void +iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int chanidx, + uint32_t rx_pkt_status, int is_shortpre, int rate_n_flags, + uint32_t device_timestamp, struct ieee80211_rxinfo *rxi, + struct mbuf_list *ml) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct ifnet *ifp = IC2IFP(ic); + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct ieee80211_channel *bss_chan; + uint8_t saved_bssid[IEEE80211_ADDR_LEN] = { 0 }; + + 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) { + /* + * We may switch ic_bss's channel during scans. + * Record the current channel so we can restore it later. + */ + bss_chan = ni->ni_chan; + IEEE80211_ADDR_COPY(&saved_bssid, ni->ni_macaddr); + } + ni->ni_chan = &ic->ic_channels[chanidx]; + + if ((rxi->rxi_flags & IEEE80211_RXI_HWDEC) && + iwx_ccmp_decap(sc, m, ni, rxi) != 0) { + ifp->if_ierrors++; + m_freem(m); + ieee80211_release_node(ic, ni); + return; + } + #if NBPFILTER > 0 if (sc->sc_drvbpf != NULL) { struct iwx_rx_radiotap_header *tap = &sc->sc_rxtap; @@ -3424,7 +3669,376 @@ iwx_rx_frame(struct iwx_softc *sc, struct mbuf *m, int ieee80211_release_node(ic, ni); } +/* + * Drop duplicate 802.11 retransmissions + * (IEEE 802.11-2012: 9.3.2.10 "Duplicate detection and recovery") + * and handle pseudo-duplicate frames which result from deaggregation + * of A-MSDU frames in hardware. + */ +int +iwx_detect_duplicate(struct iwx_softc *sc, struct mbuf *m, + struct iwx_rx_mpdu_desc *desc, struct ieee80211_rxinfo *rxi) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct iwx_node *in = (void *)ic->ic_bss; + struct iwx_rxq_dup_data *dup_data = &in->dup_data; + uint8_t tid = IWX_MAX_TID_COUNT, subframe_idx; + struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); + uint8_t type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + uint8_t subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + int hasqos = ieee80211_has_qos(wh); + uint16_t seq; + + if (type == IEEE80211_FC0_TYPE_CTL || + (hasqos && (subtype & IEEE80211_FC0_SUBTYPE_NODATA)) || + IEEE80211_IS_MULTICAST(wh->i_addr1)) + return 0; + + if (hasqos) { + tid = (ieee80211_get_qos(wh) & IEEE80211_QOS_TID); + if (tid > IWX_MAX_TID_COUNT) + tid = IWX_MAX_TID_COUNT; + } + + /* If this wasn't a part of an A-MSDU the sub-frame index will be 0 */ + subframe_idx = desc->amsdu_info & + IWX_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK; + + seq = letoh16(*(u_int16_t *)wh->i_seq) >> IEEE80211_SEQ_SEQ_SHIFT; + if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && + dup_data->last_seq[tid] == seq && + dup_data->last_sub_frame[tid] >= subframe_idx) + return 1; + + /* + * Allow the same frame sequence number for all A-MSDU subframes + * following the first subframe. + * Otherwise these subframes would be discarded as replays. + */ + if (dup_data->last_seq[tid] == seq && + subframe_idx > dup_data->last_sub_frame[tid] && + (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_AMSDU)) { + rxi->rxi_flags |= IEEE80211_RXI_SAME_SEQ; + } + + dup_data->last_seq[tid] = seq; + dup_data->last_sub_frame[tid] = subframe_idx; + + return 0; +} + +/* + * Returns true if sn2 - buffer_size < sn1 < sn2. + * To be used only in order to compare reorder buffer head with NSSN. + * We fully trust NSSN unless it is behind us due to reorder timeout. + * Reorder timeout can only bring us up to buffer_size SNs ahead of NSSN. + */ +int +iwx_is_sn_less(uint16_t sn1, uint16_t sn2, uint16_t buffer_size) +{ + return SEQ_LT(sn1, sn2) && !SEQ_LT(sn1, sn2 - buffer_size); +} + void +iwx_release_frames(struct iwx_softc *sc, struct ieee80211_node *ni, + struct iwx_rxba_data *rxba, struct iwx_reorder_buffer *reorder_buf, + uint16_t nssn, struct mbuf_list *ml) +{ + struct iwx_reorder_buf_entry *entries = &rxba->entries[0]; + uint16_t ssn = reorder_buf->head_sn; + + /* ignore nssn smaller than head sn - this can happen due to timeout */ + if (iwx_is_sn_less(nssn, ssn, reorder_buf->buf_size)) + goto set_timer; + + while (iwx_is_sn_less(ssn, nssn, reorder_buf->buf_size)) { + int index = ssn % reorder_buf->buf_size; + struct mbuf *m; + int chanidx, is_shortpre; + uint32_t rx_pkt_status, rate_n_flags, device_timestamp; + struct ieee80211_rxinfo *rxi; + + /* This data is the same for all A-MSDU subframes. */ + chanidx = entries[index].chanidx; + rx_pkt_status = entries[index].rx_pkt_status; + is_shortpre = entries[index].is_shortpre; + rate_n_flags = entries[index].rate_n_flags; + device_timestamp = entries[index].device_timestamp; + rxi = &entries[index].rxi; + + /* + * Empty the list. Will have more than one frame for A-MSDU. + * Empty list is valid as well since nssn indicates frames were + * received. + */ + while ((m = ml_dequeue(&entries[index].frames)) != NULL) { + iwx_rx_frame(sc, m, chanidx, rx_pkt_status, is_shortpre, + rate_n_flags, device_timestamp, rxi, ml); + reorder_buf->num_stored--; + + /* + * Allow the same frame sequence number and CCMP PN for + * all A-MSDU subframes following the first subframe. + * Otherwise they would be discarded as replays. + */ + rxi->rxi_flags |= IEEE80211_RXI_SAME_SEQ; + rxi->rxi_flags |= IEEE80211_RXI_HWDEC_SAME_PN; + } + + ssn = (ssn + 1) & 0xfff; + } + reorder_buf->head_sn = nssn; + +set_timer: + if (reorder_buf->num_stored && !reorder_buf->removed) { + timeout_add_usec(&reorder_buf->reorder_timer, + RX_REORDER_BUF_TIMEOUT_MQ_USEC); + } else + timeout_del(&reorder_buf->reorder_timer); +} + +int +iwx_oldsn_workaround(struct iwx_softc *sc, struct ieee80211_node *ni, int tid, + struct iwx_reorder_buffer *buffer, uint32_t reorder_data, uint32_t gp2) +{ + struct ieee80211com *ic = &sc->sc_ic; + + if (gp2 != buffer->consec_oldsn_ampdu_gp2) { + /* we have a new (A-)MPDU ... */ + + /* + * reset counter to 0 if we didn't have any oldsn in + * the last A-MPDU (as detected by GP2 being identical) + */ + if (!buffer->consec_oldsn_prev_drop) + buffer->consec_oldsn_drops = 0; + + /* either way, update our tracking state */ + buffer->consec_oldsn_ampdu_gp2 = gp2; + } else if (buffer->consec_oldsn_prev_drop) { + /* + * tracking state didn't change, and we had an old SN + * indication before - do nothing in this case, we + * already noted this one down and are waiting for the + * next A-MPDU (by GP2) + */ + return 0; + } + + /* return unless this MPDU has old SN */ + if (!(reorder_data & IWX_RX_MPDU_REORDER_BA_OLD_SN)) + return 0; + + /* update state */ + buffer->consec_oldsn_prev_drop = 1; + buffer->consec_oldsn_drops++; + + /* if limit is reached, send del BA and reset state */ + if (buffer->consec_oldsn_drops == IWX_AMPDU_CONSEC_DROPS_DELBA) { + ieee80211_delba_request(ic, ni, IEEE80211_REASON_UNSPECIFIED, + 0, tid); + buffer->consec_oldsn_prev_drop = 0; + buffer->consec_oldsn_drops = 0; + return 1; + } + + return 0; +} + +/* + * Handle re-ordering of frames which were de-aggregated in hardware. + * Returns 1 if the MPDU was consumed (buffered or dropped). + * Returns 0 if the MPDU should be passed to upper layer. + */ +int +iwx_rx_reorder(struct iwx_softc *sc, struct mbuf *m, int chanidx, + struct iwx_rx_mpdu_desc *desc, int is_shortpre, int rate_n_flags, + uint32_t device_timestamp, struct ieee80211_rxinfo *rxi, + struct mbuf_list *ml) +{ + struct ieee80211com *ic = &sc->sc_ic; + struct ieee80211_frame *wh; + struct ieee80211_node *ni; + struct iwx_rxba_data *rxba; + struct iwx_reorder_buffer *buffer; + uint32_t reorder_data = le32toh(desc->reorder_data); + int is_amsdu = (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_AMSDU); + int last_subframe = + (desc->amsdu_info & IWX_RX_MPDU_AMSDU_LAST_SUBFRAME); + uint8_t tid; + uint8_t subframe_idx = (desc->amsdu_info & + IWX_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK); + struct iwx_reorder_buf_entry *entries; + int index; + uint16_t nssn, sn; + uint8_t baid, type, subtype; + int hasqos; + + wh = mtod(m, struct ieee80211_frame *); + hasqos = ieee80211_has_qos(wh); + tid = hasqos ? ieee80211_get_qos(wh) & IEEE80211_QOS_TID : 0; + + type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK; + subtype = wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK; + ni = ieee80211_find_rxnode(ic, wh); + + /* + * We are only interested in Block Ack requests and unicast QoS data. + */ + if (IEEE80211_IS_MULTICAST(wh->i_addr1)) + return 0; + if (hasqos) { + if (subtype & IEEE80211_FC0_SUBTYPE_NODATA) + return 0; + } else { + if (type != IEEE80211_FC0_TYPE_CTL || + subtype != IEEE80211_FC0_SUBTYPE_BAR) + return 0; + } + + baid = (reorder_data & IWX_RX_MPDU_REORDER_BAID_MASK) >> + IWX_RX_MPDU_REORDER_BAID_SHIFT; + if (baid == IWX_RX_REORDER_DATA_INVALID_BAID || + baid >= nitems(sc->sc_rxba_data)) + return 0; + + rxba = &sc->sc_rxba_data[baid]; + if (rxba == NULL || tid != rxba->tid || rxba->sta_id != IWX_STATION_ID) + return 0; + + /* Bypass A-MPDU re-ordering in net80211. */ + rxi->rxi_flags |= IEEE80211_RXI_AMPDU_DONE; + + nssn = reorder_data & IWX_RX_MPDU_REORDER_NSSN_MASK; + sn = (reorder_data & IWX_RX_MPDU_REORDER_SN_MASK) >> + IWX_RX_MPDU_REORDER_SN_SHIFT; + + buffer = &rxba->reorder_buf; + entries = &rxba->entries[0]; + + if (!buffer->valid) { + if (reorder_data & IWX_RX_MPDU_REORDER_BA_OLD_SN) + return 0; + buffer->valid = 1; + } + + if (type == IEEE80211_FC0_TYPE_CTL && + subtype == IEEE80211_FC0_SUBTYPE_BAR) { + iwx_release_frames(sc, ni, rxba, buffer, nssn, ml); + goto drop; + } + + /* + * If there was a significant jump in the nssn - adjust. + * If the SN is smaller than the NSSN it might need to first go into + * the reorder buffer, in which case we just release up to it and the + * rest of the function will take care of storing it and releasing up to + * the nssn. + */ + if (!iwx_is_sn_less(nssn, buffer->head_sn + buffer->buf_size, + buffer->buf_size) || + !SEQ_LT(sn, buffer->head_sn + buffer->buf_size)) { + uint16_t min_sn = SEQ_LT(sn, nssn) ? sn : nssn; + ic->ic_stats.is_ht_rx_frame_above_ba_winend++; + iwx_release_frames(sc, ni, rxba, buffer, min_sn, ml); + } + + if (iwx_oldsn_workaround(sc, ni, tid, buffer, reorder_data, + device_timestamp)) { + /* BA session will be torn down. */ + ic->ic_stats.is_ht_rx_ba_window_jump++; + goto drop; + + } + + /* drop any outdated packets */ + if (SEQ_LT(sn, buffer->head_sn)) { + ic->ic_stats.is_ht_rx_frame_below_ba_winstart++; + goto drop; + } + + /* release immediately if allowed by nssn and no stored frames */ + if (!buffer->num_stored && SEQ_LT(sn, nssn)) { + if (iwx_is_sn_less(buffer->head_sn, nssn, buffer->buf_size) && + (!is_amsdu || last_subframe)) + buffer->head_sn = nssn; + return 0; + } + + /* + * release immediately if there are no stored frames, and the sn is + * equal to the head. + * This can happen due to reorder timer, where NSSN is behind head_sn. + * When we released everything, and we got the next frame in the + * sequence, according to the NSSN we can't release immediately, + * while technically there is no hole and we can move forward. + */ + if (!buffer->num_stored && sn == buffer->head_sn) { + if (!is_amsdu || last_subframe) + buffer->head_sn = (buffer->head_sn + 1) & 0xfff; + return 0; + } + + index = sn % buffer->buf_size; + + /* + * Check if we already stored this frame + * As AMSDU is either received or not as whole, logic is simple: + * If we have frames in that position in the buffer and the last frame + * originated from AMSDU had a different SN then it is a retransmission. + * If it is the same SN then if the subframe index is incrementing it + * is the same AMSDU - otherwise it is a retransmission. + */ + if (!ml_empty(&entries[index].frames)) { + if (!is_amsdu) { + ic->ic_stats.is_ht_rx_ba_no_buf++; + goto drop; + } else if (sn != buffer->last_amsdu || + buffer->last_sub_index >= subframe_idx) { + ic->ic_stats.is_ht_rx_ba_no_buf++; + goto drop; + } + } else { + /* This data is the same for all A-MSDU subframes. */ + entries[index].chanidx = chanidx; + entries[index].is_shortpre = is_shortpre; + entries[index].rate_n_flags = rate_n_flags; + entries[index].device_timestamp = device_timestamp; + memcpy(&entries[index].rxi, rxi, sizeof(entries[index].rxi)); + } + + /* put in reorder buffer */ + ml_enqueue(&entries[index].frames, m); + buffer->num_stored++; + getmicrouptime(&entries[index].reorder_time); + + if (is_amsdu) { + buffer->last_amsdu = sn; + buffer->last_sub_index = subframe_idx; + } + + /* + * We cannot trust NSSN for AMSDU sub-frames that are not the last. + * The reason is that NSSN advances on the first sub-frame, and may + * cause the reorder buffer to advance before all the sub-frames arrive. + * Example: reorder buffer contains SN 0 & 2, and we receive AMSDU with + * SN 1. NSSN for first sub frame will be 3 with the result of driver + * releasing SN 0,1, 2. When sub-frame 1 arrives - reorder buffer is + * already ahead and it will be dropped. + * If the last sub-frame is not on this queue - we will get frame + * release notification with up to date NSSN. + */ + if (!is_amsdu || last_subframe) + iwx_release_frames(sc, ni, rxba, buffer, nssn, ml); + + return 1; + +drop: + m_freem(m); + return 1; +} + +void iwx_rx_mpdu_mq(struct iwx_softc *sc, struct mbuf *m, void *pktdata, size_t maxlen, struct mbuf_list *ml) { @@ -3498,6 +4112,55 @@ iwx_rx_mpdu_mq(struct iwx_softc *sc, struct mbuf *m, v m_adj(m, 2); } + memset(&rxi, 0, sizeof(rxi)); + + /* + * Hardware de-aggregates A-MSDUs and copies the same MAC header + * in place for each subframe. But it leaves the 'A-MSDU present' + * bit set in the frame header. We need to clear this bit ourselves. + * (XXX This workaround is not required on AX200/AX201 devices that + * have been tested by me, but it's unclear when this problem was + * fixed in the hardware. It definitely affects the 9k generation. + * Leaving this in place for now since some 9k/AX200 hybrids seem + * to exist that we may eventually add support for.) + * + * And we must allow the same CCMP PN for subframes following the + * first subframe. Otherwise they would be discarded as replays. + */ + if (desc->mac_flags2 & IWX_RX_MPDU_MFLG2_AMSDU) { + struct ieee80211_frame *wh = mtod(m, struct ieee80211_frame *); + uint8_t subframe_idx = (desc->amsdu_info & + IWX_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK); + if (subframe_idx > 0) + rxi.rxi_flags |= IEEE80211_RXI_HWDEC_SAME_PN; + if (ieee80211_has_qos(wh) && ieee80211_has_addr4(wh) && + m->m_len >= sizeof(struct ieee80211_qosframe_addr4)) { + struct ieee80211_qosframe_addr4 *qwh4 = mtod(m, + struct ieee80211_qosframe_addr4 *); + qwh4->i_qos[0] &= htole16(~IEEE80211_QOS_AMSDU); + } else if (ieee80211_has_qos(wh) && + m->m_len >= sizeof(struct ieee80211_qosframe)) { + struct ieee80211_qosframe *qwh = mtod(m, + struct ieee80211_qosframe *); + qwh->i_qos[0] &= htole16(~IEEE80211_QOS_AMSDU); + } + } + + /* + * Verify decryption before duplicate detection. The latter uses + * the TID supplied in QoS frame headers and this TID is implicitly + * verified as part of the CCMP nonce. + */ + if (iwx_rx_hwdecrypt(sc, m, le16toh(desc->status), &rxi)) { + m_freem(m); + return; + } + + if (iwx_detect_duplicate(sc, m, desc, &rxi)) { + m_freem(m); + return; + } + phy_info = le16toh(desc->phy_info); rate_n_flags = le32toh(desc->v1.rate_n_flags); chanidx = desc->v1.channel; @@ -3507,10 +4170,14 @@ iwx_rx_mpdu_mq(struct iwx_softc *sc, struct mbuf *m, v rssi = (0 - IWX_MIN_DBM) + rssi; /* normalize */ rssi = MIN(rssi, ic->ic_max_rssi); /* clip to max. 100% */ - memset(&rxi, 0, sizeof(rxi)); rxi.rxi_rssi = rssi; rxi.rxi_tstamp = le64toh(desc->v1.tsf_on_air_rise); + if (iwx_rx_reorder(sc, m, chanidx, desc, + (phy_info & IWX_RX_MPDU_PHY_SHORT_PREAMBLE), + rate_n_flags, device_timestamp, &rxi, ml)) + return; + iwx_rx_frame(sc, m, chanidx, le16toh(desc->status), (phy_info & IWX_RX_MPDU_PHY_SHORT_PREAMBLE), rate_n_flags, device_timestamp, &rxi, ml); @@ -5818,6 +6485,10 @@ iwx_disassoc(struct iwx_softc *sc) } sc->sc_flags &= ~IWX_FLAG_STA_ACTIVE; sc->sc_rx_ba_sessions = 0; + sc->ba_start_tidmask = 0; + sc->ba_stop_tidmask = 0; + sc->ba_start_tidmask = 0; + sc->ba_stop_tidmask = 0; } return 0; @@ -6179,10 +6850,15 @@ iwx_newstate(struct ieee80211com *ic, enum ieee80211_s { struct ifnet *ifp = IC2IFP(ic); struct iwx_softc *sc = ifp->if_softc; + int i; if (ic->ic_state == IEEE80211_S_RUN) { iwx_del_task(sc, systq, &sc->ba_task); iwx_del_task(sc, systq, &sc->htprot_task); + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + struct iwx_rxba_data *rxba = &sc->sc_rxba_data[i]; + iwx_clear_reorder_buffer(sc, rxba); + } } sc->ns_nstate = nstate; @@ -6778,9 +7454,18 @@ iwx_stop(struct ifnet *ifp) sc->sc_flags &= ~IWX_FLAG_SHUTDOWN; sc->sc_rx_ba_sessions = 0; + sc->ba_start_tidmask = 0; + sc->ba_stop_tidmask = 0; + memset(sc->ba_ssn, 0, sizeof(sc->ba_ssn)); + memset(sc->ba_winsize, 0, sizeof(sc->ba_winsize)); + memset(sc->ba_timeout_val, 0, sizeof(sc->ba_timeout_val)); sc->sc_newstate(ic, IEEE80211_S_INIT, -1); + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + struct iwx_rxba_data *rxba = &sc->sc_rxba_data[i]; + iwx_clear_reorder_buffer(sc, rxba); + } ifp->if_timer = sc->sc_tx_timer = 0; splx(s); @@ -7856,7 +8541,7 @@ iwx_attach(struct device *parent, struct device *self, struct ifnet *ifp = &ic->ic_if; const char *intrstr; int err; - int txq_i, i; + int txq_i, i, j; sc->sc_pct = pa->pa_pc; sc->sc_pcitag = pa->pa_tag; @@ -8124,6 +8809,17 @@ iwx_attach(struct device *parent, struct device *self, #if NBPFILTER > 0 iwx_radiotap_attach(sc); #endif + for (i = 0; i < nitems(sc->sc_rxba_data); i++) { + struct iwx_rxba_data *rxba = &sc->sc_rxba_data[i]; + rxba->baid = IWX_RX_REORDER_DATA_INVALID_BAID; + rxba->sc = sc; + timeout_set(&rxba->session_timer, iwx_rx_ba_session_expired, + rxba); + timeout_set(&rxba->reorder_buf.reorder_timer, + iwx_reorder_timer_expired, &rxba->reorder_buf); + for (j = 0; j < nitems(rxba->entries); j++) + ml_init(&rxba->entries[j].frames); + } task_set(&sc->init_task, iwx_init_task, sc); task_set(&sc->newstate_task, iwx_newstate_task, sc); task_set(&sc->ba_task, iwx_ba_task, sc); blob - ddc43a933a04178985bf83c06d656f0a3638f0dd blob + 90e72a64c596b261b91a8832220bf27c0ae11f54 --- sys/dev/pci/if_iwxreg.h +++ sys/dev/pci/if_iwxreg.h @@ -3075,6 +3075,9 @@ struct iwx_rx_mpdu_res_start { #define IWX_RX_MPDU_MFLG2_PAD 0x20 #define IWX_RX_MPDU_MFLG2_AMSDU 0x40 +#define IWX_RX_MPDU_AMSDU_SUBFRAME_IDX_MASK 0x7f +#define IWX_RX_MPDU_AMSDU_LAST_SUBFRAME 0x80 + #define IWX_RX_MPDU_PHY_AMPDU (1 << 5) #define IWX_RX_MPDU_PHY_AMPDU_TOGGLE (1 << 6) #define IWX_RX_MPDU_PHY_SHORT_PREAMBLE (1 << 7) @@ -3105,6 +3108,15 @@ struct iwx_rx_mpdu_desc_v1 { }; } __packed; +#define IWX_RX_REORDER_DATA_INVALID_BAID 0x7f + +#define IWX_RX_MPDU_REORDER_NSSN_MASK 0x00000fff +#define IWX_RX_MPDU_REORDER_SN_MASK 0x00fff000 +#define IWX_RX_MPDU_REORDER_SN_SHIFT 12 +#define IWX_RX_MPDU_REORDER_BAID_MASK 0x7f000000 +#define IWX_RX_MPDU_REORDER_BAID_SHIFT 24 +#define IWX_RX_MPDU_REORDER_BA_OLD_SN 0x80000000 + struct iwx_rx_mpdu_desc { uint16_t mpdu_len; uint8_t mac_flags1; @@ -4720,6 +4732,7 @@ struct iwx_tlc_update_notif { /* * TID for non QoS frames - to be written in tid_tspec */ +#define IWX_MAX_TID_COUNT 8 #define IWX_TID_NON_QOS 0 /* blob - d17b6abbe17bdc2710711d8b3850f67d716f9146 blob + a1ac3f9a204cca37a32003d7a0db03f2dccb8958 --- sys/dev/pci/if_iwxvar.h +++ sys/dev/pci/if_iwxvar.h @@ -345,6 +345,99 @@ struct iwx_self_init_dram { int paging_cnt; }; +/** + * struct iwx_reorder_buffer - per ra/tid/queue reorder buffer + * @head_sn: reorder window head sn + * @num_stored: number of mpdus stored in the buffer + * @buf_size: the reorder buffer size as set by the last addba request + * @queue: queue of this reorder buffer + * @last_amsdu: track last ASMDU SN for duplication detection + * @last_sub_index: track ASMDU sub frame index for duplication detection + * @reorder_timer: timer for frames are in the reorder buffer. For AMSDU + * it is the time of last received sub-frame + * @removed: prevent timer re-arming + * @valid: reordering is valid for this queue + * @consec_oldsn_drops: consecutive drops due to old SN + * @consec_oldsn_ampdu_gp2: A-MPDU GP2 timestamp to track + * when to apply old SN consecutive drop workaround + * @consec_oldsn_prev_drop: track whether or not an MPDU + * that was single/part of the previous A-MPDU was + * dropped due to old SN + */ +struct iwx_reorder_buffer { + uint16_t head_sn; + uint16_t num_stored; + uint16_t buf_size; + uint16_t last_amsdu; + uint8_t last_sub_index; + struct timeout reorder_timer; + int removed; + int valid; + unsigned int consec_oldsn_drops; + uint32_t consec_oldsn_ampdu_gp2; + unsigned int consec_oldsn_prev_drop; +#define IWX_AMPDU_CONSEC_DROPS_DELBA 10 +}; + +/** + * struct iwx_reorder_buf_entry - reorder buffer entry per frame sequence number + * @frames: list of mbufs stored (A-MSDU subframes share a sequence number) + * @reorder_time: time the packet was stored in the reorder buffer + */ +struct iwx_reorder_buf_entry { + struct mbuf_list frames; + struct timeval reorder_time; + uint32_t rx_pkt_status; + int chanidx; + int is_shortpre; + uint32_t rate_n_flags; + uint32_t device_timestamp; + struct ieee80211_rxinfo rxi; +}; + +/** + * struct iwx_rxba_data - BA session data + * @sta_id: station id + * @tid: tid of the session + * @baid: baid of the session + * @timeout: the timeout set in the addba request + * @entries_per_queue: # of buffers per queue + * @last_rx: last rx timestamp, updated only if timeout passed from last update + * @session_timer: timer to check if BA session expired, runs at 2 * timeout + * @sc: softc pointer, needed for timer context + * @reorder_buf: reorder buffer + * @reorder_buf_data: buffered frames, one entry per sequence number + */ +struct iwx_rxba_data { + uint8_t sta_id; + uint8_t tid; + uint8_t baid; + uint16_t timeout; + uint16_t entries_per_queue; + struct timeval last_rx; + struct timeout session_timer; + struct iwx_softc *sc; + struct iwx_reorder_buffer reorder_buf; + struct iwx_reorder_buf_entry entries[IEEE80211_BA_MAX_WINSZ]; +}; + +static inline struct iwx_rxba_data * +iwx_rxba_data_from_reorder_buf(struct iwx_reorder_buffer *buf) +{ + return (void *)((uint8_t *)buf - + offsetof(struct iwx_rxba_data, reorder_buf)); +} + +/** + * struct iwx_rxq_dup_data - per station per rx queue data + * @last_seq: last sequence per tid for duplicate packet detection + * @last_sub_frame: last subframe packet + */ +struct iwx_rxq_dup_data { + uint16_t last_seq[IWX_MAX_TID_COUNT + 1]; + uint8_t last_sub_frame[IWX_MAX_TID_COUNT + 1]; +}; + struct iwx_softc { struct device sc_dev; struct ieee80211com sc_ic; @@ -359,10 +452,11 @@ struct iwx_softc { /* Task for firmware BlockAck setup/teardown and its arguments. */ struct task ba_task; - int ba_start; - int ba_tid; - uint16_t ba_ssn; - uint16_t ba_winsize; + uint32_t ba_start_tidmask; + uint32_t ba_stop_tidmask; + uint16_t ba_ssn[IWX_MAX_TID_COUNT]; + uint16_t ba_winsize[IWX_MAX_TID_COUNT]; + int ba_timeout_val[IWX_MAX_TID_COUNT]; /* Task for HT protection updates. */ struct task htprot_task; @@ -465,6 +559,8 @@ struct iwx_softc { struct iwx_rx_phy_info sc_last_phy_info; int sc_ampdu_ref; +#define IWX_MAX_BAID 32 + struct iwx_rxba_data sc_rxba_data[IWX_MAX_BAID]; uint32_t sc_time_event_uid; @@ -510,6 +606,8 @@ struct iwx_node { uint16_t in_id; uint16_t in_color; + + struct iwx_rxq_dup_data dup_data; }; #define IWX_STATION_ID 0 #define IWX_AUX_STA_ID 1 blob - a2de00f7bcdef99ced5d09da5e9b4bc8615156bd blob + 8532becbec317b00ee9ace0588e8bb4b9216180e --- sys/net80211/ieee80211_input.c +++ sys/net80211/ieee80211_input.c @@ -59,7 +59,8 @@ #include <net80211/ieee80211_priv.h> struct mbuf *ieee80211_input_hwdecrypt(struct ieee80211com *, - struct ieee80211_node *, struct mbuf *); + struct ieee80211_node *, struct mbuf *, + struct ieee80211_rxinfo *rxi); struct mbuf *ieee80211_defrag(struct ieee80211com *, struct mbuf *, int); void ieee80211_defrag_timeout(void *); void ieee80211_input_ba(struct ieee80211com *, struct mbuf *, @@ -153,7 +154,7 @@ ieee80211_get_hdrlen(const struct ieee80211_frame *wh) /* Post-processing for drivers which perform decryption in hardware. */ struct mbuf * ieee80211_input_hwdecrypt(struct ieee80211com *ic, struct ieee80211_node *ni, - struct mbuf *m) + struct mbuf *m, struct ieee80211_rxinfo *rxi) { struct ieee80211_key *k; struct ieee80211_frame *wh; @@ -188,7 +189,12 @@ ieee80211_input_hwdecrypt(struct ieee80211com *ic, str } if (ieee80211_ccmp_get_pn(&pn, &prsc, m, k) != 0) return NULL; - if (pn <= *prsc) { + if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) { + if (pn < *prsc) { + ic->ic_stats.is_ccmp_replays++; + return NULL; + } + } else if (pn <= *prsc) { ic->ic_stats.is_ccmp_replays++; return NULL; } @@ -213,8 +219,12 @@ ieee80211_input_hwdecrypt(struct ieee80211com *ic, str } if (ieee80211_tkip_get_tsc(&pn, &prsc, m, k) != 0) return NULL; - - if (pn <= *prsc) { + if (rxi->rxi_flags & IEEE80211_RXI_HWDEC_SAME_PN) { + if (pn < *prsc) { + ic->ic_stats.is_tkip_replays++; + return NULL; + } + } else if (pn <= *prsc) { ic->ic_stats.is_tkip_replays++; return NULL; } @@ -381,7 +391,13 @@ ieee80211_inputm(struct ifnet *ifp, struct mbuf *m, st orxseq = &ni->ni_qos_rxseqs[tid]; else orxseq = &ni->ni_rxseq; - if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && + if (rxi->rxi_flags & IEEE80211_RXI_SAME_SEQ) { + if (nrxseq != *orxseq) { + /* duplicate, silently discarded */ + ic->ic_stats.is_rx_dup++; + goto out; + } + } else if ((wh->i_fc[1] & IEEE80211_FC1_RETRY) && nrxseq == *orxseq) { /* duplicate, silently discarded */ ic->ic_stats.is_rx_dup++; @@ -557,7 +573,7 @@ ieee80211_inputm(struct ifnet *ifp, struct mbuf *m, st goto err; } } else { - m = ieee80211_input_hwdecrypt(ic, ni, m); + m = ieee80211_input_hwdecrypt(ic, ni, m, rxi); if (m == NULL) goto err; } @@ -2758,10 +2774,7 @@ ieee80211_recv_addba_req(struct ieee80211com *ic, stru ba->ba_params = (params & IEEE80211_ADDBA_BA_POLICY); ba->ba_params |= ((ba->ba_winsize << IEEE80211_ADDBA_BUFSZ_SHIFT) | (tid << IEEE80211_ADDBA_TID_SHIFT)); -#if 0 - /* iwm(4) 9k and iwx(4) need more work before AMSDU can be enabled. */ ba->ba_params |= IEEE80211_ADDBA_AMSDU; -#endif ba->ba_winstart = ssn; ba->ba_winend = (ba->ba_winstart + ba->ba_winsize - 1) & 0xfff; /* allocate and setup our reordering buffer */ blob - 510eb534ae7ab07e69845b57367b9b79d523916e blob + 86fe60d986e5256ea052b7e0cf8c256aa8a9df8c --- sys/net80211/ieee80211_node.h +++ sys/net80211/ieee80211_node.h @@ -182,6 +182,8 @@ struct ieee80211_rxinfo { }; #define IEEE80211_RXI_HWDEC 0x00000001 #define IEEE80211_RXI_AMPDU_DONE 0x00000002 +#define IEEE80211_RXI_HWDEC_SAME_PN 0x00000004 +#define IEEE80211_RXI_SAME_SEQ 0x00000008 /* Block Acknowledgement Record */ struct ieee80211_tx_ba { blob - 5bd45d993b558bac50a513c1c4422508d96f44ba blob + b5c40528766d7aff8f21faf2975fd9c02257cf6c --- sys/net80211/ieee80211_proto.c +++ sys/net80211/ieee80211_proto.c @@ -695,10 +695,7 @@ ieee80211_addba_request(struct ieee80211com *ic, struc ba->ba_params = (ba->ba_winsize << IEEE80211_ADDBA_BUFSZ_SHIFT) | (tid << IEEE80211_ADDBA_TID_SHIFT); -#if 0 - /* iwm(4) 9k and iwx(4) need more work before AMSDU can be enabled. */ ba->ba_params |= IEEE80211_ADDBA_AMSDU; -#endif if ((ic->ic_htcaps & IEEE80211_HTCAP_DELAYEDBA) == 0) /* immediate BA */ ba->ba_params |= IEEE80211_ADDBA_BA_POLICY;
