Merge commit 'c1e140bf79d817d4a7aa9932eb98b0359c87af33' from mac80211-next
[cascardo/linux.git] / net / wireless / reg.c
index 7b83098..886cc7c 100644 (file)
@@ -109,7 +109,7 @@ static struct regulatory_request core_request_world = {
  * protected by RTNL (and can be accessed with RCU protection)
  */
 static struct regulatory_request __rcu *last_request =
-       (void __rcu *)&core_request_world;
+       (void __force __rcu *)&core_request_world;
 
 /* To trigger userspace events */
 static struct platform_device *reg_pdev;
@@ -142,7 +142,7 @@ static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
        return rtnl_dereference(cfg80211_regdomain);
 }
 
-static const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)
+const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)
 {
        return rtnl_dereference(wiphy->regd);
 }
@@ -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",
@@ -1683,8 +1686,12 @@ static void handle_channel_custom(struct wiphy *wiphy,
        if (IS_ERR(reg_rule)) {
                REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n",
                              chan->center_freq);
-               chan->orig_flags |= IEEE80211_CHAN_DISABLED;
-               chan->flags = chan->orig_flags;
+               if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
+                       chan->flags |= IEEE80211_CHAN_DISABLED;
+               } else {
+                       chan->orig_flags |= IEEE80211_CHAN_DISABLED;
+                       chan->flags = chan->orig_flags;
+               }
                return;
        }
 
@@ -1709,7 +1716,13 @@ static void handle_channel_custom(struct wiphy *wiphy,
        chan->dfs_state = NL80211_DFS_USABLE;
 
        chan->beacon_found = false;
-       chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
+
+       if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
+               chan->flags = chan->orig_flags | bw_flags |
+                             map_regdom_flags(reg_rule->flags);
+       else
+               chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
+
        chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
        chan->max_reg_power = chan->max_power =
                (int) MBM_TO_DBM(power_rule->max_eirp);
@@ -2095,6 +2108,26 @@ out_free:
        reg_free_request(reg_request);
 }
 
+static bool reg_only_self_managed_wiphys(void)
+{
+       struct cfg80211_registered_device *rdev;
+       struct wiphy *wiphy;
+       bool self_managed_found = false;
+
+       ASSERT_RTNL();
+
+       list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
+               wiphy = &rdev->wiphy;
+               if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
+                       self_managed_found = true;
+               else
+                       return false;
+       }
+
+       /* make sure at least one self-managed wiphy exists */
+       return self_managed_found;
+}
+
 /*
  * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_*
  * Regulatory hints come on a first come first serve basis and we
@@ -2126,6 +2159,11 @@ static void reg_process_pending_hints(void)
 
        spin_unlock(&reg_requests_lock);
 
+       if (reg_only_self_managed_wiphys()) {
+               reg_free_request(reg_request);
+               return;
+       }
+
        reg_process_hint(reg_request);
 }
 
@@ -2153,11 +2191,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();
 }
 
@@ -2438,6 +2517,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);
        }
@@ -2841,10 +2922,79 @@ int set_regdom(const struct ieee80211_regdomain *rd)
        return 0;
 }
 
+static 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);
+       return 0;
+}
+
+int regulatory_set_wiphy_regd(struct wiphy *wiphy,
+                             struct ieee80211_regdomain *rd)
+{
+       int ret = __regulatory_set_wiphy_regd(wiphy, rd);
+
+       if (ret)
+               return ret;
+
+       schedule_work(&reg_work);
+       return 0;
+}
+EXPORT_SYMBOL(regulatory_set_wiphy_regd);
+
+int regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy,
+                                       struct ieee80211_regdomain *rd)
+{
+       int ret;
+
+       ASSERT_RTNL();
+
+       ret = __regulatory_set_wiphy_regd(wiphy, rd);
+       if (ret)
+               return ret;
+
+       /* process the request immediately */
+       reg_process_self_managed_hints();
+       return 0;
+}
+EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync_rtnl);
+
 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++;