| /* |
| |
| 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/sched.h> /* for capable() */ |
| #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; |
| unsigned long flags; |
| struct bcm43xx_phyinfo *phy; |
| char suffix[7] = { 0 }; |
| int have_a = 0, have_b = 0, have_g = 0; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| 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); |
| } |
| } |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| int freq; |
| int err = -EINVAL; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| if ((data->freq.m >= 0) && (data->freq.m <= 1000)) { |
| channel = data->freq.m; |
| freq = bcm43xx_channel_to_freq(bcm, channel); |
| } else { |
| channel = bcm43xx_freq_to_channel(bcm, data->freq.m); |
| freq = data->freq.m; |
| } |
| 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: |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| unsigned long flags; |
| int err = -ENODEV; |
| u16 channel; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| 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: |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| |
| bcm43xx_lock_irqsafe(bcm, 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; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| unsigned long flags; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| data->mode = bcm->ieee->iw_mode; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| unsigned long flags; |
| 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; |
| /* TODO: Real max RSSI */ |
| range->max_qual.level = 3; |
| range->max_qual.noise = 100; |
| range->max_qual.updated = 7; |
| |
| range->avg_qual.qual = 70; |
| range->avg_qual.level = 2; |
| range->avg_qual.noise = 40; |
| range->avg_qual.updated = 7; |
| |
| 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; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| 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; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_9MB; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_12MB; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_18MB; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_24MB; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_36MB; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_48MB; |
| range->bitrate[i++] = IEEE80211_OFDM_RATE_54MB; |
| } |
| if (phy->type == BCM43xx_PHYTYPE_B || |
| phy->type == BCM43xx_PHYTYPE_G) { |
| range->num_bitrates += 4; |
| range->bitrate[i++] = IEEE80211_CCK_RATE_1MB; |
| range->bitrate[i++] = IEEE80211_CCK_RATE_2MB; |
| range->bitrate[i++] = IEEE80211_CCK_RATE_5MB; |
| range->bitrate[i++] = IEEE80211_CCK_RATE_11MB; |
| } |
| |
| 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;//FIXME? |
| 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;//FIXME? |
| range->freq[j].e = 1; |
| j++; |
| } |
| range->num_frequency = j; |
| |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| |
| bcm43xx_lock_noirq(bcm); |
| len = min((size_t)data->data.length, (size_t)IW_ESSID_MAX_SIZE); |
| memcpy(bcm->nick, extra, len); |
| bcm->nick[len] = '\0'; |
| bcm43xx_unlock_noirq(bcm); |
| |
| 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; |
| |
| bcm43xx_lock_noirq(bcm); |
| len = strlen(bcm->nick) + 1; |
| memcpy(extra, bcm->nick, len); |
| data->data.length = (__u16)len; |
| data->data.flags = 1; |
| bcm43xx_unlock_noirq(bcm); |
| |
| 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; |
| |
| bcm43xx_lock_irqsafe(bcm, 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; |
| } |
| } |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| unsigned long flags; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| data->rts.value = bcm->rts_threshold; |
| data->rts.fixed = 0; |
| data->rts.disabled = (bcm->rts_threshold == BCM43xx_MAX_RTS_THRESHOLD); |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| |
| bcm43xx_lock_irqsafe(bcm, 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; |
| } |
| } |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| unsigned long flags; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| data->frag.value = bcm->ieee->fts; |
| data->frag.fixed = 0; |
| data->frag.disabled = (bcm->ieee->fts == MAX_FRAG_THRESHOLD); |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| } |
| |
| bcm43xx_lock_irqsafe(bcm, 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: |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| unsigned long flags; |
| int err = -ENODEV; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| 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: |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| } |
| |
| bcm43xx_lock_irqsafe(bcm, 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; |
| } |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| unsigned long flags; |
| int mode; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| mode = bcm43xx_current_radio(bcm)->interfmode; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| bcm43xx_lock_irqsafe(bcm, flags); |
| bcm->short_preamble = !!on; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| unsigned long flags; |
| int on; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| on = bcm->short_preamble; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| bcm->ieee->host_encrypt = !!on; |
| bcm->ieee->host_decrypt = !!on; |
| bcm->ieee->host_build_iv = !on; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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); |
| unsigned long flags; |
| int on; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| on = bcm->ieee->host_encrypt; |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| |
| 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; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| err = -ENODEV; |
| if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED) |
| err = bcm43xx_sprom_read(bcm, sprom); |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| 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; |
| |
| bcm43xx_lock_irqsafe(bcm, flags); |
| err = -ENODEV; |
| if (bcm43xx_status(bcm) == BCM43xx_STAT_INITIALIZED) |
| err = bcm43xx_sprom_write(bcm, sprom); |
| bcm43xx_unlock_irqsafe(bcm, flags); |
| 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; |
| |
| wstats = &bcm->stats.wstats; |
| if (!mac->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_NOISE_INVALID | |
| IW_QUAL_QUAL_INVALID | IW_QUAL_LEVEL_INVALID; |
| return wstats; |
| } |
| /* fill in the real statistics when iface associated */ |
| wstats->qual.qual = 100; // TODO: get the real signal quality |
| wstats->qual.level = 3 - bcm->stats.link_quality; |
| wstats->qual.noise = bcm->stats.noise; |
| wstats->qual.updated = IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED | |
| IW_QUAL_NOISE_UPDATED; |
| 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, |
| }; |