madwifi/net80211/ieee80211_wireless.c

5608 lines
159 KiB
C
Raw Normal View History

/*-
* 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,
* without modification.
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
* redistribution must be conditioned upon including a substantially
* similar Disclaimer requirement for further binary redistribution.
* 3. Neither the names of the above-listed copyright holders nor the names
* of any contributors may 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.
*
* NO WARRANTY
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
*
* $Id$
*/
/*
* Wireless extensions support for 802.11 common code.
*/
#ifndef AUTOCONF_INCLUDED
#include <linux/config.h>
#endif
#include <linux/version.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/utsname.h>
#include <linux/if_arp.h> /* for ARPHRD_ETHER */
#include <linux/delay.h>
#include <linux/wireless.h>
#include <net/iw_handler.h>
#if WIRELESS_EXT < 15
#error "Wireless extensions v15 or better is needed."
#endif
#include <asm/uaccess.h>
#include <net80211/if_media.h>
#include <net80211/if_athproto.h>
#include <net80211/ieee80211_var.h>
#include <net80211/ieee80211_linux.h>
#include "ah.h"
#define IS_UP(_dev) \
(((_dev)->flags & (IFF_RUNNING|IFF_UP)) == (IFF_RUNNING|IFF_UP))
#define IS_UP_AUTO(_vap) \
(IS_UP((_vap)->iv_dev) && \
(_vap)->iv_ic->ic_roaming == IEEE80211_ROAMING_AUTO)
#define RESCAN 1
static void
pre_announced_chanswitch(struct net_device *dev, u_int32_t channel, u_int32_t tbtt);
static int
preempt_scan(struct net_device *dev, int max_grace, int max_wait)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
int total_delay = 0;
int canceled = 0, ready = 0;
while (!ready && total_delay < max_grace + max_wait) {
if ((ic->ic_flags & IEEE80211_F_SCAN) == 0) {
ready = 1;
} else {
if (!canceled && (total_delay > max_grace)) {
/* Cancel any existing active scan, so that any new parameters
* in this scan ioctl (or the defaults) can be honored, then
* wait around a while to see if the scan cancels properly. */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: cancel pending scan request\n", __func__);
(void) ieee80211_cancel_scan(vap);
canceled = 1;
}
mdelay (1);
total_delay += 1;
}
}
if (!ready) {
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: Timeout canceling current scan.\n",
__func__);
}
return ready;
}
static struct iw_statistics *
ieee80211_iw_getstats(struct net_device *dev)
{
struct ieee80211vap *vap = dev->priv;
struct iw_statistics *is = &vap->iv_iwstats;
struct ieee80211com *ic = vap->iv_ic;
set_quality(&is->qual, ieee80211_getrssi(vap->iv_ic),
ic->ic_channoise);
is->status = vap->iv_state;
is->discard.nwid = vap->iv_stats.is_rx_wrongbss +
vap->iv_stats.is_rx_ssidmismatch;
is->discard.code = vap->iv_stats.is_rx_wepfail +
vap->iv_stats.is_rx_decryptcrc;
is->discard.fragment = 0;
is->discard.retries = 0;
is->discard.misc = 0;
is->miss.beacon = 0;
return is;
}
static int
ieee80211_ioctl_giwname(struct net_device *dev, struct iw_request_info *info,
char *name, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211_channel *c = vap->iv_ic->ic_curchan;
if (IEEE80211_IS_CHAN_108G(c))
strncpy(name, "IEEE 802.11Tg", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_108A(c))
strncpy(name, "IEEE 802.11Ta", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_TURBO(c))
strncpy(name, "IEEE 802.11T", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_ANYG(c))
strncpy(name, "IEEE 802.11g", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_A(c))
strncpy(name, "IEEE 802.11a", IFNAMSIZ);
else if (IEEE80211_IS_CHAN_B(c))
strncpy(name, "IEEE 802.11b", IFNAMSIZ);
else
strncpy(name, "IEEE 802.11", IFNAMSIZ);
/* XXX FHSS */
return 0;
}
/*
* Get a key index from a request. If nothing is
* specified in the request we use the current xmit
* key index. Otherwise we just convert the index
* to be base zero.
*/
static int
getiwkeyix(struct ieee80211vap *vap, const struct iw_point* erq, ieee80211_keyix_t *rkix)
{
ieee80211_keyix_t kix;
kix = erq->flags & IW_ENCODE_INDEX;
if ((erq->flags & IW_ENCODE_INDEX) == (u_int8_t)IEEE80211_KEYIX_NONE)
kix = IEEE80211_KEYIX_NONE;
if (kix < 1 || kix > IEEE80211_WEP_NKID) {
kix = vap->iv_def_txkey;
if (kix == IEEE80211_KEYIX_NONE)
kix = 0;
} else
--kix;
if (kix < IEEE80211_WEP_NKID) {
*rkix = kix;
return 0;
} else
return -EINVAL;
}
static int
ieee80211_ioctl_siwencode(struct net_device *dev,
struct iw_request_info *info, struct iw_point *erq, char *keybuf)
{
struct ieee80211vap *vap = dev->priv;
int error;
int wepchange = 0;
ieee80211_keyix_t kix;
if ((erq->flags & IW_ENCODE_DISABLED) == 0) {
/* Check WEP support, load WEP module if needed */
if (!ieee80211_crypto_available(vap, IEEE80211_CIPHER_WEP))
return -EOPNOTSUPP;
/*
* Enable crypto, set key contents, and
* set the default transmit key.
*/
error = getiwkeyix(vap, erq, &kix);
if (error < 0)
return error;
if (erq->length > IEEE80211_KEYBUF_SIZE)
return -E2BIG;
/* XXX no way to install 0-length key */
ieee80211_key_update_begin(vap);
if (erq->length > 0) {
struct ieee80211_key *k = &vap->iv_nw_keys[kix];
/*
* Set key contents. This interface only supports WEP.
* Indicate intended key index.
*/
k->wk_keyix = kix;
if (ieee80211_crypto_newkey(vap, IEEE80211_CIPHER_WEP,
IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV, k)) {
k->wk_keylen = erq->length;
memcpy(k->wk_key, keybuf, erq->length);
memset(k->wk_key + erq->length, 0,
IEEE80211_KEYBUF_SIZE - erq->length);
if (!ieee80211_crypto_setkey(vap, k, vap->iv_myaddr, NULL))
error = -EINVAL; /* XXX */
} else
error = -EINVAL;
} else {
/*
* When the length is zero the request only changes
* the default transmit key. Verify the new key has
* a non-zero length.
*/
if (vap->iv_nw_keys[kix].wk_keylen == 0)
error = -EINVAL;
}
if (error == 0) {
/*
* The default transmit key is only changed when:
* 1. Privacy is enabled and no key matter is
* specified.
* 2. Privacy is currently disabled.
* This is deduced from the iwconfig man page.
*/
if (erq->length == 0 ||
(vap->iv_flags & IEEE80211_F_PRIVACY) == 0)
vap->iv_def_txkey = kix;
wepchange = (vap->iv_flags & IEEE80211_F_PRIVACY) == 0;
vap->iv_flags |= IEEE80211_F_PRIVACY;
}
ieee80211_key_update_end(vap);
} else {
if ((vap->iv_flags & IEEE80211_F_PRIVACY) == 0)
return 0;
vap->iv_flags &= ~IEEE80211_F_PRIVACY;
wepchange = 1;
error = 0;
}
if (error == 0) {
/* Set policy for unencrypted frames */
if ((erq->flags & IW_ENCODE_OPEN) &&
(!(erq->flags & IW_ENCODE_RESTRICTED))) {
vap->iv_flags &= ~IEEE80211_F_DROPUNENC;
} else if (!(erq->flags & IW_ENCODE_OPEN) &&
(erq->flags & IW_ENCODE_RESTRICTED)) {
vap->iv_flags |= IEEE80211_F_DROPUNENC;
} else {
/* Default policy */
if (vap->iv_flags & IEEE80211_F_PRIVACY)
vap->iv_flags |= IEEE80211_F_DROPUNENC;
else
vap->iv_flags &= ~IEEE80211_F_DROPUNENC;
}
}
if ((error == 0) && IS_UP(vap->iv_dev)) {
/*
* Device is up and running; we must kick it to
* effect the change. If we're enabling/disabling
* crypto use then we must re-initialize the device
* so the 802.11 state machine is reset. Otherwise
* the key state should have been updated above.
*/
if (wepchange && IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
#ifdef ATH_SUPERG_XR
/* set the same params on the xr vap device if exists */
if (!error && vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR))
ieee80211_ioctl_siwencode(vap->iv_xrvap->iv_dev, info, erq, keybuf);
#endif
return error;
}
static int
ieee80211_ioctl_giwencode(struct net_device *dev, struct iw_request_info *info,
struct iw_point *erq, char *key)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211_key *k;
int error;
ieee80211_keyix_t kix;
if (vap->iv_flags & IEEE80211_F_PRIVACY) {
error = getiwkeyix(vap, erq, &kix);
if (error < 0)
return error;
k = &vap->iv_nw_keys[kix];
/* XXX no way to return cipher/key type */
erq->flags = kix + 1; /* NB: base 1 */
if (erq->length > k->wk_keylen)
erq->length = k->wk_keylen;
memcpy(key, k->wk_key, erq->length);
erq->flags |= IW_ENCODE_ENABLED;
} else {
erq->length = 0;
erq->flags = IW_ENCODE_DISABLED;
}
if (vap->iv_flags & IEEE80211_F_DROPUNENC)
erq->flags |= IW_ENCODE_RESTRICTED;
else
erq->flags |= IW_ENCODE_OPEN;
return 0;
}
#ifndef ifr_media
#define ifr_media ifr_ifru.ifru_ivalue
#endif
static int
ieee80211_ioctl_siwrate(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
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,
};
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ifreq ifr;
int rate, retv;
if (vap->iv_media.ifm_cur == NULL)
return -EINVAL;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_media = vap->iv_media.ifm_cur->ifm_media &~ (IFM_MMASK|IFM_TMASK);
ifr.ifr_media |= mopts[vap->iv_des_mode];
if (rrq->fixed) {
/* XXX fudge checking rates */
rate = ieee80211_rate2media(ic, 2 * rrq->value / 1000000,
vap->iv_des_mode);
if (rate == IFM_AUTO) /* NB: unknown rate */
return -EINVAL;
} else
rate = IFM_AUTO;
ifr.ifr_media |= IFM_SUBTYPE(rate);
/* refresh media capabilities based on channel */
ifmedia_removeall(&vap->iv_media);
(void) ieee80211_media_setup(ic, &vap->iv_media,
vap->iv_caps, vap->iv_media.ifm_change, vap->iv_media.ifm_status);
retv = ifmedia_ioctl(vap->iv_dev, &ifr, &vap->iv_media, SIOCSIFMEDIA);
if (retv == -ENETRESET)
retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0;
return retv;
}
static int
ieee80211_ioctl_giwrate(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ifmediareq imr;
int rate;
memset(&imr, 0, sizeof(imr));
vap->iv_media.ifm_status(vap->iv_dev, &imr);
rrq->fixed = IFM_SUBTYPE(vap->iv_media.ifm_media) != IFM_AUTO;
/* media status will have the current xmit rate if available */
rate = ieee80211_media2rate(imr.ifm_active);
if (rate == -1) /* IFM_AUTO */
rate = 0;
rrq->value = 1000000 * (rate / 2);
return 0;
}
static int
ieee80211_ioctl_siwsens(struct net_device *dev, struct iw_request_info *info,
struct iw_param *sens, char *extra)
{
return -EOPNOTSUPP;
}
static int
ieee80211_ioctl_giwsens(struct net_device *dev, struct iw_request_info *info,
struct iw_param *sens, char *extra)
{
sens->value = 1;
sens->fixed = 1;
return 0;
}
static int
ieee80211_ioctl_siwrts(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
u16 val;
if (rts->disabled)
val = IEEE80211_RTS_MAX;
else if ((IEEE80211_RTS_MIN <= rts->value) &&
(rts->value <= IEEE80211_RTS_MAX))
val = rts->value;
else
return -EINVAL;
if (val != vap->iv_rtsthreshold) {
vap->iv_rtsthreshold = val;
if (IS_UP(vap->iv_dev))
return ic->ic_reset(ic->ic_dev);
}
return 0;
}
static int
ieee80211_ioctl_giwrts(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = dev->priv;
rts->value = vap->iv_rtsthreshold;
rts->disabled = (rts->value == IEEE80211_RTS_MAX);
rts->fixed = 1;
return 0;
}
static int
ieee80211_ioctl_siwfrag(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
u16 val;
if (rts->disabled)
val = 2346;
else if ((rts->value < 256) || (rts->value > 2346))
return -EINVAL;
else
val = (rts->value & ~0x1);
if (val != vap->iv_fragthreshold) {
vap->iv_fragthreshold = val;
if (IS_UP(ic->ic_dev))
return ic->ic_reset(ic->ic_dev);
}
return 0;
}
static int
ieee80211_ioctl_giwfrag(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rts, char *extra)
{
struct ieee80211vap *vap = dev->priv;
rts->value = vap->iv_fragthreshold;
rts->disabled = (rts->value == 2346);
rts->fixed = 1;
return 0;
}
static int
ieee80211_ioctl_siwap(struct net_device *dev, struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct ieee80211vap *vap = dev->priv;
/* NB: should not be set when in AP mode */
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
return -EOPNOTSUPP;
if (vap->iv_opmode == IEEE80211_M_WDS)
IEEE80211_ADDR_COPY(vap->wds_mac, &ap_addr->sa_data);
/*
* zero address corresponds to 'iwconfig ath0 ap off', which means
* enable automatic choice of AP without actually forcing a
* reassociation.
*
* broadcast address corresponds to 'iwconfig ath0 ap any', which
* means scan for the current best AP.
*
* anything else specifies a particular AP.
*/
vap->iv_flags &= ~IEEE80211_F_DESBSSID;
if (!IEEE80211_ADDR_NULL(&ap_addr->sa_data)) {
if (!IEEE80211_ADDR_EQ(vap->iv_des_bssid, (u_int8_t*) "\xff\xff\xff\xff\xff\xff"))
vap->iv_flags |= IEEE80211_F_DESBSSID;
IEEE80211_ADDR_COPY(vap->iv_des_bssid, &ap_addr->sa_data);
if (IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
return 0;
}
static int
ieee80211_ioctl_giwap(struct net_device *dev, struct iw_request_info *info,
struct sockaddr *ap_addr, char *extra)
{
struct ieee80211vap *vap = dev->priv;
if (vap->iv_flags & IEEE80211_F_DESBSSID)
IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->iv_des_bssid);
else {
if (vap->iv_state == IEEE80211_S_RUN)
if (vap->iv_opmode != IEEE80211_M_WDS)
IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->iv_bss->ni_bssid);
else
IEEE80211_ADDR_COPY(&ap_addr->sa_data, vap->wds_mac);
else
IEEE80211_ADDR_SET_NULL(&ap_addr->sa_data);
}
ap_addr->sa_family = ARPHRD_ETHER;
return 0;
}
static int
ieee80211_ioctl_siwnickn(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *nickname)
{
struct ieee80211vap *vap = dev->priv;
if (data->length > IEEE80211_NWID_LEN)
return -E2BIG;
memset(vap->iv_nickname, 0, IEEE80211_NWID_LEN);
memcpy(vap->iv_nickname, nickname, data->length);
vap->iv_nicknamelen = data->length;
return 0;
}
static int
ieee80211_ioctl_giwnickn(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *nickname)
{
struct ieee80211vap *vap = dev->priv;
if (data->length > vap->iv_nicknamelen + 1)
data->length = vap->iv_nicknamelen + 1;
if (data->length > 0) {
memcpy(nickname, vap->iv_nickname, data->length - 1); /* XXX: strcpy? */
nickname[data->length-1] = '\0';
}
return 0;
}
static int
find11gchannel(struct ieee80211com *ic, int i, int freq)
{
for (; i < ic->ic_nchans; i++) {
const struct ieee80211_channel *c = &ic->ic_channels[i];
if (c->ic_freq == freq && IEEE80211_IS_CHAN_ANYG(c))
return 1;
}
return 0;
}
static struct ieee80211_channel *
findchannel(struct ieee80211com *ic, int ieee, int mode)
{
static const u_int chanflags[] = {
0, /* IEEE80211_MODE_AUTO */
IEEE80211_CHAN_A, /* IEEE80211_MODE_11A */
IEEE80211_CHAN_B, /* IEEE80211_MODE_11B */
IEEE80211_CHAN_PUREG, /* IEEE80211_MODE_11G */
IEEE80211_CHAN_FHSS, /* IEEE80211_MODE_FH */
IEEE80211_CHAN_108A, /* IEEE80211_MODE_TURBO_A */
IEEE80211_CHAN_108G, /* IEEE80211_MODE_TURBO_G */
IEEE80211_CHAN_ST, /* IEEE80211_MODE_TURBO_STATIC_A */
};
u_int modeflags;
unsigned int i;
modeflags = chanflags[mode];
for (i = 0; i < ic->ic_nchans; i++) {
struct ieee80211_channel *c = &ic->ic_channels[i];
if (c->ic_ieee != ieee)
continue;
if (mode == IEEE80211_MODE_AUTO) {
/* ignore turbo channels for autoselect */
if (!(ic->ic_ath_cap & IEEE80211_ATHC_TURBOP) &&
IEEE80211_IS_CHAN_TURBO(c))
continue;
/*
* XXX special-case 11b/g channels so we
* always select the g channel if both
* are present.
*/
if (!IEEE80211_IS_CHAN_B(c) ||
!find11gchannel(ic, i + 1, c->ic_freq))
return c;
} else {
if ((c->ic_flags & modeflags) == modeflags)
return c;
}
}
return NULL;
}
#define IEEE80211_MODE_TURBO_STATIC_A IEEE80211_MODE_MAX
static int
ieee80211_check_mode_consistency(struct ieee80211com *ic, int mode,
struct ieee80211_channel *c)
{
if (c == IEEE80211_CHAN_ANYC)
return 0;
switch (mode) {
case IEEE80211_MODE_11B:
if (IEEE80211_IS_CHAN_B(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11G:
if (IEEE80211_IS_CHAN_ANYG(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_11A:
if (IEEE80211_IS_CHAN_A(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_TURBO_STATIC_A:
if (IEEE80211_IS_CHAN_A(c) && IEEE80211_IS_CHAN_STURBO(c))
return 0;
else
return 1;
break;
case IEEE80211_MODE_AUTO:
return 0;
break;
}
return 1;
}
#undef IEEE80211_MODE_TURBO_STATIC_A
static int
ieee80211_ioctl_siwfreq(struct net_device *dev, struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_channel *c, *c2;
int i;
if (freq->e > 1)
return -EINVAL;
if (freq->e == 1)
i = (ic->ic_mhz2ieee)(ic, freq->m / 100000, 0);
else
i = freq->m;
if ((i != 0) && (i != -1)) {
if (i > IEEE80211_CHAN_MAX)
return -EINVAL;
c = findchannel(ic, i, vap->iv_des_mode);
if (c == NULL) {
c = findchannel(ic, i, IEEE80211_MODE_AUTO);
if (c == NULL) /* no channel */
return -EINVAL;
}
/*
* Fine tune channel selection based on desired mode:
* if 11b is requested, find the 11b version of any
* 11g channel returned,
* if static turbo, find the turbo version of any
* 11a channel return,
* otherwise we should be ok with what we've got.
*/
switch (vap->iv_des_mode) {
case IEEE80211_MODE_11B:
if (IEEE80211_IS_CHAN_ANYG(c)) {
c2 = findchannel(ic, i, IEEE80211_MODE_11B);
/* NB: should not happen, =>'s 11g w/o 11b */
if (c2 != NULL)
c = c2;
}
break;
case IEEE80211_MODE_TURBO_A:
if (IEEE80211_IS_CHAN_A(c)) {
c2 = findchannel(ic, i, IEEE80211_MODE_TURBO_A);
if (c2 != NULL)
c = c2;
}
break;
default: /* NB: no static turboG */
break;
}
if (ieee80211_check_mode_consistency(ic, vap->iv_des_mode, c)) {
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
return -EINVAL;
}
if ((vap->iv_state == IEEE80211_S_RUN) && (c == vap->iv_des_chan))
return 0; /* no change, return */
/* Don't allow to change to channel with radar found */
if (c->ic_flags & IEEE80211_CHAN_RADAR)
return -EINVAL;
/*
* Mark desired channel and if running force a
* radio change.
*/
vap->iv_des_chan = c;
} else {
/*
* Intepret channel 0 to mean "no desired channel";
* otherwise there's no way to undo fixing the desired
* channel.
*/
if (vap->iv_des_chan == IEEE80211_CHAN_ANYC)
return 0;
vap->iv_des_chan = IEEE80211_CHAN_ANYC;
}
#if 0
if (vap->iv_des_chan != IEEE80211_CHAN_ANYC) {
int mode = ieee80211_chan2mode(vap->iv_des_chan);
if (mode != ic->ic_curmode)
ieee80211_setmode(ic, mode);
}
#endif
if ((vap->iv_opmode == IEEE80211_M_MONITOR ||
vap->iv_opmode == IEEE80211_M_WDS) &&
vap->iv_des_chan != IEEE80211_CHAN_ANYC) {
/* Monitor and wds modes can switch directly. */
ic->ic_curchan = vap->iv_des_chan;
if (vap->iv_state == IEEE80211_S_RUN) {
ic->ic_set_channel(ic);
}
} else if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
/* Need to use channel switch announcement on beacon if we are
* up and running. We use ic_set_channel directly if we are
* "running" but not "up". Otherwise, iv_des_chan will take
* effect when we are transitioned to RUN state later. */
if (IS_UP(vap->iv_dev) &&
(0 == (vap->iv_des_chan->ic_flags & CHANNEL_DFS))) {
pre_announced_chanswitch(dev, ieee80211_chan2ieee(ic, vap->iv_des_chan),
IEEE80211_DEFAULT_CHANCHANGE_TBTT_COUNT);
}
else if (vap->iv_state == IEEE80211_S_RUN) {
ic->ic_curchan = vap->iv_des_chan;
ic->ic_set_channel(ic);
}
} else {
/* Need to go through the state machine in case we need
* to reassociate or the like. The state machine will
* pickup the desired channel and avoid scanning. */
if (IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
return 0;
}
static int
ieee80211_ioctl_giwfreq(struct net_device *dev, struct iw_request_info *info,
struct iw_freq *freq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
if (vap->iv_state == IEEE80211_S_RUN &&
vap->iv_opmode != IEEE80211_M_MONITOR) {
/*
* NB: use curchan for monitor mode so you can see
* manual scanning by apps like kismet.
*/
KASSERT(ic->ic_bsschan != IEEE80211_CHAN_ANYC,
("bss channel not set"));
freq->m = ic->ic_bsschan->ic_freq;
} else if (vap->iv_state != IEEE80211_S_INIT) /* e.g. when scanning */
freq->m = ic->ic_curchan->ic_freq;
else if (vap->iv_des_chan != IEEE80211_CHAN_ANYC)
freq->m = vap->iv_des_chan->ic_freq;
else
freq->m = 0;
freq->m *= 100000;
freq->e = 1;
return 0;
}
#ifdef ATH_SUPERG_XR
/*
* Copy desired ssid state from one vap to another.
*/
static void
copy_des_ssid(struct ieee80211vap *dst, const struct ieee80211vap *src)
{
dst->iv_des_nssid = src->iv_des_nssid;
memcpy(dst->iv_des_ssid, src->iv_des_ssid,
src->iv_des_nssid * sizeof(src->iv_des_ssid[0]));
}
#endif /* ATH_SUPERG_XR */
static int
ieee80211_ioctl_siwessid(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *ssid)
{
struct ieee80211vap *vap = dev->priv;
if (vap->iv_opmode == IEEE80211_M_WDS)
return -EOPNOTSUPP;
if (data->flags == 0) /* ANY */
vap->iv_des_nssid = 0;
else {
if (data->length > IEEE80211_NWID_LEN)
data->length = IEEE80211_NWID_LEN;
/* NB: always use entry 0 */
memcpy(vap->iv_des_ssid[0].ssid, ssid, data->length);
vap->iv_des_ssid[0].len = data->length;
vap->iv_des_nssid = 1;
#if WIRELESS_EXT < 21
/*
* Deduct a trailing \0 since iwconfig passes a string
* length that includes this. Unfortunately this means
* that specifying a string with multiple trailing \0's
* won't be handled correctly. Not sure there's a good
* solution; the API is botched (the length should be
* exactly those bytes that are meaningful and not include
* extraneous stuff).
*/
/* The API was fixed in WE21 */
if (data->length > 0 &&
vap->iv_des_ssid[0].ssid[data->length - 1] == '\0')
vap->iv_des_ssid[0].len--;
#endif /* WIRELESS_EXT < 21 */
}
#ifdef ATH_SUPERG_XR
if (vap->iv_xrvap != NULL && !(vap->iv_flags & IEEE80211_F_XR)) {
if (data->flags == 0)
vap->iv_des_nssid = 0;
else
copy_des_ssid(vap->iv_xrvap, vap);
}
#endif
return IS_UP_AUTO(vap) ? ieee80211_init(vap->iv_dev, RESCAN) : 0;
}
static int
ieee80211_ioctl_giwessid(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *essid)
{
struct ieee80211vap *vap = dev->priv;
if (vap->iv_opmode == IEEE80211_M_WDS)
return -EOPNOTSUPP;
data->flags = 1; /* active */
if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
if (vap->iv_des_nssid > 0) {
if (data->length > vap->iv_des_ssid[0].len)
data->length = vap->iv_des_ssid[0].len;
memcpy(essid, vap->iv_des_ssid[0].ssid, data->length);
} else
data->length = 0;
} else {
if (vap->iv_des_nssid == 0) {
if (data->length > vap->iv_bss->ni_esslen)
data->length = vap->iv_bss->ni_esslen;
memcpy(essid, vap->iv_bss->ni_essid, data->length);
} else {
if (data->length > vap->iv_des_ssid[0].len)
data->length = vap->iv_des_ssid[0].len;
memcpy(essid, vap->iv_des_ssid[0].ssid, data->length);
}
}
return 0;
}
static int
ieee80211_ioctl_giwrange(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *ni = vap->iv_bss;
struct iw_range *range = (struct iw_range *) extra;
struct ieee80211_rateset *rs;
u_int8_t reported[IEEE80211_CHAN_BYTES]; /* XXX stack usage? */
int i, r;
int step = 0;
data->length = sizeof(struct iw_range);
memset(range, 0, sizeof(struct iw_range));
/* txpower (128 values, but will print out only IW_MAX_TXPOWER) */
range->num_txpower = (ic->ic_txpowlimit >= 8) ? IW_MAX_TXPOWER : ic->ic_txpowlimit;
step = ic->ic_txpowlimit / (2 * (IW_MAX_TXPOWER - 1));
range->txpower[0] = 0;
for (i = 1; i < IW_MAX_TXPOWER; i++)
range->txpower[i] = (ic->ic_txpowlimit/2)
- (IW_MAX_TXPOWER - i - 1) * step;
range->txpower_capa = IW_TXPOW_DBM;
if (vap->iv_opmode == IEEE80211_M_STA ||
vap->iv_opmode == IEEE80211_M_IBSS) {
range->min_pmp = 1 * 1024;
range->max_pmp = 65535 * 1024;
range->min_pmt = 1 * 1024;
range->max_pmt = 1000 * 1024;
range->pmp_flags = IW_POWER_PERIOD;
range->pmt_flags = IW_POWER_TIMEOUT;
range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT |
IW_POWER_UNICAST_R | IW_POWER_ALL_R;
}
range->we_version_compiled = WIRELESS_EXT;
range->we_version_source = 18;
range->retry_capa = IW_RETRY_LIMIT;
range->retry_flags = IW_RETRY_LIMIT;
range->min_retry = 0;
range->max_retry = 255;
range->num_channels = ic->ic_nchans;
range->num_frequency = 0;
memset(reported, 0, sizeof(reported));
for (i = 0; i < ic->ic_nchans; i++) {
const struct ieee80211_channel *c = &ic->ic_channels[i];
/* discard if previously reported (e.g. b/g) */
if (isclr(reported, c->ic_ieee)) {
setbit(reported, c->ic_ieee);
range->freq[range->num_frequency].i = c->ic_ieee;
range->freq[range->num_frequency].m =
ic->ic_channels[i].ic_freq * 100000;
range->freq[range->num_frequency].e = 1;
if (++range->num_frequency == IW_MAX_FREQUENCIES)
break;
}
}
/* Atheros' RSSI value is SNR: 0 -> 60 for old chipsets. Range
* for newer chipsets is unknown. This value is arbitarily chosen
* to give an indication that full rate will be available and to be
* a practicable maximum. */
range->max_qual.qual = 70;
#if WIRELESS_EXT >= 19
/* XXX: This should be updated to use the current noise floor. */
/* These are negative full bytes.
* Min. quality is noise + 1 */
range->max_qual.updated |= IW_QUAL_DBM;
range->max_qual.level = ATH_DEFAULT_NOISE + 1;
range->max_qual.noise = ATH_DEFAULT_NOISE;
#else
/* Values larger than the maximum are assumed to be absolute */
range->max_qual.level = 0;
range->max_qual.noise = 0;
#endif
range->sensitivity = 1;
range->max_encoding_tokens = IEEE80211_WEP_NKID;
/* XXX query driver to find out supported key sizes */
range->num_encoding_sizes = 3;
range->encoding_size[0] = 5; /* 40-bit */
range->encoding_size[1] = 13; /* 104-bit */
range->encoding_size[2] = 16; /* 128-bit */
/* XXX this only works for station mode */
rs = &ni->ni_rates;
range->num_bitrates = rs->rs_nrates;
if (range->num_bitrates > IW_MAX_BITRATES)
range->num_bitrates = IW_MAX_BITRATES;
for (i = 0; i < range->num_bitrates; i++) {
r = rs->rs_rates[i] & IEEE80211_RATE_VAL;
range->bitrate[i] = (r * 1000000) / 2;
}
/* estimated maximum TCP throughput values (bps) */
range->throughput = 5500000;
range->min_rts = 0;
range->max_rts = 2347;
range->min_frag = 256;
range->max_frag = 2346;
#if WIRELESS_EXT >= 17
/* Event capability (kernel) */
IW_EVENT_CAPA_SET_KERNEL(range->event_capa);
/* Event capability (driver) */
if (vap->iv_opmode == IEEE80211_M_STA ||
vap->iv_opmode == IEEE80211_M_IBSS ||
vap->iv_opmode == IEEE80211_M_AHDEMO) {
/* for now, only ibss, ahdemo, sta has this cap */
IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN);
}
if (vap->iv_opmode == IEEE80211_M_STA) {
/* for sta only */
IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP);
IW_EVENT_CAPA_SET(range->event_capa, IWEVREGISTERED);
IW_EVENT_CAPA_SET(range->event_capa, IWEVEXPIRED);
}
/* this is used for reporting replay failure, which is used by the different encoding schemes */
IW_EVENT_CAPA_SET(range->event_capa, IWEVCUSTOM);
#endif
#if WIRELESS_EXT >= 18
/* report supported WPA/WPA2 capabilities to userspace */
range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 |
IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP;
#endif
return 0;
}
static int
ieee80211_ioctl_setspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
/* save the list of node addresses */
struct ieee80211vap *vap = dev->priv;
struct sockaddr address[IW_MAX_SPY];
unsigned int number = data->length;
int i;
if (number > IW_MAX_SPY)
return -E2BIG;
/* get the addresses into the driver */
if (data->pointer) {
if (copy_from_user(address, data->pointer,
sizeof(struct sockaddr) * number))
return -EFAULT;
} else
return -EINVAL;
/* copy the MAC addresses into a list */
if (number > 0) {
/* extract the MAC addresses */
for (i = 0; i < number; i++)
memcpy(&vap->iv_spy.mac[i * IEEE80211_ADDR_LEN],
address[i].sa_data, IEEE80211_ADDR_LEN);
/* init rssi timestamps to 0 */
memset(vap->iv_spy.ts_rssi, 0, sizeof(vap->iv_spy.ts_rssi));
}
vap->iv_spy.num = number;
return 0;
}
static int
ieee80211_ioctl_getspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
/*
* locate nodes by mac (ieee80211_find_node()),
* copy out rssi, set updated flag appropriately
*/
struct ieee80211vap *vap = dev->priv;
struct ieee80211_node_table *nt = &vap->iv_ic->ic_sta;
struct ieee80211_node *ni;
struct ieee80211com *ic = vap->iv_ic;
struct sockaddr *address;
struct iw_quality *spy_stat;
unsigned int number = vap->iv_spy.num;
int i;
address = (struct sockaddr *) extra;
spy_stat = (struct iw_quality *) (extra + number * sizeof(struct sockaddr));
for (i = 0; i < number; i++) {
memcpy(address[i].sa_data, &vap->iv_spy.mac[i * IEEE80211_ADDR_LEN],
IEEE80211_ADDR_LEN);
address[i].sa_family = AF_PACKET;
}
/* locate a node, read its rssi, check if updated, convert to dBm */
for (i = 0; i < number; i++) {
ni = ieee80211_find_node(nt, &vap->iv_spy.mac[i * IEEE80211_ADDR_LEN]);
/* check we are associated w/ this vap */
if (ni) {
if (ni->ni_vap == vap) {
set_quality(&spy_stat[i], ni->ni_rssi, ic->ic_channoise);
if (ni->ni_rtsf != vap->iv_spy.ts_rssi[i]) {
vap->iv_spy.ts_rssi[i] = ni->ni_rtsf;
} else {
spy_stat[i].updated = 0;
}
}
ieee80211_unref_node(&ni);
} else {
spy_stat[i].updated = IW_QUAL_ALL_INVALID;
}
}
/* copy results to userspace */
data->length = number;
return 0;
}
#if WIRELESS_EXT >= 16
/* Enhanced iwspy support */
static int
ieee80211_ioctl_setthrspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct iw_thrspy threshold;
if (data->length != 1)
return -EINVAL;
/* get the threshold values into the driver */
if (data->pointer) {
if (copy_from_user(&threshold, data->pointer,
sizeof(struct iw_thrspy)))
return -EFAULT;
} else
return -EINVAL;
if (threshold.low.level == 0) {
/* disable threshold */
vap->iv_spy.thr_low = 0;
vap->iv_spy.thr_high = 0;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG,
"%s: disabled iw_spy threshold\n", __func__);
} else {
/* We are passed a signal level/strength - calculate
* corresponding RSSI values */
/* XXX: We should use current noise value. */
vap->iv_spy.thr_low = threshold.low.level + ATH_DEFAULT_NOISE;
vap->iv_spy.thr_high = threshold.high.level + ATH_DEFAULT_NOISE;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG,
"%s: enabled iw_spy threshold\n", __func__);
}
return 0;
}
static int
ieee80211_ioctl_getthrspy(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct iw_thrspy *threshold;
threshold = (struct iw_thrspy *) extra;
/* set threshold values */
set_quality(&(threshold->low), vap->iv_spy.thr_low, ic->ic_channoise);
set_quality(&(threshold->high), vap->iv_spy.thr_high, ic->ic_channoise);
/* copy results to userspace */
data->length = 1;
return 0;
}
#endif
static int
ieee80211_ioctl_siwmode(struct net_device *dev, struct iw_request_info *info,
__u32 *mode, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ifmediareq imr;
int valid = 0;
memset(&imr, 0, sizeof(imr));
vap->iv_media.ifm_status(vap->iv_dev, &imr);
if (imr.ifm_active & IFM_IEEE80211_HOSTAP)
valid = (*mode == IW_MODE_MASTER);
else if (imr.ifm_active & IFM_IEEE80211_MONITOR)
valid = (*mode == IW_MODE_MONITOR);
else if (imr.ifm_active & IFM_IEEE80211_ADHOC)
valid = (*mode == IW_MODE_ADHOC);
else if (imr.ifm_active & IFM_IEEE80211_WDS)
valid = (*mode == IW_MODE_REPEAT);
else
valid = (*mode == IW_MODE_INFRA);
return valid ? 0 : -EINVAL;
}
static int
ieee80211_ioctl_giwmode(struct net_device *dev, struct iw_request_info *info,
__u32 *mode, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ifmediareq imr;
memset(&imr, 0, sizeof(imr));
vap->iv_media.ifm_status(vap->iv_dev, &imr);
if (imr.ifm_active & IFM_IEEE80211_HOSTAP)
*mode = IW_MODE_MASTER;
else if (imr.ifm_active & IFM_IEEE80211_MONITOR)
*mode = IW_MODE_MONITOR;
else if (imr.ifm_active & IFM_IEEE80211_ADHOC)
*mode = IW_MODE_ADHOC;
else if (imr.ifm_active & IFM_IEEE80211_WDS)
*mode = IW_MODE_REPEAT;
else
*mode = IW_MODE_INFRA;
return 0;
}
static int
ieee80211_ioctl_siwpower(struct net_device *dev, struct iw_request_info *info,
struct iw_param *wrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
/* XXX: These values, flags, and caps do not seem to be used elsewhere
* at all? */
if ((ic->ic_caps & IEEE80211_C_PMGT) == 0)
return -EOPNOTSUPP;
if (wrq->disabled) {
ic->ic_flags &= ~IEEE80211_F_PMGTON;
} else {
switch (wrq->flags & IW_POWER_MODE) {
case IW_POWER_UNICAST_R:
case IW_POWER_ALL_R:
case IW_POWER_ON:
if (wrq->flags & IW_POWER_PERIOD) {
if (IEEE80211_BINTVAL_VALID(wrq->value))
ic->ic_lintval = IEEE80211_MS_TO_TU(wrq->value);
else
return -EINVAL;
}
if (wrq->flags & IW_POWER_TIMEOUT)
ic->ic_holdover = IEEE80211_MS_TO_TU(wrq->value);
ic->ic_flags |= IEEE80211_F_PMGTON;
break;
default:
return -EINVAL;
}
}
return IS_UP(ic->ic_dev) ? ic->ic_reset(ic->ic_dev) : 0;
}
static int
ieee80211_ioctl_giwpower(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
rrq->disabled = (ic->ic_flags & IEEE80211_F_PMGTON) == 0;
if (!rrq->disabled) {
switch (rrq->flags & IW_POWER_TYPE) {
case IW_POWER_TIMEOUT:
rrq->flags = IW_POWER_TIMEOUT;
rrq->value = IEEE80211_TU_TO_MS(ic->ic_holdover);
break;
case IW_POWER_PERIOD:
rrq->flags = IW_POWER_PERIOD;
rrq->value = IEEE80211_TU_TO_MS(ic->ic_lintval);
break;
}
rrq->flags |= IW_POWER_ALL_R;
}
return 0;
}
static int
ieee80211_ioctl_siwretry(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
if (rrq->disabled) {
if (vap->iv_flags & IEEE80211_F_SWRETRY) {
vap->iv_flags &= ~IEEE80211_F_SWRETRY;
goto done;
}
return 0;
}
if ((vap->iv_caps & IEEE80211_C_SWRETRY) == 0)
return -EOPNOTSUPP;
if (rrq->flags == IW_RETRY_LIMIT) {
if (rrq->value >= 0) {
vap->iv_txmin = rrq->value;
vap->iv_txmax = rrq->value; /* XXX */
vap->iv_txlifetime = 0; /* XXX */
vap->iv_flags |= IEEE80211_F_SWRETRY;
} else {
vap->iv_flags &= ~IEEE80211_F_SWRETRY;
}
return 0;
}
done:
return IS_UP(vap->iv_dev) ? ic->ic_reset(vap->iv_dev) : 0;
}
static int
ieee80211_ioctl_giwretry(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
rrq->disabled = (vap->iv_flags & IEEE80211_F_SWRETRY) == 0;
if (!rrq->disabled) {
switch (rrq->flags & IW_RETRY_TYPE) {
case IW_RETRY_LIFETIME:
rrq->flags = IW_RETRY_LIFETIME;
rrq->value = IEEE80211_TU_TO_MS(vap->iv_txlifetime);
break;
case IW_RETRY_LIMIT:
rrq->flags = IW_RETRY_LIMIT;
switch (rrq->flags & IW_RETRY_MODIFIER) {
case IW_RETRY_MIN:
rrq->flags |= IW_RETRY_MAX;
rrq->value = vap->iv_txmin;
break;
case IW_RETRY_MAX:
rrq->flags |= IW_RETRY_MAX;
rrq->value = vap->iv_txmax;
break;
}
break;
}
}
return 0;
}
static int
ieee80211_ioctl_siwtxpow(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
int fixed, disabled;
fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED);
disabled = (fixed && vap->iv_bss->ni_txpower == 0);
if (rrq->disabled) {
if (!disabled) {
if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0)
return -EOPNOTSUPP;
ic->ic_flags |= IEEE80211_F_TXPOW_FIXED;
vap->iv_bss->ni_txpower = 0;
goto done;
}
return 0;
}
if (rrq->fixed) {
if ((ic->ic_caps & IEEE80211_C_TXPMGT) == 0)
return -EOPNOTSUPP;
if (rrq->flags != IW_TXPOW_DBM)
return -EINVAL;
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC) {
if ((ic->ic_bsschan->ic_maxregpower >= rrq->value) &&
(ic->ic_txpowlimit/2 >= rrq->value)) {
vap->iv_bss->ni_txpower = 2 * rrq->value;
ic->ic_newtxpowlimit = 2 * rrq->value;
ic->ic_flags |= IEEE80211_F_TXPOW_FIXED;
} else
return -EINVAL;
} else {
/*
* No channel set yet
*/
if (ic->ic_txpowlimit/2 >= rrq->value) {
vap->iv_bss->ni_txpower = 2 * rrq->value;
ic->ic_newtxpowlimit = 2 * rrq->value;
ic->ic_flags |= IEEE80211_F_TXPOW_FIXED;
}
else
return -EINVAL;
}
} else {
if (!fixed) /* no change */
return 0;
ic->ic_newtxpowlimit = IEEE80211_TXPOWER_MAX;
ic->ic_flags &= ~IEEE80211_F_TXPOW_FIXED;
}
done:
return IS_UP(ic->ic_dev) ? ic->ic_reset(ic->ic_dev) : 0;
}
static int
ieee80211_get_txcont(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
params[0] = ic->ic_get_txcont(ic);
return 0;
}
static int
ieee80211_get_txcont_rate(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
params[0] = ic->ic_get_txcont_rate(ic);
return 0;
}
static int
ieee80211_set_txcont(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
ic->ic_set_txcont(ic, params[1]);
return 0;
}
static int
ieee80211_set_txcont_rate(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
ic->ic_set_txcont_rate(ic, params[1]);
return 0;
}
static int
ieee80211_set_txcont_power(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
ic->ic_set_txcont_power(ic, params[1]);
return 0;
}
static int
ieee80211_get_txcont_power(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
params[0] = ic->ic_get_txcont_power(ic);
return 0;
}
static int
ieee80211_ioctl_giwtxpow(struct net_device *dev, struct iw_request_info *info,
struct iw_param *rrq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
rrq->value = vap->iv_bss->ni_txpower / 2;
rrq->fixed = (ic->ic_flags & IEEE80211_F_TXPOW_FIXED) != 0;
rrq->disabled = (rrq->fixed && rrq->value == 0);
rrq->flags = IW_TXPOW_DBM;
return 0;
}
#ifdef ATH_REVERSE_ENGINEERING
static int
ieee80211_dump_registers(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
unsigned int *params = (unsigned int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
switch (params[1]) {
case 2:
ic->ic_registers_mark(ic);
break;
case 1:
ic->ic_registers_dump_delta(ic);
break;
case 0:
default:
ic->ic_registers_dump(ic);
break;
}
return 0;
}
#endif /* #ifdef ATH_REVERSE_ENGINEERING */
#ifdef ATH_REVERSE_ENGINEERING
static int
ieee80211_ioctl_writereg(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
unsigned int *params = (unsigned int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
return ic->ic_write_register(ic, params[0], params[1]);
}
#endif /* #ifdef ATH_REVERSE_ENGINEERING */
#ifdef ATH_REVERSE_ENGINEERING
static int
ieee80211_ioctl_readreg(struct net_device *dev, struct iw_request_info *info, void *w, char *extra)
{
unsigned int *params = (unsigned int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
return ic->ic_read_register(ic, params[0], &params[0]);
}
#endif /* #ifdef ATH_REVERSE_ENGINEERING */
struct waplistreq { /* XXX: not the right place for declaration? */
struct ieee80211vap *vap;
struct sockaddr addr[IW_MAX_AP];
struct iw_quality qual[IW_MAX_AP];
int i;
};
static int
ieee80211_ioctl_hal_map(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
int *params = (int*) extra;
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
params[0] = ic->ic_dump_hal_map(ic);
return 0;
}
static int
waplist_cb(void *arg, const struct ieee80211_scan_entry *se)
{
struct waplistreq *req = arg;
int i = req->i;
if (i >= IW_MAX_AP)
return 0;
req->addr[i].sa_family = ARPHRD_ETHER;
if (req->vap->iv_opmode == IEEE80211_M_HOSTAP)
IEEE80211_ADDR_COPY(req->addr[i].sa_data, se->se_macaddr);
else
IEEE80211_ADDR_COPY(req->addr[i].sa_data, se->se_bssid);
set_quality(&req->qual[i], se->se_rssi, ATH_DEFAULT_NOISE);
req->i++;
return 0;
}
static int
ieee80211_ioctl_iwaplist(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct waplistreq req; /* XXX off stack */
req.vap = vap;
req.i = 0;
ieee80211_scan_iterate(ic, waplist_cb, &req);
data->length = req.i;
memcpy(extra, &req.addr, req.i * sizeof(req.addr[0]));
data->flags = 1; /* signal quality present (sort of) */
memcpy(extra + req.i * sizeof(req.addr[0]), &req.qual,
req.i * sizeof(req.qual[0]));
return 0;
}
#ifdef SIOCGIWSCAN
static int
ieee80211_ioctl_siwscan(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
/*
* XXX don't permit a scan to be started unless we
* know the device is ready. For the moment this means
* the device is marked up as this is the required to
* initialize the hardware. It would be better to permit
* scanning prior to being up but that'll require some
* changes to the infrastructure.
*/
if (!IS_UP(vap->iv_dev))
return -ENETDOWN;
/* XXX always manual... */
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: active scan request\n", __func__);
preempt_scan(dev, 100, 100);
#if WIRELESS_EXT > 17
if (data && (data->flags & IW_SCAN_THIS_ESSID)) {
struct iw_scan_req req;
struct ieee80211_scan_ssid ssid;
int copyLength;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: SCAN_THIS_ESSID requested\n", __func__);
if (data->length > sizeof req) {
copyLength = sizeof req;
} else {
copyLength = data->length;
}
memset(&req, 0, sizeof req);
if (copy_from_user(&req, data->pointer, copyLength))
return -EFAULT;
memcpy(&ssid.ssid, req.essid, sizeof ssid.ssid);
ssid.len = req.essid_len;
IEEE80211_DPRINTF(vap, IEEE80211_MSG_SCAN,
"%s: requesting scan of essid '%s'\n", __func__, ssid.ssid);
(void) ieee80211_start_scan(vap,
IEEE80211_SCAN_ACTIVE |
IEEE80211_SCAN_NOPICK |
IEEE80211_SCAN_ONCE, IEEE80211_SCAN_FOREVER,
1, &ssid);
return 0;
}
#endif
(void) ieee80211_start_scan(vap, IEEE80211_SCAN_ACTIVE |
IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_ONCE,
IEEE80211_SCAN_FOREVER,
/* XXX use ioctl params */
vap->iv_des_nssid, vap->iv_des_ssid);
return 0;
}
/*
* Encode a WPA or RSN information element as a custom
* element using the hostap format.
*/
static u_int
encode_ie(void *buf, size_t bufsize, const u_int8_t *ie, size_t ielen,
const char *leader, size_t leader_len)
{
u_int8_t *p;
int i;
if (bufsize < leader_len)
return 0;
p = buf;
memcpy(p, leader, leader_len);
bufsize -= leader_len;
p += leader_len;
for (i = 0; i < ielen && bufsize > 2; i++) {
p += sprintf(p, "%02x", ie[i]);
bufsize -= 2;
}
return (i == ielen ? p - (u_int8_t *)buf : 0);
}
struct iwscanreq { /* XXX: right place for this declaration? */
struct ieee80211vap *vap;
char *current_ev;
char *end_buf;
int mode;
};
static int
giwscan_cb(void *arg, const struct ieee80211_scan_entry *se)
{
struct iwscanreq *req = arg;
struct ieee80211vap *vap = req->vap;
char *current_ev = req->current_ev;
char *end_buf = req->end_buf;
char *last_ev;
#define MAX_IE_LENGTH 64 * 2 + 30
char buf[MAX_IE_LENGTH];
#ifndef IWEVGENIE
static const char rsn_leader[] = "rsn_ie=";
static const char wpa_leader[] = "wpa_ie=";
#endif
struct iw_event iwe;
char *current_val;
int j;
if (current_ev >= end_buf)
return E2BIG;
/* WPA/!WPA sort criteria */
if ((req->mode != 0) ^ (se->se_wpa_ie != NULL))
return 0;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWAP;
iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
IEEE80211_ADDR_COPY(iwe.u.ap_addr.sa_data, se->se_macaddr);
else
IEEE80211_ADDR_COPY(iwe.u.ap_addr.sa_data, se->se_bssid);
current_ev = iwe_stream_add_event(current_ev, end_buf, &iwe, IW_EV_ADDR_LEN);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWESSID;
iwe.u.data.flags = 1;
iwe.u.data.length = se->se_ssid[1];
current_ev = iwe_stream_add_point(current_ev,
end_buf, &iwe, (char *) se->se_ssid+2);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
if (se->se_capinfo & (IEEE80211_CAPINFO_ESS|IEEE80211_CAPINFO_IBSS)) {
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWMODE;
iwe.u.mode = se->se_capinfo & IEEE80211_CAPINFO_ESS ?
IW_MODE_MASTER : IW_MODE_ADHOC;
current_ev = iwe_stream_add_event(current_ev,
end_buf, &iwe, IW_EV_UINT_LEN);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
}
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWFREQ;
iwe.u.freq.m = se->se_chan->ic_freq * 100000;
iwe.u.freq.e = 1;
current_ev = iwe_stream_add_event(current_ev,
end_buf, &iwe, IW_EV_FREQ_LEN);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = IWEVQUAL;
set_quality(&iwe.u.qual, se->se_rssi, ATH_DEFAULT_NOISE);
current_ev = iwe_stream_add_event(current_ev,
end_buf, &iwe, IW_EV_QUAL_LEN);
/* We ran out of space in the buffer */
if (last_ev == current_ev)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWENCODE;
if (se->se_capinfo & IEEE80211_CAPINFO_PRIVACY)
iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
else
iwe.u.data.flags = IW_ENCODE_DISABLED;
iwe.u.data.length = 0;
current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, "");
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = SIOCGIWRATE;
current_val = current_ev + IW_EV_LCP_LEN;
/* NB: not sorted, does it matter? */
for (j = 0; j < se->se_rates[1]; j++) {
int r = se->se_rates[2 + j] & IEEE80211_RATE_VAL;
if (r != 0) {
iwe.u.bitrate.value = r * (1000000 / 2);
current_val = iwe_stream_add_value(current_ev,
current_val, end_buf, &iwe,
IW_EV_PARAM_LEN);
}
}
for (j = 0; j < se->se_xrates[1]; j++) {
int r = se->se_xrates[2+j] & IEEE80211_RATE_VAL;
if (r != 0) {
iwe.u.bitrate.value = r * (1000000 / 2);
current_val = iwe_stream_add_value(current_ev,
current_val, end_buf, &iwe,
IW_EV_PARAM_LEN);
}
}
/* remove fixed header if no rates were added */
if ((current_val - current_ev) > IW_EV_LCP_LEN) {
current_ev = current_val;
} else {
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
}
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = IWEVCUSTOM;
snprintf(buf, sizeof(buf), "bcn_int=%d", se->se_intval);
iwe.u.data.length = strlen(buf);
current_ev = iwe_stream_add_point(current_ev, end_buf, &iwe, buf);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
if (se->se_rsn_ie != NULL) {
last_ev = current_ev;
#ifdef IWEVGENIE
memset(&iwe, 0, sizeof(iwe));
if ((se->se_rsn_ie[1] + 2) > MAX_IE_LENGTH)
return E2BIG;
memcpy(buf, se->se_rsn_ie, se->se_rsn_ie[1] + 2);
iwe.cmd = IWEVGENIE;
iwe.u.data.length = se->se_rsn_ie[1] + 2;
#else
memset(&iwe, 0, sizeof(iwe));
iwe.cmd = IWEVCUSTOM;
if (se->se_rsn_ie[0] == IEEE80211_ELEMID_RSN)
iwe.u.data.length = encode_ie(buf, sizeof(buf),
se->se_rsn_ie, se->se_rsn_ie[1] + 2,
rsn_leader, sizeof(rsn_leader) - 1);
#endif
if (iwe.u.data.length != 0) {
current_ev = iwe_stream_add_point(current_ev, end_buf,
&iwe, buf);
/* We ran out of space in the buffer */
if (last_ev == current_ev)
return E2BIG;
}
}
if (se->se_wpa_ie != NULL) {
last_ev = current_ev;
#ifdef IWEVGENIE
memset(&iwe, 0, sizeof(iwe));
if ((se->se_wpa_ie[1] + 2) > MAX_IE_LENGTH)
return E2BIG;
memcpy(buf, se->se_wpa_ie, se->se_wpa_ie[1] + 2);
iwe.cmd = IWEVGENIE;
iwe.u.data.length = se->se_wpa_ie[1] + 2;
#else
memset(&iwe, 0, sizeof(iwe));
iwe.cmd = IWEVCUSTOM;
iwe.u.data.length = encode_ie(buf, sizeof(buf),
se->se_wpa_ie, se->se_wpa_ie[1] + 2,
wpa_leader, sizeof(wpa_leader) - 1);
#endif
if (iwe.u.data.length != 0) {
current_ev = iwe_stream_add_point(current_ev, end_buf,
&iwe, buf);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
}
}
if (se->se_wme_ie != NULL) {
static const char wme_leader[] = "wme_ie=";
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = IWEVCUSTOM;
iwe.u.data.length = encode_ie(buf, sizeof(buf),
se->se_wme_ie, se->se_wme_ie[1] + 2,
wme_leader, sizeof(wme_leader) - 1);
if (iwe.u.data.length != 0) {
current_ev = iwe_stream_add_point(current_ev, end_buf,
&iwe, buf);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
}
}
if (se->se_ath_ie != NULL) {
static const char ath_leader[] = "ath_ie=";
memset(&iwe, 0, sizeof(iwe));
last_ev = current_ev;
iwe.cmd = IWEVCUSTOM;
iwe.u.data.length = encode_ie(buf, sizeof(buf),
se->se_ath_ie, se->se_ath_ie[1] + 2,
ath_leader, sizeof(ath_leader) - 1);
if (iwe.u.data.length != 0) {
current_ev = iwe_stream_add_point(current_ev, end_buf,
&iwe, buf);
/* We ran out of space in the buffer. */
if (last_ev == current_ev)
return E2BIG;
}
}
req->current_ev = current_ev;
return 0;
}
static int
ieee80211_ioctl_giwscan(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct iwscanreq req;
int res = 0;
req.vap = vap;
req.current_ev = extra;
if (data->length == 0) {
req.end_buf = extra + IW_SCAN_MAX_DATA;
} else {
req.end_buf = extra + data->length;
}
/*
* NB: This is no longer needed, as long as the caller supports
* large scan results.
*
* Do two passes to ensure WPA/non-WPA scan candidates
* are sorted to the front. This is a hack to deal with
* the wireless extensions capping scan results at
* IW_SCAN_MAX_DATA bytes. In densely populated environments
* it's easy to overflow this buffer (especially with WPA/RSN
* information elements). Note this sorting hack does not
* guarantee we won't overflow anyway.
*/
req.mode = vap->iv_flags & IEEE80211_F_WPA;
res = ieee80211_scan_iterate(ic, giwscan_cb, &req);
if (res == 0) {
req.mode = req.mode ? 0 : IEEE80211_F_WPA;
res = ieee80211_scan_iterate(ic, giwscan_cb, &req);
}
data->length = req.current_ev - extra;
if (res != 0) {
return -res;
}
return res;
}
#endif /* SIOCGIWSCAN */
static int
cipher2cap(int cipher)
{
switch (cipher) {
case IEEE80211_CIPHER_WEP: return IEEE80211_C_WEP;
case IEEE80211_CIPHER_AES_OCB: return IEEE80211_C_AES;
case IEEE80211_CIPHER_AES_CCM: return IEEE80211_C_AES_CCM;
case IEEE80211_CIPHER_CKIP: return IEEE80211_C_CKIP;
case IEEE80211_CIPHER_TKIP: return IEEE80211_C_TKIP;
}
return 0;
}
#define IEEE80211_MODE_TURBO_STATIC_A IEEE80211_MODE_MAX
static int
ieee80211_convert_mode(const char *mode)
{
#define TOUPPER(c) ((((c) > 0x60) && ((c) < 0x7b)) ? ((c) - 0x20) : (c))
static const struct {
char *name;
int mode;
} mappings[] = {
/* NB: need to order longest strings first for overlaps */
{ "11AST" , IEEE80211_MODE_TURBO_STATIC_A },
{ "AUTO" , IEEE80211_MODE_AUTO },
{ "11A" , IEEE80211_MODE_11A },
{ "11B" , IEEE80211_MODE_11B },
{ "11G" , IEEE80211_MODE_11G },
{ "FH" , IEEE80211_MODE_FH },
{ "0" , IEEE80211_MODE_AUTO },
{ "1" , IEEE80211_MODE_11A },
{ "2" , IEEE80211_MODE_11B },
{ "3" , IEEE80211_MODE_11G },
{ "4" , IEEE80211_MODE_FH },
{ "5" , IEEE80211_MODE_TURBO_STATIC_A },
{ NULL }
};
int i, j;
const char *cp;
for (i = 0; mappings[i].name != NULL; i++) {
cp = mappings[i].name;
for (j = 0; j < strlen(mode) + 1; j++) {
/* convert user-specified string to upper case */
if (TOUPPER(mode[j]) != cp[j])
break;
if (cp[j] == '\0')
return mappings[i].mode;
}
}
return -1;
#undef TOUPPER
}
static int
ieee80211_ioctl_setmode(struct net_device *dev, struct iw_request_info *info,
struct iw_point *wri, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ifreq ifr;
char s[6]; /* big enough for ``11adt'' */
int retv, mode, ifr_mode;
if (ic->ic_media.ifm_cur == NULL)
return -EINVAL; /* XXX: Wrong error */
if (wri->length > sizeof(s)) /* silently truncate */
wri->length = sizeof(s);
if (copy_from_user(s, wri->pointer, wri->length))
return -EFAULT;
s[sizeof(s) - 1] = '\0'; /* ensure null termination */
mode = ieee80211_convert_mode(s);
if (mode < 0)
return -EINVAL;
if (ieee80211_check_mode_consistency(ic, mode, vap->iv_des_chan)) {
/*
* error in AP mode.
* overwrite channel selection in other modes.
*/
if (vap->iv_opmode == IEEE80211_M_HOSTAP)
return -EINVAL;
else
vap->iv_des_chan = IEEE80211_CHAN_ANYC;
}
ifr_mode = mode;
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media & ~IFM_MMASK;
if (mode == IEEE80211_MODE_TURBO_STATIC_A)
ifr_mode = IEEE80211_MODE_11A;
ifr.ifr_media |= IFM_MAKEMODE(ifr_mode);
retv = ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA);
if ((!retv || retv == -ENETRESET) && (mode != vap->iv_des_mode)) {
if (preempt_scan(dev, 100, 100))
ieee80211_scan_flush(ic); /* NB: could optimize */
else
return -ETIMEDOUT;
vap->iv_des_mode = mode;
if (IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
retv = 0;
}
#ifdef ATH_SUPERG_XR
/* set the same params on the xr vap device if exists */
if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR))
vap->iv_xrvap->iv_des_mode = mode;
#endif
return -retv;
}
#undef IEEE80211_MODE_TURBO_STATIC_A
#ifdef ATH_SUPERG_XR
static void
ieee80211_setupxr(struct ieee80211vap *vap)
{
struct net_device *dev = vap->iv_dev;
struct ieee80211com *ic = vap->iv_ic;
if (!(vap->iv_flags & IEEE80211_F_XR)) {
if ((vap->iv_ath_cap & IEEE80211_ATHC_XR) && !vap->iv_xrvap) {
struct ieee80211vap *xrvap = NULL;
char name[IFNAMSIZ];
strcpy(name, dev->name);
strcat(name, "-xr");
/*
* Create a new XR vap. If the normal VAP is already up,
* bring up the XR vap aswell.
*/
vap->iv_ath_cap &= ~IEEE80211_ATHC_TURBOP; /* turn off turbo */
ieee80211_scan_flush(ic); /* NB: could optimize */
if (!(xrvap = ic->ic_vap_create(ic, name, IEEE80211_M_HOSTAP,
IEEE80211_VAP_XR | IEEE80211_CLONE_BSSID, dev)))
return;
/* We use iv_xrvap to link to the parent VAP as well */
xrvap->iv_xrvap = vap;
xrvap->iv_ath_cap = vap->iv_ath_cap;
xrvap->iv_fragthreshold = IEEE80211_XR_FRAG_THRESHOLD;
xrvap->iv_des_mode = vap->iv_des_mode;
copy_des_ssid(xrvap, vap);
vap->iv_xrvap = xrvap;
} else if (!(vap->iv_ath_cap & IEEE80211_ATHC_XR) && vap->iv_xrvap) {
/*
* Destroy the XR vap. If the XR VAP is up, bring
* it down before destroying.
*/
if (vap->iv_xrvap) {
ieee80211_stop(vap->iv_xrvap->iv_dev);
ic->ic_vap_delete(vap->iv_xrvap);
}
vap->iv_xrvap = NULL;
}
}
}
#endif
static int
ieee80211_setathcap(struct ieee80211vap *vap, int cap, int setting)
{
struct ieee80211com *ic = vap->iv_ic;
int ocap;
if ((ic->ic_ath_cap & cap) == 0)
return EINVAL;
ocap = vap->iv_ath_cap;
if (setting)
vap->iv_ath_cap |= cap;
else
vap->iv_ath_cap &= ~cap;
return (vap->iv_ath_cap != ocap ? ENETRESET : 0);
}
static int
ieee80211_set_turbo(struct net_device *dev, int flag)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ifreq ifr;
struct ieee80211vap *tmpvap = dev->priv;
int nvap = 0;
TAILQ_FOREACH(tmpvap, &ic->ic_vaps, iv_next)
nvap++;
if ((nvap > 1) && flag)
return -EINVAL;
ifr.ifr_media = ic->ic_media.ifm_cur->ifm_media &~ IFM_MMASK;
if (flag)
ifr.ifr_media |= IFM_IEEE80211_TURBO;
else
ifr.ifr_media &= ~IFM_IEEE80211_TURBO;
(void) ifmedia_ioctl(ic->ic_dev, &ifr, &ic->ic_media, SIOCSIFMEDIA);
return 0;
}
static int
ieee80211_ioctl_setparam(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_rsnparms *rsn = &vap->iv_bss->ni_rsn;
unsigned int *i = (unsigned int *) extra;
unsigned int param = i[0]; /* parameter id is 1st */
unsigned int value = i[1]; /* NB: most values are TYPE_INT */
int retv = 0;
int j, caps;
const struct ieee80211_authenticator *auth;
const struct ieee80211_aclator *acl;
switch (param) {
case IEEE80211_PARAM_AUTHMODE:
switch (value) {
case IEEE80211_AUTH_WPA: /* WPA */
case IEEE80211_AUTH_8021X: /* 802.1x */
case IEEE80211_AUTH_OPEN: /* open */
case IEEE80211_AUTH_SHARED: /* shared-key */
case IEEE80211_AUTH_AUTO: /* auto */
auth = ieee80211_authenticator_get(value);
if (auth == NULL)
return -EINVAL; /* XXX: Wrong error */
break;
default:
return -EINVAL;
}
switch (value) {
case IEEE80211_AUTH_WPA: /* WPA w/ 802.1x */
value = IEEE80211_AUTH_8021X;
break;
case IEEE80211_AUTH_OPEN: /* open */
vap->iv_flags &= ~(IEEE80211_F_WPA);
break;
case IEEE80211_AUTH_SHARED: /* shared-key */
case IEEE80211_AUTH_AUTO: /* auto */
case IEEE80211_AUTH_8021X: /* 802.1x */
vap->iv_flags &= ~IEEE80211_F_WPA;
break;
}
/* NB: authenticator attach/detach happens on state change */
vap->iv_bss->ni_authmode = value;
/* XXX mixed/mode/usage? */
vap->iv_auth = auth;
retv = ENETRESET;
break;
case IEEE80211_PARAM_PROTMODE:
if (value > IEEE80211_PROT_RTSCTS)
return -EINVAL;
ic->ic_protmode = value;
/* NB: if not operating in 11g this can wait */
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC &&
IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan))
retv = ENETRESET;
break;
case IEEE80211_PARAM_MCASTCIPHER:
if ((vap->iv_caps & cipher2cap(value)) == 0 &&
!ieee80211_crypto_available(vap, value))
return -EINVAL;
rsn->rsn_mcastcipher = value;
if (vap->iv_flags & IEEE80211_F_WPA)
retv = ENETRESET;
break;
case IEEE80211_PARAM_MCASTKEYLEN:
if (value > IEEE80211_KEYBUF_SIZE)
return -EINVAL;
/* XXX no way to verify driver capability */
rsn->rsn_mcastkeylen = value;
if (vap->iv_flags & IEEE80211_F_WPA)
retv = ENETRESET;
break;
case IEEE80211_PARAM_UCASTCIPHERS:
/*
* NB: this logic intentionally ignores unknown and
* unsupported ciphers so folks can specify 0xff or
* similar and get all available ciphers.
*/
/* caps are really ciphers */
caps = 0;
for (j = 1; j < 32; j++) /* NB: skip WEP */
if ((value & (1 << j)) &&
((vap->iv_caps & cipher2cap(j)) ||
ieee80211_crypto_available(vap, j)))
caps |= 1 << j;
if (caps == 0) /* nothing available */
return -EINVAL;
/* XXX verify ciphers ok for unicast use? */
/* XXX disallow if running as it'll have no effect */
rsn->rsn_ucastcipherset = caps;
if (vap->iv_flags & IEEE80211_F_WPA)
retv = ENETRESET;
break;
case IEEE80211_PARAM_UCASTCIPHER:
if (!(vap->iv_caps & cipher2cap(value)) &&
!ieee80211_crypto_available(vap, value))
return -EINVAL;
rsn->rsn_ucastcipher = value;
if (vap->iv_flags & IEEE80211_F_WPA)
retv = ENETRESET;
break;
case IEEE80211_PARAM_UCASTKEYLEN:
if (value > IEEE80211_KEYBUF_SIZE)
return -EINVAL;
/* XXX no way to verify driver capability */
rsn->rsn_ucastkeylen = value;
break;
case IEEE80211_PARAM_KEYMGTALGS:
/* XXX check */
rsn->rsn_keymgmtset = value;
if (vap->iv_flags & IEEE80211_F_WPA)
retv = ENETRESET;
break;
case IEEE80211_PARAM_RSNCAPS:
/* XXX check */
rsn->rsn_caps = value;
if (vap->iv_flags & IEEE80211_F_WPA)
retv = ENETRESET;
break;
case IEEE80211_PARAM_WPA:
if (value > 3)
return -EINVAL;
/* XXX verify ciphers available */
vap->iv_flags &= ~IEEE80211_F_WPA;
switch (value) {
case 1:
vap->iv_flags |= IEEE80211_F_WPA1;
break;
case 2:
vap->iv_flags |= IEEE80211_F_WPA2;
break;
case 3:
vap->iv_flags |= IEEE80211_F_WPA1 | IEEE80211_F_WPA2;
break;
}
retv = ENETRESET; /* XXX? */
break;
case IEEE80211_PARAM_ROAMING:
switch (value) {
case IEEE80211_ROAMING_DEVICE:
case IEEE80211_ROAMING_AUTO:
case IEEE80211_ROAMING_MANUAL:
ic->ic_roaming = value;
break;
default:
return -EINVAL;
}
break;
case IEEE80211_PARAM_PRIVACY:
if (value) {
/* XXX check for key state? */
vap->iv_flags |= IEEE80211_F_PRIVACY;
} else
vap->iv_flags &= ~IEEE80211_F_PRIVACY;
break;
case IEEE80211_PARAM_DROPUNENCRYPTED:
if (value)
vap->iv_flags |= IEEE80211_F_DROPUNENC;
else
vap->iv_flags &= ~IEEE80211_F_DROPUNENC;
break;
case IEEE80211_PARAM_DROPUNENC_EAPOL:
if (value)
IEEE80211_VAP_DROPUNENC_EAPOL_ENABLE(vap);
else
IEEE80211_VAP_DROPUNENC_EAPOL_DISABLE(vap);
break;
case IEEE80211_PARAM_COUNTERMEASURES:
if (value) {
if ((vap->iv_flags & IEEE80211_F_WPA) == 0)
return -EINVAL;
vap->iv_flags |= IEEE80211_F_COUNTERM;
} else
vap->iv_flags &= ~IEEE80211_F_COUNTERM;
break;
case IEEE80211_PARAM_DRIVER_CAPS:
vap->iv_caps = value; /* NB: for testing */
break;
case IEEE80211_PARAM_MACCMD:
acl = vap->iv_acl;
switch (value) {
case IEEE80211_MACCMD_POLICY_OPEN:
case IEEE80211_MACCMD_POLICY_ALLOW:
case IEEE80211_MACCMD_POLICY_DENY:
if (acl == NULL) {
acl = ieee80211_aclator_get("mac");
if (acl == NULL || !acl->iac_attach(vap))
return -EINVAL;
vap->iv_acl = acl;
}
acl->iac_setpolicy(vap, value);
break;
case IEEE80211_MACCMD_FLUSH:
if (acl != NULL)
acl->iac_flush(vap);
/* NB: silently ignore when not in use */
break;
case IEEE80211_MACCMD_DETACH:
if (acl != NULL) {
vap->iv_acl = NULL;
acl->iac_detach(vap);
}
break;
}
break;
case IEEE80211_PARAM_WMM:
if (ic->ic_caps & IEEE80211_C_WME){
retv = ENETRESET; /* Renegotiate for capabilities */
if (value) {
/* All TKIP keys need resetting to use software MIC.
* They aren't, so this is disabled.
* XXX: Can never turn it back on. */
if (!(vap->iv_ic->ic_caps & IEEE80211_C_WME_TKIPMIC))
retv = EBUSY;
else {
vap->iv_flags |= IEEE80211_F_WME;
vap->iv_ic->ic_flags |= IEEE80211_F_WME;
}
} else {
vap->iv_flags &= ~IEEE80211_F_WME;
{
struct ieee80211vap *v = NULL;
int all = 1;
TAILQ_FOREACH(v, &vap->iv_ic->ic_vaps, iv_next) {
if (v->iv_flags & IEEE80211_F_WME) {
all = 0;
break;
}
}
if (all)
vap->iv_ic->ic_flags &= ~IEEE80211_F_WME;
}
}
}
break;
case IEEE80211_PARAM_HIDESSID:
if (value)
vap->iv_flags |= IEEE80211_F_HIDESSID;
else
vap->iv_flags &= ~IEEE80211_F_HIDESSID;
retv = ENETRESET;
break;
case IEEE80211_PARAM_APBRIDGE:
if (value == 0)
vap->iv_flags |= IEEE80211_F_NOBRIDGE;
else
vap->iv_flags &= ~IEEE80211_F_NOBRIDGE;
break;
case IEEE80211_PARAM_INACT:
vap->iv_inact_run = value / IEEE80211_INACT_WAIT;
break;
case IEEE80211_PARAM_INACT_AUTH:
vap->iv_inact_auth = value / IEEE80211_INACT_WAIT;
break;
case IEEE80211_PARAM_INACT_INIT:
vap->iv_inact_init = value / IEEE80211_INACT_WAIT;
break;
case IEEE80211_PARAM_ABOLT:
caps = 0;
/*
* Map abolt settings to capability bits;
* this also strips unknown/unwanted bits.
*/
if (value & IEEE80211_ABOLT_TURBO_PRIME)
caps |= IEEE80211_ATHC_TURBOP;
if (value & IEEE80211_ABOLT_COMPRESSION)
caps |= IEEE80211_ATHC_COMP;
if (value & IEEE80211_ABOLT_FAST_FRAME)
caps |= IEEE80211_ATHC_FF;
if (value & IEEE80211_ABOLT_XR)
caps |= IEEE80211_ATHC_XR;
if (value & IEEE80211_ABOLT_AR)
caps |= IEEE80211_ATHC_AR;
if (value & IEEE80211_ABOLT_BURST)
caps |= IEEE80211_ATHC_BURST;
/* verify requested capabilities are supported */
if ((caps & ic->ic_ath_cap) != caps)
return -EINVAL;
if (vap->iv_ath_cap != caps) {
if ((vap->iv_ath_cap ^ caps) & IEEE80211_ATHC_TURBOP) {
/* no turbo and XR at the same time */
if ((caps & IEEE80211_ATHC_TURBOP) &&
(caps & IEEE80211_ATHC_XR))
return -EINVAL;
if (ieee80211_set_turbo(dev,
(caps & IEEE80211_ATHC_TURBOP)))
return -EINVAL;
ieee80211_scan_flush(ic);
}
vap->iv_ath_cap = caps;
#ifdef ATH_SUPERG_XR
ieee80211_setupxr(vap);
#endif
retv = ENETRESET;
}
break;
case IEEE80211_PARAM_DTIM_PERIOD:
if ((vap->iv_opmode != IEEE80211_M_HOSTAP) &&
(vap->iv_opmode != IEEE80211_M_IBSS))
return -EOPNOTSUPP;
if ((IEEE80211_DTIM_MIN <= value) &&
(value <= IEEE80211_DTIM_MAX)) {
vap->iv_dtim_period = value;
retv = ENETRESET; /* requires restart */
} else
retv = EINVAL;
break;
case IEEE80211_PARAM_BEACON_INTERVAL:
if ((vap->iv_opmode != IEEE80211_M_HOSTAP) &&
(vap->iv_opmode != IEEE80211_M_IBSS))
return -EOPNOTSUPP;
if (IEEE80211_BINTVAL_VALID(value)) {
ic->ic_lintval = value; /* XXX multi-bss */
retv = ENETRESET; /* requires restart */
} else
retv = EINVAL;
break;
case IEEE80211_PARAM_DOTH:
if (value)
ic->ic_flags |= IEEE80211_F_DOTH;
else
ic->ic_flags &= ~IEEE80211_F_DOTH;
retv = ENETRESET; /* XXX: need something this drastic? */
break;
case IEEE80211_PARAM_SHPREAMBLE:
if (value) {
ic->ic_caps |= IEEE80211_C_SHPREAMBLE;
ic->ic_flags |= IEEE80211_F_SHPREAMBLE;
ic->ic_flags &= ~IEEE80211_F_USEBARKER;
} else {
ic->ic_caps &= ~IEEE80211_C_SHPREAMBLE;
ic->ic_flags &= ~IEEE80211_F_SHPREAMBLE;
ic->ic_flags |= IEEE80211_F_USEBARKER;
}
retv = ENETRESET; /* requires restart */
break;
case IEEE80211_PARAM_PWRTARGET:
ic->ic_curchanmaxpwr = value;
break;
case IEEE80211_PARAM_GENREASSOC:
IEEE80211_SEND_MGMT(vap->iv_bss, IEEE80211_FC0_SUBTYPE_REASSOC_REQ, 0);
break;
case IEEE80211_PARAM_TXCONT:
ieee80211_set_txcont(dev, info, w, extra);
break;
case IEEE80211_PARAM_TXCONT_RATE:
ieee80211_set_txcont_rate(dev, info, w, extra);
break;
case IEEE80211_PARAM_TXCONT_POWER:
ieee80211_set_txcont_power(dev, info, w, extra);
break;
case IEEE80211_PARAM_COMPRESSION:
retv = ieee80211_setathcap(vap, IEEE80211_ATHC_COMP, value);
break;
case IEEE80211_PARAM_FF:
retv = ieee80211_setathcap(vap, IEEE80211_ATHC_FF, value);
break;
case IEEE80211_PARAM_TURBO:
retv = ieee80211_setathcap(vap, IEEE80211_ATHC_TURBOP, value);
if (retv == ENETRESET) {
/* no turbo and XR at the same time */
if ((vap->iv_ath_cap & IEEE80211_ATHC_XR) && value)
return -EINVAL;
if (ieee80211_set_turbo(dev, value))
return -EINVAL;
ieee80211_scan_flush(ic);
}
break;
case IEEE80211_PARAM_XR:
/* no turbo and XR at the same time */
if ((vap->iv_ath_cap & IEEE80211_ATHC_TURBOP) && value)
return -EINVAL;
retv = ieee80211_setathcap(vap, IEEE80211_ATHC_XR, value);
#ifdef ATH_SUPERG_XR
ieee80211_setupxr(vap);
#endif
break;
case IEEE80211_PARAM_BURST:
retv = ieee80211_setathcap(vap, IEEE80211_ATHC_BURST, value);
break;
case IEEE80211_PARAM_AR:
retv = ieee80211_setathcap(vap, IEEE80211_ATHC_AR, value);
break;
case IEEE80211_PARAM_PUREG:
if (value)
vap->iv_flags |= IEEE80211_F_PUREG;
else
vap->iv_flags &= ~IEEE80211_F_PUREG;
/* NB: reset only if we're operating on an 11g channel */
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC &&
IEEE80211_IS_CHAN_ANYG(ic->ic_bsschan))
retv = ENETRESET;
break;
case IEEE80211_PARAM_WDS:
if ((vap->iv_opmode == IEEE80211_M_IBSS) ||
(vap->iv_opmode == IEEE80211_M_AHDEMO))
return -EOPNOTSUPP;
if (value)
vap->iv_flags_ext |= IEEE80211_FEXT_WDS;
else
vap->iv_flags_ext &= ~IEEE80211_FEXT_WDS;
break;
case IEEE80211_PARAM_BGSCAN:
if (value) {
if ((vap->iv_caps & IEEE80211_C_BGSCAN) == 0)
return -EINVAL;
vap->iv_flags |= IEEE80211_F_BGSCAN;
} else {
/* XXX racey? */
vap->iv_flags &= ~IEEE80211_F_BGSCAN;
ieee80211_cancel_scan(vap); /* anything current */
}
break;
case IEEE80211_PARAM_BGSCAN_IDLE:
if (value >= IEEE80211_BGSCAN_IDLE_MIN)
vap->iv_bgscanidle = msecs_to_jiffies(value);
else
retv = EINVAL;
break;
case IEEE80211_PARAM_BGSCAN_INTERVAL:
if (value >= IEEE80211_BGSCAN_INTVAL_MIN)
vap->iv_bgscanintvl = value * HZ;
else
retv = EINVAL;
break;
case IEEE80211_PARAM_MCAST_RATE:
/* units are in KILObits per second */
if (value >= 256 && value <= 54000)
vap->iv_mcast_rate = value;
else
retv = EINVAL;
break;
case IEEE80211_PARAM_COVERAGE_CLASS:
if (value <= IEEE80211_COVERAGE_CLASS_MAX) {
ic->ic_coverageclass = value;
if (IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
retv = 0;
} else
retv = EINVAL;
break;
case IEEE80211_PARAM_COUNTRY_IE:
if (value)
ic->ic_flags_ext |= IEEE80211_FEXT_COUNTRYIE;
else
ic->ic_flags_ext &= ~IEEE80211_FEXT_COUNTRYIE;
retv = ENETRESET;
break;
case IEEE80211_PARAM_REGCLASS:
if (value)
ic->ic_flags_ext |= IEEE80211_FEXT_REGCLASS;
else
ic->ic_flags_ext &= ~IEEE80211_FEXT_REGCLASS;
retv = ENETRESET;
break;
case IEEE80211_PARAM_SCANVALID:
vap->iv_scanvalid = value * HZ;
break;
case IEEE80211_PARAM_ROAM_RSSI_11A:
vap->iv_roam.rssi11a = value;
break;
case IEEE80211_PARAM_ROAM_RSSI_11B:
vap->iv_roam.rssi11bOnly = value;
break;
case IEEE80211_PARAM_ROAM_RSSI_11G:
vap->iv_roam.rssi11b = value;
break;
case IEEE80211_PARAM_ROAM_RATE_11A:
vap->iv_roam.rate11a = value;
break;
case IEEE80211_PARAM_ROAM_RATE_11B:
vap->iv_roam.rate11bOnly = value;
break;
case IEEE80211_PARAM_ROAM_RATE_11G:
vap->iv_roam.rate11b = value;
break;
case IEEE80211_PARAM_UAPSDINFO:
if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
if (ic->ic_caps & IEEE80211_C_UAPSD) {
if (value)
IEEE80211_VAP_UAPSD_ENABLE(vap);
else
IEEE80211_VAP_UAPSD_DISABLE(vap);
retv = ENETRESET;
}
} else if (vap->iv_opmode == IEEE80211_M_STA) {
vap->iv_uapsdinfo = value;
IEEE80211_VAP_UAPSD_ENABLE(vap);
retv = ENETRESET;
}
break;
case IEEE80211_PARAM_SLEEP:
/* XXX: Forced sleep for testing. Does not actually place the
* HW in sleep mode yet. this only makes sense for STAs.
*/
if (value) {
/* goto sleep */
IEEE80211_VAP_GOTOSLEEP(vap);
} else {
/* wakeup */
IEEE80211_VAP_WAKEUP(vap);
}
ieee80211_send_nulldata(ieee80211_ref_node(vap->iv_bss));
break;
case IEEE80211_PARAM_QOSNULL:
/* Force a QoS Null for testing. */
ieee80211_send_qosnulldata(vap->iv_bss, value);
break;
case IEEE80211_PARAM_PSPOLL:
/* Force a PS-POLL for testing. */
ieee80211_send_pspoll(vap->iv_bss);
break;
case IEEE80211_PARAM_EOSPDROP:
if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
if (value)
IEEE80211_VAP_EOSPDROP_ENABLE(vap);
else
IEEE80211_VAP_EOSPDROP_DISABLE(vap);
}
break;
case IEEE80211_PARAM_MARKDFS:
if (value)
ic->ic_flags_ext |= IEEE80211_FEXT_MARKDFS;
else
ic->ic_flags_ext &= ~IEEE80211_FEXT_MARKDFS;
break;
#ifdef ATH_REVERSE_ENGINEERING
case IEEE80211_PARAM_DUMPREGS:
ieee80211_dump_registers(dev, info, w, extra);
break;
#endif /* #ifdef ATH_REVERSE_ENGINEERING */
default:
retv = EOPNOTSUPP;
break;
}
#ifdef ATH_SUPERG_XR
/* set the same params on the xr vap device if exists */
if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR)) {
ieee80211_ioctl_setparam(vap->iv_xrvap->iv_dev, info, w, extra);
/* XR vap does not support any superG features */
vap->iv_xrvap->iv_ath_cap &= IEEE80211_ATHC_XR;
}
/*
* do not reset the xr vap , which is automatically
* reset by the state machine now.
*/
if (!vap->iv_xrvap || (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR))) {
if (retv == ENETRESET)
retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0;
}
#else
/* XXX should any of these cause a rescan? */
if (retv == ENETRESET)
retv = IS_UP_AUTO(vap) ? ieee80211_open(vap->iv_dev) : 0;
#endif
return -retv;
}
#if 0
static int
cap2cipher(int flag)
{
switch (flag) {
case IEEE80211_C_WEP: return IEEE80211_CIPHER_WEP;
case IEEE80211_C_AES: return IEEE80211_CIPHER_AES_OCB;
case IEEE80211_C_AES_CCM: return IEEE80211_CIPHER_AES_CCM;
case IEEE80211_C_CKIP: return IEEE80211_CIPHER_CKIP;
case IEEE80211_C_TKIP: return IEEE80211_CIPHER_TKIP;
}
return -1;
}
#endif
static int
ieee80211_ioctl_getmode(struct net_device *dev, struct iw_request_info *info,
struct iw_point *wri, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ifmediareq imr;
ic->ic_media.ifm_status(ic->ic_dev, &imr);
switch (IFM_MODE(imr.ifm_active)) {
case IFM_IEEE80211_11A:
strcpy(extra, "11a");
break;
case IFM_IEEE80211_11B:
strcpy(extra, "11b");
break;
case IFM_IEEE80211_11G:
strcpy(extra, "11g");
break;
case IFM_IEEE80211_FH:
strcpy(extra, "FH");
break;
case IFM_AUTO:
strcpy(extra, "auto");
break;
default:
return -EINVAL;
}
if (ic->ic_media.ifm_media & IFM_IEEE80211_TURBO) {
if (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP)
strcat(extra, "T");
else
strcat(extra, "ST");
}
wri->length = strlen(extra);
return 0;
}
static int
ieee80211_ioctl_getparam(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_rsnparms *rsn = &vap->iv_bss->ni_rsn;
unsigned int *param = (unsigned int *) extra;
switch (param[0]) {
case IEEE80211_PARAM_AUTHMODE:
if (vap->iv_flags & IEEE80211_F_WPA)
param[0] = IEEE80211_AUTH_WPA;
else
param[0] = vap->iv_bss->ni_authmode;
break;
case IEEE80211_PARAM_PROTMODE:
param[0] = ic->ic_protmode;
break;
case IEEE80211_PARAM_MCASTCIPHER:
param[0] = rsn->rsn_mcastcipher;
break;
case IEEE80211_PARAM_MCASTKEYLEN:
param[0] = rsn->rsn_mcastkeylen;
break;
case IEEE80211_PARAM_UCASTCIPHERS:
param[0] = rsn->rsn_ucastcipherset;
break;
case IEEE80211_PARAM_UCASTCIPHER:
param[0] = rsn->rsn_ucastcipher;
break;
case IEEE80211_PARAM_UCASTKEYLEN:
param[0] = rsn->rsn_ucastkeylen;
break;
case IEEE80211_PARAM_KEYMGTALGS:
param[0] = rsn->rsn_keymgmtset;
break;
case IEEE80211_PARAM_RSNCAPS:
param[0] = rsn->rsn_caps;
break;
case IEEE80211_PARAM_WPA:
switch (vap->iv_flags & IEEE80211_F_WPA) {
case IEEE80211_F_WPA1:
param[0] = 1;
break;
case IEEE80211_F_WPA2:
param[0] = 2;
break;
case IEEE80211_F_WPA1 | IEEE80211_F_WPA2:
param[0] = 3;
break;
default:
param[0] = 0;
break;
}
break;
case IEEE80211_PARAM_ROAMING:
param[0] = ic->ic_roaming;
break;
case IEEE80211_PARAM_PRIVACY:
param[0] = (vap->iv_flags & IEEE80211_F_PRIVACY) != 0;
break;
case IEEE80211_PARAM_DROPUNENCRYPTED:
param[0] = (vap->iv_flags & IEEE80211_F_DROPUNENC) != 0;
break;
case IEEE80211_PARAM_DROPUNENC_EAPOL:
param[0] = IEEE80211_VAP_DROPUNENC_EAPOL(vap);
break;
case IEEE80211_PARAM_COUNTERMEASURES:
param[0] = (vap->iv_flags & IEEE80211_F_COUNTERM) != 0;
break;
case IEEE80211_PARAM_DRIVER_CAPS:
param[0] = vap->iv_caps;
break;
case IEEE80211_PARAM_WMM:
param[0] = (vap->iv_flags & IEEE80211_F_WME) != 0;
break;
case IEEE80211_PARAM_HIDESSID:
param[0] = (vap->iv_flags & IEEE80211_F_HIDESSID) != 0;
break;
case IEEE80211_PARAM_APBRIDGE:
param[0] = (vap->iv_flags & IEEE80211_F_NOBRIDGE) == 0;
break;
case IEEE80211_PARAM_INACT:
param[0] = vap->iv_inact_run * IEEE80211_INACT_WAIT;
break;
case IEEE80211_PARAM_INACT_AUTH:
param[0] = vap->iv_inact_auth * IEEE80211_INACT_WAIT;
break;
case IEEE80211_PARAM_INACT_INIT:
param[0] = vap->iv_inact_init * IEEE80211_INACT_WAIT;
break;
case IEEE80211_PARAM_ABOLT:
/*
* Map capability bits to abolt settings.
*/
param[0] = 0;
if (vap->iv_ath_cap & IEEE80211_ATHC_COMP)
param[0] |= IEEE80211_ABOLT_COMPRESSION;
if (vap->iv_ath_cap & IEEE80211_ATHC_FF)
param[0] |= IEEE80211_ABOLT_FAST_FRAME;
if (vap->iv_ath_cap & IEEE80211_ATHC_XR)
param[0] |= IEEE80211_ABOLT_XR;
if (vap->iv_ath_cap & IEEE80211_ATHC_BURST)
param[0] |= IEEE80211_ABOLT_BURST;
if (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP)
param[0] |= IEEE80211_ABOLT_TURBO_PRIME;
if (vap->iv_ath_cap & IEEE80211_ATHC_AR)
param[0] |= IEEE80211_ABOLT_AR;
break;
case IEEE80211_PARAM_COMPRESSION:
param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_COMP) != 0;
break;
case IEEE80211_PARAM_FF:
param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_FF) != 0;
break;
case IEEE80211_PARAM_XR:
param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_XR) != 0;
break;
case IEEE80211_PARAM_BURST:
param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_BURST) != 0;
break;
case IEEE80211_PARAM_AR:
param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_AR) != 0;
break;
case IEEE80211_PARAM_TURBO:
param[0] = (vap->iv_ath_cap & IEEE80211_ATHC_TURBOP) != 0;
break;
case IEEE80211_PARAM_DTIM_PERIOD:
param[0] = vap->iv_dtim_period;
break;
case IEEE80211_PARAM_BEACON_INTERVAL:
/* NB: get from ic_bss for station mode */
param[0] = vap->iv_bss->ni_intval;
break;
case IEEE80211_PARAM_DOTH:
param[0] = (ic->ic_flags & IEEE80211_F_DOTH) != 0;
break;
case IEEE80211_PARAM_SHPREAMBLE:
param[0] = (ic->ic_caps & IEEE80211_C_SHPREAMBLE) != 0;
break;
case IEEE80211_PARAM_PWRTARGET:
param[0] = ic->ic_curchanmaxpwr;
break;
case IEEE80211_PARAM_TXCONT:
ieee80211_get_txcont(dev, info, w, extra);
break;
case IEEE80211_PARAM_TXCONT_RATE:
ieee80211_get_txcont_rate(dev, info, w, extra);
break;
case IEEE80211_PARAM_TXCONT_POWER:
ieee80211_get_txcont_power(dev, info, w, extra);
break;
case IEEE80211_PARAM_PUREG:
param[0] = (vap->iv_flags & IEEE80211_F_PUREG) != 0;
break;
case IEEE80211_PARAM_WDS:
param[0] = ((vap->iv_flags_ext & IEEE80211_FEXT_WDS) == IEEE80211_FEXT_WDS);
break;
case IEEE80211_PARAM_BGSCAN:
param[0] = (vap->iv_flags & IEEE80211_F_BGSCAN) != 0;
break;
case IEEE80211_PARAM_BGSCAN_IDLE:
param[0] = jiffies_to_msecs(vap->iv_bgscanidle); /* ms */
break;
case IEEE80211_PARAM_BGSCAN_INTERVAL:
param[0] = vap->iv_bgscanintvl / HZ; /* seconds */
break;
case IEEE80211_PARAM_MCAST_RATE:
param[0] = vap->iv_mcast_rate; /* seconds */
break;
case IEEE80211_PARAM_COVERAGE_CLASS:
param[0] = ic->ic_coverageclass;
break;
case IEEE80211_PARAM_COUNTRY_IE:
param[0] = (ic->ic_flags_ext & IEEE80211_FEXT_COUNTRYIE) != 0;
break;
case IEEE80211_PARAM_REGCLASS:
param[0] = (ic->ic_flags_ext & IEEE80211_FEXT_REGCLASS) != 0;
break;
case IEEE80211_PARAM_SCANVALID:
param[0] = vap->iv_scanvalid / HZ; /* seconds */
break;
case IEEE80211_PARAM_ROAM_RSSI_11A:
param[0] = vap->iv_roam.rssi11a;
break;
case IEEE80211_PARAM_ROAM_RSSI_11B:
param[0] = vap->iv_roam.rssi11bOnly;
break;
case IEEE80211_PARAM_ROAM_RSSI_11G:
param[0] = vap->iv_roam.rssi11b;
break;
case IEEE80211_PARAM_ROAM_RATE_11A:
param[0] = vap->iv_roam.rate11a;
break;
case IEEE80211_PARAM_ROAM_RATE_11B:
param[0] = vap->iv_roam.rate11bOnly;
break;
case IEEE80211_PARAM_ROAM_RATE_11G:
param[0] = vap->iv_roam.rate11b;
break;
case IEEE80211_PARAM_UAPSDINFO:
if (vap->iv_opmode == IEEE80211_M_HOSTAP) {
if (IEEE80211_VAP_UAPSD_ENABLED(vap))
param[0] = 1;
else
param[0] = 0;
} else if (vap->iv_opmode == IEEE80211_M_STA)
param[0] = vap->iv_uapsdinfo;
break;
case IEEE80211_PARAM_SLEEP:
param[0] = vap->iv_bss->ni_flags & IEEE80211_NODE_PWR_MGT;
break;
case IEEE80211_PARAM_EOSPDROP:
param[0] = IEEE80211_VAP_EOSPDROP_ENABLED(vap);
break;
case IEEE80211_PARAM_MARKDFS:
if (ic->ic_flags_ext & IEEE80211_FEXT_MARKDFS)
param[0] = 1;
else
param[0] = 0;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
/* returns non-zero if ID is for a system IE (not for app use) */
static int
is_sys_ie(u_int8_t ie_id)
{
/* XXX review this list */
switch (ie_id) {
case IEEE80211_ELEMID_SSID:
case IEEE80211_ELEMID_RATES:
case IEEE80211_ELEMID_FHPARMS:
case IEEE80211_ELEMID_DSPARMS:
case IEEE80211_ELEMID_CFPARMS:
case IEEE80211_ELEMID_TIM:
case IEEE80211_ELEMID_IBSSPARMS:
case IEEE80211_ELEMID_COUNTRY:
case IEEE80211_ELEMID_REQINFO:
case IEEE80211_ELEMID_CHALLENGE:
case IEEE80211_ELEMID_PWRCNSTR:
case IEEE80211_ELEMID_PWRCAP:
case IEEE80211_ELEMID_TPCREQ:
case IEEE80211_ELEMID_TPCREP:
case IEEE80211_ELEMID_SUPPCHAN:
case IEEE80211_ELEMID_CHANSWITCHANN:
case IEEE80211_ELEMID_MEASREQ:
case IEEE80211_ELEMID_MEASREP:
case IEEE80211_ELEMID_QUIET:
case IEEE80211_ELEMID_IBSSDFS:
case IEEE80211_ELEMID_ERP:
case IEEE80211_ELEMID_RSN:
case IEEE80211_ELEMID_XRATES:
case IEEE80211_ELEMID_TPC:
case IEEE80211_ELEMID_CCKM:
return 1;
default:
return 0;
}
}
/* returns non-zero if the buffer appears to contain a valid IE list */
static int
is_valid_ie_list(u_int32_t buf_len, void *buf, int exclude_sys_ies)
{
struct ieee80211_ie *ie = (struct ieee80211_ie *)buf;
while (buf_len >= sizeof(*ie)) {
int ie_elem_len = sizeof(*ie) + ie->len;
if (buf_len < ie_elem_len)
break;
if (exclude_sys_ies && is_sys_ie(ie->id))
break;
buf_len -= ie_elem_len;
ie = (struct ieee80211_ie *)(ie->info + ie->len);
}
return (buf_len == 0) ? 1 : 0;
}
static int
ieee80211_ioctl_setoptie(struct net_device *dev, struct iw_request_info *info,
struct iw_point *wri, char *extra)
{
struct ieee80211vap *vap = dev->priv;
void *ie;
/*
* NB: Doing this for ap operation could be useful (e.g. for
* WPA and/or WME) except that it typically is worthless
* without being able to intervene when processing
* association response frames--so disallow it for now.
*/
if (vap->iv_opmode != IEEE80211_M_STA)
return -EOPNOTSUPP;
if (!is_valid_ie_list(wri->length, extra, 0))
return -EINVAL;
/* NB: wri->length is validated by the wireless extensions code */
MALLOC(ie, void *, wri->length, M_DEVBUF, M_WAITOK);
if (ie == NULL)
return -ENOMEM;
memcpy(ie, extra, wri->length);
if (vap->iv_opt_ie != NULL)
FREE(vap->iv_opt_ie, M_DEVBUF);
vap->iv_opt_ie = ie;
vap->iv_opt_ie_len = wri->length;
#ifdef ATH_SUPERG_XR
/* set the same params on the xr vap device if exists */
if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR))
ieee80211_ioctl_setoptie(vap->iv_xrvap->iv_dev, info, wri, extra);
#endif
return 0;
}
static int
ieee80211_ioctl_getoptie(struct net_device *dev, struct iw_request_info *info,
struct iw_point *wri, char *extra)
{
struct ieee80211vap *vap = dev->priv;
if (vap->iv_opt_ie == NULL) {
wri->length = 0;
return 0;
}
wri->length = vap->iv_opt_ie_len;
memcpy(extra, vap->iv_opt_ie, vap->iv_opt_ie_len);
return 0;
}
/* the following functions are used by the set/get appiebuf functions */
static int
add_app_ie(unsigned int frame_type_index, struct ieee80211vap *vap,
struct ieee80211req_getset_appiebuf *iebuf)
{
struct ieee80211_ie *ie;
if (!is_valid_ie_list(iebuf->app_buflen, iebuf->app_buf, 1))
return -EINVAL;
/* NB: data.length is validated by the wireless extensions code */
MALLOC(ie, struct ieee80211_ie *, iebuf->app_buflen, M_DEVBUF, M_WAITOK);
if (ie == NULL)
return -ENOMEM;
memcpy(ie, iebuf->app_buf, iebuf->app_buflen);
if (vap->app_ie[frame_type_index].ie != NULL)
FREE(vap->app_ie[frame_type_index].ie, M_DEVBUF);
vap->app_ie[frame_type_index].ie = ie;
vap->app_ie[frame_type_index].length = iebuf->app_buflen;
return 0;
}
static int
remove_app_ie(unsigned int frame_type_index, struct ieee80211vap *vap)
{
struct ieee80211_app_ie *app_ie = &vap->app_ie[frame_type_index];
if (app_ie->ie != NULL) {
FREE(app_ie->ie, M_DEVBUF);
app_ie->ie = NULL;
app_ie->length = 0;
}
return 0;
}
static int
get_app_ie(unsigned int frame_type_index, struct ieee80211vap *vap,
struct ieee80211req_getset_appiebuf *iebuf)
{
struct ieee80211_app_ie *app_ie = &vap->app_ie[frame_type_index];
if (iebuf->app_buflen < app_ie->length)
return -E2BIG;
iebuf->app_buflen = app_ie->length;
memcpy(iebuf->app_buf, app_ie->ie, app_ie->length);
return 0;
}
static int
ieee80211_ioctl_setappiebuf(struct net_device *dev,
struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211req_getset_appiebuf *iebuf =
(struct ieee80211req_getset_appiebuf *)extra;
enum ieee80211_opmode chk_opmode;
int iebuf_len;
int rc = 0;
iebuf_len = data->length - sizeof(struct ieee80211req_getset_appiebuf);
if ((iebuf_len < 0) || (iebuf_len != iebuf->app_buflen) ||
(iebuf->app_buflen > IEEE80211_APPIE_MAX))
return -EINVAL;
switch (iebuf->app_frmtype) {
case IEEE80211_APPIE_FRAME_BEACON:
case IEEE80211_APPIE_FRAME_PROBE_RESP:
case IEEE80211_APPIE_FRAME_ASSOC_RESP:
chk_opmode = IEEE80211_M_HOSTAP;
break;
case IEEE80211_APPIE_FRAME_PROBE_REQ:
case IEEE80211_APPIE_FRAME_ASSOC_REQ:
chk_opmode = IEEE80211_M_STA;
break;
default:
return -EINVAL;
}
if (vap->iv_opmode != chk_opmode)
return -EOPNOTSUPP;
if (iebuf->app_buflen)
rc = add_app_ie(iebuf->app_frmtype, vap, iebuf);
else
rc = remove_app_ie(iebuf->app_frmtype, vap);
if ((iebuf->app_frmtype == IEEE80211_APPIE_FRAME_BEACON) && (rc == 0))
vap->iv_flags_ext |= IEEE80211_FEXT_APPIE_UPDATE;
return rc;
}
static int
ieee80211_ioctl_getappiebuf(struct net_device *dev, struct iw_request_info *info,
struct iw_point *data, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211req_getset_appiebuf *iebuf =
(struct ieee80211req_getset_appiebuf *)extra;
int max_iebuf_len;
int rc = 0;
max_iebuf_len = data->length - sizeof(struct ieee80211req_getset_appiebuf);
if (max_iebuf_len < 0)
return -EINVAL;
if (copy_from_user(iebuf, data->pointer, sizeof(struct ieee80211req_getset_appiebuf)))
return -EFAULT;
if (iebuf->app_buflen > max_iebuf_len)
iebuf->app_buflen = max_iebuf_len;
switch (iebuf->app_frmtype) {
case IEEE80211_APPIE_FRAME_BEACON:
case IEEE80211_APPIE_FRAME_PROBE_RESP:
case IEEE80211_APPIE_FRAME_ASSOC_RESP:
if (vap->iv_opmode == IEEE80211_M_STA)
return -EOPNOTSUPP;
break;
case IEEE80211_APPIE_FRAME_PROBE_REQ:
case IEEE80211_APPIE_FRAME_ASSOC_REQ:
if (vap->iv_opmode != IEEE80211_M_STA)
return -EOPNOTSUPP;
break;
default:
return -EINVAL;
}
rc = get_app_ie(iebuf->app_frmtype, vap, iebuf);
data->length = sizeof(struct ieee80211req_getset_appiebuf) + iebuf->app_buflen;
return rc;
}
static int
ieee80211_ioctl_setfilter(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211req_set_filter *app_filter = (struct ieee80211req_set_filter *)extra;
if ((extra == NULL) || (app_filter->app_filterype & ~IEEE80211_FILTER_TYPE_ALL))
return -EINVAL;
vap->app_filter = app_filter->app_filterype;
return 0;
}
static int
ieee80211_ioctl_setkey(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211req_key *ik = (struct ieee80211req_key *)extra;
struct ieee80211_node *ni;
struct ieee80211_key *wk;
ieee80211_keyix_t kix;
int error, flags, i;
/* Check cipher support, load crypto modules if needed */
if (!ieee80211_crypto_available(vap, ik->ik_type))
return -EOPNOTSUPP;
/* NB: this also checks ik->ik_keylen > sizeof(wk->wk_key) */
if (ik->ik_keylen > sizeof(ik->ik_keydata))
return -E2BIG;
kix = ik->ik_keyix;
if (kix == IEEE80211_KEYIX_NONE) {
/* XXX unicast keys currently must be tx/rx */
if (ik->ik_flags != (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV))
return -EINVAL;
if (vap->iv_opmode == IEEE80211_M_STA) {
ni = ieee80211_ref_node(vap->iv_bss);
This patch augments the current reference counting code with: * Counters for total outstanding instances for each resource type (skb, ath_node and ath_buf) * One pair of acquisition/release functions per resource type in unlocked and one in locked * Adds some more _debug versions of functions in the call chain that acquire/release resources so that the original func/line in the driver as well as the func/line that affected the resource use can be shown in the trace. Intermediate stack frames aren't necessary to trace the leaks. * Changes naming convention for "lock-required" functions to suffix _locked for the versions that expect locking, to be consistent with some other places in the code. * Consolidate debug messages to the helper functions that actually affect the reference count or acquire/release a resource * Additional sanity checks and leak detection (esp for detecting node ref leaks through skb) * skb references are nulled out by the new sbk unref/free function. I've tested these changes extensively and found lots of cases where we didn't get enough node references when cloning skbuff, and where the kernel drops packets due to performance issues and leaks our node references. With these changes and the tracing enabled I have verified that: * TX BUF: tx buffers always go down to zero when the tx queue is done, and you can watch tx queue usage ratio go up and down again as the driver is working. There are no leaks here at the moment, although there *are* some in the madwifi-dfs branch during CAC at the moment. * skbuff leaks in all the common flows are fixed. We were leaking node references in a lot of places where kernel was dropping skb's due to congestion and we were failing to increment node references when cloning skbuffs. These are now detected, as are skbuffs that are reaped by the kernel while still holding a node reference. * the ath_node count works correctly and on an idle system we get about 5 references per station table node, with 2 node instances per VAP. One for the bss and one for the node in the station table, I believe. The ath_node count goes up and down but always lands back at the stable number based on the vaps you have configured and the number of actual stations in the station table. The point here is that it's pretty constant what you will see over time, despite excessive node creation/release in our code during input (esp input_all). Thank god for the slab allocator. git-svn-id: http://madwifi-project.org/svn/madwifi/trunk@2902 0192ed92-7a03-0410-a25b-9323aeb14dbd
2007-11-21 23:14:11 +03:00
if (!IEEE80211_ADDR_EQ(ik->ik_macaddr, ni->ni_bssid)) {
ieee80211_unref_node(&ni);
return -EADDRNOTAVAIL;
This patch augments the current reference counting code with: * Counters for total outstanding instances for each resource type (skb, ath_node and ath_buf) * One pair of acquisition/release functions per resource type in unlocked and one in locked * Adds some more _debug versions of functions in the call chain that acquire/release resources so that the original func/line in the driver as well as the func/line that affected the resource use can be shown in the trace. Intermediate stack frames aren't necessary to trace the leaks. * Changes naming convention for "lock-required" functions to suffix _locked for the versions that expect locking, to be consistent with some other places in the code. * Consolidate debug messages to the helper functions that actually affect the reference count or acquire/release a resource * Additional sanity checks and leak detection (esp for detecting node ref leaks through skb) * skb references are nulled out by the new sbk unref/free function. I've tested these changes extensively and found lots of cases where we didn't get enough node references when cloning skbuff, and where the kernel drops packets due to performance issues and leaks our node references. With these changes and the tracing enabled I have verified that: * TX BUF: tx buffers always go down to zero when the tx queue is done, and you can watch tx queue usage ratio go up and down again as the driver is working. There are no leaks here at the moment, although there *are* some in the madwifi-dfs branch during CAC at the moment. * skbuff leaks in all the common flows are fixed. We were leaking node references in a lot of places where kernel was dropping skb's due to congestion and we were failing to increment node references when cloning skbuffs. These are now detected, as are skbuffs that are reaped by the kernel while still holding a node reference. * the ath_node count works correctly and on an idle system we get about 5 references per station table node, with 2 node instances per VAP. One for the bss and one for the node in the station table, I believe. The ath_node count goes up and down but always lands back at the stable number based on the vaps you have configured and the number of actual stations in the station table. The point here is that it's pretty constant what you will see over time, despite excessive node creation/release in our code during input (esp input_all). Thank god for the slab allocator. git-svn-id: http://madwifi-project.org/svn/madwifi/trunk@2902 0192ed92-7a03-0410-a25b-9323aeb14dbd
2007-11-21 23:14:11 +03:00
}
} else
ni = ieee80211_find_node(&ic->ic_sta, ik->ik_macaddr);
if (ni == NULL)
return -ENOENT;
wk = &ni->ni_ucastkey;
} else if (((uint8_t)IEEE80211_KEYIX_NONE <= kix) &&
(kix < IEEE80211_KEYIX_NONE)) {
/* These values must never be used as they are ambiguous as
* some of the API uses 8-bit integers for keyix. */
return -EINVAL;
} else {
if (kix >= IEEE80211_WEP_NKID)
return -EINVAL;
wk = &vap->iv_nw_keys[kix];
ni = NULL;
/* XXX auto-add group key flag until applications are updated */
if ((ik->ik_flags & IEEE80211_KEY_XMIT) == 0) /* XXX */
ik->ik_flags |= IEEE80211_KEY_GROUP; /* XXX */
}
error = 0;
flags = ik->ik_flags & IEEE80211_KEY_COMMON;
ieee80211_key_update_begin(vap);
if (ieee80211_crypto_newkey(vap, ik->ik_type, flags, wk)) {
wk->wk_keylen = ik->ik_keylen;
/* NB: MIC presence is implied by cipher type */
if (wk->wk_keylen > IEEE80211_KEYBUF_SIZE)
wk->wk_keylen = IEEE80211_KEYBUF_SIZE;
for (i = 0; i < IEEE80211_TID_SIZE; i++)
wk->wk_keyrsc[i] = ik->ik_keyrsc;
wk->wk_keytsc = 0; /* new key, reset */
memset(wk->wk_key, 0, sizeof(wk->wk_key));
memcpy(wk->wk_key, ik->ik_keydata, ik->ik_keylen);
if (!ieee80211_crypto_setkey(vap, wk,
(ni != NULL) ? ni->ni_macaddr : ik->ik_macaddr, ni))
error = -EIO;
else if ((ik->ik_flags & IEEE80211_KEY_DEFAULT))
vap->iv_def_txkey = kix;
} else
error = -ENXIO;
ieee80211_key_update_end(vap);
if (ni != NULL)
ieee80211_unref_node(&ni);
#ifdef ATH_SUPERG_XR
/* set the same params on the xr vap device if exists */
if (vap->iv_xrvap && !(vap->iv_flags & IEEE80211_F_XR))
ieee80211_ioctl_setkey(vap->iv_xrvap->iv_dev, info, w, extra);
#endif
return error;
}
static int
ieee80211_ioctl_getkey(struct net_device *dev, struct iwreq *iwr)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *ni;
struct ieee80211req_key ik;
struct ieee80211_key *wk;
const struct ieee80211_cipher *cip;
ieee80211_keyix_t kix;
if (iwr->u.data.length != sizeof(ik))
return -EINVAL;
if (copy_from_user(&ik, iwr->u.data.pointer, sizeof(ik)))
return -EFAULT;
kix = ik.ik_keyix;
if (kix == IEEE80211_KEYIX_NONE) {
ni = ieee80211_find_node(&ic->ic_sta, ik.ik_macaddr);
if (ni == NULL)
return -ENOENT;
wk = &ni->ni_ucastkey;
} else if (((uint8_t)IEEE80211_KEYIX_NONE <= kix) &&
(kix < IEEE80211_KEYIX_NONE)) {
/* These values must never be used as they are ambiguous as
* some of the API uses 8-bit integers for keyix. */
return -EINVAL;
} else {
if (kix >= IEEE80211_WEP_NKID)
return -EINVAL;
wk = &vap->iv_nw_keys[kix];
IEEE80211_ADDR_COPY(&ik.ik_macaddr, vap->iv_bss->ni_macaddr);
ni = NULL;
}
cip = wk->wk_cipher;
ik.ik_type = cip->ic_cipher;
ik.ik_keylen = wk->wk_keylen;
ik.ik_flags = wk->wk_flags & (IEEE80211_KEY_XMIT | IEEE80211_KEY_RECV);
if (wk->wk_keyix == vap->iv_def_txkey)
ik.ik_flags |= IEEE80211_KEY_DEFAULT;
if (capable(CAP_NET_ADMIN)) {
/* NB: only root can read key data */
ik.ik_keyrsc = wk->wk_keyrsc[0];
ik.ik_keytsc = wk->wk_keytsc;
memcpy(ik.ik_keydata, wk->wk_key, wk->wk_keylen);
if (cip->ic_cipher == IEEE80211_CIPHER_TKIP) {
memcpy(ik.ik_keydata+wk->wk_keylen,
wk->wk_key + IEEE80211_KEYBUF_SIZE,
IEEE80211_MICBUF_SIZE);
ik.ik_keylen += IEEE80211_MICBUF_SIZE;
}
} else {
ik.ik_keyrsc = 0;
ik.ik_keytsc = 0;
memset(ik.ik_keydata, 0, sizeof(ik.ik_keydata));
}
if (ni != NULL)
ieee80211_unref_node(&ni);
return (copy_to_user(iwr->u.data.pointer, &ik, sizeof(ik)) ? -EFAULT : 0);
}
static int
ieee80211_ioctl_delkey(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211req_del_key *dk = (struct ieee80211req_del_key *)extra;
ieee80211_keyix_t kix;
kix = dk->idk_keyix;
if (dk->idk_keyix == (u_int8_t)IEEE80211_KEYIX_NONE)
kix = IEEE80211_KEYIX_NONE;
if (kix == IEEE80211_KEYIX_NONE) {
struct ieee80211_node *ni;
ni = ieee80211_find_node(&ic->ic_sta, dk->idk_macaddr);
if (ni == NULL)
return -ENOENT;
/* XXX error return */
ieee80211_crypto_delkey(vap, &ni->ni_ucastkey, ni);
ieee80211_unref_node(&ni);
} else {
if (kix >= IEEE80211_WEP_NKID)
return -EINVAL;
/* XXX error return */
ieee80211_crypto_delkey(vap, &vap->iv_nw_keys[kix], NULL);
}
return 0;
}
static void
domlme(void *arg, struct ieee80211_node *ni)
{
struct ieee80211req_mlme *mlme = arg;
if (ni->ni_associd != 0) {
IEEE80211_SEND_MGMT(ni,
mlme->im_op == IEEE80211_MLME_DEAUTH ?
IEEE80211_FC0_SUBTYPE_DEAUTH :
IEEE80211_FC0_SUBTYPE_DISASSOC,
mlme->im_reason);
}
ieee80211_node_leave(ni);
}
struct scanlookup { /* XXX: right place for declaration? */
const u_int8_t *mac;
int esslen;
const u_int8_t *essid;
const struct ieee80211_scan_entry *se;
};
/*
* Match mac address and any ssid.
*/
static int
mlmelookup(void *arg, const struct ieee80211_scan_entry *se)
{
struct scanlookup *look = arg;
if (!IEEE80211_ADDR_EQ(look->mac, se->se_macaddr))
return 0;
if (look->esslen != 0) {
if (se->se_ssid[1] != look->esslen)
return 0;
if (memcmp(look->essid, se->se_ssid + 2, look->esslen))
return 0;
}
look->se = se;
return 0;
}
static int
ieee80211_ioctl_setmlme(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211req_mlme *mlme = (struct ieee80211req_mlme *)extra;
struct ieee80211_node *ni;
if (!IS_UP(dev)) {
switch (mlme->im_op) {
case IEEE80211_MLME_DISASSOC:
case IEEE80211_MLME_DEAUTH:
case IEEE80211_MLME_UNAUTHORIZE:
return 0;
default:
return -ENETDOWN;
}
}
switch (mlme->im_op) {
case IEEE80211_MLME_ASSOC:
if (vap->iv_opmode == IEEE80211_M_STA) {
struct scanlookup lookup;
lookup.se = NULL;
lookup.mac = mlme->im_macaddr;
/* XXX use revised api w/ explicit ssid */
lookup.esslen = vap->iv_des_ssid[0].len;
lookup.essid = vap->iv_des_ssid[0].ssid;
ieee80211_scan_iterate(ic, mlmelookup, &lookup);
if (lookup.se != NULL) {
vap->iv_nsdone = 0;
vap->iv_nsparams.result = 0;
if (ieee80211_sta_join(vap, lookup.se))
while (!vap->iv_nsdone)
IEEE80211_RESCHEDULE();
if (!vap->iv_nsparams.result)
return 0;
return -EINVAL;
} else
return -ENOENT;
} else
return -EOPNOTSUPP;
case IEEE80211_MLME_DISASSOC:
case IEEE80211_MLME_DEAUTH:
switch (vap->iv_opmode) {
case IEEE80211_M_STA:
/* XXX not quite right */
ieee80211_new_state(vap, IEEE80211_S_INIT,
mlme->im_reason);
break;
case IEEE80211_M_HOSTAP:
/* NB: the broadcast address means do 'em all */
if (!IEEE80211_ADDR_EQ(mlme->im_macaddr, vap->iv_dev->broadcast)) {
ni = ieee80211_find_node(&ic->ic_sta,
mlme->im_macaddr);
if (ni != NULL) {
if (dev == ni->ni_vap->iv_dev)
domlme(mlme, ni);
ieee80211_unref_node(&ni);
} else
return -ENOENT;
} else
ieee80211_iterate_dev_nodes(dev, &ic->ic_sta, domlme, mlme);
break;
default:
return -EOPNOTSUPP;
}
break;
case IEEE80211_MLME_AUTHORIZE:
case IEEE80211_MLME_UNAUTHORIZE:
if (vap->iv_opmode != IEEE80211_M_HOSTAP)
return -EOPNOTSUPP;
ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr);
if (ni != NULL) {
if (mlme->im_op == IEEE80211_MLME_AUTHORIZE)
ieee80211_node_authorize(ni);
else
ieee80211_node_unauthorize(ni);
ieee80211_unref_node(&ni);
} else
return -ENOENT;
break;
case IEEE80211_MLME_CLEAR_STATS:
if (vap->iv_opmode != IEEE80211_M_HOSTAP)
return -EOPNOTSUPP;
ni = ieee80211_find_node(&ic->ic_sta, mlme->im_macaddr);
if (ni != NULL) {
/* clear statistics */
memset(&ni->ni_stats, 0, sizeof(struct ieee80211_nodestats));
ieee80211_unref_node(&ni);
} else
return -ENOENT;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int
ieee80211_ioctl_wdsmac(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct sockaddr *sa = (struct sockaddr *)extra;
if (!IEEE80211_ADDR_NULL(vap->wds_mac)) {
printk("%s: Failed to add WDS MAC: " MAC_FMT "\n", dev->name,
MAC_ADDR(sa->sa_data));
printk("%s: Device already has WDS mac address attached,"
" remove first\n", dev->name);
return -1;
}
memcpy(vap->wds_mac, sa->sa_data, IEEE80211_ADDR_LEN);
printk("%s: Added WDS MAC: " MAC_FMT "\n", dev->name,
MAC_ADDR(vap->wds_mac));
if (IS_UP(vap->iv_dev)) {
/* Force us back to scan state to force us to go back through RUN
* state and create/pin the WDS peer node into memory. */
return ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
}
return 0;
}
static int
ieee80211_ioctl_wdsdelmac(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct sockaddr *sa = (struct sockaddr *)extra;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *wds_ni;
/* WDS Mac address filed already? */
if (IEEE80211_ADDR_NULL(vap->wds_mac))
return 0;
/* Compare suplied MAC address with WDS MAC of this interface
* remove when mac address is known
*/
if (memcmp(vap->wds_mac, sa->sa_data, IEEE80211_ADDR_LEN) == 0) {
if (IS_UP(vap->iv_dev)) {
wds_ni = ieee80211_find_txnode(vap, vap->wds_mac);
if (wds_ni != NULL) {
/* Release reference created by find node */
ieee80211_unref_node(&wds_ni);
/* Release reference created by transition to RUN state,
* [pinning peer node into the table] */
ieee80211_unref_node(&wds_ni);
}
}
memset(vap->wds_mac, 0x00, IEEE80211_ADDR_LEN);
if (IS_UP(vap->iv_dev)) {
/* This leaves a dead WDS node, until started again */
return ic->ic_reset(ic->ic_dev);
}
return 0;
}
printk("%s: WDS MAC address " MAC_FMT " is not known by this interface\n",
dev->name, MAC_ADDR(sa->sa_data));
return -1;
}
/*
* kick associated station with the given MAC address.
*/
static int
ieee80211_ioctl_kickmac(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct sockaddr *sa = (struct sockaddr *)extra;
struct ieee80211req_mlme mlme;
if (sa->sa_family != ARPHRD_ETHER)
return -EINVAL;
/* Setup a MLME request for disassociation of the given MAC */
mlme.im_op = IEEE80211_MLME_DISASSOC;
mlme.im_reason = IEEE80211_REASON_UNSPECIFIED;
IEEE80211_ADDR_COPY(&(mlme.im_macaddr), sa->sa_data);
/* Send the MLME request and return the result. */
return ieee80211_ioctl_setmlme(dev, info, w, (char *)&mlme);
}
static int
ieee80211_ioctl_addmac(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct sockaddr *sa = (struct sockaddr *)extra;
const struct ieee80211_aclator *acl = vap->iv_acl;
if (acl == NULL) {
acl = ieee80211_aclator_get("mac");
if (acl == NULL || !acl->iac_attach(vap))
return -EINVAL;
vap->iv_acl = acl;
}
acl->iac_add(vap, sa->sa_data);
return 0;
}
static int
ieee80211_ioctl_delmac(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct sockaddr *sa = (struct sockaddr *)extra;
const struct ieee80211_aclator *acl = vap->iv_acl;
if (acl == NULL) {
acl = ieee80211_aclator_get("mac");
if (acl == NULL || !acl->iac_attach(vap))
return -EINVAL;
vap->iv_acl = acl;
}
acl->iac_remove(vap, sa->sa_data);
return 0;
}
static int
ieee80211_ioctl_setchanlist(struct net_device *dev,
struct iw_request_info *info, void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211req_chanlist *list =
(struct ieee80211req_chanlist *)extra;
u_char chanlist[IEEE80211_CHAN_BYTES];
int i, j, nchan;
memset(chanlist, 0, sizeof(chanlist));
/*
* Since channel 0 is not available for DS, channel 1
* is assigned to LSB on WaveLAN.
*/
if (ic->ic_phytype == IEEE80211_T_DS)
i = 1;
else
i = 0;
nchan = 0;
for (j = 0; i <= IEEE80211_CHAN_MAX; i++, j++) {
/*
* NB: silently discard unavailable channels so users
* can specify 1-255 to get all available channels.
*/
if (isset(list->ic_channels, j) && isset(ic->ic_chan_avail, i)) {
setbit(chanlist, i);
nchan++;
}
}
if (nchan == 0) /* no valid channels, disallow */
return -EINVAL;
if (ic->ic_bsschan != IEEE80211_CHAN_ANYC && /* XXX */
isclr(chanlist, ic->ic_bsschan->ic_ieee))
ic->ic_bsschan = IEEE80211_CHAN_ANYC; /* invalidate */
memcpy(ic->ic_chan_active, chanlist, sizeof(ic->ic_chan_active));
if (IS_UP_AUTO(vap))
ieee80211_new_state(vap, IEEE80211_S_SCAN, 0);
return 0;
}
static int
ieee80211_ioctl_getchanlist(struct net_device *dev,
struct iw_request_info *info, void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
memcpy(extra, ic->ic_chan_active, sizeof(ic->ic_chan_active));
return 0;
}
static int
ieee80211_ioctl_getchaninfo(struct net_device *dev,
struct iw_request_info *info, void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211req_chaninfo chans;
u_int8_t reported[IEEE80211_CHAN_BYTES]; /* XXX stack usage? */
int i;
memset(&chans, 0, sizeof(chans));
memset(&reported, 0, sizeof(reported));
for (i = 0; i < ic->ic_nchans; i++) {
const struct ieee80211_channel *c = &ic->ic_channels[i];
const struct ieee80211_channel *c1 = c;
if (isclr(reported, c->ic_ieee)) {
setbit(reported, c->ic_ieee);
/* pick turbo channel over non-turbo channel, and
* 11g channel over 11b channel */
if (IEEE80211_IS_CHAN_A(c))
c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_TURBO_A);
if (IEEE80211_IS_CHAN_ANYG(c))
c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_TURBO_G);
else if (IEEE80211_IS_CHAN_B(c)) {
c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_TURBO_G);
if (!c1)
c1 = findchannel(ic, c->ic_ieee, IEEE80211_MODE_11G);
}
if (c1)
c = c1;
/* Copy the entire structure, whereas it used to just copy a few fields */
memcpy(&chans.ic_chans[chans.ic_nchans], c, sizeof(struct ieee80211_channel));
if (++chans.ic_nchans >= IEEE80211_CHAN_MAX)
break;
}
}
memcpy(extra, &chans, sizeof(struct ieee80211req_chaninfo));
return 0;
}
static int
ieee80211_ioctl_setwmmparams(struct net_device *dev,
struct iw_request_info *info, void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
unsigned int *param = (unsigned int *) extra;
unsigned int ac = (param[1] < WME_NUM_AC) ? param[1] : WME_AC_BE;
unsigned int bss = param[2];
struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme;
switch (param[0]) {
case IEEE80211_WMMPARAMS_CWMIN:
if (param[3] > 15)
return -EINVAL;
if (bss) {
wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_logcwmin = param[3];
if ((wme->wme_flags & WME_F_AGGRMODE) == 0)
wme->wme_bssChanParams.cap_wmeParams[ac].wmep_logcwmin = param[3];
} else {
wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_logcwmin = param[3];
wme->wme_chanParams.cap_wmeParams[ac].wmep_logcwmin = param[3];
}
ieee80211_wme_updateparams(vap);
break;
case IEEE80211_WMMPARAMS_CWMAX:
if (param[3] > 15)
return -EINVAL;
if (bss) {
wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_logcwmax = param[3];
if ((wme->wme_flags & WME_F_AGGRMODE) == 0)
wme->wme_bssChanParams.cap_wmeParams[ac].wmep_logcwmax = param[3];
} else {
wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_logcwmax = param[3];
wme->wme_chanParams.cap_wmeParams[ac].wmep_logcwmax = param[3];
}
ieee80211_wme_updateparams(vap);
break;
case IEEE80211_WMMPARAMS_AIFS:
if (param[3] > 15)
return -EINVAL;
if (bss) {
wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_aifsn = param[3];
if ((wme->wme_flags & WME_F_AGGRMODE) == 0)
wme->wme_bssChanParams.cap_wmeParams[ac].wmep_aifsn = param[3];
} else {
wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_aifsn = param[3];
wme->wme_chanParams.cap_wmeParams[ac].wmep_aifsn = param[3];
}
ieee80211_wme_updateparams(vap);
break;
case IEEE80211_WMMPARAMS_TXOPLIMIT:
if (param[3] > 8192)
return -EINVAL;
if (bss) {
wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_txopLimit
= IEEE80211_US_TO_TXOP(param[3]);
if ((wme->wme_flags & WME_F_AGGRMODE) == 0)
wme->wme_bssChanParams.cap_wmeParams[ac].wmep_txopLimit =
IEEE80211_US_TO_TXOP(param[3]);
} else {
wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_txopLimit
= IEEE80211_US_TO_TXOP(param[3]);
wme->wme_chanParams.cap_wmeParams[ac].wmep_txopLimit
= IEEE80211_US_TO_TXOP(param[3]);
}
ieee80211_wme_updateparams(vap);
break;
case IEEE80211_WMMPARAMS_ACM:
if (!bss || param[3] > 1)
return -EINVAL;
/* ACM bit applies to BSS case only */
wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm = param[3];
if ((wme->wme_flags & WME_F_AGGRMODE) == 0)
wme->wme_bssChanParams.cap_wmeParams[ac].wmep_acm = param[3];
break;
case IEEE80211_WMMPARAMS_NOACKPOLICY:
if (bss || param[3] > 1)
return -EINVAL;
/* ack policy applies to non-BSS case only */
wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy = param[3];
wme->wme_chanParams.cap_wmeParams[ac].wmep_noackPolicy = param[3];
break;
default:
break;
}
return 0;
}
static int
ieee80211_ioctl_getwmmparams(struct net_device *dev,
struct iw_request_info *info, void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
unsigned int *param = (unsigned int *) extra;
unsigned int ac = (param[1] < WME_NUM_AC) ? param[1] : WME_AC_BE;
struct ieee80211_wme_state *wme = &vap->iv_ic->ic_wme;
struct chanAccParams *chanParams = (param[2] == 0) ?
&(wme->wme_chanParams) : &(wme->wme_bssChanParams);
switch (param[0]) {
case IEEE80211_WMMPARAMS_CWMIN:
param[0] = chanParams->cap_wmeParams[ac].wmep_logcwmin;
break;
case IEEE80211_WMMPARAMS_CWMAX:
param[0] = chanParams->cap_wmeParams[ac].wmep_logcwmax;
break;
case IEEE80211_WMMPARAMS_AIFS:
param[0] = chanParams->cap_wmeParams[ac].wmep_aifsn;
break;
case IEEE80211_WMMPARAMS_TXOPLIMIT:
param[0] = IEEE80211_TXOP_TO_US(chanParams->cap_wmeParams[ac].wmep_txopLimit);
break;
case IEEE80211_WMMPARAMS_ACM:
param[0] = wme->wme_wmeBssChanParams.cap_wmeParams[ac].wmep_acm;
break;
case IEEE80211_WMMPARAMS_NOACKPOLICY:
param[0] = wme->wme_wmeChanParams.cap_wmeParams[ac].wmep_noackPolicy;
break;
default:
break;
}
return 0;
}
static int
ieee80211_ioctl_getwpaie(struct net_device *dev, struct iwreq *iwr)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *ni;
struct ieee80211req_wpaie wpaie;
if (iwr->u.data.length != sizeof(wpaie))
return -EINVAL;
if (copy_from_user(&wpaie, iwr->u.data.pointer, IEEE80211_ADDR_LEN))
return -EFAULT;
ni = ieee80211_find_node(&ic->ic_sta, wpaie.wpa_macaddr);
if (ni != NULL) {
memset(wpaie.wpa_ie, 0, sizeof(wpaie.wpa_ie));
if (ni->ni_wpa_ie != NULL) {
int ielen = ni->ni_wpa_ie[1] + 2;
if (ielen > sizeof(wpaie.wpa_ie))
ielen = sizeof(wpaie.wpa_ie);
memcpy(wpaie.wpa_ie, ni->ni_wpa_ie, ielen);
}
if (ni->ni_rsn_ie != NULL) {
int ielen = ni->ni_rsn_ie[1] + 2;
if (ielen > sizeof(wpaie.rsn_ie))
ielen = sizeof(wpaie.rsn_ie);
memcpy(wpaie.rsn_ie, ni->ni_rsn_ie, ielen);
}
ieee80211_unref_node(&ni);
return (copy_to_user(iwr->u.data.pointer, &wpaie, sizeof(wpaie)) ?
-EFAULT : 0);
} else
return -ENOENT;
}
static int
ieee80211_ioctl_getstastats(struct net_device *dev, struct iwreq *iwr)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211_node *ni;
u_int8_t macaddr[IEEE80211_ADDR_LEN];
const int off = __offsetof(struct ieee80211req_sta_stats, is_stats);
int error;
if (iwr->u.data.length < off)
return -E2BIG;
if (copy_from_user(macaddr, iwr->u.data.pointer, IEEE80211_ADDR_LEN))
return -EFAULT;
ni = ieee80211_find_node(&ic->ic_sta, macaddr);
if (ni != NULL) {
if (iwr->u.data.length > sizeof(struct ieee80211req_sta_stats))
iwr->u.data.length = sizeof(struct ieee80211req_sta_stats);
/* NB: copy out only the statistics */
error = copy_to_user(iwr->u.data.pointer + off, &ni->ni_stats,
iwr->u.data.length - off);
ieee80211_unref_node(&ni);
return (error ? -EFAULT : 0);
} else
return -ENOENT;
}
struct scanreq { /* XXX: right place for declaration? */
struct ieee80211req_scan_result *sr;
size_t space;
};
static size_t
scan_space(const struct ieee80211_scan_entry *se, int *ielen)
{
*ielen = 0;
if (se->se_rsn_ie != NULL)
*ielen += 2 + se->se_rsn_ie[1];
if (se->se_wpa_ie != NULL)
*ielen += 2 + se->se_wpa_ie[1];
if (se->se_wme_ie != NULL)
*ielen += 2 + se->se_wme_ie[1];
if (se->se_ath_ie != NULL)
*ielen += 2 + se->se_ath_ie[1];
return roundup(sizeof(struct ieee80211req_scan_result) +
se->se_ssid[1] + *ielen, sizeof(u_int32_t));
}
static int
get_scan_space(void *arg, const struct ieee80211_scan_entry *se)
{
struct scanreq *req = arg;
int ielen;
req->space += scan_space(se, &ielen);
return 0;
}
static int
get_scan_result(void *arg, const struct ieee80211_scan_entry *se)
{
struct scanreq *req = arg;
struct ieee80211req_scan_result *sr;
int ielen, len, nr, nxr;
u_int8_t *cp;
len = scan_space(se, &ielen);
if (len > req->space) {
printk("[madwifi] %s() : Not enough space.\n", __FUNCTION__);
return 0;
}
sr = req->sr;
memset(sr, 0, sizeof(*sr));
sr->isr_ssid_len = se->se_ssid[1];
/* XXX watch for overflow */
sr->isr_ie_len = ielen;
sr->isr_len = len;
sr->isr_freq = se->se_chan->ic_freq;
sr->isr_flags = se->se_chan->ic_flags;
sr->isr_rssi = se->se_rssi;
sr->isr_intval = se->se_intval;
sr->isr_capinfo = se->se_capinfo;
sr->isr_erp = se->se_erp;
IEEE80211_ADDR_COPY(sr->isr_bssid, se->se_bssid);
/* XXX bounds check */
nr = se->se_rates[1];
memcpy(sr->isr_rates, se->se_rates + 2, nr);
nxr = se->se_xrates[1];
memcpy(sr->isr_rates+nr, se->se_xrates + 2, nxr);
sr->isr_nrates = nr + nxr;
cp = (u_int8_t *)(sr + 1);
memcpy(cp, se->se_ssid + 2, sr->isr_ssid_len);
cp += sr->isr_ssid_len;
if (se->se_rsn_ie != NULL) {
memcpy(cp, se->se_rsn_ie, 2 + se->se_rsn_ie[1]);
cp += 2 + se->se_rsn_ie[1];
}
if (se->se_wpa_ie != NULL) {
memcpy(cp, se->se_wpa_ie, 2 + se->se_wpa_ie[1]);
cp += 2 + se->se_wpa_ie[1];
}
if (se->se_wme_ie != NULL) {
memcpy(cp, se->se_wme_ie, 2 + se->se_wme_ie[1]);
cp += 2 + se->se_wme_ie[1];
}
if (se->se_ath_ie != NULL) {
memcpy(cp, se->se_ath_ie, 2 + se->se_ath_ie[1]);
cp += 2 + se->se_ath_ie[1];
}
req->space -= len;
req->sr = (struct ieee80211req_scan_result *)(((u_int8_t *)sr) + len);
return 0;
}
static int
ieee80211_ioctl_getscanresults(struct net_device *dev, struct iwreq *iwr)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct scanreq req;
int error;
if (iwr->u.data.length < sizeof(struct scanreq))
return -E2BIG;
error = 0;
req.space = 0;
ieee80211_scan_iterate(ic, get_scan_space, &req);
if (req.space > iwr->u.data.length)
req.space = iwr->u.data.length;
if (req.space > 0) {
size_t space;
void *p;
space = req.space;
MALLOC(p, void *, space, M_TEMP, M_WAITOK);
if (p == NULL)
return -ENOMEM;
req.sr = p;
ieee80211_scan_iterate(ic, get_scan_result, &req);
iwr->u.data.length = space - req.space;
error = copy_to_user(iwr->u.data.pointer, p, iwr->u.data.length);
FREE(p, M_TEMP);
} else
iwr->u.data.length = 0;
return (error ? -EFAULT : 0);
}
struct stainforeq { /* XXX: right place for declaration? */
struct ieee80211vap *vap;
struct ieee80211req_sta_info *si;
size_t space;
};
static size_t
sta_space(const struct ieee80211_node *ni, size_t *ielen)
{
*ielen = 0;
if (ni->ni_rsn_ie != NULL)
*ielen += 2+ni->ni_rsn_ie[1];
if (ni->ni_wpa_ie != NULL)
*ielen += 2+ni->ni_wpa_ie[1];
if (ni->ni_wme_ie != NULL)
*ielen += 2+ni->ni_wme_ie[1];
if (ni->ni_ath_ie != NULL)
*ielen += 2+ni->ni_ath_ie[1];
return roundup(sizeof(struct ieee80211req_sta_info) + *ielen,
sizeof(u_int32_t));
}
static void
get_sta_space(void *arg, struct ieee80211_node *ni)
{
struct stainforeq *req = arg;
struct ieee80211vap *vap = ni->ni_vap;
size_t ielen;
if (vap != req->vap && vap != req->vap->iv_xrvap) /* only entries for this vap */
return;
if ((vap->iv_opmode == IEEE80211_M_HOSTAP ||
vap->iv_opmode == IEEE80211_M_WDS) &&
ni->ni_associd == 0) /* only associated stations or a WDS peer */
return;
req->space += sta_space(ni, &ielen);
}
static void
get_sta_info(void *arg, struct ieee80211_node *ni)
{
struct stainforeq *req = arg;
struct ieee80211vap *vap = ni->ni_vap;
struct ieee80211com *ic = vap->iv_ic;
struct ieee80211req_sta_info *si;
size_t ielen, len;
u_int8_t *cp;
if (vap != req->vap && vap != req->vap->iv_xrvap) /* only entries for this vap (or) xrvap */
return;
if ((vap->iv_opmode == IEEE80211_M_HOSTAP ||
vap->iv_opmode == IEEE80211_M_WDS) &&
ni->ni_associd == 0) /* only associated stations or a WDS peer */
return;
if (ni->ni_chan == IEEE80211_CHAN_ANYC) /* XXX bogus entry */
return;
len = sta_space(ni, &ielen);
if (len > req->space)
return;
si = req->si;
si->isi_len = len;
si->isi_ie_len = ielen;
si->isi_freq = ni->ni_chan->ic_freq;
si->isi_flags = ni->ni_chan->ic_flags;
si->isi_state = ni->ni_flags;
si->isi_authmode = ni->ni_authmode;
si->isi_rssi = ic->ic_node_getrssi(ni);
si->isi_capinfo = ni->ni_capinfo;
si->isi_athflags = ni->ni_ath_flags;
si->isi_erp = ni->ni_erp;
IEEE80211_ADDR_COPY(si->isi_macaddr, ni->ni_macaddr);
si->isi_nrates = ni->ni_rates.rs_nrates;
if (si->isi_nrates > 15)
si->isi_nrates = 15;
memcpy(si->isi_rates, ni->ni_rates.rs_rates, si->isi_nrates);
si->isi_txrate = ni->ni_txrate;
si->isi_ie_len = ielen;
si->isi_associd = ni->ni_associd;
si->isi_txpower = ni->ni_txpower;
si->isi_vlan = ni->ni_vlan;
if (ni->ni_flags & IEEE80211_NODE_QOS) {
memcpy(si->isi_txseqs, ni->ni_txseqs, sizeof(ni->ni_txseqs));
memcpy(si->isi_rxseqs, ni->ni_rxseqs, sizeof(ni->ni_rxseqs));
} else {
si->isi_txseqs[0] = ni->ni_txseqs[0];
si->isi_rxseqs[0] = ni->ni_rxseqs[0];
}
si->isi_uapsd = ni->ni_uapsd;
if (vap == req->vap->iv_xrvap)
si->isi_opmode = IEEE80211_STA_OPMODE_XR;
else
si->isi_opmode = IEEE80211_STA_OPMODE_NORMAL;
/* NB: leave all cases in case we relax ni_associd == 0 check */
if (ieee80211_node_is_authorized(ni))
si->isi_inact = vap->iv_inact_run;
else if (ni->ni_associd != 0)
si->isi_inact = vap->iv_inact_auth;
else
si->isi_inact = vap->iv_inact_init;
si->isi_inact = (si->isi_inact - ni->ni_inact) * IEEE80211_INACT_WAIT;
cp = (u_int8_t *)(si+1);
if (ni->ni_rsn_ie != NULL) {
memcpy(cp, ni->ni_rsn_ie, 2 + ni->ni_rsn_ie[1]);
cp += 2 + ni->ni_rsn_ie[1];
}
if (ni->ni_wpa_ie != NULL) {
memcpy(cp, ni->ni_wpa_ie, 2 + ni->ni_wpa_ie[1]);
cp += 2 + ni->ni_wpa_ie[1];
}
if (ni->ni_wme_ie != NULL) {
memcpy(cp, ni->ni_wme_ie, 2 + ni->ni_wme_ie[1]);
cp += 2 + ni->ni_wme_ie[1];
}
if (ni->ni_ath_ie != NULL) {
memcpy(cp, ni->ni_ath_ie, 2 + ni->ni_ath_ie[1]);
cp += 2 + ni->ni_ath_ie[1];
}
req->si = (struct ieee80211req_sta_info *)(((u_int8_t *)si) + len);
req->space -= len;
}
static int
ieee80211_ioctl_getstainfo(struct net_device *dev, struct iwreq *iwr)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
struct stainforeq req;
int error;
if (iwr->u.data.length < sizeof(struct stainforeq))
return -E2BIG;
/* estimate space required for station info */
error = 0;
req.space = sizeof(struct stainforeq);
req.vap = vap;
ieee80211_iterate_nodes(&ic->ic_sta, get_sta_space, &req);
if (req.space > iwr->u.data.length)
req.space = iwr->u.data.length;
if (req.space > 0) {
size_t space;
void *p;
space = req.space;
MALLOC(p, void *, space, M_TEMP, M_WAITOK);
req.si = (struct ieee80211req_sta_info *)p;
ieee80211_iterate_nodes(&ic->ic_sta, get_sta_info, &req);
iwr->u.data.length = space - req.space;
error = copy_to_user(iwr->u.data.pointer, p, iwr->u.data.length);
FREE(p, M_TEMP);
} else
iwr->u.data.length = 0;
return (error ? -EFAULT : 0);
}
static void
pre_announced_chanswitch(struct net_device *dev, u_int32_t channel, u_int32_t tbtt) {
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
/* now flag the beacon update to include the channel switch IE */
ic->ic_flags |= IEEE80211_F_CHANSWITCH;
ic->ic_chanchange_chan = channel;
ic->ic_chanchange_tbtt = tbtt;
}
static int
ieee80211_ioctl_chanswitch(struct net_device *dev, struct iw_request_info *info,
void *w, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct ieee80211com *ic = vap->iv_ic;
unsigned int *param = (unsigned int *) extra;
if (!(ic->ic_flags & IEEE80211_F_DOTH))
return 0;
pre_announced_chanswitch(dev, param[0], param[1]);
return 0;
}
#if WIRELESS_EXT >= 18
static int
ieee80211_ioctl_siwmlme(struct net_device *dev,
struct iw_request_info *info, struct iw_point *erq, char *data)
{
struct ieee80211req_mlme mlme;
struct iw_mlme *wextmlme = (struct iw_mlme *)data;
memset(&mlme, 0, sizeof(mlme));
switch (wextmlme->cmd) {
case IW_MLME_DEAUTH:
mlme.im_op = IEEE80211_MLME_DEAUTH;
break;
case IW_MLME_DISASSOC:
mlme.im_op = IEEE80211_MLME_DISASSOC;
break;
default:
return -EOPNOTSUPP;
}
mlme.im_reason = wextmlme->reason_code;
memcpy(mlme.im_macaddr, wextmlme->addr.sa_data, IEEE80211_ADDR_LEN);
return ieee80211_ioctl_setmlme(dev, NULL, NULL, (char*)&mlme);
}
static int
ieee80211_ioctl_giwgenie(struct net_device *dev,
struct iw_request_info *info, struct iw_point *out, char *buf)
{
struct ieee80211vap *vap = dev->priv;
if (out->length < vap->iv_opt_ie_len)
return -E2BIG;
return ieee80211_ioctl_getoptie(dev, info, out, buf);
}
static int
ieee80211_ioctl_siwgenie(struct net_device *dev,
struct iw_request_info *info, struct iw_point *erq, char *data)
{
return ieee80211_ioctl_setoptie(dev, info, erq, data);
}
static int
siwauth_wpa_version(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int ver = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_WPA;
if ((ver & IW_AUTH_WPA_VERSION_WPA) && (ver & IW_AUTH_WPA_VERSION_WPA2))
args[1] = 3;
else if (ver & IW_AUTH_WPA_VERSION_WPA2)
args[1] = 2;
else if (ver & IW_AUTH_WPA_VERSION_WPA)
args[1] = 1;
else
args[1] = 0;
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
iwcipher2ieee80211cipher(int iwciph)
{
switch (iwciph) {
case IW_AUTH_CIPHER_NONE:
return IEEE80211_CIPHER_NONE;
case IW_AUTH_CIPHER_WEP40:
case IW_AUTH_CIPHER_WEP104:
return IEEE80211_CIPHER_WEP;
case IW_AUTH_CIPHER_TKIP:
return IEEE80211_CIPHER_TKIP;
case IW_AUTH_CIPHER_CCMP:
return IEEE80211_CIPHER_AES_CCM;
}
return -1;
}
static int
ieee80211cipher2iwcipher(int ieee80211ciph)
{
switch (ieee80211ciph) {
case IEEE80211_CIPHER_NONE:
return IW_AUTH_CIPHER_NONE;
case IEEE80211_CIPHER_WEP:
return IW_AUTH_CIPHER_WEP104;
case IEEE80211_CIPHER_TKIP:
return IW_AUTH_CIPHER_TKIP;
case IEEE80211_CIPHER_AES_CCM:
return IW_AUTH_CIPHER_CCMP;
}
return -1;
}
/* TODO We don't enforce wep key lengths. */
static int
siwauth_cipher_pairwise(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int iwciph = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_UCASTCIPHER;
args[1] = iwcipher2ieee80211cipher(iwciph);
if (args[1] < 0) {
printk(KERN_WARNING "%s: unknown pairwise cipher %d\n",
dev->name, iwciph);
return -EINVAL;
}
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
/* TODO We don't enforce wep key lengths. */
static int
siwauth_cipher_group(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int iwciph = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_MCASTCIPHER;
args[1] = iwcipher2ieee80211cipher(iwciph);
if (args[1] < 0) {
printk(KERN_WARNING "%s: unknown group cipher %d\n",
dev->name, iwciph);
return -EINVAL;
}
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_key_mgmt(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int iwkm = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_KEYMGTALGS;
args[1] = WPA_ASE_NONE;
if (iwkm & IW_AUTH_KEY_MGMT_802_1X)
args[1] |= WPA_ASE_8021X_UNSPEC;
if (iwkm & IW_AUTH_KEY_MGMT_PSK)
args[1] |= WPA_ASE_8021X_PSK;
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_tkip_countermeasures(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int args[2];
args[0] = IEEE80211_PARAM_COUNTERMEASURES;
args[1] = erq->value;
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_drop_unencrypted(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int args[2];
args[0] = IEEE80211_PARAM_DROPUNENCRYPTED;
args[1] = erq->value;
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_80211_auth_alg(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
#define VALID_ALGS_MASK (IW_AUTH_ALG_OPEN_SYSTEM|IW_AUTH_ALG_SHARED_KEY|IW_AUTH_ALG_LEAP)
int mode = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_AUTHMODE;
if (mode & ~VALID_ALGS_MASK)
return -EINVAL;
if (mode & IW_AUTH_ALG_LEAP) {
args[1] = IEEE80211_AUTH_8021X;
} else if ((mode & IW_AUTH_ALG_SHARED_KEY) &&
(mode & IW_AUTH_ALG_OPEN_SYSTEM)) {
args[1] = IEEE80211_AUTH_AUTO;
} else if (mode & IW_AUTH_ALG_SHARED_KEY) {
args[1] = IEEE80211_AUTH_SHARED;
} else {
args[1] = IEEE80211_AUTH_OPEN;
}
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_wpa_enabled(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int enabled = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_WPA;
if (enabled)
args[1] = 3; /* enable WPA1 and WPA2 */
else
args[1] = 0; /* disable WPA1 and WPA2 */
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_rx_unencrypted_eapol(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rxunenc = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_DROPUNENC_EAPOL;
if (rxunenc)
args[1] = 1;
else
args[1] = 0;
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_roaming_control(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int roam = erq->value;
int args[2];
args[0] = IEEE80211_PARAM_ROAMING;
switch (roam) {
case IW_AUTH_ROAMING_ENABLE:
args[1] = IEEE80211_ROAMING_AUTO;
break;
case IW_AUTH_ROAMING_DISABLE:
args[1] = IEEE80211_ROAMING_MANUAL;
break;
default:
return -EINVAL;
}
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
static int
siwauth_privacy_invoked(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int args[2];
args[0] = IEEE80211_PARAM_PRIVACY;
args[1] = erq->value;
return ieee80211_ioctl_setparam(dev, NULL, NULL, (char*)args);
}
/*
* If this function is invoked it means someone is using the wireless extensions
* API instead of the private madwifi ioctls. That's fine. We translate their
* request into the format used by the private ioctls. Note that the
* iw_request_info and iw_param structures are not the same ones as the
* private ioctl handler expects. Luckily, the private ioctl handler doesn't
* do anything with those at the moment. We pass NULL for those, because in
* case someone does modify the ioctl handler to use those values, a null
* pointer will be easier to debug than other bad behavior.
*/
static int
ieee80211_ioctl_siwauth(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc = -EOPNOTSUPP;
switch (erq->flags & IW_AUTH_INDEX) {
case IW_AUTH_WPA_VERSION:
rc = siwauth_wpa_version(dev, info, erq, buf);
break;
case IW_AUTH_CIPHER_PAIRWISE:
rc = siwauth_cipher_pairwise(dev, info, erq, buf);
break;
case IW_AUTH_CIPHER_GROUP:
rc = siwauth_cipher_group(dev, info, erq, buf);
break;
case IW_AUTH_KEY_MGMT:
rc = siwauth_key_mgmt(dev, info, erq, buf);
break;
case IW_AUTH_TKIP_COUNTERMEASURES:
rc = siwauth_tkip_countermeasures(dev, info, erq, buf);
break;
case IW_AUTH_DROP_UNENCRYPTED:
rc = siwauth_drop_unencrypted(dev, info, erq, buf);
break;
case IW_AUTH_80211_AUTH_ALG:
rc = siwauth_80211_auth_alg(dev, info, erq, buf);
break;
case IW_AUTH_WPA_ENABLED:
rc = siwauth_wpa_enabled(dev, info, erq, buf);
break;
case IW_AUTH_RX_UNENCRYPTED_EAPOL:
rc = siwauth_rx_unencrypted_eapol(dev, info, erq, buf);
break;
case IW_AUTH_ROAMING_CONTROL:
rc = siwauth_roaming_control(dev, info, erq, buf);
break;
case IW_AUTH_PRIVACY_INVOKED:
rc = siwauth_privacy_invoked(dev, info, erq, buf);
break;
default:
printk(KERN_WARNING "%s: unknown SIOCSIWAUTH flag %d\n",
dev->name, erq->flags);
break;
}
return rc;
}
static int
giwauth_wpa_version(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int ver;
int rc;
int arg = IEEE80211_PARAM_WPA;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
switch (arg) {
case 1:
ver = IW_AUTH_WPA_VERSION_WPA;
break;
case 2:
ver = IW_AUTH_WPA_VERSION_WPA2;
break;
case 3:
ver = IW_AUTH_WPA_VERSION|IW_AUTH_WPA_VERSION_WPA2;
break;
default:
ver = IW_AUTH_WPA_VERSION_DISABLED;
break;
}
erq->value = ver;
return rc;
}
static int
giwauth_cipher_pairwise(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc;
int arg = IEEE80211_PARAM_UCASTCIPHER;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = ieee80211cipher2iwcipher(arg);
if (erq->value < 0)
return -EINVAL;
return 0;
}
static int
giwauth_cipher_group(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc;
int arg = IEEE80211_PARAM_MCASTCIPHER;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = ieee80211cipher2iwcipher(arg);
if (erq->value < 0)
return -EINVAL;
return 0;
}
static int
giwauth_key_mgmt(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int arg;
int rc;
arg = IEEE80211_PARAM_KEYMGTALGS;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = 0;
if (arg & WPA_ASE_8021X_UNSPEC)
erq->value |= IW_AUTH_KEY_MGMT_802_1X;
if (arg & WPA_ASE_8021X_PSK)
erq->value |= IW_AUTH_KEY_MGMT_PSK;
return 0;
}
static int
giwauth_tkip_countermeasures(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int arg;
int rc;
arg = IEEE80211_PARAM_COUNTERMEASURES;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = arg;
return 0;
}
static int
giwauth_drop_unencrypted(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int arg;
int rc;
arg = IEEE80211_PARAM_DROPUNENCRYPTED;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = arg;
return 0;
}
static int
giwauth_80211_auth_alg(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
return -EOPNOTSUPP;
}
static int
giwauth_wpa_enabled(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc;
int arg = IEEE80211_PARAM_WPA;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = arg;
return 0;
}
static int
giwauth_rx_unencrypted_eapol(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
return -EOPNOTSUPP;
}
static int
giwauth_roaming_control(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc;
int arg;
arg = IEEE80211_PARAM_ROAMING;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
switch (arg) {
case IEEE80211_ROAMING_DEVICE:
case IEEE80211_ROAMING_AUTO:
erq->value = IW_AUTH_ROAMING_ENABLE;
break;
default:
erq->value = IW_AUTH_ROAMING_DISABLE;
break;
}
return 0;
}
static int
giwauth_privacy_invoked(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc;
int arg;
arg = IEEE80211_PARAM_PRIVACY;
rc = ieee80211_ioctl_getparam(dev, NULL, NULL, (char*)&arg);
if (rc)
return rc;
erq->value = arg;
return 0;
}
static int
ieee80211_ioctl_giwauth(struct net_device *dev,
struct iw_request_info *info, struct iw_param *erq, char *buf)
{
int rc = -EOPNOTSUPP;
switch (erq->flags & IW_AUTH_INDEX) {
case IW_AUTH_WPA_VERSION:
rc = giwauth_wpa_version(dev, info, erq, buf);
break;
case IW_AUTH_CIPHER_PAIRWISE:
rc = giwauth_cipher_pairwise(dev, info, erq, buf);
break;
case IW_AUTH_CIPHER_GROUP:
rc = giwauth_cipher_group(dev, info, erq, buf);
break;
case IW_AUTH_KEY_MGMT:
rc = giwauth_key_mgmt(dev, info, erq, buf);
break;
case IW_AUTH_TKIP_COUNTERMEASURES:
rc = giwauth_tkip_countermeasures(dev, info, erq, buf);
break;
case IW_AUTH_DROP_UNENCRYPTED:
rc = giwauth_drop_unencrypted(dev, info, erq, buf);
break;
case IW_AUTH_80211_AUTH_ALG:
rc = giwauth_80211_auth_alg(dev, info, erq, buf);
break;
case IW_AUTH_WPA_ENABLED:
rc = giwauth_wpa_enabled(dev, info, erq, buf);
break;
case IW_AUTH_RX_UNENCRYPTED_EAPOL:
rc = giwauth_rx_unencrypted_eapol(dev, info, erq, buf);
break;
case IW_AUTH_ROAMING_CONTROL:
rc = giwauth_roaming_control(dev, info, erq, buf);
break;
case IW_AUTH_PRIVACY_INVOKED:
rc = giwauth_privacy_invoked(dev, info, erq, buf);
break;
default:
printk(KERN_WARNING "%s: unknown SIOCGIWAUTH flag %d\n",
dev->name, erq->flags);
break;
}
return rc;
}
/*
* Retrieve information about a key. Open question: should we allow
* callers to retrieve unicast keys based on a supplied MAC address?
* The ipw2200 reference implementation doesn't, so we don't either.
*/
static int
ieee80211_ioctl_giwencodeext(struct net_device *dev,
struct iw_request_info *info, struct iw_point *erq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct iw_encode_ext *ext;
struct ieee80211_key *wk;
ieee80211_keyix_t kix;
int max_key_len;
int error;
if (!capable(CAP_NET_ADMIN))
return -EPERM;
max_key_len = erq->length - sizeof(*ext);
if (max_key_len < 0)
return -EINVAL;
ext = (struct iw_encode_ext *)extra;
error = getiwkeyix(vap, erq, &kix);
if (error < 0)
return error;
wk = &vap->iv_nw_keys[kix];
if (wk->wk_keylen > max_key_len)
return -E2BIG;
erq->flags = kix + 1;
memset(ext, 0, sizeof(*ext));
ext->key_len = wk->wk_keylen;
memcpy(ext->key, wk->wk_key, wk->wk_keylen);
/* flags */
if (wk->wk_flags & IEEE80211_KEY_GROUP)
ext->ext_flags |= IW_ENCODE_EXT_GROUP_KEY;
/* algorithm */
switch (wk->wk_cipher->ic_cipher) {
case IEEE80211_CIPHER_NONE:
ext->alg = IW_ENCODE_ALG_NONE;
erq->flags |= IW_ENCODE_DISABLED;
break;
case IEEE80211_CIPHER_WEP:
ext->alg = IW_ENCODE_ALG_WEP;
break;
case IEEE80211_CIPHER_TKIP:
ext->alg = IW_ENCODE_ALG_TKIP;
break;
case IEEE80211_CIPHER_AES_OCB:
case IEEE80211_CIPHER_AES_CCM:
case IEEE80211_CIPHER_CKIP:
ext->alg = IW_ENCODE_ALG_CCMP;
break;
default:
return -EINVAL;
}
return 0;
}
static int
ieee80211_ioctl_siwencodeext(struct net_device *dev,
struct iw_request_info *info, struct iw_point *erq, char *extra)
{
struct ieee80211vap *vap = dev->priv;
struct iw_encode_ext *ext = (struct iw_encode_ext *)extra;
struct ieee80211req_key kr;
ieee80211_keyix_t kix;
int error;
error = getiwkeyix(vap, erq, &kix);
if (error < 0)
return error;
if (ext->key_len > (erq->length - sizeof(struct iw_encode_ext)))
return -E2BIG;
if (ext->alg == IW_ENCODE_ALG_NONE) {
/* convert to the format used by IEEE_80211_IOCTL_DELKEY */
struct ieee80211req_del_key dk;
memset(&dk, 0, sizeof(dk));
dk.idk_keyix = kix;
memcpy(&dk.idk_macaddr, ext->addr.sa_data, IEEE80211_ADDR_LEN);
return ieee80211_ioctl_delkey(dev, NULL, NULL, (char*)&dk);
}
/* TODO This memcmp for the broadcast address seems hackish, but
* mimics what wpa supplicant was doing. The wpa supplicant comments
* make it sound like they were having trouble with
* IEEE80211_IOCTL_SETKEY and static WEP keys. It might be worth
* figuring out what their trouble was so the rest of this function
* can be implemented in terms of ieee80211_ioctl_setkey */
if (ext->alg == IW_ENCODE_ALG_WEP &&
memcmp(ext->addr.sa_data, "\xff\xff\xff\xff\xff\xff",
IEEE80211_ADDR_LEN) == 0) {
/* convert to the format used by SIOCSIWENCODE. The old
* format just had the key in the extra buf, whereas the
* new format has the key tacked on to the end of the
* iw_encode_ext structure */
struct iw_request_info oldinfo;
struct iw_point olderq;
char *key;
memset(&oldinfo, 0, sizeof(oldinfo));
oldinfo.cmd = SIOCSIWENCODE;
oldinfo.flags = info->flags;
memset(&olderq, 0, sizeof(olderq));
olderq.flags = erq->flags;
olderq.pointer = erq->pointer;
olderq.length = ext->key_len;
key = ext->key;
return ieee80211_ioctl_siwencode(dev, &oldinfo, &olderq, key);
}
/* convert to the format used by IEEE_80211_IOCTL_SETKEY */
memset(&kr, 0, sizeof(kr));
switch (ext->alg) {
case IW_ENCODE_ALG_WEP:
kr.ik_type = IEEE80211_CIPHER_WEP;
break;
case IW_ENCODE_ALG_TKIP:
kr.ik_type = IEEE80211_CIPHER_TKIP;
break;
case IW_ENCODE_ALG_CCMP:
kr.ik_type = IEEE80211_CIPHER_AES_CCM;
break;
default:
printk(KERN_WARNING "%s: unknown algorithm %d\n",
dev->name, ext->alg);
return -EINVAL;
}
kr.ik_keyix = kix;
if (ext->key_len > sizeof(kr.ik_keydata)) {
printk(KERN_WARNING "%s: key size %d is too large\n",
dev->name, ext->key_len);
return -E2BIG;
}
memcpy(kr.ik_keydata, ext->key, ext->key_len);
kr.ik_keylen = ext->key_len;
kr.ik_flags = IEEE80211_KEY_RECV;
if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY)
kr.ik_flags |= IEEE80211_KEY_GROUP;
if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) {
kr.ik_flags |= IEEE80211_KEY_XMIT | IEEE80211_KEY_DEFAULT;
memcpy(kr.ik_macaddr, ext->addr.sa_data, IEEE80211_ADDR_LEN);
}
if (ext->ext_flags & IW_ENCODE_EXT_RX_SEQ_VALID) {
memcpy(&kr.ik_keyrsc, ext->rx_seq, sizeof(kr.ik_keyrsc));
}
return ieee80211_ioctl_setkey(dev, NULL, NULL, (char*)&kr);
}
#endif /* WIRELESS_EXT >= 18 */
#define IW_PRIV_TYPE_OPTIE \
IW_PRIV_BLOB_TYPE_ENCODING(IEEE80211_MAX_OPT_IE)
#define IW_PRIV_TYPE_KEY \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_key))
#define IW_PRIV_TYPE_DELKEY \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_del_key))
#define IW_PRIV_TYPE_MLME \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_mlme))
#define IW_PRIV_TYPE_CHANLIST \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_chanlist))
#define IW_PRIV_TYPE_CHANINFO \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_chaninfo))
#define IW_PRIV_TYPE_APPIEBUF \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_getset_appiebuf) + IEEE80211_APPIE_MAX)
#define IW_PRIV_TYPE_FILTER \
IW_PRIV_BLOB_TYPE_ENCODING(sizeof(struct ieee80211req_set_filter))
static const struct iw_priv_args ieee80211_priv_args[] = {
/* NB: setoptie & getoptie are !IW_PRIV_SIZE_FIXED */
{ IEEE80211_IOCTL_SETOPTIE,
IW_PRIV_TYPE_OPTIE, 0, "setoptie" },
{ IEEE80211_IOCTL_GETOPTIE,
0, IW_PRIV_TYPE_OPTIE, "getoptie" },
{ IEEE80211_IOCTL_SETKEY,
IW_PRIV_TYPE_KEY | IW_PRIV_SIZE_FIXED, 0, "setkey" },
{ IEEE80211_IOCTL_DELKEY,
IW_PRIV_TYPE_DELKEY | IW_PRIV_SIZE_FIXED, 0, "delkey" },
{ IEEE80211_IOCTL_SETMLME,
IW_PRIV_TYPE_MLME | IW_PRIV_SIZE_FIXED, 0, "setmlme" },
{ IEEE80211_IOCTL_ADDMAC,
IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"addmac" },
{ IEEE80211_IOCTL_DELMAC,
IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"delmac" },
{ IEEE80211_IOCTL_KICKMAC,
IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0, "kickmac"},
{ IEEE80211_IOCTL_WDSADDMAC,
IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"wds_add" },
{ IEEE80211_IOCTL_WDSDELMAC,
IW_PRIV_TYPE_ADDR | IW_PRIV_SIZE_FIXED | 1, 0,"wds_del" },
{ IEEE80211_IOCTL_SETCHANLIST,
IW_PRIV_TYPE_CHANLIST | IW_PRIV_SIZE_FIXED, 0,"setchanlist" },
{ IEEE80211_IOCTL_GETCHANLIST,
0, IW_PRIV_TYPE_CHANLIST | IW_PRIV_SIZE_FIXED,"getchanlist" },
{ IEEE80211_IOCTL_GETCHANINFO,
0, IW_PRIV_TYPE_CHANINFO | IW_PRIV_SIZE_FIXED,"getchaninfo" },
{ IEEE80211_IOCTL_SETMODE,
IW_PRIV_TYPE_CHAR | 6, 0, "mode" },
{ IEEE80211_IOCTL_GETMODE,
0, IW_PRIV_TYPE_CHAR | 6, "get_mode" },
{ IEEE80211_IOCTL_SETWMMPARAMS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 4, 0,"setwmmparams" },
{ IEEE80211_IOCTL_GETWMMPARAMS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "getwmmparams" },
/*
* These depends on sub-ioctl support which added in version 12.
*/
{ IEEE80211_IOCTL_SETWMMPARAMS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"" },
{ IEEE80211_IOCTL_GETWMMPARAMS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "" },
/* sub-ioctl handlers */
{ IEEE80211_WMMPARAMS_CWMIN,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"cwmin" },
{ IEEE80211_WMMPARAMS_CWMIN,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_cwmin" },
{ IEEE80211_WMMPARAMS_CWMAX,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"cwmax" },
{ IEEE80211_WMMPARAMS_CWMAX,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_cwmax" },
{ IEEE80211_WMMPARAMS_AIFS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"aifs" },
{ IEEE80211_WMMPARAMS_AIFS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_aifs" },
{ IEEE80211_WMMPARAMS_TXOPLIMIT,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"txoplimit" },
{ IEEE80211_WMMPARAMS_TXOPLIMIT,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_txoplimit" },
{ IEEE80211_WMMPARAMS_ACM,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"acm" },
{ IEEE80211_WMMPARAMS_ACM,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_acm" },
{ IEEE80211_WMMPARAMS_NOACKPOLICY,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3, 0,"noackpolicy" },
{ IEEE80211_WMMPARAMS_NOACKPOLICY,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_noackpolicy" },
{ IEEE80211_IOCTL_SETPARAM,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, 0, "setparam" },
/*
* These depends on sub-ioctl support which added in version 12.
*/
{ IEEE80211_IOCTL_GETPARAM,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "getparam" },
/* sub-ioctl handlers */
{ IEEE80211_IOCTL_SETPARAM,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "" },
{ IEEE80211_IOCTL_GETPARAM,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "" },
/* sub-ioctl definitions */
{ IEEE80211_PARAM_AUTHMODE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "authmode" },
{ IEEE80211_PARAM_AUTHMODE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_authmode" },
{ IEEE80211_PARAM_PROTMODE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "protmode" },
{ IEEE80211_PARAM_PROTMODE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_protmode" },
{ IEEE80211_PARAM_MCASTCIPHER,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mcastcipher" },
{ IEEE80211_PARAM_MCASTCIPHER,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_mcastcipher" },
{ IEEE80211_PARAM_MCASTKEYLEN,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mcastkeylen" },
{ IEEE80211_PARAM_MCASTKEYLEN,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_mcastkeylen" },
{ IEEE80211_PARAM_UCASTCIPHERS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ucastciphers" },
{ IEEE80211_PARAM_UCASTCIPHERS,
/*
* NB: can't use "get_ucastciphers" due to iwpriv command names
* must be <IFNAMESIZ which is 16.
*/
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_uciphers" },
{ IEEE80211_PARAM_UCASTCIPHER,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ucastcipher" },
{ IEEE80211_PARAM_UCASTCIPHER,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_ucastcipher" },
{ IEEE80211_PARAM_UCASTKEYLEN,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ucastkeylen" },
{ IEEE80211_PARAM_UCASTKEYLEN,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_ucastkeylen" },
{ IEEE80211_PARAM_KEYMGTALGS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "keymgtalgs" },
{ IEEE80211_PARAM_KEYMGTALGS,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_keymgtalgs" },
{ IEEE80211_PARAM_RSNCAPS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rsncaps" },
{ IEEE80211_PARAM_RSNCAPS,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rsncaps" },
{ IEEE80211_PARAM_ROAMING,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "hostroaming" },
{ IEEE80211_PARAM_ROAMING,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_hostroaming" },
{ IEEE80211_PARAM_PRIVACY,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "privacy" },
{ IEEE80211_PARAM_PRIVACY,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_privacy" },
{ IEEE80211_PARAM_COUNTERMEASURES,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "countermeasures" },
{ IEEE80211_PARAM_COUNTERMEASURES,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_countermeas" },
{ IEEE80211_PARAM_DROPUNENCRYPTED,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dropunencrypted" },
{ IEEE80211_PARAM_DROPUNENCRYPTED,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_dropunencry" },
{ IEEE80211_PARAM_WPA,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "wpa" },
{ IEEE80211_PARAM_WPA,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_wpa" },
{ IEEE80211_PARAM_DRIVER_CAPS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "driver_caps" },
{ IEEE80211_PARAM_DRIVER_CAPS,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_driver_caps" },
{ IEEE80211_PARAM_MACCMD,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "maccmd" },
{ IEEE80211_PARAM_WMM,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "wmm" },
{ IEEE80211_PARAM_WMM,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_wmm" },
{ IEEE80211_PARAM_HIDESSID,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "hide_ssid" },
{ IEEE80211_PARAM_HIDESSID,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_hide_ssid" },
{ IEEE80211_PARAM_APBRIDGE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ap_bridge" },
{ IEEE80211_PARAM_APBRIDGE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_ap_bridge" },
{ IEEE80211_PARAM_INACT,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "inact" },
{ IEEE80211_PARAM_INACT,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_inact" },
{ IEEE80211_PARAM_INACT_AUTH,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "inact_auth" },
{ IEEE80211_PARAM_INACT_AUTH,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_inact_auth" },
{ IEEE80211_PARAM_INACT_INIT,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "inact_init" },
{ IEEE80211_PARAM_INACT_INIT,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_inact_init" },
{ IEEE80211_PARAM_ABOLT,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "abolt" },
{ IEEE80211_PARAM_ABOLT,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_abolt" },
{ IEEE80211_PARAM_DTIM_PERIOD,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dtim_period" },
{ IEEE80211_PARAM_DTIM_PERIOD,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_dtim_period" },
/* XXX bintval chosen to avoid 16-char limit */
{ IEEE80211_PARAM_BEACON_INTERVAL,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bintval" },
{ IEEE80211_PARAM_BEACON_INTERVAL,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bintval" },
{ IEEE80211_PARAM_DOTH,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth" },
{ IEEE80211_PARAM_DOTH,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_doth" },
{ IEEE80211_PARAM_PWRTARGET,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth_pwrtgt" },
{ IEEE80211_PARAM_PWRTARGET,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_doth_pwrtgt" },
{ IEEE80211_PARAM_GENREASSOC,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth_reassoc" },
/* continuous transmission (for regulatory agency testing) */
{ IEEE80211_PARAM_TXCONT,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "txcont" },
{ IEEE80211_PARAM_TXCONT,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_txcont" },
{ IEEE80211_PARAM_TXCONT_RATE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "txcontrate" },
{ IEEE80211_PARAM_TXCONT_RATE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_txcontrate" },
{ IEEE80211_PARAM_TXCONT_POWER,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "txcontpower" },
{ IEEE80211_PARAM_TXCONT_POWER,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_txcontpower" },
{ IEEE80211_PARAM_COMPRESSION,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "compression" },
{ IEEE80211_PARAM_COMPRESSION, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_compression" },
{ IEEE80211_PARAM_FF,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ff" },
{ IEEE80211_PARAM_FF, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_ff" },
{ IEEE80211_PARAM_TURBO,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "turbo" },
{ IEEE80211_PARAM_TURBO, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_turbo" },
{ IEEE80211_PARAM_XR,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "xr" },
{ IEEE80211_PARAM_XR, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_xr" },
{ IEEE80211_PARAM_BURST,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "burst" },
{ IEEE80211_PARAM_BURST, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_burst" },
{ IEEE80211_IOCTL_CHANSWITCH,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, 0, "doth_chanswitch" },
{ IEEE80211_PARAM_PUREG,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "pureg" },
{ IEEE80211_PARAM_PUREG, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_pureg" },
{ IEEE80211_PARAM_AR,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "ar" },
{ IEEE80211_PARAM_AR, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_ar" },
{ IEEE80211_PARAM_WDS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "wds" },
{ IEEE80211_PARAM_WDS, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_wds" },
{ IEEE80211_PARAM_BGSCAN,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bgscan" },
{ IEEE80211_PARAM_BGSCAN, 0,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bgscan" },
{ IEEE80211_PARAM_BGSCAN_IDLE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bgscanidle" },
{ IEEE80211_PARAM_BGSCAN_IDLE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bgscanidle" },
{ IEEE80211_PARAM_BGSCAN_INTERVAL,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bgscanintvl" },
{ IEEE80211_PARAM_BGSCAN_INTERVAL,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bgscanintvl" },
{ IEEE80211_PARAM_MCAST_RATE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "mcast_rate" },
{ IEEE80211_PARAM_MCAST_RATE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_mcast_rate" },
{ IEEE80211_PARAM_COVERAGE_CLASS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "coverageclass" },
{ IEEE80211_PARAM_COVERAGE_CLASS,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_coveragecls" },
{ IEEE80211_PARAM_COUNTRY_IE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "countryie" },
{ IEEE80211_PARAM_COUNTRY_IE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_countryie" },
{ IEEE80211_PARAM_SCANVALID,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "scanvalid" },
{ IEEE80211_PARAM_SCANVALID,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_scanvalid" },
{ IEEE80211_PARAM_REGCLASS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "regclass" },
{ IEEE80211_PARAM_REGCLASS,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_regclass" },
{ IEEE80211_PARAM_DROPUNENC_EAPOL,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dropunenceapol" },
{ IEEE80211_PARAM_DROPUNENC_EAPOL,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_dropunencea" },
{ IEEE80211_PARAM_SHPREAMBLE,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "shpreamble" },
{ IEEE80211_PARAM_SHPREAMBLE,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_shpreamble" },
/*
* NB: these should be roamrssi* etc, but iwpriv usurps all
* strings that start with roam!
*/
{ IEEE80211_PARAM_ROAM_RSSI_11A,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rssi11a" },
{ IEEE80211_PARAM_ROAM_RSSI_11A,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rssi11a" },
{ IEEE80211_PARAM_ROAM_RSSI_11B,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rssi11b" },
{ IEEE80211_PARAM_ROAM_RSSI_11B,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rssi11b" },
{ IEEE80211_PARAM_ROAM_RSSI_11G,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rssi11g" },
{ IEEE80211_PARAM_ROAM_RSSI_11G,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rssi11g" },
{ IEEE80211_PARAM_ROAM_RATE_11A,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rate11a" },
{ IEEE80211_PARAM_ROAM_RATE_11A,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rate11a" },
{ IEEE80211_PARAM_ROAM_RATE_11B,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rate11b" },
{ IEEE80211_PARAM_ROAM_RATE_11B,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rate11b" },
{ IEEE80211_PARAM_ROAM_RATE_11G,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rate11g" },
{ IEEE80211_PARAM_ROAM_RATE_11G,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rate11g" },
{ IEEE80211_PARAM_UAPSDINFO,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "uapsd" },
{ IEEE80211_PARAM_UAPSDINFO,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_uapsd" },
{ IEEE80211_PARAM_SLEEP,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "sleep" },
{ IEEE80211_PARAM_SLEEP,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_sleep" },
{ IEEE80211_PARAM_QOSNULL,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "qosnull" },
{ IEEE80211_PARAM_PSPOLL,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "pspoll" },
{ IEEE80211_PARAM_EOSPDROP,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "eospdrop" },
{ IEEE80211_PARAM_EOSPDROP,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_eospdrop" },
{ IEEE80211_PARAM_MARKDFS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "markdfs" },
{ IEEE80211_PARAM_MARKDFS,
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_markdfs" },
{ IEEE80211_IOCTL_SET_APPIEBUF,
IW_PRIV_TYPE_APPIEBUF, 0, "setiebuf" },
{ IEEE80211_IOCTL_GET_APPIEBUF,
0, IW_PRIV_TYPE_APPIEBUF, "getiebuf" },
{ IEEE80211_IOCTL_FILTERFRAME,
IW_PRIV_TYPE_FILTER , 0, "setfilter" },
{ IEEE80211_IOCTL_HALMAP,
0, 0, "dump_hal_map" },
#ifdef ATH_REVERSE_ENGINEERING
/*
Diagnostic dump of device registers
*/
{ IEEE80211_PARAM_DUMPREGS,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dumpregs" },
{ IEEE80211_IOCTL_READREG,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "readreg" },
{ IEEE80211_IOCTL_WRITEREG,
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2, 0, "writereg" },
#endif /* #ifdef ATH_REVERSE_ENGINEERING */
};
#define set_handler(x,f) [x - SIOCIWFIRST] = (iw_handler) f
static const iw_handler ieee80211_handlers[] = {
set_handler(SIOCGIWNAME, ieee80211_ioctl_giwname),
set_handler(SIOCSIWFREQ, ieee80211_ioctl_siwfreq),
set_handler(SIOCGIWFREQ, ieee80211_ioctl_giwfreq),
set_handler(SIOCSIWMODE, ieee80211_ioctl_siwmode),
set_handler(SIOCGIWMODE, ieee80211_ioctl_giwmode),
set_handler(SIOCSIWSENS, ieee80211_ioctl_siwsens),
set_handler(SIOCGIWSENS, ieee80211_ioctl_giwsens),
set_handler(SIOCGIWRANGE, ieee80211_ioctl_giwrange),
set_handler(SIOCSIWSPY, ieee80211_ioctl_setspy),
set_handler(SIOCGIWSPY, ieee80211_ioctl_getspy),
#if WIRELESS_EXT >= 16
set_handler(SIOCSIWTHRSPY, ieee80211_ioctl_setthrspy),
set_handler(SIOCGIWTHRSPY, ieee80211_ioctl_getthrspy),
#endif
set_handler(SIOCSIWAP, ieee80211_ioctl_siwap),
set_handler(SIOCGIWAP, ieee80211_ioctl_giwap),
#ifdef SIOCSIWMLME
set_handler(SIOCSIWMLME, ieee80211_ioctl_siwmlme),
#endif
set_handler(SIOCGIWAPLIST, ieee80211_ioctl_iwaplist),
#ifdef SIOCGIWSCAN
set_handler(SIOCSIWSCAN, ieee80211_ioctl_siwscan),
set_handler(SIOCGIWSCAN, ieee80211_ioctl_giwscan),
#endif /* SIOCGIWSCAN */
set_handler(SIOCSIWESSID, ieee80211_ioctl_siwessid),
set_handler(SIOCGIWESSID, ieee80211_ioctl_giwessid),
set_handler(SIOCSIWNICKN, ieee80211_ioctl_siwnickn),
set_handler(SIOCGIWNICKN, ieee80211_ioctl_giwnickn),
set_handler(SIOCSIWRATE, ieee80211_ioctl_siwrate),
set_handler(SIOCGIWRATE, ieee80211_ioctl_giwrate),
set_handler(SIOCSIWRTS, ieee80211_ioctl_siwrts),
set_handler(SIOCGIWRTS, ieee80211_ioctl_giwrts),
set_handler(SIOCSIWFRAG, ieee80211_ioctl_siwfrag),
set_handler(SIOCGIWFRAG, ieee80211_ioctl_giwfrag),
set_handler(SIOCSIWTXPOW, ieee80211_ioctl_siwtxpow),
set_handler(SIOCGIWTXPOW, ieee80211_ioctl_giwtxpow),
set_handler(SIOCSIWRETRY, ieee80211_ioctl_siwretry),
set_handler(SIOCGIWRETRY, ieee80211_ioctl_giwretry),
set_handler(SIOCSIWENCODE, ieee80211_ioctl_siwencode),
set_handler(SIOCGIWENCODE, ieee80211_ioctl_giwencode),
set_handler(SIOCSIWPOWER, ieee80211_ioctl_siwpower),
set_handler(SIOCGIWPOWER, ieee80211_ioctl_giwpower),
#if WIRELESS_EXT >= 18
set_handler(SIOCSIWGENIE, ieee80211_ioctl_siwgenie),
set_handler(SIOCGIWGENIE, ieee80211_ioctl_giwgenie),
set_handler(SIOCSIWAUTH, ieee80211_ioctl_siwauth),
set_handler(SIOCGIWAUTH, ieee80211_ioctl_giwauth),
set_handler(SIOCSIWENCODEEXT, ieee80211_ioctl_siwencodeext),
set_handler(SIOCGIWENCODEEXT, ieee80211_ioctl_giwencodeext),
#endif /* WIRELESS_EXT >= 18 */
};
#define set_priv(x,f) [x - SIOCIWFIRSTPRIV] = (iw_handler) f
static const iw_handler ieee80211_priv_handlers[] = {
set_priv(IEEE80211_IOCTL_SETPARAM, ieee80211_ioctl_setparam),
set_priv(IEEE80211_IOCTL_GETPARAM, ieee80211_ioctl_getparam),
set_priv(IEEE80211_IOCTL_SETMODE, ieee80211_ioctl_setmode),
set_priv(IEEE80211_IOCTL_GETMODE, ieee80211_ioctl_getmode),
set_priv(IEEE80211_IOCTL_SETWMMPARAMS, ieee80211_ioctl_setwmmparams),
set_priv(IEEE80211_IOCTL_GETWMMPARAMS, ieee80211_ioctl_getwmmparams),
set_priv(IEEE80211_IOCTL_SETCHANLIST, ieee80211_ioctl_setchanlist),
set_priv(IEEE80211_IOCTL_GETCHANLIST, ieee80211_ioctl_getchanlist),
set_priv(IEEE80211_IOCTL_CHANSWITCH, ieee80211_ioctl_chanswitch),
set_priv(IEEE80211_IOCTL_GET_APPIEBUF, ieee80211_ioctl_getappiebuf),
set_priv(IEEE80211_IOCTL_SET_APPIEBUF, ieee80211_ioctl_setappiebuf),
set_priv(IEEE80211_IOCTL_FILTERFRAME, ieee80211_ioctl_setfilter),
set_priv(IEEE80211_IOCTL_GETCHANINFO, ieee80211_ioctl_getchaninfo),
set_priv(IEEE80211_IOCTL_SETOPTIE, ieee80211_ioctl_setoptie),
set_priv(IEEE80211_IOCTL_GETOPTIE, ieee80211_ioctl_getoptie),
set_priv(IEEE80211_IOCTL_SETMLME, ieee80211_ioctl_setmlme),
set_priv(IEEE80211_IOCTL_SETKEY, ieee80211_ioctl_setkey),
set_priv(IEEE80211_IOCTL_DELKEY, ieee80211_ioctl_delkey),
set_priv(IEEE80211_IOCTL_HALMAP, ieee80211_ioctl_hal_map),
set_priv(IEEE80211_IOCTL_ADDMAC, ieee80211_ioctl_addmac),
set_priv(IEEE80211_IOCTL_DELMAC, ieee80211_ioctl_delmac),
set_priv(IEEE80211_IOCTL_WDSADDMAC, ieee80211_ioctl_wdsmac),
set_priv(IEEE80211_IOCTL_WDSDELMAC, ieee80211_ioctl_wdsdelmac),
set_priv(IEEE80211_IOCTL_KICKMAC, ieee80211_ioctl_kickmac),
#ifdef ATH_REVERSE_ENGINEERING
set_priv(IEEE80211_IOCTL_READREG, ieee80211_ioctl_readreg),
set_priv(IEEE80211_IOCTL_WRITEREG, ieee80211_ioctl_writereg),
#endif /* #ifdef ATH_REVERSE_ENGINEERING */
};
static struct iw_handler_def ieee80211_iw_handler_def = {
.standard = (iw_handler *) ieee80211_handlers,
.num_standard = ARRAY_SIZE(ieee80211_handlers),
.private = (iw_handler *) ieee80211_priv_handlers,
.num_private = ARRAY_SIZE(ieee80211_priv_handlers),
.private_args = (struct iw_priv_args *) ieee80211_priv_args,
.num_private_args = ARRAY_SIZE(ieee80211_priv_args),
#if IW_HANDLER_VERSION >= 7
.get_wireless_stats = ieee80211_iw_getstats,
#endif
};
/*
* Handle private ioctl requests.
*/
static int
ieee80211_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
struct ieee80211vap *vap = dev->priv;
switch (cmd) {
case SIOCG80211STATS:
return copy_to_user(ifr->ifr_data, &vap->iv_stats,
sizeof (vap->iv_stats)) ? -EFAULT : 0;
case SIOC80211IFDESTROY:
if (!capable(CAP_NET_ADMIN))
return -EPERM;
ieee80211_stop(vap->iv_dev); /* force state before cleanup */
vap->iv_ic->ic_vap_delete(vap);
return 0;
case IEEE80211_IOCTL_GETKEY:
return ieee80211_ioctl_getkey(dev, (struct iwreq *) ifr);
case IEEE80211_IOCTL_GETWPAIE:
return ieee80211_ioctl_getwpaie(dev, (struct iwreq *) ifr);
case IEEE80211_IOCTL_STA_STATS:
return ieee80211_ioctl_getstastats(dev, (struct iwreq *) ifr);
case IEEE80211_IOCTL_STA_INFO:
return ieee80211_ioctl_getstainfo(dev, (struct iwreq *) ifr);
case IEEE80211_IOCTL_SCAN_RESULTS:
return ieee80211_ioctl_getscanresults(dev, (struct iwreq *)ifr);
}
return -EOPNOTSUPP;
}
/*
* Create a virtual ap. This is public as it must be implemented
* outside our control (e.g. in the driver).
*/
int
ieee80211_ioctl_create_vap(struct ieee80211com *ic, struct ifreq *ifr, struct net_device *mdev)
{
struct ieee80211_clone_params cp;
struct ieee80211vap *vap;
char name[IFNAMSIZ];
if (!capable(CAP_NET_ADMIN))
return -EPERM;
if (copy_from_user(&cp, ifr->ifr_data, sizeof(cp)))
return -EFAULT;
strncpy(name, cp.icp_name, sizeof(name));
vap = ieee80211_create_vap(ic, name, mdev, cp.icp_opmode, cp.icp_flags);
if (vap == NULL)
return -EIO;
/* return final device name */
strncpy(ifr->ifr_name, vap->iv_dev->name, IFNAMSIZ);
return 0;
}
EXPORT_SYMBOL(ieee80211_ioctl_create_vap);
/*
* Create a virtual ap. This is public as it must be implemented
* outside our control (e.g. in the driver).
* Must be called with rtnl_lock held
*/
struct ieee80211vap*
ieee80211_create_vap(struct ieee80211com *ic, char *name,
struct net_device *mdev, int opmode, int opflags)
{
return ic->ic_vap_create(ic, name, opmode, opflags, mdev);
}
EXPORT_SYMBOL(ieee80211_create_vap);
void
ieee80211_ioctl_vattach(struct ieee80211vap *vap)
{
struct net_device *dev = vap->iv_dev;
dev->do_ioctl = ieee80211_ioctl;
#if IW_HANDLER_VERSION < 7
dev->get_wireless_stats = ieee80211_iw_getstats;
#endif
dev->wireless_handlers = &ieee80211_iw_handler_def;
}
void
ieee80211_ioctl_vdetach(struct ieee80211vap *vap)
{
}