On Fri, Apr 17, 2020 at 06:30:22PM +0200, Stefan Sperling wrote:
> On Fri, Apr 17, 2020 at 04:03:30PM +0200, Stefan Sperling wrote:
> > In particular, this fixes wrong assumptions about what the data in the
> > firmware's "compressed block ack" notifications is supposed to represent,
> > and actually pieces information about individual subframes of aggregated
> > frames (A-MPDUs) back together when reporting to MiRA, rather than reporting
> > unrelated subframes to MiRA individually. (Recall that MiRA was primarily
> > designed to operate on units of entire A-MDPUs.)
> 
> Unfortunately, this diff can trigger a page fault. I am looking for a fix.

This new version has been tested by cwen and Josh Grosse and seems to
fix the problems they found with the previous diff. Thanks!

diff 24c5886569df7f9428f24e6e74c71e02cba8c34c 
719fff35cc167c0814926c547e92b317aad32553
blob - 8476916e4337cb6e681c11f26fc1fca5f4a48334
blob + b8beb56aa1112e65c2907b2955694b6a8907d9ae
--- sys/dev/pci/if_iwn.c
+++ sys/dev/pci/if_iwn.c
@@ -2282,7 +2282,11 @@ iwn_mira_choose(struct iwn_softc *sc, struct ieee80211
                iwn_set_link_quality(sc, ni);
 }
 
-/* Process an incoming Compressed BlockAck. */
+/*
+ * Process an incoming Compressed BlockAck.
+ * Note that these block ack notifications are generated by firmware and do
+ * not necessarily correspond to contents of block ack frames seen on the air.
+ */
 void
 iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_rx_desc *desc,
     struct iwn_rx_data *data)
@@ -2293,7 +2297,8 @@ iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_
        struct ieee80211_tx_ba *ba;
        struct iwn_node *wn;
        struct iwn_tx_ring *txq;
-       uint16_t ssn, idx;
+       uint16_t seq, ssn, idx, end_idx;
+       int min_ampdu_id, max_ampdu_id, id;
        int qid;
 
        if (ic->ic_state != IEEE80211_S_RUN)
@@ -2322,66 +2327,103 @@ iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_
        if (ba->ba_state != IEEE80211_BA_AGREED)
                return;
 
-       ssn = le16toh(cba->ssn); /* BA window starting sequence number */
-       if (!SEQ_LT(ssn, ba->ba_winstart)) {
-               ieee80211_output_ba_move_window(ic, ni, cba->tid, ssn);
+       /*
+        * The first bit in cba->bitmap corresponds to the sequence number
+        * stored in the sequence control field cba->seq.
+        * Any frames older than this can now be discarded; they should
+        * already have been reported as failures or been acknowledged.
+        *
+        * Multiple BA notifications in a row may be using this number, with
+        * additional bits being set in cba->bitmap. It is unclear how the
+        * firmware decides to shift this window forward.
+        */
+       seq = le16toh(cba->seq) >> IEEE80211_SEQ_SEQ_SHIFT;
+       if (!SEQ_LT(seq, ba->ba_winstart)) {
+               ieee80211_output_ba_move_window(ic, ni, cba->tid, seq);
                iwn_ampdu_txq_advance(sc, txq, qid,
-                   IWN_AGG_SSN_TO_TXQ_IDX(ssn));
+                   IWN_AGG_SSN_TO_TXQ_IDX(seq));
                iwn_clear_oactive(sc, txq);
        }
+       /* Our BA window should now correspond to the bitmap. */
+       if (ba->ba_winstart != seq)
+               return;
 
-       /* ba->ba_winstart should now correspond to cba->ssn */
-       if (ba->ba_winstart != cba->ssn)
+       /* Skip rate control if our Tx rate is fixed. */
+       if (ic->ic_fixed_mcs != -1)
                return;
 
        /*
-        * Update Tx rate statistics.
-        * Skip rate control if our Tx rate is fixed.
+        * The firmware's new BA window starting sequence number
+        * corresponds to the first hole in cba->bitmap, implying
+        * that all frames between 'seq' and 'ssn' have been acked.
         */
-       if (ic->ic_fixed_mcs == -1 && cba->nframes_sent > 0) {
-               int end_idx = IWN_AGG_SSN_TO_TXQ_IDX(ba->ba_winend);
-               int bit = 0, nsent = cba->nframes_sent;
+       ssn = le16toh(cba->ssn);
 
+       /* Determine the min/max IDs we assigned to AMPDUs in this range. */
+       idx = IWN_AGG_SSN_TO_TXQ_IDX(seq);
+       end_idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
+       min_ampdu_id = txq->data[idx].ampdu_id;
+       max_ampdu_id = min_ampdu_id;
+       while (idx != end_idx) {
+               struct iwn_tx_data *txdata = &txq->data[idx];
+
+               if (txdata->m != NULL) {
+                       if (min_ampdu_id > txdata->ampdu_id)
+                               min_ampdu_id = txdata->ampdu_id;
+                       if (max_ampdu_id < txdata->ampdu_id)
+                               max_ampdu_id = txdata->ampdu_id;
+               }
+
+               idx = (idx + 1) % IWN_TX_RING_COUNT;
+       }
+
+       /*
+        * Update Tx rate statistics for A-MPDUs before firmware's BA window.
+        */
+       for (id = min_ampdu_id; id <= max_ampdu_id; id++) {
+               int have_ack = 0, bit = 0;
+               idx = IWN_AGG_SSN_TO_TXQ_IDX(seq);
+               end_idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
                wn->mn.agglen = 0;
                wn->mn.ampdu_size = 0;
-
-               ssn = le16toh(ba->ba_winstart);
-               idx = IWN_AGG_SSN_TO_TXQ_IDX(ssn);
-               while (nsent && idx != end_idx) {
+               while (idx != end_idx) {
                        struct iwn_tx_data *txdata = &txq->data[idx];
-                       int have_ack = (le64toh(cba->bitmap) & (1 << bit));
-
-                       if ((ba->ba_bitmap & (1 << bit)) == 0) {
-                               /*
-                                * Don't report frames to MiRA which were sent
-                                * at a different Tx rate than ni->ni_txmcs.
-                                */
-                               if (txdata->actual_txmcs == ni->ni_txmcs) {
-                                       wn->mn.frames++;
-                                       wn->mn.agglen++;
-                                       wn->mn.ampdu_size += txdata->totlen +
-                                           IEEE80211_CRC_LEN;
-                                       if (txdata->retries > 0)
-                                               wn->mn.retries++;
-                                       if (!have_ack || txdata->txfail > 0)
-                                               wn->mn.txfail++;
-                               }
-                               if (have_ack)
-                                       ieee80211_output_ba_record_ack(ic,
-                                           ni, cba->tid, ssn);
+                       uint16_t s = (seq + bit) & 0xfff;
+                       /*
+                        * We can assume that this subframe has been ACKed
+                        * because ACK failures come as single frames and
+                        * before failing an A-MPDU subframe the firmware
+                        * sends it as a single frame at least once.
+                        *
+                        * However, when this A-MPDU was transmitted we
+                        * learned how many subframes it contained.
+                        * So if firmware isn't reporting all subframes now
+                        * we can deduce an ACK failure for missing frames.
+                        */
+                       if (txdata->m != NULL && txdata->ampdu_id == id &&
+                           txdata->ampdu_txmcs == ni->ni_txmcs &&
+                           (SEQ_LT(ba->ba_winend, s) ||
+                           (ba->ba_bitmap & (1 << bit)) == 0)) {
+                               have_ack++;
+                               wn->mn.frames = txdata->ampdu_nframes;
+                               wn->mn.agglen = txdata->ampdu_nframes;
+                               wn->mn.ampdu_size = txdata->ampdu_size;
+                               if (txdata->retries > 1)
+                                       wn->mn.retries++;
+                               if (!SEQ_LT(ba->ba_winend, s))
+                                       ieee80211_output_ba_record_ack(ic, ni,
+                                           cba->tid, s);
                        }
 
                        idx = (idx + 1) % IWN_TX_RING_COUNT;
-                       ssn = (ssn + 1) % 0xfff;
-                       nsent--;
                        bit++;
                }
 
-               if (wn->mn.ampdu_size > 0)
-                       ieee80211_mira_choose(&wn->mn, ic, ni);
+               if (have_ack > 0) {
+                       wn->mn.txfail = wn->mn.frames - have_ack;
+                       iwn_mira_choose(sc, ni);
+               }
        }
-       if (wn->mn.ampdu_size > 0)
-               iwn_mira_choose(sc, ni);
 }
 
 /*
@@ -2553,15 +2595,32 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
        if (ic->ic_state != IEEE80211_S_RUN)
                return;
 
-       /*
-        * For each subframe collect Tx status, retries, and Tx rate used.
-        * (The Tx rate is the same for all subframes in this batch.)
-        */
        if (nframes > 1) {
+               int ampdu_id, have_ampdu_id = 0, ampdu_size = 0;
                int i;
+
+               /* Compute the size of this A-MPDU. */
                for (i = 0; i < nframes; i++) {
                        uint8_t qid = agg_status[i].qid;
                        uint8_t idx = agg_status[i].idx;
+
+                       if (qid != desc->qid)
+                               continue;
+
+                       txdata = &txq->data[idx];
+                       if (txdata->ni == NULL)
+                               continue;
+
+                       ampdu_size += txdata->totlen + IEEE80211_CRC_LEN;
+               }
+
+               /*
+                * For each subframe collect Tx status, retries, and Tx rate.
+                * (The Tx rate is the same for all subframes in this batch.)
+                */
+               for (i = 0; i < nframes; i++) {
+                       uint8_t qid = agg_status[i].qid;
+                       uint8_t idx = agg_status[i].idx;
                        uint16_t txstatus = (le16toh(agg_status[i].status) &
                            IWN_AGG_TX_STATUS_MASK);
                        uint16_t trycnt = (le16toh(agg_status[i].status) &
@@ -2574,13 +2633,36 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
                        if (txdata->ni == NULL)
                                continue;
 
-                       txdata->flags |= IWN_TXDATA_IS_AMPDU_SUBFRAME;
                        if (rflags & IWN_RFLAG_MCS)
-                               txdata->actual_txmcs = rate;
+                               txdata->ampdu_txmcs = rate;
                        if (txstatus != IWN_AGG_TX_STATE_TRANSMITTED)
                                txdata->txfail++;
                        if (trycnt > 1)
-                               txdata->retries += trycnt - 1;
+                               txdata->retries++;
+
+                       /*
+                        * Assign a common ID to all subframes of this A-MPDU.
+                        * This ID will be used during Tx rate control to
+                        * infer the ACK status of individual subframes.
+                        */
+                       if (!have_ampdu_id) {
+                               wn = (void *)txdata->ni;
+                               ampdu_id = wn->next_ampdu_id++;
+                               have_ampdu_id = 1;
+                       }
+                       txdata->ampdu_id = ampdu_id;
+
+                       /*
+                        * We will also need to know the total number of
+                        * subframes and the size of this A-MPDU. We store
+                        * this redundantly on each subframe because firmware
+                        * only reports acknowledged subframes via compressed
+                        * block-ack notification. This way we will know what
+                        * the total number of subframes and size were even if
+                        * just one of these subframes gets acknowledged.
+                        */
+                       txdata->ampdu_nframes = nframes;
+                       txdata->ampdu_size = ampdu_size;
                }
                return;
        }
@@ -2599,14 +2681,10 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
 
        /*
         * Skip rate control if our Tx rate is fixed.
-        *
-        * If this frame had Tx attempts as an A-MPDU subframe then
-        * rate control is handled upon reception of a block ack frame.
-        * Otherwise, this was the only Tx attempt for this frame and
-        * handling rate control now makes sense.
+        * Don't report frames to MiRA which were sent at a different
+        * Tx rate than ni->ni_txmcs.
         */
-       if (ic->ic_fixed_mcs == -1 && txdata->txmcs == ni->ni_txmcs &&
-           (txdata->flags & IWN_TXDATA_IS_AMPDU_SUBFRAME) == 0) {
+       if (ic->ic_fixed_mcs == -1 && txdata->txmcs == ni->ni_txmcs) {
                wn->mn.frames++;
                wn->mn.agglen = 1;
                wn->mn.ampdu_size = txdata->totlen + IEEE80211_CRC_LEN;
@@ -2617,32 +2695,18 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
                iwn_mira_choose(sc, ni);
        }
 
+       /*
+        * SSN corresponds to the first (perhaps not yet transmitted) frame
+        * in firmware's BA window. Firmware is not going to retransmit any
+        * frames before its BA window so mark them all as done.
+        */
+       ieee80211_output_ba_move_window(ic, ni, tid, ssn);
+       iwn_ampdu_txq_advance(sc, txq, desc->qid, IWN_AGG_SSN_TO_TXQ_IDX(ssn));
+       iwn_clear_oactive(sc, txq);
+
+       /* Clear potential gap at the front of receiver's BA window. */
        if (txfail)
                ieee80211_tx_compressed_bar(ic, ni, tid, ssn);
-       else if (!SEQ_LT(ssn, ba->ba_winstart)) {
-               /*
-                * Move window forward if SSN lies beyond end of window,
-                * otherwise we can't record the ACK for this frame.
-                * Non-acked frames which left holes in the bitmap near
-                * the beginning of the window must be discarded.
-                */
-               uint16_t s = ssn;
-               while (SEQ_LT(ba->ba_winend, s)) {
-                       ieee80211_output_ba_move_window(ic, ni, tid, s);
-                       iwn_ampdu_txq_advance(sc, txq, desc->qid,
-                           IWN_AGG_SSN_TO_TXQ_IDX(s));
-                       s = (s + 1) % 0xfff;
-               }
-               /* SSN should now be within window; set corresponding bit. */
-               ieee80211_output_ba_record_ack(ic, ni, tid, ssn);
-       }
-
-       /* Move window forward up to the first hole in the bitmap. */
-       ieee80211_output_ba_move_window_to_first_unacked(ic, ni, tid, ssn);
-       iwn_ampdu_txq_advance(sc, txq, desc->qid,
-           IWN_AGG_SSN_TO_TXQ_IDX(ba->ba_winstart));
-
-       iwn_clear_oactive(sc, txq);
 }
 
 /*
@@ -2760,9 +2824,8 @@ iwn_tx_done_free_txdata(struct iwn_softc *sc, struct i
        data->retries = 0;
        data->txfail = 0;
        data->txmcs = 0;
-       data->actual_txmcs = 0;
+       data->ampdu_txmcs = 0;
        data->txrate = 0;
-       data->flags = 0;
 }
 
 void
@@ -3623,7 +3686,7 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ie
        data->ni = ni;
        data->txmcs = ni->ni_txmcs;
        data->txrate = ni->ni_txrate;
-       data->actual_txmcs = ni->ni_txmcs; /* updated upon Tx interrupt */
+       data->ampdu_txmcs = ni->ni_txmcs; /* updated upon Tx interrupt */
 
        DPRINTFN(4, ("sending data: qid=%d idx=%d len=%d nsegs=%d\n",
            ring->qid, ring->cur, m->m_pkthdr.len, data->map->dm_nsegs));
blob - 95020825899ecad2a2bf9366fd5411d2dc8df9bb
blob + aa9ec41c308afa34fed94d45eae7df4d36c5409e
--- sys/dev/pci/if_iwnvar.h
+++ sys/dev/pci/if_iwnvar.h
@@ -71,9 +71,12 @@ struct iwn_tx_data {
        int txfail;
        int txmcs;
        int txrate;
-       int flags;
-#define IWN_TXDATA_IS_AMPDU_SUBFRAME   0x01
-       int actual_txmcs; /* for retried A-MPDU subframes */
+
+       /* A-MPDU subframes */
+       int ampdu_id;
+       int ampdu_txmcs;
+       int ampdu_nframes;
+       int ampdu_size;
 };
 
 struct iwn_tx_ring {
@@ -111,6 +114,7 @@ struct iwn_node {
        uint16_t                        disable_tid;
        uint8_t                         id;
        uint8_t                         ridx[IEEE80211_RATE_MAXSIZE];
+       uint32_t                        next_ampdu_id;
 };
 
 struct iwn_calib_state {

Reply via email to