@@ -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
@@ -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 {
@@ -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 */
@@ -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 ||
@@ -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();
@@ -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);
@@ -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;