1036 lines
28 KiB
C
1036 lines
28 KiB
C
/*
|
|
|
|
Broadcom BCM43xx wireless driver
|
|
|
|
Copyright (c) 2005 Martin Langer <martin-langer@gmx.de>,
|
|
Stefano Brivio <st3@riseup.net>
|
|
Michael Buesch <mbuesch@freenet.de>
|
|
Danny van Dyk <kugelfang@gentoo.org>
|
|
Andreas Jaggi <andreas.jaggi@waterwave.ch>
|
|
|
|
Some parts of the code in this file are derived from the ipw2200
|
|
driver Copyright(c) 2003 - 2004 Intel Corporation.
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; see the file COPYING. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
|
|
*/
|
|
|
|
#include <linux/wireless.h>
|
|
#include <net/iw_handler.h>
|
|
#include <net/ieee80211softmac.h>
|
|
#include <net/ieee80211softmac_wx.h>
|
|
#include <linux/capability.h>
|
|
#include <linux/delay.h>
|
|
|
|
#include "bcm43xx.h"
|
|
#include "bcm43xx_wx.h"
|
|
#include "bcm43xx_main.h"
|
|
#include "bcm43xx_radio.h"
|
|
#include "bcm43xx_phy.h"
|
|
|
|
|
|
/* The WIRELESS_EXT version, which is implemented by this driver. */
|
|
#define BCM43xx_WX_VERSION 18
|
|
|
|
#define MAX_WX_STRING 80
|
|
|
|
static int bcm43xx_wx_get_name(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int i;
|
|
struct bcm43xx_phyinfo *phy;
|
|
char suffix[7] = { 0 };
|
|
int have_a = 0, have_b = 0, have_g = 0;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
for (i = 0; i < bcm->nr_80211_available; i++) {
|
|
phy = &(bcm->core_80211_ext[i].phy);
|
|
switch (phy->type) {
|
|
case BCM43xx_PHYTYPE_A:
|
|
have_a = 1;
|
|
break;
|
|
case BCM43xx_PHYTYPE_G:
|
|
have_g = 1;
|
|
case BCM43xx_PHYTYPE_B:
|
|
have_b = 1;
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
}
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
i = 0;
|
|
if (have_a) {
|
|
suffix[i++] = 'a';
|
|
suffix[i++] = '/';
|
|
}
|
|
if (have_b) {
|
|
suffix[i++] = 'b';
|
|
suffix[i++] = '/';
|
|
}
|
|
if (have_g) {
|
|
suffix[i++] = 'g';
|
|
suffix[i++] = '/';
|
|
}
|
|
if (i != 0)
|
|
suffix[i - 1] = '\0';
|
|
|
|
snprintf(data->name, IFNAMSIZ, "IEEE 802.11%s", suffix);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_channelfreq(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
u8 channel;
|
|
s8 expon;
|
|
int freq;
|
|
int err = -EINVAL;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
|
|
if ((data->freq.e == 0) &&
|
|
(data->freq.m >= 0) && (data->freq.m <= 1000)) {
|
|
channel = data->freq.m;
|
|
freq = bcm43xx_channel_to_freq(bcm, channel);
|
|
} else {
|
|
freq = data->freq.m;
|
|
expon = 6 - data->freq.e;
|
|
while (--expon >= 0) /* scale down the frequency to MHz */
|
|
freq /= 10;
|
|
assert(freq > 1000);
|
|
channel = bcm43xx_freq_to_channel(bcm, freq);
|
|
}
|
|
if (!ieee80211_is_valid_channel(bcm->ieee, channel))
|
|
goto out_unlock;
|
|
if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED) {
|
|
//ieee80211softmac_disassoc(softmac, $REASON);
|
|
bcm43xx_mac_suspend(bcm);
|
|
err = bcm43xx_radio_selectchannel(bcm, channel, 0);
|
|
bcm43xx_mac_enable(bcm);
|
|
} else {
|
|
bcm43xx_current_radio(bcm)->initial_channel = channel;
|
|
err = 0;
|
|
}
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_channelfreq(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
struct bcm43xx_radioinfo *radio;
|
|
int err = -ENODEV;
|
|
u16 channel;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
radio = bcm43xx_current_radio(bcm);
|
|
channel = radio->channel;
|
|
if (channel == 0xFF) {
|
|
channel = radio->initial_channel;
|
|
if (channel == 0xFF)
|
|
goto out_unlock;
|
|
}
|
|
assert(channel > 0 && channel <= 1000);
|
|
data->freq.e = 1;
|
|
data->freq.m = bcm43xx_channel_to_freq(bcm, channel) * 100000;
|
|
data->freq.flags = 1;
|
|
|
|
err = 0;
|
|
out_unlock:
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_mode(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
int mode;
|
|
|
|
mode = data->mode;
|
|
if (mode == IW_MODE_AUTO)
|
|
mode = BCM43xx_INITIAL_IWMODE;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED) {
|
|
if (bcm->ieee->iw_mode != mode)
|
|
bcm43xx_set_iwmode(bcm, mode);
|
|
} else
|
|
bcm->ieee->iw_mode = mode;
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_mode(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
data->mode = bcm->ieee->iw_mode;
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_rangeparams(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
struct iw_range *range = (struct iw_range *)extra;
|
|
const struct ieee80211_geo *geo;
|
|
int i, j;
|
|
struct bcm43xx_phyinfo *phy;
|
|
|
|
data->data.length = sizeof(*range);
|
|
memset(range, 0, sizeof(*range));
|
|
|
|
//TODO: What about 802.11b?
|
|
/* 54Mb/s == ~27Mb/s payload throughput (802.11g) */
|
|
range->throughput = 27 * 1000 * 1000;
|
|
|
|
range->max_qual.qual = 100;
|
|
range->max_qual.level = 146; /* set floor at -110 dBm (146 - 256) */
|
|
range->max_qual.noise = 146;
|
|
range->max_qual.updated = IW_QUAL_ALL_UPDATED;
|
|
|
|
range->avg_qual.qual = 50;
|
|
range->avg_qual.level = 0;
|
|
range->avg_qual.noise = 0;
|
|
range->avg_qual.updated = IW_QUAL_ALL_UPDATED;
|
|
|
|
range->min_rts = BCM43xx_MIN_RTS_THRESHOLD;
|
|
range->max_rts = BCM43xx_MAX_RTS_THRESHOLD;
|
|
range->min_frag = MIN_FRAG_THRESHOLD;
|
|
range->max_frag = MAX_FRAG_THRESHOLD;
|
|
|
|
range->encoding_size[0] = 5;
|
|
range->encoding_size[1] = 13;
|
|
range->num_encoding_sizes = 2;
|
|
range->max_encoding_tokens = WEP_KEYS;
|
|
|
|
range->we_version_compiled = WIRELESS_EXT;
|
|
range->we_version_source = BCM43xx_WX_VERSION;
|
|
|
|
range->enc_capa = IW_ENC_CAPA_WPA |
|
|
IW_ENC_CAPA_WPA2 |
|
|
IW_ENC_CAPA_CIPHER_TKIP |
|
|
IW_ENC_CAPA_CIPHER_CCMP;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
phy = bcm43xx_current_phy(bcm);
|
|
|
|
range->num_bitrates = 0;
|
|
i = 0;
|
|
if (phy->type == BCM43xx_PHYTYPE_A ||
|
|
phy->type == BCM43xx_PHYTYPE_G) {
|
|
range->num_bitrates = 8;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_6MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_9MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_12MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_18MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_24MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_36MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_48MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_OFDM_RATE_54MB * 500000;
|
|
}
|
|
if (phy->type == BCM43xx_PHYTYPE_B ||
|
|
phy->type == BCM43xx_PHYTYPE_G) {
|
|
range->num_bitrates += 4;
|
|
range->bitrate[i++] = IEEE80211_CCK_RATE_1MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_CCK_RATE_2MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_CCK_RATE_5MB * 500000;
|
|
range->bitrate[i++] = IEEE80211_CCK_RATE_11MB * 500000;
|
|
}
|
|
|
|
geo = ieee80211_get_geo(bcm->ieee);
|
|
range->num_channels = geo->a_channels + geo->bg_channels;
|
|
j = 0;
|
|
for (i = 0; i < geo->a_channels; i++) {
|
|
if (j == IW_MAX_FREQUENCIES)
|
|
break;
|
|
range->freq[j].i = j + 1;
|
|
range->freq[j].m = geo->a[i].freq * 100000;
|
|
range->freq[j].e = 1;
|
|
j++;
|
|
}
|
|
for (i = 0; i < geo->bg_channels; i++) {
|
|
if (j == IW_MAX_FREQUENCIES)
|
|
break;
|
|
range->freq[j].i = j + 1;
|
|
range->freq[j].m = geo->bg[i].freq * 100000;
|
|
range->freq[j].e = 1;
|
|
j++;
|
|
}
|
|
range->num_frequency = j;
|
|
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_nick(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
size_t len;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
len = min((size_t)data->data.length, (size_t)IW_ESSID_MAX_SIZE);
|
|
memcpy(bcm->nick, extra, len);
|
|
bcm->nick[len] = '\0';
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_nick(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
size_t len;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
len = strlen(bcm->nick);
|
|
memcpy(extra, bcm->nick, len);
|
|
data->data.length = (__u16)len;
|
|
data->data.flags = 1;
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_rts(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
int err = -EINVAL;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
if (data->rts.disabled) {
|
|
bcm->rts_threshold = BCM43xx_MAX_RTS_THRESHOLD;
|
|
err = 0;
|
|
} else {
|
|
if (data->rts.value >= BCM43xx_MIN_RTS_THRESHOLD &&
|
|
data->rts.value <= BCM43xx_MAX_RTS_THRESHOLD) {
|
|
bcm->rts_threshold = data->rts.value;
|
|
err = 0;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_rts(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
data->rts.value = bcm->rts_threshold;
|
|
data->rts.fixed = 0;
|
|
data->rts.disabled = (bcm->rts_threshold == BCM43xx_MAX_RTS_THRESHOLD);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_frag(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
int err = -EINVAL;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
if (data->frag.disabled) {
|
|
bcm->ieee->fts = MAX_FRAG_THRESHOLD;
|
|
err = 0;
|
|
} else {
|
|
if (data->frag.value >= MIN_FRAG_THRESHOLD &&
|
|
data->frag.value <= MAX_FRAG_THRESHOLD) {
|
|
bcm->ieee->fts = data->frag.value & ~0x1;
|
|
err = 0;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_frag(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
data->frag.value = bcm->ieee->fts;
|
|
data->frag.fixed = 0;
|
|
data->frag.disabled = (bcm->ieee->fts == MAX_FRAG_THRESHOLD);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_xmitpower(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
struct bcm43xx_radioinfo *radio;
|
|
struct bcm43xx_phyinfo *phy;
|
|
unsigned long flags;
|
|
int err = -ENODEV;
|
|
u16 maxpower;
|
|
|
|
if ((data->txpower.flags & IW_TXPOW_TYPE) != IW_TXPOW_DBM) {
|
|
printk(PFX KERN_ERR "TX power not in dBm.\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
if (bcm43xx_status(bcm) != BCM43xx_STAT_INITIALIZED)
|
|
goto out_unlock;
|
|
radio = bcm43xx_current_radio(bcm);
|
|
phy = bcm43xx_current_phy(bcm);
|
|
if (data->txpower.disabled != (!(radio->enabled))) {
|
|
if (data->txpower.disabled)
|
|
bcm43xx_radio_turn_off(bcm);
|
|
else
|
|
bcm43xx_radio_turn_on(bcm);
|
|
}
|
|
if (data->txpower.value > 0) {
|
|
/* desired and maxpower dBm values are in Q5.2 */
|
|
if (phy->type == BCM43xx_PHYTYPE_A)
|
|
maxpower = bcm->sprom.maxpower_aphy;
|
|
else
|
|
maxpower = bcm->sprom.maxpower_bgphy;
|
|
radio->txpower_desired = limit_value(data->txpower.value << 2,
|
|
0, maxpower);
|
|
bcm43xx_phy_xmitpower(bcm);
|
|
}
|
|
err = 0;
|
|
|
|
out_unlock:
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_xmitpower(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
struct bcm43xx_radioinfo *radio;
|
|
int err = -ENODEV;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
if (bcm43xx_status(bcm) != BCM43xx_STAT_INITIALIZED)
|
|
goto out_unlock;
|
|
radio = bcm43xx_current_radio(bcm);
|
|
/* desired dBm value is in Q5.2 */
|
|
data->txpower.value = radio->txpower_desired >> 2;
|
|
data->txpower.fixed = 1;
|
|
data->txpower.flags = IW_TXPOW_DBM;
|
|
data->txpower.disabled = !(radio->enabled);
|
|
|
|
err = 0;
|
|
out_unlock:
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_encoding(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int err;
|
|
|
|
err = ieee80211_wx_set_encode(bcm->ieee, info, data, extra);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_encodingext(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int err;
|
|
|
|
err = ieee80211_wx_set_encodeext(bcm->ieee, info, data, extra);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_encoding(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int err;
|
|
|
|
err = ieee80211_wx_get_encode(bcm->ieee, info, data, extra);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_encodingext(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int err;
|
|
|
|
err = ieee80211_wx_get_encodeext(bcm->ieee, info, data, extra);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_interfmode(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
int mode, err = 0;
|
|
|
|
mode = *((int *)extra);
|
|
switch (mode) {
|
|
case 0:
|
|
mode = BCM43xx_RADIO_INTERFMODE_NONE;
|
|
break;
|
|
case 1:
|
|
mode = BCM43xx_RADIO_INTERFMODE_NONWLAN;
|
|
break;
|
|
case 2:
|
|
mode = BCM43xx_RADIO_INTERFMODE_MANUALWLAN;
|
|
break;
|
|
case 3:
|
|
mode = BCM43xx_RADIO_INTERFMODE_AUTOWLAN;
|
|
break;
|
|
default:
|
|
printk(KERN_ERR PFX "set_interfmode allowed parameters are: "
|
|
"0 => None, 1 => Non-WLAN, 2 => WLAN, "
|
|
"3 => Auto-WLAN\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED) {
|
|
err = bcm43xx_radio_set_interference_mitigation(bcm, mode);
|
|
if (err) {
|
|
printk(KERN_ERR PFX "Interference Mitigation not "
|
|
"supported by device\n");
|
|
}
|
|
} else {
|
|
if (mode == BCM43xx_RADIO_INTERFMODE_AUTOWLAN) {
|
|
printk(KERN_ERR PFX "Interference Mitigation mode Auto-WLAN "
|
|
"not supported while the interface is down.\n");
|
|
err = -ENODEV;
|
|
} else
|
|
bcm43xx_current_radio(bcm)->interfmode = mode;
|
|
}
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_interfmode(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int mode;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
mode = bcm43xx_current_radio(bcm)->interfmode;
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
switch (mode) {
|
|
case BCM43xx_RADIO_INTERFMODE_NONE:
|
|
strncpy(extra, "0 (No Interference Mitigation)", MAX_WX_STRING);
|
|
break;
|
|
case BCM43xx_RADIO_INTERFMODE_NONWLAN:
|
|
strncpy(extra, "1 (Non-WLAN Interference Mitigation)", MAX_WX_STRING);
|
|
break;
|
|
case BCM43xx_RADIO_INTERFMODE_MANUALWLAN:
|
|
strncpy(extra, "2 (WLAN Interference Mitigation)", MAX_WX_STRING);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
data->data.length = strlen(extra) + 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_shortpreamble(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
int on;
|
|
|
|
on = *((int *)extra);
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
bcm->short_preamble = !!on;
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_shortpreamble(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int on;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
on = bcm->short_preamble;
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
if (on)
|
|
strncpy(extra, "1 (Short Preamble enabled)", MAX_WX_STRING);
|
|
else
|
|
strncpy(extra, "0 (Short Preamble disabled)", MAX_WX_STRING);
|
|
data->data.length = strlen(extra) + 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_set_swencryption(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
unsigned long flags;
|
|
int on;
|
|
|
|
on = *((int *)extra);
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
bcm->ieee->host_encrypt = !!on;
|
|
bcm->ieee->host_decrypt = !!on;
|
|
bcm->ieee->host_build_iv = !on;
|
|
bcm->ieee->host_strip_iv_icv = !on;
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_get_swencryption(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int on;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
on = bcm->ieee->host_encrypt;
|
|
mutex_unlock(&bcm->mutex);
|
|
|
|
if (on)
|
|
strncpy(extra, "1 (SW encryption enabled) ", MAX_WX_STRING);
|
|
else
|
|
strncpy(extra, "0 (SW encryption disabled) ", MAX_WX_STRING);
|
|
data->data.length = strlen(extra + 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Enough buffer to hold a hexdump of the sprom data. */
|
|
#define SPROM_BUFFERSIZE 512
|
|
|
|
static int sprom2hex(const u16 *sprom, char *dump)
|
|
{
|
|
int i, pos = 0;
|
|
|
|
for (i = 0; i < BCM43xx_SPROM_SIZE; i++) {
|
|
pos += snprintf(dump + pos, SPROM_BUFFERSIZE - pos - 1,
|
|
"%04X", swab16(sprom[i]) & 0xFFFF);
|
|
}
|
|
|
|
return pos + 1;
|
|
}
|
|
|
|
static int hex2sprom(u16 *sprom, const char *dump, unsigned int len)
|
|
{
|
|
char tmp[5] = { 0 };
|
|
int cnt = 0;
|
|
unsigned long parsed;
|
|
|
|
if (len < BCM43xx_SPROM_SIZE * sizeof(u16) * 2)
|
|
return -EINVAL;
|
|
while (cnt < BCM43xx_SPROM_SIZE) {
|
|
memcpy(tmp, dump, 4);
|
|
dump += 4;
|
|
parsed = simple_strtoul(tmp, NULL, 16);
|
|
sprom[cnt++] = swab16((u16)parsed);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bcm43xx_wx_sprom_read(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int err = -EPERM;
|
|
u16 *sprom;
|
|
unsigned long flags;
|
|
|
|
if (!capable(CAP_SYS_RAWIO))
|
|
goto out;
|
|
|
|
err = -ENOMEM;
|
|
sprom = kmalloc(BCM43xx_SPROM_SIZE * sizeof(*sprom),
|
|
GFP_KERNEL);
|
|
if (!sprom)
|
|
goto out;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
err = -ENODEV;
|
|
if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED)
|
|
err = bcm43xx_sprom_read(bcm, sprom);
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
if (!err)
|
|
data->data.length = sprom2hex(sprom, extra);
|
|
kfree(sprom);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int bcm43xx_wx_sprom_write(struct net_device *net_dev,
|
|
struct iw_request_info *info,
|
|
union iwreq_data *data,
|
|
char *extra)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
int err = -EPERM;
|
|
u16 *sprom;
|
|
unsigned long flags;
|
|
char *input;
|
|
unsigned int len;
|
|
|
|
if (!capable(CAP_SYS_RAWIO))
|
|
goto out;
|
|
|
|
err = -ENOMEM;
|
|
sprom = kmalloc(BCM43xx_SPROM_SIZE * sizeof(*sprom),
|
|
GFP_KERNEL);
|
|
if (!sprom)
|
|
goto out;
|
|
|
|
len = data->data.length;
|
|
extra[len - 1] = '\0';
|
|
input = strchr(extra, ':');
|
|
if (input) {
|
|
input++;
|
|
len -= input - extra;
|
|
} else
|
|
input = extra;
|
|
err = hex2sprom(sprom, input, len);
|
|
if (err)
|
|
goto out_kfree;
|
|
|
|
mutex_lock(&bcm->mutex);
|
|
spin_lock_irqsave(&bcm->irq_lock, flags);
|
|
spin_lock(&bcm->leds_lock);
|
|
err = -ENODEV;
|
|
if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED)
|
|
err = bcm43xx_sprom_write(bcm, sprom);
|
|
spin_unlock(&bcm->leds_lock);
|
|
spin_unlock_irqrestore(&bcm->irq_lock, flags);
|
|
mutex_unlock(&bcm->mutex);
|
|
out_kfree:
|
|
kfree(sprom);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/* Get wireless statistics. Called by /proc/net/wireless and by SIOCGIWSTATS */
|
|
|
|
static struct iw_statistics *bcm43xx_get_wireless_stats(struct net_device *net_dev)
|
|
{
|
|
struct bcm43xx_private *bcm = bcm43xx_priv(net_dev);
|
|
struct ieee80211softmac_device *mac = ieee80211_priv(net_dev);
|
|
struct iw_statistics *wstats;
|
|
struct ieee80211_network *network = NULL;
|
|
static int tmp_level = 0;
|
|
static int tmp_qual = 0;
|
|
unsigned long flags;
|
|
|
|
wstats = &bcm->stats.wstats;
|
|
if (!mac->associnfo.associated) {
|
|
wstats->miss.beacon = 0;
|
|
// bcm->ieee->ieee_stats.tx_retry_limit_exceeded = 0; // FIXME: should this be cleared here?
|
|
wstats->discard.retries = 0;
|
|
// bcm->ieee->ieee_stats.tx_discards_wrong_sa = 0; // FIXME: same question
|
|
wstats->discard.nwid = 0;
|
|
// bcm->ieee->ieee_stats.rx_discards_undecryptable = 0; // FIXME: ditto
|
|
wstats->discard.code = 0;
|
|
// bcm->ieee->ieee_stats.rx_fragments = 0; // FIXME: same here
|
|
wstats->discard.fragment = 0;
|
|
wstats->discard.misc = 0;
|
|
wstats->qual.qual = 0;
|
|
wstats->qual.level = 0;
|
|
wstats->qual.noise = 0;
|
|
wstats->qual.updated = 7;
|
|
wstats->qual.updated |= IW_QUAL_ALL_UPDATED | IW_QUAL_DBM;
|
|
return wstats;
|
|
}
|
|
/* fill in the real statistics when iface associated */
|
|
spin_lock_irqsave(&mac->ieee->lock, flags);
|
|
list_for_each_entry(network, &mac->ieee->network_list, list) {
|
|
if (!memcmp(mac->associnfo.bssid, network->bssid, ETH_ALEN)) {
|
|
if (!tmp_level) { /* get initial values */
|
|
tmp_level = network->stats.signal;
|
|
tmp_qual = network->stats.rssi;
|
|
} else { /* smooth results */
|
|
tmp_level = (15 * tmp_level + network->stats.signal)/16;
|
|
tmp_qual = (15 * tmp_qual + network->stats.rssi)/16;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&mac->ieee->lock, flags);
|
|
wstats->qual.level = tmp_level;
|
|
wstats->qual.qual = 100 * tmp_qual / RX_RSSI_MAX;
|
|
wstats->qual.noise = bcm->stats.noise;
|
|
wstats->qual.updated = IW_QUAL_ALL_UPDATED | IW_QUAL_DBM;
|
|
wstats->discard.code = bcm->ieee->ieee_stats.rx_discards_undecryptable;
|
|
wstats->discard.retries = bcm->ieee->ieee_stats.tx_retry_limit_exceeded;
|
|
wstats->discard.nwid = bcm->ieee->ieee_stats.tx_discards_wrong_sa;
|
|
wstats->discard.fragment = bcm->ieee->ieee_stats.rx_fragments;
|
|
wstats->discard.misc = 0; // FIXME
|
|
wstats->miss.beacon = 0; // FIXME
|
|
return wstats;
|
|
}
|
|
|
|
|
|
#ifdef WX
|
|
# undef WX
|
|
#endif
|
|
#define WX(ioctl) [(ioctl) - SIOCSIWCOMMIT]
|
|
static const iw_handler bcm43xx_wx_handlers[] = {
|
|
/* Wireless Identification */
|
|
WX(SIOCGIWNAME) = bcm43xx_wx_get_name,
|
|
/* Basic operations */
|
|
WX(SIOCSIWFREQ) = bcm43xx_wx_set_channelfreq,
|
|
WX(SIOCGIWFREQ) = bcm43xx_wx_get_channelfreq,
|
|
WX(SIOCSIWMODE) = bcm43xx_wx_set_mode,
|
|
WX(SIOCGIWMODE) = bcm43xx_wx_get_mode,
|
|
/* Informative stuff */
|
|
WX(SIOCGIWRANGE) = bcm43xx_wx_get_rangeparams,
|
|
/* Access Point manipulation */
|
|
WX(SIOCSIWAP) = ieee80211softmac_wx_set_wap,
|
|
WX(SIOCGIWAP) = ieee80211softmac_wx_get_wap,
|
|
WX(SIOCSIWSCAN) = ieee80211softmac_wx_trigger_scan,
|
|
WX(SIOCGIWSCAN) = ieee80211softmac_wx_get_scan_results,
|
|
/* 802.11 specific support */
|
|
WX(SIOCSIWESSID) = ieee80211softmac_wx_set_essid,
|
|
WX(SIOCGIWESSID) = ieee80211softmac_wx_get_essid,
|
|
WX(SIOCSIWNICKN) = bcm43xx_wx_set_nick,
|
|
WX(SIOCGIWNICKN) = bcm43xx_wx_get_nick,
|
|
/* Other parameters */
|
|
WX(SIOCSIWRATE) = ieee80211softmac_wx_set_rate,
|
|
WX(SIOCGIWRATE) = ieee80211softmac_wx_get_rate,
|
|
WX(SIOCSIWRTS) = bcm43xx_wx_set_rts,
|
|
WX(SIOCGIWRTS) = bcm43xx_wx_get_rts,
|
|
WX(SIOCSIWFRAG) = bcm43xx_wx_set_frag,
|
|
WX(SIOCGIWFRAG) = bcm43xx_wx_get_frag,
|
|
WX(SIOCSIWTXPOW) = bcm43xx_wx_set_xmitpower,
|
|
WX(SIOCGIWTXPOW) = bcm43xx_wx_get_xmitpower,
|
|
//TODO WX(SIOCSIWRETRY) = bcm43xx_wx_set_retry,
|
|
//TODO WX(SIOCGIWRETRY) = bcm43xx_wx_get_retry,
|
|
/* Encoding */
|
|
WX(SIOCSIWENCODE) = bcm43xx_wx_set_encoding,
|
|
WX(SIOCGIWENCODE) = bcm43xx_wx_get_encoding,
|
|
WX(SIOCSIWENCODEEXT) = bcm43xx_wx_set_encodingext,
|
|
WX(SIOCGIWENCODEEXT) = bcm43xx_wx_get_encodingext,
|
|
/* Power saving */
|
|
//TODO WX(SIOCSIWPOWER) = bcm43xx_wx_set_power,
|
|
//TODO WX(SIOCGIWPOWER) = bcm43xx_wx_get_power,
|
|
WX(SIOCSIWGENIE) = ieee80211softmac_wx_set_genie,
|
|
WX(SIOCGIWGENIE) = ieee80211softmac_wx_get_genie,
|
|
WX(SIOCSIWAUTH) = ieee80211_wx_set_auth,
|
|
WX(SIOCGIWAUTH) = ieee80211_wx_get_auth,
|
|
};
|
|
#undef WX
|
|
|
|
static const iw_handler bcm43xx_priv_wx_handlers[] = {
|
|
/* Set Interference Mitigation Mode. */
|
|
bcm43xx_wx_set_interfmode,
|
|
/* Get Interference Mitigation Mode. */
|
|
bcm43xx_wx_get_interfmode,
|
|
/* Enable/Disable Short Preamble mode. */
|
|
bcm43xx_wx_set_shortpreamble,
|
|
/* Get Short Preamble mode. */
|
|
bcm43xx_wx_get_shortpreamble,
|
|
/* Enable/Disable Software Encryption mode */
|
|
bcm43xx_wx_set_swencryption,
|
|
/* Get Software Encryption mode */
|
|
bcm43xx_wx_get_swencryption,
|
|
/* Write SRPROM data. */
|
|
bcm43xx_wx_sprom_write,
|
|
/* Read SPROM data. */
|
|
bcm43xx_wx_sprom_read,
|
|
};
|
|
|
|
#define PRIV_WX_SET_INTERFMODE (SIOCIWFIRSTPRIV + 0)
|
|
#define PRIV_WX_GET_INTERFMODE (SIOCIWFIRSTPRIV + 1)
|
|
#define PRIV_WX_SET_SHORTPREAMBLE (SIOCIWFIRSTPRIV + 2)
|
|
#define PRIV_WX_GET_SHORTPREAMBLE (SIOCIWFIRSTPRIV + 3)
|
|
#define PRIV_WX_SET_SWENCRYPTION (SIOCIWFIRSTPRIV + 4)
|
|
#define PRIV_WX_GET_SWENCRYPTION (SIOCIWFIRSTPRIV + 5)
|
|
#define PRIV_WX_SPROM_WRITE (SIOCIWFIRSTPRIV + 6)
|
|
#define PRIV_WX_SPROM_READ (SIOCIWFIRSTPRIV + 7)
|
|
|
|
#define PRIV_WX_DUMMY(ioctl) \
|
|
{ \
|
|
.cmd = (ioctl), \
|
|
.name = "__unused" \
|
|
}
|
|
|
|
static const struct iw_priv_args bcm43xx_priv_wx_args[] = {
|
|
{
|
|
.cmd = PRIV_WX_SET_INTERFMODE,
|
|
.set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
|
|
.name = "set_interfmode",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_GET_INTERFMODE,
|
|
.get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING,
|
|
.name = "get_interfmode",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_SET_SHORTPREAMBLE,
|
|
.set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
|
|
.name = "set_shortpreamb",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_GET_SHORTPREAMBLE,
|
|
.get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING,
|
|
.name = "get_shortpreamb",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_SET_SWENCRYPTION,
|
|
.set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
|
|
.name = "set_swencrypt",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_GET_SWENCRYPTION,
|
|
.get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_WX_STRING,
|
|
.name = "get_swencrypt",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_SPROM_WRITE,
|
|
.set_args = IW_PRIV_TYPE_CHAR | SPROM_BUFFERSIZE,
|
|
.name = "write_sprom",
|
|
},
|
|
{
|
|
.cmd = PRIV_WX_SPROM_READ,
|
|
.get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | SPROM_BUFFERSIZE,
|
|
.name = "read_sprom",
|
|
},
|
|
};
|
|
|
|
const struct iw_handler_def bcm43xx_wx_handlers_def = {
|
|
.standard = bcm43xx_wx_handlers,
|
|
.num_standard = ARRAY_SIZE(bcm43xx_wx_handlers),
|
|
.num_private = ARRAY_SIZE(bcm43xx_priv_wx_handlers),
|
|
.num_private_args = ARRAY_SIZE(bcm43xx_priv_wx_args),
|
|
.private = bcm43xx_priv_wx_handlers,
|
|
.private_args = bcm43xx_priv_wx_args,
|
|
.get_wireless_stats = bcm43xx_get_wireless_stats,
|
|
};
|