]>
Commit | Line | Data |
---|---|---|
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 |
10 | static bool |
11 | ieee80211_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 |
52 | static 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 | |
68 | static struct ieee80211_chanctx * | |
69 | ieee80211_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 | ||
104 | static struct ieee80211_chanctx * | |
105 | ieee80211_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 | ||
142 | static 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 | ||
160 | static 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 |
178 | static enum nl80211_channel_type |
179 | ieee80211_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 | ||
204 | static 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 |
215 | static 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 | ||
233 | static 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 |
253 | void 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 |
336 | int 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 | ||
374 | void 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 | } |