2006-10-21 15:33:03 -04:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2003 - 2006 NetXen, Inc.
|
|
|
|
* All rights reserved.
|
2006-12-04 12:18:00 -05:00
|
|
|
*
|
2006-10-21 15:33:03 -04:00
|
|
|
* 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, or (at your option) any later version.
|
2006-12-04 12:18:00 -05:00
|
|
|
*
|
2006-10-21 15:33:03 -04:00
|
|
|
* 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.
|
2006-12-04 12:18:00 -05:00
|
|
|
*
|
2006-10-21 15:33:03 -04:00
|
|
|
* 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.
|
2006-12-04 12:18:00 -05:00
|
|
|
*
|
2006-10-21 15:33:03 -04:00
|
|
|
* The full GNU General Public License is included in this distribution
|
|
|
|
* in the file called LICENSE.
|
2006-12-04 12:18:00 -05:00
|
|
|
*
|
2006-10-21 15:33:03 -04:00
|
|
|
* Contact Information:
|
|
|
|
* info@netxen.com
|
|
|
|
* NetXen,
|
|
|
|
* 3965 Freedom Circle, Fourth floor,
|
|
|
|
* Santa Clara, CA 95054
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Main source file for NetXen NIC Driver on Linux
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
2006-11-29 12:00:10 -05:00
|
|
|
#include <linux/vmalloc.h>
|
2006-12-04 12:23:25 -05:00
|
|
|
#include <linux/highmem.h>
|
2006-10-21 15:33:03 -04:00
|
|
|
#include "netxen_nic_hw.h"
|
|
|
|
|
|
|
|
#include "netxen_nic.h"
|
|
|
|
#include "netxen_nic_phan_reg.h"
|
|
|
|
|
2006-11-07 05:12:16 -05:00
|
|
|
#include <linux/dma-mapping.h>
|
2007-03-12 19:09:15 -04:00
|
|
|
#include <net/ip.h>
|
2006-11-07 05:12:16 -05:00
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
MODULE_DESCRIPTION("NetXen Multi port (1/10) Gigabit Network Driver");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_VERSION(NETXEN_NIC_LINUX_VERSIONID);
|
|
|
|
|
2007-07-19 05:11:09 -04:00
|
|
|
char netxen_nic_driver_name[] = "netxen_nic";
|
2006-10-21 15:33:03 -04:00
|
|
|
static char netxen_nic_driver_string[] = "NetXen Network Driver version "
|
2006-11-29 12:00:10 -05:00
|
|
|
NETXEN_NIC_LINUX_VERSIONID;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
#define NETXEN_NETDEV_WEIGHT 120
|
|
|
|
#define NETXEN_ADAPTER_UP_MAGIC 777
|
2006-11-29 12:00:10 -05:00
|
|
|
#define NETXEN_NIC_PEG_TUNE 0
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
/* Local functions to NetXen NIC driver */
|
|
|
|
static int __devinit netxen_nic_probe(struct pci_dev *pdev,
|
|
|
|
const struct pci_device_id *ent);
|
|
|
|
static void __devexit netxen_nic_remove(struct pci_dev *pdev);
|
|
|
|
static int netxen_nic_open(struct net_device *netdev);
|
|
|
|
static int netxen_nic_close(struct net_device *netdev);
|
|
|
|
static int netxen_nic_xmit_frame(struct sk_buff *, struct net_device *);
|
|
|
|
static void netxen_tx_timeout(struct net_device *netdev);
|
2006-12-05 14:36:26 -05:00
|
|
|
static void netxen_tx_timeout_task(struct work_struct *work);
|
2006-10-21 15:33:03 -04:00
|
|
|
static void netxen_watchdog(unsigned long);
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
static int netxen_nic_poll(struct napi_struct *napi, int budget);
|
2006-10-21 15:33:03 -04:00
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
static void netxen_nic_poll_controller(struct net_device *netdev);
|
|
|
|
#endif
|
2006-11-07 05:12:16 -05:00
|
|
|
static irqreturn_t netxen_intr(int irq, void *data);
|
2008-03-17 22:59:49 -04:00
|
|
|
static irqreturn_t netxen_msi_intr(int irq, void *data);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
int physical_port[] = {0, 1, 2, 3};
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
/* PCI Device ID Table */
|
|
|
|
static struct pci_device_id netxen_pci_tbl[] __devinitdata = {
|
|
|
|
{PCI_DEVICE(0x4040, 0x0001)},
|
|
|
|
{PCI_DEVICE(0x4040, 0x0002)},
|
|
|
|
{PCI_DEVICE(0x4040, 0x0003)},
|
|
|
|
{PCI_DEVICE(0x4040, 0x0004)},
|
|
|
|
{PCI_DEVICE(0x4040, 0x0005)},
|
2006-12-18 08:50:59 -05:00
|
|
|
{PCI_DEVICE(0x4040, 0x0024)},
|
|
|
|
{PCI_DEVICE(0x4040, 0x0025)},
|
2006-10-21 15:33:03 -04:00
|
|
|
{0,}
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(pci, netxen_pci_tbl);
|
|
|
|
|
2008-03-30 19:22:18 -04:00
|
|
|
/*
|
|
|
|
* In netxen_nic_down(), we must wait for any pending callback requests into
|
|
|
|
* netxen_watchdog_task() to complete; eg otherwise the watchdog_timer could be
|
|
|
|
* reenabled right after it is deleted in netxen_nic_down().
|
|
|
|
* FLUSH_SCHEDULED_WORK() does this synchronization.
|
|
|
|
*
|
|
|
|
* Normally, schedule_work()/flush_scheduled_work() could have worked, but
|
|
|
|
* netxen_nic_close() is invoked with kernel rtnl lock held. netif_carrier_off()
|
|
|
|
* call in netxen_nic_close() triggers a schedule_work(&linkwatch_work), and a
|
|
|
|
* subsequent call to flush_scheduled_work() in netxen_nic_down() would cause
|
|
|
|
* linkwatch_event() to be executed which also attempts to acquire the rtnl
|
|
|
|
* lock thus causing a deadlock.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct workqueue_struct *netxen_workq;
|
|
|
|
#define SCHEDULE_WORK(tp) queue_work(netxen_workq, tp)
|
|
|
|
#define FLUSH_SCHEDULED_WORK() flush_workqueue(netxen_workq)
|
|
|
|
|
2006-12-04 12:23:25 -05:00
|
|
|
static void netxen_watchdog(unsigned long);
|
|
|
|
|
2007-11-05 12:07:31 -05:00
|
|
|
static void netxen_nic_update_cmd_producer(struct netxen_adapter *adapter,
|
|
|
|
uint32_t crb_producer)
|
2007-04-20 10:52:37 -04:00
|
|
|
{
|
|
|
|
switch (adapter->portnum) {
|
|
|
|
case 0:
|
|
|
|
writel(crb_producer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_PRODUCER_OFFSET));
|
|
|
|
return;
|
|
|
|
case 1:
|
|
|
|
writel(crb_producer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_PRODUCER_OFFSET_1));
|
|
|
|
return;
|
|
|
|
case 2:
|
|
|
|
writel(crb_producer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_PRODUCER_OFFSET_2));
|
|
|
|
return;
|
|
|
|
case 3:
|
|
|
|
writel(crb_producer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_PRODUCER_OFFSET_3));
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
printk(KERN_WARNING "We tried to update "
|
|
|
|
"CRB_CMD_PRODUCER_OFFSET for invalid "
|
|
|
|
"PCI function id %d\n",
|
|
|
|
adapter->portnum);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-11-05 12:07:31 -05:00
|
|
|
static void netxen_nic_update_cmd_consumer(struct netxen_adapter *adapter,
|
|
|
|
u32 crb_consumer)
|
2007-04-20 10:52:37 -04:00
|
|
|
{
|
|
|
|
switch (adapter->portnum) {
|
|
|
|
case 0:
|
|
|
|
writel(crb_consumer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_CONSUMER_OFFSET));
|
|
|
|
return;
|
|
|
|
case 1:
|
|
|
|
writel(crb_consumer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_CONSUMER_OFFSET_1));
|
|
|
|
return;
|
|
|
|
case 2:
|
|
|
|
writel(crb_consumer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_CONSUMER_OFFSET_2));
|
|
|
|
return;
|
|
|
|
case 3:
|
|
|
|
writel(crb_consumer, NETXEN_CRB_NORMALIZE
|
|
|
|
(adapter, CRB_CMD_CONSUMER_OFFSET_3));
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
printk(KERN_WARNING "We tried to update "
|
|
|
|
"CRB_CMD_PRODUCER_OFFSET for invalid "
|
|
|
|
"PCI function id %d\n",
|
|
|
|
adapter->portnum);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define ADAPTER_LIST_SIZE 12
|
|
|
|
|
2008-03-17 22:59:48 -04:00
|
|
|
static uint32_t msi_tgt_status[4] = {
|
|
|
|
ISR_INT_TARGET_STATUS, ISR_INT_TARGET_STATUS_F1,
|
|
|
|
ISR_INT_TARGET_STATUS_F2, ISR_INT_TARGET_STATUS_F3
|
|
|
|
};
|
|
|
|
|
|
|
|
static uint32_t sw_int_mask[4] = {
|
|
|
|
CRB_SW_INT_MASK_0, CRB_SW_INT_MASK_1,
|
|
|
|
CRB_SW_INT_MASK_2, CRB_SW_INT_MASK_3
|
|
|
|
};
|
|
|
|
|
2007-07-05 19:13:55 -04:00
|
|
|
static void netxen_nic_disable_int(struct netxen_adapter *adapter)
|
|
|
|
{
|
2008-03-17 22:59:48 -04:00
|
|
|
u32 mask = 0x7ff;
|
2007-07-05 19:13:55 -04:00
|
|
|
int retries = 32;
|
2008-03-17 22:59:48 -04:00
|
|
|
int port = adapter->portnum;
|
|
|
|
int pci_fn = adapter->ahw.pci_func;
|
2007-07-05 19:13:55 -04:00
|
|
|
|
2008-03-25 23:53:24 -04:00
|
|
|
if (adapter->msi_mode != MSI_MODE_MULTIFUNC)
|
2008-03-17 22:59:48 -04:00
|
|
|
writel(0x0, NETXEN_CRB_NORMALIZE(adapter, sw_int_mask[port]));
|
2007-07-05 19:13:55 -04:00
|
|
|
|
|
|
|
if (adapter->intr_scheme != -1 &&
|
|
|
|
adapter->intr_scheme != INTR_SCHEME_PERPORT)
|
|
|
|
writel(mask,PCI_OFFSET_SECOND_RANGE(adapter, ISR_INT_MASK));
|
|
|
|
|
|
|
|
if (!(adapter->flags & NETXEN_NIC_MSI_ENABLED)) {
|
|
|
|
do {
|
|
|
|
writel(0xffffffff,
|
|
|
|
PCI_OFFSET_SECOND_RANGE(adapter, ISR_INT_TARGET_STATUS));
|
|
|
|
mask = readl(pci_base_offset(adapter, ISR_INT_VECTOR));
|
|
|
|
if (!(mask & 0x80))
|
|
|
|
break;
|
|
|
|
udelay(10);
|
|
|
|
} while (--retries);
|
|
|
|
|
|
|
|
if (!retries) {
|
|
|
|
printk(KERN_NOTICE "%s: Failed to disable interrupt completely\n",
|
|
|
|
netxen_nic_driver_name);
|
|
|
|
}
|
2008-03-17 22:59:48 -04:00
|
|
|
} else {
|
|
|
|
if (adapter->msi_mode == MSI_MODE_MULTIFUNC) {
|
|
|
|
writel(0xffffffff, PCI_OFFSET_SECOND_RANGE(adapter,
|
|
|
|
msi_tgt_status[pci_fn]));
|
|
|
|
}
|
2007-07-05 19:13:55 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void netxen_nic_enable_int(struct netxen_adapter *adapter)
|
|
|
|
{
|
|
|
|
u32 mask;
|
2008-03-17 22:59:48 -04:00
|
|
|
int port = adapter->portnum;
|
2007-07-05 19:13:55 -04:00
|
|
|
|
|
|
|
DPRINTK(1, INFO, "Entered ISR Enable \n");
|
|
|
|
|
|
|
|
if (adapter->intr_scheme != -1 &&
|
|
|
|
adapter->intr_scheme != INTR_SCHEME_PERPORT) {
|
|
|
|
switch (adapter->ahw.board_type) {
|
|
|
|
case NETXEN_NIC_GBE:
|
|
|
|
mask = 0x77b;
|
|
|
|
break;
|
|
|
|
case NETXEN_NIC_XGBE:
|
|
|
|
mask = 0x77f;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
mask = 0x7ff;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
writel(mask, PCI_OFFSET_SECOND_RANGE(adapter, ISR_INT_MASK));
|
|
|
|
}
|
|
|
|
|
2008-03-17 22:59:48 -04:00
|
|
|
writel(0x1, NETXEN_CRB_NORMALIZE(adapter, sw_int_mask[port]));
|
2007-07-05 19:13:55 -04:00
|
|
|
|
|
|
|
if (!(adapter->flags & NETXEN_NIC_MSI_ENABLED)) {
|
|
|
|
mask = 0xbff;
|
|
|
|
if (adapter->intr_scheme != -1 &&
|
|
|
|
adapter->intr_scheme != INTR_SCHEME_PERPORT) {
|
|
|
|
writel(0X0, NETXEN_CRB_NORMALIZE(adapter, CRB_INT_VECTOR));
|
|
|
|
}
|
|
|
|
writel(mask,
|
|
|
|
PCI_OFFSET_SECOND_RANGE(adapter, ISR_INT_TARGET_MASK));
|
|
|
|
}
|
|
|
|
|
|
|
|
DPRINTK(1, INFO, "Done with enable Int\n");
|
|
|
|
}
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
/*
|
|
|
|
* netxen_nic_probe()
|
|
|
|
*
|
|
|
|
* The Linux system will invoke this after identifying the vendor ID and
|
|
|
|
* device Id in the pci_tbl supported by this module.
|
|
|
|
*
|
|
|
|
* A quad port card has one operational PCI config space, (function 0),
|
|
|
|
* which is used to access all four ports.
|
|
|
|
*
|
|
|
|
* This routine will initialize the adapter, and setup the global parameters
|
|
|
|
* along with the port's specific structure.
|
|
|
|
*/
|
|
|
|
static int __devinit
|
|
|
|
netxen_nic_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|
|
|
{
|
|
|
|
struct net_device *netdev = NULL;
|
|
|
|
struct netxen_adapter *adapter = NULL;
|
2006-12-04 12:23:25 -05:00
|
|
|
void __iomem *mem_ptr0 = NULL;
|
|
|
|
void __iomem *mem_ptr1 = NULL;
|
|
|
|
void __iomem *mem_ptr2 = NULL;
|
2007-04-20 10:55:26 -04:00
|
|
|
unsigned long first_page_group_end;
|
|
|
|
unsigned long first_page_group_start;
|
|
|
|
|
2006-11-29 12:00:10 -05:00
|
|
|
|
2007-01-02 05:39:10 -05:00
|
|
|
u8 __iomem *db_ptr = NULL;
|
2006-12-04 12:23:25 -05:00
|
|
|
unsigned long mem_base, mem_len, db_base, db_len;
|
2007-04-20 10:52:37 -04:00
|
|
|
int pci_using_dac, i = 0, err;
|
2006-10-21 15:33:03 -04:00
|
|
|
int ring;
|
|
|
|
struct netxen_recv_context *recv_ctx = NULL;
|
|
|
|
struct netxen_rcv_desc_ctx *rcv_desc = NULL;
|
|
|
|
struct netxen_cmd_buffer *cmd_buf_arr = NULL;
|
2007-12-22 14:44:00 -05:00
|
|
|
__le64 mac_addr[FLASH_NUM_PORTS + 1];
|
2007-04-20 10:55:26 -04:00
|
|
|
int valid_mac = 0;
|
|
|
|
u32 val;
|
2007-04-20 10:52:37 -04:00
|
|
|
int pci_func_id = PCI_FUNC(pdev->devfn);
|
2007-10-03 20:59:30 -04:00
|
|
|
DECLARE_MAC_BUF(mac);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2006-11-29 12:00:10 -05:00
|
|
|
printk(KERN_INFO "%s \n", netxen_nic_driver_string);
|
2007-04-20 10:55:26 -04:00
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
if (pdev->class != 0x020000) {
|
2007-11-19 20:48:24 -05:00
|
|
|
printk(KERN_ERR"NetXen function %d, class %x will not "
|
2007-04-20 10:52:37 -04:00
|
|
|
"be enabled.\n",pci_func_id, pdev->class);
|
2006-12-04 12:23:25 -05:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
if ((err = pci_enable_device(pdev)))
|
|
|
|
return err;
|
|
|
|
if (!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {
|
|
|
|
err = -ENODEV;
|
|
|
|
goto err_out_disable_pdev;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = pci_request_regions(pdev, netxen_nic_driver_name)))
|
|
|
|
goto err_out_disable_pdev;
|
|
|
|
|
|
|
|
pci_set_master(pdev);
|
2007-06-08 18:46:36 -04:00
|
|
|
if (pdev->revision == NX_P2_C1 &&
|
2006-12-04 12:23:25 -05:00
|
|
|
(pci_set_dma_mask(pdev, DMA_35BIT_MASK) == 0) &&
|
|
|
|
(pci_set_consistent_dma_mask(pdev, DMA_35BIT_MASK) == 0)) {
|
2006-10-21 15:33:03 -04:00
|
|
|
pci_using_dac = 1;
|
2006-12-04 12:23:25 -05:00
|
|
|
} else {
|
2006-10-21 15:33:03 -04:00
|
|
|
if ((err = pci_set_dma_mask(pdev, DMA_32BIT_MASK)) ||
|
|
|
|
(err = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK)))
|
|
|
|
goto err_out_free_res;
|
|
|
|
|
|
|
|
pci_using_dac = 0;
|
|
|
|
}
|
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
|
|
|
|
netdev = alloc_etherdev(sizeof(struct netxen_adapter));
|
|
|
|
if(!netdev) {
|
|
|
|
printk(KERN_ERR"%s: Failed to allocate memory for the "
|
|
|
|
"device block.Check system memory resource"
|
|
|
|
" usage.\n", netxen_nic_driver_name);
|
|
|
|
goto err_out_free_res;
|
|
|
|
}
|
|
|
|
|
|
|
|
SET_NETDEV_DEV(netdev, &pdev->dev);
|
|
|
|
|
|
|
|
adapter = netdev->priv;
|
|
|
|
|
|
|
|
adapter->ahw.pdev = pdev;
|
2007-04-20 10:53:05 -04:00
|
|
|
adapter->ahw.pci_func = pci_func_id;
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
/* remap phys address */
|
|
|
|
mem_base = pci_resource_start(pdev, 0); /* 0 is for BAR 0 */
|
|
|
|
mem_len = pci_resource_len(pdev, 0);
|
|
|
|
|
|
|
|
/* 128 Meg of memory */
|
2007-04-20 10:55:26 -04:00
|
|
|
if (mem_len == NETXEN_PCI_128MB_SIZE) {
|
|
|
|
mem_ptr0 = ioremap(mem_base, FIRST_PAGE_GROUP_SIZE);
|
|
|
|
mem_ptr1 = ioremap(mem_base + SECOND_PAGE_GROUP_START,
|
|
|
|
SECOND_PAGE_GROUP_SIZE);
|
|
|
|
mem_ptr2 = ioremap(mem_base + THIRD_PAGE_GROUP_START,
|
|
|
|
THIRD_PAGE_GROUP_SIZE);
|
|
|
|
first_page_group_start = FIRST_PAGE_GROUP_START;
|
|
|
|
first_page_group_end = FIRST_PAGE_GROUP_END;
|
|
|
|
} else if (mem_len == NETXEN_PCI_32MB_SIZE) {
|
|
|
|
mem_ptr1 = ioremap(mem_base, SECOND_PAGE_GROUP_SIZE);
|
|
|
|
mem_ptr2 = ioremap(mem_base + THIRD_PAGE_GROUP_START -
|
|
|
|
SECOND_PAGE_GROUP_START, THIRD_PAGE_GROUP_SIZE);
|
|
|
|
first_page_group_start = 0;
|
|
|
|
first_page_group_end = 0;
|
|
|
|
} else {
|
2007-11-23 21:23:36 -05:00
|
|
|
err = -EIO;
|
2007-04-20 10:55:26 -04:00
|
|
|
goto err_out_free_netdev;
|
|
|
|
}
|
2006-11-29 12:00:10 -05:00
|
|
|
|
2008-01-24 05:06:46 -05:00
|
|
|
if ((!mem_ptr0 && mem_len == NETXEN_PCI_128MB_SIZE) ||
|
|
|
|
!mem_ptr1 || !mem_ptr2) {
|
2006-12-04 12:23:25 -05:00
|
|
|
DPRINTK(ERR,
|
2006-11-29 12:00:10 -05:00
|
|
|
"Cannot remap adapter memory aborting.:"
|
|
|
|
"0 -> %p, 1 -> %p, 2 -> %p\n",
|
|
|
|
mem_ptr0, mem_ptr1, mem_ptr2);
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
err = -EIO;
|
2006-12-04 12:23:25 -05:00
|
|
|
goto err_out_iounmap;
|
|
|
|
}
|
|
|
|
db_base = pci_resource_start(pdev, 4); /* doorbell is on bar 4 */
|
|
|
|
db_len = pci_resource_len(pdev, 4);
|
|
|
|
|
|
|
|
if (db_len == 0) {
|
|
|
|
printk(KERN_ERR "%s: doorbell is disabled\n",
|
|
|
|
netxen_nic_driver_name);
|
|
|
|
err = -EIO;
|
|
|
|
goto err_out_iounmap;
|
|
|
|
}
|
|
|
|
DPRINTK(INFO, "doorbell ioremap from %lx a size of %lx\n", db_base,
|
|
|
|
db_len);
|
|
|
|
|
|
|
|
db_ptr = ioremap(db_base, NETXEN_DB_MAPSIZE_BYTES);
|
2007-01-02 05:39:10 -05:00
|
|
|
if (!db_ptr) {
|
2006-12-04 12:23:25 -05:00
|
|
|
printk(KERN_ERR "%s: Failed to allocate doorbell map.",
|
|
|
|
netxen_nic_driver_name);
|
|
|
|
err = -EIO;
|
|
|
|
goto err_out_iounmap;
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
2006-12-04 12:23:25 -05:00
|
|
|
DPRINTK(INFO, "doorbell ioremaped at %p\n", db_ptr);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
adapter->ahw.pci_base0 = mem_ptr0;
|
|
|
|
adapter->ahw.first_page_group_start = first_page_group_start;
|
|
|
|
adapter->ahw.first_page_group_end = first_page_group_end;
|
|
|
|
adapter->ahw.pci_base1 = mem_ptr1;
|
|
|
|
adapter->ahw.pci_base2 = mem_ptr2;
|
|
|
|
adapter->ahw.db_base = db_ptr;
|
|
|
|
adapter->ahw.db_len = db_len;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
adapter->netdev = netdev;
|
|
|
|
adapter->pdev = pdev;
|
2007-07-01 14:56:00 -04:00
|
|
|
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
netif_napi_add(netdev, &adapter->napi,
|
|
|
|
netxen_nic_poll, NETXEN_NETDEV_WEIGHT);
|
|
|
|
|
2007-07-01 14:56:00 -04:00
|
|
|
/* this will be read from FW later */
|
|
|
|
adapter->intr_scheme = -1;
|
2008-03-17 22:59:48 -04:00
|
|
|
adapter->msi_mode = -1;
|
2007-07-01 14:56:00 -04:00
|
|
|
|
|
|
|
/* This will be reset for mezz cards */
|
2007-04-20 10:52:37 -04:00
|
|
|
adapter->portnum = pci_func_id;
|
2007-07-01 14:56:00 -04:00
|
|
|
adapter->status &= ~NETXEN_NETDEV_STATUS;
|
2007-09-03 01:03:35 -04:00
|
|
|
adapter->rx_csum = 1;
|
2007-04-20 10:52:37 -04:00
|
|
|
|
|
|
|
netdev->open = netxen_nic_open;
|
|
|
|
netdev->stop = netxen_nic_close;
|
|
|
|
netdev->hard_start_xmit = netxen_nic_xmit_frame;
|
2007-11-23 21:23:36 -05:00
|
|
|
netdev->get_stats = netxen_nic_get_stats;
|
2007-04-20 10:52:37 -04:00
|
|
|
netdev->set_multicast_list = netxen_nic_set_multi;
|
|
|
|
netdev->set_mac_address = netxen_nic_set_mac;
|
|
|
|
netdev->change_mtu = netxen_nic_change_mtu;
|
|
|
|
netdev->tx_timeout = netxen_tx_timeout;
|
2008-03-17 22:59:49 -04:00
|
|
|
netdev->watchdog_timeo = 2*HZ;
|
2007-04-20 10:52:37 -04:00
|
|
|
|
|
|
|
netxen_nic_change_mtu(netdev, netdev->mtu);
|
|
|
|
|
|
|
|
SET_ETHTOOL_OPS(netdev, &netxen_nic_ethtool_ops);
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
netdev->poll_controller = netxen_nic_poll_controller;
|
|
|
|
#endif
|
|
|
|
/* ScatterGather support */
|
|
|
|
netdev->features = NETIF_F_SG;
|
|
|
|
netdev->features |= NETIF_F_IP_CSUM;
|
|
|
|
netdev->features |= NETIF_F_TSO;
|
|
|
|
|
|
|
|
if (pci_using_dac)
|
|
|
|
netdev->features |= NETIF_F_HIGHDMA;
|
|
|
|
|
2007-07-01 14:55:59 -04:00
|
|
|
if (pci_enable_msi(pdev))
|
2007-04-20 10:52:37 -04:00
|
|
|
adapter->flags &= ~NETXEN_NIC_MSI_ENABLED;
|
2007-07-01 14:55:59 -04:00
|
|
|
else
|
2007-04-20 10:52:37 -04:00
|
|
|
adapter->flags |= NETXEN_NIC_MSI_ENABLED;
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
netdev->irq = pdev->irq;
|
|
|
|
INIT_WORK(&adapter->tx_timeout_task, netxen_tx_timeout_task);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the CRB window to invalid. If any register in window 0 is
|
|
|
|
* accessed it should set the window to 0 and then reset it to 1.
|
|
|
|
*/
|
|
|
|
adapter->curr_window = 255;
|
|
|
|
|
|
|
|
/* initialize the adapter */
|
|
|
|
netxen_initialize_adapter_hw(adapter);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Adapter in our case is quad port so initialize it before
|
|
|
|
* initializing the ports
|
|
|
|
*/
|
|
|
|
|
|
|
|
netxen_initialize_adapter_ops(adapter);
|
|
|
|
|
|
|
|
adapter->max_tx_desc_count = MAX_CMD_DESCRIPTORS_HOST;
|
|
|
|
if ((adapter->ahw.boardcfg.board_type == NETXEN_BRDTYPE_P2_SB35_4G) ||
|
2007-11-23 21:23:36 -05:00
|
|
|
(adapter->ahw.boardcfg.board_type ==
|
|
|
|
NETXEN_BRDTYPE_P2_SB31_2G))
|
2007-04-20 10:55:26 -04:00
|
|
|
adapter->max_rx_desc_count = MAX_RCV_DESCRIPTORS_1G;
|
|
|
|
else
|
|
|
|
adapter->max_rx_desc_count = MAX_RCV_DESCRIPTORS;
|
|
|
|
adapter->max_jumbo_rx_desc_count = MAX_JUMBO_RCV_DESCRIPTORS;
|
|
|
|
adapter->max_lro_rx_desc_count = MAX_LRO_RCV_DESCRIPTORS;
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
cmd_buf_arr = (struct netxen_cmd_buffer *)vmalloc(TX_RINGSIZE);
|
|
|
|
if (cmd_buf_arr == NULL) {
|
2006-12-04 12:23:25 -05:00
|
|
|
printk(KERN_ERR
|
|
|
|
"%s: Could not allocate cmd_buf_arr memory:%d\n",
|
|
|
|
netxen_nic_driver_name, (int)TX_RINGSIZE);
|
2006-10-21 15:33:03 -04:00
|
|
|
err = -ENOMEM;
|
|
|
|
goto err_out_free_adapter;
|
|
|
|
}
|
|
|
|
memset(cmd_buf_arr, 0, TX_RINGSIZE);
|
2007-04-20 10:55:26 -04:00
|
|
|
adapter->cmd_buf_arr = cmd_buf_arr;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
for (i = 0; i < MAX_RCV_CTX; ++i) {
|
|
|
|
recv_ctx = &adapter->recv_ctx[i];
|
|
|
|
for (ring = 0; ring < NUM_RCV_DESC_RINGS; ring++) {
|
|
|
|
rcv_desc = &recv_ctx->rcv_desc[ring];
|
|
|
|
switch (RCV_DESC_TYPE(ring)) {
|
|
|
|
case RCV_DESC_NORMAL:
|
|
|
|
rcv_desc->max_rx_desc_count =
|
|
|
|
adapter->max_rx_desc_count;
|
|
|
|
rcv_desc->flags = RCV_DESC_NORMAL;
|
|
|
|
rcv_desc->dma_size = RX_DMA_MAP_LEN;
|
|
|
|
rcv_desc->skb_size = MAX_RX_BUFFER_LENGTH;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case RCV_DESC_JUMBO:
|
|
|
|
rcv_desc->max_rx_desc_count =
|
|
|
|
adapter->max_jumbo_rx_desc_count;
|
|
|
|
rcv_desc->flags = RCV_DESC_JUMBO;
|
|
|
|
rcv_desc->dma_size = RX_JUMBO_DMA_MAP_LEN;
|
|
|
|
rcv_desc->skb_size = MAX_RX_JUMBO_BUFFER_LENGTH;
|
|
|
|
break;
|
|
|
|
|
2006-12-04 12:23:25 -05:00
|
|
|
case RCV_RING_LRO:
|
|
|
|
rcv_desc->max_rx_desc_count =
|
|
|
|
adapter->max_lro_rx_desc_count;
|
|
|
|
rcv_desc->flags = RCV_DESC_LRO;
|
|
|
|
rcv_desc->dma_size = RX_LRO_DMA_MAP_LEN;
|
|
|
|
rcv_desc->skb_size = MAX_RX_LRO_BUFFER_LENGTH;
|
|
|
|
break;
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
rcv_desc->rx_buf_arr = (struct netxen_rx_buffer *)
|
|
|
|
vmalloc(RCV_BUFFSIZE);
|
|
|
|
|
|
|
|
if (rcv_desc->rx_buf_arr == NULL) {
|
2007-11-19 20:48:24 -05:00
|
|
|
printk(KERN_ERR "%s: Could not allocate "
|
2006-12-04 12:23:25 -05:00
|
|
|
"rcv_desc->rx_buf_arr memory:%d\n",
|
|
|
|
netxen_nic_driver_name,
|
|
|
|
(int)RCV_BUFFSIZE);
|
2006-10-21 15:33:03 -04:00
|
|
|
err = -ENOMEM;
|
|
|
|
goto err_out_free_rx_buffer;
|
|
|
|
}
|
|
|
|
memset(rcv_desc->rx_buf_arr, 0, RCV_BUFFSIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2006-12-04 12:23:25 -05:00
|
|
|
netxen_initialize_adapter_sw(adapter); /* initialize the buffers in adapter */
|
2007-04-20 10:55:26 -04:00
|
|
|
|
2007-04-20 10:53:05 -04:00
|
|
|
/* Mezz cards have PCI function 0,2,3 enabled */
|
2007-04-20 10:55:26 -04:00
|
|
|
if ((adapter->ahw.boardcfg.board_type == NETXEN_BRDTYPE_P2_SB31_10G_IMEZ)
|
|
|
|
&& (pci_func_id >= 2))
|
2007-04-20 10:53:05 -04:00
|
|
|
adapter->portnum = pci_func_id - 2;
|
|
|
|
|
2006-11-29 12:00:10 -05:00
|
|
|
#ifdef CONFIG_IA64
|
2007-04-20 10:53:05 -04:00
|
|
|
if(adapter->portnum == 0) {
|
2007-04-20 10:52:37 -04:00
|
|
|
netxen_pinit_from_rom(adapter, 0);
|
|
|
|
udelay(500);
|
|
|
|
netxen_load_firmware(adapter);
|
|
|
|
}
|
2006-11-29 12:00:10 -05:00
|
|
|
#endif
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
init_timer(&adapter->watchdog_timer);
|
|
|
|
adapter->ahw.xg_linkup = 0;
|
|
|
|
adapter->watchdog_timer.function = &netxen_watchdog;
|
|
|
|
adapter->watchdog_timer.data = (unsigned long)adapter;
|
2006-12-05 14:36:26 -05:00
|
|
|
INIT_WORK(&adapter->watchdog_task, netxen_watchdog_task);
|
2006-10-21 15:33:03 -04:00
|
|
|
adapter->ahw.pdev = pdev;
|
2007-06-08 18:46:36 -04:00
|
|
|
adapter->ahw.revision_id = pdev->revision;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
/* make sure Window == 1 */
|
|
|
|
netxen_nic_pci_change_crbwindow(adapter, 1);
|
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
netxen_nic_update_cmd_producer(adapter, 0);
|
|
|
|
netxen_nic_update_cmd_consumer(adapter, 0);
|
2007-04-20 10:55:26 -04:00
|
|
|
writel(0, NETXEN_CRB_NORMALIZE(adapter, CRB_HOST_CMD_ADDR_LO));
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
if (netxen_is_flash_supported(adapter) == 0 &&
|
|
|
|
netxen_get_flash_mac_addr(adapter, mac_addr) == 0)
|
|
|
|
valid_mac = 1;
|
|
|
|
else
|
|
|
|
valid_mac = 0;
|
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
if (valid_mac) {
|
2007-04-20 10:53:05 -04:00
|
|
|
unsigned char *p = (unsigned char *)&mac_addr[adapter->portnum];
|
2007-04-20 10:52:37 -04:00
|
|
|
netdev->dev_addr[0] = *(p + 5);
|
|
|
|
netdev->dev_addr[1] = *(p + 4);
|
|
|
|
netdev->dev_addr[2] = *(p + 3);
|
|
|
|
netdev->dev_addr[3] = *(p + 2);
|
|
|
|
netdev->dev_addr[4] = *(p + 1);
|
|
|
|
netdev->dev_addr[5] = *(p + 0);
|
|
|
|
|
|
|
|
memcpy(netdev->perm_addr, netdev->dev_addr,
|
|
|
|
netdev->addr_len);
|
|
|
|
if (!is_valid_ether_addr(netdev->perm_addr)) {
|
2007-10-03 20:59:30 -04:00
|
|
|
printk(KERN_ERR "%s: Bad MAC address %s.\n",
|
|
|
|
netxen_nic_driver_name,
|
|
|
|
print_mac(mac, netdev->dev_addr));
|
2007-04-20 10:52:37 -04:00
|
|
|
} else {
|
|
|
|
if (adapter->macaddr_set)
|
|
|
|
adapter->macaddr_set(adapter,
|
|
|
|
netdev->dev_addr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
if (adapter->portnum == 0) {
|
2007-04-20 10:53:05 -04:00
|
|
|
err = netxen_initialize_adapter_offload(adapter);
|
2007-11-23 21:23:36 -05:00
|
|
|
if (err)
|
2007-04-20 10:55:26 -04:00
|
|
|
goto err_out_free_rx_buffer;
|
2007-11-23 21:23:36 -05:00
|
|
|
val = readl(NETXEN_CRB_NORMALIZE(adapter,
|
2007-04-20 10:55:26 -04:00
|
|
|
NETXEN_CAM_RAM(0x1fc)));
|
|
|
|
if (val == 0x55555555) {
|
|
|
|
/* This is the first boot after power up */
|
2007-07-01 14:55:59 -04:00
|
|
|
netxen_nic_read_w0(adapter, NETXEN_PCIE_REG(0x4), &val);
|
2007-07-02 00:07:57 -04:00
|
|
|
if (!(val & 0x4)) {
|
|
|
|
val |= 0x4;
|
|
|
|
netxen_nic_write_w0(adapter, NETXEN_PCIE_REG(0x4), val);
|
|
|
|
netxen_nic_read_w0(adapter, NETXEN_PCIE_REG(0x4), &val);
|
|
|
|
if (!(val & 0x4))
|
|
|
|
printk(KERN_ERR "%s: failed to set MSI bit in PCI-e reg\n",
|
|
|
|
netxen_nic_driver_name);
|
|
|
|
}
|
2007-04-20 10:55:26 -04:00
|
|
|
val = readl(NETXEN_CRB_NORMALIZE(adapter,
|
|
|
|
NETXEN_ROMUSB_GLB_SW_RESET));
|
|
|
|
printk(KERN_INFO"NetXen: read 0x%08x for reset reg.\n",val);
|
|
|
|
if (val != 0x80000f) {
|
|
|
|
/* clear the register for future unloads/loads */
|
2007-07-01 14:55:59 -04:00
|
|
|
writel(0, NETXEN_CRB_NORMALIZE(adapter,
|
|
|
|
NETXEN_CAM_RAM(0x1fc)));
|
|
|
|
printk(KERN_ERR "ERROR in NetXen HW init sequence.\n");
|
|
|
|
err = -ENODEV;
|
|
|
|
goto err_out_free_dev;
|
2007-04-20 10:55:26 -04:00
|
|
|
}
|
|
|
|
}
|
2007-07-02 00:07:57 -04:00
|
|
|
|
|
|
|
/* clear the register for future unloads/loads */
|
|
|
|
writel(0, NETXEN_CRB_NORMALIZE(adapter, NETXEN_CAM_RAM(0x1fc)));
|
2007-04-20 10:55:26 -04:00
|
|
|
printk(KERN_INFO "State: 0x%0x\n",
|
|
|
|
readl(NETXEN_CRB_NORMALIZE(adapter, CRB_CMDPEG_STATE)));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Tell the hardware our version number.
|
|
|
|
*/
|
2007-11-23 21:23:36 -05:00
|
|
|
i = (_NETXEN_NIC_LINUX_MAJOR << 16)
|
2007-04-20 10:55:26 -04:00
|
|
|
| ((_NETXEN_NIC_LINUX_MINOR << 8))
|
|
|
|
| (_NETXEN_NIC_LINUX_SUBVERSION);
|
|
|
|
writel(i, NETXEN_CRB_NORMALIZE(adapter, CRB_DRIVER_VERSION));
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
/* Unlock the HW, prompting the boot sequence */
|
|
|
|
writel(1,
|
|
|
|
NETXEN_CRB_NORMALIZE(adapter,
|
|
|
|
NETXEN_ROMUSB_GLB_PEGTUNE_DONE));
|
|
|
|
/* Handshake with the card before we register the devices. */
|
|
|
|
netxen_phantom_init(adapter, NETXEN_NIC_PEG_TUNE);
|
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
/*
|
2007-04-20 10:55:26 -04:00
|
|
|
* See if the firmware gave us a virtual-physical port mapping.
|
2006-10-21 15:33:03 -04:00
|
|
|
*/
|
2007-04-20 10:55:26 -04:00
|
|
|
i = readl(NETXEN_CRB_NORMALIZE(adapter, CRB_V2P(adapter->portnum)));
|
|
|
|
if (i != 0x55555555)
|
|
|
|
physical_port[adapter->portnum] = i;
|
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
netif_carrier_off(netdev);
|
|
|
|
netif_stop_queue(netdev);
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
if ((err = register_netdev(netdev))) {
|
|
|
|
printk(KERN_ERR "%s: register_netdev failed port #%d"
|
|
|
|
" aborting\n", netxen_nic_driver_name,
|
|
|
|
adapter->portnum);
|
|
|
|
err = -EIO;
|
|
|
|
goto err_out_free_dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
pci_set_drvdata(pdev, adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
switch (adapter->ahw.board_type) {
|
2007-04-20 10:52:37 -04:00
|
|
|
case NETXEN_NIC_GBE:
|
|
|
|
printk(KERN_INFO "%s: QUAD GbE board initialized\n",
|
|
|
|
netxen_nic_driver_name);
|
|
|
|
break;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
case NETXEN_NIC_XGBE:
|
2007-11-23 21:23:36 -05:00
|
|
|
printk(KERN_INFO "%s: XGbE board initialized\n",
|
2007-04-20 10:52:37 -04:00
|
|
|
netxen_nic_driver_name);
|
|
|
|
break;
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
adapter->driver_mismatch = 0;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
err_out_free_dev:
|
|
|
|
if (adapter->portnum == 0)
|
|
|
|
netxen_free_adapter_offload(adapter);
|
2006-12-04 12:23:25 -05:00
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
err_out_free_rx_buffer:
|
2006-10-21 15:33:03 -04:00
|
|
|
for (i = 0; i < MAX_RCV_CTX; ++i) {
|
|
|
|
recv_ctx = &adapter->recv_ctx[i];
|
|
|
|
for (ring = 0; ring < NUM_RCV_DESC_RINGS; ring++) {
|
|
|
|
rcv_desc = &recv_ctx->rcv_desc[ring];
|
|
|
|
if (rcv_desc->rx_buf_arr != NULL) {
|
|
|
|
vfree(rcv_desc->rx_buf_arr);
|
|
|
|
rcv_desc->rx_buf_arr = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
vfree(cmd_buf_arr);
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
err_out_free_adapter:
|
|
|
|
if (adapter->flags & NETXEN_NIC_MSI_ENABLED)
|
|
|
|
pci_disable_msi(pdev);
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
pci_set_drvdata(pdev, NULL);
|
|
|
|
|
2006-12-04 12:23:25 -05:00
|
|
|
if (db_ptr)
|
|
|
|
iounmap(db_ptr);
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
err_out_iounmap:
|
2006-12-04 12:23:25 -05:00
|
|
|
if (mem_ptr0)
|
|
|
|
iounmap(mem_ptr0);
|
|
|
|
if (mem_ptr1)
|
|
|
|
iounmap(mem_ptr1);
|
|
|
|
if (mem_ptr2)
|
|
|
|
iounmap(mem_ptr2);
|
2006-11-29 12:00:10 -05:00
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
err_out_free_netdev:
|
|
|
|
free_netdev(netdev);
|
|
|
|
|
|
|
|
err_out_free_res:
|
2006-10-21 15:33:03 -04:00
|
|
|
pci_release_regions(pdev);
|
2007-04-20 10:55:26 -04:00
|
|
|
|
|
|
|
err_out_disable_pdev:
|
2006-10-21 15:33:03 -04:00
|
|
|
pci_disable_device(pdev);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __devexit netxen_nic_remove(struct pci_dev *pdev)
|
|
|
|
{
|
|
|
|
struct netxen_adapter *adapter;
|
2007-04-20 10:52:37 -04:00
|
|
|
struct net_device *netdev;
|
2006-10-21 15:33:03 -04:00
|
|
|
struct netxen_rx_buffer *buffer;
|
|
|
|
struct netxen_recv_context *recv_ctx;
|
|
|
|
struct netxen_rcv_desc_ctx *rcv_desc;
|
2007-07-02 00:07:57 -04:00
|
|
|
int i, ctxid, ring;
|
|
|
|
static int init_firmware_done = 0;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
adapter = pci_get_drvdata(pdev);
|
2006-10-21 15:33:03 -04:00
|
|
|
if (adapter == NULL)
|
|
|
|
return;
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
netdev = adapter->netdev;
|
|
|
|
|
2007-07-02 00:07:57 -04:00
|
|
|
unregister_netdev(netdev);
|
|
|
|
|
|
|
|
if (adapter->is_up == NETXEN_ADAPTER_UP_MAGIC) {
|
|
|
|
init_firmware_done++;
|
2006-10-21 15:33:03 -04:00
|
|
|
netxen_free_hw_resources(adapter);
|
2007-07-02 00:07:57 -04:00
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
for (ctxid = 0; ctxid < MAX_RCV_CTX; ++ctxid) {
|
|
|
|
recv_ctx = &adapter->recv_ctx[ctxid];
|
|
|
|
for (ring = 0; ring < NUM_RCV_DESC_RINGS; ring++) {
|
|
|
|
rcv_desc = &recv_ctx->rcv_desc[ring];
|
|
|
|
for (i = 0; i < rcv_desc->max_rx_desc_count; ++i) {
|
|
|
|
buffer = &(rcv_desc->rx_buf_arr[i]);
|
|
|
|
if (buffer->state == NETXEN_BUFFER_FREE)
|
|
|
|
continue;
|
|
|
|
pci_unmap_single(pdev, buffer->dma,
|
|
|
|
rcv_desc->dma_size,
|
|
|
|
PCI_DMA_FROMDEVICE);
|
|
|
|
if (buffer->skb != NULL)
|
|
|
|
dev_kfree_skb_any(buffer->skb);
|
|
|
|
}
|
|
|
|
vfree(rcv_desc->rx_buf_arr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
vfree(adapter->cmd_buf_arr);
|
|
|
|
|
2007-07-02 00:07:57 -04:00
|
|
|
if (adapter->portnum == 0) {
|
|
|
|
if (init_firmware_done) {
|
|
|
|
i = 100;
|
2007-07-19 05:11:09 -04:00
|
|
|
do {
|
|
|
|
if (dma_watchdog_shutdown_request(adapter) == 1)
|
|
|
|
break;
|
2007-07-02 00:07:57 -04:00
|
|
|
msleep(100);
|
2007-07-19 05:11:09 -04:00
|
|
|
if (dma_watchdog_shutdown_poll_result(adapter) == 1)
|
|
|
|
break;
|
|
|
|
} while (--i);
|
2007-07-02 00:07:57 -04:00
|
|
|
|
2007-07-19 05:11:09 -04:00
|
|
|
if (i == 0)
|
|
|
|
printk(KERN_ERR "%s: dma_watchdog_shutdown failed\n",
|
|
|
|
netdev->name);
|
2007-07-02 00:07:57 -04:00
|
|
|
|
|
|
|
/* clear the register for future unloads/loads */
|
|
|
|
writel(0, NETXEN_CRB_NORMALIZE(adapter, NETXEN_CAM_RAM(0x1fc)));
|
|
|
|
printk(KERN_INFO "State: 0x%0x\n",
|
|
|
|
readl(NETXEN_CRB_NORMALIZE(adapter, CRB_CMDPEG_STATE)));
|
|
|
|
|
|
|
|
/* leave the hw in the same state as reboot */
|
|
|
|
writel(0, NETXEN_CRB_NORMALIZE(adapter, CRB_CMDPEG_STATE));
|
2007-07-19 05:11:09 -04:00
|
|
|
netxen_pinit_from_rom(adapter, 0);
|
2007-07-02 00:07:57 -04:00
|
|
|
msleep(1);
|
2007-07-19 05:11:09 -04:00
|
|
|
netxen_load_firmware(adapter);
|
2007-07-02 00:07:57 -04:00
|
|
|
netxen_phantom_init(adapter, NETXEN_NIC_PEG_TUNE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* clear the register for future unloads/loads */
|
|
|
|
writel(0, NETXEN_CRB_NORMALIZE(adapter, NETXEN_CAM_RAM(0x1fc)));
|
|
|
|
printk(KERN_INFO "State: 0x%0x\n",
|
|
|
|
readl(NETXEN_CRB_NORMALIZE(adapter, CRB_CMDPEG_STATE)));
|
|
|
|
|
|
|
|
i = 100;
|
2007-07-19 05:11:09 -04:00
|
|
|
do {
|
|
|
|
if (dma_watchdog_shutdown_request(adapter) == 1)
|
|
|
|
break;
|
2007-07-02 00:07:57 -04:00
|
|
|
msleep(100);
|
2007-07-19 05:11:09 -04:00
|
|
|
if (dma_watchdog_shutdown_poll_result(adapter) == 1)
|
|
|
|
break;
|
|
|
|
} while (--i);
|
2007-07-02 00:07:57 -04:00
|
|
|
|
|
|
|
if (i) {
|
|
|
|
netxen_free_adapter_offload(adapter);
|
|
|
|
} else {
|
2007-07-19 05:11:09 -04:00
|
|
|
printk(KERN_ERR "%s: dma_watchdog_shutdown failed\n",
|
|
|
|
netdev->name);
|
2007-07-02 00:07:57 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-08-28 07:53:26 -04:00
|
|
|
if (adapter->irq)
|
|
|
|
free_irq(adapter->irq, adapter);
|
|
|
|
|
|
|
|
if (adapter->flags & NETXEN_NIC_MSI_ENABLED)
|
|
|
|
pci_disable_msi(pdev);
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
iounmap(adapter->ahw.db_base);
|
|
|
|
iounmap(adapter->ahw.pci_base0);
|
|
|
|
iounmap(adapter->ahw.pci_base1);
|
|
|
|
iounmap(adapter->ahw.pci_base2);
|
2007-04-20 10:53:52 -04:00
|
|
|
|
|
|
|
pci_release_regions(pdev);
|
2007-08-28 07:53:26 -04:00
|
|
|
pci_disable_device(pdev);
|
2007-04-20 10:53:52 -04:00
|
|
|
pci_set_drvdata(pdev, NULL);
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
free_netdev(netdev);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called when a network interface is made active
|
|
|
|
* @returns 0 on success, negative value on failure
|
|
|
|
*/
|
|
|
|
static int netxen_nic_open(struct net_device *netdev)
|
|
|
|
{
|
2007-04-20 10:52:37 -04:00
|
|
|
struct netxen_adapter *adapter = (struct netxen_adapter *)netdev->priv;
|
2006-10-21 15:33:03 -04:00
|
|
|
int err = 0;
|
|
|
|
int ctx, ring;
|
2008-03-17 22:59:49 -04:00
|
|
|
irq_handler_t handler;
|
|
|
|
unsigned long flags = IRQF_SAMPLE_RANDOM;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
if (adapter->is_up != NETXEN_ADAPTER_UP_MAGIC) {
|
|
|
|
err = netxen_init_firmware(adapter);
|
|
|
|
if (err != 0) {
|
|
|
|
printk(KERN_ERR "Failed to init firmware\n");
|
|
|
|
return -EIO;
|
|
|
|
}
|
|
|
|
netxen_nic_flash_print(adapter);
|
|
|
|
|
|
|
|
/* setup all the resources for the Phantom... */
|
|
|
|
/* this include the descriptors for rcv, tx, and status */
|
|
|
|
netxen_nic_clear_stats(adapter);
|
|
|
|
err = netxen_nic_hw_resources(adapter);
|
|
|
|
if (err) {
|
|
|
|
printk(KERN_ERR "Error in setting hw resources:%d\n",
|
|
|
|
err);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
for (ctx = 0; ctx < MAX_RCV_CTX; ++ctx) {
|
2006-11-29 12:00:10 -05:00
|
|
|
for (ring = 0; ring < NUM_RCV_DESC_RINGS; ring++)
|
2006-10-21 15:33:03 -04:00
|
|
|
netxen_post_rx_buffers(adapter, ctx, ring);
|
|
|
|
}
|
2006-12-04 12:23:25 -05:00
|
|
|
adapter->irq = adapter->ahw.pdev->irq;
|
2008-03-17 22:59:49 -04:00
|
|
|
if (adapter->flags & NETXEN_NIC_MSI_ENABLED)
|
|
|
|
handler = netxen_msi_intr;
|
|
|
|
else {
|
|
|
|
flags |= IRQF_SHARED;
|
|
|
|
handler = netxen_intr;
|
|
|
|
}
|
|
|
|
err = request_irq(adapter->irq, handler,
|
|
|
|
flags, netdev->name, adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
if (err) {
|
|
|
|
printk(KERN_ERR "request_irq failed with: %d\n", err);
|
2006-12-04 12:23:25 -05:00
|
|
|
netxen_free_hw_resources(adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
return err;
|
|
|
|
}
|
2006-12-04 12:23:25 -05:00
|
|
|
|
|
|
|
adapter->is_up = NETXEN_ADAPTER_UP_MAGIC;
|
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
/* Done here again so that even if phantom sw overwrote it,
|
|
|
|
* we set it */
|
2007-04-20 10:53:52 -04:00
|
|
|
if (adapter->init_port
|
|
|
|
&& adapter->init_port(adapter, adapter->portnum) != 0) {
|
|
|
|
printk(KERN_ERR "%s: Failed to initialize port %d\n",
|
|
|
|
netxen_nic_driver_name, adapter->portnum);
|
|
|
|
return -EIO;
|
|
|
|
}
|
2007-07-25 10:43:11 -04:00
|
|
|
if (adapter->macaddr_set)
|
|
|
|
adapter->macaddr_set(adapter, netdev->dev_addr);
|
2007-04-20 10:53:52 -04:00
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
netxen_nic_set_link_parameters(adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
netxen_nic_set_multi(netdev);
|
2006-12-04 12:23:25 -05:00
|
|
|
if (adapter->set_mtu)
|
2007-04-20 10:52:37 -04:00
|
|
|
adapter->set_mtu(adapter, netdev->mtu);
|
2006-12-04 12:23:25 -05:00
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
if (!adapter->driver_mismatch)
|
|
|
|
mod_timer(&adapter->watchdog_timer, jiffies);
|
|
|
|
|
|
|
|
napi_enable(&adapter->napi);
|
|
|
|
netxen_nic_enable_int(adapter);
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
if (!adapter->driver_mismatch)
|
|
|
|
netif_start_queue(netdev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* netxen_nic_close - Disables a network interface entry point
|
|
|
|
*/
|
|
|
|
static int netxen_nic_close(struct net_device *netdev)
|
|
|
|
{
|
2007-04-20 10:52:37 -04:00
|
|
|
struct netxen_adapter *adapter = netdev_priv(netdev);
|
2006-10-21 15:33:03 -04:00
|
|
|
int i, j;
|
|
|
|
struct netxen_cmd_buffer *cmd_buff;
|
|
|
|
struct netxen_skb_frag *buffrag;
|
|
|
|
|
|
|
|
netif_carrier_off(netdev);
|
|
|
|
netif_stop_queue(netdev);
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
napi_disable(&adapter->napi);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-12-26 13:23:56 -05:00
|
|
|
if (adapter->stop_port)
|
|
|
|
adapter->stop_port(adapter);
|
|
|
|
|
2007-07-25 10:43:12 -04:00
|
|
|
netxen_nic_disable_int(adapter);
|
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
cmd_buff = adapter->cmd_buf_arr;
|
|
|
|
for (i = 0; i < adapter->max_tx_desc_count; i++) {
|
|
|
|
buffrag = cmd_buff->frag_array;
|
|
|
|
if (buffrag->dma) {
|
|
|
|
pci_unmap_single(adapter->pdev, buffrag->dma,
|
|
|
|
buffrag->length, PCI_DMA_TODEVICE);
|
2007-07-02 00:07:57 -04:00
|
|
|
buffrag->dma = 0ULL;
|
2007-04-20 10:52:37 -04:00
|
|
|
}
|
|
|
|
for (j = 0; j < cmd_buff->frag_count; j++) {
|
|
|
|
buffrag++;
|
2006-10-21 15:33:03 -04:00
|
|
|
if (buffrag->dma) {
|
2007-04-20 10:52:37 -04:00
|
|
|
pci_unmap_page(adapter->pdev, buffrag->dma,
|
2007-11-23 21:23:36 -05:00
|
|
|
buffrag->length,
|
2007-04-20 10:52:37 -04:00
|
|
|
PCI_DMA_TODEVICE);
|
2007-07-02 00:07:57 -04:00
|
|
|
buffrag->dma = 0ULL;
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
}
|
2007-04-20 10:52:37 -04:00
|
|
|
/* Free the skb we received in netxen_nic_xmit_frame */
|
|
|
|
if (cmd_buff->skb) {
|
|
|
|
dev_kfree_skb_any(cmd_buff->skb);
|
|
|
|
cmd_buff->skb = NULL;
|
|
|
|
}
|
|
|
|
cmd_buff++;
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
2007-07-02 00:07:57 -04:00
|
|
|
if (adapter->is_up == NETXEN_ADAPTER_UP_MAGIC) {
|
|
|
|
FLUSH_SCHEDULED_WORK();
|
|
|
|
del_timer_sync(&adapter->watchdog_timer);
|
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int netxen_nic_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
|
|
|
|
{
|
2007-04-20 10:52:37 -04:00
|
|
|
struct netxen_adapter *adapter = netdev_priv(netdev);
|
2006-10-21 15:33:03 -04:00
|
|
|
struct netxen_hardware_context *hw = &adapter->ahw;
|
|
|
|
unsigned int first_seg_len = skb->len - skb->data_len;
|
|
|
|
struct netxen_skb_frag *buffrag;
|
|
|
|
unsigned int i;
|
|
|
|
|
2008-03-17 22:59:50 -04:00
|
|
|
u32 producer, consumer;
|
2006-10-21 15:33:03 -04:00
|
|
|
u32 saved_producer = 0;
|
|
|
|
struct cmd_desc_type0 *hwdesc;
|
|
|
|
int k;
|
|
|
|
struct netxen_cmd_buffer *pbuf = NULL;
|
|
|
|
int frag_count;
|
|
|
|
int no_of_desc;
|
2008-03-17 22:59:50 -04:00
|
|
|
u32 num_txd = adapter->max_tx_desc_count;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
frag_count = skb_shinfo(skb)->nr_frags + 1;
|
|
|
|
|
|
|
|
/* There 4 fragments per descriptor */
|
|
|
|
no_of_desc = (frag_count + 3) >> 2;
|
2006-11-29 12:00:10 -05:00
|
|
|
if (netdev->features & NETIF_F_TSO) {
|
|
|
|
if (skb_shinfo(skb)->gso_size > 0) {
|
|
|
|
|
2006-10-21 15:33:03 -04:00
|
|
|
no_of_desc++;
|
2007-03-18 20:43:48 -04:00
|
|
|
if ((ip_hdrlen(skb) + tcp_hdrlen(skb) +
|
2007-03-12 19:09:15 -04:00
|
|
|
sizeof(struct ethhdr)) >
|
2006-12-04 12:23:25 -05:00
|
|
|
(sizeof(struct cmd_desc_type0) - 2)) {
|
2006-11-29 12:00:10 -05:00
|
|
|
no_of_desc++;
|
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
}
|
2007-12-26 13:23:58 -05:00
|
|
|
|
2008-03-17 22:59:50 -04:00
|
|
|
producer = adapter->cmd_producer;
|
|
|
|
smp_mb();
|
|
|
|
consumer = adapter->last_cmd_consumer;
|
|
|
|
if ((no_of_desc+2) > find_diff_among(producer, consumer, num_txd)) {
|
|
|
|
netif_stop_queue(netdev);
|
|
|
|
smp_mb();
|
|
|
|
return NETDEV_TX_BUSY;
|
2007-12-26 13:23:58 -05:00
|
|
|
}
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
/* Copy the descriptors into the hardware */
|
|
|
|
saved_producer = producer;
|
|
|
|
hwdesc = &hw->cmd_desc_head[producer];
|
|
|
|
memset(hwdesc, 0, sizeof(struct cmd_desc_type0));
|
|
|
|
/* Take skb->data itself */
|
|
|
|
pbuf = &adapter->cmd_buf_arr[producer];
|
2006-11-29 12:00:10 -05:00
|
|
|
if ((netdev->features & NETIF_F_TSO) && skb_shinfo(skb)->gso_size > 0) {
|
2007-01-02 05:39:10 -05:00
|
|
|
pbuf->mss = skb_shinfo(skb)->gso_size;
|
2006-12-18 08:53:59 -05:00
|
|
|
hwdesc->mss = cpu_to_le16(skb_shinfo(skb)->gso_size);
|
2006-10-21 15:33:03 -04:00
|
|
|
} else {
|
|
|
|
pbuf->mss = 0;
|
|
|
|
hwdesc->mss = 0;
|
|
|
|
}
|
|
|
|
pbuf->total_length = skb->len;
|
|
|
|
pbuf->skb = skb;
|
|
|
|
pbuf->cmd = TX_ETHER_PKT;
|
|
|
|
pbuf->frag_count = frag_count;
|
2007-04-20 10:52:37 -04:00
|
|
|
pbuf->port = adapter->portnum;
|
2006-10-21 15:33:03 -04:00
|
|
|
buffrag = &pbuf->frag_array[0];
|
2007-04-20 10:52:37 -04:00
|
|
|
buffrag->dma = pci_map_single(adapter->pdev, skb->data, first_seg_len,
|
2006-10-21 15:33:03 -04:00
|
|
|
PCI_DMA_TODEVICE);
|
|
|
|
buffrag->length = first_seg_len;
|
2006-12-04 12:23:25 -05:00
|
|
|
netxen_set_cmd_desc_totallength(hwdesc, skb->len);
|
|
|
|
netxen_set_cmd_desc_num_of_buff(hwdesc, frag_count);
|
|
|
|
netxen_set_cmd_desc_opcode(hwdesc, TX_ETHER_PKT);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2007-04-20 10:52:37 -04:00
|
|
|
netxen_set_cmd_desc_port(hwdesc, adapter->portnum);
|
2007-04-20 10:55:26 -04:00
|
|
|
netxen_set_cmd_desc_ctxid(hwdesc, adapter->portnum);
|
2006-10-21 15:33:03 -04:00
|
|
|
hwdesc->buffer1_length = cpu_to_le16(first_seg_len);
|
|
|
|
hwdesc->addr_buffer1 = cpu_to_le64(buffrag->dma);
|
|
|
|
|
|
|
|
for (i = 1, k = 1; i < frag_count; i++, k++) {
|
|
|
|
struct skb_frag_struct *frag;
|
|
|
|
int len, temp_len;
|
|
|
|
unsigned long offset;
|
|
|
|
dma_addr_t temp_dma;
|
|
|
|
|
|
|
|
/* move to next desc. if there is a need */
|
|
|
|
if ((i & 0x3) == 0) {
|
|
|
|
k = 0;
|
2008-03-17 22:59:50 -04:00
|
|
|
producer = get_next_index(producer, num_txd);
|
2006-10-21 15:33:03 -04:00
|
|
|
hwdesc = &hw->cmd_desc_head[producer];
|
|
|
|
memset(hwdesc, 0, sizeof(struct cmd_desc_type0));
|
2007-12-26 13:23:58 -05:00
|
|
|
pbuf = &adapter->cmd_buf_arr[producer];
|
|
|
|
pbuf->skb = NULL;
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
frag = &skb_shinfo(skb)->frags[i - 1];
|
|
|
|
len = frag->size;
|
|
|
|
offset = frag->page_offset;
|
|
|
|
|
|
|
|
temp_len = len;
|
2007-04-20 10:52:37 -04:00
|
|
|
temp_dma = pci_map_page(adapter->pdev, frag->page, offset,
|
2006-10-21 15:33:03 -04:00
|
|
|
len, PCI_DMA_TODEVICE);
|
|
|
|
|
|
|
|
buffrag++;
|
|
|
|
buffrag->dma = temp_dma;
|
|
|
|
buffrag->length = temp_len;
|
|
|
|
|
|
|
|
switch (k) {
|
|
|
|
case 0:
|
|
|
|
hwdesc->buffer1_length = cpu_to_le16(temp_len);
|
|
|
|
hwdesc->addr_buffer1 = cpu_to_le64(temp_dma);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
hwdesc->buffer2_length = cpu_to_le16(temp_len);
|
|
|
|
hwdesc->addr_buffer2 = cpu_to_le64(temp_dma);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
hwdesc->buffer3_length = cpu_to_le16(temp_len);
|
|
|
|
hwdesc->addr_buffer3 = cpu_to_le64(temp_dma);
|
|
|
|
break;
|
|
|
|
case 3:
|
2007-01-02 05:39:10 -05:00
|
|
|
hwdesc->buffer4_length = cpu_to_le16(temp_len);
|
2006-10-21 15:33:03 -04:00
|
|
|
hwdesc->addr_buffer4 = cpu_to_le64(temp_dma);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
frag++;
|
|
|
|
}
|
2008-03-17 22:59:50 -04:00
|
|
|
producer = get_next_index(producer, num_txd);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
/* might change opcode to TX_TCP_LSO */
|
|
|
|
netxen_tso_check(adapter, &hw->cmd_desc_head[saved_producer], skb);
|
|
|
|
|
|
|
|
/* For LSO, we need to copy the MAC/IP/TCP headers into
|
|
|
|
* the descriptor ring
|
|
|
|
*/
|
2006-12-04 12:23:25 -05:00
|
|
|
if (netxen_get_cmd_desc_opcode(&hw->cmd_desc_head[saved_producer])
|
|
|
|
== TX_TCP_LSO) {
|
2006-10-21 15:33:03 -04:00
|
|
|
int hdr_len, first_hdr_len, more_hdr;
|
|
|
|
hdr_len = hw->cmd_desc_head[saved_producer].total_hdr_length;
|
2006-12-04 12:23:25 -05:00
|
|
|
if (hdr_len > (sizeof(struct cmd_desc_type0) - 2)) {
|
|
|
|
first_hdr_len = sizeof(struct cmd_desc_type0) - 2;
|
2006-10-21 15:33:03 -04:00
|
|
|
more_hdr = 1;
|
|
|
|
} else {
|
|
|
|
first_hdr_len = hdr_len;
|
|
|
|
more_hdr = 0;
|
|
|
|
}
|
|
|
|
/* copy the MAC/IP/TCP headers to the cmd descriptor list */
|
|
|
|
hwdesc = &hw->cmd_desc_head[producer];
|
2007-12-26 13:23:58 -05:00
|
|
|
pbuf = &adapter->cmd_buf_arr[producer];
|
|
|
|
pbuf->skb = NULL;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
/* copy the first 64 bytes */
|
2006-12-04 12:23:25 -05:00
|
|
|
memcpy(((void *)hwdesc) + 2,
|
2006-10-21 15:33:03 -04:00
|
|
|
(void *)(skb->data), first_hdr_len);
|
2008-03-17 22:59:50 -04:00
|
|
|
producer = get_next_index(producer, num_txd);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
if (more_hdr) {
|
|
|
|
hwdesc = &hw->cmd_desc_head[producer];
|
2007-12-26 13:23:58 -05:00
|
|
|
pbuf = &adapter->cmd_buf_arr[producer];
|
|
|
|
pbuf->skb = NULL;
|
2006-10-21 15:33:03 -04:00
|
|
|
/* copy the next 64 bytes - should be enough except
|
|
|
|
* for pathological case
|
|
|
|
*/
|
2007-03-27 17:55:52 -04:00
|
|
|
skb_copy_from_linear_data_offset(skb, first_hdr_len,
|
|
|
|
hwdesc,
|
|
|
|
(hdr_len -
|
|
|
|
first_hdr_len));
|
2008-03-17 22:59:50 -04:00
|
|
|
producer = get_next_index(producer, num_txd);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
}
|
2007-04-20 10:55:26 -04:00
|
|
|
|
2008-03-17 22:59:50 -04:00
|
|
|
adapter->cmd_producer = producer;
|
2007-12-31 13:08:57 -05:00
|
|
|
adapter->stats.txbytes += skb->len;
|
2007-04-20 10:55:26 -04:00
|
|
|
|
2008-03-17 22:59:50 -04:00
|
|
|
netxen_nic_update_cmd_producer(adapter, adapter->cmd_producer);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2008-03-17 22:59:50 -04:00
|
|
|
adapter->stats.xmitcalled++;
|
2006-10-21 15:33:03 -04:00
|
|
|
netdev->trans_start = jiffies;
|
|
|
|
|
|
|
|
return NETDEV_TX_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void netxen_watchdog(unsigned long v)
|
|
|
|
{
|
|
|
|
struct netxen_adapter *adapter = (struct netxen_adapter *)v;
|
2006-12-04 12:23:25 -05:00
|
|
|
|
|
|
|
SCHEDULE_WORK(&adapter->watchdog_task);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void netxen_tx_timeout(struct net_device *netdev)
|
|
|
|
{
|
2007-04-20 10:52:37 -04:00
|
|
|
struct netxen_adapter *adapter = (struct netxen_adapter *)
|
|
|
|
netdev_priv(netdev);
|
|
|
|
SCHEDULE_WORK(&adapter->tx_timeout_task);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
2006-12-05 14:36:26 -05:00
|
|
|
static void netxen_tx_timeout_task(struct work_struct *work)
|
2006-10-21 15:33:03 -04:00
|
|
|
{
|
2007-11-23 21:23:36 -05:00
|
|
|
struct netxen_adapter *adapter =
|
2007-04-20 10:52:37 -04:00
|
|
|
container_of(work, struct netxen_adapter, tx_timeout_task);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
printk(KERN_ERR "%s %s: transmit timeout, resetting.\n",
|
2007-04-20 10:55:26 -04:00
|
|
|
netxen_nic_driver_name, adapter->netdev->name);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2008-03-17 22:59:50 -04:00
|
|
|
netxen_nic_disable_int(adapter);
|
|
|
|
napi_disable(&adapter->napi);
|
|
|
|
|
2007-04-20 10:55:26 -04:00
|
|
|
adapter->netdev->trans_start = jiffies;
|
2008-03-17 22:59:50 -04:00
|
|
|
|
|
|
|
napi_enable(&adapter->napi);
|
|
|
|
netxen_nic_enable_int(adapter);
|
2007-04-20 10:55:26 -04:00
|
|
|
netif_wake_queue(adapter->netdev);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
static inline void
|
|
|
|
netxen_handle_int(struct netxen_adapter *adapter)
|
2006-10-21 15:33:03 -04:00
|
|
|
{
|
2007-07-01 14:56:00 -04:00
|
|
|
netxen_nic_disable_int(adapter);
|
2008-03-17 22:59:49 -04:00
|
|
|
napi_schedule(&adapter->napi);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
2006-11-07 05:12:16 -05:00
|
|
|
irqreturn_t netxen_intr(int irq, void *data)
|
2006-10-21 15:33:03 -04:00
|
|
|
{
|
2007-10-19 03:14:03 -04:00
|
|
|
struct netxen_adapter *adapter = data;
|
2007-07-25 10:43:12 -04:00
|
|
|
u32 our_int = 0;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
our_int = readl(NETXEN_CRB_NORMALIZE(adapter, CRB_INT_VECTOR));
|
|
|
|
/* not our interrupt */
|
|
|
|
if ((our_int & (0x80 << adapter->portnum)) == 0)
|
|
|
|
return IRQ_NONE;
|
2007-07-25 10:43:12 -04:00
|
|
|
|
|
|
|
if (adapter->intr_scheme == INTR_SCHEME_PERPORT) {
|
|
|
|
/* claim interrupt */
|
2008-03-17 22:59:49 -04:00
|
|
|
writel(our_int & ~((u32)(0x80 << adapter->portnum)),
|
2007-07-25 10:43:12 -04:00
|
|
|
NETXEN_CRB_NORMALIZE(adapter, CRB_INT_VECTOR));
|
|
|
|
}
|
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
netxen_handle_int(adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
irqreturn_t netxen_msi_intr(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct netxen_adapter *adapter = data;
|
|
|
|
|
|
|
|
netxen_handle_int(adapter);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
static int netxen_nic_poll(struct napi_struct *napi, int budget)
|
2006-10-21 15:33:03 -04:00
|
|
|
{
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
struct netxen_adapter *adapter = container_of(napi, struct netxen_adapter, napi);
|
2008-03-17 22:59:49 -04:00
|
|
|
int tx_complete;
|
2006-10-21 15:33:03 -04:00
|
|
|
int ctx;
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
int work_done;
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
tx_complete = netxen_process_cmd_ring(adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
|
2006-12-04 12:18:00 -05:00
|
|
|
work_done = 0;
|
2006-10-21 15:33:03 -04:00
|
|
|
for (ctx = 0; ctx < MAX_RCV_CTX; ++ctx) {
|
|
|
|
/*
|
|
|
|
* Fairness issue. This will give undue weight to the
|
|
|
|
* receive context 0.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* To avoid starvation, we give each of our receivers,
|
|
|
|
* a fraction of the quota. Sometimes, it might happen that we
|
|
|
|
* have enough quota to process every packet, but since all the
|
|
|
|
* packets are on one context, it gets only half of the quota,
|
|
|
|
* and ends up not processing it.
|
|
|
|
*/
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
work_done += netxen_process_rcv_ring(adapter, ctx,
|
|
|
|
budget / MAX_RCV_CTX);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
2008-03-17 22:59:49 -04:00
|
|
|
if ((work_done < budget) && tx_complete) {
|
|
|
|
netif_rx_complete(adapter->netdev, &adapter->napi);
|
2006-10-21 15:33:03 -04:00
|
|
|
netxen_nic_enable_int(adapter);
|
|
|
|
}
|
|
|
|
|
[NET]: Make NAPI polling independent of struct net_device objects.
Several devices have multiple independant RX queues per net
device, and some have a single interrupt doorbell for several
queues.
In either case, it's easier to support layouts like that if the
structure representing the poll is independant from the net
device itself.
The signature of the ->poll() call back goes from:
int foo_poll(struct net_device *dev, int *budget)
to
int foo_poll(struct napi_struct *napi, int budget)
The caller is returned the number of RX packets processed (or
the number of "NAPI credits" consumed if you want to get
abstract). The callee no longer messes around bumping
dev->quota, *budget, etc. because that is all handled in the
caller upon return.
The napi_struct is to be embedded in the device driver private data
structures.
Furthermore, it is the driver's responsibility to disable all NAPI
instances in it's ->stop() device close handler. Since the
napi_struct is privatized into the driver's private data structures,
only the driver knows how to get at all of the napi_struct instances
it may have per-device.
With lots of help and suggestions from Rusty Russell, Roland Dreier,
Michael Chan, Jeff Garzik, and Jamal Hadi Salim.
Bug fixes from Thomas Graf, Roland Dreier, Peter Zijlstra,
Joseph Fannin, Scott Wood, Hans J. Koch, and Michael Chan.
[ Ported to current tree and all drivers converted. Integrated
Stephen's follow-on kerneldoc additions, and restored poll_list
handling to the old style to fix mutual exclusion issues. -DaveM ]
Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2007-10-03 19:41:36 -04:00
|
|
|
return work_done;
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
static void netxen_nic_poll_controller(struct net_device *netdev)
|
|
|
|
{
|
2007-04-20 10:52:37 -04:00
|
|
|
struct netxen_adapter *adapter = netdev_priv(netdev);
|
2006-10-21 15:33:03 -04:00
|
|
|
disable_irq(adapter->irq);
|
2006-11-07 05:12:16 -05:00
|
|
|
netxen_intr(adapter->irq, adapter);
|
2006-10-21 15:33:03 -04:00
|
|
|
enable_irq(adapter->irq);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static struct pci_driver netxen_driver = {
|
|
|
|
.name = netxen_nic_driver_name,
|
|
|
|
.id_table = netxen_pci_tbl,
|
|
|
|
.probe = netxen_nic_probe,
|
|
|
|
.remove = __devexit_p(netxen_nic_remove)
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Driver Registration on NetXen card */
|
|
|
|
|
|
|
|
static int __init netxen_init_module(void)
|
|
|
|
{
|
2008-01-24 05:06:46 -05:00
|
|
|
if ((netxen_workq = create_singlethread_workqueue("netxen")) == NULL)
|
2006-12-04 12:23:25 -05:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2007-01-22 09:52:53 -05:00
|
|
|
return pci_register_driver(&netxen_driver);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
module_init(netxen_init_module);
|
|
|
|
|
|
|
|
static void __exit netxen_exit_module(void)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Wait for some time to allow the dma to drain, if any.
|
|
|
|
*/
|
2007-07-02 00:07:57 -04:00
|
|
|
msleep(100);
|
2006-10-21 15:33:03 -04:00
|
|
|
pci_unregister_driver(&netxen_driver);
|
2007-02-21 09:37:06 -05:00
|
|
|
destroy_workqueue(netxen_workq);
|
2006-10-21 15:33:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
module_exit(netxen_exit_module);
|