/******************************************************************************
  
  Copyright(c) 2003 - 2004 Intel Corporation. All rights reserved.
  
  This program is free software; you can redistribute it and/or modify it 
  under the terms of the GNU General Public License as published by the Free 
  Software Foundation; either version 2 of the License, or (at your option) 
  any later version.
  
  This program is distributed in the hope that it will be useful, but WITHOUT 
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
  more details.
  
  You should have received a copy of the GNU General Public License along with
  this program; 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:
  James P. Ketrenos <ipw2100-admin@linux.intel.com>
  Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497

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

 Initial driver on which this is based was developed by Janusz Gorycki, 
 Maciej Urbaniak, and Maciej Sosnowski.


Theory of Operation

Tx - Commands and Data

Firmware and host share a circular queue of Transmit Buffer Descriptors (TBDs)
Each TBD contains a pointer to the physical (dma_addr_t) address of data being
sent to the firmware as well as the length of the data.

The host writes to the TBD queue at the WRITE index.  The WRITE index points
to the _next_ packet to be written and is advanced when after the TBD has been
filled.

The firmware pulls from the TBD queue at the READ index.  The READ index points
to the currently being read entry, and is advanced once the firmware is
done with a packet.

When data is sent to the firmware, the first TBD is used to indicate to the
firmware if a Command or Data is being sent.  If it is Command, all of the 
command information is contained within the physical address referred to by the
TBD.  If it is Data, the first TBD indicates the type of data packet, number
of fragments, etc.  The next TBD then referrs to the actual packet location.

The Tx flow cycle is as follows:

1) ipw2100_tx() is called by kernel with SKB to transmit
2) Packet is move from the tx_free_list and appended to the transmit pending 
   list (tx_pend_list)
3) work is scheduled to move pending packets into the shared circular queue.
4) when placing packet in the circular queue, the incoming SKB is DMA mapped
   to a physical address.  That address is entered into a TBD.  Two TBDs are
   filled out.  The first indicating a data packet, the second referring to the
   actual payload data.
5) the packet is removed from tx_pend_list and placed on the end of the 
   firmware pending list (fw_pend_list)
6) firmware is notified that the WRITE index has 
7) Once the firmware has processed the TBD, INTA is triggered.  
8) For each Tx interrupt received from the firmware, the READ index is checked
   to see which TBDs are done being processed.
9) For each TBD that has been processed, the ISR pulls the oldest packet 
   from the fw_pend_list.  
10)The packet structure contained in the fw_pend_list is then used
   to unmap the DMA address and to free the SKB originally passed to the driver
   from the kernel.
11)The packet structure is placed onto the tx_free_list

The above steps are the same for commands, only the msg_free_list/msg_pend_list
are used instead of tx_free_list/tx_pend_list

...

Critical Sections / Locking :

There are two locks utilized.  The first is the low level lock (priv->low_lock)
that protects the following:

- Access to the Tx/Rx queue lists via priv->low_lock. The lists are as follows:

  tx_free_list : Holds pre-allocated Tx buffers.
    TAIL modified in __ipw2100_tx_process()
    HEAD modified in ipw2100_tx()

  tx_pend_list : Holds used Tx buffers waiting to go into the TBD ring 
    TAIL modified ipw2100_tx()
    HEAD modified by X__ipw2100_tx_send_data()

  msg_free_list : Holds pre-allocated Msg (Command) buffers
    TAIL modified in __ipw2100_tx_process()
    HEAD modified in ipw2100_hw_send_command()

  msg_pend_list : Holds used Msg buffers waiting to go into the TBD ring
    TAIL modified in ipw2100_hw_send_command()
    HEAD modified in X__ipw2100_tx_send_commands()

  The flow of data on the TX side is as follows:

  MSG_FREE_LIST + COMMAND => MSG_PEND_LIST => TBD => MSG_FREE_LIST
  TX_FREE_LIST + DATA => TX_PEND_LIST => TBD => TX_FREE_LIST

  The methods that work on the TBD ring are protected via priv->low_lock.

- The internal data state of the device itself
- Access to the firmware read/write indexes for the BD queues
  and associated logic

...

TODO

- Deprecate the proc filesystem code (how to cleanly support 2.4 and 2.6 in
  one driver base?)  ethtool_ops ?
-   

...

Files:
  ipw2100_fw.c/h   Defines the interface and implementation for 
                   loading and unloading the firmware and microcode 
		   onto the NIC MAC.  The code in these files will be 
		   rewritten in the coming weeks to use a dynamic 
		   loading approach.

  ipw2100_hw.h     Inline functions for read/writing to the NIC.

  ipw2100_main.c/h This file - defines modules entry points, ISR, 
                   wireless extension, etc.

  ipw2100_wx.c/h   Defines wireless extension handlers.	


*/

#include <linux/802_11.h>
#include <linux/compiler.h>
#include <linux/config.h>
#include <linux/errno.h>
#include <linux/if_arp.h>
#include <linux/in6.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/ethtool.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/tcp.h>
#include <linux/types.h>
#include <linux/version.h>
#include <linux/time.h>
#include <asm/uaccess.h>


#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#include <linux/sched.h>
#endif

#ifndef IRQ_NONE
typedef void irqreturn_t;
#define IRQ_NONE 
#define IRQ_HANDLED 
#define IRQ_RETVAL(x)
#endif

#ifndef HAVE_FREE_NETDEV
#define free_netdev(x) kfree(x)
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
/* Some 2.4 kernels configured with different schedulers do not have the
 * __wake_up_sync function.  As such, we need to switch the 
 * wake_up_interruptible_sync's accordingly */
#  ifndef wake_up_sync
/* Some versions don't define wake_up_sync but do define 
 * wake_up_interruptible_sync.  We can't just check for
 * the later since sometimes it is defined as an actual function
 * and not a #define--in these cases, wake_up_sync is not defined */
#    ifdef wake_up_interruptible_sync
#      undef wake_up_interruptible_sync
#    endif
#  define wake_up_interruptible_sync wake_up_interruptible
#  endif
#endif

#define IPW2100_MAIN
#include "ipw2100.h"
#include "ipw2100_hw.h"
#include "ipw2100_fw.h"
#include "ipw2100_wx.h"
#include "ipwversion.h"

#define DRV_NAME	"ipw2100"
#define DRV_VERSION	IPW2100_VERSION
#define DRV_DESCRIPTION	"Intel(R) PRO/Wireless 2100 Network Driver"
#define DRV_COPYRIGHT	"Copyright(c) 2003-2004 Intel Corporation"

static int debug = -1;

MODULE_DESCRIPTION(DRV_DESCRIPTION);
MODULE_AUTHOR(DRV_COPYRIGHT);
MODULE_LICENSE("GPL");

static char *if_name = NULL;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)

MODULE_PARM(debug, "i");
MODULE_PARM(if_name, "s");

#else /* LINUX_VERSION_CODE < 2.6.0 */

#include <linux/moduleparam.h>
module_param(debug, int, -1);
module_param(if_name, charp, 0);

#endif /* LINUX_VERSION_CODE < 2.6.0 */

MODULE_PARM_DESC(debug, "debug level");
MODULE_PARM_DESC(if_name, "interface name (default 'eth%d')");

unsigned long ipw2100_debug_level = IPW2100_DL_UNINIT; /* Unset */

const char *port_type_str[] = { 
	"Managed", "Ad-Hoc", "Monitor"
};

#ifdef CONFIG_IPW2100_DEBUG
const char *command_types[] = {
	"undefined",
	"unused", /* HOST_ATTENTION */ 
	"HOST_COMPLETE",
	"unused", /* SLEEP */
	"unused", /* HOST_POWER_DOWN */
	"unused", 
	"SYSTEM_CONFIG",
	"unused", /* SET_IMR */
	"SSID",
	"MANDATORY_BSSID",
	"AUTHENTICATION_TYPE",
	"ADAPTER_ADDRESS",
	"PORT_TYPE",
	"INTERNATIONAL_MODE",
	"CHANNEL",
	"RTS_THRESHOLD",
	"FRAG_THRESHOLD",
	"POWER_MODE",
	"TX_RATES",
	"BASIC_TX_RATES",
	"WEP_KEY_INFO",
	"unused",
	"unused",
	"unused",
	"unused",
	"WEP_KEY_INDEX",
	"WEP_FLAGS",
	"ADD_MULTICAST",
	"CLEAR_ALL_MULTICAST",
	"BEACON_INTERVAL",
	"ATIM_WINDOW",
	"CLEAR_STATISTICS",
	"undefined",
	"undefined",
	"undefined",
	"undefined",
	"TX_POWER_INDEX",
	"undefined",
	"undefined",
	"undefined",
	"undefined",
	"undefined",
	"undefined",
	"BROADCAST_SCAN",
	"CARD_DISABLE",
	"PREFERRED_BSSID",
	"SET_SCAN_OPTIONS",
	"SCAN_DWELL_TIME",
	"SWEEP_TABLE",
	"AP_OR_STATION_TABLE",
	"GROUP_ORDINALS",
	"SHORT_RETRY_LIMIT",
	"LONG_RETRY_LIMIT",
	"unused", /* SAVE_CALIBRATION */
	"unused", /* RESTORE_CALIBRATION */
	"undefined",
	"undefined",
	"undefined",
	"HOST_PRE_POWER_DOWN",
	"unused", /* HOST_INTERRUPT_COALESCING */
	"undefined",
	"CARD_DISABLE_PHY_OFF",
	"MSDU_TX_RATES"
	"undefined",
	"undefined",
	"SET_STATION_STAT_BITS",
	"CLEAR_STATIONS_STAT_BITS",
	"LEAP_ROGUE_MODE",
	"SET_SECURITY_INFORMATION",
	"DISASSOCIATION_BSSID",
	"SET_WPA_ASS_IE"
};
#endif


/* Pre-decl until we get the code solid and then we can clean it up */
static void X__ipw2100_tx_send_commands(struct ipw2100_priv *priv);
static void X__ipw2100_tx_send_data(struct ipw2100_priv *priv);
static int ipw2100_adapter_setup(struct ipw2100_priv *priv);

static void ipw2100_queues_initialize(struct ipw2100_priv *priv);
static void ipw2100_queues_free(struct ipw2100_priv *priv);
static int ipw2100_queues_allocate(struct ipw2100_priv *priv);

static int ipw2100_proc_init(void);
static void ipw2100_proc_cleanup(void);
static void ipw2100_proc_dev_cleanup(struct ipw2100_priv *priv);
static int ipw2100_proc_dev_init(struct ipw2100_priv *priv);

static inline int ipw2100_hw_is_adapter_in_system(struct net_device *dev)
{
	return (dev->base_addr && 
		(readl(dev->base_addr + IPW_REG_DOA_DEBUG_AREA_START)
		 == IPW_DATA_DOA_DEBUG_VALUE));
}

int ipw2100_get_ordinal(struct ipw2100_priv *priv, u32 ord, void *val, 
			       u32 *len)
{
	struct ipw2100_ordinals *ordinals = &priv->ordinals;
	u32 addr;
	u32 field_info;
	u16 field_len;
	u16 field_count;
	u32 total_length;

	if (ordinals->table1_addr == 0) {
		printk(KERN_WARNING DRV_NAME ": attempt to use fw ordinals "
		       "before they have been loaded.\n");
		return -EINVAL;
	}

	if (IS_ORDINAL_TABLE_ONE(ordinals, ord)) {
		if (*len < IPW_ORD_TAB_1_ENTRY_SIZE) {
			*len = IPW_ORD_TAB_1_ENTRY_SIZE;

			printk(KERN_WARNING DRV_NAME 
			       ": ordinal buffer length too small, need %d\n",
			       IPW_ORD_TAB_1_ENTRY_SIZE);

			return -EINVAL;
		}

		read_nic_dword(priv->ndev, ordinals->table1_addr + (ord << 2), 
			       &addr);
		read_nic_dword(priv->ndev, addr, val);

		*len = IPW_ORD_TAB_1_ENTRY_SIZE;
		
		return 0;
	} 
	
	if (IS_ORDINAL_TABLE_TWO(ordinals, ord)) {

		ord -= IPW_START_ORD_TAB_2;
		
		/* get the address of statistic */
		read_nic_dword(priv->ndev, ordinals->table2_addr + (ord << 3), 
			       &addr);
		
		/* get the second DW of statistics ; 
		 * two 16-bit words - first is length, second is count */
		read_nic_dword(priv->ndev, 
			       ordinals->table2_addr + (ord << 3) + sizeof(u32),
			       &field_info);
		
		/* get each entry length */
		field_len = *((u16 *)&field_info);
		
		/* get number of entries */
		field_count = *(((u16 *)&field_info) + 1);
		
		/* abort if no enought memory */
		total_length = field_len * field_count;
		if (total_length > *len) {
			*len = total_length;
			return -EINVAL;
		}
		
		*len = total_length;
		if (!total_length)
			return 0;
		
		/* read the ordinal data from the SRAM */
		read_nic_memory(priv->ndev, addr, total_length, val);
		
		return 0;
	}

	printk(KERN_WARNING DRV_NAME ": ordinal %d neither in table 1 nor "
	       "in table 2\n", ord);

	return -EINVAL;
}

static int ipw2100_set_ordinal(struct ipw2100_priv *priv, u32 ord, u32 *val, 
			       u32 *len)
{
	struct ipw2100_ordinals *ordinals = &priv->ordinals;
	u32 addr;

	if (IS_ORDINAL_TABLE_ONE(ordinals, ord)) {
		if (*len != IPW_ORD_TAB_1_ENTRY_SIZE) {
			*len = IPW_ORD_TAB_1_ENTRY_SIZE;
			IPW2100_DEBUG_INFO("wrong size\n");
			return -EINVAL;
		}

		read_nic_dword(priv->ndev, ordinals->table1_addr + (ord << 2), 
			       &addr);

		write_nic_dword(priv->ndev, addr, *val);

		*len = IPW_ORD_TAB_1_ENTRY_SIZE;

		return 0;
	} 

	IPW2100_DEBUG_INFO("wrong table\n");
	if (IS_ORDINAL_TABLE_TWO(ordinals, ord)) 
		return -EINVAL;

	return -EINVAL;
}

/*
 * 1. check if message in progress, ret EBUSY if so
 * 2. issue command
 * 3. wait for command to complete; timeout in 3s
 */
static int ipw2100_hw_send_command(struct ipw2100_priv *priv,
				   struct host_command * cmd) 
{
	struct list_head *element;
	struct ipw2100_tx_packet *packet;
	unsigned long flags;
	int err = 0;

	IPW2100_DEBUG_INFO("enter - '%s' (%d)\n",
	       command_types[cmd->host_command],
	       cmd->host_command);
	
	spin_lock_irqsave(&priv->low_lock, flags);

	if (priv->fatal_error) {
		IPW2100_DEBUG_INFO("Attempt to send command while hardware "
				   "in fatal error condition.");
		err = -EIO;
		goto fail_unlock;
	}

	if (priv->stopped) {
		IPW2100_DEBUG_INFO("Attempt to send command while hardware "
				   "is not running.");
		err = -EIO;
		goto fail_unlock;
	}

	if (priv->message_sending) {
		IPW2100_DEBUG_INFO("Attempt to send command while hardware "
				   "another command is pending.");
		err = -EBUSY;
		goto fail_unlock;
	}

	if (list_empty(&priv->msg_free_list)) {
		IPW2100_DEBUG_INFO("no available msg buffers\n");
		goto fail_unlock;
	}

	priv->message_sending = 1;
	priv->messages_sent++;

	element = priv->msg_free_list.next;

	packet = list_entry(element, struct ipw2100_tx_packet, list);
	packet->jiffy_start = jiffies;

	/* initialize the firmware command packet */
	packet->cmd->host_command_reg = cmd->host_command;
	packet->cmd->host_command_reg1 = cmd->host_command1;
	packet->cmd->host_command_len_reg = cmd->host_command_length;
	packet->cmd->sequence = cmd->host_command_sequence;

	memcpy(packet->cmd->host_command_params_reg,
	       cmd->host_command_parameters,
	       sizeof(packet->cmd->host_command_params_reg));

	list_del(element);
	DEC_STAT(&priv->msg_free_stat);

	list_add_tail(element, &priv->msg_pend_list);
	INC_STAT(&priv->msg_pend_stat);

	X__ipw2100_tx_send_commands(priv);
	X__ipw2100_tx_send_data(priv);

	spin_unlock_irqrestore(&priv->low_lock, flags);

	/*
	 * We must wait for this command to complete before another
	 * command can be sent...  but if we wait more than 3 seconds
	 * then there is a problem.
	 */

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	err = interruptible_sleep_on_timeout(
		&priv->wait_command_queue, 3 * HZ);
#else
	err = wait_event_interruptible_timeout(
		priv->wait_command_queue, !priv->message_sending, 3 * HZ);
#endif
	if (err == 0) {
		printk(KERN_WARNING 
		       "%s: ipw2100_hw_send_command timed out.\n",
		       priv->ndev->name);
		priv->fatal_error = IPW2100_ERR_MSG_TIMEOUT;
		priv->message_sending = 0;
		return -EIO;
	}

	if (priv->fatal_error) {
		printk(KERN_WARNING "%s: firmware fatal error\n",
		       priv->ndev->name);
		return -EIO;
	}

	IPW2100_DEBUG_INFO("exit - command %d\n", cmd->host_command);

	/* !!!!! HACK TEST !!!!! 
	 * When lots of debug trace statements are enabled, the driver 
	 * doesn't seem to have as many firmware restart cycles...
	 * 
	 * As a test, we're sticking in a 1/100s delay here */
	set_current_state(TASK_UNINTERRUPTIBLE);
	schedule_timeout(HZ / 100);
	
	return 0;

 fail_unlock:
	spin_unlock_irqrestore(&priv->low_lock, flags);

	return err;
}


/*
 * Verify the values and data access of the hardware
 * No locks needed or used.  No functions called.
 */
static int ipw2100_verify(struct ipw2100_priv *priv)
{
	u32 data1, data2;
	u32 address;

	u32 val1 = 0x76543210;
	u32 val2 = 0xFEDCBA98;

	/* Domain 0 check - all values should be DOA_DEBUG */
	for (address = IPW_REG_DOA_DEBUG_AREA_START;
	     address < IPW_REG_DOA_DEBUG_AREA_END;
	     address += sizeof(u32)) {
		read_register(priv->ndev, address, &data1);
		if (data1 != IPW_DATA_DOA_DEBUG_VALUE)
			return -EIO;
	}

	/* Domain 1 check - use arbitrary read/write compare  */
	for (address = 0; address < 5; address++) {
		/* The memory area is not used now */
		write_register(priv->ndev, IPW_REG_DOMAIN_1_OFFSET + 0x32, 
			       val1);
		write_register(priv->ndev, IPW_REG_DOMAIN_1_OFFSET + 0x36, 
			       val2);
		read_register(priv->ndev, IPW_REG_DOMAIN_1_OFFSET + 0x32, 
			      &data1);
		read_register(priv->ndev, IPW_REG_DOMAIN_1_OFFSET + 0x36, 
			      &data2);
		if (val1 == data1 && val2 == data2)
			return 0;
	}

	return -EIO;
}

/*
 * 
 * Loop until the CARD_DISABLED bit is the same value as the
 * supplied parameter
 *
 * TODO: See if it would be more efficient to do a wait/wake
 *       cycle and have the completion event trigger the wakeup
 * 
 */
static int ipw2100_wait_for_card_state(struct ipw2100_priv *priv, int disabled)
{
	int i;
	u32 card_disabled;
	u32 len = sizeof(card_disabled);
	int err;
	
	for (i = 0; i <= IPW_CARD_DISABLE_COMPLETE_WAIT * 1000; i += 50) {
		err = ipw2100_get_ordinal(priv, IPW_ORD_CARD_DISABLED, 
					  &card_disabled, &len);
		if (err) {
			IPW2100_DEBUG_INFO("query failed.\n");
			break;
		}

		/* We'll break out if either the HW state says it is
		 * in the state we want, or if HOST_COMPLETE command
		 * finishes */
		if (card_disabled == disabled ||
		    priv->disabled == disabled) {
			priv->disabled = disabled;
			return 0;
		}

		udelay(50);
	}

	if (i >= IPW_CARD_DISABLE_COMPLETE_WAIT * 1000) {
		IPW2100_DEBUG_INFO("ipw2100_wait_for_card_state to %s state "
				   "timed out\n", 
				   disabled ? "DISABLED" : "ENABLED");
		return -EIO;
	}

	return 0;
}


/*********************************************************************
    Procedure   :   sw_reset_and_clock
    Purpose     :   Asserts s/w reset, asserts clock initialization 
                    and waits for clock stabilization
 ********************************************************************/
static int sw_reset_and_clock(struct ipw2100_priv *priv)
{
	int i;
	u32 r;

	// assert s/w reset
	write_register(priv->ndev, IPW_REG_RESET_REG, 
		       IPW_AUX_HOST_RESET_REG_SW_RESET);

	// wait for clock stabilization
	for (i = 0; i < 1000; i++) {
		udelay(IPW_WAIT_RESET_ARC_COMPLETE_DELAY);

		// check clock ready bit
		read_register(priv->ndev, IPW_REG_RESET_REG, &r);
		if (r & IPW_AUX_HOST_RESET_REG_PRINCETON_RESET)
			break;
	}

	if (i == 1000)
		return -EIO;	// TODO: better error value

	/* set "initialization complete" bit to move adapter to 
	 * D0 state */
	write_register(priv->ndev, IPW_REG_GP_CNTRL,
		       IPW_AUX_HOST_GP_CNTRL_BIT_INIT_DONE);

	/* wait for clock stabilization */
	for (i = 0; i < 10000; i++) {
		udelay(IPW_WAIT_CLOCK_STABILIZATION_DELAY * 4);

		/* check clock ready bit */
		read_register(priv->ndev, IPW_REG_GP_CNTRL, &r);
		if (r & IPW_AUX_HOST_GP_CNTRL_BIT_CLOCK_READY)
			break;
	}

	if (i == 10000)
		return -EIO;	/* TODO: better error value */

//#if CONFIG_IPW2100_D0ENABLED
	/* set D0 standby bit */
	read_register(priv->ndev, IPW_REG_GP_CNTRL, &r);
	write_register(priv->ndev, IPW_REG_GP_CNTRL,
		       r | IPW_AUX_HOST_GP_CNTRL_BIT_HOST_ALLOWS_STANDBY);
//#endif

	return 0;
}

/*********************************************************************
    Procedure   :   ipw2100_ipw2100_download_firmware
    Purpose     :   Initiaze adapter after power on.
                    The sequence is:
                    1. assert s/w reset first!
                    2. awake clocks & wait for clock stabilization
                    3. hold ARC (don't ask me why...)
                    4. load Dino ucode and reset/clock init again
                    5. zero-out shared mem
                    6. download f/w
 *******************************************************************/
static int ipw2100_download_firmware(struct ipw2100_priv *priv)
{
	u32 address;
	int err;

	/* Fetch the firmware and microcode */
	struct ipw2100_fw fw;
	
	if (priv->fatal_error) {
		printk(KERN_ERR "%s: ipw2100_download_firmware called after "
		       "fatal error %d.  Interface must be brought down.\n",
		       priv->ndev->name, priv->fatal_error);
		return -EINVAL;
	}

	err = ipw2100_get_firmware(priv, &fw);
	if (err) {
		printk(KERN_ERR "%s: ipw2100_get_firmware failed: %d\n",
		       priv->ndev->name, err);
		priv->fatal_error = IPW2100_ERR_FW_LOAD;
		return err;
	}
	priv->firmware_version = fw.version;

	/* s/w reset and clock stabilization */
	err = sw_reset_and_clock(priv);
	if (err) {
		printk(KERN_ERR "%s: sw_reset_and_clock failed: %d\n",
		       priv->ndev->name, err);
		goto fail;
	}

	err = ipw2100_verify(priv);
	if (err) {
		printk(KERN_ERR "%s: ipw2100_verify failed: %d\n",
		       priv->ndev->name, err);
		goto fail;
	}

	/* Hold ARC */
	write_nic_dword(priv->ndev,
			IPW_INTERNAL_REGISTER_HALT_AND_RESET,
			0x80000000);

	/* allow ARC to run */
	write_register(priv->ndev, IPW_REG_RESET_REG, 0);

	/* load microcode */
	err = ipw2100_ucode_download(priv, &fw);
	if (err) {
		printk(KERN_ERR "%s: Error loading microcode: %d\n",
		       priv->ndev->name, err);
		goto fail;
	}

	/* release ARC */
	write_nic_dword(priv->ndev,
			IPW_INTERNAL_REGISTER_HALT_AND_RESET,
			0x00000000);

	/* s/w reset and clock stabilization (again!!!) */
	err = sw_reset_and_clock(priv);
	if (err) {
		printk(KERN_ERR "%s: sw_reset_and_clock failed: %d\n",
		       priv->ndev->name, err);
		goto fail;
	}

	/* load f/w */
	err = ipw2100_fw_download(priv, &fw);
	if (err) {
		printk(KERN_ERR "%s: Error loading firmware: %d\n",
		       priv->ndev->name, err);
		goto fail;
	}
	
	/* free any storage allocated for firmware image */
	ipw2100_release_firmware(priv, &fw);

	/* zero out Domain 1 area indirectly (Si requirement) */
	for (address = IPW_HOST_FW_SHARED_AREA0; 
	     address < IPW_HOST_FW_SHARED_AREA0_END; address += 4) 
		write_nic_dword(priv->ndev, address, 0);
	for (address = IPW_HOST_FW_SHARED_AREA1; 
	     address < IPW_HOST_FW_SHARED_AREA1_END; address += 4)
		write_nic_dword(priv->ndev, address, 0);
	for (address = IPW_HOST_FW_SHARED_AREA2; 
	     address < IPW_HOST_FW_SHARED_AREA2_END; address += 4)
		write_nic_dword(priv->ndev, address, 0);
	for (address = IPW_HOST_FW_SHARED_AREA3; 
	     address < IPW_HOST_FW_SHARED_AREA3_END; address += 4)
		write_nic_dword(priv->ndev, address, 0);
	for (address = IPW_HOST_FW_INTERRUPT_AREA; 
	     address < IPW_HOST_FW_INTERRUPT_AREA_END; address += 4)
		write_nic_dword(priv->ndev, address, 0);

	return 0;

 fail:
	ipw2100_release_firmware(priv, &fw);
	return err;
}

static inline void ipw2100_hw_disable_interrupt(struct ipw2100_priv *priv)
{
	write_register(priv->ndev, IPW_REG_INTA_MASK, 0x0);
}

static inline void ipw2100_hw_enable_interrupt(struct ipw2100_priv *priv)
{
	write_register(priv->ndev, IPW_REG_INTA_MASK, IPW_INTERRUPT_MASK);
}

static void ipw2100_initialize_ordinals(struct ipw2100_priv *priv)
{
	struct ipw2100_ordinals *ord = &priv->ordinals;

	IPW2100_DEBUG_INFO("enter\n");

	read_register(priv->ndev, IPW_MEM_HOST_SHARED_ORDINALS_TABLE_1,
		      &ord->table1_addr);

	read_register(priv->ndev, IPW_MEM_HOST_SHARED_ORDINALS_TABLE_2,
		      &ord->table2_addr);

	read_nic_dword(priv->ndev, ord->table1_addr, &ord->table1_size);
	read_nic_dword(priv->ndev, ord->table2_addr, &ord->table2_size);

	ord->table2_size &= 0x0000FFFF;

	IPW2100_DEBUG_INFO("table 1 size: %d\n", ord->table1_size);
	IPW2100_DEBUG_INFO("table 2 size: %d\n", ord->table2_size);
	IPW2100_DEBUG_INFO("exit\n");
}

static inline void ipw2100_hw_set_gpio(struct ipw2100_priv *priv)
{
	u32 reg = 0;
	/*
	 * Set GPIO 3 writable by FW; GPIO 1 writable 
	 * by driver and enable clock
	 */
	reg = (IPW_BIT_GPIO_GPIO3_MASK | IPW_BIT_GPIO_GPIO1_ENABLE |
	       IPW_BIT_GPIO_LED_OFF);
	write_register(priv->ndev, IPW_REG_GPIO, reg);
}

static inline int ipw2100_get_rf_switch(struct ipw2100_priv *priv)
{
#define MAX_RF_KILL_CHECKS 5
#define RF_KILL_CHECK_DELAY 40
#define RF_KILL_CHECK_THRESHOLD 3
	
	unsigned short value = 0;
	u32 reg = 0;
	int i;
	
	if (!(priv->hw_features & HW_FEATURE_RFKILL)) 
		return 0;

	for (i = 0; i < MAX_RF_KILL_CHECKS; i++) {
		udelay(RF_KILL_CHECK_DELAY);
		read_register(priv->ndev, IPW_REG_GPIO, &reg);
		value = (value << 1) | ((reg & IPW_BIT_GPIO_RF_KILL) ? 0 : 1);
	}

	return (value == 0);
}

static int ipw2100_get_hw_features(struct ipw2100_priv *priv)
{
	u32 addr, len;
	u32 val;

	/* 
	 * EEPROM_SRAM_DB_START_ADDRESS using ordinal in ordinal table 1
	 */
	len = sizeof(addr);
	if (ipw2100_get_ordinal(
		    priv, IPW_ORD_EEPROM_SRAM_DB_BLOCK_START_ADDRESS,
		    &addr, &len)) {
		printk(KERN_INFO "failed querying ordinals at line %d\n", 
		       __LINE__);
		return -EIO;
	}

	IPW2100_DEBUG_INFO("EEPROM address: %08X\n", addr);
 
	/*
	 * EEPROM version is the byte at offset 0xfd in firmware
	 * We read 4 bytes, then shift out the byte we actually want */ 
	read_nic_dword(priv->ndev, addr + 0xFC, &val);
	priv->eeprom_version = (val >> 24) & 0xFF;
	IPW2100_DEBUG_INFO("EEPROM version: %d\n", priv->eeprom_version);

        /*
	 *  HW RF Kill enable is bit 0 in byte at offset 0x21 in firmware
	 *
	 *  notice that the EEPROM bit is reverse polarity, i.e.
	 *     bit = 0  signifies HW RF kill switch is supported
	 *     bit = 1  signifies HW RF kill switch is NOT supported
	 */
	read_nic_dword(priv->ndev, addr + 0x20, &val);
	if (!((val >> 24) & 0x01))
		priv->hw_features |= HW_FEATURE_RFKILL;

	IPW2100_DEBUG_INFO("HW RF Kill: %ssupported.\n",
			   (priv->hw_features & HW_FEATURE_RFKILL) ?
			   "" : "not ");

	return 0;
}

static int ipw2100_beacons_allocate(struct ipw2100_priv *priv)
{
	if (priv->beacons)
		return 0;

	IPW2100_DEBUG_SCAN("Allocating %d beacon slots.\n", 
			   MAX_BEACON_COUNT);

	priv->beacons = (struct ipw2100_beacon *)kmalloc(
		MAX_BEACON_COUNT * sizeof(struct ipw2100_beacon),
		GFP_KERNEL);
	if (!priv->beacons) {
		printk(KERN_WARNING DRV_NAME 
		       ": Out of memory allocating beacons\n");
		return -ENOMEM;
	}

	memset(priv->beacons, 0, 
	       MAX_BEACON_COUNT * sizeof(struct ipw2100_beacon));

	return 0;
}

static void ipw2100_beacons_free(struct ipw2100_priv *priv)
{
	if (!priv->beacons)
		return;
	kfree(priv->beacons);
	priv->beacons = NULL;
}

static void ipw2100_beacons_initialize(struct ipw2100_priv *priv)
{
	int i;

	INIT_LIST_HEAD(&priv->beacon_free_list);
	INIT_LIST_HEAD(&priv->beacon_list);
	for (i = 0; i < MAX_BEACON_COUNT; i++) 
		list_add_tail(&priv->beacons[i].list, &priv->beacon_free_list);
}

/*
 * Start firmware execution after power on and intialization
 * The sequence is:
 *  1. Release ARC
 *  2. Wait for f/w initialization completes;
 */
static int ipw2100_start_adapter(struct ipw2100_priv *priv)
{
#define IPW_WAIT_FW_INIT_COMPLETE_DELAY (40 * HZ / 1000)
	int i;
	u32 r;

	IPW2100_DEBUG_INFO("enter\n");

	if (!priv->stopped)
		return 0;

	/*
	 * Initialize the hw - drive adapter to DO state by setting 
	 * init_done bit. Wait for clk_ready bit and Download 
	 * fw & dino ucode
	 */
	if (ipw2100_download_firmware(priv)) {
		printk(KERN_ERR "%s: Failed to power on the adapter.\n",
		       priv->ndev->name);
		return -EIO;
	}
	
	/* Clear the Tx, Rx and Msg queues and the r/w indexes
	 * in the firmware RBD and TBD ring queue */
	ipw2100_queues_initialize(priv);
	ipw2100_beacons_initialize(priv);

	ipw2100_hw_set_gpio(priv);

	/* Release ARC - clear reset bit */
	write_register(priv->ndev, IPW_REG_RESET_REG, 0);

	/* wait for f/w intialization complete */
	IPW2100_DEBUG_FW("Waiting for f/w initialization to complete...\n");
	i = 5000;
	do {
  		set_current_state(TASK_UNINTERRUPTIBLE);
		schedule_timeout(IPW_WAIT_FW_INIT_COMPLETE_DELAY);

		read_register(priv->ndev, IPW_REG_INTA, &r);

		/* check "init done" bit */
		if (r & IPW2100_INTA_FW_INIT_DONE) {
			/* reset "init done" bit */
			write_register(priv->ndev, IPW_REG_INTA,
				       IPW2100_INTA_FW_INIT_DONE);
			break;
		}

		/* check error conditions : we check these after the firmware
		 * check so that if there is an error, the interrupt handler
		 * will see it and the adapter will be reset */
		if (r & 
		    (IPW2100_INTA_FATAL_ERROR | IPW2100_INTA_PARITY_ERROR)) {
			/* clear error conditions */
			write_register(priv->ndev, IPW_REG_INTA,
				       IPW2100_INTA_FATAL_ERROR |
				       IPW2100_INTA_PARITY_ERROR);
		}


	} while (i--);

	IPW2100_DEBUG_FW("f/w initialization complete: %s\n",
			 i ? "SUCCESS" : "FAILED");

	/* Start firmware hang check timer */
	if (priv->stop_hang_check) {
		priv->stop_hang_check = 0;
		priv->hang_check_timer.expires = jiffies + 2 * HZ;
		add_timer(&priv->hang_check_timer);
	}
	
	if (!i) {
		printk(KERN_WARNING "%s: Firmware did not initialize.\n",
		       priv->ndev->name);
		return -EIO;
	}

	/* allow firmware to write to GPIO1 & GPIO3 */
	read_register(priv->ndev, IPW_REG_GPIO, &r);

	r |= (IPW_BIT_GPIO_GPIO1_MASK | IPW_BIT_GPIO_GPIO3_MASK);

	write_register(priv->ndev, IPW_REG_GPIO, r);

	priv->stopped = 0;

	IPW2100_DEBUG_INFO("exit\n");

	return 0;
}

static inline void ipw2100_reset_sw(struct ipw2100_priv *priv)
{
	/* Forget whatever we know about any associated SSIDs... */
	memset(priv->ctx->ssid, 0, sizeof(priv->ctx->ssid));
	priv->ctx->port_type = BSS;
	priv->ctx->channel = 1;
	
	ipw2100_hw_set_gpio(priv);

	/* SW Reset the adapter */
	write_register(priv->ndev, IPW_REG_RESET_REG, 0);
}

static inline void ipw2100_reset_fatalerror(struct ipw2100_priv *priv)
{
	if (!priv->fatal_error)
		return;

	priv->fatal_errors[priv->fatal_index++] = priv->fatal_error;
	priv->fatal_index %= IPW2100_ERROR_QUEUE;
	priv->fatal_error = 0;
}


/* NOTE: Our interrupt is disabled when this method is called */
static int ipw2100_power_cycle_adapter(struct ipw2100_priv *priv)
{
	u32 reg;
	int i;

	IPW2100_DEBUG_INFO("Power cycling the hardware.\n");

	ipw2100_hw_set_gpio(priv);

	/* Step 1. Stop Master Assert */
	write_register(priv->ndev, IPW_REG_RESET_REG, 
		       IPW_AUX_HOST_RESET_REG_STOP_MASTER);    
	
	/* Step 2. Wait for stop Master Assert 
	 *         (not more then 50us, otherwise ret error */
	i = 5;
	do {
		udelay(IPW_WAIT_RESET_MASTER_ASSERT_COMPLETE_DELAY);
		read_register(priv->ndev, IPW_REG_RESET_REG, &reg);
		
		if (reg & IPW_AUX_HOST_RESET_REG_MASTER_DISABLED)
			break;
	}  while(i--);

	priv->reset_pending = 0;

	if (!i) {
		IPW2100_DEBUG_INFO("exit - waited too long for master assert "
		       "stop\n");
		return -EIO;
	}

	write_register(priv->ndev, IPW_REG_RESET_REG, 
		       IPW_AUX_HOST_RESET_REG_SW_RESET);

	
	/* Reset any fatal_error conditions */
	ipw2100_reset_fatalerror(priv);

	/* At this point, the adapter is now stopped and disabled */
	priv->stopped = 1;
	priv->disabled = 1;

	return 0;
}

/*
 * Send the CARD_DISABLE_PHY_OFF comamnd to the card to disable it
 *
 * After disabling, if the card was associated, a STATUS_ASSN_LOST will be sent.
 *
 * STATUS_CARD_DISABLE_NOTIFICATION will be sent regardless of
 * if STATUS_ASSN_LOST is sent.
 */
static int ipw2100_hw_phy_off(struct ipw2100_priv *priv)
{

#define HW_PHY_OFF_LOOP_DELAY (HZ / 5000)

	struct host_command cmd = {
		.host_command = CARD_DISABLE_PHY_OFF,
		.host_command_sequence = 0,
		.host_command_length = 0,
	};
	int err, i;
	u32 val1, val2;

	if (priv->phy_off == 1)
		return 0;

	IPW2100_DEBUG_FW_COMMAND("CARD_DISABLE_PHY_OFF\n");

	/* Turn off the radio */
	err = ipw2100_hw_send_command(priv, &cmd);
	if (err) 
		return err;

	for (i = 0; i < 2500; i++) {
		read_nic_dword(priv->ndev, IPW2100_CONTROL_REG, &val1);
		read_nic_dword(priv->ndev, IPW2100_COMMAND, &val2);

		if ((val1 & IPW2100_CONTROL_PHY_OFF) &&
		    (val2 & IPW2100_COMMAND_PHY_OFF)) {
			priv->phy_off = 1;
			return 0;
		}
		
		set_current_state(TASK_UNINTERRUPTIBLE);
		schedule_timeout(HW_PHY_OFF_LOOP_DELAY);
	}

	return -EIO;
}


static int ipw2100_enable_adapter(struct ipw2100_priv *priv)
{	
	struct host_command cmd = {
		.host_command = HOST_COMPLETE,
		.host_command_sequence = 0,
		.host_command_length = 0
	};
	int err;

	IPW2100_DEBUG_FW_COMMAND("HOST_COMPLETE\n");
	
	if (!priv->disabled) 
		return 0;

	err = ipw2100_hw_send_command(priv, &cmd);
	if (err) {
		IPW2100_DEBUG_INFO("Failed to send HOST_COMPLETE command\n");
		return err;
	}

	err = ipw2100_wait_for_card_state(priv, STATE_ENABLED);
	if (err) {
		printk(KERN_WARNING 
		       "%s: card not responding to init command.\n",
		       priv->ndev->name);
		return err;
	}

	IPW2100_DEBUG_INFO("TODO: implement scan state machine\n");

	return 0;
}

static int ipw2100_hw_stop_adapter(struct ipw2100_priv *priv)
{
#define HW_POWER_DOWN_DELAY (HZ / 10)

	struct host_command cmd = {
		.host_command = HOST_PRE_POWER_DOWN,
		.host_command_sequence = 0,
		.host_command_length = 0,
	};
	int err, i;
	u32 reg;

	if (priv->stopped)
		return 0;

	/* We can only shut down the card if the firmware is operational.  So,
	 * if we haven't reset since a fatal_error, then we can not send the
	 * shutdown commands. */
	if (priv->fatal_error) {
		/* First, make sure the adapter is enabled so that the PHY_OFF 
		 * command can shut it down */
		ipw2100_enable_adapter(priv);

		err = ipw2100_hw_phy_off(priv);
		if (err) 
			IPW2100_DEBUG_WARNING("Error disabling radio %d\n", err);
		
		/*
		 * If in D0-standby mode going directly to D3 may cause a
		 * PCI bus violation.  Therefore we must change out of the D0 
		 * state.
		 *
		 * Sending the PREPARE_FOR_POWER_DOWN will restrict the 
		 * hardware from going into standby mode and will transition 
		 * out of D0-standy if it is already in that state.
		 * 
		 * STATUS_PREPARE_POWER_DOWN_COMPLETE will be sent by the
		 * driver upon completion.  Once received, the driver can 
		 * proceed to the D3 state.
		 *
		 * Prepare for power down command to fw.  This command would 
		 * take HW out of D0-standby and prepare it for D3 state.
		 * 
		 * Currently FW does not support event notification for this 
		 * event. Therefore, skip waiting for it.  Just wait a fixed 
		 * 100ms
		 */
		IPW2100_DEBUG_FW_COMMAND("HOST_PRE_POWER_DOWN\n");
		
		err = ipw2100_hw_send_command(priv, &cmd);
		if (err) 
			printk(KERN_WARNING 
			       "%s: Power down command failed: Error %d\n",
			       priv->ndev->name, err);
		else {
			set_current_state(TASK_UNINTERRUPTIBLE);
			schedule_timeout(HW_POWER_DOWN_DELAY);
		}
	}
	
	priv->disabled = 1;

	/*
	 * Set GPIO 3 writable by FW; GPIO 1 writable 
	 * by driver and enable clock
	 */
	ipw2100_hw_set_gpio(priv);

	/*
	 * Power down adapter.  Sequence:
	 * 1. Stop master assert (RESET_REG[9]=1)
	 * 2. Wait for stop master (RESET_REG[8]==1)
	 * 3. S/w reset assert (RESET_REG[7] = 1)
	 */

	/* Stop master assert */
	write_register(priv->ndev, IPW_REG_RESET_REG,
		       IPW_AUX_HOST_RESET_REG_STOP_MASTER);

	/* wait stop master not more than 50 usec. 
	 * Otherwise return error. */
	for (i = 5; i > 0; i--) {
		udelay(10);

		/* Check master stop bit */
		read_register(priv->ndev, IPW_REG_RESET_REG, &reg);

		if (reg & IPW_AUX_HOST_RESET_REG_MASTER_DISABLED)
			break;
	}

	if (i == 0) 
		printk(KERN_WARNING DRV_NAME 
		       ": %s: Could now power down adapter.\n", 
		       priv->ndev->name);

	/* assert s/w reset */
	write_register(priv->ndev, IPW_REG_RESET_REG,
		       IPW_AUX_HOST_RESET_REG_SW_RESET);

	priv->stopped = 1;

	return 0;
}


static int ipw2100_disable_adapter(struct ipw2100_priv *priv)
{
	struct host_command cmd = {
		.host_command = CARD_DISABLE,
		.host_command_sequence = 0,
		.host_command_length = 0
	};
	int err;

	IPW2100_DEBUG_FW_COMMAND("CARD_DISABLE\n");

	if (priv->disabled) 
		return 0;

	err = ipw2100_hw_send_command(priv, &cmd);
	if (err) {
		IPW2100_DEBUG_WARNING("exit - failed to send CARD_DISABLE "
				      "command\n");
		return err;
	}

	err = ipw2100_wait_for_card_state(priv, STATE_DISABLED);
	if (err) {
		IPW2100_DEBUG_WARNING("exit - card failed to change to "
				      "DISABLED\n");
		return err;
	}

	IPW2100_DEBUG_INFO("TODO: implement scan state machine\n");


	return 0;
}


static int ipw2100_up(struct ipw2100_priv *priv)
{
	unsigned long flags;
	int rc = 0;
	u32 lock;
	u32 ord_len = sizeof(lock);

	/* If the interrupt is enabled, turn it off... */
	spin_lock_irqsave(&priv->low_lock, flags);
	if (priv->irq_enabled) {
		ipw2100_hw_disable_interrupt(priv);
		priv->irq_enabled = 0;
	}

	/* Reset any fatal_error conditions */
	ipw2100_reset_fatalerror(priv);
	spin_unlock_irqrestore(&priv->low_lock, flags);

	if (priv->been_powered || priv->reset_pending) {
		/* Power cycle the card ... */
		if (ipw2100_power_cycle_adapter(priv)) {
			printk(KERN_WARNING "%s: Could not cycle adapter.\n",
			       priv->ndev->name);
			rc = 1;
			goto exit;
		}
	} else 
		priv->been_powered = 1;
	
	/* Load the firmeware, start the clocks, etc. */
	if (ipw2100_start_adapter(priv)) {
	       	printk(KERN_ERR "%s: Failed to start the firmware.\n",
		       priv->ndev->name);
		rc = 1;
		goto exit;
	}

	ipw2100_initialize_ordinals(priv);
	
	/* Determine capabilities of this particular HW configuration */
	if (ipw2100_get_hw_features(priv)) {
		printk(KERN_ERR "%s: Failed to determine HW features.\n",
		       priv->ndev->name);
		rc = 1;
		goto exit;
	}
	
	lock = LOCK_NONE;
	if (ipw2100_set_ordinal(priv, IPW_ORD_PERS_DB_LOCK, &lock, &ord_len)) {
		printk(KERN_ERR "%s: Failed to clear ordinal lock.\n",
		       priv->ndev->name);
		rc = 1;
		goto exit;
	}
	
	/* Turn on the interrupt so that commands can be processed */
	ipw2100_hw_enable_interrupt(priv);
	priv->irq_enabled = 1;
	
	/* Send all of the commands that must be sent prior to 
	 * HOST_COMPLETE */
	if (ipw2100_adapter_setup(priv)) {
		printk(KERN_ERR "%s: Failed to start the card.\n", 
		       priv->ndev->name);
		rc = 1;
		goto exit;
	}
	
        /* Enable the adapter - sends HOST_COMPLETE */
	if (ipw2100_enable_adapter(priv)) {
		printk(KERN_ERR "%s: failed in call to enable adapter.\n",
		       priv->ndev->name);
		rc = 1;
		goto exit;
	}

	/* Start a scan . . . */
	ipw2100_start_scan(priv);

 exit:
	return rc;
}


static void ipw2100_down(struct ipw2100_priv *priv)
{
	unsigned long flags;

	/* Kill the RF switch timer */
	if (!priv->stop_rf_check) {
		priv->stop_rf_check = 1;
		del_timer_sync(&priv->rf_switch_timer);
	}
	
	/* Kill the firmare hang check timer */
	if (!priv->stop_hang_check) {
		priv->stop_hang_check = 1;
		del_timer_sync(&priv->hang_check_timer);
	}

	/* Make sure the interrupt is on so that FW commands will be 
	 * processed correctly */
	spin_lock_irqsave(&priv->low_lock, flags);
	if (!priv->irq_enabled) {
		ipw2100_hw_enable_interrupt(priv);
		priv->irq_enabled = 1;
	}
	spin_unlock_irqrestore(&priv->low_lock, flags);
	
	if (ipw2100_hw_stop_adapter(priv)) 
		printk(KERN_ERR "%s: Error stopping adapter.\n", 
		       priv->ndev->name);
	
	/* Do not disable the interrupt until _after_ we disable
	 * the adaptor.  Otherwise the CARD_DISABLE command will never
	 * be ack'd by the firmware */
	spin_lock_irqsave(&priv->low_lock, flags);
	if (priv->irq_enabled) {
		ipw2100_hw_disable_interrupt(priv);
		priv->irq_enabled = 0;
	}
	spin_unlock_irqrestore(&priv->low_lock, flags);
}

static void ipw2100_reset_adapter(struct ipw2100_priv *priv)
{
	unsigned long flags;
	spin_lock_irqsave(&priv->low_lock, flags);
	printk(KERN_INFO DRV_NAME ": %s: Restarting adapter.\n", 
	       priv->ndev->name);
	priv->resets++;
	priv->connected = 0;
	priv->security_updated = 1;
	
	/* Force a power cycle even if interface hasn't been opened
	 * yet */
	priv->reset_pending = 1;
	spin_unlock_irqrestore(&priv->low_lock, flags);

	ipw2100_up(priv);

}


static void isr_indicate_associated(struct ipw2100_priv *priv, u32 status)
{

#define MAC_ASSOCIATION_READ_DELAY (HZ)
	int ret;
	int len;
	char essid[IW_ESSID_MAX_SIZE + 1];
	u32 txrate;
	u32 chan;
	char *txratename;

	IPW2100_DEBUG_INFO("enter\n");

	/*
	 * TBD: BSSID is usually 00:00:00:00:00:00 here and not
	 *      an actual MAC of the AP. Seems like FW sets this
	 *      address too late. Read it later and expose through 
	 *      /proc or schedule a later task to query and update
	 */
#if 0
	len = ETH_ALEN;
	ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_AP_BSSID, 
				  &priv->ctx->bssid,  &len);
	if (ret) {
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);
		return;
	}
#endif

	memset(essid, 0, IW_ESSID_MAX_SIZE + 1);

	len = IW_ESSID_MAX_SIZE;
	ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_SSID, 
				  essid, &len);
	if (ret) {
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);
		return;
	}

	len = sizeof(u32);
	ret = ipw2100_get_ordinal(priv, IPW_ORD_CURRENT_TX_RATE, 
				  &txrate, &len);
	if (ret) {
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);
		return;
	}

	len = sizeof(u32);
	ret = ipw2100_get_ordinal(priv, IPW_ORD_OUR_FREQ, &chan, &len);
	if (ret) {
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);
		return;
	}

	switch (txrate) {
	case TX_RATE_1_MBIT:
		txratename = "1Mbps";
		break;
	case TX_RATE_2_MBIT:
		txratename = "2Mbsp";
		break;
	case TX_RATE_5_5_MBIT:
		txratename = "5.5Mbps";
		break;
	case TX_RATE_11_MBIT:
		txratename = "11Mbps";
		break;
	default:
		txratename = "unknown rate";
		break;
	}

	printk(KERN_INFO DRV_NAME 
	       ": Associated with '%s' at %s, channel %d\n",
	       essid, txratename, chan);

	/* now we copy read ssid into dev */
	memcpy(priv->ctx->ssid, essid, IW_ESSID_MAX_SIZE);
	priv->ctx->channel = chan;

	priv->connected = 1;
	priv->connect_start = get_seconds();

	/* TODO: We probably shouldn't start the queue until we receive
	 * an association.  Likewise, for netif_carrier_on() . . .
	 */
	netif_carrier_on(priv->ndev);
	if (netif_queue_stopped(priv->ndev))
		netif_wake_queue(priv->ndev);
	else 
		netif_start_queue(priv->ndev);

	if (!priv->wx_ap_event_pending) {
		priv->wx_ap_event_pending = 1;
		schedule_work(&priv->wx_event_work);
	}
}

static void isr_indicate_association_lost(struct ipw2100_priv *priv, u32 status)
{
#if 0
	struct host_command cmd = {
		.host_command = BROADCAST_SCAN,
		.host_command_sequence = 0,
		.host_command_length = 4,
	};
#endif

	printk(KERN_DEBUG "%s: Association lost.\n", priv->ndev->name);

	IPW2100_DEBUG_INFO("TODO: only scan if scanning enabled and "
	       "radio is on\n");

	IPW2100_DEBUG_INFO("TODO: Move broadcast_scan initiation to scheduled "
	       "work\n");

#if 0
	ipw2100_hw_send_command(priv, &cmd);
#endif

	priv->connected = 0;
	netif_carrier_off(priv->ndev);
	netif_stop_queue(priv->ndev);
	
	if (priv->security_updated)
		schedule_work(&priv->security_work);

      priv->wx_ap_event_pending = 1;
      schedule_work(&priv->wx_event_work);
}

static void isr_indicate_rf_kill(struct ipw2100_priv *priv, u32 status)
{
	printk(KERN_INFO "%s: RF Kill state changed to radio OFF.\n",
	       priv->ndev->name);

	/* RF_KILL is now enabled (else we wouldn't be here) */
	priv->rf_kill = 1;

	/* If not already running, we now fire up a timer that will poll
	 * the state of the RF switch on the hardware so we can re-enable
	 * the firmware if the switch is enabled */
	if (priv->stop_rf_check) {
		priv->stop_rf_check = 0;
		priv->rf_switch_timer.expires = jiffies + HZ;
		add_timer(&priv->rf_switch_timer);
	}
}

static void isr_scan_complete(struct ipw2100_priv *priv, u32 status)
{
	IPW2100_DEBUG_SCAN("scan complete\n");
	/* Age the scan results... */
	priv->scans++;
}

#ifdef CONFIG_IPW2100_DEBUG
#define IPW2100_HANDLER(v, f) { v, f, # v }
struct ipw2100_status_indicator {
	int status;
	void (*cb)(struct ipw2100_priv *priv, u32 status);
	char *name;
};
#else
#define IPW2100_HANDLER(v, f) { v, f }
struct ipw2100_status_indicator {
	int status;
	void (*cb)(struct ipw2100_priv *priv, u32 status);
};
#endif /* CONFIG_IPW2100_DEBUG */

const struct ipw2100_status_indicator status_handlers[] = {
	IPW2100_HANDLER(STATUS_INIT_COMPLETE, 0),
	IPW2100_HANDLER(STATUS_COUNTRY_FOUND, 0),
	IPW2100_HANDLER(STATUS_ASSOCIATED, isr_indicate_associated),
	IPW2100_HANDLER(STATUS_ASSN_LOST, isr_indicate_association_lost),
	IPW2100_HANDLER(STATUS_ASSN_CHANGED, 0),
	IPW2100_HANDLER(STATUS_SCAN_COMPLETE, isr_scan_complete),
	IPW2100_HANDLER(STATUS_ENTERED_PSP, 0),
	IPW2100_HANDLER(STATUS_WAKEN_FROM_PSP, 0),
	IPW2100_HANDLER(STATUS_RF_KILL_SWITCH, isr_indicate_rf_kill),
	IPW2100_HANDLER(STATUS_CARD_DISABLE_COMPLETE, 0),
	IPW2100_HANDLER(STATUS_PREP_PWR_DOWN_COMPLETE, 0),
	IPW2100_HANDLER(STATUS_SCANNING, 0),
	IPW2100_HANDLER(-1, 0)
};

static void isr_status_change(struct ipw2100_priv *priv, int status)
{
	int i;

	for (i = 0; status_handlers[i].status != -1; i++) {
		if (status == status_handlers[i].status) {
			IPW2100_DEBUG_STATUS("Status change: %s\n", 
					     status_handlers[i].name);
			if (status_handlers[i].cb)
				status_handlers[i].cb(priv, status);
			priv->wstats.status = status;
			return;
		}
	}

	IPW2100_DEBUG_STATUS("unknown status received: %04x\n", status);
}

static void isr_indicate_host_notification(struct ipw2100_priv *priv,
					   struct ipw2100_notification * hdr)
{

#ifdef CONFIG_IPW2100_DEBUG

	char *notif;

	switch (hdr->hnhdr_subtype) {
	case TYPE_SWEEP_ORD:
		notif = "TYPE_SWEEP_ORD";
		break;
	case TYPE_IBSS_STTN_ORD:
		notif = "TYPE_IBSS_STTN_ORD";
		break;
	case TYPE_BSS_AP_ORD:
		notif = "TYPE_BSS_AP_ORD";
		break;
	case TYPE_CALIBRATION_DATA:
		notif = "TYPE_CALIBRATION_DATA";
		break;
	case TYPE_RAW_BEACON_ENTRY:
		notif = "TYPE_RAW_BEACON_ENTRY";
		break;
	case TYPE_ROGUE_AP_DATA:
		notif = "TYPE_ROGUE_AP_DATA";
		break;
	case TYPE_ASSOCIATION_REQUEST:
		notif = "TYPE_ASSOCIATION_REQUEST";
		break;
	case TYPE_REASSOCIATION_REQUEST:
		notif = "TYPE_REASSOCIATION_REQUEST";
		break;
	default:
		notif = "*UNKNOWN*";
		break;
	}

	IPW2100_DEBUG_INFO("received %s host notification, size %d\n",
	     notif, hdr->hnhdr_size);
#endif
}



static void isr_rx_complete_command(
	struct ipw2100_priv *priv,
	struct ipw2100_cmd_header *cmd)
{
#ifdef CONFIG_IPW2100_DEBUG
	if (cmd->host_command_reg < sizeof(command_types) / 
	    sizeof(*command_types)) {
		IPW2100_DEBUG_RX("Command '%s (%d)' completed\n",
				 command_types[cmd->host_command_reg],
				 cmd->host_command_reg);
	}
#endif
	if (cmd->host_command_reg == HOST_COMPLETE) {
		priv->disabled = 0;
	}
	if (cmd->host_command_reg == CARD_DISABLE) {
		priv->disabled = 1;
	}
	if (cmd->host_command_reg == CARD_DISABLE_PHY_OFF) {
		priv->phy_off = 1;
	}

	priv->message_sending = 0;

	wake_up_interruptible(&priv->wait_command_queue);

#ifdef CONFIG_IPW2100_DEBUG
	IPW2100_DEBUG_INFO("received command status:\n");
	IPW2100_DEBUG_INFO("    CMD    = %d\n", cmd->host_command_reg);
	IPW2100_DEBUG_INFO("    CMD1   = %08x\n", cmd->host_command_reg1);
	IPW2100_DEBUG_INFO("    SEQ    = %d\n", cmd->sequence);
	IPW2100_DEBUG_INFO("    STATUS = %d\n", cmd->cmd_status_reg);
#endif

}

static  void isr_handle_ctl_packet(
	struct ipw2100_priv *priv, 
	struct ieee80211_hdr *header,
	struct ipw2100_status *status)
{
	IPW2100_DEBUG_RX("Ignoring control frame.\n");
}

static inline void isr_beacon_add(
	struct ipw2100_priv *priv,
	struct ieee80211_probe_response *new_beacon,
	s8 rssi)
{
	struct list_head *element;
	struct ipw2100_beacon *beacon;
	struct ieee80211_info_element *info_element;

	element = priv->beacon_free_list.next;
	
	beacon = list_entry(element, struct ipw2100_beacon, list);
	
	/* Pull out fixed field data */
	memcpy(beacon->bssid, new_beacon->header.addr3, ETH_ALEN);
	beacon->capability = new_beacon->capability;
	beacon->rssi = rssi;
	beacon->last_scanned = priv->scans;
/*
	if ((priv->long_preamble == 0) &&
	    !(beacon->capability & WLAN_CAPABILITY_SHORT_PREAMBLE)) {
		printk(KERN_INFO "%s: Beacon received from AP (" MACSTR ") "
		       "that has preable length conflict.\n",
		       priv->ndev->name, MAC2STR(beacon->bssid));
	}
*/
	/* First info element is the SSID */
	info_element = &new_beacon->info_element;
	if (info_element->id != MFIE_TYPE_SSID) {
		printk(KERN_INFO DRV_NAME ": invalid MFIE type for SSID.\n");
		IPW2100_DEBUG_SCAN("exit - ivalid MFIE type : %d\n",
				   info_element->id);
		return;
	}
	beacon->ssid_len = min(info_element->len, SSID_LENGTH);
	memcpy(beacon->ssid, info_element->data, beacon->ssid_len);
	if (beacon->ssid_len < SSID_LENGTH)
		memset(beacon->ssid + beacon->ssid_len, 0, 
		       SSID_LENGTH - beacon->ssid_len);

	/* Second info element is the RATES */
	info_element = (struct ieee80211_info_element *)
		&info_element->data[info_element->len];
	if (info_element->id != MFIE_TYPE_RATES) {
		printk(KERN_INFO DRV_NAME ": invalid MFIE type for RATES.\n");
		IPW2100_DEBUG_SCAN("exit - ivalid MFIE type : %d\n",
				   info_element->id);
		return;
	}
	beacon->rates_len = min(info_element->len, MAX_RATES_LENGTH);
	memcpy(beacon->rates, info_element->data, beacon->rates_len);

	/* Third info element is either FH or DS, based on BSS capability */
	info_element = (struct ieee80211_info_element *)
		&info_element->data[info_element->len];
	if (beacon->capability & WLAN_CAPABILITY_BSS) {
		/* BSS - Direct Sequence */
		if (info_element->id != MFIE_TYPE_DS_SET) {
			printk(KERN_INFO DRV_NAME 
			       ": invalid MFIE type for DIRECT SEQUENCE.\n");
			IPW2100_DEBUG_SCAN("exit - ivalid MFIE type : %d\n",
					   info_element->id);
			return;
		}
		
		beacon->channel = info_element->data[0];
	} else if (beacon->capability & WLAN_CAPABILITY_IBSS) {
		/* IBSS - Frequency Hop */
		if (info_element->id != MFIE_TYPE_FH_SET) {
			IPW2100_DEBUG_SCAN(
				"invalid MFIE type for FREQ HOP.\n");
			return;
		}
	} else {
		/* Invalid ESS/IBSS capability! */
		printk(KERN_INFO DRV_NAME
		       ": Invalid ESS/IBSS capability (both 0).\n");
		IPW2100_DEBUG_SCAN("exit - invalid ESS/IBSS capability: %1X\n",
				   beacon->capability & 0x3);
		return;
	}

	/* For now, we ignore the optional CF and IBSS parameter sets */

	list_del(element);

	IPW2100_DEBUG_SCAN("adding '%s' to beacon list.\n", beacon->ssid);
	
	list_add_tail(&beacon->list, &priv->beacon_list);
}

static void isr_process_probe_response(
	struct ipw2100_priv *priv,
	struct ieee80211_probe_response *new_beacon,
	s8 rssi)
{
	struct ieee80211_info_element *ssid_info;
	struct list_head *element;
	struct ipw2100_beacon *beacon;
	u8 ssid_len;

	IPW2100_DEBUG_SCAN(
		"\n"
		"Time Stamp 1    : %08X\n"
		"Time Stamp 2    : %08X\n"
		"Beacon Interval : %04X\n"
		"Capabilities    : %c%c%c%c-%c%c%c%c\n",
		new_beacon->time_stamp[0],
		new_beacon->time_stamp[1],
		new_beacon->beacon_interval,
		(new_beacon->capability & BIT(7)) ? '1' : '0',
		(new_beacon->capability & BIT(6)) ? '1' : '0',
		(new_beacon->capability & BIT(5)) ? '1' : '0',
		(new_beacon->capability & BIT(4)) ? '1' : '0',
		(new_beacon->capability & BIT(3)) ? '1' : '0',
		(new_beacon->capability & BIT(2)) ? '1' : '0',
		(new_beacon->capability & BIT(1)) ? '1' : '0',
		(new_beacon->capability & BIT(0)) ? '1' : '0');

	if (new_beacon->capability & WLAN_CAPABILITY_IBSS) {
		IPW2100_DEBUG_SCAN("IBSS Beacon received.  Dropping.\n");
		return;
	}

	ssid_info = &new_beacon->info_element;
	if (ssid_info->id != MFIE_TYPE_SSID) {
		printk(KERN_INFO DRV_NAME ": invalid MFIE type for SSID.\n");
		IPW2100_DEBUG_SCAN("exit - invalid MFIE type for SSID: %d\n",
				   ssid_info->id);
		return;
	}

	/* Search for this entry in the list and nuke it if it is 
	 * already there */
	ssid_len = min(ssid_info->len, SSID_LENGTH);
	for (element = priv->beacon_list.next;
	     element != &priv->beacon_list;
	     element = element->next) {
		beacon = list_entry(element, struct ipw2100_beacon, list);
		if (!memcmp(beacon->bssid, new_beacon->header.addr3, 
			    ETH_ALEN)) {
			list_del(element);
			list_add_tail(&beacon->list, &priv->beacon_free_list);
			break;
		}
	}

	if (list_empty(&priv->beacon_free_list)) {
		IPW2100_DEBUG_SCAN("TODO: Expire oldest beacon to add new.\n");
		printk(KERN_WARNING DRV_NAME ": Beacon free list is empty.\n");
	} else 
		isr_beacon_add(priv, new_beacon, rssi);

	IPW2100_DEBUG_SCAN("exit\n");
}
	
static void isr_handle_mgt_packet(
	struct ipw2100_priv *priv, 
	struct ieee80211_hdr *header,
	struct ipw2100_status *status)
{
	char *msg = "UNKNOWN";
	
	switch (WLAN_FC_GET_STYPE(header->frame_control)) {
	case ieee_802_11_frame_subtype_Association_Resp:
		msg = "ASSOCIATION RESPONSE";
		break;

	case ieee_802_11_frame_subtype_Reassociation_Resp:
		msg = "REASSOCIATION RESPONSE";
		break;

	case ieee_802_11_frame_subtype_Probe_Resp:
		msg = "PROBE RESPONSE";
		IPW2100_DEBUG_SCAN("Probe response\n");
		isr_process_probe_response(
			priv, (struct ieee80211_probe_response *)header,
			status->rssi);
		break;

	case ieee_802_11_frame_subtype_Beacon:
		msg = "BEACON";
		IPW2100_DEBUG_SCAN("Beacon\n");
		isr_process_probe_response(
			priv, (struct ieee80211_probe_response *)header,
			status->rssi);
		break;

	default:
		IPW2100_DEBUG_WARNING("%s: Unknown management packet: %d\n",
				      priv->ndev->name, 
				      WLAN_FC_GET_STYPE(header->frame_control));
		break;
	}

	IPW2100_DEBUG_RX("received %s(%d)\n", msg, 
			 WLAN_FC_GET_STYPE(header->frame_control));
}



#ifdef CONFIG_IPW2100_DEBUG
const char *frame_types[] = {
	"COMMAND_STATUS_VAL",
	"STATUS_CHANGE_VAL",
	"P80211_DATA_VAL",
	"P8023_DATA_VAL",
	"HOST_NOTIFICATION_VAL"
};
#endif


static inline struct ipw2100_rx_packet *ipw2100_alloc_skb(
	struct ipw2100_priv *priv,
	struct ipw2100_rx_packet *packet)
{
	packet->skb = dev_alloc_skb(sizeof(struct ipw2100_rx));
	if (!packet->skb)
		return NULL;

	packet->rxp = (struct ipw2100_rx *)packet->skb->data;
	packet->dma_addr = pci_map_single(priv->pdev, packet->skb->data,
					  sizeof(struct ipw2100_rx),
					  PCI_DMA_FROMDEVICE);
	/* NOTE: pci_map_single does not return an error code, and 0 is a valid
	 *       dma_addr */
	
	return packet;
}

static inline void schedule_reset(struct ipw2100_priv *priv) 
{
	if (!priv->reset_pending) {
		printk(KERN_INFO "%s: Scheduling firmware restart.\n",
		       priv->ndev->name);
		priv->connected = 0;
		netif_carrier_off(priv->ndev);
		netif_stop_queue(priv->ndev);
		priv->reset_pending = 1;
		schedule_work(&priv->reset_work);
		wake_up_interruptible(&priv->wait_command_queue);
	} else
		printk(KERN_INFO 
		       "%s: Firmware restart already in progress.\n",
		       priv->ndev->name);
}

#define SEARCH_ERROR   0xffffffff
#define SEARCH_FAIL    0xfffffffe
#define SEARCH_SUCCESS 0xfffffff0
#define SEARCH_DISCARD 0
#define SEARCH_SNAPSHOT 1

#define SNAPSHOT_ADDR(ofs) (priv->snapshot[((ofs) >> 12) & 0xff] + ((ofs) & 0xfff))
static inline int ipw2100_snapshot_alloc(struct ipw2100_priv *priv) 
{
	int i;
	if (priv->snapshot[0])
		return 1;
	for (i = 0; i < 0x30; i++) {
		priv->snapshot[i] = (u8*)kmalloc(0x1000, GFP_ATOMIC);
		if (!priv->snapshot[i]) {
			printk(KERN_DEBUG "%s: Error allocating snapshot "
			       "buffer %d\n", priv->ndev->name, i);
			while (i > 0)
				kfree(priv->snapshot[--i]);
			priv->snapshot[0] = NULL;
			return 0;
		}
	}

	return 1;
}

static inline void ipw2100_snapshot_free(struct ipw2100_priv *priv) 
{
	int i;
	if (!priv->snapshot[0])
		return;
	for (i = 0; i < 0x30; i++) 
		kfree(priv->snapshot[i]);
	priv->snapshot[0] = NULL;
}

static inline u32 ipw2100_match_buf(struct ipw2100_priv *priv, u8 *in_buf, 
				    size_t len, int mode)
{
	u32 i, j;
	u32 tmp;
	u8 *s, *d;
	u32 ret;

	s = in_buf;
	if (mode == SEARCH_SNAPSHOT) {
		if (!ipw2100_snapshot_alloc(priv))
			mode = SEARCH_DISCARD;
	}

	for (ret = SEARCH_FAIL, i = 0; i < 0x30000; i += 4) {
		read_nic_dword(priv->ndev, i, &tmp);
		if (mode == SEARCH_SNAPSHOT)
			*(u32 *)SNAPSHOT_ADDR(i) = tmp;
		if (ret == SEARCH_FAIL) {
			d = (u8*)&tmp;
			for (j = 0; j < 4; j++) {
				if (*s != *d) {
					s = in_buf;
					continue;
				}
				
				s++;
				d++;
				
				if ((s - in_buf) == len)
					ret = (i + j) - len + 1;
			}
		} else if (mode == SEARCH_DISCARD)
			return ret;
	}

	return ret;
}

/*
 *
 * 0) Disconnect the SKB from the firmware (just unmap)
 * 1) Pack the ETH header into the SKB
 * 2) Pass the SKB to the network stack
 *
 * When packet is provided by the firmware, it contains the following:
 * 
 * .  ieee80211_hdr
 * .  ieee_802_11_snap_header
 *
 * The size of the constructed ethernet 
 *
 */
#ifdef CONFIG_IPW2100_RX_DEBUG
	u8 packet_data[IPW_RX_NIC_BUFFER_LENGTH]
#endif
	
static inline int isr_rx(struct ipw2100_priv *priv, int i)
{
	struct ipw2100_status *status = &priv->status_queue.drv[i];
	struct ipw2100_rx_packet *packet = &priv->rx_buffers[i];
	struct ieee80211_rx_stats stats = {
		.signal = status->rssi,
		.noise = IPW2100_RSSI_TO_DBM,
		.rate = 0,
		.mac_time = jiffies,
	};
	unsigned int payload_len;
#ifdef CONFIG_IPW2100_DEBUG
	u32 match;
#endif
	IPW2100_DEBUG_RX("Handler...\n");

	payload_len = status->frame_size - IPW_802_11_PAYLOAD_OFFSET + 
		ETH_ALEN * 2;

	if (payload_len > IPW_MAX_802_11_PAYLOAD_LENGTH) {
		printk(KERN_INFO 
		       "%s: Packet payload too large (%u) - dropping:\n"
		       "status packet = \n"
		       "   bd index      = %d\n"
		       "   frame_size    = %u\n"
		       "   status_fields = 0x%04X\n"
		       "   flags         = 0x%02X\n"
		       "   rssi          = 0x%02X\n"
		       "Scheduling firmware restart.\n",
		       priv->ndev->name, payload_len, i,
		       status->frame_size, status->status_fields,
		       status->flags, status->rssi);

#ifdef CONFIG_IPW2100_DEBUG
		match = ipw2100_match_buf(priv, (u8*)status, 
					  sizeof(struct ipw2100_status),
					  SEARCH_SNAPSHOT);
		if (match < SEARCH_SUCCESS) 
			printk(KERN_INFO "%s: DMA packet match in Firmware at "
			       "offset 0x%06X, length %d:\n",
			       priv->ndev->name, match, 
			       sizeof(struct ipw2100_status));
		else 
			printk(KERN_INFO "%s: No DMA packet match in "
			       "Firmware.\n", priv->ndev->name);
		printk_buf((u8*)status, sizeof(struct ipw2100_status));
#endif		

		priv->fatal_error = IPW2100_ERR_C3_CORRUPTION;
		priv->ieee.stats.rx_errors++;
		schedule_reset(priv);
		return 0;
	}
	
	if (status->frame_size > skb_tailroom(packet->skb)) {
		printk(KERN_INFO "%s: frame_size (%u) > skb_tailroom (%u)!"
		       "  Dropping.\n",
		       priv->ndev->name,
		       status->frame_size, skb_tailroom(packet->skb));
		priv->ieee.stats.rx_errors++;
		return 0;
	}

	pci_unmap_single(priv->pdev,
			 packet->dma_addr,
			 sizeof(struct ipw2100_rx),
			 PCI_DMA_FROMDEVICE);

	skb_put(packet->skb, status->frame_size);

#ifdef CONFIG_IPW2100_RX_DEBUG
	memcpy(packet_data, packet->skb->data, 
	       min(status->frame_size, IPW_RX_NIC_BUFFER_LENGTH));
#endif

	if (!ieee80211_rx(&priv->ieee, packet->skb, &stats))
		goto not_consumed;
	
	/* SKB was sent and is now the property of the networking stack.  So,
	 * we need to allocate a new SKB and attach it to the RDB. */
	packet = ipw2100_alloc_skb(priv, packet);
	if (!packet) {
		printk(KERN_WARNING 
		       "%s: Unable to allocate SKB onto RBD ring - disabling "
		       "adapter.\n", priv->ndev->name);
		/* TODO: schedule adapter shutdown */
		IPW2100_DEBUG_INFO("TODO: Shutdown adapter...\n");
	}

	/* Update the RDB entry */
	priv->rx_queue.drv[i].host_addr = packet->dma_addr;

	return 1;

not_consumed:
	/* This packet was not handed to netif_rx, so reset it for future 
	 * use */
	printk(KERN_INFO "%s: Non consumed packet: %d\n", 
	       priv->ndev->name, i);

	/* Reset the SKB back to default positions */
	packet->skb->data = packet->skb->head;
	packet->skb->tail = packet->skb->head;
	packet->skb->len = 0;
	
	/* Remap the DMA address */
	packet->dma_addr = pci_map_single(
		priv->pdev, packet->skb->data, sizeof(struct ipw2100_rx),
		PCI_DMA_FROMDEVICE);

	/* Update the RDB entry */
	priv->rx_queue.drv[i].host_addr = packet->dma_addr;

#ifdef CONFIG_IPW2100_RX_DEBUG
	printk(KERN_DEBUG "%s: Non consumed packet:\n",
	       priv->ndev->name);
	printk_buf(packet_data, status->frame_size);
#endif

	return 0;
}
 
/*
 * ipw2100 interrupts are disabled at this point, and the ISR
 * is the only code that calls this method.  So, we do not need
 * to play with any locks.  
 *
 * RX Queue works as follows:
 * 
 * Read index - firmware places packet in entry identified by the 
 *              Read index and advances Read index.  In this manner, 
 *              Read index will always point to the next packet to 
 *              be filled--but not yet valid.
 *
 * Write index - driver fills this entry with an unused RBD entry.
 *               This entry has not filled by the firmware yet.
 *
 * In between the W and R indexes are the RBDs that have been received
 * but not yet processed.  
 *
 * The process of handling packets will start at WRITE + 1 and advance
 * until it reaches the READ index.  
 *
 * The WRITE index is cached in the variable 'priv->rx_queue.next'.
 *
 */
static inline void __ipw2100_rx_process(struct ipw2100_priv *priv)
{
	struct ipw2100_bd_queue *rxq = &priv->rx_queue;
	struct ipw2100_status_queue *sq = &priv->status_queue;
	struct ipw2100_rx_packet *packet;
	u16 frame_type;
	u32 frame_size;
	u32 r, w, i, s;//, j;
	struct ipw2100_rx *u;
	
	read_register(priv->ndev, IPW_MEM_HOST_SHARED_RX_READ_INDEX, &r);
	read_register(priv->ndev, IPW_MEM_HOST_SHARED_RX_WRITE_INDEX, &w);
	
	if (r >= rxq->entries) {
		IPW2100_DEBUG_RX("exit - bad read index\n");
		return;
	}

	i = (rxq->next + 1) % rxq->entries;
	s = i;
	while (i != r) {
		/* IPW2100_DEBUG_RX("r = %d : w = %d : processing = %d\n",
		   r, rxq->next, i); */
		
		packet = &priv->rx_buffers[i];

		/* Sync the DMA for the STATUS buffer so CPU is sure to get 
		 * the correct values */
		pci_dma_sync_single(
			priv->pdev, 
			sq->nic + sizeof(struct ipw2100_status) * i,
			sizeof(struct ipw2100_status),
			PCI_DMA_FROMDEVICE);

		/* Sync the DMA for the RX buffer so CPU is sure to get 
		 * the correct values */
		pci_dma_sync_single(priv->pdev, packet->dma_addr,
				    sizeof(struct ipw2100_rx),
				    PCI_DMA_FROMDEVICE);
		
		u = packet->rxp;
		frame_type = sq->drv[i].status_fields & STATUS_TYPE_MASK;
		frame_size = sq->drv[i].frame_size;
		
		IPW2100_DEBUG_RX("%s: '%s' frame type received (%d).\n",
				 priv->ndev->name, frame_types[frame_type],
				 frame_size);

		switch (frame_type) {
		case COMMAND_STATUS_VAL:
			if (frame_size == sizeof(u->command))
			    isr_rx_complete_command(priv, &u->command);
			break;

		case STATUS_CHANGE_VAL:
			if (frame_size == sizeof(u->status))
				isr_status_change(priv, u->status);
			break;

		case P80211_DATA_VAL:
		case P8023_DATA_VAL:
			if (frame_size < sizeof(u->header))
				break;
			switch (WLAN_FC_GET_TYPE(u->header.frame_control)) {
			case WLAN_FC_TYPE_MGMT:
				isr_handle_mgt_packet(priv, &u->header,
						      &sq->drv[i]);
				break;
				
			case WLAN_FC_TYPE_CTRL:
				isr_handle_ctl_packet(priv, &u->header,
						      &sq->drv[i]);
				break;
				
			case WLAN_FC_TYPE_DATA:
				/* We only process data packets if the 
				 * interface is open */
				if (!priv->open) {
					priv->wstats.discard.misc++;
					IPW2100_DEBUG_DROP(
						"Dropping packet while "
						"interface is not up.\n");
					break;
				}

				isr_rx(priv, i);
				break;
				
			default:
				priv->wstats.discard.misc++;
				IPW2100_DEBUG_DROP("received unknown packet "
						   "type, dropping...\n");
				break;
			}
			break;

		case HOST_NOTIFICATION_VAL:
			if (frame_size >= sizeof(u->notification))
			    isr_indicate_host_notification(priv, 
							   &u->notification);
			break;

		default:
			priv->wstats.discard.misc++;
			IPW2100_DEBUG_DROP(
				"received unknown frame type: %d, "
				"ignoring...\n", frame_type);
			break;
		}

		/* clear status field associated with this RBD */
		rxq->drv[i].status.field = 0;

		i = (i + 1) % rxq->entries;
	}

	if (i != s) {
		/* backtrack one entry, wrapping to end if at 0 */
		rxq->next = (i ? i : rxq->entries) - 1;
	
		write_register(priv->ndev,
			       IPW_MEM_HOST_SHARED_RX_WRITE_INDEX,
			       rxq->next);
	}
}


/*
 * __ipw2100_tx_process
 *
 * This routine will determine whether the next packet on
 * the fw_pend_list has been processed by the firmware yet.
 *
 * If not, then it does nothing and returns.
 *
 * If so, then it removes the item from the fw_pend_list, frees
 * any associated storage, and places the item back on the
 * free list of its source (either msg_free_list or tx_free_list)
 *
 * TX Queue works as follows:
 * 
 * Read index - points to the next TBD that the firmware will 
 *              process.  The firmware will read the data, and once
 *              done processing, it will advance the Read index.
 *
 * Write index - driver fills this entry with an constructed TBD 
 *               entry.  The Write index is not advanced until the
 *               packet has been configured.
 *
 * In between the W and R indexes are the TBDs that have NOT been 
 * processed.  Lagging behind the R index are packets that have
 * been processed but have not been freed by the driver.
 *
 * In order to free old storage, an internal index will be maintained
 * that points to the next packet to be freed.  When all used
 * packets have been freed, the oldest index will be the same as the
 * firmware's read index.
 *
 * The OLDEST index is cached in the variable 'priv->tx_queue.oldest'
 *
 * Because the TBD structure can not contain arbitrary data, the
 * driver must keep an internal queue of cached allocations such that
 * it can put that data back into the tx_free_list and msg_free_list
 * for use by future command and data packets.
 *
 */
static inline int __ipw2100_tx_process(struct ipw2100_priv *priv)
{
	struct ipw2100_bd_queue *txq = &priv->tx_queue;
        struct ipw2100_bd *tbd;
	struct list_head *element;
	struct ipw2100_tx_packet *packet;
	int descriptors_used;
	int e, i;
	u32 r, w, frag_num = 0;

	if (list_empty(&priv->fw_pend_list)) 
		return 0;
	
	element = priv->fw_pend_list.next;

	packet = list_entry(element, struct ipw2100_tx_packet, list);
        tbd = &txq->drv[packet->index];

	/* Determine how many TBD entries must be finished... */
	switch (packet->type) {
	case COMMAND:
		/* COMMAND uses only one slot; don't advance */
		descriptors_used = 1;
		e = txq->oldest;
		break;

	case DATA:
		/* DATA uses two slots; advance and loop position. */
		descriptors_used = tbd->num_fragments;
                frag_num = tbd->num_fragments - 1;
		e = txq->oldest + frag_num;
		e %= txq->entries;
		break;

	default:
		IPW2100_DEBUG_WARNING("%s: Bad fw_pend_list entry!\n", 
				   priv->ndev->name);
		return 0;
	}

	/* if the last TBD is not done by NIC yet, then packet is
	 * not ready to be released. 
	 * 
	 */
	read_register(priv->ndev, IPW_MEM_HOST_SHARED_TX_QUEUE_READ_INDEX(0), 
		      &r);
	read_register(priv->ndev, IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX(0), 
		      &w);
	if (w != txq->next) 
		printk(KERN_WARNING "%s: write index mismatch\n", 
		       priv->ndev->name);

        /*
	 * txq->next is the index of the last packet written txq->oldest is 
	 * the index of the r is the index of the next packet to be read by 
	 * firmware
	 */


	/* 
	 * Quick graphic to help you visualize the following
	 * if / else statement
	 * 
	 * ===>|                     s---->|===============
	 *                               e>|
	 * | a | b | c | d | e | f | g | h | i | j | k | l 
	 *       r---->|
	 *               w
	 *
	 * w - updated by driver
	 * r - updated by firmware
	 * s - start of oldest BD entry (txq->oldest)
	 * e - end of oldest BD entry
	 *
	 */
	if (!((r <= w && (e < r || e >= w)) || (e < r && e >= w))) { 
		IPW2100_DEBUG_TX("exit - no processed packets ready to "
				 "release.\n");
		return 0;
	}

	list_del(element);
	DEC_STAT(&priv->fw_pend_stat);

#ifdef CONFIG_IPW2100_DEBUG
	{
		int i = txq->oldest;
		IPW2100_DEBUG_TX(
			"TX%d V=%p P=%p T=%p L=%d\n", i,
			&txq->drv[i],
			(void*)txq->nic + i * sizeof(struct ipw2100_bd),
			(void*)txq->drv[i].host_addr,
			txq->drv[i].buf_length);
		
		if (packet->type == DATA) {
			i = (i + 1) % txq->entries;
			
			IPW2100_DEBUG_TX(
				"TX%d V=%p P=%p T=%p L=%d\n", i,
				&txq->drv[i],
				(void*)txq->nic + i * 
				sizeof(struct ipw2100_bd),
				(void*)txq->drv[i].host_addr,
				txq->drv[i].buf_length);
		}
	}
#endif
 
	switch (packet->type) {
	case DATA:
		if (txq->drv[txq->oldest].status.txType != 0) 
			printk(KERN_WARNING "%s: Queue mismatch.  "
			       "Expecting DATA TBD but pulled "
			       "something else: ids %d=%d.\n", 
			       priv->ndev->name, txq->oldest, packet->index);
		
		/* DATA packet; we have to unmap and free the SKB */
		priv->ieee.stats.tx_packets++;
		for (i = 0; i < frag_num; i++) {
			tbd = &txq->drv[(packet->index + 1 + i) % 
					txq->entries];
			priv->ieee.stats.tx_bytes += tbd->buf_length;
			
			IPW2100_DEBUG_TX(
				"TX%d P=%08x L=%d\n",
				(packet->index + 1 + i) % txq->entries,
				tbd->host_addr, tbd->buf_length);
			pci_unmap_single(priv->pdev,
					 tbd->host_addr,
					 tbd->buf_length,
					 PCI_DMA_TODEVICE);
		}

		skb_queue_purge(&packet->skb_queue);

		list_add_tail(element, &priv->tx_free_list);
		INC_STAT(&priv->tx_free_stat);

		/* We have a free slot in the Tx queue, so wake up the
		 * transmit layer if it is stopped. */
		if (priv->open &&
		    priv->connected && netif_queue_stopped(priv->ndev))
			netif_wake_queue(priv->ndev);

		/* A packet was processed by the hardware, so update the 
		 * watchdog */
		priv->ndev->trans_start = jiffies;

		break;

	case COMMAND:
		if (txq->drv[txq->oldest].status.txType != 1) 
			printk(KERN_WARNING "%s: Queue mismatch.  "
			       "Expecting COMMAND TBD but pulled "
			       "something else: ids %d=%d.\n", 
			       priv->ndev->name, txq->oldest, packet->index);

#ifdef CONFIG_IPW2100_DEBUG
		if (packet->cmd->host_command_reg <
		    sizeof(command_types) / sizeof(*command_types)) 
			IPW2100_DEBUG_TX(
				"Command '%s (%d)' processed: %d.\n",
				command_types[packet->cmd->host_command_reg],
				packet->cmd->host_command_reg,
				packet->cmd->cmd_status_reg);
#endif

		list_add_tail(element, &priv->msg_free_list);
		INC_STAT(&priv->msg_free_stat);
		break;
	}
        
	/* advance oldest used TBD pointer to start of next entry */
	txq->oldest = (e + 1) % txq->entries;
	/* increase available TBDs number */
	txq->available += descriptors_used;
	SET_STAT(&priv->txq_stat, txq->available);

	IPW2100_DEBUG_TX("packet latency (send to process)  %ld jiffies\n",
			 jiffies - packet->jiffy_start);
	
	return (!list_empty(&priv->fw_pend_list));
}


static inline void __ipw2100_tx_complete(struct ipw2100_priv *priv)
{
	int i = 0;

	while (__ipw2100_tx_process(priv) && i < 200) i++;

	if (i == 200) {
		printk(KERN_WARNING 
		       "%s: Driver is running slow (%d iters).\n",
		       priv->ndev->name, i);
	}
}


static void X__ipw2100_tx_send_commands(struct ipw2100_priv *priv)
{
	struct list_head *element;
	struct ipw2100_tx_packet *packet;
	struct ipw2100_bd_queue *txq = &priv->tx_queue;
	struct ipw2100_bd *tbd;
	int next = txq->next;

	while (!list_empty(&priv->msg_pend_list)) {
		/* if there isn't enough space in TBD queue, then
		 * don't stuff a new one in. 
		 * NOTE: 3 are needed as a command will take one,
		 *       and there is a minimum of 2 that must be
		 *       maintained between the r and w indexes
		 */
		if (txq->available <= 3) {
			IPW2100_DEBUG_TX("no room in tx_queue\n");
			break;
		}

		element = priv->msg_pend_list.next;
		list_del(element);
		DEC_STAT(&priv->msg_pend_stat);

		packet = list_entry(element, 
				    struct ipw2100_tx_packet, list);
		
		IPW2100_DEBUG_TX("using TBD at virt=%p, phys=%p\n",
				 &txq->drv[txq->next],
				 (void*)(txq->nic + txq->next * 
					 sizeof(struct ipw2100_bd)));

		packet->index = txq->next;

		tbd = &txq->drv[txq->next];

		/* initialize TBD */
		tbd->host_addr = packet->cmd_phys;
		tbd->buf_length = sizeof(struct ipw2100_cmd_header);
		/* not marking number of fragments causes problems 
		 * with f/w debug version */
		tbd->num_fragments = 1;
		tbd->status.field =
			IPW_BD_STATUS_TX_FRAME_COMMAND |
			IPW_BD_STATUS_TX_INTERRUPT_ENABLE;

		/* update TBD queue counters */
		txq->next++;
		txq->next %= txq->entries;
		txq->available--;
		DEC_STAT(&priv->txq_stat);

		list_add_tail(element, &priv->fw_pend_list);
		INC_STAT(&priv->fw_pend_stat);
	}

	if (txq->next != next) {
		/* kick off the DMA by notifying firmware the 
		 * write index has moved; make sure TBD stores are sync'd */
		wmb();
		write_register(priv->ndev, 
			       IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX(0),
			       txq->next);
	}
}


/*
 * X__ipw2100_tx_send_data
 *
 */
static void X__ipw2100_tx_send_data(struct ipw2100_priv *priv)
{
	struct list_head *element;
	struct ipw2100_tx_packet *packet;
	struct ipw2100_bd_queue *txq = &priv->tx_queue;
	struct ipw2100_bd *tbd;
	struct sk_buff *skb;
	int next = txq->next;
        int i = 0;

	while (!list_empty(&priv->tx_pend_list)) {
		/* if there isn't enough space in TBD queue, then
		 * don't stuff a new one in. 
		 * NOTE: 4 are needed as a data will take two,
		 *       and there is a minimum of 2 that must be
		 *       maintained between the r and w indexes
		 */
		element = priv->tx_pend_list.next;
                packet = list_entry(element, struct ipw2100_tx_packet, list);

		if (txq->available <= 3 + packet->fragments) {
			IPW2100_DEBUG_TX("no room in tx_queue\n");
			break;
		}

		list_del(element);
		DEC_STAT(&priv->tx_pend_stat);

		tbd = &txq->drv[txq->next];

		packet->index = txq->next;

		tbd->host_addr = packet->data_phys;
		tbd->buf_length = sizeof(struct ipw2100_data_header);
		tbd->num_fragments = 1 + packet->fragments; 
		tbd->status.field =
			IPW_BD_STATUS_TX_FRAME_802_3 |
			IPW_BD_STATUS_TX_FRAME_NOT_LAST_FRAGMENT;
		txq->next++;
		txq->next %= txq->entries;

		IPW2100_DEBUG_TX(
			"data header tbd TX%d P=%08x L=%d\n", 
			packet->index, tbd->host_addr, 
			tbd->buf_length);
#ifdef CONFIG_IPW2100_DEBUG		
		if (packet->fragments > 1) 
			IPW2100_DEBUG_FRAG("fragment Tx: %d frames\n", 
					   packet->fragments);
#endif

                for (i = 0; i < packet->fragments; i++) {
		        tbd = &txq->drv[txq->next];

			/* Move this from the 802.11 TX queue and put into the
			 * ownership queue for this data packet */
			skb = skb_dequeue(&priv->ieee.tx_queue);
			skb_queue_head(&packet->skb_queue, skb);

			if (i == packet->fragments - 1)
				tbd->status.field = 
					IPW_BD_STATUS_TX_FRAME_802_3 |
					IPW_BD_STATUS_TX_INTERRUPT_ENABLE;
			else
				tbd->status.field =
					IPW_BD_STATUS_TX_FRAME_802_3 |
					IPW_BD_STATUS_TX_FRAME_NOT_LAST_FRAGMENT;
			
			tbd->buf_length = skb->len;
 
                        tbd->host_addr = pci_map_single(
				priv->pdev, 
				skb->data, skb->len, PCI_DMA_TODEVICE);
			/* NOTE: pci_map_single does not return an error code, 
			 *       and 0 is a valid dma_addr */


			IPW2100_DEBUG_TX(
				"data frag tbd TX%d P=%08x L=%d\n", 
				txq->next, tbd->host_addr, tbd->buf_length);

			pci_dma_sync_single(priv->pdev,
                                            tbd->host_addr,
                                            tbd->buf_length,
                                            PCI_DMA_TODEVICE);

			txq->next++;
			txq->next %= txq->entries;
                }
		
		txq->available -= 1 + packet->fragments;
		SET_STAT(&priv->txq_stat, txq->available);

		list_add_tail(element, &priv->fw_pend_list);
		INC_STAT(&priv->fw_pend_stat);
	}

	if (txq->next != next) {
		/* kick off the DMA by notifying firmware the 
		 * write index has moved; make sure TBD stores are sync'd */
		write_register(priv->ndev, 
			       IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX(0),
			       txq->next);
	}
        return;
}

static void ipw2100_irq_tasklet(struct ipw2100_priv *priv)
{
	struct net_device *dev = priv->ndev;
	unsigned long flags;
	u32 inta, tmp;

	spin_lock_irqsave(&priv->low_lock, flags);
	ipw2100_hw_disable_interrupt(priv);

	read_register(dev, IPW_REG_INTA, &inta);
			 
	IPW2100_DEBUG_ISR("enter - INTA: 0x%08lX\n", 
			  (unsigned long)inta & IPW_INTERRUPT_MASK);

	priv->in_isr++;
	priv->interrupts++;

	/* We do not loop and keep polling for more interrupts as this
	 * is frowned upon and doesn't play nicely with other potentially
	 * chained IRQs */
	IPW2100_DEBUG_ISR("INTA: 0x%08lX\n", 
			  (unsigned long)inta & IPW_INTERRUPT_MASK);

	if (inta & IPW2100_INTA_FATAL_ERROR) {
		printk(KERN_WARNING DRV_NAME 
		       ": Fatal interrupt. Scheduling firmware restart.\n");
		priv->inta_other++;
		write_register(
			dev, IPW_REG_INTA,
			IPW2100_INTA_FATAL_ERROR);

		read_nic_dword(dev, IPW_NIC_FATAL_ERROR, &priv->fatal_error);
		printk(KERN_INFO "%s: Fatal error value: 0x%08X\n",
		       priv->ndev->name, priv->fatal_error);

		read_nic_dword(dev, IPW_ERROR_ADDR(priv->fatal_error), &tmp);
		printk(KERN_INFO "%s: Fatal error address value: 0x%08X\n",
		       priv->ndev->name, tmp);
		
		/* Wake up any sleeping jobs */
		schedule_reset(priv);
	}
	
	if (inta & IPW2100_INTA_PARITY_ERROR) {
		IPW2100_DEBUG_ERROR("***** PARITY ERROR INTERRUPT !!!! \n");
		priv->inta_other++;
		write_register(
			dev, IPW_REG_INTA, 
			IPW2100_INTA_PARITY_ERROR);
	}

	if (inta & IPW2100_INTA_RX_TRANSFER) {
		IPW2100_DEBUG_ISR("RX interrupt\n");

		priv->rx_interrupts++;

		write_register(
			dev, IPW_REG_INTA,
			IPW2100_INTA_RX_TRANSFER);
		
		__ipw2100_rx_process(priv);
		__ipw2100_tx_complete(priv);
	}
	
	if (inta & IPW2100_INTA_TX_TRANSFER) {
		IPW2100_DEBUG_ISR("TX interrupt\n");

		priv->tx_interrupts++;
		
		write_register(dev, IPW_REG_INTA,
			       IPW2100_INTA_TX_TRANSFER);
		
		__ipw2100_tx_complete(priv);
		X__ipw2100_tx_send_commands(priv);
		X__ipw2100_tx_send_data(priv);
	}
	
	if (inta & IPW2100_INTA_TX_COMPLETE) {
		IPW2100_DEBUG_ISR("TX complete\n");
		priv->inta_other++;
		write_register(
			dev, IPW_REG_INTA,
			IPW2100_INTA_TX_COMPLETE);
		
		__ipw2100_tx_complete(priv);
	}
	
	if (inta & IPW2100_INTA_EVENT_INTERRUPT) {
		/* ipw2100_handle_event(dev); */
		priv->inta_other++;
		write_register(
			dev, IPW_REG_INTA,
			IPW2100_INTA_EVENT_INTERRUPT);
	}
	
	if (inta & IPW2100_INTA_FW_INIT_DONE) {
		IPW2100_DEBUG_ISR("FW init done interrupt\n");
		priv->inta_other++;
		
		read_register(dev, IPW_REG_INTA, &tmp);
		if (tmp & (IPW2100_INTA_FATAL_ERROR |
			   IPW2100_INTA_PARITY_ERROR)) {
			write_register(
				dev, IPW_REG_INTA,
				IPW2100_INTA_FATAL_ERROR |
				IPW2100_INTA_PARITY_ERROR);
		}
		
		write_register(dev, IPW_REG_INTA,
			       IPW2100_INTA_FW_INIT_DONE);
	}
	
	if (inta & IPW2100_INTA_STATUS_CHANGE) {
		IPW2100_DEBUG_ISR("Status change interrupt\n");
		priv->inta_other++;
		write_register(
			dev, IPW_REG_INTA,
			IPW2100_INTA_STATUS_CHANGE);
	}
	
	if (inta & IPW2100_INTA_SLAVE_MODE_HOST_COMMAND_DONE) {
		IPW2100_DEBUG_ISR("slave host mode interrupt\n");
		priv->inta_other++;
		write_register(
			dev, IPW_REG_INTA,
			IPW2100_INTA_SLAVE_MODE_HOST_COMMAND_DONE);
	}
	
	priv->in_isr--;
	ipw2100_hw_enable_interrupt(priv);
	priv->irq_enabled = 1;

	spin_unlock_irqrestore(&priv->low_lock, flags);

	IPW2100_DEBUG_ISR("exit\n");
}


static irqreturn_t ipw2100_interrupt(int irq, void *dev_id, 
				     struct pt_regs *regs)
{
	struct net_device *dev = (struct net_device *) dev_id;
	struct ipw2100_priv *priv = dev->priv;
	u32 inta, inta_mask;

	if (!dev)
		return IRQ_NONE;

	read_register(dev, IPW_REG_INTA_MASK, &inta_mask);
	read_register(dev, IPW_REG_INTA, &inta);
	inta &= IPW_INTERRUPT_MASK;

	if (!(inta & inta_mask)) {
		/* Shared interrupt */
		return IRQ_NONE;
	}

	if (inta == 0xFFFFFFFF) {
		/* Hardware disappeared */
		return IRQ_HANDLED;
	}

	/* We disable the hardware interrupt here just to prevent unneeded
	 * calls to be made.  We disable this again within the actual
	 * work tasklet, so if another part of the code re-enables the
	 * interrupt, that is fine */
	ipw2100_hw_disable_interrupt(priv);

	tasklet_schedule(&priv->irq_tasklet);

	return IRQ_HANDLED;
}

static int ipw2100_tx(struct sk_buff *skb, struct net_device *dev)
{
	struct ipw2100_priv *priv = dev->priv;
	struct list_head *element;
	struct ipw2100_data_header *header;
	struct ipw2100_tx_packet *packet;
	unsigned long flags;
	int encrypt, ether_type;
	void *ether_type_ptr;

	ether_type_ptr = skb->data + sizeof(struct ethhdr) - sizeof(u16);
	ether_type = ntohs(*(u16 *)(ether_type_ptr));
	encrypt = (priv->ieee.host_encrypt && ether_type != ETH_P_PAE &&
			priv->ieee.crypt && priv->ieee.crypt->ops);

	if (priv->ieee.host_encrypt && ether_type == ETH_P_PAE) {
		printk(KERN_DEBUG "%s: TX: IEEE 802.1X - passing "
				"unencrypted EAPOL frame\n", dev->name);
	}
	
	/* In addition to locking the priv data, this lock will also
	 * lock access to the ieee->tx_queue, which is only modified
	 * by ieee80211_tx.  In this way we don't have to worry about
	 * extra TX packets being written to the queue before we pull
	 * off this SKBs fragments */
	spin_lock_irqsave(&priv->low_lock, flags);

	if (!priv->connected) { 
		/* TODO: check code to ensure that xmit disabled during
		 * any call that results in priv->connected == false */
		IPW2100_DEBUG_INFO("Can not transmit when not connected.\n");
		priv->ieee.stats.tx_carrier_errors++;
		netif_stop_queue(dev);
		goto fail_unlock;
	}

	element = priv->tx_free_list.next;
	packet = list_entry(element, struct ipw2100_tx_packet, list);

	header = packet->data;
	header->host_command_reg = SEND;
	header->host_command_reg1 = 0;
	header->encrypted = encrypt;
	header->needs_encryption = 0;
	header->fragment_size = 0;
	memcpy(header->dst_addr, skb->data, ETH_ALEN);
	memcpy(header->src_addr, skb->data + ETH_ALEN, ETH_ALEN);

	packet->fragments = ieee80211_tx(&priv->ieee, skb);
	if (packet->fragments == -1) {
		IPW2100_DEBUG_DROP("Failed to Tx packet\n");
		priv->ieee.stats.tx_errors++;
		goto fail_unlock;
	} else if (packet->fragments == 0) {
		IPW2100_DEBUG_DROP("No fragments constructed\n");
		priv->ieee.stats.tx_carrier_errors++;
		goto fail_unlock;
	}
	packet->jiffy_start = jiffies;

	list_del(element);
	DEC_STAT(&priv->tx_free_stat);

	list_add_tail(element, &priv->tx_pend_list);
	INC_STAT(&priv->tx_pend_stat);
	
	X__ipw2100_tx_send_data(priv);
	
	spin_unlock_irqrestore(&priv->low_lock, flags);

	IPW2100_DEBUG_INFO("exit\n");

	return 0;

 fail_unlock:
	netif_stop_queue(dev);
	IPW2100_DEBUG_INFO("exit - failed\n");
	spin_unlock_irqrestore(&priv->low_lock, flags);
	return 1;
}



static int ipw2100_msg_allocate(struct ipw2100_priv *priv)
{
	int i, j, err = -EINVAL;
	void *v;
	dma_addr_t p;
	     
	priv->msg_buffers = (struct ipw2100_tx_packet *)kmalloc(
		IPW_COMMAND_POOL_SIZE * sizeof(struct ipw2100_tx_packet), 
		GFP_KERNEL);
	if (!priv->msg_buffers) {
		printk(KERN_ERR "%s: PCI alloc failed for msg "
		       "buffers.\n", priv->ndev->name);
		return -ENOMEM;
	}

	for (i = 0; i < IPW_COMMAND_POOL_SIZE; i++) {
		v = pci_alloc_consistent(
			priv->pdev,
			sizeof(struct ipw2100_cmd_header), 
			&p);
		if (!v) {
			printk(KERN_ERR 
			       "%s: PCI alloc failed for msg "
			       "buffers.\n",
			       priv->ndev->name);
			err = -ENOMEM;
			break;
		} 

		memset(v, 0, sizeof(struct ipw2100_cmd_header));
		
		priv->msg_buffers[i].type = COMMAND;
		priv->msg_buffers[i].cmd = 
			(struct ipw2100_cmd_header*)v;
		priv->msg_buffers[i].cmd_phys = p;
	}

	if (i == IPW_COMMAND_POOL_SIZE)
		return 0;

	for (j = 0; j < i; j++) {
		pci_free_consistent(
			priv->pdev,
			sizeof(struct ipw2100_cmd_header), 
			priv->msg_buffers[j].cmd,
			priv->msg_buffers[j].cmd_phys);
	}
	
	kfree(priv->msg_buffers);
	priv->msg_buffers = NULL;

	return err;
}

static int ipw2100_msg_initialize(struct ipw2100_priv *priv)
{
	int i;

	INIT_LIST_HEAD(&priv->msg_free_list);
	INIT_LIST_HEAD(&priv->msg_pend_list);

	for (i = 0; i < IPW_COMMAND_POOL_SIZE; i++) 
		list_add_tail(&priv->msg_buffers[i].list, &priv->msg_free_list);
	SET_STAT(&priv->msg_free_stat, i);

	return 0;
}

static void ipw2100_msg_free(struct ipw2100_priv *priv)
{
	int i;

	if (!priv->msg_buffers) 
		return;

	for (i = 0; i < IPW_COMMAND_POOL_SIZE; i++) {
		pci_free_consistent(priv->pdev,
				    sizeof(struct ipw2100_cmd_header),
				    priv->msg_buffers[i].cmd,
				    priv->msg_buffers[i].cmd_phys);
	}
	
	kfree(priv->msg_buffers);
	priv->msg_buffers = NULL;
}

static struct proc_dir_entry *ipw2100_proc = NULL;

static int proc_get_pci(char *page, char **start,
		       off_t offset, int count,
		       int *eof, void *data)
{
	struct net_device *dev = (struct net_device *)data;
	struct ipw2100_priv *priv = dev->priv;
	struct pci_dev *pdev = priv->pdev;
	int i, j, len;
	u32 val;

	
	len = 0;

	len += snprintf(page + len, count - len, "PCI Configuration Data\n");
	
	for (i = 0; i < 16; i++) {
		len += snprintf(page + len, count - len, "[%08X] ", i * 16);
		for (j = 0; j < 16; j += 4) {
			pci_read_config_dword(pdev, i * 16 + j, &val); 
			len += snprintf(page + len, count - len, "%08X ", val);
		}
		len += snprintf(page + len, count - len, "\n");
	}

	*eof = 1;
	return len;
}


#define IPW2100_REG(x) { IPW_ ##x, #x }
const struct {
	u32 addr;
	const char *name;
} hw_data[] = {
	IPW2100_REG(REG_GP_CNTRL),
	IPW2100_REG(REG_GPIO),
	IPW2100_REG(REG_INTA),
	IPW2100_REG(REG_INTA_MASK),
	IPW2100_REG(REG_RESET_REG),
};
#define IPW2100_NIC(x, s) { x, #x, s }
const struct {
	u32 addr;
	const char *name;
	size_t size;
} nic_data[] = {
	IPW2100_NIC(IPW2100_CONTROL_REG, 2),
	IPW2100_NIC(0x210014, 1),
	IPW2100_NIC(0x210000, 1),
};
#define IPW2100_ORD(x, d) { IPW_ORD_ ##x, #x, d }
const struct {
	u8 index;
	const char *name;
	const char *desc;
} ord_data[] = {
	IPW2100_ORD(STAT_TX_HOST_REQUESTS, "requested Host Tx's (MSDU)"),
	IPW2100_ORD(STAT_TX_HOST_COMPLETE, "successful Host Tx's (MSDU)"),
	IPW2100_ORD(STAT_TX_DIR_DATA,	   "successful Directed Tx's (MSDU)"),
	IPW2100_ORD(STAT_TX_DIR_DATA1,	   "successful Directed Tx's (MSDU) @ 1MB"),
	IPW2100_ORD(STAT_TX_DIR_DATA2,	   "successful Directed Tx's (MSDU) @ 2MB"),
	IPW2100_ORD(STAT_TX_DIR_DATA5_5,   "successful Directed Tx's (MSDU) @ 5_5MB"),
	IPW2100_ORD(STAT_TX_DIR_DATA11,	   "successful Directed Tx's (MSDU) @ 11MB"),
	IPW2100_ORD(STAT_TX_NODIR_DATA1,   "successful Non_Directed Tx's (MSDU) @ 1MB"),
	IPW2100_ORD(STAT_TX_NODIR_DATA2,   "successful Non_Directed Tx's (MSDU) @ 2MB"),
	IPW2100_ORD(STAT_TX_NODIR_DATA5_5, "successful Non_Directed Tx's (MSDU) @ 5.5MB"),
	IPW2100_ORD(STAT_TX_NODIR_DATA11,  "successful Non_Directed Tx's (MSDU) @ 11MB"),
	IPW2100_ORD(STAT_NULL_DATA,	   "successful NULL data Tx's"),
	IPW2100_ORD(STAT_TX_RTS,	   "successful Tx RTS"),
	IPW2100_ORD(STAT_TX_CTS,	   "successful Tx CTS"),         
	IPW2100_ORD(STAT_TX_ACK,	   "successful Tx ACK"),
	IPW2100_ORD(STAT_TX_ASSN,	   "successful Association Tx's"),               
	IPW2100_ORD(STAT_TX_ASSN_RESP,	   "successful Association response Tx's"),   
	IPW2100_ORD(STAT_TX_REASSN,	   "successful Reassociation Tx's"),           
	IPW2100_ORD(STAT_TX_REASSN_RESP,   "successful Reassociation response Tx's"),
	IPW2100_ORD(STAT_TX_PROBE,	   "probes successfully transmitted"), 
	IPW2100_ORD(STAT_TX_PROBE_RESP,	   "probe responses successfully transmitted"),                
	IPW2100_ORD(STAT_TX_BEACON,	   "tx beacon"),             
	IPW2100_ORD(STAT_TX_ATIM,	   "Tx ATIM"),            
	IPW2100_ORD(STAT_TX_DISASSN,	   "successful Disassociation TX"),           
	IPW2100_ORD(STAT_TX_AUTH,	   "successful Authentication Tx"),                 
	IPW2100_ORD(STAT_TX_DEAUTH,	   "successful Deauthentication TX"),                
	IPW2100_ORD(STAT_TX_TOTAL_BYTES,   "Total successful Tx data bytes"),
	IPW2100_ORD(STAT_TX_RETRIES,       "Tx retries"),
	IPW2100_ORD(STAT_TX_RETRY1,        "Tx retries at 1MBPS"),
	IPW2100_ORD(STAT_TX_RETRY2,        "Tx retries at 2MBPS"),
	IPW2100_ORD(STAT_TX_RETRY5_5,	   "Tx retries at 5.5MBPS"),
	IPW2100_ORD(STAT_TX_RETRY11,	   "Tx retries at 11MBPS"),
	IPW2100_ORD(STAT_TX_FAILURES,	   "Tx Failures"),
	IPW2100_ORD(STAT_TX_MAX_TRIES_IN_HOP,"times max tries in a hop failed"),
	IPW2100_ORD(STAT_TX_DISASSN_FAIL,	"times disassociation failed"),
	IPW2100_ORD(STAT_TX_ERR_CTS,         "missed/bad CTS frames"),
	IPW2100_ORD(STAT_TX_ERR_ACK,	"tx err due to acks"),
	IPW2100_ORD(STAT_RX_HOST,	"packets passed to host"),
	IPW2100_ORD(STAT_RX_DIR_DATA,	"directed packets"),                
	IPW2100_ORD(STAT_RX_DIR_DATA1,	"directed packets at 1MB"),            
	IPW2100_ORD(STAT_RX_DIR_DATA2,	"directed packets at 2MB"),           
	IPW2100_ORD(STAT_RX_DIR_DATA5_5,	"directed packets at 5.5MB"),            
	IPW2100_ORD(STAT_RX_DIR_DATA11,	"directed packets at 11MB"),            
	IPW2100_ORD(STAT_RX_NODIR_DATA,"nondirected packets"),        
	IPW2100_ORD(STAT_RX_NODIR_DATA1,	"nondirected packets at 1MB"),            
	IPW2100_ORD(STAT_RX_NODIR_DATA2,	"nondirected packets at 2MB"),           
	IPW2100_ORD(STAT_RX_NODIR_DATA5_5,	"nondirected packets at 5.5MB"),            
	IPW2100_ORD(STAT_RX_NODIR_DATA11,	"nondirected packets at 11MB"),            
	IPW2100_ORD(STAT_RX_NULL_DATA,	"null data rx's"),            
	IPW2100_ORD(STAT_RX_RTS,	"Rx RTS"),                
	IPW2100_ORD(STAT_RX_CTS,	"Rx CTS"),                
	IPW2100_ORD(STAT_RX_ACK,	"Rx ACK"),                
	IPW2100_ORD(STAT_RX_CFEND,	"Rx CF End"),                
	IPW2100_ORD(STAT_RX_CFEND_ACK,	"Rx CF End + CF Ack"),             
	IPW2100_ORD(STAT_RX_ASSN,	"Association Rx's"),                    
	IPW2100_ORD(STAT_RX_ASSN_RESP,	"Association response Rx's"),            
	IPW2100_ORD(STAT_RX_REASSN,	"Reassociation Rx's"),            
	IPW2100_ORD(STAT_RX_REASSN_RESP,	"Reassociation response Rx's"),            
	IPW2100_ORD(STAT_RX_PROBE,	"probe Rx's"),                
	IPW2100_ORD(STAT_RX_PROBE_RESP,	"probe response Rx's"),            
	IPW2100_ORD(STAT_RX_BEACON,	"Rx beacon"),            
	IPW2100_ORD(STAT_RX_ATIM,	"Rx ATIM"),                    
	IPW2100_ORD(STAT_RX_DISASSN,	"disassociation Rx"),            
	IPW2100_ORD(STAT_RX_AUTH,	"authentication Rx"),                    
	IPW2100_ORD(STAT_RX_DEAUTH,	"deauthentication Rx"),            
	IPW2100_ORD(STAT_RX_TOTAL_BYTES,"Total rx data bytes received"),
	IPW2100_ORD(STAT_RX_ERR_CRC,	 "packets with Rx CRC error"),
	IPW2100_ORD(STAT_RX_ERR_CRC1,	 "Rx CRC errors at 1MB"),
	IPW2100_ORD(STAT_RX_ERR_CRC2,	 "Rx CRC errors at 2MB"),
	IPW2100_ORD(STAT_RX_ERR_CRC5_5,	 "Rx CRC errors at 5.5MB"),
	IPW2100_ORD(STAT_RX_ERR_CRC11,	 "Rx CRC errors at 11MB"),
	IPW2100_ORD(STAT_RX_DUPLICATE1, "duplicate rx packets at 1MB"),
	IPW2100_ORD(STAT_RX_DUPLICATE2,	 "duplicate rx packets at 2MB"),
	IPW2100_ORD(STAT_RX_DUPLICATE5_5,	 "duplicate rx packets at 5.5MB"),
	IPW2100_ORD(STAT_RX_DUPLICATE11,	 "duplicate rx packets at 11MB"),
	IPW2100_ORD(STAT_RX_DUPLICATE, "duplicate rx packets"),            
	IPW2100_ORD(PERS_DB_LOCK,	"locking fw permanent  db"),
	IPW2100_ORD(PERS_DB_SIZE,	"size of fw permanent  db"),           
	IPW2100_ORD(PERS_DB_ADDR,	"address of fw permanent  db"),           
	IPW2100_ORD(STAT_RX_INVALID_PROTOCOL,	"rx frames with invalid protocol"),        
	IPW2100_ORD(SYS_BOOT_TIME,	"Boot time"),
	IPW2100_ORD(STAT_RX_NO_BUFFER,	"rx frames rejected due to no buffer"), 
	IPW2100_ORD(STAT_RX_MISSING_FRAG,	"rx frames dropped due to missing fragment"),            
	IPW2100_ORD(STAT_RX_ORPHAN_FRAG,	"rx frames dropped due to non-sequential fragment"),        
	IPW2100_ORD(STAT_RX_ORPHAN_FRAME,	"rx frames dropped due to unmatched 1st frame"),            
	IPW2100_ORD(STAT_RX_FRAG_AGEOUT,	"rx frames dropped due to uncompleted frame"),        
	IPW2100_ORD(STAT_RX_ICV_ERRORS,	"ICV errors during decryption"),        
	IPW2100_ORD(STAT_PSP_SUSPENSION,"times adapter suspended"),        
	IPW2100_ORD(STAT_PSP_BCN_TIMEOUT,	"beacon timeout"),           
	IPW2100_ORD(STAT_PSP_POLL_TIMEOUT,	"poll response timeouts"),       
	IPW2100_ORD(STAT_PSP_NONDIR_TIMEOUT, "timeouts waiting for last {broad,multi}cast pkt"),
	IPW2100_ORD(STAT_PSP_RX_DTIMS,	"PSP DTIMs received"),
	IPW2100_ORD(STAT_PSP_RX_TIMS,	"PSP TIMs received"),
	IPW2100_ORD(STAT_PSP_STATION_ID,"PSP Station ID"),
	IPW2100_ORD(LAST_ASSN_TIME,	"RTC time of last association"),               
	IPW2100_ORD(STAT_PERCENT_MISSED_BCNS,"current calculation of % missed beacons"),       
	IPW2100_ORD(STAT_PERCENT_RETRIES,"current calculation of % missed tx retries"),            
	IPW2100_ORD(ASSOCIATED_AP_PTR,	"0 if not associated, else pointer to AP table entry"),
	IPW2100_ORD(AVAILABLE_AP_CNT,	"AP's decsribed in the AP table"),
	IPW2100_ORD(AP_LIST_PTR,	"Ptr to list of available APs"),
	IPW2100_ORD(STAT_AP_ASSNS,	"associations"),
	IPW2100_ORD(STAT_ASSN_FAIL,	"association failures"),       
	IPW2100_ORD(STAT_ASSN_RESP_FAIL,"failures due to response fail"),       
	IPW2100_ORD(STAT_FULL_SCANS,	"full scans"),
	IPW2100_ORD(CARD_DISABLED,	"Card Disabled"),
	IPW2100_ORD(STAT_ROAM_INHIBIT,	"times roaming was inhibited due to activity"),       
	IPW2100_ORD(RSSI_AT_ASSN,	"RSSI of associated AP at time of association"),           
	IPW2100_ORD(STAT_ASSN_CAUSE1,	"reassociation: no probe response or TX on hop"),
	IPW2100_ORD(STAT_ASSN_CAUSE2,	"reassociation: poor tx/rx quality"),            
	IPW2100_ORD(STAT_ASSN_CAUSE3,	"reassociation: tx/rx quality (excessive AP load"),
	IPW2100_ORD(STAT_ASSN_CAUSE4,	"reassociation: AP RSSI level"),
	IPW2100_ORD(STAT_ASSN_CAUSE5,	"reassociations due to load leveling"),            
	IPW2100_ORD(STAT_AUTH_FAIL,	"times authentication failed"),        
	IPW2100_ORD(STAT_AUTH_RESP_FAIL,"times authentication response failed"),       
	IPW2100_ORD(STATION_TABLE_CNT,	"entries in association table"),        
	IPW2100_ORD(RSSI_AVG_CURR,	"Current avg RSSI"),
	IPW2100_ORD(POWER_MGMT_MODE,	"Power mode - 0=CAM, 1=PSP"),       
	IPW2100_ORD(COUNTRY_CODE,	"IEEE country code as recv'd from beacon"),
	IPW2100_ORD(COUNTRY_CHANNELS,	"channels suported by country"),           
	IPW2100_ORD(RESET_CNT,	"adapter resets (warm)"),       
	IPW2100_ORD(BEACON_INTERVAL,	"Beacon interval"),       
	IPW2100_ORD(ANTENNA_DIVERSITY,	"TRUE if antenna diversity is disabled"),       
	IPW2100_ORD(DTIM_PERIOD,	"beacon intervals between DTIMs"),       
	IPW2100_ORD(OUR_FREQ,	"current radio freq lower digits - channel ID"),
	IPW2100_ORD(RTC_TIME,	"current RTC time"),           
	IPW2100_ORD(PORT_TYPE,	"operating mode"),       
	IPW2100_ORD(CURRENT_TX_RATE,	"current tx rate"),
	IPW2100_ORD(SUPPORTED_RATES,	"supported tx rates"),       
	IPW2100_ORD(ATIM_WINDOW,	"current ATIM Window"),
	IPW2100_ORD(BASIC_RATES,	"basic tx rates"),       
	IPW2100_ORD(NIC_HIGHEST_RATE,	"NIC highest tx rate"),
	IPW2100_ORD(AP_HIGHEST_RATE,	"AP highest tx rate"),
	IPW2100_ORD(CAPABILITIES,	"Management frame capability field"),
	IPW2100_ORD(AUTH_TYPE,	"Type of authentication"),       
	IPW2100_ORD(RADIO_TYPE,	"Adapter card platform type"),       
	IPW2100_ORD(RTS_THRESHOLD,	"Min packet length for RTS handshaking"),       
	IPW2100_ORD(INT_MODE,	"International mode"),           
	IPW2100_ORD(FRAGMENTATION_THRESHOLD,	"protocol frag threshold"),       
	IPW2100_ORD(EEPROM_SRAM_DB_BLOCK_START_ADDRESS,	"EEPROM offset in SRAM"),
	IPW2100_ORD(EEPROM_SRAM_DB_BLOCK_SIZE,	"EEPROM size in SRAM"),
	IPW2100_ORD(EEPROM_SKU_CAPABILITY,	"EEPROM SKU Capability"),
	IPW2100_ORD(EEPROM_IBSS_11B_CHANNELS,	"EEPROM IBSS 11b channel set"),       
	IPW2100_ORD(MAC_VERSION,	"MAC Version"),       
	IPW2100_ORD(MAC_REVISION,	"MAC Revision"),       
	IPW2100_ORD(RADIO_VERSION,	"Radio Version"),       
	IPW2100_ORD(NIC_MANF_DATE_TIME,	"MANF Date/Time STAMP"),
	IPW2100_ORD(UCODE_VERSION,	"Ucode Version"),       
};


static int proc_get_registers(char *page, char **start,
		       off_t offset, int count,
		       int *eof, void *data)
{
	struct net_device *dev = (struct net_device *)data;
	int i;
	int len = 0;
	u32 val = 0;

	len += snprintf(page + len, count - len, 
			"%30s [Address ] : Hex\n", "Register");
	
	for (i = 0; 
	     i < (sizeof(hw_data) / sizeof(*hw_data)); 
	     i++) {
		read_register(dev, hw_data[i].addr, &val);

		len += snprintf(page + len, count - len, 
				"%30s [%08X] : %08X\n",
				hw_data[i].name, hw_data[i].addr,
				val);
	}

	*eof = 1;
	return len;
}


static int proc_get_hw(char *page, char **start,
		       off_t offset, int count,
		       int *eof, void *data)
{
	struct net_device *dev = (struct net_device *)data;
	int i;
	int len = 0;

	len += snprintf(page + len, count - len, 
			"%30s [Address ] : Hex\n", "NIC entry");
	
	for (i = 0; 
	     i < (sizeof(nic_data) / sizeof(*nic_data)); 
	     i++) {
		u8 tmp8;
		u16 tmp16;
		u32 tmp32;
		
		switch (nic_data[i].size) {
		case 1:
			read_nic_byte(dev, nic_data[i].addr, &tmp8);
			len += snprintf(page + len, count - len, 
					"%30s [%08X] : %02X\n",
					nic_data[i].name, nic_data[i].addr,
					tmp8);
			break;
		case 2:
			read_nic_word(dev, nic_data[i].addr, &tmp16);
			len += snprintf(page + len, count - len, 
					"%30s [%08X] : %04X\n",
					nic_data[i].name, nic_data[i].addr,
					tmp16);
			break;
		case 4:
			read_nic_dword(dev, nic_data[i].addr, &tmp32);
			len += snprintf(page + len, count - len, 
					"%30s [%08X] : %08X\n",
					nic_data[i].name, nic_data[i].addr,
					tmp32);
			break;
		}
	}
	*eof = 1;
	return len;
}


static int proc_set_memory(struct file *file, const char *buffer,
			   unsigned long count, void *data)
{
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	char buf[] = "0000";
	unsigned long len = (sizeof(buf) - 1) > count ? count : 
		sizeof(buf) - 1;
	char *p = buf;

	if (copy_from_user(buf, buffer, len)) {
		IPW2100_DEBUG_INFO("can't copy data from userspace\n");
                /* stupid? yes, but how do I signal an error here? */
		return count;	
	} else
		buf[len] = 0;
	
	if (p[0] == '1' || (tolower(p[0]) == 'o' && tolower(p[1] == 'n'))) {
		printk(KERN_INFO "%s: Setting memory dump to RAW mode.\n",
		       dev->name);
		priv->dump_raw = 1;
	} else if (p[0] == '0' || 
		   (tolower(p[0]) == 'o' && tolower(p[1] == 'f'))) {
		printk(KERN_INFO "%s: Setting memory dump to HEX mode.\n",
		       dev->name);
		priv->dump_raw = 0;
	} else if (tolower(p[0]) == 'r') {
		printk(KERN_INFO "%s: Resetting firmware snapshot.\n",
		       dev->name);
		ipw2100_snapshot_free(priv);
	} else 
		printk(KERN_INFO "%s: Usage: 0|on = HEX, 1|off = RAW, "
		       "reset = clear memory snapshot\n",
		       dev->name);

	return count;
}

static int proc_get_memory(char *page, char **start,
			   off_t offset, int count,
			   int *eof, void *data)
{
	struct net_device *dev = (struct net_device *)data;
	struct ipw2100_priv *priv = dev->priv;
	int len = 0;
	static unsigned long loop = 0;
	u32 buf[4];
	int i;
	char line[81];

	if (offset == 0)
		loop = 0;

	/* If we've reached EOF or the user is cutting is short, then
	 * restart the counter and return 0 bytes */
	if (loop >= 0x30000) {
		*start = NULL;
		*eof = 1;
		return 0;
	}

	/* Return around 2k per pass... */
	while (count - len > 256 && 
	       len < 2048 && 
	       loop < 0x30000) {
		
		if (priv->snapshot[0]) for (i = 0; i < 4; i++) 
			buf[i] = *(u32 *)SNAPSHOT_ADDR(loop + i * 4);
		else for (i = 0; i < 4; i++) 
			read_nic_dword(dev, loop + i * 4, &buf[i]);
		
		if (priv->dump_raw) 
			len += snprintf(page + len, count - len, 
					"%c%c%c%c"
					"%c%c%c%c"
					"%c%c%c%c"
					"%c%c%c%c",
					((u8*)buf)[0x0], 
					((u8*)buf)[0x1], 
					((u8*)buf)[0x2], 
					((u8*)buf)[0x3], 
					((u8*)buf)[0x4], 
					((u8*)buf)[0x5], 
					((u8*)buf)[0x6], 
					((u8*)buf)[0x7], 
					((u8*)buf)[0x8], 
					((u8*)buf)[0x9], 
					((u8*)buf)[0xa], 
					((u8*)buf)[0xb], 
					((u8*)buf)[0xc], 
					((u8*)buf)[0xd], 
					((u8*)buf)[0xe], 
					((u8*)buf)[0xf]);
		else
			len += snprintf(page + len, count - len, "%s\n",
					snprint_line(
						line, sizeof(line), (u8*)buf, 
						16, loop));

		loop += 16;
	}
	
	/* see comment in fs/proc/generic.c proc_file_read */
	if (len) 
		*start = (char*)len; 
	else
		*eof = 1;

	return len;
}


static int proc_get_ordinals(char *page, char **start,
			     off_t offset, int count,
			     int *eof, void *data)
{
	struct net_device *dev = (struct net_device *)data;
	struct ipw2100_priv *priv = dev->priv;
	int len = 0;
	u32 val_len;
	u32 val = 0;
	static int loop = 0;

	if (offset == 0)
		loop = 0;

	/* If we've reached EOF or the user is cutting is short, then
	 * restart the counter and return 0 bytes */
	if (loop >= (sizeof(ord_data) / sizeof(*ord_data))) {
		*start = NULL;
		*eof = 1;
		return 0;
	}

	/* Return around 2k per pass... */
	while (count - len > 256 && 
	       len < 2048 && 
	       loop < (sizeof(ord_data) / sizeof(*ord_data))) {
		
		val_len = sizeof(u32);

		if (ipw2100_get_ordinal(priv, ord_data[loop].index, &val, 
					&val_len)) 
			len += snprintf(page + len, count - len,
					"[0x%02X] = ERROR    %s\n",
					ord_data[loop].index,
					ord_data[loop].desc);
		else
			len += snprintf(page + len, count - len,
					"[0x%02X] = 0x%08X %s\n",
					ord_data[loop].index, 
					val, ord_data[loop].desc);

		loop++;
	}
	
	/* see comment in fs/proc/generic.c proc_file_read */
	if (len) 
		*start = (char*)len; 
	else
		*eof = 1;

	return len;
}

static int proc_get_version(char *page, char **start,
			    off_t offset, int count,
			    int *eof, void *data)
{
	struct net_device *dev = (struct net_device *)data;
	struct ipw2100_priv *priv = dev->priv;
	int err;
	int len = 0;
	char tmp[MAX_FW_VERSION_LEN];

	len += snprintf(page + len, count - len, 
			"hardware    : 0x%04X\n", 
			priv->pdev->subsystem_device);

#ifdef CONFIG_IPW2100_NOWEP
	len += snprintf(page + len, count - len, 
			"driver      : " DRV_VERSION " [NO WEP]\n");
#else
	len += snprintf(page + len, count - len, 
			"driver      : " DRV_VERSION " [WEP]\n");
#endif

	len += snprintf(page + len, count - len,
			"build date  : " __DATE__ "\n");

	len += snprintf(page + len, count - len,
			"build time  : " __TIME__ "\n");

	len += snprintf(page + len, count - len,
			"eeprom      : %d\n", priv->eeprom_version);
	err = ipw2100_get_ucodeversion(priv, tmp, sizeof(tmp));
	if (err < 0) 
		len += snprintf(page + len, count - len,
				"ucode       : error\n");
	else 
		len += snprintf(page + len, count - len,
				"ucode       : %s\n", tmp);
	err = ipw2100_get_fwversion(priv, tmp, sizeof(tmp));
	if (err < 0) 
		len += snprintf(page + len, count - len,
				"firmware    : error\n");
	else 
		len += snprintf(page + len, count - len,
				"firmware    : %s\n", tmp);

	len += snprintf(page + len, count - len,
			"firmware img: 0x%04X\n", priv->firmware_version);

	*eof = 1;
	return len;
}

static int proc_get_stats(char *page, char **start,
			  off_t offset, int count,
			  int *eof, void *data)
{
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	int len = 0;

	len += snprintf(page + len, count - len,
			"interrupts: %d {tx: %d, rx: %d, other: %d}\n", 
			priv->interrupts, priv->tx_interrupts,
			priv->rx_interrupts, priv->inta_other);
	len += snprintf(page + len, count - len,
			"firmware resets: %d\n", priv->resets);
	len += snprintf(page + len, count - len,
			"firmware hangs: %d\n", priv->hangs);
#ifdef CONFIG_IPW2100_DEBUG
	len += snprintf(page + len, count - len,
			"packet mismatch image: %s\n",
			priv->snapshot[0] ? "YES" : "NO");
#endif

	*eof = 1;
	return len;
}

static int proc_get_internals(char *page, char **start,
			      off_t offset, int count,
			      int *eof, void *data)
{
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	int len = 0;

#define DUMP_VAR(x,y) \
        len += snprintf(page + len, count - len, \
			# x ": %" # y "\n", priv-> x)

	DUMP_VAR(connected, d);
	DUMP_VAR(connected ? get_seconds() - priv->connect_start : 0, lu);
	DUMP_VAR(ieee.crypt, p);
	DUMP_VAR(reset_pending, d);
	DUMP_VAR(stopped, d);
	DUMP_VAR(disabled, d);
	DUMP_VAR(phy_off, d);
	DUMP_VAR(been_powered, d);
	DUMP_VAR(fatal_error, d);
	DUMP_VAR(rf_kill, d);
	DUMP_VAR(stop_hang_check, d);
	DUMP_VAR(stop_rf_check, d);
	DUMP_VAR(message_sending, d);
	DUMP_VAR(messages_sent, d);

	DUMP_VAR(tx_pend_stat.value, d);
	DUMP_VAR(tx_pend_stat.hi, d);

	DUMP_VAR(tx_free_stat.value, d);
	DUMP_VAR(tx_free_stat.lo, d);

	DUMP_VAR(msg_free_stat.value, d);
	DUMP_VAR(msg_free_stat.lo, d);

	DUMP_VAR(msg_pend_stat.value, d);
	DUMP_VAR(msg_pend_stat.hi, d);

	DUMP_VAR(fw_pend_stat.value, d);
	DUMP_VAR(fw_pend_stat.hi, d);

	DUMP_VAR(txq_stat.value, d);
	DUMP_VAR(txq_stat.lo, d);

	DUMP_VAR(scans, d);

	*eof = 1;
	return len;
}

static int proc_get_bssinfo(char *page,
			    char **start,
			    off_t offset,
			    int count, int *eof, void *data)
{
	int len = 0;
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	int ret;
	int length;
	char essid[IW_ESSID_MAX_SIZE + 1];
	u8 bssid[ETH_ALEN];
	u32 chan = 0;

	memset(essid, 0, sizeof(essid));
	memset(bssid, 0, sizeof(bssid));

	length = IW_ESSID_MAX_SIZE;
	ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_SSID, essid, &length);
	if (ret) 
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);

	length = sizeof(bssid);
	ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_AP_BSSID,
				  bssid, &length);
	if (ret)
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);

	length = sizeof(u32);
	ret = ipw2100_get_ordinal(priv, IPW_ORD_OUR_FREQ, &chan, &length);
	if (ret) 
		IPW2100_DEBUG_INFO("failed querying ordinals at line %d\n", 
				   __LINE__);

	len += snprintf(page + len, count - len, "ESSID: %s\n", essid);
	len += snprintf(page + len, count - len,
			"BSSID:   %02x:%02x:%02x:%02x:%02x:%02x\n",
			bssid[0], bssid[1], bssid[2],
			bssid[3], bssid[4], bssid[5]);
	len += snprintf(page + len, count - len, "Channel: %d\n", chan);

	*eof = 1;
	return len;
}

static int proc_get_txqueue(char *page,
			    char **start,
			    off_t offset,
			    int count, int *eof, void *data)
{
	int len = 0;
	u32 tbdr_r, tbdr_w;
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;

	IPW2100_DEBUG_INFO("enter\n");

	read_register(dev, IPW_MEM_HOST_SHARED_TX_QUEUE_READ_INDEX(0),
		      &tbdr_r);
	read_register(dev, IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX(0),
		      &tbdr_w);

	IPW2100_DEBUG_INFO("after register read\n");

	len += snprintf(page, count,
			"Tx Queue\nnic:\n\tread  index=%d\n\twrite index=%d\n",
			tbdr_r, tbdr_w);
	len += snprintf(page + len, count - len,
			"drv:\n\tread index=%d\n\twrite index=%d\n",
			priv->tx_queue.oldest,
			priv->tx_queue.next);
	*eof = 1;

	IPW2100_DEBUG_INFO("exit\n");

	return len;
}

static int proc_get_rxqueue(char *page,
			    char **start,
			    off_t offset,
			    int count, int *eof, void *data)
{
	int len = 0;
	u32 rbdr_r, rbdr_w;
	struct net_device *dev = (struct net_device *)data;
	struct ipw2100_priv *priv = dev->priv;

	IPW2100_DEBUG_INFO("enter\n");

	read_register(dev, IPW_MEM_HOST_SHARED_RX_READ_INDEX, &rbdr_r);
	read_register(dev, IPW_MEM_HOST_SHARED_RX_WRITE_INDEX, &rbdr_w);

	IPW2100_DEBUG_INFO("after register read\n");

	len += snprintf(page, count,
			"Rx Queue\nnic:\n\tread  index=%d\n\twrite index=%d\n",
			rbdr_r, rbdr_w);
	len += snprintf(page + len, count - len,
			"drv:\n\tread  index=NOT USED\n\twrite index=%d\n",
			priv->rx_queue.next);

	*eof = 1;

	IPW2100_DEBUG_INFO("exit\n");

	return len;
}


#ifdef CONFIG_IPW2100_DEBUG
static int proc_get_debug_level(char *page, char **start, off_t offset,
				int count, int *eof, void *data)
{
	int len = 0;
	int i;
	len += snprintf(page + len, count - len, 
			"%-25s\tHex        SET Decimal\n",
			"Description");
	for (i = 0; i < sizeof(ipw2100_debug_levels) / 
		     sizeof(struct ipw2100_dl); i++) {
		len += snprintf(
			page + len, count - len, "%-25s\t0x%08lX [%c] %lu\n",
			ipw2100_debug_levels[i].name,
			ipw2100_debug_levels[i].value,
			(ipw2100_debug_level & ipw2100_debug_levels[i].value) ?
			'*' : ' ',
			ipw2100_debug_levels[i].value);
	}
	
	len += snprintf(page + len, count - len, 
			"debug_level = 0x%08lX (* = enabled)\n", 
			ipw2100_debug_level);

	*eof = 1;
	return len;
}

static int proc_set_debug_level(struct file *file, const char *buffer,
				unsigned long count, void *data)
{
	char buf[] = "0x00000000";
	unsigned long len =
	    (sizeof(buf) - 1) > count ? count : sizeof(buf) - 1;
	unsigned long val;
	char *p = buf;
	int i;

	if (copy_from_user(buf, buffer, len)) {
		IPW2100_DEBUG_INFO("can't copy data from userspace\n");
		return count;	
	} else
		buf[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 == buf) 
		printk(KERN_INFO DRV_NAME ": %s is not in hex [0x] or "
		       "decimal form.\n", buf);
	else {
		printk(KERN_INFO DRV_NAME ": Debug level set to 0x%08lX\n", 
		       val);
		ipw2100_debug_level = val;

		for (i = 0; i < sizeof(ipw2100_debug_levels) / 
			     sizeof(struct ipw2100_dl); i++) {
			if ((ipw2100_debug_level & 
			     ipw2100_debug_levels[i].value) ==
			    ipw2100_debug_levels[i].value)
				printk(KERN_INFO DRV_NAME 
				       ": tracing enabled for %-25s (%08lX).\n",
				       ipw2100_debug_levels[i].name,
				       ipw2100_debug_levels[i].value);
		}
	}

	return count;
}
#endif /* CONFIG_IPW2100_DEBUG */

static int proc_get_fatal_error(char *page, char **start, off_t offset,
				int count, int *eof, void *data)
{
	struct net_device *dev = data;
	struct ipw2100_priv *priv = dev->priv;
	int len = 0;
	int i;
	int errors = 0;

	if (priv->fatal_error) 
		len += snprintf(page + len, count - len,
				"Current error: 0x%08X\n", priv->fatal_error);
	else 
		len += snprintf(page + len, count - len,
				"No current error condition.\n");

	for (i = 1; i <= IPW2100_ERROR_QUEUE; i++) {
		if (!priv->fatal_errors[(priv->fatal_index - i) % 
					IPW2100_ERROR_QUEUE]) 
			continue;

		if (!errors)
			len += snprintf(page + len, count - len,
					"Up to last %d errors:\n",
					IPW2100_ERROR_QUEUE);
		len += snprintf(page + len, count - len,
				"%d. Error: 0x%08X\n", i,
				priv->fatal_errors[(priv->fatal_index - i) %
						   IPW2100_ERROR_QUEUE]);
		errors++;
	}

	if (!errors)
		len += snprintf(page + len, count - len,
				"No errors encountered.\n");

#ifdef CONFIG_IPW2100_DEBUG
	len += snprintf(page + len, count - len,
			"Packet mismatch image: %s\n",
			priv->snapshot[0] ? "YES" : "NO");
#endif


	if (priv->fatal_error)
		len += snprintf(page + len, count - len,
				"`echo 0 > fatal_error` to force firmware "
				"restart and clear current error "
				"condition.\n");
		
	*eof = 1;
	return len;
}

static int proc_set_fatal_error(struct file *file, const char *buffer,
				unsigned long count, void *data)
{
	struct net_device *dev = data;
	struct ipw2100_priv *priv = dev->priv;
	schedule_reset(priv);
	return count;
}

static int proc_get_cardmem(char *page,
			    char **start,
			    off_t offset,
			    int count, int *eof, void *data)
{
	int len = 0;
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	u32 d;

	read_nic_dword(dev, priv->proc_cardmemaddr, &d);

	len += snprintf(page, count,
			"cardmem addr[0x%08x] = 0x%08x (%d)\n",
			priv->proc_cardmemaddr, d, d);

	*eof = 1;
	return len;
}

static int proc_set_cardmemaddr(struct file *file, const char
				*buffer,
				unsigned long count, void *data)
{
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	char buf[] = "00000000";
	unsigned long len =
	    (sizeof(buf) - 1) > count ? count : sizeof(buf) - 1;
	u32 val;
	char *p = buf;

	IPW2100_DEBUG_INFO("enter\n");

	if (copy_from_user(buf, buffer, len)) {
		IPW2100_DEBUG_INFO("can't copy data from userspace\n");
                /* stupid? yes, but how do I signal an error here? */
		return count;	
	} else
		buf[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 == buf) {
		printk(KERN_INFO "%s: user supplied invalid address value.\n",
		       dev->name);
	} else {
		priv->proc_cardmemaddr = val;
		IPW2100_DEBUG_INFO("set mem addr = 0x%08x\n", val);
	}
	
	IPW2100_DEBUG_INFO("exit\n");
	return count;
}

static int proc_get_state(char *page,
			  char **start,
			  off_t offset,
			  int count, int *eof, void *data)
{
	int len = 0;
	struct net_device *dev = (struct net_device *) data;
	struct ipw2100_priv *priv = dev->priv;
	
	if (priv->hw_features & HW_FEATURE_RFKILL) 
		len += snprintf(page, count, "Radio is %s by RF switch\n",
				ipw2100_get_rf_switch(priv) ? 
				"disabled" : "enabled");
	else
		len += snprintf(page, count, 
				"Your hardware does not have an RF switch\n");
	
	*eof = 1;
	return len;
}


int ipw2100_proc_dev_init(struct ipw2100_priv *priv)
{
	struct proc_dir_entry *e;

	IPW2100_DEBUG_INFO("enter %s\n", priv->ndev->name);

	priv->dir_dev = create_proc_entry(priv->ndev->name, 
					  S_IFDIR | S_IRUGO | S_IXUGO, 
					  ipw2100_proc);
	if (!priv->dir_dev) {
		printk(KERN_ERR
		       "Unable to initialize /proc/net/ipw2100/%s\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("hw", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_hw, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/hw\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("registers", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_registers, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/registers\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("ordinals", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_ordinals, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/ordinals\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("pci", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_pci, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/pci\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("version", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_version, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/version\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("stats", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_stats, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/stats\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("internals", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_internals, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/internals\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("txqueue", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_txqueue, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/txqueue\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("rxqueue", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_rxqueue, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/rxqueue\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("bssinfo", S_IFREG | S_IRUGO,
				   priv->dir_dev, proc_get_bssinfo, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/bssinfo\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_entry("memory", S_IFREG | S_IRUGO | S_IWUSR,
			      priv->dir_dev);
	if (e) {
		e->read_proc = proc_get_memory;
		e->write_proc = proc_set_memory;
		e->data = priv->ndev;
	} else {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/memory\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_entry("cardmem", S_IFREG | S_IRUGO | S_IWUSR, 
			      priv->dir_dev);
	if (e) {
		e->read_proc = proc_get_cardmem;
		e->write_proc = proc_set_cardmemaddr;
		e->data = priv->ndev;
	} else {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/cardmem\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_entry("fatal_error", S_IFREG | S_IRUGO | S_IWUSR, 
			      priv->dir_dev);
	if (e) {
		e->read_proc = proc_get_fatal_error;
		e->write_proc = proc_set_fatal_error;
		e->data = priv->ndev;
	} else {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/fatal_error\n",
		       priv->ndev->name);
		goto fail;
	}

	e = create_proc_read_entry("state", S_IFREG | S_IRUGO,
			      priv->dir_dev, proc_get_state, priv->ndev);
	if (!e) {
		printk(KERN_ERR
		       "Unable to initialize "
		       "/proc/net/ipw2100/%s/state\n",
		       priv->ndev->name);
		goto fail;
	}

	IPW2100_DEBUG_INFO("exit %s\n", priv->ndev->name);

	return 0;

      fail:
	ipw2100_proc_dev_cleanup(priv);
	IPW2100_DEBUG_INFO("exit on fail %s\n", priv->ndev->name);

	return -ENOMEM;
}

void ipw2100_proc_dev_cleanup(struct ipw2100_priv *priv)
{
	IPW2100_DEBUG_INFO("enter %s\n", priv->ndev->name);

	if (priv->dir_dev) {
		remove_proc_entry("stats", priv->dir_dev);
		remove_proc_entry("internals", priv->dir_dev);
		remove_proc_entry("txqueue", priv->dir_dev);
		remove_proc_entry("rxqueue", priv->dir_dev);
		remove_proc_entry("cardmem", priv->dir_dev);
		remove_proc_entry("bssinfo", priv->dir_dev);
		remove_proc_entry("state", priv->dir_dev);
		remove_proc_entry("version", priv->dir_dev);
		remove_proc_entry("hw", priv->dir_dev);
		remove_proc_entry("registers", priv->dir_dev);
		remove_proc_entry("memory", priv->dir_dev);
		remove_proc_entry("ordinals", priv->dir_dev);
		remove_proc_entry("pci", priv->dir_dev);
		remove_proc_entry("fatal_error", priv->dir_dev);
		remove_proc_entry(priv->ndev->name, ipw2100_proc);
		priv->dir_dev = NULL;
	}

	IPW2100_DEBUG_INFO("exit %s\n", priv->ndev->name);
}

int ipw2100_proc_init(void)
{
	IPW2100_DEBUG_INFO("enter\n");

	ipw2100_proc = create_proc_entry(DRV_NAME, S_IFDIR, proc_net);
	if (ipw2100_proc == NULL) {
		printk(KERN_ERR
		       "Unable to initialise /proc/net/ipw2100.\n");
		goto fail;
	}

#ifdef CONFIG_IPW2100_DEBUG
	{
		struct proc_dir_entry *e;
		e = create_proc_entry("debug_level", S_IFREG | S_IRUGO | S_IWUSR, 
				      ipw2100_proc);
		if (e) {
			e->read_proc = proc_get_debug_level;
			e->write_proc = proc_set_debug_level;
			e->data = NULL;
		} else {
			printk(KERN_ERR
			       "Unable to initialize "
			       "/proc/net/ipw2100/debug_level\n");
			goto fail;
		}
	}
#endif /* CONFIG_IPW2100_DEBUG */

	IPW2100_DEBUG_INFO("exit\n");

	return 0;

 fail:
	ipw2100_proc_cleanup();
	IPW2100_DEBUG_INFO("exit on fail\n");
	
	return -ENOMEM;
}

void ipw2100_proc_cleanup(void)
{
	IPW2100_DEBUG_INFO("enter\n");

	if (ipw2100_proc) {
#ifdef CONFIG_IPW2100_DEBUG
		remove_proc_entry("debug_level", ipw2100_proc);
#endif
		remove_proc_entry(DRV_NAME, proc_net);
		ipw2100_proc = NULL;
	}

	IPW2100_DEBUG_INFO("exit\n");
}


#define RX_QUEUE_LENGTH 256
#define TX_QUEUE_LENGTH 256
#define HW_QUEUE_LENGTH 256

#define TX_PENDED_QUEUE_LENGTH (TX_QUEUE_LENGTH / NUMBER_OF_BD_PER_DATA_PACKET)


static int status_queue_allocate(struct ipw2100_priv *priv, int entries)
{
	struct ipw2100_status_queue *q = &priv->status_queue;

	IPW2100_DEBUG_INFO("enter\n");

	q->size = entries * sizeof(struct ipw2100_status);
	q->drv = (struct ipw2100_status *)pci_alloc_consistent(
		priv->pdev, q->size, &q->nic);
	if (!q->drv) {
		printk(KERN_WARNING 
		       "Can not allocate status queue.\n");
		return -ENOMEM;
	}

	memset(q->drv, 0, q->size);

	IPW2100_DEBUG_INFO("exit\n");

	return 0;
}

static void status_queue_free(struct ipw2100_priv *priv)
{
	IPW2100_DEBUG_INFO("enter\n");

	if (priv->status_queue.drv)
		pci_free_consistent(
			priv->pdev, priv->status_queue.size, 
			priv->status_queue.drv, priv->status_queue.nic);

	IPW2100_DEBUG_INFO("exit\n");
}

static int bd_queue_allocate(struct ipw2100_priv *priv,
			     struct ipw2100_bd_queue *q, int entries)
{
	IPW2100_DEBUG_INFO("enter\n");

	memset(q, 0, sizeof(struct ipw2100_bd_queue));

	q->entries = entries;
	q->size = entries * sizeof(struct ipw2100_bd);
	q->drv = pci_alloc_consistent(priv->pdev, q->size, &q->nic);
	if (!q->drv) {
		IPW2100_DEBUG_INFO("can't allocate shared memory for "
		       "buffer descriptors\n");
		return -ENOMEM;
	}
	memset(q->drv, 0, q->size);

	IPW2100_DEBUG_INFO("exit\n");

	return 0;
}

static void bd_queue_free(struct ipw2100_priv *priv,
			  struct ipw2100_bd_queue *q)
{
	IPW2100_DEBUG_INFO("enter\n");

	if (!q)
		return;

	if (q->drv)
		pci_free_consistent(priv->pdev,
				    q->size, q->drv, q->nic);

	IPW2100_DEBUG_INFO("exit\n");
}

static void bd_queue_initialize(
	struct ipw2100_priv *priv, struct ipw2100_bd_queue * q,
	u32 base, u32 size, u32 r, u32 w)
{
	IPW2100_DEBUG_INFO("enter\n");

	IPW2100_DEBUG_INFO("initializing bd queue at virt=%p, phys=%08x\n", q->drv, q->nic);

	write_register(priv->ndev, base, q->nic);
	write_register(priv->ndev, size, q->entries);
	write_register(priv->ndev, r, q->oldest);
	write_register(priv->ndev, w, q->next);

	IPW2100_DEBUG_INFO("exit\n");
}

static int ipw2100_tx_allocate(struct ipw2100_priv *priv)
{
	int i, j, err = -EINVAL;
	void *v;
	dma_addr_t p;

	IPW2100_DEBUG_INFO("enter\n");

	err = bd_queue_allocate(priv, &priv->tx_queue, TX_QUEUE_LENGTH);
	if (err) {
		printk(KERN_ERR "%s: failed bd_queue_allocate\n",
		       priv->ndev->name);
		return err;
	}

	priv->tx_buffers = (struct ipw2100_tx_packet *)kmalloc(
		TX_PENDED_QUEUE_LENGTH * sizeof(struct ipw2100_tx_packet), 
		GFP_KERNEL);
	if (!priv->tx_buffers) {
		printk(KERN_ERR "%s: alloc failed form tx buffers.\n",
		       priv->ndev->name);
		bd_queue_free(priv, &priv->tx_queue);
		return -ENOMEM;
	}

	for (i = 0; i < TX_PENDED_QUEUE_LENGTH; i++) {
		v = pci_alloc_consistent(
			priv->pdev, sizeof(struct ipw2100_data_header), &p);
		if (!v) {
			printk(KERN_ERR "%s: PCI alloc failed for tx "
			       "buffers.\n", priv->ndev->name);
			err = -ENOMEM;
			break;
		}

		priv->tx_buffers[i].type = DATA;
		priv->tx_buffers[i].data = (struct ipw2100_data_header*)v;
		priv->tx_buffers[i].data_phys = p;
		skb_queue_head_init(&priv->tx_buffers[i].skb_queue);
	}
	
	if (i == TX_PENDED_QUEUE_LENGTH)
		return 0;

	for (j = 0; j < i; j++) {
		pci_free_consistent(
			priv->pdev,
			sizeof(struct ipw2100_data_header), 
			priv->tx_buffers[j].data,
			priv->tx_buffers[j].data_phys);
	}
	
	kfree(priv->tx_buffers);
	priv->tx_buffers = NULL;

	return err;
}

static void ipw2100_tx_initialize(struct ipw2100_priv *priv)
{
	int i;

	IPW2100_DEBUG_INFO("enter\n");

	/*
	 * reinitialize packet info lists
	 */
	INIT_LIST_HEAD(&priv->fw_pend_list);
	INIT_STAT(&priv->fw_pend_stat);

	/* 
	 * reinitialize lists
	 */
	INIT_LIST_HEAD(&priv->tx_pend_list);
	INIT_LIST_HEAD(&priv->tx_free_list);
	INIT_STAT(&priv->tx_pend_stat);
	INIT_STAT(&priv->tx_free_stat);

	for (i = 0; i < TX_PENDED_QUEUE_LENGTH; i++) {
		/* We simply drop any SKBs that have been queued for
		 * transmit */
		skb_queue_purge(&priv->tx_buffers[i].skb_queue);
		list_add_tail(&priv->tx_buffers[i].list, &priv->tx_free_list);
	}

	SET_STAT(&priv->tx_free_stat, i);

	priv->tx_queue.oldest = 0;
	priv->tx_queue.available = priv->tx_queue.entries;
	priv->tx_queue.next = 0;
	INIT_STAT(&priv->txq_stat);
	SET_STAT(&priv->txq_stat, priv->tx_queue.available);

	bd_queue_initialize(priv, &priv->tx_queue,
			    IPW_MEM_HOST_SHARED_TX_QUEUE_BD_BASE(0),
			    IPW_MEM_HOST_SHARED_TX_QUEUE_BD_SIZE(0),
			    IPW_MEM_HOST_SHARED_TX_QUEUE_READ_INDEX(0),
			    IPW_MEM_HOST_SHARED_TX_QUEUE_WRITE_INDEX(0));

	IPW2100_DEBUG_INFO("exit\n");

}

static void ipw2100_tx_free(struct ipw2100_priv *priv)
{
	int i;

	IPW2100_DEBUG_INFO("enter\n");

	bd_queue_free(priv, &priv->tx_queue);

	if (!priv->tx_buffers)
		return;

	for (i = 0; i < TX_PENDED_QUEUE_LENGTH; i++) {
		skb_queue_purge(&priv->tx_buffers[i].skb_queue);
		
		if (priv->tx_buffers[i].data)
			pci_free_consistent(
				priv->pdev,
				sizeof(struct ipw2100_data_header),
				priv->tx_buffers[i].data,
				priv->tx_buffers[i].data_phys);
	}
	
	kfree(priv->tx_buffers);
	priv->tx_buffers = NULL;

	IPW2100_DEBUG_INFO("exit\n");
}



static int ipw2100_rx_allocate(struct ipw2100_priv *priv)
{
	int i, j, err = -EINVAL;

	IPW2100_DEBUG_INFO("enter\n");

	err = bd_queue_allocate(priv, &priv->rx_queue, RX_QUEUE_LENGTH);
	if (err) {
		IPW2100_DEBUG_INFO("failed bd_queue_allocate\n");
		return err;
	}

	err = status_queue_allocate(priv, RX_QUEUE_LENGTH);
	if (err) {
		IPW2100_DEBUG_INFO("failed status_queue_allocate\n");
		bd_queue_free(priv, &priv->rx_queue);
		return err;
	}

	/*
	 * allocate packets
	 */
	priv->rx_buffers = (struct ipw2100_rx_packet *)
	    kmalloc(RX_QUEUE_LENGTH * sizeof(struct ipw2100_rx_packet), 
		    GFP_KERNEL);
	if (!priv->rx_buffers) {
		IPW2100_DEBUG_INFO("can't allocate rx packet buffer table\n");

		bd_queue_free(priv, &priv->rx_queue);

		status_queue_free(priv);

		return -ENOMEM;
	}

	for (i = 0; i < RX_QUEUE_LENGTH; i++) {
		struct ipw2100_rx_packet *packet;

		packet = ipw2100_alloc_skb(priv, &priv->rx_buffers[i]);
		if (!packet) {
			err = -ENOMEM;
			break;
		}

		/* The BD holds the cache aligned address */
		priv->rx_queue.drv[i].host_addr = packet->dma_addr;
		priv->rx_queue.drv[i].buf_length = IPW_RX_NIC_BUFFER_LENGTH;
		priv->status_queue.drv[i].status_fields = 0;
	}

	if (i == RX_QUEUE_LENGTH) 
		return 0;

	for (j = 0; j < i; j++) {
		pci_unmap_single(priv->pdev, priv->rx_buffers[j].dma_addr,
				 sizeof(struct ipw2100_rx_packet),
				 PCI_DMA_FROMDEVICE);
		dev_kfree_skb(priv->rx_buffers[j].skb);
	}
	
	kfree(priv->rx_buffers);
	priv->rx_buffers = NULL;

	bd_queue_free(priv, &priv->rx_queue);

	status_queue_free(priv);

	return err;
}

static void ipw2100_rx_initialize(struct ipw2100_priv *priv)
{
	IPW2100_DEBUG_INFO("enter\n");

	priv->rx_queue.oldest = 0;
	priv->rx_queue.available = priv->rx_queue.entries - 1;
	priv->rx_queue.next = priv->rx_queue.entries - 1;

	INIT_STAT(&priv->rxq_stat);
	SET_STAT(&priv->rxq_stat, priv->rx_queue.available);

	bd_queue_initialize(priv, &priv->rx_queue,
			    IPW_MEM_HOST_SHARED_RX_BD_BASE,
			    IPW_MEM_HOST_SHARED_RX_BD_SIZE,
			    IPW_MEM_HOST_SHARED_RX_READ_INDEX,
			    IPW_MEM_HOST_SHARED_RX_WRITE_INDEX);

	/* set up the status queue */
	write_register(priv->ndev, IPW_MEM_HOST_SHARED_RX_STATUS_BASE,
		       priv->status_queue.nic);

	IPW2100_DEBUG_INFO("exit\n");
}

static void ipw2100_rx_free(struct ipw2100_priv *priv)
{
	int i;

	IPW2100_DEBUG_INFO("enter\n");

	bd_queue_free(priv, &priv->rx_queue);
	status_queue_free(priv);

	if (!priv->rx_buffers)
		return;

	for (i = 0; i < RX_QUEUE_LENGTH; i++) {
		if (priv->rx_buffers[i].rxp) {
			pci_unmap_single(priv->pdev, 
					 priv->rx_buffers[i].dma_addr,
					 sizeof(struct ipw2100_rx),
					 PCI_DMA_FROMDEVICE);
			dev_kfree_skb(priv->rx_buffers[i].skb);
		}
	}
	
	kfree(priv->rx_buffers);
	priv->rx_buffers = NULL;

	IPW2100_DEBUG_INFO("exit\n");
}

static int ipw2100_read_mac_address(struct ipw2100_priv *priv)
{
	u32 length = ETH_ALEN;
	u8 mac[ETH_ALEN];

	int err;

	err = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ADAPTER_MAC, 
				  mac, &length);
	if (err) {
		IPW2100_DEBUG_INFO("MAC address read failed\n");
		return -EIO;
	}
	IPW2100_DEBUG_INFO("card MAC is %02X:%02X:%02X:%02X:%02X:%02X\n",
	       mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	memcpy(priv->ndev->dev_addr, mac, ETH_ALEN);

	return 0;
}

/********************************************************************
 *
 * Firmware Commands
 *
 ********************************************************************/
int ipw2100_start_scan(struct ipw2100_priv *priv)
{
	struct host_command cmd = {
		.host_command = BROADCAST_SCAN,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = 0
	};
	int err;

	IPW2100_DEBUG_INFO("enter\n");

	/* Not clearing here; doing so makes iwlist always return nothing... 
	 *
	 * We should modify the table logic to use aging tables vs. clearing 
	 * the table on each scan start.
	 */
	IPW2100_DEBUG_SCAN("starting scan\n");
	err =  ipw2100_hw_send_command(priv, &cmd);

	IPW2100_DEBUG_INFO("exit\n");

	return err;
}

int ipw2100_set_mac_address(struct ipw2100_priv *priv, int batch_mode)
{
	struct host_command cmd = {
		.host_command = ADAPTER_ADDRESS,
		.host_command_sequence = 0,
		.host_command_length = ETH_ALEN
	};
	int err;

	IPW2100_DEBUG_INFO("enter\n");
	memcpy(cmd.host_command_parameters, priv->ndev->dev_addr, ETH_ALEN);

	err = ipw2100_hw_send_command(priv, &cmd);

	IPW2100_DEBUG_INFO("exit\n");
	return err;
}

int ipw2100_set_port_type(struct ipw2100_priv *priv, u32 port_type, 
				 int batch_mode)
{
	struct host_command cmd = {
		.host_command = PORT_TYPE,
		.host_command_sequence = 0,
		.host_command_length = sizeof(u32),
		.host_command_parameters[0] = port_type
	};
	int err;

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) {
			printk(KERN_ERR "%s: Could not disable adapter %d\n",
			       priv->ndev->name, err);
			return err;
		}
	}

	/* send cmd to firmware */
	err = ipw2100_hw_send_command(priv, &cmd);

	/* store if successful */
	if (!err) 
		priv->ctx->port_type = port_type;

	if (!batch_mode) 
		ipw2100_enable_adapter(priv);

	return err;
}


int ipw2100_set_channel(struct ipw2100_priv *priv, u32 channel, int batch_mode)
{
	struct host_command cmd = {
		.host_command = CHANNEL,
		.host_command_sequence = 0,
		.host_command_length = sizeof(channel),
		.host_command_parameters[0] = channel
	};
	int err;

	IPW2100_DEBUG_FW_COMMAND("CHANNEL: %d\n", channel);

	/* If not BSS then we don't support channel selection */
	if (priv->ctx->port_type != IBSS) 
		return 0;

	if ((priv->ctx->channel < REG_MIN_CHANNEL) ||
	    (priv->ctx->channel > REG_MAX_CHANNEL)) 
		return -EINVAL;
		
	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) 
			return err;
	}
	
		
	err = ipw2100_hw_send_command(priv, &cmd);
	if (err) {
		IPW2100_DEBUG_INFO
			("Failed to set channel to %d",
			 channel);
		return err;
	}
	
	priv->ctx->channel = channel;
	
	if (!batch_mode) {
		err = ipw2100_enable_adapter(priv);
		if (err) 
			return err;
	}

	return 0;
}

static int ipw2100_system_config(struct ipw2100_priv *priv, int batch_mode)
{
	struct host_command cmd = {
		.host_command = SYSTEM_CONFIG,
		.host_command_sequence = 0,
		.host_command_length = 12,
	};
	int err;

	/* Set system configuration */

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) 
			return err;
	}

	cmd.host_command_parameters[0] = CFG_BSS_MASK;

	/* 11b only */
	/*cmd.host_command_parameters[0] |= DIVERSITY_ANTENNA_A;*/

	/* long preamble - workaround for a faulty 802.11b radio */
	cmd.host_command_parameters[0] |= CFG_PREAMBLE_LEN;
	cmd.host_command_parameters[0] |= CFG_802_1x_ENABLE;

	cmd.host_command_parameters[1] = REG_CHANNEL_MASK;

	cmd.host_command_parameters[2] = 0;

	err = ipw2100_hw_send_command(priv, &cmd);
	if (err)
		return err;

/* If IPv6 is configured in the kernel then we don't want to filter out all
 * of the multicast packets as IPv6 needs some. */
#ifndef CONFIG_IPV6
	cmd.host_command = ADD_MULTICAST;
	cmd.host_command_sequence = 0;
	cmd.host_command_length = 0;

	ipw2100_hw_send_command(priv, &cmd);
#endif

	if (!batch_mode) {
		err = ipw2100_enable_adapter(priv);
		if (err) 
			return err;
	}

	return 0;
}

int ipw2100_set_tx_rates(struct ipw2100_priv *priv, u32 rate, int batch_mode)
{
	struct host_command cmd = {
		.host_command = BASIC_TX_RATES,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = rate & TX_RATE_MASK
	};
	int err;

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) 
			return err;
	}

	/* Set BASIC TX Rate */
	ipw2100_hw_send_command(priv, &cmd);

	/* Set TX Rate */
	cmd.host_command = TX_RATES;
	ipw2100_hw_send_command(priv, &cmd);

	if (!batch_mode) {
		err = ipw2100_enable_adapter(priv);
		if (err) 
			return err;
	}

	return 0;
}

int ipw2100_set_power_mode(struct ipw2100_priv *priv, 
			   int power_level)
{
	struct host_command cmd = {
		.host_command = POWER_MODE,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = power_level
	};
	int err;

	err = ipw2100_hw_send_command(priv, &cmd);
	if (err)
		return err;

#ifdef CONFIG_IPW2100_TX_POWER
	if (priv->port_type == IBSS && 
	    priv->adhoc_power != DFTL_IBSS_TX_POWER) {
		/* Set beacon interval */
		cmd.host_command = TX_POWER_INDEX;
		cmd.host_command_parameters[0] = (u32)priv->adhoc_power;

		err = ipw2100_hw_send_command(priv, &cmd);
		if (err)
			return err;
	}
#endif

	return 0;
}


int ipw2100_set_rts_threshold(struct ipw2100_priv *priv, u32 threshold)
{
	struct host_command cmd = {
		.host_command = RTS_THRESHOLD,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = threshold
	};
	int err;

	err = ipw2100_hw_send_command(priv, &cmd);
	if (err) 
		return err;

	priv->rts_threshold = threshold;

	return 0;
}

#if 0
int ipw2100_set_fragmentation_threshold(struct ipw2100_priv *priv,
					u32 threshold, int batch_mode)
{
	struct host_command cmd = {
		.host_command = FRAG_THRESHOLD,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = 0,
	};
	int err;

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) 
			return err;
	}

	if (threshold == 0) 
		threshold = DEFAULT_FRAG_THRESHOLD;
	else {
		threshold = max(threshold, MIN_FRAG_THRESHOLD);
		threshold = min(threshold, MAX_FRAG_THRESHOLD);
	}

	cmd.host_command_parameters[0] = threshold;

	IPW2100_DEBUG_FW_COMMAND("FRAG_THRESHOLD: %u\n", threshold);

	err = ipw2100_hw_send_command(priv, &cmd);

	if (!batch_mode)
		ipw2100_enable_adapter(priv);

	if (!err)
		priv->frag_threshold = threshold;

	return err;
}
#endif

int ipw2100_set_short_retry(struct ipw2100_priv *priv, u32 retry)
{
	struct host_command cmd = {
		.host_command = SHORT_RETRY_LIMIT,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = retry
	};
	int err;
	
	err = ipw2100_hw_send_command(priv, &cmd);
	if (err)
		return err;

	priv->short_retry_limit = retry;

	return 0;
}

int ipw2100_set_long_retry(struct ipw2100_priv *priv, u32 retry)
{
	struct host_command cmd = {
		.host_command = LONG_RETRY_LIMIT,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = retry
	};
	int err;

	err = ipw2100_hw_send_command(priv, &cmd);
	if (err) 
		return err;

	priv->long_retry_limit = retry;

	return 0;
}


int ipw2100_set_mandatory_bssid(struct ipw2100_priv *priv, u8 *bssid, 
				int batch_mode)
{
	struct host_command cmd = {
		.host_command = MANDATORY_BSSID,
		.host_command_sequence = 0,
		.host_command_length = (bssid == NULL) ? 0 : ETH_ALEN 
	};
	int err;

#ifdef CONFIG_IPW2100_DEBUG
	if (bssid != NULL)
		IPW2100_DEBUG_FW_COMMAND(
			"MANDATORY_BSSID: %02X:%02X:%02X:%02X:%02X:%02X\n",
			bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], 
			bssid[5]);
	else
		IPW2100_DEBUG_FW_COMMAND("MANDATORY_BSSID: <clear>\n");
#endif
	/* if BSSID is empty then we disable mandatory bssid mode */
	if (bssid != NULL)
		memcpy((u8 *)cmd.host_command_parameters, bssid, ETH_ALEN);
	
	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) 
			return err;
	}

	err = ipw2100_hw_send_command(priv, &cmd);
	
	if (!batch_mode) 
		ipw2100_enable_adapter(priv);

	return err;
}

#if 0
static int ipw2100_disassociate_bssid(struct ipw2100_priv *priv)
{
	struct host_command cmd = {
		.host_command = DISASSOCIATION_BSSID,
		.host_command_sequence = 0,
		.host_command_length = ETH_ALEN
	};
	int err;
	int len;

	IPW2100_DEBUG_FW_COMMAND("DISASSOCIATION_BSSID\n");

	len = ETH_ALEN;
	ret = ipw2100_get_ordinal(priv, IPW_ORD_STAT_ASSN_AP_BSSID, 
				  &priv->ctx->bssid,  &len);
	memcpy(cmd.host_command_parameters, priv->ctx->bssid, ETH_ALEN);

	err = ipw2100_hw_send_command(priv, &cmd);

	return err;
}
#endif
		
int ipw2100_set_essid(struct ipw2100_priv *priv, char *essid, int batch_mode)
{
	int ssid_length = strnlen(essid, IW_ESSID_MAX_SIZE);
	struct host_command cmd = {
		.host_command = SSID,
		.host_command_sequence = 0,
		.host_command_length = ssid_length
	};
	int err;

	IPW2100_DEBUG_FW_COMMAND("SSID: %s\n", essid);
	
	if (ssid_length) 
		strncpy((char*)cmd.host_command_parameters, 
			essid, ssid_length);

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err)
			return err;
	}

	ipw2100_start_scan(priv);
		
	/* NOTE:  We always send the SSID command even if the provided ESSID is
	 * the same as what we currently think is set. */
	
	err = ipw2100_hw_send_command(priv, &cmd);
	if (!err) {
		memset(priv->ctx->ssid, 0, IW_ESSID_MAX_SIZE);
		memcpy(priv->ctx->ssid, essid, ssid_length);
		
	}

	if (!batch_mode) {
		if (ipw2100_enable_adapter(priv)) 
			err = -EIO;
	}

	return err;
}


struct security_info_params {
	u32 allowed_ciphers;
	u16 version;
	u8 auth_mode;
	u8 replay_counters_number;
	u8 unicast_using_group;
} __attribute__ ((packed));

int ipw2100_set_security_information(struct ipw2100_priv *priv, 
				     int auth_mode, int batch_mode)
{
	struct host_command cmd = {
		.host_command = SET_SECURITY_INFORMATION,
		.host_command_sequence = 0,
		.host_command_length = sizeof(struct security_info_params)
	};
	struct security_info_params *security = 
		(struct security_info_params *)&cmd.host_command_parameters;
	int err;
	memset(security, 0, sizeof(*security));

	/* If shared key AP authentication is turned on, then we need to 
	 * configure the firmware to try and use it.
	 *
	 * Actual data encryption/decryption is handled by the host. */
	security->auth_mode = auth_mode;

	if (security->auth_mode == IPW_AUTH_OPEN) 
		security->allowed_ciphers = IPW_NONE_CIPHER;
	else if (security->auth_mode == IPW_AUTH_SHARED) 
		security->allowed_ciphers = IPW_WEP40_CIPHER | 
			IPW_WEP104_CIPHER;

	IPW2100_DEBUG_FW_COMMAND(
		"SET_SECURITY_INFORMATION: auth:%d cipher:0x%02X\n",
		security->auth_mode, security->allowed_ciphers);

	security->replay_counters_number = 0;

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err)
			return err;
	}

	err = ipw2100_hw_send_command(priv, &cmd);
		
	if (!batch_mode) 
		ipw2100_enable_adapter(priv);

	return err;
}

int ipw2100_set_ibss_beacon_interval(struct ipw2100_priv *priv, 
				     u32 interval, int batch_mode)
{
	struct host_command cmd = {
		.host_command = BEACON_INTERVAL,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = interval
	};
	int err;

	IPW2100_DEBUG_INFO("enter\n");

	if (priv->port_type == IBSS) {
		if (!batch_mode) {
			err = ipw2100_disable_adapter(priv);
			if (err)
				return err;
		}
		
		ipw2100_hw_send_command(priv, &cmd);
		
		if (!batch_mode) {
			err = ipw2100_enable_adapter(priv);
			if (err)
				return err;
		}
	}

	IPW2100_DEBUG_INFO("exit\n");

	return 0;
}

int ipw2100_set_scan_options(struct ipw2100_priv *priv)
{
	struct host_command cmd = {
		.host_command = SET_SCAN_OPTIONS,
		.host_command_sequence = 0,
		.host_command_length = 8,
		.host_command_parameters[0] = priv->sec.enabled ? 0x2 : 0x0,
		.host_command_parameters[1] = 0
	};
	int err;

	IPW2100_DEBUG_INFO("enter\n");

	IPW2100_DEBUG_SCAN("setting scan options\n");

	err = ipw2100_hw_send_command(priv, &cmd);

	IPW2100_DEBUG_INFO("exit\n");

	return err;
}


void ipw2100_queues_initialize(struct ipw2100_priv *priv)
{
	ipw2100_tx_initialize(priv);
	ipw2100_rx_initialize(priv);
	ipw2100_msg_initialize(priv);
}

void ipw2100_queues_free(struct ipw2100_priv *priv)
{
	ipw2100_tx_free(priv);
	ipw2100_rx_free(priv);
	ipw2100_msg_free(priv);
}

int ipw2100_queues_allocate(struct ipw2100_priv *priv)
{
	int err;
	err = ipw2100_tx_allocate(priv);
	if (err)
		goto fail;

	err = ipw2100_rx_allocate(priv);
	if (err)
		goto fail;
	
	err = ipw2100_msg_allocate(priv);
	if (err)
		goto fail;

	return 0;

 fail:
	ipw2100_rx_free(priv);
	ipw2100_msg_free(priv);
	return -ENOMEM;
}

#define IPW_PRIVACY_CAPABLE 0x0008

static int ipw2100_set_wep_flags(struct ipw2100_priv *priv, u32 flags, 
				 int batch_mode)
{
	struct host_command cmd = {
		.host_command = WEP_FLAGS,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = flags,
	};
	int err;

	IPW2100_DEBUG_FW_COMMAND("WEP_FLAGS: flags = 0x%08X\n", flags);
	
	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) {
			printk(KERN_ERR "%s: Could not disable adapter %d\n",
			       priv->ndev->name, err);
			return err;
		}
	}

	/* send cmd to firmware */
	err = ipw2100_hw_send_command(priv, &cmd);

	if (!batch_mode) 
		ipw2100_enable_adapter(priv);

	return err;
}

struct ipw2100_wep_key {
	u8 idx;
	u8 len;
	u8 key[13];
};

static int ipw2100_set_key(struct ipw2100_priv *priv, 
			   int idx, char *key, int len, int batch_mode)
{
	int keylen = len ? (len <= 5 ? 5 : 13) : 0;
	struct host_command cmd = {
		.host_command = WEP_KEY_INFO,
		.host_command_sequence = 0,
		.host_command_length = sizeof(struct ipw2100_wep_key),
	};
	struct ipw2100_wep_key *wep_key = (void*)cmd.host_command_parameters;
	int err;
	
	IPW2100_DEBUG_FW_COMMAND("WEP_KEY_INFO: index = %d, len = %d/%d\n", idx,
				 keylen, len);

	/* NOTE: We don't check cached values in case the firmware was reset
	 * or some other problem is occuring.  If the user is setting the key,
	 * then we push the change */
	
	wep_key->idx = idx;
	wep_key->len = keylen;

	if (keylen) {
		memcpy(wep_key->key, key, len);
		memset(wep_key->key + len, 0, keylen - len);
	}

#ifdef CONFIG_IPW2100_DEBUG
#define WEP_FMT_64  "%02X%02X%02X%02X-%02X"
#define WEP_FMT_128 "%02X%02X%02X%02X-%02X%02X%02X%02X-%02X%02X%02X"
#define WEP_STR_64(x) x[0],x[1],x[2],x[3],x[4]
#define WEP_STR_128(x) x[0],x[1],x[2],x[3],x[4],x[5],x[6],x[7],x[8],x[9],x[10]
	if (keylen == 0)
		IPW2100_DEBUG_WEP("%s: Clearing key %d\n",
				  priv->ndev->name, wep_key->idx);
	else if (keylen == 5)
		IPW2100_DEBUG_WEP("%s: idx: %d, len: %d key: " WEP_FMT_64 "\n",
				  priv->ndev->name, wep_key->idx, wep_key->len,
				  WEP_STR_64(wep_key->key));
	else 
		IPW2100_DEBUG_WEP("%s: idx: %d, len: %d key: " WEP_FMT_128 
				  "\n",
				  priv->ndev->name, wep_key->idx, wep_key->len,
				  WEP_STR_128(wep_key->key));
#endif

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) {
			printk(KERN_ERR "%s: Could not disable adapter %d\n",
			       priv->ndev->name, err);
			return err;
		}
	}

	/* send cmd to firmware */
	err = ipw2100_hw_send_command(priv, &cmd);
	
	if (!batch_mode) 
		ipw2100_enable_adapter(priv);

	return err;
}

#if 0
static int ipw2100_set_key_index(struct ipw2100_priv *priv, 
				 int idx, int batch_mode)
{
	struct host_command cmd = {
		.host_command = WEP_KEY_INDEX,
		.host_command_sequence = 0,
		.host_command_length = 4,
		.host_command_parameters[0] = idx,
	};
	int err;

	IPW2100_DEBUG_FW_COMMAND("WEP_KEY_INDEX: index = %d\n", idx);
	
	if (idx < 0 || idx > 3)
		return -EINVAL;

	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err) {
			printk(KERN_ERR "%s: Could not disable adapter %d\n",
			       priv->ndev->name, err);
			return err;
		}
	}

	/* send cmd to firmware */
	err = ipw2100_hw_send_command(priv, &cmd);

	if (!batch_mode) 
		ipw2100_enable_adapter(priv);

	return err;
}
#endif


static int ipw2100_configure_security(struct ipw2100_priv *priv, 
				      int batch_mode)
{
	int i, err;
	
	if (!batch_mode) {
		err = ipw2100_disable_adapter(priv);
		if (err)
			return err;
	}

	err = ipw2100_set_security_information(
		priv, (priv->sec.enabled && 
		       (priv->sec.flags & SEC_AUTH_MODE) &&
		       (priv->sec.auth_mode == WLAN_AUTH_SHARED_KEY)) ?
		IPW_AUTH_SHARED : IPW_AUTH_OPEN, 1);
	if (err)
		goto exit;

	if (priv->sec.enabled) {
		for (i = 0; i < 4; i++) { 
			if (!(priv->sec.flags & (1 << i))) {
				memset(priv->sec.keys[i], 0, WEP_KEY_LEN);
				priv->sec.key_sizes[i] = 0;
			} else {
				err = ipw2100_set_key(priv, i, 
						      priv->sec.keys[i], 
						      priv->sec.key_sizes[i], 
						      1);
				if (err)
					goto exit;
			}
		}   
	}

	/* Always enable privacy so the Host can filter WEP packets if
	 * encrypted data is sent up */
	err = ipw2100_set_wep_flags(
		priv, priv->sec.enabled ? IPW_PRIVACY_CAPABLE : 0, 1);
	if (err)
		goto exit;
	
	priv->security_updated = 0;

 exit:
	if (!batch_mode)
		ipw2100_enable_adapter(priv);
	
	return err;
}
 
static void ipw2100_security_work(struct ipw2100_priv *priv)
{
	/* If we happen to have reconnected before we get a chance to
	 * process this, then update the security settings--which causes
	 * a disassociation to occur */
	if (!priv->connected && priv->security_updated)
		ipw2100_configure_security(priv, 0);
}

static void shim__set_security(struct ieee80211_device *ieee, 
			       struct ieee80211_security *sec)
{
	struct ipw2100_priv *priv = ieee->priv;
	int i;

	for (i = 0; i < 4; i++) { 
		if (sec->flags & (1 << i)) {
			priv->sec.key_sizes[i] = sec->key_sizes[i];
			if (sec->key_sizes[i] == 0)
				priv->sec.flags &= ~(1 << i);
			else
				memcpy(priv->sec.keys[i], sec->keys[i], 
				       sec->key_sizes[i]);
			priv->sec.flags |= (1 << i);
			priv->security_updated = 1;
		} 
	}

	if ((sec->flags & SEC_ACTIVE_KEY) &&
	    priv->sec.active_key != sec->active_key) {
		if (sec->active_key <= 3) {
			priv->sec.active_key = sec->active_key;
			priv->sec.flags |= SEC_ACTIVE_KEY;
		} else 
			priv->sec.flags &= ~SEC_ACTIVE_KEY;
			
		priv->security_updated = 1;
	}

	if ((sec->flags & SEC_AUTH_MODE) &&
	    (priv->sec.auth_mode != sec->auth_mode)) {
		priv->sec.auth_mode = sec->auth_mode;
		priv->sec.flags |= SEC_AUTH_MODE;
		priv->security_updated = 1;
	}
	
	if (sec->flags & SEC_ENABLED &&
	    priv->sec.enabled != sec->enabled) {
		priv->sec.enabled = sec->enabled;
		priv->security_updated = 1;
	}

	IPW2100_DEBUG_WEP("Security flags: %c%c%c%c %c%c%c%c\n",
			  priv->sec.flags & BIT(7) ? '1' : '0',
			  priv->sec.flags & BIT(6) ? '1' : '0',
			  priv->sec.flags & BIT(5) ? '1' : '0',
			  priv->sec.flags & BIT(4) ? '1' : '0',
			  priv->sec.flags & BIT(3) ? '1' : '0',
			  priv->sec.flags & BIT(2) ? '1' : '0',
			  priv->sec.flags & BIT(1) ? '1' : '0',
			  priv->sec.flags & BIT(0) ? '1' : '0');

	if (!priv->connected)
		ipw2100_configure_security(priv, 0);
}

static struct ieee80211_helper_functions ipw2100_ieee_callbacks = {
	.set_security = shim__set_security,
	.card_present = NULL,
	.cor_sreset = NULL,
	.dev_open = NULL,
	.dev_close = NULL,
	.genesis_reset = NULL,
	.set_unencrypted_filter = NULL,
	.hw_enable = NULL,
	.hw_config = NULL,
	.hw_reset = NULL,
	.hw_shutdown = NULL,
	.reset_port = NULL,
	.tx = NULL,
	.schedule_reset = NULL,
	.tx_80211 = NULL
};

static int ipw2100_adapter_setup(struct ipw2100_priv *priv)
{
	int err;
	int batch_mode = 1;

	IPW2100_DEBUG_INFO("enter\n");

	err = ipw2100_disable_adapter(priv);
	if (err)
		return err;
	
	err = ipw2100_read_mac_address(priv);
	if (err)
		return -EIO;

	err = ipw2100_set_mac_address(priv, batch_mode);
	if (err)
		return err;
	
	err = ipw2100_set_port_type(priv, BSS, batch_mode);
	if (err)
		return err;

	err = ipw2100_set_channel(priv, priv->ctx->channel, batch_mode);
	if (err)
		return err;

	err  = ipw2100_system_config(priv, batch_mode);
	if (err)
		return err;

	err = ipw2100_set_tx_rates(priv, 0xFFFFFFFF, batch_mode);
	if (err)
		return err;

	err = ipw2100_set_power_mode(priv, IPW_POWER_MODE_CAM);
	if (err)
		return err;

	err = ipw2100_set_rts_threshold(priv, priv->rts_threshold);
	if (err)
		return err;

	err = ipw2100_set_mandatory_bssid(priv, NULL, batch_mode);
	if (err)
		return err;

	err = ipw2100_set_essid(priv, priv->ctx->ssid, batch_mode);
	if (err)
		return err;

	err = ipw2100_configure_security(priv, batch_mode);
	if (err)
		return err;

	if (priv->ctx->port_type == IBSS) {
		err = ipw2100_set_ibss_beacon_interval(
			priv, priv->beacon_interval, batch_mode);
		if (err)
			return err;
	}

	err = ipw2100_set_scan_options(priv);
	if (err)
		return err;

	/*
	 err = ipw2100_set_tx_power_mode(priv, ...);
	 if (err) 
	 return err;
	err = ipw2100_set_fragmentation_threshold(
		priv, priv->frag_threshold, batch_mode);
	if (err) 
		return err;
	*/
	
	IPW2100_DEBUG_INFO("exit\n");

	return 0;
}


/*************************************************************************
 * 
 * EXTERNALLY CALLED METHODS
 *
 *************************************************************************/

/* This method is called by the network layer -- not to be confused with
 * ipw2100_set_mac_address() declared above called by this driver (and this
 * method as well) to talk to the firmware */
static int ipw2100_set_address(struct net_device *dev, void *p)
{
	struct ipw2100_priv *priv = dev->priv;
	struct sockaddr *addr = p;
	int err;

	if (!is_valid_ether_addr(addr->sa_data))
		return -EADDRNOTAVAIL;

	memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);

	err = ipw2100_set_mac_address(priv, 0);
	if (err)
		return err;

	return 0;
}

static int ipw2100_open(struct net_device *dev)
{
	struct ipw2100_priv *priv = dev->priv;
	unsigned long flags;

	IPW2100_DEBUG_INFO("enter\n");
	
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	MOD_INC_USE_COUNT;
#endif

	spin_lock_irqsave(&priv->low_lock, flags);
	priv->open = 1;
	spin_unlock_irqrestore(&priv->low_lock, flags);

	IPW2100_DEBUG_INFO("exit\n");
	return 0;
}

static int ipw2100_close(struct net_device *dev)
{
	struct ipw2100_priv *priv = dev->priv;
	unsigned long flags;
	struct list_head *element;
	struct ipw2100_tx_packet *packet;

	IPW2100_DEBUG_INFO("enter\n");

	spin_lock_irqsave(&priv->low_lock, flags);

	if (priv->connected)
		netif_carrier_off(dev);

	priv->open = 0;
	
	/* Flush the TX queue ... */
	while (!list_empty(&priv->tx_pend_list)) {
		element = priv->tx_pend_list.next;
                packet = list_entry(element, struct ipw2100_tx_packet, list);

		list_del(element);
		DEC_STAT(&priv->tx_pend_stat);

		skb_queue_purge(&packet->skb_queue);

		list_add_tail(element, &priv->tx_free_list);
		INC_STAT(&priv->tx_free_stat);
	}
	spin_unlock_irqrestore(&priv->low_lock, flags);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	MOD_DEC_USE_COUNT;
#endif

	IPW2100_DEBUG_INFO("exit\n");

	return 0;
}



/*
 * TODO:  Fix this function... its just wrong
 */
static void ipw2100_tx_timeout(struct net_device *dev)
{
	struct ipw2100_priv *priv = dev->priv;

	IPW2100_DEBUG_TX("enter (connected=%d)\n", priv->connected);

	priv->ieee.stats.tx_errors++;

	/* TODO: Make sure we shut off the network interface on 
	 * disassociation */
	printk(KERN_INFO "%s: TX timed out.  Scheduling firmware restart.\n",
	       dev->name);
	schedule_reset(priv);

	IPW2100_DEBUG_TX("exit\n");
}


/*
 * TODO: reimplement it so that it reads statistics
 *       from the adapter using ordinal tables
 *       instead of/in addition to collecting them
 *       in the driver
 */
static struct net_device_stats *ipw2100_stats(struct net_device *dev)
{
	struct ipw2100_priv *priv = (struct ipw2100_priv*)dev->priv;
	
	return &priv->ieee.stats;
}

static int ipw2100_change_mtu(struct net_device *dev, int new_mtu)
{
	if (new_mtu < ETH_ZLEN || new_mtu > ETH_DATA_LEN)
		return -EINVAL;

	dev->mtu = new_mtu;
	return 0;
}


static int ipw2100_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
	IPW2100_DEBUG_INFO("enter\n");

	IPW2100_DEBUG_INFO("exit\n");
	return -EOPNOTSUPP;
}


static u32 ipw2100_ethtool_get_link(struct net_device *dev)
{
    struct ipw2100_priv *priv = (struct ipw2100_priv*)dev->priv;
    return priv->connected;
}


#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,23)
static struct ethtool_ops ipw2100_ethtool_ops = {
    .get_link        = ipw2100_ethtool_get_link,
};
#endif

static void ipw2100_hang_check(unsigned long arg)
{
	struct ipw2100_priv *priv = (struct ipw2100_priv *)arg;
	unsigned long flags;
	u32 rtc = 0xa5a5a5a5;
	u32 len = sizeof(rtc);
	int restart = 0;

	spin_lock_irqsave(&priv->low_lock, flags);

	if (priv->fatal_error != 0) {
		/* If fatal_error is set then we need to restart */
		printk(KERN_INFO "%s: Hardware fatal error detected.\n",
		       priv->ndev->name);

		restart = 1;

	} else if (ipw2100_get_ordinal(priv, IPW_ORD_RTC_TIME, &rtc, &len) ||
		   (rtc == priv->last_rtc)) {
		/* Check if firmware is hung */
		printk(KERN_INFO "%s: Firmware RTC stalled.\n",
		       priv->ndev->name);

		restart = 1;

	}

	if (restart) {
		/* Kill timer */
		priv->stop_hang_check = 1;
		
		priv->hangs++;
		
		/* Restart the NIC */
		schedule_reset(priv);
	} 

	priv->last_rtc = rtc;

	/* Check again in two seconds */
	if (!priv->stop_hang_check)
		mod_timer(&priv->hang_check_timer, jiffies + 2 * HZ);

	spin_unlock_irqrestore(&priv->low_lock, flags);
}


static void ipw2100_rf_switch_check(unsigned long arg)
{
	struct ipw2100_priv *priv = (struct ipw2100_priv *)arg;
	unsigned long flags;

	spin_lock_irqsave(&priv->low_lock, flags);
	if (priv->rf_kill != ipw2100_get_rf_switch(priv)) {
		/* Toggle the switch to match what the HW told us */
		priv->rf_kill = !priv->rf_kill;
		
		printk(KERN_INFO "%s: RF Kill state changed to radio %s.\n",
		       priv->ndev->name,
		       priv->rf_kill ? "OFF" : "ON");
		
		if (priv->rf_kill) {
			/* RF switch is ON; firmware will have killed the HW */
			priv->rf_kill = 0;
		} else {
                        /* Kill timer; we don't need to poll for RF going
			 * off since the firmware will tell us */
			priv->stop_rf_check = 1;
			
			/* Restart the NIC */
			schedule_reset(priv);
		} 
	} 
	
	/* Check again in one second */
	if (!priv->stop_rf_check)
		mod_timer(&priv->rf_switch_timer, jiffies + HZ);

	spin_unlock_irqrestore(&priv->low_lock, flags);
}

static void ipw2100_irq_tasklet(struct ipw2100_priv *priv);

static struct net_device *ipw2100_alloc_device(
	struct pci_dev *pdev,
	char *base_addr, 
	unsigned long mem_start,
	unsigned long mem_len)
{
	struct ipw2100_priv *priv;
	struct net_device *dev;

	dev = alloc_etherdev(sizeof(struct ipw2100_priv));
	if (!dev)
		return NULL;

	dev->type = ARPHRD_ETHER;
	dev->open = ipw2100_open;
	dev->stop = ipw2100_close;
	dev->hard_start_xmit = ipw2100_tx;
	dev->do_ioctl = ipw2100_ioctl;
	dev->get_stats = ipw2100_stats;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,23)
	dev->ethtool_ops = &ipw2100_ethtool_ops;
#endif
	dev->change_mtu = ipw2100_change_mtu;
	dev->tx_timeout = ipw2100_tx_timeout;
	dev->wireless_handlers = &ipw2100_wx_handler_def;
	dev->get_wireless_stats = ipw2100_wx_wireless_stats;
	dev->set_mac_address = ipw2100_set_address;
	dev->watchdog_timeo = 3*HZ;
	dev->irq = 0;

	dev->base_addr = (unsigned long)base_addr;
	dev->mem_start = mem_start;
	dev->mem_end = dev->mem_start + mem_len - 1;
	
	if (!if_name || if_name[0] == '\0')
                strncpy(dev->name, "eth%d", IFNAMSIZ - 1);
        else 
                strncpy(dev->name, if_name, IFNAMSIZ - 1);

	/* NOTE: We don't use the wireless_handlers hook
	 * in dev as the system will start throwing WX requests
	 * to us before we're actually initialized and it just
	 * ends up causing problems.  So, we just handle
	 * the WX extensions through the ipw2100_ioctl interface */

	priv = (struct ipw2100_priv*)dev->priv;
	memset(priv, 0, sizeof(struct ipw2100_priv));

	priv->ctx = &priv->active_ctx;
	priv->pdev = pdev;
	priv->ndev = dev;

	/* memset() puts everything to 0, so we only have explicitely set
	 * those values that need to be something else */
	priv->disabled = 1;
	priv->stopped = 1;

	priv->ctx->port_type = BSS;
	priv->ctx->channel = 1;

	priv->beacon_interval = DEFAULT_BEACON_INTERVAL;
	priv->short_retry_limit = DEFAULT_SHORT_RETRY_LIMIT;
	priv->long_retry_limit = DEFAULT_LONG_RETRY_LIMIT;
	priv->rts_threshold = DEFAULT_RTS_THRESHOLD;

	priv->tx_power = 0;

	strcpy(priv->nick, "ipw2100");


	IPW2100_DEBUG_INFO("Port type set to %s\n", 
			   port_type_str[priv->ctx->port_type - 1]);

	spin_lock_init(&priv->low_lock);

	init_waitqueue_head(&priv->wait_command_queue);

	priv->connected = 0;
	netif_carrier_off(dev);

	INIT_LIST_HEAD(&priv->msg_free_list);
	INIT_LIST_HEAD(&priv->msg_pend_list);
	INIT_STAT(&priv->msg_free_stat);
	INIT_STAT(&priv->msg_pend_stat);

	INIT_LIST_HEAD(&priv->tx_free_list);
	INIT_LIST_HEAD(&priv->tx_pend_list);
	INIT_STAT(&priv->tx_free_stat);
	INIT_STAT(&priv->tx_pend_stat);

	INIT_LIST_HEAD(&priv->fw_pend_list);
	INIT_STAT(&priv->fw_pend_stat);
	
	INIT_WORK(&priv->reset_work, 
		  (void (*)(void *))ipw2100_reset_adapter, priv);
	INIT_WORK(&priv->security_work, 
		  (void (*)(void *))ipw2100_security_work, priv);
	INIT_WORK(&priv->wx_event_work,
		  (void (*)(void *))ipw2100_wx_event_work, priv);
	priv->wx_ap_event_pending = 0;
	
	tasklet_init(&priv->irq_tasklet, (void (*)(unsigned long))
		     ipw2100_irq_tasklet, (unsigned long)priv);

	/* NOTE:  We do not start the timer yet; we only run the timer if the
	 *        the RF kill switch has been triggered */
	init_timer(&priv->rf_switch_timer);
	priv->rf_switch_timer.data = (unsigned long)priv;
	priv->rf_switch_timer.function = ipw2100_rf_switch_check;
	priv->stop_rf_check = 1;   /* Timer not running */

	init_timer(&priv->hang_check_timer);
	priv->hang_check_timer.data = (unsigned long)priv;
	priv->hang_check_timer.function = ipw2100_hang_check;
	priv->stop_hang_check = 1; /* Timer not running */
	return dev;
}



#define PCI_DMA_32BIT   0x00000000ffffffffULL

static int ipw2100_pci_init_one(struct pci_dev *pdev, 
				const struct pci_device_id *ent)
{
	unsigned long mem_start, mem_len, mem_flags;
	char *base_addr = NULL;
	struct net_device *dev = NULL;
	struct ipw2100_priv *priv = NULL;
	int err = 0;
	int registered = 0;
	u32 val;

	IPW2100_DEBUG_INFO("enter\n");

	/* set up PCI mappings for device */
	err = pci_enable_device(pdev);
	if (err)
		return err;

	pci_set_master(pdev);

	err = pci_set_dma_mask(pdev, PCI_DMA_32BIT);
	if (err) {
		IPW2100_DEBUG_INFO("failed pci_set_dma_mask!\n");
		pci_disable_device(pdev);
		return err;
	}

	pci_set_drvdata(pdev, NULL);

	err = pci_request_regions(pdev, DRV_NAME);
	if (err) {
		pci_disable_device(pdev);
		return err;
	}

        /* We disable the RETRY_TIMEOUT register (0x41) to keep 
	 * PCI Tx retries from interfering with C3 CPU state */
	pci_read_config_dword(pdev, 0x40, &val); 
	if ((val & 0x0000ff00) != 0) 
		pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);

	mem_start = pci_resource_start(pdev, 0);
	mem_len = pci_resource_len(pdev, 0);
	mem_flags = pci_resource_flags(pdev, 0);

	if ((mem_flags & IORESOURCE_MEM) != IORESOURCE_MEM) {
		IPW2100_DEBUG_INFO("weird - resource type is not memory\n");
		err = -ENODEV;
		goto fail;
	}

	base_addr = ioremap_nocache(mem_start, mem_len);
	if (!base_addr) {
		err = -EIO;
		goto fail;
	}

	pci_set_power_state(pdev, 0);
	pci_restore_state(pdev, NULL);

	/* allocate and initialize our net_device */
	dev = ipw2100_alloc_device(pdev, base_addr, mem_start, mem_len);
	if (!dev) {
		err = -ENOMEM;
		goto fail;
	}

	if (!ipw2100_hw_is_adapter_in_system(dev)) {
		err = -ENODEV;
		goto fail;
	}

	pci_set_drvdata(pdev, dev);

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
	SET_NETDEV_DEV(dev, &pdev->dev);
#endif

	priv = dev->priv;

	/* Initialize WEP */
	ieee80211_init(&priv->ieee, dev, priv);
	priv->ieee.func = &ipw2100_ieee_callbacks;
	priv->ieee.tx_payload_only = 1;

	/* begin initializing the hardware */
	ipw2100_hw_disable_interrupt(priv);
	priv->irq_enabled = 0;

	/* Allocate and initialize the beacon list */
	if (ipw2100_beacons_allocate(priv)) {
		err = -ENOMEM;
		goto fail;
	}
	ipw2100_beacons_initialize(priv);

	/* Allocate and initialize the Tx/Rx queues and lists */
	if (ipw2100_queues_allocate(priv)) {
		err = -ENOMEM;
		goto fail;
	}
	ipw2100_queues_initialize(priv);

	err = request_irq(pdev->irq,
			  ipw2100_interrupt, SA_SHIRQ, 
			  dev->name, dev);
	if (err) {
		printk(KERN_ERR
		       "ipw2100: Error allocating IRQ %d.\n",
		       pdev->irq);
		err = -EBUSY;
		goto fail;
	}
	dev->irq = pdev->irq;

	IPW2100_DEBUG_INFO("Attempting to register device...\n");

	err = register_netdev(dev);
	if (err) {
		err = -ENODEV;
		goto fail;
	}
	registered = 1;

	SET_MODULE_OWNER(dev);

	printk(KERN_INFO "Detected ipw2100 PCI device at "
	       "%s, dev: %s, mem: 0x%lX-0x%lX -> %p, irq: %d\n",
	       pdev->slot_name, dev->name,
	       dev->mem_start, dev->mem_end, base_addr, pdev->irq);

	/* perform this after register_netdev so that dev->name is
	 * set */
	err = ipw2100_proc_dev_init(priv);
	if (err) {
		printk(KERN_ERR
		       "%s: Failed to create /proc node\n",
		       dev->name);
		err = -EIO;
		goto fail;
	}

	/* Bring up the HW */
	if (ipw2100_up(dev->priv)) {
		err = -EIO;
		goto fail;
	}
		
	IPW2100_DEBUG_INFO("exit\n");

	return 0;
	
 fail:
	if (dev) {
		if (!priv->stop_hang_check) {
			priv->stop_hang_check = 1;
			del_timer_sync(&priv->hang_check_timer);
		}

		if (registered) 
			unregister_netdev(dev);

		if (dev->irq) {
			free_irq(dev->irq, dev);
		}

		/* These are safe to call even if they weren't allocated */
		ipw2100_beacons_free(priv);
		ipw2100_queues_free(priv);
		ipw2100_proc_dev_cleanup(priv);

		free_netdev(dev);
		pci_set_drvdata(pdev, NULL);
	}

	if (base_addr) 
		iounmap((char*)base_addr);

	pci_release_regions(pdev);
	pci_disable_device(pdev);

	return err;
}

static void __devexit ipw2100_pci_remove_one(struct pci_dev *pdev)
{
	struct net_device *dev = pci_get_drvdata(pdev);
	struct ipw2100_priv *priv;

	if (dev) {
		priv = dev->priv;

		/* Unregister the device first - this results in close() 
		 * being called if the device is open.  If we free storage
		 * first, then close() will crash. */
		unregister_netdev(dev);

		ieee80211_deinit(&priv->ieee);

		ipw2100_down(priv);

 		ipw2100_proc_dev_cleanup(priv);

		ipw2100_beacons_free(priv);
		ipw2100_queues_free(priv);

		/* Free potential debugging firmware snapshot */
		ipw2100_snapshot_free(priv);

		if (dev->irq)
			free_irq(dev->irq, dev);

		if (dev->base_addr)
			iounmap((unsigned char *)dev->base_addr);

		free_netdev(dev);
	}

	pci_release_regions(pdev);
	pci_disable_device(pdev);

	IPW2100_DEBUG_INFO("exit\n");
}

#ifdef CONFIG_PM

static int ipw2100_suspend(struct pci_dev *pdev, u32 state)
{
	struct net_device *dev = pci_get_drvdata(pdev);
	struct ipw2100_priv *priv = dev->priv;
	
	
	netif_device_detach(dev);

	if (netif_running(dev))
		ipw2100_down(priv);
	
	pci_save_state(pdev, priv->pm_state);
	pci_disable_device(pdev);       // needed?
	pci_set_power_state(pdev, state);
	
	return 0;
}


static int ipw2100_resume(struct pci_dev *pdev)
{
	struct net_device *dev = pci_get_drvdata(pdev);
	struct ipw2100_priv *priv = dev->priv;
	u32 val;
	
	pci_set_power_state(pdev, 0);
	pci_restore_state(pdev, priv->pm_state);
	
        /* Suspend/Resume resets the PCI configuration space, so we have to
	 * re-disable the RETRY_TIMEOUT register (0x41) to keep 
	 * PCI Tx retries from interfering with C3 CPU state */
	pci_read_config_dword(pdev, 0x40, &val); 
	if ((val & 0x0000ff00) != 0) 
		pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);

	if (netif_running(dev))
		ipw2100_up(priv);
	
	netif_device_attach(dev);
	
	return 0;
}

#endif /* CONFIG_PM */

#define IPW2100_DEV_ID(x) { PCI_VENDOR_ID_INTEL, 0x1043, 0x8086, x }

static struct pci_device_id ipw2100_pci_id_table[] __devinitdata = {
	IPW2100_DEV_ID(0x2520), /* IN 2100A mPCI 3A */
	IPW2100_DEV_ID(0x2521), /* IN 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2524), /* IN 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2525), /* IN 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2526), /* IN 2100A mPCI Gen A3 */
	IPW2100_DEV_ID(0x2522), /* IN 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2523), /* IN 2100 mPCI 3A */
	IPW2100_DEV_ID(0x2527), /* IN 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2528), /* IN 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2529), /* IN 2100 mPCI 3B */
	IPW2100_DEV_ID(0x252B), /* IN 2100 mPCI 3A */
	IPW2100_DEV_ID(0x252C), /* IN 2100 mPCI 3A */
	IPW2100_DEV_ID(0x252D), /* IN 2100 mPCI 3A */

	IPW2100_DEV_ID(0x2550), /* IB 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2551), /* IB 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2553), /* IB 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2554), /* IB 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2555), /* IB 2100 mPCI 3B */

	IPW2100_DEV_ID(0x2560), /* DE 2100A mPCI 3A */
	IPW2100_DEV_ID(0x2562), /* DE 2100A mPCI 3A */
	IPW2100_DEV_ID(0x2563), /* DE 2100A mPCI 3A */
	IPW2100_DEV_ID(0x2561), /* DE 2100 mPCI 3A */
	IPW2100_DEV_ID(0x2565), /* DE 2100 mPCI 3A */
	IPW2100_DEV_ID(0x2566), /* DE 2100 mPCI 3A */
	IPW2100_DEV_ID(0x2567), /* DE 2100 mPCI 3A */

	IPW2100_DEV_ID(0x2570), /* GA 2100 mPCI 3B */

	IPW2100_DEV_ID(0x2580), /* TO 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2582), /* TO 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2583), /* TO 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2581), /* TO 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2585), /* TO 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2586), /* TO 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2587), /* TO 2100 mPCI 3B */

	IPW2100_DEV_ID(0x2590), /* SO 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2592), /* SO 2100A mPCI 3B */
	IPW2100_DEV_ID(0x2591), /* SO 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2593), /* SO 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2596), /* SO 2100 mPCI 3B */
	IPW2100_DEV_ID(0x2598), /* SO 2100 mPCI 3B */
	
	IPW2100_DEV_ID(0x25A0), /* HP 2100 mPCI 3B */
	{0,},
};

MODULE_DEVICE_TABLE(pci, ipw2100_pci_id_table);

static struct pci_driver ipw2100_pci_driver = {
	.name = DRV_NAME,
	.id_table = ipw2100_pci_id_table,
	.probe = ipw2100_pci_init_one,
	.remove = __devexit_p(ipw2100_pci_remove_one),
#ifdef CONFIG_PM
	.suspend = ipw2100_suspend,
	.resume = ipw2100_resume,
#endif
};

/*
 * module stuff
 */

static int __init ipw2100_init(void)
{
	int err;

	printk(KERN_INFO DRV_NAME ": %s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
	printk(KERN_INFO DRV_NAME ": %s\n", DRV_COPYRIGHT);

#ifdef CONFIG_IPW2100_NOWEP
	printk(KERN_INFO DRV_NAME ": Compiled with WEP disabled.\n");
#endif

	/* If debug module parameter declared, set debug_level to that */
	if (debug != -1) 
		ipw2100_debug_level = debug;
	else /* Otherwise, if ipw2100_debug_level has not been initialized, set
	      * it to NONE */
		if (ipw2100_debug_level & IPW2100_DL_UNINIT)
			ipw2100_debug_level = IPW2100_DL_NONE;

	ipw2100_proc_init();

	err = pci_module_init(&ipw2100_pci_driver);
	if (err)
		ipw2100_proc_cleanup();

	return err;
}

static void __exit ipw2100_exit(void)
{
	pci_unregister_driver(&ipw2100_pci_driver);

	ipw2100_proc_cleanup();
}

module_init(ipw2100_init);
module_exit(ipw2100_exit);

/*
         1         2         3         4         5         6         7
12345678901234567890123456789012345678901234567890123456789012345678901234567890
*/
