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