mac80211: handle VHT operating mode notification
authorJohannes Berg <johannes.berg@intel.com>
Thu, 27 Dec 2012 17:55:36 +0000 (18:55 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Fri, 15 Feb 2013 08:41:32 +0000 (09:41 +0100)
Handle the operating mode notification action frame.
When the supported streams or the bandwidth change
let the driver and rate control algorithm know.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/linux/ieee80211.h
include/net/mac80211.h
net/mac80211/ht.c
net/mac80211/ieee80211_i.h
net/mac80211/rx.c
net/mac80211/sta_info.h
net/mac80211/vht.c

index 67c1a6c..12b5996 100644 (file)
@@ -1301,6 +1301,7 @@ struct ieee80211_vht_operation {
 #define IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454                        0x00000002
 #define IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ               0x00000004
 #define IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ      0x00000008
+#define IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK                 0x0000000C
 #define IEEE80211_VHT_CAP_RXLDPC                               0x00000010
 #define IEEE80211_VHT_CAP_SHORT_GI_80                          0x00000020
 #define IEEE80211_VHT_CAP_SHORT_GI_160                         0x00000040
index a608ab9..b7fb311 100644 (file)
@@ -2118,11 +2118,14 @@ enum ieee80211_frame_release_type {
  * @IEEE80211_RC_SUPP_RATES_CHANGED: The supported rate set of this peer
  *     changed (in IBSS mode) due to discovering more information about
  *     the peer.
+ * @IEEE80211_RC_NSS_CHANGED: N_SS (number of spatial streams) was changed
+ *     by the peer
  */
 enum ieee80211_rate_control_changed {
        IEEE80211_RC_BW_CHANGED         = BIT(0),
        IEEE80211_RC_SMPS_CHANGED       = BIT(1),
        IEEE80211_RC_SUPP_RATES_CHANGED = BIT(2),
+       IEEE80211_RC_NSS_CHANGED        = BIT(3),
 };
 
 /**
index a64b4f0..797969b 100644 (file)
@@ -212,6 +212,10 @@ bool ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_sub_if_data *sdata,
                changed = true;
        sta->sta.bandwidth = bw;
 
+       sta->cur_max_bandwidth =
+               ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
+                               IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
+
        return changed;
 }
 
index 3b13af4..4947c91 100644 (file)
@@ -1433,6 +1433,9 @@ void ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
                                         struct sta_info *sta);
 enum ieee80211_sta_rx_bandwidth ieee80211_sta_cur_vht_bw(struct sta_info *sta);
 void ieee80211_sta_set_rx_nss(struct sta_info *sta);
+void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
+                                struct sta_info *sta, u8 opmode,
+                                enum ieee80211_band band);
 
 /* Spectrum management */
 void ieee80211_process_measurement_req(struct ieee80211_sub_if_data *sdata,
index 8a861a5..1617e0b 100644 (file)
@@ -2436,6 +2436,36 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx)
                }
 
                break;
+       case WLAN_CATEGORY_VHT:
+               if (sdata->vif.type != NL80211_IFTYPE_STATION &&
+                   sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
+                   sdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
+                   sdata->vif.type != NL80211_IFTYPE_AP &&
+                   sdata->vif.type != NL80211_IFTYPE_ADHOC)
+                       break;
+
+               /* verify action code is present */
+               if (len < IEEE80211_MIN_ACTION_SIZE + 1)
+                       goto invalid;
+
+               switch (mgmt->u.action.u.vht_opmode_notif.action_code) {
+               case WLAN_VHT_ACTION_OPMODE_NOTIF: {
+                       u8 opmode;
+
+                       /* verify opmode is present */
+                       if (len < IEEE80211_MIN_ACTION_SIZE + 2)
+                               goto invalid;
+
+                       opmode = mgmt->u.action.u.vht_opmode_notif.operating_mode;
+
+                       ieee80211_vht_handle_opmode(rx->sdata, rx->sta,
+                                                   opmode, status->band);
+                       goto handled;
+               }
+               default:
+                       break;
+               }
+               break;
        case WLAN_CATEGORY_BACK:
                if (sdata->vif.type != NL80211_IFTYPE_STATION &&
                    sdata->vif.type != NL80211_IFTYPE_MESH_POINT &&
index 03c42f8..63dfdb5 100644 (file)
@@ -297,6 +297,8 @@ struct sta_ampdu_mlme {
  * @sta_state: duplicates information about station state (for debug)
  * @beacon_loss_count: number of times beacon loss has triggered
  * @rcu_head: RCU head used for freeing this station struct
+ * @cur_max_bandwidth: maximum bandwidth to use for TX to the station,
+ *     taken from HT/VHT capabilities or VHT operating mode notification
  */
 struct sta_info {
        /* General information, mostly static */
@@ -398,6 +400,8 @@ struct sta_info {
        } debugfs;
 #endif
 
+       enum ieee80211_sta_rx_bandwidth cur_max_bandwidth;
+
        unsigned int lost_packets;
        unsigned int beacon_loss_count;
 
index 67436e3..0951f74 100644 (file)
@@ -10,6 +10,7 @@
 #include <linux/export.h>
 #include <net/mac80211.h>
 #include "ieee80211_i.h"
+#include "rate.h"
 
 
 void ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
@@ -39,6 +40,15 @@ void ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,
        memcpy(&vht_cap->vht_mcs, &vht_cap_ie->supp_mcs,
               sizeof(struct ieee80211_vht_mcs_info));
 
+       switch (vht_cap->cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) {
+       case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ:
+       case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ:
+               sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
+               break;
+       default:
+               sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
+       }
+
        sta->sta.bandwidth = ieee80211_sta_cur_vht_bw(sta);
 }
 
@@ -46,12 +56,13 @@ enum ieee80211_sta_rx_bandwidth ieee80211_sta_cur_vht_bw(struct sta_info *sta)
 {
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        u32 cap = sta->sta.vht_cap.cap;
+       enum ieee80211_sta_rx_bandwidth bw;
 
-       if (!sta->sta.vht_cap.vht_supported)
-               return sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
+       if (!sta->sta.vht_cap.vht_supported) {
+               bw = sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
                                IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
-
-       /* TODO: handle VHT opmode notification data */
+               goto check_max;
+       }
 
        switch (sdata->vif.bss_conf.chandef.width) {
        default:
@@ -60,19 +71,31 @@ enum ieee80211_sta_rx_bandwidth ieee80211_sta_cur_vht_bw(struct sta_info *sta)
        case NL80211_CHAN_WIDTH_20_NOHT:
        case NL80211_CHAN_WIDTH_20:
        case NL80211_CHAN_WIDTH_40:
-               return sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
+               bw = sta->sta.ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 ?
                                IEEE80211_STA_RX_BW_40 : IEEE80211_STA_RX_BW_20;
+               break;
        case NL80211_CHAN_WIDTH_160:
-               if (cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ)
-                       return IEEE80211_STA_RX_BW_160;
+               if ((cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) ==
+                               IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ) {
+                       bw = IEEE80211_STA_RX_BW_160;
+                       break;
+               }
                /* fall through */
        case NL80211_CHAN_WIDTH_80P80:
-               if (cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ)
-                       return IEEE80211_STA_RX_BW_160;
+               if ((cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) ==
+                               IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ) {
+                       bw = IEEE80211_STA_RX_BW_160;
+                       break;
+               }
                /* fall through */
        case NL80211_CHAN_WIDTH_80:
-               return IEEE80211_STA_RX_BW_80;
+               bw = IEEE80211_STA_RX_BW_80;
        }
+
+ check_max:
+       if (bw > sta->cur_max_bandwidth)
+               bw = sta->cur_max_bandwidth;
+       return bw;
 }
 
 void ieee80211_sta_set_rx_nss(struct sta_info *sta)
@@ -115,3 +138,53 @@ void ieee80211_sta_set_rx_nss(struct sta_info *sta)
        ht_rx_nss = max(ht_rx_nss, vht_rx_nss);
        sta->sta.rx_nss = max_t(u8, 1, ht_rx_nss);
 }
+
+void ieee80211_vht_handle_opmode(struct ieee80211_sub_if_data *sdata,
+                                struct sta_info *sta, u8 opmode,
+                                enum ieee80211_band band)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_supported_band *sband;
+       enum ieee80211_sta_rx_bandwidth new_bw;
+       u32 changed = 0;
+       u8 nss;
+
+       sband = local->hw.wiphy->bands[band];
+
+       /* ignore - no support for BF yet */
+       if (opmode & IEEE80211_OPMODE_NOTIF_RX_NSS_TYPE_BF)
+               return;
+
+       nss = opmode & IEEE80211_OPMODE_NOTIF_RX_NSS_MASK;
+       nss >>= IEEE80211_OPMODE_NOTIF_RX_NSS_SHIFT;
+       nss += 1;
+
+       if (sta->sta.rx_nss != nss) {
+               sta->sta.rx_nss = nss;
+               changed |= IEEE80211_RC_NSS_CHANGED;
+       }
+
+       switch (opmode & IEEE80211_OPMODE_NOTIF_CHANWIDTH_MASK) {
+       case IEEE80211_OPMODE_NOTIF_CHANWIDTH_20MHZ:
+               sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_20;
+               break;
+       case IEEE80211_OPMODE_NOTIF_CHANWIDTH_40MHZ:
+               sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_40;
+               break;
+       case IEEE80211_OPMODE_NOTIF_CHANWIDTH_80MHZ:
+               sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_80;
+               break;
+       case IEEE80211_OPMODE_NOTIF_CHANWIDTH_160MHZ:
+               sta->cur_max_bandwidth = IEEE80211_STA_RX_BW_160;
+               break;
+       }
+
+       new_bw = ieee80211_sta_cur_vht_bw(sta);
+       if (new_bw != sta->sta.bandwidth) {
+               sta->sta.bandwidth = new_bw;
+               changed |= IEEE80211_RC_NSS_CHANGED;
+       }
+
+       if (changed)
+               rate_control_rate_update(local, sband, sta, changed);
+}