Kernel  |  3.10

下载     查看原文件
C++程序  |  2019行  |  59.71 KB
/*******************************************************************************
 * 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.
 *
 ******************************************************************************/

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

#include <linux/module.h>
#include <linux/slab.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/bitops.h>

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

#include <debug.h>

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

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

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

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

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


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


#if HCF_ENCAP
#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 = wl_priv(dev);
    /*------------------------------------------------------------------------*/

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

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

    /* Nothing to do, but grab the spinlock anyway just in case we ever need
       this routine */
//  wl_lock( lp, &flags );
//  wl_unlock( lp, &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 = wl_priv(dev);
    /*------------------------------------------------------------------------*/

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

    pStats = NULL;

    wl_lock( lp, &flags );

#ifdef USE_RTS
    if( lp->useRTS == 1 ) {
	wl_unlock( lp, &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 );
    }

    wl_unlock( lp, &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 = wl_priv(dev);
    unsigned long       flags;
    /*------------------------------------------------------------------------*/

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

    wl_lock( lp, &flags );

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

#ifdef USE_PROFILE
    parse_config( dev );
#endif

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

    // Holding the lock too long, make a gap to allow other processes
    wl_unlock(lp, &flags);
    wl_lock( lp, &flags );

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

    // Holding the lock too long, make a gap to allow other processes
    wl_unlock(lp, &flags);
    wl_lock( lp, &flags );

    if( status != HCF_SUCCESS ) {
	// Unsuccessful, try reset of the card to recover
	status = wl_reset( dev );
    }

    // Holding the lock too long, make a gap to allow other processes
    wl_unlock(lp, &flags);
    wl_lock( lp, &flags );

    if( status == HCF_SUCCESS ) {
	netif_carrier_on( dev );
	WL_WDS_NETIF_CARRIER_ON( lp );

	lp->is_handling_int = WL_HANDLING_INT; // Start handling interrupts
        wl_act_int_on( lp );

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

    wl_unlock( lp, &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 = wl_priv(dev);
    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
    */

    wl_lock( lp, &flags );

    wl_act_int_off( lp );
    lp->is_handling_int = WL_NOT_HANDLING_INT; // Stop handling interrupts

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

    /* Disable the ports */
    wl_disable( lp );

    wl_unlock( lp, &flags );

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

static void wl_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
{
    strlcpy(info->driver, DRIVER_NAME, sizeof(info->driver));
    strlcpy(info->version, DRV_VERSION_STR, sizeof(info->version));
//	strlcpy(info.fw_version, priv->fw_name,
//	sizeof(info.fw_version));

    if (dev->dev.parent) {
    	dev_set_name(dev->dev.parent, "%s", info->bus_info);
	//strlcpy(info->bus_info, dev->dev.parent->bus_id,
	//	sizeof(info->bus_info));
    } else {
	snprintf(info->bus_info, sizeof(info->bus_info),
		"PCMCIA FIXME");
//		    "PCMCIA 0x%lx", priv->hw.iobase);
    }
} // wl_get_drvinfo

static struct ethtool_ops wl_ethtool_ops = {
    .get_drvinfo = wl_get_drvinfo,
    .get_link = ethtool_op_get_link,
};


/*******************************************************************************
 *	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 = wl_priv(dev);
    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);

    wl_lock( lp, &flags );

    wl_act_int_off( lp );

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

	goto out_act_int_on_unlock;
    }
#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 USE_UIL
	struct uilreq  *urq = (struct uilreq *)rq;
#endif /* USE_UIL */

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

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

#ifdef USE_RTS
out_act_int_on_unlock:
#endif  /* USE_RTS */
    wl_act_int_on( lp );

    wl_unlock( lp, &flags );

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

#ifdef CONFIG_NET_POLL_CONTROLLER
void wl_poll(struct net_device *dev)
{
    struct wl_private *lp = wl_priv(dev);
    unsigned long flags;
    struct pt_regs regs;

    wl_lock( lp, &flags );
    wl_isr(dev->irq, dev, &regs);
    wl_unlock( lp, &flags );
}
#endif

/*******************************************************************************
 *	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 = wl_priv(dev);
    struct net_device_stats *pStats = NULL;
    /*------------------------------------------------------------------------*/

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

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

    wl_lock( lp, &flags );

#ifdef USE_RTS
    if( lp->useRTS == 1 ) {
	DBG_TRACE( DbgInfo, "Skipping tx_timeout handler, in RTS mode\n" );
	wl_unlock( lp, &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++;

    wl_unlock( lp, &flags );

    DBG_LEAVE( DbgInfo );
} // 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 = wl_priv(dev);
    WVLAN_LFRAME            *txF = NULL;
    struct list_head        *element;
    /*------------------------------------------------------------------------*/

    DBG_FUNC( "wl_tx" );

    /* Grab the spinlock */
    wl_lock( lp, &flags );

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

#ifdef USE_RTS
    if( lp->useRTS == 1 ) {
        DBG_PRINT( "RTS: we're getting a Tx...\n" );
	wl_unlock( lp, &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" );
	    wl_unlock( lp, &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_move(&(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;
            }
        }
    }
    wl_act_int_off( lp ); /* 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 */
    wl_act_int_on( lp );
    wl_unlock( lp, &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 = wl_priv(dev);
    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->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 );

        pktlen = lp->hcfCtx.IFB_RxLen;
        if (pktlen != 0) {
            skb = ALLOC_SKB(pktlen);
            if (skb != 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->spydata.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 netdev_hw_addr *ha;
    struct wl_private   *lp = wl_priv(dev);
    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 ) ? "Promiscuous " : "",
            ( dev->flags & IFF_MULTICAST ) ? "Multicast " : "",
            ( dev->flags & IFF_ALLMULTI ) ? "All-Multicast" : "" );

        DBG_PRINT( "  mc_count: %d\n", netdev_mc_count(dev));

	netdev_for_each_mc_addr(ha, dev)
	DBG_PRINT("    %pM (%d)\n", ha->addr, dev->addr_len);
    }
#endif /* DBG */

    if(!( 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 */

        wl_lock( lp, &flags );
        wl_act_int_off( lp );

		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 ((netdev_mc_count(dev) > 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 (!netdev_mc_empty(dev)) {
                /* Set the multicast addresses */
                lp->ltvRecord.len = ( netdev_mc_count(dev) * 3 ) + 1;
                lp->ltvRecord.typ = CFG_GROUP_ADDR;

		x = 0;
		netdev_for_each_mc_addr(ha, dev)
                    memcpy(&(lp->ltvRecord.u.u8[x++ * ETH_ALEN]),
			   ha->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 = 2;
                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 );
            }
        }
        wl_act_int_on( lp );
	wl_unlock( lp, &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 */

static const struct net_device_ops wl_netdev_ops =
{
    .ndo_start_xmit         = &wl_tx_port0,

    .ndo_set_config         = &wl_config,
    .ndo_get_stats          = &wl_stats,
    .ndo_set_rx_mode        = &wl_multicast,

    .ndo_init               = &wl_insert,
    .ndo_open               = &wl_adapter_open,
    .ndo_stop               = &wl_adapter_close,
    .ndo_do_ioctl           = &wl_ioctl,

    .ndo_tx_timeout         = &wl_tx_timeout,

#ifdef CONFIG_NET_POLL_CONTROLLER
    .ndo_poll_controller    = wl_poll,
#endif
};

/*******************************************************************************
 *	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 = alloc_etherdev(sizeof(struct wl_private));
    if (!dev)
        return NULL;

    /* 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 = wl_priv(dev);


    /* Check 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->wireless_handlers = (struct iw_handler_def *)&wl_iw_handler_def;
    lp->wireless_data.spy_data = &lp->spy_data;
    dev->wireless_data = &lp->wireless_data;

    dev->netdev_ops = &wl_netdev_ops;

    dev->watchdog_timeo     = TX_TIMEOUT;

    dev->ethtool_ops	    = &wl_ethtool_ops;

    netif_stop_queue( dev );

    /* Allocate virtual 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 = wl_priv(dev);
    /*------------------------------------------------------------------------*/

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

    /* Dealloc the WDS ports */
    WL_WDS_DEVICE_DEALLOC( lp );

    free_netdev( dev );

    DBG_LEAVE( DbgInfo );
} // 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 );
#ifdef ENABLE_DMA
    return wl_tx_dma( skb, dev, HCF_PORT_0 );
#endif
} // 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 = kzalloc(sizeof(struct net_device), GFP_KERNEL);
	if (!dev_wds) {
		DBG_LEAVE(DbgInfo);
		return;
	}

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

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

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

/*******************************************************************************
 *	wl_wds_netif_start_queue()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to start the netif queues of all the "virtual" network devices
 *      which represent 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;
            }
        }
    }
} // wl_wds_netif_start_queue
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_stop_queue()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to stop the netif queues of all the "virtual" network devices
 *      which represent 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;
            }
        }
    }
} // wl_wds_netif_stop_queue
/*============================================================================*/

/*******************************************************************************
 *	wl_wds_netif_wake_queue()
 *******************************************************************************
 *
 *  DESCRIPTION:
 *
 *      Used to wake the netif queues of all the "virtual" network devices
 *      which represent 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;
            }
        }
    }
} // 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 represent 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 );
            }
        }
    }
} // 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 represent 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);
		}
	}

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

                pktlen = GET_BUF_CNT(desc_next);
                if (pktlen != 0) {
                    skb = ALLOC_SKB(pktlen);
                    if (skb != 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