xref: /openbmc/linux/net/mac80211/chan.c (revision 55de908ab292c03f1eb280f51170ddb9c6b57e31)
1 /*
2  * mac80211 - channel management
3  */
4 
5 #include <linux/nl80211.h>
6 #include <net/cfg80211.h>
7 #include "ieee80211_i.h"
8 #include "driver-ops.h"
9 
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) {
23 	case NL80211_CHAN_NO_HT:
24 		if (compat)
25 			*compat = chantype2;
26 		break;
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 		 */
32 		if (chantype2 == NL80211_CHAN_NO_HT)
33 			break;
34 		if (compat)
35 			*compat = chantype2;
36 		break;
37 	case NL80211_CHAN_HT40PLUS:
38 	case NL80211_CHAN_HT40MINUS:
39 		/* allow smaller bandwidth and same */
40 		if (chantype2 == NL80211_CHAN_NO_HT)
41 			break;
42 		if (chantype2 == NL80211_CHAN_HT20)
43 			break;
44 		if (chantype2 == chantype1)
45 			break;
46 		return false;
47 	}
48 
49 	return true;
50 }
51 
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;
58 
59 	ctx->conf.channel_type = chantype;
60 	drv_change_chanctx(local, ctx, IEEE80211_CHANCTX_CHANGE_CHANNEL_TYPE);
61 
62 	if (!local->use_chanctx) {
63 		local->_oper_channel_type = chantype;
64 		ieee80211_hw_config(local, 0);
65 	}
66 }
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;
75 	enum nl80211_channel_type compat_type;
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) {
85 		compat_type = ctx->conf.channel_type;
86 
87 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
88 			continue;
89 		if (ctx->conf.channel != channel)
90 			continue;
91 		if (!ieee80211_channel_types_are_compatible(ctx->conf.channel_type,
92 							    channel_type,
93 							    &compat_type))
94 			continue;
95 
96 		ieee80211_change_chantype(local, ctx, compat_type);
97 
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;
111 	int err;
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 
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 		}
133 	}
134 
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 
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 	}
153 
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 {
161 	struct ieee80211_local *local = sdata->local;
162 	int ret;
163 
164 	lockdep_assert_held(&local->chanctx_mtx);
165 
166 	ret = drv_assign_vif_chanctx(local, sdata, ctx);
167 	if (ret)
168 		return ret;
169 
170 	rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
171 	ctx->refcount++;
172 
173 	return 0;
174 }
175 
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 
213 static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
214 					   struct ieee80211_chanctx *ctx)
215 {
216 	struct ieee80211_local *local = sdata->local;
217 
218 	lockdep_assert_held(&local->chanctx_mtx);
219 
220 	ctx->refcount--;
221 	rcu_assign_pointer(sdata->vif.chanctx_conf, NULL);
222 
223 	drv_unassign_vif_chanctx(local, sdata, ctx);
224 
225 	if (ctx->refcount > 0)
226 		ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
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 
258 	WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
259 
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 
271 	sdata->vif.bss_conf.channel_type = channel_type;
272 
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 {
288 	WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
289 
290 	mutex_lock(&sdata->local->chanctx_mtx);
291 	__ieee80211_vif_release_channel(sdata);
292 	mutex_unlock(&sdata->local->chanctx_mtx);
293 }
294