2007-10-04 17:07:51 +04:00
|
|
|
/*-
|
|
|
|
* Copyright (c) 2001 Atsushi Onoe
|
|
|
|
* Copyright (c) 2002-2005 Sam Leffler, Errno Consulting
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
|
|
* derived from this software without specific prior written permission.
|
|
|
|
*
|
|
|
|
* Alternatively, this software may be distributed under the terms of the
|
|
|
|
* GNU General Public License ("GPL") version 2 as published by the Free
|
|
|
|
* Software Foundation.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*
|
|
|
|
* $Id$
|
|
|
|
*/
|
|
|
|
#ifndef EXPORT_SYMTAB
|
|
|
|
#define EXPORT_SYMTAB
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* IEEE 802.11 generic handler
|
|
|
|
*/
|
2011-02-03 00:10:53 +03:00
|
|
|
#if !defined(AUTOCONF_INCLUDED) && !defined(CONFIG_LOCALVERSION)
|
2007-10-04 17:07:51 +04:00
|
|
|
#include <linux/config.h>
|
|
|
|
#endif
|
|
|
|
#include <linux/version.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/rtnetlink.h> /* XXX for rtnl_lock */
|
|
|
|
|
|
|
|
#include "if_media.h"
|
|
|
|
|
|
|
|
#include <net80211/ieee80211_var.h>
|
|
|
|
#include <net80211/if_athproto.h>
|
|
|
|
|
|
|
|
const char *ieee80211_phymode_name[] = {
|
|
|
|
"auto", /* IEEE80211_MODE_AUTO */
|
|
|
|
"11a", /* IEEE80211_MODE_11A */
|
|
|
|
"11b", /* IEEE80211_MODE_11B */
|
|
|
|
"11g", /* IEEE80211_MODE_11G */
|
|
|
|
"FH", /* IEEE80211_MODE_FH */
|
|
|
|
"turboA", /* IEEE80211_MODE_TURBO_A */
|
|
|
|
"turboG", /* IEEE80211_MODE_TURBO_G */
|
|
|
|
"staticTurboA", /* IEEE80211_MODE_TURBO_G */
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL(ieee80211_phymode_name);
|
|
|
|
|
2008-06-07 00:32:29 +04:00
|
|
|
const char *ieee80211_opmode_name[] = {
|
2008-04-10 01:21:31 +04:00
|
|
|
"ibss", /* IEEE80211_M_IBSS = 0 - IBSS (adhoc) station */
|
|
|
|
"station", /* IEEE80211_M_STA = 1 - infrastructure station */
|
|
|
|
"wds", /* IEEE80211_M_WDS = 2 - WDS link */
|
|
|
|
"ahdemo", /* IEEE80211_M_AHDEMO = 3 - Old lucent compatible adhoc demo */
|
|
|
|
"opmode4", /* invalid = 4 - invalid */
|
|
|
|
"opmode5", /* invalid = 5 - invalid */
|
|
|
|
"hostap", /* IEEE80211_M_HOSTAP = 6 - Software Access Point */
|
|
|
|
"opmode7", /* invalid = 7 - invalid */
|
|
|
|
"monitor" /* IEEE80211_M_MONITOR = 8 - Monitor mode */
|
|
|
|
};
|
|
|
|
EXPORT_SYMBOL(ieee80211_opmode_name);
|
|
|
|
|
|
|
|
|
2008-06-07 00:32:29 +04:00
|
|
|
static void ieee80211com_media_status(struct net_device *, struct ifmediareq *);
|
2007-10-04 17:07:51 +04:00
|
|
|
static int ieee80211com_media_change(struct net_device *);
|
|
|
|
static struct net_device_stats *ieee80211_getstats(struct net_device *);
|
|
|
|
static int ieee80211_change_mtu(struct net_device *, int);
|
|
|
|
static void ieee80211_set_multicast_list(struct net_device *);
|
2008-01-28 04:36:25 +03:00
|
|
|
static void ieee80211_expire_dfs_excl_timer(unsigned long);
|
2007-10-04 17:07:51 +04:00
|
|
|
|
|
|
|
MALLOC_DEFINE(M_80211_VAP, "80211vap", "802.11 vap state");
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Country Code Table for code-to-string conversion.
|
|
|
|
*/
|
|
|
|
struct country_code_to_string {
|
|
|
|
u_int16_t iso_code;
|
|
|
|
const char *iso_name;
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX: Ugly, since these must match defines in other modules.
|
|
|
|
*/
|
|
|
|
#define CTRY_DEBUG 0x1ff /* debug */
|
|
|
|
#define CTRY_DEFAULT 0 /* default */
|
|
|
|
|
|
|
|
static const struct country_code_to_string country_strings[] = {
|
|
|
|
{CTRY_DEBUG, "DB"},
|
|
|
|
{CTRY_DEFAULT, "NA"},
|
|
|
|
{CTRY_ALBANIA, "AL"},
|
|
|
|
{CTRY_ALGERIA, "DZ"},
|
|
|
|
{CTRY_ARGENTINA, "AR"},
|
|
|
|
{CTRY_ARMENIA, "AM"},
|
|
|
|
{CTRY_AUSTRALIA, "AU"},
|
|
|
|
{CTRY_AUSTRIA, "AT"},
|
|
|
|
{CTRY_AZERBAIJAN, "AZ"},
|
|
|
|
{CTRY_BAHRAIN, "BH"},
|
|
|
|
{CTRY_BELARUS, "BY"},
|
|
|
|
{CTRY_BELGIUM, "BE"},
|
|
|
|
{CTRY_BELIZE, "BZ"},
|
|
|
|
{CTRY_BOLIVIA, "BO"},
|
|
|
|
{CTRY_BRAZIL, "BR"},
|
|
|
|
{CTRY_BRUNEI_DARUSSALAM, "BN"},
|
|
|
|
{CTRY_BULGARIA, "BG"},
|
|
|
|
{CTRY_CANADA, "CA"},
|
|
|
|
{CTRY_CHILE, "CL"},
|
|
|
|
{CTRY_CHINA, "CN"},
|
|
|
|
{CTRY_COLOMBIA, "CO"},
|
|
|
|
{CTRY_COSTA_RICA, "CR"},
|
|
|
|
{CTRY_CROATIA, "HR"},
|
|
|
|
{CTRY_CYPRUS, "CY"},
|
|
|
|
{CTRY_CZECH, "CZ"},
|
|
|
|
{CTRY_DENMARK, "DK"},
|
|
|
|
{CTRY_DOMINICAN_REPUBLIC, "DO"},
|
|
|
|
{CTRY_ECUADOR, "EC"},
|
|
|
|
{CTRY_EGYPT, "EG"},
|
|
|
|
{CTRY_EL_SALVADOR, "SV"},
|
|
|
|
{CTRY_ESTONIA, "EE"},
|
|
|
|
{CTRY_FINLAND, "FI"},
|
|
|
|
{CTRY_FRANCE, "FR"},
|
|
|
|
{CTRY_FRANCE2, "F2"},
|
|
|
|
{CTRY_GEORGIA, "GE"},
|
|
|
|
{CTRY_GERMANY, "DE"},
|
|
|
|
{CTRY_GREECE, "GR"},
|
|
|
|
{CTRY_GUATEMALA, "GT"},
|
|
|
|
{CTRY_HONDURAS, "HN"},
|
|
|
|
{CTRY_HONG_KONG, "HK"},
|
|
|
|
{CTRY_HUNGARY, "HU"},
|
|
|
|
{CTRY_ICELAND, "IS"},
|
|
|
|
{CTRY_INDIA, "IN"},
|
|
|
|
{CTRY_INDONESIA, "ID"},
|
|
|
|
{CTRY_IRAN, "IR"},
|
|
|
|
{CTRY_IRELAND, "IE"},
|
|
|
|
{CTRY_ISRAEL, "IL"},
|
|
|
|
{CTRY_ITALY, "IT"},
|
|
|
|
{CTRY_JAPAN, "JP"},
|
|
|
|
{CTRY_JAPAN1, "J1"},
|
|
|
|
{CTRY_JAPAN2, "J2"},
|
|
|
|
{CTRY_JAPAN3, "J3"},
|
|
|
|
{CTRY_JAPAN4, "J4"},
|
|
|
|
{CTRY_JAPAN5, "J5"},
|
|
|
|
{CTRY_JAPAN7, "JP"},
|
|
|
|
{CTRY_JAPAN6, "JP"},
|
|
|
|
{CTRY_JAPAN8, "JP"},
|
|
|
|
{CTRY_JAPAN9, "JP"},
|
|
|
|
{CTRY_JAPAN10, "JP"},
|
|
|
|
{CTRY_JAPAN11, "JP"},
|
|
|
|
{CTRY_JAPAN12, "JP"},
|
|
|
|
{CTRY_JAPAN13, "JP"},
|
|
|
|
{CTRY_JAPAN14, "JP"},
|
|
|
|
{CTRY_JAPAN15, "JP"},
|
|
|
|
{CTRY_JAPAN16, "JP"},
|
|
|
|
{CTRY_JAPAN17, "JP"},
|
|
|
|
{CTRY_JAPAN18, "JP"},
|
|
|
|
{CTRY_JAPAN19, "JP"},
|
|
|
|
{CTRY_JAPAN20, "JP"},
|
|
|
|
{CTRY_JAPAN21, "JP"},
|
|
|
|
{CTRY_JAPAN22, "JP"},
|
|
|
|
{CTRY_JAPAN23, "JP"},
|
|
|
|
{CTRY_JAPAN24, "JP"},
|
|
|
|
{CTRY_JAPAN25, "JP"},
|
|
|
|
{CTRY_JAPAN26, "JP"},
|
|
|
|
{CTRY_JAPAN27, "JP"},
|
|
|
|
{CTRY_JAPAN28, "JP"},
|
|
|
|
{CTRY_JAPAN29, "JP"},
|
|
|
|
{CTRY_JAPAN30, "JP"},
|
|
|
|
{CTRY_JAPAN31, "JP"},
|
|
|
|
{CTRY_JAPAN32, "JP"},
|
|
|
|
{CTRY_JAPAN33, "JP"},
|
|
|
|
{CTRY_JAPAN34, "JP"},
|
|
|
|
{CTRY_JAPAN35, "JP"},
|
|
|
|
{CTRY_JAPAN36, "JP"},
|
|
|
|
{CTRY_JAPAN37, "JP"},
|
|
|
|
{CTRY_JAPAN38, "JP"},
|
|
|
|
{CTRY_JAPAN39, "JP"},
|
|
|
|
{CTRY_JAPAN40, "JP"},
|
|
|
|
{CTRY_JAPAN41, "JP"},
|
|
|
|
{CTRY_JAPAN42, "JP"},
|
|
|
|
{CTRY_JAPAN43, "JP"},
|
|
|
|
{CTRY_JAPAN44, "JP"},
|
|
|
|
{CTRY_JAPAN45, "JP"},
|
|
|
|
{CTRY_JAPAN46, "JP"},
|
|
|
|
{CTRY_JAPAN47, "JP"},
|
|
|
|
{CTRY_JAPAN48, "JP"},
|
|
|
|
{CTRY_JORDAN, "JO"},
|
|
|
|
{CTRY_KAZAKHSTAN, "KZ"},
|
|
|
|
{CTRY_KOREA_NORTH, "KP"},
|
|
|
|
{CTRY_KOREA_ROC, "KR"},
|
|
|
|
{CTRY_KOREA_ROC2, "K2"},
|
|
|
|
{CTRY_KUWAIT, "KW"},
|
|
|
|
{CTRY_LATVIA, "LV"},
|
|
|
|
{CTRY_LEBANON, "LB"},
|
|
|
|
{CTRY_LIECHTENSTEIN, "LI"},
|
|
|
|
{CTRY_LITHUANIA, "LT"},
|
|
|
|
{CTRY_LUXEMBOURG, "LU"},
|
|
|
|
{CTRY_MACAU, "MO"},
|
|
|
|
{CTRY_MACEDONIA, "MK"},
|
|
|
|
{CTRY_MALAYSIA, "MY"},
|
|
|
|
{CTRY_MEXICO, "MX"},
|
|
|
|
{CTRY_MONACO, "MC"},
|
|
|
|
{CTRY_MOROCCO, "MA"},
|
|
|
|
{CTRY_NETHERLANDS, "NL"},
|
|
|
|
{CTRY_NEW_ZEALAND, "NZ"},
|
|
|
|
{CTRY_NORWAY, "NO"},
|
|
|
|
{CTRY_OMAN, "OM"},
|
|
|
|
{CTRY_PAKISTAN, "PK"},
|
|
|
|
{CTRY_PANAMA, "PA"},
|
|
|
|
{CTRY_PERU, "PE"},
|
|
|
|
{CTRY_PHILIPPINES, "PH"},
|
|
|
|
{CTRY_POLAND, "PL"},
|
|
|
|
{CTRY_PORTUGAL, "PT"},
|
|
|
|
{CTRY_PUERTO_RICO, "PR"},
|
|
|
|
{CTRY_QATAR, "QA"},
|
|
|
|
{CTRY_ROMANIA, "RO"},
|
|
|
|
{CTRY_RUSSIA, "RU"},
|
|
|
|
{CTRY_SAUDI_ARABIA, "SA"},
|
|
|
|
{CTRY_SINGAPORE, "SG"},
|
|
|
|
{CTRY_SLOVAKIA, "SK"},
|
|
|
|
{CTRY_SLOVENIA, "SI"},
|
|
|
|
{CTRY_SOUTH_AFRICA, "ZA"},
|
|
|
|
{CTRY_SPAIN, "ES"},
|
|
|
|
{CTRY_SWEDEN, "SE"},
|
|
|
|
{CTRY_SWITZERLAND, "CH"},
|
|
|
|
{CTRY_SYRIA, "SY"},
|
|
|
|
{CTRY_TAIWAN, "TW"},
|
|
|
|
{CTRY_THAILAND, "TH"},
|
|
|
|
{CTRY_TRINIDAD_Y_TOBAGO, "TT"},
|
|
|
|
{CTRY_TUNISIA, "TN"},
|
|
|
|
{CTRY_TURKEY, "TR"},
|
|
|
|
{CTRY_UKRAINE, "UA"},
|
|
|
|
{CTRY_UAE, "AE"},
|
|
|
|
{CTRY_UNITED_KINGDOM, "GB"},
|
|
|
|
{CTRY_UNITED_STATES, "US"},
|
|
|
|
{CTRY_UNITED_STATES_FCC49, "US"},
|
|
|
|
{CTRY_URUGUAY, "UY"},
|
|
|
|
{CTRY_UZBEKISTAN, "UZ"},
|
|
|
|
{CTRY_VENEZUELA, "VE"},
|
|
|
|
{CTRY_VIET_NAM, "VN"},
|
|
|
|
{CTRY_YEMEN, "YE"},
|
|
|
|
{CTRY_ZIMBABWE, "ZW"}
|
|
|
|
};
|
|
|
|
|
|
|
|
int
|
|
|
|
ieee80211_ifattach(struct ieee80211com *ic)
|
|
|
|
{
|
|
|
|
struct net_device *dev = ic->ic_dev;
|
|
|
|
struct ieee80211_channel *c;
|
|
|
|
struct ifmediareq imr;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
_MOD_INC_USE(THIS_MODULE, return -ENODEV);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Pick an initial operating mode until we have a vap
|
|
|
|
* created to lock it down correctly. This is only
|
|
|
|
* drivers have something defined for configuring the
|
|
|
|
* hardware at startup.
|
|
|
|
*/
|
|
|
|
ic->ic_opmode = IEEE80211_M_STA; /* everyone supports this */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fill in 802.11 available channel set, mark
|
|
|
|
* all available channels as active, and pick
|
|
|
|
* a default channel if not already specified.
|
|
|
|
*/
|
|
|
|
KASSERT(0 < ic->ic_nchans && ic->ic_nchans < IEEE80211_CHAN_MAX,
|
|
|
|
("invalid number of channels specified: %u", ic->ic_nchans));
|
|
|
|
memset(ic->ic_chan_avail, 0, sizeof(ic->ic_chan_avail));
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_AUTO;
|
|
|
|
|
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
|
|
|
KASSERT(c->ic_flags != 0, ("channel with no flags"));
|
|
|
|
KASSERT(c->ic_ieee < IEEE80211_CHAN_MAX,
|
|
|
|
("channel with bogus ieee number %u", c->ic_ieee));
|
|
|
|
setbit(ic->ic_chan_avail, c->ic_ieee);
|
|
|
|
|
|
|
|
/* Identify mode capabilities. */
|
|
|
|
if (IEEE80211_IS_CHAN_A(c))
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_11A;
|
|
|
|
if (IEEE80211_IS_CHAN_B(c))
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_11B;
|
|
|
|
if (IEEE80211_IS_CHAN_PUREG(c))
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_11G;
|
|
|
|
if (IEEE80211_IS_CHAN_FHSS(c))
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_FH;
|
|
|
|
if (IEEE80211_IS_CHAN_108A(c))
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_TURBO_A;
|
|
|
|
if (IEEE80211_IS_CHAN_108G(c))
|
|
|
|
ic->ic_modecaps |= 1 << IEEE80211_MODE_TURBO_G;
|
|
|
|
}
|
|
|
|
/* Initialize candidate channels to all available */
|
|
|
|
memcpy(ic->ic_chan_active, ic->ic_chan_avail,
|
|
|
|
sizeof(ic->ic_chan_avail));
|
2008-01-26 23:48:11 +03:00
|
|
|
/* update Supported Channels information element */
|
|
|
|
ieee80211_build_sc_ie(ic);
|
2007-10-04 17:07:51 +04:00
|
|
|
/* Validate ic->ic_curmode */
|
|
|
|
if ((ic->ic_modecaps & (1 << ic->ic_curmode)) == 0)
|
|
|
|
ic->ic_curmode = IEEE80211_MODE_AUTO;
|
|
|
|
/*
|
|
|
|
* When 11g is supported, force the rate set to
|
|
|
|
* include basic rates suitable for a mixed b/g bss.
|
|
|
|
*/
|
|
|
|
if (ic->ic_modecaps & (1 << IEEE80211_MODE_11G))
|
|
|
|
ieee80211_set11gbasicrates(
|
|
|
|
&ic->ic_sup_rates[IEEE80211_MODE_11G],
|
|
|
|
IEEE80211_MODE_11G);
|
|
|
|
|
|
|
|
/* Setup initial channel settings */
|
|
|
|
ic->ic_bsschan = IEEE80211_CHAN_ANYC;
|
|
|
|
/* Arbitrarily pick the first channel */
|
|
|
|
ic->ic_curchan = &ic->ic_channels[0];
|
|
|
|
|
|
|
|
/* Enable marking of dfs by default */
|
|
|
|
ic->ic_flags_ext |= IEEE80211_FEXT_MARKDFS;
|
|
|
|
|
|
|
|
/* Enable WME by default, if we're capable. */
|
|
|
|
if (ic->ic_caps & IEEE80211_C_WME)
|
|
|
|
ic->ic_flags |= IEEE80211_F_WME;
|
|
|
|
(void) ieee80211_setmode(ic, ic->ic_curmode);
|
|
|
|
|
2008-01-31 02:50:16 +03:00
|
|
|
/* Store default beacon interval, as nec. */
|
2007-10-04 17:07:51 +04:00
|
|
|
if (ic->ic_lintval == 0)
|
|
|
|
ic->ic_lintval = IEEE80211_BINTVAL_DEFAULT;
|
2008-01-31 02:50:16 +03:00
|
|
|
|
|
|
|
/* We store the beacon miss threshold in integral number of beacons,
|
|
|
|
* to keep the calculations on the critical path simple. */
|
|
|
|
if (ic->ic_bmissthreshold == 0) {
|
2008-04-10 01:21:31 +04:00
|
|
|
ic->ic_bmissthreshold = howmany(roundup(
|
|
|
|
IEEE80211_MS_TO_TU(IEEE80211_BMISSTHRESH_DEFAULT_MS),
|
|
|
|
ic->ic_lintval), ic->ic_lintval);
|
2008-01-31 02:50:16 +03:00
|
|
|
}
|
|
|
|
|
2007-10-04 17:07:51 +04:00
|
|
|
IEEE80211_LOCK_INIT(ic, "ieee80211com");
|
|
|
|
IEEE80211_VAPS_LOCK_INIT(ic, "ieee80211com_vaps");
|
|
|
|
TAILQ_INIT(&ic->ic_vaps);
|
|
|
|
|
|
|
|
ic->ic_txpowlimit = IEEE80211_TXPOWER_MAX;
|
|
|
|
ic->ic_txpowlimit = IEEE80211_TXPOWER_MIN;
|
|
|
|
ic->ic_newtxpowlimit = IEEE80211_TXPOWER_MAX;
|
|
|
|
|
2008-01-28 04:36:25 +03:00
|
|
|
init_timer(&ic->ic_dfs_excl_timer);
|
|
|
|
ic->ic_dfs_excl_timer.function =
|
|
|
|
ieee80211_expire_dfs_excl_timer;
|
|
|
|
ic->ic_dfs_excl_timer.data = (unsigned long) ic;
|
2008-01-26 23:48:11 +03:00
|
|
|
|
2009-07-11 21:20:58 +04:00
|
|
|
/* Initialize CSA related variables. */
|
|
|
|
init_timer(&ic->ic_csa_timer);
|
|
|
|
ic->ic_csa_timer.data = (unsigned long)ic;
|
|
|
|
ic->ic_csa_timer.function = ieee80211_doth_switch_channel_tmr;
|
|
|
|
|
2007-10-04 17:07:51 +04:00
|
|
|
ieee80211_crypto_attach(ic);
|
|
|
|
ieee80211_node_attach(ic);
|
|
|
|
ieee80211_power_attach(ic);
|
|
|
|
ieee80211_proto_attach(ic);
|
|
|
|
ieee80211_scan_attach(ic);
|
|
|
|
|
|
|
|
ieee80211_media_setup(ic, &ic->ic_media, ic->ic_caps,
|
|
|
|
ieee80211com_media_change, ieee80211com_media_status);
|
|
|
|
ieee80211com_media_status(dev, &imr);
|
|
|
|
ifmedia_set(&ic->ic_media, imr.ifm_active);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_ifattach);
|
|
|
|
|
|
|
|
void
|
|
|
|
ieee80211_ifdetach(struct ieee80211com *ic)
|
|
|
|
{
|
|
|
|
struct ieee80211vap *vap;
|
|
|
|
|
|
|
|
rtnl_lock();
|
|
|
|
while ((vap = TAILQ_FIRST(&ic->ic_vaps)) != NULL)
|
|
|
|
ic->ic_vap_delete(vap);
|
|
|
|
rtnl_unlock();
|
|
|
|
|
2008-01-28 04:36:25 +03:00
|
|
|
del_timer(&ic->ic_dfs_excl_timer);
|
2007-10-04 17:07:51 +04:00
|
|
|
ieee80211_scan_detach(ic);
|
|
|
|
ieee80211_proto_detach(ic);
|
|
|
|
ieee80211_crypto_detach(ic);
|
|
|
|
ieee80211_power_detach(ic);
|
|
|
|
ieee80211_node_detach(ic);
|
|
|
|
ifmedia_removeall(&ic->ic_media);
|
|
|
|
|
|
|
|
IEEE80211_VAPS_LOCK_DESTROY(ic);
|
|
|
|
IEEE80211_LOCK_DESTROY(ic);
|
|
|
|
|
|
|
|
_MOD_DEC_USE(THIS_MODULE);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_ifdetach);
|
|
|
|
|
2009-04-15 08:21:12 +04:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
|
|
|
|
static const struct net_device_ops ieee80211_netdev_ops = {
|
|
|
|
.ndo_get_stats = ieee80211_getstats,
|
|
|
|
.ndo_open = ieee80211_open,
|
|
|
|
.ndo_stop = ieee80211_stop,
|
|
|
|
.ndo_start_xmit = ieee80211_hardstart,
|
2011-11-22 21:22:34 +04:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
|
|
|
|
.ndo_set_rx_mode = ieee80211_set_multicast_list,
|
|
|
|
#else
|
2009-04-15 08:21:12 +04:00
|
|
|
.ndo_set_multicast_list = ieee80211_set_multicast_list,
|
2011-11-22 21:22:34 +04:00
|
|
|
#endif
|
2009-04-15 08:21:12 +04:00
|
|
|
.ndo_change_mtu = ieee80211_change_mtu,
|
|
|
|
.ndo_do_ioctl = ieee80211_ioctl,
|
2009-06-26 02:22:58 +04:00
|
|
|
#if IEEE80211_VLAN_TAG_USED
|
|
|
|
.ndo_vlan_rx_register = ieee80211_vlan_register,
|
|
|
|
.ndo_vlan_rx_add_vid = ieee80211_vlan_add_vid,
|
|
|
|
.ndo_vlan_rx_kill_vid = ieee80211_vlan_kill_vid,
|
|
|
|
#endif
|
2009-04-15 08:21:12 +04:00
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2007-10-04 17:07:51 +04:00
|
|
|
int
|
|
|
|
ieee80211_vap_setup(struct ieee80211com *ic, struct net_device *dev,
|
|
|
|
const char *name, int opmode, int flags)
|
|
|
|
{
|
|
|
|
#define IEEE80211_C_OPMODE \
|
|
|
|
(IEEE80211_C_IBSS | IEEE80211_C_HOSTAP | IEEE80211_C_AHDEMO | \
|
|
|
|
IEEE80211_C_MONITOR)
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211vap *vap = netdev_priv(dev);
|
2007-10-04 17:07:51 +04:00
|
|
|
struct net_device *parent = ic->ic_dev;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (name != NULL) { /* XXX */
|
|
|
|
if (strchr(name, '%')) {
|
|
|
|
if ((err = dev_alloc_name(dev, name)) < 0) {
|
|
|
|
printk(KERN_ERR "can't alloc name %s\n", name);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
strncpy(dev->name, name, sizeof(dev->name));
|
|
|
|
}
|
|
|
|
|
2009-04-15 08:21:12 +04:00
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
|
2007-10-04 17:07:51 +04:00
|
|
|
dev->get_stats = ieee80211_getstats;
|
|
|
|
dev->open = ieee80211_open;
|
|
|
|
dev->stop = ieee80211_stop;
|
|
|
|
dev->hard_start_xmit = ieee80211_hardstart;
|
|
|
|
dev->set_multicast_list = ieee80211_set_multicast_list;
|
|
|
|
dev->change_mtu = ieee80211_change_mtu;
|
2009-04-15 07:11:34 +04:00
|
|
|
dev->do_ioctl = ieee80211_ioctl;
|
2009-06-26 02:22:58 +04:00
|
|
|
#if IEEE80211_VLAN_TAG_USED
|
|
|
|
dev->vlan_rx_register = ieee80211_vlan_register;
|
|
|
|
dev->vlan_rx_add_vid = ieee80211_vlan_add_vid;
|
|
|
|
dev->vlan_rx_kill_vid = ieee80211_vlan_kill_vid;
|
|
|
|
#endif
|
2009-04-15 08:21:12 +04:00
|
|
|
#else
|
|
|
|
dev->netdev_ops = &ieee80211_netdev_ops;
|
|
|
|
#endif
|
2007-10-04 17:07:51 +04:00
|
|
|
dev->tx_queue_len = 0; /* NB: bypass queuing */
|
|
|
|
dev->hard_header_len = parent->hard_header_len;
|
|
|
|
/*
|
|
|
|
* The caller is assumed to allocate the device with
|
|
|
|
* alloc_etherdev or similar so we arrange for the
|
|
|
|
* space to be reclaimed accordingly.
|
|
|
|
*/
|
|
|
|
dev->destructor = free_netdev;
|
|
|
|
|
|
|
|
vap->iv_ic = ic;
|
|
|
|
vap->iv_dev = dev; /* back pointer */
|
|
|
|
vap->iv_xrvap = NULL;
|
|
|
|
vap->iv_flags = ic->ic_flags; /* propagate common flags */
|
|
|
|
vap->iv_flags_ext = ic->ic_flags_ext;
|
|
|
|
vap->iv_ath_cap = ic->ic_ath_cap;
|
|
|
|
vap->iv_mcast_rate = 1000; /* Default multicast traffic to lowest rate of 1Mbps */
|
|
|
|
|
|
|
|
#ifdef ATH_SUPERG_XR
|
|
|
|
/*
|
|
|
|
* Setup XR VAP specific flags.
|
|
|
|
* link the XR VAP to its normal val.
|
|
|
|
*/
|
|
|
|
if (flags & IEEE80211_VAP_XR) {
|
|
|
|
vap->iv_flags |= IEEE80211_F_XR; /* propagate common flags and add XR flag */
|
|
|
|
vap->iv_mcast_rate = 256; /* Default multicast rate to lowest possible 256 kbps */
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
vap->iv_caps = ic->ic_caps &~ IEEE80211_C_OPMODE;
|
|
|
|
switch (opmode) {
|
|
|
|
case IEEE80211_M_STA:
|
|
|
|
/* WDS/Repeater */
|
2008-04-09 07:24:05 +04:00
|
|
|
if (flags & IEEE80211_USE_SW_BEACON_TIMERS)
|
2007-10-04 17:07:51 +04:00
|
|
|
vap->iv_flags_ext |= IEEE80211_FEXT_SWBMISS;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_IBSS:
|
|
|
|
vap->iv_caps |= IEEE80211_C_IBSS;
|
|
|
|
vap->iv_ath_cap &= ~IEEE80211_ATHC_XR;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_AHDEMO:
|
|
|
|
vap->iv_caps |= IEEE80211_C_AHDEMO;
|
|
|
|
vap->iv_ath_cap &= ~IEEE80211_ATHC_XR;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_HOSTAP:
|
|
|
|
vap->iv_caps |= IEEE80211_C_HOSTAP;
|
|
|
|
vap->iv_ath_cap &= ~IEEE80211_ATHC_TURBOP;
|
|
|
|
if ((vap->iv_flags & IEEE80211_VAP_XR) == 0)
|
|
|
|
vap->iv_ath_cap &= ~IEEE80211_ATHC_XR;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_MONITOR:
|
|
|
|
vap->iv_caps |= IEEE80211_C_MONITOR;
|
|
|
|
vap->iv_ath_cap &= ~(IEEE80211_ATHC_XR | IEEE80211_ATHC_TURBOP);
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_WDS:
|
|
|
|
vap->iv_caps |= IEEE80211_C_WDS;
|
|
|
|
vap->iv_ath_cap &= ~(IEEE80211_ATHC_XR | IEEE80211_ATHC_TURBOP);
|
|
|
|
vap->iv_flags_ext |= IEEE80211_FEXT_WDS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
vap->iv_opmode = opmode;
|
|
|
|
IEEE80211_INIT_TQUEUE(&vap->iv_stajoin1tq, ieee80211_sta_join1_tasklet, vap);
|
|
|
|
|
|
|
|
/* Enable various functionality by default, if we're capable. */
|
|
|
|
#ifdef ATH_WME /* Not yet the default */
|
|
|
|
if (vap->iv_caps & IEEE80211_C_WME)
|
|
|
|
vap->iv_flags |= IEEE80211_F_WME;
|
|
|
|
#endif
|
|
|
|
if (vap->iv_caps & IEEE80211_C_FF)
|
|
|
|
vap->iv_flags |= IEEE80211_F_FF;
|
|
|
|
/* NB: Background scanning only makes sense for station mode right now */
|
|
|
|
if (ic->ic_opmode == IEEE80211_M_STA &&
|
|
|
|
(vap->iv_caps & IEEE80211_C_BGSCAN))
|
|
|
|
vap->iv_flags |= IEEE80211_F_BGSCAN;
|
|
|
|
|
|
|
|
vap->iv_dtim_period = IEEE80211_DTIM_DEFAULT;
|
|
|
|
vap->iv_des_chan = IEEE80211_CHAN_ANYC; /* any channel is OK */
|
|
|
|
|
|
|
|
vap->iv_monitor_crc_errors = 0;
|
|
|
|
vap->iv_monitor_phy_errors = 0;
|
|
|
|
|
|
|
|
IEEE80211_ADDR_COPY(vap->iv_myaddr, ic->ic_myaddr);
|
2008-01-26 23:48:11 +03:00
|
|
|
IEEE80211_ADDR_COPY(vap->iv_bssid, ic->ic_myaddr);
|
2007-10-04 17:07:51 +04:00
|
|
|
/* NB: Defer setting dev_addr so driver can override */
|
|
|
|
|
|
|
|
ieee80211_crypto_vattach(vap);
|
|
|
|
ieee80211_node_vattach(vap);
|
|
|
|
ieee80211_power_vattach(vap);
|
|
|
|
ieee80211_proto_vattach(vap);
|
|
|
|
ieee80211_scan_vattach(vap);
|
|
|
|
ieee80211_vlan_vattach(vap);
|
|
|
|
ieee80211_ioctl_vattach(vap);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
#undef IEEE80211_C_OPMODE
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_vap_setup);
|
|
|
|
|
|
|
|
int
|
|
|
|
ieee80211_vap_attach(struct ieee80211vap *vap,
|
|
|
|
ifm_change_cb_t media_change, ifm_stat_cb_t media_status)
|
|
|
|
{
|
|
|
|
struct net_device *dev = vap->iv_dev;
|
|
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
|
|
struct ifmediareq imr;
|
|
|
|
ieee80211_node_latevattach(vap); /* XXX: move into vattach */
|
|
|
|
ieee80211_power_latevattach(vap); /* XXX: move into vattach */
|
|
|
|
|
|
|
|
memset(vap->wds_mac, 0x00, IEEE80211_ADDR_LEN);
|
|
|
|
|
|
|
|
(void) ieee80211_media_setup(ic, &vap->iv_media, vap->iv_caps,
|
|
|
|
media_change, media_status);
|
|
|
|
ieee80211_media_status(dev, &imr);
|
|
|
|
ifmedia_set(&vap->iv_media, imr.ifm_active);
|
|
|
|
IEEE80211_LOCK_IRQ(ic);
|
|
|
|
TAILQ_INSERT_TAIL(&ic->ic_vaps, vap, iv_next);
|
|
|
|
IEEE80211_UNLOCK_IRQ(ic);
|
|
|
|
|
|
|
|
IEEE80211_ADDR_COPY(dev->dev_addr, vap->iv_myaddr);
|
|
|
|
|
|
|
|
#ifdef ATH_SUPERG_XR
|
|
|
|
/* Do not register XR VAP device with OS. */
|
|
|
|
if (vap->iv_flags & IEEE80211_F_XR)
|
|
|
|
return 0;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
ieee80211_scanner_get(vap->iv_opmode, 1);
|
|
|
|
|
|
|
|
/* NB: rtnl_lock is held on entry, so don't use register_netdev */
|
|
|
|
if (register_netdevice(dev)) {
|
|
|
|
printk(KERN_ERR "%s: unable to register device\n", dev->name);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* SysFS needs to be initialised after the device, as it uses the
|
|
|
|
* device koject */
|
|
|
|
ieee80211_virtfs_latevattach(vap);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_vap_attach);
|
|
|
|
|
|
|
|
void
|
|
|
|
ieee80211_vap_detach(struct ieee80211vap *vap)
|
|
|
|
{
|
|
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
|
|
struct net_device *dev = vap->iv_dev;
|
|
|
|
|
|
|
|
IEEE80211_CANCEL_TQUEUE(&vap->iv_stajoin1tq);
|
|
|
|
IEEE80211_LOCK_IRQ(ic);
|
|
|
|
TAILQ_REMOVE(&ic->ic_vaps, vap, iv_next);
|
|
|
|
if (TAILQ_EMPTY(&ic->ic_vaps)) /* reset to supported mode */
|
|
|
|
ic->ic_opmode = IEEE80211_M_STA;
|
|
|
|
IEEE80211_UNLOCK_IRQ(ic);
|
|
|
|
|
|
|
|
ifmedia_removeall(&vap->iv_media);
|
|
|
|
|
|
|
|
ieee80211_virtfs_vdetach(vap);
|
|
|
|
ieee80211_proc_cleanup(vap);
|
|
|
|
ieee80211_ioctl_vdetach(vap);
|
|
|
|
ieee80211_vlan_vdetach(vap);
|
|
|
|
ieee80211_scan_vdetach(vap);
|
|
|
|
ieee80211_proto_vdetach(vap);
|
|
|
|
ieee80211_crypto_vdetach(vap);
|
|
|
|
ieee80211_power_vdetach(vap);
|
|
|
|
ieee80211_node_vdetach(vap);
|
|
|
|
|
|
|
|
#ifdef ATH_SUPERG_XR
|
|
|
|
/* XR VAP is not registered. */
|
|
|
|
if (!(vap->iv_flags & IEEE80211_F_XR))
|
|
|
|
#endif
|
|
|
|
/* NB: rtnl_lock is held on entry so don't use unregister_netdev */
|
|
|
|
unregister_netdevice(dev);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_vap_detach);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert MHz frequency to IEEE channel number.
|
|
|
|
*/
|
|
|
|
u_int
|
|
|
|
ieee80211_mhz2ieee(u_int freq, u_int flags)
|
|
|
|
{
|
|
|
|
if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */
|
|
|
|
if (freq == 2484) /* Japan */
|
|
|
|
return 14;
|
|
|
|
if ((freq >= 2412) && (freq < 2484)) /* don't number non-IEEE channels */
|
|
|
|
return (freq - 2407) / 5;
|
|
|
|
return 0;
|
|
|
|
} else if (flags & IEEE80211_CHAN_5GHZ) { /* 5Ghz band */
|
|
|
|
if ((freq >= 5150) && (freq <= 5825)) /* don't number non-IEEE channels */
|
|
|
|
return (freq - 5000) / 5;
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
/* Something is fishy, don't do anything */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_mhz2ieee);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert channel to IEEE channel number.
|
|
|
|
*/
|
|
|
|
u_int
|
|
|
|
ieee80211_chan2ieee(struct ieee80211com *ic, const struct ieee80211_channel *c)
|
|
|
|
{
|
|
|
|
if (c == NULL) {
|
|
|
|
if_printf(ic->ic_dev, "invalid channel (NULL)\n");
|
|
|
|
return 0; /* XXX */
|
|
|
|
}
|
|
|
|
return (c == IEEE80211_CHAN_ANYC ? IEEE80211_CHAN_ANY : c->ic_ieee);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_chan2ieee);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert IEEE channel number to MHz frequency.
|
|
|
|
*/
|
|
|
|
u_int
|
|
|
|
ieee80211_ieee2mhz(u_int chan, u_int flags)
|
|
|
|
{
|
|
|
|
if (flags & IEEE80211_CHAN_2GHZ) { /* 2GHz band */
|
|
|
|
if (chan == 14)
|
|
|
|
return 2484;
|
|
|
|
if (chan < 14)
|
|
|
|
return 2407 + chan * 5;
|
|
|
|
else
|
|
|
|
return 2512 + ((chan - 15) * 20);
|
|
|
|
} else if (flags & IEEE80211_CHAN_5GHZ) /* 5Ghz band */
|
|
|
|
return 5000 + (chan * 5);
|
|
|
|
else { /* either, guess */
|
|
|
|
if (chan == 14)
|
|
|
|
return 2484;
|
|
|
|
if (chan < 14) /* 0-13 */
|
|
|
|
return 2407 + chan * 5;
|
|
|
|
if (chan < 27) /* 15-26 */
|
|
|
|
return 2512 + ((chan - 15) * 20);
|
|
|
|
return 5000 + (chan * 5);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_ieee2mhz);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Locate a channel given a frequency+flags. We cache
|
|
|
|
* the previous lookup to optimize swithing between two
|
|
|
|
* channels--as happens with dynamic turbo.
|
|
|
|
*/
|
|
|
|
struct ieee80211_channel *
|
|
|
|
ieee80211_find_channel(struct ieee80211com *ic, int freq, int flags)
|
|
|
|
{
|
|
|
|
struct ieee80211_channel *c;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Brute force search */
|
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
|
|
|
if (c->ic_freq == freq &&
|
2008-01-23 21:53:34 +03:00
|
|
|
(flags == 0 ||
|
2009-06-25 04:49:06 +04:00
|
|
|
(c->ic_flags & IEEE80211_CHAN_ALLTURBO) ==
|
2009-06-25 04:47:42 +04:00
|
|
|
(flags & IEEE80211_CHAN_ALLTURBO)))
|
2007-10-04 17:07:51 +04:00
|
|
|
return c;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_find_channel);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Setup the media data structures according to the channel and
|
|
|
|
* rate tables. This must be called by the driver after
|
|
|
|
* ieee80211_attach and before most anything else.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
ieee80211_media_setup(struct ieee80211com *ic,
|
|
|
|
struct ifmedia *media, u_int32_t caps,
|
|
|
|
ifm_change_cb_t media_change, ifm_stat_cb_t media_stat)
|
|
|
|
{
|
|
|
|
#define ADD(_media, _s, _o) \
|
|
|
|
ifmedia_add(_media, IFM_MAKEWORD(IFM_IEEE80211, (_s), (_o), 0), 0, NULL)
|
|
|
|
int i, j, mode, rate, maxrate, mword, mopt, r;
|
|
|
|
struct ieee80211_rateset *rs;
|
|
|
|
struct ieee80211_rateset allrates;
|
|
|
|
|
|
|
|
/* Fill in media characteristics. */
|
|
|
|
ifmedia_init(media, 0, media_change, media_stat);
|
|
|
|
maxrate = 0;
|
|
|
|
memset(&allrates, 0, sizeof(allrates));
|
|
|
|
|
|
|
|
for (mode = IEEE80211_MODE_AUTO; mode < IEEE80211_MODE_MAX; mode++) {
|
|
|
|
static const u_int mopts[] = {
|
|
|
|
IFM_AUTO,
|
|
|
|
IFM_IEEE80211_11A,
|
|
|
|
IFM_IEEE80211_11B,
|
|
|
|
IFM_IEEE80211_11G,
|
|
|
|
IFM_IEEE80211_FH,
|
|
|
|
IFM_IEEE80211_11A | IFM_IEEE80211_TURBO,
|
|
|
|
IFM_IEEE80211_11G | IFM_IEEE80211_TURBO,
|
|
|
|
};
|
|
|
|
if ((ic->ic_modecaps & (1 << mode)) == 0)
|
|
|
|
continue;
|
|
|
|
mopt = mopts[mode];
|
|
|
|
ADD(media, IFM_AUTO, mopt); /* e.g. 11a auto */
|
|
|
|
if (caps & IEEE80211_C_IBSS)
|
|
|
|
ADD(media, IFM_AUTO, mopt | IFM_IEEE80211_ADHOC);
|
|
|
|
if (caps & IEEE80211_C_HOSTAP)
|
|
|
|
ADD(media, IFM_AUTO, mopt | IFM_IEEE80211_HOSTAP);
|
|
|
|
if (caps & IEEE80211_C_AHDEMO)
|
|
|
|
ADD(media, IFM_AUTO, mopt | IFM_IEEE80211_ADHOC | IFM_FLAG0);
|
|
|
|
if (caps & IEEE80211_C_MONITOR)
|
|
|
|
ADD(media, IFM_AUTO, mopt | IFM_IEEE80211_MONITOR);
|
|
|
|
if (caps & IEEE80211_C_WDS)
|
|
|
|
ADD(media, IFM_AUTO, mopt | IFM_IEEE80211_WDS);
|
|
|
|
if (mode == IEEE80211_MODE_AUTO)
|
|
|
|
continue;
|
|
|
|
rs = &ic->ic_sup_rates[mode];
|
|
|
|
|
|
|
|
for (i = 0; i < rs->rs_nrates; i++) {
|
|
|
|
rate = rs->rs_rates[i];
|
|
|
|
mword = ieee80211_rate2media(ic, rate, mode);
|
|
|
|
if (mword == 0)
|
|
|
|
continue;
|
|
|
|
ADD(media, mword, mopt);
|
|
|
|
if (caps & IEEE80211_C_IBSS)
|
|
|
|
ADD(media, mword, mopt | IFM_IEEE80211_ADHOC);
|
|
|
|
if (caps & IEEE80211_C_HOSTAP)
|
|
|
|
ADD(media, mword, mopt | IFM_IEEE80211_HOSTAP);
|
|
|
|
if (caps & IEEE80211_C_AHDEMO)
|
|
|
|
ADD(media, mword, mopt | IFM_IEEE80211_ADHOC | IFM_FLAG0);
|
|
|
|
if (caps & IEEE80211_C_MONITOR)
|
|
|
|
ADD(media, mword, mopt | IFM_IEEE80211_MONITOR);
|
|
|
|
if (caps & IEEE80211_C_WDS)
|
|
|
|
ADD(media, mword, mopt | IFM_IEEE80211_WDS);
|
|
|
|
/* Add rate to the collection of all rates. */
|
|
|
|
r = rate & IEEE80211_RATE_VAL;
|
|
|
|
for (j = 0; j < allrates.rs_nrates; j++)
|
|
|
|
if (allrates.rs_rates[j] == r)
|
|
|
|
break;
|
|
|
|
if (j == allrates.rs_nrates) {
|
|
|
|
/* Unique, add to the set */
|
|
|
|
allrates.rs_rates[j] = r;
|
|
|
|
allrates.rs_nrates++;
|
|
|
|
}
|
|
|
|
rate = (rate & IEEE80211_RATE_VAL) / 2;
|
|
|
|
if (rate > maxrate)
|
|
|
|
maxrate = rate;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < allrates.rs_nrates; i++) {
|
|
|
|
mword = ieee80211_rate2media(ic, allrates.rs_rates[i],
|
|
|
|
IEEE80211_MODE_AUTO);
|
|
|
|
if (mword == 0)
|
|
|
|
continue;
|
|
|
|
mword = IFM_SUBTYPE(mword); /* remove media options */
|
|
|
|
ADD(media, mword, 0);
|
|
|
|
if (caps & IEEE80211_C_IBSS)
|
|
|
|
ADD(media, mword, IFM_IEEE80211_ADHOC);
|
|
|
|
if (caps & IEEE80211_C_HOSTAP)
|
|
|
|
ADD(media, mword, IFM_IEEE80211_HOSTAP);
|
|
|
|
if (caps & IEEE80211_C_AHDEMO)
|
|
|
|
ADD(media, mword, IFM_IEEE80211_ADHOC | IFM_FLAG0);
|
|
|
|
if (caps & IEEE80211_C_MONITOR)
|
|
|
|
ADD(media, mword, IFM_IEEE80211_MONITOR);
|
|
|
|
if (caps & IEEE80211_C_WDS)
|
|
|
|
ADD(media, mword, IFM_IEEE80211_WDS);
|
|
|
|
}
|
|
|
|
|
|
|
|
return maxrate;
|
|
|
|
#undef ADD
|
|
|
|
}
|
|
|
|
|
2008-01-26 23:48:11 +03:00
|
|
|
/*
|
2008-07-17 08:50:29 +04:00
|
|
|
* Perform the DFS action (channel switch) using scan cache or a randomly
|
|
|
|
* chosen channel. The choice of the random channel is done in
|
|
|
|
* ieee80211_scan_dfs_action().
|
2008-01-26 23:48:11 +03:00
|
|
|
*
|
2008-07-17 08:50:29 +04:00
|
|
|
* This was moved out of ieee80211_mark_dfs(), because the same functionality
|
|
|
|
* is used also in ieee80211_ioctl_chanswitch().
|
2008-01-26 23:48:11 +03:00
|
|
|
*/
|
|
|
|
void
|
|
|
|
ieee80211_dfs_action(struct ieee80211com *ic) {
|
|
|
|
struct ieee80211vap *vap;
|
|
|
|
|
|
|
|
/* Get an AP mode VAP */
|
|
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
|
|
if (vap->iv_state == IEEE80211_S_RUN) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (vap == NULL) {
|
|
|
|
/*
|
|
|
|
* No running VAP was found, check
|
|
|
|
* if any one is scanning.
|
|
|
|
*/
|
|
|
|
|
|
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
|
|
if (vap->iv_state == IEEE80211_S_SCAN) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-27 08:48:23 +03:00
|
|
|
/* No running/scanning VAP was found, so they're all in
|
|
|
|
* INIT state, no channel change needed. */
|
|
|
|
if (vap == NULL)
|
2008-01-26 23:48:11 +03:00
|
|
|
return;
|
|
|
|
/* Is it really Scanning */
|
|
|
|
/* XXX: Race condition? */
|
|
|
|
if (ic->ic_flags & IEEE80211_F_SCAN)
|
|
|
|
return;
|
2008-01-27 08:48:23 +03:00
|
|
|
/* It is not scanning, but waiting for ath driver to move the
|
|
|
|
* vap to RUN. */
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
2009-07-11 21:20:58 +04:00
|
|
|
|
|
|
|
ieee80211_scan_dfs_action(vap);
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
|
|
|
|
2009-07-11 21:20:58 +04:00
|
|
|
/* Check if some Non-Occupancy Period timer have expired and update flags
|
|
|
|
* accordingly */
|
|
|
|
|
|
|
|
static void
|
2008-01-28 04:36:25 +03:00
|
|
|
ieee80211_expire_excl_restrictions(struct ieee80211com *ic)
|
2008-01-26 23:48:11 +03:00
|
|
|
{
|
2008-06-07 00:32:29 +04:00
|
|
|
struct ieee80211_channel *c = NULL;
|
2008-01-26 23:48:11 +03:00
|
|
|
struct net_device *dev = ic->ic_dev;
|
|
|
|
struct timeval tv_now;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
do_gettimeofday(&tv_now);
|
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
2009-07-11 14:15:28 +04:00
|
|
|
if (IEEE80211_IS_CHAN_RADAR(c)) {
|
2008-01-26 23:48:11 +03:00
|
|
|
if (timeval_compare(&ic->ic_chan_non_occupy[i],
|
|
|
|
&tv_now) < 0) {
|
|
|
|
if_printf(dev,
|
2008-01-27 08:48:23 +03:00
|
|
|
"Returning channel %3d (%4d MHz) "
|
|
|
|
"radar avoidance marker expired. "
|
|
|
|
"Channel now available again. -- "
|
|
|
|
"Time: %10ld.%06ld\n",
|
2008-01-26 23:48:11 +03:00
|
|
|
c->ic_ieee, c->ic_freq, tv_now.tv_sec,
|
|
|
|
tv_now.tv_usec);
|
|
|
|
c->ic_flags &= ~IEEE80211_CHAN_RADAR;
|
2009-07-11 21:20:58 +04:00
|
|
|
ic->ic_chan_non_occupy[i].tv_sec = 0;
|
|
|
|
ic->ic_chan_non_occupy[i].tv_usec = 0;
|
|
|
|
/* FIXME : should we use ic_curchan or ic_bsschan ? */
|
|
|
|
if (c == ic->ic_curchan) {
|
|
|
|
ic->ic_set_dfs_interference(ic, 0);
|
|
|
|
}
|
2008-01-26 23:48:11 +03:00
|
|
|
} else {
|
|
|
|
if_printf(dev,
|
2008-01-27 08:48:23 +03:00
|
|
|
"Channel %3d (%4d MHz) is still "
|
|
|
|
"marked for radar. Channel will "
|
|
|
|
"become usable in %u seconds at "
|
|
|
|
"Time: %10ld.%06ld\n",
|
2008-01-26 23:48:11 +03:00
|
|
|
c->ic_ieee, c->ic_freq,
|
2008-01-27 08:48:23 +03:00
|
|
|
ic->ic_chan_non_occupy[i].tv_sec -
|
|
|
|
tv_now.tv_sec,
|
2008-01-26 23:48:11 +03:00
|
|
|
ic->ic_chan_non_occupy[i].tv_sec,
|
|
|
|
ic->ic_chan_non_occupy[i].tv_usec);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-01-28 04:36:25 +03:00
|
|
|
EXPORT_SYMBOL(ieee80211_expire_excl_restrictions);
|
2008-01-26 23:48:11 +03:00
|
|
|
|
|
|
|
/* Update the Non-Occupancy Period timer with the first Non-Occupancy Period
|
|
|
|
* that will expire */
|
|
|
|
static void
|
2008-01-28 04:36:25 +03:00
|
|
|
ieee80211_update_dfs_excl_timer(struct ieee80211com *ic)
|
2008-01-26 23:48:11 +03:00
|
|
|
{
|
|
|
|
struct ieee80211_channel * chan;
|
|
|
|
struct timeval tv_now, tv_next;
|
|
|
|
int i;
|
|
|
|
unsigned long jiffies_tmp;
|
|
|
|
|
|
|
|
do_gettimeofday(&tv_now);
|
|
|
|
jiffies_tmp = jiffies;
|
|
|
|
|
|
|
|
tv_next.tv_sec = 0;
|
|
|
|
tv_next.tv_usec = 0;
|
2008-01-27 08:48:23 +03:00
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
2008-01-26 23:48:11 +03:00
|
|
|
chan = &ic->ic_channels[i];
|
2009-07-11 14:15:28 +04:00
|
|
|
if (IEEE80211_IS_CHAN_RADAR(chan)) {
|
2008-01-26 23:48:11 +03:00
|
|
|
if ((tv_next.tv_sec == 0) &&
|
|
|
|
(tv_next.tv_usec == 0)) {
|
|
|
|
tv_next = ic->ic_chan_non_occupy[i];
|
|
|
|
}
|
|
|
|
if (timeval_compare(&ic->ic_chan_non_occupy[i],
|
|
|
|
&tv_next) < 0) {
|
|
|
|
tv_next = ic->ic_chan_non_occupy[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((tv_next.tv_sec == 0) &&
|
|
|
|
(tv_next.tv_usec == 0)) {
|
2008-01-28 04:36:25 +03:00
|
|
|
del_timer(&ic->ic_dfs_excl_timer);
|
2008-01-26 23:48:11 +03:00
|
|
|
} else {
|
2008-01-28 04:36:25 +03:00
|
|
|
mod_timer(&ic->ic_dfs_excl_timer,
|
2008-01-27 08:48:23 +03:00
|
|
|
jiffies_tmp +
|
|
|
|
(tv_next.tv_sec - tv_now.tv_sec + 1) * HZ);
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-07-11 21:20:58 +04:00
|
|
|
/* Timer callback : periodically expire radar avoidance marks. */
|
2008-01-26 23:48:11 +03:00
|
|
|
static void
|
2008-01-28 04:36:25 +03:00
|
|
|
ieee80211_expire_dfs_excl_timer(unsigned long data)
|
2008-01-26 23:48:11 +03:00
|
|
|
{
|
2008-06-07 00:32:29 +04:00
|
|
|
struct ieee80211com *ic = (struct ieee80211com *)data;
|
2008-01-26 23:48:11 +03:00
|
|
|
struct ieee80211vap *vap;
|
|
|
|
|
2008-01-27 08:48:23 +03:00
|
|
|
printk(KERN_INFO "%s: %s: expiring Non-Occupancy Period\n",
|
|
|
|
DEV_NAME(ic->ic_dev), __func__);
|
2008-01-26 23:48:11 +03:00
|
|
|
|
|
|
|
if (ic->ic_flags_ext & IEEE80211_FEXT_MARKDFS) {
|
2008-01-27 08:48:23 +03:00
|
|
|
/* Make sure there are no channels that have just become
|
|
|
|
* available. */
|
2008-01-28 04:36:25 +03:00
|
|
|
ieee80211_expire_excl_restrictions(ic);
|
2008-01-26 23:48:11 +03:00
|
|
|
/* Go through and clear any interference flag we have, if we
|
|
|
|
* just got it cleared up for us */
|
|
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
|
|
if ((vap->iv_state == IEEE80211_S_RUN) &&
|
2009-07-11 21:20:58 +04:00
|
|
|
IEEE80211_IS_MODE_DFS_MASTER(vap->iv_opmode) &&
|
|
|
|
/* Desired channel is really defined */
|
|
|
|
(vap->iv_des_chan != NULL) &&
|
|
|
|
(vap->iv_des_chan != IEEE80211_CHAN_ANYC)) {
|
|
|
|
|
2008-01-26 23:48:11 +03:00
|
|
|
struct ieee80211_channel *des_chan =
|
2008-01-27 08:48:23 +03:00
|
|
|
ieee80211_find_channel(ic,
|
|
|
|
vap->iv_des_chan->
|
|
|
|
ic_freq,
|
|
|
|
vap->iv_des_chan->
|
|
|
|
ic_flags);
|
2008-01-26 23:48:11 +03:00
|
|
|
/* Can we switch to it? */
|
|
|
|
if (NULL == des_chan) {
|
2008-01-27 08:48:23 +03:00
|
|
|
IEEE80211_DPRINTF(vap,
|
|
|
|
IEEE80211_MSG_DOTH,
|
|
|
|
"%s: Desired channel "
|
|
|
|
"not found: %u/%x\n",
|
|
|
|
__func__,
|
|
|
|
vap->iv_des_chan->
|
|
|
|
ic_freq,
|
|
|
|
vap->iv_des_chan->
|
|
|
|
ic_flags);
|
2009-07-11 14:15:28 +04:00
|
|
|
} else if (!IEEE80211_IS_CHAN_RADAR(des_chan)) {
|
2008-01-27 08:48:23 +03:00
|
|
|
IEEE80211_DPRINTF(vap,
|
|
|
|
IEEE80211_MSG_DOTH,
|
|
|
|
"%s: Desired channel "
|
|
|
|
"found and available. "
|
|
|
|
"Switching to %u/%x\n",
|
|
|
|
__func__,
|
|
|
|
vap->iv_des_chan->
|
|
|
|
ic_freq,
|
|
|
|
vap->iv_des_chan->
|
|
|
|
ic_flags);
|
2009-07-11 21:20:58 +04:00
|
|
|
|
|
|
|
/* Directly call ic_set_channel() in
|
|
|
|
* order to start CAC if we are not
|
|
|
|
* really changing channel (it happens
|
|
|
|
* at the end of the Non-Occupancy
|
|
|
|
* Period for instance */
|
|
|
|
|
|
|
|
if (des_chan == ic->ic_bsschan) {
|
|
|
|
ic->ic_curchan = ic->ic_bsschan;
|
|
|
|
ic->ic_set_channel(ic);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
ieee80211_start_new_csa(vap,
|
|
|
|
IEEE80211_CSA_CAN_STOP_TX,
|
|
|
|
des_chan,
|
|
|
|
IEEE80211_DEFAULT_CHANCHANGE_TBTT_COUNT,
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
2008-01-27 08:48:23 +03:00
|
|
|
} else if (ieee80211_msg_is_reported(vap,
|
|
|
|
IEEE80211_MSG_DOTH)) {
|
|
|
|
/* Find the desired channel in
|
|
|
|
* ic_channels, so we can find the
|
|
|
|
* index into ic_chan_non_occupy. */
|
|
|
|
int i_des_chan = -1, i = 0;
|
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
if (&ic->ic_channels[i] ==
|
|
|
|
des_chan) {
|
|
|
|
i_des_chan = i;
|
|
|
|
break;
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
|
|
|
}
|
2008-01-27 08:48:23 +03:00
|
|
|
IEEE80211_DPRINTF(vap,
|
|
|
|
IEEE80211_MSG_DOTH,
|
|
|
|
"%s: Desired channel "
|
|
|
|
"found and not "
|
|
|
|
"available until Time: "
|
|
|
|
"%10ld.%06ld\n",
|
|
|
|
__func__,
|
|
|
|
ic->ic_chan_non_occupy
|
|
|
|
[i_des_chan].
|
|
|
|
tv_sec,
|
|
|
|
ic->ic_chan_non_occupy
|
|
|
|
[i_des_chan].
|
|
|
|
tv_usec
|
|
|
|
);
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* update the timer */
|
2008-01-28 04:36:25 +03:00
|
|
|
ieee80211_update_dfs_excl_timer(ic);
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* This function is called whenever a radar is detected on channel ichan */
|
|
|
|
|
2007-10-04 17:07:51 +04:00
|
|
|
void
|
|
|
|
ieee80211_mark_dfs(struct ieee80211com *ic, struct ieee80211_channel *ichan)
|
|
|
|
{
|
|
|
|
struct ieee80211_channel *c=NULL;
|
|
|
|
struct net_device *dev = ic->ic_dev;
|
2008-01-26 23:48:11 +03:00
|
|
|
struct timeval tv_now;
|
2008-01-28 04:36:25 +03:00
|
|
|
unsigned int excl_period = ic->ic_get_dfs_excl_period(ic);
|
2009-07-11 21:20:58 +04:00
|
|
|
int i, was_on_radar;
|
2007-10-04 17:07:51 +04:00
|
|
|
|
2008-01-26 23:48:11 +03:00
|
|
|
do_gettimeofday(&tv_now);
|
2008-01-27 08:48:23 +03:00
|
|
|
if_printf(dev, "Radar found on channel %3d (%4d MHz) -- "
|
|
|
|
"Time: %ld.%06ld\n",
|
|
|
|
ichan->ic_ieee, ichan->ic_freq,
|
|
|
|
tv_now.tv_sec, tv_now.tv_usec);
|
2008-01-26 23:48:11 +03:00
|
|
|
if (IEEE80211_IS_MODE_DFS_MASTER(ic->ic_opmode)) {
|
2009-07-11 21:20:58 +04:00
|
|
|
|
|
|
|
/* Check if the current channel is already under radar before
|
|
|
|
* setting radar flag */
|
|
|
|
was_on_radar = IEEE80211_IS_CHAN_RADAR(ic->ic_curchan);
|
|
|
|
|
2007-10-04 17:07:51 +04:00
|
|
|
/* Mark the channel in the ic_chan list */
|
|
|
|
if (ic->ic_flags_ext & IEEE80211_FEXT_MARKDFS) {
|
2008-01-27 08:48:23 +03:00
|
|
|
if_printf(dev, "Marking channel %3d (%4d MHz) in "
|
|
|
|
"ic_chan list -- Time: %ld.%06ld\n",
|
|
|
|
ichan->ic_ieee, ichan->ic_freq,
|
|
|
|
tv_now.tv_sec, tv_now.tv_usec);
|
2007-10-04 17:07:51 +04:00
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
2008-01-26 23:48:11 +03:00
|
|
|
if (c->ic_freq == ichan->ic_freq) {
|
|
|
|
c->ic_flags |= IEEE80211_CHAN_RADAR;
|
2008-01-27 08:48:23 +03:00
|
|
|
ic->ic_chan_non_occupy[i].tv_sec =
|
2008-01-28 04:36:25 +03:00
|
|
|
tv_now.tv_sec + excl_period;
|
2008-01-27 08:48:23 +03:00
|
|
|
ic->ic_chan_non_occupy[i].tv_usec =
|
|
|
|
tv_now.tv_usec;
|
|
|
|
|
|
|
|
if_printf(dev, "Channel %3d (%4d MHz) "
|
|
|
|
"will become usable in %u "
|
|
|
|
"seconds. Suspending use of "
|
|
|
|
"the channel until: "
|
|
|
|
"%ld.%06ld\n",
|
|
|
|
ichan->ic_ieee, ichan->ic_freq,
|
2008-01-28 04:36:25 +03:00
|
|
|
excl_period,
|
2008-01-27 08:48:23 +03:00
|
|
|
ic->ic_chan_non_occupy[i].tv_sec,
|
|
|
|
ic->ic_chan_non_occupy[i].tv_usec);
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
|
|
|
|
2008-01-27 08:48:23 +03:00
|
|
|
/* Recompute the next time a Non-Occupancy Period
|
2008-01-27 23:00:41 +03:00
|
|
|
* expires. */
|
2008-01-28 04:36:25 +03:00
|
|
|
ieee80211_update_dfs_excl_timer(ic);
|
2008-01-26 23:48:11 +03:00
|
|
|
|
2008-01-27 08:48:23 +03:00
|
|
|
c = ieee80211_find_channel(ic, ichan->ic_freq,
|
|
|
|
ichan->ic_flags);
|
2007-10-04 17:07:51 +04:00
|
|
|
if (c == NULL) {
|
2008-01-27 08:48:23 +03:00
|
|
|
if_printf(dev, "%s: Couldn't find matching "
|
|
|
|
"channel for dfs chanchange "
|
|
|
|
"(%d, 0x%x)\n",
|
|
|
|
__func__, ichan->ic_freq,
|
|
|
|
ichan->ic_flags);
|
2007-10-04 17:07:51 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (ic->ic_curchan->ic_freq == c->ic_freq) {
|
2009-07-11 21:20:58 +04:00
|
|
|
|
|
|
|
/* If current channel is already marked for
|
|
|
|
* radar, do nothing. It prevents a new CSA
|
|
|
|
* process to start */
|
|
|
|
if (was_on_radar) {
|
|
|
|
if_printf(dev,
|
|
|
|
"%s: current channel "
|
|
|
|
"already under radar\n",
|
|
|
|
__func__);
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if_printf(dev, "%s: Invoking "
|
2008-01-27 08:48:23 +03:00
|
|
|
"ieee80211_dfs_action "
|
|
|
|
"(%d, 0x%x)\n",
|
|
|
|
__func__, ichan->ic_freq,
|
|
|
|
ichan->ic_flags);
|
2009-07-11 21:20:58 +04:00
|
|
|
/* The current channel has been
|
|
|
|
* marked. We need to move away from
|
|
|
|
* it. */
|
|
|
|
ieee80211_dfs_action(ic);
|
|
|
|
}
|
2008-01-27 08:48:23 +03:00
|
|
|
} else
|
2008-01-26 23:48:11 +03:00
|
|
|
if_printf(dev,
|
2008-01-27 08:48:23 +03:00
|
|
|
"Unexpected channel frequency! "
|
|
|
|
"ichan=%3d (%4d MHz) "
|
|
|
|
"ic_curchan=%3d (%4d MHz). "
|
|
|
|
"Not invoking "
|
|
|
|
"ieee80211_dfs_action.\n",
|
|
|
|
ichan->ic_ieee,
|
|
|
|
ichan->ic_freq,
|
|
|
|
ic->ic_curchan->ic_ieee,
|
|
|
|
ic->ic_curchan->ic_freq);
|
2007-10-04 17:07:51 +04:00
|
|
|
} else {
|
2009-07-11 21:20:58 +04:00
|
|
|
struct ieee80211vap *vap;
|
|
|
|
|
2008-07-17 07:44:41 +04:00
|
|
|
/* Change to a radar free 11a channel for dfstesttime
|
2009-07-11 21:20:58 +04:00
|
|
|
* seconds.
|
|
|
|
* Start a channel switch on all available VAPs. */
|
|
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
|
|
c = ieee80211_doth_findchan(
|
|
|
|
vap,
|
|
|
|
IEEE80211_RADAR_TEST_MUTE_CHAN);
|
|
|
|
|
|
|
|
ieee80211_start_new_csa(vap, IEEE80211_CSA_CAN_STOP_TX,
|
|
|
|
c, IEEE80211_RADAR_CHANCHANGE_TBTT_COUNT, 0);
|
|
|
|
}
|
|
|
|
|
2008-01-26 23:48:11 +03:00
|
|
|
if_printf(dev,
|
2008-01-27 08:48:23 +03:00
|
|
|
"Mute test - markdfs is off, we are "
|
|
|
|
"in hostap mode, found radar on "
|
|
|
|
"channel %3d (%4d MHz) "
|
|
|
|
"ic->ic_curchan=%3d (%4d MHz). "
|
|
|
|
"Not invoking ieee80211_dfs_action.\n",
|
|
|
|
ichan->ic_ieee, ichan->ic_freq,
|
|
|
|
ic->ic_curchan->ic_ieee,
|
|
|
|
ic->ic_curchan->ic_freq);
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
|
|
|
} else {
|
2008-07-17 07:44:41 +04:00
|
|
|
/* XXX: Are we in STA mode? If so, send an action msg. to AP
|
2008-01-27 08:48:23 +03:00
|
|
|
* saying we found a radar? */
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_mark_dfs);
|
|
|
|
|
|
|
|
void
|
|
|
|
ieee80211_announce(struct ieee80211com *ic)
|
|
|
|
{
|
2008-06-21 18:40:43 +04:00
|
|
|
/* Disabled - creates noise but no useful information. */
|
|
|
|
#if 0
|
2007-10-04 17:07:51 +04:00
|
|
|
struct net_device *dev = ic->ic_dev;
|
|
|
|
int i, mode, rate, mword;
|
|
|
|
struct ieee80211_rateset *rs;
|
|
|
|
|
|
|
|
for (mode = IEEE80211_MODE_11A; mode < IEEE80211_MODE_MAX; mode++) {
|
|
|
|
if ((ic->ic_modecaps & (1 << mode)) == 0)
|
|
|
|
continue;
|
|
|
|
if_printf(dev, "%s rates: ", ieee80211_phymode_name[mode]);
|
|
|
|
rs = &ic->ic_sup_rates[mode];
|
|
|
|
for (i = 0; i < rs->rs_nrates; i++) {
|
|
|
|
rate = rs->rs_rates[i];
|
|
|
|
mword = ieee80211_rate2media(ic, rate, mode);
|
|
|
|
if (mword == 0)
|
|
|
|
continue;
|
2008-01-26 23:48:11 +03:00
|
|
|
printk("%s%d%sMbps", (i != 0 ? " " : ""),
|
2007-10-04 17:07:51 +04:00
|
|
|
(rate & IEEE80211_RATE_VAL) / 2,
|
|
|
|
((rate & 0x1) != 0 ? ".5" : ""));
|
|
|
|
}
|
2008-01-26 23:48:11 +03:00
|
|
|
printk("\n");
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
|
|
|
if_printf(dev, "H/W encryption support:");
|
|
|
|
if (ic->ic_caps & IEEE80211_C_WEP)
|
|
|
|
printk(" WEP");
|
|
|
|
if (ic->ic_caps & IEEE80211_C_AES)
|
|
|
|
printk(" AES");
|
|
|
|
if (ic->ic_caps & IEEE80211_C_AES_CCM)
|
|
|
|
printk(" AES_CCM");
|
|
|
|
if (ic->ic_caps & IEEE80211_C_CKIP)
|
|
|
|
printk(" CKIP");
|
|
|
|
if (ic->ic_caps & IEEE80211_C_TKIP)
|
|
|
|
printk(" TKIP");
|
|
|
|
printk("\n");
|
2008-06-21 18:40:43 +04:00
|
|
|
#endif
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_announce);
|
|
|
|
|
|
|
|
void
|
|
|
|
ieee80211_announce_channels(struct ieee80211com *ic)
|
|
|
|
{
|
|
|
|
const struct ieee80211_channel *c;
|
|
|
|
char type;
|
|
|
|
int i;
|
|
|
|
|
2008-01-26 23:48:11 +03:00
|
|
|
printk(KERN_INFO "Chan Freq RegPwr MinPwr MaxPwr\n");
|
2007-10-04 17:07:51 +04:00
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
|
|
|
if (IEEE80211_IS_CHAN_ST(c))
|
|
|
|
type = 'S';
|
|
|
|
else if (IEEE80211_IS_CHAN_108A(c))
|
|
|
|
type = 'T';
|
|
|
|
else if (IEEE80211_IS_CHAN_108G(c))
|
|
|
|
type = 'G';
|
|
|
|
else if (IEEE80211_IS_CHAN_A(c))
|
|
|
|
type = 'a';
|
|
|
|
else if (IEEE80211_IS_CHAN_ANYG(c))
|
|
|
|
type = 'g';
|
|
|
|
else if (IEEE80211_IS_CHAN_B(c))
|
|
|
|
type = 'b';
|
|
|
|
else
|
|
|
|
type = 'f';
|
2008-01-26 23:48:11 +03:00
|
|
|
printk(KERN_INFO "%4d %4d%c %6d %6d %6d\n",
|
2007-10-04 17:07:51 +04:00
|
|
|
c->ic_ieee, c->ic_freq, type,
|
|
|
|
c->ic_maxregpower,
|
|
|
|
c->ic_minpower, c->ic_maxpower
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_announce_channels);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common code to calculate the media status word
|
|
|
|
* from the operating mode and channel state.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
media_status(enum ieee80211_opmode opmode, const struct ieee80211_channel *chan)
|
|
|
|
{
|
|
|
|
int status;
|
|
|
|
|
|
|
|
status = IFM_IEEE80211;
|
|
|
|
switch (opmode) {
|
|
|
|
case IEEE80211_M_STA:
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_AHDEMO:
|
|
|
|
status |= IFM_IEEE80211_ADHOC | IFM_FLAG0;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_IBSS:
|
|
|
|
status |= IFM_IEEE80211_ADHOC;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_HOSTAP:
|
|
|
|
status |= IFM_IEEE80211_HOSTAP;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_MONITOR:
|
|
|
|
status |= IFM_IEEE80211_MONITOR;
|
|
|
|
break;
|
|
|
|
case IEEE80211_M_WDS:
|
|
|
|
status |= IFM_IEEE80211_WDS;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (IEEE80211_IS_CHAN_A(chan)) {
|
|
|
|
status |= IFM_IEEE80211_11A;
|
|
|
|
if (IEEE80211_IS_CHAN_TURBO(chan))
|
|
|
|
status |= IFM_IEEE80211_TURBO;
|
|
|
|
} else if (IEEE80211_IS_CHAN_B(chan)) {
|
|
|
|
status |= IFM_IEEE80211_11B;
|
|
|
|
} else if (IEEE80211_IS_CHAN_ANYG(chan)) {
|
|
|
|
status |= IFM_IEEE80211_11G;
|
|
|
|
if (IEEE80211_IS_CHAN_TURBO(chan))
|
|
|
|
status |= IFM_IEEE80211_TURBO;
|
|
|
|
} else if (IEEE80211_IS_CHAN_FHSS(chan)) {
|
|
|
|
status |= IFM_IEEE80211_FH;
|
|
|
|
}
|
|
|
|
/* XXX: Otherwise complain? */
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a media requests on the base interface.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
ieee80211com_media_status(struct net_device *dev, struct ifmediareq *imr)
|
|
|
|
{
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211com *ic = netdev_priv(dev); /* XXX */
|
2007-10-04 17:07:51 +04:00
|
|
|
|
|
|
|
imr->ifm_status = IFM_AVALID;
|
|
|
|
if (!TAILQ_EMPTY(&ic->ic_vaps))
|
|
|
|
imr->ifm_status |= IFM_ACTIVE;
|
|
|
|
imr->ifm_active = media_status(ic->ic_opmode, ic->ic_curchan);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert a media specification to an 802.11 PHY mode.
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
media2mode(const struct ifmedia_entry *ime, enum ieee80211_phymode *mode)
|
|
|
|
{
|
|
|
|
|
|
|
|
switch (IFM_MODE(ime->ifm_media)) {
|
|
|
|
case IFM_IEEE80211_11A:
|
|
|
|
*mode = IEEE80211_MODE_11A;
|
|
|
|
break;
|
|
|
|
case IFM_IEEE80211_11B:
|
|
|
|
*mode = IEEE80211_MODE_11B;
|
|
|
|
break;
|
|
|
|
case IFM_IEEE80211_11G:
|
|
|
|
*mode = IEEE80211_MODE_11G;
|
|
|
|
break;
|
|
|
|
case IFM_IEEE80211_FH:
|
|
|
|
*mode = IEEE80211_MODE_FH;
|
|
|
|
break;
|
|
|
|
case IFM_AUTO:
|
|
|
|
*mode = IEEE80211_MODE_AUTO;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Turbo mode is an 'option'.
|
|
|
|
* XXX: Turbo currently does not apply to AUTO
|
|
|
|
*/
|
|
|
|
if (ime->ifm_media & IFM_IEEE80211_TURBO) {
|
|
|
|
if (*mode == IEEE80211_MODE_11A)
|
|
|
|
*mode = IEEE80211_MODE_TURBO_A;
|
|
|
|
else if (*mode == IEEE80211_MODE_11G)
|
|
|
|
*mode = IEEE80211_MODE_TURBO_G;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ieee80211com_media_change(struct net_device *dev)
|
|
|
|
{
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211com *ic = netdev_priv(dev); /* XXX */
|
2007-10-04 17:07:51 +04:00
|
|
|
struct ieee80211vap *vap;
|
|
|
|
struct ifmedia_entry *ime = ic->ic_media.ifm_cur;
|
|
|
|
enum ieee80211_phymode newphymode;
|
|
|
|
int j, error = 0;
|
|
|
|
|
|
|
|
/* XXX: Is rtnl_lock held here? */
|
|
|
|
/* First, identify the phy mode. */
|
|
|
|
if (!media2mode(ime, &newphymode))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* NB: Mode must be supported, no need to check */
|
|
|
|
/*
|
|
|
|
* Autoselect doesn't make sense when operating as an AP.
|
|
|
|
* If no phy mode has been selected, pick one and lock it
|
|
|
|
* down so rate tables can be used in forming beacon frames
|
|
|
|
* and the like.
|
|
|
|
*/
|
|
|
|
if ((ic->ic_opmode == IEEE80211_M_HOSTAP) &&
|
|
|
|
(newphymode == IEEE80211_MODE_AUTO)) {
|
|
|
|
for (j = IEEE80211_MODE_11A; j < IEEE80211_MODE_MAX; j++)
|
|
|
|
if (ic->ic_modecaps & (1 << j)) {
|
|
|
|
newphymode = j;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Handle PHY mode change. */
|
|
|
|
IEEE80211_LOCK_IRQ(ic);
|
|
|
|
if (ic->ic_curmode != newphymode) { /* change PHY mode */
|
|
|
|
error = ieee80211_setmode(ic, newphymode);
|
|
|
|
if (error != 0) {
|
|
|
|
IEEE80211_UNLOCK_IRQ_EARLY(ic);
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
TAILQ_FOREACH(vap, &ic->ic_vaps, iv_next) {
|
|
|
|
/* Reset WME state */
|
|
|
|
ieee80211_wme_initparams_locked(vap);
|
|
|
|
/*
|
|
|
|
* Setup an initial rate set according to the
|
|
|
|
* current/default channel selected above. This
|
|
|
|
* will be changed when scanning but must exist
|
|
|
|
* now so drivers have a consistent state.
|
|
|
|
*/
|
|
|
|
KASSERT(vap->iv_bss != NULL, ("no bss node"));
|
|
|
|
vap->iv_bss->ni_rates = ic->ic_sup_rates[newphymode];
|
|
|
|
}
|
|
|
|
error = -ENETRESET;
|
|
|
|
}
|
|
|
|
IEEE80211_UNLOCK_IRQ(ic);
|
|
|
|
|
|
|
|
#ifdef notdef
|
|
|
|
if (error == 0)
|
|
|
|
ifp->if_baudrate = ifmedia_baudrate(ime->ifm_media);
|
|
|
|
#endif
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
findrate(struct ieee80211com *ic, enum ieee80211_phymode mode, int rate)
|
|
|
|
{
|
|
|
|
#define IEEERATE(_ic,_m,_i) \
|
|
|
|
((_ic)->ic_sup_rates[_m].rs_rates[_i] & IEEE80211_RATE_VAL)
|
|
|
|
int i, nrates = ic->ic_sup_rates[mode].rs_nrates;
|
|
|
|
for (i = 0; i < nrates; i++)
|
|
|
|
if (IEEERATE(ic, mode, i) == rate)
|
|
|
|
return i;
|
|
|
|
return -1;
|
|
|
|
#undef IEEERATE
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert a media specification to a rate index and possibly a mode
|
|
|
|
* (if the rate is fixed and the mode is specified as 'auto' then
|
|
|
|
* we need to lock down the mode so the index is meaningful).
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
checkrate(struct ieee80211com *ic, enum ieee80211_phymode mode, int rate)
|
|
|
|
{
|
|
|
|
|
|
|
|
/* Check the rate table for the specified/current PHY. */
|
|
|
|
if (mode == IEEE80211_MODE_AUTO) {
|
|
|
|
int i;
|
|
|
|
/* In autoselect mode search for the rate. */
|
|
|
|
for (i = IEEE80211_MODE_11A; i < IEEE80211_MODE_MAX; i++) {
|
|
|
|
if ((ic->ic_modecaps & (1 << i)) &&
|
|
|
|
findrate(ic, i, rate) != -1)
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
/* Mode is fixed; check for rate. */
|
|
|
|
return (findrate(ic, mode, rate) != -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle a media change request; the only per-vap
|
|
|
|
* information that is meaningful is the fixed rate
|
|
|
|
* and desired PHY mode.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
ieee80211_media_change(struct net_device *dev)
|
|
|
|
{
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211vap *vap = netdev_priv(dev);
|
2007-10-04 17:07:51 +04:00
|
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
|
|
struct ifmedia_entry *ime = vap->iv_media.ifm_cur;
|
|
|
|
enum ieee80211_phymode newmode;
|
|
|
|
int newrate, error;
|
|
|
|
|
|
|
|
/* First, identify the desired PHY mode. */
|
|
|
|
if (!media2mode(ime, &newmode))
|
|
|
|
return -EINVAL;
|
|
|
|
/* Check for fixed/variable rate. */
|
|
|
|
if (IFM_SUBTYPE(ime->ifm_media) != IFM_AUTO) {
|
|
|
|
/*
|
|
|
|
* Convert media subtype to rate and potentially
|
|
|
|
* lock down the mode.
|
|
|
|
*/
|
|
|
|
newrate = ieee80211_media2rate(ime->ifm_media);
|
|
|
|
if ((newrate == 0) || !checkrate(ic, newmode, newrate))
|
|
|
|
return -EINVAL;
|
|
|
|
} else
|
|
|
|
newrate = IEEE80211_FIXED_RATE_NONE;
|
|
|
|
|
|
|
|
/* Install the rate & mode settings. */
|
|
|
|
error = 0;
|
|
|
|
if (vap->iv_fixed_rate != newrate) {
|
|
|
|
vap->iv_fixed_rate = newrate; /* fixed TX rate */
|
|
|
|
error = -ENETRESET;
|
|
|
|
}
|
|
|
|
if (vap->iv_des_mode != newmode) {
|
|
|
|
vap->iv_des_mode = newmode; /* desired PHY mode */
|
|
|
|
error = -ENETRESET;
|
|
|
|
}
|
|
|
|
return error;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_media_change);
|
|
|
|
|
|
|
|
void
|
|
|
|
ieee80211_media_status(struct net_device *dev, struct ifmediareq *imr)
|
|
|
|
{
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211vap *vap = netdev_priv(dev);
|
2007-10-04 17:07:51 +04:00
|
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
|
|
enum ieee80211_phymode mode;
|
|
|
|
struct ieee80211_rateset *rs;
|
|
|
|
|
|
|
|
imr->ifm_status = IFM_AVALID;
|
|
|
|
/*
|
|
|
|
* NB: use the current channel's mode to lock down a xmit
|
|
|
|
* rate only when running; otherwise we may have a mismatch
|
|
|
|
* in which case the rate will not be convertible.
|
|
|
|
*/
|
|
|
|
if (vap->iv_state == IEEE80211_S_RUN) {
|
|
|
|
imr->ifm_status |= IFM_ACTIVE;
|
|
|
|
mode = ieee80211_chan2mode(ic->ic_curchan);
|
|
|
|
} else
|
|
|
|
mode = IEEE80211_MODE_AUTO;
|
|
|
|
imr->ifm_active = media_status(vap->iv_opmode, ic->ic_curchan);
|
|
|
|
/* Calculate a current rate, if possible. */
|
|
|
|
if (vap->iv_fixed_rate != IEEE80211_FIXED_RATE_NONE) {
|
|
|
|
/* A fixed rate is set, report that. */
|
|
|
|
imr->ifm_active |= ieee80211_rate2media(ic,
|
|
|
|
vap->iv_fixed_rate, mode);
|
|
|
|
} else if (vap->iv_opmode == IEEE80211_M_STA) {
|
|
|
|
/* In station mode, report the current transmit rate. */
|
|
|
|
rs = &vap->iv_bss->ni_rates;
|
|
|
|
imr->ifm_active |= ieee80211_rate2media(ic,
|
|
|
|
rs->rs_rates[vap->iv_bss->ni_txrate], mode);
|
|
|
|
} else
|
|
|
|
imr->ifm_active |= IFM_AUTO;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_media_status);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the current PHY mode.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
ieee80211_setmode(struct ieee80211com *ic, enum ieee80211_phymode mode)
|
|
|
|
{
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/* Potentially invalidate the BSS channel. */
|
|
|
|
/* XXX not right/too conservative */
|
|
|
|
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC &&
|
|
|
|
mode != ieee80211_chan2mode(ic->ic_bsschan))
|
|
|
|
ic->ic_bsschan = IEEE80211_CHAN_ANYC; /* invalidate */
|
|
|
|
#endif
|
|
|
|
ieee80211_reset_erp(ic, mode); /* reset ERP state */
|
|
|
|
ic->ic_curmode = mode; /* NB: must do post reset_erp */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_setmode);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return the PHY mode for with the specified channel.
|
|
|
|
*/
|
|
|
|
enum ieee80211_phymode
|
|
|
|
ieee80211_chan2mode(const struct ieee80211_channel *chan)
|
|
|
|
{
|
|
|
|
/*
|
2009-07-11 21:33:54 +04:00
|
|
|
* Callers should handle this case properly, rather than just relying
|
|
|
|
* that this function returns a sane value. XXX: Probably needs to be
|
|
|
|
* revised. chan is undefined if channel is 0 for instance and kernel
|
|
|
|
* panic would happen when called by ieee80211_sta_join1() in IBSS
|
|
|
|
* mode.
|
2007-10-04 17:07:51 +04:00
|
|
|
*/
|
2009-07-11 21:33:54 +04:00
|
|
|
|
|
|
|
if (chan == NULL ||
|
|
|
|
chan == IEEE80211_CHAN_ANYC) {
|
|
|
|
printk(KERN_ERR "%s: BUG channel not setup: %p\n",
|
|
|
|
__func__, chan);
|
|
|
|
return IEEE80211_MODE_11B;
|
|
|
|
}
|
2007-10-04 17:07:51 +04:00
|
|
|
|
|
|
|
if (IEEE80211_IS_CHAN_108G(chan))
|
|
|
|
return IEEE80211_MODE_TURBO_G;
|
|
|
|
else if (IEEE80211_IS_CHAN_TURBO(chan))
|
|
|
|
return IEEE80211_MODE_TURBO_A;
|
|
|
|
else if (IEEE80211_IS_CHAN_A(chan))
|
|
|
|
return IEEE80211_MODE_11A;
|
|
|
|
else if (IEEE80211_IS_CHAN_ANYG(chan))
|
|
|
|
return IEEE80211_MODE_11G;
|
|
|
|
else if (IEEE80211_IS_CHAN_B(chan))
|
|
|
|
return IEEE80211_MODE_11B;
|
|
|
|
else if (IEEE80211_IS_CHAN_FHSS(chan))
|
|
|
|
return IEEE80211_MODE_FH;
|
|
|
|
|
|
|
|
/* NB: Should not get here */
|
2008-01-26 23:48:11 +03:00
|
|
|
printk(KERN_ERR "%s: cannot map channel to mode; freq %u flags 0x%x\n",
|
2007-10-04 17:07:51 +04:00
|
|
|
__func__, chan->ic_freq, chan->ic_flags);
|
|
|
|
return IEEE80211_MODE_11B;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_chan2mode);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Convert IEEE80211 rate value to ifmedia subtype.
|
|
|
|
* ieee80211 rate is in unit of 0.5Mbps.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
ieee80211_rate2media(struct ieee80211com *ic, int rate, enum ieee80211_phymode mode)
|
|
|
|
{
|
|
|
|
static const struct {
|
|
|
|
u_int m; /* rate & mode */
|
|
|
|
u_int r; /* if_media rate */
|
|
|
|
} rates[] = {
|
|
|
|
{ 2 | IFM_IEEE80211_FH, IFM_IEEE80211_FH1 },
|
|
|
|
{ 4 | IFM_IEEE80211_FH, IFM_IEEE80211_FH2 },
|
|
|
|
{ 2 | IFM_IEEE80211_11B, IFM_IEEE80211_DS1 },
|
|
|
|
{ 4 | IFM_IEEE80211_11B, IFM_IEEE80211_DS2 },
|
|
|
|
{ 11 | IFM_IEEE80211_11B, IFM_IEEE80211_DS5 },
|
|
|
|
{ 22 | IFM_IEEE80211_11B, IFM_IEEE80211_DS11 },
|
|
|
|
{ 44 | IFM_IEEE80211_11B, IFM_IEEE80211_DS22 },
|
|
|
|
{ 3 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM1_50 },
|
|
|
|
{ 4 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM2_25 },
|
|
|
|
{ 6 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM3 },
|
|
|
|
{ 9 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM4_50 },
|
|
|
|
{ 12 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM6 },
|
|
|
|
{ 18 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM9 },
|
|
|
|
{ 24 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM12 },
|
|
|
|
{ 27 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM13_5 },
|
|
|
|
{ 36 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM18 },
|
|
|
|
{ 48 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM24 },
|
|
|
|
{ 54 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM27 },
|
|
|
|
{ 72 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM36 },
|
|
|
|
{ 96 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM48 },
|
|
|
|
{ 108 | IFM_IEEE80211_11A, IFM_IEEE80211_OFDM54 },
|
|
|
|
{ 2 | IFM_IEEE80211_11G, IFM_IEEE80211_DS1 },
|
|
|
|
{ 4 | IFM_IEEE80211_11G, IFM_IEEE80211_DS2 },
|
|
|
|
{ 11 | IFM_IEEE80211_11G, IFM_IEEE80211_DS5 },
|
|
|
|
{ 22 | IFM_IEEE80211_11G, IFM_IEEE80211_DS11 },
|
|
|
|
{ 12 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM6 },
|
|
|
|
{ 18 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM9 },
|
|
|
|
{ 24 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM12 },
|
|
|
|
{ 36 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM18 },
|
|
|
|
{ 48 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM24 },
|
|
|
|
{ 72 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM36 },
|
|
|
|
{ 96 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM48 },
|
|
|
|
{ 108 | IFM_IEEE80211_11G, IFM_IEEE80211_OFDM54 },
|
|
|
|
/* NB: OFDM72 doesn't really exist so we don't handle it */
|
|
|
|
};
|
|
|
|
|
|
|
|
u_int mask, i;
|
|
|
|
mask = rate & IEEE80211_RATE_VAL;
|
|
|
|
switch (mode) {
|
|
|
|
case IEEE80211_MODE_11A:
|
|
|
|
case IEEE80211_MODE_TURBO_A:
|
|
|
|
mask |= IFM_IEEE80211_11A;
|
|
|
|
break;
|
|
|
|
case IEEE80211_MODE_11B:
|
|
|
|
mask |= IFM_IEEE80211_11B;
|
|
|
|
break;
|
|
|
|
case IEEE80211_MODE_FH:
|
|
|
|
mask |= IFM_IEEE80211_FH;
|
|
|
|
break;
|
|
|
|
case IEEE80211_MODE_AUTO:
|
|
|
|
/* NB: ic may be NULL for some drivers */
|
|
|
|
if (ic && (ic->ic_phytype == IEEE80211_T_FH)) {
|
|
|
|
mask |= IFM_IEEE80211_FH;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* NB: Hack, 11g matches both 11b & 11a rates */
|
|
|
|
/* *Fall through* */
|
|
|
|
case IEEE80211_MODE_11G:
|
|
|
|
case IEEE80211_MODE_TURBO_G:
|
|
|
|
mask |= IFM_IEEE80211_11G;
|
|
|
|
break;
|
|
|
|
}
|
2007-11-28 08:36:29 +03:00
|
|
|
for (i = 0; i < ARRAY_SIZE(rates); i++)
|
2007-10-04 17:07:51 +04:00
|
|
|
if (rates[i].m == mask)
|
|
|
|
return rates[i].r;
|
|
|
|
return IFM_AUTO;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_rate2media);
|
|
|
|
|
|
|
|
int
|
|
|
|
ieee80211_media2rate(int mword)
|
|
|
|
{
|
|
|
|
static const int ieeerates[] = {
|
|
|
|
-1, /* IFM_AUTO */
|
|
|
|
0, /* IFM_MANUAL */
|
|
|
|
0, /* IFM_NONE */
|
|
|
|
2, /* IFM_IEEE80211_FH1 */
|
|
|
|
4, /* IFM_IEEE80211_FH2 */
|
|
|
|
2, /* IFM_IEEE80211_DS1 */
|
|
|
|
4, /* IFM_IEEE80211_DS2 */
|
|
|
|
11, /* IFM_IEEE80211_DS5 */
|
|
|
|
22, /* IFM_IEEE80211_DS11 */
|
|
|
|
44, /* IFM_IEEE80211_DS22 */
|
|
|
|
3, /* IFM_IEEE80211_OFDM1_50 */
|
|
|
|
4, /* IFM_IEEE80211_OFDM2_25 */
|
|
|
|
6, /* IFM_IEEE80211_OFDM3 */
|
|
|
|
9, /* IFM_IEEE80211_OFDM4_50 */
|
|
|
|
12, /* IFM_IEEE80211_OFDM6 */
|
|
|
|
18, /* IFM_IEEE80211_OFDM9 */
|
|
|
|
24, /* IFM_IEEE80211_OFDM12 */
|
|
|
|
27, /* IFM_IEEE80211_OFDM13_5 */
|
|
|
|
36, /* IFM_IEEE80211_OFDM18 */
|
|
|
|
48, /* IFM_IEEE80211_OFDM24 */
|
|
|
|
54, /* IFM_IEEE80211_OFDM27 */
|
|
|
|
72, /* IFM_IEEE80211_OFDM36 */
|
|
|
|
96, /* IFM_IEEE80211_OFDM48 */
|
|
|
|
108, /* IFM_IEEE80211_OFDM54 */
|
|
|
|
144, /* IFM_IEEE80211_OFDM72 */
|
|
|
|
};
|
2007-11-28 08:36:29 +03:00
|
|
|
return IFM_SUBTYPE(mword) < ARRAY_SIZE(ieeerates) ?
|
2007-10-04 17:07:51 +04:00
|
|
|
ieeerates[IFM_SUBTYPE(mword)] : 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ieee80211_media2rate);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return netdevice statistics.
|
|
|
|
*/
|
|
|
|
static struct net_device_stats *
|
|
|
|
ieee80211_getstats(struct net_device *dev)
|
|
|
|
{
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211vap *vap = netdev_priv(dev);
|
2007-10-04 17:07:51 +04:00
|
|
|
struct net_device_stats *stats = &vap->iv_devstats;
|
|
|
|
|
|
|
|
/* XXX: Total guess as to what to count where */
|
|
|
|
/* Update according to private statistics */
|
|
|
|
stats->tx_errors = vap->iv_stats.is_tx_nodefkey
|
|
|
|
+ vap->iv_stats.is_tx_noheadroom
|
|
|
|
+ vap->iv_stats.is_crypto_enmicfail;
|
|
|
|
stats->tx_dropped = vap->iv_stats.is_tx_nobuf
|
|
|
|
+ vap->iv_stats.is_tx_nonode
|
|
|
|
+ vap->iv_stats.is_tx_unknownmgt
|
|
|
|
+ vap->iv_stats.is_tx_badcipher
|
|
|
|
+ vap->iv_stats.is_tx_nodefkey;
|
|
|
|
stats->rx_errors = vap->iv_stats.is_rx_tooshort
|
|
|
|
+ vap->iv_stats.is_rx_wepfail
|
|
|
|
+ vap->iv_stats.is_rx_decap
|
|
|
|
+ vap->iv_stats.is_rx_nobuf
|
|
|
|
+ vap->iv_stats.is_rx_decryptcrc
|
|
|
|
+ vap->iv_stats.is_rx_ccmpmic
|
|
|
|
+ vap->iv_stats.is_rx_tkipmic
|
|
|
|
+ vap->iv_stats.is_rx_tkipicv;
|
|
|
|
stats->rx_crc_errors = 0;
|
|
|
|
|
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
ieee80211_change_mtu(struct net_device *dev, int mtu)
|
|
|
|
{
|
|
|
|
if ((IEEE80211_MTU_MIN >= mtu) || (mtu > IEEE80211_MTU_MAX))
|
|
|
|
return -EINVAL;
|
|
|
|
dev->mtu = mtu;
|
|
|
|
/* XXX: Coordinate with parent device */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ieee80211_set_multicast_list(struct net_device *dev)
|
|
|
|
{
|
2009-01-14 05:36:53 +03:00
|
|
|
struct ieee80211vap *vap = netdev_priv(dev);
|
2007-10-04 17:07:51 +04:00
|
|
|
struct ieee80211com *ic = vap->iv_ic;
|
|
|
|
struct net_device *parent = ic->ic_dev;
|
|
|
|
|
|
|
|
IEEE80211_LOCK_IRQ(ic);
|
|
|
|
if (dev->flags & IFF_PROMISC) {
|
|
|
|
if ((vap->iv_flags & IEEE80211_F_PROMISC) == 0) {
|
|
|
|
vap->iv_flags |= IEEE80211_F_PROMISC;
|
|
|
|
ic->ic_promisc++;
|
|
|
|
parent->flags |= IFF_PROMISC;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (vap->iv_flags & IEEE80211_F_PROMISC) {
|
|
|
|
vap->iv_flags &= ~IEEE80211_F_PROMISC;
|
|
|
|
ic->ic_promisc--;
|
|
|
|
parent->flags &= ~IFF_PROMISC;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (dev->flags & IFF_ALLMULTI) {
|
|
|
|
if ((vap->iv_flags & IEEE80211_F_ALLMULTI) == 0) {
|
|
|
|
vap->iv_flags |= IEEE80211_F_ALLMULTI;
|
|
|
|
ic->ic_allmulti++;
|
|
|
|
parent->flags |= IFF_ALLMULTI;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (vap->iv_flags & IEEE80211_F_ALLMULTI) {
|
|
|
|
vap->iv_flags &= ~IEEE80211_F_ALLMULTI;
|
|
|
|
ic->ic_allmulti--;
|
|
|
|
parent->flags &= ~IFF_ALLMULTI;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IEEE80211_UNLOCK_IRQ(ic);
|
|
|
|
|
|
|
|
/* XXX: Merge multicast list into parent device */
|
2011-11-22 21:22:34 +04:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
|
|
|
|
parent->netdev_ops->ndo_set_rx_mode(ic->ic_dev);
|
2009-04-15 08:21:12 +04:00
|
|
|
#else
|
2011-11-22 21:22:34 +04:00
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29)
|
2009-04-15 08:21:12 +04:00
|
|
|
parent->netdev_ops->ndo_set_multicast_list(ic->ic_dev);
|
2011-11-22 21:22:34 +04:00
|
|
|
#else
|
|
|
|
parent->set_multicast_list(ic->ic_dev);
|
|
|
|
#endif
|
2009-04-15 08:21:12 +04:00
|
|
|
#endif
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ieee80211_build_countryie(struct ieee80211com *ic)
|
|
|
|
{
|
2008-01-26 23:48:11 +03:00
|
|
|
int i, found;
|
2007-10-04 17:07:51 +04:00
|
|
|
struct net_device *dev = ic->ic_dev;
|
|
|
|
struct ieee80211_channel *c;
|
|
|
|
u_int8_t *cur_runlen, *cur_chan, *cur_pow, prevchan;
|
|
|
|
|
|
|
|
/* Fill in country IE. */
|
|
|
|
memset(&ic->ic_country_ie, 0, sizeof(ic->ic_country_ie));
|
|
|
|
ic->ic_country_ie.country_id = IEEE80211_ELEMID_COUNTRY;
|
|
|
|
|
|
|
|
/* Initialize country IE */
|
|
|
|
found = 0;
|
2007-11-28 08:36:29 +03:00
|
|
|
for (i = 0; i < ARRAY_SIZE(country_strings); i++) {
|
2007-10-04 17:07:51 +04:00
|
|
|
if (country_strings[i].iso_code == ic->ic_country_code) {
|
|
|
|
ic->ic_country_ie.country_str[0] = country_strings[i].iso_name[0];
|
|
|
|
ic->ic_country_ie.country_str[1] = country_strings[i].iso_name[1];
|
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
if_printf(dev, "bad country string ignored: %d\n",
|
|
|
|
ic->ic_country_code);
|
|
|
|
ic->ic_country_ie.country_str[0] = ' ';
|
|
|
|
ic->ic_country_ie.country_str[1] = ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Indoor/Outdoor portion if country string.
|
|
|
|
* NB: this is not quite right, since we should have one of:
|
|
|
|
* 'I': indoor only
|
|
|
|
* 'O': outdoor only
|
2011-08-26 19:31:01 +04:00
|
|
|
* ' ': all environments
|
2007-10-04 17:07:51 +04:00
|
|
|
* we currently can only provide 'I' or ' '.
|
|
|
|
*/
|
|
|
|
ic->ic_country_ie.country_str[2] = 'I';
|
|
|
|
if (ic->ic_country_outdoor)
|
|
|
|
ic->ic_country_ie.country_str[2] = ' ';
|
|
|
|
/* Runlength encoded channel max. TX power info. */
|
|
|
|
cur_runlen = &ic->ic_country_ie.country_triplet[1];
|
|
|
|
cur_chan = &ic->ic_country_ie.country_triplet[0];
|
|
|
|
cur_pow = &ic->ic_country_ie.country_triplet[2];
|
|
|
|
prevchan = 0;
|
|
|
|
ic->ic_country_ie.country_len = 3; /* invalid, but just initialize */
|
|
|
|
|
|
|
|
if ((ic->ic_flags_ext & IEEE80211_FEXT_REGCLASS) && ic->ic_nregclass) {
|
|
|
|
/* Add regulatory triplets.
|
|
|
|
* chan/no_of_chans/tx power triplet is overridden as
|
|
|
|
* as follows:
|
|
|
|
* cur_chan == REGULATORY EXTENSION ID.
|
|
|
|
* cur_runlen = Regulatory class.
|
|
|
|
* cur_pow = coverage class.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < ic->ic_nregclass; i++) {
|
|
|
|
*cur_chan = IEEE80211_REG_EXT_ID;
|
|
|
|
*cur_runlen = ic->ic_regclassids[i];
|
|
|
|
*cur_pow = ic->ic_coverageclass;
|
|
|
|
|
|
|
|
cur_runlen += 3;
|
|
|
|
cur_chan += 3;
|
|
|
|
cur_pow += 3;
|
|
|
|
ic->ic_country_ie.country_len += 3;
|
|
|
|
}
|
|
|
|
} else {
|
2008-01-26 23:48:11 +03:00
|
|
|
u_int16_t curmode_noturbo = ic->ic_curmode;
|
|
|
|
/* advertise only non-turbo channels */
|
|
|
|
/* XXX: shouldn't turbo channels be included as well? */
|
|
|
|
switch (curmode_noturbo) {
|
|
|
|
case IEEE80211_MODE_TURBO_A:
|
|
|
|
curmode_noturbo = IEEE80211_MODE_11A;
|
|
|
|
break;
|
|
|
|
case IEEE80211_MODE_TURBO_G:
|
|
|
|
curmode_noturbo = IEEE80211_MODE_11G;
|
|
|
|
break;
|
|
|
|
}
|
2007-10-04 17:07:51 +04:00
|
|
|
|
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
|
|
|
|
|
|
|
/* Does channel belong to current operation mode */
|
2008-01-26 23:48:11 +03:00
|
|
|
if (ieee80211_chan2mode(c) != curmode_noturbo)
|
2007-10-04 17:07:51 +04:00
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Skip half/quarter rate channels */
|
|
|
|
if (IEEE80211_IS_CHAN_HALF(c) ||
|
|
|
|
IEEE80211_IS_CHAN_QUARTER(c))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (*cur_runlen == 0) {
|
|
|
|
(*cur_runlen)++;
|
|
|
|
*cur_pow = c->ic_maxregpower;
|
|
|
|
*cur_chan = c->ic_ieee;
|
|
|
|
prevchan = c->ic_ieee;
|
|
|
|
ic->ic_country_ie.country_len += 3;
|
|
|
|
} else if (*cur_pow == c->ic_maxregpower &&
|
2008-01-26 23:48:11 +03:00
|
|
|
c->ic_ieee == prevchan +
|
|
|
|
(IEEE80211_IS_CHAN_5GHZ(c) ? 4 : 1)) {
|
2007-10-04 17:07:51 +04:00
|
|
|
(*cur_runlen)++;
|
|
|
|
prevchan = c->ic_ieee;
|
|
|
|
} else {
|
|
|
|
cur_runlen +=3;
|
|
|
|
cur_chan += 3;
|
|
|
|
cur_pow += 3;
|
|
|
|
(*cur_runlen)++;
|
|
|
|
*cur_pow = c->ic_maxregpower;
|
|
|
|
*cur_chan = c->ic_ieee;
|
|
|
|
prevchan = c->ic_ieee;
|
|
|
|
ic->ic_country_ie.country_len += 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Pad */
|
|
|
|
if (ic->ic_country_ie.country_len & 1)
|
|
|
|
ic->ic_country_ie.country_len++;
|
2008-01-26 23:48:11 +03:00
|
|
|
}
|
2007-10-04 17:07:51 +04:00
|
|
|
|
2008-01-26 23:48:11 +03:00
|
|
|
void
|
|
|
|
ieee80211_build_sc_ie(struct ieee80211com *ic)
|
|
|
|
{
|
|
|
|
struct ieee80211_ie_sc *ie = &ic->ic_sc_ie;
|
|
|
|
int i, j;
|
|
|
|
struct ieee80211_channel *c;
|
|
|
|
u_int8_t prevchan;
|
|
|
|
|
2008-01-27 08:48:23 +03:00
|
|
|
/* Fill in Supported Channels IE. */
|
2008-01-26 23:48:11 +03:00
|
|
|
memset(ie, 0, sizeof(*ie));
|
|
|
|
ie->sc_id = IEEE80211_ELEMID_SUPPCHAN;
|
|
|
|
|
|
|
|
prevchan = 0;
|
|
|
|
|
|
|
|
j = 0;
|
|
|
|
for (i = 0; i < ic->ic_nchans; i++) {
|
|
|
|
c = &ic->ic_channels[i];
|
|
|
|
|
|
|
|
/* Skip disabled channels */
|
|
|
|
if (isclr(ic->ic_chan_active, c->ic_ieee))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* XXX Skip turbo channels */
|
|
|
|
if (IEEE80211_IS_CHAN_TURBO(c))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Skip half/quarter rate channels */
|
|
|
|
if (IEEE80211_IS_CHAN_HALF(c) ||
|
|
|
|
IEEE80211_IS_CHAN_QUARTER(c))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Skip duplicate frequencies (separate b/g channels) */
|
|
|
|
if (c->ic_ieee == prevchan)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (ie->sc_subband[j].sc_number == 0) {
|
|
|
|
ie->sc_subband[j].sc_first = c->ic_ieee;
|
|
|
|
} else if (c->ic_ieee != prevchan +
|
|
|
|
/* XXX: see 802.11d-2001-4-05-03-interp,
|
|
|
|
* but what about .11j, turbo, etc.? */
|
|
|
|
(IEEE80211_IS_CHAN_5GHZ(c) ? 4 : 1)) {
|
|
|
|
j++;
|
|
|
|
ie->sc_subband[j].sc_first = c->ic_ieee;
|
|
|
|
}
|
|
|
|
ie->sc_subband[j].sc_number++;
|
|
|
|
prevchan = c->ic_ieee;
|
|
|
|
}
|
|
|
|
ie->sc_len = (j+1) * 2;
|
2007-10-04 17:07:51 +04:00
|
|
|
}
|
2007-11-21 14:16:05 +03:00
|
|
|
|
|
|
|
int ath_debug_global = 0;
|
|
|
|
EXPORT_SYMBOL(ath_debug_global);
|
|
|
|
|