This adds a new regulatory hint to be used when we know all devices have been disconnected and idle. This can happen when we suspend, for instance. When we disconnect we can no longer assume the same regulatory rules learned from a country IE or beacon hints are applicable so restore regulatory settings to an initial state. Signed-off-by: Luis R. Rodriguez <lrodriguez@xxxxxxxxxxx> --- I was trying to address adding suspend and resume support for the regulatory code. I then noticed we don't have a subsystem specific suspend and resume call we could hook into but only suspend and resume calls for each wireless device. It then occurred to me that instead of addressing this just for the suspend and resume case it would make sense to start things from a clean state after disconnection from APs. So I gave that a shot. This is what I came up with in the plane during the holidays, unfortunately there seems to be a locking bug somewhere as this hangs my system. Apart from that there the more obvious consideration I have for something like this is the delay incurred by the regulatory hint disconnect. Also, since this doesn't yet work I haven't been able to test where in the suspend cycle this would trigger yet. I was hoping it would trigger right before suspend and if not was considering flushing theg global workqueue right after we schedule the disconnect work. Another thing worth mentioning is that the driver hints would be stored on each wiphy->regd but would not be re-issued with this implementation but so technically it would not replicate the same regulatory settings when using multiple devices upon bootup. We *could* queue those up as well as this patch queues up the user hings but would we then treat them as new driver hints? It doesn't seem worth the effort. include/net/regulatory.h | 1 + net/wireless/core.c | 1 + net/wireless/reg.c | 170 ++++++++++++++++++++++++++++++++++++++++++++-- net/wireless/reg.h | 18 +++++ net/wireless/sme.c | 47 +++++++++++++ 5 files changed, 231 insertions(+), 6 deletions(-) diff --git a/include/net/regulatory.h b/include/net/regulatory.h index 47995b8..0d816ec 100644 --- a/include/net/regulatory.h +++ b/include/net/regulatory.h @@ -39,6 +39,7 @@ enum environment_cap { * 00 - World regulatory domain * 99 - built by driver but a specific alpha2 cannot be determined * 98 - result of an intersection between two regulatory domains + * 97 - regulatory domain has not yet been configured * @intersect: indicates whether the wireless core should intersect * the requested regulatory domain with the presently set regulatory * domain. diff --git a/net/wireless/core.c b/net/wireless/core.c index c2a2c56..ae58c6a 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -580,6 +580,7 @@ void wiphy_unregister(struct wiphy *wiphy) flush_work(&rdev->scan_done_wk); cancel_work_sync(&rdev->conn_work); + cancel_work_sync(&rdev->conn_work); flush_work(&rdev->event_work); } EXPORT_SYMBOL(wiphy_unregister); diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 87ea60d..3694cbe 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2,7 +2,7 @@ * Copyright 2002-2005, Instant802 Networks, Inc. * Copyright 2005-2006, Devicescape Software, Inc. * Copyright 2007 Johannes Berg <johannes@xxxxxxxxxxxxxxxx> - * Copyright 2008 Luis R. Rodriguez <lrodriguz@xxxxxxxxxxx> + * Copyright 2008-2010 Luis R. Rodriguez <lrodriguz@xxxxxxxxxxx> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -43,6 +43,15 @@ #include "regdb.h" #include "nl80211.h" +#ifdef CONFIG_CFG80211_REG_DEBUG +#define REG_DBG_PRINT(args...) \ + do { \ + printk(KERN_DEBUG args); \ + } while (0) +#else +#define REG_DBG_PRINT(args) +#endif + /* Receipt of information from last regulatory request */ static struct regulatory_request *last_request; @@ -125,10 +134,13 @@ static const struct ieee80211_regdomain *cfg80211_world_regdom = &world_regdom; static char *ieee80211_regdom = "00"; +static char user_alpha2[2]; module_param(ieee80211_regdom, charp, 0444); MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); +static bool is_user_regdom_saved(void); + static void reset_regdomains(void) { /* avoid freeing static information or freeing something twice */ @@ -243,6 +255,24 @@ static bool regdom_changes(const char *alpha2) return true; } +/* + * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets + * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER + * has ever been issued. + */ +static bool is_user_regdom_saved(void) +{ + if (user_alpha2[0] == '9' && user_alpha2[1] == '7') + return false; + + /* This would indicate a mistake on the design */ + if (WARN_ON(!is_world_regdom(user_alpha2) && + !is_an_alpha2(user_alpha2))) + return false; + + return true; +} + /** * country_ie_integrity_changes - tells us if the country IE has changed * @checksum: checksum of country IE of fields we are interested in @@ -1524,6 +1554,11 @@ new_request: pending_request = NULL; + if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) { + user_alpha2[0] = last_request->alpha2[0]; + user_alpha2[1] = last_request->alpha2[1]; + } + /* When r == REG_INTERSECT we do need to call CRDA */ if (r < 0) { /* @@ -1643,12 +1678,16 @@ static void queue_regulatory_request(struct regulatory_request *request) schedule_work(®_work); } -/* Core regulatory hint -- happens once during cfg80211_init() */ +/* + * Core regulatory hint -- happens during cfg80211_init() + * and when we restore regulatory settings. + */ static int regulatory_hint_core(const char *alpha2) { struct regulatory_request *request; - BUG_ON(last_request); + kfree(last_request); + last_request = NULL; request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); @@ -1662,8 +1701,8 @@ static int regulatory_hint_core(const char *alpha2) queue_regulatory_request(request); /* - * This ensures last_request is populated once modules - * come swinging in and calling regulatory hints and + * This ensures last_request is populated once new device + * drivers come swinging in and calling regulatory hints and * wiphy_apply_custom_regulatory(). */ flush_scheduled_work(); @@ -1685,7 +1724,7 @@ int regulatory_hint_user(const char *alpha2) request->wiphy_idx = WIPHY_IDX_STALE; request->alpha2[0] = alpha2[0]; request->alpha2[1] = alpha2[1]; - request->initiator = NL80211_REGDOM_SET_BY_USER, + request->initiator = NL80211_REGDOM_SET_BY_USER; queue_regulatory_request(request); @@ -1845,6 +1884,125 @@ out: mutex_unlock(®_mutex); } +static void restore_alpha2(char *alpha2, bool reset_user) +{ + /* indicates there is no alpha2 to consider for restoration */ + alpha2[0] = '9'; + alpha2[1] = '7'; + + /* The user setting has precedence over the module parameter */ + if (is_user_regdom_saved()) { + /* Unless we're asked to ignore it and reset it */ + if (reset_user) { + REG_DBG_PRINT("cfg80211: Restoring regulatory settings " + "including user preference\n"); + user_alpha2[0] = 9; + user_alpha2[1] = 7; + + /* + * If we're ignoring user settings, we still need to + * check the module parameter to ensure we put things + * back as they were for a full restore. + */ + if (!is_world_regdom(ieee80211_regdom)) { + REG_DBG_PRINT("cfg80211: Keeping preference on " + "module parameter ieee80211_regdom: %c%c\n", + ieee80211_regdom[0], + ieee80211_regdom[1]); + alpha2[0] = ieee80211_regdom[0]; + alpha2[1] = ieee80211_regdom[1]; + } + } else { + REG_DBG_PRINT("cfg80211: Restoring regulatory settings " + "while preserving user preference for: %c%c\n", + user_alpha2[0], + user_alpha2[1]); + alpha2[0] = user_alpha2[0]; + alpha2[1] = user_alpha2[1]; + } + } else if (!is_world_regdom(ieee80211_regdom)) { + REG_DBG_PRINT("cfg80211: Keeping preference on " + "module parameter ieee80211_regdom: %c%c\n", + ieee80211_regdom[0], + ieee80211_regdom[1]); + alpha2[0] = ieee80211_regdom[0]; + alpha2[1] = ieee80211_regdom[1]; + } else + REG_DBG_PRINT("cfg80211: Restoring regulatory settings\n"); +} + +/* + * Must hold cfg80211 mutex and reg_mutex. + * + * Restoring regulatory settings involves ingoring any + * possibly stale country IE information and user regulatory + * settings if so desired, this includes any beacon hints + * learned as we could have traveled outside to another country + * after disconnection. To restore regulatory settings we do + * exactly what we did at bootup: + * + * - send a core regulatory hint + * - send a user regulatory hint if applicable + * + * Device drivers that send a regulatory hint for a specific country + * keep their own regulatory domain on wiphy->regd so that does does + * not need to be remembered. + */ +static void restore_regulatory_settings(bool reset_user) +{ + char alpha2[2]; + struct reg_beacon *reg_beacon, *btmp; + + assert_cfg80211_lock(); + + reset_regdomains(); + restore_alpha2(alpha2, reset_user); + + /* Clear beacon hints */ + spin_lock_bh(®_pending_beacons_lock); + if (!list_empty(®_pending_beacons)) { + list_for_each_entry_safe(reg_beacon, btmp, + ®_pending_beacons, list) { + list_del(®_beacon->list); + kfree(reg_beacon); + } + } + spin_unlock_bh(®_pending_beacons_lock); + + if (!list_empty(®_beacon_list)) { + list_for_each_entry_safe(reg_beacon, btmp, + ®_beacon_list, list) { + list_del(®_beacon->list); + kfree(reg_beacon); + } + } + + /* First restore to the basic regulatory settings */ + cfg80211_regdomain = cfg80211_world_regdom; + regulatory_hint_core(cfg80211_regdomain->alpha2); + + /* + * This restores the ieee80211_regdom modular parameter + * preference or the last user requested regulatory + * settings, user regulatory settings takes precedence. + */ + if (is_an_alpha2(alpha2)) + regulatory_hint_user(user_alpha2); +} + + +/* Must hold cfg80211_mutex */ +void regulatory_hint_disconnect(void) +{ + assert_cfg80211_lock(); + + mutex_lock(®_mutex); + REG_DBG_PRINT("cfg80211: All devices are disconnected, going to " + "restore regulatory settings\n"); + restore_regulatory_settings(false); + mutex_unlock(®_mutex); +} + static bool freq_is_chan_12_13_14(u16 freq) { if (freq == ieee80211_channel_to_frequency(12) || diff --git a/net/wireless/reg.h b/net/wireless/reg.h index 3362c7c..8cce3ea 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h @@ -52,4 +52,22 @@ void regulatory_hint_11d(struct wiphy *wiphy, u8 *country_ie, u8 country_ie_len); +/** + * regulatory_hint_disconnect - informs all devices have been disconneted + * + * Regulotory rules can be enhanced further upon scanning and upon + * connection to an AP. These rules become stale if we disconnect + * and go to another country, whether or not we suspend and resume. + * If we suspend, go to another country and resume we'll automatically + * get disconnected shortly after resuming and things will be reset as well. + * This routine is a helper to restore regulatory settings to how they were + * prior to our first connect attempt. This includes ignoring country IE and + * beacon regulatory hints. The ieee80211_regdom module parameter will always + * be respected but if a user had set the regulatory domain that will take + * precedence. + * + * Must be called from process context and hold the cfg80211_mutex. + */ +void regulatory_hint_disconnect(void); + #endif /* __NET_WIRELESS_REG_H */ diff --git a/net/wireless/sme.c b/net/wireless/sme.c index 2333d78..e36eaac 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -34,6 +34,50 @@ struct cfg80211_conn { bool auto_auth, prev_bssid_valid; }; +bool cfg80211_is_all_idle(void) +{ + struct cfg80211_registered_device *rdev; + struct wireless_dev *wdev; + bool is_all_idle = true; + + rtnl_lock(); + + /* + * All devices must be idle as otherwise if you are actively + * scanning some new beacon hints could be learned and would + * count as new regulatory hints. + */ + list_for_each_entry(rdev, &cfg80211_rdev_list, list) { + cfg80211_lock_rdev(rdev); + list_for_each_entry(wdev, &rdev->netdev_list, list) { + wdev_lock(wdev); + if (wdev->sme_state != CFG80211_SME_IDLE) + is_all_idle = false; + wdev_unlock(wdev); + } + cfg80211_unlock_rdev(rdev); + } + rtnl_unlock(); + + return is_all_idle; +} + +static void disconnect_work(struct work_struct *work) +{ + printk("== regulatory_hint_disconnect()\n"); + + if (!cfg80211_is_all_idle()) { + printk("== Not all devices are idle..\n"); + return; + } + printk("== All devices are idle!\n"); + + mutex_lock(&cfg80211_mutex); + regulatory_hint_disconnect(); + mutex_unlock(&cfg80211_mutex); +} + +static DECLARE_WORK(cfg80211_disconnect_work, disconnect_work); static int cfg80211_conn_scan(struct wireless_dev *wdev) { @@ -656,6 +700,9 @@ void __cfg80211_disconnected(struct net_device *dev, const u8 *ie, wrqu.ap_addr.sa_family = ARPHRD_ETHER; wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); #endif + + schedule_work(&cfg80211_disconnect_work); + //flush_scheduled_work(); } void cfg80211_disconnected(struct net_device *dev, u16 reason, -- 1.6.3.3 -- To unsubscribe from this list: send the line "unsubscribe linux-wireless" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html