Skip to content
Snippets Groups Projects
hostap_hw.c 93.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Host AP (software wireless LAN access point) driver for
     * Intersil Prism2/2.5/3.
     *
     * Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen
    
     * <j@w1.fi>
     * Copyright (c) 2002-2005, Jouni Malinen <j@w1.fi>
    
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License version 2 as
     * published by the Free Software Foundation. See README and COPYING for
     * more details.
     *
     * FIX:
     * - there is currently no way of associating TX packets to correct wds device
     *   when TX Exc/OK event occurs, so all tx_packets and some
     *   tx_errors/tx_dropped are added to the main netdevice; using sw_support
     *   field in txdesc might be used to fix this (using Alloc event to increment
     *   tx_packets would need some further info in txfid table)
     *
     * Buffer Access Path (BAP) usage:
     *   Prism2 cards have two separate BAPs for accessing the card memory. These
     *   should allow concurrent access to two different frames and the driver
     *   previously used BAP0 for sending data and BAP1 for receiving data.
     *   However, there seems to be number of issues with concurrent access and at
     *   least one know hardware bug in using BAP0 and BAP1 concurrently with PCI
     *   Prism2.5. Therefore, the driver now only uses BAP0 for moving data between
     *   host and card memories. BAP0 accesses are protected with local->baplock
     *   (spin_lock_bh) to prevent concurrent use.
     */
    
    
    
    #include <asm/delay.h>
    #include <asm/uaccess.h>
    
    #include <linux/slab.h>
    #include <linux/netdevice.h>
    #include <linux/etherdevice.h>
    #include <linux/proc_fs.h>
    #include <linux/if_arp.h>
    #include <linux/delay.h>
    #include <linux/random.h>
    #include <linux/wait.h>
    #include <linux/sched.h>
    #include <linux/rtnetlink.h>
    #include <linux/wireless.h>
    #include <net/iw_handler.h>
    
    #include <net/lib80211.h>
    
    #include <asm/irq.h>
    
    #include "hostap_80211.h"
    #include "hostap.h"
    #include "hostap_ap.h"
    
    
    /* #define final_version */
    
    static int mtu = 1500;
    module_param(mtu, int, 0444);
    MODULE_PARM_DESC(mtu, "Maximum transfer unit");
    
    static int channel[MAX_PARM_DEVICES] = { 3, DEF_INTS };
    module_param_array(channel, int, NULL, 0444);
    MODULE_PARM_DESC(channel, "Initial channel");
    
    static char essid[33] = "test";
    module_param_string(essid, essid, sizeof(essid), 0444);
    MODULE_PARM_DESC(essid, "Host AP's ESSID");
    
    static int iw_mode[MAX_PARM_DEVICES] = { IW_MODE_MASTER, DEF_INTS };
    module_param_array(iw_mode, int, NULL, 0444);
    MODULE_PARM_DESC(iw_mode, "Initial operation mode");
    
    static int beacon_int[MAX_PARM_DEVICES] = { 100, DEF_INTS };
    module_param_array(beacon_int, int, NULL, 0444);
    MODULE_PARM_DESC(beacon_int, "Beacon interval (1 = 1024 usec)");
    
    static int dtim_period[MAX_PARM_DEVICES] = { 1, DEF_INTS };
    module_param_array(dtim_period, int, NULL, 0444);
    MODULE_PARM_DESC(dtim_period, "DTIM period");
    
    static char dev_template[16] = "wlan%d";
    module_param_string(dev_template, dev_template, sizeof(dev_template), 0444);
    MODULE_PARM_DESC(dev_template, "Prefix for network device name (default: "
    		 "wlan%d)");
    
    #ifdef final_version
    #define EXTRA_EVENTS_WTERR 0
    #else
    /* check WTERR events (Wait Time-out) in development versions */
    #define EXTRA_EVENTS_WTERR HFA384X_EV_WTERR
    #endif
    
    /* Events that will be using BAP0 */
    #define HFA384X_BAP0_EVENTS \
    	(HFA384X_EV_TXEXC | HFA384X_EV_RX | HFA384X_EV_INFO | HFA384X_EV_TX)
    
    /* event mask, i.e., events that will result in an interrupt */
    #define HFA384X_EVENT_MASK \
    	(HFA384X_BAP0_EVENTS | HFA384X_EV_ALLOC | HFA384X_EV_INFDROP | \
    	HFA384X_EV_CMD | HFA384X_EV_TICK | \
    
    
    /* Default TX control flags: use 802.11 headers and request interrupt for
     * failed transmits. Frames that request ACK callback, will add
     * _TX_OK flag and _ALT_RTRY flag may be used to select different retry policy.
     */
    #define HFA384X_TX_CTRL_FLAGS \
    	(HFA384X_TX_CTRL_802_11 | HFA384X_TX_CTRL_TX_EX)
    
    
    /* ca. 1 usec */
    #define HFA384X_CMD_BUSY_TIMEOUT 5000
    #define HFA384X_BAP_BUSY_TIMEOUT 50000
    
    /* ca. 10 usec */
    #define HFA384X_CMD_COMPL_TIMEOUT 20000
    #define HFA384X_DL_COMPL_TIMEOUT 1000000
    
    /* Wait times for initialization; yield to other processes to avoid busy
     * waiting for long time. */
    #define HFA384X_INIT_TIMEOUT (HZ / 2) /* 500 ms */
    #define HFA384X_ALLOC_COMPL_TIMEOUT (HZ / 20) /* 50 ms */
    
    
    static void prism2_hw_reset(struct net_device *dev);
    static void prism2_check_sta_fw_version(local_info_t *local);
    
    #ifdef PRISM2_DOWNLOAD_SUPPORT
    /* hostap_download.c */
    static int prism2_download_aux_dump(struct net_device *dev,
    				    unsigned int addr, int len, u8 *buf);
    static u8 * prism2_read_pda(struct net_device *dev);
    static int prism2_download(local_info_t *local,
    			   struct prism2_download_param *param);
    static void prism2_download_free_data(struct prism2_download_data *dl);
    static int prism2_download_volatile(local_info_t *local,
    				    struct prism2_download_data *param);
    static int prism2_download_genesis(local_info_t *local,
    				   struct prism2_download_data *param);
    static int prism2_get_ram_size(local_info_t *local);
    #endif /* PRISM2_DOWNLOAD_SUPPORT */
    
    
    
    
    #ifndef final_version
    /* magic value written to SWSUPPORT0 reg. for detecting whether card is still
     * present */
    #define HFA384X_MAGIC 0x8A32
    #endif
    
    
    static u16 hfa384x_read_reg(struct net_device *dev, u16 reg)
    {
    	return HFA384X_INW(reg);
    }
    
    
    static void hfa384x_read_regs(struct net_device *dev,
    			      struct hfa384x_regs *regs)
    {
    	regs->cmd = HFA384X_INW(HFA384X_CMD_OFF);
    	regs->evstat = HFA384X_INW(HFA384X_EVSTAT_OFF);
    	regs->offset0 = HFA384X_INW(HFA384X_OFFSET0_OFF);
    	regs->offset1 = HFA384X_INW(HFA384X_OFFSET1_OFF);
    	regs->swsupport0 = HFA384X_INW(HFA384X_SWSUPPORT0_OFF);
    }
    
    
    /**
     * __hostap_cmd_queue_free - Free Prism2 command queue entry (private)
     * @local: pointer to private Host AP driver data
     * @entry: Prism2 command queue entry to be freed
     * @del_req: request the entry to be removed
     *
     * Internal helper function for freeing Prism2 command queue entries.
     * Caller must have acquired local->cmdlock before calling this function.
     */
    static inline void __hostap_cmd_queue_free(local_info_t *local,
    					   struct hostap_cmd_queue *entry,
    					   int del_req)
    {
    	if (del_req) {
    		entry->del_req = 1;
    		if (!list_empty(&entry->list)) {
    			list_del_init(&entry->list);
    			local->cmd_queue_len--;
    		}
    	}
    
    	if (atomic_dec_and_test(&entry->usecnt) && entry->del_req)
    		kfree(entry);
    }
    
    
    /**
     * hostap_cmd_queue_free - Free Prism2 command queue entry
     * @local: pointer to private Host AP driver data
     * @entry: Prism2 command queue entry to be freed
     * @del_req: request the entry to be removed
     *
     * Free a Prism2 command queue entry.
     */
    static inline void hostap_cmd_queue_free(local_info_t *local,
    					 struct hostap_cmd_queue *entry,
    					 int del_req)
    {
    	unsigned long flags;
    
    	spin_lock_irqsave(&local->cmdlock, flags);
    	__hostap_cmd_queue_free(local, entry, del_req);
    	spin_unlock_irqrestore(&local->cmdlock, flags);
    }
    
    
    /**
     * prism2_clear_cmd_queue - Free all pending Prism2 command queue entries
     * @local: pointer to private Host AP driver data
     */
    static void prism2_clear_cmd_queue(local_info_t *local)
    {
    	struct list_head *ptr, *n;
    	unsigned long flags;
    	struct hostap_cmd_queue *entry;
    
    	spin_lock_irqsave(&local->cmdlock, flags);
    	list_for_each_safe(ptr, n, &local->cmd_queue) {
    		entry = list_entry(ptr, struct hostap_cmd_queue, list);
    		atomic_inc(&entry->usecnt);
    		printk(KERN_DEBUG "%s: removed pending cmd_queue entry "
    		       "(type=%d, cmd=0x%04x, param0=0x%04x)\n",
    		       local->dev->name, entry->type, entry->cmd,
    		       entry->param0);
    		__hostap_cmd_queue_free(local, entry, 1);
    	}
    	if (local->cmd_queue_len) {
    		/* This should not happen; print debug message and clear
    		 * queue length. */
    		printk(KERN_DEBUG "%s: cmd_queue_len (%d) not zero after "
    		       "flush\n", local->dev->name, local->cmd_queue_len);
    		local->cmd_queue_len = 0;
    	}
    	spin_unlock_irqrestore(&local->cmdlock, flags);
    }
    
    
    /**
     * hfa384x_cmd_issue - Issue a Prism2 command to the hardware
     * @dev: pointer to net_device
     * @entry: Prism2 command queue entry to be issued
     */
    
    static int hfa384x_cmd_issue(struct net_device *dev,
    
    				    struct hostap_cmd_queue *entry)
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    	int tries;
    	u16 reg;
    	unsigned long flags;
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    
    	if (local->func->card_present && !local->func->card_present(local))
    		return -ENODEV;
    
    	if (entry->issued) {
    		printk(KERN_DEBUG "%s: driver bug - re-issuing command @%p\n",
    		       dev->name, entry);
    	}
    
    	/* wait until busy bit is clear; this should always be clear since the
    	 * commands are serialized */
    	tries = HFA384X_CMD_BUSY_TIMEOUT;
    	while (HFA384X_INW(HFA384X_CMD_OFF) & HFA384X_CMD_BUSY && tries > 0) {
    		tries--;
    		udelay(1);
    	}
    #ifndef final_version
    	if (tries != HFA384X_CMD_BUSY_TIMEOUT) {
    		prism2_io_debug_error(dev, 1);
    		printk(KERN_DEBUG "%s: hfa384x_cmd_issue: cmd reg was busy "
    		       "for %d usec\n", dev->name,
    		       HFA384X_CMD_BUSY_TIMEOUT - tries);
    	}
    #endif
    	if (tries == 0) {
    		reg = HFA384X_INW(HFA384X_CMD_OFF);
    		prism2_io_debug_error(dev, 2);
    		printk(KERN_DEBUG "%s: hfa384x_cmd_issue - timeout - "
    		       "reg=0x%04x\n", dev->name, reg);
    		return -ETIMEDOUT;
    	}
    
    	/* write command */
    	spin_lock_irqsave(&local->cmdlock, flags);
    	HFA384X_OUTW(entry->param0, HFA384X_PARAM0_OFF);
    	HFA384X_OUTW(entry->param1, HFA384X_PARAM1_OFF);
    	HFA384X_OUTW(entry->cmd, HFA384X_CMD_OFF);
    	entry->issued = 1;
    	spin_unlock_irqrestore(&local->cmdlock, flags);
    
    	return 0;
    }
    
    
    /**
     * hfa384x_cmd - Issue a Prism2 command and wait (sleep) for completion
     * @dev: pointer to net_device
     * @cmd: Prism2 command code (HFA384X_CMD_CODE_*)
     * @param0: value for Param0 register
     * @param1: value for Param1 register (pointer; %NULL if not used)
     * @resp0: pointer for Resp0 data or %NULL if Resp0 is not needed
     *
     * Issue given command (possibly after waiting in command queue) and sleep
     * until the command is completed (or timed out or interrupted). This can be
     * called only from user process context.
     */
    static int hfa384x_cmd(struct net_device *dev, u16 cmd, u16 param0,
    		       u16 *param1, u16 *resp0)
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    	int err, res, issue, issued = 0;
    	unsigned long flags;
    	struct hostap_cmd_queue *entry;
    	DECLARE_WAITQUEUE(wait, current);
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    
    	if (in_interrupt()) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd called from interrupt "
    		       "context\n", dev->name);
    		return -1;
    	}
    
    	if (local->cmd_queue_len >= HOSTAP_CMD_QUEUE_MAX_LEN) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd: cmd_queue full\n",
    		       dev->name);
    		return -1;
    	}
    
    	if (signal_pending(current))
    		return -EINTR;
    
    
    	entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
    
    	if (entry == NULL) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd - kmalloc failed\n",
    		       dev->name);
    		return -ENOMEM;
    	}
    	atomic_set(&entry->usecnt, 1);
    	entry->type = CMD_SLEEP;
    	entry->cmd = cmd;
    	entry->param0 = param0;
    	if (param1)
    		entry->param1 = *param1;
    	init_waitqueue_head(&entry->compl);
    
    	/* prepare to wait for command completion event, but do not sleep yet
    	 */
    	add_wait_queue(&entry->compl, &wait);
    	set_current_state(TASK_INTERRUPTIBLE);
    
    	spin_lock_irqsave(&local->cmdlock, flags);
    	issue = list_empty(&local->cmd_queue);
    	if (issue)
    		entry->issuing = 1;
    	list_add_tail(&entry->list, &local->cmd_queue);
    	local->cmd_queue_len++;
    	spin_unlock_irqrestore(&local->cmdlock, flags);
    
    	err = 0;
    	if (!issue)
    		goto wait_completion;
    
    	if (signal_pending(current))
    		err = -EINTR;
    
    	if (!err) {
    		if (hfa384x_cmd_issue(dev, entry))
    			err = -ETIMEDOUT;
    		else
    			issued = 1;
    	}
    
     wait_completion:
    	if (!err && entry->type != CMD_COMPLETED) {
    		/* sleep until command is completed or timed out */
    		res = schedule_timeout(2 * HZ);
    	} else
    		res = -1;
    
    	if (!err && signal_pending(current))
    		err = -EINTR;
    
    	if (err && issued) {
    		/* the command was issued, so a CmdCompl event should occur
    		 * soon; however, there's a pending signal and
    		 * schedule_timeout() would be interrupted; wait a short period
    		 * of time to avoid removing entry from the list before
    		 * CmdCompl event */
    		udelay(300);
    	}
    
    	set_current_state(TASK_RUNNING);
    	remove_wait_queue(&entry->compl, &wait);
    
    	/* If entry->list is still in the list, it must be removed
    	 * first and in this case prism2_cmd_ev() does not yet have
    	 * local reference to it, and the data can be kfree()'d
    	 * here. If the command completion event is still generated,
    	 * it will be assigned to next (possibly) pending command, but
    	 * the driver will reset the card anyway due to timeout
    	 *
    	 * If the entry is not in the list prism2_cmd_ev() has a local
    	 * reference to it, but keeps cmdlock as long as the data is
    	 * needed, so the data can be kfree()'d here. */
    
    	/* FIX: if the entry->list is in the list, it has not been completed
    	 * yet, so removing it here is somewhat wrong.. this could cause
    	 * references to freed memory and next list_del() causing NULL pointer
    	 * dereference.. it would probably be better to leave the entry in the
    	 * list and the list should be emptied during hw reset */
    
    	spin_lock_irqsave(&local->cmdlock, flags);
    	if (!list_empty(&entry->list)) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd: entry still in list? "
    		       "(entry=%p, type=%d, res=%d)\n", dev->name, entry,
    		       entry->type, res);
    		list_del_init(&entry->list);
    		local->cmd_queue_len--;
    	}
    	spin_unlock_irqrestore(&local->cmdlock, flags);
    
    	if (err) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd: interrupted; err=%d\n",
    		       dev->name, err);
    		res = err;
    		goto done;
    	}
    
    	if (entry->type != CMD_COMPLETED) {
    		u16 reg = HFA384X_INW(HFA384X_EVSTAT_OFF);
    		printk(KERN_DEBUG "%s: hfa384x_cmd: command was not "
    		       "completed (res=%d, entry=%p, type=%d, cmd=0x%04x, "
    		       "param0=0x%04x, EVSTAT=%04x INTEN=%04x)\n", dev->name,
    		       res, entry, entry->type, entry->cmd, entry->param0, reg,
    		       HFA384X_INW(HFA384X_INTEN_OFF));
    		if (reg & HFA384X_EV_CMD) {
    			/* Command completion event is pending, but the
    			 * interrupt was not delivered - probably an issue
    			 * with pcmcia-cs configuration. */
    			printk(KERN_WARNING "%s: interrupt delivery does not "
    			       "seem to work\n", dev->name);
    		}
    		prism2_io_debug_error(dev, 3);
    		res = -ETIMEDOUT;
    		goto done;
    	}
    
    	if (resp0 != NULL)
    		*resp0 = entry->resp0;
    #ifndef final_version
    	if (entry->res) {
    		printk(KERN_DEBUG "%s: CMD=0x%04x => res=0x%02x, "
    		       "resp0=0x%04x\n",
    		       dev->name, cmd, entry->res, entry->resp0);
    	}
    #endif /* final_version */
    
    	res = entry->res;
     done:
    	hostap_cmd_queue_free(local, entry, 1);
    	return res;
    }
    
    
    /**
     * hfa384x_cmd_callback - Issue a Prism2 command; callback when completed
     * @dev: pointer to net_device
     * @cmd: Prism2 command code (HFA384X_CMD_CODE_*)
     * @param0: value for Param0 register
     * @callback: command completion callback function (%NULL = no callback)
    
    Pavel Roskin's avatar
    Pavel Roskin committed
     * @context: context data to be given to the callback function
    
     *
     * Issue given command (possibly after waiting in command queue) and use
     * callback function to indicate command completion. This can be called both
     * from user and interrupt context. The callback function will be called in
     * hardware IRQ context. It can be %NULL, when no function is called when
     * command is completed.
     */
    static int hfa384x_cmd_callback(struct net_device *dev, u16 cmd, u16 param0,
    				void (*callback)(struct net_device *dev,
    
    Pavel Roskin's avatar
    Pavel Roskin committed
    						 long context, u16 resp0,
    
    						 u16 status),
    
    Pavel Roskin's avatar
    Pavel Roskin committed
    				long context)
    
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    	int issue, ret;
    	unsigned long flags;
    	struct hostap_cmd_queue *entry;
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    
    	if (local->cmd_queue_len >= HOSTAP_CMD_QUEUE_MAX_LEN + 2) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd: cmd_queue full\n",
    		       dev->name);
    		return -1;
    	}
    
    
    	entry = kzalloc(sizeof(*entry), GFP_ATOMIC);
    
    	if (entry == NULL) {
    		printk(KERN_DEBUG "%s: hfa384x_cmd_callback - kmalloc "
    		       "failed\n", dev->name);
    		return -ENOMEM;
    	}
    	atomic_set(&entry->usecnt, 1);
    	entry->type = CMD_CALLBACK;
    	entry->cmd = cmd;
    	entry->param0 = param0;
    	entry->callback = callback;
    	entry->context = context;
    
    	spin_lock_irqsave(&local->cmdlock, flags);
    	issue = list_empty(&local->cmd_queue);
    	if (issue)
    		entry->issuing = 1;
    	list_add_tail(&entry->list, &local->cmd_queue);
    	local->cmd_queue_len++;
    	spin_unlock_irqrestore(&local->cmdlock, flags);
    
    	if (issue && hfa384x_cmd_issue(dev, entry))
    		ret = -ETIMEDOUT;
    	else
    		ret = 0;
    
    	hostap_cmd_queue_free(local, entry, ret);
    
    	return ret;
    }
    
    
    /**
     * __hfa384x_cmd_no_wait - Issue a Prism2 command (private)
     * @dev: pointer to net_device
     * @cmd: Prism2 command code (HFA384X_CMD_CODE_*)
     * @param0: value for Param0 register
     * @io_debug_num: I/O debug error number
     *
     * Shared helper function for hfa384x_cmd_wait() and hfa384x_cmd_no_wait().
     */
    static int __hfa384x_cmd_no_wait(struct net_device *dev, u16 cmd, u16 param0,
    				 int io_debug_num)
    {
    	int tries;
    	u16 reg;
    
    	/* wait until busy bit is clear; this should always be clear since the
    	 * commands are serialized */
    	tries = HFA384X_CMD_BUSY_TIMEOUT;
    	while (HFA384X_INW(HFA384X_CMD_OFF) & HFA384X_CMD_BUSY && tries > 0) {
    		tries--;
    		udelay(1);
    	}
    	if (tries == 0) {
    		reg = HFA384X_INW(HFA384X_CMD_OFF);
    		prism2_io_debug_error(dev, io_debug_num);
    		printk(KERN_DEBUG "%s: __hfa384x_cmd_no_wait(%d) - timeout - "
    		       "reg=0x%04x\n", dev->name, io_debug_num, reg);
    		return -ETIMEDOUT;
    	}
    
    	/* write command */
    	HFA384X_OUTW(param0, HFA384X_PARAM0_OFF);
    	HFA384X_OUTW(cmd, HFA384X_CMD_OFF);
    
    	return 0;
    }
    
    
    /**
     * hfa384x_cmd_wait - Issue a Prism2 command and busy wait for completion
     * @dev: pointer to net_device
     * @cmd: Prism2 command code (HFA384X_CMD_CODE_*)
     * @param0: value for Param0 register
     */
    static int hfa384x_cmd_wait(struct net_device *dev, u16 cmd, u16 param0)
    {
    	int res, tries;
    	u16 reg;
    
    	res = __hfa384x_cmd_no_wait(dev, cmd, param0, 4);
    	if (res)
    		return res;
    
            /* wait for command completion */
    	if ((cmd & HFA384X_CMDCODE_MASK) == HFA384X_CMDCODE_DOWNLOAD)
    		tries = HFA384X_DL_COMPL_TIMEOUT;
    	else
    		tries = HFA384X_CMD_COMPL_TIMEOUT;
    
            while (!(HFA384X_INW(HFA384X_EVSTAT_OFF) & HFA384X_EV_CMD) &&
                   tries > 0) {
                    tries--;
                    udelay(10);
            }
            if (tries == 0) {
                    reg = HFA384X_INW(HFA384X_EVSTAT_OFF);
    		prism2_io_debug_error(dev, 5);
                    printk(KERN_DEBUG "%s: hfa384x_cmd_wait - timeout2 - "
    		       "reg=0x%04x\n", dev->name, reg);
                    return -ETIMEDOUT;
            }
    
            res = (HFA384X_INW(HFA384X_STATUS_OFF) &
                   (BIT(14) | BIT(13) | BIT(12) | BIT(11) | BIT(10) | BIT(9) |
                    BIT(8))) >> 8;
    #ifndef final_version
    	if (res) {
    		printk(KERN_DEBUG "%s: CMD=0x%04x => res=0x%02x\n",
    		       dev->name, cmd, res);
    	}
    #endif
    
    	HFA384X_OUTW(HFA384X_EV_CMD, HFA384X_EVACK_OFF);
    
    	return res;
    }
    
    
    /**
     * hfa384x_cmd_no_wait - Issue a Prism2 command; do not wait for completion
     * @dev: pointer to net_device
     * @cmd: Prism2 command code (HFA384X_CMD_CODE_*)
     * @param0: value for Param0 register
     */
    static inline int hfa384x_cmd_no_wait(struct net_device *dev, u16 cmd,
    				      u16 param0)
    {
    	return __hfa384x_cmd_no_wait(dev, cmd, param0, 6);
    }
    
    
    /**
     * prism2_cmd_ev - Prism2 command completion event handler
     * @dev: pointer to net_device
     *
     * Interrupt handler for command completion events. Called by the main
     * interrupt handler in hardware IRQ context. Read Resp0 and status registers
     * from the hardware and ACK the event. Depending on the issued command type
     * either wake up the sleeping process that is waiting for command completion
     * or call the callback function. Issue the next command, if one is pending.
     */
    static void prism2_cmd_ev(struct net_device *dev)
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    	struct hostap_cmd_queue *entry = NULL;
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    
    	spin_lock(&local->cmdlock);
    	if (!list_empty(&local->cmd_queue)) {
    		entry = list_entry(local->cmd_queue.next,
    				   struct hostap_cmd_queue, list);
    		atomic_inc(&entry->usecnt);
    		list_del_init(&entry->list);
    		local->cmd_queue_len--;
    
    		if (!entry->issued) {
    			printk(KERN_DEBUG "%s: Command completion event, but "
    			       "cmd not issued\n", dev->name);
    			__hostap_cmd_queue_free(local, entry, 1);
    			entry = NULL;
    		}
    	}
    	spin_unlock(&local->cmdlock);
    
    	if (!entry) {
    		HFA384X_OUTW(HFA384X_EV_CMD, HFA384X_EVACK_OFF);
    		printk(KERN_DEBUG "%s: Command completion event, but no "
    		       "pending commands\n", dev->name);
    		return;
    	}
    
    	entry->resp0 = HFA384X_INW(HFA384X_RESP0_OFF);
    	entry->res = (HFA384X_INW(HFA384X_STATUS_OFF) &
    		      (BIT(14) | BIT(13) | BIT(12) | BIT(11) | BIT(10) |
    		       BIT(9) | BIT(8))) >> 8;
    	HFA384X_OUTW(HFA384X_EV_CMD, HFA384X_EVACK_OFF);
    
    	/* TODO: rest of the CmdEv handling could be moved to tasklet */
    	if (entry->type == CMD_SLEEP) {
    		entry->type = CMD_COMPLETED;
    		wake_up_interruptible(&entry->compl);
    	} else if (entry->type == CMD_CALLBACK) {
    		if (entry->callback)
    			entry->callback(dev, entry->context, entry->resp0,
    					entry->res);
    	} else {
    		printk(KERN_DEBUG "%s: Invalid command completion type %d\n",
    		       dev->name, entry->type);
    	}
    	hostap_cmd_queue_free(local, entry, 1);
    
    	/* issue next command, if pending */
    	entry = NULL;
    	spin_lock(&local->cmdlock);
    	if (!list_empty(&local->cmd_queue)) {
    		entry = list_entry(local->cmd_queue.next,
    				   struct hostap_cmd_queue, list);
    		if (entry->issuing) {
    			/* hfa384x_cmd() has already started issuing this
    			 * command, so do not start here */
    			entry = NULL;
    		}
    		if (entry)
    			atomic_inc(&entry->usecnt);
    	}
    	spin_unlock(&local->cmdlock);
    
    	if (entry) {
    		/* issue next command; if command issuing fails, remove the
    		 * entry from cmd_queue */
    		int res = hfa384x_cmd_issue(dev, entry);
    		spin_lock(&local->cmdlock);
    		__hostap_cmd_queue_free(local, entry, res);
    		spin_unlock(&local->cmdlock);
    	}
    }
    
    
    
    static int hfa384x_wait_offset(struct net_device *dev, u16 o_off)
    
    {
    	int tries = HFA384X_BAP_BUSY_TIMEOUT;
    	int res = HFA384X_INW(o_off) & HFA384X_OFFSET_BUSY;
    
    	while (res && tries > 0) {
    		tries--;
    		udelay(1);
    		res = HFA384X_INW(o_off) & HFA384X_OFFSET_BUSY;
    	}
    	return res;
    }
    
    
    /* Offset must be even */
    static int hfa384x_setup_bap(struct net_device *dev, u16 bap, u16 id,
    			     int offset)
    {
    	u16 o_off, s_off;
    	int ret = 0;
    
    	if (offset % 2 || bap > 1)
    		return -EINVAL;
    
    	if (bap == BAP1) {
    		o_off = HFA384X_OFFSET1_OFF;
    		s_off = HFA384X_SELECT1_OFF;
    	} else {
    		o_off = HFA384X_OFFSET0_OFF;
    		s_off = HFA384X_SELECT0_OFF;
    	}
    
    	if (hfa384x_wait_offset(dev, o_off)) {
    		prism2_io_debug_error(dev, 7);
    		printk(KERN_DEBUG "%s: hfa384x_setup_bap - timeout before\n",
    		       dev->name);
    		ret = -ETIMEDOUT;
    		goto out;
    	}
    
    	HFA384X_OUTW(id, s_off);
    	HFA384X_OUTW(offset, o_off);
    
    	if (hfa384x_wait_offset(dev, o_off)) {
    		prism2_io_debug_error(dev, 8);
    		printk(KERN_DEBUG "%s: hfa384x_setup_bap - timeout after\n",
    		       dev->name);
    		ret = -ETIMEDOUT;
    		goto out;
    	}
    #ifndef final_version
    	if (HFA384X_INW(o_off) & HFA384X_OFFSET_ERR) {
    		prism2_io_debug_error(dev, 9);
    		printk(KERN_DEBUG "%s: hfa384x_setup_bap - offset error "
    		       "(%d,0x04%x,%d); reg=0x%04x\n",
    		       dev->name, bap, id, offset, HFA384X_INW(o_off));
    		ret = -EINVAL;
    	}
    #endif
    
     out:
    	return ret;
    }
    
    
    static int hfa384x_get_rid(struct net_device *dev, u16 rid, void *buf, int len,
    			   int exact_len)
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    	int res, rlen = 0;
    	struct hfa384x_rid_hdr rec;
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    
    	if (local->no_pri) {
    		printk(KERN_DEBUG "%s: cannot get RID %04x (len=%d) - no PRI "
    		       "f/w\n", dev->name, rid, len);
    		return -ENOTTY; /* Well.. not really correct, but return
    				 * something unique enough.. */
    	}
    
    	if ((local->func->card_present && !local->func->card_present(local)) ||
    	    local->hw_downloading)
    		return -ENODEV;
    
    
    	res = mutex_lock_interruptible(&local->rid_bap_mtx);
    
    	if (res)
    		return res;
    
    	res = hfa384x_cmd(dev, HFA384X_CMDCODE_ACCESS, rid, NULL, NULL);
    	if (res) {
    		printk(KERN_DEBUG "%s: hfa384x_get_rid: CMDCODE_ACCESS failed "
    		       "(res=%d, rid=%04x, len=%d)\n",
    		       dev->name, res, rid, len);
    
    		mutex_unlock(&local->rid_bap_mtx);
    
    		return res;
    	}
    
    	spin_lock_bh(&local->baplock);
    
    	res = hfa384x_setup_bap(dev, BAP0, rid, 0);
    	if (!res)
    		res = hfa384x_from_bap(dev, BAP0, &rec, sizeof(rec));
    
    	if (le16_to_cpu(rec.len) == 0) {
    		/* RID not available */
    		res = -ENODATA;
    	}
    
    	rlen = (le16_to_cpu(rec.len) - 1) * 2;
    	if (!res && exact_len && rlen != len) {
    		printk(KERN_DEBUG "%s: hfa384x_get_rid - RID len mismatch: "
    		       "rid=0x%04x, len=%d (expected %d)\n",
    		       dev->name, rid, rlen, len);
    		res = -ENODATA;
    	}
    
    	if (!res)
    		res = hfa384x_from_bap(dev, BAP0, buf, len);
    
    	spin_unlock_bh(&local->baplock);
    
    	mutex_unlock(&local->rid_bap_mtx);
    
    
    	if (res) {
    		if (res != -ENODATA)
    			printk(KERN_DEBUG "%s: hfa384x_get_rid (rid=%04x, "
    			       "len=%d) - failed - res=%d\n", dev->name, rid,
    			       len, res);
    		if (res == -ETIMEDOUT)
    			prism2_hw_reset(dev);
    		return res;
    	}
    
    	return rlen;
    }
    
    
    static int hfa384x_set_rid(struct net_device *dev, u16 rid, void *buf, int len)
    {
    	struct hostap_interface *iface;
    	local_info_t *local;
    	struct hfa384x_rid_hdr rec;
    	int res;
    
    	iface = netdev_priv(dev);
    	local = iface->local;
    
    	if (local->no_pri) {
    		printk(KERN_DEBUG "%s: cannot set RID %04x (len=%d) - no PRI "
    		       "f/w\n", dev->name, rid, len);
    		return -ENOTTY; /* Well.. not really correct, but return
    				 * something unique enough.. */
    	}
    
    	if ((local->func->card_present && !local->func->card_present(local)) ||
    	    local->hw_downloading)
    		return -ENODEV;
    
    	rec.rid = cpu_to_le16(rid);
    	/* RID len in words and +1 for rec.rid */
    	rec.len = cpu_to_le16(len / 2 + len % 2 + 1);
    
    
    	res = mutex_lock_interruptible(&local->rid_bap_mtx);
    
    	if (res)
    		return res;
    
    	spin_lock_bh(&local->baplock);
    	res = hfa384x_setup_bap(dev, BAP0, rid, 0);
    	if (!res)
    		res = hfa384x_to_bap(dev, BAP0, &rec, sizeof(rec));
    	if (!res)
    		res = hfa384x_to_bap(dev, BAP0, buf, len);
    	spin_unlock_bh(&local->baplock);
    
    	if (res) {
    		printk(KERN_DEBUG "%s: hfa384x_set_rid (rid=%04x, len=%d) - "
    		       "failed - res=%d\n", dev->name, rid, len, res);
    
    		mutex_unlock(&local->rid_bap_mtx);
    
    		return res;
    	}
    
    	res = hfa384x_cmd(dev, HFA384X_CMDCODE_ACCESS_WRITE, rid, NULL, NULL);
    
    	mutex_unlock(&local->rid_bap_mtx);
    
    	if (res) {
    		printk(KERN_DEBUG "%s: hfa384x_set_rid: CMDCODE_ACCESS_WRITE "
    		       "failed (res=%d, rid=%04x, len=%d)\n",
    		       dev->name, res, rid, len);
    
    
    		if (res == -ETIMEDOUT)
    			prism2_hw_reset(dev);
    	}
    
    
    	return res;
    }
    
    
    static void hfa384x_disable_interrupts(struct net_device *dev)
    {
    	/* disable interrupts and clear event status */
    	HFA384X_OUTW(0, HFA384X_INTEN_OFF);
    	HFA384X_OUTW(0xffff, HFA384X_EVACK_OFF);
    }
    
    
    static void hfa384x_enable_interrupts(struct net_device *dev)
    {
    	/* ack pending events and enable interrupts from selected events */
    	HFA384X_OUTW(0xffff, HFA384X_EVACK_OFF);
    	HFA384X_OUTW(HFA384X_EVENT_MASK, HFA384X_INTEN_OFF);
    }
    
    
    static void hfa384x_events_no_bap0(struct net_device *dev)
    {
    	HFA384X_OUTW(HFA384X_EVENT_MASK & ~HFA384X_BAP0_EVENTS,
    		     HFA384X_INTEN_OFF);
    }
    
    
    static void hfa384x_events_all(struct net_device *dev)
    {
    	HFA384X_OUTW(HFA384X_EVENT_MASK, HFA384X_INTEN_OFF);
    }
    
    
    static void hfa384x_events_only_cmd(struct net_device *dev)
    {
    	HFA384X_OUTW(HFA384X_EV_CMD, HFA384X_INTEN_OFF);
    }
    
    
    static u16 hfa384x_allocate_fid(struct net_device *dev, int len)
    {
    	u16 fid;
    	unsigned long delay;
    
    	/* FIX: this could be replace with hfa384x_cmd() if the Alloc event
    	 * below would be handled like CmdCompl event (sleep here, wake up from
    	 * interrupt handler */
    	if (hfa384x_cmd_wait(dev, HFA384X_CMDCODE_ALLOC, len)) {
    		printk(KERN_DEBUG "%s: cannot allocate fid, len=%d\n",
    		       dev->name, len);
    		return 0xffff;
    	}
    
    	delay = jiffies + HFA384X_ALLOC_COMPL_TIMEOUT;
    	while (!(HFA384X_INW(HFA384X_EVSTAT_OFF) & HFA384X_EV_ALLOC) &&
    	       time_before(jiffies, delay))
    		yield();
    	if (!(HFA384X_INW(HFA384X_EVSTAT_OFF) & HFA384X_EV_ALLOC)) {
    		printk("%s: fid allocate, len=%d - timeout\n", dev->name, len);
    		return 0xffff;
    	}
    
    	fid = HFA384X_INW(HFA384X_ALLOCFID_OFF);
    	HFA384X_OUTW(HFA384X_EV_ALLOC, HFA384X_EVACK_OFF);
    
    	return fid;