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