diff mbox

[V2,3/3] cfg80211: support ieee80211-freq-limit DT property

Message ID 20170102132747.3491-3-zajec5@gmail.com (mailing list archive)
State Changes Requested
Delegated to: Johannes Berg
Headers show

Commit Message

Rafał Miłecki Jan. 2, 2017, 1:27 p.m. UTC
From: Rafał Miłecki <rafal@milecki.pl>

It allows specifying hardware limitations of supported channels. This
may be useful for specifying single band devices or devices that support
only some part of the whole band.
This can be useful for some tri-band routers that have separated radios
for lower and higher part of 5 GHz band.

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
---
V2: Put main code in core.c as it isn't strictly part of regulatory - pointed
    by Arend.
    Update to support ieee80211-freq-limit (new property).
---
 include/net/cfg80211.h |   6 +++
 net/wireless/core.c    | 110 +++++++++++++++++++++++++++++++++++++++++++++++++
 net/wireless/core.h    |   2 +
 net/wireless/reg.c     |   8 +++-
 net/wireless/reg.h     |   2 +
 5 files changed, 126 insertions(+), 2 deletions(-)

Comments

Johannes Berg Jan. 2, 2017, 2:04 p.m. UTC | #1
> +	prop = of_find_property(np, "ieee80211-freq-limit", &i);
> +	if (!prop)
> +		return 0;
> +
> +	i = i / sizeof(u32);

What if it's not even a multiple of sizeof(u32)? Shouldn't you check
that, in case it's completely bogus?

> +	if (i % 2) {
> +		dev_err(dev, "ieee80211-freq-limit wrong value");

say "wrong format" perhaps? we don't care (much) above the values.

> +		return -EPROTO;
> +	}
> +	wiphy->n_freq_limits = i / 2;

I don't like this use of the 'i' variable - use something like
'len[gth]' instead?

> +	wiphy->freq_limits = kzalloc(wiphy->n_freq_limits *
> sizeof(*wiphy->freq_limits),
> +				     GFP_KERNEL);
> +	if (!wiphy->freq_limits) {
> +		err = -ENOMEM;
> +		goto out;
> +	}
> +
> +	p = NULL;
> +	for (i = 0; i < wiphy->n_freq_limits; i++) {
> +		struct ieee80211_freq_range *limit = &wiphy-
> >freq_limits[i];
> +
> +		p = of_prop_next_u32(prop, p, &limit-
> >start_freq_khz);
> +		if (!p) {
> +			err = -EINVAL;
> +			goto out;
> +		}
> +
> +		p = of_prop_next_u32(prop, p, &limit->end_freq_khz);
> +		if (!p) {
> +			err = -EINVAL;
> +			goto out;
> +		}
> +	}

You should also reject nonsense like empty ranges, or ranges with a
higher beginning than end, etc. I think


put

	return 0;

here.

> +out:
> +	if (err) {

then you can make that a pure "error" label and remove the "if (err)"
check.

> +void wiphy_freq_limits_apply(struct wiphy *wiphy)

I don't see any point in having this here rather than in reg.c, which
is the only user.

> +			if (!wiphy_freq_limits_valid_chan(wiphy,
> chan)) {
> +				pr_debug("Disabling freq %d MHz as
> it's out of OF limits\n",
> +					 chan->center_freq);
> +				chan->flags |= IEEE80211_CHAN_DISABLED;

This seems wrong.

The sband and channels can be static data and be shared across
different wiphys for the same driver. If the driver has custom
regulatory etc. then this can't work, but that's up to the driver. OF
data is handled here though, so if OF data for one device disables a
channel, this would also cause the channel to be disabled for another
device, if the data is shared.

To avoid this, you'd have to have drivers that never share it - but you
can't really guarantee that at this level.

In order to fix that, you probably need to memdup the sband/channel
structs during wiphy registration. Then, if you set it up the right
way, you can actually simply edit them according to the OF data
directly there, so that *orig_flags* (rather than just flags) already
gets the DISABLED bit - and that allows you to get rid of the reg.c
hooks entirely since it'll look the same to reg.c independent of the
driver or the OF stuff doing this.


That can actually be inefficient though, since drivers may already have
copied the channel data somewhere and then you copy it again since you
can't know.

Perhaps a better approach would be to not combine this with wiphy
registration, but require drivers that may use this to call a new
helper function *before* wiphy registration (and *after* calling
set_wiphy_dev()), like e.g.

   ieee80211_read_of_data(wiphy);

You can then also make this an inline when OF is not configured in
(something which you haven't really taken into account now, you should
have used dev_of_node() too instead of dev->of_node)

Yes, this would mean that it doesn't automatically get applied to
arbitrary drivers, but it seems unlikely that arbitrary drivers like
realtek USB would suddenly get OF node entries ... so that's not
necessarily a bad thing.

In the documentation for this function you could then document that it
will modify flags, and as such must not be called when the sband and
channel data is shared, getting rid of the waste/complexity of the copy
you otherwise have to make in cfg80211.

johannes
Rafał Miłecki Jan. 2, 2017, 2:09 p.m. UTC | #2
On 2 January 2017 at 15:04, Johannes Berg <johannes@sipsolutions.net> wrote:
>> +     prop = of_find_property(np, "ieee80211-freq-limit", &i);
>> +     if (!prop)
>> +             return 0;
>> +
>> +     i = i / sizeof(u32);
>
> What if it's not even a multiple of sizeof(u32)? Shouldn't you check
> that, in case it's completely bogus?
>
>> +     if (i % 2) {
>> +             dev_err(dev, "ieee80211-freq-limit wrong value");
>
> say "wrong format" perhaps? we don't care (much) above the values.
>
>> +             return -EPROTO;
>> +     }
>> +     wiphy->n_freq_limits = i / 2;
>
> I don't like this use of the 'i' variable - use something like
> 'len[gth]' instead?
>
>> +     wiphy->freq_limits = kzalloc(wiphy->n_freq_limits *
>> sizeof(*wiphy->freq_limits),
>> +                                  GFP_KERNEL);
>> +     if (!wiphy->freq_limits) {
>> +             err = -ENOMEM;
>> +             goto out;
>> +     }
>> +
>> +     p = NULL;
>> +     for (i = 0; i < wiphy->n_freq_limits; i++) {
>> +             struct ieee80211_freq_range *limit = &wiphy-
>> >freq_limits[i];
>> +
>> +             p = of_prop_next_u32(prop, p, &limit-
>> >start_freq_khz);
>> +             if (!p) {
>> +                     err = -EINVAL;
>> +                     goto out;
>> +             }
>> +
>> +             p = of_prop_next_u32(prop, p, &limit->end_freq_khz);
>> +             if (!p) {
>> +                     err = -EINVAL;
>> +                     goto out;
>> +             }
>> +     }
>
> You should also reject nonsense like empty ranges, or ranges with a
> higher beginning than end, etc. I think
>
>
> put
>
>         return 0;
>
> here.
>
>> +out:
>> +     if (err) {
>
> then you can make that a pure "error" label and remove the "if (err)"
> check.
>
>> +void wiphy_freq_limits_apply(struct wiphy *wiphy)
>
> I don't see any point in having this here rather than in reg.c, which
> is the only user.
>
>> +                     if (!wiphy_freq_limits_valid_chan(wiphy,
>> chan)) {
>> +                             pr_debug("Disabling freq %d MHz as
>> it's out of OF limits\n",
>> +                                      chan->center_freq);
>> +                             chan->flags |= IEEE80211_CHAN_DISABLED;
>
> This seems wrong.
>
> The sband and channels can be static data and be shared across
> different wiphys for the same driver. If the driver has custom
> regulatory etc. then this can't work, but that's up to the driver. OF
> data is handled here though, so if OF data for one device disables a
> channel, this would also cause the channel to be disabled for another
> device, if the data is shared.
>
> To avoid this, you'd have to have drivers that never share it - but you
> can't really guarantee that at this level.
>
> In order to fix that, you probably need to memdup the sband/channel
> structs during wiphy registration. Then, if you set it up the right
> way, you can actually simply edit them according to the OF data
> directly there, so that *orig_flags* (rather than just flags) already
> gets the DISABLED bit - and that allows you to get rid of the reg.c
> hooks entirely since it'll look the same to reg.c independent of the
> driver or the OF stuff doing this.
>
>
> That can actually be inefficient though, since drivers may already have
> copied the channel data somewhere and then you copy it again since you
> can't know.
>
> Perhaps a better approach would be to not combine this with wiphy
> registration, but require drivers that may use this to call a new
> helper function *before* wiphy registration (and *after* calling
> set_wiphy_dev()), like e.g.
>
>    ieee80211_read_of_data(wiphy);
>
> You can then also make this an inline when OF is not configured in
> (something which you haven't really taken into account now, you should
> have used dev_of_node() too instead of dev->of_node)
>
> Yes, this would mean that it doesn't automatically get applied to
> arbitrary drivers, but it seems unlikely that arbitrary drivers like
> realtek USB would suddenly get OF node entries ... so that's not
> necessarily a bad thing.
>
> In the documentation for this function you could then document that it
> will modify flags, and as such must not be called when the sband and
> channel data is shared, getting rid of the waste/complexity of the copy
> you otherwise have to make in cfg80211.

Thank you, I appreciate your review a lot, I'll work on this according
to your comments!
Rafał Miłecki Jan. 2, 2017, 3:05 p.m. UTC | #3
On 2 January 2017 at 15:04, Johannes Berg <johannes@sipsolutions.net> wrote:
> Perhaps a better approach would be to not combine this with wiphy
> registration, but require drivers that may use this to call a new
> helper function *before* wiphy registration (and *after* calling
> set_wiphy_dev()), like e.g.
>
>    ieee80211_read_of_data(wiphy);
>
> (...)
>
> Yes, this would mean that it doesn't automatically get applied to
> arbitrary drivers, but it seems unlikely that arbitrary drivers like
> realtek USB would suddenly get OF node entries ... so that's not
> necessarily a bad thing.
>
> In the documentation for this function you could then document that it
> will modify flags, and as such must not be called when the sband and
> channel data is shared, getting rid of the waste/complexity of the copy
> you otherwise have to make in cfg80211.

I just think it may be better to stick to something like
ieee80211_read_of_freq_limits
or
wiphy_read_of_freq_limits

As you noted this function will be a bit specific because of modifying
(possibly shared) band channels. At some point we may want to add more
helpers for other OF properties which won't have extra requirements
for driver code. Some drivers may want to use them while not necessary
risking have shared band channels modified.
Johannes Berg Jan. 2, 2017, 3:10 p.m. UTC | #4
On Mon, 2017-01-02 at 16:05 +0100, Rafał Miłecki wrote:
> On 2 January 2017 at 15:04, Johannes Berg <johannes@sipsolutions.net>
> wrote:
> > Perhaps a better approach would be to not combine this with wiphy
> > registration, but require drivers that may use this to call a new
> > helper function *before* wiphy registration (and *after* calling
> > set_wiphy_dev()), like e.g.
> > 
> >    ieee80211_read_of_data(wiphy);
> > 
> > (...)

> I just think it may be better to stick to something like
> ieee80211_read_of_freq_limits
> or
> wiphy_read_of_freq_limits

I have no objection to that.

> As you noted this function will be a bit specific because of
> modifying (possibly shared) band channels. At some point we may want
> to add more helpers for other OF properties which won't have extra
> requirements for driver code. Some drivers may want to use them while
> not necessary risking have shared band channels modified.

That makes sense, although at that time we might still wish to have a
common "read it all" with the combined requirements. But we can cross
that bridge when we get to it.

johannes
diff mbox

Patch

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index e952cca..6609c39 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3515,6 +3515,9 @@  struct wiphy_iftype_ext_capab {
  *	attribute indices defined in &enum nl80211_bss_select_attr.
  *
  * @cookie_counter: unique generic cookie counter, used to identify objects.
+ *
+ * @n_freq_limits: number of frequency limits
+ * @freq_limits: array of extra frequency limits
  */
 struct wiphy {
 	/* assign these fields before you register the wiphy */
@@ -3646,6 +3649,9 @@  struct wiphy {
 
 	u64 cookie_counter;
 
+	unsigned int n_freq_limits;
+	struct ieee80211_freq_range *freq_limits;
+
 	char priv[0] __aligned(NETDEV_ALIGN);
 };
 
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 398922a..c2a5c81 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -87,6 +87,113 @@  struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx)
 	return &rdev->wiphy;
 }
 
+static int wiphy_freq_limits_init(struct wiphy *wiphy)
+{
+	struct device *dev = wiphy_dev(wiphy);
+	struct device_node *np;
+	struct property *prop;
+	const __be32 *p;
+	int i;
+	int err = 0;
+
+	if (wiphy->n_freq_limits)
+		return 0;
+
+	if (!dev)
+		return 0;
+	np = dev->of_node;
+	if (!np)
+		return 0;
+
+	prop = of_find_property(np, "ieee80211-freq-limit", &i);
+	if (!prop)
+		return 0;
+
+	i = i / sizeof(u32);
+	if (i % 2) {
+		dev_err(dev, "ieee80211-freq-limit wrong value");
+		return -EPROTO;
+	}
+	wiphy->n_freq_limits = i / 2;
+
+	wiphy->freq_limits = kzalloc(wiphy->n_freq_limits * sizeof(*wiphy->freq_limits),
+				     GFP_KERNEL);
+	if (!wiphy->freq_limits) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	p = NULL;
+	for (i = 0; i < wiphy->n_freq_limits; i++) {
+		struct ieee80211_freq_range *limit = &wiphy->freq_limits[i];
+
+		p = of_prop_next_u32(prop, p, &limit->start_freq_khz);
+		if (!p) {
+			err = -EINVAL;
+			goto out;
+		}
+
+		p = of_prop_next_u32(prop, p, &limit->end_freq_khz);
+		if (!p) {
+			err = -EINVAL;
+			goto out;
+		}
+	}
+
+out:
+	if (err) {
+		dev_err(dev, "Failed to get limits: %d\n", err);
+		kfree(wiphy->freq_limits);
+		wiphy->n_freq_limits = 0;
+	}
+	return err;
+}
+
+static bool wiphy_freq_limits_valid_chan(struct wiphy *wiphy,
+					 struct ieee80211_channel *chan)
+{
+	u32 bw = MHZ_TO_KHZ(20);
+	int i;
+
+	for (i = 0; i < wiphy->n_freq_limits; i++) {
+		struct ieee80211_freq_range *limit = &wiphy->freq_limits[i];
+
+		if (reg_does_bw_fit(limit, MHZ_TO_KHZ(chan->center_freq), bw))
+			return true;
+	}
+
+	return false;
+}
+
+void wiphy_freq_limits_apply(struct wiphy *wiphy)
+{
+	enum nl80211_band band;
+	int i;
+
+	if (!wiphy->n_freq_limits)
+		return;
+
+	for (band = 0; band < NUM_NL80211_BANDS; band++) {
+		struct ieee80211_supported_band *sband = wiphy->bands[band];
+
+		if (!sband)
+			continue;
+
+		for (i = 0; i < sband->n_channels; i++) {
+			struct ieee80211_channel *chan = &sband->channels[i];
+
+			if (chan->flags & IEEE80211_CHAN_DISABLED)
+				continue;
+
+			if (!wiphy_freq_limits_valid_chan(wiphy, chan)) {
+				pr_debug("Disabling freq %d MHz as it's out of OF limits\n",
+					 chan->center_freq);
+				chan->flags |= IEEE80211_CHAN_DISABLED;
+			}
+		}
+	}
+}
+
 static int cfg80211_dev_check_name(struct cfg80211_registered_device *rdev,
 				   const char *newname)
 {
@@ -481,6 +588,8 @@  struct wiphy *wiphy_new_nm(struct device *dev, const struct cfg80211_ops *ops,
 
 	init_waitqueue_head(&rdev->dev_wait);
 
+	wiphy_freq_limits_init(&rdev->wiphy);
+
 	/*
 	 * Initialize wiphy parameters to IEEE 802.11 MIB default values.
 	 * Fragmentation and RTS threshold are disabled by default with the
@@ -942,6 +1051,7 @@  void cfg80211_dev_free(struct cfg80211_registered_device *rdev)
 
 void wiphy_free(struct wiphy *wiphy)
 {
+	kfree(wiphy->freq_limits);
 	put_device(&wiphy->dev);
 }
 EXPORT_SYMBOL(wiphy_free);
diff --git a/net/wireless/core.h b/net/wireless/core.h
index af6e023..ce94f82 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -271,6 +271,8 @@  struct cfg80211_iface_destroy {
 	u32 nlportid;
 };
 
+void wiphy_freq_limits_apply(struct wiphy *wiphy);
+
 void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
 
 /* free object */
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 5dbac37..cb5a264 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -748,8 +748,8 @@  static bool is_valid_rd(const struct ieee80211_regdomain *rd)
 	return true;
 }
 
-static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range,
-			    u32 center_freq_khz, u32 bw_khz)
+bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range,
+		     u32 center_freq_khz, u32 bw_khz)
 {
 	u32 start_freq_khz, end_freq_khz;
 
@@ -1693,6 +1693,8 @@  static void wiphy_update_regulatory(struct wiphy *wiphy,
 	for (band = 0; band < NUM_NL80211_BANDS; band++)
 		handle_band(wiphy, initiator, wiphy->bands[band]);
 
+	wiphy_freq_limits_apply(wiphy);
+
 	reg_process_beacons(wiphy);
 	reg_process_ht_flags(wiphy);
 	reg_call_notifier(wiphy, lr);
@@ -1800,6 +1802,8 @@  void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
 		bands_set++;
 	}
 
+	wiphy_freq_limits_apply(wiphy);
+
 	/*
 	 * no point in calling this if it won't have any effect
 	 * on your device's supported bands.
diff --git a/net/wireless/reg.h b/net/wireless/reg.h
index f6ced31..90eddc3 100644
--- a/net/wireless/reg.h
+++ b/net/wireless/reg.h
@@ -25,6 +25,8 @@  extern const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
 
 bool reg_is_valid_request(const char *alpha2);
 bool is_world_regdom(const char *alpha2);
+bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range,
+		     u32 center_freq_khz, u32 bw_khz);
 bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region);
 enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy);