blob: 41e1aa69f7aabbcb9224253eeba701cbc3679e4f [file] [log] [blame]
Johannes Bergf444de02010-05-05 15:25:02 +02001/*
2 * mac80211 - channel management
3 */
4
Johannes Berg0aaffa92010-05-05 15:28:27 +02005#include <linux/nl80211.h>
Paul Stewart3117bbdb2012-03-13 07:46:18 -07006#include <net/cfg80211.h>
Johannes Bergf444de02010-05-05 15:25:02 +02007#include "ieee80211_i.h"
Michal Kazior35f2fce2012-06-26 14:37:20 +02008#include "driver-ops.h"
Johannes Bergf444de02010-05-05 15:25:02 +02009
Michal Kazior23a85b452012-06-26 14:37:21 +020010static 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) {
Johannes Berg0aaffa92010-05-05 15:28:27 +020023 case NL80211_CHAN_NO_HT:
Michal Kazior23a85b452012-06-26 14:37:21 +020024 if (compat)
25 *compat = chantype2;
26 break;
Johannes Berg0aaffa92010-05-05 15:28:27 +020027 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 */
Michal Kazior23a85b452012-06-26 14:37:21 +020032 if (chantype2 == NL80211_CHAN_NO_HT)
Johannes Berg0aaffa92010-05-05 15:28:27 +020033 break;
Michal Kazior23a85b452012-06-26 14:37:21 +020034 if (compat)
35 *compat = chantype2;
Johannes Berg0aaffa92010-05-05 15:28:27 +020036 break;
37 case NL80211_CHAN_HT40PLUS:
38 case NL80211_CHAN_HT40MINUS:
39 /* allow smaller bandwidth and same */
Michal Kazior23a85b452012-06-26 14:37:21 +020040 if (chantype2 == NL80211_CHAN_NO_HT)
Johannes Berg0aaffa92010-05-05 15:28:27 +020041 break;
Michal Kazior23a85b452012-06-26 14:37:21 +020042 if (chantype2 == NL80211_CHAN_HT20)
Johannes Berg0aaffa92010-05-05 15:28:27 +020043 break;
Michal Kazior23a85b452012-06-26 14:37:21 +020044 if (chantype2 == chantype1)
Johannes Berg0aaffa92010-05-05 15:28:27 +020045 break;
Michal Kazior23a85b452012-06-26 14:37:21 +020046 return false;
Johannes Berg0aaffa92010-05-05 15:28:27 +020047 }
48
Michal Kazior23a85b452012-06-26 14:37:21 +020049 return true;
50}
51
Michal Kaziore89a96f2012-06-26 14:37:22 +020052static 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);
Johannes Berg55de9082012-07-26 17:24:39 +020061
62 if (!local->use_chanctx) {
63 local->_oper_channel_type = chantype;
64 ieee80211_hw_config(local, 0);
65 }
Johannes Berg0aaffa92010-05-05 15:28:27 +020066}
Michal Kaziord01a1e62012-06-26 14:37:16 +020067
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;
Michal Kaziore89a96f2012-06-26 14:37:22 +020075 enum nl80211_channel_type compat_type;
Michal Kaziord01a1e62012-06-26 14:37:16 +020076
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) {
Michal Kaziore89a96f2012-06-26 14:37:22 +020085 compat_type = ctx->conf.channel_type;
86
Michal Kaziord01a1e62012-06-26 14:37:16 +020087 if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
88 continue;
89 if (ctx->conf.channel != channel)
90 continue;
Michal Kaziore89a96f2012-06-26 14:37:22 +020091 if (!ieee80211_channel_types_are_compatible(ctx->conf.channel_type,
92 channel_type,
93 &compat_type))
Michal Kaziord01a1e62012-06-26 14:37:16 +020094 continue;
95
Michal Kaziore89a96f2012-06-26 14:37:22 +020096 ieee80211_change_chantype(local, ctx, compat_type);
97
Michal Kaziord01a1e62012-06-26 14:37:16 +020098 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;
Michal Kazior35f2fce2012-06-26 14:37:20 +0200111 int err;
Michal Kaziord01a1e62012-06-26 14:37:16 +0200112
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
Johannes Berg55de9082012-07-26 17:24:39 +0200123 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 }
Michal Kazior35f2fce2012-06-26 14:37:20 +0200133 }
134
Michal Kaziord01a1e62012-06-26 14:37:16 +0200135 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
Johannes Berg55de9082012-07-26 17:24:39 +0200147 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 }
Michal Kazior35f2fce2012-06-26 14:37:20 +0200153
Michal Kaziord01a1e62012-06-26 14:37:16 +0200154 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{
Michal Kazior35f2fce2012-06-26 14:37:20 +0200161 struct ieee80211_local *local = sdata->local;
162 int ret;
Michal Kaziord01a1e62012-06-26 14:37:16 +0200163
164 lockdep_assert_held(&local->chanctx_mtx);
165
Michal Kazior35f2fce2012-06-26 14:37:20 +0200166 ret = drv_assign_vif_chanctx(local, sdata, ctx);
167 if (ret)
168 return ret;
169
Michal Kaziord01a1e62012-06-26 14:37:16 +0200170 rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
171 ctx->refcount++;
172
173 return 0;
174}
175
Michal Kaziore89a96f2012-06-26 14:37:22 +0200176static 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
Michal Kaziord01a1e62012-06-26 14:37:16 +0200213static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
214 struct ieee80211_chanctx *ctx)
215{
Michal Kazior35f2fce2012-06-26 14:37:20 +0200216 struct ieee80211_local *local = sdata->local;
Michal Kaziord01a1e62012-06-26 14:37:16 +0200217
218 lockdep_assert_held(&local->chanctx_mtx);
219
220 ctx->refcount--;
221 rcu_assign_pointer(sdata->vif.chanctx_conf, NULL);
Michal Kazior35f2fce2012-06-26 14:37:20 +0200222
223 drv_unassign_vif_chanctx(local, sdata, ctx);
Michal Kaziore89a96f2012-06-26 14:37:22 +0200224
225 if (ctx->refcount > 0)
226 ieee80211_recalc_chanctx_chantype(sdata->local, ctx);
Michal Kaziord01a1e62012-06-26 14:37:16 +0200227}
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
Johannes Berg55de9082012-07-26 17:24:39 +0200258 WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
259
Michal Kaziord01a1e62012-06-26 14:37:16 +0200260 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
Johannes Berg55de9082012-07-26 17:24:39 +0200271 sdata->vif.bss_conf.channel_type = channel_type;
272
Michal Kaziord01a1e62012-06-26 14:37:16 +0200273 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{
Johannes Berg55de9082012-07-26 17:24:39 +0200288 WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));
289
Michal Kaziord01a1e62012-06-26 14:37:16 +0200290 mutex_lock(&sdata->local->chanctx_mtx);
291 __ieee80211_vif_release_channel(sdata);
292 mutex_unlock(&sdata->local->chanctx_mtx);
293}