cfg80211: regulatory: introduce NL80211_RRF_AUTO_BW rule flag
[firefly-linux-kernel-4.4.55.git] / net / wireless / reg.c
index 9b897fca7487dd4fe8d8364368e8516ab8f369c0..6b6f33ad78f2b25be6b1d1318cb237705c17e6ba 100644 (file)
@@ -91,7 +91,7 @@ static struct regulatory_request __rcu *last_request =
 /* To trigger userspace events */
 static struct platform_device *reg_pdev;
 
-static struct device_type reg_device_type = {
+static const struct device_type reg_device_type = {
        .uevent = reg_device_uevent,
 };
 
@@ -522,6 +522,71 @@ bool reg_is_valid_request(const char *alpha2)
        return alpha2_equal(lr->alpha2, alpha2);
 }
 
+static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy)
+{
+       struct regulatory_request *lr = get_last_request();
+
+       /*
+        * Follow the driver's regulatory domain, if present, unless a country
+        * IE has been processed or a user wants to help complaince further
+        */
+       if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
+           lr->initiator != NL80211_REGDOM_SET_BY_USER &&
+           wiphy->regd)
+               return get_wiphy_regdom(wiphy);
+
+       return get_cfg80211_regdom();
+}
+
+unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
+                                  const struct ieee80211_reg_rule *rule)
+{
+       const struct ieee80211_freq_range *freq_range = &rule->freq_range;
+       const struct ieee80211_freq_range *freq_range_tmp;
+       const struct ieee80211_reg_rule *tmp;
+       u32 start_freq, end_freq, idx, no;
+
+       for (idx = 0; idx < rd->n_reg_rules; idx++)
+               if (rule == &rd->reg_rules[idx])
+                       break;
+
+       if (idx == rd->n_reg_rules)
+               return 0;
+
+       /* get start_freq */
+       no = idx;
+
+       while (no) {
+               tmp = &rd->reg_rules[--no];
+               freq_range_tmp = &tmp->freq_range;
+
+               if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz)
+                       break;
+
+               freq_range = freq_range_tmp;
+       }
+
+       start_freq = freq_range->start_freq_khz;
+
+       /* get end_freq */
+       freq_range = &rule->freq_range;
+       no = idx;
+
+       while (no < rd->n_reg_rules - 1) {
+               tmp = &rd->reg_rules[++no];
+               freq_range_tmp = &tmp->freq_range;
+
+               if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz)
+                       break;
+
+               freq_range = freq_range_tmp;
+       }
+
+       end_freq = freq_range->end_freq_khz;
+
+       return end_freq - start_freq;
+}
+
 /* Sanity check on a regulatory rule */
 static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
 {
@@ -630,7 +695,9 @@ reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1,
  * Helper for regdom_intersect(), this does the real
  * mathematical intersection fun
  */
-static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1,
+static int reg_rules_intersect(const struct ieee80211_regdomain *rd1,
+                              const struct ieee80211_regdomain *rd2,
+                              const struct ieee80211_reg_rule *rule1,
                               const struct ieee80211_reg_rule *rule2,
                               struct ieee80211_reg_rule *intersected_rule)
 {
@@ -638,7 +705,7 @@ static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1,
        struct ieee80211_freq_range *freq_range;
        const struct ieee80211_power_rule *power_rule1, *power_rule2;
        struct ieee80211_power_rule *power_rule;
-       u32 freq_diff;
+       u32 freq_diff, max_bandwidth1, max_bandwidth2;
 
        freq_range1 = &rule1->freq_range;
        freq_range2 = &rule2->freq_range;
@@ -652,8 +719,32 @@ static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1,
                                         freq_range2->start_freq_khz);
        freq_range->end_freq_khz = min(freq_range1->end_freq_khz,
                                       freq_range2->end_freq_khz);
-       freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz,
-                                           freq_range2->max_bandwidth_khz);
+
+       max_bandwidth1 = freq_range1->max_bandwidth_khz;
+       max_bandwidth2 = freq_range2->max_bandwidth_khz;
+
+       if (rule1->flags & NL80211_RRF_AUTO_BW)
+               max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1);
+       if (rule2->flags & NL80211_RRF_AUTO_BW)
+               max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2);
+
+       freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2);
+
+       intersected_rule->flags = rule1->flags | rule2->flags;
+
+       /*
+        * In case NL80211_RRF_AUTO_BW requested for both rules
+        * set AUTO_BW in intersected rule also. Next we will
+        * calculate BW correctly in handle_channel function.
+        * In other case remove AUTO_BW flag while we calculate
+        * maximum bandwidth correctly and auto calculation is
+        * not required.
+        */
+       if ((rule1->flags & NL80211_RRF_AUTO_BW) &&
+           (rule2->flags & NL80211_RRF_AUTO_BW))
+               intersected_rule->flags |= NL80211_RRF_AUTO_BW;
+       else
+               intersected_rule->flags &= ~NL80211_RRF_AUTO_BW;
 
        freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
        if (freq_range->max_bandwidth_khz > freq_diff)
@@ -664,8 +755,6 @@ static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1,
        power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain,
                power_rule2->max_antenna_gain);
 
-       intersected_rule->flags = rule1->flags | rule2->flags;
-
        if (!is_valid_reg_rule(intersected_rule))
                return -EINVAL;
 
@@ -713,7 +802,8 @@ regdom_intersect(const struct ieee80211_regdomain *rd1,
                rule1 = &rd1->reg_rules[x];
                for (y = 0; y < rd2->n_reg_rules; y++) {
                        rule2 = &rd2->reg_rules[y];
-                       if (!reg_rules_intersect(rule1, rule2, &dummy_rule))
+                       if (!reg_rules_intersect(rd1, rd2, rule1, rule2,
+                                                &dummy_rule))
                                num_rules++;
                }
        }
@@ -738,7 +828,8 @@ regdom_intersect(const struct ieee80211_regdomain *rd1,
                         * a memcpy()
                         */
                        intersected_rule = &rd->reg_rules[rule_idx];
-                       r = reg_rules_intersect(rule1, rule2, intersected_rule);
+                       r = reg_rules_intersect(rd1, rd2, rule1, rule2,
+                                               intersected_rule);
                        /*
                         * No need to memset here the intersected rule here as
                         * we're not using the stack anymore
@@ -821,18 +912,8 @@ const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
                                               u32 center_freq)
 {
        const struct ieee80211_regdomain *regd;
-       struct regulatory_request *lr = get_last_request();
 
-       /*
-        * Follow the driver's regulatory domain, if present, unless a country
-        * IE has been processed or a user wants to help complaince further
-        */
-       if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
-           lr->initiator != NL80211_REGDOM_SET_BY_USER &&
-           wiphy->regd)
-               regd = get_wiphy_regdom(wiphy);
-       else
-               regd = get_cfg80211_regdom();
+       regd = reg_get_regdomain(wiphy);
 
        return freq_reg_info_regd(wiphy, center_freq, regd);
 }
@@ -857,31 +938,42 @@ 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(struct ieee80211_channel *chan,
+static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
+                                   struct ieee80211_channel *chan,
                                    const struct ieee80211_reg_rule *reg_rule)
 {
        const struct ieee80211_power_rule *power_rule;
        const struct ieee80211_freq_range *freq_range;
-       char max_antenna_gain[32];
+       char max_antenna_gain[32], bw[32];
 
        power_rule = &reg_rule->power_rule;
        freq_range = &reg_rule->freq_range;
 
        if (!power_rule->max_antenna_gain)
-               snprintf(max_antenna_gain, 32, "N/A");
+               snprintf(max_antenna_gain, sizeof(max_antenna_gain), "N/A");
        else
-               snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain);
+               snprintf(max_antenna_gain, sizeof(max_antenna_gain), "%d",
+                        power_rule->max_antenna_gain);
+
+       if (reg_rule->flags & NL80211_RRF_AUTO_BW)
+               snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO",
+                        freq_range->max_bandwidth_khz,
+                        reg_get_max_bandwidth(regd, reg_rule));
+       else
+               snprintf(bw, sizeof(bw), "%d KHz",
+                        freq_range->max_bandwidth_khz);
 
        REG_DBG_PRINT("Updating information on frequency %d MHz with regulatory rule:\n",
                      chan->center_freq);
 
-       REG_DBG_PRINT("%d KHz - %d KHz @ %d KHz), (%s mBi, %d mBm)\n",
+       REG_DBG_PRINT("%d KHz - %d KHz @ %s), (%s mBi, %d mBm)\n",
                      freq_range->start_freq_khz, freq_range->end_freq_khz,
-                     freq_range->max_bandwidth_khz, max_antenna_gain,
+                     bw, max_antenna_gain,
                      power_rule->max_eirp);
 }
 #else
-static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan,
+static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
+                                   struct ieee80211_channel *chan,
                                    const struct ieee80211_reg_rule *reg_rule)
 {
        return;
@@ -903,6 +995,8 @@ static void handle_channel(struct wiphy *wiphy,
        const struct ieee80211_freq_range *freq_range = NULL;
        struct wiphy *request_wiphy = NULL;
        struct regulatory_request *lr = get_last_request();
+       const struct ieee80211_regdomain *regd;
+       u32 max_bandwidth_khz;
 
        request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
 
@@ -939,16 +1033,22 @@ static void handle_channel(struct wiphy *wiphy,
                return;
        }
 
-       chan_reg_rule_print_dbg(chan, reg_rule);
+       regd = reg_get_regdomain(wiphy);
+       chan_reg_rule_print_dbg(regd, chan, reg_rule);
 
        power_rule = &reg_rule->power_rule;
        freq_range = &reg_rule->freq_range;
 
-       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
+       max_bandwidth_khz = freq_range->max_bandwidth_khz;
+       /* Check if auto calculation requested */
+       if (reg_rule->flags & NL80211_RRF_AUTO_BW)
+               max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
+
+       if (max_bandwidth_khz < MHZ_TO_KHZ(40))
                bw_flags = IEEE80211_CHAN_NO_HT40;
-       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(80))
+       if (max_bandwidth_khz < MHZ_TO_KHZ(80))
                bw_flags |= IEEE80211_CHAN_NO_80MHZ;
-       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(160))
+       if (max_bandwidth_khz < MHZ_TO_KHZ(160))
                bw_flags |= IEEE80211_CHAN_NO_160MHZ;
 
        if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
@@ -1334,6 +1434,7 @@ static void handle_channel_custom(struct wiphy *wiphy,
        const struct ieee80211_reg_rule *reg_rule = NULL;
        const struct ieee80211_power_rule *power_rule = NULL;
        const struct ieee80211_freq_range *freq_range = NULL;
+       u32 max_bandwidth_khz;
 
        reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq),
                                      regd);
@@ -1346,16 +1447,21 @@ static void handle_channel_custom(struct wiphy *wiphy,
                return;
        }
 
-       chan_reg_rule_print_dbg(chan, reg_rule);
+       chan_reg_rule_print_dbg(regd, chan, reg_rule);
 
        power_rule = &reg_rule->power_rule;
        freq_range = &reg_rule->freq_range;
 
-       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
+       max_bandwidth_khz = freq_range->max_bandwidth_khz;
+       /* Check if auto calculation requested */
+       if (reg_rule->flags & NL80211_RRF_AUTO_BW)
+               max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
+
+       if (max_bandwidth_khz < MHZ_TO_KHZ(40))
                bw_flags = IEEE80211_CHAN_NO_HT40;
-       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(80))
+       if (max_bandwidth_khz < MHZ_TO_KHZ(80))
                bw_flags |= IEEE80211_CHAN_NO_80MHZ;
-       if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(160))
+       if (max_bandwidth_khz < MHZ_TO_KHZ(160))
                bw_flags |= IEEE80211_CHAN_NO_160MHZ;
 
        chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
@@ -1683,17 +1789,9 @@ static void reg_process_hint(struct regulatory_request *reg_request)
        struct wiphy *wiphy = NULL;
        enum reg_request_treatment treatment;
 
-       if (WARN_ON(!reg_request->alpha2))
-               return;
-
        if (reg_request->wiphy_idx != WIPHY_IDX_INVALID)
                wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);
 
-       if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && !wiphy) {
-               kfree(reg_request);
-               return;
-       }
-
        switch (reg_request->initiator) {
        case NL80211_REGDOM_SET_BY_CORE:
                reg_process_hint_core(reg_request);
@@ -1703,23 +1801,33 @@ static void reg_process_hint(struct regulatory_request *reg_request)
                if (treatment == REG_REQ_OK ||
                    treatment == REG_REQ_ALREADY_SET)
                        return;
-               schedule_delayed_work(&reg_timeout, msecs_to_jiffies(3142));
+               queue_delayed_work(system_power_efficient_wq,
+                                  &reg_timeout, msecs_to_jiffies(3142));
                return;
        case NL80211_REGDOM_SET_BY_DRIVER:
+               if (!wiphy)
+                       goto out_free;
                treatment = reg_process_hint_driver(wiphy, reg_request);
                break;
        case NL80211_REGDOM_SET_BY_COUNTRY_IE:
+               if (!wiphy)
+                       goto out_free;
                treatment = reg_process_hint_country_ie(wiphy, reg_request);
                break;
        default:
                WARN(1, "invalid initiator %d\n", reg_request->initiator);
-               return;
+               goto out_free;
        }
 
        /* This is required so that the orig_* parameters are saved */
        if (treatment == REG_REQ_ALREADY_SET && wiphy &&
            wiphy->regulatory_flags & REGULATORY_STRICT_REG)
                wiphy_update_regulatory(wiphy, reg_request->initiator);
+
+       return;
+
+out_free:
+       kfree(reg_request);
 }
 
 /*
@@ -2147,6 +2255,7 @@ static void print_rd_rules(const struct ieee80211_regdomain *rd)
        const struct ieee80211_reg_rule *reg_rule = NULL;
        const struct ieee80211_freq_range *freq_range = NULL;
        const struct ieee80211_power_rule *power_rule = NULL;
+       char bw[32];
 
        pr_info("  (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)\n");
 
@@ -2155,22 +2264,30 @@ static void print_rd_rules(const struct ieee80211_regdomain *rd)
                freq_range = &reg_rule->freq_range;
                power_rule = &reg_rule->power_rule;
 
+               if (reg_rule->flags & NL80211_RRF_AUTO_BW)
+                       snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO",
+                                freq_range->max_bandwidth_khz,
+                                reg_get_max_bandwidth(rd, reg_rule));
+               else
+                       snprintf(bw, sizeof(bw), "%d KHz",
+                                freq_range->max_bandwidth_khz);
+
                /*
                 * There may not be documentation for max antenna gain
                 * in certain regions
                 */
                if (power_rule->max_antenna_gain)
-                       pr_info("  (%d KHz - %d KHz @ %d KHz), (%d mBi, %d mBm)\n",
+                       pr_info("  (%d KHz - %d KHz @ %s), (%d mBi, %d mBm)\n",
                                freq_range->start_freq_khz,
                                freq_range->end_freq_khz,
-                               freq_range->max_bandwidth_khz,
+                               bw,
                                power_rule->max_antenna_gain,
                                power_rule->max_eirp);
                else
-                       pr_info("  (%d KHz - %d KHz @ %d KHz), (N/A, %d mBm)\n",
+                       pr_info("  (%d KHz - %d KHz @ %s), (N/A, %d mBm)\n",
                                freq_range->start_freq_khz,
                                freq_range->end_freq_khz,
-                               freq_range->max_bandwidth_khz,
+                               bw,
                                power_rule->max_eirp);
        }
 }
@@ -2294,7 +2411,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) {
-               schedule_delayed_work(&reg_timeout, 0);
+               queue_delayed_work(system_power_efficient_wq,
+                                  &reg_timeout, 0);
                return -ENODEV;
        }
 
@@ -2354,7 +2472,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) {
-               schedule_delayed_work(&reg_timeout, 0);
+               queue_delayed_work(system_power_efficient_wq,
+                                  &reg_timeout, 0);
                return -ENODEV;
        }