diff options
-rw-r--r-- | include/net/regulatory.h | 1 | ||||
-rw-r--r-- | net/wireless/reg.c | 157 | ||||
-rw-r--r-- | net/wireless/reg.h | 18 | ||||
-rw-r--r-- | net/wireless/sme.c | 40 |
4 files changed, 213 insertions, 3 deletions
diff --git a/include/net/regulatory.h b/include/net/regulatory.h index 47995b81c5d7..f873ee37f7e4 100644 --- a/include/net/regulatory.h +++ b/include/net/regulatory.h | |||
@@ -39,6 +39,7 @@ enum environment_cap { | |||
39 | * 00 - World regulatory domain | 39 | * 00 - World regulatory domain |
40 | * 99 - built by driver but a specific alpha2 cannot be determined | 40 | * 99 - built by driver but a specific alpha2 cannot be determined |
41 | * 98 - result of an intersection between two regulatory domains | 41 | * 98 - result of an intersection between two regulatory domains |
42 | * 97 - regulatory domain has not yet been configured | ||
42 | * @intersect: indicates whether the wireless core should intersect | 43 | * @intersect: indicates whether the wireless core should intersect |
43 | * the requested regulatory domain with the presently set regulatory | 44 | * the requested regulatory domain with the presently set regulatory |
44 | * domain. | 45 | * domain. |
diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 5dcda28b6f04..ed89c59bb431 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c | |||
@@ -134,6 +134,7 @@ static const struct ieee80211_regdomain *cfg80211_world_regdom = | |||
134 | &world_regdom; | 134 | &world_regdom; |
135 | 135 | ||
136 | static char *ieee80211_regdom = "00"; | 136 | static char *ieee80211_regdom = "00"; |
137 | static char user_alpha2[2]; | ||
137 | 138 | ||
138 | module_param(ieee80211_regdom, charp, 0444); | 139 | module_param(ieee80211_regdom, charp, 0444); |
139 | MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); | 140 | MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); |
@@ -252,6 +253,27 @@ static bool regdom_changes(const char *alpha2) | |||
252 | return true; | 253 | return true; |
253 | } | 254 | } |
254 | 255 | ||
256 | /* | ||
257 | * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets | ||
258 | * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER | ||
259 | * has ever been issued. | ||
260 | */ | ||
261 | static bool is_user_regdom_saved(void) | ||
262 | { | ||
263 | if (user_alpha2[0] == '9' && user_alpha2[1] == '7') | ||
264 | return false; | ||
265 | |||
266 | /* This would indicate a mistake on the design */ | ||
267 | if (WARN((!is_world_regdom(user_alpha2) && | ||
268 | !is_an_alpha2(user_alpha2)), | ||
269 | "Unexpected user alpha2: %c%c\n", | ||
270 | user_alpha2[0], | ||
271 | user_alpha2[1])) | ||
272 | return false; | ||
273 | |||
274 | return true; | ||
275 | } | ||
276 | |||
255 | /** | 277 | /** |
256 | * country_ie_integrity_changes - tells us if the country IE has changed | 278 | * country_ie_integrity_changes - tells us if the country IE has changed |
257 | * @checksum: checksum of country IE of fields we are interested in | 279 | * @checksum: checksum of country IE of fields we are interested in |
@@ -1646,7 +1668,7 @@ static int ignore_request(struct wiphy *wiphy, | |||
1646 | 1668 | ||
1647 | switch (pending_request->initiator) { | 1669 | switch (pending_request->initiator) { |
1648 | case NL80211_REGDOM_SET_BY_CORE: | 1670 | case NL80211_REGDOM_SET_BY_CORE: |
1649 | return -EINVAL; | 1671 | return 0; |
1650 | case NL80211_REGDOM_SET_BY_COUNTRY_IE: | 1672 | case NL80211_REGDOM_SET_BY_COUNTRY_IE: |
1651 | 1673 | ||
1652 | last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); | 1674 | last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); |
@@ -1785,6 +1807,11 @@ new_request: | |||
1785 | 1807 | ||
1786 | pending_request = NULL; | 1808 | pending_request = NULL; |
1787 | 1809 | ||
1810 | if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) { | ||
1811 | user_alpha2[0] = last_request->alpha2[0]; | ||
1812 | user_alpha2[1] = last_request->alpha2[1]; | ||
1813 | } | ||
1814 | |||
1788 | /* When r == REG_INTERSECT we do need to call CRDA */ | 1815 | /* When r == REG_INTERSECT we do need to call CRDA */ |
1789 | if (r < 0) { | 1816 | if (r < 0) { |
1790 | /* | 1817 | /* |
@@ -1904,12 +1931,16 @@ static void queue_regulatory_request(struct regulatory_request *request) | |||
1904 | schedule_work(®_work); | 1931 | schedule_work(®_work); |
1905 | } | 1932 | } |
1906 | 1933 | ||
1907 | /* Core regulatory hint -- happens once during cfg80211_init() */ | 1934 | /* |
1935 | * Core regulatory hint -- happens during cfg80211_init() | ||
1936 | * and when we restore regulatory settings. | ||
1937 | */ | ||
1908 | static int regulatory_hint_core(const char *alpha2) | 1938 | static int regulatory_hint_core(const char *alpha2) |
1909 | { | 1939 | { |
1910 | struct regulatory_request *request; | 1940 | struct regulatory_request *request; |
1911 | 1941 | ||
1912 | BUG_ON(last_request); | 1942 | kfree(last_request); |
1943 | last_request = NULL; | ||
1913 | 1944 | ||
1914 | request = kzalloc(sizeof(struct regulatory_request), | 1945 | request = kzalloc(sizeof(struct regulatory_request), |
1915 | GFP_KERNEL); | 1946 | GFP_KERNEL); |
@@ -2107,6 +2138,123 @@ out: | |||
2107 | mutex_unlock(®_mutex); | 2138 | mutex_unlock(®_mutex); |
2108 | } | 2139 | } |
2109 | 2140 | ||
2141 | static void restore_alpha2(char *alpha2, bool reset_user) | ||
2142 | { | ||
2143 | /* indicates there is no alpha2 to consider for restoration */ | ||
2144 | alpha2[0] = '9'; | ||
2145 | alpha2[1] = '7'; | ||
2146 | |||
2147 | /* The user setting has precedence over the module parameter */ | ||
2148 | if (is_user_regdom_saved()) { | ||
2149 | /* Unless we're asked to ignore it and reset it */ | ||
2150 | if (reset_user) { | ||
2151 | REG_DBG_PRINT("cfg80211: Restoring regulatory settings " | ||
2152 | "including user preference\n"); | ||
2153 | user_alpha2[0] = '9'; | ||
2154 | user_alpha2[1] = '7'; | ||
2155 | |||
2156 | /* | ||
2157 | * If we're ignoring user settings, we still need to | ||
2158 | * check the module parameter to ensure we put things | ||
2159 | * back as they were for a full restore. | ||
2160 | */ | ||
2161 | if (!is_world_regdom(ieee80211_regdom)) { | ||
2162 | REG_DBG_PRINT("cfg80211: Keeping preference on " | ||
2163 | "module parameter ieee80211_regdom: %c%c\n", | ||
2164 | ieee80211_regdom[0], | ||
2165 | ieee80211_regdom[1]); | ||
2166 | alpha2[0] = ieee80211_regdom[0]; | ||
2167 | alpha2[1] = ieee80211_regdom[1]; | ||
2168 | } | ||
2169 | } else { | ||
2170 | REG_DBG_PRINT("cfg80211: Restoring regulatory settings " | ||
2171 | "while preserving user preference for: %c%c\n", | ||
2172 | user_alpha2[0], | ||
2173 | user_alpha2[1]); | ||
2174 | alpha2[0] = user_alpha2[0]; | ||
2175 | alpha2[1] = user_alpha2[1]; | ||
2176 | } | ||
2177 | } else if (!is_world_regdom(ieee80211_regdom)) { | ||
2178 | REG_DBG_PRINT("cfg80211: Keeping preference on " | ||
2179 | "module parameter ieee80211_regdom: %c%c\n", | ||
2180 | ieee80211_regdom[0], | ||
2181 | ieee80211_regdom[1]); | ||
2182 | alpha2[0] = ieee80211_regdom[0]; | ||
2183 | alpha2[1] = ieee80211_regdom[1]; | ||
2184 | } else | ||
2185 | REG_DBG_PRINT("cfg80211: Restoring regulatory settings\n"); | ||
2186 | } | ||
2187 | |||
2188 | /* | ||
2189 | * Restoring regulatory settings involves ingoring any | ||
2190 | * possibly stale country IE information and user regulatory | ||
2191 | * settings if so desired, this includes any beacon hints | ||
2192 | * learned as we could have traveled outside to another country | ||
2193 | * after disconnection. To restore regulatory settings we do | ||
2194 | * exactly what we did at bootup: | ||
2195 | * | ||
2196 | * - send a core regulatory hint | ||
2197 | * - send a user regulatory hint if applicable | ||
2198 | * | ||
2199 | * Device drivers that send a regulatory hint for a specific country | ||
2200 | * keep their own regulatory domain on wiphy->regd so that does does | ||
2201 | * not need to be remembered. | ||
2202 | */ | ||
2203 | static void restore_regulatory_settings(bool reset_user) | ||
2204 | { | ||
2205 | char alpha2[2]; | ||
2206 | struct reg_beacon *reg_beacon, *btmp; | ||
2207 | |||
2208 | mutex_lock(&cfg80211_mutex); | ||
2209 | mutex_lock(®_mutex); | ||
2210 | |||
2211 | reset_regdomains(); | ||
2212 | restore_alpha2(alpha2, reset_user); | ||
2213 | |||
2214 | /* Clear beacon hints */ | ||
2215 | spin_lock_bh(®_pending_beacons_lock); | ||
2216 | if (!list_empty(®_pending_beacons)) { | ||
2217 | list_for_each_entry_safe(reg_beacon, btmp, | ||
2218 | ®_pending_beacons, list) { | ||
2219 | list_del(®_beacon->list); | ||
2220 | kfree(reg_beacon); | ||
2221 | } | ||
2222 | } | ||
2223 | spin_unlock_bh(®_pending_beacons_lock); | ||
2224 | |||
2225 | if (!list_empty(®_beacon_list)) { | ||
2226 | list_for_each_entry_safe(reg_beacon, btmp, | ||
2227 | ®_beacon_list, list) { | ||
2228 | list_del(®_beacon->list); | ||
2229 | kfree(reg_beacon); | ||
2230 | } | ||
2231 | } | ||
2232 | |||
2233 | /* First restore to the basic regulatory settings */ | ||
2234 | cfg80211_regdomain = cfg80211_world_regdom; | ||
2235 | |||
2236 | mutex_unlock(®_mutex); | ||
2237 | mutex_unlock(&cfg80211_mutex); | ||
2238 | |||
2239 | regulatory_hint_core(cfg80211_regdomain->alpha2); | ||
2240 | |||
2241 | /* | ||
2242 | * This restores the ieee80211_regdom module parameter | ||
2243 | * preference or the last user requested regulatory | ||
2244 | * settings, user regulatory settings takes precedence. | ||
2245 | */ | ||
2246 | if (is_an_alpha2(alpha2)) | ||
2247 | regulatory_hint_user(user_alpha2); | ||
2248 | } | ||
2249 | |||
2250 | |||
2251 | void regulatory_hint_disconnect(void) | ||
2252 | { | ||
2253 | REG_DBG_PRINT("cfg80211: All devices are disconnected, going to " | ||
2254 | "restore regulatory settings\n"); | ||
2255 | restore_regulatory_settings(false); | ||
2256 | } | ||
2257 | |||
2110 | static bool freq_is_chan_12_13_14(u16 freq) | 2258 | static bool freq_is_chan_12_13_14(u16 freq) |
2111 | { | 2259 | { |
2112 | if (freq == ieee80211_channel_to_frequency(12) || | 2260 | if (freq == ieee80211_channel_to_frequency(12) || |
@@ -2496,6 +2644,9 @@ int regulatory_init(void) | |||
2496 | 2644 | ||
2497 | cfg80211_regdomain = cfg80211_world_regdom; | 2645 | cfg80211_regdomain = cfg80211_world_regdom; |
2498 | 2646 | ||
2647 | user_alpha2[0] = '9'; | ||
2648 | user_alpha2[1] = '7'; | ||
2649 | |||
2499 | /* We always try to get an update for the static regdomain */ | 2650 | /* We always try to get an update for the static regdomain */ |
2500 | err = regulatory_hint_core(cfg80211_regdomain->alpha2); | 2651 | err = regulatory_hint_core(cfg80211_regdomain->alpha2); |
2501 | if (err) { | 2652 | if (err) { |
diff --git a/net/wireless/reg.h b/net/wireless/reg.h index 3018508226ab..b26224a9f3bc 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h | |||
@@ -63,4 +63,22 @@ void regulatory_hint_11d(struct wiphy *wiphy, | |||
63 | u8 *country_ie, | 63 | u8 *country_ie, |
64 | u8 country_ie_len); | 64 | u8 country_ie_len); |
65 | 65 | ||
66 | /** | ||
67 | * regulatory_hint_disconnect - informs all devices have been disconneted | ||
68 | * | ||
69 | * Regulotory rules can be enhanced further upon scanning and upon | ||
70 | * connection to an AP. These rules become stale if we disconnect | ||
71 | * and go to another country, whether or not we suspend and resume. | ||
72 | * If we suspend, go to another country and resume we'll automatically | ||
73 | * get disconnected shortly after resuming and things will be reset as well. | ||
74 | * This routine is a helper to restore regulatory settings to how they were | ||
75 | * prior to our first connect attempt. This includes ignoring country IE and | ||
76 | * beacon regulatory hints. The ieee80211_regdom module parameter will always | ||
77 | * be respected but if a user had set the regulatory domain that will take | ||
78 | * precedence. | ||
79 | * | ||
80 | * Must be called from process context. | ||
81 | */ | ||
82 | void regulatory_hint_disconnect(void); | ||
83 | |||
66 | #endif /* __NET_WIRELESS_REG_H */ | 84 | #endif /* __NET_WIRELESS_REG_H */ |
diff --git a/net/wireless/sme.c b/net/wireless/sme.c index 745c37e7992e..17fde0da1b08 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c | |||
@@ -34,6 +34,44 @@ struct cfg80211_conn { | |||
34 | bool auto_auth, prev_bssid_valid; | 34 | bool auto_auth, prev_bssid_valid; |
35 | }; | 35 | }; |
36 | 36 | ||
37 | bool cfg80211_is_all_idle(void) | ||
38 | { | ||
39 | struct cfg80211_registered_device *rdev; | ||
40 | struct wireless_dev *wdev; | ||
41 | bool is_all_idle = true; | ||
42 | |||
43 | mutex_lock(&cfg80211_mutex); | ||
44 | |||
45 | /* | ||
46 | * All devices must be idle as otherwise if you are actively | ||
47 | * scanning some new beacon hints could be learned and would | ||
48 | * count as new regulatory hints. | ||
49 | */ | ||
50 | list_for_each_entry(rdev, &cfg80211_rdev_list, list) { | ||
51 | cfg80211_lock_rdev(rdev); | ||
52 | list_for_each_entry(wdev, &rdev->netdev_list, list) { | ||
53 | wdev_lock(wdev); | ||
54 | if (wdev->sme_state != CFG80211_SME_IDLE) | ||
55 | is_all_idle = false; | ||
56 | wdev_unlock(wdev); | ||
57 | } | ||
58 | cfg80211_unlock_rdev(rdev); | ||
59 | } | ||
60 | |||
61 | mutex_unlock(&cfg80211_mutex); | ||
62 | |||
63 | return is_all_idle; | ||
64 | } | ||
65 | |||
66 | static void disconnect_work(struct work_struct *work) | ||
67 | { | ||
68 | if (!cfg80211_is_all_idle()) | ||
69 | return; | ||
70 | |||
71 | regulatory_hint_disconnect(); | ||
72 | } | ||
73 | |||
74 | static DECLARE_WORK(cfg80211_disconnect_work, disconnect_work); | ||
37 | 75 | ||
38 | static int cfg80211_conn_scan(struct wireless_dev *wdev) | 76 | static int cfg80211_conn_scan(struct wireless_dev *wdev) |
39 | { | 77 | { |
@@ -658,6 +696,8 @@ void __cfg80211_disconnected(struct net_device *dev, const u8 *ie, | |||
658 | wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); | 696 | wireless_send_event(dev, SIOCGIWAP, &wrqu, NULL); |
659 | wdev->wext.connect.ssid_len = 0; | 697 | wdev->wext.connect.ssid_len = 0; |
660 | #endif | 698 | #endif |
699 | |||
700 | schedule_work(&cfg80211_disconnect_work); | ||
661 | } | 701 | } |
662 | 702 | ||
663 | void cfg80211_disconnected(struct net_device *dev, u16 reason, | 703 | void cfg80211_disconnected(struct net_device *dev, u16 reason, |