* @IEEE80211_HW_BUFF_MMPDU_TXQ: use the TXQ for bufferable MMPDUs, this of
  *     course requires the driver to use TXQs to start with.
  *
+ * @IEEE80211_HW_SUPPORTS_VHT_EXT_NSS_BW: (Hardware) rate control supports VHT
+ *     extended NSS BW (dot11VHTExtendedNSSBWCapable). This flag will be set if
+ *     the selected rate control algorithm sets %RATE_CTRL_CAPA_VHT_EXT_NSS_BW
+ *     but if the rate control is built-in then it must be set by the driver.
+ *     See also the documentation for that flag.
+ *
  * @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
  */
 enum ieee80211_hw_flags {
        IEEE80211_HW_DEAUTH_NEED_MGD_TX_PREP,
        IEEE80211_HW_DOESNT_SUPPORT_QOS_NDP,
        IEEE80211_HW_BUFF_MMPDU_TXQ,
+       IEEE80211_HW_SUPPORTS_VHT_EXT_NSS_BW,
 
        /* keep last, obviously */
        NUM_IEEE80211_HW_FLAGS
        bool bss;
 };
 
+/**
+ * enum rate_control_capabilities - rate control capabilities
+ */
+enum rate_control_capabilities {
+       /**
+        * @RATE_CTRL_CAPA_VHT_EXT_NSS_BW:
+        * Support for extended NSS BW support (dot11VHTExtendedNSSCapable)
+        * Note that this is only looked at if the minimum number of chains
+        * that the AP uses is < the number of TX chains the hardware has,
+        * otherwise the NSS difference doesn't bother us.
+        */
+       RATE_CTRL_CAPA_VHT_EXT_NSS_BW = BIT(0),
+};
+
 struct rate_control_ops {
+       unsigned long capa;
        const char *name;
        void *(*alloc)(struct ieee80211_hw *hw, struct dentry *debugfsdir);
        void (*free)(void *priv);
 
  * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
  * Copyright 2013-2014  Intel Mobile Communications GmbH
  * Copyright (C) 2017     Intel Deutschland GmbH
+ * Copyright (C) 2018 Intel Corporation
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
                goto fail_rate;
        }
 
+       if (local->rate_ctrl) {
+               clear_bit(IEEE80211_HW_SUPPORTS_VHT_EXT_NSS_BW, hw->flags);
+               if (local->rate_ctrl->ops->capa & RATE_CTRL_CAPA_VHT_EXT_NSS_BW)
+                       ieee80211_hw_set(hw, SUPPORTS_VHT_EXT_NSS_BW);
+       }
+
+       /*
+        * If the VHT capabilities don't have IEEE80211_VHT_EXT_NSS_BW_CAPABLE,
+        * or have it when we don't, copy the sband structure and set/clear it.
+        * This is necessary because rate scaling algorithms could be switched
+        * and have different support values.
+        * Print a message so that in the common case the reallocation can be
+        * avoided.
+        */
+       BUILD_BUG_ON(NUM_NL80211_BANDS > 8 * sizeof(local->sband_allocated));
+       for (band = 0; band < NUM_NL80211_BANDS; band++) {
+               struct ieee80211_supported_band *sband;
+               bool local_cap, ie_cap;
+
+               local_cap = ieee80211_hw_check(hw, SUPPORTS_VHT_EXT_NSS_BW);
+
+               sband = local->hw.wiphy->bands[band];
+               if (!sband || !sband->vht_cap.vht_supported)
+                       continue;
+
+               ie_cap = !!(sband->vht_cap.vht_mcs.tx_highest &
+                           cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE));
+
+               if (local_cap == ie_cap)
+                       continue;
+
+               sband = kmemdup(sband, sizeof(*sband), GFP_KERNEL);
+               if (!sband)
+                       goto fail_rate;
+
+               wiphy_dbg(hw->wiphy, "copying sband (band %d) due to VHT EXT NSS BW flag\n",
+                         band);
+
+               sband->vht_cap.vht_mcs.tx_highest ^=
+                       cpu_to_le16(IEEE80211_VHT_EXT_NSS_BW_CAPABLE);
+
+               local->hw.wiphy->bands[band] = sband;
+               local->sband_allocated |= BIT(band);
+       }
+
        /* add one default STA interface if supported */
        if (local->hw.wiphy->interface_modes & BIT(NL80211_IFTYPE_STATION) &&
            !ieee80211_hw_check(hw, NO_AUTO_VIF)) {
 void ieee80211_free_hw(struct ieee80211_hw *hw)
 {
        struct ieee80211_local *local = hw_to_local(hw);
+       enum nl80211_band band;
 
        mutex_destroy(&local->iflist_mtx);
        mutex_destroy(&local->mtx);
 
        ieee80211_free_led_names(local);
 
+       for (band = 0; band < NUM_NL80211_BANDS; band++) {
+               if (!(local->sband_allocated & BIT(band)))
+                       continue;
+               kfree(local->hw.wiphy->bands[band]);
+       }
+
        wiphy_free(local->hw.wiphy);
 }
 EXPORT_SYMBOL(ieee80211_free_hw);