This implements a new rate adaptation module for net80211, called "RA",
which resulted from a long discussion and exchanges of various diffs
between Christian Ehrhardt and myself, targeting problems with MiRA.

Tests with any of the various iwn(4) and iwm(4) devices are very welcome.

The iwn and iwm drivers get support in this patch.
No other drivers are affected. iwx(4) users in particular can ignore
this whole topic since Tx rate selection is handled in iwx firmware.

If this works out well I will convert additional drivers later.
The next target would be athn(4).

Christian helped me a lot by pin-pointing issues with MiRA's implementation
and the API between driver and MiRA. Christian even wrote a software
simulator that runs MiRA in userspace with fake input data. This helped
us identify some of the bugs (such as the fact that mira.c throughput
measurements are inaccurate even under perfect conditions created by the
simulator, and that upwards-probing never happens with the timeouts disabled).

There is no reason any of the issues could not be fixed in ieee8011_mira.c,
but instead I copied the file and changed it. I felt it was a good moment
to burn the house down and start over. And since this code diverges from
how MiRA is described in the research paper I think applying the "MiRA"
label to this code becomes inappropriate.

We're getting rid of much complexity of mira.c (timeouts, dynamic RTS
thresholds, complicated heuristics for throughput estimation) in favour
of simpler code that's easier to debug and, in our testing, works better.

The main difference is that RA does not attempt to precisely measure
actual throughput, but simply deducts a loss percentage from the
theoretical throughput which can be achieved by a given MCS. This is
somewhat similar to what ieee80211_amrr.c does, in the sense that the
main quality indicator for a given Tx rate is the number of Tx retries
(the less retries, the better the rate). But it also takes achievable
throughput into account, so a high rate like MCS 15 can afford a small
amount of retries before we begin to prefer a much lower rate. Of course,
if throughput drops to 0 because we're losing 100% of the frames we
immediately switch to lower rates.

Unlike MiRA, RA doesn't care whether or not a frame was part of an A-MPDU.
It simply collect stats about individual subframes. This makes reporting
very easy for drivers and seems to work well enough in practice, at least
on iwn(4) with Tx aggregation.

Another difference is that drivers can report multi-rate retries properly
via a new API: ieee80211_ra_add_stats_ht(mcs, total, fail) can be called
several times before ieee80211_ra_choose() selects a new rate.
So if firmware finds that e.g. MCS 7, 6, and 5 are broken but a retry at
MCS 4 worked out then the RA module has all this information immediately and
won't need to probe MCS 6, 5, and 4 again to arrive at the same conclusion.
The mira.c code cannot do this because the API communicates MCS via the
ni->ni_txmcs variable, a mechanism which I copied from amrr.c.


diff refs/heads/master refs/heads/ra
blob - f0344638d6ba5fa45b4f8ba3d0d4419e76164c3d
blob + f2feaff04bd1cf523c07a7c181873ac3bfc16d33
--- distrib/sets/lists/comp/mi
+++ distrib/sets/lists/comp/mi
@@ -1010,6 +1010,7 @@
 ./usr/include/net80211/ieee80211_node.h
 ./usr/include/net80211/ieee80211_priv.h
 ./usr/include/net80211/ieee80211_proto.h
+./usr/include/net80211/ieee80211_ra.h
 ./usr/include/net80211/ieee80211_radiotap.h
 ./usr/include/net80211/ieee80211_regdomain.h
 ./usr/include/net80211/ieee80211_rssadapt.h
blob - ba16d783d774fda440282bb11e2e5198a0e72e34
blob + 20e72d2ef7fccb46693842525a35d8f432ad874a
--- sys/conf/files
+++ sys/conf/files
@@ -861,6 +861,7 @@ file net80211/ieee80211_output.c    wlan
 file net80211/ieee80211_pae_input.c    wlan
 file net80211/ieee80211_pae_output.c   wlan
 file net80211/ieee80211_proto.c                wlan
+file net80211/ieee80211_ra.c           wlan
 file net80211/ieee80211_rssadapt.c     wlan
 file net80211/ieee80211_regdomain.c    wlan
 file netinet/if_ether.c                        ether
blob - f5deebe6fd519276cefe383eea81bfc69b110872
blob + d53ba73654a7f5a2ea7c49d730f290f407df3c29
--- sys/dev/pci/if_iwm.c
+++ sys/dev/pci/if_iwm.c
@@ -142,7 +142,7 @@
 
 #include <net80211/ieee80211_var.h>
 #include <net80211/ieee80211_amrr.h>
-#include <net80211/ieee80211_mira.h>
+#include <net80211/ieee80211_ra.h>
 #include <net80211/ieee80211_radiotap.h>
 
 #define DEVNAME(_s)    ((_s)->sc_dev.dv_xname)
@@ -4262,36 +4262,69 @@ iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_
         * Tx rate control decisions.
         */
        if ((ni->ni_flags & IEEE80211_NODE_HT) == 0) {
-               if (txrate == ni->ni_txrate) {
+               if (txrate != ni->ni_txrate) {
+                       if (++in->lq_rate_mismatch > 15) {
+                               /* Try to sync firmware with the driver... */
+                               iwm_setrates(in, 1);
+                               in->lq_rate_mismatch = 0;
+                       }
+               } else {
+                       in->lq_rate_mismatch = 0;
+
                        in->in_amn.amn_txcnt++;
                        if (txfail)
                                in->in_amn.amn_retrycnt++;
                        if (tx_resp->failure_frame > 0)
                                in->in_amn.amn_retrycnt++;
                }
-       } else if (ic->ic_fixed_mcs == -1 && txmcs == ni->ni_txmcs) {
-               in->in_mn.frames += tx_resp->frame_count;
-               in->in_mn.ampdu_size = le16toh(tx_resp->byte_cnt);
-               in->in_mn.agglen = tx_resp->frame_count;
-               if (tx_resp->failure_frame > 0)
-                       in->in_mn.retries += tx_resp->failure_frame;
-               if (txfail)
-                       in->in_mn.txfail += tx_resp->frame_count;
-               if (ic->ic_state == IEEE80211_S_RUN) {
-                       int best_mcs;
+       } else if (ic->ic_fixed_mcs == -1 && ic->ic_state == IEEE80211_S_RUN &&
+           (le32toh(tx_resp->initial_rate) & IWM_RATE_MCS_HT_MSK)) {
+               uint32_t fw_txmcs = le32toh(tx_resp->initial_rate) &
+                  (IWM_RATE_HT_MCS_RATE_CODE_MSK | IWM_RATE_HT_MCS_NSS_MSK);
+               /* Ignore Tx reports which don't match our last LQ command. */
+               if (fw_txmcs != ni->ni_txmcs) {
+                       if (++in->lq_rate_mismatch > 15) {
+                               /* Try to sync firmware with the driver... */
+                               iwm_setrates(in, 1);
+                               in->lq_rate_mismatch = 0;
+                       }
+               } else {
+                       int mcs = fw_txmcs;
+                       const struct ieee80211_ht_rateset *rs =
+                           ieee80211_ra_get_ht_rateset(fw_txmcs,
+                           ieee80211_node_supports_ht_sgi20(ni));
+                       unsigned int retries = 0, i;
+                       int old_txmcs = ni->ni_txmcs;
 
-                       ieee80211_mira_choose(&in->in_mn, ic, &in->in_ni);
+                       in->lq_rate_mismatch = 0;
+
+                       for (i = 0; i < tx_resp->failure_frame; i++) {
+                               if (mcs > rs->min_mcs) {
+                                       ieee80211_ra_add_stats_ht(&in->in_rn,
+                                           ic, ni, mcs, 1, 1);
+                                       mcs--;
+                               } else
+                                       retries++;
+                       }
+
+                       if (txfail && tx_resp->failure_frame == 0) {
+                               ieee80211_ra_add_stats_ht(&in->in_rn, ic, ni,
+                                   fw_txmcs, 1, 1);
+                       } else {
+                               ieee80211_ra_add_stats_ht(&in->in_rn, ic, ni,
+                                   mcs, retries + 1, retries);
+                       }
+
+                       ieee80211_ra_choose(&in->in_rn, ic, ni);
+       
                        /* 
-                        * If MiRA has chosen a new TX rate we must update
-                        * the firwmare's LQ rate table from process context.
+                        * If RA has chosen a new TX rate we must update
+                        * the firmware's LQ rate table.
                         * ni_txmcs may change again before the task runs so
                         * cache the chosen rate in the iwm_node structure.
                         */
-                       best_mcs = ieee80211_mira_get_best_mcs(&in->in_mn);
-                       if (best_mcs != in->chosen_txmcs) {
-                               in->chosen_txmcs = best_mcs;
+                       if (ni->ni_txmcs != old_txmcs)
                                iwm_setrates(in, 1);
-                       }
                }
        }
 
@@ -4835,10 +4868,6 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
                ridx = sc->sc_fixed_ridx;
        } else if (ic->ic_fixed_rate != -1) {
                ridx = sc->sc_fixed_ridx;
-       } else if ((ni->ni_flags & IEEE80211_NODE_HT) &&
-           ieee80211_mira_is_probing(&in->in_mn)) {
-               /* Keep Tx rate constant while mira is probing. */
-               ridx = iwm_mcs2ridx[ni->ni_txmcs];
        } else {
                int i;
                /* Use firmware rateset retry table. */
@@ -4899,7 +4928,7 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ie
        bus_dma_segment_t *seg;
        uint8_t tid, type;
        int i, totlen, err, pad;
-       int hdrlen2, rtsthres = ic->ic_rtsthreshold;
+       int hdrlen2;
 
        wh = mtod(m, struct ieee80211_frame *);
        hdrlen = ieee80211_get_hdrlen(wh);
@@ -4990,13 +5019,9 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ie
                flags |= IWM_TX_CMD_FLG_ACK;
        }
 
-       if (ni->ni_flags & IEEE80211_NODE_HT)
-               rtsthres = ieee80211_mira_get_rts_threshold(&in->in_mn, ic, ni,
-                   totlen + IEEE80211_CRC_LEN);
-
        if (type == IEEE80211_FC0_TYPE_DATA &&
            !IEEE80211_IS_MULTICAST(wh->i_addr1) &&
-           (totlen + IEEE80211_CRC_LEN > rtsthres ||
+           (totlen + IEEE80211_CRC_LEN > ic->ic_rtsthreshold ||
            (ic->ic_flags & IEEE80211_F_USEPROT)))
                flags |= IWM_TX_CMD_FLG_PROT_REQUIRE;
 
@@ -6841,7 +6866,7 @@ iwm_run(struct iwm_softc *sc)
        }
 
        ieee80211_amrr_node_init(&sc->sc_amrr, &in->in_amn);
-       ieee80211_mira_node_init(&in->in_mn);
+       ieee80211_ra_node_init(&in->in_rn);
 
        if (ic->ic_opmode == IEEE80211_M_MONITOR) {
                iwm_led_blink_start(sc);
@@ -6851,8 +6876,6 @@ iwm_run(struct iwm_softc *sc)
        /* Start at lowest available bit-rate, AMRR will raise. */
        in->in_ni.ni_txrate = 0;
        in->in_ni.ni_txmcs = 0;
-       in->chosen_txrate = 0;
-       in->chosen_txmcs = 0;
        iwm_setrates(in, 0);
 
        timeout_add_msec(&sc->sc_calib_to, 500);
@@ -7034,17 +7057,16 @@ iwm_calib_timeout(void *arg)
        if ((ic->ic_fixed_rate == -1 || ic->ic_fixed_mcs == -1) &&
            (ni->ni_flags & IEEE80211_NODE_HT) == 0 &&
            ic->ic_opmode == IEEE80211_M_STA && ic->ic_bss) {
+               int old_txrate = ni->ni_txrate;
                ieee80211_amrr_choose(&sc->sc_amrr, &in->in_ni, &in->in_amn);
                /* 
                 * If AMRR has chosen a new TX rate we must update
-                * the firwmare's LQ rate table from process context.
+                * the firwmare's LQ rate table.
                 * ni_txrate may change again before the task runs so
                 * cache the chosen rate in the iwm_node structure.
                 */
-               if (ni->ni_txrate != in->chosen_txrate) {
-                       in->chosen_txrate = ni->ni_txrate;
+               if (ni->ni_txrate != old_txrate)
                        iwm_setrates(in, 1);
-               }
        }
 
        splx(s);
@@ -7091,7 +7113,7 @@ iwm_setrates(struct iwm_node *in, int async)
         */
        j = 0;
        ridx_min = iwm_rval2ridx(ieee80211_min_basic_rate(ic));
-       mimo = iwm_is_mimo_mcs(in->chosen_txmcs);
+       mimo = iwm_is_mimo_mcs(ni->ni_txmcs);
        ridx_max = (mimo ? IWM_RIDX_MAX : IWM_LAST_HT_SISO_RATE);
        for (ridx = ridx_max; ridx >= ridx_min; ridx--) {
                uint8_t plcp = iwm_rates[ridx].plcp;
@@ -7107,7 +7129,7 @@ iwm_setrates(struct iwm_node *in, int async)
                        if ((mimo && !iwm_is_mimo_ht_plcp(ht_plcp)) ||
                            (!mimo && iwm_is_mimo_ht_plcp(ht_plcp)))
                                continue;
-                       for (i = in->chosen_txmcs; i >= 0; i--) {
+                       for (i = ni->ni_txmcs; i >= 0; i--) {
                                if (isclr(ni->ni_rxmcs, i))
                                        continue;
                                if (ridx == iwm_mcs2ridx[i]) {
@@ -7119,7 +7141,7 @@ iwm_setrates(struct iwm_node *in, int async)
                                }
                        }
                } else if (plcp != IWM_RATE_INVM_PLCP) {
-                       for (i = in->chosen_txrate; i >= 0; i--) {
+                       for (i = ni->ni_txrate; i >= 0; i--) {
                                if (iwm_rates[ridx].rate == (rs->rs_rates[i] &
                                    IEEE80211_RATE_VAL)) {
                                        tab = plcp;
@@ -7305,11 +7327,9 @@ iwm_newstate(struct ieee80211com *ic, enum ieee80211_s
 {
        struct ifnet *ifp = IC2IFP(ic);
        struct iwm_softc *sc = ifp->if_softc;
-       struct iwm_node *in = (void *)ic->ic_bss;
 
        if (ic->ic_state == IEEE80211_S_RUN) {
                timeout_del(&sc->sc_calib_to);
-               ieee80211_mira_cancel_timeouts(&in->in_mn);
                iwm_del_task(sc, systq, &sc->ba_task);
                iwm_del_task(sc, systq, &sc->htprot_task);
        }
@@ -8107,8 +8127,6 @@ iwm_stop(struct ifnet *ifp)
        ifq_clr_oactive(&ifp->if_snd);
 
        in->in_phyctxt = NULL;
-       if (ic->ic_state == IEEE80211_S_RUN)
-               ieee80211_mira_cancel_timeouts(&in->in_mn); /* XXX refcount? */
 
        sc->sc_flags &= ~(IWM_FLAG_SCANNING | IWM_FLAG_BGSCAN);
        sc->sc_flags &= ~IWM_FLAG_MAC_ACTIVE;
blob - 300a91de418e95fa594bc59e37c99a8293fff63c
blob + bc0a6b7f2a5c7cbeb445f13c245ed3e657b544d5
--- sys/dev/pci/if_iwmvar.h
+++ sys/dev/pci/if_iwmvar.h
@@ -546,9 +546,8 @@ struct iwm_node {
        uint16_t in_color;
 
        struct ieee80211_amrr_node in_amn;
-       int chosen_txrate;
-       struct ieee80211_mira_node in_mn;
-       int chosen_txmcs;
+       struct ieee80211_ra_node in_rn;
+       int lq_rate_mismatch;
 };
 #define IWM_STATION_ID 0
 #define IWM_AUX_STA_ID 1
blob - a7b7249d46e8b132b6dae3da58ace6a87bb6b6a0
blob + 6451d88fcbe20d094c7bcbdfccbe5974ba33e840
--- sys/dev/pci/if_iwn.c
+++ sys/dev/pci/if_iwn.c
@@ -55,7 +55,7 @@
 
 #include <net80211/ieee80211_var.h>
 #include <net80211/ieee80211_amrr.h>
-#include <net80211/ieee80211_mira.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 */
@@ -157,9 +157,11 @@ void               iwn_rx_phy(struct iwn_softc *, struct 
iwn_rx_des
                    struct iwn_rx_data *);
 void           iwn_rx_done(struct iwn_softc *, struct iwn_rx_desc *,
                    struct iwn_rx_data *, struct mbuf_list *);
-void           iwn_mira_choose(struct iwn_softc *, struct ieee80211_node *);
+void           iwn_ra_choose(struct iwn_softc *, struct ieee80211_node *);
 void           iwn_ampdu_rate_control(struct iwn_softc *, struct 
ieee80211_node *,
-                   struct iwn_tx_ring *, int, uint16_t, uint16_t);
+                   struct iwn_tx_ring *, uint16_t, uint16_t);
+void           iwn_ht_single_rate_control(struct iwn_softc *,
+                   struct ieee80211_node *, uint8_t, uint8_t, uint8_t, int);
 void           iwn_rx_compressed_ba(struct iwn_softc *, struct iwn_rx_desc *,
                    struct iwn_rx_data *);
 void           iwn5000_rx_calib_results(struct iwn_softc *,
@@ -179,7 +181,7 @@ void                iwn_tx_done_free_txdata(struct 
iwn_softc *,
                    struct iwn_tx_data *);
 void           iwn_clear_oactive(struct iwn_softc *, struct iwn_tx_ring *);
 void           iwn_tx_done(struct iwn_softc *, struct iwn_rx_desc *,
-                   uint8_t, uint8_t, int, int, uint16_t);
+                   uint8_t, uint8_t, uint8_t, int, int, uint16_t);
 void           iwn_cmd_done(struct iwn_softc *, struct iwn_rx_desc *);
 void           iwn_notif_intr(struct iwn_softc *);
 void           iwn_wakeup_intr(struct iwn_softc *);
@@ -1771,7 +1773,6 @@ iwn_newstate(struct ieee80211com *ic, enum ieee80211_s
        struct ifnet *ifp = &ic->ic_if;
        struct iwn_softc *sc = ifp->if_softc;
        struct ieee80211_node *ni = ic->ic_bss;
-       struct iwn_node *wn = (void *)ni;
        int error;
 
        if (ic->ic_state == IEEE80211_S_RUN) {
@@ -1783,7 +1784,6 @@ iwn_newstate(struct ieee80211com *ic, enum ieee80211_s
                        ieee80211_stop_ampdu_tx(ic, ni, -1);
                        ieee80211_ba_del(ni);
                }
-               ieee80211_mira_cancel_timeouts(&wn->mn);
                timeout_del(&sc->calib_to);
                sc->calib.state = IWN_CALIB_STATE_INIT;
                if (sc->sc_flags & IWN_FLAG_BGSCAN)
@@ -2229,104 +2229,98 @@ iwn_rx_done(struct iwn_softc *sc, struct iwn_rx_desc *
 }
 
 void
-iwn_mira_choose(struct iwn_softc *sc, struct ieee80211_node *ni)
+iwn_ra_choose(struct iwn_softc *sc, struct ieee80211_node *ni)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwn_node *wn = (void *)ni;
-       int best_mcs = ieee80211_mira_get_best_mcs(&wn->mn);
+       int old_txmcs = ni->ni_txmcs;
 
-       ieee80211_mira_choose(&wn->mn, ic, ni);
+       ieee80211_ra_choose(&wn->rn, ic, ni);
 
-       /*
-        * Update firmware's LQ retry table if MiRA has chosen a new MCS.
-        *
-        * We only need to do this if the best MCS has changed because
-        * we ask firmware to use a fixed MCS while MiRA is probing a
-        * candidate MCS.
-        * While not probing we ask firmware to retry at lower rates in case
-        * Tx at the newly chosen best MCS ends up failing, and then report
-        * any resulting Tx retries to MiRA in order to trigger probing.
-        */
-       if (best_mcs != ieee80211_mira_get_best_mcs(&wn->mn))
+       /* Update firmware's LQ retry table if RA has chosen a new MCS. */
+       if (ni->ni_txmcs != old_txmcs)
                iwn_set_link_quality(sc, ni);
 }
 
 void
 iwn_ampdu_rate_control(struct iwn_softc *sc, struct ieee80211_node *ni,
-    struct iwn_tx_ring *txq, int tid, uint16_t seq, uint16_t ssn)
+    struct iwn_tx_ring *txq, uint16_t seq, uint16_t ssn)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwn_node *wn = (void *)ni;
-       struct ieee80211_tx_ba *ba = &ni->ni_tx_ba[tid];
-       int min_ampdu_id, max_ampdu_id, id;
        int idx, end_idx;
 
-       /* Determine the min/max IDs we assigned to AMPDUs in this range. */
+       /*
+        * Update Tx rate statistics for A-MPDUs before firmware's BA window.
+        */
        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;
-               while (idx != end_idx) {
-                       struct iwn_tx_data *txdata = &txq->data[idx];
-                       uint16_t s = (seq + bit) & 0xfff;
+               if (txdata->m != NULL && txdata->ampdu_nframes > 1) {
                        /*
                         * 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 &&
-                           txdata->ampdu_nframes > 0 &&
-                           (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,
-                                           tid, s);
-                       }
+                       ieee80211_ra_add_stats_ht(&wn->rn, ic, ni,
+                           txdata->ampdu_txmcs, 1, 0);
 
-                       idx = (idx + 1) % IWN_TX_RING_COUNT;
-                       bit++;
+                       /* Report this frame only once. */
+                       txdata->ampdu_nframes = 0;
                }
 
-               if (have_ack > 0) {
-                       wn->mn.txfail = wn->mn.frames - have_ack;
-                       iwn_mira_choose(sc, ni);
+               idx = (idx + 1) % IWN_TX_RING_COUNT;
+       }
+
+       iwn_ra_choose(sc, ni);
+}
+
+void
+iwn_ht_single_rate_control(struct iwn_softc *sc, struct ieee80211_node *ni,
+    uint8_t rate, uint8_t rflags, uint8_t ackfailcnt, int txfail)
+{
+       struct ieee80211com *ic = &sc->sc_ic;
+       struct iwn_node *wn = (void *)ni;
+       int mcs = rate;
+       const struct ieee80211_ht_rateset *rs =
+           ieee80211_ra_get_ht_rateset(rate,
+           ieee80211_node_supports_ht_sgi20(ni));
+       unsigned int retries = 0, i;
+
+       /*
+        * Ignore Tx reports which don't match our last LQ command.
+        */
+       if (rate != ni->ni_txmcs) {
+               if (++wn->lq_rate_mismatch > 15) {
+                       /* Try to sync firmware with driver. */
+                       iwn_set_link_quality(sc, ni);
+                       wn->lq_rate_mismatch = 0;
                }
+               return;
        }
+
+       wn->lq_rate_mismatch = 0;
+
+       /*
+        * Firmware has attempted rates in this rate set in sequence.
+        * Retries at a basic rate are counted against the minimum MCS.
+        */
+       for (i = 0; i < ackfailcnt; i++) {
+               if (mcs > rs->min_mcs) {
+                       ieee80211_ra_add_stats_ht(&wn->rn, ic, ni, mcs, 1, 1);
+                       mcs--;
+               } else
+                       retries++;
+       }
+
+       if (txfail && ackfailcnt == 0)
+               ieee80211_ra_add_stats_ht(&wn->rn, ic, ni, mcs, 1, 1);
+       else
+               ieee80211_ra_add_stats_ht(&wn->rn, ic, ni, mcs, retries + 1, 
retries);
+
+       iwn_ra_choose(sc, ni);
 }
 
 /*
@@ -2391,8 +2385,7 @@ iwn_rx_compressed_ba(struct iwn_softc *sc, struct iwn_
 
        /* Skip rate control if our Tx rate is fixed. */
        if (ic->ic_fixed_mcs == -1)
-               iwn_ampdu_rate_control(sc, ni, txq, cba->tid, ba->ba_winstart,
-                   ssn);
+               iwn_ampdu_rate_control(sc, ni, txq, ba->ba_winstart, ssn);
 
        /*
         * SSN corresponds to the first (perhaps not yet transmitted) frame
@@ -2555,9 +2548,9 @@ iwn_ampdu_txq_advance(struct iwn_softc *sc, struct iwn
 /*
  * Handle A-MPDU Tx queue status report.
  * Tx failures come as single frames (perhaps out of order), and before failing
- * an A-MPDU subframe the firmware transmits it as a single frame at least once
- * and reports Tx success/failure here. Frames successfully transmitted in an
- * A-MPDU are completed when a compressed block ack notification is received.
+ * an A-MPDU subframe the firmware transmits it as a single frame at least 
once.
+ * Frames successfully transmitted in an A-MPDU are completed when a compressed
+ * block ack notification is received.
  */
 void
 iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_ring *txq,
@@ -2569,7 +2562,6 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
        int tid = desc->qid - sc->first_agg_txq;
        struct iwn_tx_data *txdata = &txq->data[desc->idx];
        struct ieee80211_node *ni = txdata->ni;
-       struct iwn_node *wn = (void *)ni;
        int txfail = (status != IWN_TX_STATUS_SUCCESS &&
            status != IWN_TX_STATUS_DIRECT_DONE);
        struct ieee80211_tx_ba *ba;
@@ -2581,73 +2573,30 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
                return;
 
        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.)
+               /*
+                * Collect information about this A-MPDU.
                 */
                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) &
-                           IWN_AGG_TX_TRY) >> IWN_AGG_TX_TRY_SHIFT;
 
-                       if (qid != desc->qid)
-                               continue;
-
-                       txdata = &txq->data[idx];
-                       if (txdata->ni == NULL)
-                               continue;
-
-                       if (rflags & IWN_RFLAG_MCS)
-                               txdata->ampdu_txmcs = rate;
                        if (txstatus != IWN_AGG_TX_STATE_TRANSMITTED)
-                               txdata->txfail++;
-                       if (trycnt > 1)
-                               txdata->retries++;
+                               continue;
 
-                       /*
-                        * 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;
+                       if (qid != desc->qid)
+                               continue;
 
-                       /*
-                        * 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 = &txq->data[idx];
+                       if (txdata->ni == NULL)
+                               continue;
+
+                       /* The Tx rate was the same for all subframes. */
+                       txdata->ampdu_txmcs = rate;
                        txdata->ampdu_nframes = nframes;
-                       txdata->ampdu_size = ampdu_size;
                }
                return;
        }
@@ -2664,18 +2613,24 @@ iwn_ampdu_tx_done(struct iwn_softc *sc, struct iwn_tx_
 
        /*
         * Skip rate control if our Tx rate is fixed.
-        * 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) {
-               wn->mn.frames++;
-               wn->mn.agglen = 1;
-               wn->mn.ampdu_size = txdata->totlen + IEEE80211_CRC_LEN;
-               if (ackfailcnt > 0)
-                       wn->mn.retries++;
-               if (txfail)
-                       wn->mn.txfail++;
-               iwn_mira_choose(sc, ni);
+       if (ic->ic_fixed_mcs == -1) {
+               if (txdata->ampdu_nframes > 1) {
+                       struct iwn_node *wn = (void *)ni;
+                       /*
+                        * This frame was once part of an A-MPDU.
+                        * Report one failed A-MPDU Tx attempt.
+                        * The firmware might have made several such
+                        * attempts but we don't keep track of this.
+                        */
+                       ieee80211_ra_add_stats_ht(&wn->rn, ic, ni,
+                           txdata->ampdu_txmcs, 1, 1);
+               }
+
+               /* Report the final single-frame Tx attempt. */
+               if (rflags & IWN_RFLAG_MCS)
+                       iwn_ht_single_rate_control(sc, ni, rate, rflags,
+                           ackfailcnt, txfail);
        }
 
        if (txfail)
@@ -2746,8 +2701,8 @@ iwn4965_tx_done(struct iwn_softc *sc, struct iwn_rx_de
                int txfail = (status != IWN_TX_STATUS_SUCCESS &&
                    status != IWN_TX_STATUS_DIRECT_DONE);
 
-               iwn_tx_done(sc, desc, stat->ackfailcnt, stat->rate, txfail,
-                   desc->qid, framelen);
+               iwn_tx_done(sc, desc, stat->ackfailcnt, stat->rate,
+                   stat->rflags, txfail, desc->qid, framelen);
        } else {
                memcpy(&ssn, &stat->stat.status + stat->nframes, sizeof(ssn));
                ssn = le32toh(ssn) & 0xfff;
@@ -2794,8 +2749,8 @@ iwn5000_tx_done(struct iwn_softc *sc, struct iwn_rx_de
                /* Reset TX scheduler slot. */
                iwn5000_reset_sched(sc, desc->qid, desc->idx);
 
-               iwn_tx_done(sc, desc, stat->ackfailcnt, stat->rate, txfail,
-                   desc->qid, letoh16(stat->len));
+               iwn_tx_done(sc, desc, stat->ackfailcnt, stat->rate,
+                   stat->rflags, txfail, desc->qid, letoh16(stat->len));
        } else {
                memcpy(&ssn, &stat->stat.status + stat->nframes, sizeof(ssn));
                ssn = le32toh(ssn) & 0xfff;
@@ -2818,11 +2773,8 @@ iwn_tx_done_free_txdata(struct iwn_softc *sc, struct i
        ieee80211_release_node(ic, data->ni);
        data->ni = NULL;
        data->totlen = 0;
-       data->retries = 0;
-       data->txfail = 0;
-       data->txmcs = 0;
+       data->ampdu_nframes = 0;
        data->ampdu_txmcs = 0;
-       data->txrate = 0;
 }
 
 void
@@ -2846,7 +2798,8 @@ iwn_clear_oactive(struct iwn_softc *sc, struct iwn_tx_
  */
 void
 iwn_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *desc,
-    uint8_t ackfailcnt, uint8_t rate, int txfail, int qid, uint16_t len)
+    uint8_t ackfailcnt, uint8_t rate, uint8_t rflags, int txfail,
+    int qid, uint16_t len)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct ifnet *ifp = &ic->ic_if;
@@ -2859,23 +2812,26 @@ iwn_tx_done(struct iwn_softc *sc, struct iwn_rx_desc *
 
        if (data->ni->ni_flags & IEEE80211_NODE_HT) {
                if (ic->ic_state == IEEE80211_S_RUN &&
-                   ic->ic_fixed_mcs == -1 &&
-                   data->txmcs == data->ni->ni_txmcs) {
-                       wn->mn.frames++;
-                       wn->mn.ampdu_size = len;
-                       wn->mn.agglen = 1;
+                   ic->ic_fixed_mcs == -1 && (rflags & IWN_RFLAG_MCS)) {
+                       iwn_ht_single_rate_control(sc, data->ni, rate, rflags,
+                           ackfailcnt, txfail);
+               }
+       } else {
+               if (rate != data->ni->ni_txrate) {
+                       if (++wn->lq_rate_mismatch > 15) {
+                               /* Try to sync firmware with driver. */
+                               iwn_set_link_quality(sc, data->ni);
+                               wn->lq_rate_mismatch = 0;
+                       }
+               } else {
+                       wn->lq_rate_mismatch = 0;
+
+                       wn->amn.amn_txcnt++;
                        if (ackfailcnt > 0)
-                               wn->mn.retries++;
+                               wn->amn.amn_retrycnt++;
                        if (txfail)
-                               wn->mn.txfail++;
-                       iwn_mira_choose(sc, data->ni);
+                               wn->amn.amn_retrycnt++;
                }
-       } else if (data->txrate == data->ni->ni_txrate) {
-               wn->amn.amn_txcnt++;
-               if (ackfailcnt > 0)
-                       wn->amn.amn_retrycnt++;
-               if (txfail)
-                       wn->amn.amn_retrycnt++;
        }
        if (txfail)
                ifp->if_oerrors++;
@@ -3528,13 +3484,8 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ie
 
        /* Check if frame must be protected using RTS/CTS or CTS-to-self. */
        if (!IEEE80211_IS_MULTICAST(wh->i_addr1)) {
-               int rtsthres = ic->ic_rtsthreshold;
-               if (ni->ni_flags & IEEE80211_NODE_HT)
-                       rtsthres = ieee80211_mira_get_rts_threshold(&wn->mn,
-                           ic, ni, totlen + IEEE80211_CRC_LEN);
-
                /* NB: Group frames are sent using CCK in 802.11b/g/n (2GHz). */
-               if (totlen + IEEE80211_CRC_LEN > rtsthres) {
+               if (totlen + IEEE80211_CRC_LEN > ic->ic_rtsthreshold) {
                        flags |= IWN_TX_NEED_RTS;
                } else if ((ic->ic_flags & IEEE80211_F_USEPROT) &&
                    ridx >= IWN_RIDX_OFDM6) {
@@ -3601,15 +3552,9 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ie
        }
        else
                tx->rflags = rinfo->flags;
-       /*
-        * Keep the Tx rate constant while mira is probing, or if this is
-        * an aggregation queue in which case a fixed Tx rate works around
-        * FIFO_UNDERRUN Tx errors.
-        */
-       if (tx->id == sc->broadcast_id || ieee80211_mira_is_probing(&wn->mn) ||
-           qid >= sc->first_agg_txq ||
-           ic->ic_fixed_mcs != -1 || ic->ic_fixed_rate != -1) {
-               /* Group or management frame, or probing, or fixed Tx rate. */
+       if (tx->id == sc->broadcast_id || ic->ic_fixed_mcs != -1 ||
+           ic->ic_fixed_rate != -1) {
+               /* Group or management frame, or fixed Tx rate. */
                tx->linkq = 0;
                /* XXX Alternate between antenna A and B? */
                txant = IWN_LSB(sc->txchainmask);
@@ -3680,8 +3625,6 @@ iwn_tx(struct iwn_softc *sc, struct mbuf *m, struct ie
 
        data->m = m;
        data->ni = ni;
-       data->txmcs = ni->ni_txmcs;
-       data->txrate = ni->ni_txrate;
        data->ampdu_txmcs = ni->ni_txmcs; /* updated upon Tx interrupt */
 
        DPRINTFN(4, ("sending data: qid=%d idx=%d len=%d nsegs=%d\n",
@@ -5649,7 +5592,7 @@ iwn_run(struct iwn_softc *sc)
        sc->calib_cnt = 0;
        timeout_add_msec(&sc->calib_to, 500);
 
-       ieee80211_mira_node_init(&wn->mn);
+       ieee80211_ra_node_init(&wn->rn);
 
        /* Link LED always on while associated. */
        iwn_set_led(sc, IWN_LED_LINK, 0, 1);
blob - 3af35055a6ea1c3586ad1c56c4ea9339f8e33e73
blob + 2d28f851c128c2fd9645f27410981abbf4132de4
--- sys/dev/pci/if_iwnvar.h
+++ sys/dev/pci/if_iwnvar.h
@@ -65,16 +65,10 @@ struct iwn_tx_data {
        struct mbuf             *m;
        struct ieee80211_node   *ni;
        int totlen;
-       int retries;
-       int txfail;
-       int txmcs;
-       int txrate;
 
        /* A-MPDU subframes */
-       int ampdu_id;
        int ampdu_txmcs;
        int ampdu_nframes;
-       int ampdu_size;
 };
 
 struct iwn_tx_ring {
@@ -108,11 +102,12 @@ struct iwn_rx_ring {
 struct iwn_node {
        struct  ieee80211_node          ni;     /* must be the first */
        struct  ieee80211_amrr_node     amn;
-       struct  ieee80211_mira_node     mn;
+       struct  ieee80211_ra_node       rn;
        uint16_t                        disable_tid;
        uint8_t                         id;
        uint8_t                         ridx[IEEE80211_RATE_MAXSIZE];
        uint32_t                        next_ampdu_id;
+       int                             lq_rate_mismatch;
 };
 
 struct iwn_calib_state {
blob - /dev/null
blob + aa1c15eb2c8012912e3f97ee705403826f1ca087 (mode 644)
--- /dev/null
+++ sys/net80211/ieee80211_ra.c
@@ -0,0 +1,704 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Christian Ehrhardt <ehrha...@genua.de>
+ * Copyright (c) 2016, 2021 Stefan Sperling <s...@openbsd.org>
+ * Copyright (c) 2016 Theo Buehler <t...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/socket.h>
+
+#include <net/if.h>
+#include <net/if_media.h>
+
+#include <netinet/in.h>
+#include <netinet/if_ether.h>
+
+#include <net80211/ieee80211_var.h>
+#include <net80211/ieee80211_ra.h>
+
+int    ieee80211_ra_next_intra_rate(struct ieee80211_ra_node *,
+           struct ieee80211_node *);
+const struct ieee80211_ht_rateset * ieee80211_ra_next_rateset(
+                   struct ieee80211_ra_node *, struct ieee80211_node *);
+int    ieee80211_ra_best_mcs_in_rateset(struct ieee80211_ra_node *,
+           const struct ieee80211_ht_rateset *);
+void   ieee80211_ra_probe_next_rateset(struct ieee80211_ra_node *,
+           struct ieee80211_node *, const struct ieee80211_ht_rateset *);
+int    ieee80211_ra_next_mcs(struct ieee80211_ra_node *,
+           struct ieee80211_node *);
+int    ieee80211_ra_probe_valid(struct ieee80211_ra_node *,
+           struct ieee80211_node *);
+void   ieee80211_ra_probe_done(struct ieee80211_ra_node *);
+int    ieee80211_ra_intra_mode_ra_finished(
+           struct ieee80211_ra_node *, struct ieee80211_node *);
+void   ieee80211_ra_trigger_next_rateset(struct ieee80211_ra_node *,
+           struct ieee80211_node *);
+int    ieee80211_ra_inter_mode_ra_finished(
+           struct ieee80211_ra_node *, struct ieee80211_node *);
+int    ieee80211_ra_best_rate(struct ieee80211_ra_node *,
+           struct ieee80211_node *);
+void   ieee80211_ra_probe_next_rate(struct ieee80211_ra_node *,
+           struct ieee80211_node *);
+int    ieee80211_ra_valid_tx_mcs(struct ieee80211com *, int);
+uint32_t ieee80211_ra_valid_rates(struct ieee80211com *,
+           struct ieee80211_node *);
+
+/* We use fixed point arithmetic with 64 bit integers. */
+#define RA_FP_SHIFT    21
+#define RA_FP_INT(x)   (x ## ULL << RA_FP_SHIFT) /* the integer x */
+#define RA_FP_1        RA_FP_INT(1)
+
+/* Multiply two fixed point numbers. */
+#define RA_FP_MUL(a, b) \
+       (((a) * (b)) >> RA_FP_SHIFT)
+
+/* Divide two fixed point numbers. */
+#define RA_FP_DIV(a, b) \
+       (b == 0 ? (uint64_t)-1 : (((a) << RA_FP_SHIFT) / (b)))
+
+#define RA_DEBUG
+#ifdef RA_DEBUG
+#define DPRINTF(x)     do { if (ra_debug > 0) printf x; } while (0)
+#define DPRINTFN(n, x) do { if (ra_debug >= (n)) printf x; } while (0)
+int ra_debug = 0;
+#else
+#define DPRINTF(x)     do { ; } while (0)
+#define DPRINTFN(n, x) do { ; } while (0)
+#endif
+
+#ifdef RA_DEBUG
+void
+ra_fixedp_split(uint32_t *i, uint32_t *f, uint64_t fp)
+{
+       uint64_t tmp;
+
+       /* integer part */
+       *i = (fp >> RA_FP_SHIFT);
+
+       /* fractional part */
+       tmp = (fp & ((uint64_t)-1 >> (64 - RA_FP_SHIFT)));
+       tmp *= 100;
+       *f = (uint32_t)(tmp >> RA_FP_SHIFT);
+}
+
+char *
+ra_fp_sprintf(uint64_t fp)
+{
+       uint32_t i, f;
+       static char buf[64];
+       int ret;
+
+       ra_fixedp_split(&i, &f, fp);
+       ret = snprintf(buf, sizeof(buf), "%u.%02u", i, f);
+       if (ret == -1 || ret >= sizeof(buf))
+               return "ERR";
+
+       return buf;
+}
+#endif /* RA_DEBUG */
+
+const struct ieee80211_ht_rateset *
+ieee80211_ra_get_ht_rateset(int mcs, int sgi20)
+{
+       const struct ieee80211_ht_rateset *rs;
+       int i;
+
+       for (i = 0; i < IEEE80211_HT_NUM_RATESETS; i++) {
+               rs = &ieee80211_std_ratesets_11n[i];
+               if (sgi20 != rs->sgi)
+                       continue;
+               if (mcs >= rs->min_mcs && mcs <= rs->max_mcs)
+                       return rs;
+       }
+
+       panic("MCS %d is not part of any rateset", mcs);
+}
+
+/*
+ * Update goodput statistics.
+ */
+
+uint64_t
+ieee80211_ra_get_txrate(int mcs, int sgi20)
+{
+       const struct ieee80211_ht_rateset *rs;
+       uint64_t txrate;
+
+       rs = ieee80211_ra_get_ht_rateset(mcs, sgi20);
+       txrate = rs->rates[mcs - rs->min_mcs];
+       txrate <<= RA_FP_SHIFT; /* convert to fixed-point */
+       txrate *= 500; /* convert to kbit/s */
+       txrate /= 1000; /* convert to mbit/s */
+
+       return txrate;
+}
+
+/*
+ * Rate selection.
+ */
+
+/* A rate's goodput has to be at least this much larger to be "better". */
+#define IEEE80211_RA_RATE_THRESHOLD    (RA_FP_1 / 64) /* ~ 0.015 */
+
+/* Number of (sub-)frames which render a probe valid. */
+#define IEEE80211_RA_MIN_PROBE_FRAMES  8
+
+/* Number of Tx retries which, alternatively, render a probe valid. */
+#define IEEE80211_RA_MAX_PROBE_RETRIES 4
+
+int
+ieee80211_ra_next_lower_intra_rate(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_ht_rateset *rs;
+       int i, next;
+       int sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+
+       rs = ieee80211_ra_get_ht_rateset(ni->ni_txmcs, sgi20);
+       if (ni->ni_txmcs == rs->min_mcs)
+               return rs->min_mcs;
+
+       next = ni->ni_txmcs;
+       for (i = rs->nrates - 1; i >= 0; i--) {
+               if ((rn->valid_rates & (1 << (i + rs->min_mcs))) == 0)
+                       continue;
+               if (i + rs->min_mcs < ni->ni_txmcs) {
+                       next = i + rs->min_mcs;
+                       break;
+               }
+       }
+
+       return next;
+}
+
+int
+ieee80211_ra_next_intra_rate(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_ht_rateset *rs;
+       int i, next;
+       int sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+
+       rs = ieee80211_ra_get_ht_rateset(ni->ni_txmcs, sgi20);
+       if (ni->ni_txmcs == rs->max_mcs)
+               return rs->max_mcs;
+
+       next = ni->ni_txmcs;
+       for (i = 0; i < rs->nrates; i++) {
+               if ((rn->valid_rates & (1 << (i + rs->min_mcs))) == 0)
+                       continue;
+               if (i + rs->min_mcs > ni->ni_txmcs) {
+                       next = i + rs->min_mcs;
+                       break;
+               }
+       }
+
+       return next;
+}
+
+const struct ieee80211_ht_rateset *
+ieee80211_ra_next_rateset(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_ht_rateset *rs, *rsnext;
+       int next;
+       int mcs = ni->ni_txmcs;
+       int sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+
+       rs = ieee80211_ra_get_ht_rateset(mcs, sgi20);
+       if (rn->probing & IEEE80211_RA_PROBING_UP) {
+               if (rs->max_mcs == 7)   /* MCS 0-7 */
+                       next = sgi20 ? IEEE80211_HT_RATESET_MIMO2_SGI :
+                           IEEE80211_HT_RATESET_MIMO2;
+               else if (rs->max_mcs == 15)     /* MCS 8-15 */
+                       next = sgi20 ? IEEE80211_HT_RATESET_MIMO3_SGI :
+                           IEEE80211_HT_RATESET_MIMO3;
+               else if (rs->max_mcs == 23)     /* MCS 16-23 */
+                       next = sgi20 ? IEEE80211_HT_RATESET_MIMO4_SGI :
+                           IEEE80211_HT_RATESET_MIMO4;
+               else                            /* MCS 24-31 */
+                       return NULL;
+       } else if (rn->probing & IEEE80211_RA_PROBING_DOWN) {
+               if (rs->min_mcs == 24)  /* MCS 24-31 */
+                       next = sgi20 ? IEEE80211_HT_RATESET_MIMO3_SGI :
+                           IEEE80211_HT_RATESET_MIMO3;
+               else if (rs->min_mcs == 16)     /* MCS 16-23 */
+                       next = sgi20 ? IEEE80211_HT_RATESET_MIMO2_SGI :
+                           IEEE80211_HT_RATESET_MIMO2;
+               else if (rs->min_mcs == 8)      /* MCS 8-15 */
+                       next = sgi20 ? IEEE80211_HT_RATESET_SISO_SGI :
+                           IEEE80211_HT_RATESET_SISO;
+               else                            /* MCS 0-7 */
+                       return NULL;
+       } else
+               panic("%s: invalid probing mode %d", __func__, rn->probing);
+
+       rsnext = &ieee80211_std_ratesets_11n[next];
+       if ((rsnext->mcs_mask & rn->valid_rates) == 0)
+               return NULL;
+
+       return rsnext;
+}
+
+int
+ieee80211_ra_best_mcs_in_rateset(struct ieee80211_ra_node *rn,
+    const struct ieee80211_ht_rateset *rs)
+{
+       uint64_t gmax = 0;
+       int i, best_mcs = rs->min_mcs;
+
+       for (i = 0; i < rs->nrates; i++) {
+               int mcs = rs->min_mcs + i;
+               struct ieee80211_ra_goodput_stats *g = &rn->g[mcs];
+               if (((1 << mcs) & rn->valid_rates) == 0)
+                       continue;
+               if (g->measured > gmax + IEEE80211_RA_RATE_THRESHOLD) {
+                       gmax = g->measured;
+                       best_mcs = mcs;
+               }
+       }
+
+       return best_mcs;
+}
+    
+void
+ieee80211_ra_probe_next_rateset(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni, const struct ieee80211_ht_rateset *rsnext)
+{
+       const struct ieee80211_ht_rateset *rs;
+       struct ieee80211_ra_goodput_stats *g;
+       int best_mcs, i;
+       int sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+
+       /* Find most recently measured best MCS from the current rateset. */
+       rs = ieee80211_ra_get_ht_rateset(ni->ni_txmcs, sgi20);
+       best_mcs = ieee80211_ra_best_mcs_in_rateset(rn, rs);
+
+       /* Switch to the next rateset. */
+       ni->ni_txmcs = rsnext->min_mcs;
+       if ((rn->valid_rates & (1 << rsnext->min_mcs)) == 0)
+               ni->ni_txmcs = ieee80211_ra_next_intra_rate(rn, ni);
+
+       /* Select the lowest rate from the next rateset with loss-free
+        * goodput close to the current best measurement. */
+       g = &rn->g[best_mcs];
+       for (i = 0; i < rsnext->nrates; i++) {
+               int mcs = rsnext->min_mcs + i;
+               uint64_t txrate = rsnext->rates[i];
+
+               if ((rn->valid_rates & (1 << mcs)) == 0)
+                       continue;
+
+               txrate = txrate * 500; /* convert to kbit/s */
+               txrate <<= RA_FP_SHIFT; /* convert to fixed-point */
+               txrate /= 1000; /* convert to mbit/s */
+
+               if (txrate > g->measured + IEEE80211_RA_RATE_THRESHOLD) {
+                       ni->ni_txmcs = mcs;
+                       break;
+               }
+       }
+       /* If all rates are lower the maximum rate is the closest match. */
+       if (i == rsnext->nrates)
+               ni->ni_txmcs = rsnext->max_mcs;
+
+       /* Add rates from the next rateset as candidates. */
+       rn->candidate_rates |= (1 << ni->ni_txmcs);
+       if (rn->probing & IEEE80211_RA_PROBING_UP) {
+               rn->candidate_rates |=
+                 (1 << ieee80211_ra_next_intra_rate(rn, ni));
+       } else if (rn->probing & IEEE80211_RA_PROBING_DOWN) {
+               rn->candidate_rates |=
+                   (1 << ieee80211_ra_next_lower_intra_rate(rn, ni));
+       } else
+               panic("%s: invalid probing mode %d", __func__, rn->probing);
+}
+
+int
+ieee80211_ra_next_mcs(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       int next;
+
+       if (rn->probing & IEEE80211_RA_PROBING_DOWN)
+               next = ieee80211_ra_next_lower_intra_rate(rn, ni);
+       else if (rn->probing & IEEE80211_RA_PROBING_UP)
+               next = ieee80211_ra_next_intra_rate(rn, ni);
+       else
+               panic("%s: invalid probing mode %d", __func__, rn->probing);
+
+       return next;
+}
+
+int
+ieee80211_ra_probe_valid(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       return rn->valid_probes & (1UL << ni->ni_txmcs);
+}
+
+void
+ieee80211_ra_probe_clear(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       struct ieee80211_ra_goodput_stats *g = &rn->g[ni->ni_txmcs];
+
+       g->nprobe_pkts = 0;
+       g->nprobe_fail = 0;
+}
+
+void
+ieee80211_ra_probe_done(struct ieee80211_ra_node *rn)
+{
+       rn->probing = IEEE80211_RA_NOT_PROBING;
+       rn->probed_rates = 0;
+       rn->valid_probes = 0;
+       rn->candidate_rates = 0;
+}
+
+int
+ieee80211_ra_intra_mode_ra_finished(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_ht_rateset *rs;
+       struct ieee80211_ra_goodput_stats *g = &rn->g[ni->ni_txmcs];
+       int next_mcs, best_mcs;
+       uint64_t next_rate;
+       int sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+
+       rn->probed_rates = (rn->probed_rates | (1 << ni->ni_txmcs));
+
+       /* Check if the min/max MCS in this rateset has been probed. */
+       rs = ieee80211_ra_get_ht_rateset(ni->ni_txmcs, sgi20);
+       if (rn->probing & IEEE80211_RA_PROBING_DOWN) {
+               if (ni->ni_txmcs == rs->min_mcs ||
+                   rn->probed_rates & (1 << rs->min_mcs)) {
+                       ieee80211_ra_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+       } else if (rn->probing & IEEE80211_RA_PROBING_UP) {
+               if (ni->ni_txmcs == rs->max_mcs ||
+                   rn->probed_rates & (1 << rs->max_mcs)) {
+                       ieee80211_ra_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+       }
+
+       /*
+        * Check if the measured goodput is loss-free and better than the
+        * loss-free goodput of the candidate rate.
+        */
+       next_mcs = ieee80211_ra_next_mcs(rn, ni);
+       if (next_mcs == ni->ni_txmcs) {
+               ieee80211_ra_trigger_next_rateset(rn, ni);
+               return 1;
+       }
+       next_rate = ieee80211_ra_get_txrate(next_mcs, sgi20);
+       if (g->loss == 0 &&
+           g->measured >= next_rate + IEEE80211_RA_RATE_THRESHOLD) {
+               ieee80211_ra_trigger_next_rateset(rn, ni);
+               return 1;
+       }
+
+       /* Check if we had a better measurement at a previously probed MCS. */
+       best_mcs = ieee80211_ra_best_mcs_in_rateset(rn, rs);
+       if (best_mcs != ni->ni_txmcs && (rn->probed_rates & (1 << best_mcs))) {
+               if ((rn->probing & IEEE80211_RA_PROBING_UP) &&
+                   best_mcs < ni->ni_txmcs) {
+                       ieee80211_ra_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+               if ((rn->probing & IEEE80211_RA_PROBING_DOWN) &&
+                   best_mcs > ni->ni_txmcs) {
+                       ieee80211_ra_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+       }
+
+       /* Check if all rates in the set of candidate rates have been probed. */
+       if ((rn->candidate_rates & rn->probed_rates) == rn->candidate_rates) {
+               /* Remain in the current rateset until above checks trigger. */
+               rn->probing &= ~IEEE80211_RA_PROBING_INTER;
+               return 1;
+       }
+
+       return 0;
+}
+
+void
+ieee80211_ra_trigger_next_rateset(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_ht_rateset *rsnext;
+
+       rsnext = ieee80211_ra_next_rateset(rn, ni);
+       if (rsnext) {
+               ieee80211_ra_probe_next_rateset(rn, ni, rsnext);
+               rn->probing |= IEEE80211_RA_PROBING_INTER;
+       } else
+               rn->probing &= ~IEEE80211_RA_PROBING_INTER;
+}
+
+int
+ieee80211_ra_inter_mode_ra_finished(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       return ((rn->probing & IEEE80211_RA_PROBING_INTER) == 0);
+}
+
+int
+ieee80211_ra_best_rate(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       int i, best = rn->best_mcs;
+       uint64_t gmax = rn->g[rn->best_mcs].measured;
+
+       for (i = 0; i < nitems(rn->g); i++) {
+               struct ieee80211_ra_goodput_stats *g = &rn->g[i];
+               if (((1 << i) & rn->valid_rates) == 0)
+                       continue;
+               if (g->measured > gmax + IEEE80211_RA_RATE_THRESHOLD) {
+                       gmax = g->measured;
+                       best = i;
+               }
+       }
+
+#ifdef RA_DEBUG
+       if (rn->best_mcs != best) {
+               DPRINTF(("MCS %d is best; MCS{cur|avg|loss}:", best));
+               for (i = 0; i < IEEE80211_HT_RATESET_NUM_MCS; i++) {
+                       struct ieee80211_ra_goodput_stats *g = &rn->g[i];
+                       if ((rn->valid_rates & (1 << i)) == 0)
+                               continue;
+                       DPRINTF((" %d{%s|", i, ra_fp_sprintf(g->measured)));
+                       DPRINTF(("%s|", ra_fp_sprintf(g->average)));
+                       DPRINTF(("%s%%}", ra_fp_sprintf(g->loss)));
+               }
+               DPRINTF(("\n"));
+       }
+#endif
+       return best;
+}
+
+void
+ieee80211_ra_probe_next_rate(struct ieee80211_ra_node *rn,
+    struct ieee80211_node *ni)
+{
+       /* Select the next rate to probe. */
+       rn->probed_rates |= (1 << ni->ni_txmcs);
+       ni->ni_txmcs = ieee80211_ra_next_mcs(rn, ni);
+}
+
+int
+ieee80211_ra_valid_tx_mcs(struct ieee80211com *ic, int mcs)
+{
+       uint32_t ntxstreams = 1;
+       static const int max_mcs[] = { 7, 15, 23, 31 };
+
+       if ((ic->ic_tx_mcs_set & IEEE80211_TX_RX_MCS_NOT_EQUAL) == 0)
+               return isset(ic->ic_sup_mcs, mcs);
+
+       ntxstreams += ((ic->ic_tx_mcs_set & IEEE80211_TX_SPATIAL_STREAMS) >> 2);
+       if (ntxstreams < 1 || ntxstreams > 4)
+               panic("invalid number of Tx streams: %u", ntxstreams);
+       return (mcs <= max_mcs[ntxstreams - 1] && isset(ic->ic_sup_mcs, mcs));
+}
+
+uint32_t
+ieee80211_ra_valid_rates(struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+       uint32_t valid_mcs = 0;
+       int i;
+
+       for (i = 0; i < IEEE80211_HT_RATESET_NUM_MCS; i++) {
+               if (!isset(ni->ni_rxmcs, i))
+                       continue;
+               if (!ieee80211_ra_valid_tx_mcs(ic, i))
+                       continue;
+               valid_mcs |= (1 << i);
+       }
+
+       return valid_mcs;
+}
+
+void
+ieee80211_ra_add_stats_ht(struct ieee80211_ra_node *rn,
+    struct ieee80211com *ic, struct ieee80211_node *ni,
+    int mcs, uint32_t total, uint32_t fail)
+{
+       static const uint64_t alpha = RA_FP_1 / 8; /* 1/8 = 0.125 */
+       static const uint64_t beta =  RA_FP_1 / 4; /* 1/4 = 0.25 */
+       int s, sgi20;
+       struct ieee80211_ra_goodput_stats *g = &rn->g[mcs];
+       uint64_t sfer, rate, delta;
+
+       /*
+        * Ignore invalid values. These values may come from hardware
+        * so asserting valid values via panic is not appropriate.
+        */
+       if (mcs < 0 || mcs >= IEEE80211_HT_RATESET_NUM_MCS)
+               return;
+       if (total == 0)
+               return;
+
+       s = splnet();
+
+       g->nprobe_pkts += total;
+       g->nprobe_fail += fail;
+
+       if (g->nprobe_pkts < IEEE80211_RA_MIN_PROBE_FRAMES &&
+            g->nprobe_fail < IEEE80211_RA_MAX_PROBE_RETRIES) {
+               splx(s);
+               return;
+       }
+
+       if (g->nprobe_fail > g->nprobe_pkts) {
+               DPRINTF(("%s fail %u > pkts %u\n",
+                   ether_sprintf(ni->ni_macaddr),
+                   g->nprobe_fail, g->nprobe_pkts));
+               g->nprobe_fail = g->nprobe_pkts;
+       }
+
+       sfer = g->nprobe_fail << RA_FP_SHIFT;
+       sfer /= g->nprobe_pkts;
+       rn->valid_probes |= 1U << mcs;
+       g->nprobe_fail = 0;
+       g->nprobe_pkts = 0;
+
+       sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+       rate = ieee80211_ra_get_txrate(mcs, sgi20);
+
+       g->loss = sfer * 100;
+       g->measured = RA_FP_MUL(RA_FP_1 - sfer, rate);
+       g->average = RA_FP_MUL(RA_FP_1 - alpha, g->average);
+       g->average += RA_FP_MUL(alpha, g->measured);
+
+       g->stddeviation = RA_FP_MUL(RA_FP_1 - beta, g->stddeviation);
+       if (g->average > g->measured)
+               delta = g->average - g->measured;
+       else
+               delta = g->measured - g->average;
+       g->stddeviation += RA_FP_MUL(beta, delta);
+
+       splx(s);
+}
+
+void
+ieee80211_ra_choose(struct ieee80211_ra_node *rn, struct ieee80211com *ic,
+    struct ieee80211_node *ni)
+{
+       struct ieee80211_ra_goodput_stats *g = &rn->g[ni->ni_txmcs];
+       int s;
+       int sgi20 = (ni->ni_flags & IEEE80211_NODE_HT_SGI20) ? 1 : 0;
+       const struct ieee80211_ht_rateset *rs, *rsnext;
+
+       s = splnet();
+
+       if (rn->valid_rates == 0)
+               rn->valid_rates = ieee80211_ra_valid_rates(ic, ni);
+
+       if (rn->probing) {
+               /* Probe another rate or settle at the best rate. */
+               if (!ieee80211_ra_probe_valid(rn, ni)) {
+                       splx(s);
+                       return;
+               }
+               ieee80211_ra_probe_clear(rn, ni);
+               if (!ieee80211_ra_intra_mode_ra_finished(rn, ni)) {
+                       ieee80211_ra_probe_next_rate(rn, ni);
+                       DPRINTFN(3, ("probing MCS %d\n", ni->ni_txmcs));
+               } else if (ieee80211_ra_inter_mode_ra_finished(rn, ni)) {
+                       rn->best_mcs = ieee80211_ra_best_rate(rn, ni);
+                       ni->ni_txmcs = rn->best_mcs;
+                       ieee80211_ra_probe_done(rn);
+               }
+
+               splx(s);
+               return;
+       } else {
+               rn->valid_probes = 0;
+       }
+
+       rs = ieee80211_ra_get_ht_rateset(ni->ni_txmcs, sgi20);
+       if ((g->measured >> RA_FP_SHIFT) == 0LL ||
+           (g->average >= 3 * g->stddeviation &&
+           g->measured < g->average - 3 * g->stddeviation)) {
+               /* Channel becomes bad. Probe downwards. */
+               rn->probing = IEEE80211_RA_PROBING_DOWN;
+               rn->probed_rates = 0;
+               if (ni->ni_txmcs == rs->min_mcs) {
+                       rsnext = ieee80211_ra_next_rateset(rn, ni);
+                       if (rsnext) {
+                               ieee80211_ra_probe_next_rateset(rn, ni,
+                                   rsnext);
+                       } else {
+                               /* Cannot probe further down. */
+                               rn->probing = IEEE80211_RA_NOT_PROBING;
+                       }
+               } else {
+                       ni->ni_txmcs = ieee80211_ra_next_mcs(rn, ni);
+                       rn->candidate_rates = (1 << ni->ni_txmcs);
+               }
+       } else if (g->loss < 2 * RA_FP_1 ||
+           g->measured > g->average + 3 * g->stddeviation) {
+               /* Channel becomes good. */
+               rn->probing = IEEE80211_RA_PROBING_UP;
+               rn->probed_rates = 0;
+               if (ni->ni_txmcs == rs->max_mcs) {
+                       rsnext = ieee80211_ra_next_rateset(rn, ni);
+                       if (rsnext) {
+                               ieee80211_ra_probe_next_rateset(rn, ni,
+                                   rsnext);
+                       } else {
+                               /* Cannot probe further up. */
+                               rn->probing = IEEE80211_RA_NOT_PROBING;
+                       }
+               } else {
+                       ni->ni_txmcs = ieee80211_ra_next_mcs(rn, ni);
+                       rn->candidate_rates = (1 << ni->ni_txmcs);
+               }
+       } else {
+               /* Remain at current rate. */
+               rn->probing = IEEE80211_RA_NOT_PROBING;
+               rn->probed_rates = 0;
+               rn->candidate_rates = 0;
+       }
+
+       splx(s);
+
+       if (rn->probing) {
+               if (rn->probing & IEEE80211_RA_PROBING_UP)
+                       DPRINTFN(2, ("channel becomes good; probe up\n"));
+               else
+                       DPRINTFN(2, ("channel becomes bad; probe down\n"));
+
+               DPRINTFN(3, ("measured: %s Mbit/s\n",
+                   ra_fp_sprintf(g->measured)));
+               DPRINTFN(3, ("average: %s Mbit/s\n",
+                   ra_fp_sprintf(g->average)));
+               DPRINTFN(3, ("stddeviation: %s\n",
+                   ra_fp_sprintf(g->stddeviation)));
+               DPRINTFN(3, ("loss: %s%%\n", ra_fp_sprintf(g->loss)));
+       }
+}
+
+void
+ieee80211_ra_node_init(struct ieee80211_ra_node *rn)
+{
+       memset(rn, 0, sizeof(*rn));
+}
blob - /dev/null
blob + 9ad9f2900e01eeaff6e5f1c9333cca47b879c561 (mode 644)
--- /dev/null
+++ sys/net80211/ieee80211_ra.h
@@ -0,0 +1,79 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Christian Ehrhardt <ehrha...@genua.de>
+ * Copyright (c) 2021 Stefan Sperling <s...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* 
+ * Goodput statistics struct. Measures the effective data rate of an MCS.
+ * All uint64_t numbers in this struct use fixed-point arithmetic.
+ */
+struct ieee80211_ra_goodput_stats {
+       uint64_t measured;      /* Most recently measured goodput. */
+       uint64_t average;       /* Average measured goodput. */
+       uint64_t stddeviation;  /* Goodput standard deviation. */
+       uint64_t loss;          /* This rate's loss percentage SFER. */
+       uint32_t nprobe_pkts;   /* Number of packets in current probe. */
+       uint32_t nprobe_fail;   /* Number of failed packets. */
+};
+
+/*
+ * Rate adaptation state.
+ *
+ * Drivers should not modify any fields of this structure directly.
+ * Use ieee80211_ra_init() and ieee80211_ra_add_stats() only.
+ */
+struct ieee80211_ra_node {
+       /* Bitmaps MCS 0-31. */
+       uint32_t valid_probes;
+       uint32_t valid_rates;
+       uint32_t candidate_rates;
+       uint32_t probed_rates;
+
+       /* Probing state. */
+       int probing;
+#define IEEE80211_RA_NOT_PROBING       0x0
+#define IEEE80211_RA_PROBING_DOWN      0x1
+#define IEEE80211_RA_PROBING_UP                0x2
+#define IEEE80211_RA_PROBING_INTER     0x4 /* combined with UP or DOWN */
+
+       /* The current best MCS found by probing. */
+       int best_mcs;
+
+       /* Goodput statistics for each MCS. */
+       struct ieee80211_ra_goodput_stats g[IEEE80211_HT_RATESET_NUM_MCS];
+};
+
+/* Initialize rate adaptation state. */
+void   ieee80211_ra_node_init(struct ieee80211_ra_node *);
+
+/*
+ * Drivers report information about 802.11n/HT Tx attempts here.
+ * mcs: The HT MCS used during this Tx attempt.
+ * total: How many Tx attempts (initial attempt + any retries) were made?
+ * fail: How many of these Tx attempts failed?
+ */
+void   ieee80211_ra_add_stats_ht(struct ieee80211_ra_node *,
+           struct ieee80211com *, struct ieee80211_node *,
+           int mcs, unsigned int total, unsigned int fail);
+
+/* Drivers call this function to update ni->ni_txmcs. */
+void   ieee80211_ra_choose(struct ieee80211_ra_node *,
+           struct ieee80211com *, struct ieee80211_node *);
+
+/* Get the HT rateset for a particular HT MCS with SGI20 on/off. */
+const struct ieee80211_ht_rateset * ieee80211_ra_get_ht_rateset(int mcs,
+           int sgi20);

Reply via email to