cfg80211: Add API to change the indoor regulatory setting
[cascardo/linux.git] / net / wireless / reg.c
index d39d1cb..4239dd4 100644 (file)
  *     be intersected with the current one.
  * @REG_REQ_ALREADY_SET: the regulatory request will not change the current
  *     regulatory settings, and no further processing is required.
- * @REG_REQ_USER_HINT_HANDLED: a non alpha2  user hint was handled and no
- *     further processing is required, i.e., not need to update last_request
- *     etc. This should be used for user hints that do not provide an alpha2
- *     but some other type of regulatory hint, i.e., indoor operation.
  */
 enum reg_request_treatment {
        REG_REQ_OK,
        REG_REQ_IGNORE,
        REG_REQ_INTERSECT,
        REG_REQ_ALREADY_SET,
-       REG_REQ_USER_HINT_HANDLED,
 };
 
 static struct regulatory_request core_request_world = {
@@ -109,7 +104,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;
@@ -133,16 +128,19 @@ static int reg_num_devs_support_basehint;
  * State variable indicating if the platform on which the devices
  * are attached is operating in an indoor environment. The state variable
  * is relevant for all registered devices.
- * (protected by RTNL)
  */
 static bool reg_is_indoor;
+static spinlock_t reg_indoor_lock;
+
+/* Used to track the userspace process controlling the indoor setting */
+static u32 reg_is_indoor_portid;
 
 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);
 }
@@ -1248,13 +1246,6 @@ static bool reg_request_cell_base(struct regulatory_request *request)
        return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE;
 }
 
-static bool reg_request_indoor(struct regulatory_request *request)
-{
-       if (request->initiator != NL80211_REGDOM_SET_BY_USER)
-               return false;
-       return request->user_reg_hint_type == NL80211_USER_REG_HINT_INDOOR;
-}
-
 bool reg_last_request_cell_base(void)
 {
        return reg_request_cell_base(get_last_request());
@@ -1307,6 +1298,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",
@@ -1695,8 +1689,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;
        }
 
@@ -1721,7 +1719,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);
@@ -1820,11 +1824,6 @@ __reg_process_hint_user(struct regulatory_request *user_request)
 {
        struct regulatory_request *lr = get_last_request();
 
-       if (reg_request_indoor(user_request)) {
-               reg_is_indoor = true;
-               return REG_REQ_USER_HINT_HANDLED;
-       }
-
        if (reg_request_cell_base(user_request))
                return reg_ignore_cell_hint(user_request);
 
@@ -1872,8 +1871,7 @@ reg_process_hint_user(struct regulatory_request *user_request)
 
        treatment = __reg_process_hint_user(user_request);
        if (treatment == REG_REQ_IGNORE ||
-           treatment == REG_REQ_ALREADY_SET ||
-           treatment == REG_REQ_USER_HINT_HANDLED) {
+           treatment == REG_REQ_ALREADY_SET) {
                reg_free_request(user_request);
                return treatment;
        }
@@ -1934,7 +1932,6 @@ reg_process_hint_driver(struct wiphy *wiphy,
        case REG_REQ_OK:
                break;
        case REG_REQ_IGNORE:
-       case REG_REQ_USER_HINT_HANDLED:
                reg_free_request(driver_request);
                return treatment;
        case REG_REQ_INTERSECT:
@@ -2034,7 +2031,6 @@ reg_process_hint_country_ie(struct wiphy *wiphy,
        case REG_REQ_OK:
                break;
        case REG_REQ_IGNORE:
-       case REG_REQ_USER_HINT_HANDLED:
                /* fall through */
        case REG_REQ_ALREADY_SET:
                reg_free_request(country_ie_request);
@@ -2073,8 +2069,7 @@ static void reg_process_hint(struct regulatory_request *reg_request)
        case NL80211_REGDOM_SET_BY_USER:
                treatment = reg_process_hint_user(reg_request);
                if (treatment == REG_REQ_IGNORE ||
-                   treatment == REG_REQ_ALREADY_SET ||
-                   treatment == REG_REQ_USER_HINT_HANDLED)
+                   treatment == REG_REQ_ALREADY_SET)
                        return;
                queue_delayed_work(system_power_efficient_wq,
                                   &reg_timeout, msecs_to_jiffies(3142));
@@ -2107,6 +2102,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
@@ -2138,6 +2153,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);
 }
 
@@ -2165,11 +2185,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();
 }
 
@@ -2230,22 +2291,50 @@ int regulatory_hint_user(const char *alpha2,
        return 0;
 }
 
-int regulatory_hint_indoor_user(void)
+int regulatory_hint_indoor(bool is_indoor, u32 portid)
 {
-       struct regulatory_request *request;
+       spin_lock(&reg_indoor_lock);
 
-       request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
-       if (!request)
-               return -ENOMEM;
+       /* It is possible that more than one user space process is trying to
+        * configure the indoor setting. To handle such cases, clear the indoor
+        * setting in case that some process does not think that the device
+        * is operating in an indoor environment. In addition, if a user space
+        * process indicates that it is controlling the indoor setting, save its
+        * portid, i.e., make it the owner.
+        */
+       reg_is_indoor = is_indoor;
+       if (reg_is_indoor) {
+               if (!reg_is_indoor_portid)
+                       reg_is_indoor_portid = portid;
+       } else {
+               reg_is_indoor_portid = 0;
+       }
 
-       request->wiphy_idx = WIPHY_IDX_INVALID;
-       request->initiator = NL80211_REGDOM_SET_BY_USER;
-       request->user_reg_hint_type = NL80211_USER_REG_HINT_INDOOR;
-       queue_regulatory_request(request);
+       spin_unlock(&reg_indoor_lock);
+
+       if (!is_indoor)
+               reg_check_channels();
 
        return 0;
 }
 
+void regulatory_netlink_notify(u32 portid)
+{
+       spin_lock(&reg_indoor_lock);
+
+       if (reg_is_indoor_portid != portid) {
+               spin_unlock(&reg_indoor_lock);
+               return;
+       }
+
+       reg_is_indoor = false;
+       reg_is_indoor_portid = 0;
+
+       spin_unlock(&reg_indoor_lock);
+
+       reg_check_channels();
+}
+
 /* Driver hints */
 int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
 {
@@ -2413,7 +2502,17 @@ static void restore_regulatory_settings(bool reset_user)
 
        ASSERT_RTNL();
 
-       reg_is_indoor = false;
+       /*
+        * Clear the indoor setting in case that it is not controlled by user
+        * space, as otherwise there is no guarantee that the device is still
+        * operating in an indoor environment.
+        */
+       spin_lock(&reg_indoor_lock);
+       if (reg_is_indoor && !reg_is_indoor_portid) {
+               reg_is_indoor = false;
+               reg_check_channels();
+       }
+       spin_unlock(&reg_indoor_lock);
 
        reset_regdomains(true, &world_regdom);
        restore_alpha2(alpha2, reset_user);
@@ -2450,6 +2549,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);
        }
@@ -2853,10 +2954,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++;
 
@@ -2939,6 +3109,7 @@ int __init regulatory_init(void)
 
        spin_lock_init(&reg_requests_lock);
        spin_lock_init(&reg_pending_beacons_lock);
+       spin_lock_init(&reg_indoor_lock);
 
        reg_regdb_size_check();