/*+M*************************************************************************
 * WaveLAN/IEEE PCMCIA device driver for Linux (WVLAN49).
 *
 * Copyright (c) 1998-2000 Lucent Technologies Inc. -- http://www.wavelan.com
 * All rights reserved.
 *
 * Developed for Lucent Technologies Inc. by TriplePoint, Inc.
 *   http://www.triplepoint.com
 *
 *---------------------------------------------------------------------------
 * This driver supports the following features:
 *   - Hot plug/unplug
 *   - Access Point and Ad-Hoc (peer-to-peer) communication
 *   - Card power management
 *   - Standard, Turbo and Turbo 11Mb cards
 *   - Wired Equivalent Privacy (WEP)
 *   - Driver utility interface (UIL)
 *
 *   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
#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]))

// 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 HCF_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

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 spinlock_t
typedef struct {
    volatile unsigned int lock;
} spinlock_t;
#endif // spinlock_t

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

#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 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                       APDensity;          // 1 - 3 (1)
    hcf_16                      RTSThreshold;       // 0 - 2347 (2347)
    hcf_8                       PMEnabled;          // 0 - 1 (0)
    hcf_8                       MicrowaveRobustness;// 0 - 1 (1)
    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");

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     ap_density              = 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  *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(ap_density,             "b");
MODULE_PARM_DESC(ap_density,             "AP Density (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(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 device *dev);
static int wvlan2_close(struct device *dev);
static int wvlan2_ioctl(struct device *dev, struct ifreq *rq, int cmd);
static void wvlan2_isr IRQ(int irq, void *dev_id, struct pt_regs *regs);
static int wvlan2_tx(struct sk_buff *skb, struct device *dev);
static int wvlan2_rx(struct device *dev);

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

#ifdef NEW_MULTICAST
static void wvlan2_multicast(struct device *dev);
#else
static void wvlan2_multicast(struct device *dev, int num_addrs, void *addrs);
#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   01

static const char version[] =
"wavelan2_cs.c 6.01BETA 04/17/2000 10:30:00 (Lucent Technologies)";

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

#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 WaveLAN device is "initialized"
 *   by the net software, because we only register already-found cards.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_init(struct 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 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 device), GFP_KERNEL);
    memset(dev, 0, sizeof(struct 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;
    dev->name       = ((struct wvlan2_private *) dev->priv)->node.dev_name;
    dev->init               = &wvlan2_init;
    dev->open               = &wvlan2_open;
    dev->stop               = &wvlan2_close;
    dev->do_ioctl           = &wvlan2_ioctl;
    dev->tbusy              = 1;

    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, 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 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;
            }

            kfree_s(lp, sizeof(struct wvlan2_private));
        }
        kfree_s(link->priv, sizeof(struct 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 unregister the net
 *   device, and 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;
    struct device  *dev = link->priv;

    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;
    }

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

    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 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)
        {
            dev->tbusy = 1; dev->start = 0;

            // 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)
            {
                dev->tbusy = 1; dev->start = 0;
                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 (link->open)
            {
                if (event == CS_EVENT_PM_RESUME)
                {
                    wvlan2_resume(dev);
                }
                else
                {
                    wvlan2_reset(dev);
                }
                dev->tbusy = 0; dev->start = 1;
            }
        }
        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 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;

#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;
    dev->tbusy = 0;

    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;

    // 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, "ap_density", "%d", ap_density);
        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, "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 "WaveLAN/IEEE, 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((ap_density >= 1) && (ap_density <= 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(!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->APDensity           = ap_density;
        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("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;
        
        // 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;
        }

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

        // Commit the parameters to the adapter.
        if ((hcfStatus = wvlan2_commit(lp)) !=
            HCF_SUCCESS)
        {
            goto hcf_failed;
        }
    }

    printk(KERN_INFO "%s: WaveLAN/IEEE, 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:
    if (hcfStatus != HCF_SUCCESS)
    {
        char     buffer[64], *pMsg;

        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: WaveLAN/IEEE, HCF failure: \"%s\"\n",
            dev->name, pMsg);
    }

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 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 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 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);

        // Commit the adapter parameters
        wvlan2_commit(lp);

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

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_reset
 *
 * Description:
 *   Reset the adapter.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
wvlan2_reset(struct device *dev)
{
    DBG_FUNC("wvlan2_reset")
    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);

        // 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);
        hcf_enable(lp->hcfCtx, HCF_PORT_0);

        // Commit the adapter parameters
        wvlan2_commit(lp);

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

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_commit
 *
 * Description:
 *   Write the parameters to the adapter.
 *   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:
        // - reload parameters
        // - restore multicast list
        // - enable Tx/Rx
        // - enable interrupts

        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
        //
        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 / AP Density
        //
        lp->ltvRecord.len = 2;
        lp->ltvRecord.cmd = CFG_CNF_SYSTEM_SCALE;
        lp->ltvRecord.u.u16[0] = htoas(lp->APDensity);
        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));

        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;
            key_string2key(key_1, &(lp->DefaultKeys.key[0]));
            key_string2key(key_2, &(lp->DefaultKeys.key[1]));
            key_string2key(key_3, &(lp->DefaultKeys.key[2]));
            key_string2key(key_4, &(lp->DefaultKeys.key[3]));
            hcf_put_info(lp->hcfCtx, (LTVP) &(lp->DefaultKeys));
        }
        
        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 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 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 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 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:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_open(struct device *dev)
{
    DBG_FUNC("wvlan2_open")
    dev_link_t *link;

    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++;
    MOD_INC_USE_COUNT;

    dev->interrupt = 0; dev->tbusy = 0; dev->start = 1;

    // Reset the adapter
    wvlan2_reset(dev);

    DBG_LEAVE(DbgInfo);
    return 0;
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_close
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_close(struct 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
    dev->tbusy = 1;
    dev->start = 0;

    // 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--;
    dev->start = 0;
    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 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, "WaveLAN/IEEE");
            break;

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

            // Validate the new value
            if (!(((wrq->u.freq.m >= 0) && (wrq->u.freq.m <= 14)) &&
                (wrq->u.freq.e == 0)))
            {
                ret = -EINVAL;
                break;
            }

            // Validate the new value against the valid adapter channels.
            if (wrq->u.freq.m != 0)
            {
                __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 << (wrq->u.freq.m - 1))))
                    {
                        ret = -EINVAL;
                        break;
                    }
                }
                else
                {
                    ret = -EIO;
                    break;
                }
            }

            lp->Channel = wrq->u.freq.m;

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

        case SIOCGIWFREQ:       // get channel/frequency
        {
            __u16      *pChannel;

            memset(&(wrq->u.freq), 0, sizeof(wrq->u.freq));

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

                wrq->u.freq.m = *pChannel;
            }
        }
            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;
#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

                // 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
#define DEBUG(n, args...) printk(KERN_DEBUG args);

                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 */

#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 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;
                                dev->tbusy = 0;
                            }
                        }

                        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;
                        dev->tbusy = 1;

                        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;
                            dev->tbusy = 0;
                        }

                        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
                    // NOTE: See Q1 above for explaination
                    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);

                        // NOTE: See Q1 above for explaination
                        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 device          *dev = (struct 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) || !dev->start)
    {
        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.
                dev->tbusy = 0;
                mark_bh(NET_BH);

                done = FALSE;
            }
        }

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

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   wvlan2_tx
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
wvlan2_tx(struct sk_buff *skb, struct 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

    // Tell the upper layers that we are busy, and cannot accept any
    // more buffers.
    if (test_and_set_bit(0, (void *) &(dev->tbusy)) != 0)
    {
        if ((jiffies - dev->trans_start) >= HCF_TX_TIMEOUT)
        {
            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;
            dev->tbusy = 0;
        }

        status = 1;
        goto wvlan2_tx_exit;
    }

    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)
        {
            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)
                {
                    dev->tbusy = 0;
                }
            }
            else
            {
                DBG_TX(DbgInfo, "Transmit failed!\n");
                lp->stats.tx_errors++;
                dev->tbusy = 0;
                status = 1;
            }
        }
        else
        {
            DBG_WARNING(DbgInfo, "HCF is not ready for transmit!\n");
            lp->stats.tx_dropped++;
            dev->tbusy = 0;
            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
        {
            dev->tbusy = 0;
        }
    }

wvlan2_tx_exit:
    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 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 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 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 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_PRINT("%s (%s-%s)\n", version, __DATE__, __TIME__);

    CardServices(GetCardServicesInfo, &serv);
    if (serv.Revision != CS_RELEASE_CODE)
    {
        printk(KERN_NOTICE "wavelan2_cs: Card Services release "
            "does not match!\n");
        DBG_LEAVE(DbgInfo);
        return -1;
    }

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

    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);
}

