iwm(4) A-MSDU support

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

iwm(4) A-MSDU support

Stefan Sperling-5
This patch attempts to add support for receiving A-MSDUs to iwm(4).
If you are using iwm(4) then please run with this patch and let me
know if it causes regressions. Thanks!

ACHTUNG: This patch breaks iwx(4)! Don't use it there! For this reason,
the patch can neither be committed nor be put into snapshots!!!

Our net80211 stack de-aggregates A-MSDUs in software. This works fine with
iwm 7k and 8k devices. However, iwm 9k devices de-aggregate A-MSDUs in
hardware and net80211 is currently unable to handle this.

Our current iwm 9k code only works because long ago I disabled reception
of A-MSDUs for all devices. Unfortunately, the only way to get A-MSDUs
working on 9k hardware is to add a bunch of driver-specific code which
handles de-aggregation. Otherwise, net80211 will drop frames which appear
to arrive out of order, or appear as duplicates even though they were
simply part of the same A-MSDU and thus share a sequence number.
The driver can re-order frames correctly based on information provided
by firmware. I'd rather implement this than letting these devices miss
out on A-MSDUs because the Rx speed advantage can be significant, around
80/90 Mbps (but this will very much depend on the AP).

If these A-* acronyms don't make sense and you'd like to know more, here
is a fairly good explanation: https://mrncciew.com/2013/04/11/a-mpdu-a-msdu/
Note that we care about the nested case, which is also mentioned in this
article but not explained in detail. It's simply an A-MSDU stashed inside
an A-MPDU. If an AP can do 11AC, then it should support this.

iwx(4) hardware has the same problem.
If this patch works fine on iwm(4) then I can port the changes to iwx(4),
do another round of testing, and eventually commit to both drivers at
the same time.

Some of the changes below are based on code from the Linux iwlwifi driver.
I am not entirely happy with some of its style. But the code should be in
good enough shape to be tested.

diff refs/heads/master refs/heads/amsdu
blob - 00bf20b37ed33a652232885349c2f3dfa0d666d3
blob + c353ee60473b7cfd237e1889e4a4b9235cb8bdc7
--- 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,385 @@ 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)
+ 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 +4791,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 +4855,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 +4913,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 +7380,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 +7460,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 +8020,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 +8835,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 +9924,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 +10235,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 - 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;

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Marcus MERIGHI
Hello!

[hidden email] (Stefan Sperling), 2021.03.29 (Mon) 19:27 (CEST):
> This patch attempts to add support for receiving A-MSDUs to iwm(4).
> If you are using iwm(4) then please run with this patch and let me
> know if it causes regressions. Thanks!

no regressions with:

iwm0 at pci2 dev 0 function 0 "Intel Dual Band Wireless AC 8260" rev
        0x3a, msi
iwm0: hw rev 0x200, fw ver 34.0.1, address 34:f3:9a:xx:yy:zz

thanks for all your work on wifi!

Marcus

OpenBSD 6.9-beta (GENERIC.MP) #1: Mon Mar 29 20:50:53 CEST 2021
    me@myself:/usr/src/sys/arch/amd64/compile/GENERIC.MP
real mem = 7944069120 (7576MB)
avail mem = 7687913472 (7331MB)
random: good seed from bootblocks
mpath0 at root
scsibus0 at mpath0: 256 targets
mainbus0 at root
bios0 at mainbus0: SMBIOS rev. 2.8 @ 0xb7058000 (65 entries)
bios0: vendor LENOVO version "N1CET81W (1.49 )" date 06/04/2020
bios0: LENOVO 20FAS06G00
acpi0 at bios0: ACPI 5.0
acpi0: sleep states S0 S3 S4 S5
acpi0: tables DSDT FACP UEFI SSDT SSDT ECDT HPET APIC MCFG SSDT DBGP DBG2 BOOT BATB SLIC SSDT SSDT MSDM DMAR ASF! FPDT UEFI
acpi0: wakeup devices LID_(S4) SLPB(S3) IGBE(S4) EXP2(S4) XHCI(S3)
acpitimer0 at acpi0: 3579545 Hz, 24 bits
acpiec0 at acpi0
acpihpet0 at acpi0: 23999999 Hz
acpimadt0 at acpi0 addr 0xfee00000: PC-AT compat
cpu0 at mainbus0: apid 0 (boot processor)
cpu0: Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz, 1596.94 MHz, 06-4e-03
cpu0: FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,CFLUSH,DS,ACPI,MMX,FXSR,SSE,SSE2,SS,HTT,TM,PBE,SSE3,PCLMUL,DTES64,MWAIT,DS-CPL,VMX,SMX,EST,TM2,SSSE3,SDBG,FMA3,CX16,xTPR,PDCM,PCID,SSE4.1,SSE4.2,x2APIC,MOVBE,POPCNT,DEADLINE,AES,XSAVE,AVX,F16C,RDRAND,NXE,PAGE1GB,RDTSCP,LONG,LAHF,ABM,3DNOWP,PERF,ITSC,FSGSBASE,TSC_ADJUST,SGX,BMI1,HLE,AVX2,SMEP,BMI2,ERMS,INVPCID,RTM,MPX,RDSEED,ADX,SMAP,CLFLUSHOPT,PT,SRBDS_CTRL,MD_CLEAR,TSXFA,IBRS,IBPB,STIBP,L1DF,SSBD,SENSOR,ARAT,XSAVEOPT,XSAVEC,XGETBV1,XSAVES,MELTDOWN
cpu0: 256KB 64b/line 8-way L2 cache
cpu0: smt 0, core 0, package 0
mtrr: Pentium Pro MTRR support, 10 var ranges, 88 fixed ranges
cpu0: apic clock running at 24MHz
cpu0: mwait min=64, max=64, C-substates=0.2.1.2.4.1.1.1, IBE
cpu1 at mainbus0: apid 2 (application processor)
cpu1: Intel(R) Core(TM) i7-6600U CPU @ 2.60GHz, 1435.78 MHz, 06-4e-03
cpu1: FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36,CFLUSH,DS,ACPI,MMX,FXSR,SSE,SSE2,SS,HTT,TM,PBE,SSE3,PCLMUL,DTES64,MWAIT,DS-CPL,VMX,SMX,EST,TM2,SSSE3,SDBG,FMA3,CX16,xTPR,PDCM,PCID,SSE4.1,SSE4.2,x2APIC,MOVBE,POPCNT,DEADLINE,AES,XSAVE,AVX,F16C,RDRAND,NXE,PAGE1GB,RDTSCP,LONG,LAHF,ABM,3DNOWP,PERF,ITSC,FSGSBASE,TSC_ADJUST,SGX,BMI1,HLE,AVX2,SMEP,BMI2,ERMS,INVPCID,RTM,MPX,RDSEED,ADX,SMAP,CLFLUSHOPT,PT,SRBDS_CTRL,MD_CLEAR,TSXFA,IBRS,IBPB,STIBP,L1DF,SSBD,SENSOR,ARAT,XSAVEOPT,XSAVEC,XGETBV1,XSAVES,MELTDOWN
cpu1: 256KB 64b/line 8-way L2 cache
cpu1: smt 0, core 1, package 0
ioapic0 at mainbus0: apid 2 pa 0xfec00000, version 20, 120 pins
acpimcfg0 at acpi0
acpimcfg0: addr 0xf8000000, bus 0-63
acpiprt0 at acpi0: bus 0 (PCI0)
acpiprt1 at acpi0: bus -1 (PEG0)
acpiprt2 at acpi0: bus -1 (PEG1)
acpiprt3 at acpi0: bus -1 (PEG2)
acpiprt4 at acpi0: bus 2 (EXP1)
acpiprt5 at acpi0: bus -1 (EXP2)
acpiprt6 at acpi0: bus 4 (EXP3)
acpiprt7 at acpi0: bus -1 (EXP5)
acpiprt8 at acpi0: bus -1 (RP09)
acpibtn0 at acpi0: LID_
acpibtn1 at acpi0: SLPB
acpipci0 at acpi0 PCI0: 0x00000000 0x00000011 0x00000001
acpicmos0 at acpi0
acpibat0 at acpi0: BAT0 model "00HW023" serial   605 type LiP oem "SMP"
acpibat1 at acpi0: BAT1 model "01AV462" serial   125 type LiP oem "SMP"
acpiac0 at acpi0: AC unit online
acpithinkpad0 at acpi0: version 2.0
"PNP0C14" at acpi0 not configured
"PNP0C14" at acpi0 not configured
"PNP0C14" at acpi0 not configured
acpicpu0 at acpi0: C3(200@1034 mwait.1@0x60), C2(200@151 mwait.1@0x33), C1(1000@1 mwait.1), PSS
acpicpu1 at acpi0: C3(200@1034 mwait.1@0x60), C2(200@151 mwait.1@0x33), C1(1000@1 mwait.1), PSS
acpipwrres0 at acpi0: PUBS, resource for XHCI
acpipwrres1 at acpi0: PG00, resource for PEG0
acpipwrres2 at acpi0: PG01, resource for PEG1
acpipwrres3 at acpi0: PG02, resource for PEG2
acpipwrres4 at acpi0: WRST
acpipwrres5 at acpi0: WRST
acpitz0 at acpi0: critical temperature is 128 degC
acpivideo0 at acpi0: GFX0
acpivout0 at acpivideo0: DD1F
cpu0: using VERW MDS workaround (except on vmm entry)
cpu0: Enhanced SpeedStep 1596 MHz: speeds: 2701, 2700, 2600, 2500, 2300, 2100, 1900, 1800, 1600, 1400, 1300, 1100, 800, 700, 600, 400 MHz
pci0 at mainbus0 bus 0
pchb0 at pci0 dev 0 function 0 "Intel Core 6G Host" rev 0x08
inteldrm0 at pci0 dev 2 function 0 "Intel HD Graphics 520" rev 0x07
drm0 at inteldrm0
inteldrm0: msi, SKYLAKE, gen 9
"Intel Core GMM" rev 0x00 at pci0 dev 8 function 0 not configured
xhci0 at pci0 dev 20 function 0 "Intel 100 Series xHCI" rev 0x21: msi, xHCI 1.0
usb0 at xhci0: USB revision 3.0
uhub0 at usb0 configuration 1 interface 0 "Intel xHCI root hub" rev 3.00/1.00 addr 1
pchtemp0 at pci0 dev 20 function 2 "Intel 100 Series Thermal" rev 0x21
"Intel 100 Series MEI" rev 0x21 at pci0 dev 22 function 0 not configured
ahci0 at pci0 dev 23 function 0 "Intel 100 Series AHCI" rev 0x21: msi, AHCI 1.3.1
ahci0: port 1: 6.0Gb/s
scsibus1 at ahci0: 32 targets
sd0 at scsibus1 targ 1 lun 0: <ATA, SAMSUNG MZNLN512, MAV2> naa.5002538d00000000
sd0: 488386MB, 512 bytes/sector, 1000215216 sectors, thin
ppb0 at pci0 dev 28 function 0 "Intel 100 Series PCIE" rev 0xf1: msi
pci1 at ppb0 bus 2
rtsx0 at pci1 dev 0 function 0 "Realtek RTS522A Card Reader" rev 0x01: msi
sdmmc0 at rtsx0: 4-bit, dma
ppb1 at pci0 dev 28 function 2 "Intel 100 Series PCIE" rev 0xf1: msi
pci2 at ppb1 bus 4
iwm0 at pci2 dev 0 function 0 "Intel Dual Band Wireless AC 8260" rev 0x3a, msi
pcib0 at pci0 dev 31 function 0 "Intel 100 Series LPC" rev 0x21
"Intel 100 Series PMC" rev 0x21 at pci0 dev 31 function 2 not configured
azalia0 at pci0 dev 31 function 3 "Intel 100 Series HD Audio" rev 0x21: msi
azalia0: codecs: Realtek ALC293, Intel/0x2809, using Realtek ALC293
audio0 at azalia0
ichiic0 at pci0 dev 31 function 4 "Intel 100 Series SMBus" rev 0x21: apic 2 int 16
iic0 at ichiic0
em0 at pci0 dev 31 function 6 "Intel I219-LM" rev 0x21: msi, address c8:5b:76:bf:0e:17
isa0 at pcib0
isadma0 at isa0
pckbc0 at isa0 port 0x60/5 irq 1 irq 12
pckbd0 at pckbc0 (kbd slot)
wskbd0 at pckbd0: console keyboard
pms0 at pckbc0 (aux slot)
wsmouse0 at pms0 mux 0
wsmouse1 at pms0 mux 0
pms0: Synaptics clickpad, firmware 8.2, 0x1e2b1 0x943300 0x334940 0xf003a3 0x12e800
pcppi0 at isa0 port 0x61
spkr0 at pcppi0
vmm0 at mainbus0: VMX/EPT
umb0 at uhub0 port 2 "Huawei Technologies Co., Ltd. HUAWEI Mobile" rev 2.00/1.02 addr 2
ugen0 at uhub0 port 7 "Intel Bluetooth" rev 2.00/0.01 addr 3
uvideo0 at uhub0 port 8 configuration 1 interface 0 "Chicony Electronics Co.,Ltd. Integrated Camera" rev 2.00/0.29 addr 4
video0 at uvideo0
ugen1 at uhub0 port 9 "Validity Sensors product 0x0090" rev 2.00/1.64 addr 5
vscsi0 at root
scsibus2 at vscsi0: 256 targets
softraid0 at root
scsibus3 at softraid0: 256 targets
root on sd0a (19edbd140bf83c26.a) swap on sd0b dump on sd0b
inteldrm0: 1920x1080, 32bpp
wsdisplay0 at inteldrm0 mux 1: console (std, vt100 emulation), using wskbd0
wsdisplay0: screen 1-5 added (std, vt100 emulation)
iwm0: hw rev 0x200, fw ver 34.0.1, address 34:f3:9a:54:26:08

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Dave Voutila-2
In reply to this post by Stefan Sperling-5

Stefan Sperling writes:

> This patch attempts to add support for receiving A-MSDUs to iwm(4).
> If you are using iwm(4) then please run with this patch and let me
> know if it causes regressions. Thanks!

Been running your diff on a workstation the past 2 hours with no
noticeable regressions.

iwm0 at pci13 dev 0 function 0 "Intel Dual Band Wireless AC 7260" rev 0x73, msi
iwm0: hw rev 0x140, fw ver 17.3216344376.0, address 7c:5c:f8:f0:40:2e

-dv

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Brian Callahan-6
Hi Stefan --

> Stefan Sperling writes:
>
> > This patch attempts to add support for receiving A-MSDUs to iwm(4).
> >
> > If you are using iwm(4) then please run with this patch and let me
> >
> > know if it causes regressions. Thanks!
>

All is good on my machine as well.

iwm0 at pci4 dev 0 function 0 "Intel Dual Band Wireless AC 7260" rev 0xbb, msi
iwm0: hw rev 0x140, fw ver 17.3216344376.0, address ac:fd:ce:09:87:97

Thanks!

~Brian

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Tracey Emery
On Mon, Mar 29, 2021 at 09:27:31PM +0000, Brian Callahan wrote:

> Hi Stefan --
>
> > Stefan Sperling writes:
> >
> > > This patch attempts to add support for receiving A-MSDUs to iwm(4).
> > >
> > > If you are using iwm(4) then please run with this patch and let me
> > >
> > > know if it causes regressions. Thanks!
> >
>
> All is good on my machine as well.
>
> iwm0 at pci4 dev 0 function 0 "Intel Dual Band Wireless AC 7260" rev 0xbb, msi
> iwm0: hw rev 0x140, fw ver 17.3216344376.0, address ac:fd:ce:09:87:97
>
> Thanks!
>
> ~Brian

Running all day without issue.

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 cc:2f:71:9f:86:7a

--

Tracey Emery

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Matthias Schmidt
In reply to this post by Stefan Sperling-5
Hi Stefan,

* Stefan Sperling wrote:
> This patch attempts to add support for receiving A-MSDUs to iwm(4).
> If you are using iwm(4) then please run with this patch and let me
> know if it causes regressions. Thanks!

Works so far fine with the following HW:

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:2a:31:4d:1c:b9

Cheers

        Matthias

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Peter Hessler
In reply to this post by Stefan Sperling-5
On 2021 Mar 29 (Mon) at 19:27:15 +0200 (+0200), Stefan Sperling wrote:
:This patch attempts to add support for receiving A-MSDUs to iwm(4).
:If you are using iwm(4) then please run with this patch and let me
:know if it causes regressions. Thanks!
:
:ACHTUNG: This patch breaks iwx(4)! Don't use it there! For this reason,
:the patch can neither be committed nor be put into snapshots!!!
:
:Our net80211 stack de-aggregates A-MSDUs in software. This works fine with
:iwm 7k and 8k devices. However, iwm 9k devices de-aggregate A-MSDUs in
:hardware and net80211 is currently unable to handle this.
:
:Our current iwm 9k code only works because long ago I disabled reception
:of A-MSDUs for all devices. Unfortunately, the only way to get A-MSDUs
:working on 9k hardware is to add a bunch of driver-specific code which
:handles de-aggregation. Otherwise, net80211 will drop frames which appear
:to arrive out of order, or appear as duplicates even though they were
:simply part of the same A-MSDU and thus share a sequence number.
:The driver can re-order frames correctly based on information provided
:by firmware. I'd rather implement this than letting these devices miss
:out on A-MSDUs because the Rx speed advantage can be significant, around
:80/90 Mbps (but this will very much depend on the AP).
:
:If these A-* acronyms don't make sense and you'd like to know more, here
:is a fairly good explanation: https://mrncciew.com/2013/04/11/a-mpdu-a-msdu/
:Note that we care about the nested case, which is also mentioned in this
:article but not explained in detail. It's simply an A-MSDU stashed inside
:an A-MPDU. If an AP can do 11AC, then it should support this.
:
:iwx(4) hardware has the same problem.
:If this patch works fine on iwm(4) then I can port the changes to iwx(4),
:do another round of testing, and eventually commit to both drivers at
:the same time.
:
:Some of the changes below are based on code from the Linux iwlwifi driver.
:I am not entirely happy with some of its style. But the code should be in
:good enough shape to be tested.
:

Been running this for about 24 hours on my x395, seems to be good.

Had only one stuck wifi when first trying it, but I was also stuck on a
2.4GHz channel and live in a dense apartment building.  Forcing it to
move to a 5GHz channel fixed it all up, and no problems since then.

  iwm0 at pci1 dev 0 function 0 "Intel Dual Band Wireless-AC 9260" rev 0x29, msix
  iwm0: hw rev 0x320, fw ver 34.3125811985.0, address 0c:dd:24:83:e1:40



--
Churchill's Commentary on Man:
        Man will occasionally stumble over the truth, but most of the
        time he will pick himself up and continue on.

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Stefan Sperling-5
On Tue, Mar 30, 2021 at 07:36:28PM +0200, Peter Hessler wrote:
> Been running this for about 24 hours on my x395, seems to be good.
>
> Had only one stuck wifi when first trying it, but I was also stuck on a
> 2.4GHz channel and live in a dense apartment building.  Forcing it to
> move to a 5GHz channel fixed it all up, and no problems since then.

Understanding situations where it doesn't work is actually quite important.
Is it repeatable? And how big is the impact?
If you can fly somewhat in Minecraft on 2 GHz, and if it consistently
recovers after stuttering, I'd consider that success.

This is a huge change for the device you are using; all the Rx BA logic
is now handled by completely new code in the driver, with a bypass of the
corresponding logic in net80211. We now let the firmware move the BA
window forward and try to follow along, instead of maintaining our own
(duplicated) state of the Rx BA session. net80211 is only involved in
BA setup/teardown handshakes with the AP.

In good conditions, I see virtually no packet loss.
I've tried testing error recovery by moving far out and back to the AP.
This seems to be OK. However, as with our net80211 Rx BA code we risk stuck
connections if the Rx BA window doesn't resync properly after packet loss.

The logic implemented here is from Intel, with very few changes (such as
fixed timeout periods), so if the firmware and this new driver code work
reliably on Linux, it should also work fine for us. But I cannot be sure
that this code is free of bugs causing stuck connections. Like our net80211
Rx BA code, this code will have to prove itself over time...

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Peter Hessler
On 2021 Mar 30 (Tue) at 20:22:09 +0200 (+0200), Stefan Sperling wrote:
:On Tue, Mar 30, 2021 at 07:36:28PM +0200, Peter Hessler wrote:
:> Been running this for about 24 hours on my x395, seems to be good.
:>
:> Had only one stuck wifi when first trying it, but I was also stuck on a
:> 2.4GHz channel and live in a dense apartment building.  Forcing it to
:> move to a 5GHz channel fixed it all up, and no problems since then.
:
:Understanding situations where it doesn't work is actually quite important.
:Is it repeatable? And how big is the impact?
:If you can fly somewhat in Minecraft on 2 GHz, and if it consistently
:recovers after stuttering, I'd consider that success.
:

I can fly around pretty well in Minecraft while in 2 GHz, taking off is
easy to do.

However, when I go to a part of my apartment with dodgy wifi
connectivity, I notice that my signal strength goes down to 23%, and I
can't connect any more.  I stay at HT-MCS15, even while it is flipping
around trying to connect.  if I try to ping the gateway I get the
dreaded "ping: sendmsg: No buffer space available" error, until I
down/up the interface.  That does occasionally happen without this diff,
so that is not a regression.

No noticable packet loss in my testing, even on 2GHz during the busiest
time of the day.


:This is a huge change for the device you are using; all the Rx BA logic
:is now handled by completely new code in the driver, with a bypass of the
:corresponding logic in net80211. We now let the firmware move the BA
:window forward and try to follow along, instead of maintaining our own
:(duplicated) state of the Rx BA session. net80211 is only involved in
:BA setup/teardown handshakes with the AP.
:
:In good conditions, I see virtually no packet loss.
:I've tried testing error recovery by moving far out and back to the AP.
:This seems to be OK. However, as with our net80211 Rx BA code we risk stuck
:connections if the Rx BA window doesn't resync properly after packet loss.
:
:The logic implemented here is from Intel, with very few changes (such as
:fixed timeout periods), so if the firmware and this new driver code work
:reliably on Linux, it should also work fine for us. But I cannot be sure
:that this code is free of bugs causing stuck connections. Like our net80211
:Rx BA code, this code will have to prove itself over time...
:

--
Misfortune, n.:
        The kind of fortune that never misses.
                -- Ambrose Bierce, "The Devil's Dictionary"

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Uwe Werler-4
In reply to this post by Stefan Sperling-5
On 29 Mar 19:27, Stefan Sperling wrote:

> This patch attempts to add support for receiving A-MSDUs to iwm(4).
> If you are using iwm(4) then please run with this patch and let me
> know if it causes regressions. Thanks!
>
> ACHTUNG: This patch breaks iwx(4)! Don't use it there! For this reason,
> the patch can neither be committed nor be put into snapshots!!!
>
> Our net80211 stack de-aggregates A-MSDUs in software. This works fine with
> iwm 7k and 8k devices. However, iwm 9k devices de-aggregate A-MSDUs in
> hardware and net80211 is currently unable to handle this.
>
> Our current iwm 9k code only works because long ago I disabled reception
> of A-MSDUs for all devices. Unfortunately, the only way to get A-MSDUs
> working on 9k hardware is to add a bunch of driver-specific code which
> handles de-aggregation. Otherwise, net80211 will drop frames which appear
> to arrive out of order, or appear as duplicates even though they were
> simply part of the same A-MSDU and thus share a sequence number.
> The driver can re-order frames correctly based on information provided
> by firmware. I'd rather implement this than letting these devices miss
> out on A-MSDUs because the Rx speed advantage can be significant, around
> 80/90 Mbps (but this will very much depend on the AP).
>
> If these A-* acronyms don't make sense and you'd like to know more, here
> is a fairly good explanation: https://mrncciew.com/2013/04/11/a-mpdu-a-msdu/
> Note that we care about the nested case, which is also mentioned in this
> article but not explained in detail. It's simply an A-MSDU stashed inside
> an A-MPDU. If an AP can do 11AC, then it should support this.
>
> iwx(4) hardware has the same problem.
> If this patch works fine on iwm(4) then I can port the changes to iwx(4),
> do another round of testing, and eventually commit to both drivers at
> the same time.
>
> Some of the changes below are based on code from the Linux iwlwifi driver.
> I am not entirely happy with some of its style. But the code should be in
> good enough shape to be tested.
>

Hi Stefan,

I tested the patch with my:

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 xx:xx:xx:xx:xx:xx

against my Technicolor MediaAccess TG789vac.

5GHz:

Down:
Conn:   1 Mbps:      105.479 Peak Mbps:      107.955 Avg Mbps: 105.479 25076
13282504      106.366  100.00%                          

Up:
Conn:   1 Mbps:       28.056 Peak Mbps:       28.096 Avg Mbps: 28.056 17004
3483888       27.899  100.00%                          

2GHz:

Down:
Conn:   1 Mbps:       87.640 Peak Mbps:       87.965 Avg Mbps: 87.640 14041
11096024       88.680  100.00%                          

UP:
Conn:   1 Mbps:       22.786 Peak Mbps:       22.844 Avg Mbps: 22.786 9003
3176912       25.441  100.00%

Thanks for your hard work!

--
wq: ~uw

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

trondd-2
In reply to this post by Stefan Sperling-5
Stefan Sperling <[hidden email]> wrote:

> This patch attempts to add support for receiving A-MSDUs to iwm(4).
> If you are using iwm(4) then please run with this patch and let me
> know if it causes regressions. Thanks!
>
> ACHTUNG: This patch breaks iwx(4)! Don't use it there! For this reason,
> the patch can neither be committed nor be put into snapshots!!!
>
> Our net80211 stack de-aggregates A-MSDUs in software. This works fine with
> iwm 7k and 8k devices. However, iwm 9k devices de-aggregate A-MSDUs in
> hardware and net80211 is currently unable to handle this.
>
> Our current iwm 9k code only works because long ago I disabled reception
> of A-MSDUs for all devices. Unfortunately, the only way to get A-MSDUs
> working on 9k hardware is to add a bunch of driver-specific code which
> handles de-aggregation. Otherwise, net80211 will drop frames which appear
> to arrive out of order, or appear as duplicates even though they were
> simply part of the same A-MSDU and thus share a sequence number.
> The driver can re-order frames correctly based on information provided
> by firmware. I'd rather implement this than letting these devices miss
> out on A-MSDUs because the Rx speed advantage can be significant, around
> 80/90 Mbps (but this will very much depend on the AP).
>
> If these A-* acronyms don't make sense and you'd like to know more, here
> is a fairly good explanation: https://mrncciew.com/2013/04/11/a-mpdu-a-msdu/
> Note that we care about the nested case, which is also mentioned in this
> article but not explained in detail. It's simply an A-MSDU stashed inside
> an A-MPDU. If an AP can do 11AC, then it should support this.
>
> iwx(4) hardware has the same problem.
> If this patch works fine on iwm(4) then I can port the changes to iwx(4),
> do another round of testing, and eventually commit to both drivers at
> the same time.
>
> Some of the changes below are based on code from the Linux iwlwifi driver.
> I am not entirely happy with some of its style. But the code should be in
> good enough shape to be tested.

No regression noticed here for several days under my normal workload.

iwm0 at pci2 dev 0 function 0 "Intel Dual Band Wireless AC 7265" rev 0x59, msi
iwm0: hw rev 0x210, fw ver 17.3216344376.0, address dc:53:60:4a:b1:ea

Tim.

Reply | Threaded
Open this post in threaded view
|

Re: iwm(4) A-MSDU support

Uwe Werler-4
In reply to this post by Stefan Sperling-5

Hi Stefan,

just tested today with my other laptop (Dell Latitude 7400):
iwm0 at pci0 dev 20 function 3 "Intel Dual Band Wireless AC 9560" rev 0x30,
msix iwm0: hw rev 0x310, fw ver 34.3125811985.0, address 60:f2:62:06:61:f2

...against an Ubiquity UniFi AC AP Pro.


Before patch:

Up:

Conn:   1 Mbps:       24.859 Peak Mbps:       25.577 Avg Mbps: 24.859 8017
2932200       23.271  100.00%

Down:

Conn:   1 Mbps:       81.806 Peak Mbps:       87.146 Avg Mbps:       81.806
12016        8855968       70.848  100.00%

After patch:

Up:

Conn:   1 Mbps:       33.617 Peak Mbps:       33.617 Avg Mbps:       33.617
13076        3963176       31.674  100.00%

Down:

Conn:   1 Mbps:       87.707 Peak Mbps:       87.707 Avg Mbps:       87.707
8018       10584880       84.594  100.00%

Thanks for your work!

Uwe

> diff refs/heads/master refs/heads/amsdu
> blob - 00bf20b37ed33a652232885349c2f3dfa0d666d3
> blob + c353ee60473b7cfd237e1889e4a4b9235cb8bdc7
> --- 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,385 @@ 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)
> + 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 +4791,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 +4855,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 +4913,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 +7380,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 +7460,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 +8020,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 +8835,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 +9924,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 +10235,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 - 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;
>



--
wq: ~uw