| /****************************************************************************** |
| |
| Copyright(c) 2003 - 2006 Intel Corporation. All rights reserved. |
| |
| 802.11 status code portion of this file from ethereal-0.10.6: |
| Copyright 2000, Axis Communications AB |
| Ethereal - Network traffic analyzer |
| By Gerald Combs <gerald@ethereal.com> |
| Copyright 1998 Gerald Combs |
| |
| This program is free software; you can redistribute it and/or modify it |
| under the terms of version 2 of the GNU General Public License as |
| published by the Free Software Foundation. |
| |
| 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; if not, write to the Free Software Foundation, Inc., 59 |
| Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
| |
| The full GNU General Public License is included in this distribution in the |
| file called LICENSE. |
| |
| Contact Information: |
| Intel Linux Wireless <ilw@linux.intel.com> |
| Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 |
| |
| ******************************************************************************/ |
| |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <net/cfg80211-wext.h> |
| #include "ipw2200.h" |
| #include "ipw.h" |
| |
| |
| #ifndef KBUILD_EXTMOD |
| #define VK "k" |
| #else |
| #define VK |
| #endif |
| |
| #ifdef CONFIG_IPW2200_DEBUG |
| #define VD "d" |
| #else |
| #define VD |
| #endif |
| |
| #ifdef CONFIG_IPW2200_MONITOR |
| #define VM "m" |
| #else |
| #define VM |
| #endif |
| |
| #ifdef CONFIG_IPW2200_PROMISCUOUS |
| #define VP "p" |
| #else |
| #define VP |
| #endif |
| |
| #ifdef CONFIG_IPW2200_RADIOTAP |
| #define VR "r" |
| #else |
| #define VR |
| #endif |
| |
| #ifdef CONFIG_IPW2200_QOS |
| #define VQ "q" |
| #else |
| #define VQ |
| #endif |
| |
| #define IPW2200_VERSION "1.2.2" VK VD VM VP VR VQ |
| #define DRV_DESCRIPTION "Intel(R) PRO/Wireless 2200/2915 Network Driver" |
| #define DRV_COPYRIGHT "Copyright(c) 2003-2006 Intel Corporation" |
| #define DRV_VERSION IPW2200_VERSION |
| |
| #define ETH_P_80211_STATS (ETH_P_80211_RAW + 1) |
| |
| MODULE_DESCRIPTION(DRV_DESCRIPTION); |
| MODULE_VERSION(DRV_VERSION); |
| MODULE_AUTHOR(DRV_COPYRIGHT); |
| MODULE_LICENSE("GPL"); |
| MODULE_FIRMWARE("ipw2200-ibss.fw"); |
| #ifdef CONFIG_IPW2200_MONITOR |
| MODULE_FIRMWARE("ipw2200-sniffer.fw"); |
| #endif |
| MODULE_FIRMWARE("ipw2200-bss.fw"); |
| |
| static int cmdlog = 0; |
| static int debug = 0; |
| static int default_channel = 0; |
| static int network_mode = 0; |
| |
| static u32 ipw_debug_level; |
| static int associate; |
| static int auto_create = 1; |
| static int led_support = 1; |
| static int disable = 0; |
| static int bt_coexist = 0; |
| static int hwcrypto = 0; |
| static int roaming = 1; |
| static const char ipw_modes[] = { |
| 'a', 'b', 'g', '?' |
| }; |
| static int antenna = CFG_SYS_ANTENNA_BOTH; |
| |
| #ifdef CONFIG_IPW2200_PROMISCUOUS |
| static int rtap_iface = 0; /* def: 0 -- do not create rtap interface */ |
| #endif |
| |
| static struct ieee80211_rate ipw2200_rates[] = { |
| { .bitrate = 10 }, |
| { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE }, |
| { .bitrate = 60 }, |
| { .bitrate = 90 }, |
| { .bitrate = 120 }, |
| { .bitrate = 180 }, |
| { .bitrate = 240 }, |
| { .bitrate = 360 }, |
| { .bitrate = 480 }, |
| { .bitrate = 540 } |
| }; |
| |
| #define ipw2200_a_rates (ipw2200_rates + 4) |
| #define ipw2200_num_a_rates 8 |
| #define ipw2200_bg_rates (ipw2200_rates + 0) |
| #define ipw2200_num_bg_rates 12 |
| |
| /* Ugly macro to convert literal channel numbers into their mhz equivalents |
| * There are certianly some conditions that will break this (like feeding it '30') |
| * but they shouldn't arise since nothing talks on channel 30. */ |
| #define ieee80211chan2mhz(x) \ |
| (((x) <= 14) ? \ |
| (((x) == 14) ? 2484 : ((x) * 5) + 2407) : \ |
| ((x) + 1000) * 5) |
| |
| #ifdef CONFIG_IPW2200_QOS |
| static int qos_enable = 0; |
| static int qos_burst_enable = 0; |
| static int qos_no_ack_mask = 0; |
| static int burst_duration_CCK = 0; |
| static int burst_duration_OFDM = 0; |
| |
| static struct libipw_qos_parameters def_qos_parameters_OFDM = { |
| {QOS_TX0_CW_MIN_OFDM, QOS_TX1_CW_MIN_OFDM, QOS_TX2_CW_MIN_OFDM, |
| QOS_TX3_CW_MIN_OFDM}, |
| {QOS_TX0_CW_MAX_OFDM, QOS_TX1_CW_MAX_OFDM, QOS_TX2_CW_MAX_OFDM, |
| QOS_TX3_CW_MAX_OFDM}, |
| {QOS_TX0_AIFS, QOS_TX1_AIFS, QOS_TX2_AIFS, QOS_TX3_AIFS}, |
| {QOS_TX0_ACM, QOS_TX1_ACM, QOS_TX2_ACM, QOS_TX3_ACM}, |
| {QOS_TX0_TXOP_LIMIT_OFDM, QOS_TX1_TXOP_LIMIT_OFDM, |
| QOS_TX2_TXOP_LIMIT_OFDM, QOS_TX3_TXOP_LIMIT_OFDM} |
| }; |
| |
| static struct libipw_qos_parameters def_qos_parameters_CCK = { |
| {QOS_TX0_CW_MIN_CCK, QOS_TX1_CW_MIN_CCK, QOS_TX2_CW_MIN_CCK, |
| QOS_TX3_CW_MIN_CCK}, |
| {QOS_TX0_CW_MAX_CCK, QOS_TX1_CW_MAX_CCK, QOS_TX2_CW_MAX_CCK, |
| QOS_TX3_CW_MAX_CCK}, |
| {QOS_TX0_AIFS, QOS_TX1_AIFS, QOS_TX2_AIFS, QOS_TX3_AIFS}, |
| {QOS_TX0_ACM, QOS_TX1_ACM, QOS_TX2_ACM, QOS_TX3_ACM}, |
| {QOS_TX0_TXOP_LIMIT_CCK, QOS_TX1_TXOP_LIMIT_CCK, QOS_TX2_TXOP_LIMIT_CCK, |
| QOS_TX3_TXOP_LIMIT_CCK} |
| }; |
| |
| static struct libipw_qos_parameters def_parameters_OFDM = { |
| {DEF_TX0_CW_MIN_OFDM, DEF_TX1_CW_MIN_OFDM, DEF_TX2_CW_MIN_OFDM, |
| DEF_TX3_CW_MIN_OFDM}, |
| {DEF_TX0_CW_MAX_OFDM, DEF_TX1_CW_MAX_OFDM, DEF_TX2_CW_MAX_OFDM, |
| DEF_TX3_CW_MAX_OFDM}, |
| {DEF_TX0_AIFS, DEF_TX1_AIFS, DEF_TX2_AIFS, DEF_TX3_AIFS}, |
| {DEF_TX0_ACM, DEF_TX1_ACM, DEF_TX2_ACM, DEF_TX3_ACM}, |
| {DEF_TX0_TXOP_LIMIT_OFDM, DEF_TX1_TXOP_LIMIT_OFDM, |
| DEF_TX2_TXOP_LIMIT_OFDM, DEF_TX3_TXOP_LIMIT_OFDM} |
| }; |
| |
| static struct libipw_qos_parameters def_parameters_CCK = { |
| {DEF_TX0_CW_MIN_CCK, DEF_TX1_CW_MIN_CCK, DEF_TX2_CW_MIN_CCK, |
| DEF_TX3_CW_MIN_CCK}, |
| {DEF_TX0_CW_MAX_CCK, DEF_TX1_CW_MAX_CCK, DEF_TX2_CW_MAX_CCK, |
| DEF_TX3_CW_MAX_CCK}, |
| {DEF_TX0_AIFS, DEF_TX1_AIFS, DEF_TX2_AIFS, DEF_TX3_AIFS}, |
| {DEF_TX0_ACM, DEF_TX1_ACM, DEF_TX2_ACM, DEF_TX3_ACM}, |
| {DEF_TX0_TXOP_LIMIT_CCK, DEF_TX1_TXOP_LIMIT_CCK, DEF_TX2_TXOP_LIMIT_CCK, |
| DEF_TX3_TXOP_LIMIT_CCK} |
| }; |
| |
| static u8 qos_oui[QOS_OUI_LEN] = { 0x00, 0x50, 0xF2 }; |
| |
| static int from_priority_to_tx_queue[] = { |
| IPW_TX_QUEUE_1, IPW_TX_QUEUE_2, IPW_TX_QUEUE_2, IPW_TX_QUEUE_1, |
| IPW_TX_QUEUE_3, IPW_TX_QUEUE_3, IPW_TX_QUEUE_4, IPW_TX_QUEUE_4 |
| }; |
| |
| static u32 ipw_qos_get_burst_duration(struct ipw_priv *priv); |
| |
| static int ipw_send_qos_params_command(struct ipw_priv *priv, struct libipw_qos_parameters |
| *qos_param); |
| static int ipw_send_qos_info_command(struct ipw_priv *priv, struct libipw_qos_information_element |
| *qos_param); |
| #endif /* CONFIG_IPW2200_QOS */ |
| |
| static struct iw_statistics *ipw_get_wireless_stats(struct net_device *dev); |
| static void ipw_remove_current_network(struct ipw_priv *priv); |
| static void ipw_rx(struct ipw_priv *priv); |
| static int ipw_queue_tx_reclaim(struct ipw_priv *priv, |
| struct clx2_tx_queue *txq, int qindex); |
| static int ipw_queue_reset(struct ipw_priv *priv); |
| |
| static int ipw_queue_tx_hcmd(struct ipw_priv *priv, int hcmd, void *buf, |
| int len, int sync); |
| |
| static void ipw_tx_queue_free(struct ipw_priv *); |
| |
| static struct ipw_rx_queue *ipw_rx_queue_alloc(struct ipw_priv *); |
| static void ipw_rx_queue_free(struct ipw_priv *, struct ipw_rx_queue *); |
| static void ipw_rx_queue_replenish(void *); |
| static int ipw_up(struct ipw_priv *); |
| static void ipw_bg_up(struct work_struct *work); |
| static void ipw_down(struct ipw_priv *); |
| static void ipw_bg_down(struct work_struct *work); |
| static int ipw_config(struct ipw_priv *); |
| static int init_supported_rates(struct ipw_priv *priv, |
| struct ipw_supported_rates *prates); |
| static void ipw_set_hwcrypto_keys(struct ipw_priv *); |
| static void ipw_send_wep_keys(struct ipw_priv *, int); |
| |
| static int snprint_line(char *buf, size_t count, |
| const u8 * data, u32 len, u32 ofs) |
| { |
| int out, i, j, l; |
| char c; |
| |
| out = snprintf(buf, count, "%08X", ofs); |
| |
| for (l = 0, i = 0; i < 2; i++) { |
| out += snprintf(buf + out, count - out, " "); |
| for (j = 0; j < 8 && l < len; j++, l++) |
| out += snprintf(buf + out, count - out, "%02X ", |
| data[(i * 8 + j)]); |
| for (; j < 8; j++) |
| out += snprintf(buf + out, count - out, " "); |
| } |
| |
| out += snprintf(buf + out, count - out, " "); |
| for (l = 0, i = 0; i < 2; i++) { |
| out += snprintf(buf + out, count - out, " "); |
| for (j = 0; j < 8 && l < len; j++, l++) { |
| c = data[(i * 8 + j)]; |
| if (!isascii(c) || !isprint(c)) |
| c = '.'; |
| |
| out += snprintf(buf + out, count - out, "%c", c); |
| } |
| |
| for (; j < 8; j++) |
| out += snprintf(buf + out, count - out, " "); |
| } |
| |
| return out; |
| } |
| |
| static void printk_buf(int level, const u8 * data, u32 len) |
| { |
| char line[81]; |
| u32 ofs = 0; |
| if (!(ipw_debug_level & level)) |
| return; |
| |
| while (len) { |
| snprint_line(line, sizeof(line), &data[ofs], |
| min(len, 16U), ofs); |
| printk(KERN_DEBUG "%s\n", line); |
| ofs += 16; |
| len -= min(len, 16U); |
| } |
| } |
| |
| static int snprintk_buf(u8 * output, size_t size, const u8 * data, size_t len) |
| { |
| size_t out = size; |
| u32 ofs = 0; |
| int total = 0; |
| |
| while (size && len) { |
| out = snprint_line(output, size, &data[ofs], |
| min_t(size_t, len, 16U), ofs); |
| |
| ofs += 16; |
| output += out; |
| size -= out; |
| len -= min_t(size_t, len, 16U); |
| total += out; |
| } |
| return total; |
| } |
| |
| /* alias for 32-bit indirect read (for SRAM/reg above 4K), with debug wrapper */ |
| static u32 _ipw_read_reg32(struct ipw_priv *priv, u32 reg); |
| #define ipw_read_reg32(a, b) _ipw_read_reg32(a, b) |
| |
| /* alias for 8-bit indirect read (for SRAM/reg above 4K), with debug wrapper */ |
| static u8 _ipw_read_reg8(struct ipw_priv *ipw, u32 reg); |
| #define ipw_read_reg8(a, b) _ipw_read_reg8(a, b) |
| |
| /* 8-bit indirect write (for SRAM/reg above 4K), with debug wrapper */ |
| static void _ipw_write_reg8(struct ipw_priv *priv, u32 reg, u8 value); |
| static inline void ipw_write_reg8(struct ipw_priv *a, u32 b, u8 c) |
| { |
| IPW_DEBUG_IO("%s %d: write_indirect8(0x%08X, 0x%08X)\n", __FILE__, |
| __LINE__, (u32) (b), (u32) (c)); |
| _ipw_write_reg8(a, b, c); |
| } |
| |
| /* 16-bit indirect write (for SRAM/reg above 4K), with debug wrapper */ |
| static void _ipw_write_reg16(struct ipw_priv *priv, u32 reg, u16 value); |
| static inline void ipw_write_reg16(struct ipw_priv *a, u32 b, u16 c) |
| { |
| IPW_DEBUG_IO("%s %d: write_indirect16(0x%08X, 0x%08X)\n", __FILE__, |
| __LINE__, (u32) (b), (u32) (c)); |
| _ipw_write_reg16(a, b, c); |
| } |
| |
| /* 32-bit indirect write (for SRAM/reg above 4K), with debug wrapper */ |
| static void _ipw_write_reg32(struct ipw_priv *priv, u32 reg, u32 value); |
| static inline void ipw_write_reg32(struct ipw_priv *a, u32 b, u32 c) |
| { |
| IPW_DEBUG_IO("%s %d: write_indirect32(0x%08X, 0x%08X)\n", __FILE__, |
| __LINE__, (u32) (b), (u32) (c)); |
| _ipw_write_reg32(a, b, c); |
| } |
| |
| /* 8-bit direct write (low 4K) */ |
| static inline void _ipw_write8(struct ipw_priv *ipw, unsigned long ofs, |
| u8 val) |
| { |
| writeb(val, ipw->hw_base + ofs); |
| } |
| |
| /* 8-bit direct write (for low 4K of SRAM/regs), with debug wrapper */ |
| #define ipw_write8(ipw, ofs, val) do { \ |
| IPW_DEBUG_IO("%s %d: write_direct8(0x%08X, 0x%08X)\n", __FILE__, \ |
| __LINE__, (u32)(ofs), (u32)(val)); \ |
| _ipw_write8(ipw, ofs, val); \ |
| } while (0) |
| |
| /* 16-bit direct write (low 4K) */ |
| static inline void _ipw_write16(struct ipw_priv *ipw, unsigned long ofs, |
| u16 val) |
| { |
| writew(val, ipw->hw_base + ofs); |
| } |
| |
| /* 16-bit direct write (for low 4K of SRAM/regs), with debug wrapper */ |
| #define ipw_write16(ipw, ofs, val) do { \ |
| IPW_DEBUG_IO("%s %d: write_direct16(0x%08X, 0x%08X)\n", __FILE__, \ |
| __LINE__, (u32)(ofs), (u32)(val)); \ |
| _ipw_write16(ipw, ofs, val); \ |
| } while (0) |
| |
| /* 32-bit direct write (low 4K) */ |
| static inline void _ipw_write32(struct ipw_priv *ipw, unsigned long ofs, |
| u32 val) |
| { |
| writel(val, ipw->hw_base + ofs); |
| } |
| |
| /* 32-bit direct write (for low 4K of SRAM/regs), with debug wrapper */ |
| #define ipw_write32(ipw, ofs, val) do { \ |
| IPW_DEBUG_IO("%s %d: write_direct32(0x%08X, 0x%08X)\n", __FILE__, \ |
| __LINE__, (u32)(ofs), (u32)(val)); \ |
| _ipw_write32(ipw, ofs, val); \ |
| } while (0) |
| |
| /* 8-bit direct read (low 4K) */ |
| static inline u8 _ipw_read8(struct ipw_priv *ipw, unsigned long ofs) |
| { |
| return readb(ipw->hw_base + ofs); |
| } |
| |
| /* alias to 8-bit direct read (low 4K of SRAM/regs), with debug wrapper */ |
| #define ipw_read8(ipw, ofs) ({ \ |
| IPW_DEBUG_IO("%s %d: read_direct8(0x%08X)\n", __FILE__, __LINE__, \ |
| (u32)(ofs)); \ |
| _ipw_read8(ipw, ofs); \ |
| }) |
| |
| /* 16-bit direct read (low 4K) */ |
| static inline u16 _ipw_read16(struct ipw_priv *ipw, unsigned long ofs) |
| { |
| return readw(ipw->hw_base + ofs); |
| } |
| |
| /* alias to 16-bit direct read (low 4K of SRAM/regs), with debug wrapper */ |
| #define ipw_read16(ipw, ofs) ({ \ |
| IPW_DEBUG_IO("%s %d: read_direct16(0x%08X)\n", __FILE__, __LINE__, \ |
| (u32)(ofs)); \ |
| _ipw_read16(ipw, ofs); \ |
| }) |
| |
| /* 32-bit direct read (low 4K) */ |
| static inline u32 _ipw_read32(struct ipw_priv *ipw, unsigned long ofs) |
| { |
| return readl(ipw->hw_base + ofs); |
| } |
| |
| /* alias to 32-bit direct read (low 4K of SRAM/regs), with debug wrapper */ |
| #define ipw_read32(ipw, ofs) ({ \ |
| IPW_DEBUG_IO("%s %d: read_direct32(0x%08X)\n", __FILE__, __LINE__, \ |
| (u32)(ofs)); \ |
| _ipw_read32(ipw, ofs); \ |
| }) |
| |
| static void _ipw_read_indirect(struct ipw_priv *, u32, u8 *, int); |
| /* alias to multi-byte read (SRAM/regs above 4K), with debug wrapper */ |
| #define ipw_read_indirect(a, b, c, d) ({ \ |
| IPW_DEBUG_IO("%s %d: read_indirect(0x%08X) %u bytes\n", __FILE__, \ |
| __LINE__, (u32)(b), (u32)(d)); \ |
| _ipw_read_indirect(a, b, c, d); \ |
| }) |
| |
| /* alias to multi-byte read (SRAM/regs above 4K), with debug wrapper */ |
| static void _ipw_write_indirect(struct ipw_priv *priv, u32 addr, u8 * data, |
| int num); |
| #define ipw_write_indirect(a, b, c, d) do { \ |
| IPW_DEBUG_IO("%s %d: write_indirect(0x%08X) %u bytes\n", __FILE__, \ |
| __LINE__, (u32)(b), (u32)(d)); \ |
| _ipw_write_indirect(a, b, c, d); \ |
| } while (0) |
| |
| /* 32-bit indirect write (above 4K) */ |
| static void _ipw_write_reg32(struct ipw_priv *priv, u32 reg, u32 value) |
| { |
| IPW_DEBUG_IO(" %p : reg = 0x%8X : value = 0x%8X\n", priv, reg, value); |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, reg); |
| _ipw_write32(priv, IPW_INDIRECT_DATA, value); |
| } |
| |
| /* 8-bit indirect write (above 4K) */ |
| static void _ipw_write_reg8(struct ipw_priv *priv, u32 reg, u8 value) |
| { |
| u32 aligned_addr = reg & IPW_INDIRECT_ADDR_MASK; /* dword align */ |
| u32 dif_len = reg - aligned_addr; |
| |
| IPW_DEBUG_IO(" reg = 0x%8X : value = 0x%8X\n", reg, value); |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); |
| _ipw_write8(priv, IPW_INDIRECT_DATA + dif_len, value); |
| } |
| |
| /* 16-bit indirect write (above 4K) */ |
| static void _ipw_write_reg16(struct ipw_priv *priv, u32 reg, u16 value) |
| { |
| u32 aligned_addr = reg & IPW_INDIRECT_ADDR_MASK; /* dword align */ |
| u32 dif_len = (reg - aligned_addr) & (~0x1ul); |
| |
| IPW_DEBUG_IO(" reg = 0x%8X : value = 0x%8X\n", reg, value); |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); |
| _ipw_write16(priv, IPW_INDIRECT_DATA + dif_len, value); |
| } |
| |
| /* 8-bit indirect read (above 4K) */ |
| static u8 _ipw_read_reg8(struct ipw_priv *priv, u32 reg) |
| { |
| u32 word; |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, reg & IPW_INDIRECT_ADDR_MASK); |
| IPW_DEBUG_IO(" reg = 0x%8X :\n", reg); |
| word = _ipw_read32(priv, IPW_INDIRECT_DATA); |
| return (word >> ((reg & 0x3) * 8)) & 0xff; |
| } |
| |
| /* 32-bit indirect read (above 4K) */ |
| static u32 _ipw_read_reg32(struct ipw_priv *priv, u32 reg) |
| { |
| u32 value; |
| |
| IPW_DEBUG_IO("%p : reg = 0x%08x\n", priv, reg); |
| |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, reg); |
| value = _ipw_read32(priv, IPW_INDIRECT_DATA); |
| IPW_DEBUG_IO(" reg = 0x%4X : value = 0x%4x\n", reg, value); |
| return value; |
| } |
| |
| /* General purpose, no alignment requirement, iterative (multi-byte) read, */ |
| /* for area above 1st 4K of SRAM/reg space */ |
| static void _ipw_read_indirect(struct ipw_priv *priv, u32 addr, u8 * buf, |
| int num) |
| { |
| u32 aligned_addr = addr & IPW_INDIRECT_ADDR_MASK; /* dword align */ |
| u32 dif_len = addr - aligned_addr; |
| u32 i; |
| |
| IPW_DEBUG_IO("addr = %i, buf = %p, num = %i\n", addr, buf, num); |
| |
| if (num <= 0) { |
| return; |
| } |
| |
| /* Read the first dword (or portion) byte by byte */ |
| if (unlikely(dif_len)) { |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); |
| /* Start reading at aligned_addr + dif_len */ |
| for (i = dif_len; ((i < 4) && (num > 0)); i++, num--) |
| *buf++ = _ipw_read8(priv, IPW_INDIRECT_DATA + i); |
| aligned_addr += 4; |
| } |
| |
| /* Read all of the middle dwords as dwords, with auto-increment */ |
| _ipw_write32(priv, IPW_AUTOINC_ADDR, aligned_addr); |
| for (; num >= 4; buf += 4, aligned_addr += 4, num -= 4) |
| *(u32 *) buf = _ipw_read32(priv, IPW_AUTOINC_DATA); |
| |
| /* Read the last dword (or portion) byte by byte */ |
| if (unlikely(num)) { |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); |
| for (i = 0; num > 0; i++, num--) |
| *buf++ = ipw_read8(priv, IPW_INDIRECT_DATA + i); |
| } |
| } |
| |
| /* General purpose, no alignment requirement, iterative (multi-byte) write, */ |
| /* for area above 1st 4K of SRAM/reg space */ |
| static void _ipw_write_indirect(struct ipw_priv *priv, u32 addr, u8 * buf, |
| int num) |
| { |
| u32 aligned_addr = addr & IPW_INDIRECT_ADDR_MASK; /* dword align */ |
| u32 dif_len = addr - aligned_addr; |
| u32 i; |
| |
| IPW_DEBUG_IO("addr = %i, buf = %p, num = %i\n", addr, buf, num); |
| |
| if (num <= 0) { |
| return; |
| } |
| |
| /* Write the first dword (or portion) byte by byte */ |
| if (unlikely(dif_len)) { |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); |
| /* Start writing at aligned_addr + dif_len */ |
| for (i = dif_len; ((i < 4) && (num > 0)); i++, num--, buf++) |
| _ipw_write8(priv, IPW_INDIRECT_DATA + i, *buf); |
| aligned_addr += 4; |
| } |
| |
| /* Write all of the middle dwords as dwords, with auto-increment */ |
| _ipw_write32(priv, IPW_AUTOINC_ADDR, aligned_addr); |
| for (; num >= 4; buf += 4, aligned_addr += 4, num -= 4) |
| _ipw_write32(priv, IPW_AUTOINC_DATA, *(u32 *) buf); |
| |
| /* Write the last dword (or portion) byte by byte */ |
| if (unlikely(num)) { |
| _ipw_write32(priv, IPW_INDIRECT_ADDR, aligned_addr); |
| for (i = 0; num > 0; i++, num--, buf++) |
| _ipw_write8(priv, IPW_INDIRECT_DATA + i, *buf); |
| } |
| } |
| |
| /* General purpose, no alignment requirement, iterative (multi-byte) write, */ |
| /* for 1st 4K of SRAM/regs space */ |
| static void ipw_write_direct(struct ipw_priv *priv, u32 addr, void *buf, |
| int num) |
| { |
| memcpy_toio((priv->hw_base + addr), buf, num); |
| } |
| |
| /* Set bit(s) in low 4K of SRAM/regs */ |
| static inline void ipw_set_bit(struct ipw_priv *priv, u32 reg, u32 mask) |
| { |
| ipw_write32(priv, reg, ipw_read32(priv, reg) | mask); |
| } |
| |
| /* Clear bit(s) in low 4K of SRAM/regs */ |
| static inline void ipw_clear_bit(struct ipw_priv *priv, u32 reg, u32 mask) |
| { |
| ipw_write32(priv, reg, ipw_read32(priv, reg) & ~mask); |
| } |
| |
| static inline void __ipw_enable_interrupts(struct ipw_priv *priv) |
| { |
| if (priv->status & STATUS_INT_ENABLED) |
| return; |
| priv->status |= STATUS_INT_ENABLED; |
| ipw_write32(priv, IPW_INTA_MASK_R, IPW_INTA_MASK_ALL); |
| } |
| |
| static inline void __ipw_disable_interrupts(struct ipw_priv *priv) |
| { |
| if (!(priv->status & STATUS_INT_ENABLED)) |
| return; |
| priv->status &= ~STATUS_INT_ENABLED; |
| ipw_write32(priv, IPW_INTA_MASK_R, ~IPW_INTA_MASK_ALL); |
| } |
| |
| static inline void ipw_enable_interrupts(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| __ipw_enable_interrupts(priv); |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| } |
| |
| static inline void ipw_disable_interrupts(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| __ipw_disable_interrupts(priv); |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| } |
| |
| static char *ipw_error_desc(u32 val) |
| { |
| switch (val) { |
| case IPW_FW_ERROR_OK: |
| return "ERROR_OK"; |
| case IPW_FW_ERROR_FAIL: |
| return "ERROR_FAIL"; |
| case IPW_FW_ERROR_MEMORY_UNDERFLOW: |
| return "MEMORY_UNDERFLOW"; |
| case IPW_FW_ERROR_MEMORY_OVERFLOW: |
| return "MEMORY_OVERFLOW"; |
| case IPW_FW_ERROR_BAD_PARAM: |
| return "BAD_PARAM"; |
| case IPW_FW_ERROR_BAD_CHECKSUM: |
| return "BAD_CHECKSUM"; |
| case IPW_FW_ERROR_NMI_INTERRUPT: |
| return "NMI_INTERRUPT"; |
| case IPW_FW_ERROR_BAD_DATABASE: |
| return "BAD_DATABASE"; |
| case IPW_FW_ERROR_ALLOC_FAIL: |
| return "ALLOC_FAIL"; |
| case IPW_FW_ERROR_DMA_UNDERRUN: |
| return "DMA_UNDERRUN"; |
| case IPW_FW_ERROR_DMA_STATUS: |
| return "DMA_STATUS"; |
| case IPW_FW_ERROR_DINO_ERROR: |
| return "DINO_ERROR"; |
| case IPW_FW_ERROR_EEPROM_ERROR: |
| return "EEPROM_ERROR"; |
| case IPW_FW_ERROR_SYSASSERT: |
| return "SYSASSERT"; |
| case IPW_FW_ERROR_FATAL_ERROR: |
| return "FATAL_ERROR"; |
| default: |
| return "UNKNOWN_ERROR"; |
| } |
| } |
| |
| static void ipw_dump_error_log(struct ipw_priv *priv, |
| struct ipw_fw_error *error) |
| { |
| u32 i; |
| |
| if (!error) { |
| IPW_ERROR("Error allocating and capturing error log. " |
| "Nothing to dump.\n"); |
| return; |
| } |
| |
| IPW_ERROR("Start IPW Error Log Dump:\n"); |
| IPW_ERROR("Status: 0x%08X, Config: %08X\n", |
| error->status, error->config); |
| |
| for (i = 0; i < error->elem_len; i++) |
| IPW_ERROR("%s %i 0x%08x 0x%08x 0x%08x 0x%08x 0x%08x\n", |
| ipw_error_desc(error->elem[i].desc), |
| error->elem[i].time, |
| error->elem[i].blink1, |
| error->elem[i].blink2, |
| error->elem[i].link1, |
| error->elem[i].link2, error->elem[i].data); |
| for (i = 0; i < error->log_len; i++) |
| IPW_ERROR("%i\t0x%08x\t%i\n", |
| error->log[i].time, |
| error->log[i].data, error->log[i].event); |
| } |
| |
| static inline int ipw_is_init(struct ipw_priv *priv) |
| { |
| return (priv->status & STATUS_INIT) ? 1 : 0; |
| } |
| |
| static int ipw_get_ordinal(struct ipw_priv *priv, u32 ord, void *val, u32 * len) |
| { |
| u32 addr, field_info, field_len, field_count, total_len; |
| |
| IPW_DEBUG_ORD("ordinal = %i\n", ord); |
| |
| if (!priv || !val || !len) { |
| IPW_DEBUG_ORD("Invalid argument\n"); |
| return -EINVAL; |
| } |
| |
| /* verify device ordinal tables have been initialized */ |
| if (!priv->table0_addr || !priv->table1_addr || !priv->table2_addr) { |
| IPW_DEBUG_ORD("Access ordinals before initialization\n"); |
| return -EINVAL; |
| } |
| |
| switch (IPW_ORD_TABLE_ID_MASK & ord) { |
| case IPW_ORD_TABLE_0_MASK: |
| /* |
| * TABLE 0: Direct access to a table of 32 bit values |
| * |
| * This is a very simple table with the data directly |
| * read from the table |
| */ |
| |
| /* remove the table id from the ordinal */ |
| ord &= IPW_ORD_TABLE_VALUE_MASK; |
| |
| /* boundary check */ |
| if (ord > priv->table0_len) { |
| IPW_DEBUG_ORD("ordinal value (%i) longer then " |
| "max (%i)\n", ord, priv->table0_len); |
| return -EINVAL; |
| } |
| |
| /* verify we have enough room to store the value */ |
| if (*len < sizeof(u32)) { |
| IPW_DEBUG_ORD("ordinal buffer length too small, " |
| "need %zd\n", sizeof(u32)); |
| return -EINVAL; |
| } |
| |
| IPW_DEBUG_ORD("Reading TABLE0[%i] from offset 0x%08x\n", |
| ord, priv->table0_addr + (ord << 2)); |
| |
| *len = sizeof(u32); |
| ord <<= 2; |
| *((u32 *) val) = ipw_read32(priv, priv->table0_addr + ord); |
| break; |
| |
| case IPW_ORD_TABLE_1_MASK: |
| /* |
| * TABLE 1: Indirect access to a table of 32 bit values |
| * |
| * This is a fairly large table of u32 values each |
| * representing starting addr for the data (which is |
| * also a u32) |
| */ |
| |
| /* remove the table id from the ordinal */ |
| ord &= IPW_ORD_TABLE_VALUE_MASK; |
| |
| /* boundary check */ |
| if (ord > priv->table1_len) { |
| IPW_DEBUG_ORD("ordinal value too long\n"); |
| return -EINVAL; |
| } |
| |
| /* verify we have enough room to store the value */ |
| if (*len < sizeof(u32)) { |
| IPW_DEBUG_ORD("ordinal buffer length too small, " |
| "need %zd\n", sizeof(u32)); |
| return -EINVAL; |
| } |
| |
| *((u32 *) val) = |
| ipw_read_reg32(priv, (priv->table1_addr + (ord << 2))); |
| *len = sizeof(u32); |
| break; |
| |
| case IPW_ORD_TABLE_2_MASK: |
| /* |
| * TABLE 2: Indirect access to a table of variable sized values |
| * |
| * This table consist of six values, each containing |
| * - dword containing the starting offset of the data |
| * - dword containing the lengh in the first 16bits |
| * and the count in the second 16bits |
| */ |
| |
| /* remove the table id from the ordinal */ |
| ord &= IPW_ORD_TABLE_VALUE_MASK; |
| |
| /* boundary check */ |
| if (ord > priv->table2_len) { |
| IPW_DEBUG_ORD("ordinal value too long\n"); |
| return -EINVAL; |
| } |
| |
| /* get the address of statistic */ |
| addr = ipw_read_reg32(priv, priv->table2_addr + (ord << 3)); |
| |
| /* get the second DW of statistics ; |
| * two 16-bit words - first is length, second is count */ |
| field_info = |
| ipw_read_reg32(priv, |
| priv->table2_addr + (ord << 3) + |
| sizeof(u32)); |
| |
| /* get each entry length */ |
| field_len = *((u16 *) & field_info); |
| |
| /* get number of entries */ |
| field_count = *(((u16 *) & field_info) + 1); |
| |
| /* abort if not enough memory */ |
| total_len = field_len * field_count; |
| if (total_len > *len) { |
| *len = total_len; |
| return -EINVAL; |
| } |
| |
| *len = total_len; |
| if (!total_len) |
| return 0; |
| |
| IPW_DEBUG_ORD("addr = 0x%08x, total_len = %i, " |
| "field_info = 0x%08x\n", |
| addr, total_len, field_info); |
| ipw_read_indirect(priv, addr, val, total_len); |
| break; |
| |
| default: |
| IPW_DEBUG_ORD("Invalid ordinal!\n"); |
| return -EINVAL; |
| |
| } |
| |
| return 0; |
| } |
| |
| static void ipw_init_ordinals(struct ipw_priv *priv) |
| { |
| priv->table0_addr = IPW_ORDINALS_TABLE_LOWER; |
| priv->table0_len = ipw_read32(priv, priv->table0_addr); |
| |
| IPW_DEBUG_ORD("table 0 offset at 0x%08x, len = %i\n", |
| priv->table0_addr, priv->table0_len); |
| |
| priv->table1_addr = ipw_read32(priv, IPW_ORDINALS_TABLE_1); |
| priv->table1_len = ipw_read_reg32(priv, priv->table1_addr); |
| |
| IPW_DEBUG_ORD("table 1 offset at 0x%08x, len = %i\n", |
| priv->table1_addr, priv->table1_len); |
| |
| priv->table2_addr = ipw_read32(priv, IPW_ORDINALS_TABLE_2); |
| priv->table2_len = ipw_read_reg32(priv, priv->table2_addr); |
| priv->table2_len &= 0x0000ffff; /* use first two bytes */ |
| |
| IPW_DEBUG_ORD("table 2 offset at 0x%08x, len = %i\n", |
| priv->table2_addr, priv->table2_len); |
| |
| } |
| |
| static u32 ipw_register_toggle(u32 reg) |
| { |
| reg &= ~IPW_START_STANDBY; |
| if (reg & IPW_GATE_ODMA) |
| reg &= ~IPW_GATE_ODMA; |
| if (reg & IPW_GATE_IDMA) |
| reg &= ~IPW_GATE_IDMA; |
| if (reg & IPW_GATE_ADMA) |
| reg &= ~IPW_GATE_ADMA; |
| return reg; |
| } |
| |
| /* |
| * LED behavior: |
| * - On radio ON, turn on any LEDs that require to be on during start |
| * - On initialization, start unassociated blink |
| * - On association, disable unassociated blink |
| * - On disassociation, start unassociated blink |
| * - On radio OFF, turn off any LEDs started during radio on |
| * |
| */ |
| #define LD_TIME_LINK_ON msecs_to_jiffies(300) |
| #define LD_TIME_LINK_OFF msecs_to_jiffies(2700) |
| #define LD_TIME_ACT_ON msecs_to_jiffies(250) |
| |
| static void ipw_led_link_on(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| u32 led; |
| |
| /* If configured to not use LEDs, or nic_type is 1, |
| * then we don't toggle a LINK led */ |
| if (priv->config & CFG_NO_LED || priv->nic_type == EEPROM_NIC_TYPE_1) |
| return; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| if (!(priv->status & STATUS_RF_KILL_MASK) && |
| !(priv->status & STATUS_LED_LINK_ON)) { |
| IPW_DEBUG_LED("Link LED On\n"); |
| led = ipw_read_reg32(priv, IPW_EVENT_REG); |
| led |= priv->led_association_on; |
| |
| led = ipw_register_toggle(led); |
| |
| IPW_DEBUG_LED("Reg: 0x%08X\n", led); |
| ipw_write_reg32(priv, IPW_EVENT_REG, led); |
| |
| priv->status |= STATUS_LED_LINK_ON; |
| |
| /* If we aren't associated, schedule turning the LED off */ |
| if (!(priv->status & STATUS_ASSOCIATED)) |
| schedule_delayed_work(&priv->led_link_off, |
| LD_TIME_LINK_ON); |
| } |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| |
| static void ipw_bg_led_link_on(struct work_struct *work) |
| { |
| struct ipw_priv *priv = |
| container_of(work, struct ipw_priv, led_link_on.work); |
| mutex_lock(&priv->mutex); |
| ipw_led_link_on(priv); |
| mutex_unlock(&priv->mutex); |
| } |
| |
| static void ipw_led_link_off(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| u32 led; |
| |
| /* If configured not to use LEDs, or nic type is 1, |
| * then we don't goggle the LINK led. */ |
| if (priv->config & CFG_NO_LED || priv->nic_type == EEPROM_NIC_TYPE_1) |
| return; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| if (priv->status & STATUS_LED_LINK_ON) { |
| led = ipw_read_reg32(priv, IPW_EVENT_REG); |
| led &= priv->led_association_off; |
| led = ipw_register_toggle(led); |
| |
| IPW_DEBUG_LED("Reg: 0x%08X\n", led); |
| ipw_write_reg32(priv, IPW_EVENT_REG, led); |
| |
| IPW_DEBUG_LED("Link LED Off\n"); |
| |
| priv->status &= ~STATUS_LED_LINK_ON; |
| |
| /* If we aren't associated and the radio is on, schedule |
| * turning the LED on (blink while unassociated) */ |
| if (!(priv->status & STATUS_RF_KILL_MASK) && |
| !(priv->status & STATUS_ASSOCIATED)) |
| schedule_delayed_work(&priv->led_link_on, |
| LD_TIME_LINK_OFF); |
| |
| } |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| |
| static void ipw_bg_led_link_off(struct work_struct *work) |
| { |
| struct ipw_priv *priv = |
| container_of(work, struct ipw_priv, led_link_off.work); |
| mutex_lock(&priv->mutex); |
| ipw_led_link_off(priv); |
| mutex_unlock(&priv->mutex); |
| } |
| |
| static void __ipw_led_activity_on(struct ipw_priv *priv) |
| { |
| u32 led; |
| |
| if (priv->config & CFG_NO_LED) |
| return; |
| |
| if (priv->status & STATUS_RF_KILL_MASK) |
| return; |
| |
| if (!(priv->status & STATUS_LED_ACT_ON)) { |
| led = ipw_read_reg32(priv, IPW_EVENT_REG); |
| led |= priv->led_activity_on; |
| |
| led = ipw_register_toggle(led); |
| |
| IPW_DEBUG_LED("Reg: 0x%08X\n", led); |
| ipw_write_reg32(priv, IPW_EVENT_REG, led); |
| |
| IPW_DEBUG_LED("Activity LED On\n"); |
| |
| priv->status |= STATUS_LED_ACT_ON; |
| |
| cancel_delayed_work(&priv->led_act_off); |
| schedule_delayed_work(&priv->led_act_off, LD_TIME_ACT_ON); |
| } else { |
| /* Reschedule LED off for full time period */ |
| cancel_delayed_work(&priv->led_act_off); |
| schedule_delayed_work(&priv->led_act_off, LD_TIME_ACT_ON); |
| } |
| } |
| |
| #if 0 |
| void ipw_led_activity_on(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| spin_lock_irqsave(&priv->lock, flags); |
| __ipw_led_activity_on(priv); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| #endif /* 0 */ |
| |
| static void ipw_led_activity_off(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| u32 led; |
| |
| if (priv->config & CFG_NO_LED) |
| return; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| if (priv->status & STATUS_LED_ACT_ON) { |
| led = ipw_read_reg32(priv, IPW_EVENT_REG); |
| led &= priv->led_activity_off; |
| |
| led = ipw_register_toggle(led); |
| |
| IPW_DEBUG_LED("Reg: 0x%08X\n", led); |
| ipw_write_reg32(priv, IPW_EVENT_REG, led); |
| |
| IPW_DEBUG_LED("Activity LED Off\n"); |
| |
| priv->status &= ~STATUS_LED_ACT_ON; |
| } |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| |
| static void ipw_bg_led_activity_off(struct work_struct *work) |
| { |
| struct ipw_priv *priv = |
| container_of(work, struct ipw_priv, led_act_off.work); |
| mutex_lock(&priv->mutex); |
| ipw_led_activity_off(priv); |
| mutex_unlock(&priv->mutex); |
| } |
| |
| static void ipw_led_band_on(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| u32 led; |
| |
| /* Only nic type 1 supports mode LEDs */ |
| if (priv->config & CFG_NO_LED || |
| priv->nic_type != EEPROM_NIC_TYPE_1 || !priv->assoc_network) |
| return; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| led = ipw_read_reg32(priv, IPW_EVENT_REG); |
| if (priv->assoc_network->mode == IEEE_A) { |
| led |= priv->led_ofdm_on; |
| led &= priv->led_association_off; |
| IPW_DEBUG_LED("Mode LED On: 802.11a\n"); |
| } else if (priv->assoc_network->mode == IEEE_G) { |
| led |= priv->led_ofdm_on; |
| led |= priv->led_association_on; |
| IPW_DEBUG_LED("Mode LED On: 802.11g\n"); |
| } else { |
| led &= priv->led_ofdm_off; |
| led |= priv->led_association_on; |
| IPW_DEBUG_LED("Mode LED On: 802.11b\n"); |
| } |
| |
| led = ipw_register_toggle(led); |
| |
| IPW_DEBUG_LED("Reg: 0x%08X\n", led); |
| ipw_write_reg32(priv, IPW_EVENT_REG, led); |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| |
| static void ipw_led_band_off(struct ipw_priv *priv) |
| { |
| unsigned long flags; |
| u32 led; |
| |
| /* Only nic type 1 supports mode LEDs */ |
| if (priv->config & CFG_NO_LED || priv->nic_type != EEPROM_NIC_TYPE_1) |
| return; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| led = ipw_read_reg32(priv, IPW_EVENT_REG); |
| led &= priv->led_ofdm_off; |
| led &= priv->led_association_off; |
| |
| led = ipw_register_toggle(led); |
| |
| IPW_DEBUG_LED("Reg: 0x%08X\n", led); |
| ipw_write_reg32(priv, IPW_EVENT_REG, led); |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } |
| |
| static void ipw_led_radio_on(struct ipw_priv *priv) |
| { |
| ipw_led_link_on(priv); |
| } |
| |
| static void ipw_led_radio_off(struct ipw_priv *priv) |
| { |
| ipw_led_activity_off(priv); |
| ipw_led_link_off(priv); |
| } |
| |
| static void ipw_led_link_up(struct ipw_priv *priv) |
| { |
| /* Set the Link Led on for all nic types */ |
| ipw_led_link_on(priv); |
| } |
| |
| static void ipw_led_link_down(struct ipw_priv *priv) |
| { |
| ipw_led_activity_off(priv); |
| ipw_led_link_off(priv); |
| |
| if (priv->status & STATUS_RF_KILL_MASK) |
| ipw_led_radio_off(priv); |
| } |
| |
| static void ipw_led_init(struct ipw_priv *priv) |
| { |
| priv->nic_type = priv->eeprom[EEPROM_NIC_TYPE]; |
| |
| /* Set the default PINs for the link and activity leds */ |
| priv->led_activity_on = IPW_ACTIVITY_LED; |
| priv->led_activity_off = ~(IPW_ACTIVITY_LED); |
| |
| priv->led_association_on = IPW_ASSOCIATED_LED; |
| priv->led_association_off = ~(IPW_ASSOCIATED_LED); |
| |
| /* Set the default PINs for the OFDM leds */ |
| priv->led_ofdm_on = IPW_OFDM_LED; |
| priv->led_ofdm_off = ~(IPW_OFDM_LED); |
| |
| switch (priv->nic_type) { |
| case EEPROM_NIC_TYPE_1: |
| /* In this NIC type, the LEDs are reversed.... */ |
| priv->led_activity_on = IPW_ASSOCIATED_LED; |
| priv->led_activity_off = ~(IPW_ASSOCIATED_LED); |
| priv->led_association_on = IPW_ACTIVITY_LED; |
| priv->led_association_off = ~(IPW_ACTIVITY_LED); |
| |
| if (!(priv->config & CFG_NO_LED)) |
| ipw_led_band_on(priv); |
| |
| /* And we don't blink link LEDs for this nic, so |
| * just return here */ |
| return; |
| |
| case EEPROM_NIC_TYPE_3: |
| case EEPROM_NIC_TYPE_2: |
| case EEPROM_NIC_TYPE_4: |
| case EEPROM_NIC_TYPE_0: |
| break; |
| |
| default: |
| IPW_DEBUG_INFO("Unknown NIC type from EEPROM: %d\n", |
| priv->nic_type); |
| priv->nic_type = EEPROM_NIC_TYPE_0; |
| break; |
| } |
| |
| if (!(priv->config & CFG_NO_LED)) { |
| if (priv->status & STATUS_ASSOCIATED) |
| ipw_led_link_on(priv); |
| else |
| ipw_led_link_off(priv); |
| } |
| } |
| |
| static void ipw_led_shutdown(struct ipw_priv *priv) |
| { |
| ipw_led_activity_off(priv); |
| ipw_led_link_off(priv); |
| ipw_led_band_off(priv); |
| cancel_delayed_work(&priv->led_link_on); |
| cancel_delayed_work(&priv->led_link_off); |
| cancel_delayed_work(&priv->led_act_off); |
| } |
| |
| /* |
| * The following adds a new attribute to the sysfs representation |
| * of this device driver (i.e. a new file in /sys/bus/pci/drivers/ipw/) |
| * used for controlling the debug level. |
| * |
| * See the level definitions in ipw for details. |
| */ |
| static ssize_t show_debug_level(struct device_driver *d, char *buf) |
| { |
| return sprintf(buf, "0x%08X\n", ipw_debug_level); |
| } |
| |
| static ssize_t store_debug_level(struct device_driver *d, const char *buf, |
| size_t count) |
| { |
| char *p = (char *)buf; |
| u32 val; |
| |
| if (p[1] == 'x' || p[1] == 'X' || p[0] == 'x' || p[0] == 'X') { |
| p++; |
| if (p[0] == 'x' || p[0] == 'X') |
| p++; |
| val = simple_strtoul(p, &p, 16); |
| } else |
| val = simple_strtoul(p, &p, 10); |
| if (p == buf) |
| printk(KERN_INFO DRV_NAME |
| ": %s is not in hex or decimal form.\n", buf); |
| else |
| ipw_debug_level = val; |
| |
| return strnlen(buf, count); |
| } |
| |
| static DRIVER_ATTR(debug_level, S_IWUSR | S_IRUGO, |
| show_debug_level, store_debug_level); |
| |
| static inline u32 ipw_get_event_log_len(struct ipw_priv *priv) |
| { |
| /* length = 1st dword in log */ |
| return ipw_read_reg32(priv, ipw_read32(priv, IPW_EVENT_LOG)); |
| } |
| |
| static void ipw_capture_event_log(struct ipw_priv *priv, |
| u32 log_len, struct ipw_event *log) |
| { |
| u32 base; |
| |
| if (log_len) { |
| base = ipw_read32(priv, IPW_EVENT_LOG); |
| ipw_read_indirect(priv, base + sizeof(base) + sizeof(u32), |
| (u8 *) log, sizeof(*log) * log_len); |
| } |
| } |
| |
| static struct ipw_fw_error *ipw_alloc_error_log(struct ipw_priv *priv) |
| { |
| struct ipw_fw_error *error; |
| u32 log_len = ipw_get_event_log_len(priv); |
| u32 base = ipw_read32(priv, IPW_ERROR_LOG); |
| u32 elem_len = ipw_read_reg32(priv, base); |
| |
| error = kmalloc(sizeof(*error) + |
| sizeof(*error->elem) * elem_len + |
| sizeof(*error->log) * log_len, GFP_ATOMIC); |
| if (!error) { |
| IPW_ERROR("Memory allocation for firmware error log " |
| "failed.\n"); |
| return NULL; |
| } |
| error->jiffies = jiffies; |
| error->status = priv->status; |
| error->config = priv->config; |
| error->elem_len = elem_len; |
| error->log_len = log_len; |
| error->elem = (struct ipw_error_elem *)error->payload; |
| error->log = (struct ipw_event *)(error->elem + elem_len); |
| |
| ipw_capture_event_log(priv, log_len, error->log); |
| |
| if (elem_len) |
| ipw_read_indirect(priv, base + sizeof(base), (u8 *) error->elem, |
| sizeof(*error->elem) * elem_len); |
| |
| return error; |
| } |
| |
| static ssize_t show_event_log(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| u32 log_len = ipw_get_event_log_len(priv); |
| u32 log_size; |
| struct ipw_event *log; |
| u32 len = 0, i; |
| |
| /* not using min() because of its strict type checking */ |
| log_size = PAGE_SIZE / sizeof(*log) > log_len ? |
| sizeof(*log) * log_len : PAGE_SIZE; |
| log = kzalloc(log_size, GFP_KERNEL); |
| if (!log) { |
| IPW_ERROR("Unable to allocate memory for log\n"); |
| return 0; |
| } |
| log_len = log_size / sizeof(*log); |
| ipw_capture_event_log(priv, log_len, log); |
| |
| len += snprintf(buf + len, PAGE_SIZE - len, "%08X", log_len); |
| for (i = 0; i < log_len; i++) |
| len += snprintf(buf + len, PAGE_SIZE - len, |
| "\n%08X%08X%08X", |
| log[i].time, log[i].event, log[i].data); |
| len += snprintf(buf + len, PAGE_SIZE - len, "\n"); |
| kfree(log); |
| return len; |
| } |
| |
| static DEVICE_ATTR(event_log, S_IRUGO, show_event_log, NULL); |
| |
| static ssize_t show_error(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| u32 len = 0, i; |
| if (!priv->error) |
| return 0; |
| len += snprintf(buf + len, PAGE_SIZE - len, |
| "%08lX%08X%08X%08X", |
| priv->error->jiffies, |
| priv->error->status, |
| priv->error->config, priv->error->elem_len); |
| for (i = 0; i < priv->error->elem_len; i++) |
| len += snprintf(buf + len, PAGE_SIZE - len, |
| "\n%08X%08X%08X%08X%08X%08X%08X", |
| priv->error->elem[i].time, |
| priv->error->elem[i].desc, |
| priv->error->elem[i].blink1, |
| priv->error->elem[i].blink2, |
| priv->error->elem[i].link1, |
| priv->error->elem[i].link2, |
| priv->error->elem[i].data); |
| |
| len += snprintf(buf + len, PAGE_SIZE - len, |
| "\n%08X", priv->error->log_len); |
| for (i = 0; i < priv->error->log_len; i++) |
| len += snprintf(buf + len, PAGE_SIZE - len, |
| "\n%08X%08X%08X", |
| priv->error->log[i].time, |
| priv->error->log[i].event, |
| priv->error->log[i].data); |
| len += snprintf(buf + len, PAGE_SIZE - len, "\n"); |
| return len; |
| } |
| |
| static ssize_t clear_error(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| kfree(priv->error); |
| priv->error = NULL; |
| return count; |
| } |
| |
| static DEVICE_ATTR(error, S_IRUGO | S_IWUSR, show_error, clear_error); |
| |
| static ssize_t show_cmd_log(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| u32 len = 0, i; |
| if (!priv->cmdlog) |
| return 0; |
| for (i = (priv->cmdlog_pos + 1) % priv->cmdlog_len; |
| (i != priv->cmdlog_pos) && (len < PAGE_SIZE); |
| i = (i + 1) % priv->cmdlog_len) { |
| len += |
| snprintf(buf + len, PAGE_SIZE - len, |
| "\n%08lX%08X%08X%08X\n", priv->cmdlog[i].jiffies, |
| priv->cmdlog[i].retcode, priv->cmdlog[i].cmd.cmd, |
| priv->cmdlog[i].cmd.len); |
| len += |
| snprintk_buf(buf + len, PAGE_SIZE - len, |
| (u8 *) priv->cmdlog[i].cmd.param, |
| priv->cmdlog[i].cmd.len); |
| len += snprintf(buf + len, PAGE_SIZE - len, "\n"); |
| } |
| len += snprintf(buf + len, PAGE_SIZE - len, "\n"); |
| return len; |
| } |
| |
| static DEVICE_ATTR(cmd_log, S_IRUGO, show_cmd_log, NULL); |
| |
| #ifdef CONFIG_IPW2200_PROMISCUOUS |
| static void ipw_prom_free(struct ipw_priv *priv); |
| static int ipw_prom_alloc(struct ipw_priv *priv); |
| static ssize_t store_rtap_iface(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| int rc = 0; |
| |
| if (count < 1) |
| return -EINVAL; |
| |
| switch (buf[0]) { |
| case '0': |
| if (!rtap_iface) |
| return count; |
| |
| if (netif_running(priv->prom_net_dev)) { |
| IPW_WARNING("Interface is up. Cannot unregister.\n"); |
| return count; |
| } |
| |
| ipw_prom_free(priv); |
| rtap_iface = 0; |
| break; |
| |
| case '1': |
| if (rtap_iface) |
| return count; |
| |
| rc = ipw_prom_alloc(priv); |
| if (!rc) |
| rtap_iface = 1; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| if (rc) { |
| IPW_ERROR("Failed to register promiscuous network " |
| "device (error %d).\n", rc); |
| } |
| |
| return count; |
| } |
| |
| static ssize_t show_rtap_iface(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| if (rtap_iface) |
| return sprintf(buf, "%s", priv->prom_net_dev->name); |
| else { |
| buf[0] = '-'; |
| buf[1] = '1'; |
| buf[2] = '\0'; |
| return 3; |
| } |
| } |
| |
| static DEVICE_ATTR(rtap_iface, S_IWUSR | S_IRUSR, show_rtap_iface, |
| store_rtap_iface); |
| |
| static ssize_t store_rtap_filter(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| if (!priv->prom_priv) { |
| IPW_ERROR("Attempting to set filter without " |
| "rtap_iface enabled.\n"); |
| return -EPERM; |
| } |
| |
| priv->prom_priv->filter = simple_strtol(buf, NULL, 0); |
| |
| IPW_DEBUG_INFO("Setting rtap filter to " BIT_FMT16 "\n", |
| BIT_ARG16(priv->prom_priv->filter)); |
| |
| return count; |
| } |
| |
| static ssize_t show_rtap_filter(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| return sprintf(buf, "0x%04X", |
| priv->prom_priv ? priv->prom_priv->filter : 0); |
| } |
| |
| static DEVICE_ATTR(rtap_filter, S_IWUSR | S_IRUSR, show_rtap_filter, |
| store_rtap_filter); |
| #endif |
| |
| static ssize_t show_scan_age(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| return sprintf(buf, "%d\n", priv->ieee->scan_age); |
| } |
| |
| static ssize_t store_scan_age(struct device *d, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| struct net_device *dev = priv->net_dev; |
| char buffer[] = "00000000"; |
| unsigned long len = |
| (sizeof(buffer) - 1) > count ? count : sizeof(buffer) - 1; |
| unsigned long val; |
| char *p = buffer; |
| |
| IPW_DEBUG_INFO("enter\n"); |
| |
| strncpy(buffer, buf, len); |
| buffer[len] = 0; |
| |
| if (p[1] == 'x' || p[1] == 'X' || p[0] == 'x' || p[0] == 'X') { |
| p++; |
| if (p[0] == 'x' || p[0] == 'X') |
| p++; |
| val = simple_strtoul(p, &p, 16); |
| } else |
| val = simple_strtoul(p, &p, 10); |
| if (p == buffer) { |
| IPW_DEBUG_INFO("%s: user supplied invalid value.\n", dev->name); |
| } else { |
| priv->ieee->scan_age = val; |
| IPW_DEBUG_INFO("set scan_age = %u\n", priv->ieee->scan_age); |
| } |
| |
| IPW_DEBUG_INFO("exit\n"); |
| return len; |
| } |
| |
| static DEVICE_ATTR(scan_age, S_IWUSR | S_IRUGO, show_scan_age, store_scan_age); |
| |
| static ssize_t show_led(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| return sprintf(buf, "%d\n", (priv->config & CFG_NO_LED) ? 0 : 1); |
| } |
| |
| static ssize_t store_led(struct device *d, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| IPW_DEBUG_INFO("enter\n"); |
| |
| if (count == 0) |
| return 0; |
| |
| if (*buf == 0) { |
| IPW_DEBUG_LED("Disabling LED control.\n"); |
| priv->config |= CFG_NO_LED; |
| ipw_led_shutdown(priv); |
| } else { |
| IPW_DEBUG_LED("Enabling LED control.\n"); |
| priv->config &= ~CFG_NO_LED; |
| ipw_led_init(priv); |
| } |
| |
| IPW_DEBUG_INFO("exit\n"); |
| return count; |
| } |
| |
| static DEVICE_ATTR(led, S_IWUSR | S_IRUGO, show_led, store_led); |
| |
| static ssize_t show_status(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ipw_priv *p = dev_get_drvdata(d); |
| return sprintf(buf, "0x%08x\n", (int)p->status); |
| } |
| |
| static DEVICE_ATTR(status, S_IRUGO, show_status, NULL); |
| |
| static ssize_t show_cfg(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *p = dev_get_drvdata(d); |
| return sprintf(buf, "0x%08x\n", (int)p->config); |
| } |
| |
| static DEVICE_ATTR(cfg, S_IRUGO, show_cfg, NULL); |
| |
| static ssize_t show_nic_type(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| return sprintf(buf, "TYPE: %d\n", priv->nic_type); |
| } |
| |
| static DEVICE_ATTR(nic_type, S_IRUGO, show_nic_type, NULL); |
| |
| static ssize_t show_ucode_version(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| u32 len = sizeof(u32), tmp = 0; |
| struct ipw_priv *p = dev_get_drvdata(d); |
| |
| if (ipw_get_ordinal(p, IPW_ORD_STAT_UCODE_VERSION, &tmp, &len)) |
| return 0; |
| |
| return sprintf(buf, "0x%08x\n", tmp); |
| } |
| |
| static DEVICE_ATTR(ucode_version, S_IWUSR | S_IRUGO, show_ucode_version, NULL); |
| |
| static ssize_t show_rtc(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| u32 len = sizeof(u32), tmp = 0; |
| struct ipw_priv *p = dev_get_drvdata(d); |
| |
| if (ipw_get_ordinal(p, IPW_ORD_STAT_RTC, &tmp, &len)) |
| return 0; |
| |
| return sprintf(buf, "0x%08x\n", tmp); |
| } |
| |
| static DEVICE_ATTR(rtc, S_IWUSR | S_IRUGO, show_rtc, NULL); |
| |
| /* |
| * Add a device attribute to view/control the delay between eeprom |
| * operations. |
| */ |
| static ssize_t show_eeprom_delay(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ipw_priv *p = dev_get_drvdata(d); |
| int n = p->eeprom_delay; |
| return sprintf(buf, "%i\n", n); |
| } |
| static ssize_t store_eeprom_delay(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *p = dev_get_drvdata(d); |
| sscanf(buf, "%i", &p->eeprom_delay); |
| return strnlen(buf, count); |
| } |
| |
| static DEVICE_ATTR(eeprom_delay, S_IWUSR | S_IRUGO, |
| show_eeprom_delay, store_eeprom_delay); |
| |
| static ssize_t show_command_event_reg(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| u32 reg = 0; |
| struct ipw_priv *p = dev_get_drvdata(d); |
| |
| reg = ipw_read_reg32(p, IPW_INTERNAL_CMD_EVENT); |
| return sprintf(buf, "0x%08x\n", reg); |
| } |
| static ssize_t store_command_event_reg(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| u32 reg; |
| struct ipw_priv *p = dev_get_drvdata(d); |
| |
| sscanf(buf, "%x", ®); |
| ipw_write_reg32(p, IPW_INTERNAL_CMD_EVENT, reg); |
| return strnlen(buf, count); |
| } |
| |
| static DEVICE_ATTR(command_event_reg, S_IWUSR | S_IRUGO, |
| show_command_event_reg, store_command_event_reg); |
| |
| static ssize_t show_mem_gpio_reg(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| u32 reg = 0; |
| struct ipw_priv *p = dev_get_drvdata(d); |
| |
| reg = ipw_read_reg32(p, 0x301100); |
| return sprintf(buf, "0x%08x\n", reg); |
| } |
| static ssize_t store_mem_gpio_reg(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| u32 reg; |
| struct ipw_priv *p = dev_get_drvdata(d); |
| |
| sscanf(buf, "%x", ®); |
| ipw_write_reg32(p, 0x301100, reg); |
| return strnlen(buf, count); |
| } |
| |
| static DEVICE_ATTR(mem_gpio_reg, S_IWUSR | S_IRUGO, |
| show_mem_gpio_reg, store_mem_gpio_reg); |
| |
| static ssize_t show_indirect_dword(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| u32 reg = 0; |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| if (priv->status & STATUS_INDIRECT_DWORD) |
| reg = ipw_read_reg32(priv, priv->indirect_dword); |
| else |
| reg = 0; |
| |
| return sprintf(buf, "0x%08x\n", reg); |
| } |
| static ssize_t store_indirect_dword(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| sscanf(buf, "%x", &priv->indirect_dword); |
| priv->status |= STATUS_INDIRECT_DWORD; |
| return strnlen(buf, count); |
| } |
| |
| static DEVICE_ATTR(indirect_dword, S_IWUSR | S_IRUGO, |
| show_indirect_dword, store_indirect_dword); |
| |
| static ssize_t show_indirect_byte(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| u8 reg = 0; |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| if (priv->status & STATUS_INDIRECT_BYTE) |
| reg = ipw_read_reg8(priv, priv->indirect_byte); |
| else |
| reg = 0; |
| |
| return sprintf(buf, "0x%02x\n", reg); |
| } |
| static ssize_t store_indirect_byte(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| sscanf(buf, "%x", &priv->indirect_byte); |
| priv->status |= STATUS_INDIRECT_BYTE; |
| return strnlen(buf, count); |
| } |
| |
| static DEVICE_ATTR(indirect_byte, S_IWUSR | S_IRUGO, |
| show_indirect_byte, store_indirect_byte); |
| |
| static ssize_t show_direct_dword(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| u32 reg = 0; |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| if (priv->status & STATUS_DIRECT_DWORD) |
| reg = ipw_read32(priv, priv->direct_dword); |
| else |
| reg = 0; |
| |
| return sprintf(buf, "0x%08x\n", reg); |
| } |
| static ssize_t store_direct_dword(struct device *d, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| sscanf(buf, "%x", &priv->direct_dword); |
| priv->status |= STATUS_DIRECT_DWORD; |
| return strnlen(buf, count); |
| } |
| |
| static DEVICE_ATTR(direct_dword, S_IWUSR | S_IRUGO, |
| show_direct_dword, store_direct_dword); |
| |
| static int rf_kill_active(struct ipw_priv *priv) |
| { |
| if (0 == (ipw_read32(priv, 0x30) & 0x10000)) { |
| priv->status |= STATUS_RF_KILL_HW; |
| wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, true); |
| } else { |
| priv->status &= ~STATUS_RF_KILL_HW; |
| wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, false); |
| } |
| |
| return (priv->status & STATUS_RF_KILL_HW) ? 1 : 0; |
| } |
| |
| static ssize_t show_rf_kill(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| /* 0 - RF kill not enabled |
| 1 - SW based RF kill active (sysfs) |
| 2 - HW based RF kill active |
| 3 - Both HW and SW baed RF kill active */ |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| int val = ((priv->status & STATUS_RF_KILL_SW) ? 0x1 : 0x0) | |
| (rf_kill_active(priv) ? 0x2 : 0x0); |
| return sprintf(buf, "%i\n", val); |
| } |
| |
| static int ipw_radio_kill_sw(struct ipw_priv *priv, int disable_radio) |
| { |
| if ((disable_radio ? 1 : 0) == |
| ((priv->status & STATUS_RF_KILL_SW) ? 1 : 0)) |
| return 0; |
| |
| IPW_DEBUG_RF_KILL("Manual SW RF Kill set to: RADIO %s\n", |
| disable_radio ? "OFF" : "ON"); |
| |
| if (disable_radio) { |
| priv->status |= STATUS_RF_KILL_SW; |
| |
| cancel_delayed_work(&priv->request_scan); |
| cancel_delayed_work(&priv->request_direct_scan); |
| cancel_delayed_work(&priv->request_passive_scan); |
| cancel_delayed_work(&priv->scan_event); |
| schedule_work(&priv->down); |
| } else { |
| priv->status &= ~STATUS_RF_KILL_SW; |
| if (rf_kill_active(priv)) { |
| IPW_DEBUG_RF_KILL("Can not turn radio back on - " |
| "disabled by HW switch\n"); |
| /* Make sure the RF_KILL check timer is running */ |
| cancel_delayed_work(&priv->rf_kill); |
| schedule_delayed_work(&priv->rf_kill, |
| round_jiffies_relative(2 * HZ)); |
| } else |
| schedule_work(&priv->up); |
| } |
| |
| return 1; |
| } |
| |
| static ssize_t store_rf_kill(struct device *d, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| |
| ipw_radio_kill_sw(priv, buf[0] == '1'); |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(rf_kill, S_IWUSR | S_IRUGO, show_rf_kill, store_rf_kill); |
| |
| static ssize_t show_speed_scan(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| int pos = 0, len = 0; |
| if (priv->config & CFG_SPEED_SCAN) { |
| while (priv->speed_scan[pos] != 0) |
| len += sprintf(&buf[len], "%d ", |
| priv->speed_scan[pos++]); |
| return len + sprintf(&buf[len], "\n"); |
| } |
| |
| return sprintf(buf, "0\n"); |
| } |
| |
| static ssize_t store_speed_scan(struct device *d, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| int channel, pos = 0; |
| const char *p = buf; |
| |
| /* list of space separated channels to scan, optionally ending with 0 */ |
| while ((channel = simple_strtol(p, NULL, 0))) { |
| if (pos == MAX_SPEED_SCAN - 1) { |
| priv->speed_scan[pos] = 0; |
| break; |
| } |
| |
| if (libipw_is_valid_channel(priv->ieee, channel)) |
| priv->speed_scan[pos++] = channel; |
| else |
| IPW_WARNING("Skipping invalid channel request: %d\n", |
| channel); |
| p = strchr(p, ' '); |
| if (!p) |
| break; |
| while (*p == ' ' || *p == '\t') |
| p++; |
| } |
| |
| if (pos == 0) |
| priv->config &= ~CFG_SPEED_SCAN; |
| else { |
| priv->speed_scan_pos = 0; |
| priv->config |= CFG_SPEED_SCAN; |
| } |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(speed_scan, S_IWUSR | S_IRUGO, show_speed_scan, |
| store_speed_scan); |
| |
| static ssize_t show_net_stats(struct device *d, struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| return sprintf(buf, "%c\n", (priv->config & CFG_NET_STATS) ? '1' : '0'); |
| } |
| |
| static ssize_t store_net_stats(struct device *d, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| if (buf[0] == '1') |
| priv->config |= CFG_NET_STATS; |
| else |
| priv->config &= ~CFG_NET_STATS; |
| |
| return count; |
| } |
| |
| static DEVICE_ATTR(net_stats, S_IWUSR | S_IRUGO, |
| show_net_stats, store_net_stats); |
| |
| static ssize_t show_channels(struct device *d, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct ipw_priv *priv = dev_get_drvdata(d); |
| const struct libipw_geo *geo = libipw_get_geo(priv->ieee); |
| int len = 0, i; |
| |
| len = sprintf(&buf[len], |
| "Displaying %d channels in 2.4Ghz band " |
| "(802.11bg):\n", geo->bg_channels); |
| |
| for (i = 0; i < geo->bg_channels; i++) { |
| len += sprintf(&buf[len], "%d: BSS%s%s, %s, Band %s.\n", |
| geo->bg[i].channel, |
| geo->bg[i].flags & LIBIPW_CH_RADAR_DETECT ? |
| " (radar spectrum)" : "", |
| ((geo->bg[i].flags & LIBIPW_CH_NO_IBSS) || |
| (geo->bg[i].flags & LIBIPW_CH_RADAR_DETECT)) |
| ? "" : ", IBSS", |
| geo->bg[i].flags & LIBIPW_CH_PASSIVE_ONLY ? |
| "passive only" : "active/passive", |
| geo->bg[i].flags & LIBIPW_CH_B_ONLY ? |
| "B" : "B/G"); |
| } |
| |
| len += sprintf(&buf[len], |
| "Displaying %d channels in 5.2Ghz band " |
| "(802.11a):\n", geo->a_channels); |
| for (i = 0; i < geo->a_channels; i++) { |
| len += sprintf(&buf[len], "%d: BSS%s%s, %s.\n", |
| geo->a[i].channel, |
| geo->a[i].flags & LIBIPW_CH_RADAR_DETECT ? |
| " (radar spectrum)" : "", |
| ((geo->a[i].flags & LIBIPW_CH_NO_IBSS) || |
| (geo->a[i].flags & LIBIPW_CH_RADAR_DETECT)) |
| ? "" : ", IBSS", |
| geo->a[i].flags & LIBIPW_CH_PASSIVE_ONLY ? |
| "passive only" : "active/passive"); |
| } |
| |
| return len; |
| } |
| |
| static DEVICE_ATTR(channels, S_IRUSR, show_channels, NULL); |
| |
| static void notify_wx_assoc_event(struct ipw_priv *priv) |
| { |
| union iwreq_data wrqu; |
| wrqu.ap_addr.sa_family = ARPHRD_ETHER; |
| if (priv->status & STATUS_ASSOCIATED) |
| memcpy(wrqu.ap_addr.sa_data, priv->bssid, ETH_ALEN); |
| else |
| eth_zero_addr(wrqu.ap_addr.sa_data); |
| wireless_send_event(priv->net_dev, SIOCGIWAP, &wrqu, NULL); |
| } |
| |
| static void ipw_irq_tasklet(struct ipw_priv *priv) |
| { |
| u32 inta, inta_mask, handled = 0; |
| unsigned long flags; |
| int rc = 0; |
| |
| spin_lock_irqsave(&priv->irq_lock, flags); |
| |
| inta = ipw_read32(priv, IPW_INTA_RW); |
| inta_mask = ipw_read32(priv, IPW_INTA_MASK_R); |
| |
| if (inta == 0xFFFFFFFF) { |
| /* Hardware disappeared */ |
| IPW_WARNING("TASKLET INTA == 0xFFFFFFFF\n"); |
| /* Only handle the cached INTA values */ |
| inta = 0; |
| } |
| inta &= (IPW_INTA_MASK_ALL & inta_mask); |
| |
| /* Add any cached INTA values that need to be handled */ |
| inta |= priv->isr_inta; |
| |
| spin_unlock_irqrestore(&priv->irq_lock, flags); |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| |
| /* handle all the justifications for the interrupt */ |
| if (inta & IPW_INTA_BIT_RX_TRANSFER) { |
| ipw_rx(priv); |
| handled |= IPW_INTA_BIT_RX_TRANSFER; |
| } |
| |
| if (inta & IPW_INTA_BIT_TX_CMD_QUEUE) { |
| IPW_DEBUG_HC("Command completed.\n"); |
| rc = ipw_queue_tx_reclaim(priv, &priv->txq_cmd, -1); |
| priv->status &= ~STATUS_HCMD_ACTIVE; |
| wake_up_interruptible(&priv->wait_command_queue); |
| handled |= IPW_INTA_BIT_TX_CMD_QUEUE; |
| } |
| |
| if (inta & IPW_INTA_BIT_TX_QUEUE_1) { |
| IPW_DEBUG_TX("TX_QUEUE_1\n"); |
| rc = ipw_queue_tx_reclaim(priv, &priv->txq[0], 0); |
| handled |= IPW_INTA_BIT_TX_QUEUE_1; |
| } |
| |
| if (inta & IPW_INTA_BIT_TX_QUEUE_2) { |
| IPW_DEBUG_TX("TX_QUEUE_2\n"); |
| rc = ipw_queue_tx_reclaim(priv, &priv->txq[1], 1); |
| handled |= IPW_INTA_BIT_TX_QUEUE_2; |
| } |
| |
| if (inta & IPW_INTA_BIT_TX_QUEUE_3) { |
| IPW_DEBUG_TX("TX_QUEUE_3\n"); |
| rc = ipw_queue_tx_reclaim(priv, &priv->txq[2], 2); |
| handled |= IPW_INTA_BIT_TX_QUEUE_3; |
| } |
| |
| if (inta & IPW_INTA_BIT_TX_QUEUE_4) { |
| IPW_DEBUG_TX("TX_QUEUE_4\n"); |
| rc = ipw_queue_tx_reclaim(priv, &priv->txq[3], 3); |
| handled |= IPW_INTA_BIT_TX_QUEUE_4; |
| } |
| |
| if (inta & IPW_INTA_BIT_STATUS_CHANGE) { |
| IPW_WARNING("STATUS_CHANGE\n"); |
| handled |= IPW_INTA_BIT_STATUS_CHANGE; |
| } |
| |
| if (inta & IPW_INTA_BIT_BEACON_PERIOD_EXPIRED) { |
| IPW_WARNING("TX_PERIOD_EXPIRED\n"); |
| handled |= IPW_INTA_BIT_BEACON_PERIOD_EXPIRED; |
| } |
| |
| if (inta & IPW_INTA_BIT_SLAVE_MODE_HOST_CMD_DONE) { |
| IPW_WARNING("HOST_CMD_DONE\n"); |
| handled |= IPW_INTA_BIT_SLAVE_MODE_HOST_CMD_DONE; |
| } |
| |
| if (inta & IPW_INTA_BIT_FW_INITIALIZATION_DONE) { |
| IPW_WARNING("FW_INITIALIZATION_DONE\n"); |
| handled |= IPW_INTA_BIT_FW_INITIALIZATION_DONE; |
| } |
| |
| if (inta & IPW_INTA_BIT_FW_CARD_DISABLE_PHY_OFF_DONE) { |
| IPW_WARNING("PHY_OFF_DONE\n"); |
| handled |= IPW_INTA_BIT_FW_CARD_DISABLE_PHY_OFF_DONE; |
| } |
| |
| if (inta & IPW_INTA_BIT_RF_KILL_DONE) { |
| IPW_DEBUG_RF_KILL("RF_KILL_DONE\n"); |
| priv->status |= STATUS_RF_KILL_HW; |
| wiphy_rfkill_set_hw_state(priv->ieee->wdev.wiphy, true); |
| wake_up_interruptible(&priv->wait_command_queue); |
| priv->status &= ~(STATUS_ASSOCIATED | STATUS_ASSOCIATING); |
| cancel_delayed_work(&priv->request_scan); |
| cancel_delayed_work(&priv->request_direct_scan); |
| cancel_delayed_work(&priv->request_passive_scan); |
| cancel_delayed_work(&priv->scan_event); |
| schedule_work(&priv->link_down); |
| schedule_delayed_work(&priv->rf_kill, 2 * HZ); |
| handled |= IPW_INTA_BIT_RF_KILL_DONE; |
| } |
| |
| if (inta & IPW_INTA_BIT_FATAL_ERROR) { |
| IPW_WARNING("Firmware error detected. Restarting.\n"); |
| if (priv->error) { |
| IPW_DEBUG_FW("Sysfs 'error' log already exists.\n"); |
| if (ipw_debug_level & IPW_DL_FW_ERRORS) { |
| struct ipw_fw_error *error = |
| ipw_alloc_error_log(priv); |
| ipw_dump_error_log(priv, error); |
| kfree(error); |
| } |
| } else { |
| priv->error = ipw_alloc_error_log(priv); |
| if (priv->error) |
| IPW_DEBUG_FW("Sysfs 'error' log captured.\n"); |
| else |
| IPW_DEBUG_FW("Error allocating sysfs 'error' " |
| "log.\n"); |
| if (ipw_debug_level & IPW_DL_FW_ERRORS) |
| ipw_dump_error_log(priv, priv->error); |
| } |
| |
| /* XXX: If hardware encryption is for WPA/WPA2, |
| * we have to notify the supplicant. */ |
| if (priv->ieee->sec.encrypt) { |
| priv->status &= ~STATUS_ASSOCIATED; |
| notify_wx_assoc_event(priv); |
| } |
| |
| /* Keep the restart process from trying to send host |
| * commands by clearing the INIT status bit */ |
| priv->status &= ~STATUS_INIT; |
| |
| /* Cancel currently queued command. */ |
| priv->status &= ~STATUS_HCMD_ACTIVE; |
| wake_up_interruptible(&priv->wait_command_queue); |
| |
| schedule_work(&priv->adapter_restart); |
| handled |= IPW_INTA_BIT_FATAL_ERROR; |
| } |
| |
| if (inta & IPW_INTA_BIT_PARITY_ERROR) { |
| IPW_ERROR("Parity error\n"); |
| handled |= IPW_INTA_BIT_PARITY_ERROR; |
| } |
| |
| if (handled != inta) { |
| IPW_ERROR("Unhandled INTA bits 0x%08x\n", inta & ~handled); |
| } |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| /* enable all interrupts */ |
| ipw_enable_interrupts(priv); |
| } |
| |
| #define IPW_CMD(x) case IPW_CMD_ ## x : return #x |
| static char *get_cmd_string(u8 cmd) |
| { |
| switch (cmd) { |
| IPW_CMD(HOST_COMPLETE); |
| IPW_CMD(POWER_DOWN); |
| IPW_CMD(SYSTEM_CONFIG); |
| IPW_CMD(MULTICAST_ADDRESS); |
| IPW_CMD(SSID); |
| IPW_CMD(ADAPTER_ADDRESS); |
| IPW_CMD(PORT_TYPE); |
| IPW_CMD(RTS_THRESHOLD); |
| IPW_CMD(FRAG_THRESHOLD); |
| IPW_CMD(POWER_MODE); |
| IPW_CMD(WEP_KEY); |
| IPW_CMD(TGI_TX_KEY); |
| IPW_CMD(SCAN_REQUEST); |
| IPW_CMD(SCAN_REQUEST_EXT); |
| IPW_CMD(ASSOCIATE); |
| IPW_CMD(SUPPORTED_RATES); |
| IPW_CMD(SCAN_ABORT); |
| IPW_CMD(TX_FLUSH); |
| IPW_CMD(QOS_PARAMETERS); |
| IPW_CMD(DINO_CONFIG); |
| IPW_CMD(RSN_CAPABILITIES); |
| IPW_CMD(RX_KEY); |
| IPW_CMD(CARD_DISABLE); |
| IPW_CMD(SEED_NUMBER); |
| IPW_CMD(TX_POWER); |
| IPW_CMD(COUNTRY_INFO); |
| IPW_CMD(AIRONET_INFO); |
| IPW_CMD(AP_TX_POWER); |
| IPW_CMD(CCKM_INFO); |
| IPW_CMD(CCX_VER_INFO); |
| IPW_CMD(SET_CALIBRATION); |
| IPW_CMD(SENSITIVITY_CALIB); |
| IPW_CMD(RETRY_LIMIT); |
| IPW_CMD(IPW_PRE_POWER_DOWN); |
| IPW_CMD(VAP_BEACON_TEMPLATE); |
| IPW_CMD(VAP_DTIM_PERIOD); |
| IPW_CMD(EXT_SUPPORTED_RATES); |
| IPW_CMD(VAP_LOCAL_TX_PWR_CONSTRAINT); |
| IPW_CMD(VAP_QUIET_INTERVALS); |
| IPW_CMD(VAP_CHANNEL_SWITCH); |
| IPW_CMD(VAP_MANDATORY_CHANNELS); |
| IPW_CMD(VAP_CELL_PWR_LIMIT); |
| IPW_CMD(VAP_CF_PARAM_SET); |
| IPW_CMD(VAP_SET_BEACONING_STATE); |
| IPW_CMD(MEASUREMENT); |
| IPW_CMD(POWER_CAPABILITY); |
| IPW_CMD(SUPPORTED_CHANNELS); |
| IPW_CMD(TPC_REPORT); |
| IPW_CMD(WME_INFO); |
| IPW_CMD(PRODUCTION_COMMAND); |
| default: |
| return "UNKNOWN"; |
| } |
| } |
| |
| #define HOST_COMPLETE_TIMEOUT HZ |
| |
| static int __ipw_send_cmd(struct ipw_priv *priv, struct host_cmd *cmd) |
| { |
| int rc = 0; |
| unsigned long flags; |
| unsigned long now, end; |
| |
| spin_lock_irqsave(&priv->lock, flags); |
| if (priv->status & STATUS_HCMD_ACTIVE) { |
| IPW_ERROR("Failed to send %s: Already sending a command.\n", |
| get_cmd_string(cmd->cmd)); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| return -EAGAIN; |
| } |
| |
| priv->status |= STATUS_HCMD_ACTIVE; |
| |
| if (priv->cmdlog) { |
| priv->cmdlog[priv->cmdlog_pos].jiffies = jiffies; |
| priv->cmdlog[priv->cmdlog_pos].cmd.cmd = cmd->cmd; |
| priv->cmdlog[priv->cmdlog_pos].cmd.len = cmd->len; |
| memcpy(priv->cmdlog[priv->cmdlog_pos].cmd.param, cmd->param, |
| cmd->len); |
| priv->cmdlog[priv->cmdlog_pos].retcode = -1; |
| } |
| |
| IPW_DEBUG_HC("%s command (#%d) %d bytes: 0x%08X\n", |
| get_cmd_string(cmd->cmd), cmd->cmd, cmd->len, |
| priv->status); |
| |
| #ifndef DEBUG_CMD_WEP_KEY |
| if (cmd->cmd == IPW_CMD_WEP_KEY) |
| IPW_DEBUG_HC("WEP_KEY command masked out for secure.\n"); |
| else |
| #endif |
| printk_buf(IPW_DL_HOST_COMMAND, (u8 *) cmd->param, cmd->len); |
| |
| rc = ipw_queue_tx_hcmd(priv, cmd->cmd, cmd->param, cmd->len, 0); |
| if (rc) { |
| priv->status &= ~STATUS_HCMD_ACTIVE; |
| IPW_ERROR("Failed to send %s: Reason %d\n", |
| get_cmd_string(cmd->cmd), rc); |
| spin_unlock_irqrestore(&priv->lock, flags); |
| goto exit; |
| } |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| now = jiffies; |
| end = now + HOST_COMPLETE_TIMEOUT; |
| again: |
| rc = wait_event_interruptible_timeout(priv->wait_command_queue, |
| !(priv-> |
| status & STATUS_HCMD_ACTIVE), |
| end - now); |
| if (rc < 0) { |
| now = jiffies; |
| if (time_before(now, end)) |
| goto again; |
| rc = 0; |
| } |
| |
| if (rc == 0) { |
| spin_lock_irqsave(&priv->lock, flags); |
| if (priv->status & STATUS_HCMD_ACTIVE) { |
| IPW_ERROR("Failed to send %s: Command timed out.\n", |
| get_cmd_string(cmd->cmd)); |
| priv->status &= ~STATUS_HCMD_ACTIVE; |
| spin_unlock_irqrestore(&priv->lock, flags); |
| rc = -EIO; |
| goto exit; |
| } |
| spin_unlock_irqrestore(&priv->lock, flags); |
| } else |
| rc = 0; |
| |
| if (priv->status & STATUS_RF_KILL_HW) { |
| IPW_ERROR("Failed to send %s: Aborted due to RF kill switch.\n", |
| get_cmd_string(cmd->cmd)); |
| rc = -EIO; |
| goto exit; |
| } |
| |
| exit: |
| if (priv->cmdlog) { |
| priv->cmdlog[priv->cmdlog_pos++].retcode = rc; |
| priv->cmdlog_pos %= priv->cmdlog_len; |
| } |
| return rc; |
| } |
| |
| static int ipw_send_cmd_simple(struct ipw_priv *priv, u8 command) |
| { |
| struct host_cmd cmd = { |
| .cmd = command, |
| }; |
| |
| return __ipw_send_cmd(priv, &cmd); |
| } |
| |
| static int ipw_send_cmd_pdu(struct ipw_priv *priv, u8 command, u8 len, |
| void *data) |
| { |
| struct host_cmd cmd = { |
| .cmd = command, |
| .len = len, |
| .param = data, |
| }; |
| |
| return __ipw_send_cmd(priv, &cmd); |
| } |
| |
| static int ipw_send_host_complete(struct ipw_priv *priv) |
| { |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_simple(priv, IPW_CMD_HOST_COMPLETE); |
| } |
| |
| static int ipw_send_system_config(struct ipw_priv *priv) |
| { |
| return ipw_send_cmd_pdu(priv, IPW_CMD_SYSTEM_CONFIG, |
| sizeof(priv->sys_config), |
| &priv->sys_config); |
| } |
| |
| static int ipw_send_ssid(struct ipw_priv *priv, u8 * ssid, int len) |
| { |
| if (!priv || !ssid) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_SSID, min(len, IW_ESSID_MAX_SIZE), |
| ssid); |
| } |
| |
| static int ipw_send_adapter_address(struct ipw_priv *priv, u8 * mac) |
| { |
| if (!priv || !mac) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| IPW_DEBUG_INFO("%s: Setting MAC to %pM\n", |
| priv->net_dev->name, mac); |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_ADAPTER_ADDRESS, ETH_ALEN, mac); |
| } |
| |
| static void ipw_adapter_restart(void *adapter) |
| { |
| struct ipw_priv *priv = adapter; |
| |
| if (priv->status & STATUS_RF_KILL_MASK) |
| return; |
| |
| ipw_down(priv); |
| |
| if (priv->assoc_network && |
| (priv->assoc_network->capability & WLAN_CAPABILITY_IBSS)) |
| ipw_remove_current_network(priv); |
| |
| if (ipw_up(priv)) { |
| IPW_ERROR("Failed to up device\n"); |
| return; |
| } |
| } |
| |
| static void ipw_bg_adapter_restart(struct work_struct *work) |
| { |
| struct ipw_priv *priv = |
| container_of(work, struct ipw_priv, adapter_restart); |
| mutex_lock(&priv->mutex); |
| ipw_adapter_restart(priv); |
| mutex_unlock(&priv->mutex); |
| } |
| |
| static void ipw_abort_scan(struct ipw_priv *priv); |
| |
| #define IPW_SCAN_CHECK_WATCHDOG (5 * HZ) |
| |
| static void ipw_scan_check(void *data) |
| { |
| struct ipw_priv *priv = data; |
| |
| if (priv->status & STATUS_SCAN_ABORTING) { |
| IPW_DEBUG_SCAN("Scan completion watchdog resetting " |
| "adapter after (%dms).\n", |
| jiffies_to_msecs(IPW_SCAN_CHECK_WATCHDOG)); |
| schedule_work(&priv->adapter_restart); |
| } else if (priv->status & STATUS_SCANNING) { |
| IPW_DEBUG_SCAN("Scan completion watchdog aborting scan " |
| "after (%dms).\n", |
| jiffies_to_msecs(IPW_SCAN_CHECK_WATCHDOG)); |
| ipw_abort_scan(priv); |
| schedule_delayed_work(&priv->scan_check, HZ); |
| } |
| } |
| |
| static void ipw_bg_scan_check(struct work_struct *work) |
| { |
| struct ipw_priv *priv = |
| container_of(work, struct ipw_priv, scan_check.work); |
| mutex_lock(&priv->mutex); |
| ipw_scan_check(priv); |
| mutex_unlock(&priv->mutex); |
| } |
| |
| static int ipw_send_scan_request_ext(struct ipw_priv *priv, |
| struct ipw_scan_request_ext *request) |
| { |
| return ipw_send_cmd_pdu(priv, IPW_CMD_SCAN_REQUEST_EXT, |
| sizeof(*request), request); |
| } |
| |
| static int ipw_send_scan_abort(struct ipw_priv *priv) |
| { |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_simple(priv, IPW_CMD_SCAN_ABORT); |
| } |
| |
| static int ipw_set_sensitivity(struct ipw_priv *priv, u16 sens) |
| { |
| struct ipw_sensitivity_calib calib = { |
| .beacon_rssi_raw = cpu_to_le16(sens), |
| }; |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_SENSITIVITY_CALIB, sizeof(calib), |
| &calib); |
| } |
| |
| static int ipw_send_associate(struct ipw_priv *priv, |
| struct ipw_associate *associate) |
| { |
| if (!priv || !associate) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_ASSOCIATE, sizeof(*associate), |
| associate); |
| } |
| |
| static int ipw_send_supported_rates(struct ipw_priv *priv, |
| struct ipw_supported_rates *rates) |
| { |
| if (!priv || !rates) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_SUPPORTED_RATES, sizeof(*rates), |
| rates); |
| } |
| |
| static int ipw_set_random_seed(struct ipw_priv *priv) |
| { |
| u32 val; |
| |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| get_random_bytes(&val, sizeof(val)); |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_SEED_NUMBER, sizeof(val), &val); |
| } |
| |
| static int ipw_send_card_disable(struct ipw_priv *priv, u32 phy_off) |
| { |
| __le32 v = cpu_to_le32(phy_off); |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_CARD_DISABLE, sizeof(v), &v); |
| } |
| |
| static int ipw_send_tx_power(struct ipw_priv *priv, struct ipw_tx_power *power) |
| { |
| if (!priv || !power) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_TX_POWER, sizeof(*power), power); |
| } |
| |
| static int ipw_set_tx_power(struct ipw_priv *priv) |
| { |
| const struct libipw_geo *geo = libipw_get_geo(priv->ieee); |
| struct ipw_tx_power tx_power; |
| s8 max_power; |
| int i; |
| |
| memset(&tx_power, 0, sizeof(tx_power)); |
| |
| /* configure device for 'G' band */ |
| tx_power.ieee_mode = IPW_G_MODE; |
| tx_power.num_channels = geo->bg_channels; |
| for (i = 0; i < geo->bg_channels; i++) { |
| max_power = geo->bg[i].max_power; |
| tx_power.channels_tx_power[i].channel_number = |
| geo->bg[i].channel; |
| tx_power.channels_tx_power[i].tx_power = max_power ? |
| min(max_power, priv->tx_power) : priv->tx_power; |
| } |
| if (ipw_send_tx_power(priv, &tx_power)) |
| return -EIO; |
| |
| /* configure device to also handle 'B' band */ |
| tx_power.ieee_mode = IPW_B_MODE; |
| if (ipw_send_tx_power(priv, &tx_power)) |
| return -EIO; |
| |
| /* configure device to also handle 'A' band */ |
| if (priv->ieee->abg_true) { |
| tx_power.ieee_mode = IPW_A_MODE; |
| tx_power.num_channels = geo->a_channels; |
| for (i = 0; i < tx_power.num_channels; i++) { |
| max_power = geo->a[i].max_power; |
| tx_power.channels_tx_power[i].channel_number = |
| geo->a[i].channel; |
| tx_power.channels_tx_power[i].tx_power = max_power ? |
| min(max_power, priv->tx_power) : priv->tx_power; |
| } |
| if (ipw_send_tx_power(priv, &tx_power)) |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int ipw_send_rts_threshold(struct ipw_priv *priv, u16 rts) |
| { |
| struct ipw_rts_threshold rts_threshold = { |
| .rts_threshold = cpu_to_le16(rts), |
| }; |
| |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_RTS_THRESHOLD, |
| sizeof(rts_threshold), &rts_threshold); |
| } |
| |
| static int ipw_send_frag_threshold(struct ipw_priv *priv, u16 frag) |
| { |
| struct ipw_frag_threshold frag_threshold = { |
| .frag_threshold = cpu_to_le16(frag), |
| }; |
| |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_FRAG_THRESHOLD, |
| sizeof(frag_threshold), &frag_threshold); |
| } |
| |
| static int ipw_send_power_mode(struct ipw_priv *priv, u32 mode) |
| { |
| __le32 param; |
| |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| /* If on battery, set to 3, if AC set to CAM, else user |
| * level */ |
| switch (mode) { |
| case IPW_POWER_BATTERY: |
| param = cpu_to_le32(IPW_POWER_INDEX_3); |
| break; |
| case IPW_POWER_AC: |
| param = cpu_to_le32(IPW_POWER_MODE_CAM); |
| break; |
| default: |
| param = cpu_to_le32(mode); |
| break; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_POWER_MODE, sizeof(param), |
| ¶m); |
| } |
| |
| static int ipw_send_retry_limit(struct ipw_priv *priv, u8 slimit, u8 llimit) |
| { |
| struct ipw_retry_limit retry_limit = { |
| .short_retry_limit = slimit, |
| .long_retry_limit = llimit |
| }; |
| |
| if (!priv) { |
| IPW_ERROR("Invalid args\n"); |
| return -1; |
| } |
| |
| return ipw_send_cmd_pdu(priv, IPW_CMD_RETRY_LIMIT, sizeof(retry_limit), |
| &retry_limit); |
| } |
| |
| /* |
| * The IPW device contains a Microwire compatible EEPROM that stores |
| * various data like the MAC address. Usually the firmware has exclusive |
| * access to the eeprom, but during device initialization (before the |
| * device driver has sent the HostComplete command to the firmware) the |
| * device driver has read access to the EEPROM by way of indirect addressing |
| * through a couple of memory mapped registers. |
| * |
| * The following is a simplified implementation for pulling data out of the |
| * the eeprom, along with some helper functions to find information in |
| * the per device private data's copy of the eeprom. |
| * |
| * NOTE: To better understand how these functions work (i.e what is a chip |
| * select and why do have to keep driving the eeprom clock?), read |
| * just about any data sheet for a Microwire compatible EEPROM. |
| */ |
| |
| /* write a 32 bit value into the indirect accessor register */ |
| static inline void eeprom_write_reg(struct ipw_priv *p, u32 data) |
| { |
| ipw_write_reg32(p, FW_MEM_REG_EEPROM_ACCESS, data); |
| |
| /* the eeprom requires some time to complete the operation */ |
| udelay(p->eeprom_delay); |
| } |
| |
| /* perform a chip select operation */ |
| static void eeprom_cs(struct ipw_priv *priv) |
| { |
| eeprom_write_reg(priv, 0); |
| eeprom_write_reg(priv, EEPROM_BIT_CS); |
| eeprom_write_reg(priv, EEPROM_BIT_CS | EEPROM_BIT_SK); |
| eeprom_write_reg(priv, EEPROM_BIT_CS); |
| } |
| |
| /* perform a chip select operation */ |
| static void eeprom_disable_cs(struct ipw_priv *priv) |
| { |
| eeprom_write_reg(priv, EEPROM_BIT_CS); |
| eeprom_write_reg(priv, 0); |
| eeprom_write_reg(priv, EEPROM_BIT_SK); |
| } |
| |
| /* push a single bit down to the eeprom */ |
| static inline void eeprom_write_bit(struct ipw_priv *p, u8 bit) |
| { |
| int d = (bit ? EEPROM_BIT_DI : 0); |
| eeprom_write_reg(p, EEPROM_BIT_CS | d); |
| eeprom_write_reg(p, EEPROM_BIT_CS | d | EEPROM_BIT_SK); |
| } |
| |
| /* push an opcode followed by an address down to the eeprom */ |
| static void eeprom_op(struct ipw_priv *priv, u8 op, u8 addr) |
| { |
| int i; |
| |
| eeprom_cs(priv); |
| eeprom_write_bit(priv, 1); |
| eeprom_write_bit(priv, op & 2); |
| eeprom_write_bit(priv, op & 1); |
| for (i = 7; i >= 0; i--) { |
| eeprom_write_bit(priv, addr & (1 << i)); |
| } |
| } |
| |
| /* pull 16 bits off the eeprom, one bit at a time */ |
| static u16 eeprom_read_u16(struct ipw_priv *priv, u8 addr) |
| { |
| int i; |
| u16 r = 0; |
| |
| /* Send READ Opcode */ |
| eeprom_op(priv, EEPROM_CMD_READ, addr); |
| |
| /* Send dummy bit */ |
| eeprom_write_reg(priv, EEPROM_BIT_CS); |
| |
| /* Read the byte off the eeprom one bit at a time */ |
| for (i = 0; i < 16; i++) { |
| u32 data = 0; |
| eeprom_write_reg(priv, EEPROM_BIT_CS | EEPROM_BIT_SK); |
| eeprom_write_reg(priv, EEPROM_BIT_CS); |
| data = ipw_read_reg32(priv, FW_MEM_REG_EEPROM_ACCESS); |
| r = (r << 1) | ((data & EEPROM_BIT_DO) ? 1 : 0); |
| } |
| |
| /* Send another dummy bit */ |
| eeprom_write_reg(priv, 0); |
| eeprom_disable_cs(priv); |
| |
| return r; |
| } |
| |
| /* helper function for pulling the mac address out of the private */ |
| /* data's copy of the eeprom data */ |
| static void eeprom_parse_mac(struct ipw_priv *priv, u8 * mac) |
| { |
| memcpy(mac, &priv->eeprom[EEPROM_MAC_ADDRESS], ETH_ALEN); |
| } |
| |
| static void ipw_read_eeprom(struct ipw_priv *priv) |
| { |
| int i; |
| __le16 *eeprom = (__le16 *) priv->eeprom; |
| |
| IPW_DEBUG_TRACE(">>\n"); |
| |
| /* read entire contents of eeprom into private buffer */ |
| for (i = 0; i < 128; i++) |
| eeprom[i] = cpu_to_le16(eeprom_read_u16(priv, (u8) i)); |
| |
| IPW_DEBUG_TRACE("<<\n"); |
| } |
| |
| /* |
| * Either the device driver (i.e. the host) or the firmware can |
| * load eeprom data into the designated region in SRAM. If neither |
| * happens then the FW will shutdown with a fatal error. |
| * |
| * In order to signal the FW to load the EEPROM, the EEPROM_LOAD_DISABLE |
| * bit needs region of shared SRAM needs to be non-zero. |
| */ |
| static void ipw_eeprom_init_sram(struct ipw_priv *priv) |
| { |
| int i; |
| |
| IPW_DEBUG_TRACE(">>\n"); |
| |
| /* |
| If the data looks correct, then copy it to our private |
| copy. Otherwise let the firmware know to perform the operation |
| on its own. |
| */ |
| if (priv->eeprom[EEPROM_VERSION] != 0) { |
| IPW_DEBUG_INFO("Writing EEPROM data into SRAM\n"); |
| |
| /* write the eeprom data to sram */ |
| for (i = 0; i < IPW_EEPROM_IMAGE_SIZE; i++) |
| ipw_write8(priv, IPW_EEPROM_DATA + i, priv->eeprom[i]); |
| |
| /* Do not load eeprom data on fatal error or suspend */ |
| ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 0); |
| } else { |
| IPW_DEBUG_INFO("Enabling FW initializationg of SRAM\n"); |
| |
| /* Load eeprom data on fatal error or suspend */ |
| ipw_write32(priv, IPW_EEPROM_LOAD_DISABLE, 1); |
| } |
| |
| IPW_DEBUG_TRACE("<<\n"); |
| } |
| |
| static void ipw_zero_memory(struct ipw_priv *priv, u32 start, u32 count) |
| { |
| count >>= 2; |
| if (!count) |
| return; |
| _ipw_write32(priv, IPW_AUTOINC_ADDR, start); |
| while (count--) |
| _ipw_write32(priv, IPW_AUTOINC_DATA, 0); |
| } |
| |
| static inline void ipw_fw_dma_reset_command_blocks(struct ipw_priv *priv) |
| { |
| ipw_zero_memory(priv, IPW_SHARED_SRAM_DMA_CONTROL, |
| CB_NUMBER_OF_ELEMENTS_SMALL * |
| sizeof(struct command_block)); |
| } |
| |
| static int ipw_fw_dma_enable(struct ipw_priv *priv) |
| { /* start dma engine but no transfers yet */ |
| |
| IPW_DEBUG_FW(">> :\n"); |
| |
| /* Start the dma */ |
| ipw_fw_dma_reset_command_blocks(priv); |
| |
| /* Write CB base address */ |
| ipw_write_reg32(priv, IPW_DMA_I_CB_BASE, IPW_SHARED_SRAM_DMA_CONTROL); |
| |
| IPW_DEBUG_FW("<< :\n"); |
| return 0; |
| } |
| |
| static void ipw_fw_dma_abort(struct ipw_priv *priv) |
| { |
| u32 control = 0; |
| |
| IPW_DEBUG_FW(">> :\n"); |
| |
| /* set the Stop and Abort bit */ |
| control = DMA_CONTROL_SMALL_CB_CONST_VALUE | DMA_CB_STOP_AND_ABORT; |
| ipw_write_reg32(priv, IPW_DMA_I_DMA_CONTROL, control); |
| priv->sram_desc.last_cb_index = 0; |
| |
| IPW_DEBUG_FW("<<\n"); |
| } |
| |
| static int ipw_fw_dma_write_command_block(struct ipw_priv *priv, int index, |
| struct command_block *cb) |
| { |
| u32 address = |
| IPW_SHARED_SRAM_DMA_CONTROL + |
| (sizeof(struct command_block) * index); |
| IPW_DEBUG_FW(">> :\n"); |
| |
| ipw_write_indirect(priv, address, (u8 *) cb, |
| (int)sizeof(struct command_block)); |
| |
| IPW_DEBUG_FW("<< :\n"); |
| return 0; |
| |
| } |
| |
| static int ipw_fw_dma_kick(struct ipw_priv *priv) |
| { |
| u32 control = 0; |
| u32 index = 0; |
| |
| IPW_DEBUG_FW(">> :\n"); |
| |
| for (index = 0; index < priv->sram_desc.last_cb_index; index++) |
| ipw_fw_dma_write_command_block(priv, index, |
| &priv->sram_desc.cb_list[index]); |
| |
| /* Enable the DMA in the CSR register */ |
| ipw_clear_bit(priv, IPW_RESET_REG, |
| IPW_RESET_REG_MASTER_DISABLED | |
| IPW_RESET_REG_STOP_MASTER); |
| |
| /* Set the Start bit. */ |
| control = DMA_CONTROL_SMALL_CB_CONST_VALUE | DMA_CB_START; |
| ipw_write_reg32(priv, IPW_DMA_I_DMA_CONTROL, control); |
| |
| IPW_DEBUG_FW("<< :\n"); |
| return 0; |
| } |
| |
| static void ipw_fw_dma_dump_command_block(struct ipw_priv *priv) |
| { |
| u32 address; |
| u32 register_value = 0; |
| u32 cb_fields_address = 0; |
| |
| IPW_DEBUG_FW(">> :\n"); |
| address = ipw_read_reg32(priv, IPW_DMA_I_CURRENT_CB); |
| IPW_DEBUG_FW_INFO("Current CB is 0x%x\n", address); |
| |
| /* Read the DMA Controlor register */ |
| register_value = ipw_read_reg32(priv, IPW_DMA_I_DMA_CONTROL); |
| IPW_DEBUG_FW_INFO("IPW_DMA_I_DMA_CONTROL is 0x%x\n", register_value); |
| |
| /* Print the CB values */ |
| cb_fields_address = address; |
| register_value = ipw_read_reg32(priv, cb_fields_address); |
| IPW_DEBUG_FW_INFO("Current CB Control Field is 0x%x\n", register_value); |
| |
| cb_fields_address += sizeof(u32); |
| register_value = ipw_read_reg32(priv, cb_fields_address); |
| IPW_DEBUG_FW_INFO("Current CB Source Field is 0x%x\n", register_value); |
| |
| cb_fields_address += sizeof(u32); |
| register_value = ipw_read_reg32(priv, cb_fields_address); |
| IPW_DEBUG_FW_INFO("Current CB Destination Field is 0x%x\n", |
| register_value); |
| |
| cb_fields_address += sizeof(u32); |
| register_value = ipw_read_reg32(priv, cb_fields_address); |
| IPW_DEBUG_FW_INFO("Current CB Status Field is 0x%x\n", register_value); |
| |
| IPW_DEBUG_FW(">> :\n"); |
| } |
| |
| static int ipw_fw_dma_command_block_index(struct ipw_priv *priv) |
| { |
| u32 current_cb_address = 0; |
| u32 current_cb_index = 0; |
| |
| IPW_DEBUG_FW("<< :\n"); |
| current_cb_address = ipw_read_reg32(priv, IPW_DMA_I_CURRENT_CB); |
| |
| current_cb_index = (current_cb_address - IPW_SHARED_SRAM_DMA_CONTROL) / |
| sizeof(struct command_block); |
| |
| IPW_DEBUG_FW_INFO("Current CB index 0x%x address = 0x%X\n", |
| current_cb_index, current_cb_address); |
| |
| IPW_DEBUG_FW(">> :\n"); |
| return current_cb_index; |
| |
| } |
| |
| static int ipw_fw_dma_add_command_block(struct ipw_priv *priv, |
| u32 src_address, |
| u32 dest_address, |
| u32 length, |
| int interrupt_enabled, int is_last) |
| { |
| |
| u32 control = CB_VALID | CB_SRC_LE | CB_DEST_LE | CB_SRC_AUTOINC | |
| CB_SRC_IO_GATED | CB_DEST_AUTOINC | CB_SRC_SIZE_LONG | |
| CB_DEST_SIZE_LONG; |
| struct command_block *cb; |
| u32 last_cb_element = 0; |
| |
| IPW_DEBUG_FW_INFO("src_address=0x%x dest_address=0x%x length=0x%x\n", |
| src_address, dest_address, length); |
| |
| if (priv->sram_desc.last_cb_index >= CB_NUMBER_OF_ELEMENTS_SMALL) |
| return -1; |
| |
| last_cb_element = priv->sram_desc.last_cb_index; |
| cb = &priv->sram_desc.cb_list[last_cb_element]; |
| priv->sram_desc.last_cb_index++; |
| |
| /* Calculate the new CB control word */ |
| if (interrupt_enabled) |
| control |= CB_INT_ENABLED; |
| |
| if (is_last) |
| control |= CB_LAST_VALID; |
| |
| control |= length; |
| |
| /* Calculate the CB Element's checksum value */ |
| cb->status = control ^ src_address ^ dest_address; |
| |
| /* Copy the Source and Destination addresses */ |
| cb->dest_addr = dest_address; |
| cb->source_addr = src_address; |
| |
| /* Copy the Control Word last */ |
| cb->control = control; |
| |
| return 0; |
| } |
| |
| static int ipw_fw_dma_add_buffer(struct ipw_priv *priv, dma_addr_t *src_address, |
| int nr, u32 dest_address, u32 len) |
| { |
| int ret, i; |
| u32 size; |
| |
| IPW_DEBUG_FW(">>\n"); |
| IPW_DEBUG_FW_INFO("nr=%d dest_address=0x%x len=0x%x\n", |
| nr, dest_address, len); |
| |
| for (i = 0; i < nr; i++) { |
| size = min_t(u32, len - i * CB_MAX_LENGTH, CB_MAX_LENGTH); |
| ret = ipw_fw_dma_add_command_block(priv, src_address[i], |
| dest_address + |
| i * CB_MAX_LENGTH, size, |
| 0, 0); |
| if (ret) { |
| IPW_DEBUG_FW_INFO(": Failed\n"); |
| return -1; |
| } else |
| IPW_DEBUG_FW_INFO(": Added new cb\n"); |
| } |
| |
| IPW_DEBUG_FW("<<\n"); |
| return 0; |
| } |
| |
| static int ipw_fw_dma_wait(struct ipw_priv *priv) |
| { |
| u32 current_index = 0, previous_index; |
| u32 watchdog = 0; |
| |
| IPW_DEBUG_FW(">> :\n"); |
| |
| current_index = ipw_fw_dma_command_block_index(priv); |
| IPW_DEBUG_FW_INFO("sram_desc.last_cb_index:0x%08X\n", |
| (int)priv->sram_desc.last_cb_index); |
| |
| while (current_index < priv->sram_desc.last_cb_index) { |
| udelay(50); |
| previous_index = current_index; |
| current_index = ipw_fw_dma_command_block_index(priv); |
| |
| if (previous_index < current_index) { |
| watchdog = 0; |
| continue; |
| } |
| if (++watchdog > 400) { |
| IPW_DEBUG_FW_INFO("Timeout\n"); |
| ipw_fw_dma_dump_command_block(priv); |
| ipw_fw_dma_abort(priv); |
| return -1; |
| } |
| } |
| |
| ipw_fw_dma_abort(priv); |
| |
| /*Disable the DMA in the CSR register */ |
| ipw_set_bit(priv, IPW_RESET_REG, |
| IPW_RESET_REG_MASTER_DISABLED | IPW_RESET_REG_STOP_MASTER); |
| |
| IPW_DEBUG_FW("<< dmaWaitSync\n"); |
| return 0; |
| } |
| |
| static void ipw_remove_current_network(struct ipw_priv *priv) |
| { |
| struct list_head *element, *safe; |
| struct libipw_network *network = NULL; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&priv->ieee->lock, flags); |
| list_for_each_safe(element, safe, &priv->ieee->network_list) { |
| network = list_entry(element, struct libipw_network, list); |
| if (ether_addr_equal
|