blob: aaf6b77d2686c745f87258afa6ce51ba2555cc9a [file] [log] [blame]
/******************************************************************************
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 CPTCFG_IPW2200_DEBUG
#define VD "d"
#else
#define VD
#endif
#ifdef CPTCFG_IPW2200_MONITOR
#define VM "m"
#else
#define VM
#endif
#ifdef CPTCFG_IPW2200_PROMISCUOUS
#define VP "p"
#else
#define VP
#endif
#ifdef CPTCFG_IPW2200_RADIOTAP
#define VR "r"
#else
#define VR
#endif
#ifdef CPTCFG_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 CPTCFG_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 CPTCFG_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 CPTCFG_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 /* CPTCFG_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 CPTCFG_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", &reg);
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", &reg);
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),
&param);
}
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);
}