/*******************************************************************************
 * Agere Systems Inc.
 * Wireless device driver for Linux (wlags49).
 *
 * Copyright (c) 1998-2003 Agere Systems Inc. 
 * All rights reserved.
 *   http://www.agere.com
 *
 * Initially developed by TriplePoint, Inc.
 *   http://www.triplepoint.com
 *
 *------------------------------------------------------------------------------
 *
 *   This file contains handler functions registered with the net_device
 *   structure.
 *
 *------------------------------------------------------------------------------
 *
 * SOFTWARE LICENSE
 *
 * This software is provided subject to the following terms and conditions,
 * which you should read carefully before using the software.  Using this
 * software indicates your acceptance of these terms and conditions.  If you do
 * not agree with these terms and conditions, do not use the software.
 *
 * Copyright © 2003 Agere Systems Inc.
 * All rights reserved.
 *
 * Redistribution and use in source or binary forms, with or without
 * modifications, are permitted provided that the following conditions are met:
 *
 * . Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following Disclaimer as comments in the code as
 *    well as in the documentation and/or other materials provided with the
 *    distribution.
 * 
 * . 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.
 * 
 * . Neither the name of Agere Systems Inc. nor the names of the contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * Disclaimer
 *
 * THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, INFRINGEMENT AND THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  ANY
 * USE, MODIFICATION OR DISTRIBUTION OF THIS SOFTWARE IS SOLELY AT THE USERS OWN
 * RISK. IN NO EVENT SHALL AGERE SYSTEMS INC. OR CONTRIBUTORS 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, INCLUDING, BUT NOT LIMITED TO, 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.
 *
 ******************************************************************************/



/*******************************************************************************
 * VERSION CONTROL INFORMATION
 *******************************************************************************
 *
 * $Author: nico $
 * $Date: 2004/08/03 13:33:44 $
 * $Revision: 1.8 $
 * $Source: /usr/local/cvs/wl_lkm/wireless/wl_netdev.c,v $
 *
 ******************************************************************************/



/*******************************************************************************
 * include files
 ******************************************************************************/
#include <wireless/wl_version.h>

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
// #include <linux/sched.h>
// #include <linux/ptrace.h>
// #include <linux/slab.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 <linux/skbuff.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>

#include <hcf/debug.h>

#include <hcf.h>
#include <dhf.h>
// #include <hcfdef.h>

#include <wireless/wl_if.h>
#include <wireless/wl_internal.h>
#include <wireless/wl_util.h>
#include <wireless/wl_priv.h>
#include <wireless/wl_main.h>
#include <wireless/wl_netdev.h>
#include <wireless/wl_wext.h>

#ifdef USE_PROFILE
#include <wireless/wl_profile.h>
#endif  /* USE_PROFILE */

#ifdef BUS_PCMCIA
#include <wireless/wl_cs.h>
#endif  /* BUS_PCMCIA */

#ifdef BUS_PCI
#include <wireless/wl_pci.h>
#endif  /* BUS_PCI */


/*******************************************************************************
 * global variables
 ******************************************************************************/
#if DBG
extern dbg_info_t *DbgInfo;
#endif  /* DBG */


#if HCF_ENCAPSULATION == 1
#define MTU_MAX (HCF_MAX_MSG - ETH_HLEN - 8)
#else
#define MTU_MAX (HCF_MAX_MSG - ETH_HLEN)
#endif

static int mtu = MTU_MAX;
MODULE_PARM(mtu, "i");
MODULE_PARM_DESC(mtu, "MTU");

/*******************************************************************************
 * macros
 ******************************************************************************/
#define BLOCK_INPUT(buf, len) \
    desc->buf_addr = buf; \
    desc->BUF_SIZE = len; \
    status = hcf_rcv_msg(&(lp->hcfCtx), desc, 0)

#define BLOCK_INPUT_DMA(buf, len) memcpy( buf, desc_next->buf_addr, pktlen )

/*******************************************************************************
 * function prototypes
 ******************************************************************************/

/*******************************************************************************
 *	wl_init()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      We never need to do anything when a "Wireless" device is "initialized"
 *  by the net software, because we only register already-found cards.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int wl_init( struct net_device *dev )
{
    unsigned long       flags;
    struct wl_private   *lp = NULL;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_init" );
    DBG_ENTER( DbgInfo );

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

    lp = (struct wl_private *)dev->priv;
    if( lp != NULL ) {
        /* Nothing to do, but grab the spinlock anyway just in case we ever need
           this routine */
        spin_lock_irqsave( &( lp->slock ), flags );
        spin_unlock_irqrestore( &( lp->slock ), flags );
    }

    DBG_LEAVE( DbgInfo );
    return 0;
} // wl_init
/*============================================================================*/

/*******************************************************************************
 *	wl_config()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Implement the SIOCSIFMAP interface.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *      map - a pointer to the device's ifmap structure
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno otherwise
 *
 ******************************************************************************/
int wl_config( struct net_device *dev, struct ifmap *map )
{
    DBG_FUNC( "wl_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_TRACE( DbgInfo, "%s: %s called.\n", dev->name, __FUNC__ );

    DBG_LEAVE( DbgInfo );
    return 0;
} // wl_config
/*============================================================================*/

/*******************************************************************************
 *	wl_stats()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Return the current device statistics.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      a pointer to a net_device_stats structure containing the network
 *      statistics.
 *
 ******************************************************************************/
struct net_device_stats *wl_stats( struct net_device *dev )
{
#ifdef USE_WDS
    int                         count;
#endif  /* USE_WDS */
    unsigned long               flags;
    struct net_device_stats     *pStats;
    struct wl_private           *lp = NULL;
    /*------------------------------------------------------------------------*/

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

    pStats = NULL;

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

#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            spin_unlock_irqrestore( &( lp->slock ), flags );

            //DBG_LEAVE( DbgInfo );
            return NULL;
        }
#endif  /* USE_RTS */

        /* Return the statistics for the appropriate device */
#ifdef USE_WDS

        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( dev == lp->wds_port[count].dev ) {
                pStats = &( lp->wds_port[count].stats );
            }
        }

#endif  /* USE_WDS */

        /* If pStats is still NULL, then the device is not a WDS port */
        if( pStats == NULL ) {
            pStats = &( lp->stats );
        }

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

    //DBG_LEAVE( DbgInfo );

    return pStats;
} // wl_stats
/*============================================================================*/

/*******************************************************************************
 *	wl_open()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Open the device.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno otherwise
 *
 ******************************************************************************/
int wl_open(struct net_device *dev)
{
    int                 status = HCF_SUCCESS;
    struct wl_private   *lp = NULL;
    unsigned long       flags;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_open" );
    DBG_ENTER( DbgInfo );

    lp = (struct wl_private *)dev->priv;

    if( lp == NULL ) {
        DBG_WARNING( DbgInfo, "lp pointer is NULL, cannot open card\n" );
        status = HCF_ERR_NO_NIC;			//;?suspect use of HCF-mnemonics
    } else {
        spin_lock_irqsave( &( lp->slock ), flags );

#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            DBG_TRACE( DbgInfo, "Skipping device open, in RTS mode\n" );
            spin_unlock_irqrestore( &( lp->slock ), flags );
            DBG_LEAVE( DbgInfo );
            return -EIO;
        }
#endif  /* USE_RTS */

#ifdef USE_PROFILE
        parse_config( dev );
#endif

        //wl_put_ltv_init( lp ); ;?

        if( lp->portState == WVLAN_PORT_STATE_DISABLED ) {
            DBG_TRACE( DbgInfo, "Enabling Port 0\n" );
            status = wl_enable( lp );

            if( status != HCF_SUCCESS ) {
                DBG_TRACE( DbgInfo, "Enable port 0 failed: 0x%x\n", status );
            }
        }
		if ( strlen( lp->fw_image_filename ) ) {
        	DBG_TRACE( DbgInfo, ";???? Kludgy way to force a download\n" );
            status = wl_go( lp );
        } else {
            status = wl_apply( lp );
        }
        if( status == HCF_SUCCESS ) {
            MOD_INC_USE_COUNT;

            netif_carrier_on( dev );
            WL_WDS_NETIF_CARRIER_ON( lp );

			status = hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_ON );
			
			netif_start_queue( dev );
			WL_WDS_NETIF_START_QUEUE( lp );
        } else {
            wl_hcf_error( dev, status ); 		/* Report the error */
            netif_device_detach( dev ); 		/* Stop the device and queue */
        }
        spin_unlock_irqrestore( &( lp->slock ), flags );
    }
    DBG_LEAVE( DbgInfo );
    return status;
} // wl_open
/*============================================================================*/

/*******************************************************************************
 *	wl_close()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Close the device.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno otherwise
 *
 ******************************************************************************/
int wl_close( struct net_device *dev )
{
    struct wl_private   *lp = NULL;
    unsigned long   flags;
    /*------------------------------------------------------------------------*/

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

    /* Mark the adapter as busy */
    netif_stop_queue( dev );
    WL_WDS_NETIF_STOP_QUEUE( lp );

    netif_carrier_off( dev );
    WL_WDS_NETIF_CARRIER_OFF( lp );
    
    /* Shutdown the adapter:
            Disable adapter interrupts
            Stop Tx/Rx
            Update statistics
            Set low power mode
    */

    if(( lp = (struct wl_private *)dev->priv ) != NULL ) {
        spin_lock_irqsave( &( lp->slock ), flags );
#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            DBG_TRACE( DbgInfo, "Skipping device close, in RTS mode\n" );
            spin_unlock_irqrestore( &( lp->slock ), flags );
            DBG_LEAVE( DbgInfo );
            return -EIO;
        }
#endif  /* USE_RTS */

        /* Disable the ports */
        wl_disable( lp );
        hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_OFF );
        spin_unlock_irqrestore( &( lp->slock ), flags );
    }
    MOD_DEC_USE_COUNT;
    DBG_LEAVE( DbgInfo );
    return 0;
} // wl_close
/*============================================================================*/

/*******************************************************************************
 *	wl_ioctl()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The IOCTL handler for the device.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device struct.
 *      rq  - a pointer to the IOCTL request buffer.
 *      cmd - the IOCTL command code.
 *
 *  RETURNS:
 *
 *      0 on success
 *      errno value otherwise
 *
 ******************************************************************************/
int wl_ioctl( struct net_device *dev, struct ifreq *rq, int cmd )
{
    struct wl_private  *lp = NULL;
    unsigned long           flags;
    int                     ret = 0;
    /*------------------------------------------------------------------------*/

    DBG_FUNC("wl_ioctl");
    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 wl_private *)dev->priv ) != NULL ) {
        spin_lock_irqsave( &( lp->slock ), flags );
        hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_OFF );
#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            /* Handle any RTS IOCTL here */
            if( cmd == WL_IOCTL_RTS ) {
                DBG_TRACE( DbgInfo, "IOCTL: WL_IOCTL_RTS\n" );
                ret = wvlan_rts( (struct rtsreq *)rq, dev->base_addr );
            } else {
                DBG_TRACE( DbgInfo, "IOCTL not supported in RTS mode: 0x%X\n", cmd );
                ret = -EOPNOTSUPP;
            }
            hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_ON );
            spin_unlock_irqrestore( &( lp->slock ), flags );
            DBG_LEAVE( DbgInfo );
            return ret;
        }
#endif  /* USE_RTS */

        /* 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( !wl_wireless_ioctl( dev, rq, cmd, &ret ))
#endif /* WIRELESS_EXT */
            {
#ifdef USE_UIL
                struct uilreq  *urq = (struct uilreq *)rq;
#endif /* USE_UIL */

#ifdef WIRELESS_EXT
                struct iwreq   *wrq = (struct iwreq *)rq;
#endif /* WIRELESS_EXT */

                switch( cmd ) {
                // ================== Private IOCTLs (up to 16) ==================
#ifdef USE_UIL
                case WVLAN2_IOCTL_UIL:
                    DBG_TRACE( DbgInfo, "IOCTL: WVLAN2_IOCTL_UIL\n" );
                    ret = wvlan_uil( urq, lp );
                    break;
#endif  /* USE_UIL */         

#ifdef WIRELESS_EXT
                case SIOCSIWNETNAME:       // set the network name
                    DBG_TRACE( DbgInfo, "IOCTL: SIOCSIWNETNAME\n" );
                    ret = wvlan_set_netname( wrq, lp );
                    break;

                case SIOCGIWNETNAME:       // get the network name
                    DBG_TRACE(DbgInfo, "IOCTL: SIOCGIWNETNAME\n");
                    ret = wvlan_get_netname( wrq, lp );
                    break;

                case SIOCSIWSTANAME:       // set the station name
                    DBG_TRACE(DbgInfo, "IOCTL: SIOCSIWSTANAME\n");
                    ret = wvlan_set_station_nickname( wrq, lp );
                    break;

                case SIOCGIWSTANAME:       // get the station name
                    DBG_TRACE(DbgInfo, "IOCTL: SIOCGIWSTANAME\n");
                    ret = wvlan_get_station_nickname( wrq, lp );
                    break;

#if 1 //;? (HCF_TYPE) & HCF_TYPE_STA
//;?seems reasonable that even an AP-only driver could afford this small additional footprint
                case SIOCSIWPORTTYPE:       // set the port type
                    DBG_TRACE(DbgInfo, "IOCTL: SIOCSIWPORTTYPE\n");
				//;?should we return an error status in AP mode
                    ret = wvlan_set_porttype( wrq, lp );
                    break;

                case SIOCGIWPORTTYPE:       // get the port type
                    DBG_TRACE(DbgInfo, "IOCTL: SIOCGIWPORTTYPE\n");
                    ret = wvlan_get_porttype( wrq, lp );
                    break;
#endif /* HCF_STA */
#endif /* WIRELESS_EXT */

                default:
                    DBG_TRACE(DbgInfo, "IOCTL CODE NOT SUPPORTED: 0x%X\n", cmd );
                    ret = -EOPNOTSUPP;
                    break;
                }
            }
        } else {
            DBG_WARNING( DbgInfo, "DEVICE IS BUSY, CANNOT PROCESS REQUEST\n" );
            ret = -EBUSY;
        }
        hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_ON );
        spin_unlock_irqrestore( &( lp->slock ), flags );
    }

    DBG_LEAVE( DbgInfo );
    return ret;
} // wl_ioctl
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_timeout()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler called when, for some reason, a Tx request is not completed.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device struct.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_tx_timeout( struct net_device *dev )
{
#ifdef USE_WDS
    int                     count;
#endif  /* USE_WDS */
    unsigned long           flags;
    struct wl_private       *lp = NULL;
    struct net_device_stats *pStats = NULL;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_tx_timeout" );
    DBG_ENTER( DbgInfo );

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

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

#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            DBG_TRACE( DbgInfo, "Skipping tx_timeout handler, in RTS mode\n" );
            spin_unlock_irqrestore( &( lp->slock ), flags );
            
            DBG_LEAVE( DbgInfo );
            return;
        }
#endif  /* USE_RTS */

        /* Figure out which device (the "root" device or WDS port) this timeout
           is for */
#ifdef USE_WDS

        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( dev == lp->wds_port[count].dev ) {
                pStats = &( lp->wds_port[count].stats );

                /* Break the loop so that we can use the counter to access WDS
                   information in the private structure */
                break;
            }
        }

#endif  /* USE_WDS */

        /* If pStats is still NULL, then the device is not a WDS port */
        if( pStats == NULL ) {
            pStats = &( lp->stats );
        }

        /* Accumulate the timeout error */
        pStats->tx_errors++;

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

    DBG_LEAVE( DbgInfo );
    return;
} // wl_tx_timeout
/*============================================================================*/

/*******************************************************************************
 *	wl_send()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The routine which performs data transmits.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's wl_private struct.
 *
 *  RETURNS:
 *
 *      0 on success
 *      1 on error
 *
 ******************************************************************************/
int wl_send( struct wl_private *lp )
{

    int                 status;
    DESC_STRCT          *desc;
    WVLAN_LFRAME        *txF = NULL;
    struct list_head    *element;
    int                 len;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_send" );

    if( lp == NULL ) {
        DBG_ERROR( DbgInfo, "Private adapter struct is NULL\n" );
        return FALSE;
    }
    if( lp->dev == NULL ) {
        DBG_ERROR( DbgInfo, "net_device struct in wl_private is NULL\n" );
        return FALSE;
    }

    /* Check for the availability of FIDs; if none are available, don't take any
       frames off the txQ */
    if( lp->hcfCtx.IFB_RscInd == 0 ) {
        return FALSE;
    }

    /* Reclaim the TxQ Elements and place them back on the free queue */
    if( !list_empty( &( lp->txQ[0] ))) {
        element = lp->txQ[0].next;

        txF = (WVLAN_LFRAME * )list_entry( element, WVLAN_LFRAME, node );
        if( txF != NULL ) {
            lp->txF.skb  = txF->frame.skb;
            lp->txF.port = txF->frame.port;

            txF->frame.skb  = NULL;
            txF->frame.port = 0;

            list_del( &( txF->node ));
            list_add( element, &( lp->txFree ));

            lp->txQ_count--;

            if( lp->txQ_count < TX_Q_LOW_WATER_MARK ) {
                if( lp->netif_queue_on == FALSE ) {
                    DBG_TX( DbgInfo, "Kickstarting Q: %d\n", lp->txQ_count );
                    netif_wake_queue( lp->dev );
                    WL_WDS_NETIF_WAKE_QUEUE( lp );
                    lp->netif_queue_on = TRUE;
                }
            }
        }
    }
 
    if( lp->txF.skb == NULL ) {
        return FALSE;
    }

    /* If the device has resources (FIDs) available, then Tx the packet */
    /* Format the TxRequest and send it to the adapter */
    len = lp->txF.skb->len < ETH_ZLEN ? ETH_ZLEN : lp->txF.skb->len;

    desc                    = &( lp->desc_tx );
    desc->buf_addr          = lp->txF.skb->data;
    desc->BUF_CNT           = len;
    desc->next_desc_addr    = NULL;

    status = hcf_send_msg( &( lp->hcfCtx ), desc, lp->txF.port );

    if( status == HCF_SUCCESS ) {
        lp->dev->trans_start = jiffies;

        DBG_TX( DbgInfo, "Transmit...\n" );

        if( lp->txF.port == HCF_PORT_0 ) {
            lp->stats.tx_packets++;
            lp->stats.tx_bytes += lp->txF.skb->len;
        }

#ifdef USE_WDS
        else
        {
            lp->wds_port[(( lp->txF.port >> 8 ) - 1)].stats.tx_packets++;
            lp->wds_port[(( lp->txF.port >> 8 ) - 1)].stats.tx_bytes += lp->txF.skb->len;
        }
        
#endif  /* USE_WDS */

        /* Free the skb and perform queue cleanup, as the buffer was
            transmitted successfully */
        dev_kfree_skb( lp->txF.skb );

        lp->txF.skb = NULL;
        lp->txF.port = 0;
    }

    return TRUE;
} // wl_send
/*============================================================================*/

/*******************************************************************************
 *	wl_tx()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The Tx handler function for the network layer.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff structure containing the data to transfer.
 *      dev - a pointer to the device's net_device structure.
 *
 *  RETURNS:
 *
 *      0 on success
 *      1 on error
 *
 ******************************************************************************/
int wl_tx( struct sk_buff *skb, struct net_device *dev, int port )
{
    unsigned long           flags;
    struct wl_private       *lp = NULL;
    WVLAN_LFRAME            *txF = NULL;
    struct list_head        *element;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_tx" );

    lp = (struct wl_private *)dev->priv;
    if( lp == NULL ) {
        DBG_ERROR( DbgInfo, "Private adapter struct is NULL\n" );
        return 1;
    }
    spin_lock_irqsave( &( lp->slock ), flags );

    if( lp->flags & WVLAN2_UIL_BUSY ) {
        DBG_WARNING( DbgInfo, "UIL has device blocked\n" );
        /* Start dropping packets here??? */
        spin_unlock_irqrestore( &( lp->slock ), flags );
        return 1;
    }

#ifdef USE_RTS
    if( lp->useRTS == 1 ) {
        DBG_PRINT( "RTS: we're getting a Tx...\n" );
        spin_unlock_irqrestore( &( lp->slock ), flags );
        return 1;
    }
#endif  /* USE_RTS */

    if( !lp->use_dma ) {
        /* Get an element from the queue */
        element = lp->txFree.next;
        txF = (WVLAN_LFRAME *)list_entry( element, WVLAN_LFRAME, node );
        if( txF == NULL ) {
            DBG_ERROR( DbgInfo, "Problem with list_entry\n" );
            spin_unlock_irqrestore( &( lp->slock ), flags );
            return 1;
        }
        /* Fill out the frame */
        txF->frame.skb = skb;
        txF->frame.port = port;
        /* Move the frame to the txQ */
        /* NOTE: Here's where we would do priority queueing */
        list_del( &( txF->node ));
        list_add( &( txF->node ), &( lp->txQ[0] ));
        
        lp->txQ_count++;
        if( lp->txQ_count >= DEFAULT_NUM_TX_FRAMES ) {
            DBG_TX( DbgInfo, "Q Full: %d\n", lp->txQ_count );
            if( lp->netif_queue_on == TRUE ) {
                netif_stop_queue( lp->dev );
                WL_WDS_NETIF_STOP_QUEUE( lp );
                lp->netif_queue_on = FALSE;
            }
        }
    }
    hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_OFF ); /* Disable Interrupts */

    /* Send the data to the hardware using the appropriate method */
#ifdef ENABLE_DMA
    if( lp->use_dma ) {
        wl_send_dma( lp, skb, port );
    }
    else
#endif
    {
        wl_send( lp );
    }
    /* Re-enable Interrupts, release the spinlock and return */
    hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_ON );
    spin_unlock_irqrestore( &( lp->slock ), flags );
    return 0;
} // wl_tx
/*============================================================================*/

/*******************************************************************************
 *	wl_rx()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The routine which performs data reception.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure.
 *
 *  RETURNS:
 *
 *      0 on success
 *      1 on error
 *
 ******************************************************************************/
int wl_rx(struct net_device *dev)
{
    int                     port;
    struct sk_buff          *skb;
    struct wl_private       *lp = NULL;
    int                     status;
    hcf_16                  pktlen;
    hcf_16                  hfs_stat;
    DESC_STRCT              *desc;
    /*------------------------------------------------------------------------*/

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

    if((( lp = (struct wl_private *)dev->priv ) != NULL ) &&
          !( lp->flags & WVLAN2_UIL_BUSY )) {

#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            DBG_PRINT( "RTS: We're getting an Rx...\n" );
            return -EIO;
        }
#endif  /* USE_RTS */

        /* Read the HFS_STAT register from the lookahead buffer */
        hfs_stat = (hcf_16)(( lp->lookAheadBuf[HFS_STAT] ) | 
                            ( lp->lookAheadBuf[HFS_STAT + 1] << 8 ));

        /* Make sure the frame isn't bad */
        if(( hfs_stat & HFS_STAT_ERR ) != HCF_SUCCESS ) {
            DBG_WARNING( DbgInfo, "HFS_STAT_ERROR (0x%x) in Rx Packet\n",
                         lp->lookAheadBuf[HFS_STAT] );
            return -EIO;
        }

        /* Determine what port this packet is for */
        port = ( hfs_stat >> 8 ) & 0x0007;
        DBG_RX( DbgInfo, "Rx frame for port %d\n", port );

        if(( pktlen = lp->hcfCtx.IFB_RxLen ) != 0 ) {
            if(( skb = ALLOC_SKB( pktlen )) != NULL ) {
                /* Set the netdev based on the port */
                switch( port ) {
#ifdef USE_WDS 
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                    skb->dev = lp->wds_port[port-1].dev;
                    break;
#endif  /* USE_WDS */

                case 0:
                default:
                    skb->dev = dev;
                    break;
                }

                desc = &( lp->desc_rx );

                desc->next_desc_addr = NULL;

/*
#define BLOCK_INPUT(buf, len) \
    desc->buf_addr = buf; \
    desc->BUF_SIZE = len; \
    status = hcf_rcv_msg(&(lp->hcfCtx), desc, 0)
*/

                GET_PACKET( skb->dev, skb, pktlen );

                if( status == HCF_SUCCESS ) {
                    netif_rx( skb );

                    if( port == 0 ) {
                        lp->stats.rx_packets++;
                        lp->stats.rx_bytes += pktlen;
                    }
#ifdef USE_WDS
                    else
                    {
                        lp->wds_port[port-1].stats.rx_packets++;
                        lp->wds_port[port-1].stats.rx_bytes += pktlen;
                    }
#endif  /* USE_WDS */

                    dev->last_rx = jiffies;

#ifdef WIRELESS_EXT
#ifdef WIRELESS_SPY
                    if( lp->spy_number > 0 ) {
                        char *srcaddr = skb->mac.raw + MAC_ADDR_SIZE;

                        wl_spy_gather( dev, srcaddr );
                    } 
#endif /* WIRELESS_SPY */
#endif /* WIRELESS_EXT */
                } else {
                    DBG_ERROR( DbgInfo, "Rx request to card FAILED\n" );

                    if( port == 0 ) {
                        lp->stats.rx_dropped++;
                    }
#ifdef USE_WDS
                    else
                    {
                        lp->wds_port[port-1].stats.rx_dropped++;
                    }
#endif  /* USE_WDS */

                    dev_kfree_skb( skb );
                }
            } else {
                DBG_ERROR( DbgInfo, "Could not alloc skb\n" );
                
                if( port == 0 ) {
                    lp->stats.rx_dropped++;
                }
#ifdef USE_WDS
                else
                {
                    lp->wds_port[port-1].stats.rx_dropped++;
                }
#endif  /* USE_WDS */
            }
        }
    }

    return 0;
} // wl_rx
/*============================================================================*/

/*******************************************************************************
 *	wl_multicast()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Function to handle multicast packets
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
#ifdef NEW_MULTICAST

void wl_multicast( struct net_device *dev )
{
#if 1 //;? (HCF_TYPE) & HCF_TYPE_STA //;?should we return an error status in AP mode
//;?seems reasonable that even an AP-only driver could afford this small additional footprint

    int                 x;
    struct dev_mc_list  *mclist;
    struct wl_private   *lp = NULL;
    unsigned long       flags;
    /*------------------------------------------------------------------------*/

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

    if( !wl_adapter_is_open( dev )) {
        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 wl_private *)dev->priv ) != NULL ) &&
          !( lp->flags & WVLAN2_UIL_BUSY )) {
        
#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            DBG_TRACE( DbgInfo, "Skipping multicast, in RTS mode\n" );
            
            DBG_LEAVE( DbgInfo );
            return;
        }
#endif  /* USE_RTS */

        spin_lock_irqsave( &( lp->slock ), flags );
        hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_OFF );

		if ( CNV_INT_TO_LITTLE( lp->hcfCtx.IFB_FWIdentity.comp_id ) == COMP_ID_FW_STA  ) {
            if( dev->flags & IFF_PROMISC ) {
                /* Enable promiscuous mode */
                lp->ltvRecord.len       = 2;
                lp->ltvRecord.typ       = CFG_PROMISCUOUS_MODE;
                lp->ltvRecord.u.u16[0]  = CNV_INT_TO_LITTLE( 1 );
                DBG_PRINT( "Enabling Promiscuous mode (IFF_PROMISC)\n" );
                hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord ));
            }
            else if(( dev->mc_count > HCF_MAX_MULTICAST ) ||
                    ( dev->flags & IFF_ALLMULTI )) {
                /* Shutting off this filter will enable all multicast frames to
                   be sent up from the device; however, this is a static RID, so
                   a call to wl_apply() is needed */
                lp->ltvRecord.len       = 2;
                lp->ltvRecord.typ       = CFG_CNF_RX_ALL_GROUP_ADDR;
                lp->ltvRecord.u.u16[0]  = CNV_INT_TO_LITTLE( 0 );
                DBG_PRINT( "Enabling all multicast mode (IFF_ALLMULTI)\n" );
                hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord ));
                wl_apply( lp );
            }
            else if( dev->mc_count != 0 ) {
                /* Set the multicast addresses */
                lp->ltvRecord.len = ( dev->mc_count * 3 ) + 1;
                lp->ltvRecord.typ = 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 );
                }
                DBG_PRINT( "Setting multicast list\n" );
                hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord ));
            } else {
                /* Disable promiscuous mode */
                lp->ltvRecord.len       = 2;
                lp->ltvRecord.typ       = CFG_PROMISCUOUS_MODE;
                lp->ltvRecord.u.u16[0]  = CNV_INT_TO_LITTLE( 0 );
                DBG_PRINT( "Disabling Promiscuous mode\n" );
                hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord ));

                /* Disable multicast mode */
                lp->ltvRecord.len = 1;
                lp->ltvRecord.typ = CFG_GROUP_ADDR;
                DBG_PRINT( "Disabling Multicast mode\n" );
                hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord ));

                /* Turning on this filter will prevent all multicast frames from
                   being sent up from the device; however, this is a static RID,
                   so a call to wl_apply() is needed */
                lp->ltvRecord.len       = 2;
                lp->ltvRecord.typ       = CFG_CNF_RX_ALL_GROUP_ADDR;
                lp->ltvRecord.u.u16[0]  = CNV_INT_TO_LITTLE( 1 );
                DBG_PRINT( "Disabling all multicast mode (IFF_ALLMULTI)\n" );
                hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord ));
                wl_apply( lp );
            }
        }
        hcf_action( &( lp->hcfCtx ), HCF_ACT_INT_ON );
        spin_unlock_irqrestore( &( lp->slock ), flags );
    }
    DBG_LEAVE( DbgInfo );
#endif /* HCF_STA */
} // wl_multicast
/*============================================================================*/

#else /* NEW_MULTICAST */

void wl_multicast( struct net_device *dev, int num_addrs, void *addrs )
{
    DBG_FUNC( "wl_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 );
} // wl_multicast
/*============================================================================*/

#endif /* NEW_MULTICAST */

/*******************************************************************************
 *	wl_device_alloc()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Create instances of net_device and wl_private for the new adapter
 *  and register the device's entry points in the net_device structure.
 *
 *  PARAMETERS:
 *
 *      N/A
 *
 *  RETURNS:
 *
 *      a pointer to an allocated and initialized net_device struct for this
 *      device.
 *
 ******************************************************************************/
struct net_device * wl_device_alloc( void )
{
    struct net_device   *dev = NULL;
    struct wl_private   *lp = NULL;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_device_alloc" );
    DBG_ENTER( DbgInfo );

    /* Alloc a net_device struct */
    dev = kmalloc( sizeof( struct net_device ), GFP_KERNEL );
    memset( dev, 0, sizeof( struct net_device ));

    /* Alloc a wl-specific-data struct */
    dev->priv = kmalloc( sizeof( struct wl_private ), GFP_KERNEL );
    memset( dev->priv, 0, sizeof( struct wl_private ));

    /* Initialize the 'next' pointer in the struct. Currently only used for PCI,
       but do it here just in case it's used for other buses in the future */
    lp = (struct wl_private *)dev->priv;

    if( lp != NULL ) {
        lp->dev_next = NULL;
    }

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

    /* Set MTU */
    dev->mtu = mtu;
    if( dev->mtu > MTU_MAX )
    {
	    DBG_WARNING( DbgInfo, "%s: MTU set too high, limiting to %d.\n",
                        dev->name, MTU_MAX );
    	dev->mtu = MTU_MAX;
    }

    /* Setup the function table in the device structure. */
    dev->hard_start_xmit    = &wl_tx_port0;

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

    dev->set_config         = &wl_config;
    dev->get_stats          = &wl_stats;
    dev->set_multicast_list = &wl_multicast;

#ifdef BUS_PCMCIA
    init_dev_name( dev, ( (struct wl_private *)dev->priv)->node );
#endif  // BUS_PCMCIA

    dev->init               = &wl_init;
    dev->open               = &wl_adapter_open;
    dev->stop               = &wl_adapter_close;
    dev->do_ioctl           = &wl_ioctl;

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

    /* Allocate virutal devices for WDS support if needed */
    WL_WDS_DEVICE_ALLOC( lp );

    DBG_LEAVE( DbgInfo );
    return dev;
} // wl_device_alloc
/*============================================================================*/

/*******************************************************************************
 *	wl_device_dealloc()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Free instances of net_device and wl_private strcutres for an adapter
 *  and perform basic cleanup.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_device_dealloc( struct net_device *dev )
{
    struct wl_private   *lp = NULL;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_device_dealloc" );
    DBG_ENTER( DbgInfo );

    if( dev != NULL ) {
        if(( lp = (struct wl_private *)dev->priv ) != NULL ) {
            /* Dealloc the WDS ports */
            WL_WDS_DEVICE_DEALLOC( lp );

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

            kfree( lp );
        }
        
        kfree( dev );
    }

    DBG_LEAVE( DbgInfo );
    return;
} // wl_device_dealloc
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_port0()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_0.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_0.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port0( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 0\n" );
    return wl_tx( skb, dev, HCF_PORT_0 );
    //return wl_tx_dma( skb, dev, HCF_PORT_0 );
} // wl_tx_port0
/*============================================================================*/

#ifdef USE_WDS

/*******************************************************************************
 *	wl_tx_port1()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_1.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_1.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port1( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 1\n" );
    return wl_tx( skb, dev, HCF_PORT_1 );
} // wl_tx_port1
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_port2()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_2.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_2.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port2( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 2\n" );
    return wl_tx( skb, dev, HCF_PORT_2 );
} // wl_tx_port2
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_port3()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_3.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_3.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port3( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 3\n" );
    return wl_tx( skb, dev, HCF_PORT_3 );
} // wl_tx_port3
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_port4()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_4.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_4.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port4( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 4\n" );
    return wl_tx( skb, dev, HCF_PORT_4 );
} // wl_tx_port4
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_port5()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_5.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_5.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port5( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 5\n" );
    return wl_tx( skb, dev, HCF_PORT_5 );
} // wl_tx_port5
/*============================================================================*/

/*******************************************************************************
 *	wl_tx_port6()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The handler routine for Tx over HCF_PORT_6.
 *
 *  PARAMETERS:
 *
 *      skb - a pointer to the sk_buff to transmit.
 *      dev - a pointer to a net_device structure representing HCF_PORT_6.
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
int wl_tx_port6( struct sk_buff *skb, struct net_device *dev )
{
    DBG_TX( DbgInfo, "Tx on Port 6\n" );
    return wl_tx( skb, dev, HCF_PORT_6 );
} // wl_tx_port6
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_device_alloc()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Create instances of net_device to represent the WDS ports, and register
 *  the device's entry points in the net_device structure.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A, but will place pointers to the allocated and initialized net_device
 *      structs in the private adapter structure.
 *
 ******************************************************************************/
void wl_wds_device_alloc( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_wds_device_alloc" );
    DBG_ENTER( DbgInfo );

    /* WDS support requires additional net_device structs to be allocated,
       so that user space apps can use these virtual devices to specify the
       port on which to Tx/Rx */
    for( count = 0; count < NUM_WDS_PORTS; count++ ) {
        struct net_device *dev_wds = NULL;

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

        ether_setup( dev_wds );

        lp->wds_port[count].dev = dev_wds;

        /* Re-use wl_init for all the devices, as it currently does nothing, but
           is required. Re-use the stats/tx_timeout handler for all as well; the 
           WDS port which is requesting these operations can be determined by 
           the net_device pointer. Set the private member of all devices to point
           to the same net_device struct; that way, all information gets 
           funnelled through the one "real" net_device. Name the WDS ports
           "wds<n>" */
        lp->wds_port[count].dev->init           = &wl_init;
        lp->wds_port[count].dev->get_stats      = &wl_stats;
        lp->wds_port[count].dev->tx_timeout     = &wl_tx_timeout;
        lp->wds_port[count].dev->watchdog_timeo = TX_TIMEOUT;
        lp->wds_port[count].dev->priv           = lp;

        sprintf( lp->wds_port[count].dev->name, "wds%d", count );
    }

    /* Register the Tx handlers */
    lp->wds_port[0].dev->hard_start_xmit = &wl_tx_port1;
    lp->wds_port[1].dev->hard_start_xmit = &wl_tx_port2;
    lp->wds_port[2].dev->hard_start_xmit = &wl_tx_port3;
    lp->wds_port[3].dev->hard_start_xmit = &wl_tx_port4;
    lp->wds_port[4].dev->hard_start_xmit = &wl_tx_port5;
    lp->wds_port[5].dev->hard_start_xmit = &wl_tx_port6;

    WL_WDS_NETIF_STOP_QUEUE( lp );

    DBG_LEAVE( DbgInfo );
    return;
} // wl_wds_device_alloc
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_device_dealloc()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Free instances of net_device structures used to support WDS.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_wds_device_dealloc( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_wds_device_dealloc" );
    DBG_ENTER( DbgInfo );

    for( count = 0; count < NUM_WDS_PORTS; count++ ) {
        struct net_device *dev_wds = NULL;

        dev_wds = lp->wds_port[count].dev;
        
        if( dev_wds != NULL ) {
            if( dev_wds->flags & IFF_UP ) {
                dev_close( dev_wds );
                dev_wds->flags &= ~( IFF_UP | IFF_RUNNING );
            }

            kfree( dev_wds );
            lp->wds_port[count].dev = NULL;
        }
    }

    DBG_LEAVE( DbgInfo );
    return;
} // wl_wds_device_dealloc
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_start_queue()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to start the netif queues of all the "virtual" network devices
 *      which repesent the WDS ports.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_wds_netif_start_queue( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    if( lp != NULL ) {
        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( lp->wds_port[count].is_registered && 
                lp->wds_port[count].netif_queue_on == FALSE ) {
                netif_start_queue( lp->wds_port[count].dev );
                lp->wds_port[count].netif_queue_on = TRUE;
            }
        }
    }

    return;
} // wl_wds_netif_start_queue
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_stop_queue()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to stop the netif queues of all the "virtual" network devices
 *      which repesent the WDS ports.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_wds_netif_stop_queue( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    if( lp != NULL ) {
        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( lp->wds_port[count].is_registered &&
                lp->wds_port[count].netif_queue_on == TRUE ) {
                netif_stop_queue( lp->wds_port[count].dev );
                lp->wds_port[count].netif_queue_on = FALSE;
            }
        }
    }

    return;
} // wl_wds_netif_stop_queue
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_wake_queue()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to wake the netif queues of all the "virtual" network devices
 *      which repesent the WDS ports.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_wds_netif_wake_queue( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    if( lp != NULL ) {
        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( lp->wds_port[count].is_registered &&
                lp->wds_port[count].netif_queue_on == FALSE ) {
                netif_wake_queue( lp->wds_port[count].dev );
                lp->wds_port[count].netif_queue_on = TRUE;
            }
        }
    }

    return;
} // wl_wds_netif_wake_queue
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_carrier_on()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to signal the network layer that carrier is present on all of the 
 *      "virtual" network devices which repesent the WDS ports.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_wds_netif_carrier_on( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    if( lp != NULL ) {
        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( lp->wds_port[count].is_registered ) {
                netif_carrier_on( lp->wds_port[count].dev );
            }
        }
    }

    return;
} // wl_wds_netif_carrier_on
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_carrier_off()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to signal the network layer that carrier is NOT present on all of 
 *      the "virtual" network devices which repesent the WDS ports.
 *
 *  PARAMETERS:
 *
 *      lp  - a pointer to the device's private adapter structure
 *
 *  RETURNS:
 *
 *      N/A
 *
 ******************************************************************************/
void wl_wds_netif_carrier_off( struct wl_private *lp )
{
    int count;
    /*------------------------------------------------------------------------*/

    if( lp != NULL ) {
        for( count = 0; count < NUM_WDS_PORTS; count++ ) {
            if( lp->wds_port[count].is_registered ) {
                netif_carrier_off( lp->wds_port[count].dev );
            }
        }
    }

    return;
} // wl_wds_netif_carrier_off
/*============================================================================*/

#endif  /* USE_WDS */

#ifdef ENABLE_DMA
/*******************************************************************************
 *	wl_send_dma()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The routine which performs data transmits when using busmaster DMA.
 *
 *  PARAMETERS:
 *
 *      lp   - a pointer to the device's wl_private struct.
 *      skb  - a pointer to the network layer's data buffer.
 *      port - the Hermes port on which to transmit.
 *
 *  RETURNS:
 *
 *      0 on success
 *      1 on error
 *
 ******************************************************************************/
int wl_send_dma( struct wl_private *lp, struct sk_buff *skb, int port )
{
    int         len;
    DESC_STRCT *desc = NULL;
    DESC_STRCT *desc_next = NULL;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_send_dma" );

    if( lp == NULL )
    {
        DBG_ERROR( DbgInfo, "Private adapter struct is NULL\n" );
        return FALSE;
    }

    if( lp->dev == NULL )
    {
        DBG_ERROR( DbgInfo, "net_device struct in wl_private is NULL\n" );
        return FALSE;
    }

    /* AGAIN, ALL THE QUEUEING DONE HERE IN I/O MODE IS NOT PERFORMED */

    if( skb == NULL )
    {
        DBG_WARNING (DbgInfo, "Nothing to send.\n");
        return FALSE;
    }

    len = skb->len;

    /* Get a free descriptor */
    desc = wl_pci_dma_get_tx_packet( lp );

    if( desc == NULL )
    {
        if( lp->netif_queue_on == TRUE ) {
            netif_stop_queue( lp->dev );
            WL_WDS_NETIF_STOP_QUEUE( lp );
            lp->netif_queue_on = FALSE;

            dev_kfree_skb( skb );
            return 0;
        }
    }

    SET_BUF_CNT( desc, /*HCF_DMA_FD_CNT*/HFS_ADDR_DEST );
    SET_BUF_SIZE( desc, HCF_DMA_TX_BUF1_SIZE );

    desc_next = desc->next_desc_addr;

    if( desc_next->buf_addr == NULL )
    {
        DBG_ERROR( DbgInfo, "DMA descriptor buf_addr is NULL\n" );
        return FALSE;
    }

    /* Copy the payload into the DMA packet */
    memcpy( desc_next->buf_addr, skb->data, len );

    SET_BUF_CNT( desc_next, len );
    SET_BUF_SIZE( desc_next, HCF_MAX_PACKET_SIZE );
    
    hcf_dma_tx_put( &( lp->hcfCtx ), desc, 0 );

    /* Free the skb and perform queue cleanup, as the buffer was
            transmitted successfully */
    dev_kfree_skb( skb );

    return TRUE;
} // wl_send_dma
/*============================================================================*/

/*******************************************************************************
 *	wl_rx_dma()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      The routine which performs data reception when using busmaster DMA.
 *
 *  PARAMETERS:
 *
 *      dev - a pointer to the device's net_device structure.
 *
 *  RETURNS:
 *
 *      0 on success
 *      1 on error
 *
 ******************************************************************************/
int wl_rx_dma( struct net_device *dev )
{
    int                      port;
    hcf_16                   pktlen;
    hcf_16                   hfs_stat;
    struct sk_buff          *skb;
    struct wl_private       *lp = NULL;
    DESC_STRCT              *desc, *desc_next;
    //CFG_MB_INFO_RANGE2_STRCT x;
    /*------------------------------------------------------------------------*/

    DBG_FUNC("wl_rx")
    DBG_PARAM(DbgInfo, "dev", "%s (0x%p)", dev->name, dev);
    
    if((( lp = (struct wl_private *)dev->priv ) != NULL ) &&
          !( lp->flags & WVLAN2_UIL_BUSY )) {

#ifdef USE_RTS
        if( lp->useRTS == 1 ) {
            DBG_PRINT( "RTS: We're getting an Rx...\n" );
            return -EIO;
        }
#endif  /* USE_RTS */

        //if( lp->dma.status == 0 )
        //{
            desc = hcf_dma_rx_get( &( lp->hcfCtx ));

            if( desc != NULL )
            {
                /* Check and see if we rcvd. a WMP frame */
                /*
                if((( *(hcf_8 *)&desc->buf_addr[HFS_STAT] ) &
                    ( HFS_STAT_MSG_TYPE | HFS_STAT_ERR )) == HFS_STAT_WMP_MSG )
                {
                    DBG_TRACE( DbgInfo, "Got a WMP frame\n" );

                    x.len = sizeof( CFG_MB_INFO_RANGE2_STRCT ) / sizeof( hcf_16 );
				    x.typ = CFG_MB_INFO;
				    x.base_typ = CFG_WMP;
				    x.frag_cnt = 2;
				    x.frag_buf[0].frag_len  = GET_BUF_CNT( descp ) / sizeof( hcf_16 );
				    x.frag_buf[0].frag_addr = (hcf_8 *) descp->buf_addr ;
				    x.frag_buf[1].frag_len  = ( GET_BUF_CNT( descp->next_desc_addr ) + 1 ) / sizeof( hcf_16 );
				    x.frag_buf[1].frag_addr = (hcf_8 *) descp->next_desc_addr->buf_addr ;
				    
                    hcf_put_info( &( lp->hcfCtx ), (LTVP)&x );
                }
                */

                desc_next = desc->next_desc_addr;

                /* Make sure the buffer isn't empty */
                if( GET_BUF_CNT( desc ) == 0 ) {
                    DBG_WARNING( DbgInfo, "Buffer is empty!\n" );
                  
                    /* Give the descriptor back to the HCF */
                    hcf_dma_rx_put( &( lp->hcfCtx ), desc );
                    return -EIO;
                }

                /* Read the HFS_STAT register from the lookahead buffer */
                hfs_stat = (hcf_16)( desc->buf_addr[HFS_STAT/2] );

                /* Make sure the frame isn't bad */
                if(( hfs_stat & HFS_STAT_ERR ) != HCF_SUCCESS )
                {
                    DBG_WARNING( DbgInfo, "HFS_STAT_ERROR (0x%x) in Rx Packet\n",
                                desc->buf_addr[HFS_STAT/2] );
                    
                    /* Give the descriptor back to the HCF */
                    hcf_dma_rx_put( &( lp->hcfCtx ), desc );
                    return -EIO;
                }

                /* Determine what port this packet is for */
                port = ( hfs_stat >> 8 ) & 0x0007;
                DBG_RX( DbgInfo, "Rx frame for port %d\n", port );

                if(( pktlen = GET_BUF_CNT( desc_next )) != 0 ) {
                    if(( skb = ALLOC_SKB( pktlen )) != NULL ) {
                        switch( port ) {
#ifdef USE_WDS
                        case 1:
                        case 2:
                        case 3:
                        case 4:
                        case 5:
                        case 6:
                            skb->dev = lp->wds_port[port-1].dev;
                            break;
#endif  /* USE_WDS */

                        case 0:
                        default:
                            skb->dev = dev;
                            break;
                        }

                        GET_PACKET_DMA( skb->dev, skb, pktlen );

                        /* Give the descriptor back to the HCF */
                        hcf_dma_rx_put( &( lp->hcfCtx ), desc );

                        netif_rx( skb );

                        if( port == 0 ) {
                            lp->stats.rx_packets++;
                            lp->stats.rx_bytes += pktlen;
                        }
#ifdef USE_WDS
                        else
                        {
                            lp->wds_port[port-1].stats.rx_packets++;
                            lp->wds_port[port-1].stats.rx_bytes += pktlen;
                        }
#endif  /* USE_WDS */

                        dev->last_rx = jiffies;

                    } else {
                        DBG_ERROR( DbgInfo, "Could not alloc skb\n" );

                        if( port == 0 )
	                    {
	                        lp->stats.rx_dropped++;
	                    }
#ifdef USE_WDS
                        else
                        {
                            lp->wds_port[port-1].stats.rx_dropped++;
                        }
#endif  /* USE_WDS */
                    }
                }
            }
        //}
    }

    return 0;
} // wl_rx_dma
/*============================================================================*/
#endif  // ENABLE_DMA
