/****************************************************************************** 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 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 Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 ******************************************************************************/ #include #include #include #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) && (PAGE_SIZE - len); 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 memset(wrqu.ap_addr.sa_data, 0, ETH_ALEN); 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); sched