Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  ipw2200.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/******************************************************************************

  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


  Contact Information:
  Intel Linux Wireless <ilw@linux.intel.com>
  Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497

******************************************************************************/


#include <linux/sched.h>
#include <linux/slab.h>
#include <net/cfg80211-wext.h>
#include "ipw2200.h"
#include "ipw.h"


#ifndef KBUILD_EXTMOD
#define VK "k"
#else
#define VK
#endif

#ifdef CONFIG_IPW2200_DEBUG
#define VD "d"
#else
#define VD
#endif

#ifdef CONFIG_IPW2200_MONITOR
#define VM "m"
#else
#define VM
#endif

#ifdef CONFIG_IPW2200_PROMISCUOUS
#define VP "p"
#else
#define VP
#endif

#ifdef CONFIG_IPW2200_RADIOTAP
#define VR "r"
#else
#define VR
#endif

#ifdef CONFIG_IPW2200_QOS
#define VQ "q"
#else
#define VQ
#endif

#define IPW2200_VERSION "1.2.2" VK VD VM VP VR VQ
#define DRV_DESCRIPTION "Intel(R) PRO/Wireless 2200/2915 Network Driver"
#define DRV_COPYRIGHT "Copyright(c) 2003-2006 Intel Corporation"
#define DRV_VERSION     IPW2200_VERSION

#define ETH_P_80211_STATS (ETH_P_80211_RAW + 1)

MODULE_DESCRIPTION(DRV_DESCRIPTION);
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR(DRV_COPYRIGHT);
MODULE_LICENSE("GPL");
MODULE_FIRMWARE("ipw2200-ibss.fw");
#ifdef CONFIG_IPW2200_MONITOR
MODULE_FIRMWARE("ipw2200-sniffer.fw");
#endif
MODULE_FIRMWARE("ipw2200-bss.fw");

static int cmdlog = 0;
static int debug = 0;
static int default_channel = 0;
static int network_mode = 0;

static u32 ipw_debug_level;
static int associate;
static int auto_create = 1;
static int led_support = 1;
static int disable = 0;
static int bt_coexist = 0;
static int hwcrypto = 0;
static int roaming = 1;
static const char ipw_modes[] = {
 'a''b''g''?'
};
static int antenna = CFG_SYS_ANTENNA_BOTH;

#ifdef CONFIG_IPW2200_PROMISCUOUS
static int rtap_iface = 0;     /* def: 0 -- do not create rtap interface */
#endif

static struct ieee80211_rate ipw2200_rates[] = {
 { .bitrate = 10 },
 { .bitrate = 20, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
 { .bitrate = 55, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
 { .bitrate = 110, .flags = IEEE80211_RATE_SHORT_PREAMBLE },
 { .bitrate = 60 },
 { .bitrate = 90 },
 { .bitrate = 120 },
 { .bitrate = 180 },
 { .bitrate = 240 },
 { .bitrate = 360 },
 { .bitrate = 480 },
 { .bitrate = 540 }
};

#define ipw2200_a_rates  (ipw2200_rates + 4)
#define ipw2200_num_a_rates 8
#define ipw2200_bg_rates (ipw2200_rates + 0)
#define ipw2200_num_bg_rates 12

/* Ugly macro to convert literal channel numbers into their mhz equivalents
 * There are certianly some conditions that will break this (like feeding it '30')
 * but they shouldn't arise since nothing talks on channel 30. */

#define ieee80211chan2mhz(x) \
 (((x) <= 14) ? \
 (((x) == 14) ? 2484 : ((x) * 5) + 2407) : \
 ((x) + 1000) * 5)

#ifdef CONFIG_IPW2200_QOS
static int qos_enable = 0;
static int qos_burst_enable = 0;
static int qos_no_ack_mask = 0;
static int burst_duration_CCK = 0;
static int burst_duration_OFDM = 0;

static struct libipw_qos_parameters def_qos_parameters_OFDM = {
 {QOS_TX0_CW_MIN_OFDM, QOS_TX1_CW_MIN_OFDM, QOS_TX2_CW_MIN_OFDM,
  QOS_TX3_CW_MIN_OFDM},
 {QOS_TX0_CW_MAX_OFDM, QOS_TX1_CW_MAX_OFDM, QOS_TX2_CW_MAX_OFDM,
  QOS_TX3_CW_MAX_OFDM},
 {QOS_TX0_AIFS, QOS_TX1_AIFS, QOS_TX2_AIFS, QOS_TX3_AIFS},
 {QOS_TX0_ACM, QOS_TX1_ACM, QOS_TX2_ACM, QOS_TX3_ACM},
 {QOS_TX0_TXOP_LIMIT_OFDM, QOS_TX1_TXOP_LIMIT_OFDM,
  QOS_TX2_TXOP_LIMIT_OFDM, QOS_TX3_TXOP_LIMIT_OFDM}
};

static struct libipw_qos_parameters def_qos_parameters_CCK = {
 {QOS_TX0_CW_MIN_CCK, QOS_TX1_CW_MIN_CCK, QOS_TX2_CW_MIN_CCK,
  QOS_TX3_CW_MIN_CCK},
 {QOS_TX0_CW_MAX_CCK, QOS_TX1_CW_MAX_CCK, QOS_TX2_CW_MAX_CCK,
  QOS_TX3_CW_MAX_CCK},
 {QOS_TX0_AIFS, QOS_TX1_AIFS, QOS_TX2_AIFS, QOS_TX3_AIFS},
 {QOS_TX0_ACM, QOS_TX1_ACM, QOS_TX2_ACM, QOS_TX3_ACM},
 {QOS_TX0_TXOP_LIMIT_CCK, QOS_TX1_TXOP_LIMIT_CCK, QOS_TX2_TXOP_LIMIT_CCK,
  QOS_TX3_TXOP_LIMIT_CCK}
};

static struct libipw_qos_parameters def_parameters_OFDM = {
 {DEF_TX0_CW_MIN_OFDM, DEF_TX1_CW_MIN_OFDM, DEF_TX2_CW_MIN_OFDM,
  DEF_TX3_CW_MIN_OFDM},
 {DEF_TX0_CW_MAX_OFDM, DEF_TX1_CW_MAX_OFDM, DEF_TX2_CW_MAX_OFDM,
  DEF_TX3_CW_MAX_OFDM},
 {DEF_TX0_AIFS, DEF_TX1_AIFS, DEF_TX2_AIFS, DEF_TX3_AIFS},
 {DEF_TX0_ACM, DEF_TX1_ACM, DEF_TX2_ACM, DEF_TX3_ACM},
 {DEF_TX0_TXOP_LIMIT_OFDM, DEF_TX1_TXOP_LIMIT_OFDM,
  DEF_TX2_TXOP_LIMIT_OFDM, DEF_TX3_TXOP_LIMIT_OFDM}
};

static struct libipw_qos_parameters def_parameters_CCK = {
 {DEF_TX0_CW_MIN_CCK, DEF_TX1_CW_MIN_CCK, DEF_TX2_CW_MIN_CCK,
  DEF_TX3_CW_MIN_CCK},
 {DEF_TX0_CW_MAX_CCK, DEF_TX1_CW_MAX_CCK, DEF_TX2_CW_MAX_CCK,
  DEF_TX3_CW_MAX_CCK},
 {DEF_TX0_AIFS, DEF_TX1_AIFS, DEF_TX2_AIFS, DEF_TX3_AIFS},
 {DEF_TX0_ACM, DEF_TX1_ACM, DEF_TX2_ACM, DEF_TX3_ACM},
 {DEF_TX0_TXOP_LIMIT_CCK, DEF_TX1_TXOP_LIMIT_CCK, DEF_TX2_TXOP_LIMIT_CCK,
  DEF_TX3_TXOP_LIMIT_CCK}
};

static u8 qos_oui[QOS_OUI_LEN] = { 0x00, 0x50, 0xF2 };

static int from_priority_to_tx_queue[] = {
 IPW_TX_QUEUE_1, IPW_TX_QUEUE_2, IPW_TX_QUEUE_2, IPW_TX_QUEUE_1,
 IPW_TX_QUEUE_3, IPW_TX_QUEUE_3, IPW_TX_QUEUE_4, IPW_TX_QUEUE_4
};

static u32 ipw_qos_get_burst_duration(struct ipw_priv *priv);

static int ipw_send_qos_params_command(struct ipw_priv *priv, struct libipw_qos_parameters
           *qos_param);
static int ipw_send_qos_info_command(struct ipw_priv *priv, struct libipw_qos_information_element
         *qos_param);
#endif    /* CONFIG_IPW2200_QOS */

static struct iw_statistics *ipw_get_wireless_stats(struct net_device *dev);
static void ipw_remove_current_network(struct ipw_priv *priv);
static void ipw_rx(struct ipw_priv *priv);
static int ipw_queue_tx_reclaim(struct ipw_priv *priv,
    struct clx2_tx_queue *txq, int qindex);
static int ipw_queue_reset(struct ipw_priv *priv);

static int ipw_queue_tx_hcmd(struct ipw_priv *priv, int hcmd, const 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 = scnprintf(buf, count, "%08X", ofs);

 for (l = 0, i = 0; i < 2; i++) {
  out += scnprintf(buf + out, count - out, " ");
  for (j = 0; j < 8 && l < len; j++, l++)
   out += scnprintf(buf + out, count - out, "%02X ",
     data[(i * 8 + j)]);
  for (; j < 8; j++)
   out += scnprintf(buf + out, count - out, " ");
 }

 out += scnprintf(buf + out, count - out, " ");
 for (l = 0, i = 0; i < 2; i++) {
  out += scnprintf(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 += scnprintf(buf + out, count - out, "%c", c);
  }

  for (; j < 8; j++)
   out += scnprintf(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); \
})

/* 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 debug_level_show(struct device_driver *d, char *buf)
{
 return sprintf(buf, "0x%08X\n", ipw_debug_level);
}

static ssize_t debug_level_store(struct device_driver *d, const char *buf,
     size_t count)
{
 unsigned long val;

 int result = kstrtoul(buf, 0, &val);

 if (result == -EINVAL)
  printk(KERN_INFO DRV_NAME
         ": %s is not in hex or decimal form.\n", buf);
 else if (result == -ERANGE)
  printk(KERN_INFO DRV_NAME
    ": %s has overflowed.\n", buf);
 else
  ipw_debug_level = val;

 return count;
}
static DRIVER_ATTR_RW(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(size_add(struct_size(error, elem, elem_len),
     array_size(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->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 event_log_show(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 += scnprintf(buf + len, PAGE_SIZE - len, "%08X", log_len);
 for (i = 0; i < log_len; i++)
  len += scnprintf(buf + len, PAGE_SIZE - len,
    "\n%08X%08X%08X",
    log[i].time, log[i].event, log[i].data);
 len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
 kfree(log);
 return len;
}

static DEVICE_ATTR_RO(event_log);

static ssize_t error_show(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 += scnprintf(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 += scnprintf(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 += scnprintf(buf + len, PAGE_SIZE - len,
   "\n%08X", priv->error->log_len);
 for (i = 0; i < priv->error->log_len; i++)
  len += scnprintf(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 += scnprintf(buf + len, PAGE_SIZE - len, "\n");
 return len;
}

static ssize_t error_store(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_RW(error);

static ssize_t cmd_log_show(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 +=
      scnprintf(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 += scnprintf(buf + len, PAGE_SIZE - len, "\n");
 }
 len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
 return len;
}

static DEVICE_ATTR_RO(cmd_log);

#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 rtap_iface_store(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 rtap_iface_show(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_ADMIN_RW(rtap_iface);

static ssize_t rtap_filter_store(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 rtap_filter_show(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_ADMIN_RW(rtap_filter);
#endif

static ssize_t scan_age_show(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 scan_age_store(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;

 IPW_DEBUG_INFO("enter\n");

 unsigned long val;
 int result = kstrtoul(buf, 0, &val);

 if (result == -EINVAL || result == -ERANGE) {
  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 count;
}

static DEVICE_ATTR_RW(scan_age);

static ssize_t led_show(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 led_store(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_RW(led);

static ssize_t status_show(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_RO(status);

static ssize_t cfg_show(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_RO(cfg);

static ssize_t nic_type_show(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_RO(nic_type);

static ssize_t ucode_version_show(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_RO(ucode_version);

static ssize_t rtc_show(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_RO(rtc);

/*
 * Add a device attribute to view/control the delay between eeprom
 * operations.
 */

static ssize_t eeprom_delay_show(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 eeprom_delay_store(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_RW(eeprom_delay);

static ssize_t command_event_reg_show(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 command_event_reg_store(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_RW(command_event_reg);

static ssize_t mem_gpio_reg_show(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 mem_gpio_reg_store(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_RW(mem_gpio_reg);

static ssize_t indirect_dword_show(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 indirect_dword_store(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_RW(indirect_dword);

static ssize_t indirect_byte_show(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 indirect_byte_store(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_RW(indirect_byte);

static ssize_t direct_dword_show(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 direct_dword_store(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_RW(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 rf_kill_show(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 rf_kill_store(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_RW(rf_kill);

static ssize_t speed_scan_show(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 speed_scan_store(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_RW(speed_scan);

static ssize_t net_stats_show(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 net_stats_store(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_RW(net_stats);

static ssize_t channels_show(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_ADMIN_RO(channels);

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 tasklet_struct *t)
{
 struct ipw_priv *priv = from_tasklet(priv, t, irq_tasklet);
 u32 inta, inta_mask, handled = 0;
 unsigned long flags;

 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");
  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");
  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");
  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");
  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");
  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,
       const 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, const 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;

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=96 H=95 G=95

¤ Dauer der Verarbeitung: 0.24 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge