]>
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; | |
121 | ctx->mode = mode; | |
122 | ||
55de908a JB |
123 | if (!local->use_chanctx) { |
124 | local->_oper_channel_type = channel_type; | |
125 | local->_oper_channel = channel; | |
126 | ieee80211_hw_config(local, 0); | |
127 | } else { | |
128 | err = drv_add_chanctx(local, ctx); | |
129 | if (err) { | |
130 | kfree(ctx); | |
131 | return ERR_PTR(err); | |
132 | } | |
35f2fce9 MK |
133 | } |
134 | ||
d01a1e65 MK |
135 | list_add(&ctx->list, &local->chanctx_list); |
136 | ||
137 | return ctx; | |
138 | } | |
139 | ||
140 | static void ieee80211_free_chanctx(struct ieee80211_local *local, | |
141 | struct ieee80211_chanctx *ctx) | |
142 | { | |
143 | lockdep_assert_held(&local->chanctx_mtx); | |
144 | ||
145 | WARN_ON_ONCE(ctx->refcount != 0); | |
146 | ||
55de908a JB |
147 | if (!local->use_chanctx) { |
148 | local->_oper_channel_type = NL80211_CHAN_NO_HT; | |
149 | ieee80211_hw_config(local, 0); | |
150 | } else { | |
151 | drv_remove_chanctx(local, ctx); | |
152 | } | |
35f2fce9 | 153 | |
d01a1e65 MK |
154 | list_del(&ctx->list); |
155 | kfree_rcu(ctx, rcu_head); | |
156 | } | |
157 | ||
158 | static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata, | |
159 | struct ieee80211_chanctx *ctx) | |
160 | { | |
35f2fce9 MK |
161 | struct ieee80211_local *local = sdata->local; |
162 | int ret; | |
d01a1e65 MK |
163 | |
164 | lockdep_assert_held(&local->chanctx_mtx); | |
165 | ||
35f2fce9 MK |
166 | ret = drv_assign_vif_chanctx(local, sdata, ctx); |
167 | if (ret) | |
168 | return ret; | |
169 | ||
d01a1e65 MK |
170 | rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf); |
171 | ctx->refcount++; | |
172 | ||
173 | return 0; | |
174 | } | |
175 | ||
e89a96f5 MK |
176 | static enum nl80211_channel_type |
177 | ieee80211_calc_chantype(struct ieee80211_local *local, | |
178 | struct ieee80211_chanctx *ctx) | |
179 | { | |
180 | struct ieee80211_chanctx_conf *conf = &ctx->conf; | |
181 | struct ieee80211_sub_if_data *sdata; | |
182 | enum nl80211_channel_type result = NL80211_CHAN_NO_HT; | |
183 | ||
184 | lockdep_assert_held(&local->chanctx_mtx); | |
185 | ||
186 | rcu_read_lock(); | |
187 | list_for_each_entry_rcu(sdata, &local->interfaces, list) { | |
188 | if (!ieee80211_sdata_running(sdata)) | |
189 | continue; | |
190 | if (rcu_access_pointer(sdata->vif.chanctx_conf) != conf) | |
191 | continue; | |
192 | ||
193 | WARN_ON_ONCE(!ieee80211_channel_types_are_compatible( | |
194 | sdata->vif.bss_conf.channel_type, | |
195 | result, &result)); | |
196 | } | |
197 | rcu_read_unlock(); | |
198 | ||
199 | return result; | |
200 | } | |
201 | ||
202 | static void ieee80211_recalc_chanctx_chantype(struct ieee80211_local *local, | |
203 | struct ieee80211_chanctx *ctx) | |
204 | { | |
205 | enum nl80211_channel_type chantype; | |
206 | ||
207 | lockdep_assert_held(&local->chanctx_mtx); | |
208 | ||
209 | chantype = ieee80211_calc_chantype(local, ctx); | |
210 | ieee80211_change_chantype(local, ctx, chantype); | |
211 | } | |
212 | ||
d01a1e65 MK |
213 | static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata, |
214 | struct ieee80211_chanctx *ctx) | |
215 | { | |
35f2fce9 | 216 | struct ieee80211_local *local = sdata->local; |
d01a1e65 MK |
217 | |
218 | lockdep_assert_held(&local->chanctx_mtx); | |
219 | ||
220 | ctx->refcount--; | |
221 | rcu_assign_pointer(sdata->vif.chanctx_conf, NULL); | |
35f2fce9 MK |
222 | |
223 | drv_unassign_vif_chanctx(local, sdata, ctx); | |
e89a96f5 MK |
224 | |
225 | if (ctx->refcount > 0) | |
226 | ieee80211_recalc_chanctx_chantype(sdata->local, ctx); | |
d01a1e65 MK |
227 | } |
228 | ||
229 | static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) | |
230 | { | |
231 | struct ieee80211_local *local = sdata->local; | |
232 | struct ieee80211_chanctx_conf *conf; | |
233 | struct ieee80211_chanctx *ctx; | |
234 | ||
235 | lockdep_assert_held(&local->chanctx_mtx); | |
236 | ||
237 | conf = rcu_dereference_protected(sdata->vif.chanctx_conf, | |
238 | lockdep_is_held(&local->chanctx_mtx)); | |
239 | if (!conf) | |
240 | return; | |
241 | ||
242 | ctx = container_of(conf, struct ieee80211_chanctx, conf); | |
243 | ||
244 | ieee80211_unassign_vif_chanctx(sdata, ctx); | |
245 | if (ctx->refcount == 0) | |
246 | ieee80211_free_chanctx(local, ctx); | |
247 | } | |
248 | ||
249 | int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata, | |
250 | struct ieee80211_channel *channel, | |
251 | enum nl80211_channel_type channel_type, | |
252 | enum ieee80211_chanctx_mode mode) | |
253 | { | |
254 | struct ieee80211_local *local = sdata->local; | |
255 | struct ieee80211_chanctx *ctx; | |
256 | int ret; | |
257 | ||
55de908a JB |
258 | WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev)); |
259 | ||
d01a1e65 MK |
260 | mutex_lock(&local->chanctx_mtx); |
261 | __ieee80211_vif_release_channel(sdata); | |
262 | ||
263 | ctx = ieee80211_find_chanctx(local, channel, channel_type, mode); | |
264 | if (!ctx) | |
265 | ctx = ieee80211_new_chanctx(local, channel, channel_type, mode); | |
266 | if (IS_ERR(ctx)) { | |
267 | ret = PTR_ERR(ctx); | |
268 | goto out; | |
269 | } | |
270 | ||
55de908a JB |
271 | sdata->vif.bss_conf.channel_type = channel_type; |
272 | ||
d01a1e65 MK |
273 | ret = ieee80211_assign_vif_chanctx(sdata, ctx); |
274 | if (ret) { | |
275 | /* if assign fails refcount stays the same */ | |
276 | if (ctx->refcount == 0) | |
277 | ieee80211_free_chanctx(local, ctx); | |
278 | goto out; | |
279 | } | |
280 | ||
281 | out: | |
282 | mutex_unlock(&local->chanctx_mtx); | |
283 | return ret; | |
284 | } | |
285 | ||
286 | void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata) | |
287 | { | |
55de908a JB |
288 | WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev)); |
289 | ||
d01a1e65 MK |
290 | mutex_lock(&sdata->local->chanctx_mtx); |
291 | __ieee80211_vif_release_channel(sdata); | |
292 | mutex_unlock(&sdata->local->chanctx_mtx); | |
293 | } |