These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / net / wireless / reg.c
index 0e347f8..06d050d 100644 (file)
@@ -135,10 +135,7 @@ static spinlock_t reg_indoor_lock;
 /* Used to track the userspace process controlling the indoor setting */
 static u32 reg_is_indoor_portid;
 
-/* Max number of consecutive attempts to communicate with CRDA  */
-#define REG_MAX_CRDA_TIMEOUTS 10
-
-static u32 reg_crda_timeouts;
+static void restore_regulatory_settings(bool reset_user);
 
 static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
 {
@@ -226,9 +223,6 @@ static DECLARE_DELAYED_WORK(reg_check_chans, reg_check_chans_work);
 static void reg_todo(struct work_struct *work);
 static DECLARE_WORK(reg_work, reg_todo);
 
-static void reg_timeout_work(struct work_struct *work);
-static DECLARE_DELAYED_WORK(reg_timeout, reg_timeout_work);
-
 /* We keep a static world regulatory domain in case of the absence of CRDA */
 static const struct ieee80211_regdomain world_regdom = {
        .n_reg_rules = 8,
@@ -262,7 +256,7 @@ static const struct ieee80211_regdomain world_regdom = {
                REG_RULE(5745-10, 5825+10, 80, 6, 20,
                        NL80211_RRF_NO_IR),
 
-               /* IEEE 802.11ad (60gHz), channels 1..3 */
+               /* IEEE 802.11ad (60GHz), channels 1..3 */
                REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0),
        }
 };
@@ -279,6 +273,9 @@ MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
 
 static void reg_free_request(struct regulatory_request *request)
 {
+       if (request == &core_request_world)
+               return;
+
        if (request != get_last_request())
                kfree(request);
 }
@@ -453,68 +450,70 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd)
 }
 
 #ifdef CONFIG_CFG80211_INTERNAL_REGDB
-struct reg_regdb_search_request {
-       char alpha2[2];
+struct reg_regdb_apply_request {
        struct list_head list;
+       const struct ieee80211_regdomain *regdom;
 };
 
-static LIST_HEAD(reg_regdb_search_list);
-static DEFINE_MUTEX(reg_regdb_search_mutex);
+static LIST_HEAD(reg_regdb_apply_list);
+static DEFINE_MUTEX(reg_regdb_apply_mutex);
 
-static void reg_regdb_search(struct work_struct *work)
+static void reg_regdb_apply(struct work_struct *work)
 {
-       struct reg_regdb_search_request *request;
-       const struct ieee80211_regdomain *curdom, *regdom = NULL;
-       int i;
+       struct reg_regdb_apply_request *request;
 
        rtnl_lock();
 
-       mutex_lock(&reg_regdb_search_mutex);
-       while (!list_empty(&reg_regdb_search_list)) {
-               request = list_first_entry(&reg_regdb_search_list,
-                                          struct reg_regdb_search_request,
+       mutex_lock(&reg_regdb_apply_mutex);
+       while (!list_empty(&reg_regdb_apply_list)) {
+               request = list_first_entry(&reg_regdb_apply_list,
+                                          struct reg_regdb_apply_request,
                                           list);
                list_del(&request->list);
 
-               for (i = 0; i < reg_regdb_size; i++) {
-                       curdom = reg_regdb[i];
-
-                       if (alpha2_equal(request->alpha2, curdom->alpha2)) {
-                               regdom = reg_copy_regd(curdom);
-                               break;
-                       }
-               }
-
+               set_regdom(request->regdom, REGD_SOURCE_INTERNAL_DB);
                kfree(request);
        }
-       mutex_unlock(&reg_regdb_search_mutex);
-
-       if (!IS_ERR_OR_NULL(regdom))
-               set_regdom(regdom, REGD_SOURCE_INTERNAL_DB);
+       mutex_unlock(&reg_regdb_apply_mutex);
 
        rtnl_unlock();
 }
 
-static DECLARE_WORK(reg_regdb_work, reg_regdb_search);
+static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);
 
-static void reg_regdb_query(const char *alpha2)
+static int reg_query_builtin(const char *alpha2)
 {
-       struct reg_regdb_search_request *request;
+       const struct ieee80211_regdomain *regdom = NULL;
+       struct reg_regdb_apply_request *request;
+       unsigned int i;
 
-       if (!alpha2)
-               return;
+       for (i = 0; i < reg_regdb_size; i++) {
+               if (alpha2_equal(alpha2, reg_regdb[i]->alpha2)) {
+                       regdom = reg_regdb[i];
+                       break;
+               }
+       }
 
-       request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL);
+       if (!regdom)
+               return -ENODATA;
+
+       request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL);
        if (!request)
-               return;
+               return -ENOMEM;
 
-       memcpy(request->alpha2, alpha2, 2);
+       request->regdom = reg_copy_regd(regdom);
+       if (IS_ERR_OR_NULL(request->regdom)) {
+               kfree(request);
+               return -ENOMEM;
+       }
 
-       mutex_lock(&reg_regdb_search_mutex);
-       list_add_tail(&request->list, &reg_regdb_search_list);
-       mutex_unlock(&reg_regdb_search_mutex);
+       mutex_lock(&reg_regdb_apply_mutex);
+       list_add_tail(&request->list, &reg_regdb_apply_list);
+       mutex_unlock(&reg_regdb_apply_mutex);
 
        schedule_work(&reg_regdb_work);
+
+       return 0;
 }
 
 /* Feel free to add any other sanity checks here */
@@ -525,9 +524,45 @@ static void reg_regdb_size_check(void)
 }
 #else
 static inline void reg_regdb_size_check(void) {}
-static inline void reg_regdb_query(const char *alpha2) {}
+static inline int reg_query_builtin(const char *alpha2)
+{
+       return -ENODATA;
+}
 #endif /* CONFIG_CFG80211_INTERNAL_REGDB */
 
+#ifdef CONFIG_CFG80211_CRDA_SUPPORT
+/* Max number of consecutive attempts to communicate with CRDA  */
+#define REG_MAX_CRDA_TIMEOUTS 10
+
+static u32 reg_crda_timeouts;
+
+static void crda_timeout_work(struct work_struct *work);
+static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work);
+
+static void crda_timeout_work(struct work_struct *work)
+{
+       REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
+       rtnl_lock();
+       reg_crda_timeouts++;
+       restore_regulatory_settings(true);
+       rtnl_unlock();
+}
+
+static void cancel_crda_timeout(void)
+{
+       cancel_delayed_work(&crda_timeout);
+}
+
+static void cancel_crda_timeout_sync(void)
+{
+       cancel_delayed_work_sync(&crda_timeout);
+}
+
+static void reset_crda_timeouts(void)
+{
+       reg_crda_timeouts = 0;
+}
+
 /*
  * This lets us keep regulatory code which is updated on a regulatory
  * basis in userspace.
@@ -536,36 +571,50 @@ static int call_crda(const char *alpha2)
 {
        char country[12];
        char *env[] = { country, NULL };
+       int ret;
 
        snprintf(country, sizeof(country), "COUNTRY=%c%c",
                 alpha2[0], alpha2[1]);
 
-       /* query internal regulatory database (if it exists) */
-       reg_regdb_query(alpha2);
-
        if (reg_crda_timeouts > REG_MAX_CRDA_TIMEOUTS) {
-               pr_info("Exceeded CRDA call max attempts. Not calling CRDA\n");
+               pr_debug("Exceeded CRDA call max attempts. Not calling CRDA\n");
                return -EINVAL;
        }
 
        if (!is_world_regdom((char *) alpha2))
-               pr_info("Calling CRDA for country: %c%c\n",
+               pr_debug("Calling CRDA for country: %c%c\n",
                        alpha2[0], alpha2[1]);
        else
-               pr_info("Calling CRDA to update world regulatory domain\n");
+               pr_debug("Calling CRDA to update world regulatory domain\n");
 
-       return kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, env);
+       ret = kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, env);
+       if (ret)
+               return ret;
+
+       queue_delayed_work(system_power_efficient_wq,
+                          &crda_timeout, msecs_to_jiffies(3142));
+       return 0;
+}
+#else
+static inline void cancel_crda_timeout(void) {}
+static inline void cancel_crda_timeout_sync(void) {}
+static inline void reset_crda_timeouts(void) {}
+static inline int call_crda(const char *alpha2)
+{
+       return -ENODATA;
 }
+#endif /* CONFIG_CFG80211_CRDA_SUPPORT */
 
-static enum reg_request_treatment
-reg_call_crda(struct regulatory_request *request)
+static bool reg_query_database(struct regulatory_request *request)
 {
-       if (call_crda(request->alpha2))
-               return REG_REQ_IGNORE;
+       /* query internal regulatory database (if it exists) */
+       if (reg_query_builtin(request->alpha2) == 0)
+               return true;
 
-       queue_delayed_work(system_power_efficient_wq,
-                          &reg_timeout, msecs_to_jiffies(3142));
-       return REG_REQ_OK;
+       if (call_crda(request->alpha2) == 0)
+               return true;
+
+       return false;
 }
 
 bool reg_is_valid_request(const char *alpha2)
@@ -989,8 +1038,8 @@ static u32 map_regdom_flags(u32 rd_flags)
                channel_flags |= IEEE80211_CHAN_NO_OFDM;
        if (rd_flags & NL80211_RRF_NO_OUTDOOR)
                channel_flags |= IEEE80211_CHAN_INDOOR_ONLY;
-       if (rd_flags & NL80211_RRF_GO_CONCURRENT)
-               channel_flags |= IEEE80211_CHAN_GO_CONCURRENT;
+       if (rd_flags & NL80211_RRF_IR_CONCURRENT)
+               channel_flags |= IEEE80211_CHAN_IR_CONCURRENT;
        if (rd_flags & NL80211_RRF_NO_HT40MINUS)
                channel_flags |= IEEE80211_CHAN_NO_HT40MINUS;
        if (rd_flags & NL80211_RRF_NO_HT40PLUS)
@@ -1004,7 +1053,7 @@ static u32 map_regdom_flags(u32 rd_flags)
 
 static const struct ieee80211_reg_rule *
 freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
-                  const struct ieee80211_regdomain *regd)
+                  const struct ieee80211_regdomain *regd, u32 bw)
 {
        int i;
        bool band_rule_found = false;
@@ -1028,7 +1077,7 @@ freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
                if (!band_rule_found)
                        band_rule_found = freq_in_rule_band(fr, center_freq);
 
-               bw_fits = reg_does_bw_fit(fr, center_freq, MHZ_TO_KHZ(20));
+               bw_fits = reg_does_bw_fit(fr, center_freq, bw);
 
                if (band_rule_found && bw_fits)
                        return rr;
@@ -1040,14 +1089,26 @@ freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
        return ERR_PTR(-EINVAL);
 }
 
-const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
-                                              u32 center_freq)
+static const struct ieee80211_reg_rule *
+__freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw)
 {
-       const struct ieee80211_regdomain *regd;
+       const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy);
+       const struct ieee80211_reg_rule *reg_rule = NULL;
+       u32 bw;
 
-       regd = reg_get_regdomain(wiphy);
+       for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) {
+               reg_rule = freq_reg_info_regd(wiphy, center_freq, regd, bw);
+               if (!IS_ERR(reg_rule))
+                       return reg_rule;
+       }
+
+       return reg_rule;
+}
 
-       return freq_reg_info_regd(wiphy, center_freq, regd);
+const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
+                                              u32 center_freq)
+{
+       return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(20));
 }
 EXPORT_SYMBOL(freq_reg_info);
 
@@ -1069,11 +1130,11 @@ const char *reg_initiator_name(enum nl80211_reg_initiator initiator)
 }
 EXPORT_SYMBOL(reg_initiator_name);
 
-#ifdef CONFIG_CFG80211_REG_DEBUG
 static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
                                    struct ieee80211_channel *chan,
                                    const struct ieee80211_reg_rule *reg_rule)
 {
+#ifdef CONFIG_CFG80211_REG_DEBUG
        const struct ieee80211_power_rule *power_rule;
        const struct ieee80211_freq_range *freq_range;
        char max_antenna_gain[32], bw[32];
@@ -1084,7 +1145,7 @@ static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
        if (!power_rule->max_antenna_gain)
                snprintf(max_antenna_gain, sizeof(max_antenna_gain), "N/A");
        else
-               snprintf(max_antenna_gain, sizeof(max_antenna_gain), "%d",
+               snprintf(max_antenna_gain, sizeof(max_antenna_gain), "%d mBi",
                         power_rule->max_antenna_gain);
 
        if (reg_rule->flags & NL80211_RRF_AUTO_BW)
@@ -1098,19 +1159,12 @@ static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
        REG_DBG_PRINT("Updating information on frequency %d MHz with regulatory rule:\n",
                      chan->center_freq);
 
-       REG_DBG_PRINT("%d KHz - %d KHz @ %s), (%s mBi, %d mBm)\n",
+       REG_DBG_PRINT("(%d KHz - %d KHz @ %s), (%s, %d mBm)\n",
                      freq_range->start_freq_khz, freq_range->end_freq_khz,
                      bw, max_antenna_gain,
                      power_rule->max_eirp);
-}
-#else
-static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
-                                   struct ieee80211_channel *chan,
-                                   const struct ieee80211_reg_rule *reg_rule)
-{
-       return;
-}
 #endif
+}
 
 /*
  * Note that right now we assume the desired channel bandwidth
@@ -1176,8 +1230,20 @@ static void handle_channel(struct wiphy *wiphy,
        if (reg_rule->flags & NL80211_RRF_AUTO_BW)
                max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
 
+       /* If we get a reg_rule we can assume that at least 5Mhz fit */
+       if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
+                            MHZ_TO_KHZ(10)))
+               bw_flags |= IEEE80211_CHAN_NO_10MHZ;
+       if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
+                            MHZ_TO_KHZ(20)))
+               bw_flags |= IEEE80211_CHAN_NO_20MHZ;
+
+       if (max_bandwidth_khz < MHZ_TO_KHZ(10))
+               bw_flags |= IEEE80211_CHAN_NO_10MHZ;
+       if (max_bandwidth_khz < MHZ_TO_KHZ(20))
+               bw_flags |= IEEE80211_CHAN_NO_20MHZ;
        if (max_bandwidth_khz < MHZ_TO_KHZ(40))
-               bw_flags = IEEE80211_CHAN_NO_HT40;
+               bw_flags |= IEEE80211_CHAN_NO_HT40;
        if (max_bandwidth_khz < MHZ_TO_KHZ(80))
                bw_flags |= IEEE80211_CHAN_NO_80MHZ;
        if (max_bandwidth_khz < MHZ_TO_KHZ(160))
@@ -1287,7 +1353,8 @@ static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
        return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS);
 }
 #else
-static int reg_ignore_cell_hint(struct regulatory_request *pending_request)
+static enum reg_request_treatment
+reg_ignore_cell_hint(struct regulatory_request *pending_request)
 {
        return REG_REQ_IGNORE;
 }
@@ -1589,7 +1656,7 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_P2P_GO:
        case NL80211_IFTYPE_ADHOC:
-               return cfg80211_reg_can_beacon(wiphy, &chandef, iftype);
+               return cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype);
        case NL80211_IFTYPE_STATION:
        case NL80211_IFTYPE_P2P_CLIENT:
                return cfg80211_chandef_usable(wiphy, &chandef,
@@ -1695,9 +1762,15 @@ static void handle_channel_custom(struct wiphy *wiphy,
        const struct ieee80211_power_rule *power_rule = NULL;
        const struct ieee80211_freq_range *freq_range = NULL;
        u32 max_bandwidth_khz;
+       u32 bw;
 
-       reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq),
-                                     regd);
+       for (bw = MHZ_TO_KHZ(20); bw >= MHZ_TO_KHZ(5); bw = bw / 2) {
+               reg_rule = freq_reg_info_regd(wiphy,
+                                             MHZ_TO_KHZ(chan->center_freq),
+                                             regd, bw);
+               if (!IS_ERR(reg_rule))
+                       break;
+       }
 
        if (IS_ERR(reg_rule)) {
                REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n",
@@ -1721,8 +1794,20 @@ static void handle_channel_custom(struct wiphy *wiphy,
        if (reg_rule->flags & NL80211_RRF_AUTO_BW)
                max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
 
+       /* If we get a reg_rule we can assume that at least 5Mhz fit */
+       if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
+                            MHZ_TO_KHZ(10)))
+               bw_flags |= IEEE80211_CHAN_NO_10MHZ;
+       if (!reg_does_bw_fit(freq_range, MHZ_TO_KHZ(chan->center_freq),
+                            MHZ_TO_KHZ(20)))
+               bw_flags |= IEEE80211_CHAN_NO_20MHZ;
+
+       if (max_bandwidth_khz < MHZ_TO_KHZ(10))
+               bw_flags |= IEEE80211_CHAN_NO_10MHZ;
+       if (max_bandwidth_khz < MHZ_TO_KHZ(20))
+               bw_flags |= IEEE80211_CHAN_NO_20MHZ;
        if (max_bandwidth_khz < MHZ_TO_KHZ(40))
-               bw_flags = IEEE80211_CHAN_NO_HT40;
+               bw_flags |= IEEE80211_CHAN_NO_HT40;
        if (max_bandwidth_khz < MHZ_TO_KHZ(80))
                bw_flags |= IEEE80211_CHAN_NO_80MHZ;
        if (max_bandwidth_khz < MHZ_TO_KHZ(160))
@@ -1804,7 +1889,7 @@ static void reg_set_request_processed(void)
                need_more_processing = true;
        spin_unlock(&reg_requests_lock);
 
-       cancel_delayed_work(&reg_timeout);
+       cancel_crda_timeout();
 
        if (need_more_processing)
                schedule_work(&reg_work);
@@ -1816,19 +1901,18 @@ static void reg_set_request_processed(void)
  *
  * The wireless subsystem can use this function to process
  * a regulatory request issued by the regulatory core.
- *
- * Returns one of the different reg request treatment values.
  */
 static enum reg_request_treatment
 reg_process_hint_core(struct regulatory_request *core_request)
 {
+       if (reg_query_database(core_request)) {
+               core_request->intersect = false;
+               core_request->processed = false;
+               reg_update_last_request(core_request);
+               return REG_REQ_OK;
+       }
 
-       core_request->intersect = false;
-       core_request->processed = false;
-
-       reg_update_last_request(core_request);
-
-       return reg_call_crda(core_request);
+       return REG_REQ_IGNORE;
 }
 
 static enum reg_request_treatment
@@ -1873,8 +1957,6 @@ __reg_process_hint_user(struct regulatory_request *user_request)
  *
  * The wireless subsystem can use this function to process
  * a regulatory request initiated by userspace.
- *
- * Returns one of the different reg request treatment values.
  */
 static enum reg_request_treatment
 reg_process_hint_user(struct regulatory_request *user_request)
@@ -1883,20 +1965,20 @@ 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) {
-               reg_free_request(user_request);
-               return treatment;
-       }
+           treatment == REG_REQ_ALREADY_SET)
+               return REG_REQ_IGNORE;
 
        user_request->intersect = treatment == REG_REQ_INTERSECT;
        user_request->processed = false;
 
-       reg_update_last_request(user_request);
-
-       user_alpha2[0] = user_request->alpha2[0];
-       user_alpha2[1] = user_request->alpha2[1];
+       if (reg_query_database(user_request)) {
+               reg_update_last_request(user_request);
+               user_alpha2[0] = user_request->alpha2[0];
+               user_alpha2[1] = user_request->alpha2[1];
+               return REG_REQ_OK;
+       }
 
-       return reg_call_crda(user_request);
+       return REG_REQ_IGNORE;
 }
 
 static enum reg_request_treatment
@@ -1944,16 +2026,12 @@ reg_process_hint_driver(struct wiphy *wiphy,
        case REG_REQ_OK:
                break;
        case REG_REQ_IGNORE:
-               reg_free_request(driver_request);
-               return treatment;
+               return REG_REQ_IGNORE;
        case REG_REQ_INTERSECT:
-               /* fall through */
        case REG_REQ_ALREADY_SET:
                regd = reg_copy_regd(get_cfg80211_regdom());
-               if (IS_ERR(regd)) {
-                       reg_free_request(driver_request);
+               if (IS_ERR(regd))
                        return REG_REQ_IGNORE;
-               }
 
                tmp = get_wiphy_regdom(wiphy);
                rcu_assign_pointer(wiphy->regd, regd);
@@ -1964,8 +2042,6 @@ reg_process_hint_driver(struct wiphy *wiphy,
        driver_request->intersect = treatment == REG_REQ_INTERSECT;
        driver_request->processed = false;
 
-       reg_update_last_request(driver_request);
-
        /*
         * Since CRDA will not be called in this case as we already
         * have applied the requested regulatory domain before we just
@@ -1973,11 +2049,17 @@ reg_process_hint_driver(struct wiphy *wiphy,
         */
        if (treatment == REG_REQ_ALREADY_SET) {
                nl80211_send_reg_change_event(driver_request);
+               reg_update_last_request(driver_request);
                reg_set_request_processed();
-               return treatment;
+               return REG_REQ_ALREADY_SET;
+       }
+
+       if (reg_query_database(driver_request)) {
+               reg_update_last_request(driver_request);
+               return REG_REQ_OK;
        }
 
-       return reg_call_crda(driver_request);
+       return REG_REQ_IGNORE;
 }
 
 static enum reg_request_treatment
@@ -2043,12 +2125,11 @@ reg_process_hint_country_ie(struct wiphy *wiphy,
        case REG_REQ_OK:
                break;
        case REG_REQ_IGNORE:
-               /* fall through */
+               return REG_REQ_IGNORE;
        case REG_REQ_ALREADY_SET:
                reg_free_request(country_ie_request);
-               return treatment;
+               return REG_REQ_ALREADY_SET;
        case REG_REQ_INTERSECT:
-               reg_free_request(country_ie_request);
                /*
                 * This doesn't happen yet, not sure we
                 * ever want to support it for this case.
@@ -2060,9 +2141,12 @@ reg_process_hint_country_ie(struct wiphy *wiphy,
        country_ie_request->intersect = false;
        country_ie_request->processed = false;
 
-       reg_update_last_request(country_ie_request);
+       if (reg_query_database(country_ie_request)) {
+               reg_update_last_request(country_ie_request);
+               return REG_REQ_OK;
+       }
 
-       return reg_call_crda(country_ie_request);
+       return REG_REQ_IGNORE;
 }
 
 /* This processes *all* regulatory hints */
@@ -2076,14 +2160,11 @@ static void reg_process_hint(struct regulatory_request *reg_request)
 
        switch (reg_request->initiator) {
        case NL80211_REGDOM_SET_BY_CORE:
-               reg_process_hint_core(reg_request);
-               return;
+               treatment = reg_process_hint_core(reg_request);
+               break;
        case NL80211_REGDOM_SET_BY_USER:
                treatment = reg_process_hint_user(reg_request);
-               if (treatment == REG_REQ_IGNORE ||
-                   treatment == REG_REQ_ALREADY_SET)
-                       return;
-               return;
+               break;
        case NL80211_REGDOM_SET_BY_DRIVER:
                if (!wiphy)
                        goto out_free;
@@ -2099,7 +2180,15 @@ static void reg_process_hint(struct regulatory_request *reg_request)
                goto out_free;
        }
 
-       /* This is required so that the orig_* parameters are saved */
+       if (treatment == REG_REQ_IGNORE)
+               goto out_free;
+
+       WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET,
+            "unexpected treatment value %d\n", treatment);
+
+       /* This is required so that the orig_* parameters are saved.
+        * NOTE: treatment must be set for any case that reaches here!
+        */
        if (treatment == REG_REQ_ALREADY_SET && wiphy &&
            wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
                wiphy_update_regulatory(wiphy, reg_request->initiator);
@@ -2304,7 +2393,7 @@ int regulatory_hint_user(const char *alpha2,
        request->user_reg_hint_type = user_reg_hint_type;
 
        /* Allow calling CRDA again */
-       reg_crda_timeouts = 0;
+       reset_crda_timeouts();
 
        queue_regulatory_request(request);
 
@@ -2376,7 +2465,7 @@ int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
        request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
 
        /* Allow calling CRDA again */
-       reg_crda_timeouts = 0;
+       reset_crda_timeouts();
 
        queue_regulatory_request(request);
 
@@ -2432,7 +2521,7 @@ void regulatory_hint_country_ie(struct wiphy *wiphy, enum ieee80211_band band,
        request->country_ie_env = env;
 
        /* Allow calling CRDA again */
-       reg_crda_timeouts = 0;
+       reset_crda_timeouts();
 
        queue_regulatory_request(request);
        request = NULL;
@@ -2584,7 +2673,7 @@ static void restore_regulatory_settings(bool reset_user)
         * settings, user regulatory settings takes precedence.
         */
        if (is_an_alpha2(alpha2))
-               regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER);
+               regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER);
 
        spin_lock(&reg_requests_lock);
        list_splice_tail_init(&tmp_reg_req_list, &reg_requests_list);
@@ -2833,11 +2922,8 @@ static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
        }
 
        request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx);
-       if (!request_wiphy) {
-               queue_delayed_work(system_power_efficient_wq,
-                                  &reg_timeout, 0);
+       if (!request_wiphy)
                return -ENODEV;
-       }
 
        if (!driver_request->intersect) {
                if (request_wiphy->regd)
@@ -2894,11 +2980,8 @@ static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd,
        }
 
        request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx);
-       if (!request_wiphy) {
-               queue_delayed_work(system_power_efficient_wq,
-                                  &reg_timeout, 0);
+       if (!request_wiphy)
                return -ENODEV;
-       }
 
        if (country_ie_request->intersect)
                return -EINVAL;
@@ -2925,7 +3008,7 @@ int set_regdom(const struct ieee80211_regdomain *rd,
        }
 
        if (regd_src == REGD_SOURCE_CRDA)
-               reg_crda_timeouts = 0;
+               reset_crda_timeouts();
 
        lr = get_last_request();
 
@@ -2946,6 +3029,7 @@ int set_regdom(const struct ieee80211_regdomain *rd,
                break;
        default:
                WARN(1, "invalid initiator %d\n", lr->initiator);
+               kfree(rd);
                return -EINVAL;
        }
 
@@ -3082,15 +3166,6 @@ void wiphy_regulatory_deregister(struct wiphy *wiphy)
        lr->country_ie_env = ENVIRON_ANY;
 }
 
-static void reg_timeout_work(struct work_struct *work)
-{
-       REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
-       rtnl_lock();
-       reg_crda_timeouts++;
-       restore_regulatory_settings(true);
-       rtnl_unlock();
-}
-
 /*
  * See http://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii, for
  * UNII band definitions
@@ -3147,8 +3222,10 @@ int __init regulatory_init(void)
        /* We always try to get an update for the static regdomain */
        err = regulatory_hint_core(cfg80211_world_regdom->alpha2);
        if (err) {
-               if (err == -ENOMEM)
+               if (err == -ENOMEM) {
+                       platform_device_unregister(reg_pdev);
                        return err;
+               }
                /*
                 * N.B. kobject_uevent_env() can fail mainly for when we're out
                 * memory which is handled and propagated appropriately above
@@ -3176,7 +3253,7 @@ void regulatory_exit(void)
        struct reg_beacon *reg_beacon, *btmp;
 
        cancel_work_sync(&reg_work);
-       cancel_delayed_work_sync(&reg_timeout);
+       cancel_crda_timeout_sync();
        cancel_delayed_work_sync(&reg_check_chans);
 
        /* Lock to suppress warnings */