aboutsummaryrefslogtreecommitdiffstats
path: root/net/mac80211/chan.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/mac80211/chan.c')
-rw-r--r--net/mac80211/chan.c455
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"
9static 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
58enum ieee80211_chan_mode
59ieee80211_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
71static enum nl80211_channel_type
72ieee80211_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
109static bool 11static bool
110ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1, 12ieee80211_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
151bool ieee80211_set_channel_type(struct ieee80211_local *local, 53static 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) 69static struct ieee80211_chanctx *
166 sdata->vif.bss_conf.channel_type = chantype; 70ieee80211_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
105static struct ieee80211_chanctx *
106ieee80211_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
143static 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
161static 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
179static enum nl80211_channel_type
180ieee80211_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
205static 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
216static 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
234static 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
254void 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
337int 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
375void 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
384void 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}
399EXPORT_SYMBOL_GPL(ieee80211_iter_chan_contexts_atomic);