|
@@ -341,11 +341,13 @@ static void ieee80211_add_ht_ie(struct ieee80211_sub_if_data *sdata,
|
|
|
|
|
|
static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
|
|
|
struct sk_buff *skb,
|
|
|
- struct ieee80211_supported_band *sband)
|
|
|
+ struct ieee80211_supported_band *sband,
|
|
|
+ struct ieee80211_vht_cap *ap_vht_cap)
|
|
|
{
|
|
|
u8 *pos;
|
|
|
u32 cap;
|
|
|
struct ieee80211_sta_vht_cap vht_cap;
|
|
|
+ int i;
|
|
|
|
|
|
BUILD_BUG_ON(sizeof(vht_cap) != sizeof(sband->vht_cap));
|
|
|
|
|
@@ -364,6 +366,42 @@ static void ieee80211_add_vht_ie(struct ieee80211_sub_if_data *sdata,
|
|
|
cap &= ~IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
|
|
|
}
|
|
|
|
|
|
+ /*
|
|
|
+ * Some APs apparently get confused if our capabilities are better
|
|
|
+ * than theirs, so restrict what we advertise in the assoc request.
|
|
|
+ */
|
|
|
+ if (!(ap_vht_cap->vht_cap_info &
|
|
|
+ cpu_to_le32(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE)))
|
|
|
+ cap &= ~IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE;
|
|
|
+
|
|
|
+ if (!(ap_vht_cap->vht_cap_info &
|
|
|
+ cpu_to_le32(IEEE80211_VHT_CAP_TXSTBC)))
|
|
|
+ cap &= ~(IEEE80211_VHT_CAP_RXSTBC_1 |
|
|
|
+ IEEE80211_VHT_CAP_RXSTBC_3 |
|
|
|
+ IEEE80211_VHT_CAP_RXSTBC_4);
|
|
|
+
|
|
|
+ for (i = 0; i < 8; i++) {
|
|
|
+ int shift = i * 2;
|
|
|
+ u16 mask = IEEE80211_VHT_MCS_NOT_SUPPORTED << shift;
|
|
|
+ u16 ap_mcs, our_mcs;
|
|
|
+
|
|
|
+ ap_mcs = (le16_to_cpu(ap_vht_cap->supp_mcs.tx_mcs_map) &
|
|
|
+ mask) >> shift;
|
|
|
+ our_mcs = (le16_to_cpu(vht_cap.vht_mcs.rx_mcs_map) &
|
|
|
+ mask) >> shift;
|
|
|
+
|
|
|
+ switch (ap_mcs) {
|
|
|
+ default:
|
|
|
+ if (our_mcs <= ap_mcs)
|
|
|
+ break;
|
|
|
+ /* fall through */
|
|
|
+ case IEEE80211_VHT_MCS_NOT_SUPPORTED:
|
|
|
+ vht_cap.vht_mcs.rx_mcs_map &= cpu_to_le16(~mask);
|
|
|
+ vht_cap.vht_mcs.rx_mcs_map |=
|
|
|
+ cpu_to_le16(ap_mcs << shift);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/* reserve and fill IE */
|
|
|
pos = skb_put(skb, sizeof(struct ieee80211_vht_cap) + 2);
|
|
|
ieee80211_ie_build_vht_cap(pos, &vht_cap, cap);
|
|
@@ -562,7 +600,8 @@ static void ieee80211_send_assoc(struct ieee80211_sub_if_data *sdata)
|
|
|
sband, chan, sdata->smps_mode);
|
|
|
|
|
|
if (!(ifmgd->flags & IEEE80211_STA_DISABLE_VHT))
|
|
|
- ieee80211_add_vht_ie(sdata, skb, sband);
|
|
|
+ ieee80211_add_vht_ie(sdata, skb, sband,
|
|
|
+ &assoc_data->ap_vht_cap);
|
|
|
|
|
|
/* if present, add any custom non-vendor IEs that go after HT */
|
|
|
if (assoc_data->ie_len && assoc_data->ie) {
|
|
@@ -3753,7 +3792,7 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
|
|
|
struct ieee80211_bss *bss = (void *)req->bss->priv;
|
|
|
struct ieee80211_mgd_assoc_data *assoc_data;
|
|
|
struct ieee80211_supported_band *sband;
|
|
|
- const u8 *ssidie, *ht_ie;
|
|
|
+ const u8 *ssidie, *ht_ie, *vht_ie;
|
|
|
int i, err;
|
|
|
|
|
|
assoc_data = kzalloc(sizeof(*assoc_data) + req->ie_len, GFP_KERNEL);
|
|
@@ -3872,6 +3911,12 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata,
|
|
|
((struct ieee80211_ht_operation *)(ht_ie + 2))->ht_param;
|
|
|
else
|
|
|
ifmgd->flags |= IEEE80211_STA_DISABLE_HT;
|
|
|
+ vht_ie = ieee80211_bss_get_ie(req->bss, WLAN_EID_VHT_CAPABILITY);
|
|
|
+ if (vht_ie && vht_ie[1] >= sizeof(struct ieee80211_vht_cap))
|
|
|
+ memcpy(&assoc_data->ap_vht_cap, vht_ie + 2,
|
|
|
+ sizeof(struct ieee80211_vht_cap));
|
|
|
+ else
|
|
|
+ ifmgd->flags |= IEEE80211_STA_DISABLE_VHT;
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
if (bss->wmm_used && bss->uapsd_supported &&
|