]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/blame - net/mac80211/chan.c
mac80211: use channel contexts
[mirror_ubuntu-jammy-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;
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
140static 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
158static 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
176static enum nl80211_channel_type
177ieee80211_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
202static 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
213static 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
229static 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
249int 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
286void 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}