diff mbox

[4/5] cfg80211: allow wiphy specific regdomain management

Message ID 1414046257-22184-5-git-send-email-arik@wizery.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Arik Nemtsov Oct. 23, 2014, 6:37 a.m. UTC
From: Jonathan Doron <jond@wizery.com>

Add a new regulatory flag that allows a driver to manage regdomain
changes/updates for its own wiphy.
In this case the regdomain is local to the driver, and it does not use
the shared cfg80211 regdomain. It also implies that the driver does not
wish to get regulatory updates generated by other wiphys or by usermode.

A new API lets the driver send a complete regdomain, to be applied on
its wiphy only.

After a wiphy-specific regdomain change takes place, usermode will get
a new type of change notification. The regulatory core also takes care
enforce regulatory restrictions, in case some interfaces are on
forbidden channels.

Signed-off-by: Jonathan Doron <jonathanx.doron@intel.com>
Signed-off-by: Arik Nemtsov <arikx.nemtsov@intel.com>
---
 include/net/cfg80211.h       | 14 ++++++++
 include/net/regulatory.h     |  9 +++++
 include/uapi/linux/nl80211.h |  5 +++
 net/wireless/core.c          |  7 ++++
 net/wireless/nl80211.c       | 80 ++++++++++++++++++++++++++++++++++----------
 net/wireless/nl80211.h       |  1 +
 net/wireless/reg.c           | 59 ++++++++++++++++++++++++++++++++
 7 files changed, 157 insertions(+), 18 deletions(-)
diff mbox

Patch

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 77aa805..7f2c595 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3685,6 +3685,20 @@  const u8 *cfg80211_find_vendor_ie(unsigned int oui, u8 oui_type,
 int regulatory_hint(struct wiphy *wiphy, const char *alpha2);
 
 /**
+ * regulatory_set_wiphy_regd_rtnl - set regdom info for self managed drivers
+ * @wiphy: the wireless device we want to process the regulatory domain on
+ * @rd: the regulatory domain informatoin to use for this wiphy
+ *
+ * Set the regulatory domain information for self managed drivers, only they
+ * can use this function.
+ * This function requires the caller to hold the rtnl_lock.
+ *
+ * Return: 0 on success. -EINVAL, -EPERM
+ */
+int regulatory_set_wiphy_regd_rtnl(struct wiphy *wiphy,
+				   struct ieee80211_regdomain *rd);
+
+/**
  * wiphy_apply_custom_regulatory - apply a custom driver regulatory domain
  * @wiphy: the wireless device we want to process the regulatory domain on
  * @regd: the custom regulatory domain to use for this wiphy
diff --git a/include/net/regulatory.h b/include/net/regulatory.h
index 701177c..42345f4 100644
--- a/include/net/regulatory.h
+++ b/include/net/regulatory.h
@@ -141,6 +141,14 @@  struct regulatory_request {
  *	change, the interfaces are given a grace period to disconnect or move
  *	to an allowed channels. Interfaces on forbidden channels are forcibly
  *	disconnected.
+ * @REGULATORY_WIPHY_SELF_MANAGED: for devices that employ wiphy-specific
+ *	regdom management. These devices will ignore all regdom changes not
+ *	originating from their own wiphy. This flag is incompatible with the
+ *	flags: %REGULATORY_CUSTOM_REG, %REGULATORY_STRICT_REG,
+ *	%REGULATORY_COUNTRY_IE_FOLLOW_POWER, %REGULATORY_COUNTRY_IE_IGNORE and
+ *	%REGULATORY_DISABLE_BEACON_HINTS. Mixing any of the above flags with
+ *	this flag will result in a failure to register the wiphy. This flag
+ *	implies %REGULATORY_DISABLE_BEACON_HINTS.
  */
 enum ieee80211_regulatory_flags {
 	REGULATORY_CUSTOM_REG			= BIT(0),
@@ -150,6 +158,7 @@  enum ieee80211_regulatory_flags {
 	REGULATORY_COUNTRY_IE_IGNORE		= BIT(4),
 	REGULATORY_ENABLE_RELAX_NO_IR           = BIT(5),
 	REGULATORY_ENFORCE_CHANNELS             = BIT(6),
+	REGULATORY_WIPHY_SELF_MANAGED		= BIT(7),
 };
 
 struct ieee80211_freq_range {
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index 1970d29..4c8be9e 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -746,6 +746,9 @@ 
  *	destination %NL80211_ATTR_MAC on the interface identified by
  *	%NL80211_ATTR_IFINDEX.
  *
+ * @NL80211_CMD_WIPHY_REG_CHANGE: Similar to %NL80211_CMD_REG_CHANGE, but used
+ *	for indicating changes for devices with wiphy-specific regdom management
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -922,6 +925,8 @@  enum nl80211_commands {
 
 	NL80211_CMD_GET_MPP,
 
+	NL80211_CMD_WIPHY_REG_CHANGE,
+
 	/* add new commands above here */
 
 	/* used to define NL80211_CMD_MAX below */
diff --git a/net/wireless/core.c b/net/wireless/core.c
index f52a4cd..22f4b08 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -503,6 +503,13 @@  int wiphy_register(struct wiphy *wiphy)
 		    !wiphy->wowlan->tcp))
 		return -EINVAL;
 #endif
+	if (WARN_ON((wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) &&
+		    (wiphy->regulatory_flags &
+		     (REGULATORY_CUSTOM_REG | REGULATORY_STRICT_REG |
+		      REGULATORY_COUNTRY_IE_FOLLOW_POWER |
+		      REGULATORY_COUNTRY_IE_IGNORE |
+		      REGULATORY_DISABLE_BEACON_HINTS))))
+		return -EINVAL;
 
 	if (WARN_ON(wiphy->coalesce &&
 		    (!wiphy->coalesce->n_rules ||
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index d98d4ea..44c95e3 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -10617,25 +10617,9 @@  void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
 				NL80211_MCGRP_SCAN, GFP_KERNEL);
 }
 
-/*
- * This can happen on global regulatory changes or device specific settings
- * based on custom world regulatory domains.
- */
-void nl80211_send_reg_change_event(struct regulatory_request *request)
+static bool nl80211_reg_change_event_fill(struct sk_buff *msg,
+					  struct regulatory_request *request)
 {
-	struct sk_buff *msg;
-	void *hdr;
-
-	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
-	if (!msg)
-		return;
-
-	hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_REG_CHANGE);
-	if (!hdr) {
-		nlmsg_free(msg);
-		return;
-	}
-
 	/* Userspace can always count this one always being set */
 	if (nla_put_u8(msg, NL80211_ATTR_REG_INITIATOR, request->initiator))
 		goto nla_put_failure;
@@ -10665,6 +10649,66 @@  void nl80211_send_reg_change_event(struct regulatory_request *request)
 	    nla_put_u32(msg, NL80211_ATTR_WIPHY, request->wiphy_idx))
 		goto nla_put_failure;
 
+	return true;
+
+nla_put_failure:
+	return false;
+}
+
+/*
+ * This can happen on global regulatory changes or device specific settings
+ * based on custom world regulatory domains.
+ */
+void nl80211_send_reg_change_event(struct regulatory_request *request)
+{
+	struct sk_buff *msg;
+	void *hdr;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		return;
+
+	hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_REG_CHANGE);
+	if (!hdr) {
+		nlmsg_free(msg);
+		return;
+	}
+
+	if (nl80211_reg_change_event_fill(msg, request) == false)
+		goto nla_put_failure;
+
+	genlmsg_end(msg, hdr);
+
+	rcu_read_lock();
+	genlmsg_multicast_allns(&nl80211_fam, msg, 0,
+				NL80211_MCGRP_REGULATORY, GFP_ATOMIC);
+	rcu_read_unlock();
+
+	return;
+
+nla_put_failure:
+	genlmsg_cancel(msg, hdr);
+	nlmsg_free(msg);
+}
+
+void nl80211_send_wiphy_reg_change_event(struct regulatory_request *request)
+{
+	struct sk_buff *msg;
+	void *hdr;
+
+	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+	if (!msg)
+		return;
+
+	hdr = nl80211hdr_put(msg, 0, 0, 0, NL80211_CMD_WIPHY_REG_CHANGE);
+	if (!hdr) {
+		nlmsg_free(msg);
+		return;
+	}
+
+	if (nl80211_reg_change_event_fill(msg, request) == false)
+		goto nla_put_failure;
+
 	genlmsg_end(msg, hdr);
 
 	rcu_read_lock();
diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h
index 7ad70d6..b91b9c5 100644
--- a/net/wireless/nl80211.h
+++ b/net/wireless/nl80211.h
@@ -18,6 +18,7 @@  void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
 void nl80211_send_sched_scan_results(struct cfg80211_registered_device *rdev,
 				     struct net_device *netdev);
 void nl80211_send_reg_change_event(struct regulatory_request *request);
+void nl80211_send_wiphy_reg_change_event(struct regulatory_request *request);
 void nl80211_send_rx_auth(struct cfg80211_registered_device *rdev,
 			  struct net_device *netdev,
 			  const u8 *buf, size_t len, gfp_t gfp);
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 9575aea..50e8ffc 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -1307,6 +1307,9 @@  static bool ignore_reg_update(struct wiphy *wiphy,
 {
 	struct regulatory_request *lr = get_last_request();
 
+	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
+		return true;
+
 	if (!lr) {
 		REG_DBG_PRINT("Ignoring regulatory request set by %s "
 			      "since last_request is not set\n",
@@ -1370,6 +1373,9 @@  static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
 	sband = wiphy->bands[reg_beacon->chan.band];
 	chan = &sband->channels[chan_idx];
 
+	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
+		return;
+
 	if (likely(chan->center_freq != reg_beacon->chan.center_freq))
 		return;
 
@@ -2424,6 +2430,9 @@  static void restore_regulatory_settings(bool reset_user)
 	world_alpha2[1] = cfg80211_world_regdom->alpha2[1];
 
 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+		if (rdev->wiphy.regulatory_flags &
+		    REGULATORY_WIPHY_SELF_MANAGED)
+			continue;
 		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG)
 			restore_custom_reg_settings(&rdev->wiphy);
 	}
@@ -2827,6 +2836,56 @@  int set_regdom(const struct ieee80211_regdomain *rd)
 	return 0;
 }
 
+int regulatory_set_wiphy_regd_rtnl(struct wiphy *wiphy,
+				   struct ieee80211_regdomain *rd)
+{
+	const struct ieee80211_regdomain *regd;
+	const struct ieee80211_regdomain *tmp;
+	enum ieee80211_band band;
+	struct regulatory_request request = {};
+
+	if (WARN_ON(!wiphy || !rd))
+		return -EINVAL;
+
+	if (WARN(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED,
+		 "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n"))
+		return -EPERM;
+
+	WARN_ON_ONCE(!lockdep_rtnl_is_held());
+
+	if (WARN(!is_valid_rd(rd), "Invalid regulatory domain detected\n")) {
+		print_regdomain_info(rd);
+		return -EINVAL;
+	}
+
+	regd = reg_copy_regd(rd);
+	if (IS_ERR(regd))
+		return PTR_ERR(regd);
+
+	tmp = get_wiphy_regdom(wiphy);
+	rcu_assign_pointer(wiphy->regd, regd);
+	rcu_free_regdom(tmp);
+
+	for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+		handle_band_custom(wiphy,
+				   wiphy->bands[band],
+				   regd);
+	}
+
+	reg_process_ht_flags(wiphy);
+
+	request.wiphy_idx = get_wiphy_idx(wiphy);
+	request.alpha2[0] = rd->alpha2[0];
+	request.alpha2[1] = rd->alpha2[1];
+	request.initiator = NL80211_REGDOM_SET_BY_DRIVER;
+
+	nl80211_send_wiphy_reg_change_event(&request);
+
+	reg_check_channels();
+	return 0;
+}
+EXPORT_SYMBOL(regulatory_set_wiphy_regd_rtnl);
+
 void wiphy_regulatory_register(struct wiphy *wiphy)
 {
 	struct regulatory_request *lr;