diff options
author | Johannes Berg <johannes.berg@intel.com> | 2012-12-06 09:47:38 -0500 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2013-01-03 07:01:29 -0500 |
commit | 458f4f9e960b9a3b674c4b87d996eef186b1fe83 (patch) | |
tree | febf655badc2a045b3aaaf63271226729c76e325 /net/wireless/reg.c | |
parent | 379b82f4c9dc6e67bf61aa61b096c06a2f320f60 (diff) |
regulatory: use RCU to protect global and wiphy regdomains
To simplify the locking and not require cfg80211_mutex
(which nl80211 uses to access the global regdomain) and
also to make it possible for drivers to access their
wiphy->regd safely, use RCU to protect these pointers.
Acked-by: Luis R. Rodriguez <mcgrof@do-not-panic.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/wireless/reg.c')
-rw-r--r-- | net/wireless/reg.c | 113 |
1 files changed, 66 insertions, 47 deletions
diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 35541d6d4145..9b64b201cdf1 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c | |||
@@ -95,15 +95,15 @@ static struct device_type reg_device_type = { | |||
95 | * Central wireless core regulatory domains, we only need two, | 95 | * Central wireless core regulatory domains, we only need two, |
96 | * the current one and a world regulatory domain in case we have no | 96 | * the current one and a world regulatory domain in case we have no |
97 | * information to give us an alpha2. | 97 | * information to give us an alpha2. |
98 | * Protected by the cfg80211_mutex. | ||
99 | */ | 98 | */ |
100 | const struct ieee80211_regdomain *cfg80211_regdomain; | 99 | const struct ieee80211_regdomain __rcu *cfg80211_regdomain; |
101 | 100 | ||
102 | /* | 101 | /* |
103 | * Protects static reg.c components: | 102 | * Protects static reg.c components: |
104 | * - cfg80211_world_regdom | 103 | * - cfg80211_regdomain (if not used with RCU) |
105 | * - last_request | 104 | * - cfg80211_world_regdom |
106 | * - reg_num_devs_support_basehint | 105 | * - last_request |
106 | * - reg_num_devs_support_basehint | ||
107 | */ | 107 | */ |
108 | static DEFINE_MUTEX(reg_mutex); | 108 | static DEFINE_MUTEX(reg_mutex); |
109 | 109 | ||
@@ -118,6 +118,25 @@ static inline void assert_reg_lock(void) | |||
118 | lockdep_assert_held(®_mutex); | 118 | lockdep_assert_held(®_mutex); |
119 | } | 119 | } |
120 | 120 | ||
121 | static const struct ieee80211_regdomain *get_cfg80211_regdom(void) | ||
122 | { | ||
123 | return rcu_dereference_protected(cfg80211_regdomain, | ||
124 | lockdep_is_held(®_mutex)); | ||
125 | } | ||
126 | |||
127 | static const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy) | ||
128 | { | ||
129 | return rcu_dereference_protected(wiphy->regd, | ||
130 | lockdep_is_held(®_mutex)); | ||
131 | } | ||
132 | |||
133 | static void rcu_free_regdom(const struct ieee80211_regdomain *r) | ||
134 | { | ||
135 | if (!r) | ||
136 | return; | ||
137 | kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); | ||
138 | } | ||
139 | |||
121 | /* Used to queue up regulatory hints */ | 140 | /* Used to queue up regulatory hints */ |
122 | static LIST_HEAD(reg_requests_list); | 141 | static LIST_HEAD(reg_requests_list); |
123 | static spinlock_t reg_requests_lock; | 142 | static spinlock_t reg_requests_lock; |
@@ -186,22 +205,25 @@ MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); | |||
186 | static void reset_regdomains(bool full_reset, | 205 | static void reset_regdomains(bool full_reset, |
187 | const struct ieee80211_regdomain *new_regdom) | 206 | const struct ieee80211_regdomain *new_regdom) |
188 | { | 207 | { |
189 | assert_cfg80211_lock(); | 208 | const struct ieee80211_regdomain *r; |
209 | |||
190 | assert_reg_lock(); | 210 | assert_reg_lock(); |
191 | 211 | ||
212 | r = get_cfg80211_regdom(); | ||
213 | |||
192 | /* avoid freeing static information or freeing something twice */ | 214 | /* avoid freeing static information or freeing something twice */ |
193 | if (cfg80211_regdomain == cfg80211_world_regdom) | 215 | if (r == cfg80211_world_regdom) |
194 | cfg80211_regdomain = NULL; | 216 | r = NULL; |
195 | if (cfg80211_world_regdom == &world_regdom) | 217 | if (cfg80211_world_regdom == &world_regdom) |
196 | cfg80211_world_regdom = NULL; | 218 | cfg80211_world_regdom = NULL; |
197 | if (cfg80211_regdomain == &world_regdom) | 219 | if (r == &world_regdom) |
198 | cfg80211_regdomain = NULL; | 220 | r = NULL; |
199 | 221 | ||
200 | kfree(cfg80211_regdomain); | 222 | rcu_free_regdom(r); |
201 | kfree(cfg80211_world_regdom); | 223 | rcu_free_regdom(cfg80211_world_regdom); |
202 | 224 | ||
203 | cfg80211_world_regdom = &world_regdom; | 225 | cfg80211_world_regdom = &world_regdom; |
204 | cfg80211_regdomain = new_regdom; | 226 | rcu_assign_pointer(cfg80211_regdomain, new_regdom); |
205 | 227 | ||
206 | if (!full_reset) | 228 | if (!full_reset) |
207 | return; | 229 | return; |
@@ -219,7 +241,6 @@ static void update_world_regdomain(const struct ieee80211_regdomain *rd) | |||
219 | { | 241 | { |
220 | WARN_ON(!last_request); | 242 | WARN_ON(!last_request); |
221 | 243 | ||
222 | assert_cfg80211_lock(); | ||
223 | assert_reg_lock(); | 244 | assert_reg_lock(); |
224 | 245 | ||
225 | reset_regdomains(false, rd); | 246 | reset_regdomains(false, rd); |
@@ -280,11 +301,11 @@ static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) | |||
280 | 301 | ||
281 | static bool regdom_changes(const char *alpha2) | 302 | static bool regdom_changes(const char *alpha2) |
282 | { | 303 | { |
283 | assert_cfg80211_lock(); | 304 | const struct ieee80211_regdomain *r = get_cfg80211_regdom(); |
284 | 305 | ||
285 | if (!cfg80211_regdomain) | 306 | if (!r) |
286 | return true; | 307 | return true; |
287 | return !alpha2_equal(cfg80211_regdomain->alpha2, alpha2); | 308 | return !alpha2_equal(r->alpha2, alpha2); |
288 | } | 309 | } |
289 | 310 | ||
290 | /* | 311 | /* |
@@ -727,7 +748,6 @@ int freq_reg_info(struct wiphy *wiphy, u32 center_freq, | |||
727 | const struct ieee80211_regdomain *regd; | 748 | const struct ieee80211_regdomain *regd; |
728 | 749 | ||
729 | assert_reg_lock(); | 750 | assert_reg_lock(); |
730 | assert_cfg80211_lock(); | ||
731 | 751 | ||
732 | /* | 752 | /* |
733 | * Follow the driver's regulatory domain, if present, unless a country | 753 | * Follow the driver's regulatory domain, if present, unless a country |
@@ -736,9 +756,9 @@ int freq_reg_info(struct wiphy *wiphy, u32 center_freq, | |||
736 | if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && | 756 | if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && |
737 | last_request->initiator != NL80211_REGDOM_SET_BY_USER && | 757 | last_request->initiator != NL80211_REGDOM_SET_BY_USER && |
738 | wiphy->regd) | 758 | wiphy->regd) |
739 | regd = wiphy->regd; | 759 | regd = get_wiphy_regdom(wiphy); |
740 | else | 760 | else |
741 | regd = cfg80211_regdomain; | 761 | regd = get_cfg80211_regdom(); |
742 | 762 | ||
743 | return freq_reg_info_regd(wiphy, center_freq, reg_rule, regd); | 763 | return freq_reg_info_regd(wiphy, center_freq, reg_rule, regd); |
744 | } | 764 | } |
@@ -809,8 +829,6 @@ static void handle_channel(struct wiphy *wiphy, | |||
809 | const struct ieee80211_freq_range *freq_range = NULL; | 829 | const struct ieee80211_freq_range *freq_range = NULL; |
810 | struct wiphy *request_wiphy = NULL; | 830 | struct wiphy *request_wiphy = NULL; |
811 | 831 | ||
812 | assert_cfg80211_lock(); | ||
813 | |||
814 | request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); | 832 | request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); |
815 | 833 | ||
816 | flags = chan->orig_flags; | 834 | flags = chan->orig_flags; |
@@ -1060,15 +1078,17 @@ static void wiphy_update_beacon_reg(struct wiphy *wiphy) | |||
1060 | 1078 | ||
1061 | static bool reg_is_world_roaming(struct wiphy *wiphy) | 1079 | static bool reg_is_world_roaming(struct wiphy *wiphy) |
1062 | { | 1080 | { |
1063 | assert_cfg80211_lock(); | 1081 | const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); |
1082 | const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); | ||
1064 | 1083 | ||
1065 | if (is_world_regdom(cfg80211_regdomain->alpha2) || | 1084 | if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) |
1066 | (wiphy->regd && is_world_regdom(wiphy->regd->alpha2))) | ||
1067 | return true; | 1085 | return true; |
1086 | |||
1068 | if (last_request && | 1087 | if (last_request && |
1069 | last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && | 1088 | last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && |
1070 | wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) | 1089 | wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) |
1071 | return true; | 1090 | return true; |
1091 | |||
1072 | return false; | 1092 | return false; |
1073 | } | 1093 | } |
1074 | 1094 | ||
@@ -1165,13 +1185,12 @@ static void wiphy_update_regulatory(struct wiphy *wiphy, | |||
1165 | { | 1185 | { |
1166 | enum ieee80211_band band; | 1186 | enum ieee80211_band band; |
1167 | 1187 | ||
1168 | assert_cfg80211_lock(); | ||
1169 | assert_reg_lock(); | 1188 | assert_reg_lock(); |
1170 | 1189 | ||
1171 | if (ignore_reg_update(wiphy, initiator)) | 1190 | if (ignore_reg_update(wiphy, initiator)) |
1172 | return; | 1191 | return; |
1173 | 1192 | ||
1174 | last_request->dfs_region = cfg80211_regdomain->dfs_region; | 1193 | last_request->dfs_region = get_cfg80211_regdom()->dfs_region; |
1175 | 1194 | ||
1176 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) | 1195 | for (band = 0; band < IEEE80211_NUM_BANDS; band++) |
1177 | handle_band(wiphy, initiator, wiphy->bands[band]); | 1196 | handle_band(wiphy, initiator, wiphy->bands[band]); |
@@ -1188,6 +1207,8 @@ static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) | |||
1188 | struct cfg80211_registered_device *rdev; | 1207 | struct cfg80211_registered_device *rdev; |
1189 | struct wiphy *wiphy; | 1208 | struct wiphy *wiphy; |
1190 | 1209 | ||
1210 | assert_cfg80211_lock(); | ||
1211 | |||
1191 | list_for_each_entry(rdev, &cfg80211_rdev_list, list) { | 1212 | list_for_each_entry(rdev, &cfg80211_rdev_list, list) { |
1192 | wiphy = &rdev->wiphy; | 1213 | wiphy = &rdev->wiphy; |
1193 | wiphy_update_regulatory(wiphy, initiator); | 1214 | wiphy_update_regulatory(wiphy, initiator); |
@@ -1402,7 +1423,7 @@ static void reg_set_request_processed(void) | |||
1402 | * | 1423 | * |
1403 | * Returns one of the different reg request treatment values. | 1424 | * Returns one of the different reg request treatment values. |
1404 | * | 1425 | * |
1405 | * Caller must hold &cfg80211_mutex and ®_mutex | 1426 | * Caller must hold ®_mutex |
1406 | */ | 1427 | */ |
1407 | static enum reg_request_treatment | 1428 | static enum reg_request_treatment |
1408 | __regulatory_hint(struct wiphy *wiphy, | 1429 | __regulatory_hint(struct wiphy *wiphy, |
@@ -1412,20 +1433,18 @@ __regulatory_hint(struct wiphy *wiphy, | |||
1412 | bool intersect = false; | 1433 | bool intersect = false; |
1413 | enum reg_request_treatment treatment; | 1434 | enum reg_request_treatment treatment; |
1414 | 1435 | ||
1415 | assert_cfg80211_lock(); | ||
1416 | |||
1417 | treatment = get_reg_request_treatment(wiphy, pending_request); | 1436 | treatment = get_reg_request_treatment(wiphy, pending_request); |
1418 | 1437 | ||
1419 | switch (treatment) { | 1438 | switch (treatment) { |
1420 | case REG_REQ_INTERSECT: | 1439 | case REG_REQ_INTERSECT: |
1421 | if (pending_request->initiator == | 1440 | if (pending_request->initiator == |
1422 | NL80211_REGDOM_SET_BY_DRIVER) { | 1441 | NL80211_REGDOM_SET_BY_DRIVER) { |
1423 | regd = reg_copy_regd(cfg80211_regdomain); | 1442 | regd = reg_copy_regd(get_cfg80211_regdom()); |
1424 | if (IS_ERR(regd)) { | 1443 | if (IS_ERR(regd)) { |
1425 | kfree(pending_request); | 1444 | kfree(pending_request); |
1426 | return PTR_ERR(regd); | 1445 | return PTR_ERR(regd); |
1427 | } | 1446 | } |
1428 | wiphy->regd = regd; | 1447 | rcu_assign_pointer(wiphy->regd, regd); |
1429 | } | 1448 | } |
1430 | intersect = true; | 1449 | intersect = true; |
1431 | break; | 1450 | break; |
@@ -1439,13 +1458,13 @@ __regulatory_hint(struct wiphy *wiphy, | |||
1439 | */ | 1458 | */ |
1440 | if (treatment == REG_REQ_ALREADY_SET && | 1459 | if (treatment == REG_REQ_ALREADY_SET && |
1441 | pending_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) { | 1460 | pending_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) { |
1442 | regd = reg_copy_regd(cfg80211_regdomain); | 1461 | regd = reg_copy_regd(get_cfg80211_regdom()); |
1443 | if (IS_ERR(regd)) { | 1462 | if (IS_ERR(regd)) { |
1444 | kfree(pending_request); | 1463 | kfree(pending_request); |
1445 | return REG_REQ_IGNORE; | 1464 | return REG_REQ_IGNORE; |
1446 | } | 1465 | } |
1447 | treatment = REG_REQ_ALREADY_SET; | 1466 | treatment = REG_REQ_ALREADY_SET; |
1448 | wiphy->regd = regd; | 1467 | rcu_assign_pointer(wiphy->regd, regd); |
1449 | goto new_request; | 1468 | goto new_request; |
1450 | } | 1469 | } |
1451 | kfree(pending_request); | 1470 | kfree(pending_request); |
@@ -2051,6 +2070,8 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
2051 | 2070 | ||
2052 | /* Some basic sanity checks first */ | 2071 | /* Some basic sanity checks first */ |
2053 | 2072 | ||
2073 | assert_reg_lock(); | ||
2074 | |||
2054 | if (!reg_is_valid_request(rd->alpha2)) | 2075 | if (!reg_is_valid_request(rd->alpha2)) |
2055 | return -EINVAL; | 2076 | return -EINVAL; |
2056 | 2077 | ||
@@ -2120,7 +2141,7 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
2120 | if (IS_ERR(regd)) | 2141 | if (IS_ERR(regd)) |
2121 | return PTR_ERR(regd); | 2142 | return PTR_ERR(regd); |
2122 | 2143 | ||
2123 | request_wiphy->regd = regd; | 2144 | rcu_assign_pointer(request_wiphy->regd, regd); |
2124 | reset_regdomains(false, rd); | 2145 | reset_regdomains(false, rd); |
2125 | return 0; | 2146 | return 0; |
2126 | } | 2147 | } |
@@ -2128,7 +2149,7 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
2128 | /* Intersection requires a bit more work */ | 2149 | /* Intersection requires a bit more work */ |
2129 | 2150 | ||
2130 | if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { | 2151 | if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { |
2131 | intersected_rd = regdom_intersect(rd, cfg80211_regdomain); | 2152 | intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); |
2132 | if (!intersected_rd) | 2153 | if (!intersected_rd) |
2133 | return -EINVAL; | 2154 | return -EINVAL; |
2134 | 2155 | ||
@@ -2138,7 +2159,7 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
2138 | * domain we keep it for its private use | 2159 | * domain we keep it for its private use |
2139 | */ | 2160 | */ |
2140 | if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) | 2161 | if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) |
2141 | request_wiphy->regd = rd; | 2162 | rcu_assign_pointer(request_wiphy->regd, rd); |
2142 | else | 2163 | else |
2143 | kfree(rd); | 2164 | kfree(rd); |
2144 | 2165 | ||
@@ -2156,14 +2177,12 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
2156 | /* | 2177 | /* |
2157 | * Use this call to set the current regulatory domain. Conflicts with | 2178 | * Use this call to set the current regulatory domain. Conflicts with |
2158 | * multiple drivers can be ironed out later. Caller must've already | 2179 | * multiple drivers can be ironed out later. Caller must've already |
2159 | * kmalloc'd the rd structure. Caller must hold cfg80211_mutex | 2180 | * kmalloc'd the rd structure. |
2160 | */ | 2181 | */ |
2161 | int set_regdom(const struct ieee80211_regdomain *rd) | 2182 | int set_regdom(const struct ieee80211_regdomain *rd) |
2162 | { | 2183 | { |
2163 | int r; | 2184 | int r; |
2164 | 2185 | ||
2165 | assert_cfg80211_lock(); | ||
2166 | |||
2167 | mutex_lock(®_mutex); | 2186 | mutex_lock(®_mutex); |
2168 | 2187 | ||
2169 | /* Note that this doesn't update the wiphys, this is done below */ | 2188 | /* Note that this doesn't update the wiphys, this is done below */ |
@@ -2177,7 +2196,8 @@ int set_regdom(const struct ieee80211_regdomain *rd) | |||
2177 | } | 2196 | } |
2178 | 2197 | ||
2179 | /* This would make this whole thing pointless */ | 2198 | /* This would make this whole thing pointless */ |
2180 | if (WARN_ON(!last_request->intersect && rd != cfg80211_regdomain)) { | 2199 | if (WARN_ON(!last_request->intersect && |
2200 | rd != get_cfg80211_regdom())) { | ||
2181 | r = -EINVAL; | 2201 | r = -EINVAL; |
2182 | goto out; | 2202 | goto out; |
2183 | } | 2203 | } |
@@ -2185,7 +2205,7 @@ int set_regdom(const struct ieee80211_regdomain *rd) | |||
2185 | /* update all wiphys now with the new established regulatory domain */ | 2205 | /* update all wiphys now with the new established regulatory domain */ |
2186 | update_all_wiphy_regulatory(last_request->initiator); | 2206 | update_all_wiphy_regulatory(last_request->initiator); |
2187 | 2207 | ||
2188 | print_regdomain(cfg80211_regdomain); | 2208 | print_regdomain(get_cfg80211_regdom()); |
2189 | 2209 | ||
2190 | nl80211_send_reg_change_event(last_request); | 2210 | nl80211_send_reg_change_event(last_request); |
2191 | 2211 | ||
@@ -2238,7 +2258,8 @@ void wiphy_regulatory_deregister(struct wiphy *wiphy) | |||
2238 | if (!reg_dev_ignore_cell_hint(wiphy)) | 2258 | if (!reg_dev_ignore_cell_hint(wiphy)) |
2239 | reg_num_devs_support_basehint--; | 2259 | reg_num_devs_support_basehint--; |
2240 | 2260 | ||
2241 | kfree(wiphy->regd); | 2261 | rcu_free_regdom(get_wiphy_regdom(wiphy)); |
2262 | rcu_assign_pointer(wiphy->regd, NULL); | ||
2242 | 2263 | ||
2243 | if (last_request) | 2264 | if (last_request) |
2244 | request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); | 2265 | request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); |
@@ -2273,13 +2294,13 @@ int __init regulatory_init(void) | |||
2273 | 2294 | ||
2274 | reg_regdb_size_check(); | 2295 | reg_regdb_size_check(); |
2275 | 2296 | ||
2276 | cfg80211_regdomain = cfg80211_world_regdom; | 2297 | rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); |
2277 | 2298 | ||
2278 | user_alpha2[0] = '9'; | 2299 | user_alpha2[0] = '9'; |
2279 | user_alpha2[1] = '7'; | 2300 | user_alpha2[1] = '7'; |
2280 | 2301 | ||
2281 | /* We always try to get an update for the static regdomain */ | 2302 | /* We always try to get an update for the static regdomain */ |
2282 | err = regulatory_hint_core(cfg80211_regdomain->alpha2); | 2303 | err = regulatory_hint_core(cfg80211_world_regdom->alpha2); |
2283 | if (err) { | 2304 | if (err) { |
2284 | if (err == -ENOMEM) | 2305 | if (err == -ENOMEM) |
2285 | return err; | 2306 | return err; |
@@ -2313,10 +2334,8 @@ void regulatory_exit(void) | |||
2313 | cancel_delayed_work_sync(®_timeout); | 2334 | cancel_delayed_work_sync(®_timeout); |
2314 | 2335 | ||
2315 | /* Lock to suppress warnings */ | 2336 | /* Lock to suppress warnings */ |
2316 | mutex_lock(&cfg80211_mutex); | ||
2317 | mutex_lock(®_mutex); | 2337 | mutex_lock(®_mutex); |
2318 | reset_regdomains(true, NULL); | 2338 | reset_regdomains(true, NULL); |
2319 | mutex_unlock(&cfg80211_mutex); | ||
2320 | mutex_unlock(®_mutex); | 2339 | mutex_unlock(®_mutex); |
2321 | 2340 | ||
2322 | dev_set_uevent_suppress(®_pdev->dev, true); | 2341 | dev_set_uevent_suppress(®_pdev->dev, true); |