This patch adds initial 11ac support to the iwm(4) driver.
It allows use of 80 MHz channels and VHT MCS.

In net80211 I added a new rate control module to support VHT rates, as
a new file called ieee80211_ra_vht.c, derived from ieee80211_ra.c which
we use in 11n mode. The task of this code is to determine the fastest
VHT rate that is usable. (This was not needed to add 11ac support to
iwx(4) because iwx(4) devices perform this task in firmware.)
Retries at lower rates must be handled by the driver. iwm(4) does this via
the "LQ" command which sends a table of Tx rates the firmware should use.

So far, I have tested this successfully on iwm(4) 8265.
In my testing performance peaks at around 250Mbit/s.
There is no device-specific code involved so I would expect this to
work on all cards supported by the driver.

I will test a few more devices I have here soon, but I don't have
access to all iwm(4) hardware variants at the moment. Testing on any
iwm(4) device would be appreciated. Please be precise about which model
of iwm(4) you are testing on when reporting results.

diff refs/heads/master refs/heads/11ac
blob - 7df604d4b6e3913ffeef8c16e7e47637c84af881
blob + 8ed3356b0144a014bece57fa7dcf734624be9fbb
--- sys/conf/files
+++ sys/conf/files
@@ -847,6 +847,7 @@ 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_ra_vht.c       wlan
 file net80211/ieee80211_rssadapt.c     wlan
 file net80211/ieee80211_regdomain.c    wlan
 file netinet/if_ether.c                        ether
blob - 7e0e0f4841d3ba61fcf8335bc21c0df2fb416801
blob + ddb2d586ed0ed8c43da2c38d982d47685db940b3
--- sys/dev/pci/if_iwm.c
+++ sys/dev/pci/if_iwm.c
@@ -143,6 +143,7 @@
 #include <net80211/ieee80211_var.h>
 #include <net80211/ieee80211_amrr.h>
 #include <net80211/ieee80211_ra.h>
+#include <net80211/ieee80211_ra_vht.h>
 #include <net80211/ieee80211_radiotap.h>
 #include <net80211/ieee80211_priv.h> /* for SEQ_LT */
 #undef DPRINTF /* defined in ieee80211_priv.h */
@@ -222,7 +223,7 @@ const struct iwm_rate {
 #define IWM_RVAL_IS_OFDM(_i_) ((_i_) >= 12 && (_i_) != 22)
 
 /* Convert an MCS index into an iwm_rates[] index. */
-const int iwm_mcs2ridx[] = {
+const int iwm_ht_mcs2ridx[] = {
        IWM_RATE_MCS_0_INDEX,
        IWM_RATE_MCS_1_INDEX,
        IWM_RATE_MCS_2_INDEX,
@@ -247,7 +248,7 @@ struct iwm_nvm_section {
 };
 
 int    iwm_is_mimo_ht_plcp(uint8_t);
-int    iwm_is_mimo_mcs(int);
+int    iwm_is_mimo_ht_mcs(int);
 int    iwm_store_cscheme(struct iwm_softc *, uint8_t *, size_t);
 int    iwm_firmware_store_section(struct iwm_softc *, enum iwm_ucode_type,
            uint8_t *, size_t);
@@ -334,6 +335,7 @@ void        iwm_init_channel_map(struct iwm_softc *, const 
ui
            const uint8_t *nvm_channels, int nchan);
 int    iwm_mimo_enabled(struct iwm_softc *);
 void   iwm_setup_ht_rates(struct iwm_softc *);
+void   iwm_setup_vht_rates(struct iwm_softc *);
 void   iwm_mac_ctxt_task(void *);
 void   iwm_phy_ctxt_task(void *);
 void   iwm_updateprot(struct ieee80211com *);
@@ -396,6 +398,8 @@ void        iwm_rx_frame(struct iwm_softc *, struct mbuf *, 
i
            uint32_t, struct ieee80211_rxinfo *, struct mbuf_list *);
 void   iwm_ht_single_rate_control(struct iwm_softc *, struct ieee80211_node *,
            int, uint8_t, int);
+void   iwm_vht_single_rate_control(struct iwm_softc *, struct ieee80211_node *,
+           int, int, uint8_t, int);
 void   iwm_rx_tx_cmd_single(struct iwm_softc *, struct iwm_rx_packet *,
            struct iwm_node *, int, int);
 void   iwm_txd_done(struct iwm_softc *, struct iwm_tx_data *);
@@ -409,14 +413,15 @@ void      iwm_rx_compressed_ba(struct iwm_softc *, struct 
i
 void   iwm_rx_bmiss(struct iwm_softc *, struct iwm_rx_packet *,
            struct iwm_rx_data *);
 int    iwm_binding_cmd(struct iwm_softc *, struct iwm_node *, uint32_t);
+uint8_t        iwm_get_vht_ctrl_pos(struct ieee80211com *, struct 
ieee80211_channel *);
 int    iwm_phy_ctxt_cmd_uhb(struct iwm_softc *, struct iwm_phy_ctxt *, uint8_t,
-           uint8_t, uint32_t, uint32_t, uint8_t);
+           uint8_t, uint32_t, uint32_t, uint8_t, uint8_t);
 void   iwm_phy_ctxt_cmd_hdr(struct iwm_softc *, struct iwm_phy_ctxt *,
            struct iwm_phy_context_cmd *, uint32_t, uint32_t);
 void   iwm_phy_ctxt_cmd_data(struct iwm_softc *, struct iwm_phy_context_cmd *,
-           struct ieee80211_channel *, uint8_t, uint8_t, uint8_t);
+           struct ieee80211_channel *, uint8_t, uint8_t, uint8_t, uint8_t);
 int    iwm_phy_ctxt_cmd(struct iwm_softc *, struct iwm_phy_ctxt *, uint8_t,
-           uint8_t, uint32_t, uint32_t, uint8_t);
+           uint8_t, uint32_t, uint32_t, uint8_t, uint8_t);
 int    iwm_send_cmd(struct iwm_softc *, struct iwm_host_cmd *);
 int    iwm_send_cmd_pdu(struct iwm_softc *, uint32_t, uint32_t, uint16_t,
            const void *);
@@ -428,7 +433,7 @@ void        iwm_free_resp(struct iwm_softc *, struct 
iwm_host
 void   iwm_cmd_done(struct iwm_softc *, int, int, int);
 void   iwm_update_sched(struct iwm_softc *, int, int, uint8_t, uint16_t);
 void   iwm_reset_sched(struct iwm_softc *, int, int, uint8_t);
-const struct iwm_rate *iwm_tx_fill_cmd(struct iwm_softc *, struct iwm_node *,
+uint8_t        iwm_tx_fill_cmd(struct iwm_softc *, struct iwm_node *,
            struct ieee80211_frame *, struct iwm_tx_cmd *);
 int    iwm_tx(struct iwm_softc *, struct mbuf *, struct ieee80211_node *, int);
 int    iwm_flush_tx_path(struct iwm_softc *, int);
@@ -484,7 +489,8 @@ int iwm_umac_scan_abort(struct iwm_softc *);
 int    iwm_lmac_scan_abort(struct iwm_softc *);
 int    iwm_scan_abort(struct iwm_softc *);
 int    iwm_phy_ctxt_update(struct iwm_softc *, struct iwm_phy_ctxt *,
-           struct ieee80211_channel *, uint8_t, uint8_t, uint32_t, uint8_t);
+           struct ieee80211_channel *, uint8_t, uint8_t, uint32_t, uint8_t,
+           uint8_t);
 int    iwm_auth(struct iwm_softc *);
 int    iwm_deauth(struct iwm_softc *);
 int    iwm_run(struct iwm_softc *);
@@ -499,6 +505,8 @@ void        iwm_delete_key_v1(struct ieee80211com *,
 void   iwm_delete_key(struct ieee80211com *,
            struct ieee80211_node *, struct ieee80211_key *);
 void   iwm_calib_timeout(void *);
+void   iwm_set_rate_table_vht(struct iwm_node *, struct iwm_lq_cmd *);
+void   iwm_set_rate_table(struct iwm_node *, struct iwm_lq_cmd *);
 void   iwm_setrates(struct iwm_node *, int);
 int    iwm_media_change(struct ifnet *);
 void   iwm_newstate_task(void *);
@@ -583,9 +591,9 @@ iwm_is_mimo_ht_plcp(uint8_t ht_plcp)
 }
 
 int
-iwm_is_mimo_mcs(int mcs)
+iwm_is_mimo_ht_mcs(int mcs)
 {
-       int ridx = iwm_mcs2ridx[mcs];
+       int ridx = iwm_ht_mcs2ridx[mcs];
        return iwm_is_mimo_ht_plcp(iwm_rates[ridx].ht_plcp);
        
 }
@@ -3078,6 +3086,12 @@ iwm_init_channel_map(struct iwm_softc *sc, const uint1
                        if (ch_flags & IWM_NVM_CHANNEL_40MHZ)
                                channel->ic_flags |= IEEE80211_CHAN_40MHZ;
                }
+
+               if (is_5ghz && data->sku_cap_11ac_enable) {
+                       channel->ic_flags |= IEEE80211_CHAN_VHT;
+                       if (ch_flags & IWM_NVM_CHANNEL_80MHZ)
+                               channel->ic_xflags |= IEEE80211_CHANX_80MHZ;
+               }
        }
 }
 
@@ -3112,6 +3126,34 @@ iwm_setup_ht_rates(struct iwm_softc *sc)
 }
 
 void
+iwm_setup_vht_rates(struct iwm_softc *sc)
+{
+       struct ieee80211com *ic = &sc->sc_ic;
+       uint8_t rx_ant = iwm_fw_valid_rx_ant(sc);
+       int n;
+
+       ic->ic_vht_rxmcs = (IEEE80211_VHT_MCS_0_9 <<
+           IEEE80211_VHT_MCS_FOR_SS_SHIFT(1));
+
+       if (iwm_mimo_enabled(sc) &&
+           ((rx_ant & IWM_ANT_AB) == IWM_ANT_AB ||
+           (rx_ant & IWM_ANT_BC) == IWM_ANT_BC)) {
+               ic->ic_vht_rxmcs |= (IEEE80211_VHT_MCS_0_9 <<
+                   IEEE80211_VHT_MCS_FOR_SS_SHIFT(2));
+       } else {
+               ic->ic_vht_rxmcs |= (IEEE80211_VHT_MCS_SS_NOT_SUPP <<
+                   IEEE80211_VHT_MCS_FOR_SS_SHIFT(2));
+       }
+
+       for (n = 3; n <= IEEE80211_VHT_NUM_SS; n++) {
+               ic->ic_vht_rxmcs |= (IEEE80211_VHT_MCS_SS_NOT_SUPP <<
+                   IEEE80211_VHT_MCS_FOR_SS_SHIFT(n));
+       }
+
+       ic->ic_vht_txmcs = ic->ic_vht_rxmcs;
+}
+
+void
 iwm_init_reorder_buffer(struct iwm_reorder_buffer *reorder_buf,
     uint16_t ssn, uint16_t buf_size)
 {
@@ -3419,7 +3461,7 @@ iwm_phy_ctxt_task(void *arg)
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_node *in = (void *)ic->ic_bss;
        struct ieee80211_node *ni = &in->in_ni;
-       uint8_t chains, sco;
+       uint8_t chains, sco, vht_chan_width;
        int err, s = splnet();
 
        if ((sc->sc_flags & IWM_FLAG_SHUTDOWN) ||
@@ -3431,13 +3473,23 @@ iwm_phy_ctxt_task(void *arg)
        }
 
        chains = iwm_mimo_enabled(sc) ? 2 : 1;
-       if (ieee80211_node_supports_ht_chan40(ni))
+       if ((ni->ni_flags & IEEE80211_NODE_HT) &&
+           IEEE80211_CHAN_40MHZ_ALLOWED(ni->ni_chan) &&
+           ieee80211_node_supports_ht_chan40(ni))
                sco = (ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK);
        else
                sco = IEEE80211_HTOP0_SCO_SCN;
-       if (in->in_phyctxt->sco != sco) {
+       if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
+           IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
+           ieee80211_node_supports_vht_chan80(ni))
+               vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
+       else
+               vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
+       if (in->in_phyctxt->sco != sco ||
+           in->in_phyctxt->vht_chan_width != vht_chan_width) {
                err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
-                   in->in_phyctxt->channel, chains, chains, 0, sco);
+                   in->in_phyctxt->channel, chains, chains, 0, sco,
+                   vht_chan_width);
                if (err)
                        printf("%s: failed to update PHY\n", DEVNAME(sc));
                iwm_setrates(in, 0);
@@ -3834,6 +3886,7 @@ iwm_parse_nvm_data(struct iwm_softc *sc, const uint16_
        data->sku_cap_band_24GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_24GHZ;
        data->sku_cap_band_52GHz_enable = sku & IWM_NVM_SKU_CAP_BAND_52GHZ;
        data->sku_cap_11n_enable = sku & IWM_NVM_SKU_CAP_11N_ENABLE;
+       data->sku_cap_11ac_enable = sku & IWM_NVM_SKU_CAP_11AC_ENABLE;
        data->sku_cap_mimo_disable = sku & IWM_NVM_SKU_CAP_MIMO_DISABLE;
 
        if (sc->sc_device_family >= IWM_DEVICE_FAMILY_8000) {
@@ -4759,8 +4812,13 @@ iwm_rx_frame(struct iwm_softc *sc, struct mbuf *m, int
                tap->wr_chan_freq =
                    htole16(ic->ic_channels[chanidx].ic_freq);
                chan_flags = ic->ic_channels[chanidx].ic_flags;
-               if (ic->ic_curmode != IEEE80211_MODE_11N)
+               if (ic->ic_curmode != IEEE80211_MODE_11N &&
+                   ic->ic_curmode != IEEE80211_MODE_11AC) {
                        chan_flags &= ~IEEE80211_CHAN_HT;
+                       chan_flags &= ~IEEE80211_CHAN_40MHZ;
+               }
+               if (ic->ic_curmode != IEEE80211_MODE_11AC)
+                       chan_flags &= ~IEEE80211_CHAN_VHT;
                tap->wr_chan_flags = htole16(chan_flags);
                tap->wr_dbm_antsignal = (int8_t)rxi->rxi_rssi;
                tap->wr_dbm_antnoise = (int8_t)sc->sc_noise;
@@ -5424,14 +5482,18 @@ iwm_ra_choose(struct iwm_softc *sc, struct ieee80211_n
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_node *in = (void *)ni;
        int old_txmcs = ni->ni_txmcs;
+       int old_nss = ni->ni_vht_ss;
 
-       ieee80211_ra_choose(&in->in_rn, ic, ni);
+       if (ni->ni_flags & IEEE80211_NODE_VHT)
+               ieee80211_ra_vht_choose(&in->in_rn_vht, ic, ni);
+       else
+               ieee80211_ra_choose(&in->in_rn, ic, ni);
 
        /* 
         * If RA has chosen a new TX rate we must update
         * the firmware's LQ rate table.
         */
-       if (ni->ni_txmcs != old_txmcs)
+       if (ni->ni_txmcs != old_txmcs || ni->ni_vht_ss != old_nss)
                iwm_setrates(in, 1);
 }
 
@@ -5481,6 +5543,47 @@ iwm_ht_single_rate_control(struct iwm_softc *sc, struc
 }
 
 void
+iwm_vht_single_rate_control(struct iwm_softc *sc, struct ieee80211_node *ni,
+    int txmcs, int nss, uint8_t failure_frame, int txfail)
+{
+       struct ieee80211com *ic = &sc->sc_ic;
+       struct iwm_node *in = (void *)ni;
+
+       /* Ignore Tx reports which don't match our last LQ command. */
+       if (txmcs != ni->ni_txmcs && nss != ni->ni_vht_ss) {
+               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 = txmcs;
+               unsigned int retries = 0, i;
+
+               in->lq_rate_mismatch = 0;
+
+               for (i = 0; i < failure_frame; i++) {
+                       if (mcs > 0) {
+                               ieee80211_ra_vht_add_stats(&in->in_rn_vht,
+                                   ic, ni, mcs, nss, 1, 1);
+                               mcs--;
+                       } else
+                               retries++;
+               }
+
+               if (txfail && failure_frame == 0) {
+                       ieee80211_ra_vht_add_stats(&in->in_rn_vht, ic, ni,
+                           txmcs, nss, 1, 1);
+               } else {
+                       ieee80211_ra_vht_add_stats(&in->in_rn_vht, ic, ni,
+                           mcs, nss, retries + 1, retries);
+               }
+
+               iwm_ra_choose(sc, ni);
+       }
+}
+
+void
 iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_rx_packet *pkt,
     struct iwm_node *in, int txmcs, int txrate)
 {
@@ -5489,6 +5592,7 @@ iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_
        struct ifnet *ifp = IC2IFP(ic);
        struct iwm_tx_resp *tx_resp = (void *)pkt->data;
        int status = le16toh(tx_resp->status.status) & IWM_TX_STATUS_MSK;
+       uint32_t initial_rate = le32toh(tx_resp->initial_rate);
        int txfail;
        
        KASSERT(tx_resp->frame_count == 1);
@@ -5520,9 +5624,17 @@ iwm_rx_tx_cmd_single(struct iwm_softc *sc, struct iwm_
                        if (tx_resp->failure_frame > 0)
                                in->in_amn.amn_retrycnt++;
                }
+       } else if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
+           ic->ic_fixed_mcs == -1 && ic->ic_state == IEEE80211_S_RUN &&
+           (initial_rate & IWM_RATE_MCS_VHT_MSK)) {
+               int txmcs = initial_rate & IWM_RATE_VHT_MCS_RATE_CODE_MSK;
+               int nss = ((initial_rate & IWM_RATE_VHT_MCS_NSS_MSK) >>
+                   IWM_RATE_VHT_MCS_NSS_POS) + 1;
+               iwm_vht_single_rate_control(sc, ni, txmcs, nss,
+                   tx_resp->failure_frame, txfail);
        } else if (ic->ic_fixed_mcs == -1 && ic->ic_state == IEEE80211_S_RUN &&
-           (le32toh(tx_resp->initial_rate) & IWM_RATE_MCS_HT_MSK)) {
-               int txmcs = le32toh(tx_resp->initial_rate) &
+           (initial_rate & IWM_RATE_MCS_HT_MSK)) {
+               int txmcs = initial_rate &
                    (IWM_RATE_HT_MCS_RATE_CODE_MSK | IWM_RATE_HT_MCS_NSS_MSK);
                iwm_ht_single_rate_control(sc, ni, txmcs,
                    tx_resp->failure_frame, txfail);
@@ -5548,6 +5660,7 @@ iwm_txd_done(struct iwm_softc *sc, struct iwm_tx_data 
        txd->in = NULL;
        txd->ampdu_nframes = 0;
        txd->ampdu_txmcs = 0;
+       txd->ampdu_txnss = 0;
 }
 
 void
@@ -5612,10 +5725,20 @@ iwm_ampdu_tx_done(struct iwm_softc *sc, struct iwm_cmd
                                continue;
 
                        /* The Tx rate was the same for all subframes. */
-                       txdata->ampdu_txmcs = initial_rate &
-                          (IWM_RATE_HT_MCS_RATE_CODE_MSK |
-                          IWM_RATE_HT_MCS_NSS_MSK);
-                       txdata->ampdu_nframes = nframes;
+                       if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
+                           (initial_rate & IWM_RATE_MCS_VHT_MSK)) {
+                               txdata->ampdu_txmcs = initial_rate &
+                                   IWM_RATE_VHT_MCS_RATE_CODE_MSK;
+                               txdata->ampdu_txnss = ((initial_rate &
+                                   IWM_RATE_VHT_MCS_NSS_MSK) >>
+                                   IWM_RATE_VHT_MCS_NSS_POS) + 1;
+                               txdata->ampdu_nframes = nframes;
+                       } else if (initial_rate & IWM_RATE_MCS_HT_MSK) {
+                               txdata->ampdu_txmcs = initial_rate &
+                                   (IWM_RATE_HT_MCS_RATE_CODE_MSK |
+                                   IWM_RATE_HT_MCS_NSS_MSK);
+                               txdata->ampdu_nframes = nframes;
+                       }
                }
                return;
        }
@@ -5642,13 +5765,28 @@ iwm_ampdu_tx_done(struct iwm_softc *sc, struct iwm_cmd
                         * The firmware might have made several such
                         * attempts but we don't keep track of this.
                         */
-                       ieee80211_ra_add_stats_ht(&in->in_rn, ic, ni,
-                           txdata->ampdu_txmcs, 1, 1);
+                       if (ni->ni_flags & IEEE80211_NODE_VHT) {
+                               ieee80211_ra_vht_add_stats(&in->in_rn_vht,
+                                   ic, ni, txdata->ampdu_txmcs,
+                                   txdata->ampdu_txnss, 1, 1);
+                       } else {
+                               ieee80211_ra_add_stats_ht(&in->in_rn, ic, ni,
+                                   txdata->ampdu_txmcs, 1, 1);
+                       }
                }
 
                /* Report the final single-frame Tx attempt. */
-               if (initial_rate & IWM_RATE_HT_MCS_RATE_CODE_MSK) {
+               if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
+                   (initial_rate & IWM_RATE_MCS_VHT_MSK)) {
                        int txmcs = initial_rate &
+                           IWM_RATE_VHT_MCS_RATE_CODE_MSK;
+                       int nss = ((initial_rate &
+                           IWM_RATE_VHT_MCS_NSS_MSK) >>
+                           IWM_RATE_VHT_MCS_NSS_POS) + 1;
+                       iwm_vht_single_rate_control(sc, ni, txmcs, nss,
+                           failure_frame, txfail);
+               } else if (initial_rate & IWM_RATE_MCS_HT_MSK) {
+                       int txmcs = initial_rate &
                           (IWM_RATE_HT_MCS_RATE_CODE_MSK |
                           IWM_RATE_HT_MCS_NSS_MSK);
                        iwm_ht_single_rate_control(sc, ni, txmcs,
@@ -5763,9 +5901,14 @@ iwm_ampdu_rate_control(struct iwm_softc *sc, struct ie
                         * before failing an A-MPDU subframe the firmware
                         * sends it as a single frame at least once.
                         */
-                       ieee80211_ra_add_stats_ht(&in->in_rn, ic, ni,
-                           txdata->ampdu_txmcs, 1, 0);
-
+                       if (ni->ni_flags & IEEE80211_NODE_VHT) {
+                               ieee80211_ra_vht_add_stats(&in->in_rn_vht,
+                                   ic, ni, txdata->ampdu_txmcs,
+                                   txdata->ampdu_txnss, 1, 0);
+                       } else {
+                               ieee80211_ra_add_stats_ht(&in->in_rn, ic, ni,
+                                   txdata->ampdu_txmcs, 1, 0);
+                       }
                        /* Report this frame only once. */
                        txdata->ampdu_nframes = 0;
                }
@@ -5946,7 +6089,7 @@ iwm_phy_ctxt_cmd_hdr(struct iwm_softc *sc, struct iwm_
 void
 iwm_phy_ctxt_cmd_data(struct iwm_softc *sc, struct iwm_phy_context_cmd *cmd,
     struct ieee80211_channel *chan, uint8_t chains_static,
-    uint8_t chains_dynamic, uint8_t sco)
+    uint8_t chains_dynamic, uint8_t sco, uint8_t vht_chan_width)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        uint8_t active_cnt, idle_cnt;
@@ -5954,7 +6097,10 @@ iwm_phy_ctxt_cmd_data(struct iwm_softc *sc, struct iwm
        cmd->ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
            IWM_PHY_BAND_24 : IWM_PHY_BAND_5;
        cmd->ci.channel = ieee80211_chan2ieee(ic, chan);
-       if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
+       if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
+               cmd->ci.ctrl_pos = iwm_get_vht_ctrl_pos(ic, chan);
+               cmd->ci.width = IWM_PHY_VHT_CHANNEL_MODE80;
+       } else if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
                if (sco == IEEE80211_HTOP0_SCO_SCA) {
                        /* secondary chan above -> control chan below */
                        cmd->ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
@@ -5985,10 +6131,42 @@ iwm_phy_ctxt_cmd_data(struct iwm_softc *sc, struct iwm
        cmd->txchain_info = htole32(iwm_fw_valid_tx_ant(sc));
 }
 
+uint8_t
+iwm_get_vht_ctrl_pos(struct ieee80211com *ic, struct ieee80211_channel *chan)
+{
+       int center_idx = ic->ic_bss->ni_vht_chan_center_freq_idx0;
+       int primary_idx = ieee80211_chan2ieee(ic, ic->ic_bss->ni_chan);
+       /*
+        * The FW is expected to check the control channel position only
+        * when in HT/VHT and the channel width is not 20MHz. Return
+        * this value as the default one:
+        */
+       uint8_t pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+
+       switch (primary_idx - center_idx) {
+       case -6:
+               pos = IWM_PHY_VHT_CTRL_POS_2_BELOW;
+               break;
+       case -2:
+               pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
+               break;
+       case 2:
+               pos = IWM_PHY_VHT_CTRL_POS_1_ABOVE;
+               break;
+       case 6:
+               pos = IWM_PHY_VHT_CTRL_POS_2_ABOVE;
+               break;
+       default:
+               break;
+       }
+
+       return pos;
+}
+
 int
 iwm_phy_ctxt_cmd_uhb(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt,
     uint8_t chains_static, uint8_t chains_dynamic, uint32_t action,
-    uint32_t apply_time, uint8_t sco)
+    uint32_t apply_time, uint8_t sco, uint8_t vht_chan_width)
 {
        struct ieee80211com *ic = &sc->sc_ic;
        struct iwm_phy_context_cmd_uhb cmd;
@@ -6004,7 +6182,10 @@ iwm_phy_ctxt_cmd_uhb(struct iwm_softc *sc, struct iwm_
        cmd.ci.band = IEEE80211_IS_CHAN_2GHZ(chan) ?
            IWM_PHY_BAND_24 : IWM_PHY_BAND_5;
        cmd.ci.channel = htole32(ieee80211_chan2ieee(ic, chan));
-       if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
+       if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80) {
+               cmd.ci.ctrl_pos = iwm_get_vht_ctrl_pos(ic, chan);
+               cmd.ci.width = IWM_PHY_VHT_CHANNEL_MODE80;
+       } else if (chan->ic_flags & IEEE80211_CHAN_40MHZ) {
                if (sco == IEEE80211_HTOP0_SCO_SCA) {
                        /* secondary chan above -> control chan below */
                        cmd.ci.ctrl_pos = IWM_PHY_VHT_CTRL_POS_1_BELOW;
@@ -6037,7 +6218,7 @@ iwm_phy_ctxt_cmd_uhb(struct iwm_softc *sc, struct iwm_
 int
 iwm_phy_ctxt_cmd(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt,
     uint8_t chains_static, uint8_t chains_dynamic, uint32_t action,
-    uint32_t apply_time, uint8_t sco)
+    uint32_t apply_time, uint8_t sco, uint8_t vht_chan_width)
 {
        struct iwm_phy_context_cmd cmd;
 
@@ -6050,12 +6231,12 @@ iwm_phy_ctxt_cmd(struct iwm_softc *sc, struct iwm_phy_
         */
        if (isset(sc->sc_enabled_capa, IWM_UCODE_TLV_CAPA_ULTRA_HB_CHANNELS))
                return iwm_phy_ctxt_cmd_uhb(sc, ctxt, chains_static,
-                   chains_dynamic, action, apply_time, sco);
+                   chains_dynamic, action, apply_time, sco, vht_chan_width);
 
        iwm_phy_ctxt_cmd_hdr(sc, ctxt, &cmd, action, apply_time);
 
        iwm_phy_ctxt_cmd_data(sc, &cmd, ctxt->channel,
-           chains_static, chains_dynamic, sco);
+           chains_static, chains_dynamic, sco, vht_chan_width);
 
        return iwm_send_cmd_pdu(sc, IWM_PHY_CONTEXT_CMD, 0,
            sizeof(struct iwm_phy_context_cmd), &cmd);
@@ -6388,9 +6569,9 @@ iwm_reset_sched(struct iwm_softc *sc, int qid, int idx
 /*
  * Fill in various bit for management frames, and leave them
  * unfilled for data frames (firmware takes care of that).
- * Return the selected TX rate.
+ * Return the selected legacy TX rate, or zero if HT/VHT is used.
  */
-const struct iwm_rate *
+uint8_t
 iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node *in,
     struct ieee80211_frame *wh, struct iwm_tx_cmd *tx)
 {
@@ -6400,6 +6581,7 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
        int type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
        int min_ridx = iwm_rval2ridx(ieee80211_min_basic_rate(ic));
        int ridx, rate_flags;
+       uint8_t rate = 0;
 
        tx->rts_retry_limit = IWM_RTS_DFAULT_RETRY_LIMIT;
        tx->data_retry_limit = IWM_LOW_RETRY_LIMIT;
@@ -6410,7 +6592,10 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
                ridx = min_ridx;
                tx->data_retry_limit = IWM_MGMT_DFAULT_RETRY_LIMIT;
        } else if (ic->ic_fixed_mcs != -1) {
-               ridx = sc->sc_fixed_ridx;
+               if (ni->ni_flags & IEEE80211_NODE_VHT)
+                       ridx = IWM_FIRST_OFDM_RATE;
+               else
+                       ridx = sc->sc_fixed_ridx;
        } else if (ic->ic_fixed_rate != -1) {
                ridx = sc->sc_fixed_ridx;
        } else {
@@ -6418,10 +6603,8 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
                /* Use firmware rateset retry table. */
                tx->initial_rate_index = 0;
                tx->tx_flags |= htole32(IWM_TX_CMD_FLG_STA_RATE);
-               if (ni->ni_flags & IEEE80211_NODE_HT) {
-                       ridx = iwm_mcs2ridx[ni->ni_txmcs];
-                       return &iwm_rates[ridx];
-               }
+               if (ni->ni_flags & IEEE80211_NODE_HT) /* VHT implies HT */
+                       return 0;
                ridx = (IEEE80211_IS_CHAN_5GHZ(ni->ni_chan)) ?
                    IWM_RIDX_OFDM : IWM_RIDX_CCK;
                for (i = 0; i < ni->ni_rates.rs_nrates; i++) {
@@ -6431,11 +6614,12 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
                                break;
                        }
                }
-               return &iwm_rates[ridx];
+               return iwm_rates[ridx].rate & 0xff;
        }
 
        rinfo = &iwm_rates[ridx];
-       if (iwm_is_mimo_ht_plcp(rinfo->ht_plcp))
+       if ((ni->ni_flags & IEEE80211_NODE_VHT) == 0 &&
+           iwm_is_mimo_ht_plcp(rinfo->ht_plcp))
                rate_flags = IWM_RATE_MCS_ANT_AB_MSK;
        else if (sc->sc_device_family == IWM_DEVICE_FAMILY_9000)
                rate_flags = IWM_RATE_MCS_ANT_B_MSK;
@@ -6446,13 +6630,26 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
        if ((ni->ni_flags & IEEE80211_NODE_HT) &&
            type == IEEE80211_FC0_TYPE_DATA &&
            rinfo->ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP) {
-               uint8_t sco;
-               if (ieee80211_node_supports_ht_chan40(ni))
+               uint8_t sco = IEEE80211_HTOP0_SCO_SCN;
+               uint8_t vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
+               if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
+                   IEEE80211_CHAN_80MHZ_ALLOWED(ni->ni_chan) &&
+                   ieee80211_node_supports_vht_chan80(ni))
+                       vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
+               else if (IEEE80211_CHAN_40MHZ_ALLOWED(ni->ni_chan) &&
+                   ieee80211_node_supports_ht_chan40(ni))
                        sco = (ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK);
+               if (ni->ni_flags & IEEE80211_NODE_VHT)
+                       rate_flags |= IWM_RATE_MCS_VHT_MSK; 
                else
-                       sco = IEEE80211_HTOP0_SCO_SCN;
-               rate_flags |= IWM_RATE_MCS_HT_MSK; 
-               if ((sco == IEEE80211_HTOP0_SCO_SCA || 
+                       rate_flags |= IWM_RATE_MCS_HT_MSK; 
+               if (vht_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_80 &&
+                   in->in_phyctxt != NULL &&
+                   in->in_phyctxt->vht_chan_width == vht_chan_width) {
+                       rate_flags |= IWM_RATE_MCS_CHAN_WIDTH_80;
+                       if (ieee80211_node_supports_vht_sgi80(ni))
+                               rate_flags |= IWM_RATE_MCS_SGI_MSK;
+               } else if ((sco == IEEE80211_HTOP0_SCO_SCA || 
                    sco == IEEE80211_HTOP0_SCO_SCB) &&
                    in->in_phyctxt != NULL && in->in_phyctxt->sco == sco) {
                        rate_flags |= IWM_RATE_MCS_CHAN_WIDTH_40;
@@ -6460,11 +6657,20 @@ iwm_tx_fill_cmd(struct iwm_softc *sc, struct iwm_node 
                                rate_flags |= IWM_RATE_MCS_SGI_MSK;
                } else if (ieee80211_node_supports_ht_sgi20(ni))
                        rate_flags |= IWM_RATE_MCS_SGI_MSK;
-               tx->rate_n_flags = htole32(rate_flags | rinfo->ht_plcp);
+               if (ni->ni_flags & IEEE80211_NODE_VHT) {
+                       /*
+                        * ifmedia only provides an MCS index, no NSS.
+                        * Use a fixed SISO rate.
+                        */
+                       tx->rate_n_flags = htole32(rate_flags |
+                           (ic->ic_fixed_mcs &
+                           IWM_RATE_VHT_MCS_RATE_CODE_MSK));
+               } else
+                       tx->rate_n_flags = htole32(rate_flags | rinfo->ht_plcp);
        } else
                tx->rate_n_flags = htole32(rate_flags | rinfo->plcp);
 
-       return rinfo;
+       return rate;
 }
 
 #define TB0_SIZE 16
@@ -6480,7 +6686,7 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ie
        struct iwm_tx_cmd *tx;
        struct ieee80211_frame *wh;
        struct ieee80211_key *k = NULL;
-       const struct iwm_rate *rinfo;
+       uint8_t rate;
        uint8_t *ivp;
        uint32_t flags;
        u_int hdrlen;
@@ -6549,7 +6755,7 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ie
        tx = (void *)cmd->data;
        memset(tx, 0, sizeof(*tx));
 
-       rinfo = iwm_tx_fill_cmd(sc, in, wh, tx);
+       rate = iwm_tx_fill_cmd(sc, in, wh, tx);
 
 #if NBPFILTER > 0
        if (sc->sc_drvbpf != NULL) {
@@ -6559,16 +6765,20 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ie
                tap->wt_flags = 0;
                tap->wt_chan_freq = htole16(ni->ni_chan->ic_freq);
                chan_flags = ni->ni_chan->ic_flags;
-               if (ic->ic_curmode != IEEE80211_MODE_11N)
+               if (ic->ic_curmode != IEEE80211_MODE_11N &&
+                   ic->ic_curmode != IEEE80211_MODE_11AC) {
                        chan_flags &= ~IEEE80211_CHAN_HT;
+                       chan_flags &= ~IEEE80211_CHAN_40MHZ;
+               }
+               if (ic->ic_curmode != IEEE80211_MODE_11AC)
+                       chan_flags &= ~IEEE80211_CHAN_VHT;
                tap->wt_chan_flags = htole16(chan_flags);
                if ((ni->ni_flags & IEEE80211_NODE_HT) &&
                    !IEEE80211_IS_MULTICAST(wh->i_addr1) &&
-                   type == IEEE80211_FC0_TYPE_DATA &&
-                   rinfo->ht_plcp != IWM_RATE_HT_SISO_MCS_INV_PLCP) {
-                       tap->wt_rate = (0x80 | rinfo->ht_plcp);
+                   type == IEEE80211_FC0_TYPE_DATA) {
+                       tap->wt_rate = (0x80 | ni->ni_txmcs);
                } else
-                       tap->wt_rate = rinfo->rate;
+                       tap->wt_rate = rate;
                if ((ic->ic_flags & IEEE80211_F_WEPON) &&
                    (wh->i_fc[1] & IEEE80211_FC1_PROTECTED))
                        tap->wt_flags |= IEEE80211_RADIOTAP_F_WEP;
@@ -6708,6 +6918,7 @@ iwm_tx(struct iwm_softc *sc, struct mbuf *m, struct ie
        data->txmcs = ni->ni_txmcs;
        data->txrate = ni->ni_txrate;
        data->ampdu_txmcs = ni->ni_txmcs; /* updated upon Tx interrupt */
+       data->ampdu_txnss = ni->ni_vht_ss; /* updated upon Tx interrupt */
 
        /* Fill TX descriptor. */
        desc->num_tbs = 2 + data->map->dm_nsegs;
@@ -6978,7 +7189,9 @@ iwm_add_sta_cmd(struct iwm_softc *sc, struct iwm_node 
 {
        struct iwm_add_sta_cmd add_sta_cmd;
        int err;
-       uint32_t status;
+       uint32_t status, aggsize;
+       const uint32_t max_aggsize = (IWM_STA_FLG_MAX_AGG_SIZE_64K >>
+                   IWM_STA_FLG_MAX_AGG_SIZE_SHIFT);
        size_t cmdsize;
        struct ieee80211com *ic = &sc->sc_ic;
 
@@ -7041,24 +7254,52 @@ iwm_add_sta_cmd(struct iwm_softc *sc, struct iwm_node 
                    IWM_STA_FLG_AGG_MPDU_DENS_MSK);
 
                if (iwm_mimo_enabled(sc)) {
-                       if (in->in_ni.ni_rxmcs[1] != 0) {
-                               add_sta_cmd.station_flags |=
-                                   htole32(IWM_STA_FLG_MIMO_EN_MIMO2);
+                       if (in->in_ni.ni_flags & IEEE80211_NODE_VHT) {
+                               uint16_t rx_mcs = (in->in_ni.ni_vht_rxmcs &
+                                   IEEE80211_VHT_MCS_FOR_SS_MASK(2)) >>
+                                   IEEE80211_VHT_MCS_FOR_SS_SHIFT(2);
+                               if (rx_mcs != IEEE80211_VHT_MCS_SS_NOT_SUPP) {
+                                       add_sta_cmd.station_flags |=
+                                           htole32(IWM_STA_FLG_MIMO_EN_MIMO2);
+                               }
+                       } else {
+                               if (in->in_ni.ni_rxmcs[1] != 0) {
+                                       add_sta_cmd.station_flags |=
+                                           htole32(IWM_STA_FLG_MIMO_EN_MIMO2);
+                               }
+                               if (in->in_ni.ni_rxmcs[2] != 0) {
+                                       add_sta_cmd.station_flags |=
+                                           htole32(IWM_STA_FLG_MIMO_EN_MIMO3);
+                               }
                        }
-                       if (in->in_ni.ni_rxmcs[2] != 0) {
-                               add_sta_cmd.station_flags |=
-                                   htole32(IWM_STA_FLG_MIMO_EN_MIMO3);
-                       }
                }
 
-               if (ieee80211_node_supports_ht_chan40(&in->in_ni)) {
+               if (IEEE80211_CHAN_40MHZ_ALLOWED(in->in_ni.ni_chan) &&
+                   ieee80211_node_supports_ht_chan40(&in->in_ni)) {
                        add_sta_cmd.station_flags |= htole32(
                            IWM_STA_FLG_FAT_EN_40MHZ);
                }
 
-               add_sta_cmd.station_flags
-                   |= htole32(IWM_STA_FLG_MAX_AGG_SIZE_64K);
-               switch (ic->ic_ampdu_params & IEEE80211_AMPDU_PARAM_SS) {
+               if (in->in_ni.ni_flags & IEEE80211_NODE_VHT) {
+                       if (IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
+                           ieee80211_node_supports_vht_chan80(&in->in_ni)) {
+                               add_sta_cmd.station_flags |= htole32(
+                                   IWM_STA_FLG_FAT_EN_80MHZ);
+                       }
+                       aggsize = (in->in_ni.ni_vhtcaps &
+                           IEEE80211_VHTCAP_MAX_AMPDU_LEN_MASK) >>
+                           IEEE80211_VHTCAP_MAX_AMPDU_LEN_SHIFT;
+               } else {
+                       aggsize = (in->in_ni.ni_ampdu_param &
+                           IEEE80211_AMPDU_PARAM_LE);
+               }
+               if (aggsize > max_aggsize)
+                       aggsize = max_aggsize;
+               add_sta_cmd.station_flags |= htole32((aggsize <<
+                   IWM_STA_FLG_MAX_AGG_SIZE_SHIFT) &
+                   IWM_STA_FLG_MAX_AGG_SIZE_MSK);
+
+               switch (in->in_ni.ni_ampdu_param & IEEE80211_AMPDU_PARAM_SS) {
                case IEEE80211_AMPDU_PARAM_SS_2:
                        add_sta_cmd.station_flags
                            |= htole32(IWM_STA_FLG_AGG_MPDU_DENS_2US);
@@ -7430,7 +7671,15 @@ iwm_fill_probe_req(struct iwm_softc *sc, struct iwm_sc
                        return ENOBUFS;
                frm = ieee80211_add_htcaps(frm, ic);
                /* XXX add WME info? */
+               remain -= frm - pos;
        }
+
+       if (ic->ic_flags & IEEE80211_F_VHTON) {
+               if (remain < 14)
+                       return ENOBUFS;
+               frm = ieee80211_add_vhtcaps(frm, ic);
+       }
+
        preq->common_data.len = htole16(frm - pos);
 
        return 0;
@@ -8416,7 +8665,8 @@ iwm_scan_abort(struct iwm_softc *sc)
 int
 iwm_phy_ctxt_update(struct iwm_softc *sc, struct iwm_phy_ctxt *phyctxt,
     struct ieee80211_channel *chan, uint8_t chains_static,
-    uint8_t chains_dynamic, uint32_t apply_time, uint8_t sco)
+    uint8_t chains_dynamic, uint32_t apply_time, uint8_t sco,
+    uint8_t vht_chan_width)
 {
        uint16_t band_flags = (IEEE80211_CHAN_2GHZ | IEEE80211_CHAN_5GHZ);
        int err;
@@ -8426,7 +8676,8 @@ iwm_phy_ctxt_update(struct iwm_softc *sc, struct iwm_p
            (phyctxt->channel->ic_flags & band_flags) !=
            (chan->ic_flags & band_flags)) {
                err = iwm_phy_ctxt_cmd(sc, phyctxt, chains_static,
-                   chains_dynamic, IWM_FW_CTXT_ACTION_REMOVE, apply_time, sco);
+                   chains_dynamic, IWM_FW_CTXT_ACTION_REMOVE, apply_time, sco,
+                   vht_chan_width);
                if (err) {
                        printf("%s: could not remove PHY context "
                            "(error %d)\n", DEVNAME(sc), err);
@@ -8434,7 +8685,8 @@ iwm_phy_ctxt_update(struct iwm_softc *sc, struct iwm_p
                }
                phyctxt->channel = chan;
                err = iwm_phy_ctxt_cmd(sc, phyctxt, chains_static,
-                   chains_dynamic, IWM_FW_CTXT_ACTION_ADD, apply_time, sco);
+                   chains_dynamic, IWM_FW_CTXT_ACTION_ADD, apply_time, sco,
+                   vht_chan_width);
                if (err) {
                        printf("%s: could not add PHY context "
                            "(error %d)\n", DEVNAME(sc), err);
@@ -8443,7 +8695,8 @@ iwm_phy_ctxt_update(struct iwm_softc *sc, struct iwm_p
        } else {
                phyctxt->channel = chan;
                err = iwm_phy_ctxt_cmd(sc, phyctxt, chains_static,
-                   chains_dynamic, IWM_FW_CTXT_ACTION_MODIFY, apply_time, sco);
+                   chains_dynamic, IWM_FW_CTXT_ACTION_MODIFY, apply_time, sco,
+                   vht_chan_width);
                if (err) {
                        printf("%s: could not update PHY context (error %d)\n",
                            DEVNAME(sc), err);
@@ -8452,6 +8705,7 @@ iwm_phy_ctxt_update(struct iwm_softc *sc, struct iwm_p
        }
 
        phyctxt->sco = sco;
+       phyctxt->vht_chan_width = vht_chan_width;
        return 0;
 }
 
@@ -8467,13 +8721,16 @@ iwm_auth(struct iwm_softc *sc)
 
        if (ic->ic_opmode == IEEE80211_M_MONITOR) {
                err = iwm_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
-                   ic->ic_ibss_chan, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN);
+                   ic->ic_ibss_chan, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
+                   IEEE80211_VHTOP0_CHAN_WIDTH_HT);
                if (err)
                        return err;
        } else {
                err = iwm_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
-                   in->in_ni.ni_chan, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN);
+                   in->in_ni.ni_chan, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
+                   IEEE80211_VHTOP0_CHAN_WIDTH_HT);
                if (err)
+               if (err)
                        return err;
        }
        in->in_phyctxt = &sc->sc_phyctxt[0];
@@ -8586,7 +8843,8 @@ iwm_deauth(struct iwm_softc *sc)
 
        /* Move unused PHY context to a default channel. */
        err = iwm_phy_ctxt_update(sc, &sc->sc_phyctxt[0],
-           &ic->ic_channels[1], 1, 1, 0, IEEE80211_HTOP0_SCO_SCN);
+           &ic->ic_channels[1], 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
+           IEEE80211_VHTOP0_CHAN_WIDTH_HT);
        if (err)
                return err;
 
@@ -8615,28 +8873,36 @@ iwm_run(struct iwm_softc *sc)
                uint8_t chains = iwm_mimo_enabled(sc) ? 2 : 1;
                err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
                    in->in_phyctxt->channel, chains, chains,
-                   0, IEEE80211_HTOP0_SCO_SCN);
+                   0, IEEE80211_HTOP0_SCO_SCN,
+                   IEEE80211_VHTOP0_CHAN_WIDTH_HT);
                if (err) {
                        printf("%s: failed to update PHY\n", DEVNAME(sc));
                        return err;
                }
        } else if (ni->ni_flags & IEEE80211_NODE_HT) {
                uint8_t chains = iwm_mimo_enabled(sc) ? 2 : 1;
-               uint8_t sco;
-               if (ieee80211_node_supports_ht_chan40(ni))
+               uint8_t sco, vht_chan_width;
+               if (IEEE80211_CHAN_40MHZ_ALLOWED(in->in_ni.ni_chan) &&
+                   ieee80211_node_supports_ht_chan40(ni))
                        sco = (ni->ni_htop0 & IEEE80211_HTOP0_SCO_MASK);
                else
                        sco = IEEE80211_HTOP0_SCO_SCN;
+               if ((ni->ni_flags & IEEE80211_NODE_VHT) &&
+                   IEEE80211_CHAN_80MHZ_ALLOWED(in->in_ni.ni_chan) &&
+                   ieee80211_node_supports_vht_chan80(ni))
+                       vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_80;
+               else
+                       vht_chan_width = IEEE80211_VHTOP0_CHAN_WIDTH_HT;
                err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
                    in->in_phyctxt->channel, chains, chains,
-                   0, sco);
+                   0, sco, vht_chan_width);
                if (err) {
                        printf("%s: failed to update PHY\n", DEVNAME(sc));
                        return err;
                }
        }
 
-       /* Update STA again, for HT-related settings such as MIMO. */
+       /* Update STA again to apply HT and VHT settings. */
        err = iwm_add_sta_cmd(sc, in, 1);
        if (err) {
                printf("%s: could not update STA (error %d)\n",
@@ -8702,6 +8968,7 @@ iwm_run(struct iwm_softc *sc)
 
        ieee80211_amrr_node_init(&sc->sc_amrr, &in->in_amn);
        ieee80211_ra_node_init(&in->in_rn);
+       ieee80211_ra_vht_node_init(&in->in_rn_vht);
 
        if (ic->ic_opmode == IEEE80211_M_MONITOR) {
                iwm_led_blink_start(sc);
@@ -8711,6 +8978,7 @@ 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->in_ni.ni_vht_ss = 1;
        iwm_setrates(in, 0);
 
        timeout_add_msec(&sc->sc_calib_to, 500);
@@ -8791,7 +9059,8 @@ iwm_run_stop(struct iwm_softc *sc)
        /* Reset Tx chains in case MIMO or 40 MHz channels were enabled. */
        if (in->in_ni.ni_flags & IEEE80211_NODE_HT) {
                err = iwm_phy_ctxt_update(sc, in->in_phyctxt,
-                  in->in_phyctxt->channel, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN);
+                   in->in_phyctxt->channel, 1, 1, 0, IEEE80211_HTOP0_SCO_SCN,
+                   IEEE80211_VHTOP0_CHAN_WIDTH_HT);
                if (err) {
                        printf("%s: failed to update PHY\n", DEVNAME(sc));
                        return err;
@@ -8947,27 +9216,98 @@ iwm_calib_timeout(void *arg)
 }
 
 void
-iwm_setrates(struct iwm_node *in, int async)
+iwm_set_rate_table_vht(struct iwm_node *in, struct iwm_lq_cmd *lqcmd)
 {
        struct ieee80211_node *ni = &in->in_ni;
        struct ieee80211com *ic = ni->ni_ic;
        struct iwm_softc *sc = IC2IFP(ic)->if_softc;
-       struct iwm_lq_cmd lqcmd;
+       int ridx_min = iwm_rval2ridx(ieee80211_min_basic_rate(ic));
+       int i, tab, txmcs;
+
+       /*
+        * Fill the LQ rate selection table with VHT rates in descending
+        * order, i.e. with the node's current TX rate first. Keep reducing
+        * channel width during later Tx attempts, and eventually fall back
+        * to legacy OFDM. Do not mix SISO and MIMO rates.
+        */
+       lqcmd->mimo_delim = 0;
+       txmcs = ni->ni_txmcs;
+       for (i = 0; i < nitems(lqcmd->rs_table); i++) {
+               if (txmcs >= 0) {
+                       tab = IWM_RATE_MCS_VHT_MSK;
+                       tab |= txmcs & IWM_RATE_VHT_MCS_RATE_CODE_MSK;
+                       tab |= ((ni->ni_vht_ss - 1) <<
+                           IWM_RATE_VHT_MCS_NSS_POS) &
+                           IWM_RATE_VHT_MCS_NSS_MSK;
+                       if (ni->ni_vht_ss > 1)
+                               tab |= IWM_RATE_MCS_ANT_AB_MSK;
+                       else {
+                               if (sc->sc_device_family ==
+                                   IWM_DEVICE_FAMILY_9000)
+                                       tab |= IWM_RATE_MCS_ANT_B_MSK;
+                               else
+                                       tab |= IWM_RATE_MCS_ANT_A_MSK;
+                       }
+
+                       /*
+                        * First two Tx attempts may use 80MHz/SGI.
+                        * Next two Tx attempts may use 40MHz/SGI.
+                        * Beyond that use 20 MHz and decrease the rate.
+                        * As a special case, MCS 9 is invalid on 20 Mhz.
+                        */
+                       if (txmcs == 9) {
+                               if (i < 2 && in->in_phyctxt->vht_chan_width >=
+                                   IEEE80211_VHTOP0_CHAN_WIDTH_80)
+                                       tab |= IWM_RATE_MCS_CHAN_WIDTH_80;
+                               else
+                                       tab |= IWM_RATE_MCS_CHAN_WIDTH_40;
+                               tab |= IWM_RATE_MCS_RTS_REQUIRED_MSK;
+                               if (i < 4) {
+                                       if (ieee80211_ra_vht_use_sgi(ni))
+                                               tab |= IWM_RATE_MCS_SGI_MSK;
+                               } else
+                                       txmcs--;
+                       } else if (i < 2 && in->in_phyctxt->vht_chan_width >=
+                           IEEE80211_VHTOP0_CHAN_WIDTH_80) {
+                               tab |= IWM_RATE_MCS_CHAN_WIDTH_80;
+                               tab |= IWM_RATE_MCS_RTS_REQUIRED_MSK;
+                               if (ieee80211_ra_vht_use_sgi(ni))
+                                       tab |= IWM_RATE_MCS_SGI_MSK;
+                       } else if (i >= 2 && i < 4 &&
+                           in->in_phyctxt->vht_chan_width >=
+                           IEEE80211_VHTOP0_CHAN_WIDTH_HT &&
+                           (in->in_phyctxt->sco == IEEE80211_HTOP0_SCO_SCA ||
+                           in->in_phyctxt->sco == IEEE80211_HTOP0_SCO_SCB)) {
+                               tab |= IWM_RATE_MCS_CHAN_WIDTH_40;
+                               tab |= IWM_RATE_MCS_RTS_REQUIRED_MSK;
+                               if (ieee80211_ra_vht_use_sgi(ni))
+                                       tab |= IWM_RATE_MCS_SGI_MSK;
+                       } else if (txmcs >= 0)
+                               txmcs--;
+               } else {
+                       /* Fill the rest with the lowest possible rate. */
+                       tab = iwm_rates[ridx_min].plcp;
+                       if (sc->sc_device_family == IWM_DEVICE_FAMILY_9000)
+                               tab |= IWM_RATE_MCS_ANT_B_MSK;
+                       else
+                               tab |= IWM_RATE_MCS_ANT_A_MSK;
+                       if (ni->ni_vht_ss > 1 && lqcmd->mimo_delim == 0)
+                               lqcmd->mimo_delim = i;
+               }
+
+               lqcmd->rs_table[i] = htole32(tab);
+       }
+}
+
+void
+iwm_set_rate_table(struct iwm_node *in, struct iwm_lq_cmd *lqcmd)
+{
+       struct ieee80211_node *ni = &in->in_ni;
+       struct ieee80211com *ic = ni->ni_ic;
+       struct iwm_softc *sc = IC2IFP(ic)->if_softc;
        struct ieee80211_rateset *rs = &ni->ni_rates;
        int i, ridx, ridx_min, ridx_max, j, mimo, tab = 0;
-       struct iwm_host_cmd cmd = {
-               .id = IWM_LQ_CMD,
-               .len = { sizeof(lqcmd), },
-       };
 
-       cmd.flags = async ? IWM_CMD_ASYNC : 0;
-
-       memset(&lqcmd, 0, sizeof(lqcmd));
-       lqcmd.sta_id = IWM_STATION_ID;
-
-       if (ic->ic_flags & IEEE80211_F_USEPROT)
-               lqcmd.flags |= IWM_LQ_FLAG_USE_RTS_MSK;
-
        /*
         * Fill the LQ rate selection table with legacy and/or HT rates
         * in descending order, i.e. with the node's current TX rate first.
@@ -8979,13 +9319,13 @@ iwm_setrates(struct iwm_node *in, int async)
         */
        j = 0;
        ridx_min = iwm_rval2ridx(ieee80211_min_basic_rate(ic));
-       mimo = iwm_is_mimo_mcs(ni->ni_txmcs);
+       mimo = iwm_is_mimo_ht_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;
                uint8_t ht_plcp = iwm_rates[ridx].ht_plcp;
 
-               if (j >= nitems(lqcmd.rs_table))
+               if (j >= nitems(lqcmd->rs_table))
                        break;
                tab = 0;
                if (ni->ni_flags & IEEE80211_NODE_HT) {
@@ -8998,7 +9338,7 @@ iwm_setrates(struct iwm_node *in, int async)
                        for (i = ni->ni_txmcs; i >= 0; i--) {
                                if (isclr(ni->ni_rxmcs, i))
                                        continue;
-                               if (ridx != iwm_mcs2ridx[i])
+                               if (ridx != iwm_ht_mcs2ridx[i])
                                        continue;
                                tab = ht_plcp;
                                tab |= IWM_RATE_MCS_HT_MSK;
@@ -9038,13 +9378,13 @@ iwm_setrates(struct iwm_node *in, int async)
 
                if (IWM_RIDX_IS_CCK(ridx))
                        tab |= IWM_RATE_MCS_CCK_MSK;
-               lqcmd.rs_table[j++] = htole32(tab);
+               lqcmd->rs_table[j++] = htole32(tab);
        }
 
-       lqcmd.mimo_delim = (mimo ? j : 0);
+       lqcmd->mimo_delim = (mimo ? j : 0);
 
        /* Fill the rest with the lowest possible rate */
-       while (j < nitems(lqcmd.rs_table)) {
+       while (j < nitems(lqcmd->rs_table)) {
                tab = iwm_rates[ridx_min].plcp;
                if (IWM_RIDX_IS_CCK(ridx_min))
                        tab |= IWM_RATE_MCS_CCK_MSK;
@@ -9052,9 +9392,35 @@ iwm_setrates(struct iwm_node *in, int async)
                        tab |= IWM_RATE_MCS_ANT_B_MSK;
                else
                        tab |= IWM_RATE_MCS_ANT_A_MSK;
-               lqcmd.rs_table[j++] = htole32(tab);
+               lqcmd->rs_table[j++] = htole32(tab);
        }
+}
 
+void
+iwm_setrates(struct iwm_node *in, int async)
+{
+       struct ieee80211_node *ni = &in->in_ni;
+       struct ieee80211com *ic = ni->ni_ic;
+       struct iwm_softc *sc = IC2IFP(ic)->if_softc;
+       struct iwm_lq_cmd lqcmd;
+       struct iwm_host_cmd cmd = {
+               .id = IWM_LQ_CMD,
+               .len = { sizeof(lqcmd), },
+       };
+
+       cmd.flags = async ? IWM_CMD_ASYNC : 0;
+
+       memset(&lqcmd, 0, sizeof(lqcmd));
+       lqcmd.sta_id = IWM_STATION_ID;
+
+       if (ic->ic_flags & IEEE80211_F_USEPROT)
+               lqcmd.flags |= IWM_LQ_FLAG_USE_RTS_MSK;
+
+       if (ni->ni_flags & IEEE80211_NODE_VHT)
+               iwm_set_rate_table_vht(in, &lqcmd);
+       else
+               iwm_set_rate_table(in, &lqcmd);
+
        if (sc->sc_device_family == IWM_DEVICE_FAMILY_9000)
                lqcmd.single_stream_ant_msk = IWM_ANT_B;
        else
@@ -9082,7 +9448,7 @@ iwm_media_change(struct ifnet *ifp)
                return err;
 
        if (ic->ic_fixed_mcs != -1)
-               sc->sc_fixed_ridx = iwm_mcs2ridx[ic->ic_fixed_mcs];
+               sc->sc_fixed_ridx = iwm_ht_mcs2ridx[ic->ic_fixed_mcs];
        else if (ic->ic_fixed_rate != -1) {
                rate = ic->ic_sup_rates[ic->ic_curmode].
                    rs_rates[ic->ic_fixed_rate] & IEEE80211_RATE_VAL;
@@ -9846,7 +10212,8 @@ iwm_init_hw(struct iwm_softc *sc)
                sc->sc_phyctxt[i].id = i;
                sc->sc_phyctxt[i].channel = &ic->ic_channels[1];
                err = iwm_phy_ctxt_cmd(sc, &sc->sc_phyctxt[i], 1, 1,
-                   IWM_FW_CTXT_ACTION_ADD, 0, IEEE80211_HTOP0_SCO_SCN);
+                   IWM_FW_CTXT_ACTION_ADD, 0, IEEE80211_HTOP0_SCO_SCN,
+                   IEEE80211_VHTOP0_CHAN_WIDTH_HT);
                if (err) {
                        printf("%s: could not add phy context %d (error %d)\n",
                            DEVNAME(sc), i, err);
@@ -9997,6 +10364,8 @@ iwm_init(struct ifnet *ifp)
 
        if (sc->sc_nvm.sku_cap_11n_enable)
                iwm_setup_ht_rates(sc);
+       if (sc->sc_nvm.sku_cap_11ac_enable)
+               iwm_setup_vht_rates(sc);
 
        ifq_clr_oactive(&ifp->if_snd);
        ifp->if_flags |= IFF_RUNNING;
@@ -11554,6 +11923,13 @@ iwm_attach(struct device *parent, struct device *self,
        ic->ic_aselcaps = 0;
        ic->ic_ampdu_params = (IEEE80211_AMPDU_PARAM_SS_4 | 0x3 /* 64k */);
 
+       ic->ic_vhtcaps = IEEE80211_VHTCAP_MAX_MPDU_LENGTH_3895 |
+           (IEEE80211_VHTCAP_MAX_AMPDU_LEN_64K <<
+           IEEE80211_VHTCAP_MAX_AMPDU_LEN_SHIFT) |
+           (IEEE80211_VHTCAP_CHAN_WIDTH_80 <<
+            IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT) | IEEE80211_VHTCAP_SGI80 |
+           IEEE80211_VHTCAP_RX_ANT_PATTERN | IEEE80211_VHTCAP_TX_ANT_PATTERN;
+
        ic->ic_sup_rates[IEEE80211_MODE_11A] = ieee80211_std_rateset_11a;
        ic->ic_sup_rates[IEEE80211_MODE_11B] = ieee80211_std_rateset_11b;
        ic->ic_sup_rates[IEEE80211_MODE_11G] = ieee80211_std_rateset_11g;
@@ -11561,6 +11937,8 @@ iwm_attach(struct device *parent, struct device *self,
        for (i = 0; i < nitems(sc->sc_phyctxt); i++) {
                sc->sc_phyctxt[i].id = i;
                sc->sc_phyctxt[i].sco = IEEE80211_HTOP0_SCO_SCN;
+               sc->sc_phyctxt[i].vht_chan_width =
+                   IEEE80211_VHTOP0_CHAN_WIDTH_HT;
        }
 
        sc->sc_amrr.amrr_min_success_threshold =  1;
blob - 1f4c5278ec0e8a45f4cd42cc270d91c16a575914
blob + a8b407dc4d1eaaeac655920b82d05b500d66ad9b
--- sys/dev/pci/if_iwmreg.h
+++ sys/dev/pci/if_iwmreg.h
@@ -4442,7 +4442,6 @@ enum {
        IWM_FIRST_OFDM_RATE = IWM_RATE_6M_INDEX,
        IWM_RATE_MCS_0_INDEX = IWM_RATE_6M_INDEX,
        IWM_FIRST_HT_RATE = IWM_RATE_MCS_0_INDEX,
-       IWM_FIRST_VHT_RATE = IWM_RATE_MCS_0_INDEX,
        IWM_RATE_9M_INDEX,
        IWM_RATE_12M_INDEX,
        IWM_RATE_MCS_1_INDEX = IWM_RATE_12M_INDEX,
@@ -4469,7 +4468,6 @@ enum {
        IWM_RATE_MCS_14_INDEX,
        IWM_RATE_MCS_15_INDEX,
        IWM_LAST_HT_RATE = IWM_RATE_MCS_15_INDEX,
-       IWM_LAST_VHT_RATE = IWM_RATE_MCS_9_INDEX,
        IWM_RATE_COUNT_LEGACY = IWM_LAST_NON_HT_RATE + 1,
        IWM_RATE_COUNT = IWM_LAST_HT_RATE + 1,
 };
blob - 4de3e2bfefe4760baecbccee7739972f6d36fb92
blob + fe33f5bc83661c73d75922b2146103bbb9d13299
--- sys/dev/pci/if_iwmvar.h
+++ sys/dev/pci/if_iwmvar.h
@@ -190,6 +190,7 @@ struct iwm_nvm_data {
        int sku_cap_band_24GHz_enable;
        int sku_cap_band_52GHz_enable;
        int sku_cap_11n_enable;
+       int sku_cap_11ac_enable;
        int sku_cap_amt_enable;
        int sku_cap_ipan_enable;
        int sku_cap_mimo_disable;
@@ -264,6 +265,7 @@ struct iwm_tx_data {
 
        /* A-MPDU subframes */
        int ampdu_txmcs;
+       int ampdu_txnss;
        int ampdu_nframes;
 };
 
@@ -361,6 +363,7 @@ struct iwm_phy_ctxt {
        uint32_t ref;
        struct ieee80211_channel *channel;
        uint8_t sco; /* 40 MHz secondary channel offset */
+       uint8_t vht_chan_width;
 };
 
 struct iwm_bf_data {
@@ -670,6 +673,7 @@ struct iwm_node {
 
        struct ieee80211_amrr_node in_amn;
        struct ieee80211_ra_node in_rn;
+       struct ieee80211_ra_vht_node in_rn_vht;
        int lq_rate_mismatch;
 
        struct iwm_rxq_dup_data dup_data;
blob - fcd7637726707c59ff4d8601158a7bda81cb8a1f
blob + 3408ca60b74b50051106386ab4fc70960c84d4c1
--- sys/net80211/ieee80211.c
+++ sys/net80211/ieee80211.c
@@ -892,40 +892,52 @@ const struct ieee80211_ht_rateset ieee80211_std_ratese
 
 const struct ieee80211_vht_rateset ieee80211_std_ratesets_11ac[] = {
        /* MCS 0-8 (MCS 9 N/A), 1 SS, 20MHz channel, no SGI */
-       { 9, { 13, 26, 39, 52, 78, 104, 117, 130, 156 }, 1, 0 },
+       { 0, 9, { 13, 26, 39, 52, 78, 104, 117, 130, 156 },
+           1, 0, 0, 0 },
 
        /* MCS 0-8 (MCS 9 N/A), 1 SS, 20MHz channel, SGI */
-       { 9, { 14, 29, 43, 58, 87, 116, 130, 144, 174 }, 1, 1 },
+       { 1, 9, { 14, 29, 43, 58, 87, 116, 130, 144, 174 },
+           1, 0, 0, 1 },
 
        /* MCS 0-8 (MCS 9 N/A), 2 SS, 20MHz channel, no SGI */
-       { 9, { 26, 52, 78, 104, 156, 208, 234, 260, 312 }, 2, 0 },
+       { 2, 9, { 26, 52, 78, 104, 156, 208, 234, 260, 312 },
+           2, 0, 0, 0 },
 
        /* MCS 0-8 (MCS 9 N/A), 2 SS, 20MHz channel, SGI */
-       { 9, { 29, 58, 87, 116, 173, 231, 261, 289, 347 }, 2, 1 },
+       { 3, 9, { 29, 58, 87, 116, 173, 231, 261, 289, 347 },
+           2, 0, 0, 1 },
 
        /* MCS 0-9, 1 SS, 40MHz channel, no SGI */
-       { 10, { 27, 54, 81, 108, 162, 216, 243, 270, 324, 360 }, 1, 0 },
+       { 4, 10, { 27, 54, 81, 108, 162, 216, 243, 270, 324, 360 },
+           1, 1, 0, 0 },
 
        /* MCS 0-9, 1 SS, 40MHz channel, SGI */
-       { 10, { 30, 60, 90, 120, 180, 240, 270, 300, 360, 400 }, 1, 1 },
+       { 5, 10, { 30, 60, 90, 120, 180, 240, 270, 300, 360, 400 },
+           1, 1, 0, 1 },
 
        /* MCS 0-9, 2 SS, 40MHz channel, no SGI */
-       { 10, { 54, 108, 162, 216, 324, 432, 486, 540, 648, 720 }, 2, 0 },
+       { 6, 10, { 54, 108, 162, 216, 324, 432, 486, 540, 648, 720 },
+           2, 1, 0, 0 },
 
        /* MCS 0-9, 2 SS, 40MHz channel, SGI */
-       { 10, { 60, 120, 180, 240, 360, 480, 540, 600, 720, 800 }, 2, 1 },
+       { 7, 10, { 60, 120, 180, 240, 360, 480, 540, 600, 720, 800 },
+           2, 1, 0, 1 },
 
        /* MCS 0-9, 1 SS, 80MHz channel, no SGI */
-       { 10, { 59, 117, 176, 234, 351, 468, 527, 585, 702, 780 }, 1, 0 },
+       { 8, 10, { 59, 117, 176, 234, 351, 468, 527, 585, 702, 780 },
+           1, 0, 1, 0 },
 
        /* MCS 0-9, 1 SS, 80MHz channel, SGI */
-       { 10, { 65, 130, 195, 260, 390, 520, 585, 650, 780, 867 }, 1, 1 },
+       { 9, 10, { 65, 130, 195, 260, 390, 520, 585, 650, 780, 867 },
+           1, 0, 1, 1 },
 
        /* MCS 0-9, 2 SS, 80MHz channel, no SGI */
-       { 10, { 117, 234, 351, 468, 702, 936, 1053, 1404, 1560 }, 2, 0 },
+       { 10, 10, { 117, 234, 351, 468, 702, 936, 1053, 1404, 1560 },
+           2, 0, 1, 0 },
 
        /* MCS 0-9, 2 SS, 80MHz channel, SGI */
-       { 10, { 130, 260, 390, 520, 780, 1040, 1170, 1300, 1560, 1734 }, 2, 1 },
+       { 11, 10, { 130, 260, 390, 520, 780, 1040, 1170, 1300, 1560, 1734 },
+           2, 0, 1, 1 },
 };
 
 /*
blob - d1535a318cc1416dc7bf427d3e237c909318e06d
blob + 2b7428067d384476a2a9427ade9297f380ad329a
--- sys/net80211/ieee80211_node.h
+++ sys/net80211/ieee80211_node.h
@@ -113,16 +113,20 @@ extern const struct ieee80211_ht_rateset ieee80211_std
 #define IEEE80211_VHT_RATESET_MIMO2_80_SGI     11
 #define IEEE80211_VHT_NUM_RATESETS             12
 
-/* Maximum number of rates in a HT rateset. */
+/* Maximum number of rates in a VHT rateset. */
 #define IEEE80211_VHT_RATESET_MAX_NRATES       10
 
 struct ieee80211_vht_rateset {
+       int idx; /* This rateset's index in ieee80211_std_rateset_11ac[]. */
+
        uint32_t nrates;
        uint32_t rates[IEEE80211_VHT_RATESET_MAX_NRATES]; /* 500 kbit/s units */
 
        /* Number of spatial streams used by rates in this rateset. */
        int num_ss;
 
+       int chan40;
+       int chan80;
        int sgi;
 };
 
@@ -574,6 +578,27 @@ ieee80211_node_supports_vht_chan80(struct ieee80211_no
            op_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_8080);
 }
 
+/* Check if the peer can receive frames sent on a 160 MHz channel. */
+static inline int
+ieee80211_node_supports_vht_chan160(struct ieee80211_node *ni)
+{
+       uint8_t cap_chan_width, op_chan_width;
+
+       if (!ieee80211_node_supports_vht(ni))
+               return 0;
+
+       cap_chan_width = (ni->ni_vhtcaps & IEEE80211_VHTCAP_CHAN_WIDTH_MASK) >>
+           IEEE80211_VHTCAP_CHAN_WIDTH_SHIFT;
+       if (cap_chan_width != IEEE80211_VHTCAP_CHAN_WIDTH_160)
+               return 0;
+
+       op_chan_width = (ni->ni_vht_chan_width &
+           IEEE80211_VHTOP0_CHAN_WIDTH_MASK) >>
+           IEEE80211_VHTOP0_CHAN_WIDTH_SHIFT;
+
+       return (op_chan_width == IEEE80211_VHTOP0_CHAN_WIDTH_160);
+}
+
 struct ieee80211com;
 
 typedef void ieee80211_iter_func(void *, struct ieee80211_node *);
blob - /dev/null
blob + 3d13d9f024f349521cdf890955a4b6ce2e88fd5d (mode 644)
--- /dev/null
+++ sys/net80211/ieee80211_ra_vht.c
@@ -0,0 +1,752 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Christian Ehrhardt <[email protected]>
+ * Copyright (c) 2016, 2021, 2022 Stefan Sperling <[email protected]>
+ * Copyright (c) 2016 Theo Buehler <[email protected]>
+ *
+ * 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_vht.h>
+
+int    ieee80211_ra_vht_next_intra_rate(struct ieee80211_ra_vht_node *,
+           struct ieee80211_node *);
+const struct ieee80211_vht_rateset * ieee80211_ra_vht_next_rateset(
+                   struct ieee80211_ra_vht_node *, struct ieee80211_node *);
+int    ieee80211_ra_vht_best_mcs_in_rateset(struct ieee80211_ra_vht_node *,
+           const struct ieee80211_vht_rateset *);
+void   ieee80211_ra_vht_probe_next_rateset(struct ieee80211_ra_vht_node *,
+           struct ieee80211_node *, const struct ieee80211_vht_rateset *);
+int    ieee80211_ra_vht_next_mcs(struct ieee80211_ra_vht_node *,
+           struct ieee80211_node *);
+void   ieee80211_ra_vht_probe_done(struct ieee80211_ra_vht_node *, int);
+int    ieee80211_ra_vht_intra_mode_ra_finished(
+           struct ieee80211_ra_vht_node *, struct ieee80211_node *);
+void   ieee80211_ra_vht_trigger_next_rateset(struct ieee80211_ra_vht_node *,
+           struct ieee80211_node *);
+int    ieee80211_ra_vht_inter_mode_ra_finished(
+           struct ieee80211_ra_vht_node *, struct ieee80211_node *);
+int    ieee80211_ra_vht_best_rate(struct ieee80211_ra_vht_node *,
+           struct ieee80211_node *);
+void   ieee80211_ra_vht_probe_next_rate(struct ieee80211_ra_vht_node *,
+           struct ieee80211_node *);
+void   ieee80211_ra_vht_init_valid_rates(struct ieee80211com *,
+           struct ieee80211_node *, struct ieee80211_ra_vht_node *);
+int    ieee80211_ra_vht_probe_valid(struct ieee80211_ra_vht_goodput_stats *);
+
+/* 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_vht_debug > 0) printf x; } while (0)
+#define DPRINTFN(n, x) do { if (ra_vht_debug >= (n)) printf x; } while (0)
+int ra_vht_debug = 0;
+#else
+#define DPRINTF(x)     do { ; } while (0)
+#define DPRINTFN(n, x) do { ; } while (0)
+#endif
+
+#ifdef RA_DEBUG
+void
+ra_vht_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_vht_fp_sprintf(uint64_t fp)
+{
+       uint32_t i, f;
+       static char buf[64];
+       int ret;
+
+       ra_vht_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_vht_rateset *
+ieee80211_ra_vht_get_rateset(int mcs, int nss, int chan40, int chan80, int sgi)
+{
+       const struct ieee80211_vht_rateset *rs;
+       int i;
+
+       for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) {
+               rs = &ieee80211_std_ratesets_11ac[i];
+               if (mcs < rs->nrates && rs->num_ss == nss &&
+                   chan40 == rs->chan40 && chan80 == rs->chan80 &&
+                   sgi == rs->sgi)
+                       return rs;
+       }
+
+       panic("MCS %d NSS %d is not part of any rateset", mcs, nss);
+}
+
+int
+ieee80211_ra_vht_use_sgi(struct ieee80211_node *ni)
+{
+       if ((ni->ni_chan->ic_xflags & IEEE80211_CHANX_160MHZ) &&
+           ieee80211_node_supports_vht_chan160(ni)) {
+               if (ni->ni_flags & IEEE80211_NODE_VHT_SGI160)
+                       return 1;
+       }
+
+       if ((ni->ni_chan->ic_xflags & IEEE80211_CHANX_80MHZ) &&
+           ieee80211_node_supports_vht_chan80(ni)) {
+               if (ni->ni_flags & IEEE80211_NODE_VHT_SGI80)
+                       return 1;
+       }
+       
+       return 0;
+}
+
+/*
+ * Update goodput statistics.
+ */
+
+uint64_t
+ieee80211_ra_vht_get_txrate(int mcs, int nss, int chan40, int chan80, int sgi)
+{
+       const struct ieee80211_vht_rateset *rs;
+       uint64_t txrate;
+
+       rs = ieee80211_ra_vht_get_rateset(mcs, nss, chan40, chan80, sgi);
+       txrate = rs->rates[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 */
+
+int
+ieee80211_ra_vht_next_lower_intra_rate(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       if (ni->ni_txmcs <= 0)
+               return 0;
+
+       return ni->ni_txmcs - 1;
+}
+
+int
+ieee80211_ra_vht_get_max_mcs(int vht_mcs, int nss, int chan40)
+{
+       int supp_mcs = (vht_mcs & IEEE80211_VHT_MCS_FOR_SS_MASK(nss)) >>
+           IEEE80211_VHT_MCS_FOR_SS_SHIFT(nss);
+       int max_mcs = -1;
+
+       switch (supp_mcs) {
+       case IEEE80211_VHT_MCS_SS_NOT_SUPP:
+               break;
+       case IEEE80211_VHT_MCS_0_7:
+               max_mcs = 7;
+               break;
+       case IEEE80211_VHT_MCS_0_8:
+               max_mcs = 8;
+               break;
+       case IEEE80211_VHT_MCS_0_9:
+               /* Disable VHT MCS 9 for 20MHz-only stations. */
+               if (!chan40)
+                       max_mcs = 8;
+               else
+                       max_mcs = 9;
+               break;
+       default:
+               /* Should not happen; Values above cover the possible range. */
+               panic("invalid VHT Rx MCS value %u", supp_mcs);
+       }
+
+       return max_mcs;
+}
+
+int
+ieee80211_ra_vht_next_intra_rate(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       int max_mcs;
+
+       max_mcs = ieee80211_ra_vht_get_max_mcs(ni->ni_vht_rxmcs,
+           ni->ni_vht_ss, ieee80211_node_supports_ht_chan40(ni));
+       if (max_mcs != 7 && max_mcs != 8 && max_mcs != 9)
+               panic("ni->ni_vht_ss invalid: %u", ni->ni_vht_ss);
+
+       if (ni->ni_txmcs >= max_mcs)
+               return max_mcs;
+
+       return ni->ni_txmcs + 1;
+}
+
+const struct ieee80211_vht_rateset *
+ieee80211_ra_vht_next_rateset(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_vht_rateset *rs;
+       int next;
+       int sgi = ieee80211_ra_vht_use_sgi(ni);
+       int mcs = ni->ni_txmcs;
+       int nss = ni->ni_vht_ss;
+
+       /*
+        * We only probe 80MHz ratesets.
+        * Drivers handle retries on slower rates if needed.
+        */
+       rs = ieee80211_ra_vht_get_rateset(mcs, nss, 0, 1, sgi);
+       if (rn->probing & IEEE80211_RA_PROBING_UP) {
+               switch (rs->idx) {
+               case IEEE80211_VHT_RATESET_SISO_80:
+                       next = IEEE80211_VHT_RATESET_MIMO2_80;
+                       break;
+               case IEEE80211_VHT_RATESET_SISO_80_SGI:
+                       next = IEEE80211_VHT_RATESET_MIMO2_80_SGI;
+                       break;
+               default:
+                       return NULL;
+               }
+       } else if (rn->probing & IEEE80211_RA_PROBING_DOWN) {
+               switch (rs->idx) {
+               case IEEE80211_VHT_RATESET_MIMO2_80:
+                       next = IEEE80211_VHT_RATESET_SISO_80;
+                       break;
+               case IEEE80211_VHT_RATESET_MIMO2_80_SGI:
+                       next = IEEE80211_VHT_RATESET_SISO_80_SGI;
+                       break;
+               default:
+                       return NULL;
+               }
+       } else
+               panic("%s: invalid probing mode %d", __func__, rn->probing);
+
+       return &ieee80211_std_ratesets_11ac[next];
+}
+
+int
+ieee80211_ra_vht_best_mcs_in_rateset(struct ieee80211_ra_vht_node *rn,
+    const struct ieee80211_vht_rateset *rs)
+{
+       uint64_t gmax = 0;
+       int mcs, best_mcs = 0;
+
+       for (mcs = 0; mcs < rs->nrates; mcs++) {
+               struct ieee80211_ra_vht_goodput_stats *g = &rn->g[rs->idx][mcs];
+               if (((1 << mcs) & rn->valid_rates[rs->num_ss - 1]) == 0)
+                       continue;
+               if (g->measured > gmax + IEEE80211_RA_RATE_THRESHOLD) {
+                       gmax = g->measured;
+                       best_mcs = mcs;
+               }
+       }
+
+       return best_mcs;
+}
+
+void
+ieee80211_ra_vht_probe_next_rateset(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni, const struct ieee80211_vht_rateset *rsnext)
+{
+       const struct ieee80211_vht_rateset *rs;
+       struct ieee80211_ra_vht_goodput_stats *g;
+       int best_mcs, mcs;
+
+       /* Find most recently measured best MCS from the current rateset. */
+       rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, ni->ni_vht_ss, 0, 1,
+           ieee80211_ra_vht_use_sgi(ni));
+       best_mcs = ieee80211_ra_vht_best_mcs_in_rateset(rn, rs);
+
+       /* Switch to the next rateset. */
+       ni->ni_txmcs = 0;
+       ni->ni_vht_ss = rsnext->num_ss;
+
+       /* Select the lowest rate from the next rateset with loss-free
+        * goodput close to the current best measurement. */
+       g = &rn->g[rs->idx][best_mcs];
+       for (mcs = 0; mcs < rsnext->nrates; mcs++) {
+               uint64_t txrate = rsnext->rates[mcs];
+
+               if ((rn->valid_rates[rsnext->num_ss - 1] & (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 (mcs == rsnext->nrates)
+               ni->ni_txmcs = rn->max_mcs[rsnext->num_ss - 1];
+
+       /* Add rates from the next rateset as candidates. */
+       rn->candidate_rates[rsnext->num_ss - 1] |= (1 << ni->ni_txmcs);
+       if (rn->probing & IEEE80211_RA_PROBING_UP) {
+               rn->candidate_rates[rsnext->num_ss - 1] |=
+                 (1 << ieee80211_ra_vht_next_intra_rate(rn, ni));
+       } else if (rn->probing & IEEE80211_RA_PROBING_DOWN) {
+               rn->candidate_rates[rsnext->num_ss - 1] |=
+                   (1 << ieee80211_ra_vht_next_lower_intra_rate(rn, ni));
+       } else
+               panic("%s: invalid probing mode %d", __func__, rn->probing);
+}
+
+int
+ieee80211_ra_vht_next_mcs(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       int next;
+
+       if (rn->probing & IEEE80211_RA_PROBING_DOWN)
+               next = ieee80211_ra_vht_next_lower_intra_rate(rn, ni);
+       else if (rn->probing & IEEE80211_RA_PROBING_UP)
+               next = ieee80211_ra_vht_next_intra_rate(rn, ni);
+       else
+               panic("%s: invalid probing mode %d", __func__, rn->probing);
+
+       return next;
+}
+
+void
+ieee80211_ra_vht_probe_clear(struct ieee80211_ra_vht_goodput_stats *g)
+{
+       g->nprobe_pkts = 0;
+       g->nprobe_fail = 0;
+}
+
+void
+ieee80211_ra_vht_probe_done(struct ieee80211_ra_vht_node *rn, int nss)
+{
+       rn->probing = IEEE80211_RA_NOT_PROBING;
+       rn->probed_rates[nss - 1] = 0;
+       rn->valid_probes[nss - 1] = 0;
+       rn->candidate_rates[nss - 1] = 0;
+}
+
+int
+ieee80211_ra_vht_intra_mode_ra_finished(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_vht_rateset *rs;
+       struct ieee80211_ra_vht_goodput_stats *g;
+       int next_mcs, best_mcs;
+       uint64_t next_rate;
+       int nss = ni->ni_vht_ss;
+       int sgi = ieee80211_ra_vht_use_sgi(ni);
+
+       rn->probed_rates[nss - 1] = (rn->probed_rates[nss - 1] |
+           (1 << ni->ni_txmcs));
+
+       /* Check if the min/max MCS in this rateset has been probed. */
+       rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, nss, 0, 1, sgi);
+       if (rn->probing & IEEE80211_RA_PROBING_DOWN) {
+               if (ni->ni_txmcs == 0 ||
+                   rn->probed_rates[nss - 1] & (1 << 0)) {
+                       ieee80211_ra_vht_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+       } else if (rn->probing & IEEE80211_RA_PROBING_UP) {
+               if (ni->ni_txmcs == rn->max_mcs[nss - 1] ||
+                   rn->probed_rates[nss - 1] & (1 << rn->max_mcs[nss - 1])) {
+                       ieee80211_ra_vht_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_vht_next_mcs(rn, ni);
+       if (next_mcs == ni->ni_txmcs) {
+               ieee80211_ra_vht_trigger_next_rateset(rn, ni);
+               return 1;
+       }
+       next_rate = ieee80211_ra_vht_get_txrate(next_mcs, nss, 0, 1, sgi);
+       g = &rn->g[rs->idx][ni->ni_txmcs];
+       if (g->loss == 0 &&
+           g->measured >= next_rate + IEEE80211_RA_RATE_THRESHOLD) {
+               ieee80211_ra_vht_trigger_next_rateset(rn, ni);
+               return 1;
+       }
+
+       /* Check if we had a better measurement at a previously probed MCS. */
+       best_mcs = ieee80211_ra_vht_best_mcs_in_rateset(rn, rs);
+       if (best_mcs != ni->ni_txmcs &&
+           (rn->probed_rates[nss - 1] & (1 << best_mcs))) {
+               if ((rn->probing & IEEE80211_RA_PROBING_UP) &&
+                   best_mcs < ni->ni_txmcs) {
+                       ieee80211_ra_vht_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+               if ((rn->probing & IEEE80211_RA_PROBING_DOWN) &&
+                   best_mcs > ni->ni_txmcs) {
+                       ieee80211_ra_vht_trigger_next_rateset(rn, ni);
+                       return 1;
+               }
+       }
+
+       /* Check if all rates in the set of candidate rates have been probed. */
+       if ((rn->candidate_rates[nss - 1] & rn->probed_rates[nss - 1]) ==
+           rn->candidate_rates[nss - 1]) {
+               /* Remain in the current rateset until above checks trigger. */
+               rn->probing &= ~IEEE80211_RA_PROBING_INTER;
+               return 1;
+       }
+
+       return 0;
+}
+
+void
+ieee80211_ra_vht_trigger_next_rateset(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_vht_rateset *rsnext;
+
+       rsnext = ieee80211_ra_vht_next_rateset(rn, ni);
+       if (rsnext) {
+               ieee80211_ra_vht_probe_next_rateset(rn, ni, rsnext);
+               rn->probing |= IEEE80211_RA_PROBING_INTER;
+       } else
+               rn->probing &= ~IEEE80211_RA_PROBING_INTER;
+}
+
+int
+ieee80211_ra_vht_inter_mode_ra_finished(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       return ((rn->probing & IEEE80211_RA_PROBING_INTER) == 0);
+}
+
+int
+ieee80211_ra_vht_best_rate(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       const struct ieee80211_vht_rateset *rs;
+       int i, j, best_mcs = rn->best_mcs, best_nss = rn->best_nss;
+       uint64_t gmax;
+
+       rs = ieee80211_ra_vht_get_rateset(best_mcs, best_nss, 0, 1,
+           ieee80211_ra_vht_use_sgi(ni));
+       gmax = rn->g[rs->idx][best_mcs].measured;
+
+       for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) {
+               rs = &ieee80211_std_ratesets_11ac[i];
+               for (j = 0; j < IEEE80211_VHT_RATESET_MAX_NRATES; j++) {
+                       struct ieee80211_ra_vht_goodput_stats *g = &rn->g[i][j];
+                       if (((1 << i) & rn->valid_rates[rs->num_ss - 1]) == 0)
+                               continue;
+                       if (g->measured > gmax + IEEE80211_RA_RATE_THRESHOLD) {
+                               gmax = g->measured;
+                               best_mcs = j;
+                               best_nss = rs->num_ss;
+                       }
+               }
+       }
+
+#ifdef RA_DEBUG
+       if (rn->best_mcs != best_mcs || rn->best_nss != best_nss) {
+               DPRINTF(("MCS,NSS %d,%d is best; MCS,NSS{cur|avg|loss}:",
+                   best_mcs, best_nss));
+               for (i = 0; i < IEEE80211_VHT_NUM_RATESETS; i++) {
+                       rs = &ieee80211_std_ratesets_11ac[i];
+                       for (j = 0; j < IEEE80211_VHT_RATESET_MAX_NRATES; j++) {
+                               struct ieee80211_ra_vht_goodput_stats *g;
+                               g = &rn->g[i][j];
+                               if ((rn->valid_rates[rs->num_ss - 1] &
+                                   (1 << i)) == 0)
+                                       continue;
+                               DPRINTF((" %d,%d{%s|", i, j,
+                                   ra_vht_fp_sprintf(g->measured)));
+                               DPRINTF(("%s|", ra_vht_fp_sprintf(g->average)));
+                               DPRINTF(("%s%%}", ra_vht_fp_sprintf(g->loss)));
+                       }
+               }
+               DPRINTF(("\n"));
+       }
+#endif
+       return best_mcs;
+}
+
+void
+ieee80211_ra_vht_probe_next_rate(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211_node *ni)
+{
+       /* Select the next rate to probe. */
+       rn->probed_rates[ni->ni_vht_ss - 1] |= (1 << ni->ni_txmcs);
+       ni->ni_txmcs = ieee80211_ra_vht_next_mcs(rn, ni);
+}
+
+void
+ieee80211_ra_vht_init_valid_rates(struct ieee80211com *ic,
+    struct ieee80211_node *ni, struct ieee80211_ra_vht_node *rn)
+{
+       int nss, ic_max_mcs, ni_max_mcs, max_mcs;
+
+       memset(rn->max_mcs, 0, sizeof(rn->max_mcs));
+       memset(rn->valid_rates, 0, sizeof(rn->valid_rates));
+
+       for (nss = 1; nss <= IEEE80211_VHT_NUM_SS; nss++) {
+               ic_max_mcs = ieee80211_ra_vht_get_max_mcs(ic->ic_vht_txmcs,
+                   nss, IEEE80211_CHAN_40MHZ_ALLOWED(ic->ic_bss->ni_chan));
+               ni_max_mcs = ieee80211_ra_vht_get_max_mcs(ni->ni_vht_rxmcs,
+                   nss, ieee80211_node_supports_ht_chan40(ni));
+               if ((ic_max_mcs != 7 && ic_max_mcs != 8 && ic_max_mcs != 9) ||
+                   (ni_max_mcs != 7 && ni_max_mcs != 8 && ni_max_mcs != 9))
+                       continue;
+
+               max_mcs = MIN(ic_max_mcs, ni_max_mcs);
+               rn->max_mcs[nss - 1] = max_mcs;
+               rn->valid_rates[nss - 1] = ((1 << (max_mcs + 1)) - 1);
+       }
+}
+
+int
+ieee80211_ra_vht_probe_valid(struct ieee80211_ra_vht_goodput_stats *g)
+{
+       /* 128 packets make up a valid probe in any case. */
+       if (g->nprobe_pkts >= 128)
+               return 1;
+
+       /* 8 packets with > 75% loss make a valid probe, too. */
+       if (g->nprobe_pkts >= 8 &&
+           g->nprobe_pkts - g->nprobe_fail < g->nprobe_pkts / 4)
+               return 1;
+
+       return 0;
+}
+
+void
+ieee80211_ra_vht_add_stats(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211com *ic, struct ieee80211_node *ni,
+    int mcs, int nss, 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;
+       const struct ieee80211_vht_rateset *rs;
+       struct ieee80211_ra_vht_goodput_stats *g;
+       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_VHT_RATESET_MAX_NRATES)
+               return;
+       if (nss <= 0 || nss > IEEE80211_VHT_NUM_SS)
+               return;
+       if (total == 0)
+               return;
+
+       s = splnet();
+
+       rs = ieee80211_ra_vht_get_rateset(mcs, nss, 0, 1,
+           ieee80211_ra_vht_use_sgi(ni));
+       g = &rn->g[rs->idx][mcs];
+       g->nprobe_pkts += total;
+       g->nprobe_fail += fail;
+
+       if (!ieee80211_ra_vht_probe_valid(g)) {
+               splx(s);
+               return;
+       }
+       rn->valid_probes[nss - 1] |= 1U << mcs;
+
+       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;
+       g->nprobe_fail = 0;
+       g->nprobe_pkts = 0;
+
+       rate = ieee80211_ra_vht_get_txrate(mcs, nss, 0, 1,
+           ieee80211_ra_vht_use_sgi(ni));
+
+       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_vht_choose(struct ieee80211_ra_vht_node *rn,
+    struct ieee80211com *ic, struct ieee80211_node *ni)
+{
+       struct ieee80211_ra_vht_goodput_stats *g;
+       int s;
+       int sgi = ieee80211_ra_vht_use_sgi(ni);
+       const struct ieee80211_vht_rateset *rs, *rsnext;
+       int nss = ni->ni_vht_ss;
+
+       s = splnet();
+
+       if (rn->valid_rates[0] == 0) {
+               ieee80211_ra_vht_init_valid_rates(ic, ni, rn);
+               if (rn->valid_rates[0] == 0)
+                       panic("VHT not supported");
+       }
+
+       rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, nss, 0, 1, sgi);
+       g = &rn->g[rs->idx][ni->ni_txmcs];
+
+       if (rn->probing) {
+               /* Probe another rate or settle at the best rate. */
+               if (!(rn->valid_probes[nss - 1] & (1UL << ni->ni_txmcs))) {
+                       splx(s);
+                       return;
+               }
+               ieee80211_ra_vht_probe_clear(g);
+               if (!ieee80211_ra_vht_intra_mode_ra_finished(rn, ni)) {
+                       ieee80211_ra_vht_probe_next_rate(rn, ni);
+                       DPRINTFN(3, ("probing MCS,NSS %d,%d\n",
+                           ni->ni_txmcs, ni->ni_vht_ss));
+               } else if (ieee80211_ra_vht_inter_mode_ra_finished(rn, ni)) {
+                       rn->best_mcs = ieee80211_ra_vht_best_rate(rn, ni);
+                       ni->ni_txmcs = rn->best_mcs;
+                       ni->ni_vht_ss = rn->best_nss;
+                       ieee80211_ra_vht_probe_done(rn, nss);
+               }
+
+               splx(s);
+               return;
+       } else {
+               rn->valid_probes[nss - 1] = 0;
+       }
+
+
+       rs = ieee80211_ra_vht_get_rateset(ni->ni_txmcs, nss, 0, 1, sgi);
+       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[nss - 1] = 0;
+               if (ni->ni_txmcs == 0) {
+                       rsnext = ieee80211_ra_vht_next_rateset(rn, ni);
+                       if (rsnext) {
+                               ieee80211_ra_vht_probe_next_rateset(rn, ni,
+                                   rsnext);
+                       } else {
+                               /* Cannot probe further down. */
+                               rn->probing = IEEE80211_RA_NOT_PROBING;
+                       }
+               } else {
+                       ni->ni_txmcs = ieee80211_ra_vht_next_mcs(rn, ni);
+                       rn->candidate_rates[nss - 1] = (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[nss - 1] = 0;
+               if (ni->ni_txmcs == rn->max_mcs[nss - 1]) {
+                       rsnext = ieee80211_ra_vht_next_rateset(rn, ni);
+                       if (rsnext) {
+                               ieee80211_ra_vht_probe_next_rateset(rn, ni,
+                                   rsnext);
+                       } else {
+                               /* Cannot probe further up. */
+                               rn->probing = IEEE80211_RA_NOT_PROBING;
+                       }
+               } else {
+                       ni->ni_txmcs = ieee80211_ra_vht_next_mcs(rn, ni);
+                       rn->candidate_rates[nss - 1] = (1 << ni->ni_txmcs);
+               }
+       } else {
+               /* Remain at current rate. */
+               rn->probing = IEEE80211_RA_NOT_PROBING;
+               rn->probed_rates[nss - 1] = 0;
+               rn->candidate_rates[nss - 1] = 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_vht_fp_sprintf(g->measured)));
+               DPRINTFN(3, ("average: %s Mbit/s\n",
+                   ra_vht_fp_sprintf(g->average)));
+               DPRINTFN(3, ("stddeviation: %s\n",
+                   ra_vht_fp_sprintf(g->stddeviation)));
+               DPRINTFN(3, ("loss: %s%%\n", ra_vht_fp_sprintf(g->loss)));
+       }
+}
+
+void
+ieee80211_ra_vht_node_init(struct ieee80211_ra_vht_node *rn)
+{
+       memset(rn, 0, sizeof(*rn));
+       rn->best_nss = 1;
+}
blob - /dev/null
blob + 853c8d4ca9c9a1edcc050ffa2d8086e32d720430 (mode 644)
--- /dev/null
+++ sys/net80211/ieee80211_ra_vht.h
@@ -0,0 +1,91 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2021 Christian Ehrhardt <[email protected]>
+ * Copyright (c) 2021, 2022 Stefan Sperling <[email protected]>
+ *
+ * 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 a rate.
+ * All uint64_t numbers in this struct use fixed-point arithmetic.
+ */
+struct ieee80211_ra_vht_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_vht_init() and ieee80211_ra_vht_add_stats() only.
+ */
+struct ieee80211_ra_vht_node {
+       /* Bitmaps for MCS 0-9 for a given number of spatial streams. */
+       uint16_t valid_probes[IEEE80211_VHT_NUM_SS];
+       uint16_t valid_rates[IEEE80211_VHT_NUM_SS];
+       uint16_t candidate_rates[IEEE80211_VHT_NUM_SS];
+       uint16_t probed_rates[IEEE80211_VHT_NUM_SS];
+
+       /* Maximum usable MCS per given number of spatial streams. */
+       int max_mcs[IEEE80211_VHT_NUM_SS];
+
+       /* 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,NSS found by probing. */
+       int best_mcs;
+       int best_nss;
+
+       /* Goodput statistics for each rate. */
+       struct ieee80211_ra_vht_goodput_stats
+           g[IEEE80211_VHT_NUM_RATESETS][IEEE80211_VHT_RATESET_MAX_NRATES];
+};
+
+/* Initialize rate adaptation state. */
+void   ieee80211_ra_vht_node_init(struct ieee80211_ra_vht_node *);
+
+/*
+ * Drivers report information about 802.11ac/VHT Tx attempts here.
+ * mcs: The VHT MCS used during this Tx attempt.
+ * nss: The number of spatial streams 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_vht_add_stats(struct ieee80211_ra_vht_node *,
+           struct ieee80211com *, struct ieee80211_node *,
+           int mcs, int nss, unsigned int total, unsigned int fail);
+
+/* Drivers call this function to update ni->ni_txmcs and ni->ni_vht_ss. */
+void   ieee80211_ra_vht_choose(struct ieee80211_ra_vht_node *,
+           struct ieee80211com *, struct ieee80211_node *);
+
+/*
+ * Get the VHT rateset for a particular VHT MCS, NSS, with 40MHz, 80MHz,
+ * and/or SGI on/off.
+ */
+const struct ieee80211_vht_rateset * ieee80211_ra_vht_get_rateset(int mcs,
+           int nss, int chan40, int chan80, int sgi);
+
+/* Check whether SGI should be used. */
+int ieee80211_ra_vht_use_sgi(struct ieee80211_node *);

Reply via email to