/*+M*************************************************************************
 * Wireless PCMCIA device driver for Linux (WVLAN49).
 *
 * Copyright (c) 1998-2001 Agere Systems Inc. 
 * All rights reserved.
 *   http://www.agere.com
 *
 * Initially developed by TriplePoint, Inc.
 *   http://www.triplepoint.com
 *
 *---------------------------------------------------------------------------
 * This driver supports the following features:
 *   - Hot plug/unplug
 *   - Access Point and peer-to-peer communication
 *   - Card power management
 *   - Standard, Turbo and Turbo 11Mb cards
 *   - Wired Equivalent Privacy (WEP)
 *   - Driver utility interface (UIL)
 *   - Wireless Extensions
 *
 *   Refer to the manual page for additional configuration, feature, and
 *   support information.
 *---------------------------------------------------------------------------
 * 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, 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.
 *
 * ALTERNATIVELY, this driver may be distributed under the terms of
 * the following license, in which case the provisions of this license
 * are required INSTEAD OF the GNU General Public License. (This clause
 * is necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *-M*************************************************************************/
#ifdef PCMCIA_DEBUG
#define DBG     1
#endif // PCMCIA_DEBUG

#define WVLAN_49
#define DRIVER_NAME     "WVLAN49"
#define DRV_IDENTITY    49

#include <pcmcia/config.h>
#include <pcmcia/k_compat.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <linux/malloc.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/bitops.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/if_arp.h>
#include <linux/ioport.h>

#ifdef HAS_WIRELESS_EXTENSIONS
#include <linux/wireless.h>
#define IEEE_WIRELESS
#define USE_DBM
#define WIRELESS_SPY
#define RETURN_CURRENT_NETWORKNAME
#define USE_FREQUENCY
#endif // HAS_WIRELESS_EXTENSIONS

#include <pcmcia/version.h>
#include <pcmcia/cs_types.h>
#include <pcmcia/cs.h>
#include <pcmcia/cistpl.h>
#include <pcmcia/cisreg.h>
#include <pcmcia/ciscode.h>
#include <pcmcia/ds.h>
#include <hcf/debug.h>
#include <hcf/libhcf.h>

#define NELEM(arr) (sizeof(arr) / sizeof(arr[0]))

#if (LINUX_VERSION_CODE >= VERSION(2,4,0))
#define kfree_s(a,b) kfree(a)
#endif

// The buffer size associated with the LTV record (allowing for the header
// information).
#define MAX_LTV_BUF_SIZE (512 - (sizeof(hcf_16) * 2))

#define HCF_TALLIES_SIZE (sizeof(CFG_HERMES_TALLIES_STRCT) + \
    (sizeof(hcf_16) * 2))

#define HCF_MAX_MULTICAST           16
#define HCF_MAX_PACKET_SIZE         1508
#define HCF_MAX_NAME_LEN            32
#define HCF_NUM_IO_PORTS            0x40
#define TX_TIMEOUT                  ((400 * HZ) / 1000)

#define HCF_MIN_COMM_QUALITY        0
#define HCF_MAX_COMM_QUALITY        92
#define HCF_MIN_SIGNAL_LEVEL        47
#define HCF_MAX_SIGNAL_LEVEL        138
#define HCF_MIN_NOISE_LEVEL         47
#define HCF_MAX_NOISE_LEVEL         138
#define HCF_0DBM_OFFSET             149

// For encryption (WEP):
#define MAX_KEY_SIZE 13			// 104 bits
#define MIN_KEY_SIZE  5			// 40 bits RC4 - WEP
#define MAX_KEYS      4			// 4 different keys

typedef struct {
    __u16   length;
    __u8    name[HCF_MAX_NAME_LEN];
} wvName_t;

// Host <==> Adapter conversions
#define atohl(v) cpu_to_le32(v)
#define atohs(v) cpu_to_le16(v)
#define htoal(v) le32_to_cpu(v)
#define htoas(v) le16_to_cpu(v)

#if (LINUX_VERSION_CODE < VERSION(2,1,0))

#ifndef spin_lock_init
typedef struct {
    volatile unsigned int lock;
} spinlock_t;

#define spin_lock_init(l) do { } while(0)
#endif // spin_lock_init

#ifndef CAP_NET_ADMIN
#define CAP_NET_ADMIN     12
#endif // CAP_NET_ADMIN

#ifndef capable
#define capable(x)        suser()
#endif // capable

// All pre-2.1.0 kernels only support integers (32-bit) values or
// normal strings and no MODULE_PARM_DESC().
#define MODULE_PARM_DESC(a,b)
#define p_u8    __u32
#define p_s8    __s32
#define p_u16   __u32
#define p_s16   __s32
#define p_u32   __u32
#define p_s32   __s32
#define p_char  char
#else // LINUX_VERSION_CODE
#define p_u8    __u8
#define p_s8    __s8
#define p_u16   __u16
#define p_s16   __s16
#define p_u32   __u32
#define p_s32   __s32
#define p_char  char
#endif // LINUX_VERSION_CODE

typedef enum {
    FALSE = 0,
    TRUE  = 1
} bool_t;

typedef struct {
    hcf_16      len;
    hcf_16      cmd;
    union {
        hcf_8       u8[MAX_LTV_BUF_SIZE / sizeof(hcf_8)];
        hcf_16      u16[MAX_LTV_BUF_SIZE / sizeof(hcf_16)];
        hcf_32      u32[MAX_LTV_BUF_SIZE / sizeof(hcf_32)];
    } u;
} ltv_t;

#define WVLAN2_IOCTL_UIL            SIOCDEVPRIVATE
#define   WVLAN2_UIL_CONNECT          0x01
#define   WVLAN2_UIL_DISCONNECT       0x02
#define   WVLAN2_UIL_BLOCK            0x03
#define   WVLAN2_UIL_UNBLOCK          0x04
#define   WVLAN2_UIL_ACTION_TALLIES   0x05
#define   WVLAN2_UIL_ACTION_SCAN      0x06
#define   WVLAN2_UIL_ACTION_DIAG      0x07
#define   WVLAN2_UIL_SEND_DIAG_MSG    0x08
#define   WVLAN2_UIL_GET_INFO         0x09
#define   WVLAN2_UIL_PUT_INFO         0x0A
#define SIOCSIWNETNAME              SIOCDEVPRIVATE+1
#define SIOCGIWNETNAME              SIOCDEVPRIVATE+2
#define SIOCSIWSTANAME              SIOCDEVPRIVATE+3
#define SIOCGIWSTANAME              SIOCDEVPRIVATE+4
#define SIOCSIWPORTTYPE             SIOCDEVPRIVATE+5
#define SIOCGIWPORTTYPE             SIOCDEVPRIVATE+6

struct uilreq {
    union {
        char    ifrn_name[IFNAMSIZ];
    } ifr_ifrn;

    __u8        command;
    __u8        result;
    __u16       len;
    void       *data;
};

#define UIL_FAILURE		0xFF

#define WVLAN2_UIL_CONNECTED    (0x01L << 0)
#define WVLAN2_UIL_BUSY         (0x01L << 1)

struct wvlan2_private {
    dev_node_t                  node;
    struct net_device           *dev;
    spinlock_t                  slock;
    struct net_device_stats     stats;
#ifdef WIRELESS_EXT
    struct iw_statistics        wstats;
#ifdef WIRELESS_SPY
    int                         spy_number;
    u_char                      spy_address[IW_MAX_SPY][MAC_ADDR_SIZE];
    struct iw_quality           spy_stat[IW_MAX_SPY];
#endif // WIRELESS_SPY
#endif // WIRELESS_EXT
    IFB_STRCT                  *hcfCtx;
    u_long                      flags;
    CFG_DRV_INFO_STRCT          driverInfo;
    CFG_IDENTITY_STRCT          driverIdentity;
    ltv_t                       ltvRecord;
    u_long                      txBytes;

    hcf_8                       PortType;           // 1 - 3 (1 [Normal] | 3 [AdHoc])
    hcf_8                       Channel;            // 0 - 14 (0)
    hcf_8                       TxRateControl;      // 1 - 7 (3)
    hcf_8                       DistanceBetweenAPs; // 1 - 3 (1)
    hcf_16                      RTSThreshold;       // 0 - 2347 (2347)
    hcf_8                       PMEnabled;          // 0 - 1 (0)
    hcf_8                       MicrowaveRobustness;// 0 - 1 (0)
    hcf_8                       CreateIBSS;         // 0 - 1 (0)
    hcf_8                       MulticastReceive;   // 0 - 1 (1)
    hcf_16                      MaxSleepDuration;   // 0 - 65535 (100)
    hcf_8                       MACAddress[ETH_ALEN];
    char                        NetworkName[HCF_MAX_NAME_LEN+1];
    char                        StationName[HCF_MAX_NAME_LEN+1];
    hcf_8                       EnableEncryption;   // 0 - 1 (0)
#define MAX_KEY_LEN (2 + (13 * 2)) // 0x plus 13 hex digit pairs
    char                        Key1[MAX_KEY_LEN+1];
    char                        Key2[MAX_KEY_LEN+1];
    char                        Key3[MAX_KEY_LEN+1];
    char                        Key4[MAX_KEY_LEN+1];
    hcf_8                       TransmitKeyID;      // 1 - 4 (1)
    CFG_CNF_DEFAULT_KEYS_STRCT	DefaultKeys;
#define MB_SIZE     1024
    u_char                      mailbox[MB_SIZE];
};

/*--------------------------------------------------------------------------*/
#define VALIDATE_PARAMS

#if DBG

#ifndef PCMCIA_DEBUG
#define PCMCIA_DEBUG    0
#endif // PCMCIA_DEBUG

#undef  DBG_TRAP
#define DBG_TRAP         {}

static p_u32    pc_debug = PCMCIA_DEBUG;
MODULE_PARM(pc_debug, "i");
static p_u32    debug_flags = DBG_DEFAULTS;
MODULE_PARM(debug_flags, "l");
#define DEBUG(n, args...) if (pc_debug>=(n)) printk(KERN_DEBUG args);

typedef struct {
    DBG_DEFN
} drv_info_t;

static drv_info_t   Wvlan2Info = { "wvlan2", 0, 0 };
static drv_info_t  *DbgInfo = &Wvlan2Info;

static const char *DbgHwAddr(unsigned char *hwAddr);
static const char *DbgEvent(int mask);
#endif // DBG

/*--------------------------------------------------------------------------*/
/* Parameters that can be set with 'insmod' */
static p_u16    irq_mask                = 0xdeb8; // IRQ3,4,5,7,9,10,11,12,14,15
static p_s8     irq_list[4]             = { -1 };
static p_char  *network_name            = NULL;
static p_u8     port_type               = 1;      // 1-Normal 3-AdHoc
static p_u8     channel                 = 0;
static p_u8     distance_between_aps    = 1;
static p_u8     transmit_rate           = 3;
static p_u16    medium_reservation      = 2347;
static p_char  *card_power_management   = "N";
static p_char  *microwave_robustness    = "N";
static p_char  *create_ibss             = "N";
static p_char  *receive_all_multicasts  = "Y";
static p_u16    maximum_sleep_duration  = 100;
static p_u8     mac_address[ETH_ALEN]   = { 0 };
static p_char  *station_name            = "Linux";
static p_char  *enable_encryption       = "N";
static p_char  *key_1                   = "";
static p_char  *key_2                   = "";
static p_char  *key_3                   = "";
static p_char  *key_4                   = "";
static p_u8     transmit_key_id         = 1;

MODULE_PARM(irq_mask,               "h");
MODULE_PARM_DESC(irq_mask,               "IRQ mask [0xdeb8]");
MODULE_PARM(irq_list,               "1-4b");
MODULE_PARM_DESC(irq_list,               "IRQ list [<irq_mask>]");
MODULE_PARM(network_name,           "s");
MODULE_PARM_DESC(network_name,           "Network Name (<string>) [ANY]");
MODULE_PARM(port_type,              "b");
MODULE_PARM_DESC(port_type,              "Port Type (1 - 3) [1]");
MODULE_PARM(channel,                "b");
MODULE_PARM_DESC(channel,                "Channel (0 - 14) [0]");
MODULE_PARM(distance_between_aps,   "b");
MODULE_PARM_DESC(distance_between_aps,   "Distance Between APs (1 - 3) [1]");
MODULE_PARM(transmit_rate,          "b");
MODULE_PARM_DESC(transmit_rate,          "Transmit Rate Control (1 - 7) [3]");
MODULE_PARM(medium_reservation,     "h");
MODULE_PARM_DESC(medium_reservation,     "Medium Reservation (RTS/CTS Fragment Length) (256 - 2347) [2347]");
MODULE_PARM(card_power_management,  "s");
MODULE_PARM_DESC(card_power_management,  "Power Management Enabled (<string> N or Y) [N]");
MODULE_PARM(microwave_robustness,   "s");
MODULE_PARM_DESC(microwave_robustness,   "Microwave Oven Robustness Enabled (<string> N or Y) [N]");
MODULE_PARM(create_ibss,            "s");
MODULE_PARM_DESC(create_ibss,            "Create IBSS (<string> N or Y) [N]");
MODULE_PARM(receive_all_multicasts, "s");
MODULE_PARM_DESC(receive_all_multicasts, "Multicast Receive Enable (<string> N or Y) [Y]");
MODULE_PARM(maximum_sleep_duration, "h");
MODULE_PARM_DESC(maximum_sleep_duration, "Maximum Power Management Sleep Duration (0 - 65535) [100]");
MODULE_PARM(mac_address,            "6b");
MODULE_PARM_DESC(mac_address,            "Hardware Ethernet Address ([0x00-0xff],[0x00-0xff],[0x00-0xff],[0x00-0xff],[0x00-0xff],[0x00-0xff]) [<factory value>]");
MODULE_PARM(station_name,           "s");
MODULE_PARM_DESC(station_name,           "Station Name (<string>) [Linux]");
MODULE_PARM(enable_encryption,      "s");
MODULE_PARM_DESC(enable_encryption,      "Enable Encryption (<string> N or Y) [N]");
MODULE_PARM(key_1,                  "s");
MODULE_PARM_DESC(key_1,                  "Data Encryption Key 1 (<string>) []");
MODULE_PARM(key_2,                  "s");
MODULE_PARM_DESC(key_2,                  "Data Encryption Key 2 (<string>) []");
MODULE_PARM(key_3,                  "s");
MODULE_PARM_DESC(key_3,                  "Data Encryption Key 3 (<string>) []");
MODULE_PARM(key_4,                  "s");
MODULE_PARM_DESC(key_4,                  "Data Encryption Key 4 (<string>) []");
MODULE_PARM(transmit_key_id,        "b");
MODULE_PARM_DESC(transmit_key_id,        "Transmit Key ID (1 - 4) [1]");

/*--------------------------------------------------------------------------*/
static int wvlan2_open(struct net_device *dev);
static int wvlan2_close(struct net_device *dev);
static int wvlan2_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
static void wvlan2_isr IRQ(int irq, void *dev_id, struct pt_regs *regs);
static void wvlan2_tx_timeout(struct net_device *dev);
static int wvlan2_tx(struct sk_buff *skb, struct net_device *dev);
static int wvlan2_rx(struct net_device *dev);

static void wvlan2_insert(dev_link_t *link);
static void wvlan2_remove(struct net_device *dev);
static void wvlan2_suspend(struct net_device *dev);
static void wvlan2_resume(struct net_device *dev);
static int wvlan2_reset(struct net_device *dev);
static int wvlan2_config(struct net_device *dev, struct ifmap *map);
static struct net_device_stats *wvlan2_stats(struct net_device *dev);
#ifdef WIRELESS_EXT
static struct iw_statistics *wvlan2_wireless_stats(struct net_device *dev);
#ifdef WIRELESS_SPY
static inline void wvlan2_spy_gather (struct net_device *dev, u_char *mac);
#endif // WIRELESS_SPY
#endif // WIRELESS_EXT
static void wvlan2_hcf_error(struct net_device *dev, int hcfStatus);
static int wvlan2_is_open(struct wvlan2_private *lp);
static int wvlan2_commit(struct wvlan2_private *lp);

#ifdef NEW_MULTICAST
static void wvlan2_multicast(struct net_device *dev);
#else
static void wvlan2_multicast(struct net_device *dev, int num_addrs, void *addrs);
#endif
#if WIRELESS_EXT > 8
static int wvlan2_has_wep (IFBP ifbp);
#endif

static dev_link_t *adapter_attach(void);
static void adapter_detach(dev_link_t *);
static void adapter_release(u_long arg);
static int adapter_event(event_t event, int priority,
    event_callback_args_t *args);

/*--------------------------------------------------------------------------*/
#define DRV_VARIANT         1
#define DRV_MAJOR_VERSION   6
#define DRV_MINOR_VERSION   16

static const char version[] =
"wavelan2_cs.c 6.16 11/23/2001 13:00:00";

static dev_info_t  dev_info = "wavelan2_cs";
static dev_link_t *dev_list = NULL;

// Frequency of channel 1..14 in MHz
static const long frequency_list[] = 
{ 
    2412, 2417, 2422, 2427, 2432, 2437, 2442,
    2447, 2452, 2457, 2462, 2467, 2472, 2484
};

#ifdef WIRELESS_EXT
/*+F*************************************************************************
 * Function:
 *   dbm
 *
 * Description:
 *   Return the energy value in dBm.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
dbm(int value)
{
    // Truncate the value to be between min and max.
    if (value < HCF_MIN_SIGNAL_LEVEL)
        value = HCF_MIN_SIGNAL_LEVEL;
    if (value > HCF_MAX_SIGNAL_LEVEL) 
        value = HCF_MAX_SIGNAL_LEVEL;

    // Return the energy value in dBm.
    return value - HCF_0DBM_OFFSET;
}

#ifndef USE_DBM
/*+F*************************************************************************
 * Function:
 *   percent
 *
 * Description:
 *   Return the value as a percentage of min to max.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
percent(int value, int min, int max)
{
    // Truncate the value to be between min and max.
    if (value < min) value = min;
    if (value > max) value = max;

    // Return the value as a percentage of min to max.
    return (((value - min) * 100) / (max - min));
}
#endif // USE_DBM
#endif // WIRELESS_EXT

#ifdef VALIDATE_PARAMS
/*+F*************************************************************************
 * Function:
 *   is_valid_key_string
 *
 * Description:
 *   Returns non-zero if the string contains a valid key
 *
 * Status: Complete
 *-F*************************************************************************/
static int
is_valid_key_string(char *s)
{
    int l,i;
    
    l = strlen(s);
    
    // 0x followed by 5 or 13 hexadecimal digit pairs is valid
    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
    {
        if (l == 12 || l == 28)
        {
            for (i = 2; i < l; i++)
            {
                if (!isxdigit(s[i])) return 0;
            }
            return 1;
        }
        else return 0;
    }
    // string with 0,5 or 13 characters is valid
    else
    {
        return (l == 0 || l == 5 || l == 13);
    }
}
#endif // VALIDATE_PARAMS

/*+F*************************************************************************
 * Function:
 *   hexdigit2int
 *
 * Description:
 *   Converts a hexadecimal digit character to an integer 
 *
 * Status: Complete
 *-F*************************************************************************/
static int
hexdigit2int(char c)
{
   if (c >= '0' && c <= '9') return c - '0';
   if (c >= 'A' && c <= 'F') return c - 'A' + 10;
   if (c >= 'a' && c <= 'f') return c - 'a' + 10;
   return 0;
}

/*+F*************************************************************************
 * Function:
 *   key_string2key
 *
 * Description:
 *   Converts a key_string to a key
 *   Assumes the key_string is validated with is_valid_key_string()
 *
 * Status: Complete
 *-F*************************************************************************/
static void
key_string2key(char *ks, KEY_STRCT *key)
{
    int l,i,n;
    char *p;
    
    l = strlen(ks);

    // 0x followed by hexadecimal digit pairs
    if (ks[0] == '0' && (ks[1] == 'x' || ks[1] == 'X'))
    {
        n = 0;
        p = (char *)key->key;
        for (i = 2; i < l; i+=2)
        {
           *p++ = (hexdigit2int(ks[i]) << 4) + hexdigit2int(ks[i+1]);
           n++;
        }
        key->len = htoas(n);
    }
    // character string 
    else
    {
        strcpy((char *)key->key, ks);
        key->len = htoas(l);
    }
}

/*+F*************************************************************************
 * Function:
 *   cs_error
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static void
cs_error(client_handle_t handle, int func, int ret)
{
    error_info_t err = { func, ret };

    CardServices(ReportError, handle, &err);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_init
 *
 * Description:
 *   We never need to do anything when a wireless device is "initialized"
 *   by the net software, because we only register already-found cards.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_init(struct net_device *dev)
{
    DBG_FUNC("wvlan2_init")

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    /* Nothing to do */

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   adapter_attach
 *
 * Description:
 *   Creates an "instance" of the driver, allocating local data
 *   structures for one device. The device is registered with
 *   Card Services.
 *
 * Status: Complete
 *-F*************************************************************************/
static dev_link_t *
adapter_attach(void)
{
    DBG_FUNC("adapter_attach")
    client_reg_t      clientReg;
    dev_link_t        *link;
    struct net_device *dev;
    int               i, ret;

    DBG_ENTER(DbgInfo);

    /* Create new ethernet device */
    link = kmalloc(sizeof(struct dev_link_t), GFP_KERNEL);
    memset(link, 0, sizeof(struct dev_link_t));
    link->release.function  = &adapter_release;
    link->release.data      = (u_long) link;
    link->io.NumPorts1      = HCF_NUM_IO_PORTS;
    link->io.Attributes1    = IO_DATA_PATH_WIDTH_16;
    link->io.IOAddrLines    = 6;
    link->irq.Attributes    = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT;
    link->irq.IRQInfo1      = IRQ_INFO2_VALID | IRQ_LEVEL_ID;
    if (irq_list[0] == -1)
    {
        link->irq.IRQInfo2 = irq_mask;
    }
    else
    {
        for (i = 0; i < NELEM(irq_list); i++)
        {
            link->irq.IRQInfo2 |= 1 << irq_list[i];
        }
    }

    link->irq.Handler       = &wvlan2_isr;
    link->conf.Attributes   = CONF_ENABLE_IRQ;
    link->conf.Vcc          = 50;
    link->conf.IntType      = INT_MEMORY_AND_IO;
    link->conf.ConfigIndex  = 1;
    link->conf.Present      = PRESENT_OPTION;

    dev = kmalloc(sizeof(struct net_device), GFP_KERNEL);
    memset(dev, 0, sizeof(struct net_device));

    /* Make up a wvlan2-specific-data structure. */
    dev->priv = kmalloc(sizeof(struct wvlan2_private), GFP_KERNEL);
    memset(dev->priv, 0, sizeof(struct wvlan2_private));

    /* Setup the device defaults as an ethernet device. */
    ether_setup(dev);

    /* The wvlan2-specific entries in the device structure. */
    dev->hard_start_xmit    = &wvlan2_tx;

#ifdef WIRELESS_EXT
    dev->get_wireless_stats = &wvlan2_wireless_stats;
#endif // WIRELESS_EXT

    dev->set_config         = &wvlan2_config;
    dev->get_stats          = &wvlan2_stats;
    dev->set_multicast_list = &wvlan2_multicast;

    init_dev_name(dev, ((struct wvlan2_private *) dev->priv)->node);

    dev->init               = &wvlan2_init;
    dev->open               = &wvlan2_open;
    dev->stop               = &wvlan2_close;
    dev->do_ioctl           = &wvlan2_ioctl;

#ifdef HAVE_TX_TIMEOUT
    dev->tx_timeout         = &wvlan2_tx_timeout;
    dev->watchdog_timeo     = TX_TIMEOUT;
#endif
    
    netif_stop_queue(dev);

    link->priv = link->irq.Instance = dev;

    /* Add the new instance to our list of active devices */
    link->next = dev_list;
    dev_list = link;

    /* Register with Card Services */
    clientReg.dev_info      = &dev_info;
    clientReg.Attributes    = INFO_IO_CLIENT | INFO_CARD_SHARE;
    clientReg.EventMask     =
#if DBG
        CS_EVENT_REGISTRATION_COMPLETE |
#endif // DBG
        CS_EVENT_CARD_INSERTION | CS_EVENT_CARD_REMOVAL |
        CS_EVENT_RESET_PHYSICAL | CS_EVENT_CARD_RESET |
        CS_EVENT_PM_SUSPEND | CS_EVENT_PM_RESUME;
    clientReg.event_handler = &adapter_event;
    clientReg.Version       = 0x0210;
    clientReg.event_callback_args.client_data = link;
    ret = CardServices(RegisterClient, &link->handle, &clientReg);
    if (ret != CS_SUCCESS)
    {
        DBG_PRINT("Error: CardServices RegisterClient failed!\n");
        cs_error(link->handle, RegisterClient, ret);
        adapter_detach(link);
        link = NULL;
    }

    DBG_LEAVE(DbgInfo);
    return link;
}

/*+F*************************************************************************
 * Function:
 *   adapter_detach
 *
 * Description:
 *   This deletes a driver "instance". The device is de-registered
 *   with Card Services. If it has been released, then the net device is
 *   unregistered, and all local data structures are freed. 
 *   Otherwise, the structures will be freed when the device is released.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
adapter_detach(dev_link_t *link)
{
    DBG_FUNC("adapter_detach")
    struct net_device *dev;
    dev_link_t        **linkp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "link", "0x%p", link);

    /* Locate device structure */
    for (linkp = &dev_list; *linkp; linkp = &(*linkp)->next)
    {
        if (*linkp == link) break;
    }
    if (*linkp == NULL)
    {
        DBG_LEAVE(DbgInfo);
        return;
    }

    if (link->state & DEV_RELEASE_PENDING)
    {
        del_timer(&link->release);
        link->state &= ~DEV_RELEASE_PENDING;
    }

    if (link->state & DEV_CONFIG)
    {
        adapter_release((u_long) link);
        if (link->state & DEV_STALE_CONFIG)
        {
            link->state |= DEV_STALE_LINK;

            DBG_LEAVE(DbgInfo);
            return;
        }
    }

    if (link->handle)
    {
        CardServices(DeregisterClient, link->handle);
    }

    /* Unlink device structure, free bits */
    *linkp = link->next;
    if ((dev = link->priv) != NULL)
    {
        struct wvlan2_private  *lp;

        if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
        {
            /* Disconnect from the adapter hardware */
            if (lp->hcfCtx != NULL)
            {
                hcf_action(lp->hcfCtx, HCF_ACT_CARD_OUT);

                hcf_disconnect(lp->hcfCtx);

                kfree_s(lp->hcfCtx, sizeof(IFB_STRCT));
                lp->hcfCtx = NULL;
            }

            if (link->dev)
            {
                if (dev->flags & IFF_UP)
                {
                    /* Some, but not all, kernel versions close automatically. */
                    dev_close(dev);
                    dev->flags &= ~(IFF_UP|IFF_RUNNING);
                }

                unregister_netdev(dev);
                link->dev = NULL;
            }

            kfree_s(lp, sizeof(struct wvlan2_private));
        }
        kfree_s(link->priv, sizeof(struct net_device));
    }
    kfree_s(link, sizeof(struct dev_link_t));

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   adapter_release
 *
 * Description:
 *   After a card is removed, this routine will release the PCMCIA 
 *   configuration. 
 *   If the device is still open, this will be postponed until it is closed.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
adapter_release(u_long arg)
{
    DBG_FUNC("adapter_release")
    dev_link_t        *link = (dev_link_t *) arg;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "arg", "0x%08lx", arg);

    if (link->open)
    {
        DBG_PRINT("wavelan2_cs: release postponed, '%s' still open\n",
            link->dev->dev_name);

        link->state |= DEV_STALE_CONFIG;

        DBG_LEAVE(DbgInfo);
        return;
    }

    CardServices(ReleaseConfiguration, link->handle);
    CardServices(ReleaseIO, link->handle, &(link->io));
    CardServices(ReleaseIRQ, link->handle, &(link->irq));

    link->state &= ~DEV_CONFIG;
    if (link->state & DEV_STALE_LINK)
    {
        adapter_detach(link);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   adapter_event
 *
 * Description:
 *   The card status event handler. Mostly, this schedules other
 *   stuff to run after an event is received. A CARD_REMOVAL event
 *   also sets some flags to discourage the net drivers from trying
 *   to talk to the card any more.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
adapter_event(event_t event, int priority, event_callback_args_t *args)
{
    DBG_FUNC("adapter_event")
    dev_link_t        *link = args->client_data;
    struct net_device *dev = link->priv;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "event", "%s", DbgEvent(event));
    DBG_PARAM(DbgInfo, "priority", "%d", priority);
    DBG_PARAM(DbgInfo, "args", "0x%p", args);

    switch (event)
    {
    case CS_EVENT_CARD_REMOVAL:
        link->state &= ~DEV_PRESENT;
        if (link->state & DEV_CONFIG)
        {
            netif_device_detach(dev);

            // Notify the adapter that it has been removed.
            wvlan2_remove(dev);

            link->release.expires = RUN_AT(HZ/20);
            add_timer(&(link->release));
        }
        break;

    case CS_EVENT_CARD_INSERTION:
        link->state |= (DEV_PRESENT | DEV_CONFIG_PENDING);
        wvlan2_insert(link);
        break;

    case CS_EVENT_PM_SUSPEND:
        link->state |= DEV_SUSPEND;
        /* Fall through... */

    case CS_EVENT_RESET_PHYSICAL:
        if (link->state & DEV_CONFIG)
        {
            if (link->open)
            {
                netif_device_detach(dev);
                if (event == CS_EVENT_PM_SUSPEND)
                {
                    wvlan2_suspend(dev);
                }
            }
            CardServices(ReleaseConfiguration, link->handle);
        }
        break;

    case CS_EVENT_PM_RESUME:
        link->state &= ~DEV_SUSPEND;
        /* Fall through... */

    case CS_EVENT_CARD_RESET:
        if (link->state & DEV_CONFIG)
        {
            CardServices(RequestConfiguration, link->handle, &(link->conf));
            if (event == CS_EVENT_PM_RESUME)
            {
                wvlan2_resume(dev);
            }
            else
            {
                wvlan2_reset(dev);
            }
            netif_device_attach(dev);
        }
        break;
    }

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_insert
 *
 * Description:
 *   wvlan2_insert() is scheduled to run after a CARD_INSERTION event
 *   is received, to configure the PCMCIA socket, and to make the
 *   ethernet device available to the system.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_insert(dev_link_t *link)
{
    DBG_FUNC("wvlan2_insert")
    client_handle_t         handle;
    struct net_device       *dev;
    tuple_t                 tuple;
    cisparse_t              parse;
    u_char                  buf[64];
    bool_t                  found;
    int                     last_fn, last_ret, i;
    int                     status;
    int                     hcfStatus = HCF_SUCCESS;
    struct wvlan2_private  *lp;
    config_info_t conf;
    CFG_IDENTITY_STRCT      firmwareIdentity;

#define CS_CHECK(fn, args...) \
    while ((last_ret = CardServices(last_fn = (fn), args)) != 0) goto cs_failed

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "link", "0x%p", link);

    handle = link->handle;
    dev = link->priv;

    tuple.Attributes = 0;
    tuple.TupleData = buf;
    tuple.TupleDataMax = sizeof(buf);
    tuple.TupleOffset = 0;

    tuple.DesiredTuple = CISTPL_CONFIG;
    CS_CHECK(GetFirstTuple, handle, &tuple);

    CS_CHECK(GetTupleData, handle, &tuple);
    CS_CHECK(ParseTuple, handle, &tuple, &parse);

    link->conf.ConfigBase = parse.config.base;
    link->conf.Present = parse.config.rmask[0];

    /* Configure card */
    link->state |= DEV_CONFIG;

    /* Look up the current Vcc */
    CS_CHECK(GetConfigurationInfo, handle, &conf);
    link->conf.Vcc = conf.Vcc;

    CS_CHECK(RequestIO, link->handle, &link->io);
    CS_CHECK(RequestIRQ, link->handle, &link->irq);
    CS_CHECK(RequestConfiguration, link->handle, &link->conf);

    dev->irq = link->irq.AssignedIRQ;
    dev->base_addr = link->io.BasePort1;
    netif_start_queue(dev);

    if (register_netdev(dev) != 0)
    {
        printk(KERN_NOTICE "wavelan2_cs: register_netdev() failed\n");
        goto failed;
    }

    link->state &= ~DEV_CONFIG_PENDING;

    /* Get the hardware ethernet address from the card services */
    tuple.DesiredTuple = CISTPL_FUNCE;
    for (status = CardServices(GetFirstTuple, handle, &tuple), found = FALSE;
        status == CS_SUCCESS && !found;
        status = CardServices(GetNextTuple, handle, &tuple))
    {
        if ((CardServices(GetTupleData, handle, &tuple) == CS_SUCCESS) &&
            (CardServices(ParseTuple, handle, &tuple, &parse) == CS_SUCCESS))
        {
            switch (parse.funce.type)
            {
            case CISTPL_FUNCE_LAN_NODE_ID:
                {
                    cistpl_lan_node_id_t   *node_id;

                    node_id = (cistpl_lan_node_id_t *) parse.funce.data;

                    if (node_id->nb == ETH_ALEN)
                    {
                        memcpy(dev->dev_addr, node_id->id, ETH_ALEN);
                        dev->addr_len = ETH_ALEN;
                        found = TRUE;
                    }
                }
                break;
            }
        }
    }

    link->dev = &((struct wvlan2_private *) dev->priv)->node;
    strcpy(((struct wvlan2_private *) dev->priv)->node.dev_name, dev->name);


    // Initialize the adapter hardware.
    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        lp->hcfCtx = (IFBP) kmalloc(sizeof(IFB_STRCT), GFP_KERNEL);
        memset(lp->hcfCtx, 0, sizeof(IFB_STRCT));

        /* Initialize the adapter parameters. */
        spin_lock_init(&(lp->slock));
        lp->dev = dev;

        DBG_PARAM(DbgInfo, "irq_mask", "0x%04x", irq_mask & 0x0FFFF);
        DBG_PARAM(DbgInfo, "irq_list", "0x%02x 0x%02x 0x%02x 0x%02x",
            irq_list[0] & 0x0FF, irq_list[1] & 0x0FF,
            irq_list[2] & 0x0FF, irq_list[3] & 0x0FF);
        DBG_PARAM(DbgInfo, "network_name", "\"%s\"", network_name);
        DBG_PARAM(DbgInfo, "port_type", "%d", port_type);
        DBG_PARAM(DbgInfo, "channel", "%d", channel);
        DBG_PARAM(DbgInfo, "distance_between_aps", "%d", distance_between_aps);
        DBG_PARAM(DbgInfo, "transmit_rate", "%d", transmit_rate);
        DBG_PARAM(DbgInfo, "medium_reservation", "%d", medium_reservation);
        DBG_PARAM(DbgInfo, "card_power_management", "\"%s\"", card_power_management);
        DBG_PARAM(DbgInfo, "microwave_robustness", "\"%s\"", microwave_robustness);
        DBG_PARAM(DbgInfo, "create_ibss", "\"%s\"", create_ibss);
        DBG_PARAM(DbgInfo, "receive_all_multicasts", "\"%s\"", receive_all_multicasts);
        DBG_PARAM(DbgInfo, "maximum_sleep_duration", "%d", maximum_sleep_duration);
        DBG_PARAM(DbgInfo, "station_name", "\"%s\"", station_name);
        DBG_PARAM(DbgInfo, "enable_encryption", "\"%s\"", enable_encryption);
        DBG_PARAM(DbgInfo, "key_1", "\"%s\"", key_1);
        DBG_PARAM(DbgInfo, "key_2", "\"%s\"", key_2);
        DBG_PARAM(DbgInfo, "key_3", "\"%s\"", key_3);
        DBG_PARAM(DbgInfo, "key_4", "\"%s\"", key_4);
        DBG_PARAM(DbgInfo, "transmit_key_id", "%d", transmit_key_id);

#ifdef VALIDATE_PARAMS
#define VALID_PARAM(C) \
    { \
        if (!(C)) \
        { \
            printk(KERN_INFO "Wireless, parameter error: \"%s\"\n", #C); \
            goto failed; \
        } \
    }
        VALID_PARAM(!network_name || (strlen(network_name) <= HCF_MAX_NAME_LEN));
        VALID_PARAM(!station_name || (strlen(station_name) <= HCF_MAX_NAME_LEN));
        VALID_PARAM((port_type >= 1) && (port_type <= 3));
        VALID_PARAM(/* (channel >= 0) && */ (channel <= 14));
        VALID_PARAM((distance_between_aps >= 1) && (distance_between_aps <= 3));
        VALID_PARAM((transmit_rate >= 1) && (transmit_rate <= 7));
        VALID_PARAM((medium_reservation >= 256) && (medium_reservation <= 2347));
        VALID_PARAM(!card_power_management || strchr("NnYy", card_power_management[0]) != NULL);
        VALID_PARAM(!microwave_robustness || strchr("NnYy", microwave_robustness[0]) != NULL);
        VALID_PARAM(!create_ibss || strchr("NnYy", create_ibss[0]) != NULL);
        VALID_PARAM(!receive_all_multicasts || strchr("NnYy", receive_all_multicasts[0]) != NULL);
        VALID_PARAM(/* (maximum_sleep_duration >= 0) && */ (maximum_sleep_duration <= 65535));
        VALID_PARAM(!enable_encryption || strchr("NnYy", enable_encryption[0]) != NULL);
        VALID_PARAM(is_valid_key_string(key_1));
        VALID_PARAM(is_valid_key_string(key_2));
        VALID_PARAM(is_valid_key_string(key_3));
        VALID_PARAM(is_valid_key_string(key_4));
        VALID_PARAM((transmit_key_id >= 1) && (transmit_key_id <= 4));
#endif // VALIDATE_PARAMS

        // Set the driver parameters from the passed in parameters.
        lp->PortType            = port_type;
        lp->Channel             = channel;
        lp->DistanceBetweenAPs  = distance_between_aps;
        lp->TxRateControl       = transmit_rate;
        lp->RTSThreshold        = medium_reservation;
        if (strchr("Yy", card_power_management[0]) != NULL)
        {
            lp->PMEnabled = 1;
        }
        else
        {
            lp->PMEnabled = 0;
        }
        if (strchr("Yy", microwave_robustness[0]) != NULL)
        {
            lp->MicrowaveRobustness = 1;
        }
        else
        {
            lp->MicrowaveRobustness = 0;
        }
        if (strchr("Yy", create_ibss[0]) != NULL)
        {
            lp->CreateIBSS = 1;
        }
        else
        {
            lp->CreateIBSS = 0;
        }
        if (strchr("Nn", receive_all_multicasts[0]) != NULL)
        {
            lp->MulticastReceive = 0;
        }
        else
        {
            lp->MulticastReceive = 1;
        }
        lp->MaxSleepDuration    = maximum_sleep_duration;
        for (i = 0; i < ETH_ALEN; i++)
        {
           lp->MACAddress[i] = mac_address[i];
        }
        if (network_name && (strlen(network_name) <= HCF_MAX_NAME_LEN))
        {
            strcpy(lp->NetworkName, network_name);
        }
        if (station_name && (strlen(station_name) <= HCF_MAX_NAME_LEN))
        {
            strcpy(lp->StationName, station_name);
        }
        if (strchr("Yy", enable_encryption[0]) != NULL)
        {
            lp->EnableEncryption = 1;
        }
        else
        {
            lp->EnableEncryption = 0;        
        }
        if (key_1 && (strlen(key_1) <= MAX_KEY_LEN))
        {
            strcpy(lp->Key1, key_1);
        }
        if (key_2 && (strlen(key_2) <= MAX_KEY_LEN))
        {
            strcpy(lp->Key2, key_2);
        }
        if (key_3 && (strlen(key_3) <= MAX_KEY_LEN))
        {
            strcpy(lp->Key3, key_3);
        }
        if (key_4 && (strlen(key_4) <= MAX_KEY_LEN))
        {
            strcpy(lp->Key4, key_4);
        }
        lp->TransmitKeyID       = transmit_key_id;
        key_string2key(lp->Key1, &(lp->DefaultKeys.key[0]));
        key_string2key(lp->Key2, &(lp->DefaultKeys.key[1]));
        key_string2key(lp->Key3, &(lp->DefaultKeys.key[2]));
        key_string2key(lp->Key4, &(lp->DefaultKeys.key[3]));
        
        // Connect to the adapter
        hcf_connect(lp->hcfCtx, dev->base_addr);

        // Fill in the driver information structure
        lp->driverInfo.len = (sizeof(lp->driverInfo) / sizeof(hcf_16)) - 1;
        lp->driverInfo.typ = CFG_DRV_INFO;
        strcpy(lp->driverInfo.driver_name, DRIVER_NAME);
        lp->driverInfo.driver_version = (DRV_MAJOR_VERSION << 8) |
            DRV_MINOR_VERSION;
        lp->driverInfo.HCF_version = (lp->hcfCtx->IFB_HCFVersionMajor << 8) |
            lp->hcfCtx->IFB_HCFVersionMinor;
        lp->driverInfo.IO_address = dev->base_addr;
        lp->driverInfo.IO_range = HCF_NUM_IO_PORTS;
        lp->driverInfo.IRQ_number = dev->irq;
        lp->driverInfo.card_stat = lp->hcfCtx->IFB_CardStat;
        lp->driverInfo.frame_type = lp->hcfCtx->IFB_FrameType;

        // Fill in the driver identity structure
        lp->driverIdentity.len = (sizeof(lp->driverIdentity) / sizeof(hcf_16)) - 1;
        lp->driverIdentity.typ = CFG_DRV_IDENTITY;
        lp->driverIdentity.comp_id = DRV_IDENTITY;
        lp->driverIdentity.variant = DRV_VARIANT;
        lp->driverIdentity.version_major = DRV_MAJOR_VERSION;
        lp->driverIdentity.version_minor = DRV_MINOR_VERSION;

        if ((hcfStatus = hcf_action(lp->hcfCtx, HCF_ACT_CARD_IN)) !=
            HCF_SUCCESS)
        {
            goto hcf_failed;
        }

        // Emit firmware version
        firmwareIdentity.len = (sizeof(firmwareIdentity) / sizeof(hcf_16)) - 1;
        firmwareIdentity.typ = CFG_STA_IDENTITY;
        hcf_get_info(lp->hcfCtx, (LTVP) &(firmwareIdentity));
        printk(KERN_INFO "%s: Wireless, Station Functions firmware, "
            "Variant %d, Version %d.%02d\n",
            dev->name,
            firmwareIdentity.variant,
            firmwareIdentity.version_major,
            firmwareIdentity.version_minor);
    }

    printk(KERN_INFO "%s: Wireless, io_addr %#03lx, irq %d, "
        "mac_address ", dev->name, dev->base_addr, dev->irq);
    for (i = 0; i < ETH_ALEN; i++)
    {
        printk("%02X%c", dev->dev_addr[i], ((i < (ETH_ALEN-1)) ? ':' : '\n'));
    }

    DBG_LEAVE(DbgInfo);
    return;

cs_failed:
    cs_error(link->handle, last_fn, last_ret);

hcf_failed:
    wvlan2_hcf_error(dev, hcfStatus);

failed:
    adapter_release((u_long) link);

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_remove
 *
 * Description:
 *   Notify the adapter that it has been removed. Since the adapter is
 *   gone, we should no longer try to talk to it.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_remove(struct net_device *dev)
{
    DBG_FUNC("wvlan2_remove")
    struct wvlan2_private  *lp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        unsigned long   flags;
        
        spin_lock_irqsave(&(lp->slock), flags);
        hcf_action(lp->hcfCtx, HCF_ACT_CARD_OUT);
        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_suspend
 *
 * Description:
 *   Power-down and halt the adapter.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_suspend(struct net_device *dev)
{
    DBG_FUNC("wvlan2_suspend")
    struct wvlan2_private  *lp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        unsigned long   flags;

        // The adapter is suspended:
        // - Stop the adapter
        // - Power down

        spin_lock_irqsave(&(lp->slock), flags);
        hcf_action(lp->hcfCtx, HCF_ACT_CARD_OUT);
        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_resume
 *
 * Description:
 *   Resume a previously suspended adapter.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_resume(struct net_device *dev)
{
    DBG_FUNC("wvlan2_resume")
    struct wvlan2_private  *lp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        unsigned long   flags;

        spin_lock_irqsave(&(lp->slock), flags);
        hcf_action(lp->hcfCtx, HCF_ACT_CARD_IN);
        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_reset
 *
 * Description:
 *   Reset the adapter.
 *   Returns hcfStatus of hcf_enable()
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_reset(struct net_device *dev)
{
    DBG_FUNC("wvlan2_reset")
    struct wvlan2_private  *lp;
    int                     hcfStatus = HCF_SUCCESS;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        unsigned long   flags;

        spin_lock_irqsave(&(lp->slock), flags);

        // Shutdown the adapter.
        hcf_action(lp->hcfCtx, HCF_ACT_CARD_OUT);
        hcf_disconnect(lp->hcfCtx);

        // Reset the driver information.
        lp->txBytes = 0;

        // Restart the adapter.
        hcf_connect(lp->hcfCtx, dev->base_addr);
        hcf_action(lp->hcfCtx, HCF_ACT_CARD_IN);
        hcf_action(lp->hcfCtx, HCF_ACT_INT_ON);

        // Commit the adapter parameters
        hcfStatus = wvlan2_commit(lp);

        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
    return hcfStatus;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_hcf_error
 *
 * Description:
 *   Report hcf error message.
 *   Is quiet if status indicates success.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_hcf_error(struct net_device *dev, int hcfStatus)
{
    char     buffer[64], *pMsg;

    if (hcfStatus != HCF_SUCCESS)
    {
        switch (hcfStatus)
        {
        case HCF_ERR_DIAG_1:
            pMsg = "Noticed an error after a succesful diagnose command";
            break;

        case HCF_ERR_TIME_OUT:
            pMsg = "Expected adapter event did not occur in expected time";
            break;

        case HCF_ERR_NO_NIC:
            pMsg = "Card not found (ejected unexpectedly)";
            break;

        case HCF_ERR_BUSY:
            pMsg = "Inquire cmd while another Inquire in progress";
            break;

        case HCF_ERR_SEQ_BUG:
            pMsg = "Unexpected command completed";
            break;

        case HCF_ERR_LEN:
            pMsg = "Command buffer size insufficient";
            break;

        case HCF_ERR_INCOMP_PRI:
            pMsg = "Primary functions are not compatible";
            break;

        case HCF_ERR_INCOMP_STA:
            pMsg = "Primary functions are compatible, "
                "station functions are not";
            break;

        case HCF_ERR_INCOMP_FEATURE:
            pMsg = "Incompatible feature (prevents enable)";
            break;

        default:
            sprintf(buffer, "Error code %d", hcfStatus);
            pMsg = buffer;
            break;
        }

        printk(KERN_INFO "%s: Wireless, HCF failure: \"%s\"\n",
            dev->name, pMsg);
    }
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_is_open
 *
 * Description:
 *   Check whether this device is open.
 *   Returns nonzero if device is open.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_is_open(struct wvlan2_private *lp)
{
    dev_link_t *link;

    for (link = dev_list; link; link = link->next)
    {
        if (link->priv == lp->dev) break;
    }

    if (!DEV_OK(link))
    {
        return 0;
    }
    
    return (link->open);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_commit
 *
 * Description:
 *   Write the parameters to the adapter.
 *   (re-)enables the card if device is open.
 *   Returns hcfStatus of hcf_enable()
 *
 * Status: Complete
 *
 * NOTE: It is assumed that the adapter access spinlock is held as a
 *       pre-condition to calling this function.
 *-F*************************************************************************/
static int
wvlan2_commit(struct wvlan2_private *lp)
{
    int hcfStatus = HCF_SUCCESS;

    DBG_FUNC("wvlan2_commit")

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "lp", "%s (0x%p)", lp->dev->name, lp);

    DBG_ASSERT(lp != NULL);
    if (!(lp->flags & WVLAN2_UIL_BUSY))
    {
        int             i;
        __u16           len;

        // The adapter parameters have changed:
        // - disable card
        // - reload parameters
        // - enable card

        if (wvlan2_is_open(lp))
        {
           hcf_disable(lp->hcfCtx, HCF_PORT_0);
        }

#ifdef MB_SIZE
        //
        // Register the mailbox
        //
        lp->ltvRecord.len = 4;
        lp->ltvRecord.cmd = CFG_REG_MB;
        lp->ltvRecord.u.u32[0] = (u_long) &(lp->mailbox);
        lp->ltvRecord.u.u16[2] = (MB_SIZE / sizeof(hcf_16));
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
#endif // MB_SIZE

        //
        // Maximum Data Length
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_MAX_DATA_LEN;
        lp->ltvRecord.u.u16[0] = htoas(HCF_MAX_PACKET_SIZE);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Port Type
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_PORT_TYPE;
        lp->ltvRecord.u.u16[0] = htoas(lp->PortType);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Channel
        //
        // Default channel for IBSS is 10
        if (lp->CreateIBSS && (lp->Channel == 0))
        {
            lp->Channel = 10;
        }
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_OWN_CHANNEL;
        lp->ltvRecord.u.u16[0] = htoas(lp->Channel);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // RTS Threshold
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_RTS_THRH;
        lp->ltvRecord.u.u16[0] = htoas(lp->RTSThreshold);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Tx Rate Control
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_TX_RATE_CONTROL;
        lp->ltvRecord.u.u16[0] = htoas(lp->TxRateControl);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // System Scale / Distance Between APs
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_SYSTEM_SCALE;
        lp->ltvRecord.u.u16[0] = htoas(lp->DistanceBetweenAPs);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Desired SSID
        //
        // NOTE: The special keyword ANY means don't set the SSID.
        //       The adapter will search for a usable access point.
        if (((len = (strlen(lp->NetworkName) + 1) & ~0x01) != 0) &&
            (strcmp(lp->NetworkName, "ANY") != 0))
        {
            lp->ltvRecord.len = 2 + (len / sizeof(hcf_16));
            lp->ltvRecord.cmd = CFG_DESIRED_SSID;
            lp->ltvRecord.u.u16[0] = htoas(strlen(lp->NetworkName));
            memcpy(&(lp->ltvRecord.u.u8[2]), lp->NetworkName, len);
        }
        else
        {
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_DESIRED_SSID;
            lp->ltvRecord.u.u16[0] = htoas(0);
        }
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Own MAC Address
        //
        for (i = 0; i < sizeof(lp->MACAddress); i++)
        {
            if (lp->MACAddress[i] != 0)
            {
                break;
            }
        }
        if (i < sizeof(lp->MACAddress))
        {
            len = sizeof(lp->MACAddress);

            // Make the MAC address valid by:
            //  - Clearing the multicast bit
            //  - Setting the local MAC address bit
            lp->MACAddress[0] &= ~0x03;
            lp->MACAddress[0] |= 0x02;

            lp->ltvRecord.len = 1 + (len / sizeof(hcf_16));
            lp->ltvRecord.cmd = CFG_CNF_OWN_MAC_ADDR;
            memcpy(&(lp->ltvRecord.u.u8[0]), lp->MACAddress, len);
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

            memcpy(lp->dev->dev_addr, lp->MACAddress, len);
        }

        //
        // Station Name
        //
        if ((len = (strlen(lp->StationName) + 1) & ~0x01) != 0)
        {
            lp->ltvRecord.len = 2 + (len / sizeof(hcf_16));
            lp->ltvRecord.cmd = CFG_CNF_OWN_NAME;
            lp->ltvRecord.u.u16[0] = htoas(strlen(lp->StationName));
            memcpy(&(lp->ltvRecord.u.u8[2]), lp->StationName, len);
        }
        else
        {
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_CNF_OWN_NAME;
            lp->ltvRecord.u.u16[0] = htoas(0);
        }
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Power Management
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_PM_ENABLED;
        lp->ltvRecord.u.u16[0] = htoas(lp->PMEnabled);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
        
        //
        // Microwave Oven Robustness
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_MICRO_WAVE;
        lp->ltvRecord.u.u16[0] = htoas(lp->MicrowaveRobustness);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // Create IBSS
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CREATE_IBSS;
        lp->ltvRecord.u.u16[0] = htoas(lp->CreateIBSS);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_MCAST_RX;
        lp->ltvRecord.u.u16[0] = htoas(lp->MulticastReceive);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_MAX_SLEEP_DURATION;
        lp->ltvRecord.u.u16[0] = htoas(lp->MaxSleepDuration);
        hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

        //
        // WEP
        //
        if (lp->EnableEncryption)
        {
            // enable encryption
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_CNF_ENCRYPTION;
            lp->ltvRecord.u.u16[0] = htoas(lp->EnableEncryption);
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

            // set TxKeyID
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_CNF_TX_KEY_ID;
            lp->ltvRecord.u.u16[0] = htoas(lp->TransmitKeyID - 1);
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

            // write keys
            lp->DefaultKeys.len = sizeof(lp->DefaultKeys)/sizeof(hcf_16) - 1;
            lp->DefaultKeys.typ = CFG_CNF_DEFAULT_KEYS;
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->DefaultKeys));
	    DBG_NOTICE(DbgInfo, "encrypt: %d, ID: %d\n", lp->EnableEncryption, lp->TransmitKeyID);
	    DBG_NOTICE(DbgInfo, "set key: %s(%d) [%d]\n", lp->DefaultKeys.key[lp->TransmitKeyID-1].key, lp->DefaultKeys.key[lp->TransmitKeyID-1].len, lp->TransmitKeyID-1  );
        }
        
        if (wvlan2_is_open(lp))
        {
            hcfStatus = hcf_enable(lp->hcfCtx, HCF_PORT_0);
        }
    }

    DBG_LEAVE(DbgInfo);
    return hcfStatus;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_config
 *
 * Description:
 *   Implement the SIOCSIFMAP interface.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_config(struct net_device *dev, struct ifmap *map)
{
    DBG_FUNC("wvlan2_config")

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);
    DBG_PARAM(DbgInfo, "map", "0x%p", map);

    // The only thing we care about here is a port change.
    // Since this not needed, ignore the request.
    DBG_PRINT("%s: %s called.\n", dev->name, __FUNC__);

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_stats
 *
 * Description:
 *   Return the current device statistics.
 *
 * Status: Complete
 *-F*************************************************************************/
static struct net_device_stats *
wvlan2_stats(struct net_device *dev)
{
    DBG_FUNC("wvlan2_stats")
    struct net_device_stats    *pStats;
    struct wvlan2_private      *lp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    pStats = NULL;
    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        pStats = &(lp->stats);
    }

    DBG_LEAVE(DbgInfo);
    return pStats;
}

#ifdef WIRELESS_EXT
/*+F*************************************************************************
 * Function:
 *   wvlan2_wireless_stats
 *
 * Description:
 *   Return the current device wireless statistics.
 *
 * Status: Complete
 *-F*************************************************************************/
static struct iw_statistics *
wvlan2_wireless_stats(struct net_device *dev)
{
    DBG_FUNC("wvlan2_wireless_stats")
    struct iw_statistics       *pStats;
    struct wvlan2_private      *lp;
    unsigned long               flags;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    pStats = NULL;
    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        spin_lock_irqsave(&(lp->slock), flags);

        // Initialize the statistics
        pStats = &(lp->wstats);
        pStats->qual.updated = 0x00;

        if (!(lp->flags & WVLAN2_UIL_BUSY))
        {
            CFG_COMMS_QUALITY_STRCT    *pQual;
            CFG_HERMES_TALLIES_STRCT   *pTallies;
            int                         status;

            // Update driver status
            pStats->status = 0;

            //
            // Get the current link quality information
            //
            lp->ltvRecord.len = 1 + (sizeof(*pQual) / sizeof(hcf_16));
            lp->ltvRecord.cmd = CFG_COMMS_QUALITY;
            status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
            if (status == HCF_SUCCESS)
            {
                pQual = (CFG_COMMS_QUALITY_STRCT *) &(lp->ltvRecord);

#ifdef USE_DBM
                pStats->qual.qual  = (u_char) pQual->coms_qual;
                pStats->qual.level = (u_char) dbm(pQual->signal_lvl);
                pStats->qual.noise = (u_char) dbm(pQual->noise_lvl);
#else
                pStats->qual.qual = percent(pQual->coms_qual,
                    HCF_MIN_COMM_QUALITY, HCF_MAX_COMM_QUALITY);
                pStats->qual.level = percent(pQual->signal_lvl,
                    HCF_MIN_SIGNAL_LEVEL, HCF_MAX_SIGNAL_LEVEL);
                pStats->qual.noise = percent(pQual->noise_lvl,
                    HCF_MIN_NOISE_LEVEL, HCF_MAX_NOISE_LEVEL);
#endif // USE_DBM
                pStats->qual.updated |= 0x07;
            }
            else
            {
                memset(&(pStats->qual), 0, sizeof(pStats->qual));
            }

            //
            // Get the current tallies from the adapter
            //
            lp->ltvRecord.len = 1 + (sizeof(*pTallies) / sizeof(hcf_16));
            lp->ltvRecord.cmd = CFG_TALLIES;
            status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
            if (status == HCF_SUCCESS)
            {
                pTallies = (CFG_HERMES_TALLIES_STRCT *) &(lp->ltvRecord.u.u32);

                pStats->discard.nwid = 0L;
                pStats->discard.code = pTallies->RxWEPUndecryptable;
                pStats->discard.misc = pTallies->TxDiscards +
                    pTallies->RxFCSErrors + pTallies->RxDiscards_NoBuffer +
                    pTallies->TxDiscardsWrongSA;
            }
            else
            {
                memset(&(pStats->discard), 0, sizeof(pStats->discard));
            }
        }

        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
    return pStats;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_spy_gather
 *
 * Description:
 *   Gather wireless spy statistics.
 *
 * Status: Complete
 *-F*************************************************************************/
#ifdef WIRELESS_SPY
static inline void
wvlan2_spy_gather (struct net_device *dev, u_char *mac)
{
    struct wvlan2_private      *lp = (struct wvlan2_private *)dev->priv;
    int i, status;
    u_char stats[2];

    // Gather wireless spy statistics: for each packet, compare the
    // source address with out list, and if match, get the stats.
//    lp->wstats.status = 0;
    for (i=0; i<lp->spy_number; i++)
    {
        if (!memcmp(mac, lp->spy_address[i], MAC_ADDR_SIZE))
      	{
            memset(&stats, 0, sizeof(stats));
            status = hcf_get_data(lp->hcfCtx, HFS_Q_INFO, stats, 2);
            if (status == HCF_SUCCESS)
            {
                lp->spy_stat[i].level = (u_char) dbm(stats[1]);
                lp->spy_stat[i].noise = (u_char) dbm(stats[0]);
                lp->spy_stat[i].qual  = 
                    lp->spy_stat[i].level - lp->spy_stat[i].noise;
                lp->spy_stat[i].updated = 7;
            }
            break;
        }
    }
}
#endif // WIRELESS_SPY   
#endif // WIRELESS_EXT

/*+F*************************************************************************
 * Function:
 *   wvlan2_open
 *
 * Description:
 *   Open the device.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_open(struct net_device *dev)
{
    DBG_FUNC("wvlan2_open")
    dev_link_t *link;
    int         hcfStatus = HCF_SUCCESS;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    for (link = dev_list; link; link = link->next)
    {
        if (link->priv == dev) break;
    }

    if (!DEV_OK(link))
    {
        DBG_LEAVE(DbgInfo);
        return -ENODEV;
    }

    link->open++;

    netif_start_queue(dev);
    netif_mark_up(dev);

    // Reset the adapter (Now device is open, this enables the card)
    hcfStatus = wvlan2_reset(dev);

    if (hcfStatus != HCF_SUCCESS)
    {
        // Report error message
        wvlan2_hcf_error(dev, hcfStatus);
        // Stop device and queue
        netif_device_detach(dev);
        link->open--;
        DBG_LEAVE(DbgInfo);
        return -ENODEV;
    }

    MOD_INC_USE_COUNT;

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_close
 *
 * Description:
 *   Close the device
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_close(struct net_device *dev)
{
    DBG_FUNC("wvlan2_close")
    dev_link_t             *link;
    struct wvlan2_private  *lp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    for (link = dev_list; link; link = link->next)
    {
        if (link->priv == dev) break;
    }

    if (link == NULL)
    {
        DBG_LEAVE(DbgInfo);
        return -ENODEV;
    }

    DBG_PRINT("%s: Shutting down adapter.\n", dev->name);

    // Mark the adapter as busy
    netif_stop_queue(dev);
    
    // Shutdown the adapter:
    // - Disable adapter interrupts
    // - Stop Tx/Rx
    // - Update statistics
    // - Set low power mode
    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        unsigned long   flags;
        
        spin_lock_irqsave(&(lp->slock), flags);
        hcf_disable(lp->hcfCtx, HCF_PORT_0);
        hcf_action(lp->hcfCtx, HCF_ACT_INT_OFF);
        hcf_action(lp->hcfCtx, HCF_ACT_CARD_OUT);
        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    link->open--;
    netif_mark_down(dev);
    if (link->state & DEV_STALE_CONFIG)
    {
        link->release.expires = RUN_AT(HZ/20);
        link->state |= DEV_RELEASE_PENDING;
        add_timer(&link->release);
    }

    MOD_DEC_USE_COUNT;

    DBG_LEAVE(DbgInfo);
    return 0;
}

#ifdef WIRELESS_EXT
/*+F*************************************************************************
 * Function:
 *   wvlan2_wireless_ioctl
 *
 * Description:
 *   Handle the Wireless extension IOCTLs and return TRUE if it was a
 *   wireless IOCTL.
 *
 * Status: Complete
 *
 * NOTE: It is assumed that the adapter access spinlock is held as a
 *       pre-condition to calling this function.
 *-F*************************************************************************/
static bool_t
wvlan2_wireless_ioctl(struct net_device *dev, struct ifreq *rq, int cmd, int *pRet)
{
    DBG_FUNC("wvlan2_wireless_ioctl")
    struct wvlan2_private  *lp;
    int                     ret;
    bool_t                  wIoctl;
    int                     status;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);
    DBG_PARAM(DbgInfo, "rq", "0x%p", rq);
    DBG_PARAM(DbgInfo, "cmd", "0x%04x", cmd);
    DBG_PARAM(DbgInfo, "pRet", "%d (0x%p)", *pRet, pRet);

    wIoctl = FALSE;
    ret = *pRet;

    DBG_ASSERT(dev != NULL);
    lp = (struct wvlan2_private *) dev->priv;

    if ((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST))
    {
        struct iwreq   *wrq = (struct iwreq *) rq;

        wIoctl = TRUE;

        switch (cmd)
        {
        // ================= Wireless IOCTLs =================
        // Wireless basic operations
        case SIOCGIWNAME:       // get name
            strcpy(wrq->u.name, "Wireless");
            break;

        case SIOCSIWFREQ:       // set channel/frequency
        {
            int channel = 0;
            
            if (!capable(CAP_NET_ADMIN))
            {
                ret = -EPERM;
                break;
            }

            // If frequency specified, look up channel
            if (wrq->u.freq.e == 1)
            {
                int f = wrq->u.freq.m / 100000;
                int i;
                for (i = 0; i < 14; i++)
                {
                    if (f == frequency_list[i])
                    {
                        channel = i+1;
                        break;
                    }
                }
            }

            // Channel specified
            if (wrq->u.freq.e == 0)
            {
                channel = wrq->u.freq.m;
            }

            // Validate the new value against the valid adapter channels.
            if ((channel >= 0) && (channel <= 14))
            {
                __u16      *pChanList;

                lp->ltvRecord.len = 1 + (sizeof(*pChanList) / sizeof(hcf_16));
                lp->ltvRecord.cmd = CFG_CHANNEL_LIST;
                status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
                if (status == HCF_SUCCESS)
                {
                    pChanList = (__u16 *) &(lp->ltvRecord.u.u32);

                    // Check if the channel is acceptable.
                    if (!(*pChanList & (0x01L << (channel - 1))))
                    {
                        ret = -EINVAL;
                        break;
                    }
                }
                else
                {
                    ret = -EIO;
                    break;
                }
            }
            else
            {
                ret = -EINVAL;
                break;
            }

            lp->Channel = channel;

            // Commit the adapter parameters
            wvlan2_commit(lp);
            break;
        }
        
        case SIOCGIWFREQ:       // get channel/frequency
            memset(&(wrq->u.freq), 0, sizeof(wrq->u.freq));

            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_CURRENT_CHANNEL;
            status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
            if (status == HCF_SUCCESS)
            {
                int channel = lp->ltvRecord.u.u16[0];
#ifdef USE_FREQUENCY
                if (channel >= 1 && channel <= 14)
                {
                    wrq->u.freq.m = frequency_list[channel-1] * 100000;
                    wrq->u.freq.e = 1;
                }
#else
                wrq->u.freq.m = channel;
                wrq->u.freq.e = 0;
#endif // USE_FREQUENCY
            }
            break;

        case SIOCGIWRANGE:      // Get range of parameters
            if (wrq->u.data.pointer != NULL)
            {
                struct iw_range     range;
                __u16              *pTxRate;

                // Verify the user buffer
                ret = verify_area(VERIFY_WRITE,
                    wrq->u.data.pointer, sizeof(range));
                if (ret != 0)
                {
                    break;
                }

                // Set range information
                memset(&range, 0, sizeof(range));

                // Get the current transmit rate from the adapter
                lp->ltvRecord.len = 1 + (sizeof(*pTxRate) / sizeof(hcf_16));
                lp->ltvRecord.cmd = CFG_CURRENT_TX_RATE;
                status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
                if (status == HCF_SUCCESS)
                {
                    pTxRate = (__u16 *) &(lp->ltvRecord.u.u32);

                    range.throughput    = *pTxRate * 1024 * 1024;
                }
                range.num_channels      = 14;
		range.sensitivity	= 3;
#ifdef USE_DBM
                range.max_qual.qual     = (u_char) HCF_MAX_COMM_QUALITY;
                // If the value returned in /proc/net/wireless is greater than the maximum range,
                // iwconfig assumes that the value is in dBm. Because an unsigned char is used,
                // it requires a bit of contorsion...
                range.max_qual.level    = (u_char) (dbm(HCF_MIN_SIGNAL_LEVEL) - 1);
                range.max_qual.noise    = (u_char) (dbm(HCF_MIN_NOISE_LEVEL) - 1);
#else
                range.max_qual.qual     = 100;
                range.max_qual.level    = 100;
                range.max_qual.noise    = 100;
#endif // USE_DBM

#if WIRELESS_EXT > 8
		// Is WEP supported?
		if (wvlan2_has_wep(lp->hcfCtx))
		{
		    // WEP: RC4 40 bits
		    range.encoding_size[0] = MIN_KEY_SIZE;
		    // RC4 ~128 bits
		    range.encoding_size[1] = MAX_KEY_SIZE;
		    range.num_encoding_sizes = 2;
		    range.max_encoding_tokens = MAX_KEYS;	// 4 keys
		}
		else
		{
		    range.num_encoding_sizes = 0;
		    range.max_encoding_tokens = 0;
		}
#endif /* WIRELESS_EXT > 8 */

                // Copy the data into the user's buffer
                wrq->u.data.length = sizeof(range);
                copy_to_user(wrq->u.data.pointer, &range,
                    sizeof(range));
            }
            break;

#ifdef WIRELESS_SPY
        /*
            SIOCSIWSPY sets mac interface addresses to listen to. When
            packets arrive, we have to test the source mac address
            against this list and update the information.

            SIOCGIWSPY gets this information. With the information it's
            possible to compare the quality of links to different hw
            addresses.
            
            These two ioctls must cope with the iwreq structure in
            linux/wireless.h
        */

	case SIOCSIWSPY:        // Set the spy list
            if (wrq->u.data.length > IW_MAX_SPY)
            {
                ret = -E2BIG;
                break;
            }
            lp->spy_number = wrq->u.data.length;
            if (lp->spy_number > 0)
            {
                struct sockaddr address[IW_MAX_SPY];
                int i;
                ret = verify_area(VERIFY_READ, wrq->u.data.pointer, sizeof(struct sockaddr) * lp->spy_number);
                if (ret)
                    break;
                copy_from_user(address, wrq->u.data.pointer, sizeof(struct sockaddr) * lp->spy_number);
                for (i=0; i<lp->spy_number; i++)
                   memcpy(lp->spy_address[i], address[i].sa_data, MAC_ADDR_SIZE);
                memset(lp->spy_stat, 0, sizeof(struct iw_quality) * IW_MAX_SPY);
#ifdef DEBUGSPY
                DEBUG(DEBUG_INFO, "%s: New spy list:\n", dev_info);
                for (i=0; i<wrq->u.data.length; i++)
                {
                    DEBUG(DEBUG_INFO, "%s: %d - %02x:%02x:%02x:%02x:%02x:%02x\n", dev_info, i+1,
                        lp->spy_address[i][0], lp->spy_address[i][1],
                        lp->spy_address[i][2], lp->spy_address[i][3],
                        lp->spy_address[i][4], lp->spy_address[i][5]);
                }
#endif // DEBUGSPY
            }
            break;

	case SIOCGIWSPY:        // Get the spy list
            wrq->u.data.length = lp->spy_number;
            if ((lp->spy_number > 0) && (wrq->u.data.pointer))
            {
                struct sockaddr address[IW_MAX_SPY];
                int i;
                ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer, (sizeof(struct iw_quality)+sizeof(struct sockaddr)) * IW_MAX_SPY);
                if (ret)
                    break;
                for (i=0; i<lp->spy_number; i++)
                {
                    memcpy(address[i].sa_data, lp->spy_address[i], MAC_ADDR_SIZE);
                    address[i].sa_family = AF_UNIX;
                }
                copy_to_user(wrq->u.data.pointer, address, sizeof(struct sockaddr) * lp->spy_number);
                copy_to_user(wrq->u.data.pointer + (sizeof(struct sockaddr)*lp->spy_number), lp->spy_stat, sizeof(struct iw_quality) * lp->spy_number);
                for (i=0; i<lp->spy_number; i++)
                    lp->spy_stat[i].updated = 0;
            }
            break;
#endif /* WIRELESS_SPY */

#if WIRELESS_EXT > 4
        case SIOCGIWAP:         // Get the MAC Address of current AP
            // Get Current BSSID
            lp->ltvRecord.len = 4;
            lp->ltvRecord.cmd = CFG_CURRENT_BSSID;
            status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

            if (status == HCF_SUCCESS)
            {
                // Copy info into sockaddr struct
                wrq->u.data.length = ETH_ALEN;
                memcpy(wrq->u.ap_addr.sa_data, lp->ltvRecord.u.u8, wrq->u.data.length);
                wrq->u.ap_addr.sa_family = ARPHRD_ETHER;
            }
            else
            {
                ret = -EIO;
                break;
            }
            break;
#endif // WIRELESS_EXT > 4

#if WIRELESS_EXT > 5
	// Set desired network name (ESSID)
	case SIOCSIWESSID:
	    if (wrq->u.data.pointer == 0)
	        break;

	    // Validate the new value
	    if (wrq->u.data.length > HCF_MAX_NAME_LEN)
	    {
		ret = -EINVAL;
		break;
	    }

	    ret = verify_area(VERIFY_READ, wrq->u.data.pointer,
		wrq->u.data.length);
	    if (ret != 0)
	    {
		break;
	    }

	    memset(lp->NetworkName, 0, sizeof(lp->NetworkName));
	    // wrq->u.data.flags is zero to ask for "any"
	    if (wrq->u.data.flags == 0)
	        strcpy(lp->NetworkName, "ANY");
	    else
	        copy_from_user(lp->NetworkName, wrq->u.data.pointer,
		    wrq->u.data.length);
	    DBG_NOTICE(DbgInfo, "set NetworkName: %s\n", lp->NetworkName);

	    // Commit the adapter parameters
	    wvlan2_commit(lp);
	    break;

	// Get network name (ESSID)
	case SIOCGIWESSID:       // get the network name
	{
	    wvName_t   *pName;

	    if (wrq->u.data.pointer == 0)
	        break;

	    ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer,
		HCF_MAX_NAME_LEN);
	    if (ret != 0)
	    {
		break;
	    }

	    // Get the desired network name
	    lp->ltvRecord.len = 1 + (sizeof(*pName) / sizeof(hcf_16));
	    lp->ltvRecord.cmd = CFG_DESIRED_SSID;
	    status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
	    if (status == HCF_SUCCESS)
	    {
		pName = (wvName_t *) &(lp->ltvRecord.u.u32);

		// Copy the information into the user buffer
		wrq->u.data.length = pName->length + 1;
		if (pName->length < HCF_MAX_NAME_LEN)
		    pName->name[pName->length] = '\0';
		wrq->u.data.flags = 1;

#ifdef RETURN_CURRENT_NETWORKNAME
		// if desired is null ("any"), return current or "any"
		if (pName->name[0] == '\0') {
	            // Get the current network name
	            lp->ltvRecord.len = 1 + (sizeof(*pName) / sizeof(hcf_16));
	            lp->ltvRecord.cmd = CFG_CURRENT_SSID;
	            status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
	            if (status == HCF_SUCCESS)
	            {
		        pName = (wvName_t *) &(lp->ltvRecord.u.u32);

		        // Copy the information into the user buffer
		        wrq->u.data.length = pName->length + 1;
		        if (pName->length < HCF_MAX_NAME_LEN)
		            pName->name[pName->length] = '\0';
		        wrq->u.data.flags = 1;
	            }
		}
#endif // RETURN_CURRENT_NETWORKNAME

		copy_to_user(wrq->u.data.pointer, pName->name,
		    pName->length + 1);

	    }
	    else
	    {
		ret = -EIO;
		break;
	    }
	    break;
	}

#endif // WIRELESS_EXT > 5

#if WIRELESS_EXT > 8
	// Set WEP keys and mode
	case SIOCSIWENCODE:
	    // Is it supported?
	    if (!wvlan2_has_wep(lp->hcfCtx))
	    {
	    	ret = -EOPNOTSUPP;
	    	break;
	    }
	    DBG_NOTICE(DbgInfo, "pointer: %p, length: %d, flags: %#x\n",
	    	wrq->u.encoding.pointer, wrq->u.encoding.length,
		wrq->u.encoding.flags);

	    // Basic checking: do we have a key to set?
	    if (wrq->u.encoding.pointer !=  0)
	    {
	    	int index = (wrq->u.encoding.flags & IW_ENCODE_INDEX) - 1;
	    	int tk = lp->TransmitKeyID - 1;		// current key
	    	// Check the size of the key
	    	if (   wrq->u.encoding.length != MIN_KEY_SIZE
		    && wrq->u.encoding.length != MAX_KEY_SIZE)
	    	{
		    ret = -EINVAL;
		    break;
		}
		// Check the index
		if ((index < 0) || (index >= MAX_KEYS))
		    index = tk;
		// Cleanup
		memset(lp->DefaultKeys.key[index].key, 0, MAX_KEY_SIZE);
		// Copy the key in the driver
		if (copy_from_user(lp->DefaultKeys.key[index].key, wrq->u.encoding.pointer, wrq->u.encoding.length))
		{
		    lp->DefaultKeys.key[index].len = 0;
		    ret = -EFAULT;
		    break;
		}
		else {
		    // Set the length
		    lp->DefaultKeys.key[index].len = wrq->u.encoding.length;
		}
		DBG_NOTICE(DbgInfo, "encoding.length: %d\n", wrq->u.encoding.length);
		DBG_NOTICE(DbgInfo, "set key: %s(%d) [%d]\n", lp->DefaultKeys.key[index].key, lp->DefaultKeys.key[index].len, index  );
		// Enable WEP (if possible)
		if ((index == tk) && (lp->DefaultKeys.key[tk].len > 0))
		    lp->EnableEncryption = 1;
	    }
	    else
	    {
		int index = (wrq->u.encoding.flags & IW_ENCODE_INDEX) - 1;
		// Do we want to just set the current transmit key?
		if ((index >= 0) && (index < MAX_KEYS))
		{
		    DBG_NOTICE(DbgInfo, "index: %d; len: %d\n", index, lp->DefaultKeys.key[index].len);
		    if (lp->DefaultKeys.key[index].len > 0)
		    {
			lp->TransmitKeyID = index + 1;
			lp->EnableEncryption = 1;
		    }
		    else
			ret = -EINVAL;
		}
	    }
	    // Read the flags
	    if (wrq->u.encoding.flags & IW_ENCODE_DISABLED)
		lp->EnableEncryption = 0;	// disable encryption
	    if (wrq->u.encoding.flags & IW_ENCODE_RESTRICTED)
		ret = -EINVAL;		// Invalid
	    // Commit the changes
	    if (ret == 0) {
		DBG_NOTICE(DbgInfo, "encrypt: %d, ID: %d\n", lp->EnableEncryption, lp->TransmitKeyID);
	        wvlan2_commit(lp);
	    }
	    break;

	// Get the WEP keys and mode
	case SIOCGIWENCODE:
		DBG_NOTICE(DbgInfo, "GIWENCODE: encrypt: %d, ID: %d\n", lp->EnableEncryption, lp->TransmitKeyID);
		// Is it supported?
		if (!wvlan2_has_wep(lp->hcfCtx))
		{
			ret = -EOPNOTSUPP;
			break;
		}

		// Only super-user can see WEP key
		if (!capable(CAP_NET_ADMIN))
		{
			ret = -EPERM;
			break;
		}
		// Basic checking...
		if (wrq->u.encoding.pointer != 0)
		{
		    int index = (wrq->u.encoding.flags & IW_ENCODE_INDEX) - 1;
		    // Note: should read from adapter(?), and check if WEP capable
		    // Set the flags
		    wrq->u.encoding.flags = 0;
		    if (lp->EnableEncryption == 0)
		    	wrq->u.encoding.flags |= IW_ENCODE_DISABLED;
		    // Which key do we want
		    if ((index < 0) || (index >= MAX_KEYS))
		    	index = lp->TransmitKeyID-1;
		    wrq->u.encoding.flags |= index + 1;
		    // Copy the key to the user buffer
		    wrq->u.encoding.length = lp->DefaultKeys.key[index].len;
		    if (copy_to_user(wrq->u.encoding.pointer, lp->DefaultKeys.key[index].key, lp->DefaultKeys.key[index].len))
		    	ret = -EFAULT;
		}
		break;
#endif // WIRELESS_EXT > 8

#if WIRELESS_EXT > 7
	// Set desired station name
	case SIOCSIWNICKN:
	    if (!capable(CAP_NET_ADMIN))
	    {
		ret = -EPERM;
		break;
	    }

	    // Validate the new value
	    if (wrq->u.data.length > HCF_MAX_NAME_LEN)
	    {
		ret = -EINVAL;
		break;
	    }

	    ret = verify_area(VERIFY_READ, wrq->u.data.pointer, wrq->u.data.length);
	    if (ret != 0)
	    {
		break;
	    }

	    memset(lp->StationName, 0, sizeof(lp->StationName));
	    copy_from_user(lp->StationName, wrq->u.data.pointer,
		wrq->u.data.length);

	    // Commit the adapter parameters
	    wvlan2_commit(lp);
	    break;

	// Get current station name
	case SIOCGIWNICKN:
	{
	    wvName_t   *pName;

	    ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer,
		HCF_MAX_NAME_LEN);
	    if (ret != 0)
	    {
		break;
	    }

	    // Get the current station name
	    lp->ltvRecord.len = 1 + (sizeof(*pName) / sizeof(hcf_16));
	    lp->ltvRecord.cmd = CFG_CNF_OWN_NAME;
	    status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
	    if (status == HCF_SUCCESS)
	    {
		pName = (wvName_t *) &(lp->ltvRecord.u.u32);

		// Copy the information into the user buffer
		wrq->u.data.length = pName->length + 1;
		copy_to_user(wrq->u.data.pointer, pName->name,
		    pName->length + 1);
	    }
	    else
	    {
		ret = -EIO;
		break;
	    }
	}
	    break;

#endif // WIRELESS_EXT > 7

#if WIRELESS_EXT > 8
	case SIOCSIWMODE:       // set the port type
	{
	    hcf_16  portType;
	    hcf_16 *pData;

	    if (!capable(CAP_NET_ADMIN))
	    {
		ret = -EPERM;
		break;
	    }

	    // Validate the new value
	    pData = (hcf_16 *) &(wrq->u.data.pointer);
	    portType = *pData;
	    switch( portType ) {
	    case IW_MODE_ADHOC:	portType = 3; break;
	    case IW_MODE_INFRA:	portType = 1; break;
	    default:		portType = 0; ret = -EINVAL; break;
	    }

	    if (portType != 0) {
	        lp->PortType = portType;

	        // Commit the adapter parameters
	        wvlan2_commit(lp);
	    }
	}
	    break;

	case SIOCGIWMODE:       // get the port type
	{
	    hcf_16 *pPortType;
	    hcf_16 *pData;

	    // Get the current port type
	    lp->ltvRecord.len = 1 + (sizeof(*pPortType) / sizeof(hcf_16));
	    lp->ltvRecord.cmd = CFG_CNF_PORT_TYPE;
	    status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
	    if (status == HCF_SUCCESS)
	    {
		pPortType = (hcf_16 *) &(lp->ltvRecord.u.u32);

		pData = (hcf_16 *) &(wrq->u.data.pointer);
		switch( *pPortType ) {
		case 1: 	*pData = IW_MODE_INFRA; break;
		case 3: 	*pData = IW_MODE_ADHOC; break;
		}
	    }
	    else
	    {
		ret = -EIO;
		break;
	    }
	}
	    break;
#endif // WIRELESS_EXT > 8

#if WIRELESS_EXT > 7
	// Set the desired RTS threshold
	case SIOCSIWRTS:
	{
	    int rthr = wrq->u.rts.value;

	    if(wrq->u.rts.fixed == 0) {
		ret = -EINVAL;
		break;
	    }
#if WIRELESS_EXT > 8
	    if(wrq->u.rts.disabled)
	    	rthr = 2347;
#endif /* WIRELESS_EXT > 8 */
	    if((rthr < 256) || (rthr > 2347))
	    {
	    	ret = -EINVAL;
	    	break;
	    }
	    lp->RTSThreshold = rthr;
	    wvlan2_commit(lp);
	}
	break;

	// Get the current RTS threshold
	case SIOCGIWRTS:
		wrq->u.rts.value = lp->RTSThreshold;
#if WIRELESS_EXT > 8
		wrq->u.rts.disabled = (wrq->u.rts.value == 2347);
#endif /* WIRELESS_EXT > 8 */
		wrq->u.rts.fixed = 1;
		break;

	// Set the desired AP density
	case SIOCSIWSENS:
	{
	    int dens = wrq->u.sens.value;
	    if((dens < 1) || (dens > 3))
	    {
	    	ret = -EINVAL;
	    	break;
	    }
	    lp->DistanceBetweenAPs = dens;
	    wvlan2_commit(lp);
	}
	break;

	// Get the current AP density
	case SIOCGIWSENS:
	    wrq->u.sens.value = lp->DistanceBetweenAPs;
	    wrq->u.sens.fixed = 0;	/* auto */
	    break;

#endif // WIRELESS_EXT > 7

#ifdef IEEE_WIRELESS
	case SIOCGIWPRIV:       // get private ioctl interface info
	    if (wrq->u.data.pointer != NULL)
	    {
		struct iw_priv_args priv[] = {
	    	    { SIOCSIWNETNAME,
			IW_PRIV_TYPE_CHAR | HCF_MAX_NAME_LEN,
			0,
			"snetwork_name" },
	            { SIOCGIWNETNAME,
                        0,
                        IW_PRIV_TYPE_CHAR | HCF_MAX_NAME_LEN,
                        "gnetwork_name" },
                    { SIOCSIWSTANAME,
                        IW_PRIV_TYPE_CHAR | HCF_MAX_NAME_LEN,
                        0,
                        "sstation_name" },
                    { SIOCGIWSTANAME,
                        0,
                        IW_PRIV_TYPE_CHAR | HCF_MAX_NAME_LEN,
                        "gstation_name" },
                    { SIOCSIWPORTTYPE,
                        IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
                        0,
                        "sport_type" },
                    { SIOCGIWPORTTYPE,
                        0,
                        IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
                        "gport_type" },
                };

                // Verify the user buffer
                ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer,
                    sizeof(priv));
                if (ret != 0)
                {
                    break;
                }

                // Copy the data into the user's buffer
                wrq->u.data.length = NELEM(priv);
                copy_to_user(wrq->u.data.pointer, &priv,
                    sizeof(priv));
            }
            break;
#endif // IEEE_WIRELESS

        default:
            ret = -EOPNOTSUPP;
        }
    }

    *pRet = ret;

    DBG_LEAVE(DbgInfo);

    return wIoctl;
}
#endif // WIRELESS_EXT

/*+F*************************************************************************
 * Function:
 *   wvlan2_ioctl
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
{
    DBG_FUNC("wvlan2_ioctl")
    struct wvlan2_private  *lp;
    unsigned long           flags;
    int                     ret = 0;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);
    DBG_PARAM(DbgInfo, "rq", "0x%p", rq);
    DBG_PARAM(DbgInfo, "cmd", "0x%04x", cmd);

    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        spin_lock_irqsave(&(lp->slock), flags);

        // Only handle UIL IOCTL requests when the UIL has the
        // system blocked.
        if (!((lp->flags & WVLAN2_UIL_BUSY) &&
            (cmd != WVLAN2_IOCTL_UIL)))
        {
#ifdef WIRELESS_EXT
            if (!wvlan2_wireless_ioctl(dev, rq, cmd, &ret))
#endif // WIRELESS_EXT
            {
                struct uilreq  *urq = (struct uilreq *) rq;
#ifdef IEEE_WIRELESS
                struct iwreq   *wrq = (struct iwreq *) rq;
                int             status;
#endif // IEEE_WIRELESS

                switch (cmd)
                {
                // ================== Private IOCTLs (up to 16) ==================
                case WVLAN2_IOCTL_UIL:
                    DBG_TRACE(DbgInfo, "IOCTL: WVLAN2_IOCTL_UIL\n");
                    if (lp->hcfCtx == NULL)
                    {
                        urq->result = UIL_ERR_NO_DRV;
                        break;
                    }

                    switch (urq->command)
                    {
                    case WVLAN2_UIL_CONNECT:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_CONNECT\n");
                        if (!(lp->flags & WVLAN2_UIL_CONNECTED))
                        {
                            lp->flags |= WVLAN2_UIL_CONNECTED;

                            urq->result = UIL_SUCCESS;
                        }
                        else
                        {
                            urq->result = UIL_ERR_IN_USE;
                        }
                        break;

                    case WVLAN2_UIL_DISCONNECT:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_DISCONNECT\n");
                        if (lp->flags & WVLAN2_UIL_CONNECTED)
                        {
                            lp->flags &= ~WVLAN2_UIL_CONNECTED;
                            if (lp->flags & WVLAN2_UIL_BUSY)
                            {
                                lp->flags &= ~WVLAN2_UIL_BUSY;
                                netif_start_queue(dev);
                            }
                        }

                        urq->result = UIL_SUCCESS;
                        break;

                    case WVLAN2_UIL_BLOCK:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_BLOCK\n");
                        if (!capable(CAP_NET_ADMIN))
                        {
                            ret = -EPERM;
                            break;
                        }

                        lp->flags |= WVLAN2_UIL_BUSY;
                        netif_stop_queue(dev);

                        urq->result = UIL_SUCCESS;
                        break;

                    case WVLAN2_UIL_UNBLOCK:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_UNBLOCK\n");
                        if (!capable(CAP_NET_ADMIN))
                        {
                            ret = -EPERM;
                            break;
                        }

                        if (lp->flags & WVLAN2_UIL_BUSY)
                        {
                            lp->flags &= ~WVLAN2_UIL_BUSY;
                            netif_start_queue(dev);
                        }

                        urq->result = UIL_SUCCESS;
                        break;

                    case WVLAN2_UIL_ACTION_TALLIES:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_ACTION_TALLIES\n");
                        urq->result = hcf_action(lp->hcfCtx, HCF_ACT_TALLIES);
                        break;

                    case WVLAN2_UIL_ACTION_SCAN:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_ACTION_SCAN\n");
                        urq->result = hcf_action(lp->hcfCtx, HCF_ACT_SCAN);
                        break;

                    case WVLAN2_UIL_ACTION_DIAG:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_ACTION_DIAG\n");
                        if (!capable(CAP_NET_ADMIN))
                        {
                            ret = -EPERM;
                            break;
                        }

                        urq->result = hcf_action(lp->hcfCtx, HCF_ACT_DIAG);
                        break;

                    case WVLAN2_UIL_SEND_DIAG_MSG:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_SEND_DIAG_MSG\n");
                        if (!capable(CAP_NET_ADMIN))
                        {
                            ret = -EPERM;
                            break;
                        }

                        if ((urq->data != NULL) && (urq->len != 0))
                        {
                            if (lp->hcfCtx->IFB_PIFRscInd != 0)
                            {
                                u_char *data;

                                // Verify the user buffer
                                ret = verify_area(VERIFY_READ, urq->data,
                                    urq->len);
                                if (ret != 0)
                                {
                                    urq->result = UIL_FAILURE;
                                    break;
                                }

                                if ((data = kmalloc(urq->len, GFP_KERNEL)) !=
                                    NULL)
                                {
                                    // Copy the data from the user's buffer
                                    // into the local data area.
                                    copy_from_user(data, urq->data, urq->len);

                                    urq->result =
                                        hcf_send_diag_msg(lp->hcfCtx,
                                            HCF_PORT_0, data, urq->len);

                                    kfree_s(data, urq->len);
                                }
                                else
                                {
                                    urq->result = UIL_FAILURE;
                                    ret = -ENOMEM;
                                    break;
                                }
                            }
                            else
                            {
                                urq->result = UIL_ERR_BUSY;
                            }
                        }
                        else
                        {
                            urq->result = UIL_FAILURE;
                        }
                        break;

                    case WVLAN2_UIL_PUT_INFO:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_PUT_INFO\n");
                        if (!capable(CAP_NET_ADMIN))
                        {
                            ret = -EPERM;
                            break;
                        }

                        if ((urq->data != NULL) && (urq->len != 0))
                        {
                            ltv_t      *pLtv;
                            bool_t      ltvAllocated = FALSE;

                            // Make sure that we have at least a command
                            // and length to send.
                            if (urq->len < (sizeof(hcf_16) * 2))
                            {
                                urq->len = sizeof(lp->ltvRecord);
                                urq->result = UIL_ERR_LEN;
                                break;
                            }

                            // Verify the user buffer
                            ret = verify_area(VERIFY_READ, urq->data, urq->len);
                            if (ret != 0)
                            {
                                urq->result = UIL_FAILURE;
                                break;
                            }

                            // Get only the command and length information.
                            copy_from_user(&(lp->ltvRecord), urq->data,
                                sizeof(hcf_16) * 2);

                            // Make sure the incoming LTV record length is
                            // within the bounds of the IOCTL length.
                            if (((lp->ltvRecord.len + 1) * sizeof(hcf_16)) >
                                urq->len)
                            {
                                urq->len = sizeof(lp->ltvRecord);
                                urq->result = UIL_ERR_LEN;
                                break;
                            }

                            // If the requested length is greater than the
                            // size of our local LTV record, try to allocate
                            // it from the kernel stack. Otherwise, we just
                            // use our local LTV record.
                            if (urq->len > sizeof(lp->ltvRecord))
                            {
                                if ((pLtv = (ltv_t *) kmalloc(urq->len,
                                    GFP_KERNEL)) != NULL)
                                {
                                    ltvAllocated = TRUE;
                                }
                                else
                                {
                                    urq->len = sizeof(lp->ltvRecord);
                                    urq->result = UIL_ERR_LEN;
                                    ret = -ENOMEM;
                                    break;
                                }
                            }
                            else
                            {
                                pLtv = &(lp->ltvRecord);
                            }

                            // Copy the data from the user's buffer into the
                            // local LTV record data area.
                            copy_from_user(pLtv, urq->data, urq->len);

                            hcf_action(lp->hcfCtx, HCF_ACT_INT_OFF);
                            urq->result = hcf_put_info(lp->hcfCtx,
                                (LTVP) pLtv);
                            hcf_action(lp->hcfCtx, HCF_ACT_INT_ON);

                            if (ltvAllocated)
                            {
                                kfree_s(pLtv, urq->len);
                            }
                        }
                        else
                        {
                            urq->result = UIL_FAILURE;
                        }
                        break;

                    case WVLAN2_UIL_GET_INFO:
                        DBG_TRACE(DbgInfo, "  WVLAN2_UIL_GET_INFO\n");
                        if ((urq->data != NULL) && (urq->len != 0))
                        {
                            ltv_t      *pLtv;
                            bool_t      ltvAllocated = FALSE;

                            // Make sure that we have at least a command
                            // and length to send.
                            if (urq->len < (sizeof(hcf_16) * 2))
                            {
                                urq->len = sizeof(lp->ltvRecord);
                                urq->result = UIL_ERR_LEN;
                                break;
                            }

                            // Verify the user's LTV record header.
                            ret = verify_area(VERIFY_READ, urq->data,
                                sizeof(hcf_16) * 2);
                            if (ret != 0)
                            {
                                urq->result = UIL_FAILURE;
                                break;
                            }

                            // Get only the command and length information.
                            copy_from_user(&(lp->ltvRecord), urq->data,
                                sizeof(hcf_16) * 2);

                            // Make sure the incoming LTV record length is
                            // within the bounds of the IOCTL length.
                            if (((lp->ltvRecord.len + 1) * sizeof(hcf_16)) >
                                urq->len)
                            {
                                urq->len = sizeof(lp->ltvRecord);
                                urq->result = UIL_ERR_LEN;
                                break;
                            }

                            switch (lp->ltvRecord.cmd)
                            {
                            case CFG_DRV_INFO:
                                // Make sure that user buffer can handle the
                                // driver information buffer.
                                if (urq->len < sizeof(lp->driverInfo))
                                {
                                    urq->len = sizeof(lp->driverInfo);
                                    urq->result = UIL_ERR_LEN;
                                    break;
                                }

                                // Verify the user buffer.
                                ret = verify_area(VERIFY_WRITE, urq->data,
                                    sizeof(lp->driverInfo));
                                if (ret != 0)
                                {
                                    urq->result = UIL_FAILURE;
                                    break;
                                }

                                lp->driverInfo.card_stat =
                                    lp->hcfCtx->IFB_CardStat;
                                lp->driverInfo.frame_type =
                                    lp->hcfCtx->IFB_FrameType;

                                // Copy the driver information into the
                                // user's buffer.
                                urq->result = UIL_SUCCESS;
                                copy_to_user(urq->data, &(lp->driverInfo),
                                    sizeof(lp->driverInfo));
                                break;

                            case CFG_DRV_IDENTITY:
                                // Make sure that user buffer can handle the
                                // driver identity structure.
                                if (urq->len < sizeof(lp->driverIdentity))
                                {
                                    urq->len = sizeof(lp->driverIdentity);
                                    urq->result = UIL_ERR_LEN;
                                    break;
                                }

                                // Verify the user buffer.
                                ret = verify_area(VERIFY_WRITE, urq->data,
                                    sizeof(lp->driverIdentity));
                                if (ret != 0)
                                {
                                    urq->result = UIL_FAILURE;
                                    break;
                                }

                                // Copy the driver identity into the
                                // user's buffer.
                                urq->result = UIL_SUCCESS;
                                copy_to_user(urq->data, &(lp->driverIdentity),
                                    sizeof(lp->driverIdentity));
                                break;

                            case CFG_IFB:
                                // IFB can be a security hole
                                if (!capable(CAP_NET_ADMIN))
                                {
                                    ret = -EPERM;
                                    break;
                                }
                                // Else fall through to default
                                
                            default:
                                // Verify the user buffer
                                ret = verify_area(VERIFY_WRITE, urq->data,
                                    urq->len);
                                if (ret != 0)
                                {
                                    urq->result = UIL_FAILURE;
                                    break;
                                }

                                // If the requested length is greater than the
                                // size of our local LTV record, try to allocate
                                // it from the kernel stack. Otherwise, we just
                                // use our local LTV record.
                                if (urq->len > sizeof(lp->ltvRecord))
                                {
                                    if ((pLtv = (ltv_t *) kmalloc(urq->len,
                                        GFP_KERNEL)) != NULL)
                                    {
                                        ltvAllocated = TRUE;

                                        // Copy the command/length information
                                        // into the new buffer.
                                        memcpy(pLtv, &(lp->ltvRecord),
                                            sizeof(hcf_16) * 2);
                                    }
                                    else
                                    {
                                        urq->len = sizeof(lp->ltvRecord);
                                        urq->result = UIL_ERR_LEN;
                                        ret = -ENOMEM;
                                        break;
                                    }
                                }
                                else
                                {
                                    pLtv = &(lp->ltvRecord);
                                }

                                hcf_action(lp->hcfCtx, HCF_ACT_INT_OFF);
                                urq->result = hcf_get_info(lp->hcfCtx,
                                    (LTVP) pLtv);
                                hcf_action(lp->hcfCtx, HCF_ACT_INT_ON);

                                // Copy the LTV into the user's buffer.
                                copy_to_user(urq->data, pLtv, urq->len);

                                if (ltvAllocated)
                                {
                                    kfree_s(pLtv, urq->len);
                                }
                                break;
                            }
                        }
                        else
                        {
                            urq->result = UIL_FAILURE;
                        }
                        break;

                    default:
                        ret = -EOPNOTSUPP;
                        break;
                    }
                    break;

#ifdef IEEE_WIRELESS
                case SIOCSIWNETNAME:       // set the network name
                    if (!capable(CAP_NET_ADMIN))
                    {
                        ret = -EPERM;
                        break;
                    }

                    // Validate the new value
                    if (wrq->u.data.length > HCF_MAX_NAME_LEN)
                    {
                        ret = -EINVAL;
                        break;
                    }

                    ret = verify_area(VERIFY_READ, wrq->u.data.pointer,
                        wrq->u.data.length);
                    if (ret != 0)
                    {
                        break;
                    }

                    memset(lp->NetworkName, 0, sizeof(lp->NetworkName));
                    copy_from_user(lp->NetworkName, wrq->u.data.pointer,
                        wrq->u.data.length);

                    // Commit the adapter parameters
                    wvlan2_commit(lp);
                    break;

                case SIOCGIWNETNAME:       // get the network name
                {
                    wvName_t   *pName;

                    ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer,
                        HCF_MAX_NAME_LEN);
                    if (ret != 0)
                    {
                        break;
                    }

                    // Get the current network name
                    lp->ltvRecord.len = 1 + (sizeof(*pName) / sizeof(hcf_16));
                    lp->ltvRecord.cmd = CFG_CURRENT_SSID;
                    status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
                    if (status == HCF_SUCCESS)
                    {
                        pName = (wvName_t *) &(lp->ltvRecord.u.u32);

                        // Copy the information into the user buffer
                        wrq->u.data.length = pName->length + 1;
                        copy_to_user(wrq->u.data.pointer, pName->name,
                            pName->length + 1);
                    }
                    else
                    {
                        ret = -EIO;
                        break;
                    }
                }
                    break;

                case SIOCSIWSTANAME:       // set the station name
                    if (!capable(CAP_NET_ADMIN))
                    {
                        ret = -EPERM;
                        break;
                    }

                    // Validate the new value
                    if (wrq->u.data.length > HCF_MAX_NAME_LEN)
                    {
                        ret = -EINVAL;
                        break;
                    }

                    ret = verify_area(VERIFY_READ, wrq->u.data.pointer,
                        wrq->u.data.length);
                    if (ret != 0)
                    {
                        break;
                    }

                    memset(lp->StationName, 0, sizeof(lp->StationName));
                    copy_from_user(lp->StationName, wrq->u.data.pointer,
                        wrq->u.data.length);

                    // Commit the adapter parameters
                    wvlan2_commit(lp);
                    break;

                case SIOCGIWSTANAME:       // get the station name
                {
                    wvName_t   *pName;

                    ret = verify_area(VERIFY_WRITE, wrq->u.data.pointer,
                        HCF_MAX_NAME_LEN);
                    if (ret != 0)
                    {
                        break;
                    }

                    // Get the current station name
                    lp->ltvRecord.len = 1 + (sizeof(*pName) / sizeof(hcf_16));
                    lp->ltvRecord.cmd = CFG_CNF_OWN_NAME;
                    status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
                    if (status == HCF_SUCCESS)
                    {
                        pName = (wvName_t *) &(lp->ltvRecord.u.u32);

                        // Copy the information into the user buffer
                        wrq->u.data.length = pName->length + 1;
                        copy_to_user(wrq->u.data.pointer, pName->name,
                            pName->length + 1);
                    }
                    else
                    {
                        ret = -EIO;
                        break;
                    }
                }
                    break;

                case SIOCSIWPORTTYPE:       // set the port type
                {
                    hcf_16  portType;
                    hcf_16 *pData;

                    if (!capable(CAP_NET_ADMIN))
                    {
                        ret = -EPERM;
                        break;
                    }

                    // Validate the new value
                    pData = (hcf_16 *) &(wrq->u.data.pointer);
                    portType = *pData;
                    if (!((portType == 1) || (portType == 3)))
                    {
                        ret = -EINVAL;
                        break;
                    }

                    lp->PortType = portType;

                    // Commit the adapter parameters
                    wvlan2_commit(lp);
                }
                    break;

                case SIOCGIWPORTTYPE:       // get the port type
                {
                    hcf_16 *pPortType;
                    hcf_16 *pData;

                    // Get the current port type
                    lp->ltvRecord.len = 1 + (sizeof(*pPortType) / sizeof(hcf_16));
                    lp->ltvRecord.cmd = CFG_CNF_PORT_TYPE;
                    status = hcf_get_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
                    if (status == HCF_SUCCESS)
                    {
                        pPortType = (hcf_16 *) &(lp->ltvRecord.u.u32);

                        pData = (hcf_16 *) &(wrq->u.data.pointer);
                        *pData = *pPortType;
                    }
                    else
                    {
                        ret = -EIO;
                        break;
                    }
                }
                    break;
#endif // IEEE_WIRELESS

                default:
                    ret = -EOPNOTSUPP;
                    break;
                }
            }
        }
        else
        {
            ret = -EBUSY;
        }

        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
    return ret;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_isr
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_isr(int irq, void *dev_id, struct pt_regs *regs)
{
    DBG_FUNC("wvlan2_isr")
    struct net_device      *dev = (struct net_device *) dev_id;
    struct wvlan2_private  *lp;
    unsigned long           flags;
    bool_t                  done;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "irq", "%d", irq);
    DBG_PARAM(DbgInfo, "dev_id", "0x%p", dev_id);
    DBG_PARAM(DbgInfo, "regs", "0x%p", regs);

    if ((dev == NULL) || !netif_device_present(dev))
    {
        DBG_LEAVE(DbgInfo);
        return;
    }

    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
        spin_lock_irqsave(&(lp->slock), flags);
        hcf_action(lp->hcfCtx, HCF_ACT_INT_OFF);

        done = FALSE;
        while (!done)
        {
            done = TRUE;
            hcf_service_nic(lp->hcfCtx);

            // Rx complete
            if (lp->hcfCtx->IFB_RxLen != 0)
            {
                DBG_RX(DbgInfo, "Receive...\n");
                wvlan2_rx(dev);
                done = FALSE;
            }

            // Tx complete
            if ((lp->hcfCtx->IFB_PIFRscInd != 0) && (lp->txBytes != 0))
            {
                DBG_TX(DbgInfo, "Transmit complete.\n");
                lp->stats.tx_packets++;
#if (LINUX_VERSION_CODE >= VERSION(2,1,25))
                lp->stats.tx_bytes += lp->txBytes;
#endif // LINUX_VERSION_CODE
                lp->txBytes = 0;

                // Tell the upper layers that we are ready for another
                // packet.
                netif_wake_queue(dev);

                done = FALSE;
            }
        }

        hcf_action(lp->hcfCtx, HCF_ACT_INT_ON);
        spin_unlock_irqrestore(&(lp->slock), flags);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_tx_timeout
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_tx_timeout(struct net_device *dev)
{
    struct wvlan2_private  *lp;

    printk(KERN_WARNING "%s: Transmit timeout.\n", dev->name);

    // Reset the adapter
    wvlan2_reset(dev);

    // Accumulate the timeout error.
    if ((lp = (struct wvlan2_private *) dev->priv) != NULL)
    {
       lp->stats.tx_errors++;
    }
    dev->trans_start = jiffies;
    netif_start_queue(dev);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_tx
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_tx(struct sk_buff *skb, struct net_device *dev)
{
    DBG_FUNC("wvlan2_tx")
    struct wvlan2_private  *lp;
    unsigned long   flags;
    int             status;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "skb", "0x%p", skb);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    status = 0;

#if (LINUX_VERSION_CODE < VERSION(2,1,25))
    if (skb == NULL)
    {
        dev_tint(dev);

        goto wvlan2_tx_exit;
    }

    if (skb->len <= 0)
    {
        goto wvlan2_tx_exit;
    }
#endif // LINUX_VERSION_CODE

#if (LINUX_VERSION_CODE < VERSION(2,4,0))
    tx_timeout_check(dev, wvlan2_tx_timeout);
#endif

    if (((lp = (struct wvlan2_private *) dev->priv) != NULL) &&
        !(lp->flags & WVLAN2_UIL_BUSY))
    {
        spin_lock_irqsave(&(lp->slock), flags);
        hcf_action(lp->hcfCtx, HCF_ACT_INT_OFF);

#if DBG
        if (skb->next)
        {
            DBG_WARNING(DbgInfo, "Transmit skb has next!\n");
        }
#endif // DBG

        // Queue the packet to the adapter if it has Tx resources available.
        if (lp->hcfCtx->IFB_PIFRscInd != 0)
        {
#if (LINUX_VERSION_CODE >= VERSION(2,4,0))
            netif_stop_queue(dev);
#endif
            hcf_put_data(lp->hcfCtx, skb->data, skb->len, HCF_PORT_0);

            if (hcf_send(lp->hcfCtx, HCF_PORT_0) == HCF_SUCCESS)
            {
                dev->trans_start = jiffies;
                lp->txBytes += skb->len;
                DBG_TX(DbgInfo, "Transmit pending...\n");

                // If HCF thinks we have more resources available, we are
                // no longer busy!
                if (lp->hcfCtx->IFB_PIFRscInd != 0)
                {
                    netif_start_queue(dev);
                }
            }
            else
            {
                DBG_TX(DbgInfo, "Transmit failed!\n");
                lp->stats.tx_errors++;
                netif_start_queue(dev);
                status = 1;
            }
        }
        else
        {
            DBG_WARNING(DbgInfo, "HCF is not ready for transmit!\n");
            lp->stats.tx_dropped++;
            netif_start_queue(dev);
            status = 1;
        }

        hcf_action(lp->hcfCtx, HCF_ACT_INT_ON);
        spin_unlock_irqrestore(&(lp->slock), flags);
    }
    else
    {
        // If the UIL has the system blocked, return busy.
        if ((lp != NULL) && (lp->flags & WVLAN2_UIL_BUSY))
        {
            status = 1;
        }
        else
        {
            netif_start_queue(dev);
        }
    }

#if (LINUX_VERSION_CODE < VERSION(2,1,25))
wvlan2_tx_exit:
#endif // LINUX_VERSION_CODE

    if (status == 0)
    {
        DEV_KFREE_SKB(skb);
    }

    DBG_LEAVE(DbgInfo);
    return status;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_rx
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_rx(struct net_device *dev)
{
    DBG_FUNC("wvlan2_rx")
    struct sk_buff         *skb;
    struct wvlan2_private  *lp;
    int                     status;
    hcf_16                  pktlen;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    if (((lp = (struct wvlan2_private *) dev->priv) != NULL) &&
        !(lp->flags & WVLAN2_UIL_BUSY))
    {
        if ((pktlen = lp->hcfCtx->IFB_RxLen) != 0)
        {
            if ((skb = ALLOC_SKB(pktlen)) != NULL)
            {
                skb->dev = dev;

#define BLOCK_INPUT(buf, len) \
    status = hcf_get_data(lp->hcfCtx, 0, buf, len)

                GET_PACKET(dev, skb, pktlen);
                if (status == HCF_SUCCESS)
                {
                    netif_rx(skb);

                    lp->stats.rx_packets++;
#if (LINUX_VERSION_CODE >= VERSION(2,1,25))
                    lp->stats.rx_bytes += pktlen;
#endif // LINUX_VERSION_CODE
#ifdef WIRELESS_EXT
#ifdef WIRELESS_SPY
                    if (lp->spy_number > 0)
                    {
#if (LINUX_VERSION_CODE >= VERSION(1,3,0))
                        char *srcaddr = skb->mac.raw + MAC_ADDR_SIZE;
#else
                        char *srcaddr = skb->data + MAX_ADDR_SIZE;
#endif
                        wvlan2_spy_gather(dev, srcaddr);
                    }
#endif // WIRELESS_SPY
#endif // WIRELESS_EXT
                }
                else
                {
                    lp->stats.rx_dropped++;
                    DEV_KFREE_SKB(skb);
                }
            }
            else
            {
                lp->stats.rx_dropped++;
            }
        }
    }

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_multicast
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
#ifdef NEW_MULTICAST
static void
wvlan2_multicast(struct net_device *dev)
{
    DBG_FUNC("wvlan2_multicast")
    dev_link_t             *link;
    int                     x;
    struct dev_mc_list     *mclist;
    struct wvlan2_private  *lp;

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);

    for (link = dev_list; link; link = link->next)
    {
        if (link->priv == dev) break;
    }
    if (!(DEV_OK(link)))
    {
        DBG_LEAVE(DbgInfo);
        return;
    }

#if DBG
    if (DBG_FLAGS(DbgInfo) & DBG_PARAM_ON)
    {
        DBG_PRINT("  flags: %s%s%s\n",
            (dev->flags & IFF_PROMISC) ? "Promiscous " : "",
            (dev->flags & IFF_MULTICAST) ? "Multicast " : "",
            (dev->flags & IFF_ALLMULTI) ? "All-Multicast" : "");
        DBG_PRINT("  mc_count: %d\n", dev->mc_count);
        for (x = 0, mclist = dev->mc_list; mclist && x < dev->mc_count;
             x++, mclist = mclist->next)
        {
            DBG_PRINT("    %s (%d)\n", DbgHwAddr(mclist->dmi_addr), mclist->dmi_addrlen);
        }
    }
#endif // DBG

    if (((lp = (struct wvlan2_private *) dev->priv) != NULL) &&
        !(lp->flags & WVLAN2_UIL_BUSY))
    {
        if (dev->flags & IFF_PROMISC)
        {
            //
            // Enable promiscuous mode
            //
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_PROMISCUOUS_MODE;
            lp->ltvRecord.u.u16[0] = htoas(1);
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
        }
        else if ((dev->mc_count > HCF_MAX_MULTICAST) ||
            (dev->flags & IFF_ALLMULTI))
        {
            // How do we do this? Enabling promiscuous will give us more than
            // we want, but it should work...
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_PROMISCUOUS_MODE;
            lp->ltvRecord.u.u16[0] = htoas(1);
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
        }
        else if (dev->mc_count != 0)
        {
            //
            // Set the multicast addresses
            //
            lp->ltvRecord.len = (dev->mc_count * 3) + 1;
            lp->ltvRecord.cmd = CFG_GROUP_ADDR;
            for (x = 0, mclist = dev->mc_list;
                (x < dev->mc_count) && (mclist != NULL);
                 x++, mclist = mclist->next)
            {
                memcpy(&(lp->ltvRecord.u.u8[x * ETH_ALEN]),
                    mclist->dmi_addr, ETH_ALEN);
            }
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
        }
        else
        {
            //
            // Disable promiscuous mode
            //
            lp->ltvRecord.len = 2;
            lp->ltvRecord.cmd = CFG_PROMISCUOUS_MODE;
            lp->ltvRecord.u.u16[0] = htoas(0);
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));

            //
            // Disable multicast mode
            //
            lp->ltvRecord.len = 1;
            lp->ltvRecord.cmd = CFG_GROUP_ADDR;
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->ltvRecord));
        }
    }

    DBG_LEAVE(DbgInfo);
}
#else // NEW_MULTICAST
static void
wvlan2_multicast(struct net_device *dev, int num_addrs, void *addrs)
{
    DBG_FUNC("wvlan2_multicast")

    DBG_ENTER(DbgInfo);
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);
    DBG_PARAM(DbgInfo, "num_addrs", "%d", num_addrs);
    DBG_PARAM(DbgInfo, "addrs", "0x%p", addrs);

#error Obsolete set multicast interface!

    DBG_LEAVE(DbgInfo);
}
#endif // NEW_MULTICAST

#if WIRELESS_EXT > 8
/*+F*************************************************************************
 * Function:
 *   wvlan2_has_wep
 *
 * Description: 
 *	Return 1 if WEP is known enabled, else 0.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_has_wep (IFBP ifbp)
{
	CFG_ID_STRCT ltv;
	int rc, privacy;

	// This function allows us to distiguish bronze cards from other
	// types, to know if WEP exists...
	// Does not distinguish (because there's no way to) between
	// silver and gold cards.
	ltv.len = 2;
	ltv.typ = CFG_PRIVACY_OPTION_IMPLEMENTED;
	rc = hcf_get_info(ifbp, (LTVP) &ltv);
	privacy = ltv.id[0];
	return rc ? 0 : privacy;
}
#endif // WIRELESS_EXT > 8

#if DBG
/*+F*************************************************************************
 * Function:
 *   DbgHwAddr
 *
 * Description:
 *   Convert a hardware ethernet address to a character string
 *-F*************************************************************************/
static const char *
DbgHwAddr(
    unsigned char  *hwAddr
    )
{
    static char     buffer[18];

    sprintf(buffer, "%02X:%02X:%02X:%02X:%02X:%02X",
        hwAddr[0], hwAddr[1], hwAddr[2], hwAddr[3], hwAddr[4], hwAddr[5]);

    return buffer;
}

/*+F*************************************************************************
 * Function:
 *   DbgEvent
 *
 * Description:
 *   Convert the card serivces events to text for debug.
 *
 * Status: Complete
 *-F*************************************************************************/
static const char *
DbgEvent(int mask)
{
    static char DbgBuffer[256];
    char *pBuf;

    pBuf = DbgBuffer;

    *pBuf = '\0';

    if (mask & CS_EVENT_WRITE_PROTECT) strcat(pBuf, "WRITE_PROTECT ");
    if (mask & CS_EVENT_CARD_LOCK) strcat(pBuf, "CARD_LOCK ");
    if (mask & CS_EVENT_CARD_INSERTION) strcat(pBuf, "CARD_INSERTION ");
    if (mask & CS_EVENT_CARD_REMOVAL) strcat(pBuf, "CARD_REMOVAL ");
    if (mask & CS_EVENT_BATTERY_DEAD) strcat(pBuf, "BATTERY_DEAD ");
    if (mask & CS_EVENT_BATTERY_LOW) strcat(pBuf, "BATTERY_LOW ");
    if (mask & CS_EVENT_READY_CHANGE) strcat(pBuf, "READY_CHANGE ");
    if (mask & CS_EVENT_CARD_DETECT) strcat(pBuf, "CARD_DETECT ");
    if (mask & CS_EVENT_RESET_REQUEST) strcat(pBuf, "RESET_REQUEST ");
    if (mask & CS_EVENT_RESET_PHYSICAL) strcat(pBuf, "RESET_PHYSICAL ");
    if (mask & CS_EVENT_CARD_RESET) strcat(pBuf, "CARD_RESET ");
    if (mask & CS_EVENT_REGISTRATION_COMPLETE) strcat(pBuf, "REGISTRATION_COMPLETE ");
    if (mask & CS_EVENT_RESET_COMPLETE) strcat(pBuf, "RESET_COMPLETE ");
    if (mask & CS_EVENT_PM_SUSPEND) strcat(pBuf, "PM_SUSPEND ");
    if (mask & CS_EVENT_PM_RESUME) strcat(pBuf, "PM_RESUME ");
    if (mask & CS_EVENT_INSERTION_REQUEST) strcat(pBuf, "INSERTION_REQUEST ");
    if (mask & CS_EVENT_EJECTION_REQUEST) strcat(pBuf, "EJECTION_REQUEST ");
    if (mask & CS_EVENT_MTD_REQUEST) strcat(pBuf, "MTD_REQUEST ");
    if (mask & CS_EVENT_ERASE_COMPLETE) strcat(pBuf, "ERASE_COMPLETE ");
    if (mask & CS_EVENT_REQUEST_ATTENTION) strcat(pBuf, "REQUEST_ATTENTION ");
    if (mask & CS_EVENT_CB_DETECT) strcat(pBuf, "CB_DETECT ");
    if (mask & CS_EVENT_3VCARD) strcat(pBuf, "3VCARD ");
    if (mask & CS_EVENT_XVCARD) strcat(pBuf, "XVCARD ");

    if (*pBuf)
    {
        pBuf[strlen(pBuf) - 1] = '\0';
    }
    else
    {
        if (mask != 0x0)
        {
            sprintf(pBuf, "<<0x%08x>>", mask);
        }
    }

    return pBuf;
}
#endif // DBG

/*+F*************************************************************************
 * Function:
 *   init_module
 *
 * Description:
 *   Load the kernel module.
 *
 * Status: Complete
 *-F*************************************************************************/
int
init_module(void)
{
    DBG_FUNC("init_module")
    servinfo_t  serv;

#if DBG
    DbgInfo->dbgFlags = debug_flags;

    // Convert the pc_debug to a reasonable Debug Info value.
    // NOTE: The values all fall through to the lower values.
    switch (pc_debug)
    {
    case 7: DbgInfo->dbgFlags |= (DBG_RX_ON | DBG_TX_ON);
    case 6: DbgInfo->dbgFlags |= DBG_PARAM_ON;
    case 5: DbgInfo->dbgFlags |= DBG_TRACE_ON;
    case 4: DbgInfo->dbgFlags |= DBG_VERBOSE_ON;
    default: break;
    }
#endif // DBG

    DBG_ENTER(DbgInfo);

    DBG_PRINT("%s (%s-%s)\n", version, __DATE__, __TIME__);

    printk(KERN_INFO "Linux Wireless driver, Variant %d, Version %d.%02d\n",
        DRV_VARIANT, DRV_MAJOR_VERSION, DRV_MINOR_VERSION);

    CardServices(GetCardServicesInfo, &serv);
    if (serv.Revision != CS_RELEASE_CODE)
    {
        printk(KERN_WARNING
            "  The version of pcmcia-cs (%d.%d.%d) this driver is configured for\n"
            "  does not match the running Card Services version (%s).\n",
            serv.Revision >> 12, (serv.Revision & 0x0F00) >> 8, 
            serv.Revision & 0xFF, CS_RELEASE);
        DBG_LEAVE(DbgInfo);
        return -1;
    }

    register_pcmcia_driver(&dev_info, &adapter_attach, &adapter_detach);

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   cleanup_module
 *
 * Description:
 *   Unload the kernel module.
 *
 * Status: Complete
 *-F*************************************************************************/
void
cleanup_module(void)
{
    DBG_FUNC("cleanup_module")

    DBG_ENTER(DbgInfo);

    unregister_pcmcia_driver(&dev_info);

    while (dev_list != NULL)
    {
        adapter_detach(dev_list);
    }

    DBG_LEAVE(DbgInfo);
}
