Commit | Line | Data |
---|---|---|
32176cfd RP |
1 | /*- |
2 | * Copyright (c) 2005-2008 Sam Leffler, Errno Consulting | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: | |
8 | * 1. Redistributions of source code must retain the above copyright | |
9 | * notice, this list of conditions and the following disclaimer. | |
10 | * 2. Redistributions in binary form must reproduce the above copyright | |
11 | * notice, this list of conditions and the following disclaimer in the | |
12 | * documentation and/or other materials provided with the distribution. | |
13 | * | |
14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
32176cfd RP |
24 | */ |
25 | ||
085ff963 MD |
26 | #include <sys/cdefs.h> |
27 | __FBSDID("$FreeBSD$"); | |
28 | ||
32176cfd RP |
29 | /* |
30 | * IEEE 802.11 regdomain support. | |
31 | */ | |
32 | #include "opt_wlan.h" | |
33 | ||
34 | #include <sys/param.h> | |
35 | #include <sys/systm.h> | |
36 | #include <sys/kernel.h> | |
085ff963 | 37 | #include <sys/malloc.h> |
32176cfd RP |
38 | #include <sys/socket.h> |
39 | ||
40 | #include <net/if.h> | |
085ff963 | 41 | #include <net/if_var.h> |
32176cfd | 42 | #include <net/if_media.h> |
085ff963 | 43 | #include <net/ethernet.h> |
32176cfd RP |
44 | |
45 | #include <netproto/802_11/ieee80211_var.h> | |
46 | #include <netproto/802_11/ieee80211_regdomain.h> | |
47 | ||
48 | static void | |
49 | null_getradiocaps(struct ieee80211com *ic, int maxchan, | |
50 | int *n, struct ieee80211_channel *c) | |
51 | { | |
52 | /* just feed back the current channel list */ | |
53 | if (maxchan > ic->ic_nchans) | |
54 | maxchan = ic->ic_nchans; | |
55 | memcpy(c, ic->ic_channels, maxchan*sizeof(struct ieee80211_channel)); | |
56 | *n = maxchan; | |
57 | } | |
58 | ||
59 | static int | |
60 | null_setregdomain(struct ieee80211com *ic, | |
61 | struct ieee80211_regdomain *rd, | |
62 | int nchans, struct ieee80211_channel chans[]) | |
63 | { | |
64 | return 0; /* accept anything */ | |
65 | } | |
66 | ||
67 | void | |
68 | ieee80211_regdomain_attach(struct ieee80211com *ic) | |
69 | { | |
70 | if (ic->ic_regdomain.regdomain == 0 && | |
71 | ic->ic_regdomain.country == CTRY_DEFAULT) { | |
72 | ic->ic_regdomain.country = CTRY_UNITED_STATES; /* XXX */ | |
73 | ic->ic_regdomain.location = ' '; /* both */ | |
74 | ic->ic_regdomain.isocc[0] = 'U'; /* XXX */ | |
75 | ic->ic_regdomain.isocc[1] = 'S'; /* XXX */ | |
76 | /* NB: driver calls ieee80211_init_channels or similar */ | |
77 | } | |
78 | ic->ic_getradiocaps = null_getradiocaps; | |
79 | ic->ic_setregdomain = null_setregdomain; | |
80 | } | |
81 | ||
82 | void | |
83 | ieee80211_regdomain_detach(struct ieee80211com *ic) | |
84 | { | |
85 | if (ic->ic_countryie != NULL) { | |
4f655ef5 | 86 | IEEE80211_FREE(ic->ic_countryie, M_80211_NODE_IE); |
32176cfd RP |
87 | ic->ic_countryie = NULL; |
88 | } | |
89 | } | |
90 | ||
91 | void | |
92 | ieee80211_regdomain_vattach(struct ieee80211vap *vap) | |
93 | { | |
94 | } | |
95 | ||
96 | void | |
97 | ieee80211_regdomain_vdetach(struct ieee80211vap *vap) | |
98 | { | |
99 | } | |
100 | ||
4f655ef5 MD |
101 | static const uint8_t def_chan_2ghz[] = |
102 | { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 }; | |
103 | static const uint8_t def_chan_5ghz_band1[] = | |
104 | { 36, 40, 44, 48, 52, 56, 60, 64 }; | |
105 | static const uint8_t def_chan_5ghz_band2[] = | |
106 | { 100, 104, 108, 112, 116, 120, 124, 128, 132, 136, 140 }; | |
107 | static const uint8_t def_chan_5ghz_band3[] = | |
108 | { 149, 153, 157, 161 }; | |
32176cfd RP |
109 | |
110 | /* | |
111 | * Setup the channel list for the specified regulatory domain, | |
112 | * country code, and operating modes. This interface is used | |
113 | * when a driver does not obtain the channel list from another | |
114 | * source (such as firmware). | |
115 | */ | |
116 | int | |
117 | ieee80211_init_channels(struct ieee80211com *ic, | |
118 | const struct ieee80211_regdomain *rd, const uint8_t bands[]) | |
119 | { | |
4f655ef5 MD |
120 | struct ieee80211_channel *chans = ic->ic_channels; |
121 | int *nchans = &ic->ic_nchans; | |
122 | int ht40; | |
32176cfd RP |
123 | |
124 | /* XXX just do something for now */ | |
4f655ef5 MD |
125 | ht40 = !!(ic->ic_htcaps & IEEE80211_HTCAP_CHWIDTH40); |
126 | *nchans = 0; | |
32176cfd | 127 | if (isset(bands, IEEE80211_MODE_11B) || |
085ff963 MD |
128 | isset(bands, IEEE80211_MODE_11G) || |
129 | isset(bands, IEEE80211_MODE_11NG)) { | |
4f655ef5 MD |
130 | int nchan = nitems(def_chan_2ghz); |
131 | if (!(rd != NULL && rd->ecm)) | |
132 | nchan -= 3; | |
133 | ||
134 | ieee80211_add_channel_list_2ghz(chans, IEEE80211_CHAN_MAX, | |
135 | nchans, def_chan_2ghz, nchan, bands, ht40); | |
32176cfd | 136 | } |
085ff963 MD |
137 | if (isset(bands, IEEE80211_MODE_11A) || |
138 | isset(bands, IEEE80211_MODE_11NA)) { | |
4f655ef5 MD |
139 | ieee80211_add_channel_list_5ghz(chans, IEEE80211_CHAN_MAX, |
140 | nchans, def_chan_5ghz_band1, nitems(def_chan_5ghz_band1), | |
141 | bands, ht40); | |
142 | ieee80211_add_channel_list_5ghz(chans, IEEE80211_CHAN_MAX, | |
143 | nchans, def_chan_5ghz_band2, nitems(def_chan_5ghz_band2), | |
144 | bands, ht40); | |
145 | ieee80211_add_channel_list_5ghz(chans, IEEE80211_CHAN_MAX, | |
146 | nchans, def_chan_5ghz_band3, nitems(def_chan_5ghz_band3), | |
147 | bands, ht40); | |
32176cfd RP |
148 | } |
149 | if (rd != NULL) | |
150 | ic->ic_regdomain = *rd; | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | static __inline int | |
156 | chancompar(const void *a, const void *b) | |
157 | { | |
158 | const struct ieee80211_channel *ca = a; | |
159 | const struct ieee80211_channel *cb = b; | |
160 | ||
161 | return (ca->ic_freq == cb->ic_freq) ? | |
162 | (ca->ic_flags & IEEE80211_CHAN_ALL) - | |
163 | (cb->ic_flags & IEEE80211_CHAN_ALL) : | |
164 | ca->ic_freq - cb->ic_freq; | |
165 | } | |
166 | ||
167 | /* | |
168 | * Insertion sort. | |
169 | */ | |
170 | #define swap(_a, _b, _size) { \ | |
171 | uint8_t *s = _b; \ | |
172 | int i = _size; \ | |
173 | do { \ | |
174 | uint8_t tmp = *_a; \ | |
175 | *_a++ = *s; \ | |
176 | *s++ = tmp; \ | |
177 | } while (--i); \ | |
178 | _a -= _size; \ | |
179 | } | |
180 | ||
181 | static void | |
182 | sort_channels(void *a, size_t n, size_t size) | |
183 | { | |
184 | uint8_t *aa = a; | |
185 | uint8_t *ai, *t; | |
186 | ||
187 | KASSERT(n > 0, ("no channels")); | |
188 | for (ai = aa+size; --n >= 1; ai += size) | |
189 | for (t = ai; t > aa; t -= size) { | |
190 | uint8_t *u = t - size; | |
191 | if (chancompar(u, t) <= 0) | |
192 | break; | |
193 | swap(u, t, size); | |
194 | } | |
195 | } | |
196 | #undef swap | |
197 | ||
198 | /* | |
199 | * Order channels w/ the same frequency so that | |
200 | * b < g < htg and a < hta. This is used to optimize | |
201 | * channel table lookups and some user applications | |
202 | * may also depend on it (though they should not). | |
203 | */ | |
204 | void | |
205 | ieee80211_sort_channels(struct ieee80211_channel chans[], int nchans) | |
206 | { | |
207 | if (nchans > 0) | |
208 | sort_channels(chans, nchans, sizeof(struct ieee80211_channel)); | |
209 | } | |
210 | ||
211 | /* | |
212 | * Allocate and construct a Country Information IE. | |
213 | */ | |
214 | struct ieee80211_appie * | |
215 | ieee80211_alloc_countryie(struct ieee80211com *ic) | |
216 | { | |
217 | #define CHAN_UNINTERESTING \ | |
218 | (IEEE80211_CHAN_TURBO | IEEE80211_CHAN_STURBO | \ | |
219 | IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER) | |
220 | /* XXX what about auto? */ | |
221 | /* flag set of channels to be excluded (band added below) */ | |
222 | static const int skipflags[IEEE80211_MODE_MAX] = { | |
223 | [IEEE80211_MODE_AUTO] = CHAN_UNINTERESTING, | |
224 | [IEEE80211_MODE_11A] = CHAN_UNINTERESTING, | |
225 | [IEEE80211_MODE_11B] = CHAN_UNINTERESTING, | |
226 | [IEEE80211_MODE_11G] = CHAN_UNINTERESTING, | |
227 | [IEEE80211_MODE_FH] = CHAN_UNINTERESTING | |
228 | | IEEE80211_CHAN_OFDM | |
229 | | IEEE80211_CHAN_CCK | |
230 | | IEEE80211_CHAN_DYN, | |
231 | [IEEE80211_MODE_TURBO_A] = CHAN_UNINTERESTING, | |
232 | [IEEE80211_MODE_TURBO_G] = CHAN_UNINTERESTING, | |
233 | [IEEE80211_MODE_STURBO_A] = CHAN_UNINTERESTING, | |
234 | [IEEE80211_MODE_HALF] = IEEE80211_CHAN_TURBO | |
235 | | IEEE80211_CHAN_STURBO, | |
236 | [IEEE80211_MODE_QUARTER] = IEEE80211_CHAN_TURBO | |
237 | | IEEE80211_CHAN_STURBO, | |
238 | [IEEE80211_MODE_11NA] = CHAN_UNINTERESTING, | |
239 | [IEEE80211_MODE_11NG] = CHAN_UNINTERESTING, | |
240 | }; | |
241 | const struct ieee80211_regdomain *rd = &ic->ic_regdomain; | |
242 | uint8_t nextchan, chans[IEEE80211_CHAN_BYTES], *frm; | |
243 | struct ieee80211_appie *aie; | |
244 | struct ieee80211_country_ie *ie; | |
245 | int i, skip, nruns; | |
246 | ||
4f655ef5 | 247 | #if defined(__DragonFly__) |
32176cfd | 248 | aie = kmalloc(IEEE80211_COUNTRY_MAX_SIZE, M_80211_NODE_IE, |
fcaa651d | 249 | M_INTWAIT | M_ZERO); |
4f655ef5 MD |
250 | #else |
251 | aie = IEEE80211_MALLOC(IEEE80211_COUNTRY_MAX_SIZE, M_80211_NODE_IE, | |
252 | IEEE80211_M_NOWAIT | IEEE80211_M_ZERO); | |
253 | #endif | |
085ff963 | 254 | if (aie == NULL) { |
4f898719 IV |
255 | ic_printf(ic, "%s: unable to allocate memory for country ie\n", |
256 | __func__); | |
085ff963 MD |
257 | /* XXX stat */ |
258 | return NULL; | |
259 | } | |
32176cfd RP |
260 | ie = (struct ieee80211_country_ie *) aie->ie_data; |
261 | ie->ie = IEEE80211_ELEMID_COUNTRY; | |
262 | if (rd->isocc[0] == '\0') { | |
4f898719 IV |
263 | ic_printf(ic, "no ISO country string for cc %d; using blanks\n", |
264 | rd->country); | |
32176cfd RP |
265 | ie->cc[0] = ie->cc[1] = ' '; |
266 | } else { | |
267 | ie->cc[0] = rd->isocc[0]; | |
268 | ie->cc[1] = rd->isocc[1]; | |
269 | } | |
270 | /* | |
271 | * Indoor/Outdoor portion of country string: | |
272 | * 'I' indoor only | |
273 | * 'O' outdoor only | |
4f655ef5 | 274 | * ' ' all environments |
32176cfd RP |
275 | */ |
276 | ie->cc[2] = (rd->location == 'I' ? 'I' : | |
277 | rd->location == 'O' ? 'O' : ' '); | |
278 | /* | |
279 | * Run-length encoded channel+max tx power info. | |
280 | */ | |
281 | frm = (uint8_t *)&ie->band[0]; | |
282 | nextchan = 0; /* NB: impossible channel # */ | |
283 | nruns = 0; | |
284 | memset(chans, 0, sizeof(chans)); | |
285 | skip = skipflags[ieee80211_chan2mode(ic->ic_bsschan)]; | |
286 | if (IEEE80211_IS_CHAN_5GHZ(ic->ic_bsschan)) | |
287 | skip |= IEEE80211_CHAN_2GHZ; | |
288 | else if (IEEE80211_IS_CHAN_2GHZ(ic->ic_bsschan)) | |
289 | skip |= IEEE80211_CHAN_5GHZ; | |
290 | for (i = 0; i < ic->ic_nchans; i++) { | |
291 | const struct ieee80211_channel *c = &ic->ic_channels[i]; | |
292 | ||
293 | if (isset(chans, c->ic_ieee)) /* suppress dup's */ | |
294 | continue; | |
295 | if (c->ic_flags & skip) /* skip band, etc. */ | |
296 | continue; | |
297 | setbit(chans, c->ic_ieee); | |
298 | if (c->ic_ieee != nextchan || | |
299 | c->ic_maxregpower != frm[-1]) { /* new run */ | |
300 | if (nruns == IEEE80211_COUNTRY_MAX_BANDS) { | |
4f898719 | 301 | ic_printf(ic, "%s: country ie too big, " |
32176cfd RP |
302 | "runs > max %d, truncating\n", |
303 | __func__, IEEE80211_COUNTRY_MAX_BANDS); | |
304 | /* XXX stat? fail? */ | |
305 | break; | |
306 | } | |
307 | frm[0] = c->ic_ieee; /* starting channel # */ | |
308 | frm[1] = 1; /* # channels in run */ | |
309 | frm[2] = c->ic_maxregpower; /* tx power cap */ | |
310 | frm += 3; | |
311 | nextchan = c->ic_ieee + 1; /* overflow? */ | |
312 | nruns++; | |
313 | } else { /* extend run */ | |
314 | frm[-2]++; | |
315 | nextchan++; | |
316 | } | |
317 | } | |
318 | ie->len = frm - ie->cc; | |
319 | if (ie->len & 1) { /* Zero pad to multiple of 2 */ | |
320 | ie->len++; | |
321 | *frm++ = 0; | |
322 | } | |
323 | aie->ie_len = frm - aie->ie_data; | |
324 | ||
325 | return aie; | |
326 | #undef CHAN_UNINTERESTING | |
327 | } | |
328 | ||
329 | static int | |
330 | allvapsdown(struct ieee80211com *ic) | |
331 | { | |
332 | struct ieee80211vap *vap; | |
333 | ||
085ff963 | 334 | IEEE80211_LOCK_ASSERT(ic); |
32176cfd RP |
335 | TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) |
336 | if (vap->iv_state != IEEE80211_S_INIT) | |
337 | return 0; | |
338 | return 1; | |
339 | } | |
340 | ||
341 | int | |
342 | ieee80211_setregdomain(struct ieee80211vap *vap, | |
343 | struct ieee80211_regdomain_req *reg) | |
344 | { | |
345 | struct ieee80211com *ic = vap->iv_ic; | |
346 | struct ieee80211_channel *c; | |
347 | int desfreq = 0, desflags = 0; /* XXX silence gcc complaint */ | |
348 | int error, i; | |
349 | ||
350 | if (reg->rd.location != 'I' && reg->rd.location != 'O' && | |
351 | reg->rd.location != ' ') { | |
352 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, | |
353 | "%s: invalid location 0x%x\n", __func__, reg->rd.location); | |
354 | return EINVAL; | |
355 | } | |
356 | if (reg->rd.isocc[0] == '\0' || reg->rd.isocc[1] == '\0') { | |
357 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, | |
358 | "%s: invalid iso cc 0x%x:0x%x\n", __func__, | |
359 | reg->rd.isocc[0], reg->rd.isocc[1]); | |
360 | return EINVAL; | |
361 | } | |
362 | if (reg->chaninfo.ic_nchans > IEEE80211_CHAN_MAX) { | |
363 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, | |
364 | "%s: too many channels %u, max %u\n", __func__, | |
365 | reg->chaninfo.ic_nchans, IEEE80211_CHAN_MAX); | |
366 | return EINVAL; | |
367 | } | |
368 | /* | |
369 | * Calculate freq<->IEEE mapping and default max tx power | |
370 | * for channels not setup. The driver can override these | |
371 | * setting to reflect device properties/requirements. | |
372 | */ | |
373 | for (i = 0; i < reg->chaninfo.ic_nchans; i++) { | |
374 | c = ®->chaninfo.ic_chans[i]; | |
375 | if (c->ic_freq == 0 || c->ic_flags == 0) { | |
376 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, | |
377 | "%s: invalid channel spec at [%u]\n", __func__, i); | |
378 | return EINVAL; | |
379 | } | |
380 | if (c->ic_maxregpower == 0) { | |
381 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, | |
382 | "%s: invalid channel spec, zero maxregpower, " | |
383 | "freq %u flags 0x%x\n", __func__, | |
384 | c->ic_freq, c->ic_flags); | |
385 | return EINVAL; | |
386 | } | |
387 | if (c->ic_ieee == 0) | |
388 | c->ic_ieee = ieee80211_mhz2ieee(c->ic_freq,c->ic_flags); | |
389 | if (IEEE80211_IS_CHAN_HT40(c) && c->ic_extieee == 0) | |
390 | c->ic_extieee = ieee80211_mhz2ieee(c->ic_freq + | |
391 | (IEEE80211_IS_CHAN_HT40U(c) ? 20 : -20), | |
392 | c->ic_flags); | |
393 | if (c->ic_maxpower == 0) | |
394 | c->ic_maxpower = 2*c->ic_maxregpower; | |
395 | } | |
085ff963 | 396 | IEEE80211_LOCK(ic); |
32176cfd RP |
397 | /* XXX bandaid; a running vap will likely crash */ |
398 | if (!allvapsdown(ic)) { | |
085ff963 | 399 | IEEE80211_UNLOCK(ic); |
32176cfd RP |
400 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, |
401 | "%s: reject: vaps are running\n", __func__); | |
402 | return EBUSY; | |
403 | } | |
404 | error = ic->ic_setregdomain(ic, ®->rd, | |
405 | reg->chaninfo.ic_nchans, reg->chaninfo.ic_chans); | |
406 | if (error != 0) { | |
085ff963 | 407 | IEEE80211_UNLOCK(ic); |
32176cfd RP |
408 | IEEE80211_DPRINTF(vap, IEEE80211_MSG_IOCTL, |
409 | "%s: driver rejected request, error %u\n", __func__, error); | |
410 | return error; | |
411 | } | |
412 | /* | |
413 | * Commit: copy in new channel table and reset media state. | |
414 | * On return the state machines will be clocked so all vaps | |
415 | * will reset their state. | |
416 | * | |
417 | * XXX ic_bsschan is marked undefined, must have vap's in | |
418 | * INIT state or we blow up forcing stations off | |
419 | */ | |
420 | /* | |
421 | * Save any desired channel for restore below. Note this | |
422 | * needs to be done for all vaps but for now we only do | |
423 | * the one where the ioctl is issued. | |
424 | */ | |
425 | if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) { | |
426 | desfreq = vap->iv_des_chan->ic_freq; | |
427 | desflags = vap->iv_des_chan->ic_flags; | |
428 | } | |
429 | /* regdomain parameters */ | |
430 | memcpy(&ic->ic_regdomain, ®->rd, sizeof(reg->rd)); | |
431 | /* channel table */ | |
432 | memcpy(ic->ic_channels, reg->chaninfo.ic_chans, | |
433 | reg->chaninfo.ic_nchans * sizeof(struct ieee80211_channel)); | |
434 | ic->ic_nchans = reg->chaninfo.ic_nchans; | |
435 | memset(&ic->ic_channels[ic->ic_nchans], 0, | |
436 | (IEEE80211_CHAN_MAX - ic->ic_nchans) * | |
437 | sizeof(struct ieee80211_channel)); | |
4f655ef5 | 438 | ieee80211_chan_init(ic); |
32176cfd RP |
439 | |
440 | /* | |
441 | * Invalidate channel-related state. | |
442 | */ | |
443 | if (ic->ic_countryie != NULL) { | |
4f655ef5 | 444 | IEEE80211_FREE(ic->ic_countryie, M_80211_NODE_IE); |
32176cfd RP |
445 | ic->ic_countryie = NULL; |
446 | } | |
447 | ieee80211_scan_flush(vap); | |
448 | ieee80211_dfs_reset(ic); | |
449 | if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) { | |
450 | c = ieee80211_find_channel(ic, desfreq, desflags); | |
451 | /* NB: may be NULL if not present in new channel list */ | |
452 | vap->iv_des_chan = (c != NULL) ? c : IEEE80211_CHAN_ANYC; | |
453 | } | |
085ff963 | 454 | IEEE80211_UNLOCK(ic); |
32176cfd RP |
455 | |
456 | return 0; | |
457 | } |