diff options
Diffstat (limited to 'net/mac80211/vht.c')
| -rw-r--r-- | net/mac80211/vht.c | 212 | 
1 files changed, 211 insertions, 1 deletions
diff --git a/net/mac80211/vht.c b/net/mac80211/vht.c index a2c2258bc84..171344d4eb7 100644 --- a/net/mac80211/vht.c +++ b/net/mac80211/vht.c @@ -13,6 +13,104 @@  #include "rate.h" +static void __check_vhtcap_disable(struct ieee80211_sub_if_data *sdata, +				   struct ieee80211_sta_vht_cap *vht_cap, +				   u32 flag) +{ +	__le32 le_flag = cpu_to_le32(flag); + +	if (sdata->u.mgd.vht_capa_mask.vht_cap_info & le_flag && +	    !(sdata->u.mgd.vht_capa.vht_cap_info & le_flag)) +		vht_cap->cap &= ~flag; +} + +void ieee80211_apply_vhtcap_overrides(struct ieee80211_sub_if_data *sdata, +				      struct ieee80211_sta_vht_cap *vht_cap) +{ +	int i; +	u16 rxmcs_mask, rxmcs_cap, rxmcs_n, txmcs_mask, txmcs_cap, txmcs_n; + +	if (!vht_cap->vht_supported) +		return; + +	if (sdata->vif.type != NL80211_IFTYPE_STATION) +		return; + +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_RXLDPC); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_SHORT_GI_80); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_SHORT_GI_160); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_TXSTBC); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN); +	__check_vhtcap_disable(sdata, vht_cap, +			       IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN); + +	/* Allow user to decrease AMPDU length exponent */ +	if (sdata->u.mgd.vht_capa_mask.vht_cap_info & +	    cpu_to_le32(IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK)) { +		u32 cap, n; + +		n = le32_to_cpu(sdata->u.mgd.vht_capa.vht_cap_info) & +			IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; +		n >>= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; +		cap = vht_cap->cap & IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; +		cap >>= IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; + +		if (n < cap) { +			vht_cap->cap &= +				~IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK; +			vht_cap->cap |= +				n << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT; +		} +	} + +	/* Allow the user to decrease MCSes */ +	rxmcs_mask = +		le16_to_cpu(sdata->u.mgd.vht_capa_mask.supp_mcs.rx_mcs_map); +	rxmcs_n = le16_to_cpu(sdata->u.mgd.vht_capa.supp_mcs.rx_mcs_map); +	rxmcs_n &= rxmcs_mask; +	rxmcs_cap = le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map); + +	txmcs_mask = +		le16_to_cpu(sdata->u.mgd.vht_capa_mask.supp_mcs.tx_mcs_map); +	txmcs_n = le16_to_cpu(sdata->u.mgd.vht_capa.supp_mcs.tx_mcs_map); +	txmcs_n &= txmcs_mask; +	txmcs_cap = le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map); +	for (i = 0; i < 8; i++) { +		u8 m, n, c; + +		m = (rxmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; +		n = (rxmcs_n >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; +		c = (rxmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; + +		if (m && ((c != IEEE80211_VHT_MCS_NOT_SUPPORTED && n < c) || +			  n == IEEE80211_VHT_MCS_NOT_SUPPORTED)) { +			rxmcs_cap &= ~(3 << 2*i); +			rxmcs_cap |= (rxmcs_n & (3 << 2*i)); +		} + +		m = (txmcs_mask >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; +		n = (txmcs_n >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; +		c = (txmcs_cap >> 2*i) & IEEE80211_VHT_MCS_NOT_SUPPORTED; + +		if (m && ((c != IEEE80211_VHT_MCS_NOT_SUPPORTED && n < c) || +			  n == IEEE80211_VHT_MCS_NOT_SUPPORTED)) { +			txmcs_cap &= ~(3 << 2*i); +			txmcs_cap |= (txmcs_n & (3 << 2*i)); +		} +	} +	vht_cap->vht_mcs.rx_mcs_map = cpu_to_le16(rxmcs_cap); +	vht_cap->vht_mcs.tx_mcs_map = cpu_to_le16(txmcs_cap); +} +  void  ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,  				    struct ieee80211_supported_band *sband, @@ -20,6 +118,8 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,  				    struct sta_info *sta)  {  	struct ieee80211_sta_vht_cap *vht_cap = &sta->sta.vht_cap; +	struct ieee80211_sta_vht_cap own_cap; +	u32 cap_info, i;  	memset(vht_cap, 0, sizeof(*vht_cap)); @@ -35,12 +135,122 @@ ieee80211_vht_cap_ie_to_sta_vht_cap(struct ieee80211_sub_if_data *sdata,  	vht_cap->vht_supported = true; -	vht_cap->cap = le32_to_cpu(vht_cap_ie->vht_cap_info); +	own_cap = sband->vht_cap; +	/* +	 * If user has specified capability overrides, take care +	 * of that if the station we're setting up is the AP that +	 * we advertised a restricted capability set to. Override +	 * our own capabilities and then use those below. +	 */ +	if (sdata->vif.type == NL80211_IFTYPE_STATION && +	    !test_sta_flag(sta, WLAN_STA_TDLS_PEER)) +		ieee80211_apply_vhtcap_overrides(sdata, &own_cap); + +	/* take some capabilities as-is */ +	cap_info = le32_to_cpu(vht_cap_ie->vht_cap_info); +	vht_cap->cap = cap_info; +	vht_cap->cap &= IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_3895 | +			IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_7991 | +			IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454 | +			IEEE80211_VHT_CAP_RXLDPC | +			IEEE80211_VHT_CAP_VHT_TXOP_PS | +			IEEE80211_VHT_CAP_HTC_VHT | +			IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_MASK | +			IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_UNSOL_MFB | +			IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB | +			IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN | +			IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN; + +	/* and some based on our own capabilities */ +	switch (own_cap.cap & IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK) { +	case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ: +		vht_cap->cap |= cap_info & +				IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160MHZ; +		break; +	case IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ: +		vht_cap->cap |= cap_info & +				IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK; +		break; +	default: +		/* nothing */ +		break; +	} + +	/* symmetric capabilities */ +	vht_cap->cap |= cap_info & own_cap.cap & +			(IEEE80211_VHT_CAP_SHORT_GI_80 | +			 IEEE80211_VHT_CAP_SHORT_GI_160); + +	/* remaining ones */ +	if (own_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE) { +		vht_cap->cap |= cap_info & +				(IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE | +				 IEEE80211_VHT_CAP_BEAMFORMER_ANTENNAS_MAX | +				 IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_MAX); +	} + +	if (own_cap.cap & IEEE80211_VHT_CAP_SU_BEAMFORMER_CAPABLE) +		vht_cap->cap |= cap_info & +				IEEE80211_VHT_CAP_SU_BEAMFORMEE_CAPABLE; + +	if (own_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE) +		vht_cap->cap |= cap_info & +				IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE; + +	if (own_cap.cap & IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE) +		vht_cap->cap |= cap_info & +				IEEE80211_VHT_CAP_MU_BEAMFORMER_CAPABLE; + +	if (own_cap.cap & IEEE80211_VHT_CAP_TXSTBC) +		vht_cap->cap |= cap_info & IEEE80211_VHT_CAP_RXSTBC_MASK; + +	if (own_cap.cap & IEEE80211_VHT_CAP_RXSTBC_MASK) +		vht_cap->cap |= cap_info & IEEE80211_VHT_CAP_TXSTBC;  	/* Copy peer MCS info, the driver might need them. */  	memcpy(&vht_cap->vht_mcs, &vht_cap_ie->supp_mcs,  	       sizeof(struct ieee80211_vht_mcs_info)); +	/* but also restrict MCSes */ +	for (i = 0; i < 8; i++) { +		u16 own_rx, own_tx, peer_rx, peer_tx; + +		own_rx = le16_to_cpu(own_cap.vht_mcs.rx_mcs_map); +		own_rx = (own_rx >> i * 2) & IEEE80211_VHT_MCS_NOT_SUPPORTED; + +		own_tx = le16_to_cpu(own_cap.vht_mcs.tx_mcs_map); +		own_tx = (own_tx >> i * 2) & IEEE80211_VHT_MCS_NOT_SUPPORTED; + +		peer_rx = le16_to_cpu(vht_cap->vht_mcs.rx_mcs_map); +		peer_rx = (peer_rx >> i * 2) & IEEE80211_VHT_MCS_NOT_SUPPORTED; + +		peer_tx = le16_to_cpu(vht_cap->vht_mcs.tx_mcs_map); +		peer_tx = (peer_tx >> i * 2) & IEEE80211_VHT_MCS_NOT_SUPPORTED; + +		if (peer_tx != IEEE80211_VHT_MCS_NOT_SUPPORTED) { +			if (own_rx == IEEE80211_VHT_MCS_NOT_SUPPORTED) +				peer_tx = IEEE80211_VHT_MCS_NOT_SUPPORTED; +			else if (own_rx < peer_tx) +				peer_tx = own_rx; +		} + +		if (peer_rx != IEEE80211_VHT_MCS_NOT_SUPPORTED) { +			if (own_tx == IEEE80211_VHT_MCS_NOT_SUPPORTED) +				peer_rx = IEEE80211_VHT_MCS_NOT_SUPPORTED; +			else if (own_tx < peer_rx) +				peer_rx = own_tx; +		} + +		vht_cap->vht_mcs.rx_mcs_map &= +			~cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << i * 2); +		vht_cap->vht_mcs.rx_mcs_map |= cpu_to_le16(peer_rx << i * 2); + +		vht_cap->vht_mcs.tx_mcs_map &= +			~cpu_to_le16(IEEE80211_VHT_MCS_NOT_SUPPORTED << i * 2); +		vht_cap->vht_mcs.tx_mcs_map |= cpu_to_le16(peer_tx << i * 2); +	} + +	/* finally set up the bandwidth */  	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:  |