+ /*
+ * Please note that this assumes target bandwidth is 20 MHz,
+ * if that ever changes we also need to change the below logic
+ * to include that as well.
+ */
+ if (is_ht40_not_allowed(channel_before))
+ channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
+ else
+ channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
+
+ if (is_ht40_not_allowed(channel_after))
+ channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
+ else
+ channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
+}
+
+static void reg_process_ht_flags_band(struct wiphy *wiphy,
+ enum ieee80211_band band)
+{
+ unsigned int i;
+ struct ieee80211_supported_band *sband;
+
+ BUG_ON(!wiphy->bands[band]);
+ sband = wiphy->bands[band];
+
+ for (i = 0; i < sband->n_channels; i++)
+ reg_process_ht_flags_channel(wiphy, band, i);
+}
+
+static void reg_process_ht_flags(struct wiphy *wiphy)
+{
+ enum ieee80211_band band;
+
+ if (!wiphy)
+ return;
+
+ for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+ if (wiphy->bands[band])
+ reg_process_ht_flags_band(wiphy, band);
+ }
+
+}
+
+void wiphy_update_regulatory(struct wiphy *wiphy,
+ enum nl80211_reg_initiator initiator)
+{
+ enum ieee80211_band band;
+
+ if (ignore_reg_update(wiphy, initiator))
+ goto out;
+ for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+ if (wiphy->bands[band])
+ handle_band(wiphy, band);
+ }
+out:
+ reg_process_beacons(wiphy);
+ reg_process_ht_flags(wiphy);
+ if (wiphy->reg_notifier)
+ wiphy->reg_notifier(wiphy, last_request);
+}
+
+static void handle_channel_custom(struct wiphy *wiphy,
+ enum ieee80211_band band,
+ unsigned int chan_idx,
+ const struct ieee80211_regdomain *regd)
+{
+ int r;
+ u32 desired_bw_khz = MHZ_TO_KHZ(20);
+ u32 bw_flags = 0;
+ const struct ieee80211_reg_rule *reg_rule = NULL;
+ const struct ieee80211_power_rule *power_rule = NULL;
+ const struct ieee80211_freq_range *freq_range = NULL;
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_channel *chan;
+
+ assert_cfg80211_lock();
+
+ sband = wiphy->bands[band];
+ BUG_ON(chan_idx >= sband->n_channels);
+ chan = &sband->channels[chan_idx];
+
+ r = freq_reg_info_regd(wiphy,
+ MHZ_TO_KHZ(chan->center_freq),
+ desired_bw_khz,
+ ®_rule,
+ regd);
+
+ if (r) {
+ chan->flags = IEEE80211_CHAN_DISABLED;
+ return;
+ }
+
+ power_rule = ®_rule->power_rule;
+ freq_range = ®_rule->freq_range;
+
+ if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
+ bw_flags = IEEE80211_CHAN_NO_HT40;
+
+ 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_bandwidth = KHZ_TO_MHZ(desired_bw_khz);
+ chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
+}
+
+static void handle_band_custom(struct wiphy *wiphy, enum ieee80211_band band,
+ const struct ieee80211_regdomain *regd)
+{
+ unsigned int i;
+ struct ieee80211_supported_band *sband;
+
+ BUG_ON(!wiphy->bands[band]);
+ sband = wiphy->bands[band];
+
+ for (i = 0; i < sband->n_channels; i++)
+ handle_channel_custom(wiphy, band, i, regd);
+}
+
+/* Used by drivers prior to wiphy registration */
+void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
+ const struct ieee80211_regdomain *regd)
+{
+ enum ieee80211_band band;
+ unsigned int bands_set = 0;
+
+ mutex_lock(&cfg80211_mutex);
+ for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
+ if (!wiphy->bands[band])
+ continue;
+ handle_band_custom(wiphy, band, regd);
+ bands_set++;
+ }
+ mutex_unlock(&cfg80211_mutex);
+
+ /*
+ * no point in calling this if it won't have any effect
+ * on your device's supportd bands.
+ */
+ WARN_ON(!bands_set);
+}
+EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
+
+static int reg_copy_regd(const struct ieee80211_regdomain **dst_regd,
+ const struct ieee80211_regdomain *src_regd)
+{
+ struct ieee80211_regdomain *regd;
+ int size_of_regd = 0;
+ unsigned int i;
+
+ size_of_regd = sizeof(struct ieee80211_regdomain) +
+ ((src_regd->n_reg_rules + 1) * sizeof(struct ieee80211_reg_rule));
+
+ regd = kzalloc(size_of_regd, GFP_KERNEL);
+ if (!regd)
+ return -ENOMEM;
+
+ memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain));
+
+ for (i = 0; i < src_regd->n_reg_rules; i++)
+ memcpy(®d->reg_rules[i], &src_regd->reg_rules[i],
+ sizeof(struct ieee80211_reg_rule));
+
+ *dst_regd = regd;
+ return 0;
+}
+
+/*
+ * Return value which can be used by ignore_request() to indicate
+ * it has been determined we should intersect two regulatory domains
+ */
+#define REG_INTERSECT 1
+
+/* This has the logic which determines when a new request
+ * should be ignored. */
+static int ignore_request(struct wiphy *wiphy,
+ struct regulatory_request *pending_request)
+{
+ struct wiphy *last_wiphy = NULL;
+
+ assert_cfg80211_lock();
+
+ /* All initial requests are respected */
+ if (!last_request)
+ return 0;
+
+ switch (pending_request->initiator) {
+ case NL80211_REGDOM_SET_BY_CORE:
+ return -EINVAL;
+ case NL80211_REGDOM_SET_BY_COUNTRY_IE:
+
+ last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
+
+ if (unlikely(!is_an_alpha2(pending_request->alpha2)))
+ return -EINVAL;
+ if (last_request->initiator ==
+ NL80211_REGDOM_SET_BY_COUNTRY_IE) {
+ if (last_wiphy != wiphy) {
+ /*
+ * Two cards with two APs claiming different
+ * different Country IE alpha2s. We could
+ * intersect them, but that seems unlikely
+ * to be correct. Reject second one for now.
+ */
+ if (regdom_changes(pending_request->alpha2))
+ return -EOPNOTSUPP;
+ return -EALREADY;
+ }
+ /*
+ * Two consecutive Country IE hints on the same wiphy.
+ * This should be picked up early by the driver/stack
+ */
+ if (WARN_ON(regdom_changes(pending_request->alpha2)))
+ return 0;
+ return -EALREADY;
+ }
+ return REG_INTERSECT;
+ case NL80211_REGDOM_SET_BY_DRIVER:
+ if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE) {
+ if (is_old_static_regdom(cfg80211_regdomain))
+ return 0;
+ if (regdom_changes(pending_request->alpha2))
+ return 0;
+ return -EALREADY;
+ }
+
+ /*
+ * This would happen if you unplug and plug your card
+ * back in or if you add a new device for which the previously
+ * loaded card also agrees on the regulatory domain.
+ */
+ if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
+ !regdom_changes(pending_request->alpha2))
+ return -EALREADY;
+
+ return REG_INTERSECT;
+ case NL80211_REGDOM_SET_BY_USER:
+ if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
+ return REG_INTERSECT;
+ /*
+ * If the user knows better the user should set the regdom
+ * to their country before the IE is picked up
+ */
+ if (last_request->initiator == NL80211_REGDOM_SET_BY_USER &&
+ last_request->intersect)
+ return -EOPNOTSUPP;
+ /*
+ * Process user requests only after previous user/driver/core
+ * requests have been processed
+ */
+ if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE ||
+ last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER ||
+ last_request->initiator == NL80211_REGDOM_SET_BY_USER) {
+ if (regdom_changes(last_request->alpha2))
+ return -EAGAIN;
+ }
+
+ if (!is_old_static_regdom(cfg80211_regdomain) &&
+ !regdom_changes(pending_request->alpha2))
+ return -EALREADY;
+
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+/**
+ * __regulatory_hint - hint to the wireless core a regulatory domain
+ * @wiphy: if the hint comes from country information from an AP, this
+ * is required to be set to the wiphy that received the information
+ * @pending_request: the regulatory request currently being processed
+ *
+ * The Wireless subsystem can use this function to hint to the wireless core
+ * what it believes should be the current regulatory domain.
+ *
+ * Returns zero if all went fine, %-EALREADY if a regulatory domain had
+ * already been set or other standard error codes.
+ *
+ * Caller must hold &cfg80211_mutex
+ */
+static int __regulatory_hint(struct wiphy *wiphy,
+ struct regulatory_request *pending_request)
+{
+ bool intersect = false;
+ int r = 0;
+
+ assert_cfg80211_lock();
+
+ r = ignore_request(wiphy, pending_request);
+
+ if (r == REG_INTERSECT) {
+ if (pending_request->initiator ==
+ NL80211_REGDOM_SET_BY_DRIVER) {
+ r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain);
+ if (r) {
+ kfree(pending_request);
+ return r;
+ }
+ }
+ intersect = true;
+ } else if (r) {
+ /*
+ * If the regulatory domain being requested by the
+ * driver has already been set just copy it to the
+ * wiphy
+ */
+ if (r == -EALREADY &&
+ pending_request->initiator ==
+ NL80211_REGDOM_SET_BY_DRIVER) {
+ r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain);
+ if (r) {
+ kfree(pending_request);
+ return r;
+ }
+ r = -EALREADY;
+ goto new_request;
+ }
+ kfree(pending_request);
+ return r;
+ }
+
+new_request:
+ kfree(last_request);
+
+ last_request = pending_request;
+ last_request->intersect = intersect;
+
+ pending_request = NULL;
+
+ /* When r == REG_INTERSECT we do need to call CRDA */
+ if (r < 0) {
+ /*
+ * Since CRDA will not be called in this case as we already
+ * have applied the requested regulatory domain before we just
+ * inform userspace we have processed the request
+ */
+ if (r == -EALREADY)
+ nl80211_send_reg_change_event(last_request);
+ return r;
+ }
+
+ return call_crda(last_request->alpha2);
+}
+
+/* This processes *all* regulatory hints */
+static void reg_process_hint(struct regulatory_request *reg_request)
+{
+ int r = 0;
+ struct wiphy *wiphy = NULL;
+
+ BUG_ON(!reg_request->alpha2);
+
+ mutex_lock(&cfg80211_mutex);
+
+ if (wiphy_idx_valid(reg_request->wiphy_idx))
+ wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);
+
+ if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
+ !wiphy) {
+ kfree(reg_request);
+ goto out;
+ }
+
+ r = __regulatory_hint(wiphy, reg_request);
+ /* This is required so that the orig_* parameters are saved */
+ if (r == -EALREADY && wiphy && wiphy->strict_regulatory)
+ wiphy_update_regulatory(wiphy, reg_request->initiator);
+out:
+ mutex_unlock(&cfg80211_mutex);
+}
+
+/* Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* */
+static void reg_process_pending_hints(void)
+ {
+ struct regulatory_request *reg_request;
+
+ spin_lock(®_requests_lock);
+ while (!list_empty(®_requests_list)) {
+ reg_request = list_first_entry(®_requests_list,
+ struct regulatory_request,
+ list);
+ list_del_init(®_request->list);
+
+ spin_unlock(®_requests_lock);
+ reg_process_hint(reg_request);
+ spin_lock(®_requests_lock);
+ }
+ spin_unlock(®_requests_lock);
+}
+
+/* Processes beacon hints -- this has nothing to do with country IEs */
+static void reg_process_pending_beacon_hints(void)
+{
+ struct cfg80211_registered_device *drv;
+ struct reg_beacon *pending_beacon, *tmp;
+
+ mutex_lock(&cfg80211_mutex);
+
+ /* This goes through the _pending_ beacon list */
+ spin_lock_bh(®_pending_beacons_lock);
+
+ if (list_empty(®_pending_beacons)) {
+ spin_unlock_bh(®_pending_beacons_lock);
+ goto out;
+ }
+
+ list_for_each_entry_safe(pending_beacon, tmp,
+ ®_pending_beacons, list) {
+
+ list_del_init(&pending_beacon->list);
+
+ /* Applies the beacon hint to current wiphys */
+ list_for_each_entry(drv, &cfg80211_drv_list, list)
+ wiphy_update_new_beacon(&drv->wiphy, pending_beacon);
+
+ /* Remembers the beacon hint for new wiphys or reg changes */
+ list_add_tail(&pending_beacon->list, ®_beacon_list);
+ }
+
+ spin_unlock_bh(®_pending_beacons_lock);
+out:
+ mutex_unlock(&cfg80211_mutex);
+}
+
+static void reg_todo(struct work_struct *work)
+{
+ reg_process_pending_hints();
+ reg_process_pending_beacon_hints();
+}
+
+static DECLARE_WORK(reg_work, reg_todo);
+
+static void queue_regulatory_request(struct regulatory_request *request)
+{
+ spin_lock(®_requests_lock);
+ list_add_tail(&request->list, ®_requests_list);
+ spin_unlock(®_requests_lock);
+
+ schedule_work(®_work);
+}
+
+/* Core regulatory hint -- happens once during cfg80211_init() */
+static int regulatory_hint_core(const char *alpha2)
+{
+ struct regulatory_request *request;
+
+ BUG_ON(last_request);
+
+ request = kzalloc(sizeof(struct regulatory_request),
+ GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->alpha2[0] = alpha2[0];
+ request->alpha2[1] = alpha2[1];
+ request->initiator = NL80211_REGDOM_SET_BY_CORE;
+
+ queue_regulatory_request(request);
+
+ /*
+ * This ensures last_request is populated once modules
+ * come swinging in and calling regulatory hints and
+ * wiphy_apply_custom_regulatory().
+ */
+ flush_scheduled_work();
+
+ return 0;
+}
+
+/* User hints */
+int regulatory_hint_user(const char *alpha2)
+{
+ struct regulatory_request *request;
+
+ BUG_ON(!alpha2);
+
+ request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->wiphy_idx = WIPHY_IDX_STALE;
+ request->alpha2[0] = alpha2[0];
+ request->alpha2[1] = alpha2[1];
+ request->initiator = NL80211_REGDOM_SET_BY_USER,
+
+ queue_regulatory_request(request);
+
+ return 0;
+}
+
+/* Driver hints */
+int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
+{
+ struct regulatory_request *request;
+
+ BUG_ON(!alpha2);
+ BUG_ON(!wiphy);
+
+ request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
+ if (!request)
+ return -ENOMEM;
+
+ request->wiphy_idx = get_wiphy_idx(wiphy);
+
+ /* Must have registered wiphy first */
+ BUG_ON(!wiphy_idx_valid(request->wiphy_idx));
+
+ request->alpha2[0] = alpha2[0];
+ request->alpha2[1] = alpha2[1];
+ request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
+
+ queue_regulatory_request(request);
+
+ return 0;
+}
+EXPORT_SYMBOL(regulatory_hint);
+
+static bool reg_same_country_ie_hint(struct wiphy *wiphy,
+ u32 country_ie_checksum)
+{
+ struct wiphy *request_wiphy;
+
+ assert_cfg80211_lock();
+
+ if (unlikely(last_request->initiator !=
+ NL80211_REGDOM_SET_BY_COUNTRY_IE))
+ return false;
+
+ request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
+
+ if (!request_wiphy)
+ return false;
+
+ if (likely(request_wiphy != wiphy))
+ return !country_ie_integrity_changes(country_ie_checksum);
+ /*
+ * We should not have let these through at this point, they
+ * should have been picked up earlier by the first alpha2 check
+ * on the device
+ */
+ if (WARN_ON(!country_ie_integrity_changes(country_ie_checksum)))
+ return true;
+ return false;
+}
+
+void regulatory_hint_11d(struct wiphy *wiphy,
+ u8 *country_ie,
+ u8 country_ie_len)
+{
+ struct ieee80211_regdomain *rd = NULL;
+ char alpha2[2];
+ u32 checksum = 0;
+ enum environment_cap env = ENVIRON_ANY;
+ struct regulatory_request *request;
+
+ mutex_lock(&cfg80211_mutex);
+
+ if (unlikely(!last_request)) {
+ mutex_unlock(&cfg80211_mutex);
+ return;
+ }
+
+ /* IE len must be evenly divisible by 2 */
+ if (country_ie_len & 0x01)
+ goto out;
+
+ if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
+ goto out;
+
+ /*
+ * Pending country IE processing, this can happen after we
+ * call CRDA and wait for a response if a beacon was received before
+ * we were able to process the last regulatory_hint_11d() call
+ */
+ if (country_ie_regdomain)
+ goto out;
+
+ alpha2[0] = country_ie[0];
+ alpha2[1] = country_ie[1];