cfg80211: allow wiphy specific regdomain management
authorJonathan Doron <jond@wizery.com>
Mon, 15 Dec 2014 17:26:00 +0000 (19:26 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 17 Dec 2014 10:49:55 +0000 (11:49 +0100)
Add a new regulatory flag that allows a driver to manage regdomain
changes/updates for its own wiphy.
A self-managed wiphys only employs regulatory information obtained from
the FW and driver and does not use other cfg80211 sources like
beacon-hints, country-code IEs and hints from other devices on the same
system. Conversely, a self-managed wiphy does not share its regulatory
hints with other devices in the system. If a system contains several
devices, one or more of which are self-managed, there might be
contradictory regulatory settings between them. Usage of flag is
generally discouraged. Only use it if the FW/driver is incompatible
with non-locally originated hints.

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>
Reviewed-by: Luis R. Rodriguez <mcgrof@suse.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
include/net/regulatory.h
include/uapi/linux/nl80211.h
net/wireless/core.c
net/wireless/core.h
net/wireless/nl80211.c
net/wireless/nl80211.h
net/wireless/reg.c

index 4ebb816241fa1ad76506d30265878cacb47b1aec..4bc1fc9971a557ee63f1f1cb9be7eea6e62dd6f5 100644 (file)
@@ -3807,6 +3807,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 - 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 wiphys, only they
+ * may use this function. See %REGULATORY_WIPHY_SELF_MANAGED for more
+ * information.
+ *
+ * Return: 0 on success. -EINVAL, -EPERM
+ */
+int regulatory_set_wiphy_regd(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
index b776d72d84be8f03fde9650c4b90d1b17940d543..ebc5a2ed86317dfdab8939d6259ed7ea7b0199fb 100644 (file)
@@ -147,6 +147,24 @@ struct regulatory_request {
  *     NL80211_IFTYPE_P2P_CLIENT, NL80211_IFTYPE_P2P_GO,
  *     NL80211_IFTYPE_P2P_DEVICE. The flag will be set by default if a device
  *     includes any modes unsupported for enforcement checking.
+ * @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.
+ *     A self-managed wiphys only employs regulatory information obtained from
+ *     the FW and driver and does not use other cfg80211 sources like
+ *     beacon-hints, country-code IEs and hints from other devices on the same
+ *     system. Conversely, a self-managed wiphy does not share its regulatory
+ *     hints with other devices in the system. If a system contains several
+ *     devices, one or more of which are self-managed, there might be
+ *     contradictory regulatory settings between them. Usage of flag is
+ *     generally discouraged. Only use it if the FW/driver is incompatible
+ *     with non-locally originated hints.
+ *     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 and %REGULATORY_COUNTRY_IE_IGNORE.
  */
 enum ieee80211_regulatory_flags {
        REGULATORY_CUSTOM_REG                   = BIT(0),
@@ -156,6 +174,7 @@ enum ieee80211_regulatory_flags {
        REGULATORY_COUNTRY_IE_IGNORE            = BIT(4),
        REGULATORY_ENABLE_RELAX_NO_IR           = BIT(5),
        REGULATORY_IGNORE_STALE_KICKOFF         = BIT(6),
+       REGULATORY_WIPHY_SELF_MANAGED           = BIT(7),
 };
 
 struct ieee80211_freq_range {
index 2d384d0412248df1a0f849a1cdb6755db3ba1b82..fb58e654f52327813d355fa1fec8372f0c39276b 100644 (file)
  *     peer given by %NL80211_ATTR_MAC. Both peers must be on the base channel
  *     when this command completes.
  *
+ * @NL80211_CMD_WIPHY_REG_CHANGE: Similar to %NL80211_CMD_REG_CHANGE, but used
+ *     as an event to indicate changes for devices with wiphy-specific regdom
+ *     management.
+ *
  * @NL80211_CMD_MAX: highest used command number
  * @__NL80211_CMD_AFTER_LAST: internal use
  */
@@ -966,6 +970,8 @@ enum nl80211_commands {
        NL80211_CMD_TDLS_CHANNEL_SWITCH,
        NL80211_CMD_TDLS_CANCEL_CHANNEL_SWITCH,
 
+       NL80211_CMD_WIPHY_REG_CHANGE,
+
        /* add new commands above here */
 
        /* used to define NL80211_CMD_MAX below */
index 4910758baab11be4a318fd34a626b7d6bb90c9f3..b661fcce7e3c3f57237010a62d5cb85fbab73920 100644 (file)
@@ -561,6 +561,14 @@ int wiphy_register(struct wiphy *wiphy)
                                       BIT(NL80211_IFTYPE_MONITOR)))
                wiphy->regulatory_flags |= REGULATORY_IGNORE_STALE_KICKOFF;
 
+       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))))
+               return -EINVAL;
+
        if (WARN_ON(wiphy->coalesce &&
                    (!wiphy->coalesce->n_rules ||
                     !wiphy->coalesce->n_patterns) &&
index faa5b1609aaebb8991ff3c6312735082a42e6e3e..e87cae57a83b96debe9f2496a569b328b15862d9 100644 (file)
@@ -36,6 +36,13 @@ struct cfg80211_registered_device {
         * the country on the country IE changed. */
        char country_ie_alpha2[2];
 
+       /*
+        * the driver requests the regulatory core to set this regulatory
+        * domain as the wiphy's. Only used for %REGULATORY_WIPHY_SELF_MANAGED
+        * devices using the regulatory_set_wiphy_regd() API
+        */
+       const struct ieee80211_regdomain *requested_regd;
+
        /* If a Country IE has been received this tells us the environment
         * which its telling us its in. This defaults to ENVIRON_ANY */
        enum environment_cap env;
index 2d5dc428c5ab42e8a62f1fcf73964fe23e943c87..eebb7e42298968d4de73872d157f67d0e6eb90b2 100644 (file)
@@ -11042,25 +11042,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;
@@ -11094,6 +11078,35 @@ void nl80211_send_reg_change_event(struct regulatory_request *request)
                        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 regulatory domains.
+ */
+void nl80211_common_reg_change_event(enum nl80211_commands cmd_id,
+                                    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, cmd_id);
+       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();
index 7ad70d6f0cc699147e26c679c298d1200e182f1a..84d4edf1d545de51fe43c378d1b7b13457aa2c19 100644 (file)
@@ -17,7 +17,21 @@ void nl80211_send_sched_scan(struct cfg80211_registered_device *rdev,
                             struct net_device *netdev, u32 cmd);
 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_common_reg_change_event(enum nl80211_commands cmd_id,
+                                    struct regulatory_request *request);
+
+static inline void
+nl80211_send_reg_change_event(struct regulatory_request *request)
+{
+       nl80211_common_reg_change_event(NL80211_CMD_REG_CHANGE, request);
+}
+
+static inline void
+nl80211_send_wiphy_reg_change_event(struct regulatory_request *request)
+{
+       nl80211_common_reg_change_event(NL80211_CMD_WIPHY_REG_CHANGE, request);
+}
+
 void nl80211_send_rx_auth(struct cfg80211_registered_device *rdev,
                          struct net_device *netdev,
                          const u8 *buf, size_t len, gfp_t gfp);
index 2d9760084b7479cbfd12831b42dce0e2a518cba7..c040f8a0f1ed7d61d5f6602229bad9d1a5cf2981 100644 (file)
@@ -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",
@@ -2147,11 +2150,52 @@ static void reg_process_pending_beacon_hints(void)
        spin_unlock_bh(&reg_pending_beacons_lock);
 }
 
+static void reg_process_self_managed_hints(void)
+{
+       struct cfg80211_registered_device *rdev;
+       struct wiphy *wiphy;
+       const struct ieee80211_regdomain *tmp;
+       const struct ieee80211_regdomain *regd;
+       enum ieee80211_band band;
+       struct regulatory_request request = {};
+
+       list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+               wiphy = &rdev->wiphy;
+
+               spin_lock(&reg_requests_lock);
+               regd = rdev->requested_regd;
+               rdev->requested_regd = NULL;
+               spin_unlock(&reg_requests_lock);
+
+               if (regd == NULL)
+                       continue;
+
+               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] = regd->alpha2[0];
+               request.alpha2[1] = regd->alpha2[1];
+               request.initiator = NL80211_REGDOM_SET_BY_DRIVER;
+
+               nl80211_send_wiphy_reg_change_event(&request);
+       }
+
+       reg_check_channels();
+}
+
 static void reg_todo(struct work_struct *work)
 {
        rtnl_lock();
        reg_process_pending_hints();
        reg_process_pending_beacon_hints();
+       reg_process_self_managed_hints();
        rtnl_unlock();
 }
 
@@ -2432,6 +2476,8 @@ 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);
        }
@@ -2835,10 +2881,52 @@ int set_regdom(const struct ieee80211_regdomain *rd)
        return 0;
 }
 
+int regulatory_set_wiphy_regd(struct wiphy *wiphy,
+                             struct ieee80211_regdomain *rd)
+{
+       const struct ieee80211_regdomain *regd;
+       const struct ieee80211_regdomain *prev_regd;
+       struct cfg80211_registered_device *rdev;
+
+       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;
+
+       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);
+
+       rdev = wiphy_to_rdev(wiphy);
+
+       spin_lock(&reg_requests_lock);
+       prev_regd = rdev->requested_regd;
+       rdev->requested_regd = regd;
+       spin_unlock(&reg_requests_lock);
+
+       kfree(prev_regd);
+
+       schedule_work(&reg_work);
+       return 0;
+}
+EXPORT_SYMBOL(regulatory_set_wiphy_regd);
+
 void wiphy_regulatory_register(struct wiphy *wiphy)
 {
        struct regulatory_request *lr;
 
+       /* self-managed devices ignore external hints */
+       if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
+               wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
+                                          REGULATORY_COUNTRY_IE_IGNORE;
+
        if (!reg_dev_ignore_cell_hint(wiphy))
                reg_num_devs_support_basehint++;