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

Reply via email to