vserver 1.9.5.x5
[linux-2.6.git] / drivers / net / typhoon.c
index ea5643a..5712cac 100644 (file)
@@ -1,6 +1,6 @@
 /* typhoon.c: A Linux Ethernet device driver for 3Com 3CR990 family of NICs */
 /*
-       Written 2002-2003 by David Dillow <dave@thedillows.org>
+       Written 2002-2004 by David Dillow <dave@thedillows.org>
        Based on code written 1998-2000 by Donald Becker <becker@scyld.com> and
        Linux 2.2.x driver by David P. McLean <davidpmclean@yahoo.com>.
 
        *) Waiting for a command response takes 8ms due to non-preemptable
                polling. Only significant for getting stats and creating
                SAs, but an ugly wart never the less.
-       *) I've not tested multicast. I think it works, but reports welcome.
+
+       TODO:
        *) Doesn't do IPSEC offloading. Yet. Keep yer pants on, it's coming.
+       *) Add more support for ethtool (especially for NIC stats)
+       *) Allow disabling of RX checksum offloading
+       *) Fix MAC changing to work while the interface is up
+               (Need to put commands on the TX ring, which changes
+               the locking)
+       *) Add in FCS to {rx,tx}_bytes, since the hardware doesn't. See
+               http://oss.sgi.com/cgi-bin/mesg.cgi?a=netdev&i=20031215152211.7003fe8e.rddunlap%40osdl.org
 */
 
 /* Set the copy breakpoint for the copy-only-tiny-frames scheme.
@@ -85,8 +93,8 @@ static const int multicast_filter_limit = 32;
 #define PKT_BUF_SZ             1536
 
 #define DRV_MODULE_NAME                "typhoon"
-#define DRV_MODULE_VERSION     "1.5.3"
-#define DRV_MODULE_RELDATE     "03/12/15"
+#define DRV_MODULE_VERSION     "1.5.4"
+#define DRV_MODULE_RELDATE     "04/09/09"
 #define PFX                    DRV_MODULE_NAME ": "
 #define ERR_PFX                        KERN_ERR PFX
 
@@ -107,13 +115,14 @@ static const int multicast_filter_limit = 32;
 #include <linux/ethtool.h>
 #include <linux/if_vlan.h>
 #include <linux/crc32.h>
+#include <linux/bitops.h>
 #include <asm/processor.h>
-#include <asm/bitops.h>
 #include <asm/io.h>
 #include <asm/uaccess.h>
 #include <linux/in6.h>
 #include <asm/checksum.h>
 #include <linux/version.h>
+#include <linux/dma-mapping.h>
 
 #include "typhoon.h"
 #include "typhoon-firmware.h"
@@ -279,7 +288,6 @@ struct typhoon {
        u16                     xcvr_select;
        u16                     wol_events;
        u32                     offload;
-       u32                     pci_state[16];
 
        /* unused stuff (future use) */
        int                     capabilities;
@@ -410,21 +418,22 @@ typhoon_reset(void __iomem *ioaddr, int wait_type)
 out:
        writel(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK);
        writel(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS);
-       udelay(100);
-       return err;
 
        /* The 3XP seems to need a little extra time to complete the load
         * of the sleep image before we can reliably boot it. Failure to
         * do this occasionally results in a hung adapter after boot in
         * typhoon_init_one() while trying to read the MAC address or
         * putting the card to sleep. 3Com's driver waits 5ms, but
-        * that seems to be overkill -- with a 50usec delay, it survives
-        * 35000 typhoon_init_one() calls, where it only make it 25-100
-        * without it.
-        *
-        * As it turns out, still occasionally getting a hung adapter,
-        * so I'm bumping it to 100us.
+        * that seems to be overkill. However, if we can sleep, we might
+        * as well give it that much time. Otherwise, we'll give it 500us,
+        * which should be enough (I've see it work well at 100us, but still
+        * saw occasional problems.)
         */
+       if(wait_type == WaitSleep)
+               msleep(5);
+       else
+               udelay(500);
+       return err;
 }
 
 static int
@@ -688,7 +697,7 @@ out:
 static void
 typhoon_vlan_rx_register(struct net_device *dev, struct vlan_group *grp)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        struct cmd_desc xp_cmd;
        int err;
 
@@ -726,7 +735,7 @@ typhoon_vlan_rx_register(struct net_device *dev, struct vlan_group *grp)
 static void
 typhoon_vlan_rx_kill_vid(struct net_device *dev, unsigned short vid)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        spin_lock_bh(&tp->state_lock);
        if(tp->vlgrp)
                tp->vlgrp->vlan_devices[vid] = NULL;
@@ -757,7 +766,7 @@ typhoon_tso_fill(struct sk_buff *skb, struct transmit_ring *txRing,
 static int
 typhoon_start_tx(struct sk_buff *skb, struct net_device *dev)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        struct transmit_ring *txRing;
        struct tx_desc *txd, *first_txd;
        dma_addr_t skb_dma;
@@ -908,7 +917,7 @@ typhoon_start_tx(struct sk_buff *skb, struct net_device *dev)
 static void
 typhoon_set_rx_mode(struct net_device *dev)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        struct cmd_desc xp_cmd;
        u32 mc_filter[2];
        u16 filter;
@@ -965,6 +974,9 @@ typhoon_do_get_stats(struct typhoon *tp)
 
        /* 3Com's Linux driver uses txMultipleCollisions as it's
         * collisions value, but there is some other collision info as well...
+        *
+        * The extra status reported would be a good candidate for
+        * ethtool_ops->get_{strings,stats}()
         */
        stats->tx_packets = le32_to_cpu(s->txPackets);
        stats->tx_bytes = le32_to_cpu(s->txBytes);
@@ -1002,7 +1014,7 @@ typhoon_do_get_stats(struct typhoon *tp)
 static struct net_device_stats *
 typhoon_get_stats(struct net_device *dev)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        struct net_device_stats *stats = &tp->stats;
        struct net_device_stats *saved = &tp->stats_saved;
 
@@ -1030,9 +1042,10 @@ typhoon_set_mac_address(struct net_device *dev, void *addr)
        return 0;
 }
 
-static inline void
-typhoon_ethtool_gdrvinfo(struct typhoon *tp, struct ethtool_drvinfo *info)
+static void
+typhoon_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
 {
+       struct typhoon *tp = netdev_priv(dev);
        struct pci_dev *pci_dev = tp->pdev;
        struct cmd_desc xp_cmd;
        struct resp_desc xp_resp[3];
@@ -1055,9 +1068,11 @@ typhoon_ethtool_gdrvinfo(struct typhoon *tp, struct ethtool_drvinfo *info)
        strcpy(info->bus_info, pci_name(pci_dev));
 }
 
-static inline void
-typhoon_ethtool_gset(struct typhoon *tp, struct ethtool_cmd *cmd)
+static int
+typhoon_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
 {
+       struct typhoon *tp = netdev_priv(dev);
+
        cmd->supported = SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
                                SUPPORTED_Autoneg;
 
@@ -1107,15 +1122,19 @@ typhoon_ethtool_gset(struct typhoon *tp, struct ethtool_cmd *cmd)
                cmd->autoneg = AUTONEG_DISABLE;
        cmd->maxtxpkt = 1;
        cmd->maxrxpkt = 1;
+
+       return 0;
 }
 
-static inline int
-typhoon_ethtool_sset(struct typhoon *tp, struct ethtool_cmd *cmd)
+static int
+typhoon_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
 {
+       struct typhoon *tp = netdev_priv(dev);
        struct cmd_desc xp_cmd;
        int xcvr;
        int err;
 
+       err = -EINVAL;
        if(cmd->autoneg == AUTONEG_ENABLE) {
                xcvr = TYPHOON_XCVR_AUTONEG;
        } else {
@@ -1125,23 +1144,23 @@ typhoon_ethtool_sset(struct typhoon *tp, struct ethtool_cmd *cmd)
                        else if(cmd->speed == SPEED_100)
                                xcvr = TYPHOON_XCVR_100HALF;
                        else
-                               return -EINVAL;
+                               goto out;
                } else if(cmd->duplex == DUPLEX_FULL) {
                        if(cmd->speed == SPEED_10)
                                xcvr = TYPHOON_XCVR_10FULL;
                        else if(cmd->speed == SPEED_100)
                                xcvr = TYPHOON_XCVR_100FULL;
                        else
-                               return -EINVAL;
+                               goto out;
                } else
-                       return -EINVAL;
+                       goto out;
        }
 
        INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_XCVR_SELECT);
        xp_cmd.parm1 = cpu_to_le16(xcvr);
        err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
        if(err < 0)
-               return err;
+               goto out;
 
        tp->xcvr_select = xcvr;
        if(cmd->autoneg == AUTONEG_ENABLE) {
@@ -1152,93 +1171,80 @@ typhoon_ethtool_sset(struct typhoon *tp, struct ethtool_cmd *cmd)
                tp->duplex = cmd->duplex;
        }
 
-       return 0;
+out:
+       return err;
 }
 
-static inline int
-typhoon_ethtool_ioctl(struct net_device *dev, void __user *useraddr)
+static void
+typhoon_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
-       u32 ethcmd;
-
-       if(copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
-               return -EFAULT;
-
-       switch (ethcmd) {
-       case ETHTOOL_GDRVINFO: {
-                       struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO };
-
-                       typhoon_ethtool_gdrvinfo(tp, &info);
-                       if(copy_to_user(useraddr, &info, sizeof(info)))
-                               return -EFAULT;
-                       return 0;
-               }
-       case ETHTOOL_GSET: {
-                       struct ethtool_cmd cmd = { ETHTOOL_GSET };
+       struct typhoon *tp = netdev_priv(dev);
+
+       wol->supported = WAKE_PHY | WAKE_MAGIC;
+       wol->wolopts = 0;
+       if(tp->wol_events & TYPHOON_WAKE_LINK_EVENT)
+               wol->wolopts |= WAKE_PHY;
+       if(tp->wol_events & TYPHOON_WAKE_MAGIC_PKT)
+               wol->wolopts |= WAKE_MAGIC;
+       memset(&wol->sopass, 0, sizeof(wol->sopass));
+}
 
-                       typhoon_ethtool_gset(tp, &cmd);
-                       if(copy_to_user(useraddr, &cmd, sizeof(cmd)))
-                               return -EFAULT;
-                       return 0;
-               }
-       case ETHTOOL_SSET: {
-                       struct ethtool_cmd cmd;
-                       if(copy_from_user(&cmd, useraddr, sizeof(cmd)))
-                               return -EFAULT;
+static int
+typhoon_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+       struct typhoon *tp = netdev_priv(dev);
 
-                       return typhoon_ethtool_sset(tp, &cmd);
-               }
-       case ETHTOOL_GLINK:{
-                       struct ethtool_value edata = { ETHTOOL_GLINK };
+       if(wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC))
+               return -EINVAL;
 
-                       edata.data = netif_carrier_ok(dev) ? 1 : 0;
-                       if(copy_to_user(useraddr, &edata, sizeof(edata)))
-                               return -EFAULT;
-                       return 0;
-               }
-       case ETHTOOL_GWOL: {
-                       struct ethtool_wolinfo wol = { ETHTOOL_GWOL };
-
-                       if(tp->wol_events & TYPHOON_WAKE_LINK_EVENT)
-                               wol.wolopts |= WAKE_PHY;
-                       if(tp->wol_events & TYPHOON_WAKE_MAGIC_PKT)
-                               wol.wolopts |= WAKE_MAGIC;
-                       if(copy_to_user(useraddr, &wol, sizeof(wol)))
-                               return -EFAULT;
-                       return 0;
-       }
-       case ETHTOOL_SWOL: {
-                       struct ethtool_wolinfo wol;
-
-                       if(copy_from_user(&wol, useraddr, sizeof(wol)))
-                               return -EFAULT;
-                       tp->wol_events = 0;
-                       if(wol.wolopts & WAKE_PHY)
-                               tp->wol_events |= TYPHOON_WAKE_LINK_EVENT;
-                       if(wol.wolopts & WAKE_MAGIC)
-                               tp->wol_events |= TYPHOON_WAKE_MAGIC_PKT;
-                       return 0;
-       }
-       default:
-               break;
-       }
+       tp->wol_events = 0;
+       if(wol->wolopts & WAKE_PHY)
+               tp->wol_events |= TYPHOON_WAKE_LINK_EVENT;
+       if(wol->wolopts & WAKE_MAGIC)
+               tp->wol_events |= TYPHOON_WAKE_MAGIC_PKT;
 
-       return -EOPNOTSUPP;
+       return 0;
 }
 
-static int
-typhoon_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+static u32
+typhoon_get_rx_csum(struct net_device *dev)
 {
-       switch (cmd) {
-       case SIOCETHTOOL:
-               return typhoon_ethtool_ioctl(dev, ifr->ifr_data);
-       default:
-               break;
-       }
+       /* For now, we don't allow turning off RX checksums.
+        */
+       return 1;
+}
 
-       return -EOPNOTSUPP;
+static void
+typhoon_get_ringparam(struct net_device *dev, struct ethtool_ringparam *ering)
+{
+       ering->rx_max_pending = RXENT_ENTRIES;
+       ering->rx_mini_max_pending = 0;
+       ering->rx_jumbo_max_pending = 0;
+       ering->tx_max_pending = TXLO_ENTRIES - 1;
+
+       ering->rx_pending = RXENT_ENTRIES;
+       ering->rx_mini_pending = 0;
+       ering->rx_jumbo_pending = 0;
+       ering->tx_pending = TXLO_ENTRIES - 1;
 }
 
+static struct ethtool_ops typhoon_ethtool_ops = {
+       .get_settings           = typhoon_get_settings,
+       .set_settings           = typhoon_set_settings,
+       .get_drvinfo            = typhoon_get_drvinfo,
+       .get_wol                = typhoon_get_wol,
+       .set_wol                = typhoon_set_wol,
+       .get_link               = ethtool_op_get_link,
+       .get_rx_csum            = typhoon_get_rx_csum,
+       .get_tx_csum            = ethtool_op_get_tx_csum,
+       .set_tx_csum            = ethtool_op_set_tx_csum,
+       .get_sg                 = ethtool_op_get_sg,
+       .set_sg                 = ethtool_op_set_sg,
+       .get_tso                = ethtool_op_get_tso,
+       .set_tso                = ethtool_op_set_tso,
+       .get_ringparam          = typhoon_get_ringparam,
+};
+
 static int
 typhoon_wait_interrupt(void __iomem *ioaddr)
 {
@@ -1681,8 +1687,7 @@ typhoon_rx(struct typhoon *tp, struct basic_ring *rxRing, volatile u32 * ready,
                skb = rxb->skb;
                dma_addr = rxb->dma_addr;
 
-               rxaddr += sizeof(struct rx_desc);
-               rxaddr %= RX_ENTRIES * sizeof(struct rx_desc);
+               typhoon_inc_rx_index(&rxaddr, 1);
 
                if(rx->flags & TYPHOON_RX_ERROR) {
                        typhoon_recycle_rx_skb(tp, idx);
@@ -1756,7 +1761,7 @@ typhoon_fill_free_ring(struct typhoon *tp)
 static int
 typhoon_poll(struct net_device *dev, int *total_budget)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        struct typhoon_indexes *indexes = tp->indexes;
        int orig_budget = *total_budget;
        int budget, work_done, done;
@@ -1884,7 +1889,7 @@ typhoon_sleep(struct typhoon *tp, int state, u16 events)
 
        pci_enable_wake(tp->pdev, state, 1);
        pci_disable_device(pdev);
-       return pci_set_power_state(pdev, state);
+       return pci_set_power_state(pdev, pci_choose_state(pdev, state));
 }
 
 static int
@@ -1893,8 +1898,8 @@ typhoon_wakeup(struct typhoon *tp, int wait_type)
        struct pci_dev *pdev = tp->pdev;
        void __iomem *ioaddr = tp->ioaddr;
 
-       pci_set_power_state(pdev, 0);
-       pci_restore_state(pdev, tp->pci_state);
+       pci_set_power_state(pdev, PCI_D0);
+       pci_restore_state(pdev);
 
        /* Post 2.x.x versions of the Sleep Image require a reset before
         * we can download the Runtime Image. But let's not make users of
@@ -2069,7 +2074,7 @@ typhoon_stop_runtime(struct typhoon *tp, int wait_type)
 static void
 typhoon_tx_timeout(struct net_device *dev)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
 
        if(typhoon_reset(tp->ioaddr, WaitNoSleep) < 0) {
                printk(KERN_WARNING "%s: could not reset in tx timeout\n",
@@ -2099,7 +2104,7 @@ truely_dead:
 static int
 typhoon_open(struct net_device *dev)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        int err;
 
        err = typhoon_wakeup(tp, WaitSleep);
@@ -2141,7 +2146,7 @@ out:
 static int
 typhoon_close(struct net_device *dev)
 {
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
 
        netif_stop_queue(dev);
 
@@ -2169,7 +2174,7 @@ static int
 typhoon_resume(struct pci_dev *pdev)
 {
        struct net_device *dev = pci_get_drvdata(pdev);
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
 
        /* If we're down, resume when we are upped.
         */
@@ -2201,7 +2206,7 @@ static int
 typhoon_suspend(struct pci_dev *pdev, u32 state)
 {
        struct net_device *dev = pci_get_drvdata(pdev);
-       struct typhoon *tp = (struct typhoon *) dev->priv;
+       struct typhoon *tp = netdev_priv(dev);
        struct cmd_desc xp_cmd;
 
        /* If we're down, we're already suspended.
@@ -2305,17 +2310,17 @@ typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
                goto error_out_dev;
        }
 
-       /* If we transitioned from D3->D0 in pci_enable_device(),
-        * we lost our configuration and need to restore it to the
-        * conditions at boot.
-        */
-       pci_restore_state(pdev, NULL);
+       err = pci_set_mwi(pdev);
+       if(err < 0) {
+               printk(ERR_PFX "%s: unable to set MWI\n", pci_name(pdev));
+               goto error_out_disable;
+       }
 
-       err = pci_set_dma_mask(pdev, 0xffffffffULL);
+       err = pci_set_dma_mask(pdev, DMA_32BIT_MASK);
        if(err < 0) {
                printk(ERR_PFX "%s: No usable DMA configuration\n",
                       pci_name(pdev));
-               goto error_out_dev;
+               goto error_out_mwi;
        }
 
        /* sanity checks, resource #1 is our mmio area
@@ -2325,25 +2330,22 @@ typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
                       "%s: region #1 not a PCI MMIO resource, aborting\n",
                       pci_name(pdev));
                err = -ENODEV;
-               goto error_out_dev;
+               goto error_out_mwi;
        }
        if(pci_resource_len(pdev, 1) < 128) {
                printk(ERR_PFX "%s: Invalid PCI MMIO region size, aborting\n",
                       pci_name(pdev));
                err = -ENODEV;
-               goto error_out_dev;
+               goto error_out_mwi;
        }
 
        err = pci_request_regions(pdev, "typhoon");
        if(err < 0) {
                printk(ERR_PFX "%s: could not request regions\n",
                       pci_name(pdev));
-               goto error_out_dev;
+               goto error_out_mwi;
        }
 
-       pci_set_master(pdev);
-       pci_set_mwi(pdev);
-
        /* map our MMIO region
         */
        ioaddr = pci_resource_start(pdev, 1);
@@ -2367,7 +2369,7 @@ typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        }
 
        dev->irq = pdev->irq;
-       tp = dev->priv;
+       tp = netdev_priv(dev);
        tp->shared = (struct typhoon_shared *) shared;
        tp->shared_dma = shared_dma;
        tp->pdev = pdev;
@@ -2377,7 +2379,7 @@ typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        tp->dev = dev;
 
        /* need to be able to restore PCI state after a suspend */
-       pci_save_state(pdev, tp->pci_state);
+       pci_save_state(pdev);
 
        /* Init sequence:
         * 1) Reset the adapter to clear any bad juju
@@ -2392,6 +2394,11 @@ typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
                goto error_out_dma;
        }
 
+       /* Now that we've reset the 3XP and are sure it's not going to
+        * write all over memory, enable bus mastering.
+        */
+       pci_set_master(pdev);
+
        /* dev->name is not valid until we register, but we need to
         * use some common routines to initialize the card. So that those
         * routines print the right name, we keep our oun pointer to the name
@@ -2465,9 +2472,9 @@ typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
        dev->watchdog_timeo     = TX_TIMEOUT;
        dev->get_stats          = typhoon_get_stats;
        dev->set_mac_address    = typhoon_set_mac_address;
-       dev->do_ioctl           = typhoon_ioctl;
        dev->vlan_rx_register   = typhoon_vlan_rx_register;
        dev->vlan_rx_kill_vid   = typhoon_vlan_rx_kill_vid;
+       SET_ETHTOOL_OPS(dev, &typhoon_ethtool_ops);
 
        /* We can handle scatter gather, up to 16 entries, and
         * we can do IP checksumming (only version 4, doh...)
@@ -2528,6 +2535,10 @@ error_out_remap:
        iounmap(ioaddr_mapped);
 error_out_regions:
        pci_release_regions(pdev);
+error_out_mwi:
+       pci_clear_mwi(pdev);
+error_out_disable:
+       pci_disable_device(pdev);
 error_out_dev:
        free_netdev(dev);
 error_out:
@@ -2538,16 +2549,17 @@ static void __devexit
 typhoon_remove_one(struct pci_dev *pdev)
 {
        struct net_device *dev = pci_get_drvdata(pdev);
-       struct typhoon *tp = (struct typhoon *) (dev->priv);
+       struct typhoon *tp = netdev_priv(dev);
 
        unregister_netdev(dev);
-       pci_set_power_state(pdev, 0);
-       pci_restore_state(pdev, tp->pci_state);
+       pci_set_power_state(pdev, PCI_D0);
+       pci_restore_state(pdev);
        typhoon_reset(tp->ioaddr, NoWait);
        iounmap(tp->ioaddr);
        pci_free_consistent(pdev, sizeof(struct typhoon_shared),
                            tp->shared, tp->shared_dma);
        pci_release_regions(pdev);
+       pci_clear_mwi(pdev);
        pci_disable_device(pdev);
        pci_set_drvdata(pdev, NULL);
        free_netdev(dev);