]>
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 | |
368a07d2 | 10 | static enum ieee80211_chan_mode |
f444de05 JB |
11 | __ieee80211_get_channel_mode(struct ieee80211_local *local, |
12 | struct ieee80211_sub_if_data *ignore) | |
13 | { | |
14 | struct ieee80211_sub_if_data *sdata; | |
15 | ||
46a5ebaf | 16 | lockdep_assert_held(&local->iflist_mtx); |
f444de05 JB |
17 | |
18 | list_for_each_entry(sdata, &local->interfaces, list) { | |
19 | if (sdata == ignore) | |
20 | continue; | |
21 | ||
22 | if (!ieee80211_sdata_running(sdata)) | |
23 | continue; | |
24 | ||
e9980e6d JB |
25 | switch (sdata->vif.type) { |
26 | case NL80211_IFTYPE_MONITOR: | |
f444de05 | 27 | continue; |
e9980e6d JB |
28 | case NL80211_IFTYPE_STATION: |
29 | if (!sdata->u.mgd.associated) | |
30 | continue; | |
31 | break; | |
32 | case NL80211_IFTYPE_ADHOC: | |
f444de05 JB |
33 | if (!sdata->u.ibss.ssid_len) |
34 | continue; | |
35 | if (!sdata->u.ibss.fixed_channel) | |
36 | return CHAN_MODE_HOPPING; | |
e9980e6d JB |
37 | break; |
38 | case NL80211_IFTYPE_AP_VLAN: | |
39 | /* will also have _AP interface */ | |
f444de05 | 40 | continue; |
e9980e6d JB |
41 | case NL80211_IFTYPE_AP: |
42 | if (!sdata->u.ap.beacon) | |
43 | continue; | |
44 | break; | |
be0f4237 TP |
45 | case NL80211_IFTYPE_MESH_POINT: |
46 | if (!sdata->wdev.mesh_id_len) | |
47 | continue; | |
48 | break; | |
e9980e6d JB |
49 | default: |
50 | break; | |
51 | } | |
f444de05 JB |
52 | |
53 | return CHAN_MODE_FIXED; | |
54 | } | |
55 | ||
56 | return CHAN_MODE_UNDEFINED; | |
57 | } | |
58 | ||
59 | enum ieee80211_chan_mode | |
60 | ieee80211_get_channel_mode(struct ieee80211_local *local, | |
61 | struct ieee80211_sub_if_data *ignore) | |
62 | { | |
63 | enum ieee80211_chan_mode mode; | |
64 | ||
65 | mutex_lock(&local->iflist_mtx); | |
66 | mode = __ieee80211_get_channel_mode(local, ignore); | |
67 | mutex_unlock(&local->iflist_mtx); | |
68 | ||
69 | return mode; | |
70 | } | |
0aaffa9b | 71 | |
23a85b45 MK |
72 | static enum nl80211_channel_type |
73 | ieee80211_get_superchan(struct ieee80211_local *local, | |
74 | struct ieee80211_sub_if_data *sdata) | |
0aaffa9b | 75 | { |
0aaffa9b | 76 | enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT; |
23a85b45 | 77 | struct ieee80211_sub_if_data *tmp; |
0aaffa9b JB |
78 | |
79 | mutex_lock(&local->iflist_mtx); | |
0aaffa9b JB |
80 | list_for_each_entry(tmp, &local->interfaces, list) { |
81 | if (tmp == sdata) | |
82 | continue; | |
83 | ||
84 | if (!ieee80211_sdata_running(tmp)) | |
85 | continue; | |
86 | ||
87 | switch (tmp->vif.bss_conf.channel_type) { | |
88 | case NL80211_CHAN_NO_HT: | |
89 | case NL80211_CHAN_HT20: | |
9db372fd FF |
90 | if (superchan > tmp->vif.bss_conf.channel_type) |
91 | break; | |
92 | ||
0aaffa9b JB |
93 | superchan = tmp->vif.bss_conf.channel_type; |
94 | break; | |
95 | case NL80211_CHAN_HT40PLUS: | |
96 | WARN_ON(superchan == NL80211_CHAN_HT40MINUS); | |
97 | superchan = NL80211_CHAN_HT40PLUS; | |
98 | break; | |
99 | case NL80211_CHAN_HT40MINUS: | |
100 | WARN_ON(superchan == NL80211_CHAN_HT40PLUS); | |
101 | superchan = NL80211_CHAN_HT40MINUS; | |
102 | break; | |
103 | } | |
104 | } | |
23a85b45 | 105 | mutex_unlock(&local->iflist_mtx); |
0aaffa9b | 106 | |
23a85b45 MK |
107 | return superchan; |
108 | } | |
109 | ||
110 | static bool | |
111 | ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1, | |
112 | enum nl80211_channel_type chantype2, | |
113 | enum nl80211_channel_type *compat) | |
114 | { | |
115 | /* | |
116 | * start out with chantype1 being the result, | |
117 | * overwriting later if needed | |
118 | */ | |
119 | if (compat) | |
120 | *compat = chantype1; | |
121 | ||
122 | switch (chantype1) { | |
0aaffa9b | 123 | case NL80211_CHAN_NO_HT: |
23a85b45 MK |
124 | if (compat) |
125 | *compat = chantype2; | |
126 | break; | |
0aaffa9b JB |
127 | case NL80211_CHAN_HT20: |
128 | /* | |
129 | * allow any change that doesn't go to no-HT | |
130 | * (if it already is no-HT no change is needed) | |
131 | */ | |
23a85b45 | 132 | if (chantype2 == NL80211_CHAN_NO_HT) |
0aaffa9b | 133 | break; |
23a85b45 MK |
134 | if (compat) |
135 | *compat = chantype2; | |
0aaffa9b JB |
136 | break; |
137 | case NL80211_CHAN_HT40PLUS: | |
138 | case NL80211_CHAN_HT40MINUS: | |
139 | /* allow smaller bandwidth and same */ | |
23a85b45 | 140 | if (chantype2 == NL80211_CHAN_NO_HT) |
0aaffa9b | 141 | break; |
23a85b45 | 142 | if (chantype2 == NL80211_CHAN_HT20) |
0aaffa9b | 143 | break; |
23a85b45 | 144 | if (chantype2 == chantype1) |
0aaffa9b | 145 | break; |
23a85b45 | 146 | return false; |
0aaffa9b JB |
147 | } |
148 | ||
23a85b45 MK |
149 | return true; |
150 | } | |
151 | ||
152 | bool ieee80211_set_channel_type(struct ieee80211_local *local, | |
153 | struct ieee80211_sub_if_data *sdata, | |
154 | enum nl80211_channel_type chantype) | |
155 | { | |
156 | enum nl80211_channel_type superchan; | |
157 | enum nl80211_channel_type compatchan; | |
158 | ||
159 | superchan = ieee80211_get_superchan(local, sdata); | |
160 | if (!ieee80211_channel_types_are_compatible(superchan, chantype, | |
161 | &compatchan)) | |
162 | return false; | |
163 | ||
164 | local->_oper_channel_type = compatchan; | |
0aaffa9b JB |
165 | |
166 | if (sdata) | |
167 | sdata->vif.bss_conf.channel_type = chantype; | |
168 | ||
23a85b45 | 169 | return true; |
0aaffa9b | 170 | |
0aaffa9b | 171 | } |
d01a1e65 MK |
172 | |
173 | static struct ieee80211_chanctx * | |
174 | ieee80211_find_chanctx(struct ieee80211_local *local, | |
175 | struct ieee80211_channel *channel, | |
176 | enum nl80211_channel_type channel_type, | |
177 | enum ieee80211_chanctx_mode mode) | |
178 | { | |
179 | struct ieee80211_chanctx *ctx; | |
180 | ||
181 | lockdep_assert_held(&local->chanctx_mtx); | |
182 | ||
183 | if (mode == IEEE80211_CHANCTX_EXCLUSIVE) | |
184 | return NULL; | |
185 | if (WARN_ON(!channel)) | |
186 | return NULL; | |
187 | ||
188 | list_for_each_entry(ctx, &local->chanctx_list, list) { | |
189 | if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) | |
190 | continue; | |
191 | if (ctx->conf.channel != channel) | |
192 | continue; | |
193 | if (ctx->conf.channel_type != channel_type) | |
194 | continue; | |
195 | ||
196 | return ctx; | |
197 | } | |
198 | ||
199 | return NULL; | |
200 | } | |
201 | ||
202 | static struct ieee80211_chanctx * | |
203 | ieee80211_new_chanctx(struct ieee80211_local *local, | |
204 | struct ieee80211_channel *channel, | |
205 | enum nl80211_channel_type channel_type, | |
206 | enum ieee80211_chanctx_mode mode) | |
207 | { | |
208 | struct ieee80211_chanctx *ctx; | |
35f2fce9 | 209 | int err; |
d01a1e65 MK |
210 | |
211 | lockdep_assert_held(&local->chanctx_mtx); | |
212 | ||
213 | ctx = kzalloc(sizeof(*ctx) + local->hw.chanctx_data_size, GFP_KERNEL); | |
214 | if (!ctx) | |
215 | return ERR_PTR(-ENOMEM); | |
216 | ||
217 | ctx->conf.channel = channel; | |
218 | ctx->conf.channel_type = channel_type; | |
219 | ctx->mode = mode; | |
220 | ||
35f2fce9 MK |
221 | err = drv_add_chanctx(local, ctx); |
222 | if (err) { | |
223 | kfree(ctx); | |
224 | return ERR_PTR(err); | |
225 | } | |
226 | ||
d01a1e65 MK |
227 | list_add(&ctx->list, &local->chanctx_list); |
228 | ||
229 | return ctx; | |
230 | } | |
231 | ||
232 | static void ieee80211_free_chanctx(struct ieee80211_local *local, | |
233 | struct ieee80211_chanctx *ctx) | |
234 | { | |
235 | lockdep_assert_held(&local->chanctx_mtx); | |
236 | ||
237 | WARN_ON_ONCE(ctx->refcount != 0); | |
238 | ||
35f2fce9 MK |
239 | drv_remove_chanctx(local, ctx); |
240 | ||
d01a1e65 MK |
241 | list_del(&ctx->list); |
242 | kfree_rcu(ctx, rcu_head); | |
243 | } | |
244 | ||
245 | static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata, | |
246 | struct ieee80211_chanctx *ctx) | |
247 | { | |
35f2fce9 MK |
248 | struct ieee80211_local *local = sdata->local; |
249 | int ret; | |
d01a1e65 MK |
250 | |
251 | lockdep_assert_held(&local->chanctx_mtx); | |
252 | ||
35f2fce9 MK |
253 | ret = drv_assign_vif_chanctx(local, sdata, ctx); |
254 | if (ret) | |
255 | return ret; | |
256 | ||
d01a1e65 MK |
257 | rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf); |
258 | ctx->refcount++; | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
263 | static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata, | |
264 | struct ieee80211_chanctx *ctx) | |
265 | { | |
35f2fce9 | 266 | struct ieee80211_local *local = sdata->local; |
d01a1e65 MK |
267 | |
268 | lockdep_assert_held(&local->chanctx_mtx); | |
269 | ||
270 | ctx->refcount--; | |
271 | rcu_assign_pointer(sdata->vif.chanctx_conf, NULL); | |
35f2fce9 MK |
272 | |
273 | drv_unassign_vif_chanctx(local, sdata, ctx); | |
d01a1e65 MK |
274 | } |
275 | ||
276 | static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) | |
277 | { | |
278 | struct ieee80211_local *local = sdata->local; | |
279 | struct ieee80211_chanctx_conf *conf; | |
280 | struct ieee80211_chanctx *ctx; | |
281 | ||
282 | lockdep_assert_held(&local->chanctx_mtx); | |
283 | ||
284 | conf = rcu_dereference_protected(sdata->vif.chanctx_conf, | |
285 | lockdep_is_held(&local->chanctx_mtx)); | |
286 | if (!conf) | |
287 | return; | |
288 | ||
289 | ctx = container_of(conf, struct ieee80211_chanctx, conf); | |
290 | ||
291 | ieee80211_unassign_vif_chanctx(sdata, ctx); | |
292 | if (ctx->refcount == 0) | |
293 | ieee80211_free_chanctx(local, ctx); | |
294 | } | |
295 | ||
296 | int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, | |
297 | struct ieee80211_channel *channel, | |
298 | enum nl80211_channel_type channel_type, | |
299 | enum ieee80211_chanctx_mode mode) | |
300 | { | |
301 | struct ieee80211_local *local = sdata->local; | |
302 | struct ieee80211_chanctx *ctx; | |
303 | int ret; | |
304 | ||
305 | mutex_lock(&local->chanctx_mtx); | |
306 | __ieee80211_vif_release_channel(sdata); | |
307 | ||
308 | ctx = ieee80211_find_chanctx(local, channel, channel_type, mode); | |
309 | if (!ctx) | |
310 | ctx = ieee80211_new_chanctx(local, channel, channel_type, mode); | |
311 | if (IS_ERR(ctx)) { | |
312 | ret = PTR_ERR(ctx); | |
313 | goto out; | |
314 | } | |
315 | ||
316 | ret = ieee80211_assign_vif_chanctx(sdata, ctx); | |
317 | if (ret) { | |
318 | /* if assign fails refcount stays the same */ | |
319 | if (ctx->refcount == 0) | |
320 | ieee80211_free_chanctx(local, ctx); | |
321 | goto out; | |
322 | } | |
323 | ||
324 | out: | |
325 | mutex_unlock(&local->chanctx_mtx); | |
326 | return ret; | |
327 | } | |
328 | ||
329 | void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) | |
330 | { | |
331 | mutex_lock(&sdata->local->chanctx_mtx); | |
332 | __ieee80211_vif_release_channel(sdata); | |
333 | mutex_unlock(&sdata->local->chanctx_mtx); | |
334 | } |