mac80211: use channel context notifications
[firefly-linux-kernel-4.4.55.git] / net / mac80211 / chan.c
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 enum ieee80211_chan_mode
11 __ieee80211_get_channel_mode(struct ieee80211_local *local,
12                              struct ieee80211_sub_if_data *ignore)
13 {
14         struct ieee80211_sub_if_data *sdata;
15
16         lockdep_assert_held(&local->iflist_mtx);
17
18         list_for_each_entry(sdata, &local->interfaces, list) {
19                 if (sdata == ignore)
20                         continue;
21
22                 if (!ieee80211_sdata_running(sdata))
23                         continue;
24
25                 switch (sdata->vif.type) {
26                 case NL80211_IFTYPE_MONITOR:
27                         continue;
28                 case NL80211_IFTYPE_STATION:
29                         if (!sdata->u.mgd.associated)
30                                 continue;
31                         break;
32                 case NL80211_IFTYPE_ADHOC:
33                         if (!sdata->u.ibss.ssid_len)
34                                 continue;
35                         if (!sdata->u.ibss.fixed_channel)
36                                 return CHAN_MODE_HOPPING;
37                         break;
38                 case NL80211_IFTYPE_AP_VLAN:
39                         /* will also have _AP interface */
40                         continue;
41                 case NL80211_IFTYPE_AP:
42                         if (!sdata->u.ap.beacon)
43                                 continue;
44                         break;
45                 case NL80211_IFTYPE_MESH_POINT:
46                         if (!sdata->wdev.mesh_id_len)
47                                 continue;
48                         break;
49                 default:
50                         break;
51                 }
52
53                 return CHAN_MODE_FIXED;
54         }
55
56         return CHAN_MODE_UNDEFINED;
57 }
58
59 enum ieee80211_chan_mode
60 ieee80211_get_channel_mode(struct ieee80211_local *local,
61                            struct ieee80211_sub_if_data *ignore)
62 {
63         enum ieee80211_chan_mode mode;
64
65         mutex_lock(&local->iflist_mtx);
66         mode = __ieee80211_get_channel_mode(local, ignore);
67         mutex_unlock(&local->iflist_mtx);
68
69         return mode;
70 }
71
72 static enum nl80211_channel_type
73 ieee80211_get_superchan(struct ieee80211_local *local,
74                         struct ieee80211_sub_if_data *sdata)
75 {
76         enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT;
77         struct ieee80211_sub_if_data *tmp;
78
79         mutex_lock(&local->iflist_mtx);
80         list_for_each_entry(tmp, &local->interfaces, list) {
81                 if (tmp == sdata)
82                         continue;
83
84                 if (!ieee80211_sdata_running(tmp))
85                         continue;
86
87                 switch (tmp->vif.bss_conf.channel_type) {
88                 case NL80211_CHAN_NO_HT:
89                 case NL80211_CHAN_HT20:
90                         if (superchan > tmp->vif.bss_conf.channel_type)
91                                 break;
92
93                         superchan = tmp->vif.bss_conf.channel_type;
94                         break;
95                 case NL80211_CHAN_HT40PLUS:
96                         WARN_ON(superchan == NL80211_CHAN_HT40MINUS);
97                         superchan = NL80211_CHAN_HT40PLUS;
98                         break;
99                 case NL80211_CHAN_HT40MINUS:
100                         WARN_ON(superchan == NL80211_CHAN_HT40PLUS);
101                         superchan = NL80211_CHAN_HT40MINUS;
102                         break;
103                 }
104         }
105         mutex_unlock(&local->iflist_mtx);
106
107         return superchan;
108 }
109
110 static bool
111 ieee80211_channel_types_are_compatible(enum nl80211_channel_type chantype1,
112                                        enum nl80211_channel_type chantype2,
113                                        enum nl80211_channel_type *compat)
114 {
115         /*
116          * start out with chantype1 being the result,
117          * overwriting later if needed
118          */
119         if (compat)
120                 *compat = chantype1;
121
122         switch (chantype1) {
123         case NL80211_CHAN_NO_HT:
124                 if (compat)
125                         *compat = chantype2;
126                 break;
127         case NL80211_CHAN_HT20:
128                 /*
129                  * allow any change that doesn't go to no-HT
130                  * (if it already is no-HT no change is needed)
131                  */
132                 if (chantype2 == NL80211_CHAN_NO_HT)
133                         break;
134                 if (compat)
135                         *compat = chantype2;
136                 break;
137         case NL80211_CHAN_HT40PLUS:
138         case NL80211_CHAN_HT40MINUS:
139                 /* allow smaller bandwidth and same */
140                 if (chantype2 == NL80211_CHAN_NO_HT)
141                         break;
142                 if (chantype2 == NL80211_CHAN_HT20)
143                         break;
144                 if (chantype2 == chantype1)
145                         break;
146                 return false;
147         }
148
149         return true;
150 }
151
152 bool ieee80211_set_channel_type(struct ieee80211_local *local,
153                                 struct ieee80211_sub_if_data *sdata,
154                                 enum nl80211_channel_type chantype)
155 {
156         enum nl80211_channel_type superchan;
157         enum nl80211_channel_type compatchan;
158
159         superchan = ieee80211_get_superchan(local, sdata);
160         if (!ieee80211_channel_types_are_compatible(superchan, chantype,
161                                                     &compatchan))
162                 return false;
163
164         local->_oper_channel_type = compatchan;
165
166         if (sdata)
167                 sdata->vif.bss_conf.channel_type = chantype;
168
169         return true;
170
171 }
172
173 static struct ieee80211_chanctx *
174 ieee80211_find_chanctx(struct ieee80211_local *local,
175                        struct ieee80211_channel *channel,
176                        enum nl80211_channel_type channel_type,
177                        enum ieee80211_chanctx_mode mode)
178 {
179         struct ieee80211_chanctx *ctx;
180
181         lockdep_assert_held(&local->chanctx_mtx);
182
183         if (mode == IEEE80211_CHANCTX_EXCLUSIVE)
184                 return NULL;
185         if (WARN_ON(!channel))
186                 return NULL;
187
188         list_for_each_entry(ctx, &local->chanctx_list, list) {
189                 if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
190                         continue;
191                 if (ctx->conf.channel != channel)
192                         continue;
193                 if (ctx->conf.channel_type != channel_type)
194                         continue;
195
196                 return ctx;
197         }
198
199         return NULL;
200 }
201
202 static struct ieee80211_chanctx *
203 ieee80211_new_chanctx(struct ieee80211_local *local,
204                       struct ieee80211_channel *channel,
205                       enum nl80211_channel_type channel_type,
206                       enum ieee80211_chanctx_mode mode)
207 {
208         struct ieee80211_chanctx *ctx;
209         int err;
210
211         lockdep_assert_held(&local->chanctx_mtx);
212
213         ctx = kzalloc(sizeof(*ctx) + local->hw.chanctx_data_size, GFP_KERNEL);
214         if (!ctx)
215                 return ERR_PTR(-ENOMEM);
216
217         ctx->conf.channel = channel;
218         ctx->conf.channel_type = channel_type;
219         ctx->mode = mode;
220
221         err = drv_add_chanctx(local, ctx);
222         if (err) {
223                 kfree(ctx);
224                 return ERR_PTR(err);
225         }
226
227         list_add(&ctx->list, &local->chanctx_list);
228
229         return ctx;
230 }
231
232 static void ieee80211_free_chanctx(struct ieee80211_local *local,
233                                    struct ieee80211_chanctx *ctx)
234 {
235         lockdep_assert_held(&local->chanctx_mtx);
236
237         WARN_ON_ONCE(ctx->refcount != 0);
238
239         drv_remove_chanctx(local, ctx);
240
241         list_del(&ctx->list);
242         kfree_rcu(ctx, rcu_head);
243 }
244
245 static int ieee80211_assign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
246                                         struct ieee80211_chanctx *ctx)
247 {
248         struct ieee80211_local *local = sdata->local;
249         int ret;
250
251         lockdep_assert_held(&local->chanctx_mtx);
252
253         ret = drv_assign_vif_chanctx(local, sdata, ctx);
254         if (ret)
255                 return ret;
256
257         rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
258         ctx->refcount++;
259
260         return 0;
261 }
262
263 static void ieee80211_unassign_vif_chanctx(struct ieee80211_sub_if_data *sdata,
264                                            struct ieee80211_chanctx *ctx)
265 {
266         struct ieee80211_local *local = sdata->local;
267
268         lockdep_assert_held(&local->chanctx_mtx);
269
270         ctx->refcount--;
271         rcu_assign_pointer(sdata->vif.chanctx_conf, NULL);
272
273         drv_unassign_vif_chanctx(local, sdata, ctx);
274 }
275
276 static void __ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
277 {
278         struct ieee80211_local *local = sdata->local;
279         struct ieee80211_chanctx_conf *conf;
280         struct ieee80211_chanctx *ctx;
281
282         lockdep_assert_held(&local->chanctx_mtx);
283
284         conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
285                                          lockdep_is_held(&local->chanctx_mtx));
286         if (!conf)
287                 return;
288
289         ctx = container_of(conf, struct ieee80211_chanctx, conf);
290
291         ieee80211_unassign_vif_chanctx(sdata, ctx);
292         if (ctx->refcount == 0)
293                 ieee80211_free_chanctx(local, ctx);
294 }
295
296 int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
297                               struct ieee80211_channel *channel,
298                               enum nl80211_channel_type channel_type,
299                               enum ieee80211_chanctx_mode mode)
300 {
301         struct ieee80211_local *local = sdata->local;
302         struct ieee80211_chanctx *ctx;
303         int ret;
304
305         mutex_lock(&local->chanctx_mtx);
306         __ieee80211_vif_release_channel(sdata);
307
308         ctx = ieee80211_find_chanctx(local, channel, channel_type, mode);
309         if (!ctx)
310                 ctx = ieee80211_new_chanctx(local, channel, channel_type, mode);
311         if (IS_ERR(ctx)) {
312                 ret = PTR_ERR(ctx);
313                 goto out;
314         }
315
316         ret = ieee80211_assign_vif_chanctx(sdata, ctx);
317         if (ret) {
318                 /* if assign fails refcount stays the same */
319                 if (ctx->refcount == 0)
320                         ieee80211_free_chanctx(local, ctx);
321                 goto out;
322         }
323
324  out:
325         mutex_unlock(&local->chanctx_mtx);
326         return ret;
327 }
328
329 void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
330 {
331         mutex_lock(&sdata->local->chanctx_mtx);
332         __ieee80211_vif_release_channel(sdata);
333         mutex_unlock(&sdata->local->chanctx_mtx);
334 }