diff options
Diffstat (limited to 'net/mac80211/chan.c')
-rw-r--r-- | net/mac80211/chan.c | 455 |
1 files changed, 342 insertions, 113 deletions
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 0bfc914ddd15..f84b86028a9c 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c | |||
@@ -3,108 +3,10 @@ | |||
3 | */ | 3 | */ |
4 | 4 | ||
5 | #include <linux/nl80211.h> | 5 | #include <linux/nl80211.h> |
6 | #include <linux/export.h> | ||
6 | #include <net/cfg80211.h> | 7 | #include <net/cfg80211.h> |
7 | #include "ieee80211_i.h" | 8 | #include "ieee80211_i.h" |
8 | 9 | #include "driver-ops.h" | |
9 | static enum ieee80211_chan_mode | ||
10 | __ieee80211_get_channel_mode(struct ieee80211_local *local, | ||
11 | struct ieee80211_sub_if_data *ignore) | ||
12 | { | ||
13 | struct ieee80211_sub_if_data *sdata; | ||
14 | |||
15 | lockdep_assert_held(&local->iflist_mtx); | ||
16 | |||
17 | list_for_each_entry(sdata, &local->interfaces, list) { | ||
18 | if (sdata == ignore) | ||
19 | continue; | ||
20 | |||
21 | if (!ieee80211_sdata_running(sdata)) | ||
22 | continue; | ||
23 | |||
24 | switch (sdata->vif.type) { | ||
25 | case NL80211_IFTYPE_MONITOR: | ||
26 | continue; | ||
27 | case NL80211_IFTYPE_STATION: | ||
28 | if (!sdata->u.mgd.associated) | ||
29 | continue; | ||
30 | break; | ||
31 | case NL80211_IFTYPE_ADHOC: | ||
32 | if (!sdata->u.ibss.ssid_len) | ||
33 | continue; | ||
34 | if (!sdata->u.ibss.fixed_channel) | ||
35 | return CHAN_MODE_HOPPING; | ||
36 | break; | ||
37 | case NL80211_IFTYPE_AP_VLAN: | ||
38 | /* will also have _AP interface */ | ||
39 | continue; | ||
40 | case NL80211_IFTYPE_AP: | ||
41 | if (!sdata->u.ap.beacon) | ||
42 | continue; | ||
43 | break; | ||
44 | case NL80211_IFTYPE_MESH_POINT: | ||
45 | if (!sdata->wdev.mesh_id_len) | ||
46 | continue; | ||
47 | break; | ||
48 | default: | ||
49 | break; | ||
50 | } | ||
51 | |||
52 | return CHAN_MODE_FIXED; | ||
53 | } | ||
54 | |||
55 | return CHAN_MODE_UNDEFINED; | ||
56 | } | ||
57 | |||
58 | enum ieee80211_chan_mode | ||
59 | ieee80211_get_channel_mode(struct ieee80211_local *local, | ||
60 | struct ieee80211_sub_if_data *ignore) | ||
61 | { | ||
62 | enum ieee80211_chan_mode mode; | ||
63 | |||
64 | mutex_lock(&local->iflist_mtx); | ||
65 | mode = __ieee80211_get_channel_mode(local, ignore); | ||
66 | mutex_unlock(&local->iflist_mtx); | ||
67 | |||
68 | return mode; | ||
69 | } | ||
70 | |||
71 | static enum nl80211_channel_type | ||
72 | ieee80211_get_superchan(struct ieee80211_local *local, | ||
73 | struct ieee80211_sub_if_data *sdata) | ||
74 | { | ||
75 | enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT; | ||
76 | struct ieee80211_sub_if_data *tmp; | ||
77 | |||
78 | mutex_lock(&local->iflist_mtx); | ||
79 | list_for_each_entry(tmp, &local->interfaces, list) { | ||
80 | if (tmp == sdata) | ||
81 | continue; | ||
82 | |||
83 | if (!ieee80211_sdata_running(tmp)) | ||
84 | continue; | ||
85 | |||
86 | switch (tmp->vif.bss_conf.channel_type) { | ||
87 | case NL80211_CHAN_NO_HT: | ||
88 | case NL80211_CHAN_HT20: | ||
89 | if (superchan > tmp->vif.bss_conf.channel_type) | ||
90 | break; | ||
91 | |||
92 | superchan = tmp->vif.bss_conf.channel_type; | ||
93 | break; | ||
94 | case NL80211_CHAN_HT40PLUS: | ||
95 | WARN_ON(superchan == NL80211_CHAN_HT40MINUS); | ||
96 | superchan = NL80211_CHAN_HT40PLUS; | ||
97 | break; | ||
98 | case NL80211_CHAN_HT40MINUS: | ||
99 | WARN_ON(superchan == NL80211_CHAN_HT40PLUS); | ||
100 | superchan = NL80211_CHAN_HT40MINUS; | ||
101 | break; | ||
102 | } | ||
103 | } | ||
104 | mutex_unlock(&local->iflist_mtx); | ||
105 | |||
106 | return superchan; | ||
107 | } | ||
108 | 10 | ||
109 | static bool | 11 | static bool |
110 | ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1, | 12 | ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1, |
@@ -148,23 +50,350 @@ ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1, | |||
148 | return true; | 50 | return true; |
149 | } | 51 | } |
150 | 52 | ||
151 | bool ieee80211_set_channel_type(struct ieee80211_local *local, | 53 | static void ieee80211_change_chantype(struct ieee80211_local *local, |
152 | struct ieee80211_sub_if_data *sdata, | 54 | struct ieee80211_chanctx *ctx, |
153 | enum nl80211_channel_type chantype) | 55 | enum nl80211_channel_type chantype) |
154 | { | 56 | { |
155 | enum nl80211_channel_type superchan; | 57 | if (chantype == ctx->conf.channel_type) |
156 | enum nl80211_channel_type compatchan; | 58 | return; |
157 | 59 | ||
158 | superchan = ieee80211_get_superchan(local, sdata); | 60 | ctx->conf.channel_type = chantype; |
159 | if (!ieee80211_channel_types_are_compatible(superchan, chantype, | 61 | drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_CHANNEL_TYPE); |
160 | &compatchan)) | ||
161 | return false; | ||
162 | 62 | ||
163 | local->_oper_channel_type = compatchan; | 63 | if (!local->use_chanctx) { |
64 | local->_oper_channel_type = chantype; | ||
65 | ieee80211_hw_config(local, 0); | ||
66 | } | ||
67 | } | ||
164 | 68 | ||
165 | if (sdata) | 69 | static struct ieee80211_chanctx * |
166 | sdata->vif.bss_conf.channel_type = chantype; | 70 | ieee80211_find_chanctx(struct ieee80211_local *local, |
71 | struct ieee80211_channel *channel, | ||
72 | enum nl80211_channel_type channel_type, | ||
73 | enum ieee80211_chanctx_mode mode) | ||
74 | { | ||
75 | struct ieee80211_chanctx *ctx; | ||
76 | enum nl80211_channel_type compat_type; | ||
167 | 77 | ||
168 | return true; | 78 | lockdep_assert_held(&local->chanctx_mtx); |
79 | |||
80 | if (mode == IEEE80211_CHANCTX_EXCLUSIVE) | ||
81 | return NULL; | ||
82 | if (WARN_ON(!channel)) | ||
83 | return NULL; | ||
84 | |||
85 | list_for_each_entry(ctx, &local->chanctx_list, list) { | ||
86 | compat_type = ctx->conf.channel_type; | ||
87 | |||
88 | if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) | ||
89 | continue; | ||
90 | if (ctx->conf.channel != channel) | ||
91 | continue; | ||
92 | if (!ieee80211_channel_types_are_compatible(ctx->conf.channel_type, | ||
93 | channel_type, | ||
94 | &compat_type)) | ||
95 | continue; | ||
96 | |||
97 | ieee80211_change_chantype(local, ctx, compat_type); | ||
98 | |||
99 | return ctx; | ||
100 | } | ||
101 | |||
102 | return NULL; | ||
103 | } | ||
104 | |||
105 | static struct ieee80211_chanctx * | ||
106 | ieee80211_new_chanctx(struct ieee80211_local *local, | ||
107 | struct ieee80211_channel *channel, | ||
108 | enum nl80211_channel_type channel_type, | ||
109 | enum ieee80211_chanctx_mode mode) | ||
110 | { | ||
111 | struct ieee80211_chanctx *ctx; | ||
112 | int err; | ||
113 | |||
114 | lockdep_assert_held(&local->chanctx_mtx); | ||
115 | |||
116 | ctx = kzalloc(sizeof(*ctx) + local->hw.chanctx_data_size, GFP_KERNEL); | ||
117 | if (!ctx) | ||
118 | return ERR_PTR(-ENOMEM); | ||
119 | |||
120 | ctx->conf.channel = channel; | ||
121 | ctx->conf.channel_type = channel_type; | ||
122 | ctx->conf.rx_chains_static = 1; | ||
123 | ctx->conf.rx_chains_dynamic = 1; | ||
124 | ctx->mode = mode; | ||
125 | |||
126 | if (!local->use_chanctx) { | ||
127 | local->_oper_channel_type = channel_type; | ||
128 | local->_oper_channel = channel; | ||
129 | ieee80211_hw_config(local, 0); | ||
130 | } else { | ||
131 | err = drv_add_chanctx(local, ctx); | ||
132 | if (err) { | ||
133 | kfree(ctx); | ||
134 | return ERR_PTR(err); | ||
135 | } | ||
136 | } | ||
137 | |||
138 | list_add_rcu(&ctx->list, &local->chanctx_list); | ||
139 | |||
140 | return ctx; | ||
141 | } | ||
142 | |||
143 | static void ieee80211_free_chanctx(struct ieee80211_local *local, | ||
144 | struct ieee80211_chanctx *ctx) | ||
145 | { | ||
146 | lockdep_assert_held(&local->chanctx_mtx); | ||
147 | |||
148 | WARN_ON_ONCE(ctx->refcount != 0); | ||
149 | |||
150 | if (!local->use_chanctx) { | ||
151 | local->_oper_channel_type = NL80211_CHAN_NO_HT; | ||
152 | ieee80211_hw_config(local, 0); | ||
153 | } else { | ||
154 | drv_remove_chanctx(local, ctx); | ||
155 | } | ||
156 | |||
157 | list_del_rcu(&ctx->list); | ||
158 | kfree_rcu(ctx, rcu_head); | ||
159 | } | ||
160 | |||
161 | static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata, | ||
162 | struct ieee80211_chanctx *ctx) | ||
163 | { | ||
164 | struct ieee80211_local *local = sdata->local; | ||
165 | int ret; | ||
166 | |||
167 | lockdep_assert_held(&local->chanctx_mtx); | ||
168 | |||
169 | ret = drv_assign_vif_chanctx(local, sdata, ctx); | ||
170 | if (ret) | ||
171 | return ret; | ||
172 | |||
173 | rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf); | ||
174 | ctx->refcount++; | ||
175 | |||
176 | return 0; | ||
177 | } | ||
178 | |||
179 | static enum nl80211_channel_type | ||
180 | ieee80211_calc_chantype(struct ieee80211_local *local, | ||
181 | struct ieee80211_chanctx *ctx) | ||
182 | { | ||
183 | struct ieee80211_chanctx_conf *conf = &ctx->conf; | ||
184 | struct ieee80211_sub_if_data *sdata; | ||
185 | enum nl80211_channel_type result = NL80211_CHAN_NO_HT; | ||
186 | |||
187 | lockdep_assert_held(&local->chanctx_mtx); | ||
188 | |||
189 | rcu_read_lock(); | ||
190 | list_for_each_entry_rcu(sdata, &local->interfaces, list) { | ||
191 | if (!ieee80211_sdata_running(sdata)) | ||
192 | continue; | ||
193 | if (rcu_access_pointer(sdata->vif.chanctx_conf) != conf) | ||
194 | continue; | ||
195 | |||
196 | WARN_ON_ONCE(!ieee80211_channel_types_are_compatible( | ||
197 | sdata->vif.bss_conf.channel_type, | ||
198 | result, &result)); | ||
199 | } | ||
200 | rcu_read_unlock(); | ||
201 | |||
202 | return result; | ||
203 | } | ||
204 | |||
205 | static void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local, | ||
206 | struct ieee80211_chanctx *ctx) | ||
207 | { | ||
208 | enum nl80211_channel_type chantype; | ||
209 | |||
210 | lockdep_assert_held(&local->chanctx_mtx); | ||
211 | |||
212 | chantype = ieee80211_calc_chantype(local, ctx); | ||
213 | ieee80211_change_chantype(local, ctx, chantype); | ||
214 | } | ||
215 | |||
216 | static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata, | ||
217 | struct ieee80211_chanctx *ctx) | ||
218 | { | ||
219 | struct ieee80211_local *local = sdata->local; | ||
220 | |||
221 | lockdep_assert_held(&local->chanctx_mtx); | ||
222 | |||
223 | ctx->refcount--; | ||
224 | rcu_assign_pointer(sdata->vif.chanctx_conf, NULL); | ||
225 | |||
226 | drv_unassign_vif_chanctx(local, sdata, ctx); | ||
227 | |||
228 | if (ctx->refcount > 0) { | ||
229 | ieee80211_recalc_chanctx_chantype(sdata->local, ctx); | ||
230 | ieee80211_recalc_smps_chanctx(local, ctx); | ||
231 | } | ||
232 | } | ||
233 | |||
234 | static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) | ||
235 | { | ||
236 | struct ieee80211_local *local = sdata->local; | ||
237 | struct ieee80211_chanctx_conf *conf; | ||
238 | struct ieee80211_chanctx *ctx; | ||
239 | |||
240 | lockdep_assert_held(&local->chanctx_mtx); | ||
241 | |||
242 | conf = rcu_dereference_protected(sdata->vif.chanctx_conf, | ||
243 | lockdep_is_held(&local->chanctx_mtx)); | ||
244 | if (!conf) | ||
245 | return; | ||
246 | |||
247 | ctx = container_of(conf, struct ieee80211_chanctx, conf); | ||
248 | |||
249 | ieee80211_unassign_vif_chanctx(sdata, ctx); | ||
250 | if (ctx->refcount == 0) | ||
251 | ieee80211_free_chanctx(local, ctx); | ||
252 | } | ||
253 | |||
254 | void ieee80211_recalc_smps_chanctx(struct ieee80211_local *local, | ||
255 | struct ieee80211_chanctx *chanctx) | ||
256 | { | ||
257 | struct ieee80211_sub_if_data *sdata; | ||
258 | u8 rx_chains_static, rx_chains_dynamic; | ||
259 | |||
260 | lockdep_assert_held(&local->chanctx_mtx); | ||
261 | |||
262 | rx_chains_static = 1; | ||
263 | rx_chains_dynamic = 1; | ||
264 | |||
265 | rcu_read_lock(); | ||
266 | list_for_each_entry_rcu(sdata, &local->interfaces, list) { | ||
267 | u8 needed_static, needed_dynamic; | ||
268 | |||
269 | if (!ieee80211_sdata_running(sdata)) | ||
270 | continue; | ||
271 | |||
272 | if (rcu_access_pointer(sdata->vif.chanctx_conf) != | ||
273 | &chanctx->conf) | ||
274 | continue; | ||
275 | |||
276 | switch (sdata->vif.type) { | ||
277 | case NL80211_IFTYPE_P2P_DEVICE: | ||
278 | continue; | ||
279 | case NL80211_IFTYPE_STATION: | ||
280 | if (!sdata->u.mgd.associated) | ||
281 | continue; | ||
282 | break; | ||
283 | case NL80211_IFTYPE_AP_VLAN: | ||
284 | continue; | ||
285 | case NL80211_IFTYPE_AP: | ||
286 | case NL80211_IFTYPE_ADHOC: | ||
287 | case NL80211_IFTYPE_WDS: | ||
288 | case NL80211_IFTYPE_MESH_POINT: | ||
289 | break; | ||
290 | default: | ||
291 | WARN_ON_ONCE(1); | ||
292 | } | ||
293 | |||
294 | switch (sdata->smps_mode) { | ||
295 | default: | ||
296 | WARN_ONCE(1, "Invalid SMPS mode %d\n", | ||
297 | sdata->smps_mode); | ||
298 | /* fall through */ | ||
299 | case IEEE80211_SMPS_OFF: | ||
300 | needed_static = sdata->needed_rx_chains; | ||
301 | needed_dynamic = sdata->needed_rx_chains; | ||
302 | break; | ||
303 | case IEEE80211_SMPS_DYNAMIC: | ||
304 | needed_static = 1; | ||
305 | needed_dynamic = sdata->needed_rx_chains; | ||
306 | break; | ||
307 | case IEEE80211_SMPS_STATIC: | ||
308 | needed_static = 1; | ||
309 | needed_dynamic = 1; | ||
310 | break; | ||
311 | } | ||
312 | |||
313 | rx_chains_static = max(rx_chains_static, needed_static); | ||
314 | rx_chains_dynamic = max(rx_chains_dynamic, needed_dynamic); | ||
315 | } | ||
316 | rcu_read_unlock(); | ||
317 | |||
318 | if (!local->use_chanctx) { | ||
319 | if (rx_chains_static > 1) | ||
320 | local->smps_mode = IEEE80211_SMPS_OFF; | ||
321 | else if (rx_chains_dynamic > 1) | ||
322 | local->smps_mode = IEEE80211_SMPS_DYNAMIC; | ||
323 | else | ||
324 | local->smps_mode = IEEE80211_SMPS_STATIC; | ||
325 | ieee80211_hw_config(local, 0); | ||
326 | } | ||
327 | |||
328 | if (rx_chains_static == chanctx->conf.rx_chains_static && | ||
329 | rx_chains_dynamic == chanctx->conf.rx_chains_dynamic) | ||
330 | return; | ||
331 | |||
332 | chanctx->conf.rx_chains_static = rx_chains_static; | ||
333 | chanctx->conf.rx_chains_dynamic = rx_chains_dynamic; | ||
334 | drv_change_chanctx(local, chanctx, IEEE80211_CHANCTX_CHANGE_RX_CHAINS); | ||
335 | } | ||
336 | |||
337 | int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, | ||
338 | struct ieee80211_channel *channel, | ||
339 | enum nl80211_channel_type channel_type, | ||
340 | enum ieee80211_chanctx_mode mode) | ||
341 | { | ||
342 | struct ieee80211_local *local = sdata->local; | ||
343 | struct ieee80211_chanctx *ctx; | ||
344 | int ret; | ||
345 | |||
346 | WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev)); | ||
347 | |||
348 | mutex_lock(&local->chanctx_mtx); | ||
349 | __ieee80211_vif_release_channel(sdata); | ||
350 | |||
351 | ctx = ieee80211_find_chanctx(local, channel, channel_type, mode); | ||
352 | if (!ctx) | ||
353 | ctx = ieee80211_new_chanctx(local, channel, channel_type, mode); | ||
354 | if (IS_ERR(ctx)) { | ||
355 | ret = PTR_ERR(ctx); | ||
356 | goto out; | ||
357 | } | ||
358 | |||
359 | sdata->vif.bss_conf.channel_type = channel_type; | ||
360 | |||
361 | ret = ieee80211_assign_vif_chanctx(sdata, ctx); | ||
362 | if (ret) { | ||
363 | /* if assign fails refcount stays the same */ | ||
364 | if (ctx->refcount == 0) | ||
365 | ieee80211_free_chanctx(local, ctx); | ||
366 | goto out; | ||
367 | } | ||
368 | |||
369 | ieee80211_recalc_smps_chanctx(local, ctx); | ||
370 | out: | ||
371 | mutex_unlock(&local->chanctx_mtx); | ||
372 | return ret; | ||
373 | } | ||
374 | |||
375 | void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) | ||
376 | { | ||
377 | WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev)); | ||
378 | |||
379 | mutex_lock(&sdata->local->chanctx_mtx); | ||
380 | __ieee80211_vif_release_channel(sdata); | ||
381 | mutex_unlock(&sdata->local->chanctx_mtx); | ||
382 | } | ||
383 | |||
384 | void ieee80211_iter_chan_contexts_atomic( | ||
385 | struct ieee80211_hw *hw, | ||
386 | void (*iter)(struct ieee80211_hw *hw, | ||
387 | struct ieee80211_chanctx_conf *chanctx_conf, | ||
388 | void *data), | ||
389 | void *iter_data) | ||
390 | { | ||
391 | struct ieee80211_local *local = hw_to_local(hw); | ||
392 | struct ieee80211_chanctx *ctx; | ||
169 | 393 | ||
394 | rcu_read_lock(); | ||
395 | list_for_each_entry_rcu(ctx, &local->chanctx_list, list) | ||
396 | iter(hw, &ctx->conf, iter_data); | ||
397 | rcu_read_unlock(); | ||
170 | } | 398 | } |
399 | EXPORT_SYMBOL_GPL(ieee80211_iter_chan_contexts_atomic); | ||