Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a7c7fbff authored by Purushottam Kushwaha's avatar Purushottam Kushwaha Committed by Johannes Berg
Browse files

cfg80211: Add support to configure a beacon data rate



This allows an option to configure a single beacon tx rate for an AP.

Signed-off-by: default avatarPurushottam Kushwaha <pkushwah@qti.qualcomm.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent c13ed534
Loading
Loading
Loading
Loading
+14 −11
Original line number Original line Diff line number Diff line
@@ -676,6 +676,18 @@ struct cfg80211_acl_data {
	struct mac_address mac_addrs[];
	struct mac_address mac_addrs[];
};
};


/*
 * cfg80211_bitrate_mask - masks for bitrate control
 */
struct cfg80211_bitrate_mask {
	struct {
		u32 legacy;
		u8 ht_mcs[IEEE80211_HT_MCS_MASK_LEN];
		u16 vht_mcs[NL80211_VHT_NSS_MAX];
		enum nl80211_txrate_gi gi;
	} control[NUM_NL80211_BANDS];
};

/**
/**
 * struct cfg80211_ap_settings - AP configuration
 * struct cfg80211_ap_settings - AP configuration
 *
 *
@@ -700,6 +712,7 @@ struct cfg80211_acl_data {
 *	MAC address based access control
 *	MAC address based access control
 * @pbss: If set, start as a PCP instead of AP. Relevant for DMG
 * @pbss: If set, start as a PCP instead of AP. Relevant for DMG
 *	networks.
 *	networks.
 * @beacon_rate: masks for setting user configured beacon tx rate.
 */
 */
struct cfg80211_ap_settings {
struct cfg80211_ap_settings {
	struct cfg80211_chan_def chandef;
	struct cfg80211_chan_def chandef;
@@ -719,6 +732,7 @@ struct cfg80211_ap_settings {
	bool p2p_opp_ps;
	bool p2p_opp_ps;
	const struct cfg80211_acl_data *acl;
	const struct cfg80211_acl_data *acl;
	bool pbss;
	bool pbss;
	struct cfg80211_bitrate_mask beacon_rate;
};
};


/**
/**
@@ -2010,17 +2024,6 @@ enum wiphy_params_flags {
	WIPHY_PARAM_DYN_ACK		= 1 << 5,
	WIPHY_PARAM_DYN_ACK		= 1 << 5,
};
};


/*
 * cfg80211_bitrate_mask - masks for bitrate control
 */
struct cfg80211_bitrate_mask {
	struct {
		u32 legacy;
		u8 ht_mcs[IEEE80211_HT_MCS_MASK_LEN];
		u16 vht_mcs[NL80211_VHT_NSS_MAX];
		enum nl80211_txrate_gi gi;
	} control[NUM_NL80211_BANDS];
};
/**
/**
 * struct cfg80211_pmksa - PMK Security Association
 * struct cfg80211_pmksa - PMK Security Association
 *
 *
+288 −222
Original line number Original line Diff line number Diff line
@@ -3340,6 +3340,279 @@ static int nl80211_set_mac_acl(struct sk_buff *skb, struct genl_info *info)
	return err;
	return err;
}
}


static u32 rateset_to_mask(struct ieee80211_supported_band *sband,
			   u8 *rates, u8 rates_len)
{
	u8 i;
	u32 mask = 0;

	for (i = 0; i < rates_len; i++) {
		int rate = (rates[i] & 0x7f) * 5;
		int ridx;

		for (ridx = 0; ridx < sband->n_bitrates; ridx++) {
			struct ieee80211_rate *srate =
				&sband->bitrates[ridx];
			if (rate == srate->bitrate) {
				mask |= 1 << ridx;
				break;
			}
		}
		if (ridx == sband->n_bitrates)
			return 0; /* rate not found */
	}

	return mask;
}

static bool ht_rateset_to_mask(struct ieee80211_supported_band *sband,
			       u8 *rates, u8 rates_len,
			       u8 mcs[IEEE80211_HT_MCS_MASK_LEN])
{
	u8 i;

	memset(mcs, 0, IEEE80211_HT_MCS_MASK_LEN);

	for (i = 0; i < rates_len; i++) {
		int ridx, rbit;

		ridx = rates[i] / 8;
		rbit = BIT(rates[i] % 8);

		/* check validity */
		if ((ridx < 0) || (ridx >= IEEE80211_HT_MCS_MASK_LEN))
			return false;

		/* check availability */
		if (sband->ht_cap.mcs.rx_mask[ridx] & rbit)
			mcs[ridx] |= rbit;
		else
			return false;
	}

	return true;
}

static u16 vht_mcs_map_to_mcs_mask(u8 vht_mcs_map)
{
	u16 mcs_mask = 0;

	switch (vht_mcs_map) {
	case IEEE80211_VHT_MCS_NOT_SUPPORTED:
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_7:
		mcs_mask = 0x00FF;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_8:
		mcs_mask = 0x01FF;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_9:
		mcs_mask = 0x03FF;
		break;
	default:
		break;
	}

	return mcs_mask;
}

static void vht_build_mcs_mask(u16 vht_mcs_map,
			       u16 vht_mcs_mask[NL80211_VHT_NSS_MAX])
{
	u8 nss;

	for (nss = 0; nss < NL80211_VHT_NSS_MAX; nss++) {
		vht_mcs_mask[nss] = vht_mcs_map_to_mcs_mask(vht_mcs_map & 0x03);
		vht_mcs_map >>= 2;
	}
}

static bool vht_set_mcs_mask(struct ieee80211_supported_band *sband,
			     struct nl80211_txrate_vht *txrate,
			     u16 mcs[NL80211_VHT_NSS_MAX])
{
	u16 tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
	u16 tx_mcs_mask[NL80211_VHT_NSS_MAX] = {};
	u8 i;

	if (!sband->vht_cap.vht_supported)
		return false;

	memset(mcs, 0, sizeof(u16) * NL80211_VHT_NSS_MAX);

	/* Build vht_mcs_mask from VHT capabilities */
	vht_build_mcs_mask(tx_mcs_map, tx_mcs_mask);

	for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
		if ((tx_mcs_mask[i] & txrate->mcs[i]) == txrate->mcs[i])
			mcs[i] = txrate->mcs[i];
		else
			return false;
	}

	return true;
}

static const struct nla_policy nl80211_txattr_policy[NL80211_TXRATE_MAX + 1] = {
	[NL80211_TXRATE_LEGACY] = { .type = NLA_BINARY,
				    .len = NL80211_MAX_SUPP_RATES },
	[NL80211_TXRATE_HT] = { .type = NLA_BINARY,
				.len = NL80211_MAX_SUPP_HT_RATES },
	[NL80211_TXRATE_VHT] = { .len = sizeof(struct nl80211_txrate_vht)},
	[NL80211_TXRATE_GI] = { .type = NLA_U8 },
};

static int nl80211_parse_tx_bitrate_mask(struct genl_info *info,
					 struct cfg80211_bitrate_mask *mask)
{
	struct nlattr *tb[NL80211_TXRATE_MAX + 1];
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	int rem, i;
	struct nlattr *tx_rates;
	struct ieee80211_supported_band *sband;
	u16 vht_tx_mcs_map;

	memset(mask, 0, sizeof(*mask));
	/* Default to all rates enabled */
	for (i = 0; i < NUM_NL80211_BANDS; i++) {
		sband = rdev->wiphy.bands[i];

		if (!sband)
			continue;

		mask->control[i].legacy = (1 << sband->n_bitrates) - 1;
		memcpy(mask->control[i].ht_mcs,
		       sband->ht_cap.mcs.rx_mask,
		       sizeof(mask->control[i].ht_mcs));

		if (!sband->vht_cap.vht_supported)
			continue;

		vht_tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
		vht_build_mcs_mask(vht_tx_mcs_map, mask->control[i].vht_mcs);
	}

	/* if no rates are given set it back to the defaults */
	if (!info->attrs[NL80211_ATTR_TX_RATES])
		goto out;

	/* The nested attribute uses enum nl80211_band as the index. This maps
	 * directly to the enum nl80211_band values used in cfg80211.
	 */
	BUILD_BUG_ON(NL80211_MAX_SUPP_HT_RATES > IEEE80211_HT_MCS_MASK_LEN * 8);
	nla_for_each_nested(tx_rates, info->attrs[NL80211_ATTR_TX_RATES], rem) {
		enum nl80211_band band = nla_type(tx_rates);
		int err;

		if (band < 0 || band >= NUM_NL80211_BANDS)
			return -EINVAL;
		sband = rdev->wiphy.bands[band];
		if (sband == NULL)
			return -EINVAL;
		err = nla_parse(tb, NL80211_TXRATE_MAX, nla_data(tx_rates),
				nla_len(tx_rates), nl80211_txattr_policy);
		if (err)
			return err;
		if (tb[NL80211_TXRATE_LEGACY]) {
			mask->control[band].legacy = rateset_to_mask(
				sband,
				nla_data(tb[NL80211_TXRATE_LEGACY]),
				nla_len(tb[NL80211_TXRATE_LEGACY]));
			if ((mask->control[band].legacy == 0) &&
			    nla_len(tb[NL80211_TXRATE_LEGACY]))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_HT]) {
			if (!ht_rateset_to_mask(
					sband,
					nla_data(tb[NL80211_TXRATE_HT]),
					nla_len(tb[NL80211_TXRATE_HT]),
					mask->control[band].ht_mcs))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_VHT]) {
			if (!vht_set_mcs_mask(
					sband,
					nla_data(tb[NL80211_TXRATE_VHT]),
					mask->control[band].vht_mcs))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_GI]) {
			mask->control[band].gi =
				nla_get_u8(tb[NL80211_TXRATE_GI]);
			if (mask->control[band].gi > NL80211_TXRATE_FORCE_LGI)
				return -EINVAL;
		}

		if (mask->control[band].legacy == 0) {
			/* don't allow empty legacy rates if HT or VHT
			 * are not even supported.
			 */
			if (!(rdev->wiphy.bands[band]->ht_cap.ht_supported ||
			      rdev->wiphy.bands[band]->vht_cap.vht_supported))
				return -EINVAL;

			for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++)
				if (mask->control[band].ht_mcs[i])
					goto out;

			for (i = 0; i < NL80211_VHT_NSS_MAX; i++)
				if (mask->control[band].vht_mcs[i])
					goto out;

			/* legacy and mcs rates may not be both empty */
			return -EINVAL;
		}
	}

out:
	return 0;
}

static int validate_beacon_tx_rate(struct cfg80211_ap_settings *params)
{
	u32 rate, count_ht, count_vht, i;
	enum nl80211_band band;

	band = params->chandef.chan->band;
	rate = params->beacon_rate.control[band].legacy;

	/* Allow only one rate */
	if (hweight32(rate) > 1)
		return -EINVAL;

	count_ht = 0;
	for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++) {
		if (hweight8(params->beacon_rate.control[band].ht_mcs[i]) > 1) {
			return -EINVAL;
		} else if (params->beacon_rate.control[band].ht_mcs[i]) {
			count_ht++;
			if (count_ht > 1)
				return -EINVAL;
		}
		if (count_ht && rate)
			return -EINVAL;
	}

	count_vht = 0;
	for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
		if (hweight16(params->beacon_rate.control[band].vht_mcs[i]) > 1) {
			return -EINVAL;
		} else if (params->beacon_rate.control[band].vht_mcs[i]) {
			count_vht++;
			if (count_vht > 1)
				return -EINVAL;
		}
		if (count_vht && rate)
			return -EINVAL;
	}

	if ((count_ht && count_vht) || (!rate && !count_ht && !count_vht))
		return -EINVAL;

	return 0;
}

static int nl80211_parse_beacon(struct nlattr *attrs[],
static int nl80211_parse_beacon(struct nlattr *attrs[],
				struct cfg80211_beacon_data *bcn)
				struct cfg80211_beacon_data *bcn)
{
{
@@ -3569,6 +3842,16 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info)
					   wdev->iftype))
					   wdev->iftype))
		return -EINVAL;
		return -EINVAL;


	if (info->attrs[NL80211_ATTR_TX_RATES]) {
		err = nl80211_parse_tx_bitrate_mask(info, &params.beacon_rate);
		if (err)
			return err;

		err = validate_beacon_tx_rate(&params);
		if (err)
			return err;
	}

	if (info->attrs[NL80211_ATTR_SMPS_MODE]) {
	if (info->attrs[NL80211_ATTR_SMPS_MODE]) {
		params.smps_mode =
		params.smps_mode =
			nla_get_u8(info->attrs[NL80211_ATTR_SMPS_MODE]);
			nla_get_u8(info->attrs[NL80211_ATTR_SMPS_MODE]);
@@ -8641,238 +8924,21 @@ static int nl80211_cancel_remain_on_channel(struct sk_buff *skb,
	return rdev_cancel_remain_on_channel(rdev, wdev, cookie);
	return rdev_cancel_remain_on_channel(rdev, wdev, cookie);
}
}


static u32 rateset_to_mask(struct ieee80211_supported_band *sband,
			   u8 *rates, u8 rates_len)
{
	u8 i;
	u32 mask = 0;

	for (i = 0; i < rates_len; i++) {
		int rate = (rates[i] & 0x7f) * 5;
		int ridx;

		for (ridx = 0; ridx < sband->n_bitrates; ridx++) {
			struct ieee80211_rate *srate =
				&sband->bitrates[ridx];
			if (rate == srate->bitrate) {
				mask |= 1 << ridx;
				break;
			}
		}
		if (ridx == sband->n_bitrates)
			return 0; /* rate not found */
	}

	return mask;
}

static bool ht_rateset_to_mask(struct ieee80211_supported_band *sband,
			       u8 *rates, u8 rates_len,
			       u8 mcs[IEEE80211_HT_MCS_MASK_LEN])
{
	u8 i;

	memset(mcs, 0, IEEE80211_HT_MCS_MASK_LEN);

	for (i = 0; i < rates_len; i++) {
		int ridx, rbit;

		ridx = rates[i] / 8;
		rbit = BIT(rates[i] % 8);

		/* check validity */
		if ((ridx < 0) || (ridx >= IEEE80211_HT_MCS_MASK_LEN))
			return false;

		/* check availability */
		if (sband->ht_cap.mcs.rx_mask[ridx] & rbit)
			mcs[ridx] |= rbit;
		else
			return false;
	}

	return true;
}

static u16 vht_mcs_map_to_mcs_mask(u8 vht_mcs_map)
{
	u16 mcs_mask = 0;

	switch (vht_mcs_map) {
	case IEEE80211_VHT_MCS_NOT_SUPPORTED:
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_7:
		mcs_mask = 0x00FF;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_8:
		mcs_mask = 0x01FF;
		break;
	case IEEE80211_VHT_MCS_SUPPORT_0_9:
		mcs_mask = 0x03FF;
		break;
	default:
		break;
	}

	return mcs_mask;
}

static void vht_build_mcs_mask(u16 vht_mcs_map,
			       u16 vht_mcs_mask[NL80211_VHT_NSS_MAX])
{
	u8 nss;

	for (nss = 0; nss < NL80211_VHT_NSS_MAX; nss++) {
		vht_mcs_mask[nss] = vht_mcs_map_to_mcs_mask(vht_mcs_map & 0x03);
		vht_mcs_map >>= 2;
	}
}

static bool vht_set_mcs_mask(struct ieee80211_supported_band *sband,
			     struct nl80211_txrate_vht *txrate,
			     u16 mcs[NL80211_VHT_NSS_MAX])
{
	u16 tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
	u16 tx_mcs_mask[NL80211_VHT_NSS_MAX] = {};
	u8 i;

	if (!sband->vht_cap.vht_supported)
		return false;

	memset(mcs, 0, sizeof(u16) * NL80211_VHT_NSS_MAX);

	/* Build vht_mcs_mask from VHT capabilities */
	vht_build_mcs_mask(tx_mcs_map, tx_mcs_mask);

	for (i = 0; i < NL80211_VHT_NSS_MAX; i++) {
		if ((tx_mcs_mask[i] & txrate->mcs[i]) == txrate->mcs[i])
			mcs[i] = txrate->mcs[i];
		else
			return false;
	}

	return true;
}

static const struct nla_policy nl80211_txattr_policy[NL80211_TXRATE_MAX + 1] = {
	[NL80211_TXRATE_LEGACY] = { .type = NLA_BINARY,
				    .len = NL80211_MAX_SUPP_RATES },
	[NL80211_TXRATE_HT] = { .type = NLA_BINARY,
				.len = NL80211_MAX_SUPP_HT_RATES },
	[NL80211_TXRATE_VHT] = { .len = sizeof(struct nl80211_txrate_vht)},
	[NL80211_TXRATE_GI] = { .type = NLA_U8 },
};

static int nl80211_set_tx_bitrate_mask(struct sk_buff *skb,
static int nl80211_set_tx_bitrate_mask(struct sk_buff *skb,
				       struct genl_info *info)
				       struct genl_info *info)
{
{
	struct nlattr *tb[NL80211_TXRATE_MAX + 1];
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct cfg80211_bitrate_mask mask;
	struct cfg80211_bitrate_mask mask;
	int rem, i;
	struct cfg80211_registered_device *rdev = info->user_ptr[0];
	struct net_device *dev = info->user_ptr[1];
	struct net_device *dev = info->user_ptr[1];
	struct nlattr *tx_rates;
	int err;
	struct ieee80211_supported_band *sband;
	u16 vht_tx_mcs_map;


	if (!rdev->ops->set_bitrate_mask)
	if (!rdev->ops->set_bitrate_mask)
		return -EOPNOTSUPP;
		return -EOPNOTSUPP;


	memset(&mask, 0, sizeof(mask));
	err = nl80211_parse_tx_bitrate_mask(info, &mask);
	/* Default to all rates enabled */
	for (i = 0; i < NUM_NL80211_BANDS; i++) {
		sband = rdev->wiphy.bands[i];

		if (!sband)
			continue;

		mask.control[i].legacy = (1 << sband->n_bitrates) - 1;
		memcpy(mask.control[i].ht_mcs,
		       sband->ht_cap.mcs.rx_mask,
		       sizeof(mask.control[i].ht_mcs));

		if (!sband->vht_cap.vht_supported)
			continue;

		vht_tx_mcs_map = le16_to_cpu(sband->vht_cap.vht_mcs.tx_mcs_map);
		vht_build_mcs_mask(vht_tx_mcs_map, mask.control[i].vht_mcs);
	}

	/* if no rates are given set it back to the defaults */
	if (!info->attrs[NL80211_ATTR_TX_RATES])
		goto out;

	/*
	 * The nested attribute uses enum nl80211_band as the index. This maps
	 * directly to the enum nl80211_band values used in cfg80211.
	 */
	BUILD_BUG_ON(NL80211_MAX_SUPP_HT_RATES > IEEE80211_HT_MCS_MASK_LEN * 8);
	nla_for_each_nested(tx_rates, info->attrs[NL80211_ATTR_TX_RATES], rem) {
		enum nl80211_band band = nla_type(tx_rates);
		int err;

		if (band < 0 || band >= NUM_NL80211_BANDS)
			return -EINVAL;
		sband = rdev->wiphy.bands[band];
		if (sband == NULL)
			return -EINVAL;
		err = nla_parse(tb, NL80211_TXRATE_MAX, nla_data(tx_rates),
				nla_len(tx_rates), nl80211_txattr_policy);
	if (err)
	if (err)
		return err;
		return err;
		if (tb[NL80211_TXRATE_LEGACY]) {
			mask.control[band].legacy = rateset_to_mask(
				sband,
				nla_data(tb[NL80211_TXRATE_LEGACY]),
				nla_len(tb[NL80211_TXRATE_LEGACY]));
			if ((mask.control[band].legacy == 0) &&
			    nla_len(tb[NL80211_TXRATE_LEGACY]))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_HT]) {
			if (!ht_rateset_to_mask(
					sband,
					nla_data(tb[NL80211_TXRATE_HT]),
					nla_len(tb[NL80211_TXRATE_HT]),
					mask.control[band].ht_mcs))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_VHT]) {
			if (!vht_set_mcs_mask(
					sband,
					nla_data(tb[NL80211_TXRATE_VHT]),
					mask.control[band].vht_mcs))
				return -EINVAL;
		}
		if (tb[NL80211_TXRATE_GI]) {
			mask.control[band].gi =
				nla_get_u8(tb[NL80211_TXRATE_GI]);
			if (mask.control[band].gi > NL80211_TXRATE_FORCE_LGI)
				return -EINVAL;
		}

		if (mask.control[band].legacy == 0) {
			/* don't allow empty legacy rates if HT or VHT
			 * are not even supported.
			 */
			if (!(rdev->wiphy.bands[band]->ht_cap.ht_supported ||
			      rdev->wiphy.bands[band]->vht_cap.vht_supported))
				return -EINVAL;

			for (i = 0; i < IEEE80211_HT_MCS_MASK_LEN; i++)
				if (mask.control[band].ht_mcs[i])
					goto out;

			for (i = 0; i < NL80211_VHT_NSS_MAX; i++)
				if (mask.control[band].vht_mcs[i])
					goto out;


			/* legacy and mcs rates may not be both empty */
			return -EINVAL;
		}
	}

out:
	return rdev_set_bitrate_mask(rdev, dev, NULL, &mask);
	return rdev_set_bitrate_mask(rdev, dev, NULL, &mask);
}
}