/*+M*************************************************************************
 * Symbol Technologies Trilogy IEEE PCMCIA device driver for Linux.
 *
 * Copyright (c) 2000 Symbol Technologies Inc. -- http://www.symbol.com
 * All rights reserved.
 *
 * Developed for Symbol 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
 *   - Turbo cards
 *   - 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*************************************************************************/
#define _S24_INIT_
#include "Spectrum24tApi.h"
#include "Spectrum24t.h"
#include <linux/kernel.h>
#include <linux/wireless.h>
#include <asm/uaccess.h>

#if DBG
static p_u32    pc_debug = PCMCIA_DEBUG;
MODULE_PARM(pc_debug, "i");
static p_u32    debug_flags = DBG_DEFAULTS;
MODULE_PARM(debug_flags, "l");

drv_info_t S24tInfo = { "Spectrum24t", 0, 0};
static drv_info_t *DbgInfo = &S24tInfo;

#endif

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

/*--------------------------------------------------------------------------*/
/* 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-BSS
static p_u8     channel                 = 0;
static p_u8     ap_density              = 1;
static p_u8     antenna_diversity       = 0;
static p_u8     transmit_rate           = DEFAULT_TX_RATE;
static p_u16    medium_reservation      = S24T_MAX_RTS;
static p_u16    frag_threshold          = DEFAULT_FRAG_THRESHOLD;
static p_u8     card_power_management   = 0;
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 int      ignore_cis_vcc          = 0;
static p_u16    ap_distance             = 0;
static p_s16    cw10_regs[MAX_CW10_REGISTER] = { -1};
static p_u16    encryption              = 0;
static p_u16    keyindex                = 1;
static p_u16    keylength               = 5;
static p_char   *key1                   = NULL;
static p_char   *key2                   = NULL;
static p_char   *key3                   = NULL;
static p_char   *key4                   = NULL;

MODULE_PARM(irq_mask,               "i");
MODULE_PARM_DESC(irq_mask,               "IRQ mask [0xdeb8]");
MODULE_PARM(irq_list,               "1-4i");
MODULE_PARM_DESC(irq_list,               "IRQ list [<irq_mask>]");
MODULE_PARM(cw10_regs,               "1-64i");
MODULE_PARM_DESC(cw10_regs,          "CW10 Registers [<reg val>]");
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(antenna_diversity,      "b");
MODULE_PARM_DESC(antenna_diversity,      "Antenna Diversity (0-Diversity, 1-Primary, 2-Auxilary) [0]");
MODULE_PARM(transmit_rate,          "h");
MODULE_PARM_DESC(transmit_rate,          "Transmit Rate Control Bit Map, any or all; 1=1mbps 2=2mbps 4=5.5mbps 8=11mbps [0xf]");
MODULE_PARM(medium_reservation,     "h");
MODULE_PARM_DESC(medium_reservation,     "Medium Reservation (RTS/CTS Fragment Length) (1 - 3000) [3000]");
MODULE_PARM(frag_threshold,     "h");
MODULE_PARM_DESC(frag_threshold,     "Fragmentation Threshold (must be an even number) (256 - 2346) [2346]");
MODULE_PARM(card_power_management,  "b");
MODULE_PARM_DESC(card_power_management,  "Power Management Enabled (1-5) [0]");
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 (1 - 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(ignore_cis_vcc,         "i");
MODULE_PARM_DESC(ignore_cis_vcc,    "Ignore Vcc request from card CIS and trust the socket will DTRT.");

MODULE_PARM(ap_distance,                "b");
MODULE_PARM_DESC(ap_distance,         "Distance from the AP in miles [0]");

MODULE_PARM(encryption,              "h");
MODULE_PARM_DESC(encryption,         "Encryption Authorization [0] (0-Disabled, 1-Open System, 2-Shared Key, 3-Shared Key 128)");
MODULE_PARM(keyindex,               "h");
MODULE_PARM_DESC(keyindex,          "Initial Encryption Key Index (1 - 4) [1]");
MODULE_PARM(keylength,               "h");
MODULE_PARM_DESC(keylength,         "Encryption Key Length (5 or 13) [5]");
MODULE_PARM(key1,                   "s");
MODULE_PARM_DESC(key1,              "Encryption Key 1 (<string>) [NULL]");
MODULE_PARM(key2,                   "s");
MODULE_PARM_DESC(key2,              "Encryption Key 2 (<string>) [NULL]");
MODULE_PARM(key3,                   "s");
MODULE_PARM_DESC(key3,              "Encryption Key 3 (<string>) [NULL]");
MODULE_PARM(key4,                   "s");
MODULE_PARM_DESC(key4,              "Encryption Key 4 (<string>) [NULL]");

/*--------------------------------------------------------------------------*/
static int S24T_open(DEVICE *dev);
static int S24T_close(DEVICE *dev);
static int S24T_ioctl(DEVICE *dev, struct ifreq *rq, int cmd);
static void S24T_isr IRQ(int irq, void *dev_id, struct pt_regs *regs);
static int S24T_tx(struct sk_buff *skb, DEVICE *dev);
static int S24T_rx(DEVICE *dev);

static void S24T_insert(dev_link_t *link);
static void S24T_remove(DEVICE *dev);
static void S24T_suspend(DEVICE *dev);
static void S24T_resume(DEVICE *dev);
static void S24T_reset(DEVICE *dev);
static int S24T_config(DEVICE *dev, struct ifmap *map);
static struct net_device_stats *S24T_stats(DEVICE *dev);
#if defined(WIRELESS_EXT) && defined(FIXME)
static struct iw_statistics *S24T_wireless_stats(DEVICE *dev);
#endif // WIRELESS_EXT
static int S24_parm_init(struct S24T_private *lp);

#ifdef NEW_MULTICAST
static void S24T_multicast(DEVICE *dev);
#else
static void S24T_multicast(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_MAJOR_VERSION   1
#define DRV_MINOR_VERSION   4

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

#if defined(WIRELESS_EXT) && defined(FIXME)

/*+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(((value - min) * 100) / (max - min));
}
#endif // WIRELESS_EXT

/*+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:
 *   S24T_init
 *
 * Description:
 *   We never need to do anything when a device is "initialized"
 *   by the net software, because we only register already-found cards.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_init(DEVICE *dev)
{
    DBG_FUNC("S24T_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;
    DEVICE  *dev;
    int             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;

    dev = kmalloc(sizeof(DEVICE), GFP_KERNEL);
    memset(dev, 0, sizeof(DEVICE));

/* Make up an S24T specific data structure. */
    dev->priv = kmalloc(sizeof(struct S24T_private), GFP_KERNEL);
    memset(dev->priv, 0, sizeof(struct S24T_private));

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

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

#if defined(WIRELESS_EXT) && defined(FIXME)
    dev->get_wireless_stats = &S24T_wireless_stats;
#endif // WIRELESS_EXT

    dev->set_config         = &S24T_config;
    dev->get_stats          = &S24T_stats;
    dev->set_multicast_list = &S24T_multicast;
    init_dev_name(dev,((struct S24T_private *) dev->priv)->node);
    dev->init               = &S24T_init;
    dev->open               = &S24T_open;
    dev->stop               = &S24T_close;
    dev->do_ioctl           = &S24T_ioctl;

    netif_stop_queue(dev);

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

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

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

    DBG_LEAVE(DbgInfo);
    return(link);
}

/*+F*************************************************************************
 * Function:
 *   adapter_detach
 *
 * Description:
 *   This deletes a driver "instance". The device is de-registered
 *   with Card Services. If it has been released, 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")
    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 S24T_private  *lp;

        if ((lp = (struct S24T_private *) dev->priv) != NULL)
        {
            kfree(lp);
        }
        kfree(link->priv);
    }
    kfree(link);

    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;
    DEVICE  *dev = link->priv;
    struct S24T_private  *lp;

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

    if (link->open)
    {
        DBG_PRINT("Spectrum24t: 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;
    }

    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
        if (lp->Cor)
        {
            iounmap((__u8 *)lp->Cor);
            lp->Cor = NULL;
        }
    }
    CardServices(ReleaseWindow, link->win);
    CardServices(ReleaseConfiguration, link->handle);
    CardServices(ReleaseIO, link->handle, &(link->io));
    CardServices(ReleaseIRQ, link->handle, &(link->irq));

    link->state &= (~(DEV_CONFIG | DEV_STALE_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;
    DEVICE  *dev = link->priv;

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

    DBG_NOTICE(DbgInfo,"%s\n", DbgEvent(event));

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

            // Notify the adapter that it has been removed.
            S24T_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);
        S24T_insert(link);
        break;

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

    case CS_EVENT_RESET_PHYSICAL:
        if (link->state & DEV_CONFIG)
        {
            if (link->open)
            {
                netif_device_detach(dev);
                if (event == CS_EVENT_PM_SUSPEND)
                {
                    S24T_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)
        {
            if (CardServices(RequestConfiguration, link->handle, &(link->conf)) != CS_SUCCESS)
            {
                DBG_PRINT("CardServices RequestConfiguration failed.\n");
            }
            if (link->open)
            {
                if (event == CS_EVENT_PM_RESUME)
                {
                    S24T_resume(dev);
                }
                else
                {
                    S24T_reset(dev);
                }
                netif_device_attach(dev);
                netif_wake_queue(dev);
            }
        }
        break;
    }

    DBG_LEAVE(DbgInfo);
    return(0);
}

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


/*+F*************************************************************************
 * Function:
 *   S24T_insert
 *
 * Description:
 *   S24T_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
S24T_insert(dev_link_t *link)
{
    DBG_FUNC("S24T_insert")
    client_handle_t         handle;
    DEVICE                  *dev;
    tuple_t                 tuple;
    cisparse_t              parse;
    u_char                  buf[64];
    win_req_t               req;
    memreq_t                map;
    int                     last_fn, last_ret, i;
    int                     S24Status=0;
    struct S24T_private     *lp;
    ulong                   flags;
    uint                    memNwin=0;
    config_info_t           conf;
#if TRILOGY3
    uint                    t3=0;
#endif

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

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

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

    tuple.DesiredTuple = CISTPL_CONFIG;
    tuple.Attributes = 0;
    tuple.TupleData = buf;
    tuple.TupleDataMax = sizeof(buf);
    tuple.TupleOffset = 0;
    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];

    DBG_NOTICE(DbgInfo,"link->conf.ConfigBase %08x link->conf.Present %08x\n",
               (uint)link->conf.ConfigBase,
               (uint)link->conf.Present
              );

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

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

// In this loop, we scan the CIS for configuration table entries,
// each of which describes a valid card configuration, including
// voltage, IO window, memory window, and interrupt settings.
// We make no assumptions about the card to be configured: we use
// just the information available in the CIS.  In an ideal world,
// this would work for any PCMCIA card, but it requires a complete
// and accurate CIS.  In practice, a driver usually "knows" most of
// these things without consulting the CIS, and most client drivers
// will only use the CIS to fill in implementation-defined details.
    tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;

    // set up Vcc as the socket reports it.  
    CS_CHECK(GetConfigurationInfo, handle, &conf);
    link->conf.Vcc = conf.Vcc;

    CS_CHECK(GetFirstTuple, handle, &tuple);
    while (1)
    {
        cistpl_cftable_entry_t dflt = { 0};
        cistpl_cftable_entry_t *cfg = &(parse.cftable_entry);
        CFG_CHECK(GetTupleData, handle, &tuple);
        CFG_CHECK(ParseTuple, handle, &tuple, &parse);

        DBG_NOTICE(DbgInfo,"cfg->index %u\n",(uint)cfg->index);

        if (cfg->index == 0) goto next_entry;
        link->conf.ConfigIndex = cfg->index;

        // Does this card need audio output?
        if (cfg->flags & CISTPL_CFTABLE_AUDIO)
        {
            link->conf.Attributes |= CONF_ENABLE_SPKR;
            link->conf.Status = CCSR_AUDIO_ENA;
        }
	// we always use the socket's Vcc, but we can optionally
	// fail if that's different from what the CIS requests.
	if (cfg->vcc.present & (1 << CISTPL_POWER_VNOM)) {
	    if (conf.Vcc !=
		cfg->vcc.param[CISTPL_POWER_VNOM] / 10000) {
		if(!ignore_cis_vcc)
		    goto next_entry;
	    }
	} else if (dflt.vcc.present & (1 << CISTPL_POWER_VNOM)) {
	    if (conf.Vcc !=
		dflt.vcc.param[CISTPL_POWER_VNOM] / 10000) {
		if(!ignore_cis_vcc)
		    goto next_entry;
	    }
	}

#if TRILOGY3
        /*
         * So far, only Trilogy3 cards are 3.3 V.
         */
        t3 = (link->conf.Vcc < 50) ? 1 : 0;
        DBG_NOTICE(DbgInfo,"Trilogy%s detected.\n",t3?" 3":"");
#endif

        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_VNOM %u\n",(uint)cfg->vcc.param[CISTPL_POWER_VNOM]);
        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_VMIN %u\n",(uint)cfg->vcc.param[CISTPL_POWER_VMIN]);
        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_VMAX %u\n",(uint)cfg->vcc.param[CISTPL_POWER_VMAX]);
        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_ISTATIC %u\n",(uint)cfg->vcc.param[CISTPL_POWER_ISTATIC]);
        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_IAVG %u\n",(uint)cfg->vcc.param[CISTPL_POWER_IAVG]);
        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_IPEAK %u\n",(uint)cfg->vcc.param[CISTPL_POWER_IPEAK]);
        DBG_NOTICE(DbgInfo,"Vcc CISTPL_POWER_IDOWN %u\n",(uint)cfg->vcc.param[CISTPL_POWER_IDOWN]);

        DBG_NOTICE(DbgInfo,"cfg->vcc.present %08x link->conf.Vcc %u\n",(uint)cfg->vcc.present,link->conf.Vcc);

        if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM))
        {
            link->conf.Vpp1 = link->conf.Vpp2 = cfg->vpp1.param[CISTPL_POWER_VNOM]/10000;
        }
        else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM))
        {
            link->conf.Vpp1 = link->conf.Vpp2 = dflt.vpp1.param[CISTPL_POWER_VNOM]/10000;
        }

        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_VNOM %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_VNOM]);
        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_VMIN %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_VMIN]);
        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_VMAX %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_VMAX]);
        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_ISTATIC %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_ISTATIC]);
        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_IAVG %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_IAVG]);
        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_IPEAK %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_IPEAK]);
        DBG_NOTICE(DbgInfo,"Vpp1 CISTPL_POWER_IDOWN %u\n",(uint)cfg->vpp1.param[CISTPL_POWER_IDOWN]);

        DBG_NOTICE(DbgInfo,"cfg->vpp1.present %08x link->conf.Vpp1 %u\n",(uint)cfg->vpp1.present,link->conf.Vpp1);

        // Do we need to allocate an interrupt?
        if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1)
        {
            DBG_NOTICE(DbgInfo,"cfg->irq.IRQInfo1 %08x dflt.irq.IRQInfo1 %08x\n",cfg->irq.IRQInfo1,dflt.irq.IRQInfo1);
            link->conf.Attributes |= CONF_ENABLE_IRQ;
        }

        DBG_NOTICE(DbgInfo,"cfg->io.nwin %u\n",(uint)cfg->io.nwin);
        {
            u_char i;
            for (i=0; i<cfg->io.nwin; i++)
            {
                DBG_NOTICE(DbgInfo,"win %u base %08x len %08x\n",(uint)i,cfg->io.win[i].base,cfg->io.win[i].len);
            }
        }

        // IO window settings. If its a 3.3 volt card, it needs 7 bits of address. The Trilogy
        // 3 has an extended register set and no memory window.
#if TRILOGY3
        link->io.IOAddrLines = t3 ? 7 : 6;
#else
        link->io.IOAddrLines = 6;
#endif
        link->io.NumPorts1 = link->io.NumPorts2 = 0;
        if ((cfg->io.nwin > 0) || (dflt.io.nwin > 0))
        {
            cistpl_io_t *io = (cfg->io.nwin) ? &cfg->io : &dflt.io;

            DBG_NOTICE(DbgInfo,"io->flags %08x io->base %08x io->len %08x\n",
                       (uint)io->flags,
                       (uint)io->win[0].base,
                       (uint)io->win[0].len
                      );

            DBG_ASSERT(!(io->flags & CISTPL_IO_8BIT));
            DBG_ASSERT(io->flags & CISTPL_IO_16BIT);

            link->io.Attributes1 = IO_DATA_PATH_WIDTH_16;
            link->io.BasePort1 = io->win[0].base;
#if TRILOGY3
            link->io.NumPorts1 = t3 ? 128 : io->win[0].len;
#else
            link->io.NumPorts1 = io->win[0].len;
#endif

            if (io->nwin > 1)
            {
                link->io.Attributes2 = link->io.Attributes1;
                link->io.BasePort2 = io->win[1].base;
                link->io.NumPorts2 = io->win[1].len;
            }

        }

        // This reserves IO space but doesn't actually enable it
        CFG_CHECK(RequestIO, link->handle, &link->io);

        DBG_NOTICE(DbgInfo,"BasePort1 %08x NumPorts1 %08x Attributes1 %08x\n",
                   (uint)link->io.BasePort1,
                   (uint)link->io.NumPorts1,
                   (uint)link->io.Attributes1
                  );
        DBG_NOTICE(DbgInfo,"BasePort2 %08x NumPorts2 %08x Attributes2 %08x\n",
                   (uint)link->io.BasePort2,
                   (uint)link->io.NumPorts2,
                   (uint)link->io.Attributes2
                  );

        // Now set up a common memory window, if needed.  There is room
        // in the dev_link_t structure for one memory window handle,
        // but if the base addresses need to be saved, or if multiple
        // windows are needed, the info should go in the private data
        // structure for this device.
        // Note that the memory window base is a physical address, and
        // needs to be mapped to virtual space with ioremap() before it
        // is used.
        DBG_NOTICE(DbgInfo,"cfg->mem.nwin %u dflt.mem.nwin %u\n",cfg->mem.nwin,dflt.mem.nwin);
        memset(&req,0,sizeof(win_req_t));
        memNwin = (uint)cfg->mem.nwin;
        if ((cfg->mem.nwin > 0) || (dflt.mem.nwin > 0))
        {
            cistpl_mem_t *mem = (cfg->mem.nwin) ? &cfg->mem : &dflt.mem;

            req.Attributes = WIN_DATA_WIDTH_8|WIN_MEMORY_TYPE_CM|WIN_ADDR_SPACE_MEM|WIN_ENABLE;
            req.Base = mem->win[0].host_addr;
            //req.Size = mem->win[0].len;
            req.Size = 0;
            req.AccessSpeed = 0;
            link->win = (window_handle_t)link->handle;

            DBG_NOTICE(DbgInfo,"nwin %u mem->win[0].host_addr %08x mem->win[0].len %08x\n",(uint)mem->nwin,(uint)mem->win[0].host_addr,(uint)mem->win[0].len);

            CFG_CHECK(RequestWindow, &link->win, &req);

            DBG_NOTICE(DbgInfo,"RequestWindow suceeded, base %08x len %08x\n",(uint)req.Base,(uint)req.Size);

            map.Page = 0; map.CardOffset = mem->win[0].card_addr;
            CFG_CHECK(MapMemPage, link->win, &map);

            DBG_NOTICE(DbgInfo,"MemWindow Base %08x Size %08x Speed %08x Attributes %08x\n",
                       (uint)req.Base,
                       (uint)req.Size,
                       (uint)req.AccessSpeed,
                       (uint)req.Attributes
                      );
        }

        // If we got this far, we're cool!
        break;

        next_entry:
        if (cfg->flags & CISTPL_CFTABLE_DEFAULT)
            dflt = *cfg;
        CS_CHECK(GetNextTuple, handle, &tuple);
    }

// Allocate an interrupt line.  Note that this does not assign a
// handler to the interrupt, unless the 'Handler' member of the
// irq structure is initialized.
    if (link->conf.Attributes & CONF_ENABLE_IRQ)
    {
        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<4; i++)
            {
                link->irq.IRQInfo2 |= 1 << irq_list[i];
            }
        }
        link->irq.Handler = S24T_isr;
        link->irq.Instance = dev;
        CS_CHECK(RequestIRQ, link->handle, &link->irq);
    }

// This actually configures the PCMCIA socket -- setting up
// the I/O windows and the interrupt mapping, and putting the
// card and host interface into "Memory and IO" mode.
    link->conf.IntType      = INT_MEMORY_AND_IO;
    CS_CHECK(RequestConfiguration, link->handle, &link->conf);

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

    DBG_NOTICE(DbgInfo,"io1 %04x NumPorts1 %04x Attributes1 %08x\nio2 %04x NumPorts2 %04x Attributes2 %08x\nIOAddrLines %u\n",
               (uint)link->io.BasePort1,
               (uint)link->io.NumPorts1,
               (uint)link->io.Attributes1,
               (uint)link->io.BasePort2,
               (uint)link->io.NumPorts2,
               (uint)link->io.Attributes2,
               (uint)link->io.IOAddrLines
              );

    DBG_NOTICE(DbgInfo,"irq %d Vcc %d Vpp1 %d\n",
               (uint)link->irq.AssignedIRQ,
               (uint)link->conf.Vcc,
               (uint)link->conf.Vpp1
              );

    link->state &= (~DEV_CONFIG_PENDING);

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

    link->dev = &((struct S24T_private *) dev->priv)->node;

// Initialize the adapter hardware.
    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
        copy_dev_name(lp->node, dev);

        /*
         * Allocate a common memory window.
         */
        if (memNwin)
        {
            DBG_ASSERT(req.Base);
            DBG_ASSERT(req.Size);

            lp->Cor = (volatile __u8 *)ioremap(req.Base,req.Size);

            DBG_NOTICE(DbgInfo,"Cor phys %08x virt %08x len %08x COR %02x\n",
                       (uint)req.Base,
                       (uint)lp->Cor,
                       (uint)req.Size,
                       lp->Cor ? (uint)lp->Cor[OFFSET_COR] : 0
                      );

            if (!lp->Cor)
            {
                goto failed;
            }
        }
#if TRILOGY3
        else
        {
            lp->Cor = NULL;
            lp->CorIo = NIC_COR;
        }
#else
        else
        {
            goto failed;
        }
#endif


        /* Initialize the adapter parameters. */
        lp->dev = dev;

        if (S24_parm_init(lp))
        {
            goto failed;
        }

        local_irq_save(flags);

        if ((S24Status = S24tInit(lp)))
        {
            local_irq_restore(flags);
            goto S24failed;
        }

        /*
         * Note our MAC address.
         */
        memcpy(dev->dev_addr, lp->MACAddress, ETH_ALEN);
        dev->addr_len = ETH_ALEN;

        printk(KERN_ERR "%s: Spectrum24t mac_address ",lp->dev->name);
        for (i = 0; i < ETH_ALEN; i++)
        {
            printk("%02x%c", lp->dev->dev_addr[i], ((i < (ETH_ALEN-1)) ? ':' : '\n'));
        }

        if ((S24Status = S24tGeneralInfo(lp)))
        {
            local_irq_restore(flags);
            goto S24failed;
        }
#if S24_PROC_SYS
        if ((lp->ProcRegistrationStatus=S24tRegisterProcSys(lp,&dev_list)) < 0)
        {
            printk(KERN_ERR "Could not register spectrum24t proc file system for %s\n",dev->name);
        }
#endif
        printk(KERN_INFO "Spectrum24 firmware version %s %s\n",lp->FwVersion,lp->FwDate);

        if (S24tEnable(lp,0))
        {
            printk(KERN_ERR "Could not enable spectrum24.\n");
            goto S24failed;
        }

        for (i=0; i<MAX_CW10_REGISTER; i++)
        {
            if (cw10_regs[i] & (~0xff))
            {
                break;
            }
            if (S24tWriteCw10Reg(lp,(__u8)i,cw10_regs[i]) < 0)
            {
                printk("Failed to write CW reg %d %02x\n",i,cw10_regs[i]);
                break;
            }
        }
        netif_device_attach(dev);
        netif_start_queue(dev);
        netif_wake_queue(dev);

        local_irq_restore(flags);
    }

    DBG_LEAVE(DbgInfo);
    return;

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

    S24failed:
    if (S24Status)
    {
        printk(KERN_INFO "%s: Spectrum24 Trilogy enable failure: error code %d\n",
               dev->name, S24Status);
    }

    failed:
    adapter_release((u_long) link);

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   S24T_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
S24T_remove(DEVICE *dev)
{
    DBG_FUNC("S24T_remove")
    struct S24T_private  *lp;

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

    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
        S24tDisable(lp,lp->ApMode);

        __skb_queue_purge(&lp->ToBeFreed);
        __skb_queue_purge(&lp->Tx802_3Queue);

#if S24_PROC_SYS
        if (lp->ProcRegistrationStatus >= 0)
        {
            S24tUnRegisterProcSys(lp);
        }
#endif
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   S24T_suspend
 *
 * Description:
 *   Power-down and halt the adapter.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
S24T_suspend(DEVICE *dev)
{
    DBG_FUNC("S24T_suspend")
    struct S24T_private *lp;
    int                 flags;

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

    printk(KERN_ERR "S24T_suspend\n");
    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
#if FIXME

        // The adapter is suspended:
        // - Stop the adapter
        // - Power down
        local_irq_save(flags);
        S24tResetCOR(lp);
        local_irq_restore(flags);
#else
        local_irq_save(flags);
        if (S24tDisable(lp,lp->ApMode))
        {
            printk(KERN_ERR "S24T_suspend: S24tDisable failed,\n");
        }
        local_irq_restore(flags);
#endif
    }

    DBG_LEAVE(DbgInfo);
}

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

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

    printk(KERN_ERR "S24T_resume\n");

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

        local_irq_save(flags);

        if (S24tInit(lp))
        {
            printk(KERN_ERR "S24T_resume: S24tInit failed.\n");
        }
        if (S24tEnable(lp,lp->ApMode))
        {
            printk(KERN_ERR "S24T_resume: S24tEnable failed.\n");
        }

        local_irq_restore(flags);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   S24T_reset
 *
 * Description:
 *   Reset the adapter.
 *
 * Status: Complete
 *-F*************************************************************************/
static void
S24T_reset(DEVICE *dev)
{
    DBG_FUNC("S24T_reset")
    struct S24T_private  *lp;

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

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

        local_irq_save(flags);

        // Shutdown the adapter.
        S24tDisable(lp,lp->ApMode);

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

        // Restart the adapter.
        S24tInit(lp);
        S24tEnable(lp,lp->ApMode);

        local_irq_restore(flags);
    }

    DBG_LEAVE(DbgInfo);
}

int
S24_load_key(
            char *KeyStr,
            __u8 *Key,
            __u32 KeyLen
            )
{
    int i;

    if (KeyStr)
    {
        /*
         * Make sure the key string represents the right number of
         * binary bytes.
         */
        if (strlen(KeyStr) != (KeyLen*2))
        {
            printk(KERN_INFO "Invalid key '%s', must be %d hex digits.\n",KeyStr,KeyLen*2);
            return(-EINVAL);
        }
        /*
         * Each 2 hex string characters represents 1 byte.
         */
        for (i=0; i<KeyLen; i++)
        {
            char t[3];
            t[0] = KeyStr[i*2];
            t[1] = KeyStr[(i*2)+1];
            t[2] = '\0';
            Key[i] = (__u8)simple_strtoul(t,NULL,16);
        }
    }
    return(0);
}

/*+F*************************************************************************
 * Function:
 *   S24_parm_init
 *
 * Description:
 *   Write the parameters to the adapter structure.
 *
 * Status: Complete
 *
 *-F*************************************************************************/
static int
S24_parm_init(struct S24T_private *lp)
{
    DBG_FUNC("S24_parm_init")

    int i,j;

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

    DBG_ASSERT(lp);

    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, "antenna_diversity", "%d", antenna_diversity);
    DBG_PARAM(DbgInfo, "transmit_rate", "%d", transmit_rate);
    DBG_PARAM(DbgInfo, "medium_reservation", "%d", medium_reservation);
    DBG_PARAM(DbgInfo, "frag_threshold", "%d", frag_threshold);
    DBG_PARAM(DbgInfo, "ap_distance", "%d", ap_distance);
    DBG_PARAM(DbgInfo, "card_power_management", "%d", card_power_management);
    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, "encryption", "%d", encryption);

    if (encryption)
    {
        DBG_PARAM(DbgInfo, "Encryption Key Length", "%d", keylength);
        DBG_PARAM(DbgInfo, "Encryption Key Index", "%d", keyindex);
        DBG_PARAM(DbgInfo, "Encryption Key 1", "\"%s\"", key1);
        DBG_PARAM(DbgInfo, "Encryption Key 2", "\"%s\"", key2);
        DBG_PARAM(DbgInfo, "Encryption Key 3", "\"%s\"", key3);
        DBG_PARAM(DbgInfo, "Encryption Key 4", "\"%s\"", key4);
    }
#ifdef VALIDATE_PARAMS
#define VALID_PARAM(C) \
{ \
    if (!(C)) \
    { \
        printk(KERN_INFO "Spectrum24t/IEEE, parameter error: \"%s\"\n", #C); \
        return(-EINVAL); \
    } \
}
    VALID_PARAM(!network_name || (strlen(network_name) <= S24T_MAX_NAME_LEN));
    VALID_PARAM(!station_name || (strlen(station_name) <= S24T_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(antenna_diversity <= 2);
    VALID_PARAM((transmit_rate >= 1) && (transmit_rate <= 15));
    VALID_PARAM(medium_reservation <= S24T_MAX_RTS);
    VALID_PARAM((frag_threshold >= S24T_MIN_FRAG_THRESHOLD) && (frag_threshold <= S24T_MAX_FRAG_THRESHOLD) && (!(frag_threshold & 1)));
    VALID_PARAM(card_power_management <= 5);
    VALID_PARAM(!receive_all_multicasts || strchr("NnYy", receive_all_multicasts[0]) != NULL);
    VALID_PARAM((maximum_sleep_duration >= 1) && (maximum_sleep_duration <= 65535));
    VALID_PARAM((keyindex>=1) && (keyindex<=4));
    VALID_PARAM(encryption <= AUTHENTICATION_ALGORITHM_SHARED_KEY_128);
    VALID_PARAM((keylength==5) || (keylength==13));

    if (encryption)
    {
        VALID_PARAM( key1 == NULL || (strlen(key1) >= (keylength*2)) );
        VALID_PARAM( key2 == NULL || (strlen(key2) >= (keylength*2)) );
        VALID_PARAM( key3 == NULL || (strlen(key3) >= (keylength*2)) );
        VALID_PARAM( key4 == NULL || (strlen(key4) >= (keylength*2)) );
    }
#endif // VALIDATE_PARAMS

    // Set the driver parameters from the passed in parameters.
    lp->Param[PortType]                 = port_type;
    lp->Param[Channel]                  = channel;
    lp->Param[APDensity]                = ap_density;
    lp->Param[TxRateControl]            = transmit_rate;
    lp->Param[EncryptionKeyId]          = keyindex-1;
    lp->Param[RtsThreshHold]            = medium_reservation;
    lp->Param[AntennaDiversity]         = antenna_diversity;
    lp->Param[MaxDataLength]            = MAX_ETHERNET_BODY_SIZE + RFC1042_SIZE;
    lp->Param[PromiscuousMode]          = 0;
    lp->Param[TickTime]                 = 0;
    lp->Param[FragmentationThreshHold]  = frag_threshold;
    lp->Param[PowerSaveMode]            = card_power_management;
    lp->Param[FragmentationThreshHold]  = DEFAULT_FRAG_THRESHOLD;
    lp->Param[EncryptionKeyLen]         = keylength;
    lp->Param[EncryptionAuthentication] = encryption;
    lp->Param[ReceiveAllMCast]          = strchr("Yy", receive_all_multicasts[0]) ? 1 : 0;
    lp->Param[MaxSleepDuration]         = maximum_sleep_duration;
    lp->Param[AckTimeout]               = S24tMilesToAckTime(lp,ap_distance);

    lp->ApDistance                      = ap_distance;

    if (!encryption)
    {
        lp->Param[EncryptionEnabled] = ENCRYPT_FLAG_DISABLE;
    }
    else
    {
        char *Keys[4];

        Keys[0] = key1;
        Keys[1] = key2;
        Keys[2] = key3;
        Keys[3] = key4;

        lp->Param[EncryptionEnabled] = ENCRYPT_FLAG_ENABLE;

        for (i=0; i<4; i++)
        {
            if ((j=S24_load_key(Keys[i],lp->EncryptionKey[i],keylength)))
            {
                return(j);
            }
        }
    }


/*
 * Override the internal MAC address with the one from the module
 * options line.
 */
    if (mac_address[0] | mac_address[1] | mac_address[2])
    {
        memcpy(lp->MACAddress, mac_address, ETH_ALEN);
        memcpy(lp->dev->dev_addr, mac_address, ETH_ALEN);
#if DBG
        printk(KERN_ERR "%s: Spectrum24t mac_address (parm) ",lp->dev->name);
        for (i = 0; i < ETH_ALEN; i++)
        {
            printk("%02x%c", lp->dev->dev_addr[i], ((i < (ETH_ALEN-1)) ? ':' : '\n'));
        }
#endif
    }

    if (network_name && (strlen(network_name) <= S24T_MAX_NAME_LEN))
    {
        /*
         * Leaving NetworkName blank implies connect to any AP.
         */
        if (strcmp(network_name,"ANY"))
        {
            strcpy(lp->NetworkName, network_name);
        }
    }
    if (station_name && (strlen(station_name) <= S24T_MAX_NAME_LEN))
    {
        strcpy(lp->StationName, station_name);
    }

#if DBG
    printk(KERN_ERR "%s: Spectrum24t, io_addr %#03lx, irq %d\n",
           lp->dev->name, lp->dev->base_addr, lp->dev->irq);
#endif

    DBG_LEAVE(DbgInfo);

    return(0);
}

/*+F*************************************************************************
 * Function:
 *   S24T_config
 *
 * Description:
 *   Implement the SIOCSIFMAP interface.
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_config(DEVICE *dev, struct ifmap *map)
{
    DBG_FUNC("S24T_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:
 *   S24T_stats
 *
 * Description:
 *   Return the current device statistics.
 *
 * Status: Complete
 *-F*************************************************************************/
static struct net_device_stats *
S24T_stats(DEVICE *dev)
{
    DBG_FUNC("S24T_stats")
    struct net_device_stats    *pStats;
    struct S24T_private      *lp;

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

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

    DBG_LEAVE(DbgInfo);
    return(pStats);
}

#if defined(WIRELESS_EXT) && defined(FIXME)
/*+F*************************************************************************
 * Function:
 *   S24T_wireless_stats
 *
 * Description:
 *   Return the current device wireless statistics.
 *
 * Status: Complete
 *-F*************************************************************************/
static struct iw_statistics *
S24T_wireless_stats(DEVICE *dev)
{
    DBG_FUNC("S24T_wireless_stats")
    struct iw_statistics       *pStats;
    struct S24T_private      *lp;
    unsigned long               flags;

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

    pStats = NULL;
    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
        local_irq_save(flags);

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

#if !FIXME

        /*
         * Needs to be implemented.
         */
        memset(&(pStats->qual), 0, sizeof(pStats->qual));
        memset(&(pStats->discard), 0, sizeof(pStats->discard));
#endif

        local_irq_restore(flags);
    }

    DBG_LEAVE(DbgInfo);
    return(pStats);
}
#endif // WIRELESS_EXT

/*+F*************************************************************************
 * Function:
 *   S24T_open
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_open(DEVICE *dev)
{
    DBG_FUNC("S24T_open")
    dev_link_t          *link;
    struct S24T_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(-ENODEV);
    }

    lp = (struct S24T_private *)dev->priv;
    if (!lp)
    {
        DBG_LEAVE(DbgInfo);
        return(-ENODEV);
    }

    link->open++;
    MOD_INC_USE_COUNT;

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

#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0))
    dev->interrupt = 0;
#endif

    DBG_LEAVE(DbgInfo);
    return(0);
}

/*+F*************************************************************************
 * Function:
 *   S24T_close
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_close(DEVICE *dev)
{
    DBG_FUNC("S24T_close")
    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 (link == NULL)
    {
        DBG_LEAVE(DbgInfo);
        return(-ENODEV);
    }

    if (!link->open)
    {
        printk(KERN_ERR "Error closing Spectrum24 adapter.\n");
        return(-ENODEV);
    }

    link->open--;
    MOD_DEC_USE_COUNT;

    if (link->open)
    {
        DBG_PRINT("S24T_close: link->open %d\n", link->open);
        DBG_LEAVE(DbgInfo);
        return(0);
    }

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

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

    DBG_LEAVE(DbgInfo);
    return(0);
}

#if defined(WIRELESS_EXT) && defined(FIXME)
/*+F*************************************************************************
 * Function:
 *   S24T_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
S24T_wireless_ioctl(DEVICE *dev, struct ifreq *rq, int cmd, int *pRet)
{
    return(-EOPNOTSUPP);
}
#endif // WIRELESS_EXT

/*+F*************************************************************************
 * Function:
 *   S24T_ioctl
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_ioctl(DEVICE *dev, struct ifreq *rq, int cmd)
{
    DBG_FUNC("S24T_ioctl")

    struct S24T_private *lp = (struct S24T_private *)dev->priv;
    struct iwreq        *wrq = (struct iwreq *)rq;
    int                 status=(-EIO);
    GROUP_ORD           grp;
    __u32               i;

    DBG_ENTER(DbgInfo);

    if (lp == NULL)
    {
        DBG_LEAVE(DbgInfo);
        return(-EIO);
    }
#if FIXME
    if (verify_area(VERIFY_WRITE,wrq,sizeof(struct iwreq)))
    {
        DBG_PRINT("%s: could not verify write of %d bytes, cmd %08x\n",__FUNC__,sizeof(struct iwreq),cmd);
        DBG_LEAVE(DbgInfo);
        return(-EIO);
    }
#endif

    switch (cmd)
    {
    case SIOCDEVPRIVATE + 0x0: /* read scan info */
        {
            if (
               (wrq->u.data.length < sizeof(DS_SCAN_RESULTS))
               ||
               verify_area(VERIFY_WRITE,wrq->u.data.pointer,sizeof(DS_SCAN_RESULTS))
               ||
               (((__u32)wrq->u.data.pointer) & 3)
               )
            {
                DBG_PRINT("%s:%d Could not verify write of %d bytes or %d\n",__FUNC__,__LINE__,wrq->u.data.length,sizeof(DS_SCAN_RESULTS));
                DBG_LEAVE(DbgInfo);
                return(-EACCES);
            }
            status = S24tReadScanResults(lp,(DS_SCAN_RESULTS *)wrq->u.data.pointer);
            break;
        }
    case SIOCDEVPRIVATE + 0x1: /* read group ordinal info */
        {
            if (
               (wrq->u.data.length < sizeof(GROUP_ORD))
               ||
               verify_area(VERIFY_WRITE,wrq->u.data.pointer,sizeof(GROUP_ORD))
               ||
               (((__u32)wrq->u.data.pointer) & 3)
               )
            {
                DBG_PRINT("%s:%d Could not verify write of %d bytes or %d\n",__FUNC__,__LINE__,wrq->u.data.length,sizeof(DS_SCAN_RESULTS));
                DBG_LEAVE(DbgInfo);
                return(-EACCES);
            }
            status = S24tReadGroupOrd(lp,(GROUP_ORD *)wrq->u.data.pointer);
            break;
        }
    case SIOCDEVPRIVATE + 0x2: /* read driver info */
        {
            PS24_DRIVER_INFO Info;
            if (
               (wrq->u.data.length < sizeof(S24_DRIVER_INFO))
               ||
               verify_area(VERIFY_WRITE,wrq->u.data.pointer,sizeof(S24_DRIVER_INFO))
               ||
               (((__u32)wrq->u.data.pointer) & 3)
               )
            {
                DBG_PRINT("%s:%d Could not verify write of %d bytes or %d\n",__FUNC__,__LINE__,wrq->u.data.length,sizeof(S24_DRIVER_INFO));
                DBG_LEAVE(DbgInfo);
                return(-EACCES);
            }

            Info = (PS24_DRIVER_INFO)wrq->u.data.pointer;
            memcpy(Info->FwVersion,lp->FwVersion,S24_FW_VERSION_LEN);
            memcpy(Info->FwDate,lp->FwDate,S24_FW_VERSION_LEN);
            Info->ApDistance = lp->ApDistance;
            Info->ApDensity = lp->Param[APDensity];
            Info->Diversity = lp->Param[AntennaDiversity];
            Info->ConfiguredTxRateMask = lp->Param[TxRateControl];
            Info->RcvAllMcast = lp->Param[ReceiveAllMCast];
            Info->ConfiguredChannel = lp->Param[Channel];
            Info->Associated = lp->LinkStatus;
            Info->UptimeSecs = jiffies/HZ;

            if (!(status=S24tGetComms(lp)))
            {
                Info->CommsCq = lp->Cq;
                Info->CommsAsl = lp->Asl;
                Info->CommsAnl = lp->Anl;

                status = S24tGetCurrTxRate(lp,(__u16 *)&Info->ActualTxRate);
            }

            break;
        }

    case SIOCDEVPRIVATE + 0x3: /* set driver info */
        {
            PS24_DRIVER_INFO_SET Set;
            if (
               (wrq->u.data.length < sizeof(S24_DRIVER_INFO_SET))
               ||
               verify_area(VERIFY_READ,wrq->u.data.pointer,sizeof(S24_DRIVER_INFO_SET))
               ||
               (((__u32)wrq->u.data.pointer) & 3)
               )
            {
                DBG_PRINT("%s:%d Could not verify read of %d bytes or %d\n",__FUNC__,__LINE__,wrq->u.data.length,sizeof(S24_DRIVER_INFO_SET));
                DBG_LEAVE(DbgInfo);
                return(-EACCES);
            }

            Set = (PS24_DRIVER_INFO_SET)wrq->u.data.pointer;

            switch (Set->InfoSet)
            {
            case S24InfoSetApDistance:
                status = S24tWriteApDistance(lp,Set->u.ApDistance);
                break;
            case S24InfoSetApDensity:
                status = S24tSetApDensity(lp,Set->u.ApDensity);
                break;
            case S24InfoSetAntennaDiversity:
                status = S24tSetAntennaDiversity(lp,Set->u.Diversity);
                break;
            case S24InfoSetTxRateMask:
                status = S24tWriteTxRate(lp,Set->u.TxRateMask);
                break;
            case S24InfoSetRcvAllMcast:
                status = S24tSetReceiveAllMulticasts(lp,Set->u.RcvAllMcast);
                break;
            case S24InfoSetHostScan:
                status = S24tStartScan(lp,Set->u.HostScan,0);
                break;
            case S24InfoSetCw10Reg:
                status = S24tWriteCw10Reg(lp,Set->u.Reg.RegNum,Set->u.Reg.RegVal);
                break;
            default:
                status = (-EINVAL);
                break;
            }
            break;
        }
#if DBG
    case SIOCDEVPRIVATE + 0x4: /* set driver debug */
        S24tInfo.dbgFlags = wrq->u.mode;
        status = 0;
        break;
#endif
    case SIOCDEVPRIVATE + 0x5: /* get ordinal values */
    {
        S24T_ORD_ARRAY *Ord;
        if (
           (wrq->u.data.length < sizeof(S24T_ORD_ARRAY))
           ||
           verify_area(VERIFY_WRITE,wrq->u.data.pointer,sizeof(S24T_ORD_ARRAY))
           ||
           (((__u32)wrq->u.data.pointer) & 3)
           )
        {
            DBG_PRINT("%s:%d Could not verify write of %d bytes or %d\n",__FUNC__,__LINE__,wrq->u.data.length,sizeof(S24T_ORD_ARRAY));
            DBG_LEAVE(DbgInfo);
            return(-EACCES);
        }

        Ord = (S24T_ORD_ARRAY *)wrq->u.data.pointer;
        status = 0;
        for (i=0; (i<=LAST_FIXED_LENGTH_ORDINAL) && (!status); i++)
        {
            if (Ord->Ord[i])
            {
                status = S24tGetTableOneOrdinal(lp,i,&Ord->Val[i]);
            }
        }
        break;
    }
    case SIOCDEVPRIVATE + 0x6: /* get CW10 registers */
    {
        S24_CW10_REGS *Regs;
        if (
           (wrq->u.data.length < sizeof(S24_CW10_REGS))
           ||
           verify_area(VERIFY_WRITE,wrq->u.data.pointer,sizeof(S24_CW10_REGS))
           ||
           (((__u32)wrq->u.data.pointer) & 3)
           )
        {
            DBG_PRINT("%s:%d Could not verify write of %d bytes or %d\n",__FUNC__,__LINE__,wrq->u.data.length,sizeof(S24_CW10_REGS));
            DBG_LEAVE(DbgInfo);
            return(-EACCES);
        }

        Regs = (S24_CW10_REGS *)wrq->u.data.pointer;

        status = 0;
        for (i=0; (!status) && (i<MAX_CW10_REGISTER); i++)
        {
            status = S24tReadCw10Reg(lp,i,&Regs->Regs[i]);
        }
        break;
    }
    case SIOCGIWNAME:
        strcpy(wrq->u.name, "IEEE 802.11-DS");
        status = strlen(wrq->u.name);
        break;

    case SIOCSIWRATE:
        {
            // 1=1Mbit, 2=2Mbit, 3=auto, 4=5.5Mbit, 5=11Mbit
            __u32 brate;
            __u32 fixed;
            __u32 upto;

            brate = wrq->u.bitrate.value / 500000;

            switch (brate)
            {
            case 0: // auto
                fixed = 0x0;
                upto = 0xf;
                break;
            case 2: // 1 Mbit
                fixed = 0x1;
                upto = 0x1;
                break;
            case 4: // 2Mbit
                fixed = 0x2;
                upto = 0x2;
                break;
            case 11: // 5.5 Mbit
                fixed = 0x4;
                upto = 0x4;
                break;
            case 22: // 11 Mbit
                fixed = 0x5;
                upto = 0x5;
                break;
            default:
                fixed = 0x0;
                upto = 0xf;
            }
            if (wrq->u.bitrate.fixed && fixed)
            {
                brate = fixed;
            }
            else
            {
                brate = upto;
            }
            status = S24tWriteTxRate(lp,brate);
            break;
        }
    case SIOCGIWRATE:
        {
            if ((status = S24tReadGroupOrd(lp,&grp)) >= 0)
            {
                __u32 brate;
                if (grp.curr_tx_rate >= 8)
                {
                    brate = 22;
                }
                else if (grp.curr_tx_rate >= 4)
                {
                    brate = 11;
                }
                else if (grp.curr_tx_rate >= 2)
                {
                    brate = 4;
                }
                else
                {
                    brate = 2;
                }
                wrq->u.bitrate.value = brate * 500000;
                wrq->u.bitrate.fixed = 0;
                wrq->u.bitrate.disabled = 0;
                status = sizeof(struct iwreq);
            }
            break;
        }
    case SIOCSIWRTS:
        {
            __u32 val = wrq->u.rts.value;

            if (wrq->u.rts.disabled)
            {
                val = S24T_MAX_RTS;
            }
            if ((val <= 0) || (val > S24T_MAX_RTS))
            {
                status = (-EINVAL);
                break;
            }
            status = S24tWriteRts(lp,val);
            break;
        }
    case SIOCGIWRTS:
        {
            wrq->u.rts.value = (__u32)lp->Param[RtsThreshHold];
            wrq->u.rts.disabled = (wrq->u.rts.value == S24T_MAX_RTS);
            wrq->u.rts.fixed = 1;
            status = 0;
            break;
        }
    case SIOCSIWFRAG:
        {
            __u32 val = wrq->u.frag.value;

            if (wrq->u.frag.fixed)
            {
                printk(KERN_WARNING "The spectrum driver does not support fixed fragmentation.\n");
            }
            if (wrq->u.frag.disabled)
            {
                val = S24T_MAX_FRAG_THRESHOLD;
            }
            else if (
                    (val < S24T_MIN_FRAG_THRESHOLD) 
                    || 
                    (val > S24T_MAX_FRAG_THRESHOLD)
                    ||
                    (val & 1)
                    )
            {
                status = (-EINVAL);
                break;
            }
            status = S24tWriteFragThreshold(lp,val);
            break;
        }
    case SIOCGIWFRAG:
        {
            wrq->u.frag.value = lp->Param[FragmentationThreshHold];
            wrq->u.frag.disabled = (wrq->u.frag.value >= S24T_MAX_FRAG_THRESHOLD);
            wrq->u.frag.fixed = 1;
            status = 0;
            break;
        }
    case SIOCSIWTXPOW:
        {
            break;
        }
    case SIOCGIWTXPOW:
        {
            break;
        }

    case SIOCSIWESSID:
        {
            char essid[S24T_MAX_NAME_LEN+1];

            if (
               verify_area(VERIFY_READ,wrq->u.essid.pointer,wrq->u.essid.length)
               )
            {
                DBG_PRINT("%s:%d Could not verify read of %d bytes\n",__FUNC__,__LINE__,wrq->u.data.length);
                DBG_LEAVE(DbgInfo);
                return(-EACCES);
            }

            memset(essid,0,S24T_MAX_NAME_LEN+1);

            /*
             * Zero flags implies the SSID 'ANY'.
             */
            if (wrq->u.essid.flags)
            {
                if (copy_from_user(essid, wrq->u.essid.pointer, wrq->u.essid.length))
                {
                    return(-EFAULT);
                }
            }
            if ((status=S24tDisable(lp,lp->ApMode)) >= 0)
            {
                if ((status = S24tSetEssId(lp,essid)) >= 0)
                {
                    status = S24tEnable(lp,lp->ApMode);
                }
            }
            break;
        }
    case SIOCGIWESSID:
        {
            wrq->u.essid.flags = 1;
            wrq->u.essid.length = strlen(lp->NetworkName) + 1;
            if (wrq->u.essid.pointer)
            {
                if (copy_to_user(wrq->u.essid.pointer, lp->NetworkName, wrq->u.essid.length))
                {
                    return(-EFAULT);
                }
            }
            status = 0;
            break;
        }
    case SIOCGIWAP:
        {
            status = 0;
            wrq->u.ap_addr.sa_family = ARPHRD_ETHER;
            memcpy(wrq->u.ap_addr.sa_data,lp->BssId,ETH_ALEN);
            break;
        }
    case SIOCGIWFREQ:
        {
            __u16 chan;
            status = S24tGetChannel(lp,&chan);
            wrq->u.freq.m = (__u32)chan;
            wrq->u.freq.e = 0;
            wrq->u.freq.i = 0;
            status = 0;
            break;
        }
    case SIOCSIWFREQ:
        {
            if (wrq->u.freq.e)
            {
                printk(KERN_WARNING "The spectrum driver does not support frequency exponents\n");
            }
            status = S24tSetChannel(lp,wrq->u.freq.m);
            break;

        }
    case SIOCSIWNICKN:
        {
            if (wrq->u.data.length < (S24T_MAX_NAME_LEN+1))
            {
                char StationName[S24T_MAX_NAME_LEN+1];

                memset(StationName,0,(S24T_MAX_NAME_LEN+1));
                if (copy_from_user(StationName, wrq->u.data.pointer, wrq->u.data.length))
                    return -EFAULT;
                status = S24tSetStationName(lp,StationName);
            }
            break;
        }
    case SIOCGIWNICKN:
        {
            wrq->u.data.length = strlen(lp->StationName) + 1;
            if (copy_to_user(wrq->u.data.pointer, lp->StationName, wrq->u.data.length))
                return(-EFAULT);
            status = 0;
            break;
        }
    default:
        DBG_PRINT("%s: unknown ioctl %08x\n",__FUNC__,cmd);
        status = (-EPERM);
        break;
    }

    DBG_LEAVE(DbgInfo);
    return(status >= 0 ? 0 : status);
}

/*+F*************************************************************************
 * Function:
 *   S24T_isr
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static void
S24T_isr(int irq, void *dev_id, struct pt_regs *regs)
{
    DBG_FUNC("S24T_isr")
    DEVICE          *dev = (DEVICE *) dev_id;
    struct S24T_private  *lp;
    unsigned long           flags;
    __u16                   EvStat;
    __u16                   EvAck;
    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);
    DBG_PARAM(DbgInfo, "dev->start", "%d", netif_running(dev));

    if (dev == NULL)
    {
        DBG_PRINT("Interrupt with no dev.\n");
        DBG_LEAVE(DbgInfo);
        return;
    }

    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
        /*
         * Check Cor because it gets unmapped during adapter_release, which also
         * seems to cause an interrupt.
         */
        if (!S24tCardInserted(lp))
        {
            DBG_LEAVE(DbgInfo);
            return;
        }

        local_irq_save(flags);

        while (1)
        {
            if (S24tEvStat(lp,&EvStat))
            {
                DBG_PRINT("S24tEvStat failed in interrupt handler.\n");
                break;
            }

            if (EvStat == 0xffff)
            {
                // The card has been removed, don't bother trying to process this
                DBG_PRINT("S24t_isr got EvStat of 0xffff, suspect card was removed.\n");
                break;
            }

            DBG_TRACE(DbgInfo,"EvStat %04x\n",EvStat);

            EvStat &= NIC_INTEN_MASK;
            if (!EvStat)
            {
                break;
            }

            EvAck = 0;

            if (EvStat & IEN_TXEXC)
            {
                EvAck |= IEN_TXEXC;
            }

            if (EvStat & IEN_TX)
            {
                EvAck |= IEN_TX;
            }

            // Rx complete
            if (EvStat & IEN_RX)
            {
                DBG_RX(DbgInfo, "Receive...\n");
                EvAck |= IEN_RX;
                S24T_rx(dev);
            }

            // Tx complete
            if (EvStat & IEN_ALLOC)
            {
                EvAck |= IEN_ALLOC;

                if (S24tGetTxFid(lp))
                {
                    break;
                }
                S24tStartTx(lp);
            }

            if (EvStat & IEN_INFO)
            {
                EvAck |= IEN_INFO;
                S24tGetInfoRec(lp);
            }

            if (EvAck)
            {
                if (S24tEvAck(lp,EvAck))
                {
                    DBG_PRINT("S24tEvAck failed.\n");
                    break;
                }
                EvStat &= (~EvAck);
            }

            if (EvStat & NIC_INTEN_MASK)
            {
                DBG_PRINT("Did not handle all EvStat %08x bits.\n",EvStat);
            }
        }

        local_irq_restore(flags);
    }

    DBG_LEAVE(DbgInfo);
}

/*+F*************************************************************************
 * Function:
 *   S24T_tx
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_tx(struct sk_buff *skb, DEVICE *dev)
{
    DBG_FUNC("S24T_tx")
    struct S24T_private  *lp = (struct S24T_private *)dev->priv;
    unsigned long   flags;
    int             status;

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

    if (!skb)
    {
        DBG_PRINT("%s - NULL skb.\n",__FUNC__);
        return(-EIO);
    }

    status = 0;

    if (lp)
    {
        local_irq_save(flags);

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

        /*
         * Queue the packet to the adapter and start the transmit if resources
         * are available.
         */
        dev->trans_start = jiffies;
        lp->stats.tx_packets++;
        lp->stats.tx_bytes += skb->len;
        DBG_TX(DbgInfo, "Transmit pending...\n");

        S24tTx(lp, skb);

        local_irq_restore(flags);
    }
    else
    {
        DEV_KFREE_SKB(skb);
    }

    DBG_LEAVE(DbgInfo);
    return(status);
}

/*+F*************************************************************************
 * Function:
 *   S24T_rx
 *
 * Description:
 *
 * Status: Complete
 *-F*************************************************************************/
static int
S24T_rx(DEVICE *dev)
{
    DBG_FUNC("S24T_rx")
    struct sk_buff       *skb;
    struct S24T_private  *lp;

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

    if ((lp = (struct S24T_private *) dev->priv) != NULL)
    {
        if ((skb = S24tRx(lp)))
        {
            skb->dev = dev;
#if S24_PROC_SYS

            /*
             * A non-zero skb->cb implies AP management packet.
             */
            if (lp->ApMode && S24tIsApMgmtPkt(skb))
            {
                S24tProcQueueRx(lp,skb);
                return(0);
            }
            else
#endif
            {
                skb->protocol = eth_type_trans(skb, dev);
            }

            lp->stats.rx_packets++;
            lp->stats.rx_bytes += skb->len;

            netif_rx(skb);
        }
        else
        {
            lp->stats.rx_dropped++;
        }
    }

    DBG_LEAVE(DbgInfo);
    return(0);
}

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

    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 S24T_private *) dev->priv) != NULL)
    {
        __u8 Mc[S24T_MAX_MULTICAST][ETH_ALEN];

        local_irq_save(flags);

        if (dev->flags & IFF_PROMISC)
        {
            //
            // Enable promiscuous mode
            //
            S24tPromiscuousMode(lp,1);
        }
        else if ((dev->mc_count > S24T_MAX_MULTICAST) ||
                 (dev->flags & IFF_ALLMULTI))
        {
            S24tPromiscuousMode(lp,1);
        }
        else if (dev->mc_count != 0)
        {
            //
            // Set the multicast addresses
            //
            for (x = 0, mclist = dev->mc_list;
                (x < dev->mc_count) && (mclist != NULL);
                x++, mclist = mclist->next)
            {
                memcpy(Mc[x], mclist->dmi_addr, ETH_ALEN);
            }
            S24tSetMulticast(lp,dev->mc_count * ETH_ALEN,&Mc[0][0]);
        }
        else
        {
            //
            // Disable promiscuous mode
            //
            S24tPromiscuousMode(lp,0);

            //
            // Disable multicast mode
            //
            S24tSetMulticast(lp,0,&Mc[0][0]);
        }

        local_irq_restore(flags);
    }

    DBG_LEAVE(DbgInfo);
}
#else // NEW_MULTICAST
static void
S24T_multicast(DEVICE *dev, int num_addrs, void *addrs)
{
    DBG_FUNC("S24T_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

    printk(KERN_INFO "Spectrum24t %d.%02d %s %s\n",DRV_MAJOR_VERSION,DRV_MINOR_VERSION, __DATE__, __TIME__);

    CardServices(GetCardServicesInfo, &serv);
    if (serv.Revision != CS_RELEASE_CODE)
    {
        printk(KERN_ERR "Spectrum24t: 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);
}

MODULE_LICENSE("GPL");
