cfg80211: add cellular base station regulatory hint support

Cellular base stations can provide hints to cfg80211 about
where they think we are. This can be done for example on
a cell phone. To enable these hints we simply allow them
through as user regulatory hints but we allow userspace
to clasify the hint as either coming directly from the
user or coming from a cellular base station. This option
is only available when you enable
CONFIG_CFG80211_CERTIFICATION_ONUS.

The base station hints themselves will not be processed
by the core unless at least one device on the system
supports this feature.

Signed-off-by: Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
diff --git a/net/wireless/core.c b/net/wireless/core.c
index 71b684b..c0307b0 100644
--- a/net/wireless/core.c
+++ b/net/wireless/core.c
@@ -542,6 +542,7 @@
 	}
 
 	/* set up regulatory info */
+	wiphy_regulatory_register(wiphy);
 	regulatory_update(wiphy, NL80211_REGDOM_SET_BY_CORE);
 
 	list_add_rcu(&rdev->list, &cfg80211_rdev_list);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 9216e45..50b1a0e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -354,6 +354,7 @@
 	[NL80211_ATTR_INACTIVITY_TIMEOUT] = { .type = NLA_U16 },
 	[NL80211_ATTR_BG_SCAN_PERIOD] = { .type = NLA_U16 },
 	[NL80211_ATTR_WDEV] = { .type = NLA_U64 },
+	[NL80211_ATTR_USER_REG_HINT_TYPE] = { .type = NLA_U32 },
 };
 
 /* policy for the key attributes */
@@ -3582,6 +3583,7 @@
 {
 	int r;
 	char *data = NULL;
+	enum nl80211_user_reg_hint_type user_reg_hint_type;
 
 	/*
 	 * You should only get this when cfg80211 hasn't yet initialized
@@ -3601,7 +3603,21 @@
 
 	data = nla_data(info->attrs[NL80211_ATTR_REG_ALPHA2]);
 
-	r = regulatory_hint_user(data);
+	if (info->attrs[NL80211_ATTR_USER_REG_HINT_TYPE])
+		user_reg_hint_type =
+		  nla_get_u32(info->attrs[NL80211_ATTR_USER_REG_HINT_TYPE]);
+	else
+		user_reg_hint_type = NL80211_USER_REG_HINT_USER;
+
+	switch (user_reg_hint_type) {
+	case NL80211_USER_REG_HINT_USER:
+	case NL80211_USER_REG_HINT_CELL_BASE:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	r = regulatory_hint_user(data, user_reg_hint_type);
 
 	return r;
 }
@@ -3971,6 +3987,11 @@
 			cfg80211_regdomain->dfs_region)))
 		goto nla_put_failure;
 
+	if (reg_last_request_cell_base() &&
+	    nla_put_u32(msg, NL80211_ATTR_USER_REG_HINT_TYPE,
+			NL80211_USER_REG_HINT_CELL_BASE))
+		goto nla_put_failure;
+
 	nl_reg_rules = nla_nest_start(msg, NL80211_ATTR_REG_RULES);
 	if (!nl_reg_rules)
 		goto nla_put_failure;
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index ad6f9029..83583a9 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -97,9 +97,16 @@
  *     - cfg80211_world_regdom
  *     - cfg80211_regdom
  *     - last_request
+ *     - reg_num_devs_support_basehint
  */
 static DEFINE_MUTEX(reg_mutex);
 
+/*
+ * Number of devices that registered to the core
+ * that support cellular base station regulatory hints
+ */
+static int reg_num_devs_support_basehint;
+
 static inline void assert_reg_lock(void)
 {
 	lockdep_assert_held(&reg_mutex);
@@ -911,6 +918,59 @@
 		handle_channel(wiphy, initiator, band, i);
 }
 
+static bool reg_request_cell_base(struct regulatory_request *request)
+{
+	if (request->initiator != NL80211_REGDOM_SET_BY_USER)
+		return false;
+	if (request->user_reg_hint_type != NL80211_USER_REG_HINT_CELL_BASE)
+		return false;
+	return true;
+}
+
+bool reg_last_request_cell_base(void)
+{
+	assert_cfg80211_lock();
+
+	mutex_lock(&reg_mutex);
+	return reg_request_cell_base(last_request);
+	mutex_unlock(&reg_mutex);
+}
+
+#ifdef CONFIG_CFG80211_CERTIFICATION_ONUS
+
+/* Core specific check */
+static int reg_ignore_cell_hint(struct regulatory_request *pending_request)
+{
+	if (!reg_num_devs_support_basehint)
+		return -EOPNOTSUPP;
+
+	if (reg_request_cell_base(last_request)) {
+		if (!regdom_changes(pending_request->alpha2))
+			return -EALREADY;
+		return 0;
+	}
+	return 0;
+}
+
+/* Device specific check */
+static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
+{
+	if (!(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS))
+		return true;
+	return false;
+}
+#else
+static int reg_ignore_cell_hint(struct regulatory_request *pending_request)
+{
+	return -EOPNOTSUPP;
+}
+static int reg_dev_ignore_cell_hint(struct wiphy *wiphy)
+{
+	return true;
+}
+#endif
+
+
 static bool ignore_reg_update(struct wiphy *wiphy,
 			      enum nl80211_reg_initiator initiator)
 {
@@ -944,6 +1004,9 @@
 		return true;
 	}
 
+	if (reg_request_cell_base(last_request))
+		return reg_dev_ignore_cell_hint(wiphy);
+
 	return false;
 }
 
@@ -1307,6 +1370,13 @@
 		return 0;
 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
 
+		if (reg_request_cell_base(last_request)) {
+			/* Trust a Cell base station over the AP's country IE */
+			if (regdom_changes(pending_request->alpha2))
+				return -EOPNOTSUPP;
+			return -EALREADY;
+		}
+
 		last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
 
 		if (unlikely(!is_an_alpha2(pending_request->alpha2)))
@@ -1351,6 +1421,12 @@
 
 		return REG_INTERSECT;
 	case NL80211_REGDOM_SET_BY_USER:
+		if (reg_request_cell_base(pending_request))
+			return reg_ignore_cell_hint(pending_request);
+
+		if (reg_request_cell_base(last_request))
+			return -EOPNOTSUPP;
+
 		if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
 			return REG_INTERSECT;
 		/*
@@ -1640,7 +1716,8 @@
 }
 
 /* User hints */
-int regulatory_hint_user(const char *alpha2)
+int regulatory_hint_user(const char *alpha2,
+			 enum nl80211_user_reg_hint_type user_reg_hint_type)
 {
 	struct regulatory_request *request;
 
@@ -1654,6 +1731,7 @@
 	request->alpha2[0] = alpha2[0];
 	request->alpha2[1] = alpha2[1];
 	request->initiator = NL80211_REGDOM_SET_BY_USER;
+	request->user_reg_hint_type = user_reg_hint_type;
 
 	queue_regulatory_request(request);
 
@@ -1906,7 +1984,7 @@
 	 * settings, user regulatory settings takes precedence.
 	 */
 	if (is_an_alpha2(alpha2))
-		regulatory_hint_user(user_alpha2);
+		regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER);
 
 	if (list_empty(&tmp_reg_req_list))
 		return;
@@ -2081,9 +2159,16 @@
 	else {
 		if (is_unknown_alpha2(rd->alpha2))
 			pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n");
-		else
-			pr_info("Regulatory domain changed to country: %c%c\n",
-				rd->alpha2[0], rd->alpha2[1]);
+		else {
+			if (reg_request_cell_base(last_request))
+				pr_info("Regulatory domain changed "
+					"to country: %c%c by Cell Station\n",
+					rd->alpha2[0], rd->alpha2[1]);
+			else
+				pr_info("Regulatory domain changed "
+					"to country: %c%c\n",
+					rd->alpha2[0], rd->alpha2[1]);
+		}
 	}
 	print_dfs_region(rd->dfs_region);
 	print_rd_rules(rd);
@@ -2293,6 +2378,18 @@
 }
 #endif /* CONFIG_HOTPLUG */
 
+void wiphy_regulatory_register(struct wiphy *wiphy)
+{
+	assert_cfg80211_lock();
+
+	mutex_lock(&reg_mutex);
+
+	if (!reg_dev_ignore_cell_hint(wiphy))
+		reg_num_devs_support_basehint++;
+
+	mutex_unlock(&reg_mutex);
+}
+
 /* Caller must hold cfg80211_mutex */
 void reg_device_remove(struct wiphy *wiphy)
 {
@@ -2302,6 +2399,9 @@
 
 	mutex_lock(&reg_mutex);
 
+	if (!reg_dev_ignore_cell_hint(wiphy))
+		reg_num_devs_support_basehint--;
+
 	kfree(wiphy->regd);
 
 	if (last_request)
@@ -2367,7 +2467,8 @@
 	 * as a user hint.
 	 */
 	if (!is_world_regdom(ieee80211_regdom))
-		regulatory_hint_user(ieee80211_regdom);
+		regulatory_hint_user(ieee80211_regdom,
+				     NL80211_USER_REG_HINT_USER);
 
 	return 0;
 }
diff --git a/net/wireless/reg.h b/net/wireless/reg.h
index e2aaaf5..519492f 100644
--- a/net/wireless/reg.h
+++ b/net/wireless/reg.h
@@ -22,9 +22,11 @@
 bool reg_is_valid_request(const char *alpha2);
 bool reg_supported_dfs_region(u8 dfs_region);
 
-int regulatory_hint_user(const char *alpha2);
+int regulatory_hint_user(const char *alpha2,
+			 enum nl80211_user_reg_hint_type user_reg_hint_type);
 
 int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env);
+void wiphy_regulatory_register(struct wiphy *wiphy);
 void reg_device_remove(struct wiphy *wiphy);
 
 int __init regulatory_init(void);
@@ -33,6 +35,7 @@
 int set_regdom(const struct ieee80211_regdomain *rd);
 
 void regulatory_update(struct wiphy *wiphy, enum nl80211_reg_initiator setby);
+bool reg_last_request_cell_base(void);
 
 /**
  * regulatory_hint_found_beacon - hints a beacon was found on a channel