/*
* Copyright (C) 2002 Intersil Americas Inc.
* Copyright (C) 2004 Aurelien Alleaume <[email protected]>
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/gfp.h>
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/if_arp.h>
#include <asm/byteorder.h>
#include "prismcompat.h"
#include "isl_38xx.h"
#include "islpci_eth.h"
#include "islpci_mgt.h"
#include "oid_mgt.h"
/******************************************************************************
Network Interface functions
******************************************************************************/
void
islpci_eth_cleanup_transmit(islpci_private *priv,
isl38xx_control_block *control_block)
{
struct sk_buff *skb;
u32 index;
/* compare the control block read pointer with the free pointer */
while (priv->free_data_tx !=
le32_to_cpu(control_block->
device_curr_frag[ISL38XX_CB_TX_DATA_LQ])) {
/* read the index of the first fragment to be freed */
index = priv->free_data_tx % ISL38XX_CB_TX_QSIZE;
/* check for holes in the arrays caused by multi fragment frames
* searching for the last fragment of a frame */
if (priv->pci_map_tx_address[index]) {
/* entry is the last fragment of a frame
* free the skb structure and unmap pci memory */
skb = priv->data_low_tx[index];
#if VERBOSE > SHOW_ERROR_MESSAGES
DEBUG(SHOW_TRACING,
"cleanup skb %p skb->data %p skb->len %u truesize %u\n ",
skb, skb->data, skb->len, skb->truesize);
#endif
pci_unmap_single(priv->pdev,
priv->pci_map_tx_address[index],
skb->len, PCI_DMA_TODEVICE);
dev_kfree_skb_irq(skb);
skb = NULL;
}
/* increment the free data low queue pointer */
priv->free_data_tx++;
}
}
netdev_tx_t
islpci_eth_transmit(struct sk_buff *skb, struct net_device *ndev)
{
islpci_private *priv = netdev_priv(ndev);
isl38xx_control_block *cb = priv->control_block;
u32 index;
dma_addr_t pci_map_address;
int frame_size;
isl38xx_fragment *fragment;
int offset;
struct sk_buff *newskb;
int newskb_offset;
unsigned long flags;
unsigned char wds_mac[6];
u32 curr_frag;
#if VERBOSE > SHOW_ERROR_MESSAGES
DEBUG(SHOW_FUNCTION_CALLS, "islpci_eth_transmit\n");
#endif
/* lock the driver code */
spin_lock_irqsave(&priv->slock, flags);
/* check whether the destination queue has enough fragments for the frame */
curr_frag = le32_to_cpu(cb->driver_curr_frag[ISL38XX_CB_TX_DATA_LQ]);
if (unlikely(curr_frag - priv->free_data_tx >= ISL38XX_CB_TX_QSIZE)) {
printk(KERN_ERR "%s: transmit device queue full when awake\n",
ndev->name);
netif_stop_queue(ndev);
/* trigger the device */
isl38xx_w32_flush(priv->device_base, ISL38XX_DEV_INT_UPDATE,
ISL38XX_DEV_INT_REG);
udelay(ISL38XX_WRITEIO_DELAY);
goto drop_free;
}
/* Check alignment and WDS frame formatting. The start of the packet should
* be aligned on a 4-byte boundary. If WDS is enabled add another 6 bytes
* and add WDS address information */
if (likely(((long) skb->data & 0x03) | init_wds)) {
/* get the number of bytes to add and re-align */
offset = (4 - (long) skb->data) & 0x03;
offset += init_wds ? 6 : 0;
/* check whether the current skb can be used */
if (!skb_cloned(skb) && (skb_tailroom(skb) >= offset)) {
unsigned char *src = skb->data;
#if VERBOSE > SHOW_ERROR_MESSAGES
DEBUG(SHOW_TRACING, "skb offset %i wds %i\n", offset,
init_wds);
#endif
/* align the buffer on 4-byte boundary */
skb_reserve(skb, (4 - (long) skb->data) & 0x03);
if (init_wds) {
/* wds requires an additional address field of 6 bytes */
skb_put(skb, 6);
#ifdef ISLPCI_ETH_DEBUG
printk("islpci_eth_transmit:wds_mac\n");
#endif
memmove(skb->data + 6, src, skb->len);
skb_copy_to_linear_data(skb, wds_mac, 6);
} else {
memmove(skb->data, src, skb->len);
}
#if VERBOSE > SHOW_ERROR_MESSAGES
DEBUG(SHOW_TRACING, "memmove %p %p %i\n", skb->data,
src, skb->len);
#endif
} else {
newskb =
dev_alloc_skb(init_wds ? skb->len + 6 : skb->len);
if (unlikely(newskb == NULL)) {
printk(KERN_ERR "%s: Cannot allocate skb\n",
ndev->name);
goto drop_free;
}
newskb_offset = (4 - (long) newskb->data) & 0x03;
/* Check if newskb->data is aligned */
if (newskb_offset)
skb_reserve(newskb, newskb_offset);
skb_put(newskb, init_wds ? skb->len + 6 : skb->len);
if (init_wds) {
skb_copy_from_linear_data(skb,
newskb->data + 6,
skb->len);
skb_copy_to_linear_data(newskb, wds_mac, 6);
#ifdef ISLPCI_ETH_DEBUG
printk("islpci_eth_transmit:wds_mac\n");
#endif
} else
skb_copy_from_linear_data(skb, newskb->data,
skb->len);
#if VERBOSE > SHOW_ERROR_MESSAGES
DEBUG(SHOW_TRACING, "memcpy %p %p %i wds %i\n",
newskb->data, skb->data, skb->len, init_wds);
#endif
newskb->dev = skb->dev;
dev_kfree_skb_irq(skb);
skb = newskb;
}
}
/* display the buffer contents for debugging */
#if VERBOSE > SHOW_ERROR_MESSAGES
DEBUG(SHOW_BUFFER_CONTENTS, "\ntx %p ", skb->data);
display_buffer((char *) skb->data, skb->len);
#endif
/* map the skb buffer to pci memory for DMA operation */
pci_map_address = pci_map_single(priv->pdev,
(void *) skb->data, skb->len,
PCI_DMA_TODEVICE);
if (unlikely(pci_map_address == 0)) {
printk(KERN_WARNING "%s: cannot map buffer to PCI\n",
ndev->name);
goto drop_free;
}
/* Place the fragment in the control block structure. */
index = curr_frag % ISL38XX_CB_TX_QSIZE;
fragment = &cb->tx_data_low[index];
priv->pci_map_tx_address[index] = pci_map_address;
/* store the skb address for future freeing */
priv->data_low_tx[index] = skb;
/* set the proper fragment start address and size information */
frame_size = skb->len;
fragment->size = cpu_to_le16(frame_size);
fragment->flags = cpu_to_le16(0); /* set to 1 if more fragments */
fragment->address = cpu_to_le32(pci_map_address);
curr_frag++;
/* The fragment address in the control block must have been
* written before announcing the frame buffer to device. */
wmb();
cb->driver_curr_frag[ISL38XX_CB_TX_DATA_LQ] = cpu_to_le32(curr_frag);
if (curr_frag - priv->free_data_tx + ISL38XX_MIN_QTHRESHOLD
> ISL38XX_CB_TX_QSIZE) {
/* stop sends from upper layers */
netif_stop_queue(ndev);
/* set the full flag for the transmission queue */
priv->data_low_tx_full = 1;
}
ndev->stats.tx_packets++;
ndev->stats.tx_bytes += skb->len;
/* trigger the device */
islpci_trigger(priv);
/* unlock the driver code */
spin_unlock_irqrestore(&priv->slock, flags);
return NETDEV_TX_OK;
drop_free:
ndev->stats.tx_dropped++;
spin_unlock_irqrestore(&priv->slock, flags);
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static inline int
islpci_monitor_rx(islpci_private *priv, struct sk_buff **skb)
{
/* The card reports full 802.11 packets but with a 20 bytes
* header and without the FCS. But there a is a bit that
* indicates if the packet is corrupted :-) */
struct rfmon_header *hdr = (struct rfmon_header *) (*skb)->data;
if (hdr->flags & 0x01)
/* This one is bad. Drop it ! */
return -1;
if (priv->ndev->type == ARPHRD_IEEE80211_PRISM) {
struct avs_80211_1_header *avs;
/* extract the relevant data from the header */
u32