ieee80211_regdomain.c revision 178354
1170530Ssam/*-
2178354Ssam * Copyright (c) 2005-2008 Sam Leffler, Errno Consulting
3170530Ssam * All rights reserved.
4170530Ssam *
5170530Ssam * Redistribution and use in source and binary forms, with or without
6170530Ssam * modification, are permitted provided that the following conditions
7170530Ssam * are met:
8170530Ssam * 1. Redistributions of source code must retain the above copyright
9170530Ssam *    notice, this list of conditions and the following disclaimer.
10170530Ssam * 2. Redistributions in binary form must reproduce the above copyright
11170530Ssam *    notice, this list of conditions and the following disclaimer in the
12170530Ssam *    documentation and/or other materials provided with the distribution.
13170530Ssam *
14170530Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15170530Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16170530Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17170530Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18170530Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19170530Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20170530Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21170530Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22170530Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23170530Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24170530Ssam */
25170530Ssam
26170530Ssam#include <sys/cdefs.h>
27170530Ssam__FBSDID("$FreeBSD: head/sys/net80211/ieee80211_regdomain.c 178354 2008-04-20 20:35:46Z sam $");
28170530Ssam
29170530Ssam/*
30170530Ssam * IEEE 802.11 regdomain support.
31170530Ssam */
32178354Ssam#include "opt_wlan.h"
33170530Ssam
34170530Ssam#include <sys/param.h>
35170530Ssam#include <sys/systm.h>
36170530Ssam#include <sys/kernel.h>
37170530Ssam
38170530Ssam#include <sys/socket.h>
39170530Ssam
40170530Ssam#include <net/if.h>
41170530Ssam#include <net/if_media.h>
42170530Ssam
43170530Ssam#include <net80211/ieee80211_var.h>
44170530Ssam#include <net80211/ieee80211_regdomain.h>
45170530Ssam
46178354Ssamstatic void
47178354Ssamnull_getradiocaps(struct ieee80211com *ic, int *n, struct ieee80211_channel *c)
48178354Ssam{
49178354Ssam	/* just feed back the current channel list */
50178354Ssam	*n = ic->ic_nchans;
51178354Ssam	memcpy(c, ic->ic_channels,
52178354Ssam	    ic->ic_nchans*sizeof(struct ieee80211_channel));
53178354Ssam}
54178354Ssam
55178354Ssamstatic int
56178354Ssamnull_setregdomain(struct ieee80211com *ic,
57178354Ssam	struct ieee80211_regdomain *rd,
58178354Ssam	int nchans, struct ieee80211_channel chans[])
59178354Ssam{
60178354Ssam	return 0;		/* accept anything */
61178354Ssam}
62178354Ssam
63170530Ssamvoid
64170530Ssamieee80211_regdomain_attach(struct ieee80211com *ic)
65170530Ssam{
66178354Ssam	if (ic->ic_regdomain.regdomain == 0 &&
67178354Ssam	    ic->ic_regdomain.country == CTRY_DEFAULT) {
68178354Ssam		ic->ic_regdomain.country = CTRY_UNITED_STATES;	/* XXX */
69178354Ssam		ic->ic_regdomain.location = ' ';		/* both */
70178354Ssam		ic->ic_regdomain.isocc[0] = 'U';		/* XXX */
71178354Ssam		ic->ic_regdomain.isocc[1] = 'S';		/* XXX */
72178354Ssam		/* XXX? too late to setup default channel list */
73178354Ssam	}
74178354Ssam	ic->ic_getradiocaps = null_getradiocaps;
75178354Ssam	ic->ic_setregdomain = null_setregdomain;
76170530Ssam}
77170530Ssam
78170530Ssamvoid
79170530Ssamieee80211_regdomain_detach(struct ieee80211com *ic)
80170530Ssam{
81178354Ssam	if (ic->ic_countryie != NULL) {
82178354Ssam		free(ic->ic_countryie, M_80211_NODE_IE);
83178354Ssam		ic->ic_countryie = NULL;
84178354Ssam	}
85170530Ssam}
86170530Ssam
87178354Ssamvoid
88178354Ssamieee80211_regdomain_vattach(struct ieee80211vap *vap)
89178354Ssam{
90178354Ssam}
91178354Ssam
92178354Ssamvoid
93178354Ssamieee80211_regdomain_vdetach(struct ieee80211vap *vap)
94178354Ssam{
95178354Ssam}
96178354Ssam
97170530Ssamstatic void
98170530Ssamaddchan(struct ieee80211com *ic, int ieee, int flags)
99170530Ssam{
100170530Ssam	struct ieee80211_channel *c;
101170530Ssam
102170530Ssam	c = &ic->ic_channels[ic->ic_nchans++];
103170530Ssam	c->ic_freq = ieee80211_ieee2mhz(ieee, flags);
104170530Ssam	c->ic_ieee = ieee;
105170530Ssam	c->ic_flags = flags;
106178354Ssam	c->ic_extieee = 0;
107170530Ssam}
108170530Ssam
109170530Ssam/*
110170530Ssam * Setup the channel list for the specified regulatory domain,
111170530Ssam * country code, and operating modes.  This interface is used
112170530Ssam * when a driver does not obtain the channel list from another
113170530Ssam * source (such as firmware).
114170530Ssam */
115178354Ssamint
116170530Ssamieee80211_init_channels(struct ieee80211com *ic,
117178354Ssam	const struct ieee80211_regdomain *rd, const uint8_t bands[])
118170530Ssam{
119170530Ssam	int i;
120170530Ssam
121170530Ssam	/* XXX just do something for now */
122170530Ssam	ic->ic_nchans = 0;
123178354Ssam	if (isset(bands, IEEE80211_MODE_11B) ||
124178354Ssam	    isset(bands, IEEE80211_MODE_11G)) {
125178354Ssam		int maxchan = 11;
126178354Ssam		if (rd != NULL && rd->ecm)
127178354Ssam			maxchan = 14;
128178354Ssam		for (i = 1; i <= maxchan; i++) {
129178354Ssam			if (isset(bands, IEEE80211_MODE_11B))
130170530Ssam				addchan(ic, i, IEEE80211_CHAN_B);
131178354Ssam			if (isset(bands, IEEE80211_MODE_11G))
132170530Ssam				addchan(ic, i, IEEE80211_CHAN_G);
133170530Ssam		}
134170530Ssam	}
135178354Ssam	if (isset(bands, IEEE80211_MODE_11A)) {
136170530Ssam		for (i = 36; i <= 64; i += 4)
137170530Ssam			addchan(ic, i, IEEE80211_CHAN_A);
138170530Ssam		for (i = 100; i <= 140; i += 4)
139170530Ssam			addchan(ic, i, IEEE80211_CHAN_A);
140170530Ssam		for (i = 149; i <= 161; i += 4)
141170530Ssam			addchan(ic, i, IEEE80211_CHAN_A);
142170530Ssam	}
143178354Ssam	if (rd != NULL)
144178354Ssam		ic->ic_regdomain = *rd;
145178354Ssam
146178354Ssam	return 0;
147170530Ssam}
148170530Ssam
149178354Ssamstatic __inline int
150178354Ssamchancompar(const void *a, const void *b)
151178354Ssam{
152178354Ssam	const struct ieee80211_channel *ca = a;
153178354Ssam	const struct ieee80211_channel *cb = b;
154178354Ssam
155178354Ssam	return (ca->ic_freq == cb->ic_freq) ?
156178354Ssam		(ca->ic_flags & IEEE80211_CHAN_ALL) -
157178354Ssam		    (cb->ic_flags & IEEE80211_CHAN_ALL) :
158178354Ssam		ca->ic_freq - cb->ic_freq;
159178354Ssam}
160178354Ssam
161170530Ssam/*
162178354Ssam * Insertion sort.
163170530Ssam */
164178354Ssam#define swap(_a, _b, _size) {			\
165178354Ssam	uint8_t *s = _b;			\
166178354Ssam	int i = _size;				\
167178354Ssam	do {					\
168178354Ssam		uint8_t tmp = *_a;		\
169178354Ssam		*_a++ = *s;			\
170178354Ssam		*s++ = tmp;			\
171178354Ssam	} while (--i);				\
172178354Ssam	_a -= _size;				\
173178354Ssam}
174178354Ssam
175178354Ssamstatic void
176178354Ssamsort_channels(void *a, size_t n, size_t size)
177170530Ssam{
178178354Ssam	uint8_t *aa = a;
179178354Ssam	uint8_t *ai, *t;
180178354Ssam
181178354Ssam	KASSERT(n > 0, ("no channels"));
182178354Ssam	for (ai = aa+size; --n >= 1; ai += size)
183178354Ssam		for (t = ai; t > aa; t -= size) {
184178354Ssam			uint8_t *u = t - size;
185178354Ssam			if (chancompar(u, t) <= 0)
186178354Ssam				break;
187178354Ssam			swap(u, t, size);
188178354Ssam		}
189178354Ssam}
190178354Ssam#undef swap
191178354Ssam
192178354Ssam/*
193178354Ssam * Order channels w/ the same frequency so that
194178354Ssam * b < g < htg and a < hta.  This is used to optimize
195178354Ssam * channel table lookups and some user applications
196178354Ssam * may also depend on it (though they should not).
197178354Ssam */
198178354Ssamvoid
199178354Ssamieee80211_sort_channels(struct ieee80211_channel chans[], int nchans)
200178354Ssam{
201178354Ssam	if (nchans > 0)
202178354Ssam		sort_channels(chans, nchans, sizeof(struct ieee80211_channel));
203178354Ssam}
204178354Ssam
205178354Ssam/*
206178354Ssam * Allocate and construct a Country Information IE.
207178354Ssam */
208178354Ssamstruct ieee80211_appie *
209178354Ssamieee80211_alloc_countryie(struct ieee80211com *ic)
210178354Ssam{
211170530Ssam#define	CHAN_UNINTERESTING \
212170530Ssam    (IEEE80211_CHAN_TURBO | IEEE80211_CHAN_STURBO | \
213170530Ssam     IEEE80211_CHAN_HALF | IEEE80211_CHAN_QUARTER)
214170530Ssam	/* XXX what about auto? */
215170530Ssam	/* flag set of channels to be excluded */
216170530Ssam	static const int skipflags[IEEE80211_MODE_MAX] = {
217170530Ssam	    CHAN_UNINTERESTING,				/* MODE_AUTO */
218170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_2GHZ,	/* MODE_11A */
219170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_5GHZ,	/* MODE_11B */
220170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_5GHZ,	/* MODE_11G */
221170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_OFDM |	/* MODE_FH */
222170530Ssam	        IEEE80211_CHAN_CCK | IEEE80211_CHAN_DYN,
223170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_2GHZ,	/* MODE_TURBO_A */
224170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_5GHZ,	/* MODE_TURBO_G */
225170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_2GHZ,	/* MODE_STURBO_A */
226170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_2GHZ,	/* MODE_11NA */
227170530Ssam	    CHAN_UNINTERESTING | IEEE80211_CHAN_5GHZ,	/* MODE_11NG */
228170530Ssam	};
229178354Ssam	const struct ieee80211_regdomain *rd = &ic->ic_regdomain;
230178354Ssam	uint8_t nextchan, chans[IEEE80211_CHAN_BYTES], *frm;
231178354Ssam	struct ieee80211_appie *aie;
232178354Ssam	struct ieee80211_country_ie *ie;
233178354Ssam	int i, skip, nruns;
234170530Ssam
235178354Ssam	aie = malloc(IEEE80211_COUNTRY_MAX_SIZE, M_80211_NODE_IE,
236178354Ssam	    M_NOWAIT | M_ZERO);
237178354Ssam	if (aie == NULL) {
238178354Ssam		if_printf(ic->ic_ifp,
239178354Ssam		    "%s: unable to allocate memory for country ie\n", __func__);
240178354Ssam		/* XXX stat */
241178354Ssam		return NULL;
242178354Ssam	}
243178354Ssam	ie = (struct ieee80211_country_ie *) aie->ie_data;
244170530Ssam	ie->ie = IEEE80211_ELEMID_COUNTRY;
245178354Ssam	if (rd->isocc[0] == '\0') {
246178354Ssam		if_printf(ic->ic_ifp, "no ISO country string for cc %d; "
247178354Ssam			"using blanks\n", rd->country);
248178354Ssam		ie->cc[0] = ie->cc[1] = ' ';
249178354Ssam	} else {
250178354Ssam		ie->cc[0] = rd->isocc[0];
251178354Ssam		ie->cc[1] = rd->isocc[1];
252170530Ssam	}
253170530Ssam	/*
254178354Ssam	 * Indoor/Outdoor portion of country string:
255170530Ssam	 *     'I' indoor only
256170530Ssam	 *     'O' outdoor only
257170530Ssam	 *     ' ' all enviroments
258170530Ssam	 */
259178354Ssam	ie->cc[2] = (rd->location == 'I' ? 'I' :
260178354Ssam		     rd->location == 'O' ? 'O' : ' ');
261170530Ssam	/*
262170530Ssam	 * Run-length encoded channel+max tx power info.
263170530Ssam	 */
264170530Ssam	frm = (uint8_t *)&ie->band[0];
265170530Ssam	nextchan = 0;			/* NB: impossible channel # */
266178354Ssam	nruns = 0;
267170530Ssam	memset(chans, 0, sizeof(chans));
268178354Ssam	skip = skipflags[ieee80211_chan2mode(ic->ic_bsschan)];
269170530Ssam	for (i = 0; i < ic->ic_nchans; i++) {
270170530Ssam		const struct ieee80211_channel *c = &ic->ic_channels[i];
271170530Ssam
272170530Ssam		if (isset(chans, c->ic_ieee))		/* suppress dup's */
273170530Ssam			continue;
274172204Ssam		if (c->ic_flags & skip)			/* skip band, etc. */
275170530Ssam			continue;
276170530Ssam		setbit(chans, c->ic_ieee);
277170530Ssam		if (c->ic_ieee != nextchan ||
278170530Ssam		    c->ic_maxregpower != frm[-1]) {	/* new run */
279178354Ssam			if (nruns == IEEE80211_COUNTRY_MAX_BANDS) {
280178354Ssam				if_printf(ic->ic_ifp, "%s: country ie too big, "
281178354Ssam				    "runs > max %d, truncating\n",
282178354Ssam				    __func__, IEEE80211_COUNTRY_MAX_BANDS);
283178354Ssam				/* XXX stat? fail? */
284178354Ssam				break;
285178354Ssam			}
286170530Ssam			frm[0] = c->ic_ieee;		/* starting channel # */
287170530Ssam			frm[1] = 1;			/* # channels in run */
288170530Ssam			frm[2] = c->ic_maxregpower;	/* tx power cap */
289170530Ssam			frm += 3;
290170530Ssam			nextchan = c->ic_ieee + 1;	/* overflow? */
291178354Ssam			nruns++;
292170530Ssam		} else {				/* extend run */
293170530Ssam			frm[-2]++;
294170530Ssam			nextchan++;
295170530Ssam		}
296170530Ssam	}
297170530Ssam	ie->len = frm - ie->cc;
298171985Ssephe	if (ie->len & 1) {		/* Zero pad to multiple of 2 */
299170530Ssam		ie->len++;
300171985Ssephe		*frm++ = 0;
301171985Ssephe	}
302178354Ssam	aie->ie_len = frm - aie->ie_data;
303178354Ssam
304178354Ssam	return aie;
305170530Ssam#undef CHAN_UNINTERESTING
306170530Ssam}
307170530Ssam
308178354Ssamstatic int
309178354Ssamallvapsdown(struct ieee80211com *ic)
310170530Ssam{
311178354Ssam	struct ieee80211vap *vap;
312170530Ssam
313178354Ssam	IEEE80211_LOCK_ASSERT(ic);
314178354Ssam	TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next)
315178354Ssam		if (vap->iv_state != IEEE80211_S_INIT)
316178354Ssam			return 0;
317178354Ssam	return 1;
318170530Ssam}
319170530Ssam
320170530Ssamint
321178354Ssamieee80211_setregdomain(struct ieee80211vap *vap,
322178354Ssam    struct ieee80211_regdomain_req *reg)
323170530Ssam{
324178354Ssam	struct ieee80211com *ic = vap->iv_ic;
325178354Ssam	struct ieee80211_channel *c;
326178354Ssam	int desfreq = 0, desflags = 0;		/* XXX silence gcc complaint */
327178354Ssam	int error, i;
328170530Ssam
329178354Ssam	if (reg->rd.location != 'I' && reg->rd.location != 'O' &&
330178354Ssam	    reg->rd.location != ' ')
331178354Ssam		return EINVAL;
332178354Ssam	if (reg->rd.isocc[0] == '\0' || reg->rd.isocc[1] == '\0')
333178354Ssam		return EINVAL;
334178354Ssam	if (reg->chaninfo.ic_nchans >= IEEE80211_CHAN_MAX)
335178354Ssam		return EINVAL;
336178354Ssam	/*
337178354Ssam	 * Calculate freq<->IEEE mapping and default max tx power
338178354Ssam	 * for channels not setup.  The driver can override these
339178354Ssam	 * setting to reflect device properties/requirements.
340178354Ssam	 */
341178354Ssam	for (i = 0; i < reg->chaninfo.ic_nchans; i++) {
342178354Ssam		c = &reg->chaninfo.ic_chans[i];
343178354Ssam		if (c->ic_freq == 0 || c->ic_flags == 0)
344178354Ssam			return EINVAL;
345178354Ssam		if (c->ic_maxregpower == 0)
346178354Ssam			return EINVAL;
347178354Ssam		if (c->ic_ieee == 0)
348178354Ssam			c->ic_ieee = ieee80211_mhz2ieee(c->ic_freq,c->ic_flags);
349178354Ssam		if (IEEE80211_IS_CHAN_HT40(c) && c->ic_extieee == 0)
350178354Ssam			c->ic_extieee = ieee80211_mhz2ieee(c->ic_freq +
351178354Ssam			    (IEEE80211_IS_CHAN_HT40U(c) ? 20 : -20),
352178354Ssam			    c->ic_flags);
353178354Ssam		if (c->ic_maxpower == 0)
354178354Ssam			c->ic_maxpower = 2*c->ic_maxregpower;
355170530Ssam	}
356178354Ssam	IEEE80211_LOCK(ic);
357178354Ssam	error = ic->ic_setregdomain(ic, &reg->rd,
358178354Ssam	    reg->chaninfo.ic_nchans, reg->chaninfo.ic_chans);
359178354Ssam	if (error != 0) {
360178354Ssam		IEEE80211_UNLOCK(ic);
361178354Ssam		return error;
362178354Ssam	}
363178354Ssam	/* XXX bandaid; a running vap will likely crash */
364178354Ssam	if (!allvapsdown(ic)) {
365178354Ssam		IEEE80211_UNLOCK(ic);
366178354Ssam		return EBUSY;
367178354Ssam	}
368178354Ssam	/*
369178354Ssam	 * Commit: copy in new channel table and reset media state.
370178354Ssam	 * On return the state machines will be clocked so all vaps
371178354Ssam	 * will reset their state.
372178354Ssam	 *
373178354Ssam	 * XXX ic_bsschan is marked undefined, must have vap's in
374178354Ssam	 *     INIT state or we blow up forcing stations off
375178354Ssam	 */
376178354Ssam	/*
377178354Ssam	 * Save any desired channel for restore below.  Note this
378178354Ssam	 * needs to be done for all vaps but for now we only do
379178354Ssam	 * the one where the ioctl is issued.
380178354Ssam	 */
381178354Ssam	if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) {
382178354Ssam		desfreq = vap->iv_des_chan->ic_freq;
383178354Ssam		desflags = vap->iv_des_chan->ic_flags;
384178354Ssam	}
385178354Ssam	/* regdomain parameters */
386178354Ssam	memcpy(&ic->ic_regdomain, &reg->rd, sizeof(reg->rd));
387178354Ssam	/* channel table */
388178354Ssam	memcpy(ic->ic_channels, reg->chaninfo.ic_chans,
389178354Ssam	    reg->chaninfo.ic_nchans * sizeof(struct ieee80211_channel));
390178354Ssam	ic->ic_nchans = reg->chaninfo.ic_nchans;
391178354Ssam	memset(&ic->ic_channels[ic->ic_nchans], 0,
392178354Ssam	    (IEEE80211_CHAN_MAX - ic->ic_nchans) *
393178354Ssam	       sizeof(struct ieee80211_channel));
394178354Ssam	ieee80211_media_init(ic);
395178354Ssam
396178354Ssam	/*
397178354Ssam	 * Invalidate channel-related state.
398178354Ssam	 */
399178354Ssam	if (ic->ic_countryie != NULL) {
400178354Ssam		free(ic->ic_countryie, M_80211_NODE_IE);
401178354Ssam		ic->ic_countryie = NULL;
402178354Ssam	}
403178354Ssam	ieee80211_scan_flush(vap);
404178354Ssam	ieee80211_dfs_reset(ic);
405178354Ssam	if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) {
406178354Ssam		/* NB: may be NULL if not present in new channel list */
407178354Ssam		vap->iv_des_chan = ieee80211_find_channel(ic, desfreq, desflags);
408178354Ssam	}
409178354Ssam	IEEE80211_UNLOCK(ic);
410178354Ssam
411178354Ssam	return 0;
412170530Ssam}
413