mirror of
https://github.com/proski/madwifi
synced 2024-11-28 17:23:11 +03:00
02bac30645
git-svn-id: http://madwifi-project.org/svn/madwifi/trunk@3809 0192ed92-7a03-0410-a25b-9323aeb14dbd
5869 lines
168 KiB
C
5869 lines
168 KiB
C
/*-
|
|
* 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;
|
|
if (retv)
|
|
IEEE80211_DPRINTF(vap, IEEE80211_MSG_DEBUG, "%s returns %d\n",
|
|
__func__, retv);
|
|
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(&ap_addr->sa_data,
|
|
vap->iv_dev->broadcast))
|
|
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_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 {
|
|
/*
|
|
* Interpret 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],
|
|
ic->ic_rssi_ewma ?
|
|
ic->ic_node_getrssi(ni) : 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_dfs_cac_time(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_dfs_cac_time(ic);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ieee80211_get_dfs_excl_period(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_dfs_excl_period(ic);
|
|
return 0;
|
|
}
|
|
static int
|
|
ieee80211_set_dfs_cac_time(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_dfs_cac_time(ic, params[1]);
|
|
return 0;
|
|
}
|
|
static int
|
|
ieee80211_set_dfs_excl_period (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_dfs_excl_period(ic, params[1]);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ieee80211_get_dfs_testmode(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_dfs_testmode(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_dfs_testmode(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_dfs_testmode(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_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
|
|
ieee80211_ioctl_radar(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;
|
|
if (!(ic->ic_flags & IEEE80211_F_DOTH))
|
|
return 0;
|
|
params[0] = ic->ic_test_radar(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], ¶ms[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
|
|
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;
|
|
struct iw_request_info *info;
|
|
char *current_ev;
|
|
char *end_buf;
|
|
int mode;
|
|
};
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 27) && !defined(IW_REQUEST_FLAG_COMPAT)
|
|
#define iwe_stream_add_event(a, b, c, d, e) iwe_stream_add_event(b, c, d, e)
|
|
#define iwe_stream_add_point(a, b, c, d, e) iwe_stream_add_point(b, c, d, e)
|
|
#define iwe_stream_add_value(a, b, c, d, e, f) \
|
|
iwe_stream_add_value(b, c, d, e, f)
|
|
#define iwe_stream_lcp_len(a) IW_EV_LCP_LEN
|
|
#endif
|
|
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(req->info, 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(req->info, 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(req->info, 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(req->info, 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(req->info, 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(req->info, 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 + iwe_stream_lcp_len(req->info);
|
|
/* 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(req->info,
|
|
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(req->info,
|
|
current_ev, current_val, end_buf,
|
|
&iwe, IW_EV_PARAM_LEN);
|
|
}
|
|
}
|
|
/* remove fixed header if no rates were added */
|
|
if ((current_val - current_ev) > iwe_stream_lcp_len(req->info)) {
|
|
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(req->info, 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(req->info,
|
|
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(req->info,
|
|
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(req->info,
|
|
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(req->info,
|
|
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.info = info;
|
|
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, bmiss;
|
|
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_RSSI_EWMA:
|
|
ic->ic_rssi_ewma = value;
|
|
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_DRAINTXQ: /* fallthrough */
|
|
case IEEE80211_PARAM_STOP_QUEUE: /* fallthrough */
|
|
case IEEE80211_PARAM_ATHRESET: /* fallthrough */
|
|
case IEEE80211_PARAM_TXTIMEOUT: /* fallthrough */
|
|
case IEEE80211_PARAM_RESETTXBUFS: /* fallthrough */
|
|
case IEEE80211_PARAM_SCANBUFS: /* fallthrough */
|
|
case IEEE80211_PARAM_LEAKTXBUFS: /* fallthrough */
|
|
retv = ic->ic_debug_ath_iwpriv(ic, param, value);
|
|
break;
|
|
|
|
case IEEE80211_PARAM_BEACON_MISS_THRESH_MS:
|
|
if ((vap->iv_opmode != IEEE80211_M_IBSS) &&
|
|
(vap->iv_opmode != IEEE80211_M_STA))
|
|
return -EOPNOTSUPP;
|
|
/* Convert ms to TU to next highest integral # beacons */
|
|
bmiss = howmany(IEEE80211_MS_TO_TU(value), ic->ic_lintval);
|
|
if (IEEE80211_BMISSTHRESH_VALID(bmiss)) {
|
|
ic->ic_bmissthreshold = bmiss;
|
|
retv = ENETRESET; /* requires restart */
|
|
} else
|
|
retv = EINVAL;
|
|
break;
|
|
case IEEE80211_PARAM_BEACON_MISS_THRESH:
|
|
if ((vap->iv_opmode != IEEE80211_M_IBSS) &&
|
|
(vap->iv_opmode != IEEE80211_M_STA))
|
|
return -EOPNOTSUPP;
|
|
if (IEEE80211_BMISSTHRESH_VALID(value)) {
|
|
ic->ic_bmissthreshold = 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)) {
|
|
/* Convert ms to TU to next highest integral
|
|
* # beacons. */
|
|
bmiss = howmany(ic->ic_bmissthreshold *
|
|
ic->ic_lintval, value);
|
|
/* Adjust beacon miss interval during a beacon interval
|
|
* change so that the duration of missed beacons allowed
|
|
* is greater than or equal to the old allowed duration
|
|
* of missed beacons. */
|
|
ic->ic_bmissthreshold = bmiss;
|
|
ic->ic_lintval = value;
|
|
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_DOTH_ALGORITHM:
|
|
ic->ic_sc_algorithm = value;
|
|
break;
|
|
case IEEE80211_PARAM_DOTH_MINCOM:
|
|
ic->ic_sc_mincom = value;
|
|
break;
|
|
case IEEE80211_PARAM_DOTH_SLCG:
|
|
ic->ic_sc_slcg = value;
|
|
break;
|
|
case IEEE80211_PARAM_DOTH_SLDG:
|
|
ic->ic_sc_sldg = value;
|
|
break;
|
|
case IEEE80211_PARAM_DFS_TESTMODE:
|
|
ieee80211_set_dfs_testmode(dev, info, w, extra);
|
|
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_DFS_CACTIME:
|
|
ieee80211_set_dfs_cac_time(dev, info, w, extra);
|
|
break;
|
|
case IEEE80211_PARAM_DFS_EXCLPERIOD:
|
|
ieee80211_set_dfs_excl_period(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.rssi11g = 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.rate11g = 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_RSSI_EWMA:
|
|
param[0] = ic->ic_rssi_ewma;
|
|
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_MISS_THRESH_MS:
|
|
param[0] = IEEE80211_TU_TO_MS(
|
|
vap->iv_ic->ic_bmissthreshold * vap->iv_ic->ic_lintval);
|
|
break;
|
|
case IEEE80211_PARAM_BEACON_MISS_THRESH:
|
|
param[0] = vap->iv_ic->ic_bmissthreshold;
|
|
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_DOTH_ALGORITHM:
|
|
param[0] = ic->ic_sc_algorithm;
|
|
break;
|
|
case IEEE80211_PARAM_DOTH_MINCOM:
|
|
param[0] = ic->ic_sc_mincom;
|
|
break;
|
|
case IEEE80211_PARAM_DOTH_SLCG:
|
|
param[0] = ic->ic_sc_slcg;
|
|
break;
|
|
case IEEE80211_PARAM_DOTH_SLDG:
|
|
param[0] = ic->ic_sc_sldg;
|
|
break;
|
|
case IEEE80211_PARAM_DFS_TESTMODE:
|
|
ieee80211_get_dfs_testmode(dev, info, w, extra);
|
|
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_DFS_CACTIME:
|
|
ieee80211_get_dfs_cac_time(dev, info, w, extra);
|
|
break;
|
|
case IEEE80211_PARAM_DFS_EXCLPERIOD:
|
|
ieee80211_get_dfs_excl_period(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.rssi11g;
|
|
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.rate11g;
|
|
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);
|
|
/* XXX: Untested use of iv_bssid. */
|
|
if (!IEEE80211_ADDR_EQ(ik->ik_macaddr, vap->iv_bssid)) {
|
|
ieee80211_unref_node(&ni);
|
|
return -EADDRNOTAVAIL;
|
|
}
|
|
} 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));
|
|
/* update Supported Channels information element */
|
|
ieee80211_build_sc_ie(ic);
|
|
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) &&
|
|
IEEE80211_ADDR_EQ(ext->addr.sa_data,
|
|
vap->iv_dev->broadcast)) {
|
|
/* 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" },
|
|
{ IEEE80211_IOCTL_RADAR,
|
|
0, 0, "doth_radar" },
|
|
{ IEEE80211_IOCTL_HALMAP,
|
|
0, 0, "dump_hal_map" },
|
|
/*
|
|
* 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_BEACON_MISS_THRESH_MS,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bmiss_ms" },
|
|
{ IEEE80211_PARAM_BEACON_MISS_THRESH_MS,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bmiss_ms" },
|
|
{ IEEE80211_PARAM_BEACON_MISS_THRESH,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "bmiss" },
|
|
{ IEEE80211_PARAM_BEACON_MISS_THRESH,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_bmiss" },
|
|
{ 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" },
|
|
{ IEEE80211_PARAM_DOTH_ALGORITHM,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth_algo" },
|
|
{ IEEE80211_PARAM_DOTH_ALGORITHM,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_doth_algo" },
|
|
{ IEEE80211_PARAM_DOTH_MINCOM,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth_mincom" },
|
|
{ IEEE80211_PARAM_DOTH_MINCOM,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_doth_mincom" },
|
|
{ IEEE80211_PARAM_DOTH_SLCG,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth_slcg" },
|
|
{ IEEE80211_PARAM_DOTH_SLCG,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_doth_slcg" },
|
|
{ IEEE80211_PARAM_DOTH_SLDG,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "doth_sldg" },
|
|
{ IEEE80211_PARAM_DOTH_SLDG,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_doth_sldg" },
|
|
/* 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_DFS_TESTMODE,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dfstestmode" },
|
|
{ IEEE80211_PARAM_DFS_TESTMODE,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_dfstestmode" },
|
|
{ IEEE80211_PARAM_DFS_CACTIME,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dfscactime" },
|
|
{ IEEE80211_PARAM_DFS_CACTIME,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_dfscactime" },
|
|
{ IEEE80211_PARAM_DFS_EXCLPERIOD,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "dfsexcltim" },
|
|
{ IEEE80211_PARAM_DFS_EXCLPERIOD,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_dfsexcltim" },
|
|
{ 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_x2" },
|
|
{ IEEE80211_PARAM_ROAM_RATE_11A,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rate11a_x2" },
|
|
{ IEEE80211_PARAM_ROAM_RATE_11B,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rate11b_x2" },
|
|
{ IEEE80211_PARAM_ROAM_RATE_11B,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rate11b_x2" },
|
|
{ IEEE80211_PARAM_ROAM_RATE_11G,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rate11g_x2" },
|
|
{ IEEE80211_PARAM_ROAM_RATE_11G,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rate11g_x2" },
|
|
{ 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_PARAM_RSSI_EWMA,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "rssi_ewma" },
|
|
{ IEEE80211_PARAM_RSSI_EWMA,
|
|
0, IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, "get_rssi_ewma" },
|
|
{ IEEE80211_PARAM_DRAINTXQ,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_draintxq" },
|
|
{ IEEE80211_PARAM_STOP_QUEUE,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_stopq" },
|
|
{ IEEE80211_PARAM_TXTIMEOUT,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_txtimeout" },
|
|
{ IEEE80211_PARAM_ATHRESET,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_athreset" },
|
|
{ IEEE80211_PARAM_RESETTXBUFS,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_newtxbufs" },
|
|
{ IEEE80211_PARAM_SCANBUFS,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_scanbufs" },
|
|
{ IEEE80211_PARAM_LEAKTXBUFS,
|
|
IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, 0, "debug_leaktxbufs" },
|
|
|
|
#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_RADAR, ieee80211_ioctl_radar),
|
|
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)
|
|
{
|
|
}
|
|
|