summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/3com
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-02 12:17:24 -0700
commit3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch)
treeb767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/net/ethernet/3com
downloadlinux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.tar.gz
linux-stable-3f1fe48a36b0b6722dc3fd421d93512bac138e9a.zip
Merge tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linuxgrafted
Pull io_uring fixes from Jens Axboe: - Fix a task_work add use-after-free with SQPOLL. The sqpoll thread could pop and complete the last request while io_req_normal_work_add() was still looking at them after the mpscq push. Use the same approach as DEFER_TASKRUN to protect from that, holding an RCU read lock across the add, and have exit wait for an RCU grace period for SQPOLL rings as well. - CQE32 ring fixes: correct the free entry check for 32b CQEs, zero the big_cqe for aux CQEs, and only post the dummy skip CQE on CQE_MIXED rings - Mark the source filter table as COW when cloning bpf filters, so registering another filter on the source doesn't modify the shared table in place - Initialize the task context before running the BPF loop - Requeue zcrx multishot receives stopped by a local resource - End a TX_TIMESTAMP multishot cmd when the CQ is full (lollipopkit) * tag 'io_uring-7.3-20261002' of git://git.kernel.org/pub/scm/linux/kernel/git/axboe/linux: io_uring: fix task_work add use-after-free with SQPOLL io_uring/cmd_net: end TX_TIMESTAMP multishot when the CQ is full io_uring/zcrx: requeue multishot receives stopped by a local resource io_uring: initialize task context before running the BPF loop io_uring: zero big_cqe for aux CQEs on CQE32 rings io_uring: fix free entry check for 32b CQEs on CQE32 rings io_uring: only post the dummy skip CQE on CQE_MIXED rings io_uring/bpf_filter: mark source as COW when cloning filters
Diffstat (limited to 'drivers/net/ethernet/3com')
-rw-r--r--drivers/net/ethernet/3com/3c509.c1543
-rw-r--r--drivers/net/ethernet/3com/3c59x.c3357
-rw-r--r--drivers/net/ethernet/3com/Kconfig71
-rw-r--r--drivers/net/ethernet/3com/Makefile8
-rw-r--r--drivers/net/ethernet/3com/typhoon.c2609
-rw-r--r--drivers/net/ethernet/3com/typhoon.h624
6 files changed, 8212 insertions, 0 deletions
diff --git a/drivers/net/ethernet/3com/3c509.c b/drivers/net/ethernet/3com/3c509.c
new file mode 100644
index 000000000..3438168be
--- /dev/null
+++ b/drivers/net/ethernet/3com/3c509.c
@@ -0,0 +1,1543 @@
+// SPDX-License-Identifier: GPL-2.0
+/* 3c509.c: A 3c509 EtherLink3 ethernet driver for linux. */
+/*
+ * Written 1993-2000 by Donald Becker.
+ *
+ * Copyright 1994-2000 by Donald Becker.
+ * Copyright 1993 United States Government as represented by the
+ * Director, National Security Agency. This software may be used and
+ * distributed according to the terms of the GNU General Public License,
+ * incorporated herein by reference.
+ *
+ * This driver is for the 3Com EtherLinkIII series.
+ *
+ * The author may be reached as becker@scyld.com, or C/O
+ * Scyld Computing Corporation
+ * 410 Severn Ave., Suite 210
+ * Annapolis MD 21403
+ *
+ * Known limitations:
+ * Because of the way 3c509 ISA detection works it's difficult to predict
+ * a priori which of several ISA-mode cards will be detected first.
+ *
+ * This driver does not use predictive interrupt mode, resulting in higher
+ * packet latency but lower overhead. If interrupts are disabled for an
+ * unusually long time it could also result in missed packets, but in
+ * practice this rarely happens.
+ *
+ *
+ * FIXES:
+ * Alan Cox: Removed the 'Unexpected interrupt' bug.
+ * Michael Meskes: Upgraded to Donald Becker's version 1.07.
+ * Alan Cox: Increased the eeprom delay. Regardless of
+ * what the docs say some people definitely
+ * get problems with lower (but in card spec)
+ * delays.
+ * v1.10 4/21/97 Fixed module code so that multiple cards may be
+ * detected, other cleanups. -djb
+ * Andrea Arcangeli: Upgraded to Donald Becker's version 1.12.
+ * Rick Payne: Fixed SMP race condition.
+ * v1.13 9/8/97 Made 'max_interrupt_work' an insmod-settable
+ * variable. -djb
+ * v1.14 10/15/97 Avoided waiting..discard message for fast
+ * machines. -djb
+ * v1.15 1/31/98 Faster recovery for Tx errors. -djb
+ * v1.16 2/3/98 Different ID port handling to avoid sound
+ * cards. -djb
+ * v1.18 12Mar2001 Andrew Morton
+ * - Avoid bogus detect of 3c590's (Andrzej Krzysztofowicz)
+ * - Reviewed against 1.18 from scyld.com
+ * v1.18a 17Nov2001 Jeff Garzik <jgarzik@pobox.com>
+ * - ethtool support.
+ * v1.18b 1Mar2002 Zwane Mwaikambo <zwane@commfireservices.com>
+ * - Power Management support.
+ * v1.18c 1Mar2002 David Ruggiero <jdr@farfalle.com>
+ * - Full duplex support.
+ * v1.19 16Oct2002 Zwane Mwaikambo <zwane@linuxpower.ca>
+ * - Additional ethtool features.
+ * v1.19a 28Oct2002 David Ruggiero <jdr@farfalle.com>
+ * - Increase *read_eeprom udelay to workaround oops with
+ * 2 cards.
+ * v1.19b 08Nov2002 Marc Zyngier <maz@wild-wind.fr.eu.org>
+ * - Introduce driver model for EISA cards.
+ * v1.20 04Feb2008 Ondrej Zary <linux@rainbow-software.org>
+ * - convert to isa_driver and pnp_driver and some
+ * cleanups.
+ */
+
+#define DRV_NAME "3c509"
+
+/* A few values that may be tweaked. */
+
+/* Time in jiffies before concluding the transmitter is hung. */
+#define TX_TIMEOUT (400 * HZ / 1000)
+
+#include <linux/bitops.h>
+#include <linux/delay.h> /* for udelay() */
+#include <linux/device.h>
+#include <linux/eisa.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/isa.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/pm.h>
+#include <linux/pnp.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/uaccess.h>
+
+#include <asm/irq.h>
+
+#ifdef EL3_DEBUG
+static int el3_debug = EL3_DEBUG;
+#else
+static int el3_debug = 2;
+#endif
+
+/* Used to do a global count of all the cards in the system. Must be
+ * a global variable so that the eisa probe routines can increment it.
+ */
+static int el3_cards;
+#define EL3_MAX_CARDS 8
+
+/* To minimize the size of the driver source I only define operating
+ * constants if they are used several times. You'll need the manual
+ * anyway if you want to understand driver details.
+ */
+/* Offsets from base I/O address. */
+#define EL3_DATA 0x00
+#define EL3_CMD 0x0e
+#define EL3_STATUS 0x0e
+#define EEPROM_READ 0x80
+
+#define EL3_IO_EXTENT 16
+
+#define EL3WINDOW(win_num) outw(SELECT_WINDOW + (win_num), ioaddr + EL3_CMD)
+
+/* The top five bits written to EL3_CMD are a command, the lower
+ * 11 bits are the parameter, if applicable.
+ */
+enum c509cmd {
+ TOTAL_RESET = 0 << 11,
+ SELECT_WINDOW = 1 << 11,
+ START_COAX = 2 << 11,
+ RX_DISABLE = 3 << 11,
+ RX_ENABLE = 4 << 11,
+ RX_RESET = 5 << 11,
+ RX_DISCARD = 8 << 11,
+ TX_ENABLE = 9 << 11,
+ TX_DISABLE = 10 << 11,
+ TX_RESET = 11 << 11,
+ FAKE_INTR = 12 << 11,
+ ACK_INTR = 13 << 11,
+ SET_INTR_ENB = 14 << 11,
+ SET_STATUS_ENB = 15 << 11,
+ SET_RX_FILTER = 16 << 11,
+ SET_RX_THRESHOLD = 17 << 11,
+ SET_TX_THRESHOLD = 18 << 11,
+ SET_TX_START = 19 << 11,
+ STATS_ENABLE = 21 << 11,
+ STATS_DISABLE = 22 << 11,
+ STOP_COAX = 23 << 11,
+ POWER_UP = 27 << 11,
+ POWER_DOWN = 28 << 11,
+ POWER_AUTO = 29 << 11,
+};
+
+enum c509status {
+ INT_LATCH = 0x0001,
+ ADAPTER_FAILURE = 0x0002,
+ TX_COMPLETE = 0x0004,
+ TX_AVAILABLE = 0x0008,
+ RX_COMPLETE = 0x0010,
+ RX_EARLY = 0x0020,
+ INT_REQ = 0x0040,
+ STATS_FULL = 0x0080,
+ CMD_BUSY = 0x1000,
+};
+
+/* The SET_RX_FILTER command accepts the following classes: */
+enum rx_filter {
+ RX_STATION = 1,
+ RX_MULTICAST = 2,
+ RX_BROADCAST = 4,
+ RX_PROM = 8,
+};
+
+/* Register window 1 offsets, the window used in normal operation. */
+#define TX_FIFO 0x00
+#define RX_FIFO 0x00
+#define RX_STATUS 0x08
+#define TX_STATUS 0x0B
+#define TX_FREE 0x0C /* Remaining free bytes in Tx buffer. */
+
+#define WN0_CONF_CTRL 0x04 /* Window 0: Configuration control register. */
+#define WN0_ADDR_CONF 0x06 /* Window 0: Address configuration register. */
+#define WN0_IRQ 0x08 /* Window 0: Set IRQ line in bits 12-15. */
+#define WN4_MEDIA 0x0A /* Window 4: Various transcvr/media bits. */
+#define MEDIA_TP 0x00C0 /* Enable link beat and jabber for 10baseT. */
+#define WN4_NETDIAG 0x06 /* Window 4: Net diagnostic. */
+#define FD_ENABLE 0x8000 /* Enable full-duplex ("external loopback"). */
+
+/*
+ * Must be a power of two (we use a binary and in the
+ * circular queue).
+ */
+#define SKB_QUEUE_SIZE 64
+
+enum el3_cardtype { EL3_ISA, EL3_PNP, EL3_EISA };
+
+struct el3_private {
+ /* for device access */
+ spinlock_t lock;
+ /* skb send-queue */
+ int head, size;
+ struct sk_buff *queue[SKB_QUEUE_SIZE];
+ enum el3_cardtype type;
+};
+
+static int id_port;
+static int current_tag;
+static struct net_device *el3_devs[EL3_MAX_CARDS];
+
+/* Parameters that may be passed into the module. */
+static int debug = -1;
+static int irq[] = {-1, -1, -1, -1, -1, -1, -1, -1};
+/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
+static int max_interrupt_work = 10;
+#ifdef CONFIG_PNP
+static int nopnp;
+#endif
+
+static int el3_common_init(struct net_device *dev);
+static void el3_common_remove(struct net_device *dev);
+static ushort id_read_eeprom(int index);
+static ushort read_eeprom(int ioaddr, int index);
+static int el3_open(struct net_device *dev);
+static netdev_tx_t el3_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static irqreturn_t el3_interrupt(int irq, void *dev_id);
+static void update_stats(struct net_device *dev);
+static struct net_device_stats *el3_get_stats(struct net_device *dev);
+static int el3_rx(struct net_device *dev);
+static int el3_close(struct net_device *dev);
+static void set_multicast_list(struct net_device *dev);
+static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue);
+static void el3_down(struct net_device *dev);
+static void el3_up(struct net_device *dev);
+static const struct ethtool_ops ethtool_ops;
+#ifdef CONFIG_PM
+static int el3_suspend(struct device *, pm_message_t);
+static int el3_resume(struct device *);
+#else
+#define el3_suspend NULL
+#define el3_resume NULL
+#endif
+
+/* Generic device remove for all device types. */
+static int el3_device_remove(struct device *device);
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void el3_poll_controller(struct net_device *dev);
+#endif
+
+/* Return 0 on success, 1 on error, 2 when found already detected PnP card. */
+static int el3_isa_id_sequence(__be16 *phys_addr)
+{
+ short lrs_state = 0xff;
+ int i;
+
+ /* ISA boards are detected by sending the ID sequence to the
+ * ID_PORT. We find cards past the first by setting the 'current_tag'
+ * on cards as they are found. Cards with their tag set will not
+ * respond to subsequent ID sequences.
+ */
+ outb(0x00, id_port);
+ outb(0x00, id_port);
+ for (i = 0; i < 255; i++) {
+ outb(lrs_state, id_port);
+ lrs_state <<= 1;
+ lrs_state = lrs_state & 0x100 ? lrs_state ^ 0xcf : lrs_state;
+ }
+ /* For the first probe, clear all board's tag registers. */
+ if (current_tag == 0)
+ outb(0xd0, id_port);
+ else /* Otherwise kill off already-found boards. */
+ outb(0xd8, id_port);
+ if (id_read_eeprom(7) != 0x6d50)
+ return 1;
+ /* Read in EEPROM data, which does contention-select.
+ * Only the lowest address board will stay "on-line".
+ * 3Com got the byte order backwards.
+ */
+ for (i = 0; i < 3; i++)
+ phys_addr[i] = htons(id_read_eeprom(i));
+#ifdef CONFIG_PNP
+ if (!nopnp) {
+ /* The ISA PnP 3c509 cards respond to the ID sequence too.
+ * This check is needed in order not to register them twice.
+ */
+ for (i = 0; i < el3_cards; i++) {
+ struct el3_private *lp = netdev_priv(el3_devs[i]);
+
+ if (lp->type == EL3_PNP &&
+ ether_addr_equal((u8 *)phys_addr,
+ el3_devs[i]->dev_addr)) {
+ if (el3_debug > 3)
+ pr_debug("3c509 with address %02x %02x %02x %02x %02x %02x was found by ISAPnP\n",
+ phys_addr[0] & 0xff,
+ phys_addr[0] >> 8,
+ phys_addr[1] & 0xff,
+ phys_addr[1] >> 8,
+ phys_addr[2] & 0xff,
+ phys_addr[2] >> 8);
+ /* Set the adaptor tag so that the next card
+ * can be found.
+ */
+ outb(0xd0 + ++current_tag, id_port);
+ return 2;
+ }
+ }
+ }
+#endif /* CONFIG_PNP */
+ return 0;
+}
+
+static void el3_dev_fill(struct net_device *dev, __be16 *phys_addr, int ioaddr,
+ int irq, int if_port, enum el3_cardtype type)
+{
+ struct el3_private *lp = netdev_priv(dev);
+
+ eth_hw_addr_set(dev, (u8 *)phys_addr);
+ dev->base_addr = ioaddr;
+ dev->irq = irq;
+ dev->if_port = if_port;
+ lp->type = type;
+}
+
+static int el3_isa_match(struct device *pdev, unsigned int ndev)
+{
+ int ioaddr, isa_irq, if_port, err;
+ struct net_device *dev;
+ unsigned int iobase;
+ __be16 phys_addr[3];
+
+ while ((err = el3_isa_id_sequence(phys_addr)) == 2)
+ ; /* Skip to next card when PnP card found */
+ if (err == 1)
+ return 0;
+
+ iobase = id_read_eeprom(8);
+ if_port = iobase >> 14;
+ ioaddr = 0x200 + ((iobase & 0x1f) << 4);
+ if (irq[el3_cards] > 1 && irq[el3_cards] < 16)
+ isa_irq = irq[el3_cards];
+ else
+ isa_irq = id_read_eeprom(9) >> 12;
+
+ dev = alloc_etherdev(sizeof(struct el3_private));
+ if (!dev)
+ return -ENOMEM;
+
+ SET_NETDEV_DEV(dev, pdev);
+
+ if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-isa")) {
+ free_netdev(dev);
+ return 0;
+ }
+
+ /* Set the adaptor tag so that the next card can be found. */
+ outb(0xd0 + ++current_tag, id_port);
+
+ /* Activate the adaptor at the EEPROM location. */
+ outb((ioaddr >> 4) | 0xe0, id_port);
+
+ EL3WINDOW(0);
+ if (inw(ioaddr) != 0x6d50) {
+ free_netdev(dev);
+ return 0;
+ }
+
+ /* Free the interrupt so that some other card can use it. */
+ outw(0x0f00, ioaddr + WN0_IRQ);
+
+ el3_dev_fill(dev, phys_addr, ioaddr, isa_irq, if_port, EL3_ISA);
+ dev_set_drvdata(pdev, dev);
+ if (el3_common_init(dev)) {
+ free_netdev(dev);
+ return 0;
+ }
+
+ el3_devs[el3_cards++] = dev;
+ return 1;
+}
+
+static void el3_isa_remove(struct device *pdev, unsigned int ndev)
+{
+ el3_device_remove(pdev);
+ dev_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM
+static int el3_isa_suspend(struct device *dev, unsigned int n,
+ pm_message_t state)
+{
+ current_tag = 0;
+ return el3_suspend(dev, state);
+}
+
+static int el3_isa_resume(struct device *dev, unsigned int n)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ int ioaddr = ndev->base_addr, err;
+ __be16 phys_addr[3];
+
+ while ((err = el3_isa_id_sequence(phys_addr)) == 2)
+ ; /* Skip to next card when PnP card found */
+ if (err == 1)
+ return 0;
+ /* Set the adaptor tag so that the next card can be found. */
+ outb(0xd0 + ++current_tag, id_port);
+ /* Enable the card */
+ outb((ioaddr >> 4) | 0xe0, id_port);
+ EL3WINDOW(0);
+ if (inw(ioaddr) != 0x6d50)
+ return 1;
+ /* Free the interrupt so that some other card can use it. */
+ outw(0x0f00, ioaddr + WN0_IRQ);
+ return el3_resume(dev);
+}
+#endif
+
+static struct isa_driver el3_isa_driver = {
+ .match = el3_isa_match,
+ .remove = el3_isa_remove,
+#ifdef CONFIG_PM
+ .suspend = el3_isa_suspend,
+ .resume = el3_isa_resume,
+#endif
+ .driver = {
+ .name = "3c509"
+ },
+};
+
+static int isa_registered;
+
+#ifdef CONFIG_PNP
+static const struct pnp_device_id el3_pnp_ids[] = {
+ { .id = "TCM5090" }, /* 3Com Etherlink III (TP) */
+ { .id = "TCM5091" }, /* 3Com Etherlink III */
+ { .id = "TCM5094" }, /* 3Com Etherlink III (combo) */
+ { .id = "TCM5095" }, /* 3Com Etherlink III (TPO) */
+ { .id = "TCM5098" }, /* 3Com Etherlink III (TPC) */
+ { .id = "PNP80f7" }, /* 3Com Etherlink III compatible */
+ { .id = "PNP80f8" }, /* 3Com Etherlink III compatible */
+ { }
+};
+MODULE_DEVICE_TABLE(pnp, el3_pnp_ids);
+
+static int el3_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id)
+{
+ struct net_device *dev = NULL;
+ int ioaddr, irq, if_port;
+ __be16 phys_addr[3];
+ short i;
+ int err;
+
+ ioaddr = pnp_port_start(pdev, 0);
+ if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509-pnp"))
+ return -EBUSY;
+ irq = pnp_irq(pdev, 0);
+ EL3WINDOW(0);
+ for (i = 0; i < 3; i++)
+ phys_addr[i] = htons(read_eeprom(ioaddr, i));
+ if_port = read_eeprom(ioaddr, 8) >> 14;
+ dev = alloc_etherdev(sizeof(struct el3_private));
+ if (!dev) {
+ release_region(ioaddr, EL3_IO_EXTENT);
+ return -ENOMEM;
+ }
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
+ el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_PNP);
+ pnp_set_drvdata(pdev, dev);
+ err = el3_common_init(dev);
+
+ if (err) {
+ pnp_set_drvdata(pdev, NULL);
+ free_netdev(dev);
+ return err;
+ }
+
+ el3_devs[el3_cards++] = dev;
+ return 0;
+}
+
+static void el3_pnp_remove(struct pnp_dev *pdev)
+{
+ el3_common_remove(pnp_get_drvdata(pdev));
+ pnp_set_drvdata(pdev, NULL);
+}
+
+#ifdef CONFIG_PM
+static int el3_pnp_suspend(struct pnp_dev *pdev, pm_message_t state)
+{
+ return el3_suspend(&pdev->dev, state);
+}
+
+static int el3_pnp_resume(struct pnp_dev *pdev)
+{
+ return el3_resume(&pdev->dev);
+}
+#endif
+
+static struct pnp_driver el3_pnp_driver = {
+ .name = "3c509",
+ .id_table = el3_pnp_ids,
+ .probe = el3_pnp_probe,
+ .remove = el3_pnp_remove,
+#ifdef CONFIG_PM
+ .suspend = el3_pnp_suspend,
+ .resume = el3_pnp_resume,
+#endif
+};
+
+static int pnp_registered;
+#endif /* CONFIG_PNP */
+
+#ifdef CONFIG_EISA
+static const struct eisa_device_id el3_eisa_ids[] = {
+ { "TCM5090" },
+ { "TCM5091" },
+ { "TCM5092" },
+ { "TCM5093" },
+ { "TCM5094" },
+ { "TCM5095" },
+ { "TCM5098" },
+ { "" }
+};
+MODULE_DEVICE_TABLE(eisa, el3_eisa_ids);
+
+static int el3_eisa_probe(struct device *device);
+
+static struct eisa_driver el3_eisa_driver = {
+ .id_table = el3_eisa_ids,
+ .driver = {
+ .name = "3c579",
+ .probe = el3_eisa_probe,
+ .remove = el3_device_remove,
+ .suspend = el3_suspend,
+ .resume = el3_resume,
+ }
+};
+
+static int eisa_registered;
+#endif
+
+static const struct net_device_ops netdev_ops = {
+ .ndo_open = el3_open,
+ .ndo_stop = el3_close,
+ .ndo_start_xmit = el3_start_xmit,
+ .ndo_get_stats = el3_get_stats,
+ .ndo_set_rx_mode = set_multicast_list,
+ .ndo_tx_timeout = el3_tx_timeout,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = el3_poll_controller,
+#endif
+};
+
+static int el3_common_init(struct net_device *dev)
+{
+ static const char *const if_names[] = {
+ "10baseT", "AUI", "undefined", "BNC"
+ };
+ struct el3_private *lp = netdev_priv(dev);
+ int err;
+
+ spin_lock_init(&lp->lock);
+
+ if (dev->mem_start & 0x05) { /* xcvr codes 1/3/4/12 */
+ dev->if_port = (dev->mem_start & 0x0f);
+ } else { /* xcvr codes 0/8 */
+ /* use eeprom value, but save user's full-duplex selection */
+ dev->if_port |= (dev->mem_start & 0x08);
+ }
+
+ /* The EL3-specific entries in the device structure. */
+ dev->netdev_ops = &netdev_ops;
+ dev->watchdog_timeo = TX_TIMEOUT;
+ dev->ethtool_ops = &ethtool_ops;
+
+ err = register_netdev(dev);
+ if (err) {
+ pr_err("Failed to register 3c5x9 at %#3.3lx, IRQ %d.\n",
+ dev->base_addr, dev->irq);
+ release_region(dev->base_addr, EL3_IO_EXTENT);
+ return err;
+ }
+
+ pr_info("%s: 3c5x9 found at %#3.3lx, %s port, address %pM, IRQ %d.\n",
+ dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)],
+ dev->dev_addr, dev->irq);
+
+ return 0;
+}
+
+static void el3_common_remove(struct net_device *dev)
+{
+ unregister_netdev(dev);
+ release_region(dev->base_addr, EL3_IO_EXTENT);
+ free_netdev(dev);
+}
+
+#ifdef CONFIG_EISA
+static int el3_eisa_probe(struct device *device)
+{
+ struct net_device *dev = NULL;
+ struct eisa_device *edev;
+ int ioaddr, irq, if_port;
+ __be16 phys_addr[3];
+ short i;
+ int err;
+
+ /* Yeepee, The driver framework is calling us ! */
+ edev = to_eisa_device(device);
+ ioaddr = edev->base_addr;
+
+ if (!request_region(ioaddr, EL3_IO_EXTENT, "3c579-eisa"))
+ return -EBUSY;
+
+ /* Change the register set to the configuration window 0. */
+ outw(SELECT_WINDOW | 0, ioaddr + 0xC80 + EL3_CMD);
+
+ irq = inw(ioaddr + WN0_IRQ) >> 12;
+ if_port = inw(ioaddr + 6) >> 14;
+ for (i = 0; i < 3; i++)
+ phys_addr[i] = htons(read_eeprom(ioaddr, i));
+
+ /* Restore the "Product ID" to the EEPROM read register. */
+ read_eeprom(ioaddr, 3);
+
+ dev = alloc_etherdev(sizeof(struct el3_private));
+ if (!dev) {
+ release_region(ioaddr, EL3_IO_EXTENT);
+ return -ENOMEM;
+ }
+
+ SET_NETDEV_DEV(dev, device);
+
+ el3_dev_fill(dev, phys_addr, ioaddr, irq, if_port, EL3_EISA);
+ eisa_set_drvdata(edev, dev);
+ err = el3_common_init(dev);
+
+ if (err) {
+ eisa_set_drvdata(edev, NULL);
+ free_netdev(dev);
+ return err;
+ }
+
+ el3_devs[el3_cards++] = dev;
+ return 0;
+}
+#endif
+
+/* This remove works for all device types.
+ *
+ * The net dev must be stored in the driver data field.
+ */
+static int el3_device_remove(struct device *device)
+{
+ struct net_device *dev;
+
+ dev = dev_get_drvdata(device);
+
+ el3_common_remove(dev);
+ return 0;
+}
+
+/* Read a word from the EEPROM using the regular EEPROM access register.
+ * Assume that we are in register window zero.
+ */
+static ushort read_eeprom(int ioaddr, int index)
+{
+ outw(EEPROM_READ + index, ioaddr + 10);
+ /* Pause for at least 162 us for the read to take place.
+ * Some chips seem to require much longer.
+ */
+ mdelay(2);
+ return inw(ioaddr + 12);
+}
+
+/* Read a word from the EEPROM when in the ISA ID probe state. */
+static ushort id_read_eeprom(int index)
+{
+ int bit, word = 0;
+
+ /* Issue read command, and pause for at least 162 us for it to
+ * complete. Assume extra-fast 16MHz bus.
+ */
+ outb(EEPROM_READ + index, id_port);
+
+ /* Pause for at least 162 us for the read to take place.
+ * Some chips seem to require much longer.
+ */
+ mdelay(4);
+
+ for (bit = 15; bit >= 0; bit--)
+ word = (word << 1) + (inb(id_port) & 0x01);
+
+ if (el3_debug > 3)
+ pr_debug(" 3c509 EEPROM word %d %#4.4x.\n", index, word);
+
+ return word;
+}
+
+static int el3_open(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+ int i;
+
+ outw(TX_RESET, ioaddr + EL3_CMD);
+ outw(RX_RESET, ioaddr + EL3_CMD);
+ outw(SET_STATUS_ENB | 0x00, ioaddr + EL3_CMD);
+
+ i = request_irq(dev->irq, el3_interrupt, 0, dev->name, dev);
+ if (i)
+ return i;
+
+ EL3WINDOW(0);
+ if (el3_debug > 3)
+ pr_debug("%s: Opening, IRQ %d status@%x %4.4x.\n",
+ dev->name, dev->irq,
+ ioaddr + EL3_STATUS, inw(ioaddr + EL3_STATUS));
+
+ el3_up(dev);
+
+ if (el3_debug > 3)
+ pr_debug("%s: Opened 3c509 IRQ %d status %4.4x.\n",
+ dev->name, dev->irq, inw(ioaddr + EL3_STATUS));
+
+ return 0;
+}
+
+static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue)
+{
+ int ioaddr = dev->base_addr;
+
+ /* Transmitter timeout, serious problems. */
+ pr_warn("%s: transmit timed out, Tx_status %2.2x status %4.4x Tx FIFO room %d\n",
+ dev->name, inb(ioaddr + TX_STATUS), inw(ioaddr + EL3_STATUS),
+ inw(ioaddr + TX_FREE));
+ dev->stats.tx_errors++;
+ netif_trans_update(dev); /* prevent tx timeout */
+ /* Issue TX_RESET and TX_START commands. */
+ outw(TX_RESET, ioaddr + EL3_CMD);
+ outw(TX_ENABLE, ioaddr + EL3_CMD);
+ netif_wake_queue(dev);
+}
+
+static netdev_tx_t el3_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct el3_private *lp = netdev_priv(dev);
+ int ioaddr = dev->base_addr;
+ unsigned long flags;
+
+ netif_stop_queue(dev);
+
+ dev->stats.tx_bytes += skb->len;
+
+ if (el3_debug > 4) {
+ pr_debug("%s: el3_start_xmit(length = %u) called, status %4.4x.\n",
+ dev->name, skb->len, inw(ioaddr + EL3_STATUS));
+ }
+ /*
+ * We lock the driver against other processors. Note
+ * we don't need to lock versus the IRQ as we suspended
+ * that. This means that we lose the ability to take
+ * an RX during a TX upload. That sucks a bit with SMP
+ * on an original 3c509 (2K buffer).
+ *
+ * Using disable_irq stops us crapping on other
+ * time sensitive devices.
+ */
+
+ spin_lock_irqsave(&lp->lock, flags);
+
+ /* Put out the doubleword header... */
+ outw(skb->len, ioaddr + TX_FIFO);
+ outw(0x00, ioaddr + TX_FIFO);
+ /* ... and the packet rounded to a doubleword. */
+ outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2);
+
+ if (inw(ioaddr + TX_FREE) > 1536) {
+ netif_start_queue(dev);
+ } else {
+ /* Interrupt us when the FIFO has room for max-sized packet. */
+ outw(SET_TX_THRESHOLD + 1536, ioaddr + EL3_CMD);
+ }
+
+ spin_unlock_irqrestore(&lp->lock, flags);
+
+ dev_consume_skb_any(skb);
+
+ /* Clear the Tx status stack. */
+ {
+ short tx_status;
+ int i = 4;
+
+ while (--i > 0 && (tx_status = inb(ioaddr + TX_STATUS)) > 0) {
+ if (tx_status & 0x38)
+ dev->stats.tx_aborted_errors++;
+ if (tx_status & 0x30)
+ outw(TX_RESET, ioaddr + EL3_CMD);
+ if (tx_status & 0x3C)
+ outw(TX_ENABLE, ioaddr + EL3_CMD);
+ /* Pop the status stack. */
+ outb(0x00, ioaddr + TX_STATUS);
+ }
+ }
+ return NETDEV_TX_OK;
+}
+
+/* The EL3 interrupt handler. */
+static irqreturn_t el3_interrupt(int irq, void *dev_id)
+{
+ struct net_device *dev = dev_id;
+ int i = max_interrupt_work;
+ struct el3_private *lp;
+ int ioaddr, status;
+
+ lp = netdev_priv(dev);
+ spin_lock(&lp->lock);
+
+ ioaddr = dev->base_addr;
+
+ if (el3_debug > 4) {
+ status = inw(ioaddr + EL3_STATUS);
+ pr_debug("%s: interrupt, status %4.4x.\n", dev->name, status);
+ }
+
+ while ((status = inw(ioaddr + EL3_STATUS)) &
+ (INT_LATCH | RX_COMPLETE | STATS_FULL)) {
+
+ if (status & RX_COMPLETE)
+ el3_rx(dev);
+
+ if (status & TX_AVAILABLE) {
+ if (el3_debug > 5)
+ pr_debug(" TX room bit was handled.\n");
+ /* There's room in the FIFO for a full-sized packet. */
+ outw(ACK_INTR | TX_AVAILABLE, ioaddr + EL3_CMD);
+ netif_wake_queue(dev);
+ }
+ if (status &
+ (ADAPTER_FAILURE | RX_EARLY | STATS_FULL | TX_COMPLETE)) {
+ /* Handle all uncommon interrupts. */
+ if (status & STATS_FULL) {
+ /* Empty statistics. */
+ update_stats(dev);
+ }
+ if (status & RX_EARLY) {
+ /* Rx early is unused. */
+ el3_rx(dev);
+ outw(ACK_INTR | RX_EARLY, ioaddr + EL3_CMD);
+ }
+ if (status & TX_COMPLETE) {
+ /* Really Tx error. */
+ short tx_status;
+ int i = 4;
+
+ while (--i > 0 &&
+ ((tx_status = inb(ioaddr + TX_STATUS))
+ > 0)) {
+ if (tx_status & 0x38)
+ dev->stats.tx_aborted_errors++;
+ if (tx_status & 0x30)
+ outw(TX_RESET,
+ ioaddr + EL3_CMD);
+ if (tx_status & 0x3C)
+ outw(TX_ENABLE,
+ ioaddr + EL3_CMD);
+ /* Pop the status stack. */
+ outb(0x00, ioaddr + TX_STATUS);
+ }
+ }
+ if (status & ADAPTER_FAILURE) {
+ /* Adapter failure requires Rx reset
+ * and reinit.
+ */
+ outw(RX_RESET, ioaddr + EL3_CMD);
+ /* Set the Rx filter to the current state. */
+ outw((SET_RX_FILTER | RX_STATION |
+ RX_BROADCAST |
+ (dev->flags & IFF_ALLMULTI ?
+ RX_MULTICAST : 0) |
+ (dev->flags & IFF_PROMISC ?
+ RX_PROM : 0)),
+ ioaddr + EL3_CMD);
+ /* Re-enable the receiver. */
+ outw(RX_ENABLE, ioaddr + EL3_CMD);
+ outw(ACK_INTR | ADAPTER_FAILURE,
+ ioaddr + EL3_CMD);
+ }
+ }
+
+ if (--i < 0) {
+ pr_err("%s: Infinite loop in interrupt, status %4.4x.\n",
+ dev->name, status);
+ /* Clear all interrupts. */
+ outw(ACK_INTR | 0xFF, ioaddr + EL3_CMD);
+ break;
+ }
+ /* Acknowledge the IRQ. */
+ outw(ACK_INTR | INT_REQ | INT_LATCH, ioaddr + EL3_CMD);
+ }
+
+ if (el3_debug > 4) {
+ pr_debug("%s: exiting interrupt, status %4.4x.\n", dev->name,
+ inw(ioaddr + EL3_STATUS));
+ }
+ spin_unlock(&lp->lock);
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+/*
+ * Polling receive - used by netconsole and other diagnostic tools
+ * to allow network i/o with interrupts disabled.
+ */
+static void el3_poll_controller(struct net_device *dev)
+{
+ disable_irq(dev->irq);
+ el3_interrupt(dev->irq, dev);
+ enable_irq(dev->irq);
+}
+#endif
+
+static struct net_device_stats *el3_get_stats(struct net_device *dev)
+{
+ struct el3_private *lp = netdev_priv(dev);
+ unsigned long flags;
+
+ /* This is fast enough not to bother with disable IRQ stuff. */
+ spin_lock_irqsave(&lp->lock, flags);
+ update_stats(dev);
+ spin_unlock_irqrestore(&lp->lock, flags);
+ return &dev->stats;
+}
+
+/* Update statistics. We change to register window 6, so this should be run
+ * single-threaded if the device is active. This is expected to be a rare
+ * operation, and it's simpler for the rest of the driver to assume that
+ * window 1 is always valid rather than use a special window-state variable.
+ */
+static void update_stats(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+
+ if (el3_debug > 5)
+ pr_debug(" Updating the statistics.\n");
+ /* Turn off statistics updates while reading. */
+ outw(STATS_DISABLE, ioaddr + EL3_CMD);
+ /* Switch to the stats window, and read everything. */
+ EL3WINDOW(6);
+ dev->stats.tx_carrier_errors += inb(ioaddr + 0);
+ dev->stats.tx_heartbeat_errors += inb(ioaddr + 1);
+ /* Multiple collisions. */ inb(ioaddr + 2);
+ dev->stats.collisions += inb(ioaddr + 3);
+ dev->stats.tx_window_errors += inb(ioaddr + 4);
+ dev->stats.rx_fifo_errors += inb(ioaddr + 5);
+ dev->stats.tx_packets += inb(ioaddr + 6);
+ /* Rx packets */ inb(ioaddr + 7);
+ /* Tx deferrals */ inb(ioaddr + 8);
+ inw(ioaddr + 10); /* Total Rx and Tx octets. */
+ inw(ioaddr + 12);
+
+ /* Back to window 1, and turn statistics back on. */
+ EL3WINDOW(1);
+ outw(STATS_ENABLE, ioaddr + EL3_CMD);
+}
+
+static int el3_rx(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+ short rx_status;
+
+ if (el3_debug > 5)
+ pr_debug(" In rx_packet(), status %4.4x, rx_status %4.4x.\n",
+ inw(ioaddr + EL3_STATUS), inw(ioaddr + RX_STATUS));
+ while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) {
+ if (rx_status & 0x4000) {
+ /* Error, update stats. */
+ short error = rx_status & 0x3800;
+
+ outw(RX_DISCARD, ioaddr + EL3_CMD);
+ dev->stats.rx_errors++;
+ switch (error) {
+ case 0x0000:
+ dev->stats.rx_over_errors++;
+ break;
+ case 0x0800:
+ dev->stats.rx_length_errors++;
+ break;
+ case 0x1000:
+ dev->stats.rx_frame_errors++;
+ break;
+ case 0x1800:
+ dev->stats.rx_length_errors++;
+ break;
+ case 0x2000:
+ dev->stats.rx_frame_errors++;
+ break;
+ case 0x2800:
+ dev->stats.rx_crc_errors++; break;
+ }
+ } else {
+ short pkt_len = rx_status & 0x7ff;
+ struct sk_buff *skb;
+
+ skb = netdev_alloc_skb(dev, pkt_len + 5);
+ if (el3_debug > 4)
+ pr_debug("Receiving packet size %d status %4.4x.\n",
+ pkt_len, rx_status);
+ if (skb) {
+ /* Align IP on 16 byte. */
+ skb_reserve(skb, 2);
+
+ /* 'skb->data' points to the start of sk_buff
+ * data area.
+ */
+ insl(ioaddr + RX_FIFO, skb_put(skb, pkt_len),
+ (pkt_len + 3) >> 2);
+
+ /* Pop top Rx packet. */
+ outw(RX_DISCARD, ioaddr + EL3_CMD);
+ skb->protocol = eth_type_trans(skb, dev);
+ netif_rx(skb);
+ dev->stats.rx_bytes += pkt_len;
+ dev->stats.rx_packets++;
+ continue;
+ }
+ outw(RX_DISCARD, ioaddr + EL3_CMD);
+ dev->stats.rx_dropped++;
+ if (el3_debug)
+ pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n",
+ dev->name, pkt_len);
+ }
+ inw(ioaddr + EL3_STATUS); /* Delay. */
+ while (inw(ioaddr + EL3_STATUS) & 0x1000)
+ pr_debug(" Waiting for 3c509 to discard packet, status %x.\n",
+ inw(ioaddr + EL3_STATUS));
+ }
+
+ return 0;
+}
+
+/* Set or clear the multicast filter for this adaptor. */
+static void set_multicast_list(struct net_device *dev)
+{
+ struct el3_private *lp = netdev_priv(dev);
+ int ioaddr = dev->base_addr;
+ int mc_count = netdev_mc_count(dev);
+ unsigned long flags;
+
+ if (el3_debug > 1) {
+ static int old;
+
+ if (old != mc_count) {
+ old = mc_count;
+ pr_debug("%s: Setting Rx mode to %d addresses.\n",
+ dev->name, mc_count);
+ }
+ }
+ spin_lock_irqsave(&lp->lock, flags);
+ if (dev->flags & IFF_PROMISC) {
+ outw((SET_RX_FILTER | RX_STATION | RX_MULTICAST |
+ RX_BROADCAST | RX_PROM),
+ ioaddr + EL3_CMD);
+ } else if (mc_count || (dev->flags & IFF_ALLMULTI)) {
+ outw(SET_RX_FILTER | RX_STATION | RX_MULTICAST | RX_BROADCAST,
+ ioaddr + EL3_CMD);
+ } else {
+ outw(SET_RX_FILTER | RX_STATION | RX_BROADCAST,
+ ioaddr + EL3_CMD);
+ }
+ spin_unlock_irqrestore(&lp->lock, flags);
+}
+
+static int el3_close(struct net_device *dev)
+{
+ struct el3_private *lp = netdev_priv(dev);
+ int ioaddr = dev->base_addr;
+
+ if (el3_debug > 2)
+ pr_debug("%s: Shutting down ethercard.\n", dev->name);
+
+ el3_down(dev);
+
+ free_irq(dev->irq, dev);
+ /* Switching back to window 0 disables the IRQ. */
+ EL3WINDOW(0);
+ if (lp->type != EL3_EISA) {
+ /* But we explicitly zero the IRQ line select anyway. Don't do
+ * it on EISA cards, it prevents the module from getting an
+ * IRQ after unload+reload...
+ */
+ outw(0x0f00, ioaddr + WN0_IRQ);
+ }
+
+ return 0;
+}
+
+static int el3_link_ok(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+ u16 tmp;
+
+ EL3WINDOW(4);
+ tmp = inw(ioaddr + WN4_MEDIA);
+ EL3WINDOW(1);
+ return tmp & (1 << 11);
+}
+
+static void el3_netdev_get_ecmd(struct net_device *dev,
+ struct ethtool_link_ksettings *cmd)
+{
+ int ioaddr = dev->base_addr;
+ u32 supported;
+ u16 tmp;
+
+ EL3WINDOW(0);
+ /* Obtain current transceiver via WN4_MEDIA? */
+ tmp = inw(ioaddr + WN0_ADDR_CONF);
+ switch (tmp >> 14) {
+ case 0:
+ cmd->base.port = PORT_TP;
+ break;
+ case 1:
+ cmd->base.port = PORT_AUI;
+ break;
+ case 3:
+ cmd->base.port = PORT_BNC;
+ break;
+ default:
+ break;
+ }
+
+ cmd->base.duplex = DUPLEX_HALF;
+ supported = 0;
+ tmp = inw(ioaddr + WN0_CONF_CTRL);
+ if (tmp & (1 << 13))
+ supported |= SUPPORTED_AUI;
+ if (tmp & (1 << 12))
+ supported |= SUPPORTED_BNC;
+ if (tmp & (1 << 9)) {
+ supported |= SUPPORTED_TP | SUPPORTED_10baseT_Half |
+ SUPPORTED_10baseT_Full; /* hmm... */
+ EL3WINDOW(4);
+ tmp = inw(ioaddr + WN4_NETDIAG);
+ if (tmp & FD_ENABLE)
+ cmd->base.duplex = DUPLEX_FULL;
+ }
+
+ ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
+ supported);
+ cmd->base.speed = SPEED_10;
+ EL3WINDOW(1);
+}
+
+static int el3_netdev_set_ecmd(struct net_device *dev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ int ioaddr = dev->base_addr;
+ u16 tmp;
+
+ if (cmd->base.speed != SPEED_10)
+ return -EINVAL;
+ if (cmd->base.duplex != DUPLEX_HALF && cmd->base.duplex != DUPLEX_FULL)
+ return -EINVAL;
+
+ /* change XCVR type */
+ EL3WINDOW(0);
+ tmp = inw(ioaddr + WN0_ADDR_CONF);
+ switch (cmd->base.port) {
+ case PORT_TP:
+ tmp &= ~(3 << 14);
+ dev->if_port = 0;
+ break;
+ case PORT_AUI:
+ tmp &= ~(3 << 14);
+ tmp |= 1 << 14;
+ dev->if_port = 1;
+ break;
+ case PORT_BNC:
+ tmp |= 3 << 14;
+ dev->if_port = 3;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ outw(tmp, ioaddr + WN0_ADDR_CONF);
+ if (dev->if_port == 3) {
+ /* Fire up the DC-DC converter if BNC gets enabled. */
+ tmp = inw(ioaddr + WN0_ADDR_CONF);
+ if (tmp & (3 << 14)) {
+ outw(START_COAX, ioaddr + EL3_CMD);
+ udelay(800);
+ } else {
+ return -EIO;
+ }
+ }
+
+ EL3WINDOW(4);
+ tmp = inw(ioaddr + WN4_NETDIAG);
+ if (cmd->base.duplex == DUPLEX_FULL)
+ tmp |= FD_ENABLE;
+ else
+ tmp &= ~FD_ENABLE;
+ outw(tmp, ioaddr + WN4_NETDIAG);
+ EL3WINDOW(1);
+
+ return 0;
+}
+
+static void el3_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ strscpy(info->driver, DRV_NAME, sizeof(info->driver));
+}
+
+static int el3_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct el3_private *lp = netdev_priv(dev);
+
+ spin_lock_irq(&lp->lock);
+ el3_netdev_get_ecmd(dev, cmd);
+ spin_unlock_irq(&lp->lock);
+ return 0;
+}
+
+static int el3_set_link_ksettings(struct net_device *dev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct el3_private *lp = netdev_priv(dev);
+ int ret;
+
+ spin_lock_irq(&lp->lock);
+ ret = el3_netdev_set_ecmd(dev, cmd);
+ spin_unlock_irq(&lp->lock);
+ return ret;
+}
+
+static u32 el3_get_link(struct net_device *dev)
+{
+ struct el3_private *lp = netdev_priv(dev);
+ u32 ret;
+
+ spin_lock_irq(&lp->lock);
+ ret = el3_link_ok(dev);
+ spin_unlock_irq(&lp->lock);
+ return ret;
+}
+
+static u32 el3_get_msglevel(struct net_device *dev)
+{
+ return el3_debug;
+}
+
+static void el3_set_msglevel(struct net_device *dev, u32 v)
+{
+ el3_debug = v;
+}
+
+static const struct ethtool_ops ethtool_ops = {
+ .get_drvinfo = el3_get_drvinfo,
+ .get_link = el3_get_link,
+ .get_msglevel = el3_get_msglevel,
+ .set_msglevel = el3_set_msglevel,
+ .get_link_ksettings = el3_get_link_ksettings,
+ .set_link_ksettings = el3_set_link_ksettings,
+};
+
+static void el3_down(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+
+ netif_stop_queue(dev);
+
+ /* Turn off statistics ASAP. We update lp->stats below. */
+ outw(STATS_DISABLE, ioaddr + EL3_CMD);
+
+ /* Disable the receiver and transmitter. */
+ outw(RX_DISABLE, ioaddr + EL3_CMD);
+ outw(TX_DISABLE, ioaddr + EL3_CMD);
+
+ if (dev->if_port == 3) {
+ /* Turn off thinnet power. Green! */
+ outw(STOP_COAX, ioaddr + EL3_CMD);
+ } else if (dev->if_port == 0) {
+ /* Disable link beat and jabber, if_port may change here next
+ * open().
+ */
+ EL3WINDOW(4);
+ outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA);
+ }
+
+ outw(SET_INTR_ENB | 0x0000, ioaddr + EL3_CMD);
+
+ update_stats(dev);
+}
+
+static void el3_up(struct net_device *dev)
+{
+ int ioaddr = dev->base_addr;
+ int i, sw_info, net_diag;
+
+ /* Activating the board required and does no harm otherwise. */
+ outw(0x0001, ioaddr + 4);
+
+ /* Set the IRQ line. */
+ outw((dev->irq << 12) | 0x0f00, ioaddr + WN0_IRQ);
+
+ /* Set the station address in window 2 each time opened. */
+ EL3WINDOW(2);
+
+ for (i = 0; i < 6; i++)
+ outb(dev->dev_addr[i], ioaddr + i);
+
+ if ((dev->if_port & 0x03) == 3) {
+ /* BNC interface */
+
+ /* Start the thinnet transceiver. We should really wait
+ * 50ms...
+ */
+ outw(START_COAX, ioaddr + EL3_CMD);
+ } else if ((dev->if_port & 0x03) == 0) {
+ /* 10baseT interface */
+
+ /* Combine secondary sw_info word (the adapter level) and
+ * primary sw_info word (duplex setting plus other useless
+ * bits).
+ */
+ EL3WINDOW(0);
+ sw_info = (read_eeprom(ioaddr, 0x14) & 0x400f) |
+ (read_eeprom(ioaddr, 0x0d) & 0xBff0);
+
+ EL3WINDOW(4);
+ net_diag = inw(ioaddr + WN4_NETDIAG);
+ /* Temporarily assume full-duplex will be set. */
+ net_diag = (net_diag | FD_ENABLE);
+ pr_info("%s: ", dev->name);
+ switch (dev->if_port & 0x0c) {
+ case 12:
+ /* Force full-duplex mode if 3c5x9b. */
+ if (sw_info & 0x000f) {
+ pr_cont("Forcing 3c5x9b full-duplex mode");
+ break;
+ }
+ fallthrough;
+ case 8:
+ /* Set full-duplex mode based on eeprom config
+ * setting.
+ */
+ if ((sw_info & 0x000f) && (sw_info & 0x8000)) {
+ pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)");
+ break;
+ }
+ fallthrough;
+ default:
+ /* xcvr = (0 || 4) OR user has an old 3c5x9 non "B"
+ * model.
+ */
+ pr_cont("Setting 3c5x9/3c5x9B half-duplex mode");
+ /* Disable full duplex. */
+ net_diag = (net_diag & ~FD_ENABLE);
+ }
+
+ outw(net_diag, ioaddr + WN4_NETDIAG);
+ pr_cont(" if_port: %d, sw_info: %4.4x\n",
+ dev->if_port, sw_info);
+ if (el3_debug > 3)
+ pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n",
+ dev->name, net_diag);
+ /* Enable link beat and jabber check. */
+ outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA);
+ }
+
+ /* Switch to the stats window, and clear all stats by reading. */
+ outw(STATS_DISABLE, ioaddr + EL3_CMD);
+ EL3WINDOW(6);
+ for (i = 0; i < 9; i++)
+ inb(ioaddr + i);
+ inw(ioaddr + 10);
+ inw(ioaddr + 12);
+
+ /* Switch to register set 1 for normal use. */
+ EL3WINDOW(1);
+
+ /* Accept b-case and phys addr only. */
+ outw(SET_RX_FILTER | RX_STATION | RX_BROADCAST, ioaddr + EL3_CMD);
+ /* Turn on statistics. */
+ outw(STATS_ENABLE, ioaddr + EL3_CMD);
+
+ /* Enable the receiver. */
+ outw(RX_ENABLE, ioaddr + EL3_CMD);
+ /* Enable transmitter. */
+ outw(TX_ENABLE, ioaddr + EL3_CMD);
+ /* Allow status bits to be seen. */
+ outw(SET_STATUS_ENB | 0xff, ioaddr + EL3_CMD);
+ /* Ack all pending events, and set active indicator mask. */
+ outw(ACK_INTR | INT_LATCH | TX_AVAILABLE | RX_EARLY | INT_REQ,
+ ioaddr + EL3_CMD);
+ outw((SET_INTR_ENB | INT_LATCH | TX_AVAILABLE | TX_COMPLETE |
+ RX_COMPLETE | STATS_FULL),
+ ioaddr + EL3_CMD);
+
+ netif_start_queue(dev);
+}
+
+/* Power Management support functions */
+#ifdef CONFIG_PM
+
+static int el3_suspend(struct device *pdev, pm_message_t state)
+{
+ struct net_device *dev;
+ struct el3_private *lp;
+ unsigned long flags;
+ int ioaddr;
+
+ dev = dev_get_drvdata(pdev);
+ lp = netdev_priv(dev);
+ ioaddr = dev->base_addr;
+
+ spin_lock_irqsave(&lp->lock, flags);
+
+ if (netif_running(dev))
+ netif_device_detach(dev);
+
+ el3_down(dev);
+ outw(POWER_DOWN, ioaddr + EL3_CMD);
+
+ spin_unlock_irqrestore(&lp->lock, flags);
+ return 0;
+}
+
+static int el3_resume(struct device *pdev)
+{
+ struct net_device *dev;
+ struct el3_private *lp;
+ unsigned long flags;
+ int ioaddr;
+
+ dev = dev_get_drvdata(pdev);
+ lp = netdev_priv(dev);
+ ioaddr = dev->base_addr;
+
+ spin_lock_irqsave(&lp->lock, flags);
+
+ outw(POWER_UP, ioaddr + EL3_CMD);
+ EL3WINDOW(0);
+ el3_up(dev);
+
+ if (netif_running(dev))
+ netif_device_attach(dev);
+
+ spin_unlock_irqrestore(&lp->lock, flags);
+ return 0;
+}
+
+#endif /* CONFIG_PM */
+
+module_param(debug, int, 0);
+module_param_hw_array(irq, int, irq, NULL, 0);
+module_param(max_interrupt_work, int, 0);
+MODULE_PARM_DESC(debug, "debug level (0-6)");
+MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)");
+MODULE_PARM_DESC(max_interrupt_work, "maximum events handled per interrupt");
+#ifdef CONFIG_PNP
+module_param(nopnp, int, 0);
+MODULE_PARM_DESC(nopnp, "disable ISA PnP support (0-1)");
+#endif /* CONFIG_PNP */
+MODULE_DESCRIPTION("3Com Etherlink III (3c509, 3c509B, 3c529, 3c579) ethernet driver");
+MODULE_LICENSE("GPL");
+
+static int __init el3_init_module(void)
+{
+ int ret = 0;
+
+ if (debug >= 0)
+ el3_debug = debug;
+
+#ifdef CONFIG_PNP
+ if (!nopnp) {
+ ret = pnp_register_driver(&el3_pnp_driver);
+ if (!ret)
+ pnp_registered = 1;
+ }
+#endif
+ /* Select an open I/O location at 0x1*0 to do ISA contention select. */
+ /* Start with 0x110 to avoid some sound cards.*/
+ for (id_port = 0x110; id_port < 0x200; id_port += 0x10) {
+ if (!request_region(id_port, 1, "3c509-control"))
+ continue;
+ outb(0x00, id_port);
+ outb(0xff, id_port);
+ if (inb(id_port) & 0x01)
+ break;
+ release_region(id_port, 1);
+ }
+ if (id_port >= 0x200) {
+ id_port = 0;
+ pr_err("No I/O port available for 3c509 activation.\n");
+ } else {
+ ret = isa_register_driver(&el3_isa_driver, EL3_MAX_CARDS);
+ if (!ret)
+ isa_registered = 1;
+ }
+#ifdef CONFIG_EISA
+ ret = eisa_driver_register(&el3_eisa_driver);
+ if (!ret)
+ eisa_registered = 1;
+#endif
+
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ ret = 0;
+#endif
+ if (isa_registered)
+ ret = 0;
+#ifdef CONFIG_EISA
+ if (eisa_registered)
+ ret = 0;
+#endif
+ return ret;
+}
+
+static void __exit el3_cleanup_module(void)
+{
+#ifdef CONFIG_PNP
+ if (pnp_registered)
+ pnp_unregister_driver(&el3_pnp_driver);
+#endif
+ if (isa_registered)
+ isa_unregister_driver(&el3_isa_driver);
+ if (id_port)
+ release_region(id_port, 1);
+#ifdef CONFIG_EISA
+ if (eisa_registered)
+ eisa_driver_unregister(&el3_eisa_driver);
+#endif
+}
+
+module_init(el3_init_module);
+module_exit(el3_cleanup_module);
diff --git a/drivers/net/ethernet/3com/3c59x.c b/drivers/net/ethernet/3com/3c59x.c
new file mode 100644
index 000000000..79beb8643
--- /dev/null
+++ b/drivers/net/ethernet/3com/3c59x.c
@@ -0,0 +1,3357 @@
+/* EtherLinkXL.c: A 3Com EtherLink PCI III/XL ethernet driver for linux. */
+/*
+ Written 1996-1999 by Donald Becker.
+
+ This software may be used and distributed according to the terms
+ of the GNU General Public License, incorporated herein by reference.
+
+ This driver is for the 3Com "Vortex" and "Boomerang" series ethercards.
+ Members of the series include Fast EtherLink 3c590/3c592/3c595/3c597
+ and the EtherLink XL 3c900 and 3c905 cards.
+
+ Problem reports and questions should be directed to
+ vortex@scyld.com
+
+ The author may be reached as becker@scyld.com, or C/O
+ Scyld Computing Corporation
+ 410 Severn Ave., Suite 210
+ Annapolis MD 21403
+
+*/
+
+/*
+ * FIXME: This driver _could_ support MTU changing, but doesn't. See Don's hamachi.c implementation
+ * as well as other drivers
+ *
+ * NOTE: If you make 'vortex_debug' a constant (#define vortex_debug 0) the driver shrinks by 2k
+ * due to dead code elimination. There will be some performance benefits from this due to
+ * elimination of all the tests and reduced cache footprint.
+ */
+
+
+#define DRV_NAME "3c59x"
+
+
+
+/* A few values that may be tweaked. */
+/* Keep the ring sizes a power of two for efficiency. */
+#define TX_RING_SIZE 16
+#define RX_RING_SIZE 32
+#define PKT_BUF_SZ 1536 /* Size of each temporary Rx buffer.*/
+
+/* "Knobs" that adjust features and parameters. */
+/* Set the copy breakpoint for the copy-only-tiny-frames scheme.
+ Setting to > 1512 effectively disables this feature. */
+#ifndef __arm__
+static int rx_copybreak = 200;
+#else
+/* ARM systems perform better by disregarding the bus-master
+ transfer capability of these cards. -- rmk */
+static int rx_copybreak = 1513;
+#endif
+/* Allow setting MTU to a larger size, bypassing the normal ethernet setup. */
+static const int mtu = 1500;
+/* Maximum events (Rx packets, etc.) to handle at each interrupt. */
+static int max_interrupt_work = 32;
+/* Tx timeout interval (millisecs) */
+static int watchdog = 5000;
+
+/* Allow aggregation of Tx interrupts. Saves CPU load at the cost
+ * of possible Tx stalls if the system is blocking interrupts
+ * somewhere else. Undefine this to disable.
+ */
+#define tx_interrupt_mitigation 1
+
+/* Put out somewhat more debugging messages. (0: no msg, 1 minimal .. 6). */
+#define vortex_debug debug
+#ifdef VORTEX_DEBUG
+static int vortex_debug = VORTEX_DEBUG;
+#else
+static int vortex_debug = 1;
+#endif
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/mii.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/ethtool.h>
+#include <linux/highmem.h>
+#include <linux/eisa.h>
+#include <linux/bitops.h>
+#include <linux/jiffies.h>
+#include <linux/gfp.h>
+#include <asm/irq.h> /* For nr_irqs only. */
+#include <asm/io.h>
+#include <linux/uaccess.h>
+
+/* Kernel compatibility defines, some common to David Hinds' PCMCIA package.
+ This is only in the support-all-kernels source code. */
+
+#define RUN_AT(x) (jiffies + (x))
+
+#include <linux/delay.h>
+
+
+static const char version[] =
+ DRV_NAME ": Donald Becker and others.\n";
+
+MODULE_AUTHOR("Donald Becker <becker@scyld.com>");
+MODULE_DESCRIPTION("3Com 3c59x/3c9xx ethernet driver ");
+MODULE_LICENSE("GPL");
+
+
+/* Operational parameter that usually are not changed. */
+
+/* The Vortex size is twice that of the original EtherLinkIII series: the
+ runtime register window, window 1, is now always mapped in.
+ The Boomerang size is twice as large as the Vortex -- it has additional
+ bus master control registers. */
+#define VORTEX_TOTAL_SIZE 0x20
+#define BOOMERANG_TOTAL_SIZE 0x40
+
+/* Set iff a MII transceiver on any interface requires mdio preamble.
+ This only set with the original DP83840 on older 3c905 boards, so the extra
+ code size of a per-interface flag is not worthwhile. */
+static char mii_preamble_required;
+
+#define PFX DRV_NAME ": "
+
+
+
+/*
+ Theory of Operation
+
+I. Board Compatibility
+
+This device driver is designed for the 3Com FastEtherLink and FastEtherLink
+XL, 3Com's PCI to 10/100baseT adapters. It also works with the 10Mbs
+versions of the FastEtherLink cards. The supported product IDs are
+ 3c590, 3c592, 3c595, 3c597, 3c900, 3c905
+
+The related ISA 3c515 is supported with a separate driver, 3c515.c, included
+with the kernel source or available from
+ cesdis.gsfc.nasa.gov:/pub/linux/drivers/3c515.html
+
+II. Board-specific settings
+
+PCI bus devices are configured by the system at boot time, so no jumpers
+need to be set on the board. The system BIOS should be set to assign the
+PCI INTA signal to an otherwise unused system IRQ line.
+
+The EEPROM settings for media type and forced-full-duplex are observed.
+The EEPROM media type should be left at the default "autoselect" unless using
+10base2 or AUI connections which cannot be reliably detected.
+
+III. Driver operation
+
+The 3c59x series use an interface that's very similar to the previous 3c5x9
+series. The primary interface is two programmed-I/O FIFOs, with an
+alternate single-contiguous-region bus-master transfer (see next).
+
+The 3c900 "Boomerang" series uses a full-bus-master interface with separate
+lists of transmit and receive descriptors, similar to the AMD LANCE/PCnet,
+DEC Tulip and Intel Speedo3. The first chip version retains a compatible
+programmed-I/O interface that has been removed in 'B' and subsequent board
+revisions.
+
+One extension that is advertised in a very large font is that the adapters
+are capable of being bus masters. On the Vortex chip this capability was
+only for a single contiguous region making it far less useful than the full
+bus master capability. There is a significant performance impact of taking
+an extra interrupt or polling for the completion of each transfer, as well
+as difficulty sharing the single transfer engine between the transmit and
+receive threads. Using DMA transfers is a win only with large blocks or
+with the flawed versions of the Intel Orion motherboard PCI controller.
+
+The Boomerang chip's full-bus-master interface is useful, and has the
+currently-unused advantages over other similar chips that queued transmit
+packets may be reordered and receive buffer groups are associated with a
+single frame.
+
+With full-bus-master support, this driver uses a "RX_COPYBREAK" scheme.
+Rather than a fixed intermediate receive buffer, this scheme allocates
+full-sized skbuffs as receive buffers. The value RX_COPYBREAK is used as
+the copying breakpoint: it is chosen to trade-off the memory wasted by
+passing the full-sized skbuff to the queue layer for all frames vs. the
+copying cost of copying a frame to a correctly-sized skbuff.
+
+IIIC. Synchronization
+The driver runs as two independent, single-threaded flows of control. One
+is the send-packet routine, which enforces single-threaded use by the
+dev->tbusy flag. The other thread is the interrupt handler, which is single
+threaded by the hardware and other software.
+
+IV. Notes
+
+Thanks to Cameron Spitzer and Terry Murphy of 3Com for providing development
+3c590, 3c595, and 3c900 boards.
+The name "Vortex" is the internal 3Com project name for the PCI ASIC, and
+the EISA version is called "Demon". According to Terry these names come
+from rides at the local amusement park.
+
+The new chips support both ethernet (1.5K) and FDDI (4.5K) packet sizes!
+This driver only supports ethernet packets because of the skbuff allocation
+limit of 4K.
+*/
+
+/* This table drives the PCI probe routines. It's mostly boilerplate in all
+ of the drivers, and will likely be provided by some future kernel.
+*/
+enum pci_flags_bit {
+ PCI_USES_MASTER=4,
+};
+
+enum { IS_VORTEX=1, IS_BOOMERANG=2, IS_CYCLONE=4, IS_TORNADO=8,
+ EEPROM_8BIT=0x10, /* AKPM: Uses 0x230 as the base bitmaps for EEPROM reads */
+ HAS_PWR_CTRL=0x20, HAS_MII=0x40, HAS_NWAY=0x80, HAS_CB_FNS=0x100,
+ INVERT_MII_PWR=0x200, INVERT_LED_PWR=0x400, MAX_COLLISION_RESET=0x800,
+ EEPROM_OFFSET=0x1000, HAS_HWCKSM=0x2000, WNO_XCVR_PWR=0x4000,
+ EXTRA_PREAMBLE=0x8000, EEPROM_RESET=0x10000, };
+
+enum vortex_chips {
+ CH_3C590 = 0,
+ CH_3C592,
+ CH_3C597,
+ CH_3C595_1,
+ CH_3C595_2,
+
+ CH_3C595_3,
+ CH_3C900_1,
+ CH_3C900_2,
+ CH_3C900_3,
+ CH_3C900_4,
+
+ CH_3C900_5,
+ CH_3C900B_FL,
+ CH_3C905_1,
+ CH_3C905_2,
+ CH_3C905B_TX,
+ CH_3C905B_1,
+
+ CH_3C905B_2,
+ CH_3C905B_FX,
+ CH_3C905C,
+ CH_3C9202,
+ CH_3C980,
+ CH_3C9805,
+
+ CH_3CSOHO100_TX,
+ CH_3C555,
+ CH_3C556,
+ CH_3C556B,
+ CH_3C575,
+
+ CH_3C575_1,
+ CH_3CCFE575,
+ CH_3CCFE575CT,
+ CH_3CCFE656,
+ CH_3CCFEM656,
+
+ CH_3CCFEM656_1,
+ CH_3C450,
+ CH_3C920,
+ CH_3C982A,
+ CH_3C982B,
+
+ CH_905BT4,
+ CH_920B_EMB_WNM,
+};
+
+
+/* note: this array directly indexed by above enums, and MUST
+ * be kept in sync with both the enums above, and the PCI device
+ * table below
+ */
+static struct vortex_chip_info {
+ const char *name;
+ int flags;
+ int drv_flags;
+ int io_size;
+} vortex_info_tbl[] = {
+ {"3c590 Vortex 10Mbps",
+ PCI_USES_MASTER, IS_VORTEX, 32, },
+ {"3c592 EISA 10Mbps Demon/Vortex", /* AKPM: from Don's 3c59x_cb.c 0.49H */
+ PCI_USES_MASTER, IS_VORTEX, 32, },
+ {"3c597 EISA Fast Demon/Vortex", /* AKPM: from Don's 3c59x_cb.c 0.49H */
+ PCI_USES_MASTER, IS_VORTEX, 32, },
+ {"3c595 Vortex 100baseTx",
+ PCI_USES_MASTER, IS_VORTEX, 32, },
+ {"3c595 Vortex 100baseT4",
+ PCI_USES_MASTER, IS_VORTEX, 32, },
+
+ {"3c595 Vortex 100base-MII",
+ PCI_USES_MASTER, IS_VORTEX, 32, },
+ {"3c900 Boomerang 10baseT",
+ PCI_USES_MASTER, IS_BOOMERANG|EEPROM_RESET, 64, },
+ {"3c900 Boomerang 10Mbps Combo",
+ PCI_USES_MASTER, IS_BOOMERANG|EEPROM_RESET, 64, },
+ {"3c900 Cyclone 10Mbps TPO", /* AKPM: from Don's 0.99M */
+ PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, },
+ {"3c900 Cyclone 10Mbps Combo",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, },
+
+ {"3c900 Cyclone 10Mbps TPC", /* AKPM: from Don's 0.99M */
+ PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, },
+ {"3c900B-FL Cyclone 10base-FL",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, },
+ {"3c905 Boomerang 100baseTx",
+ PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_RESET, 64, },
+ {"3c905 Boomerang 100baseT4",
+ PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_RESET, 64, },
+ {"3C905B-TX Fast Etherlink XL PCI",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, },
+ {"3c905B Cyclone 100baseTx",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, },
+
+ {"3c905B Cyclone 10/100/BNC",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM, 128, },
+ {"3c905B-FX Cyclone 100baseFx",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM, 128, },
+ {"3c905C Tornado",
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, },
+ {"3c920B-EMB-WNM (ATI Radeon 9100 IGP)",
+ PCI_USES_MASTER, IS_TORNADO|HAS_MII|HAS_HWCKSM, 128, },
+ {"3c980 Cyclone",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_HWCKSM|EXTRA_PREAMBLE, 128, },
+
+ {"3c980C Python-T",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM, 128, },
+ {"3cSOHO100-TX Hurricane",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, },
+ {"3c555 Laptop Hurricane",
+ PCI_USES_MASTER, IS_CYCLONE|EEPROM_8BIT|HAS_HWCKSM, 128, },
+ {"3c556 Laptop Tornado",
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|EEPROM_8BIT|HAS_CB_FNS|INVERT_MII_PWR|
+ HAS_HWCKSM, 128, },
+ {"3c556B Laptop Hurricane",
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|EEPROM_OFFSET|HAS_CB_FNS|INVERT_MII_PWR|
+ WNO_XCVR_PWR|HAS_HWCKSM, 128, },
+
+ {"3c575 [Megahertz] 10/100 LAN CardBus",
+ PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_8BIT, 128, },
+ {"3c575 Boomerang CardBus",
+ PCI_USES_MASTER, IS_BOOMERANG|HAS_MII|EEPROM_8BIT, 128, },
+ {"3CCFE575BT Cyclone CardBus",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|
+ INVERT_LED_PWR|HAS_HWCKSM, 128, },
+ {"3CCFE575CT Tornado CardBus",
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR|
+ MAX_COLLISION_RESET|HAS_HWCKSM, 128, },
+ {"3CCFE656 Cyclone CardBus",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR|
+ INVERT_LED_PWR|HAS_HWCKSM, 128, },
+
+ {"3CCFEM656B Cyclone+Winmodem CardBus",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR|
+ INVERT_LED_PWR|HAS_HWCKSM, 128, },
+ {"3CXFEM656C Tornado+Winmodem CardBus", /* From pcmcia-cs-3.1.5 */
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_CB_FNS|EEPROM_8BIT|INVERT_MII_PWR|
+ MAX_COLLISION_RESET|HAS_HWCKSM, 128, },
+ {"3c450 HomePNA Tornado", /* AKPM: from Don's 0.99Q */
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM, 128, },
+ {"3c920 Tornado",
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM, 128, },
+ {"3c982 Hydra Dual Port A",
+ PCI_USES_MASTER, IS_TORNADO|HAS_HWCKSM|HAS_NWAY, 128, },
+
+ {"3c982 Hydra Dual Port B",
+ PCI_USES_MASTER, IS_TORNADO|HAS_HWCKSM|HAS_NWAY, 128, },
+ {"3c905B-T4",
+ PCI_USES_MASTER, IS_CYCLONE|HAS_NWAY|HAS_HWCKSM|EXTRA_PREAMBLE, 128, },
+ {"3c920B-EMB-WNM Tornado",
+ PCI_USES_MASTER, IS_TORNADO|HAS_NWAY|HAS_HWCKSM, 128, },
+
+ {NULL,}, /* NULL terminated list. */
+};
+
+
+static const struct pci_device_id vortex_pci_tbl[] = {
+ { PCI_DEVICE(0x10B7, 0x5900), .driver_data = CH_3C590 },
+ { PCI_DEVICE(0x10B7, 0x5920), .driver_data = CH_3C592 },
+ { PCI_DEVICE(0x10B7, 0x5970), .driver_data = CH_3C597 },
+ { PCI_DEVICE(0x10B7, 0x5950), .driver_data = CH_3C595_1 },
+ { PCI_DEVICE(0x10B7, 0x5951), .driver_data = CH_3C595_2 },
+
+ { PCI_DEVICE(0x10B7, 0x5952), .driver_data = CH_3C595_3 },
+ { PCI_DEVICE(0x10B7, 0x9000), .driver_data = CH_3C900_1 },
+ { PCI_DEVICE(0x10B7, 0x9001), .driver_data = CH_3C900_2 },
+ { PCI_DEVICE(0x10B7, 0x9004), .driver_data = CH_3C900_3 },
+ { PCI_DEVICE(0x10B7, 0x9005), .driver_data = CH_3C900_4 },
+
+ { PCI_DEVICE(0x10B7, 0x9006), .driver_data = CH_3C900_5 },
+ { PCI_DEVICE(0x10B7, 0x900A), .driver_data = CH_3C900B_FL },
+ { PCI_DEVICE(0x10B7, 0x9050), .driver_data = CH_3C905_1 },
+ { PCI_DEVICE(0x10B7, 0x9051), .driver_data = CH_3C905_2 },
+ { PCI_DEVICE(0x10B7, 0x9054), .driver_data = CH_3C905B_TX },
+ { PCI_DEVICE(0x10B7, 0x9055), .driver_data = CH_3C905B_1 },
+
+ { PCI_DEVICE(0x10B7, 0x9058), .driver_data = CH_3C905B_2 },
+ { PCI_DEVICE(0x10B7, 0x905A), .driver_data = CH_3C905B_FX },
+ { PCI_DEVICE(0x10B7, 0x9200), .driver_data = CH_3C905C },
+ { PCI_DEVICE(0x10B7, 0x9202), .driver_data = CH_3C9202 },
+ { PCI_DEVICE(0x10B7, 0x9800), .driver_data = CH_3C980 },
+ { PCI_DEVICE(0x10B7, 0x9805), .driver_data = CH_3C9805 },
+
+ { PCI_DEVICE(0x10B7, 0x7646), .driver_data = CH_3CSOHO100_TX },
+ { PCI_DEVICE(0x10B7, 0x5055), .driver_data = CH_3C555 },
+ { PCI_DEVICE(0x10B7, 0x6055), .driver_data = CH_3C556 },
+ { PCI_DEVICE(0x10B7, 0x6056), .driver_data = CH_3C556B },
+ { PCI_DEVICE(0x10B7, 0x5b57), .driver_data = CH_3C575 },
+
+ { PCI_DEVICE(0x10B7, 0x5057), .driver_data = CH_3C575_1 },
+ { PCI_DEVICE(0x10B7, 0x5157), .driver_data = CH_3CCFE575 },
+ { PCI_DEVICE(0x10B7, 0x5257), .driver_data = CH_3CCFE575CT },
+ { PCI_DEVICE(0x10B7, 0x6560), .driver_data = CH_3CCFE656 },
+ { PCI_DEVICE(0x10B7, 0x6562), .driver_data = CH_3CCFEM656 },
+
+ { PCI_DEVICE(0x10B7, 0x6564), .driver_data = CH_3CCFEM656_1 },
+ { PCI_DEVICE(0x10B7, 0x4500), .driver_data = CH_3C450 },
+ { PCI_DEVICE(0x10B7, 0x9201), .driver_data = CH_3C920 },
+ { PCI_DEVICE(0x10B7, 0x1201), .driver_data = CH_3C982A },
+ { PCI_DEVICE(0x10B7, 0x1202), .driver_data = CH_3C982B },
+
+ { PCI_DEVICE(0x10B7, 0x9056), .driver_data = CH_905BT4 },
+ { PCI_DEVICE(0x10B7, 0x9210), .driver_data = CH_920B_EMB_WNM },
+
+ { } /* 0 terminated list. */
+};
+MODULE_DEVICE_TABLE(pci, vortex_pci_tbl);
+
+
+/* Operational definitions.
+ These are not used by other compilation units and thus are not
+ exported in a ".h" file.
+
+ First the windows. There are eight register windows, with the command
+ and status registers available in each.
+ */
+#define EL3_CMD 0x0e
+#define EL3_STATUS 0x0e
+
+/* The top five bits written to EL3_CMD are a command, the lower
+ 11 bits are the parameter, if applicable.
+ Note that 11 parameters bits was fine for ethernet, but the new chip
+ can handle FDDI length frames (~4500 octets) and now parameters count
+ 32-bit 'Dwords' rather than octets. */
+
+enum vortex_cmd {
+ TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11,
+ RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11,
+ UpStall = 6<<11, UpUnstall = (6<<11)+1,
+ DownStall = (6<<11)+2, DownUnstall = (6<<11)+3,
+ RxDiscard = 8<<11, TxEnable = 9<<11, TxDisable = 10<<11, TxReset = 11<<11,
+ FakeIntr = 12<<11, AckIntr = 13<<11, SetIntrEnb = 14<<11,
+ SetStatusEnb = 15<<11, SetRxFilter = 16<<11, SetRxThreshold = 17<<11,
+ SetTxThreshold = 18<<11, SetTxStart = 19<<11,
+ StartDMAUp = 20<<11, StartDMADown = (20<<11)+1, StatsEnable = 21<<11,
+ StatsDisable = 22<<11, StopCoax = 23<<11, SetFilterBit = 25<<11,};
+
+/* The SetRxFilter command accepts the following classes: */
+enum RxFilter {
+ RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 8 };
+
+/* Bits in the general status register. */
+enum vortex_status {
+ IntLatch = 0x0001, HostError = 0x0002, TxComplete = 0x0004,
+ TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020,
+ IntReq = 0x0040, StatsFull = 0x0080,
+ DMADone = 1<<8, DownComplete = 1<<9, UpComplete = 1<<10,
+ DMAInProgress = 1<<11, /* DMA controller is still busy.*/
+ CmdInProgress = 1<<12, /* EL3_CMD is still busy.*/
+};
+
+/* Register window 1 offsets, the window used in normal operation.
+ On the Vortex this window is always mapped at offsets 0x10-0x1f. */
+enum Window1 {
+ TX_FIFO = 0x10, RX_FIFO = 0x10, RxErrors = 0x14,
+ RxStatus = 0x18, Timer=0x1A, TxStatus = 0x1B,
+ TxFree = 0x1C, /* Remaining free bytes in Tx buffer. */
+};
+enum Window0 {
+ Wn0EepromCmd = 10, /* Window 0: EEPROM command register. */
+ Wn0EepromData = 12, /* Window 0: EEPROM results register. */
+ IntrStatus=0x0E, /* Valid in all windows. */
+};
+enum Win0_EEPROM_bits {
+ EEPROM_Read = 0x80, EEPROM_WRITE = 0x40, EEPROM_ERASE = 0xC0,
+ EEPROM_EWENB = 0x30, /* Enable erasing/writing for 10 msec. */
+ EEPROM_EWDIS = 0x00, /* Disable EWENB before 10 msec timeout. */
+};
+/* EEPROM locations. */
+enum eeprom_offset {
+ PhysAddr01=0, PhysAddr23=1, PhysAddr45=2, ModelID=3,
+ EtherLink3ID=7, IFXcvrIO=8, IRQLine=9,
+ NodeAddr01=10, NodeAddr23=11, NodeAddr45=12,
+ DriverTune=13, Checksum=15};
+
+enum Window2 { /* Window 2. */
+ Wn2_ResetOptions=12,
+};
+enum Window3 { /* Window 3: MAC/config bits. */
+ Wn3_Config=0, Wn3_MaxPktSize=4, Wn3_MAC_Ctrl=6, Wn3_Options=8,
+};
+
+#define BFEXT(value, offset, bitcount) \
+ ((((unsigned long)(value)) >> (offset)) & ((1 << (bitcount)) - 1))
+
+#define BFINS(lhs, rhs, offset, bitcount) \
+ (((lhs) & ~((((1 << (bitcount)) - 1)) << (offset))) | \
+ (((rhs) & ((1 << (bitcount)) - 1)) << (offset)))
+
+#define RAM_SIZE(v) BFEXT(v, 0, 3)
+#define RAM_WIDTH(v) BFEXT(v, 3, 1)
+#define RAM_SPEED(v) BFEXT(v, 4, 2)
+#define ROM_SIZE(v) BFEXT(v, 6, 2)
+#define RAM_SPLIT(v) BFEXT(v, 16, 2)
+#define XCVR(v) BFEXT(v, 20, 4)
+#define AUTOSELECT(v) BFEXT(v, 24, 1)
+
+enum Window4 { /* Window 4: Xcvr/media bits. */
+ Wn4_FIFODiag = 4, Wn4_NetDiag = 6, Wn4_PhysicalMgmt=8, Wn4_Media = 10,
+};
+enum Win4_Media_bits {
+ Media_SQE = 0x0008, /* Enable SQE error counting for AUI. */
+ Media_10TP = 0x00C0, /* Enable link beat and jabber for 10baseT. */
+ Media_Lnk = 0x0080, /* Enable just link beat for 100TX/100FX. */
+ Media_LnkBeat = 0x0800,
+};
+enum Window7 { /* Window 7: Bus Master control. */
+ Wn7_MasterAddr = 0, Wn7_VlanEtherType=4, Wn7_MasterLen = 6,
+ Wn7_MasterStatus = 12,
+};
+/* Boomerang bus master control registers. */
+enum MasterCtrl {
+ PktStatus = 0x20, DownListPtr = 0x24, FragAddr = 0x28, FragLen = 0x2c,
+ TxFreeThreshold = 0x2f, UpPktStatus = 0x30, UpListPtr = 0x38,
+};
+
+/* The Rx and Tx descriptor lists.
+ Caution Alpha hackers: these types are 32 bits! Note also the 8 byte
+ alignment contraint on tx_ring[] and rx_ring[]. */
+#define LAST_FRAG 0x80000000 /* Last Addr/Len pair in descriptor. */
+#define DN_COMPLETE 0x00010000 /* This packet has been downloaded */
+struct boom_rx_desc {
+ __le32 next; /* Last entry points to 0. */
+ __le32 status;
+ __le32 addr; /* Up to 63 addr/len pairs possible. */
+ __le32 length; /* Set LAST_FRAG to indicate last pair. */
+};
+/* Values for the Rx status entry. */
+enum rx_desc_status {
+ RxDComplete=0x00008000, RxDError=0x4000,
+ /* See boomerang_rx() for actual error bits */
+ IPChksumErr=1<<25, TCPChksumErr=1<<26, UDPChksumErr=1<<27,
+ IPChksumValid=1<<29, TCPChksumValid=1<<30, UDPChksumValid=1<<31,
+};
+
+#ifdef MAX_SKB_FRAGS
+#define DO_ZEROCOPY 1
+#else
+#define DO_ZEROCOPY 0
+#endif
+
+struct boom_tx_desc {
+ __le32 next; /* Last entry points to 0. */
+ __le32 status; /* bits 0:12 length, others see below. */
+#if DO_ZEROCOPY
+ struct {
+ __le32 addr;
+ __le32 length;
+ } frag[1+MAX_SKB_FRAGS];
+#else
+ __le32 addr;
+ __le32 length;
+#endif
+};
+
+/* Values for the Tx status entry. */
+enum tx_desc_status {
+ CRCDisable=0x2000, TxDComplete=0x8000,
+ AddIPChksum=0x02000000, AddTCPChksum=0x04000000, AddUDPChksum=0x08000000,
+ TxIntrUploaded=0x80000000, /* IRQ when in FIFO, but maybe not sent. */
+};
+
+/* Chip features we care about in vp->capabilities, read from the EEPROM. */
+enum ChipCaps { CapBusMaster=0x20, CapPwrMgmt=0x2000 };
+
+struct vortex_extra_stats {
+ unsigned long tx_deferred;
+ unsigned long tx_max_collisions;
+ unsigned long tx_multiple_collisions;
+ unsigned long tx_single_collisions;
+ unsigned long rx_bad_ssd;
+};
+
+struct vortex_private {
+ /* The Rx and Tx rings should be quad-word-aligned. */
+ struct boom_rx_desc* rx_ring;
+ struct boom_tx_desc* tx_ring;
+ dma_addr_t rx_ring_dma;
+ dma_addr_t tx_ring_dma;
+ /* The addresses of transmit- and receive-in-place skbuffs. */
+ struct sk_buff* rx_skbuff[RX_RING_SIZE];
+ struct sk_buff* tx_skbuff[TX_RING_SIZE];
+ unsigned int cur_rx, cur_tx; /* The next free ring entry */
+ unsigned int dirty_tx; /* The ring entries to be free()ed. */
+ struct vortex_extra_stats xstats; /* NIC-specific extra stats */
+ struct sk_buff *tx_skb; /* Packet being eaten by bus master ctrl. */
+ dma_addr_t tx_skb_dma; /* Allocated DMA address for bus master ctrl DMA. */
+
+ /* PCI configuration space information. */
+ struct device *gendev;
+ void __iomem *ioaddr; /* IO address space */
+ void __iomem *cb_fn_base; /* CardBus function status addr space. */
+
+ /* Some values here only for performance evaluation and path-coverage */
+ int rx_nocopy, rx_copy, queued_packet, rx_csumhits;
+ int card_idx;
+
+ /* The remainder are related to chip state, mostly media selection. */
+ struct timer_list timer; /* Media selection timer. */
+ int options; /* User-settable misc. driver options. */
+ unsigned int media_override:4, /* Passed-in media type. */
+ default_media:4, /* Read from the EEPROM/Wn3_Config. */
+ full_duplex:1, autoselect:1,
+ bus_master:1, /* Vortex can only do a fragment bus-m. */
+ full_bus_master_tx:1, full_bus_master_rx:2, /* Boomerang */
+ flow_ctrl:1, /* Use 802.3x flow control (PAUSE only) */
+ partner_flow_ctrl:1, /* Partner supports flow control */
+ has_nway:1,
+ enable_wol:1, /* Wake-on-LAN is enabled */
+ pm_state_valid:1, /* pci_dev->saved_config_space has sane contents */
+ open:1,
+ medialock:1,
+ large_frames:1, /* accept large frames */
+ handling_irq:1; /* private in_irq indicator */
+ /* {get|set}_wol operations are already serialized by rtnl.
+ * no additional locking is required for the enable_wol and acpi_set_WOL()
+ */
+ int drv_flags;
+ u16 status_enable;
+ u16 intr_enable;
+ u16 available_media; /* From Wn3_Options. */
+ u16 capabilities, info1, info2; /* Various, from EEPROM. */
+ u16 advertising; /* NWay media advertisement */
+ unsigned char phys[2]; /* MII device addresses. */
+ u16 deferred; /* Resend these interrupts when we
+ * bale from the ISR */
+ u16 io_size; /* Size of PCI region (for release_region) */
+
+ /* Serialises access to hardware other than MII and variables below.
+ * The lock hierarchy is rtnl_lock > {lock, mii_lock} > window_lock. */
+ spinlock_t lock;
+
+ spinlock_t mii_lock; /* Serialises access to MII */
+ struct mii_if_info mii; /* MII lib hooks/info */
+ spinlock_t window_lock; /* Serialises access to windowed regs */
+ int window; /* Register window */
+};
+
+static void window_set(struct vortex_private *vp, int window)
+{
+ if (window != vp->window) {
+ iowrite16(SelectWindow + window, vp->ioaddr + EL3_CMD);
+ vp->window = window;
+ }
+}
+
+#define DEFINE_WINDOW_IO(size) \
+static u ## size \
+window_read ## size(struct vortex_private *vp, int window, int addr) \
+{ \
+ unsigned long flags; \
+ u ## size ret; \
+ spin_lock_irqsave(&vp->window_lock, flags); \
+ window_set(vp, window); \
+ ret = ioread ## size(vp->ioaddr + addr); \
+ spin_unlock_irqrestore(&vp->window_lock, flags); \
+ return ret; \
+} \
+static void \
+window_write ## size(struct vortex_private *vp, u ## size value, \
+ int window, int addr) \
+{ \
+ unsigned long flags; \
+ spin_lock_irqsave(&vp->window_lock, flags); \
+ window_set(vp, window); \
+ iowrite ## size(value, vp->ioaddr + addr); \
+ spin_unlock_irqrestore(&vp->window_lock, flags); \
+}
+DEFINE_WINDOW_IO(8)
+DEFINE_WINDOW_IO(16)
+DEFINE_WINDOW_IO(32)
+
+#ifdef CONFIG_PCI
+#define DEVICE_PCI(dev) ((dev_is_pci(dev)) ? to_pci_dev((dev)) : NULL)
+#else
+#define DEVICE_PCI(dev) NULL
+#endif
+
+#define VORTEX_PCI(vp) \
+ ((struct pci_dev *) (((vp)->gendev) ? DEVICE_PCI((vp)->gendev) : NULL))
+
+#ifdef CONFIG_EISA
+#define DEVICE_EISA(dev) (((dev)->bus == &eisa_bus_type) ? to_eisa_device((dev)) : NULL)
+#else
+#define DEVICE_EISA(dev) NULL
+#endif
+
+#define VORTEX_EISA(vp) \
+ ((struct eisa_device *) (((vp)->gendev) ? DEVICE_EISA((vp)->gendev) : NULL))
+
+/* The action to take with a media selection timer tick.
+ Note that we deviate from the 3Com order by checking 10base2 before AUI.
+ */
+enum xcvr_types {
+ XCVR_10baseT=0, XCVR_AUI, XCVR_10baseTOnly, XCVR_10base2, XCVR_100baseTx,
+ XCVR_100baseFx, XCVR_MII=6, XCVR_NWAY=8, XCVR_ExtMII=9, XCVR_Default=10,
+};
+
+static const struct media_table {
+ char *name;
+ unsigned int media_bits:16, /* Bits to set in Wn4_Media register. */
+ mask:8, /* The transceiver-present bit in Wn3_Config.*/
+ next:8; /* The media type to try next. */
+ int wait; /* Time before we check media status. */
+} media_tbl[] = {
+ { "10baseT", Media_10TP,0x08, XCVR_10base2, (14*HZ)/10},
+ { "10Mbs AUI", Media_SQE, 0x20, XCVR_Default, (1*HZ)/10},
+ { "undefined", 0, 0x80, XCVR_10baseT, 10000},
+ { "10base2", 0, 0x10, XCVR_AUI, (1*HZ)/10},
+ { "100baseTX", Media_Lnk, 0x02, XCVR_100baseFx, (14*HZ)/10},
+ { "100baseFX", Media_Lnk, 0x04, XCVR_MII, (14*HZ)/10},
+ { "MII", 0, 0x41, XCVR_10baseT, 3*HZ },
+ { "undefined", 0, 0x01, XCVR_10baseT, 10000},
+ { "Autonegotiate", 0, 0x41, XCVR_10baseT, 3*HZ},
+ { "MII-External", 0, 0x41, XCVR_10baseT, 3*HZ },
+ { "Default", 0, 0xFF, XCVR_10baseT, 10000},
+};
+
+static struct {
+ const char str[ETH_GSTRING_LEN];
+} ethtool_stats_keys[] = {
+ { "tx_deferred" },
+ { "tx_max_collisions" },
+ { "tx_multiple_collisions" },
+ { "tx_single_collisions" },
+ { "rx_bad_ssd" },
+};
+
+/* number of ETHTOOL_GSTATS u64's */
+#define VORTEX_NUM_STATS 5
+
+static int vortex_probe1(struct device *gendev, void __iomem *ioaddr, int irq,
+ int chip_idx, int card_idx);
+static int vortex_up(struct net_device *dev);
+static void vortex_down(struct net_device *dev, int final);
+static int vortex_open(struct net_device *dev);
+static void mdio_sync(struct vortex_private *vp, int bits);
+static int mdio_read(struct net_device *dev, int phy_id, int location);
+static void mdio_write(struct net_device *vp, int phy_id, int location, int value);
+static void vortex_timer(struct timer_list *t);
+static netdev_tx_t vortex_start_xmit(struct sk_buff *skb,
+ struct net_device *dev);
+static netdev_tx_t boomerang_start_xmit(struct sk_buff *skb,
+ struct net_device *dev);
+static int vortex_rx(struct net_device *dev);
+static int boomerang_rx(struct net_device *dev);
+static irqreturn_t vortex_boomerang_interrupt(int irq, void *dev_id);
+static irqreturn_t _vortex_interrupt(int irq, struct net_device *dev);
+static irqreturn_t _boomerang_interrupt(int irq, struct net_device *dev);
+static int vortex_close(struct net_device *dev);
+static void dump_tx_ring(struct net_device *dev);
+static void update_stats(void __iomem *ioaddr, struct net_device *dev);
+static struct net_device_stats *vortex_get_stats(struct net_device *dev);
+static void set_rx_mode(struct net_device *dev);
+#ifdef CONFIG_PCI
+static int vortex_ioctl(struct net_device *dev, struct ifreq *rq, int cmd);
+#endif
+static void vortex_tx_timeout(struct net_device *dev, unsigned int txqueue);
+static void acpi_set_WOL(struct net_device *dev);
+static const struct ethtool_ops vortex_ethtool_ops;
+static void set_8021q_mode(struct net_device *dev, int enable);
+
+/* This driver uses 'options' to pass the media type, full-duplex flag, etc. */
+/* Option count limit only -- unlimited interfaces are supported. */
+#define MAX_UNITS 8
+static int options[MAX_UNITS] = { [0 ... MAX_UNITS-1] = -1 };
+static int full_duplex[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 };
+static int hw_checksums[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 };
+static int flow_ctrl[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 };
+static int enable_wol[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 };
+static int use_mmio[MAX_UNITS] = {[0 ... MAX_UNITS-1] = -1 };
+static int global_options = -1;
+static int global_full_duplex = -1;
+static int global_enable_wol = -1;
+static int global_use_mmio = -1;
+
+/* Variables to work-around the Compaq PCI BIOS32 problem. */
+static int compaq_ioaddr, compaq_irq, compaq_device_id = 0x5900;
+static struct net_device *compaq_net_device;
+
+static int vortex_cards_found;
+
+module_param(debug, int, 0);
+module_param(global_options, int, 0);
+module_param_array(options, int, NULL, 0);
+module_param(global_full_duplex, int, 0);
+module_param_array(full_duplex, int, NULL, 0);
+module_param_array(hw_checksums, int, NULL, 0);
+module_param_array(flow_ctrl, int, NULL, 0);
+module_param(global_enable_wol, int, 0);
+module_param_array(enable_wol, int, NULL, 0);
+module_param(rx_copybreak, int, 0);
+module_param(max_interrupt_work, int, 0);
+module_param_hw(compaq_ioaddr, int, ioport, 0);
+module_param_hw(compaq_irq, int, irq, 0);
+module_param(compaq_device_id, int, 0);
+module_param(watchdog, int, 0);
+module_param(global_use_mmio, int, 0);
+module_param_array(use_mmio, int, NULL, 0);
+MODULE_PARM_DESC(debug, "3c59x debug level (0-6)");
+MODULE_PARM_DESC(options, "3c59x: Bits 0-3: media type, bit 4: bus mastering, bit 9: full duplex");
+MODULE_PARM_DESC(global_options, "3c59x: same as options, but applies to all NICs if options is unset");
+MODULE_PARM_DESC(full_duplex, "3c59x full duplex setting(s) (1)");
+MODULE_PARM_DESC(global_full_duplex, "3c59x: same as full_duplex, but applies to all NICs if full_duplex is unset");
+MODULE_PARM_DESC(hw_checksums, "3c59x Hardware checksum checking by adapter(s) (0-1)");
+MODULE_PARM_DESC(flow_ctrl, "3c59x 802.3x flow control usage (PAUSE only) (0-1)");
+MODULE_PARM_DESC(enable_wol, "3c59x: Turn on Wake-on-LAN for adapter(s) (0-1)");
+MODULE_PARM_DESC(global_enable_wol, "3c59x: same as enable_wol, but applies to all NICs if enable_wol is unset");
+MODULE_PARM_DESC(rx_copybreak, "3c59x copy breakpoint for copy-only-tiny-frames");
+MODULE_PARM_DESC(max_interrupt_work, "3c59x maximum events handled per interrupt");
+MODULE_PARM_DESC(compaq_ioaddr, "3c59x PCI I/O base address (Compaq BIOS problem workaround)");
+MODULE_PARM_DESC(compaq_irq, "3c59x PCI IRQ number (Compaq BIOS problem workaround)");
+MODULE_PARM_DESC(compaq_device_id, "3c59x PCI device ID (Compaq BIOS problem workaround)");
+MODULE_PARM_DESC(watchdog, "3c59x transmit timeout in milliseconds");
+MODULE_PARM_DESC(global_use_mmio, "3c59x: same as use_mmio, but applies to all NICs if options is unset");
+MODULE_PARM_DESC(use_mmio, "3c59x: use memory-mapped PCI I/O resource (0-1)");
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void poll_vortex(struct net_device *dev)
+{
+ vortex_boomerang_interrupt(dev->irq, dev);
+}
+#endif
+
+#ifdef CONFIG_PM
+
+static int vortex_suspend(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+
+ if (!ndev || !netif_running(ndev))
+ return 0;
+
+ netif_device_detach(ndev);
+ vortex_down(ndev, 1);
+
+ return 0;
+}
+
+static int vortex_resume(struct device *dev)
+{
+ struct net_device *ndev = dev_get_drvdata(dev);
+ int err;
+
+ if (!ndev || !netif_running(ndev))
+ return 0;
+
+ err = vortex_up(ndev);
+ if (err)
+ return err;
+
+ netif_device_attach(ndev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops vortex_pm_ops = {
+ .suspend = vortex_suspend,
+ .resume = vortex_resume,
+ .freeze = vortex_suspend,
+ .thaw = vortex_resume,
+ .poweroff = vortex_suspend,
+ .restore = vortex_resume,
+};
+
+#define VORTEX_PM_OPS (&vortex_pm_ops)
+
+#else /* !CONFIG_PM */
+
+#define VORTEX_PM_OPS NULL
+
+#endif /* !CONFIG_PM */
+
+#ifdef CONFIG_EISA
+static const struct eisa_device_id vortex_eisa_ids[] = {
+ { "TCM5920", CH_3C592 },
+ { "TCM5970", CH_3C597 },
+ { "" }
+};
+MODULE_DEVICE_TABLE(eisa, vortex_eisa_ids);
+
+static int vortex_eisa_probe(struct device *device)
+{
+ void __iomem *ioaddr;
+ struct eisa_device *edev;
+
+ edev = to_eisa_device(device);
+
+ if (!request_region(edev->base_addr, VORTEX_TOTAL_SIZE, DRV_NAME))
+ return -EBUSY;
+
+ ioaddr = ioport_map(edev->base_addr, VORTEX_TOTAL_SIZE);
+
+ if (vortex_probe1(device, ioaddr, ioread16(ioaddr + 0xC88) >> 12,
+ edev->id.driver_data, vortex_cards_found)) {
+ release_region(edev->base_addr, VORTEX_TOTAL_SIZE);
+ return -ENODEV;
+ }
+
+ vortex_cards_found++;
+
+ return 0;
+}
+
+static int vortex_eisa_remove(struct device *device)
+{
+ struct eisa_device *edev;
+ struct net_device *dev;
+ struct vortex_private *vp;
+ void __iomem *ioaddr;
+
+ edev = to_eisa_device(device);
+ dev = eisa_get_drvdata(edev);
+
+ if (!dev) {
+ pr_err("vortex_eisa_remove called for Compaq device!\n");
+ BUG();
+ }
+
+ vp = netdev_priv(dev);
+ ioaddr = vp->ioaddr;
+
+ unregister_netdev(dev);
+ iowrite16(TotalReset|0x14, ioaddr + EL3_CMD);
+ release_region(edev->base_addr, VORTEX_TOTAL_SIZE);
+
+ free_netdev(dev);
+ return 0;
+}
+
+static struct eisa_driver vortex_eisa_driver = {
+ .id_table = vortex_eisa_ids,
+ .driver = {
+ .name = "3c59x",
+ .probe = vortex_eisa_probe,
+ .remove = vortex_eisa_remove
+ }
+};
+
+#endif /* CONFIG_EISA */
+
+/* returns count found (>= 0), or negative on error */
+static int __init vortex_eisa_init(void)
+{
+ int eisa_found = 0;
+ int orig_cards_found = vortex_cards_found;
+
+#ifdef CONFIG_EISA
+ int err;
+
+ err = eisa_driver_register (&vortex_eisa_driver);
+ if (!err) {
+ /*
+ * Because of the way EISA bus is probed, we cannot assume
+ * any device have been found when we exit from
+ * eisa_driver_register (the bus root driver may not be
+ * initialized yet). So we blindly assume something was
+ * found, and let the sysfs magic happened...
+ */
+ eisa_found = 1;
+ }
+#endif
+
+ /* Special code to work-around the Compaq PCI BIOS32 problem. */
+ if (compaq_ioaddr) {
+ vortex_probe1(NULL, ioport_map(compaq_ioaddr, VORTEX_TOTAL_SIZE),
+ compaq_irq, compaq_device_id, vortex_cards_found++);
+ }
+
+ return vortex_cards_found - orig_cards_found + eisa_found;
+}
+
+/* returns count (>= 0), or negative on error */
+static int vortex_init_one(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ int rc, unit, pci_bar;
+ struct vortex_chip_info *vci;
+ void __iomem *ioaddr;
+
+ /* wake up and enable device */
+ rc = pci_enable_device(pdev);
+ if (rc < 0)
+ goto out;
+
+ rc = pci_request_regions(pdev, DRV_NAME);
+ if (rc < 0)
+ goto out_disable;
+
+ unit = vortex_cards_found;
+
+ if (global_use_mmio < 0 && (unit >= MAX_UNITS || use_mmio[unit] < 0)) {
+ /* Determine the default if the user didn't override us */
+ vci = &vortex_info_tbl[ent->driver_data];
+ pci_bar = vci->drv_flags & (IS_CYCLONE | IS_TORNADO) ? 1 : 0;
+ } else if (unit < MAX_UNITS && use_mmio[unit] >= 0)
+ pci_bar = use_mmio[unit] ? 1 : 0;
+ else
+ pci_bar = global_use_mmio ? 1 : 0;
+
+ ioaddr = pci_iomap(pdev, pci_bar, 0);
+ if (!ioaddr) /* If mapping fails, fall-back to BAR 0... */
+ ioaddr = pci_iomap(pdev, 0, 0);
+ if (!ioaddr) {
+ rc = -ENOMEM;
+ goto out_release;
+ }
+
+ rc = vortex_probe1(&pdev->dev, ioaddr, pdev->irq,
+ ent->driver_data, unit);
+ if (rc < 0)
+ goto out_iounmap;
+
+ vortex_cards_found++;
+ goto out;
+
+out_iounmap:
+ pci_iounmap(pdev, ioaddr);
+out_release:
+ pci_release_regions(pdev);
+out_disable:
+ pci_disable_device(pdev);
+out:
+ return rc;
+}
+
+static const struct net_device_ops boomrang_netdev_ops = {
+ .ndo_open = vortex_open,
+ .ndo_stop = vortex_close,
+ .ndo_start_xmit = boomerang_start_xmit,
+ .ndo_tx_timeout = vortex_tx_timeout,
+ .ndo_get_stats = vortex_get_stats,
+#ifdef CONFIG_PCI
+ .ndo_eth_ioctl = vortex_ioctl,
+#endif
+ .ndo_set_rx_mode = set_rx_mode,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = poll_vortex,
+#endif
+};
+
+static const struct net_device_ops vortex_netdev_ops = {
+ .ndo_open = vortex_open,
+ .ndo_stop = vortex_close,
+ .ndo_start_xmit = vortex_start_xmit,
+ .ndo_tx_timeout = vortex_tx_timeout,
+ .ndo_get_stats = vortex_get_stats,
+#ifdef CONFIG_PCI
+ .ndo_eth_ioctl = vortex_ioctl,
+#endif
+ .ndo_set_rx_mode = set_rx_mode,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_validate_addr = eth_validate_addr,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+ .ndo_poll_controller = poll_vortex,
+#endif
+};
+
+/*
+ * Start up the PCI/EISA device which is described by *gendev.
+ * Return 0 on success.
+ *
+ * NOTE: pdev can be NULL, for the case of a Compaq device
+ */
+static int vortex_probe1(struct device *gendev, void __iomem *ioaddr, int irq,
+ int chip_idx, int card_idx)
+{
+ struct vortex_private *vp;
+ int option;
+ unsigned int eeprom[0x40], checksum = 0; /* EEPROM contents */
+ __be16 addr[ETH_ALEN / 2];
+ int i, step;
+ struct net_device *dev;
+ static int printed_version;
+ int retval, print_info;
+ struct vortex_chip_info * const vci = &vortex_info_tbl[chip_idx];
+ const char *print_name = "3c59x";
+ struct pci_dev *pdev = NULL;
+ struct eisa_device *edev = NULL;
+
+ if (!printed_version) {
+ pr_info("%s", version);
+ printed_version = 1;
+ }
+
+ if (gendev) {
+ if ((pdev = DEVICE_PCI(gendev))) {
+ print_name = pci_name(pdev);
+ }
+
+ if ((edev = DEVICE_EISA(gendev))) {
+ print_name = dev_name(&edev->dev);
+ }
+ }
+
+ dev = alloc_etherdev(sizeof(*vp));
+ retval = -ENOMEM;
+ if (!dev)
+ goto out;
+
+ SET_NETDEV_DEV(dev, gendev);
+ vp = netdev_priv(dev);
+
+ option = global_options;
+
+ /* The lower four bits are the media type. */
+ if (dev->mem_start) {
+ /*
+ * The 'options' param is passed in as the third arg to the
+ * LILO 'ether=' argument for non-modular use
+ */
+ option = dev->mem_start;
+ }
+ else if (card_idx < MAX_UNITS) {
+ if (options[card_idx] >= 0)
+ option = options[card_idx];
+ }
+
+ if (option > 0) {
+ if (option & 0x8000)
+ vortex_debug = 7;
+ if (option & 0x4000)
+ vortex_debug = 2;
+ if (option & 0x0400)
+ vp->enable_wol = 1;
+ }
+
+ print_info = (vortex_debug > 1);
+ if (print_info)
+ pr_info("See Documentation/networking/device_drivers/ethernet/3com/vortex.rst\n");
+
+ pr_info("%s: 3Com %s %s at %p.\n",
+ print_name,
+ pdev ? "PCI" : "EISA",
+ vci->name,
+ ioaddr);
+
+ dev->base_addr = (unsigned long)ioaddr;
+ dev->irq = irq;
+ dev->mtu = mtu;
+ vp->ioaddr = ioaddr;
+ vp->large_frames = mtu > 1500;
+ vp->drv_flags = vci->drv_flags;
+ vp->has_nway = (vci->drv_flags & HAS_NWAY) ? 1 : 0;
+ vp->io_size = vci->io_size;
+ vp->card_idx = card_idx;
+ vp->window = -1;
+
+ /* module list only for Compaq device */
+ if (gendev == NULL) {
+ compaq_net_device = dev;
+ }
+
+ /* PCI-only startup logic */
+ if (pdev) {
+ /* enable bus-mastering if necessary */
+ if (vci->flags & PCI_USES_MASTER)
+ pci_set_master(pdev);
+
+ if (vci->drv_flags & IS_VORTEX) {
+ u8 pci_latency;
+ u8 new_latency = 248;
+
+ /* Check the PCI latency value. On the 3c590 series the latency timer
+ must be set to the maximum value to avoid data corruption that occurs
+ when the timer expires during a transfer. This bug exists the Vortex
+ chip only. */
+ pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &pci_latency);
+ if (pci_latency < new_latency) {
+ pr_info("%s: Overriding PCI latency timer (CFLT) setting of %d, new value is %d.\n",
+ print_name, pci_latency, new_latency);
+ pci_write_config_byte(pdev, PCI_LATENCY_TIMER, new_latency);
+ }
+ }
+ }
+
+ spin_lock_init(&vp->lock);
+ spin_lock_init(&vp->mii_lock);
+ spin_lock_init(&vp->window_lock);
+ vp->gendev = gendev;
+ vp->mii.dev = dev;
+ vp->mii.mdio_read = mdio_read;
+ vp->mii.mdio_write = mdio_write;
+ vp->mii.phy_id_mask = 0x1f;
+ vp->mii.reg_num_mask = 0x1f;
+
+ /* Makes sure rings are at least 16 byte aligned. */
+ vp->rx_ring = dma_alloc_coherent(gendev, sizeof(struct boom_rx_desc) * RX_RING_SIZE
+ + sizeof(struct boom_tx_desc) * TX_RING_SIZE,
+ &vp->rx_ring_dma, GFP_KERNEL);
+ retval = -ENOMEM;
+ if (!vp->rx_ring)
+ goto free_device;
+
+ vp->tx_ring = (struct boom_tx_desc *)(vp->rx_ring + RX_RING_SIZE);
+ vp->tx_ring_dma = vp->rx_ring_dma + sizeof(struct boom_rx_desc) * RX_RING_SIZE;
+
+ /* if we are a PCI driver, we store info in pdev->driver_data
+ * instead of a module list */
+ if (pdev)
+ pci_set_drvdata(pdev, dev);
+ if (edev)
+ eisa_set_drvdata(edev, dev);
+
+ vp->media_override = 7;
+ if (option >= 0) {
+ vp->media_override = ((option & 7) == 2) ? 0 : option & 15;
+ if (vp->media_override != 7)
+ vp->medialock = 1;
+ vp->full_duplex = (option & 0x200) ? 1 : 0;
+ vp->bus_master = (option & 16) ? 1 : 0;
+ }
+
+ if (global_full_duplex > 0)
+ vp->full_duplex = 1;
+ if (global_enable_wol > 0)
+ vp->enable_wol = 1;
+
+ if (card_idx < MAX_UNITS) {
+ if (full_duplex[card_idx] > 0)
+ vp->full_duplex = 1;
+ if (flow_ctrl[card_idx] > 0)
+ vp->flow_ctrl = 1;
+ if (enable_wol[card_idx] > 0)
+ vp->enable_wol = 1;
+ }
+
+ vp->mii.force_media = vp->full_duplex;
+ vp->options = option;
+ /* Read the station address from the EEPROM. */
+ {
+ int base;
+
+ if (vci->drv_flags & EEPROM_8BIT)
+ base = 0x230;
+ else if (vci->drv_flags & EEPROM_OFFSET)
+ base = EEPROM_Read + 0x30;
+ else
+ base = EEPROM_Read;
+
+ for (i = 0; i < 0x40; i++) {
+ int timer;
+ window_write16(vp, base + i, 0, Wn0EepromCmd);
+ /* Pause for at least 162 us. for the read to take place. */
+ for (timer = 10; timer >= 0; timer--) {
+ udelay(162);
+ if ((window_read16(vp, 0, Wn0EepromCmd) &
+ 0x8000) == 0)
+ break;
+ }
+ eeprom[i] = window_read16(vp, 0, Wn0EepromData);
+ }
+ }
+ for (i = 0; i < 0x18; i++)
+ checksum ^= eeprom[i];
+ checksum = (checksum ^ (checksum >> 8)) & 0xff;
+ if (checksum != 0x00) { /* Grrr, needless incompatible change 3Com. */
+ while (i < 0x21)
+ checksum ^= eeprom[i++];
+ checksum = (checksum ^ (checksum >> 8)) & 0xff;
+ }
+ if ((checksum != 0x00) && !(vci->drv_flags & IS_TORNADO))
+ pr_cont(" ***INVALID CHECKSUM %4.4x*** ", checksum);
+ for (i = 0; i < 3; i++)
+ addr[i] = htons(eeprom[i + 10]);
+ eth_hw_addr_set(dev, (u8 *)addr);
+ if (print_info)
+ pr_cont(" %pM", dev->dev_addr);
+ /* Unfortunately an all zero eeprom passes the checksum and this
+ gets found in the wild in failure cases. Crypto is hard 8) */
+ if (!is_valid_ether_addr(dev->dev_addr)) {
+ retval = -EINVAL;
+ pr_err("*** EEPROM MAC address is invalid.\n");
+ goto free_ring; /* With every pack */
+ }
+ for (i = 0; i < 6; i++)
+ window_write8(vp, dev->dev_addr[i], 2, i);
+
+ if (print_info)
+ pr_cont(", IRQ %d\n", dev->irq);
+ /* Tell them about an invalid IRQ. */
+ if (dev->irq <= 0 || dev->irq >= irq_get_nr_irqs())
+ pr_warn(" *** Warning: IRQ %d is unlikely to work! ***\n",
+ dev->irq);
+
+ step = (window_read8(vp, 4, Wn4_NetDiag) & 0x1e) >> 1;
+ if (print_info) {
+ pr_info(" product code %02x%02x rev %02x.%d date %02d-%02d-%02d\n",
+ eeprom[6]&0xff, eeprom[6]>>8, eeprom[0x14],
+ step, (eeprom[4]>>5) & 15, eeprom[4] & 31, eeprom[4]>>9);
+ }
+
+
+ if (pdev && vci->drv_flags & HAS_CB_FNS) {
+ unsigned short n;
+
+ vp->cb_fn_base = pci_iomap(pdev, 2, 0);
+ if (!vp->cb_fn_base) {
+ retval = -ENOMEM;
+ goto free_ring;
+ }
+
+ if (print_info) {
+ pr_info("%s: CardBus functions mapped %16.16llx->%p\n",
+ print_name,
+ (unsigned long long)pci_resource_start(pdev, 2),
+ vp->cb_fn_base);
+ }
+
+ n = window_read16(vp, 2, Wn2_ResetOptions) & ~0x4010;
+ if (vp->drv_flags & INVERT_LED_PWR)
+ n |= 0x10;
+ if (vp->drv_flags & INVERT_MII_PWR)
+ n |= 0x4000;
+ window_write16(vp, n, 2, Wn2_ResetOptions);
+ if (vp->drv_flags & WNO_XCVR_PWR) {
+ window_write16(vp, 0x0800, 0, 0);
+ }
+ }
+
+ /* Extract our information from the EEPROM data. */
+ vp->info1 = eeprom[13];
+ vp->info2 = eeprom[15];
+ vp->capabilities = eeprom[16];
+
+ if (vp->info1 & 0x8000) {
+ vp->full_duplex = 1;
+ if (print_info)
+ pr_info("Full duplex capable\n");
+ }
+
+ {
+ static const char * const ram_split[] = {"5:3", "3:1", "1:1", "3:5"};
+ unsigned int config;
+ vp->available_media = window_read16(vp, 3, Wn3_Options);
+ if ((vp->available_media & 0xff) == 0) /* Broken 3c916 */
+ vp->available_media = 0x40;
+ config = window_read32(vp, 3, Wn3_Config);
+ if (print_info) {
+ pr_debug(" Internal config register is %4.4x, transceivers %#x.\n",
+ config, window_read16(vp, 3, Wn3_Options));
+ pr_info(" %dK %s-wide RAM %s Rx:Tx split, %s%s interface.\n",
+ 8 << RAM_SIZE(config),
+ RAM_WIDTH(config) ? "word" : "byte",
+ ram_split[RAM_SPLIT(config)],
+ AUTOSELECT(config) ? "autoselect/" : "",
+ XCVR(config) > XCVR_ExtMII ? "<invalid transceiver>" :
+ media_tbl[XCVR(config)].name);
+ }
+ vp->default_media = XCVR(config);
+ if (vp->default_media == XCVR_NWAY)
+ vp->has_nway = 1;
+ vp->autoselect = AUTOSELECT(config);
+ }
+
+ if (vp->media_override != 7) {
+ pr_info("%s: Media override to transceiver type %d (%s).\n",
+ print_name, vp->media_override,
+ media_tbl[vp->media_override].name);
+ dev->if_port = vp->media_override;
+ } else
+ dev->if_port = vp->default_media;
+
+ if ((vp->available_media & 0x40) || (vci->drv_flags & HAS_NWAY) ||
+ dev->if_port == XCVR_MII || dev->if_port == XCVR_NWAY) {
+ int phy, phy_idx = 0;
+ mii_preamble_required++;
+ if (vp->drv_flags & EXTRA_PREAMBLE)
+ mii_preamble_required++;
+ mdio_sync(vp, 32);
+ mdio_read(dev, 24, MII_BMSR);
+ for (phy = 0; phy < 32 && phy_idx < 1; phy++) {
+ int mii_status, phyx;
+
+ /*
+ * For the 3c905CX we look at index 24 first, because it bogusly
+ * reports an external PHY at all indices
+ */
+ if (phy == 0)
+ phyx = 24;
+ else if (phy <= 24)
+ phyx = phy - 1;
+ else
+ phyx = phy;
+ mii_status = mdio_read(dev, phyx, MII_BMSR);
+ if (mii_status && mii_status != 0xffff) {
+ vp->phys[phy_idx++] = phyx;
+ if (print_info) {
+ pr_info(" MII transceiver found at address %d, status %4x.\n",
+ phyx, mii_status);
+ }
+ if ((mii_status & 0x0040) == 0)
+ mii_preamble_required++;
+ }
+ }
+ mii_preamble_required--;
+ if (phy_idx == 0) {
+ pr_warn(" ***WARNING*** No MII transceivers found!\n");
+ vp->phys[0] = 24;
+ } else {
+ vp->advertising = mdio_read(dev, vp->phys[0], MII_ADVERTISE);
+ if (vp->full_duplex) {
+ /* Only advertise the FD media types. */
+ vp->advertising &= ~0x02A0;
+ mdio_write(dev, vp->phys[0], 4, vp->advertising);
+ }
+ }
+ vp->mii.phy_id = vp->phys[0];
+ }
+
+ if (vp->capabilities & CapBusMaster) {
+ vp->full_bus_master_tx = 1;
+ if (print_info) {
+ pr_info(" Enabling bus-master transmits and %s receives.\n",
+ (vp->info2 & 1) ? "early" : "whole-frame" );
+ }
+ vp->full_bus_master_rx = (vp->info2 & 1) ? 1 : 2;
+ vp->bus_master = 0; /* AKPM: vortex only */
+ }
+
+ /* The 3c59x-specific entries in the device structure. */
+ if (vp->full_bus_master_tx) {
+ dev->netdev_ops = &boomrang_netdev_ops;
+ /* Actually, it still should work with iommu. */
+ if (card_idx < MAX_UNITS &&
+ ((hw_checksums[card_idx] == -1 && (vp->drv_flags & HAS_HWCKSM)) ||
+ hw_checksums[card_idx] == 1)) {
+ dev->features |= NETIF_F_IP_CSUM | NETIF_F_SG;
+ }
+ } else
+ dev->netdev_ops = &vortex_netdev_ops;
+
+ if (print_info) {
+ pr_info("%s: scatter/gather %sabled. h/w checksums %sabled\n",
+ print_name,
+ (dev->features & NETIF_F_SG) ? "en":"dis",
+ (dev->features & NETIF_F_IP_CSUM) ? "en":"dis");
+ }
+
+ dev->ethtool_ops = &vortex_ethtool_ops;
+ dev->watchdog_timeo = (watchdog * HZ) / 1000;
+
+ if (pdev) {
+ vp->pm_state_valid = 1;
+ pci_save_state(pdev);
+ acpi_set_WOL(dev);
+ }
+ retval = register_netdev(dev);
+ if (retval == 0)
+ return 0;
+
+free_ring:
+ dma_free_coherent(gendev,
+ sizeof(struct boom_rx_desc) * RX_RING_SIZE +
+ sizeof(struct boom_tx_desc) * TX_RING_SIZE,
+ vp->rx_ring, vp->rx_ring_dma);
+free_device:
+ free_netdev(dev);
+ pr_err(PFX "vortex_probe1 fails. Returns %d\n", retval);
+out:
+ return retval;
+}
+
+static void
+issue_and_wait(struct net_device *dev, int cmd)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ int i;
+
+ iowrite16(cmd, ioaddr + EL3_CMD);
+ for (i = 0; i < 2000; i++) {
+ if (!(ioread16(ioaddr + EL3_STATUS) & CmdInProgress))
+ return;
+ }
+
+ /* OK, that didn't work. Do it the slow way. One second */
+ for (i = 0; i < 100000; i++) {
+ if (!(ioread16(ioaddr + EL3_STATUS) & CmdInProgress)) {
+ if (vortex_debug > 1)
+ pr_info("%s: command 0x%04x took %d usecs\n",
+ dev->name, cmd, i * 10);
+ return;
+ }
+ udelay(10);
+ }
+ pr_err("%s: command 0x%04x did not complete! Status=0x%x\n",
+ dev->name, cmd, ioread16(ioaddr + EL3_STATUS));
+}
+
+static void
+vortex_set_duplex(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ pr_info("%s: setting %s-duplex.\n",
+ dev->name, (vp->full_duplex) ? "full" : "half");
+
+ /* Set the full-duplex bit. */
+ window_write16(vp,
+ ((vp->info1 & 0x8000) || vp->full_duplex ? 0x20 : 0) |
+ (vp->large_frames ? 0x40 : 0) |
+ ((vp->full_duplex && vp->flow_ctrl && vp->partner_flow_ctrl) ?
+ 0x100 : 0),
+ 3, Wn3_MAC_Ctrl);
+}
+
+static void vortex_check_media(struct net_device *dev, unsigned int init)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ unsigned int ok_to_print = 0;
+
+ if (vortex_debug > 3)
+ ok_to_print = 1;
+
+ if (mii_check_media(&vp->mii, ok_to_print, init)) {
+ vp->full_duplex = vp->mii.full_duplex;
+ vortex_set_duplex(dev);
+ } else if (init) {
+ vortex_set_duplex(dev);
+ }
+}
+
+static int
+vortex_up(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ unsigned int config;
+ int i, mii_reg5, err = 0;
+
+ if (VORTEX_PCI(vp)) {
+ pci_set_power_state(VORTEX_PCI(vp), PCI_D0); /* Go active */
+ if (vp->pm_state_valid)
+ pci_restore_state(VORTEX_PCI(vp));
+ err = pci_enable_device(VORTEX_PCI(vp));
+ if (err) {
+ pr_warn("%s: Could not enable device\n", dev->name);
+ goto err_out;
+ }
+ }
+
+ /* Before initializing select the active media port. */
+ config = window_read32(vp, 3, Wn3_Config);
+
+ if (vp->media_override != 7) {
+ pr_info("%s: Media override to transceiver %d (%s).\n",
+ dev->name, vp->media_override,
+ media_tbl[vp->media_override].name);
+ dev->if_port = vp->media_override;
+ } else if (vp->autoselect) {
+ if (vp->has_nway) {
+ if (vortex_debug > 1)
+ pr_info("%s: using NWAY device table, not %d\n",
+ dev->name, dev->if_port);
+ dev->if_port = XCVR_NWAY;
+ } else {
+ /* Find first available media type, starting with 100baseTx. */
+ dev->if_port = XCVR_100baseTx;
+ while (! (vp->available_media & media_tbl[dev->if_port].mask))
+ dev->if_port = media_tbl[dev->if_port].next;
+ if (vortex_debug > 1)
+ pr_info("%s: first available media type: %s\n",
+ dev->name, media_tbl[dev->if_port].name);
+ }
+ } else {
+ dev->if_port = vp->default_media;
+ if (vortex_debug > 1)
+ pr_info("%s: using default media %s\n",
+ dev->name, media_tbl[dev->if_port].name);
+ }
+
+ timer_setup(&vp->timer, vortex_timer, 0);
+ mod_timer(&vp->timer, RUN_AT(media_tbl[dev->if_port].wait));
+
+ if (vortex_debug > 1)
+ pr_debug("%s: Initial media type %s.\n",
+ dev->name, media_tbl[dev->if_port].name);
+
+ vp->full_duplex = vp->mii.force_media;
+ config = BFINS(config, dev->if_port, 20, 4);
+ if (vortex_debug > 6)
+ pr_debug("vortex_up(): writing 0x%x to InternalConfig\n", config);
+ window_write32(vp, config, 3, Wn3_Config);
+
+ if (dev->if_port == XCVR_MII || dev->if_port == XCVR_NWAY) {
+ mdio_read(dev, vp->phys[0], MII_BMSR);
+ mii_reg5 = mdio_read(dev, vp->phys[0], MII_LPA);
+ vp->partner_flow_ctrl = ((mii_reg5 & 0x0400) != 0);
+ vp->mii.full_duplex = vp->full_duplex;
+
+ vortex_check_media(dev, 1);
+ }
+ else
+ vortex_set_duplex(dev);
+
+ issue_and_wait(dev, TxReset);
+ /*
+ * Don't reset the PHY - that upsets autonegotiation during DHCP operations.
+ */
+ issue_and_wait(dev, RxReset|0x04);
+
+
+ iowrite16(SetStatusEnb | 0x00, ioaddr + EL3_CMD);
+
+ if (vortex_debug > 1) {
+ pr_debug("%s: vortex_up() irq %d media status %4.4x.\n",
+ dev->name, dev->irq, window_read16(vp, 4, Wn4_Media));
+ }
+
+ /* Set the station address and mask in window 2 each time opened. */
+ for (i = 0; i < 6; i++)
+ window_write8(vp, dev->dev_addr[i], 2, i);
+ for (; i < 12; i+=2)
+ window_write16(vp, 0, 2, i);
+
+ if (vp->cb_fn_base) {
+ unsigned short n = window_read16(vp, 2, Wn2_ResetOptions) & ~0x4010;
+ if (vp->drv_flags & INVERT_LED_PWR)
+ n |= 0x10;
+ if (vp->drv_flags & INVERT_MII_PWR)
+ n |= 0x4000;
+ window_write16(vp, n, 2, Wn2_ResetOptions);
+ }
+
+ if (dev->if_port == XCVR_10base2)
+ /* Start the thinnet transceiver. We should really wait 50ms...*/
+ iowrite16(StartCoax, ioaddr + EL3_CMD);
+ if (dev->if_port != XCVR_NWAY) {
+ window_write16(vp,
+ (window_read16(vp, 4, Wn4_Media) &
+ ~(Media_10TP|Media_SQE)) |
+ media_tbl[dev->if_port].media_bits,
+ 4, Wn4_Media);
+ }
+
+ /* Switch to the stats window, and clear all stats by reading. */
+ iowrite16(StatsDisable, ioaddr + EL3_CMD);
+ for (i = 0; i < 10; i++)
+ window_read8(vp, 6, i);
+ window_read16(vp, 6, 10);
+ window_read16(vp, 6, 12);
+ /* New: On the Vortex we must also clear the BadSSD counter. */
+ window_read8(vp, 4, 12);
+ /* ..and on the Boomerang we enable the extra statistics bits. */
+ window_write16(vp, 0x0040, 4, Wn4_NetDiag);
+
+ if (vp->full_bus_master_rx) { /* Boomerang bus master. */
+ vp->cur_rx = 0;
+ /* Initialize the RxEarly register as recommended. */
+ iowrite16(SetRxThreshold + (1536>>2), ioaddr + EL3_CMD);
+ iowrite32(0x0020, ioaddr + PktStatus);
+ iowrite32(vp->rx_ring_dma, ioaddr + UpListPtr);
+ }
+ if (vp->full_bus_master_tx) { /* Boomerang bus master Tx. */
+ vp->cur_tx = vp->dirty_tx = 0;
+ if (vp->drv_flags & IS_BOOMERANG)
+ iowrite8(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold); /* Room for a packet. */
+ /* Clear the Rx, Tx rings. */
+ for (i = 0; i < RX_RING_SIZE; i++) /* AKPM: this is done in vortex_open, too */
+ vp->rx_ring[i].status = 0;
+ for (i = 0; i < TX_RING_SIZE; i++)
+ vp->tx_skbuff[i] = NULL;
+ iowrite32(0, ioaddr + DownListPtr);
+ }
+ /* Set receiver mode: presumably accept b-case and phys addr only. */
+ set_rx_mode(dev);
+ /* enable 802.1q tagged frames */
+ set_8021q_mode(dev, 1);
+ iowrite16(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */
+
+ iowrite16(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */
+ iowrite16(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */
+ /* Allow status bits to be seen. */
+ vp->status_enable = SetStatusEnb | HostError|IntReq|StatsFull|TxComplete|
+ (vp->full_bus_master_tx ? DownComplete : TxAvailable) |
+ (vp->full_bus_master_rx ? UpComplete : RxComplete) |
+ (vp->bus_master ? DMADone : 0);
+ vp->intr_enable = SetIntrEnb | IntLatch | TxAvailable |
+ (vp->full_bus_master_rx ? 0 : RxComplete) |
+ StatsFull | HostError | TxComplete | IntReq
+ | (vp->bus_master ? DMADone : 0) | UpComplete | DownComplete;
+ iowrite16(vp->status_enable, ioaddr + EL3_CMD);
+ /* Ack all pending events, and set active indicator mask. */
+ iowrite16(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq,
+ ioaddr + EL3_CMD);
+ iowrite16(vp->intr_enable, ioaddr + EL3_CMD);
+ if (vp->cb_fn_base) /* The PCMCIA people are idiots. */
+ iowrite32(0x8000, vp->cb_fn_base + 4);
+ netif_start_queue (dev);
+ netdev_reset_queue(dev);
+err_out:
+ return err;
+}
+
+static int
+vortex_open(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ int i;
+ int retval;
+ dma_addr_t dma;
+
+ /* Use the now-standard shared IRQ implementation. */
+ if ((retval = request_irq(dev->irq, vortex_boomerang_interrupt, IRQF_SHARED, dev->name, dev))) {
+ pr_err("%s: Could not reserve IRQ %d\n", dev->name, dev->irq);
+ goto err;
+ }
+
+ if (vp->full_bus_master_rx) { /* Boomerang bus master. */
+ if (vortex_debug > 2)
+ pr_debug("%s: Filling in the Rx ring.\n", dev->name);
+ for (i = 0; i < RX_RING_SIZE; i++) {
+ struct sk_buff *skb;
+ vp->rx_ring[i].next = cpu_to_le32(vp->rx_ring_dma + sizeof(struct boom_rx_desc) * (i+1));
+ vp->rx_ring[i].status = 0; /* Clear complete bit. */
+ vp->rx_ring[i].length = cpu_to_le32(PKT_BUF_SZ | LAST_FRAG);
+
+ skb = __netdev_alloc_skb(dev, PKT_BUF_SZ + NET_IP_ALIGN,
+ GFP_KERNEL);
+ vp->rx_skbuff[i] = skb;
+ if (skb == NULL)
+ break; /* Bad news! */
+
+ skb_reserve(skb, NET_IP_ALIGN); /* Align IP on 16 byte boundaries */
+ dma = dma_map_single(vp->gendev, skb->data,
+ PKT_BUF_SZ, DMA_FROM_DEVICE);
+ if (dma_mapping_error(vp->gendev, dma))
+ break;
+ vp->rx_ring[i].addr = cpu_to_le32(dma);
+ }
+ if (i != RX_RING_SIZE) {
+ pr_emerg("%s: no memory for rx ring\n", dev->name);
+ retval = -ENOMEM;
+ goto err_free_skb;
+ }
+ /* Wrap the ring. */
+ vp->rx_ring[i-1].next = cpu_to_le32(vp->rx_ring_dma);
+ }
+
+ retval = vortex_up(dev);
+ if (!retval)
+ goto out;
+
+err_free_skb:
+ for (i = 0; i < RX_RING_SIZE; i++) {
+ if (vp->rx_skbuff[i]) {
+ dev_kfree_skb(vp->rx_skbuff[i]);
+ vp->rx_skbuff[i] = NULL;
+ }
+ }
+ free_irq(dev->irq, dev);
+err:
+ if (vortex_debug > 1)
+ pr_err("%s: vortex_open() fails: returning %d\n", dev->name, retval);
+out:
+ return retval;
+}
+
+static void
+vortex_timer(struct timer_list *t)
+{
+ struct vortex_private *vp = timer_container_of(vp, t, timer);
+ struct net_device *dev = vp->mii.dev;
+ void __iomem *ioaddr = vp->ioaddr;
+ int next_tick = 60*HZ;
+ int ok = 0;
+ int media_status;
+
+ if (vortex_debug > 2) {
+ pr_debug("%s: Media selection timer tick happened, %s.\n",
+ dev->name, media_tbl[dev->if_port].name);
+ pr_debug("dev->watchdog_timeo=%d\n", dev->watchdog_timeo);
+ }
+
+ media_status = window_read16(vp, 4, Wn4_Media);
+ switch (dev->if_port) {
+ case XCVR_10baseT: case XCVR_100baseTx: case XCVR_100baseFx:
+ if (media_status & Media_LnkBeat) {
+ netif_carrier_on(dev);
+ ok = 1;
+ if (vortex_debug > 1)
+ pr_debug("%s: Media %s has link beat, %x.\n",
+ dev->name, media_tbl[dev->if_port].name, media_status);
+ } else {
+ netif_carrier_off(dev);
+ if (vortex_debug > 1) {
+ pr_debug("%s: Media %s has no link beat, %x.\n",
+ dev->name, media_tbl[dev->if_port].name, media_status);
+ }
+ }
+ break;
+ case XCVR_MII: case XCVR_NWAY:
+ {
+ ok = 1;
+ vortex_check_media(dev, 0);
+ }
+ break;
+ default: /* Other media types handled by Tx timeouts. */
+ if (vortex_debug > 1)
+ pr_debug("%s: Media %s has no indication, %x.\n",
+ dev->name, media_tbl[dev->if_port].name, media_status);
+ ok = 1;
+ }
+
+ if (dev->flags & IFF_SLAVE || !netif_carrier_ok(dev))
+ next_tick = 5*HZ;
+
+ if (vp->medialock)
+ goto leave_media_alone;
+
+ if (!ok) {
+ unsigned int config;
+
+ spin_lock_irq(&vp->lock);
+
+ do {
+ dev->if_port = media_tbl[dev->if_port].next;
+ } while ( ! (vp->available_media & media_tbl[dev->if_port].mask));
+ if (dev->if_port == XCVR_Default) { /* Go back to default. */
+ dev->if_port = vp->default_media;
+ if (vortex_debug > 1)
+ pr_debug("%s: Media selection failing, using default %s port.\n",
+ dev->name, media_tbl[dev->if_port].name);
+ } else {
+ if (vortex_debug > 1)
+ pr_debug("%s: Media selection failed, now trying %s port.\n",
+ dev->name, media_tbl[dev->if_port].name);
+ next_tick = media_tbl[dev->if_port].wait;
+ }
+ window_write16(vp,
+ (media_status & ~(Media_10TP|Media_SQE)) |
+ media_tbl[dev->if_port].media_bits,
+ 4, Wn4_Media);
+
+ config = window_read32(vp, 3, Wn3_Config);
+ config = BFINS(config, dev->if_port, 20, 4);
+ window_write32(vp, config, 3, Wn3_Config);
+
+ iowrite16(dev->if_port == XCVR_10base2 ? StartCoax : StopCoax,
+ ioaddr + EL3_CMD);
+ if (vortex_debug > 1)
+ pr_debug("wrote 0x%08x to Wn3_Config\n", config);
+ /* AKPM: FIXME: Should reset Rx & Tx here. P60 of 3c90xc.pdf */
+
+ spin_unlock_irq(&vp->lock);
+ }
+
+leave_media_alone:
+ if (vortex_debug > 2)
+ pr_debug("%s: Media selection timer finished, %s.\n",
+ dev->name, media_tbl[dev->if_port].name);
+
+ mod_timer(&vp->timer, RUN_AT(next_tick));
+ if (vp->deferred)
+ iowrite16(FakeIntr, ioaddr + EL3_CMD);
+}
+
+static void vortex_tx_timeout(struct net_device *dev, unsigned int txqueue)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+
+ pr_err("%s: transmit timed out, tx_status %2.2x status %4.4x.\n",
+ dev->name, ioread8(ioaddr + TxStatus),
+ ioread16(ioaddr + EL3_STATUS));
+ pr_err(" diagnostics: net %04x media %04x dma %08x fifo %04x\n",
+ window_read16(vp, 4, Wn4_NetDiag),
+ window_read16(vp, 4, Wn4_Media),
+ ioread32(ioaddr + PktStatus),
+ window_read16(vp, 4, Wn4_FIFODiag));
+ /* Slight code bloat to be user friendly. */
+ if ((ioread8(ioaddr + TxStatus) & 0x88) == 0x88)
+ pr_err("%s: Transmitter encountered 16 collisions --"
+ " network cable problem?\n", dev->name);
+ if (ioread16(ioaddr + EL3_STATUS) & IntLatch) {
+ pr_err("%s: Interrupt posted but not delivered --"
+ " IRQ blocked by another device?\n", dev->name);
+ /* Bad idea here.. but we might as well handle a few events. */
+ vortex_boomerang_interrupt(dev->irq, dev);
+ }
+
+ if (vortex_debug > 0)
+ dump_tx_ring(dev);
+
+ issue_and_wait(dev, TxReset);
+
+ dev->stats.tx_errors++;
+ if (vp->full_bus_master_tx) {
+ pr_debug("%s: Resetting the Tx ring pointer.\n", dev->name);
+ if (vp->cur_tx - vp->dirty_tx > 0 && ioread32(ioaddr + DownListPtr) == 0)
+ iowrite32(vp->tx_ring_dma + (vp->dirty_tx % TX_RING_SIZE) * sizeof(struct boom_tx_desc),
+ ioaddr + DownListPtr);
+ if (vp->cur_tx - vp->dirty_tx < TX_RING_SIZE) {
+ netif_wake_queue (dev);
+ netdev_reset_queue (dev);
+ }
+ if (vp->drv_flags & IS_BOOMERANG)
+ iowrite8(PKT_BUF_SZ>>8, ioaddr + TxFreeThreshold);
+ iowrite16(DownUnstall, ioaddr + EL3_CMD);
+ } else {
+ dev->stats.tx_dropped++;
+ netif_wake_queue(dev);
+ netdev_reset_queue(dev);
+ }
+ /* Issue Tx Enable */
+ iowrite16(TxEnable, ioaddr + EL3_CMD);
+ netif_trans_update(dev); /* prevent tx timeout */
+}
+
+/*
+ * Handle uncommon interrupt sources. This is a separate routine to minimize
+ * the cache impact.
+ */
+static void
+vortex_error(struct net_device *dev, int status)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ int do_tx_reset = 0, reset_mask = 0;
+ unsigned char tx_status = 0;
+
+ if (vortex_debug > 2) {
+ pr_err("%s: vortex_error(), status=0x%x\n", dev->name, status);
+ }
+
+ if (status & TxComplete) { /* Really "TxError" for us. */
+ tx_status = ioread8(ioaddr + TxStatus);
+ /* Presumably a tx-timeout. We must merely re-enable. */
+ if (vortex_debug > 2 ||
+ (tx_status != 0x88 && vortex_debug > 0)) {
+ pr_err("%s: Transmit error, Tx status register %2.2x.\n",
+ dev->name, tx_status);
+ if (tx_status == 0x82) {
+ pr_err("Probably a duplex mismatch. See "
+ "Documentation/networking/device_drivers/ethernet/3com/vortex.rst\n");
+ }
+ dump_tx_ring(dev);
+ }
+ if (tx_status & 0x14) dev->stats.tx_fifo_errors++;
+ if (tx_status & 0x38) dev->stats.tx_aborted_errors++;
+ if (tx_status & 0x08) vp->xstats.tx_max_collisions++;
+ iowrite8(0, ioaddr + TxStatus);
+ if (tx_status & 0x30) { /* txJabber or txUnderrun */
+ do_tx_reset = 1;
+ } else if ((tx_status & 0x08) && (vp->drv_flags & MAX_COLLISION_RESET)) { /* maxCollisions */
+ do_tx_reset = 1;
+ reset_mask = 0x0108; /* Reset interface logic, but not download logic */
+ } else { /* Merely re-enable the transmitter. */
+ iowrite16(TxEnable, ioaddr + EL3_CMD);
+ }
+ }
+
+ if (status & RxEarly) /* Rx early is unused. */
+ iowrite16(AckIntr | RxEarly, ioaddr + EL3_CMD);
+
+ if (status & StatsFull) { /* Empty statistics. */
+ static int DoneDidThat;
+ if (vortex_debug > 4)
+ pr_debug("%s: Updating stats.\n", dev->name);
+ update_stats(ioaddr, dev);
+ /* HACK: Disable statistics as an interrupt source. */
+ /* This occurs when we have the wrong media type! */
+ if (DoneDidThat == 0 &&
+ ioread16(ioaddr + EL3_STATUS) & StatsFull) {
+ pr_warn("%s: Updating statistics failed, disabling stats as an interrupt source\n",
+ dev->name);
+ iowrite16(SetIntrEnb |
+ (window_read16(vp, 5, 10) & ~StatsFull),
+ ioaddr + EL3_CMD);
+ vp->intr_enable &= ~StatsFull;
+ DoneDidThat++;
+ }
+ }
+ if (status & IntReq) { /* Restore all interrupt sources. */
+ iowrite16(vp->status_enable, ioaddr + EL3_CMD);
+ iowrite16(vp->intr_enable, ioaddr + EL3_CMD);
+ }
+ if (status & HostError) {
+ u16 fifo_diag;
+ fifo_diag = window_read16(vp, 4, Wn4_FIFODiag);
+ pr_err("%s: Host error, FIFO diagnostic register %4.4x.\n",
+ dev->name, fifo_diag);
+ /* Adapter failure requires Tx/Rx reset and reinit. */
+ if (vp->full_bus_master_tx) {
+ int bus_status = ioread32(ioaddr + PktStatus);
+ /* 0x80000000 PCI master abort. */
+ /* 0x40000000 PCI target abort. */
+ if (vortex_debug)
+ pr_err("%s: PCI bus error, bus status %8.8x\n", dev->name, bus_status);
+
+ /* In this case, blow the card away */
+ /* Must not enter D3 or we can't legally issue the reset! */
+ vortex_down(dev, 0);
+ issue_and_wait(dev, TotalReset | 0xff);
+ vortex_up(dev); /* AKPM: bug. vortex_up() assumes that the rx ring is full. It may not be. */
+ } else if (fifo_diag & 0x0400)
+ do_tx_reset = 1;
+ if (fifo_diag & 0x3000) {
+ /* Reset Rx fifo and upload logic */
+ issue_and_wait(dev, RxReset|0x07);
+ /* Set the Rx filter to the current state. */
+ set_rx_mode(dev);
+ /* enable 802.1q VLAN tagged frames */
+ set_8021q_mode(dev, 1);
+ iowrite16(RxEnable, ioaddr + EL3_CMD); /* Re-enable the receiver. */
+ iowrite16(AckIntr | HostError, ioaddr + EL3_CMD);
+ }
+ }
+
+ if (do_tx_reset) {
+ issue_and_wait(dev, TxReset|reset_mask);
+ iowrite16(TxEnable, ioaddr + EL3_CMD);
+ if (!vp->full_bus_master_tx)
+ netif_wake_queue(dev);
+ }
+}
+
+static netdev_tx_t
+vortex_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ int skblen = skb->len;
+
+ /* Put out the doubleword header... */
+ iowrite32(skb->len, ioaddr + TX_FIFO);
+ if (vp->bus_master) {
+ /* Set the bus-master controller to transfer the packet. */
+ int len = (skb->len + 3) & ~3;
+ vp->tx_skb_dma = dma_map_single(vp->gendev, skb->data, len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(vp->gendev, vp->tx_skb_dma)) {
+ dev_kfree_skb_any(skb);
+ dev->stats.tx_dropped++;
+ return NETDEV_TX_OK;
+ }
+
+ spin_lock_irq(&vp->window_lock);
+ window_set(vp, 7);
+ iowrite32(vp->tx_skb_dma, ioaddr + Wn7_MasterAddr);
+ iowrite16(len, ioaddr + Wn7_MasterLen);
+ spin_unlock_irq(&vp->window_lock);
+ vp->tx_skb = skb;
+ skb_tx_timestamp(skb);
+ iowrite16(StartDMADown, ioaddr + EL3_CMD);
+ /* netif_wake_queue() will be called at the DMADone interrupt. */
+ } else {
+ /* ... and the packet rounded to a doubleword. */
+ skb_tx_timestamp(skb);
+ iowrite32_rep(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2);
+ dev_consume_skb_any (skb);
+ if (ioread16(ioaddr + TxFree) > 1536) {
+ netif_start_queue (dev); /* AKPM: redundant? */
+ } else {
+ /* Interrupt us when the FIFO has room for max-sized packet. */
+ netif_stop_queue(dev);
+ iowrite16(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD);
+ }
+ }
+
+ netdev_sent_queue(dev, skblen);
+
+ /* Clear the Tx status stack. */
+ {
+ int tx_status;
+ int i = 32;
+
+ while (--i > 0 && (tx_status = ioread8(ioaddr + TxStatus)) > 0) {
+ if (tx_status & 0x3C) { /* A Tx-disabling error occurred. */
+ if (vortex_debug > 2)
+ pr_debug("%s: Tx error, status %2.2x.\n",
+ dev->name, tx_status);
+ if (tx_status & 0x04) dev->stats.tx_fifo_errors++;
+ if (tx_status & 0x38) dev->stats.tx_aborted_errors++;
+ if (tx_status & 0x30) {
+ issue_and_wait(dev, TxReset);
+ }
+ iowrite16(TxEnable, ioaddr + EL3_CMD);
+ }
+ iowrite8(0x00, ioaddr + TxStatus); /* Pop the status stack. */
+ }
+ }
+ return NETDEV_TX_OK;
+}
+
+static netdev_tx_t
+boomerang_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ /* Calculate the next Tx descriptor entry. */
+ int entry = vp->cur_tx % TX_RING_SIZE;
+ int skblen = skb->len;
+ struct boom_tx_desc *prev_entry = &vp->tx_ring[(vp->cur_tx-1) % TX_RING_SIZE];
+ unsigned long flags;
+ dma_addr_t dma_addr;
+
+ if (vortex_debug > 6) {
+ pr_debug("boomerang_start_xmit()\n");
+ pr_debug("%s: Trying to send a packet, Tx index %d.\n",
+ dev->name, vp->cur_tx);
+ }
+
+ /*
+ * We can't allow a recursion from our interrupt handler back into the
+ * tx routine, as they take the same spin lock, and that causes
+ * deadlock. Just return NETDEV_TX_BUSY and let the stack try again in
+ * a bit
+ */
+ if (vp->handling_irq)
+ return NETDEV_TX_BUSY;
+
+ if (vp->cur_tx - vp->dirty_tx >= TX_RING_SIZE) {
+ if (vortex_debug > 0)
+ pr_warn("%s: BUG! Tx Ring full, refusing to send buffer\n",
+ dev->name);
+ netif_stop_queue(dev);
+ return NETDEV_TX_BUSY;
+ }
+
+ vp->tx_skbuff[entry] = skb;
+
+ vp->tx_ring[entry].next = 0;
+#if DO_ZEROCOPY
+ if (skb->ip_summed != CHECKSUM_PARTIAL)
+ vp->tx_ring[entry].status = cpu_to_le32(skb->len | TxIntrUploaded);
+ else
+ vp->tx_ring[entry].status = cpu_to_le32(skb->len | TxIntrUploaded | AddTCPChksum | AddUDPChksum);
+
+ if (!skb_shinfo(skb)->nr_frags) {
+ dma_addr = dma_map_single(vp->gendev, skb->data, skb->len,
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(vp->gendev, dma_addr))
+ goto out_dma_err;
+
+ vp->tx_ring[entry].frag[0].addr = cpu_to_le32(dma_addr);
+ vp->tx_ring[entry].frag[0].length = cpu_to_le32(skb->len | LAST_FRAG);
+ } else {
+ int i;
+
+ dma_addr = dma_map_single(vp->gendev, skb->data,
+ skb_headlen(skb), DMA_TO_DEVICE);
+ if (dma_mapping_error(vp->gendev, dma_addr))
+ goto out_dma_err;
+
+ vp->tx_ring[entry].frag[0].addr = cpu_to_le32(dma_addr);
+ vp->tx_ring[entry].frag[0].length = cpu_to_le32(skb_headlen(skb));
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ dma_addr = skb_frag_dma_map(vp->gendev, frag,
+ 0,
+ skb_frag_size(frag),
+ DMA_TO_DEVICE);
+ if (dma_mapping_error(vp->gendev, dma_addr)) {
+ for(i = i-1; i >= 0; i--)
+ dma_unmap_page(vp->gendev,
+ le32_to_cpu(vp->tx_ring[entry].frag[i+1].addr),
+ le32_to_cpu(vp->tx_ring[entry].frag[i+1].length),
+ DMA_TO_DEVICE);
+
+ dma_unmap_single(vp->gendev,
+ le32_to_cpu(vp->tx_ring[entry].frag[0].addr),
+ le32_to_cpu(vp->tx_ring[entry].frag[0].length),
+ DMA_TO_DEVICE);
+
+ goto out_dma_err;
+ }
+
+ vp->tx_ring[entry].frag[i+1].addr =
+ cpu_to_le32(dma_addr);
+
+ if (i == skb_shinfo(skb)->nr_frags-1)
+ vp->tx_ring[entry].frag[i+1].length = cpu_to_le32(skb_frag_size(frag)|LAST_FRAG);
+ else
+ vp->tx_ring[entry].frag[i+1].length = cpu_to_le32(skb_frag_size(frag));
+ }
+ }
+#else
+ dma_addr = dma_map_single(vp->gendev, skb->data, skb->len, DMA_TO_DEVICE);
+ if (dma_mapping_error(vp->gendev, dma_addr))
+ goto out_dma_err;
+ vp->tx_ring[entry].addr = cpu_to_le32(dma_addr);
+ vp->tx_ring[entry].length = cpu_to_le32(skb->len | LAST_FRAG);
+ vp->tx_ring[entry].status = cpu_to_le32(skb->len | TxIntrUploaded);
+#endif
+
+ spin_lock_irqsave(&vp->lock, flags);
+ /* Wait for the stall to complete. */
+ issue_and_wait(dev, DownStall);
+ prev_entry->next = cpu_to_le32(vp->tx_ring_dma + entry * sizeof(struct boom_tx_desc));
+ if (ioread32(ioaddr + DownListPtr) == 0) {
+ iowrite32(vp->tx_ring_dma + entry * sizeof(struct boom_tx_desc), ioaddr + DownListPtr);
+ vp->queued_packet++;
+ }
+
+ vp->cur_tx++;
+ netdev_sent_queue(dev, skblen);
+
+ if (vp->cur_tx - vp->dirty_tx > TX_RING_SIZE - 1) {
+ netif_stop_queue (dev);
+ } else { /* Clear previous interrupt enable. */
+#if defined(tx_interrupt_mitigation)
+ /* Dubious. If in boomeang_interrupt "faster" cyclone ifdef
+ * were selected, this would corrupt DN_COMPLETE. No?
+ */
+ prev_entry->status &= cpu_to_le32(~TxIntrUploaded);
+#endif
+ }
+ skb_tx_timestamp(skb);
+ iowrite16(DownUnstall, ioaddr + EL3_CMD);
+ spin_unlock_irqrestore(&vp->lock, flags);
+out:
+ return NETDEV_TX_OK;
+out_dma_err:
+ dev_err(vp->gendev, "Error mapping dma buffer\n");
+ goto out;
+}
+
+/* The interrupt handler does all of the Rx thread work and cleans up
+ after the Tx thread. */
+
+/*
+ * This is the ISR for the vortex series chips.
+ * full_bus_master_tx == 0 && full_bus_master_rx == 0
+ */
+
+static irqreturn_t
+_vortex_interrupt(int irq, struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr;
+ int status;
+ int work_done = max_interrupt_work;
+ int handled = 0;
+ unsigned int bytes_compl = 0, pkts_compl = 0;
+
+ ioaddr = vp->ioaddr;
+
+ status = ioread16(ioaddr + EL3_STATUS);
+
+ if (vortex_debug > 6)
+ pr_debug("vortex_interrupt(). status=0x%4x\n", status);
+
+ if ((status & IntLatch) == 0)
+ goto handler_exit; /* No interrupt: shared IRQs cause this */
+ handled = 1;
+
+ if (status & IntReq) {
+ status |= vp->deferred;
+ vp->deferred = 0;
+ }
+
+ if (status == 0xffff) /* h/w no longer present (hotplug)? */
+ goto handler_exit;
+
+ if (vortex_debug > 4)
+ pr_debug("%s: interrupt, status %4.4x, latency %d ticks.\n",
+ dev->name, status, ioread8(ioaddr + Timer));
+
+ spin_lock(&vp->window_lock);
+ window_set(vp, 7);
+
+ do {
+ if (vortex_debug > 5)
+ pr_debug("%s: In interrupt loop, status %4.4x.\n",
+ dev->name, status);
+ if (status & RxComplete)
+ vortex_rx(dev);
+
+ if (status & TxAvailable) {
+ if (vortex_debug > 5)
+ pr_debug(" TX room bit was handled.\n");
+ /* There's room in the FIFO for a full-sized packet. */
+ iowrite16(AckIntr | TxAvailable, ioaddr + EL3_CMD);
+ netif_wake_queue (dev);
+ }
+
+ if (status & DMADone) {
+ if (ioread16(ioaddr + Wn7_MasterStatus) & 0x1000) {
+ iowrite16(0x1000, ioaddr + Wn7_MasterStatus); /* Ack the event. */
+ dma_unmap_single(vp->gendev, vp->tx_skb_dma, (vp->tx_skb->len + 3) & ~3, DMA_TO_DEVICE);
+ pkts_compl++;
+ bytes_compl += vp->tx_skb->len;
+ dev_consume_skb_irq(vp->tx_skb); /* Release the transferred buffer */
+ if (ioread16(ioaddr + TxFree) > 1536) {
+ /*
+ * AKPM: FIXME: I don't think we need this. If the queue was stopped due to
+ * insufficient FIFO room, the TxAvailable test will succeed and call
+ * netif_wake_queue()
+ */
+ netif_wake_queue(dev);
+ } else { /* Interrupt when FIFO has room for max-sized packet. */
+ iowrite16(SetTxThreshold + (1536>>2), ioaddr + EL3_CMD);
+ netif_stop_queue(dev);
+ }
+ }
+ }
+ /* Check for all uncommon interrupts at once. */
+ if (status & (HostError | RxEarly | StatsFull | TxComplete | IntReq)) {
+ if (status == 0xffff)
+ break;
+ if (status & RxEarly)
+ vortex_rx(dev);
+ spin_unlock(&vp->window_lock);
+ vortex_error(dev, status);
+ spin_lock(&vp->window_lock);
+ window_set(vp, 7);
+ }
+
+ if (--work_done < 0) {
+ pr_warn("%s: Too much work in interrupt, status %4.4x\n",
+ dev->name, status);
+ /* Disable all pending interrupts. */
+ do {
+ vp->deferred |= status;
+ iowrite16(SetStatusEnb | (~vp->deferred & vp->status_enable),
+ ioaddr + EL3_CMD);
+ iowrite16(AckIntr | (vp->deferred & 0x7ff), ioaddr + EL3_CMD);
+ } while ((status = ioread16(ioaddr + EL3_CMD)) & IntLatch);
+ /* The timer will reenable interrupts. */
+ mod_timer(&vp->timer, jiffies + 1*HZ);
+ break;
+ }
+ /* Acknowledge the IRQ. */
+ iowrite16(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD);
+ } while ((status = ioread16(ioaddr + EL3_STATUS)) & (IntLatch | RxComplete));
+
+ netdev_completed_queue(dev, pkts_compl, bytes_compl);
+ spin_unlock(&vp->window_lock);
+
+ if (vortex_debug > 4)
+ pr_debug("%s: exiting interrupt, status %4.4x.\n",
+ dev->name, status);
+handler_exit:
+ return IRQ_RETVAL(handled);
+}
+
+/*
+ * This is the ISR for the boomerang series chips.
+ * full_bus_master_tx == 1 && full_bus_master_rx == 1
+ */
+
+static irqreturn_t
+_boomerang_interrupt(int irq, struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr;
+ int status;
+ int work_done = max_interrupt_work;
+ int handled = 0;
+ unsigned int bytes_compl = 0, pkts_compl = 0;
+
+ ioaddr = vp->ioaddr;
+
+ vp->handling_irq = 1;
+
+ status = ioread16(ioaddr + EL3_STATUS);
+
+ if (vortex_debug > 6)
+ pr_debug("boomerang_interrupt. status=0x%4x\n", status);
+
+ if ((status & IntLatch) == 0)
+ goto handler_exit; /* No interrupt: shared IRQs can cause this */
+ handled = 1;
+
+ if (status == 0xffff) { /* h/w no longer present (hotplug)? */
+ if (vortex_debug > 1)
+ pr_debug("boomerang_interrupt(1): status = 0xffff\n");
+ goto handler_exit;
+ }
+
+ if (status & IntReq) {
+ status |= vp->deferred;
+ vp->deferred = 0;
+ }
+
+ if (vortex_debug > 4)
+ pr_debug("%s: interrupt, status %4.4x, latency %d ticks.\n",
+ dev->name, status, ioread8(ioaddr + Timer));
+ do {
+ if (vortex_debug > 5)
+ pr_debug("%s: In interrupt loop, status %4.4x.\n",
+ dev->name, status);
+ if (status & UpComplete) {
+ iowrite16(AckIntr | UpComplete, ioaddr + EL3_CMD);
+ if (vortex_debug > 5)
+ pr_debug("boomerang_interrupt->boomerang_rx\n");
+ boomerang_rx(dev);
+ }
+
+ if (status & DownComplete) {
+ unsigned int dirty_tx = vp->dirty_tx;
+
+ iowrite16(AckIntr | DownComplete, ioaddr + EL3_CMD);
+ while (vp->cur_tx - dirty_tx > 0) {
+ int entry = dirty_tx % TX_RING_SIZE;
+#if 1 /* AKPM: the latter is faster, but cyclone-only */
+ if (ioread32(ioaddr + DownListPtr) ==
+ vp->tx_ring_dma + entry * sizeof(struct boom_tx_desc))
+ break; /* It still hasn't been processed. */
+#else
+ if ((vp->tx_ring[entry].status & DN_COMPLETE) == 0)
+ break; /* It still hasn't been processed. */
+#endif
+
+ if (vp->tx_skbuff[entry]) {
+ struct sk_buff *skb = vp->tx_skbuff[entry];
+#if DO_ZEROCOPY
+ int i;
+ dma_unmap_single(vp->gendev,
+ le32_to_cpu(vp->tx_ring[entry].frag[0].addr),
+ le32_to_cpu(vp->tx_ring[entry].frag[0].length)&0xFFF,
+ DMA_TO_DEVICE);
+
+ for (i=1; i<=skb_shinfo(skb)->nr_frags; i++)
+ dma_unmap_page(vp->gendev,
+ le32_to_cpu(vp->tx_ring[entry].frag[i].addr),
+ le32_to_cpu(vp->tx_ring[entry].frag[i].length)&0xFFF,
+ DMA_TO_DEVICE);
+#else
+ dma_unmap_single(vp->gendev,
+ le32_to_cpu(vp->tx_ring[entry].addr), skb->len, DMA_TO_DEVICE);
+#endif
+ pkts_compl++;
+ bytes_compl += skb->len;
+ dev_consume_skb_irq(skb);
+ vp->tx_skbuff[entry] = NULL;
+ } else {
+ pr_debug("boomerang_interrupt: no skb!\n");
+ }
+ /* dev->stats.tx_packets++; Counted below. */
+ dirty_tx++;
+ }
+ vp->dirty_tx = dirty_tx;
+ if (vp->cur_tx - dirty_tx <= TX_RING_SIZE - 1) {
+ if (vortex_debug > 6)
+ pr_debug("boomerang_interrupt: wake queue\n");
+ netif_wake_queue (dev);
+ }
+ }
+
+ /* Check for all uncommon interrupts at once. */
+ if (status & (HostError | RxEarly | StatsFull | TxComplete | IntReq))
+ vortex_error(dev, status);
+
+ if (--work_done < 0) {
+ pr_warn("%s: Too much work in interrupt, status %4.4x\n",
+ dev->name, status);
+ /* Disable all pending interrupts. */
+ do {
+ vp->deferred |= status;
+ iowrite16(SetStatusEnb | (~vp->deferred & vp->status_enable),
+ ioaddr + EL3_CMD);
+ iowrite16(AckIntr | (vp->deferred & 0x7ff), ioaddr + EL3_CMD);
+ } while ((status = ioread16(ioaddr + EL3_CMD)) & IntLatch);
+ /* The timer will reenable interrupts. */
+ mod_timer(&vp->timer, jiffies + 1*HZ);
+ break;
+ }
+ /* Acknowledge the IRQ. */
+ iowrite16(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD);
+ if (vp->cb_fn_base) /* The PCMCIA people are idiots. */
+ iowrite32(0x8000, vp->cb_fn_base + 4);
+
+ } while ((status = ioread16(ioaddr + EL3_STATUS)) & IntLatch);
+ netdev_completed_queue(dev, pkts_compl, bytes_compl);
+
+ if (vortex_debug > 4)
+ pr_debug("%s: exiting interrupt, status %4.4x.\n",
+ dev->name, status);
+handler_exit:
+ vp->handling_irq = 0;
+ return IRQ_RETVAL(handled);
+}
+
+static irqreturn_t
+vortex_boomerang_interrupt(int irq, void *dev_id)
+{
+ struct net_device *dev = dev_id;
+ struct vortex_private *vp = netdev_priv(dev);
+ unsigned long flags;
+ irqreturn_t ret;
+
+ spin_lock_irqsave(&vp->lock, flags);
+
+ if (vp->full_bus_master_rx)
+ ret = _boomerang_interrupt(dev->irq, dev);
+ else
+ ret = _vortex_interrupt(dev->irq, dev);
+
+ spin_unlock_irqrestore(&vp->lock, flags);
+
+ return ret;
+}
+
+static int vortex_rx(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ int i;
+ short rx_status;
+
+ if (vortex_debug > 5)
+ pr_debug("vortex_rx(): status %4.4x, rx_status %4.4x.\n",
+ ioread16(ioaddr+EL3_STATUS), ioread16(ioaddr+RxStatus));
+ while ((rx_status = ioread16(ioaddr + RxStatus)) > 0) {
+ if (rx_status & 0x4000) { /* Error, update stats. */
+ unsigned char rx_error = ioread8(ioaddr + RxErrors);
+ if (vortex_debug > 2)
+ pr_debug(" Rx error: status %2.2x.\n", rx_error);
+ dev->stats.rx_errors++;
+ if (rx_error & 0x01) dev->stats.rx_over_errors++;
+ if (rx_error & 0x02) dev->stats.rx_length_errors++;
+ if (rx_error & 0x04) dev->stats.rx_frame_errors++;
+ if (rx_error & 0x08) dev->stats.rx_crc_errors++;
+ if (rx_error & 0x10) dev->stats.rx_length_errors++;
+ } else {
+ /* The packet length: up to 4.5K!. */
+ int pkt_len = rx_status & 0x1fff;
+ struct sk_buff *skb;
+
+ skb = netdev_alloc_skb(dev, pkt_len + 5);
+ if (vortex_debug > 4)
+ pr_debug("Receiving packet size %d status %4.4x.\n",
+ pkt_len, rx_status);
+ if (skb != NULL) {
+ skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */
+ /* 'skb_put()' points to the start of sk_buff data area. */
+ if (vp->bus_master &&
+ ! (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)) {
+ dma_addr_t dma = dma_map_single(vp->gendev, skb_put(skb, pkt_len),
+ pkt_len, DMA_FROM_DEVICE);
+ iowrite32(dma, ioaddr + Wn7_MasterAddr);
+ iowrite16((skb->len + 3) & ~3, ioaddr + Wn7_MasterLen);
+ iowrite16(StartDMAUp, ioaddr + EL3_CMD);
+ while (ioread16(ioaddr + Wn7_MasterStatus) & 0x8000)
+ ;
+ dma_unmap_single(vp->gendev, dma, pkt_len, DMA_FROM_DEVICE);
+ } else {
+ ioread32_rep(ioaddr + RX_FIFO,
+ skb_put(skb, pkt_len),
+ (pkt_len + 3) >> 2);
+ }
+ iowrite16(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */
+ skb->protocol = eth_type_trans(skb, dev);
+ netif_rx(skb);
+ dev->stats.rx_packets++;
+ /* Wait a limited time to go to next packet. */
+ for (i = 200; i >= 0; i--)
+ if ( ! (ioread16(ioaddr + EL3_STATUS) & CmdInProgress))
+ break;
+ continue;
+ } else if (vortex_debug > 0)
+ pr_notice("%s: No memory to allocate a sk_buff of size %d.\n",
+ dev->name, pkt_len);
+ dev->stats.rx_dropped++;
+ }
+ issue_and_wait(dev, RxDiscard);
+ }
+
+ return 0;
+}
+
+static int
+boomerang_rx(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ int entry = vp->cur_rx % RX_RING_SIZE;
+ void __iomem *ioaddr = vp->ioaddr;
+ int rx_status;
+ int rx_work_limit = RX_RING_SIZE;
+
+ if (vortex_debug > 5)
+ pr_debug("boomerang_rx(): status %4.4x\n", ioread16(ioaddr+EL3_STATUS));
+
+ while ((rx_status = le32_to_cpu(vp->rx_ring[entry].status)) & RxDComplete){
+ if (--rx_work_limit < 0)
+ break;
+ if (rx_status & RxDError) { /* Error, update stats. */
+ unsigned char rx_error = rx_status >> 16;
+ if (vortex_debug > 2)
+ pr_debug(" Rx error: status %2.2x.\n", rx_error);
+ dev->stats.rx_errors++;
+ if (rx_error & 0x01) dev->stats.rx_over_errors++;
+ if (rx_error & 0x02) dev->stats.rx_length_errors++;
+ if (rx_error & 0x04) dev->stats.rx_frame_errors++;
+ if (rx_error & 0x08) dev->stats.rx_crc_errors++;
+ if (rx_error & 0x10) dev->stats.rx_length_errors++;
+ } else {
+ /* The packet length: up to 4.5K!. */
+ int pkt_len = rx_status & 0x1fff;
+ struct sk_buff *skb, *newskb;
+ dma_addr_t newdma;
+ dma_addr_t dma = le32_to_cpu(vp->rx_ring[entry].addr);
+
+ if (vortex_debug > 4)
+ pr_debug("Receiving packet size %d status %4.4x.\n",
+ pkt_len, rx_status);
+
+ /* Check if the packet is long enough to just accept without
+ copying to a properly sized skbuff. */
+ if (pkt_len < rx_copybreak &&
+ (skb = netdev_alloc_skb(dev, pkt_len + 2)) != NULL) {
+ skb_reserve(skb, 2); /* Align IP on 16 byte boundaries */
+ dma_sync_single_for_cpu(vp->gendev, dma, PKT_BUF_SZ, DMA_FROM_DEVICE);
+ /* 'skb_put()' points to the start of sk_buff data area. */
+ skb_put_data(skb, vp->rx_skbuff[entry]->data,
+ pkt_len);
+ dma_sync_single_for_device(vp->gendev, dma, PKT_BUF_SZ, DMA_FROM_DEVICE);
+ vp->rx_copy++;
+ } else {
+ /* Pre-allocate the replacement skb. If it or its
+ * mapping fails then recycle the buffer thats already
+ * in place
+ */
+ newskb = netdev_alloc_skb_ip_align(dev, PKT_BUF_SZ);
+ if (!newskb) {
+ dev->stats.rx_dropped++;
+ goto clear_complete;
+ }
+ newdma = dma_map_single(vp->gendev, newskb->data,
+ PKT_BUF_SZ, DMA_FROM_DEVICE);
+ if (dma_mapping_error(vp->gendev, newdma)) {
+ dev->stats.rx_dropped++;
+ consume_skb(newskb);
+ goto clear_complete;
+ }
+
+ /* Pass up the skbuff already on the Rx ring. */
+ skb = vp->rx_skbuff[entry];
+ vp->rx_skbuff[entry] = newskb;
+ vp->rx_ring[entry].addr = cpu_to_le32(newdma);
+ skb_put(skb, pkt_len);
+ dma_unmap_single(vp->gendev, dma, PKT_BUF_SZ, DMA_FROM_DEVICE);
+ vp->rx_nocopy++;
+ }
+ skb->protocol = eth_type_trans(skb, dev);
+ { /* Use hardware checksum info. */
+ int csum_bits = rx_status & 0xee000000;
+ if (csum_bits &&
+ (csum_bits == (IPChksumValid | TCPChksumValid) ||
+ csum_bits == (IPChksumValid | UDPChksumValid))) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ vp->rx_csumhits++;
+ }
+ }
+ netif_rx(skb);
+ dev->stats.rx_packets++;
+ }
+
+clear_complete:
+ vp->rx_ring[entry].status = 0; /* Clear complete bit. */
+ iowrite16(UpUnstall, ioaddr + EL3_CMD);
+ entry = (++vp->cur_rx) % RX_RING_SIZE;
+ }
+ return 0;
+}
+
+static void
+vortex_down(struct net_device *dev, int final_down)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+
+ netdev_reset_queue(dev);
+ netif_stop_queue(dev);
+
+ timer_delete_sync(&vp->timer);
+
+ /* Turn off statistics ASAP. We update dev->stats below. */
+ iowrite16(StatsDisable, ioaddr + EL3_CMD);
+
+ /* Disable the receiver and transmitter. */
+ iowrite16(RxDisable, ioaddr + EL3_CMD);
+ iowrite16(TxDisable, ioaddr + EL3_CMD);
+
+ /* Disable receiving 802.1q tagged frames */
+ set_8021q_mode(dev, 0);
+
+ if (dev->if_port == XCVR_10base2)
+ /* Turn off thinnet power. Green! */
+ iowrite16(StopCoax, ioaddr + EL3_CMD);
+
+ iowrite16(SetIntrEnb | 0x0000, ioaddr + EL3_CMD);
+
+ update_stats(ioaddr, dev);
+ if (vp->full_bus_master_rx)
+ iowrite32(0, ioaddr + UpListPtr);
+ if (vp->full_bus_master_tx)
+ iowrite32(0, ioaddr + DownListPtr);
+
+ if (final_down && VORTEX_PCI(vp)) {
+ vp->pm_state_valid = 1;
+ pci_save_state(VORTEX_PCI(vp));
+ acpi_set_WOL(dev);
+ }
+}
+
+static int
+vortex_close(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ int i;
+
+ if (netif_device_present(dev))
+ vortex_down(dev, 1);
+
+ if (vortex_debug > 1) {
+ pr_debug("%s: vortex_close() status %4.4x, Tx status %2.2x.\n",
+ dev->name, ioread16(ioaddr + EL3_STATUS), ioread8(ioaddr + TxStatus));
+ pr_debug("%s: vortex close stats: rx_nocopy %d rx_copy %d"
+ " tx_queued %d Rx pre-checksummed %d.\n",
+ dev->name, vp->rx_nocopy, vp->rx_copy, vp->queued_packet, vp->rx_csumhits);
+ }
+
+#if DO_ZEROCOPY
+ if (vp->rx_csumhits &&
+ (vp->drv_flags & HAS_HWCKSM) == 0 &&
+ (vp->card_idx >= MAX_UNITS || hw_checksums[vp->card_idx] == -1)) {
+ pr_warn("%s supports hardware checksums, and we're not using them!\n",
+ dev->name);
+ }
+#endif
+
+ free_irq(dev->irq, dev);
+
+ if (vp->full_bus_master_rx) { /* Free Boomerang bus master Rx buffers. */
+ for (i = 0; i < RX_RING_SIZE; i++)
+ if (vp->rx_skbuff[i]) {
+ dma_unmap_single(vp->gendev, le32_to_cpu(vp->rx_ring[i].addr),
+ PKT_BUF_SZ, DMA_FROM_DEVICE);
+ dev_kfree_skb(vp->rx_skbuff[i]);
+ vp->rx_skbuff[i] = NULL;
+ }
+ }
+ if (vp->full_bus_master_tx) { /* Free Boomerang bus master Tx buffers. */
+ for (i = 0; i < TX_RING_SIZE; i++) {
+ if (vp->tx_skbuff[i]) {
+ struct sk_buff *skb = vp->tx_skbuff[i];
+#if DO_ZEROCOPY
+ int k;
+
+ for (k=0; k<=skb_shinfo(skb)->nr_frags; k++)
+ dma_unmap_single(vp->gendev,
+ le32_to_cpu(vp->tx_ring[i].frag[k].addr),
+ le32_to_cpu(vp->tx_ring[i].frag[k].length)&0xFFF,
+ DMA_TO_DEVICE);
+#else
+ dma_unmap_single(vp->gendev, le32_to_cpu(vp->tx_ring[i].addr), skb->len, DMA_TO_DEVICE);
+#endif
+ dev_kfree_skb(skb);
+ vp->tx_skbuff[i] = NULL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void
+dump_tx_ring(struct net_device *dev)
+{
+ if (vortex_debug > 0) {
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+
+ if (vp->full_bus_master_tx) {
+ int i;
+ int stalled = ioread32(ioaddr + PktStatus) & 0x04; /* Possible racy. But it's only debug stuff */
+
+ pr_err(" Flags; bus-master %d, dirty %d(%d) current %d(%d)\n",
+ vp->full_bus_master_tx,
+ vp->dirty_tx, vp->dirty_tx % TX_RING_SIZE,
+ vp->cur_tx, vp->cur_tx % TX_RING_SIZE);
+ pr_err(" Transmit list %8.8x vs. %p.\n",
+ ioread32(ioaddr + DownListPtr),
+ &vp->tx_ring[vp->dirty_tx % TX_RING_SIZE]);
+ issue_and_wait(dev, DownStall);
+ for (i = 0; i < TX_RING_SIZE; i++) {
+ unsigned int length;
+
+#if DO_ZEROCOPY
+ length = le32_to_cpu(vp->tx_ring[i].frag[0].length);
+#else
+ length = le32_to_cpu(vp->tx_ring[i].length);
+#endif
+ pr_err(" %d: @%p length %8.8x status %8.8x\n",
+ i, &vp->tx_ring[i], length,
+ le32_to_cpu(vp->tx_ring[i].status));
+ }
+ if (!stalled)
+ iowrite16(DownUnstall, ioaddr + EL3_CMD);
+ }
+ }
+}
+
+static struct net_device_stats *vortex_get_stats(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ unsigned long flags;
+
+ if (netif_device_present(dev)) { /* AKPM: Used to be netif_running */
+ spin_lock_irqsave (&vp->lock, flags);
+ update_stats(ioaddr, dev);
+ spin_unlock_irqrestore (&vp->lock, flags);
+ }
+ return &dev->stats;
+}
+
+/* Update statistics.
+ Unlike with the EL3 we need not worry about interrupts changing
+ the window setting from underneath us, but we must still guard
+ against a race condition with a StatsUpdate interrupt updating the
+ table. This is done by checking that the ASM (!) code generated uses
+ atomic updates with '+='.
+ */
+static void update_stats(void __iomem *ioaddr, struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ /* Unlike the 3c5x9 we need not turn off stats updates while reading. */
+ /* Switch to the stats window, and read everything. */
+ dev->stats.tx_carrier_errors += window_read8(vp, 6, 0);
+ dev->stats.tx_heartbeat_errors += window_read8(vp, 6, 1);
+ dev->stats.tx_window_errors += window_read8(vp, 6, 4);
+ dev->stats.rx_fifo_errors += window_read8(vp, 6, 5);
+ dev->stats.tx_packets += window_read8(vp, 6, 6);
+ dev->stats.tx_packets += (window_read8(vp, 6, 9) &
+ 0x30) << 4;
+ /* Rx packets */ window_read8(vp, 6, 7); /* Must read to clear */
+ /* Don't bother with register 9, an extension of registers 6&7.
+ If we do use the 6&7 values the atomic update assumption above
+ is invalid. */
+ dev->stats.rx_bytes += window_read16(vp, 6, 10);
+ dev->stats.tx_bytes += window_read16(vp, 6, 12);
+ /* Extra stats for get_ethtool_stats() */
+ vp->xstats.tx_multiple_collisions += window_read8(vp, 6, 2);
+ vp->xstats.tx_single_collisions += window_read8(vp, 6, 3);
+ vp->xstats.tx_deferred += window_read8(vp, 6, 8);
+ vp->xstats.rx_bad_ssd += window_read8(vp, 4, 12);
+
+ dev->stats.collisions = vp->xstats.tx_multiple_collisions
+ + vp->xstats.tx_single_collisions
+ + vp->xstats.tx_max_collisions;
+
+ {
+ u8 up = window_read8(vp, 4, 13);
+ dev->stats.rx_bytes += (up & 0x0f) << 16;
+ dev->stats.tx_bytes += (up & 0xf0) << 12;
+ }
+}
+
+static int vortex_nway_reset(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ return mii_nway_restart(&vp->mii);
+}
+
+static int vortex_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ mii_ethtool_get_link_ksettings(&vp->mii, cmd);
+
+ return 0;
+}
+
+static int vortex_set_link_ksettings(struct net_device *dev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ return mii_ethtool_set_link_ksettings(&vp->mii, cmd);
+}
+
+static u32 vortex_get_msglevel(struct net_device *dev)
+{
+ return vortex_debug;
+}
+
+static void vortex_set_msglevel(struct net_device *dev, u32 dbg)
+{
+ vortex_debug = dbg;
+}
+
+static int vortex_get_sset_count(struct net_device *dev, int sset)
+{
+ switch (sset) {
+ case ETH_SS_STATS:
+ return VORTEX_NUM_STATS;
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static void vortex_get_ethtool_stats(struct net_device *dev,
+ struct ethtool_stats *stats, u64 *data)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ unsigned long flags;
+
+ spin_lock_irqsave(&vp->lock, flags);
+ update_stats(ioaddr, dev);
+ spin_unlock_irqrestore(&vp->lock, flags);
+
+ data[0] = vp->xstats.tx_deferred;
+ data[1] = vp->xstats.tx_max_collisions;
+ data[2] = vp->xstats.tx_multiple_collisions;
+ data[3] = vp->xstats.tx_single_collisions;
+ data[4] = vp->xstats.rx_bad_ssd;
+}
+
+
+static void vortex_get_strings(struct net_device *dev, u32 stringset, u8 *data)
+{
+ switch (stringset) {
+ case ETH_SS_STATS:
+ memcpy(data, &ethtool_stats_keys, sizeof(ethtool_stats_keys));
+ break;
+ default:
+ WARN_ON(1);
+ break;
+ }
+}
+
+static void vortex_get_drvinfo(struct net_device *dev,
+ struct ethtool_drvinfo *info)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ strscpy(info->driver, DRV_NAME, sizeof(info->driver));
+ if (VORTEX_PCI(vp)) {
+ strscpy(info->bus_info, pci_name(VORTEX_PCI(vp)),
+ sizeof(info->bus_info));
+ } else {
+ if (VORTEX_EISA(vp))
+ strscpy(info->bus_info, dev_name(vp->gendev),
+ sizeof(info->bus_info));
+ else
+ snprintf(info->bus_info, sizeof(info->bus_info),
+ "EISA 0x%lx %d", dev->base_addr, dev->irq);
+ }
+}
+
+static void vortex_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ if (!VORTEX_PCI(vp))
+ return;
+
+ wol->supported = WAKE_MAGIC;
+
+ wol->wolopts = 0;
+ if (vp->enable_wol)
+ wol->wolopts |= WAKE_MAGIC;
+}
+
+static int vortex_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+
+ if (!VORTEX_PCI(vp))
+ return -EOPNOTSUPP;
+
+ if (wol->wolopts & ~WAKE_MAGIC)
+ return -EINVAL;
+
+ if (wol->wolopts & WAKE_MAGIC)
+ vp->enable_wol = 1;
+ else
+ vp->enable_wol = 0;
+ acpi_set_WOL(dev);
+
+ return 0;
+}
+
+static const struct ethtool_ops vortex_ethtool_ops = {
+ .get_drvinfo = vortex_get_drvinfo,
+ .get_strings = vortex_get_strings,
+ .get_msglevel = vortex_get_msglevel,
+ .set_msglevel = vortex_set_msglevel,
+ .get_ethtool_stats = vortex_get_ethtool_stats,
+ .get_sset_count = vortex_get_sset_count,
+ .get_link = ethtool_op_get_link,
+ .nway_reset = vortex_nway_reset,
+ .get_wol = vortex_get_wol,
+ .set_wol = vortex_set_wol,
+ .get_ts_info = ethtool_op_get_ts_info,
+ .get_link_ksettings = vortex_get_link_ksettings,
+ .set_link_ksettings = vortex_set_link_ksettings,
+};
+
+#ifdef CONFIG_PCI
+/*
+ * Must power the device up to do MDIO operations
+ */
+static int vortex_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ int err;
+ struct vortex_private *vp = netdev_priv(dev);
+ pci_power_t state = 0;
+
+ if(VORTEX_PCI(vp))
+ state = VORTEX_PCI(vp)->current_state;
+
+ /* The kernel core really should have pci_get_power_state() */
+
+ if(state != 0)
+ pci_set_power_state(VORTEX_PCI(vp), PCI_D0);
+ err = generic_mii_ioctl(&vp->mii, if_mii(rq), cmd, NULL);
+ if(state != 0)
+ pci_set_power_state(VORTEX_PCI(vp), state);
+
+ return err;
+}
+#endif
+
+
+/* Pre-Cyclone chips have no documented multicast filter, so the only
+ multicast setting is to receive all multicast frames. At least
+ the chip has a very clean way to set the mode, unlike many others. */
+static void set_rx_mode(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+ int new_mode;
+
+ if (dev->flags & IFF_PROMISC) {
+ if (vortex_debug > 3)
+ pr_notice("%s: Setting promiscuous mode.\n", dev->name);
+ new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast|RxProm;
+ } else if (!netdev_mc_empty(dev) || dev->flags & IFF_ALLMULTI) {
+ new_mode = SetRxFilter|RxStation|RxMulticast|RxBroadcast;
+ } else
+ new_mode = SetRxFilter | RxStation | RxBroadcast;
+
+ iowrite16(new_mode, ioaddr + EL3_CMD);
+}
+
+#if IS_ENABLED(CONFIG_VLAN_8021Q)
+/* Setup the card so that it can receive frames with an 802.1q VLAN tag.
+ Note that this must be done after each RxReset due to some backwards
+ compatibility logic in the Cyclone and Tornado ASICs */
+
+/* The Ethernet Type used for 802.1q tagged frames */
+#define VLAN_ETHER_TYPE 0x8100
+
+static void set_8021q_mode(struct net_device *dev, int enable)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ int mac_ctrl;
+
+ if ((vp->drv_flags&IS_CYCLONE) || (vp->drv_flags&IS_TORNADO)) {
+ /* cyclone and tornado chipsets can recognize 802.1q
+ * tagged frames and treat them correctly */
+
+ int max_pkt_size = dev->mtu+14; /* MTU+Ethernet header */
+ if (enable)
+ max_pkt_size += 4; /* 802.1Q VLAN tag */
+
+ window_write16(vp, max_pkt_size, 3, Wn3_MaxPktSize);
+
+ /* set VlanEtherType to let the hardware checksumming
+ treat tagged frames correctly */
+ window_write16(vp, VLAN_ETHER_TYPE, 7, Wn7_VlanEtherType);
+ } else {
+ /* on older cards we have to enable large frames */
+
+ vp->large_frames = dev->mtu > 1500 || enable;
+
+ mac_ctrl = window_read16(vp, 3, Wn3_MAC_Ctrl);
+ if (vp->large_frames)
+ mac_ctrl |= 0x40;
+ else
+ mac_ctrl &= ~0x40;
+ window_write16(vp, mac_ctrl, 3, Wn3_MAC_Ctrl);
+ }
+}
+#else
+
+static void set_8021q_mode(struct net_device *dev, int enable)
+{
+}
+
+
+#endif
+
+/* MII transceiver control section.
+ Read and write the MII registers using software-generated serial
+ MDIO protocol. See the MII specifications or DP83840A data sheet
+ for details. */
+
+/* The maximum data clock rate is 2.5 Mhz. The minimum timing is usually
+ met by back-to-back PCI I/O cycles, but we insert a delay to avoid
+ "overclocking" issues. */
+static void mdio_delay(struct vortex_private *vp)
+{
+ window_read32(vp, 4, Wn4_PhysicalMgmt);
+}
+
+#define MDIO_SHIFT_CLK 0x01
+#define MDIO_DIR_WRITE 0x04
+#define MDIO_DATA_WRITE0 (0x00 | MDIO_DIR_WRITE)
+#define MDIO_DATA_WRITE1 (0x02 | MDIO_DIR_WRITE)
+#define MDIO_DATA_READ 0x02
+#define MDIO_ENB_IN 0x00
+
+/* Generate the preamble required for initial synchronization and
+ a few older transceivers. */
+static void mdio_sync(struct vortex_private *vp, int bits)
+{
+ /* Establish sync by sending at least 32 logic ones. */
+ while (-- bits >= 0) {
+ window_write16(vp, MDIO_DATA_WRITE1, 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ window_write16(vp, MDIO_DATA_WRITE1 | MDIO_SHIFT_CLK,
+ 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ }
+}
+
+static int mdio_read(struct net_device *dev, int phy_id, int location)
+{
+ int i;
+ struct vortex_private *vp = netdev_priv(dev);
+ int read_cmd = (0xf6 << 10) | (phy_id << 5) | location;
+ unsigned int retval = 0;
+
+ spin_lock_bh(&vp->mii_lock);
+
+ if (mii_preamble_required)
+ mdio_sync(vp, 32);
+
+ /* Shift the read command bits out. */
+ for (i = 14; i >= 0; i--) {
+ int dataval = (read_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
+ window_write16(vp, dataval, 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ window_write16(vp, dataval | MDIO_SHIFT_CLK,
+ 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ }
+ /* Read the two transition, 16 data, and wire-idle bits. */
+ for (i = 19; i > 0; i--) {
+ window_write16(vp, MDIO_ENB_IN, 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ retval = (retval << 1) |
+ ((window_read16(vp, 4, Wn4_PhysicalMgmt) &
+ MDIO_DATA_READ) ? 1 : 0);
+ window_write16(vp, MDIO_ENB_IN | MDIO_SHIFT_CLK,
+ 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ }
+
+ spin_unlock_bh(&vp->mii_lock);
+
+ return retval & 0x20000 ? 0xffff : retval>>1 & 0xffff;
+}
+
+static void mdio_write(struct net_device *dev, int phy_id, int location, int value)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ int write_cmd = 0x50020000 | (phy_id << 23) | (location << 18) | value;
+ int i;
+
+ spin_lock_bh(&vp->mii_lock);
+
+ if (mii_preamble_required)
+ mdio_sync(vp, 32);
+
+ /* Shift the command bits out. */
+ for (i = 31; i >= 0; i--) {
+ int dataval = (write_cmd&(1<<i)) ? MDIO_DATA_WRITE1 : MDIO_DATA_WRITE0;
+ window_write16(vp, dataval, 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ window_write16(vp, dataval | MDIO_SHIFT_CLK,
+ 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ }
+ /* Leave the interface idle. */
+ for (i = 1; i >= 0; i--) {
+ window_write16(vp, MDIO_ENB_IN, 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ window_write16(vp, MDIO_ENB_IN | MDIO_SHIFT_CLK,
+ 4, Wn4_PhysicalMgmt);
+ mdio_delay(vp);
+ }
+
+ spin_unlock_bh(&vp->mii_lock);
+}
+
+/* ACPI: Advanced Configuration and Power Interface. */
+/* Set Wake-On-LAN mode and put the board into D3 (power-down) state. */
+static void acpi_set_WOL(struct net_device *dev)
+{
+ struct vortex_private *vp = netdev_priv(dev);
+ void __iomem *ioaddr = vp->ioaddr;
+
+ device_set_wakeup_enable(vp->gendev, vp->enable_wol);
+
+ if (vp->enable_wol) {
+ /* Power up on: 1==Downloaded Filter, 2==Magic Packets, 4==Link Status. */
+ window_write16(vp, 2, 7, 0x0c);
+ /* The RxFilter must accept the WOL frames. */
+ iowrite16(SetRxFilter|RxStation|RxMulticast|RxBroadcast, ioaddr + EL3_CMD);
+ iowrite16(RxEnable, ioaddr + EL3_CMD);
+
+ if (pci_enable_wake(VORTEX_PCI(vp), PCI_D3hot, 1)) {
+ pr_info("%s: WOL not supported.\n", pci_name(VORTEX_PCI(vp)));
+
+ vp->enable_wol = 0;
+ return;
+ }
+
+ if (VORTEX_PCI(vp)->current_state < PCI_D3hot)
+ return;
+
+ /* Change the power state to D3; RxEnable doesn't take effect. */
+ pci_set_power_state(VORTEX_PCI(vp), PCI_D3hot);
+ }
+}
+
+
+static void vortex_remove_one(struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata(pdev);
+ struct vortex_private *vp;
+
+ if (!dev) {
+ pr_err("vortex_remove_one called for Compaq device!\n");
+ BUG();
+ }
+
+ vp = netdev_priv(dev);
+
+ if (vp->cb_fn_base)
+ pci_iounmap(pdev, vp->cb_fn_base);
+
+ unregister_netdev(dev);
+
+ pci_set_power_state(pdev, PCI_D0); /* Go active */
+ if (vp->pm_state_valid)
+ pci_restore_state(pdev);
+ pci_disable_device(pdev);
+
+ /* Should really use issue_and_wait() here */
+ iowrite16(TotalReset | ((vp->drv_flags & EEPROM_RESET) ? 0x04 : 0x14),
+ vp->ioaddr + EL3_CMD);
+
+ pci_iounmap(pdev, vp->ioaddr);
+
+ dma_free_coherent(&pdev->dev,
+ sizeof(struct boom_rx_desc) * RX_RING_SIZE +
+ sizeof(struct boom_tx_desc) * TX_RING_SIZE,
+ vp->rx_ring, vp->rx_ring_dma);
+
+ pci_release_regions(pdev);
+
+ free_netdev(dev);
+}
+
+
+static struct pci_driver vortex_driver = {
+ .name = "3c59x",
+ .probe = vortex_init_one,
+ .remove = vortex_remove_one,
+ .id_table = vortex_pci_tbl,
+ .driver.pm = VORTEX_PM_OPS,
+};
+
+
+static int vortex_have_pci;
+static int vortex_have_eisa;
+
+
+static int __init vortex_init(void)
+{
+ int pci_rc, eisa_rc;
+
+ pci_rc = pci_register_driver(&vortex_driver);
+ eisa_rc = vortex_eisa_init();
+
+ if (pci_rc == 0)
+ vortex_have_pci = 1;
+ if (eisa_rc > 0)
+ vortex_have_eisa = 1;
+
+ return (vortex_have_pci + vortex_have_eisa) ? 0 : -ENODEV;
+}
+
+
+static void __exit vortex_eisa_cleanup(void)
+{
+ void __iomem *ioaddr;
+
+#ifdef CONFIG_EISA
+ /* Take care of the EISA devices */
+ eisa_driver_unregister(&vortex_eisa_driver);
+#endif
+
+ if (compaq_net_device) {
+ ioaddr = ioport_map(compaq_net_device->base_addr,
+ VORTEX_TOTAL_SIZE);
+
+ unregister_netdev(compaq_net_device);
+ iowrite16(TotalReset, ioaddr + EL3_CMD);
+ release_region(compaq_net_device->base_addr,
+ VORTEX_TOTAL_SIZE);
+
+ free_netdev(compaq_net_device);
+ }
+}
+
+
+static void __exit vortex_cleanup(void)
+{
+ if (vortex_have_pci)
+ pci_unregister_driver(&vortex_driver);
+ if (vortex_have_eisa)
+ vortex_eisa_cleanup();
+}
+
+
+module_init(vortex_init);
+module_exit(vortex_cleanup);
diff --git a/drivers/net/ethernet/3com/Kconfig b/drivers/net/ethernet/3com/Kconfig
new file mode 100644
index 000000000..81db16744
--- /dev/null
+++ b/drivers/net/ethernet/3com/Kconfig
@@ -0,0 +1,71 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# 3Com Ethernet device configuration
+#
+
+config NET_VENDOR_3COM
+ bool "3Com devices"
+ default y
+ depends on ISA || EISA || PCI || PCMCIA
+ help
+ If you have a network (Ethernet) card belonging to this class, say Y.
+
+ Note that the answer to this question doesn't directly affect the
+ kernel: saying N will just cause the configurator to skip all
+ the questions about 3Com cards. If you say Y, you will be asked for
+ your specific card in the following questions.
+
+if NET_VENDOR_3COM
+
+config EL3
+ tristate "3c509/3c579 \"EtherLink III\" support"
+ depends on (ISA || EISA)
+ help
+ If you have a network (Ethernet) card belonging to the 3Com
+ EtherLinkIII series, say Y here.
+
+ If your card is not working you may need to use the DOS
+ setup disk to disable Plug & Play mode, and to select the default
+ media type.
+
+ To compile this driver as a module, choose M here. The module
+ will be called 3c509.
+
+config VORTEX
+ tristate "3c590/3c900 series (592/595/597) \"Vortex/Boomerang\" support"
+ depends on (PCI || EISA) && HAS_IOPORT_MAP
+ select MII
+ help
+ This option enables driver support for a large number of 10Mbps and
+ 10/100Mbps EISA, PCI and Cardbus 3Com network cards:
+
+ "Vortex" (Fast EtherLink 3c590/3c592/3c595/3c597) EISA and PCI
+ "Boomerang" (EtherLink XL 3c900 or 3c905) PCI
+ "Cyclone" (3c540/3c900/3c905/3c980/3c575/3c656) PCI and Cardbus
+ "Tornado" (3c905) PCI
+ "Hurricane" (3c555/3cSOHO) PCI
+
+ If you have such a card, say Y here. More specific information is in
+ <file:Documentation/networking/device_drivers/ethernet/3com/vortex.rst>
+ and in the comments at the beginning of
+ <file:drivers/net/ethernet/3com/3c59x.c>.
+
+ To compile this support as a module, choose M here.
+
+config TYPHOON
+ tristate "3cr990 series \"Typhoon\" support"
+ depends on PCI
+ select CRC32
+ help
+ This option enables driver support for the 3cr990 series of cards:
+
+ 3C990-TX, 3CR990-TX-95, 3CR990-TX-97, 3CR990-FX-95, 3CR990-FX-97,
+ 3CR990SVR, 3CR990SVR95, 3CR990SVR97, 3CR990-FX-95 Server,
+ 3CR990-FX-97 Server, 3C990B-TX-M, 3C990BSVR
+
+ If you have a network (Ethernet) card of this type, say Y here.
+
+ To compile this driver as a module, choose M here. The module
+ will be called typhoon.
+
+endif # NET_VENDOR_3COM
diff --git a/drivers/net/ethernet/3com/Makefile b/drivers/net/ethernet/3com/Makefile
new file mode 100644
index 000000000..2c65e4721
--- /dev/null
+++ b/drivers/net/ethernet/3com/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the 3Com Ethernet device drivers
+#
+
+obj-$(CONFIG_EL3) += 3c509.o
+obj-$(CONFIG_VORTEX) += 3c59x.o
+obj-$(CONFIG_TYPHOON) += typhoon.o
diff --git a/drivers/net/ethernet/3com/typhoon.c b/drivers/net/ethernet/3com/typhoon.c
new file mode 100644
index 000000000..fadc86907
--- /dev/null
+++ b/drivers/net/ethernet/3com/typhoon.c
@@ -0,0 +1,2609 @@
+/* typhoon.c: A Linux Ethernet device driver for 3Com 3CR990 family of NICs */
+/*
+ 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>.
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License (GPL), incorporated herein by reference.
+ Drivers based on or derived from this code fall under the GPL and must
+ retain the authorship, copyright and license notice. This file is not
+ a complete program and may only be used when the entire operating
+ system is licensed under the GPL.
+
+ This software is available on a public web site. It may enable
+ cryptographic capabilities of the 3Com hardware, and may be
+ exported from the United States under License Exception "TSU"
+ pursuant to 15 C.F.R. Section 740.13(e).
+
+ This work was funded by the National Library of Medicine under
+ the Department of Energy project number 0274DD06D1 and NLM project
+ number Y1-LM-2015-01.
+
+ This driver is designed for the 3Com 3CR990 Family of cards with the
+ 3XP Processor. It has been tested on x86 and sparc64.
+
+ KNOWN ISSUES:
+ *) Cannot DMA Rx packets to a 2 byte aligned address. Also firmware
+ issue. Hopefully 3Com will fix it.
+ *) 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.
+
+ 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.
+ * Setting to > 1518 effectively disables this feature.
+ */
+static int rx_copybreak = 200;
+
+/* Should we use MMIO or Port IO?
+ * 0: Port IO
+ * 1: MMIO
+ * 2: Try MMIO, fallback to Port IO
+ */
+static unsigned int use_mmio = 2;
+
+/* end user-configurable values */
+
+/* Maximum number of multicast addresses to filter (vs. rx-all-multicast).
+ */
+static const int multicast_filter_limit = 32;
+
+/* Operational parameters that are set at compile time. */
+
+/* Keep the ring sizes a power of two for compile efficiency.
+ * The compiler will convert <unsigned>'%'<2^N> into a bit mask.
+ * Making the Tx ring too large decreases the effectiveness of channel
+ * bonding and packet priority.
+ * There are no ill effects from too-large receive rings.
+ *
+ * We don't currently use the Hi Tx ring so, don't make it very big.
+ *
+ * Beware that if we start using the Hi Tx ring, we will need to change
+ * typhoon_num_free_tx() and typhoon_tx_complete() to account for that.
+ */
+#define TXHI_ENTRIES 2
+#define TXLO_ENTRIES 128
+#define RX_ENTRIES 32
+#define COMMAND_ENTRIES 16
+#define RESPONSE_ENTRIES 32
+
+#define COMMAND_RING_SIZE (COMMAND_ENTRIES * sizeof(struct cmd_desc))
+#define RESPONSE_RING_SIZE (RESPONSE_ENTRIES * sizeof(struct resp_desc))
+
+/* The 3XP will preload and remove 64 entries from the free buffer
+ * list, and we need one entry to keep the ring from wrapping, so
+ * to keep this a power of two, we use 128 entries.
+ */
+#define RXFREE_ENTRIES 128
+#define RXENT_ENTRIES (RXFREE_ENTRIES - 1)
+
+/* Operational parameters that usually are not changed. */
+
+/* Time in jiffies before concluding the transmitter is hung. */
+#define TX_TIMEOUT (2*HZ)
+
+#define PKT_BUF_SZ 1536
+#define FIRMWARE_NAME "3com/typhoon.bin"
+
+#define pr_fmt(fmt) KBUILD_MODNAME " " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/ethtool.h>
+#include <linux/if_vlan.h>
+#include <linux/crc32.h>
+#include <linux/bitops.h>
+#include <asm/processor.h>
+#include <asm/io.h>
+#include <linux/uaccess.h>
+#include <linux/in6.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+
+#include "typhoon.h"
+
+MODULE_AUTHOR("David Dillow <dave@thedillows.org>");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(FIRMWARE_NAME);
+MODULE_DESCRIPTION("3Com Typhoon Family (3C990, 3CR990, and variants)");
+MODULE_PARM_DESC(rx_copybreak, "Packets smaller than this are copied and "
+ "the buffer given back to the NIC. Default "
+ "is 200.");
+MODULE_PARM_DESC(use_mmio, "Use MMIO (1) or PIO(0) to access the NIC. "
+ "Default is to try MMIO and fallback to PIO.");
+module_param(rx_copybreak, int, 0);
+module_param(use_mmio, int, 0);
+
+#if TXLO_ENTRIES <= (2 * MAX_SKB_FRAGS)
+#error TX ring too small!
+#endif
+
+struct typhoon_card_info {
+ const char *name;
+ const int capabilities;
+};
+
+#define TYPHOON_CRYPTO_NONE 0x00
+#define TYPHOON_CRYPTO_DES 0x01
+#define TYPHOON_CRYPTO_3DES 0x02
+#define TYPHOON_CRYPTO_VARIABLE 0x04
+#define TYPHOON_FIBER 0x08
+#define TYPHOON_WAKEUP_NEEDS_RESET 0x10
+
+enum typhoon_cards {
+ TYPHOON_TX = 0, TYPHOON_TX95, TYPHOON_TX97, TYPHOON_SVR,
+ TYPHOON_SVR95, TYPHOON_SVR97, TYPHOON_TXM, TYPHOON_BSVR,
+ TYPHOON_FX95, TYPHOON_FX97, TYPHOON_FX95SVR, TYPHOON_FX97SVR,
+ TYPHOON_FXM,
+};
+
+/* directly indexed by enum typhoon_cards, above */
+static struct typhoon_card_info typhoon_card_info[] = {
+ { "3Com Typhoon (3C990-TX)",
+ TYPHOON_CRYPTO_NONE},
+ { "3Com Typhoon (3CR990-TX-95)",
+ TYPHOON_CRYPTO_DES},
+ { "3Com Typhoon (3CR990-TX-97)",
+ TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES},
+ { "3Com Typhoon (3C990SVR)",
+ TYPHOON_CRYPTO_NONE},
+ { "3Com Typhoon (3CR990SVR95)",
+ TYPHOON_CRYPTO_DES},
+ { "3Com Typhoon (3CR990SVR97)",
+ TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES},
+ { "3Com Typhoon2 (3C990B-TX-M)",
+ TYPHOON_CRYPTO_VARIABLE},
+ { "3Com Typhoon2 (3C990BSVR)",
+ TYPHOON_CRYPTO_VARIABLE},
+ { "3Com Typhoon (3CR990-FX-95)",
+ TYPHOON_CRYPTO_DES | TYPHOON_FIBER},
+ { "3Com Typhoon (3CR990-FX-97)",
+ TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES | TYPHOON_FIBER},
+ { "3Com Typhoon (3CR990-FX-95 Server)",
+ TYPHOON_CRYPTO_DES | TYPHOON_FIBER},
+ { "3Com Typhoon (3CR990-FX-97 Server)",
+ TYPHOON_CRYPTO_DES | TYPHOON_CRYPTO_3DES | TYPHOON_FIBER},
+ { "3Com Typhoon2 (3C990B-FX-97)",
+ TYPHOON_CRYPTO_VARIABLE | TYPHOON_FIBER},
+};
+
+/* Notes on the new subsystem numbering scheme:
+ * bits 0-1 indicate crypto capabilities: (0) variable, (1) DES, or (2) 3DES
+ * bit 4 indicates if this card has secured firmware (we don't support it)
+ * bit 8 indicates if this is a (0) copper or (1) fiber card
+ * bits 12-16 indicate card type: (0) client and (1) server
+ */
+static const struct pci_device_id typhoon_pci_tbl[] = {
+ {
+ PCI_VDEVICE(3COM, PCI_DEVICE_ID_3COM_3CR990),
+ .driver_data = TYPHOON_TX,
+ }, {
+ PCI_VDEVICE(3COM, PCI_DEVICE_ID_3COM_3CR990_TX_95),
+ .driver_data = TYPHOON_TX95,
+ }, {
+ PCI_VDEVICE(3COM, PCI_DEVICE_ID_3COM_3CR990_TX_97),
+ .driver_data = TYPHOON_TX97,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990B,
+ PCI_ANY_ID, 0x1000),
+ .driver_data = TYPHOON_TXM,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990B,
+ PCI_ANY_ID, 0x1102),
+ .driver_data = TYPHOON_FXM,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990B,
+ PCI_ANY_ID, 0x2000),
+ .driver_data = TYPHOON_BSVR,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990_FX,
+ PCI_ANY_ID, 0x1101),
+ .driver_data = TYPHOON_FX95,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990_FX,
+ PCI_ANY_ID, 0x1102),
+ .driver_data = TYPHOON_FX97,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990_FX,
+ PCI_ANY_ID, 0x2101),
+ .driver_data = TYPHOON_FX95SVR,
+ }, {
+ PCI_VDEVICE_SUB(3COM, PCI_DEVICE_ID_3COM_3CR990_FX,
+ PCI_ANY_ID, 0x2102),
+ .driver_data = TYPHOON_FX97SVR,
+ }, {
+ PCI_VDEVICE(3COM, PCI_DEVICE_ID_3COM_3CR990SVR95),
+ .driver_data = TYPHOON_SVR95,
+ }, {
+ PCI_VDEVICE(3COM, PCI_DEVICE_ID_3COM_3CR990SVR97),
+ .driver_data = TYPHOON_SVR97,
+ }, {
+ PCI_VDEVICE(3COM, PCI_DEVICE_ID_3COM_3CR990SVR),
+ .driver_data = TYPHOON_SVR,
+ },
+ { }
+};
+MODULE_DEVICE_TABLE(pci, typhoon_pci_tbl);
+
+/* Define the shared memory area
+ * Align everything the 3XP will normally be using.
+ * We'll need to move/align txHi if we start using that ring.
+ */
+#define __3xp_aligned ____cacheline_aligned
+struct typhoon_shared {
+ struct typhoon_interface iface;
+ struct typhoon_indexes indexes __3xp_aligned;
+ struct tx_desc txLo[TXLO_ENTRIES] __3xp_aligned;
+ struct rx_desc rxLo[RX_ENTRIES] __3xp_aligned;
+ struct rx_desc rxHi[RX_ENTRIES] __3xp_aligned;
+ struct cmd_desc cmd[COMMAND_ENTRIES] __3xp_aligned;
+ struct resp_desc resp[RESPONSE_ENTRIES] __3xp_aligned;
+ struct rx_free rxBuff[RXFREE_ENTRIES] __3xp_aligned;
+ u32 zeroWord;
+ struct tx_desc txHi[TXHI_ENTRIES];
+} __packed;
+
+struct rxbuff_ent {
+ struct sk_buff *skb;
+ dma_addr_t dma_addr;
+};
+
+struct typhoon {
+ /* Tx cache line section */
+ struct transmit_ring txLoRing ____cacheline_aligned;
+ struct pci_dev * tx_pdev;
+ void __iomem *tx_ioaddr;
+ u32 txlo_dma_addr;
+
+ /* Irq/Rx cache line section */
+ void __iomem *ioaddr ____cacheline_aligned;
+ struct typhoon_indexes *indexes;
+ u8 awaiting_resp;
+ u8 duplex;
+ u8 speed;
+ u8 card_state;
+ struct basic_ring rxLoRing;
+ struct pci_dev * pdev;
+ struct net_device * dev;
+ struct napi_struct napi;
+ struct basic_ring rxHiRing;
+ struct basic_ring rxBuffRing;
+ struct rxbuff_ent rxbuffers[RXENT_ENTRIES];
+
+ /* general section */
+ spinlock_t command_lock ____cacheline_aligned;
+ struct basic_ring cmdRing;
+ struct basic_ring respRing;
+ struct net_device_stats stats_saved;
+ struct typhoon_shared * shared;
+ dma_addr_t shared_dma;
+ __le16 xcvr_select;
+ __le16 wol_events;
+ __le32 offload;
+
+ /* unused stuff (future use) */
+ int capabilities;
+ struct transmit_ring txHiRing;
+};
+
+enum completion_wait_values {
+ NoWait = 0, WaitNoSleep, WaitSleep,
+};
+
+/* These are the values for the typhoon.card_state variable.
+ * These determine where the statistics will come from in get_stats().
+ * The sleep image does not support the statistics we need.
+ */
+enum state_values {
+ Sleeping = 0, Running,
+};
+
+/* PCI writes are not guaranteed to be posted in order, but outstanding writes
+ * cannot pass a read, so this forces current writes to post.
+ */
+#define typhoon_post_pci_writes(x) \
+ do { if (likely(use_mmio)) ioread32(x+TYPHOON_REG_HEARTBEAT); } while (0)
+
+/* We'll wait up to six seconds for a reset, and half a second normally.
+ */
+#define TYPHOON_UDELAY 50
+#define TYPHOON_RESET_TIMEOUT_SLEEP (6 * HZ)
+#define TYPHOON_RESET_TIMEOUT_NOSLEEP ((6 * 1000000) / TYPHOON_UDELAY)
+#define TYPHOON_WAIT_TIMEOUT ((1000000 / 2) / TYPHOON_UDELAY)
+
+#if defined(NETIF_F_TSO)
+#define skb_tso_size(x) (skb_shinfo(x)->gso_size)
+#define TSO_NUM_DESCRIPTORS 2
+#define TSO_OFFLOAD_ON TYPHOON_OFFLOAD_TCP_SEGMENT
+#else
+#define NETIF_F_TSO 0
+#define skb_tso_size(x) 0
+#define TSO_NUM_DESCRIPTORS 0
+#define TSO_OFFLOAD_ON 0
+#endif
+
+static inline void
+typhoon_inc_index(u32 *index, const int count, const int num_entries)
+{
+ /* Increment a ring index -- we can use this for all rings execept
+ * the Rx rings, as they use different size descriptors
+ * otherwise, everything is the same size as a cmd_desc
+ */
+ *index += count * sizeof(struct cmd_desc);
+ *index %= num_entries * sizeof(struct cmd_desc);
+}
+
+static inline void
+typhoon_inc_cmd_index(u32 *index, const int count)
+{
+ typhoon_inc_index(index, count, COMMAND_ENTRIES);
+}
+
+static inline void
+typhoon_inc_resp_index(u32 *index, const int count)
+{
+ typhoon_inc_index(index, count, RESPONSE_ENTRIES);
+}
+
+static inline void
+typhoon_inc_rxfree_index(u32 *index, const int count)
+{
+ typhoon_inc_index(index, count, RXFREE_ENTRIES);
+}
+
+static inline void
+typhoon_inc_tx_index(u32 *index, const int count)
+{
+ /* if we start using the Hi Tx ring, this needs updating */
+ typhoon_inc_index(index, count, TXLO_ENTRIES);
+}
+
+static inline void
+typhoon_inc_rx_index(u32 *index, const int count)
+{
+ /* sizeof(struct rx_desc) != sizeof(struct cmd_desc) */
+ *index += count * sizeof(struct rx_desc);
+ *index %= RX_ENTRIES * sizeof(struct rx_desc);
+}
+
+static int
+typhoon_reset(void __iomem *ioaddr, int wait_type)
+{
+ int i, err = 0;
+ int timeout;
+
+ if (wait_type == WaitNoSleep)
+ timeout = TYPHOON_RESET_TIMEOUT_NOSLEEP;
+ else
+ timeout = TYPHOON_RESET_TIMEOUT_SLEEP;
+
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK);
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS);
+
+ iowrite32(TYPHOON_RESET_ALL, ioaddr + TYPHOON_REG_SOFT_RESET);
+ typhoon_post_pci_writes(ioaddr);
+ udelay(1);
+ iowrite32(TYPHOON_RESET_NONE, ioaddr + TYPHOON_REG_SOFT_RESET);
+
+ if (wait_type != NoWait) {
+ for (i = 0; i < timeout; i++) {
+ if (ioread32(ioaddr + TYPHOON_REG_STATUS) ==
+ TYPHOON_STATUS_WAITING_FOR_HOST)
+ goto out;
+
+ if (wait_type == WaitSleep)
+ schedule_timeout_uninterruptible(1);
+ else
+ udelay(TYPHOON_UDELAY);
+ }
+
+ err = -ETIMEDOUT;
+ }
+
+out:
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK);
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS);
+
+ /* 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. 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
+typhoon_wait_status(void __iomem *ioaddr, u32 wait_value)
+{
+ int i, err = 0;
+
+ for (i = 0; i < TYPHOON_WAIT_TIMEOUT; i++) {
+ if (ioread32(ioaddr + TYPHOON_REG_STATUS) == wait_value)
+ goto out;
+ udelay(TYPHOON_UDELAY);
+ }
+
+ err = -ETIMEDOUT;
+
+out:
+ return err;
+}
+
+static inline void
+typhoon_media_status(struct net_device *dev, struct resp_desc *resp)
+{
+ if (resp->parm1 & TYPHOON_MEDIA_STAT_NO_LINK)
+ netif_carrier_off(dev);
+ else
+ netif_carrier_on(dev);
+}
+
+static inline void
+typhoon_hello(struct typhoon *tp)
+{
+ struct basic_ring *ring = &tp->cmdRing;
+ struct cmd_desc *cmd;
+
+ /* We only get a hello request if we've not sent anything to the
+ * card in a long while. If the lock is held, then we're in the
+ * process of issuing a command, so we don't need to respond.
+ */
+ if (spin_trylock(&tp->command_lock)) {
+ cmd = (struct cmd_desc *)(ring->ringBase + ring->lastWrite);
+ typhoon_inc_cmd_index(&ring->lastWrite, 1);
+
+ INIT_COMMAND_NO_RESPONSE(cmd, TYPHOON_CMD_HELLO_RESP);
+ wmb();
+ iowrite32(ring->lastWrite, tp->ioaddr + TYPHOON_REG_CMD_READY);
+ spin_unlock(&tp->command_lock);
+ }
+}
+
+static int
+typhoon_process_response(struct typhoon *tp, int resp_size,
+ struct resp_desc *resp_save)
+{
+ struct typhoon_indexes *indexes = tp->indexes;
+ struct resp_desc *resp;
+ u8 *base = tp->respRing.ringBase;
+ int count, len, wrap_len;
+ u32 cleared;
+ u32 ready;
+
+ cleared = le32_to_cpu(indexes->respCleared);
+ ready = le32_to_cpu(indexes->respReady);
+ while (cleared != ready) {
+ resp = (struct resp_desc *)(base + cleared);
+ count = resp->numDesc + 1;
+ if (resp_save && resp->seqNo) {
+ if (count > resp_size) {
+ resp_save->flags = TYPHOON_RESP_ERROR;
+ goto cleanup;
+ }
+
+ wrap_len = 0;
+ len = count * sizeof(*resp);
+ if (unlikely(cleared + len > RESPONSE_RING_SIZE)) {
+ wrap_len = cleared + len - RESPONSE_RING_SIZE;
+ len = RESPONSE_RING_SIZE - cleared;
+ }
+
+ memcpy(resp_save, resp, len);
+ if (unlikely(wrap_len)) {
+ resp_save += len / sizeof(*resp);
+ memcpy(resp_save, base, wrap_len);
+ }
+
+ resp_save = NULL;
+ } else if (resp->cmd == TYPHOON_CMD_READ_MEDIA_STATUS) {
+ typhoon_media_status(tp->dev, resp);
+ } else if (resp->cmd == TYPHOON_CMD_HELLO_RESP) {
+ typhoon_hello(tp);
+ } else {
+ netdev_err(tp->dev,
+ "dumping unexpected response 0x%04x:%d:0x%02x:0x%04x:%08x:%08x\n",
+ le16_to_cpu(resp->cmd),
+ resp->numDesc, resp->flags,
+ le16_to_cpu(resp->parm1),
+ le32_to_cpu(resp->parm2),
+ le32_to_cpu(resp->parm3));
+ }
+
+cleanup:
+ typhoon_inc_resp_index(&cleared, count);
+ }
+
+ indexes->respCleared = cpu_to_le32(cleared);
+ wmb();
+ return resp_save == NULL;
+}
+
+static inline int
+typhoon_num_free(int lastWrite, int lastRead, int ringSize)
+{
+ /* this works for all descriptors but rx_desc, as they are a
+ * different size than the cmd_desc -- everyone else is the same
+ */
+ lastWrite /= sizeof(struct cmd_desc);
+ lastRead /= sizeof(struct cmd_desc);
+ return (ringSize + lastRead - lastWrite - 1) % ringSize;
+}
+
+static inline int
+typhoon_num_free_cmd(struct typhoon *tp)
+{
+ int lastWrite = tp->cmdRing.lastWrite;
+ int cmdCleared = le32_to_cpu(tp->indexes->cmdCleared);
+
+ return typhoon_num_free(lastWrite, cmdCleared, COMMAND_ENTRIES);
+}
+
+static inline int
+typhoon_num_free_resp(struct typhoon *tp)
+{
+ int respReady = le32_to_cpu(tp->indexes->respReady);
+ int respCleared = le32_to_cpu(tp->indexes->respCleared);
+
+ return typhoon_num_free(respReady, respCleared, RESPONSE_ENTRIES);
+}
+
+static inline int
+typhoon_num_free_tx(struct transmit_ring *ring)
+{
+ /* if we start using the Hi Tx ring, this needs updating */
+ return typhoon_num_free(ring->lastWrite, ring->lastRead, TXLO_ENTRIES);
+}
+
+static int
+typhoon_issue_command(struct typhoon *tp, int num_cmd, struct cmd_desc *cmd,
+ int num_resp, struct resp_desc *resp)
+{
+ struct typhoon_indexes *indexes = tp->indexes;
+ struct basic_ring *ring = &tp->cmdRing;
+ struct resp_desc local_resp;
+ int i, err = 0;
+ int got_resp;
+ int freeCmd, freeResp;
+ int len, wrap_len;
+
+ spin_lock(&tp->command_lock);
+
+ freeCmd = typhoon_num_free_cmd(tp);
+ freeResp = typhoon_num_free_resp(tp);
+
+ if (freeCmd < num_cmd || freeResp < num_resp) {
+ netdev_err(tp->dev, "no descs for cmd, had (needed) %d (%d) cmd, %d (%d) resp\n",
+ freeCmd, num_cmd, freeResp, num_resp);
+ err = -ENOMEM;
+ goto out;
+ }
+
+ if (cmd->flags & TYPHOON_CMD_RESPOND) {
+ /* If we're expecting a response, but the caller hasn't given
+ * us a place to put it, we'll provide one.
+ */
+ tp->awaiting_resp = 1;
+ if (resp == NULL) {
+ resp = &local_resp;
+ num_resp = 1;
+ }
+ }
+
+ wrap_len = 0;
+ len = num_cmd * sizeof(*cmd);
+ if (unlikely(ring->lastWrite + len > COMMAND_RING_SIZE)) {
+ wrap_len = ring->lastWrite + len - COMMAND_RING_SIZE;
+ len = COMMAND_RING_SIZE - ring->lastWrite;
+ }
+
+ memcpy(ring->ringBase + ring->lastWrite, cmd, len);
+ if (unlikely(wrap_len)) {
+ struct cmd_desc *wrap_ptr = cmd;
+ wrap_ptr += len / sizeof(*cmd);
+ memcpy(ring->ringBase, wrap_ptr, wrap_len);
+ }
+
+ typhoon_inc_cmd_index(&ring->lastWrite, num_cmd);
+
+ /* "I feel a presence... another warrior is on the mesa."
+ */
+ wmb();
+ iowrite32(ring->lastWrite, tp->ioaddr + TYPHOON_REG_CMD_READY);
+ typhoon_post_pci_writes(tp->ioaddr);
+
+ if ((cmd->flags & TYPHOON_CMD_RESPOND) == 0)
+ goto out;
+
+ /* Ugh. We'll be here about 8ms, spinning our thumbs, unable to
+ * preempt or do anything other than take interrupts. So, don't
+ * wait for a response unless you have to.
+ *
+ * I've thought about trying to sleep here, but we're called
+ * from many contexts that don't allow that. Also, given the way
+ * 3Com has implemented irq coalescing, we would likely timeout --
+ * this has been observed in real life!
+ *
+ * The big killer is we have to wait to get stats from the card,
+ * though we could go to a periodic refresh of those if we don't
+ * mind them getting somewhat stale. The rest of the waiting
+ * commands occur during open/close/suspend/resume, so they aren't
+ * time critical. Creating SAs in the future will also have to
+ * wait here.
+ */
+ got_resp = 0;
+ for (i = 0; i < TYPHOON_WAIT_TIMEOUT && !got_resp; i++) {
+ if (indexes->respCleared != indexes->respReady)
+ got_resp = typhoon_process_response(tp, num_resp,
+ resp);
+ udelay(TYPHOON_UDELAY);
+ }
+
+ if (!got_resp) {
+ err = -ETIMEDOUT;
+ goto out;
+ }
+
+ /* Collect the error response even if we don't care about the
+ * rest of the response
+ */
+ if (resp->flags & TYPHOON_RESP_ERROR)
+ err = -EIO;
+
+out:
+ if (tp->awaiting_resp) {
+ tp->awaiting_resp = 0;
+ smp_wmb();
+
+ /* Ugh. If a response was added to the ring between
+ * the call to typhoon_process_response() and the clearing
+ * of tp->awaiting_resp, we could have missed the interrupt
+ * and it could hang in the ring an indeterminate amount of
+ * time. So, check for it, and interrupt ourselves if this
+ * is the case.
+ */
+ if (indexes->respCleared != indexes->respReady)
+ iowrite32(1, tp->ioaddr + TYPHOON_REG_SELF_INTERRUPT);
+ }
+
+ spin_unlock(&tp->command_lock);
+ return err;
+}
+
+static inline void
+typhoon_tso_fill(struct sk_buff *skb, struct transmit_ring *txRing,
+ u32 ring_dma)
+{
+ struct tcpopt_desc *tcpd;
+ u32 tcpd_offset = ring_dma;
+
+ tcpd = (struct tcpopt_desc *) (txRing->ringBase + txRing->lastWrite);
+ tcpd_offset += txRing->lastWrite;
+ tcpd_offset += offsetof(struct tcpopt_desc, bytesTx);
+ typhoon_inc_tx_index(&txRing->lastWrite, 1);
+
+ tcpd->flags = TYPHOON_OPT_DESC | TYPHOON_OPT_TCP_SEG;
+ tcpd->numDesc = 1;
+ tcpd->mss_flags = cpu_to_le16(skb_tso_size(skb));
+ tcpd->mss_flags |= TYPHOON_TSO_FIRST | TYPHOON_TSO_LAST;
+ tcpd->respAddrLo = cpu_to_le32(tcpd_offset);
+ tcpd->bytesTx = cpu_to_le32(skb->len);
+ tcpd->status = 0;
+}
+
+static netdev_tx_t
+typhoon_start_tx(struct sk_buff *skb, struct net_device *dev)
+{
+ struct typhoon *tp = netdev_priv(dev);
+ struct transmit_ring *txRing;
+ struct tx_desc *txd, *first_txd;
+ dma_addr_t skb_dma;
+ int numDesc;
+
+ /* we have two rings to choose from, but we only use txLo for now
+ * If we start using the Hi ring as well, we'll need to update
+ * typhoon_stop_runtime(), typhoon_interrupt(), typhoon_num_free_tx(),
+ * and TXHI_ENTRIES to match, as well as update the TSO code below
+ * to get the right DMA address
+ */
+ txRing = &tp->txLoRing;
+
+ /* We need one descriptor for each fragment of the sk_buff, plus the
+ * one for the ->data area of it.
+ *
+ * The docs say a maximum of 16 fragment descriptors per TCP option
+ * descriptor, then make a new packet descriptor and option descriptor
+ * for the next 16 fragments. The engineers say just an option
+ * descriptor is needed. I've tested up to 26 fragments with a single
+ * packet descriptor/option descriptor combo, so I use that for now.
+ *
+ * If problems develop with TSO, check this first.
+ */
+ numDesc = skb_shinfo(skb)->nr_frags + 1;
+ if (skb_is_gso(skb))
+ numDesc++;
+
+ /* When checking for free space in the ring, we need to also
+ * account for the initial Tx descriptor, and we always must leave
+ * at least one descriptor unused in the ring so that it doesn't
+ * wrap and look empty.
+ *
+ * The only time we should loop here is when we hit the race
+ * between marking the queue awake and updating the cleared index.
+ * Just loop and it will appear. This comes from the acenic driver.
+ */
+ while (unlikely(typhoon_num_free_tx(txRing) < (numDesc + 2)))
+ smp_rmb();
+
+ first_txd = (struct tx_desc *) (txRing->ringBase + txRing->lastWrite);
+ typhoon_inc_tx_index(&txRing->lastWrite, 1);
+
+ first_txd->flags = TYPHOON_TX_DESC | TYPHOON_DESC_VALID;
+ first_txd->numDesc = 0;
+ first_txd->len = 0;
+ first_txd->tx_addr = (u64)((unsigned long) skb);
+ first_txd->processFlags = 0;
+
+ if (skb->ip_summed == CHECKSUM_PARTIAL) {
+ /* The 3XP will figure out if this is UDP/TCP */
+ first_txd->processFlags |= TYPHOON_TX_PF_TCP_CHKSUM;
+ first_txd->processFlags |= TYPHOON_TX_PF_UDP_CHKSUM;
+ first_txd->processFlags |= TYPHOON_TX_PF_IP_CHKSUM;
+ }
+
+ if (skb_vlan_tag_present(skb)) {
+ first_txd->processFlags |=
+ TYPHOON_TX_PF_INSERT_VLAN | TYPHOON_TX_PF_VLAN_PRIORITY;
+ first_txd->processFlags |=
+ cpu_to_le32(htons(skb_vlan_tag_get(skb)) <<
+ TYPHOON_TX_PF_VLAN_TAG_SHIFT);
+ }
+
+ if (skb_is_gso(skb)) {
+ first_txd->processFlags |= TYPHOON_TX_PF_TCP_SEGMENT;
+ first_txd->numDesc++;
+
+ typhoon_tso_fill(skb, txRing, tp->txlo_dma_addr);
+ }
+
+ txd = (struct tx_desc *) (txRing->ringBase + txRing->lastWrite);
+ typhoon_inc_tx_index(&txRing->lastWrite, 1);
+
+ /* No need to worry about padding packet -- the firmware pads
+ * it with zeros to ETH_ZLEN for us.
+ */
+ if (skb_shinfo(skb)->nr_frags == 0) {
+ skb_dma = dma_map_single(&tp->tx_pdev->dev, skb->data,
+ skb->len, DMA_TO_DEVICE);
+ txd->flags = TYPHOON_FRAG_DESC | TYPHOON_DESC_VALID;
+ txd->len = cpu_to_le16(skb->len);
+ txd->frag.addr = cpu_to_le32(skb_dma);
+ txd->frag.addrHi = 0;
+ first_txd->numDesc++;
+ } else {
+ int i, len;
+
+ len = skb_headlen(skb);
+ skb_dma = dma_map_single(&tp->tx_pdev->dev, skb->data, len,
+ DMA_TO_DEVICE);
+ txd->flags = TYPHOON_FRAG_DESC | TYPHOON_DESC_VALID;
+ txd->len = cpu_to_le16(len);
+ txd->frag.addr = cpu_to_le32(skb_dma);
+ txd->frag.addrHi = 0;
+ first_txd->numDesc++;
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ void *frag_addr;
+
+ txd = (struct tx_desc *) (txRing->ringBase +
+ txRing->lastWrite);
+ typhoon_inc_tx_index(&txRing->lastWrite, 1);
+
+ len = skb_frag_size(frag);
+ frag_addr = skb_frag_address(frag);
+ skb_dma = dma_map_single(&tp->tx_pdev->dev, frag_addr,
+ len, DMA_TO_DEVICE);
+ txd->flags = TYPHOON_FRAG_DESC | TYPHOON_DESC_VALID;
+ txd->len = cpu_to_le16(len);
+ txd->frag.addr = cpu_to_le32(skb_dma);
+ txd->frag.addrHi = 0;
+ first_txd->numDesc++;
+ }
+ }
+
+ /* Kick the 3XP
+ */
+ wmb();
+ iowrite32(txRing->lastWrite, tp->tx_ioaddr + txRing->writeRegister);
+
+ /* If we don't have room to put the worst case packet on the
+ * queue, then we must stop the queue. We need 2 extra
+ * descriptors -- one to prevent ring wrap, and one for the
+ * Tx header.
+ */
+ numDesc = MAX_SKB_FRAGS + TSO_NUM_DESCRIPTORS + 1;
+
+ if (typhoon_num_free_tx(txRing) < (numDesc + 2)) {
+ netif_stop_queue(dev);
+
+ /* A Tx complete IRQ could have gotten between, making
+ * the ring free again. Only need to recheck here, since
+ * Tx is serialized.
+ */
+ if (typhoon_num_free_tx(txRing) >= (numDesc + 2))
+ netif_wake_queue(dev);
+ }
+
+ return NETDEV_TX_OK;
+}
+
+static void
+typhoon_set_rx_mode(struct net_device *dev)
+{
+ struct typhoon *tp = netdev_priv(dev);
+ struct cmd_desc xp_cmd;
+ u32 mc_filter[2];
+ __le16 filter;
+
+ filter = TYPHOON_RX_FILTER_DIRECTED | TYPHOON_RX_FILTER_BROADCAST;
+ if (dev->flags & IFF_PROMISC) {
+ filter |= TYPHOON_RX_FILTER_PROMISCOUS;
+ } else if ((netdev_mc_count(dev) > multicast_filter_limit) ||
+ (dev->flags & IFF_ALLMULTI)) {
+ /* Too many to match, or accept all multicasts. */
+ filter |= TYPHOON_RX_FILTER_ALL_MCAST;
+ } else if (!netdev_mc_empty(dev)) {
+ struct netdev_hw_addr *ha;
+
+ memset(mc_filter, 0, sizeof(mc_filter));
+ netdev_for_each_mc_addr(ha, dev) {
+ int bit = ether_crc(ETH_ALEN, ha->addr) & 0x3f;
+ mc_filter[bit >> 5] |= 1 << (bit & 0x1f);
+ }
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd,
+ TYPHOON_CMD_SET_MULTICAST_HASH);
+ xp_cmd.parm1 = TYPHOON_MCAST_HASH_SET;
+ xp_cmd.parm2 = cpu_to_le32(mc_filter[0]);
+ xp_cmd.parm3 = cpu_to_le32(mc_filter[1]);
+ typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+
+ filter |= TYPHOON_RX_FILTER_MCAST_HASH;
+ }
+
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_RX_FILTER);
+ xp_cmd.parm1 = filter;
+ typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+}
+
+static int
+typhoon_do_get_stats(struct typhoon *tp)
+{
+ struct net_device_stats *stats = &tp->dev->stats;
+ struct net_device_stats *saved = &tp->stats_saved;
+ struct cmd_desc xp_cmd;
+ struct resp_desc xp_resp[7];
+ struct stats_resp *s = (struct stats_resp *) xp_resp;
+ int err;
+
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_STATS);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 7, xp_resp);
+ if (err < 0)
+ return err;
+
+ /* 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) +
+ saved->tx_packets;
+ stats->tx_bytes = le64_to_cpu(s->txBytes) +
+ saved->tx_bytes;
+ stats->tx_errors = le32_to_cpu(s->txCarrierLost) +
+ saved->tx_errors;
+ stats->tx_carrier_errors = le32_to_cpu(s->txCarrierLost) +
+ saved->tx_carrier_errors;
+ stats->collisions = le32_to_cpu(s->txMultipleCollisions) +
+ saved->collisions;
+ stats->rx_packets = le32_to_cpu(s->rxPacketsGood) +
+ saved->rx_packets;
+ stats->rx_bytes = le64_to_cpu(s->rxBytesGood) +
+ saved->rx_bytes;
+ stats->rx_fifo_errors = le32_to_cpu(s->rxFifoOverruns) +
+ saved->rx_fifo_errors;
+ stats->rx_errors = le32_to_cpu(s->rxFifoOverruns) +
+ le32_to_cpu(s->BadSSD) + le32_to_cpu(s->rxCrcErrors) +
+ saved->rx_errors;
+ stats->rx_crc_errors = le32_to_cpu(s->rxCrcErrors) +
+ saved->rx_crc_errors;
+ stats->rx_length_errors = le32_to_cpu(s->rxOversized) +
+ saved->rx_length_errors;
+ tp->speed = (s->linkStatus & TYPHOON_LINK_100MBPS) ?
+ SPEED_100 : SPEED_10;
+ tp->duplex = (s->linkStatus & TYPHOON_LINK_FULL_DUPLEX) ?
+ DUPLEX_FULL : DUPLEX_HALF;
+
+ return 0;
+}
+
+static struct net_device_stats *
+typhoon_get_stats(struct net_device *dev)
+{
+ struct typhoon *tp = netdev_priv(dev);
+ struct net_device_stats *stats = &tp->dev->stats;
+ struct net_device_stats *saved = &tp->stats_saved;
+
+ smp_rmb();
+ if (tp->card_state == Sleeping)
+ return saved;
+
+ if (typhoon_do_get_stats(tp) < 0) {
+ netdev_err(dev, "error getting stats\n");
+ return saved;
+ }
+
+ return stats;
+}
+
+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];
+
+ smp_rmb();
+ if (tp->card_state == Sleeping) {
+ strscpy(info->fw_version, "Sleep image",
+ sizeof(info->fw_version));
+ } else {
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_VERSIONS);
+ if (typhoon_issue_command(tp, 1, &xp_cmd, 3, xp_resp) < 0) {
+ strscpy(info->fw_version, "Unknown runtime",
+ sizeof(info->fw_version));
+ } else {
+ u32 sleep_ver = le32_to_cpu(xp_resp[0].parm2);
+ snprintf(info->fw_version, sizeof(info->fw_version),
+ "%02x.%03x.%03x", sleep_ver >> 24,
+ (sleep_ver >> 12) & 0xfff, sleep_ver & 0xfff);
+ }
+ }
+
+ strscpy(info->driver, KBUILD_MODNAME, sizeof(info->driver));
+ strscpy(info->bus_info, pci_name(pci_dev), sizeof(info->bus_info));
+}
+
+static int
+typhoon_get_link_ksettings(struct net_device *dev,
+ struct ethtool_link_ksettings *cmd)
+{
+ struct typhoon *tp = netdev_priv(dev);
+ u32 supported, advertising = 0;
+
+ supported = SUPPORTED_100baseT_Half | SUPPORTED_100baseT_Full |
+ SUPPORTED_Autoneg;
+
+ switch (tp->xcvr_select) {
+ case TYPHOON_XCVR_10HALF:
+ advertising = ADVERTISED_10baseT_Half;
+ break;
+ case TYPHOON_XCVR_10FULL:
+ advertising = ADVERTISED_10baseT_Full;
+ break;
+ case TYPHOON_XCVR_100HALF:
+ advertising = ADVERTISED_100baseT_Half;
+ break;
+ case TYPHOON_XCVR_100FULL:
+ advertising = ADVERTISED_100baseT_Full;
+ break;
+ case TYPHOON_XCVR_AUTONEG:
+ advertising = ADVERTISED_10baseT_Half |
+ ADVERTISED_10baseT_Full |
+ ADVERTISED_100baseT_Half |
+ ADVERTISED_100baseT_Full |
+ ADVERTISED_Autoneg;
+ break;
+ }
+
+ if (tp->capabilities & TYPHOON_FIBER) {
+ supported |= SUPPORTED_FIBRE;
+ advertising |= ADVERTISED_FIBRE;
+ cmd->base.port = PORT_FIBRE;
+ } else {
+ supported |= SUPPORTED_10baseT_Half |
+ SUPPORTED_10baseT_Full |
+ SUPPORTED_TP;
+ advertising |= ADVERTISED_TP;
+ cmd->base.port = PORT_TP;
+ }
+
+ /* need to get stats to make these link speed/duplex valid */
+ typhoon_do_get_stats(tp);
+ cmd->base.speed = tp->speed;
+ cmd->base.duplex = tp->duplex;
+ cmd->base.phy_address = 0;
+ if (tp->xcvr_select == TYPHOON_XCVR_AUTONEG)
+ cmd->base.autoneg = AUTONEG_ENABLE;
+ else
+ cmd->base.autoneg = AUTONEG_DISABLE;
+
+ ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.supported,
+ supported);
+ ethtool_convert_legacy_u32_to_link_mode(cmd->link_modes.advertising,
+ advertising);
+
+ return 0;
+}
+
+static int
+typhoon_set_link_ksettings(struct net_device *dev,
+ const struct ethtool_link_ksettings *cmd)
+{
+ struct typhoon *tp = netdev_priv(dev);
+ u32 speed = cmd->base.speed;
+ struct cmd_desc xp_cmd;
+ __le16 xcvr;
+ int err;
+
+ err = -EINVAL;
+ if (cmd->base.autoneg == AUTONEG_ENABLE) {
+ xcvr = TYPHOON_XCVR_AUTONEG;
+ } else {
+ if (cmd->base.duplex == DUPLEX_HALF) {
+ if (speed == SPEED_10)
+ xcvr = TYPHOON_XCVR_10HALF;
+ else if (speed == SPEED_100)
+ xcvr = TYPHOON_XCVR_100HALF;
+ else
+ goto out;
+ } else if (cmd->base.duplex == DUPLEX_FULL) {
+ if (speed == SPEED_10)
+ xcvr = TYPHOON_XCVR_10FULL;
+ else if (speed == SPEED_100)
+ xcvr = TYPHOON_XCVR_100FULL;
+ else
+ goto out;
+ } else
+ goto out;
+ }
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_XCVR_SELECT);
+ xp_cmd.parm1 = xcvr;
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto out;
+
+ tp->xcvr_select = xcvr;
+ if (cmd->base.autoneg == AUTONEG_ENABLE) {
+ tp->speed = 0xff; /* invalid */
+ tp->duplex = 0xff; /* invalid */
+ } else {
+ tp->speed = speed;
+ tp->duplex = cmd->base.duplex;
+ }
+
+out:
+ return err;
+}
+
+static void
+typhoon_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ 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));
+}
+
+static int
+typhoon_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol)
+{
+ struct typhoon *tp = netdev_priv(dev);
+
+ if (wol->wolopts & ~(WAKE_PHY | WAKE_MAGIC))
+ return -EINVAL;
+
+ 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;
+}
+
+static void
+typhoon_get_ringparam(struct net_device *dev, struct ethtool_ringparam *ering,
+ struct kernel_ethtool_ringparam *kernel_ering,
+ struct netlink_ext_ack *extack)
+{
+ ering->rx_max_pending = RXENT_ENTRIES;
+ ering->tx_max_pending = TXLO_ENTRIES - 1;
+
+ ering->rx_pending = RXENT_ENTRIES;
+ ering->tx_pending = TXLO_ENTRIES - 1;
+}
+
+static const struct ethtool_ops typhoon_ethtool_ops = {
+ .get_drvinfo = typhoon_get_drvinfo,
+ .get_wol = typhoon_get_wol,
+ .set_wol = typhoon_set_wol,
+ .get_link = ethtool_op_get_link,
+ .get_ringparam = typhoon_get_ringparam,
+ .get_link_ksettings = typhoon_get_link_ksettings,
+ .set_link_ksettings = typhoon_set_link_ksettings,
+};
+
+static int
+typhoon_wait_interrupt(void __iomem *ioaddr)
+{
+ int i, err = 0;
+
+ for (i = 0; i < TYPHOON_WAIT_TIMEOUT; i++) {
+ if (ioread32(ioaddr + TYPHOON_REG_INTR_STATUS) &
+ TYPHOON_INTR_BOOTCMD)
+ goto out;
+ udelay(TYPHOON_UDELAY);
+ }
+
+ err = -ETIMEDOUT;
+
+out:
+ iowrite32(TYPHOON_INTR_BOOTCMD, ioaddr + TYPHOON_REG_INTR_STATUS);
+ return err;
+}
+
+#define shared_offset(x) offsetof(struct typhoon_shared, x)
+
+static void
+typhoon_init_interface(struct typhoon *tp)
+{
+ struct typhoon_interface *iface = &tp->shared->iface;
+ dma_addr_t shared_dma;
+
+ memset(tp->shared, 0, sizeof(struct typhoon_shared));
+
+ /* The *Hi members of iface are all init'd to zero by the memset().
+ */
+ shared_dma = tp->shared_dma + shared_offset(indexes);
+ iface->ringIndex = cpu_to_le32(shared_dma);
+
+ shared_dma = tp->shared_dma + shared_offset(txLo);
+ iface->txLoAddr = cpu_to_le32(shared_dma);
+ iface->txLoSize = cpu_to_le32(TXLO_ENTRIES * sizeof(struct tx_desc));
+
+ shared_dma = tp->shared_dma + shared_offset(txHi);
+ iface->txHiAddr = cpu_to_le32(shared_dma);
+ iface->txHiSize = cpu_to_le32(TXHI_ENTRIES * sizeof(struct tx_desc));
+
+ shared_dma = tp->shared_dma + shared_offset(rxBuff);
+ iface->rxBuffAddr = cpu_to_le32(shared_dma);
+ iface->rxBuffSize = cpu_to_le32(RXFREE_ENTRIES *
+ sizeof(struct rx_free));
+
+ shared_dma = tp->shared_dma + shared_offset(rxLo);
+ iface->rxLoAddr = cpu_to_le32(shared_dma);
+ iface->rxLoSize = cpu_to_le32(RX_ENTRIES * sizeof(struct rx_desc));
+
+ shared_dma = tp->shared_dma + shared_offset(rxHi);
+ iface->rxHiAddr = cpu_to_le32(shared_dma);
+ iface->rxHiSize = cpu_to_le32(RX_ENTRIES * sizeof(struct rx_desc));
+
+ shared_dma = tp->shared_dma + shared_offset(cmd);
+ iface->cmdAddr = cpu_to_le32(shared_dma);
+ iface->cmdSize = cpu_to_le32(COMMAND_RING_SIZE);
+
+ shared_dma = tp->shared_dma + shared_offset(resp);
+ iface->respAddr = cpu_to_le32(shared_dma);
+ iface->respSize = cpu_to_le32(RESPONSE_RING_SIZE);
+
+ shared_dma = tp->shared_dma + shared_offset(zeroWord);
+ iface->zeroAddr = cpu_to_le32(shared_dma);
+
+ tp->indexes = &tp->shared->indexes;
+ tp->txLoRing.ringBase = (u8 *) tp->shared->txLo;
+ tp->txHiRing.ringBase = (u8 *) tp->shared->txHi;
+ tp->rxLoRing.ringBase = (u8 *) tp->shared->rxLo;
+ tp->rxHiRing.ringBase = (u8 *) tp->shared->rxHi;
+ tp->rxBuffRing.ringBase = (u8 *) tp->shared->rxBuff;
+ tp->cmdRing.ringBase = (u8 *) tp->shared->cmd;
+ tp->respRing.ringBase = (u8 *) tp->shared->resp;
+
+ tp->txLoRing.writeRegister = TYPHOON_REG_TX_LO_READY;
+ tp->txHiRing.writeRegister = TYPHOON_REG_TX_HI_READY;
+
+ tp->txlo_dma_addr = le32_to_cpu(iface->txLoAddr);
+ tp->card_state = Sleeping;
+
+ tp->offload = TYPHOON_OFFLOAD_IP_CHKSUM | TYPHOON_OFFLOAD_TCP_CHKSUM;
+ tp->offload |= TYPHOON_OFFLOAD_UDP_CHKSUM | TSO_OFFLOAD_ON;
+ tp->offload |= TYPHOON_OFFLOAD_VLAN;
+
+ spin_lock_init(&tp->command_lock);
+
+ /* Force the writes to the shared memory area out before continuing. */
+ wmb();
+}
+
+static void
+typhoon_init_rings(struct typhoon *tp)
+{
+ memset(tp->indexes, 0, sizeof(struct typhoon_indexes));
+
+ tp->txLoRing.lastWrite = 0;
+ tp->txHiRing.lastWrite = 0;
+ tp->rxLoRing.lastWrite = 0;
+ tp->rxHiRing.lastWrite = 0;
+ tp->rxBuffRing.lastWrite = 0;
+ tp->cmdRing.lastWrite = 0;
+ tp->respRing.lastWrite = 0;
+
+ tp->txLoRing.lastRead = 0;
+ tp->txHiRing.lastRead = 0;
+}
+
+static const struct firmware *typhoon_fw;
+
+static int
+typhoon_request_firmware(struct typhoon *tp)
+{
+ const struct typhoon_file_header *fHdr;
+ const struct typhoon_section_header *sHdr;
+ const u8 *image_data;
+ u32 numSections;
+ u32 section_len;
+ u32 remaining;
+ int err;
+
+ if (typhoon_fw)
+ return 0;
+
+ err = request_firmware(&typhoon_fw, FIRMWARE_NAME, &tp->pdev->dev);
+ if (err) {
+ netdev_err(tp->dev, "Failed to load firmware \"%s\"\n",
+ FIRMWARE_NAME);
+ return err;
+ }
+
+ image_data = typhoon_fw->data;
+ remaining = typhoon_fw->size;
+ if (remaining < sizeof(struct typhoon_file_header))
+ goto invalid_fw;
+
+ fHdr = (struct typhoon_file_header *) image_data;
+ if (memcmp(fHdr->tag, "TYPHOON", 8))
+ goto invalid_fw;
+
+ numSections = le32_to_cpu(fHdr->numSections);
+ image_data += sizeof(struct typhoon_file_header);
+ remaining -= sizeof(struct typhoon_file_header);
+
+ while (numSections--) {
+ if (remaining < sizeof(struct typhoon_section_header))
+ goto invalid_fw;
+
+ sHdr = (struct typhoon_section_header *) image_data;
+ image_data += sizeof(struct typhoon_section_header);
+ section_len = le32_to_cpu(sHdr->len);
+
+ if (remaining < section_len)
+ goto invalid_fw;
+
+ image_data += section_len;
+ remaining -= section_len;
+ }
+
+ return 0;
+
+invalid_fw:
+ netdev_err(tp->dev, "Invalid firmware image\n");
+ release_firmware(typhoon_fw);
+ typhoon_fw = NULL;
+ return -EINVAL;
+}
+
+static int
+typhoon_download_firmware(struct typhoon *tp)
+{
+ void __iomem *ioaddr = tp->ioaddr;
+ struct pci_dev *pdev = tp->pdev;
+ const struct typhoon_file_header *fHdr;
+ const struct typhoon_section_header *sHdr;
+ const u8 *image_data;
+ void *dpage;
+ dma_addr_t dpage_dma;
+ __sum16 csum;
+ u32 irqEnabled;
+ u32 irqMasked;
+ u32 numSections;
+ u32 section_len;
+ u32 len;
+ u32 load_addr;
+ u32 hmac;
+ int i;
+ int err;
+
+ image_data = typhoon_fw->data;
+ fHdr = (struct typhoon_file_header *) image_data;
+
+ /* Cannot just map the firmware image using dma_map_single() as
+ * the firmware is vmalloc()'d and may not be physically contiguous,
+ * so we allocate some coherent memory to copy the sections into.
+ */
+ err = -ENOMEM;
+ dpage = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, &dpage_dma, GFP_ATOMIC);
+ if (!dpage) {
+ netdev_err(tp->dev, "no DMA mem for firmware\n");
+ goto err_out;
+ }
+
+ irqEnabled = ioread32(ioaddr + TYPHOON_REG_INTR_ENABLE);
+ iowrite32(irqEnabled | TYPHOON_INTR_BOOTCMD,
+ ioaddr + TYPHOON_REG_INTR_ENABLE);
+ irqMasked = ioread32(ioaddr + TYPHOON_REG_INTR_MASK);
+ iowrite32(irqMasked | TYPHOON_INTR_BOOTCMD,
+ ioaddr + TYPHOON_REG_INTR_MASK);
+
+ err = -ETIMEDOUT;
+ if (typhoon_wait_status(ioaddr, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) {
+ netdev_err(tp->dev, "card ready timeout\n");
+ goto err_out_irq;
+ }
+
+ numSections = le32_to_cpu(fHdr->numSections);
+ load_addr = le32_to_cpu(fHdr->startAddr);
+
+ iowrite32(TYPHOON_INTR_BOOTCMD, ioaddr + TYPHOON_REG_INTR_STATUS);
+ iowrite32(load_addr, ioaddr + TYPHOON_REG_DOWNLOAD_BOOT_ADDR);
+ hmac = le32_to_cpu(fHdr->hmacDigest[0]);
+ iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_0);
+ hmac = le32_to_cpu(fHdr->hmacDigest[1]);
+ iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_1);
+ hmac = le32_to_cpu(fHdr->hmacDigest[2]);
+ iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_2);
+ hmac = le32_to_cpu(fHdr->hmacDigest[3]);
+ iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_3);
+ hmac = le32_to_cpu(fHdr->hmacDigest[4]);
+ iowrite32(hmac, ioaddr + TYPHOON_REG_DOWNLOAD_HMAC_4);
+ typhoon_post_pci_writes(ioaddr);
+ iowrite32(TYPHOON_BOOTCMD_RUNTIME_IMAGE, ioaddr + TYPHOON_REG_COMMAND);
+
+ image_data += sizeof(struct typhoon_file_header);
+
+ /* The ioread32() in typhoon_wait_interrupt() will force the
+ * last write to the command register to post, so
+ * we don't need a typhoon_post_pci_writes() after it.
+ */
+ for (i = 0; i < numSections; i++) {
+ sHdr = (struct typhoon_section_header *) image_data;
+ image_data += sizeof(struct typhoon_section_header);
+ load_addr = le32_to_cpu(sHdr->startAddr);
+ section_len = le32_to_cpu(sHdr->len);
+
+ while (section_len) {
+ len = min_t(u32, section_len, PAGE_SIZE);
+
+ if (typhoon_wait_interrupt(ioaddr) < 0 ||
+ ioread32(ioaddr + TYPHOON_REG_STATUS) !=
+ TYPHOON_STATUS_WAITING_FOR_SEGMENT) {
+ netdev_err(tp->dev, "segment ready timeout\n");
+ goto err_out_irq;
+ }
+
+ /* Do an pseudo IPv4 checksum on the data -- first
+ * need to convert each u16 to cpu order before
+ * summing. Fortunately, due to the properties of
+ * the checksum, we can do this once, at the end.
+ */
+ csum = csum_fold(csum_partial_copy_nocheck(image_data,
+ dpage, len));
+
+ iowrite32(len, ioaddr + TYPHOON_REG_BOOT_LENGTH);
+ iowrite32(le16_to_cpu((__force __le16)csum),
+ ioaddr + TYPHOON_REG_BOOT_CHECKSUM);
+ iowrite32(load_addr,
+ ioaddr + TYPHOON_REG_BOOT_DEST_ADDR);
+ iowrite32(0, ioaddr + TYPHOON_REG_BOOT_DATA_HI);
+ iowrite32(dpage_dma, ioaddr + TYPHOON_REG_BOOT_DATA_LO);
+ typhoon_post_pci_writes(ioaddr);
+ iowrite32(TYPHOON_BOOTCMD_SEG_AVAILABLE,
+ ioaddr + TYPHOON_REG_COMMAND);
+
+ image_data += len;
+ load_addr += len;
+ section_len -= len;
+ }
+ }
+
+ if (typhoon_wait_interrupt(ioaddr) < 0 ||
+ ioread32(ioaddr + TYPHOON_REG_STATUS) !=
+ TYPHOON_STATUS_WAITING_FOR_SEGMENT) {
+ netdev_err(tp->dev, "final segment ready timeout\n");
+ goto err_out_irq;
+ }
+
+ iowrite32(TYPHOON_BOOTCMD_DNLD_COMPLETE, ioaddr + TYPHOON_REG_COMMAND);
+
+ if (typhoon_wait_status(ioaddr, TYPHOON_STATUS_WAITING_FOR_BOOT) < 0) {
+ netdev_err(tp->dev, "boot ready timeout, status 0x%0x\n",
+ ioread32(ioaddr + TYPHOON_REG_STATUS));
+ goto err_out_irq;
+ }
+
+ err = 0;
+
+err_out_irq:
+ iowrite32(irqMasked, ioaddr + TYPHOON_REG_INTR_MASK);
+ iowrite32(irqEnabled, ioaddr + TYPHOON_REG_INTR_ENABLE);
+
+ dma_free_coherent(&pdev->dev, PAGE_SIZE, dpage, dpage_dma);
+
+err_out:
+ return err;
+}
+
+static int
+typhoon_boot_3XP(struct typhoon *tp, u32 initial_status)
+{
+ void __iomem *ioaddr = tp->ioaddr;
+
+ if (typhoon_wait_status(ioaddr, initial_status) < 0) {
+ netdev_err(tp->dev, "boot ready timeout\n");
+ goto out_timeout;
+ }
+
+ iowrite32(0, ioaddr + TYPHOON_REG_BOOT_RECORD_ADDR_HI);
+ iowrite32(tp->shared_dma, ioaddr + TYPHOON_REG_BOOT_RECORD_ADDR_LO);
+ typhoon_post_pci_writes(ioaddr);
+ iowrite32(TYPHOON_BOOTCMD_REG_BOOT_RECORD,
+ ioaddr + TYPHOON_REG_COMMAND);
+
+ if (typhoon_wait_status(ioaddr, TYPHOON_STATUS_RUNNING) < 0) {
+ netdev_err(tp->dev, "boot finish timeout (status 0x%x)\n",
+ ioread32(ioaddr + TYPHOON_REG_STATUS));
+ goto out_timeout;
+ }
+
+ /* Clear the Transmit and Command ready registers
+ */
+ iowrite32(0, ioaddr + TYPHOON_REG_TX_HI_READY);
+ iowrite32(0, ioaddr + TYPHOON_REG_CMD_READY);
+ iowrite32(0, ioaddr + TYPHOON_REG_TX_LO_READY);
+ typhoon_post_pci_writes(ioaddr);
+ iowrite32(TYPHOON_BOOTCMD_BOOT, ioaddr + TYPHOON_REG_COMMAND);
+
+ return 0;
+
+out_timeout:
+ return -ETIMEDOUT;
+}
+
+static u32
+typhoon_clean_tx(struct typhoon *tp, struct transmit_ring *txRing,
+ volatile __le32 * index)
+{
+ u32 lastRead = txRing->lastRead;
+ struct tx_desc *tx;
+ dma_addr_t skb_dma;
+ int dma_len;
+ int type;
+
+ while (lastRead != le32_to_cpu(*index)) {
+ tx = (struct tx_desc *) (txRing->ringBase + lastRead);
+ type = tx->flags & TYPHOON_TYPE_MASK;
+
+ if (type == TYPHOON_TX_DESC) {
+ /* This tx_desc describes a packet.
+ */
+ unsigned long ptr = tx->tx_addr;
+ struct sk_buff *skb = (struct sk_buff *) ptr;
+ dev_kfree_skb_irq(skb);
+ } else if (type == TYPHOON_FRAG_DESC) {
+ /* This tx_desc describes a memory mapping. Free it.
+ */
+ skb_dma = (dma_addr_t) le32_to_cpu(tx->frag.addr);
+ dma_len = le16_to_cpu(tx->len);
+ dma_unmap_single(&tp->pdev->dev, skb_dma, dma_len,
+ DMA_TO_DEVICE);
+ }
+
+ tx->flags = 0;
+ typhoon_inc_tx_index(&lastRead, 1);
+ }
+
+ return lastRead;
+}
+
+static void
+typhoon_tx_complete(struct typhoon *tp, struct transmit_ring *txRing,
+ volatile __le32 * index)
+{
+ u32 lastRead;
+ int numDesc = MAX_SKB_FRAGS + 1;
+
+ /* This will need changing if we start to use the Hi Tx ring. */
+ lastRead = typhoon_clean_tx(tp, txRing, index);
+ if (netif_queue_stopped(tp->dev) && typhoon_num_free(txRing->lastWrite,
+ lastRead, TXLO_ENTRIES) > (numDesc + 2))
+ netif_wake_queue(tp->dev);
+
+ txRing->lastRead = lastRead;
+ smp_wmb();
+}
+
+static void
+typhoon_recycle_rx_skb(struct typhoon *tp, u32 idx)
+{
+ struct typhoon_indexes *indexes = tp->indexes;
+ struct rxbuff_ent *rxb = &tp->rxbuffers[idx];
+ struct basic_ring *ring = &tp->rxBuffRing;
+ struct rx_free *r;
+
+ if ((ring->lastWrite + sizeof(*r)) % (RXFREE_ENTRIES * sizeof(*r)) ==
+ le32_to_cpu(indexes->rxBuffCleared)) {
+ /* no room in ring, just drop the skb
+ */
+ dev_kfree_skb_any(rxb->skb);
+ rxb->skb = NULL;
+ return;
+ }
+
+ r = (struct rx_free *) (ring->ringBase + ring->lastWrite);
+ typhoon_inc_rxfree_index(&ring->lastWrite, 1);
+ r->virtAddr = idx;
+ r->physAddr = cpu_to_le32(rxb->dma_addr);
+
+ /* Tell the card about it */
+ wmb();
+ indexes->rxBuffReady = cpu_to_le32(ring->lastWrite);
+}
+
+static int
+typhoon_alloc_rx_skb(struct typhoon *tp, u32 idx)
+{
+ struct typhoon_indexes *indexes = tp->indexes;
+ struct rxbuff_ent *rxb = &tp->rxbuffers[idx];
+ struct basic_ring *ring = &tp->rxBuffRing;
+ struct rx_free *r;
+ struct sk_buff *skb;
+ dma_addr_t dma_addr;
+
+ rxb->skb = NULL;
+
+ if ((ring->lastWrite + sizeof(*r)) % (RXFREE_ENTRIES * sizeof(*r)) ==
+ le32_to_cpu(indexes->rxBuffCleared))
+ return -ENOMEM;
+
+ skb = netdev_alloc_skb(tp->dev, PKT_BUF_SZ);
+ if (!skb)
+ return -ENOMEM;
+
+#if 0
+ /* Please, 3com, fix the firmware to allow DMA to a unaligned
+ * address! Pretty please?
+ */
+ skb_reserve(skb, 2);
+#endif
+
+ dma_addr = dma_map_single(&tp->pdev->dev, skb->data, PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+
+ /* Since no card does 64 bit DAC, the high bits will never
+ * change from zero.
+ */
+ r = (struct rx_free *) (ring->ringBase + ring->lastWrite);
+ typhoon_inc_rxfree_index(&ring->lastWrite, 1);
+ r->virtAddr = idx;
+ r->physAddr = cpu_to_le32(dma_addr);
+ rxb->skb = skb;
+ rxb->dma_addr = dma_addr;
+
+ /* Tell the card about it */
+ wmb();
+ indexes->rxBuffReady = cpu_to_le32(ring->lastWrite);
+ return 0;
+}
+
+static int
+typhoon_rx(struct typhoon *tp, struct basic_ring *rxRing, volatile __le32 * ready,
+ volatile __le32 * cleared, int budget)
+{
+ struct rx_desc *rx;
+ struct sk_buff *skb, *new_skb;
+ struct rxbuff_ent *rxb;
+ dma_addr_t dma_addr;
+ u32 local_ready;
+ u32 rxaddr;
+ int pkt_len;
+ u32 idx;
+ __le32 csum_bits;
+ int received;
+
+ received = 0;
+ local_ready = le32_to_cpu(*ready);
+ rxaddr = le32_to_cpu(*cleared);
+ while (rxaddr != local_ready && budget > 0) {
+ rx = (struct rx_desc *) (rxRing->ringBase + rxaddr);
+ idx = rx->addr;
+ rxb = &tp->rxbuffers[idx];
+ skb = rxb->skb;
+ dma_addr = rxb->dma_addr;
+
+ typhoon_inc_rx_index(&rxaddr, 1);
+
+ if (rx->flags & TYPHOON_RX_ERROR) {
+ typhoon_recycle_rx_skb(tp, idx);
+ continue;
+ }
+
+ pkt_len = le16_to_cpu(rx->frameLen);
+
+ if (pkt_len < rx_copybreak &&
+ (new_skb = netdev_alloc_skb(tp->dev, pkt_len + 2)) != NULL) {
+ skb_reserve(new_skb, 2);
+ dma_sync_single_for_cpu(&tp->pdev->dev, dma_addr,
+ PKT_BUF_SZ, DMA_FROM_DEVICE);
+ skb_copy_to_linear_data(new_skb, skb->data, pkt_len);
+ dma_sync_single_for_device(&tp->pdev->dev, dma_addr,
+ PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ skb_put(new_skb, pkt_len);
+ typhoon_recycle_rx_skb(tp, idx);
+ } else {
+ new_skb = skb;
+ skb_put(new_skb, pkt_len);
+ dma_unmap_single(&tp->pdev->dev, dma_addr, PKT_BUF_SZ,
+ DMA_FROM_DEVICE);
+ typhoon_alloc_rx_skb(tp, idx);
+ }
+ new_skb->protocol = eth_type_trans(new_skb, tp->dev);
+ csum_bits = rx->rxStatus & (TYPHOON_RX_IP_CHK_GOOD |
+ TYPHOON_RX_UDP_CHK_GOOD | TYPHOON_RX_TCP_CHK_GOOD);
+ if (csum_bits ==
+ (TYPHOON_RX_IP_CHK_GOOD | TYPHOON_RX_TCP_CHK_GOOD) ||
+ csum_bits ==
+ (TYPHOON_RX_IP_CHK_GOOD | TYPHOON_RX_UDP_CHK_GOOD)) {
+ new_skb->ip_summed = CHECKSUM_UNNECESSARY;
+ } else
+ skb_checksum_none_assert(new_skb);
+
+ if (rx->rxStatus & TYPHOON_RX_VLAN)
+ __vlan_hwaccel_put_tag(new_skb, htons(ETH_P_8021Q),
+ ntohl(rx->vlanTag) & 0xffff);
+ netif_receive_skb(new_skb);
+
+ received++;
+ budget--;
+ }
+ *cleared = cpu_to_le32(rxaddr);
+
+ return received;
+}
+
+static void
+typhoon_fill_free_ring(struct typhoon *tp)
+{
+ u32 i;
+
+ for (i = 0; i < RXENT_ENTRIES; i++) {
+ struct rxbuff_ent *rxb = &tp->rxbuffers[i];
+ if (rxb->skb)
+ continue;
+ if (typhoon_alloc_rx_skb(tp, i) < 0)
+ break;
+ }
+}
+
+static int
+typhoon_poll(struct napi_struct *napi, int budget)
+{
+ struct typhoon *tp = container_of(napi, struct typhoon, napi);
+ struct typhoon_indexes *indexes = tp->indexes;
+ int work_done;
+
+ rmb();
+ if (!tp->awaiting_resp && indexes->respReady != indexes->respCleared)
+ typhoon_process_response(tp, 0, NULL);
+
+ if (le32_to_cpu(indexes->txLoCleared) != tp->txLoRing.lastRead)
+ typhoon_tx_complete(tp, &tp->txLoRing, &indexes->txLoCleared);
+
+ work_done = 0;
+
+ if (indexes->rxHiCleared != indexes->rxHiReady) {
+ work_done += typhoon_rx(tp, &tp->rxHiRing, &indexes->rxHiReady,
+ &indexes->rxHiCleared, budget);
+ }
+
+ if (indexes->rxLoCleared != indexes->rxLoReady) {
+ work_done += typhoon_rx(tp, &tp->rxLoRing, &indexes->rxLoReady,
+ &indexes->rxLoCleared, budget - work_done);
+ }
+
+ if (le32_to_cpu(indexes->rxBuffCleared) == tp->rxBuffRing.lastWrite) {
+ /* rxBuff ring is empty, try to fill it. */
+ typhoon_fill_free_ring(tp);
+ }
+
+ if (work_done < budget) {
+ napi_complete_done(napi, work_done);
+ iowrite32(TYPHOON_INTR_NONE,
+ tp->ioaddr + TYPHOON_REG_INTR_MASK);
+ typhoon_post_pci_writes(tp->ioaddr);
+ }
+
+ return work_done;
+}
+
+static irqreturn_t
+typhoon_interrupt(int irq, void *dev_instance)
+{
+ struct net_device *dev = dev_instance;
+ struct typhoon *tp = netdev_priv(dev);
+ void __iomem *ioaddr = tp->ioaddr;
+ u32 intr_status;
+
+ intr_status = ioread32(ioaddr + TYPHOON_REG_INTR_STATUS);
+ if (!(intr_status & TYPHOON_INTR_HOST_INT))
+ return IRQ_NONE;
+
+ iowrite32(intr_status, ioaddr + TYPHOON_REG_INTR_STATUS);
+
+ if (napi_schedule_prep(&tp->napi)) {
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK);
+ typhoon_post_pci_writes(ioaddr);
+ __napi_schedule(&tp->napi);
+ } else {
+ netdev_err(dev, "Error, poll already scheduled\n");
+ }
+ return IRQ_HANDLED;
+}
+
+static void
+typhoon_free_rx_rings(struct typhoon *tp)
+{
+ u32 i;
+
+ for (i = 0; i < RXENT_ENTRIES; i++) {
+ struct rxbuff_ent *rxb = &tp->rxbuffers[i];
+ if (rxb->skb) {
+ dma_unmap_single(&tp->pdev->dev, rxb->dma_addr,
+ PKT_BUF_SZ, DMA_FROM_DEVICE);
+ dev_kfree_skb(rxb->skb);
+ rxb->skb = NULL;
+ }
+ }
+}
+
+static int
+typhoon_sleep_early(struct typhoon *tp, __le16 events)
+{
+ void __iomem *ioaddr = tp->ioaddr;
+ struct cmd_desc xp_cmd;
+ int err;
+
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_ENABLE_WAKE_EVENTS);
+ xp_cmd.parm1 = events;
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0) {
+ netdev_err(tp->dev, "typhoon_sleep(): wake events cmd err %d\n",
+ err);
+ return err;
+ }
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_GOTO_SLEEP);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0) {
+ netdev_err(tp->dev, "typhoon_sleep(): sleep cmd err %d\n", err);
+ return err;
+ }
+
+ if (typhoon_wait_status(ioaddr, TYPHOON_STATUS_SLEEPING) < 0)
+ return -ETIMEDOUT;
+
+ /* Since we cannot monitor the status of the link while sleeping,
+ * tell the world it went away.
+ */
+ netif_carrier_off(tp->dev);
+
+ return 0;
+}
+
+static int
+typhoon_sleep(struct typhoon *tp, pci_power_t state, __le16 events)
+{
+ int err;
+
+ err = typhoon_sleep_early(tp, events);
+
+ if (err)
+ return err;
+
+ pci_enable_wake(tp->pdev, state, 1);
+ pci_disable_device(tp->pdev);
+ return pci_set_power_state(tp->pdev, state);
+}
+
+static int
+typhoon_wakeup(struct typhoon *tp, int wait_type)
+{
+ void __iomem *ioaddr = tp->ioaddr;
+
+ /* 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
+ * the old firmware pay for the reset.
+ */
+ iowrite32(TYPHOON_BOOTCMD_WAKEUP, ioaddr + TYPHOON_REG_COMMAND);
+ if (typhoon_wait_status(ioaddr, TYPHOON_STATUS_WAITING_FOR_HOST) < 0 ||
+ (tp->capabilities & TYPHOON_WAKEUP_NEEDS_RESET))
+ return typhoon_reset(ioaddr, wait_type);
+
+ return 0;
+}
+
+static int
+typhoon_start_runtime(struct typhoon *tp)
+{
+ struct net_device *dev = tp->dev;
+ void __iomem *ioaddr = tp->ioaddr;
+ struct cmd_desc xp_cmd;
+ int err;
+
+ typhoon_init_rings(tp);
+ typhoon_fill_free_ring(tp);
+
+ err = typhoon_download_firmware(tp);
+ if (err < 0) {
+ netdev_err(tp->dev, "cannot load runtime on 3XP\n");
+ goto error_out;
+ }
+
+ if (typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_BOOT) < 0) {
+ netdev_err(tp->dev, "cannot boot 3XP\n");
+ err = -EIO;
+ goto error_out;
+ }
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_MAX_PKT_SIZE);
+ xp_cmd.parm1 = cpu_to_le16(PKT_BUF_SZ);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_MAC_ADDRESS);
+ xp_cmd.parm1 = cpu_to_le16(ntohs(*(__be16 *)&dev->dev_addr[0]));
+ xp_cmd.parm2 = cpu_to_le32(ntohl(*(__be32 *)&dev->dev_addr[2]));
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ /* Disable IRQ coalescing -- we can reenable it when 3Com gives
+ * us some more information on how to control it.
+ */
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_IRQ_COALESCE_CTRL);
+ xp_cmd.parm1 = 0;
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_XCVR_SELECT);
+ xp_cmd.parm1 = tp->xcvr_select;
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_VLAN_TYPE_WRITE);
+ xp_cmd.parm1 = cpu_to_le16(ETH_P_8021Q);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_OFFLOAD_TASKS);
+ xp_cmd.parm2 = tp->offload;
+ xp_cmd.parm3 = tp->offload;
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ typhoon_set_rx_mode(dev);
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_TX_ENABLE);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_RX_ENABLE);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+ if (err < 0)
+ goto error_out;
+
+ tp->card_state = Running;
+ smp_wmb();
+
+ iowrite32(TYPHOON_INTR_ENABLE_ALL, ioaddr + TYPHOON_REG_INTR_ENABLE);
+ iowrite32(TYPHOON_INTR_NONE, ioaddr + TYPHOON_REG_INTR_MASK);
+ typhoon_post_pci_writes(ioaddr);
+
+ return 0;
+
+error_out:
+ typhoon_reset(ioaddr, WaitNoSleep);
+ typhoon_free_rx_rings(tp);
+ typhoon_init_rings(tp);
+ return err;
+}
+
+static int
+typhoon_stop_runtime(struct typhoon *tp, int wait_type)
+{
+ struct typhoon_indexes *indexes = tp->indexes;
+ struct transmit_ring *txLo = &tp->txLoRing;
+ void __iomem *ioaddr = tp->ioaddr;
+ struct cmd_desc xp_cmd;
+ int i;
+
+ /* Disable interrupts early, since we can't schedule a poll
+ * when called with !netif_running(). This will be posted
+ * when we force the posting of the command.
+ */
+ iowrite32(TYPHOON_INTR_NONE, ioaddr + TYPHOON_REG_INTR_ENABLE);
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_RX_DISABLE);
+ typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+
+ /* Wait 1/2 sec for any outstanding transmits to occur
+ * We'll cleanup after the reset if this times out.
+ */
+ for (i = 0; i < TYPHOON_WAIT_TIMEOUT; i++) {
+ if (indexes->txLoCleared == cpu_to_le32(txLo->lastWrite))
+ break;
+ udelay(TYPHOON_UDELAY);
+ }
+
+ if (i == TYPHOON_WAIT_TIMEOUT)
+ netdev_err(tp->dev, "halt timed out waiting for Tx to complete\n");
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_TX_DISABLE);
+ typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+
+ /* save the statistics so when we bring the interface up again,
+ * the values reported to userspace are correct.
+ */
+ tp->card_state = Sleeping;
+ smp_wmb();
+ typhoon_do_get_stats(tp);
+ memcpy(&tp->stats_saved, &tp->dev->stats, sizeof(struct net_device_stats));
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_HALT);
+ typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL);
+
+ if (typhoon_wait_status(ioaddr, TYPHOON_STATUS_HALTED) < 0)
+ netdev_err(tp->dev, "timed out waiting for 3XP to halt\n");
+
+ if (typhoon_reset(ioaddr, wait_type) < 0) {
+ netdev_err(tp->dev, "unable to reset 3XP\n");
+ return -ETIMEDOUT;
+ }
+
+ /* cleanup any outstanding Tx packets */
+ if (indexes->txLoCleared != cpu_to_le32(txLo->lastWrite)) {
+ indexes->txLoCleared = cpu_to_le32(txLo->lastWrite);
+ typhoon_clean_tx(tp, &tp->txLoRing, &indexes->txLoCleared);
+ }
+
+ return 0;
+}
+
+static void
+typhoon_tx_timeout(struct net_device *dev, unsigned int txqueue)
+{
+ struct typhoon *tp = netdev_priv(dev);
+
+ if (typhoon_reset(tp->ioaddr, WaitNoSleep) < 0) {
+ netdev_warn(dev, "could not reset in tx timeout\n");
+ goto truly_dead;
+ }
+
+ /* If we ever start using the Hi ring, it will need cleaning too */
+ typhoon_clean_tx(tp, &tp->txLoRing, &tp->indexes->txLoCleared);
+ typhoon_free_rx_rings(tp);
+
+ if (typhoon_start_runtime(tp) < 0) {
+ netdev_err(dev, "could not start runtime in tx timeout\n");
+ goto truly_dead;
+ }
+
+ netif_wake_queue(dev);
+ return;
+
+truly_dead:
+ /* Reset the hardware, and turn off carrier to avoid more timeouts */
+ typhoon_reset(tp->ioaddr, NoWait);
+ netif_carrier_off(dev);
+}
+
+static int
+typhoon_open(struct net_device *dev)
+{
+ struct typhoon *tp = netdev_priv(dev);
+ int err;
+
+ err = typhoon_request_firmware(tp);
+ if (err)
+ goto out;
+
+ pci_set_power_state(tp->pdev, PCI_D0);
+ pci_restore_state(tp->pdev);
+
+ err = typhoon_wakeup(tp, WaitSleep);
+ if (err < 0) {
+ netdev_err(dev, "unable to wakeup device\n");
+ goto out_sleep;
+ }
+
+ err = request_irq(dev->irq, typhoon_interrupt, IRQF_SHARED,
+ dev->name, dev);
+ if (err < 0)
+ goto out_sleep;
+
+ napi_enable(&tp->napi);
+
+ err = typhoon_start_runtime(tp);
+ if (err < 0) {
+ napi_disable(&tp->napi);
+ goto out_irq;
+ }
+
+ netif_start_queue(dev);
+ return 0;
+
+out_irq:
+ free_irq(dev->irq, dev);
+
+out_sleep:
+ if (typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) {
+ netdev_err(dev, "unable to reboot into sleep img\n");
+ typhoon_reset(tp->ioaddr, NoWait);
+ goto out;
+ }
+
+ if (typhoon_sleep(tp, PCI_D3hot, 0) < 0)
+ netdev_err(dev, "unable to go back to sleep\n");
+
+out:
+ return err;
+}
+
+static int
+typhoon_close(struct net_device *dev)
+{
+ struct typhoon *tp = netdev_priv(dev);
+
+ netif_stop_queue(dev);
+ napi_disable(&tp->napi);
+
+ if (typhoon_stop_runtime(tp, WaitSleep) < 0)
+ netdev_err(dev, "unable to stop runtime\n");
+
+ /* Make sure there is no irq handler running on a different CPU. */
+ free_irq(dev->irq, dev);
+
+ typhoon_free_rx_rings(tp);
+ typhoon_init_rings(tp);
+
+ if (typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0)
+ netdev_err(dev, "unable to boot sleep image\n");
+
+ if (typhoon_sleep(tp, PCI_D3hot, 0) < 0)
+ netdev_err(dev, "unable to put card to sleep\n");
+
+ return 0;
+}
+
+static int __maybe_unused
+typhoon_resume(struct device *dev_d)
+{
+ struct net_device *dev = dev_get_drvdata(dev_d);
+ struct typhoon *tp = netdev_priv(dev);
+
+ /* If we're down, resume when we are upped.
+ */
+ if (!netif_running(dev))
+ return 0;
+
+ if (typhoon_wakeup(tp, WaitNoSleep) < 0) {
+ netdev_err(dev, "critical: could not wake up in resume\n");
+ goto reset;
+ }
+
+ if (typhoon_start_runtime(tp) < 0) {
+ netdev_err(dev, "critical: could not start runtime in resume\n");
+ goto reset;
+ }
+
+ netif_device_attach(dev);
+ return 0;
+
+reset:
+ typhoon_reset(tp->ioaddr, NoWait);
+ return -EBUSY;
+}
+
+static int __maybe_unused
+typhoon_suspend(struct device *dev_d)
+{
+ struct pci_dev *pdev = to_pci_dev(dev_d);
+ struct net_device *dev = pci_get_drvdata(pdev);
+ struct typhoon *tp = netdev_priv(dev);
+ struct cmd_desc xp_cmd;
+
+ /* If we're down, we're already suspended.
+ */
+ if (!netif_running(dev))
+ return 0;
+
+ /* TYPHOON_OFFLOAD_VLAN is always on now, so this doesn't work */
+ if (tp->wol_events & TYPHOON_WAKE_MAGIC_PKT)
+ netdev_warn(dev, "cannot do WAKE_MAGIC with VLAN offloading\n");
+
+ netif_device_detach(dev);
+
+ if (typhoon_stop_runtime(tp, WaitNoSleep) < 0) {
+ netdev_err(dev, "unable to stop runtime\n");
+ goto need_resume;
+ }
+
+ typhoon_free_rx_rings(tp);
+ typhoon_init_rings(tp);
+
+ if (typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST) < 0) {
+ netdev_err(dev, "unable to boot sleep image\n");
+ goto need_resume;
+ }
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_MAC_ADDRESS);
+ xp_cmd.parm1 = cpu_to_le16(ntohs(*(__be16 *)&dev->dev_addr[0]));
+ xp_cmd.parm2 = cpu_to_le32(ntohl(*(__be32 *)&dev->dev_addr[2]));
+ if (typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL) < 0) {
+ netdev_err(dev, "unable to set mac address in suspend\n");
+ goto need_resume;
+ }
+
+ INIT_COMMAND_NO_RESPONSE(&xp_cmd, TYPHOON_CMD_SET_RX_FILTER);
+ xp_cmd.parm1 = TYPHOON_RX_FILTER_DIRECTED | TYPHOON_RX_FILTER_BROADCAST;
+ if (typhoon_issue_command(tp, 1, &xp_cmd, 0, NULL) < 0) {
+ netdev_err(dev, "unable to set rx filter in suspend\n");
+ goto need_resume;
+ }
+
+ if (typhoon_sleep_early(tp, tp->wol_events) < 0) {
+ netdev_err(dev, "unable to put card to sleep\n");
+ goto need_resume;
+ }
+
+ device_wakeup_enable(dev_d);
+
+ return 0;
+
+need_resume:
+ typhoon_resume(dev_d);
+ return -EBUSY;
+}
+
+static int
+typhoon_test_mmio(struct pci_dev *pdev)
+{
+ void __iomem *ioaddr = pci_iomap(pdev, 1, 128);
+ int mode = 0;
+ u32 val;
+
+ if (!ioaddr)
+ goto out;
+
+ if (ioread32(ioaddr + TYPHOON_REG_STATUS) !=
+ TYPHOON_STATUS_WAITING_FOR_HOST)
+ goto out_unmap;
+
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK);
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS);
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_ENABLE);
+
+ /* Ok, see if we can change our interrupt status register by
+ * sending ourselves an interrupt. If so, then MMIO works.
+ * The 50usec delay is arbitrary -- it could probably be smaller.
+ */
+ val = ioread32(ioaddr + TYPHOON_REG_INTR_STATUS);
+ if ((val & TYPHOON_INTR_SELF) == 0) {
+ iowrite32(1, ioaddr + TYPHOON_REG_SELF_INTERRUPT);
+ ioread32(ioaddr + TYPHOON_REG_INTR_STATUS);
+ udelay(50);
+ val = ioread32(ioaddr + TYPHOON_REG_INTR_STATUS);
+ if (val & TYPHOON_INTR_SELF)
+ mode = 1;
+ }
+
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_MASK);
+ iowrite32(TYPHOON_INTR_ALL, ioaddr + TYPHOON_REG_INTR_STATUS);
+ iowrite32(TYPHOON_INTR_NONE, ioaddr + TYPHOON_REG_INTR_ENABLE);
+ ioread32(ioaddr + TYPHOON_REG_INTR_STATUS);
+
+out_unmap:
+ pci_iounmap(pdev, ioaddr);
+
+out:
+ if (!mode)
+ pr_info("%s: falling back to port IO\n", pci_name(pdev));
+ return mode;
+}
+
+#if MAX_SKB_FRAGS > 32
+
+#include <net/vxlan.h>
+
+static netdev_features_t typhoon_features_check(struct sk_buff *skb,
+ struct net_device *dev,
+ netdev_features_t features)
+{
+ if (skb_shinfo(skb)->nr_frags > 32 && skb_is_gso(skb))
+ features &= ~NETIF_F_GSO_MASK;
+
+ features = vlan_features_check(skb, features);
+ return vxlan_features_check(skb, features);
+}
+#endif
+
+static const struct net_device_ops typhoon_netdev_ops = {
+ .ndo_open = typhoon_open,
+ .ndo_stop = typhoon_close,
+#if MAX_SKB_FRAGS > 32
+ .ndo_features_check = typhoon_features_check,
+#endif
+ .ndo_start_xmit = typhoon_start_tx,
+ .ndo_set_rx_mode = typhoon_set_rx_mode,
+ .ndo_tx_timeout = typhoon_tx_timeout,
+ .ndo_get_stats = typhoon_get_stats,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_set_mac_address = eth_mac_addr,
+};
+
+static int
+typhoon_init_one(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct net_device *dev;
+ struct typhoon *tp;
+ int card_id = (int) ent->driver_data;
+ u8 addr[ETH_ALEN] __aligned(4);
+ void __iomem *ioaddr;
+ void *shared;
+ dma_addr_t shared_dma;
+ struct cmd_desc xp_cmd;
+ struct resp_desc xp_resp[3];
+ int err = 0;
+ const char *err_msg;
+
+ dev = alloc_etherdev(sizeof(*tp));
+ if (dev == NULL) {
+ err_msg = "unable to alloc new net device";
+ err = -ENOMEM;
+ goto error_out;
+ }
+ SET_NETDEV_DEV(dev, &pdev->dev);
+
+ err = pci_enable_device(pdev);
+ if (err < 0) {
+ err_msg = "unable to enable device";
+ goto error_out_dev;
+ }
+
+ err = pci_set_mwi(pdev);
+ if (err < 0) {
+ err_msg = "unable to set MWI";
+ goto error_out_disable;
+ }
+
+ err = dma_set_mask(&pdev->dev, DMA_BIT_MASK(32));
+ if (err < 0) {
+ err_msg = "No usable DMA configuration";
+ goto error_out_mwi;
+ }
+
+ /* sanity checks on IO and MMIO BARs
+ */
+ if (!(pci_resource_flags(pdev, 0) & IORESOURCE_IO)) {
+ err_msg = "region #1 not a PCI IO resource, aborting";
+ err = -ENODEV;
+ goto error_out_mwi;
+ }
+ if (pci_resource_len(pdev, 0) < 128) {
+ err_msg = "Invalid PCI IO region size, aborting";
+ err = -ENODEV;
+ goto error_out_mwi;
+ }
+ if (!(pci_resource_flags(pdev, 1) & IORESOURCE_MEM)) {
+ err_msg = "region #1 not a PCI MMIO resource, aborting";
+ err = -ENODEV;
+ goto error_out_mwi;
+ }
+ if (pci_resource_len(pdev, 1) < 128) {
+ err_msg = "Invalid PCI MMIO region size, aborting";
+ err = -ENODEV;
+ goto error_out_mwi;
+ }
+
+ err = pci_request_regions(pdev, KBUILD_MODNAME);
+ if (err < 0) {
+ err_msg = "could not request regions";
+ goto error_out_mwi;
+ }
+
+ /* map our registers
+ */
+ if (use_mmio != 0 && use_mmio != 1)
+ use_mmio = typhoon_test_mmio(pdev);
+
+ ioaddr = pci_iomap(pdev, use_mmio, 128);
+ if (!ioaddr) {
+ err_msg = "cannot remap registers, aborting";
+ err = -EIO;
+ goto error_out_regions;
+ }
+
+ /* allocate pci dma space for rx and tx descriptor rings
+ */
+ shared = dma_alloc_coherent(&pdev->dev, sizeof(struct typhoon_shared),
+ &shared_dma, GFP_KERNEL);
+ if (!shared) {
+ err_msg = "could not allocate DMA memory";
+ err = -ENOMEM;
+ goto error_out_remap;
+ }
+
+ dev->irq = pdev->irq;
+ tp = netdev_priv(dev);
+ tp->shared = shared;
+ tp->shared_dma = shared_dma;
+ tp->pdev = pdev;
+ tp->tx_pdev = pdev;
+ tp->ioaddr = ioaddr;
+ tp->tx_ioaddr = ioaddr;
+ tp->dev = dev;
+
+ /* Init sequence:
+ * 1) Reset the adapter to clear any bad juju
+ * 2) Reload the sleep image
+ * 3) Boot the sleep image
+ * 4) Get the hardware address.
+ * 5) Put the card to sleep.
+ */
+ err = typhoon_reset(ioaddr, WaitSleep);
+ if (err < 0) {
+ err_msg = "could not reset 3XP";
+ 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, and save our
+ * state for resuming after a suspend.
+ */
+ pci_set_master(pdev);
+ pci_save_state(pdev);
+
+ typhoon_init_interface(tp);
+ typhoon_init_rings(tp);
+
+ err = typhoon_boot_3XP(tp, TYPHOON_STATUS_WAITING_FOR_HOST);
+ if (err < 0) {
+ err_msg = "cannot boot 3XP sleep image";
+ goto error_out_reset;
+ }
+
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_MAC_ADDRESS);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 1, xp_resp);
+ if (err < 0) {
+ err_msg = "cannot read MAC address";
+ goto error_out_reset;
+ }
+
+ *(__be16 *)&addr[0] = htons(le16_to_cpu(xp_resp[0].parm1));
+ *(__be32 *)&addr[2] = htonl(le32_to_cpu(xp_resp[0].parm2));
+ eth_hw_addr_set(dev, addr);
+
+ if (!is_valid_ether_addr(dev->dev_addr)) {
+ err_msg = "Could not obtain valid ethernet address, aborting";
+ err = -EIO;
+ goto error_out_reset;
+ }
+
+ /* Read the Sleep Image version last, so the response is valid
+ * later when we print out the version reported.
+ */
+ INIT_COMMAND_WITH_RESPONSE(&xp_cmd, TYPHOON_CMD_READ_VERSIONS);
+ err = typhoon_issue_command(tp, 1, &xp_cmd, 3, xp_resp);
+ if (err < 0) {
+ err_msg = "Could not get Sleep Image version";
+ goto error_out_reset;
+ }
+
+ tp->capabilities = typhoon_card_info[card_id].capabilities;
+ tp->xcvr_select = TYPHOON_XCVR_AUTONEG;
+
+ /* Typhoon 1.0 Sleep Images return one response descriptor to the
+ * READ_VERSIONS command. Those versions are OK after waking up
+ * from sleep without needing a reset. Typhoon 1.1+ Sleep Images
+ * seem to need a little extra help to get started. Since we don't
+ * know how to nudge it along, just kick it.
+ */
+ if (xp_resp[0].numDesc != 0)
+ tp->capabilities |= TYPHOON_WAKEUP_NEEDS_RESET;
+
+ err = typhoon_sleep(tp, PCI_D3hot, 0);
+ if (err < 0) {
+ err_msg = "cannot put adapter to sleep";
+ goto error_out_reset;
+ }
+
+ /* The chip-specific entries in the device structure. */
+ dev->netdev_ops = &typhoon_netdev_ops;
+ netif_napi_add_weight(dev, &tp->napi, typhoon_poll, 16);
+ dev->watchdog_timeo = TX_TIMEOUT;
+
+ dev->ethtool_ops = &typhoon_ethtool_ops;
+
+ /* We can handle scatter gather, up to 16 entries, and
+ * we can do IP checksumming (only version 4, doh...)
+ *
+ * There's no way to turn off the RX VLAN offloading and stripping
+ * on the current 3XP firmware -- it does not respect the offload
+ * settings -- so we only allow the user to toggle the TX processing.
+ */
+ dev->hw_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
+ NETIF_F_HW_VLAN_CTAG_TX;
+ dev->features = dev->hw_features |
+ NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_RXCSUM;
+
+ err = register_netdev(dev);
+ if (err < 0) {
+ err_msg = "unable to register netdev";
+ goto error_out_reset;
+ }
+
+ pci_set_drvdata(pdev, dev);
+
+ netdev_info(dev, "%s at %s 0x%llx, %pM\n",
+ typhoon_card_info[card_id].name,
+ use_mmio ? "MMIO" : "IO",
+ (unsigned long long)pci_resource_start(pdev, use_mmio),
+ dev->dev_addr);
+
+ /* xp_resp still contains the response to the READ_VERSIONS command.
+ * For debugging, let the user know what version he has.
+ */
+ if (xp_resp[0].numDesc == 0) {
+ /* This is the Typhoon 1.0 type Sleep Image, last 16 bits
+ * of version is Month/Day of build.
+ */
+ u16 monthday = le32_to_cpu(xp_resp[0].parm2) & 0xffff;
+ netdev_info(dev, "Typhoon 1.0 Sleep Image built %02u/%02u/2000\n",
+ monthday >> 8, monthday & 0xff);
+ } else if (xp_resp[0].numDesc == 2) {
+ /* This is the Typhoon 1.1+ type Sleep Image
+ */
+ u32 sleep_ver = le32_to_cpu(xp_resp[0].parm2);
+ u8 *ver_string = (u8 *) &xp_resp[1];
+ ver_string[25] = 0;
+ netdev_info(dev, "Typhoon 1.1+ Sleep Image version %02x.%03x.%03x %s\n",
+ sleep_ver >> 24, (sleep_ver >> 12) & 0xfff,
+ sleep_ver & 0xfff, ver_string);
+ } else {
+ netdev_warn(dev, "Unknown Sleep Image version (%u:%04x)\n",
+ xp_resp[0].numDesc, le32_to_cpu(xp_resp[0].parm2));
+ }
+
+ return 0;
+
+error_out_reset:
+ typhoon_reset(ioaddr, NoWait);
+
+error_out_dma:
+ dma_free_coherent(&pdev->dev, sizeof(struct typhoon_shared), shared,
+ shared_dma);
+error_out_remap:
+ pci_iounmap(pdev, ioaddr);
+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:
+ pr_err("%s: %s\n", pci_name(pdev), err_msg);
+ return err;
+}
+
+static void
+typhoon_remove_one(struct pci_dev *pdev)
+{
+ struct net_device *dev = pci_get_drvdata(pdev);
+ struct typhoon *tp = netdev_priv(dev);
+
+ unregister_netdev(dev);
+ pci_set_power_state(pdev, PCI_D0);
+ pci_restore_state(pdev);
+ typhoon_reset(tp->ioaddr, NoWait);
+ pci_iounmap(pdev, tp->ioaddr);
+ dma_free_coherent(&pdev->dev, sizeof(struct typhoon_shared),
+ tp->shared, tp->shared_dma);
+ pci_release_regions(pdev);
+ pci_clear_mwi(pdev);
+ pci_disable_device(pdev);
+ free_netdev(dev);
+}
+
+static SIMPLE_DEV_PM_OPS(typhoon_pm_ops, typhoon_suspend, typhoon_resume);
+
+static struct pci_driver typhoon_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = typhoon_pci_tbl,
+ .probe = typhoon_init_one,
+ .remove = typhoon_remove_one,
+ .driver.pm = &typhoon_pm_ops,
+};
+
+static int __init
+typhoon_init(void)
+{
+ return pci_register_driver(&typhoon_driver);
+}
+
+static void __exit
+typhoon_cleanup(void)
+{
+ release_firmware(typhoon_fw);
+ pci_unregister_driver(&typhoon_driver);
+}
+
+module_init(typhoon_init);
+module_exit(typhoon_cleanup);
diff --git a/drivers/net/ethernet/3com/typhoon.h b/drivers/net/ethernet/3com/typhoon.h
new file mode 100644
index 000000000..38e6dcab4
--- /dev/null
+++ b/drivers/net/ethernet/3com/typhoon.h
@@ -0,0 +1,624 @@
+/* typhoon.h: chip info for the 3Com 3CR990 family of controllers */
+/*
+ Written 2002-2003 by David Dillow <dave@thedillows.org>
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License (GPL), incorporated herein by reference.
+ Drivers based on or derived from this code fall under the GPL and must
+ retain the authorship, copyright and license notice. This file is not
+ a complete program and may only be used when the entire operating
+ system is licensed under the GPL.
+
+ This software is available on a public web site. It may enable
+ cryptographic capabilities of the 3Com hardware, and may be
+ exported from the United States under License Exception "TSU"
+ pursuant to 15 C.F.R. Section 740.13(e).
+
+ This work was funded by the National Library of Medicine under
+ the Department of Energy project number 0274DD06D1 and NLM project
+ number Y1-LM-2015-01.
+*/
+
+/* All Typhoon ring positions are specificed in bytes, and point to the
+ * first "clean" entry in the ring -- ie the next entry we use for whatever
+ * purpose.
+ */
+
+/* The Typhoon basic ring
+ * ringBase: where this ring lives (our virtual address)
+ * lastWrite: the next entry we'll use
+ */
+struct basic_ring {
+ u8 *ringBase;
+ u32 lastWrite;
+};
+
+/* The Typhoon transmit ring -- same as a basic ring, plus:
+ * lastRead: where we're at in regard to cleaning up the ring
+ * writeRegister: register to use for writing (different for Hi & Lo rings)
+ */
+struct transmit_ring {
+ u8 *ringBase;
+ u32 lastWrite;
+ u32 lastRead;
+ int writeRegister;
+};
+
+/* The host<->Typhoon ring index structure
+ * This indicates the current positions in the rings
+ *
+ * All values must be in little endian format for the 3XP
+ *
+ * rxHiCleared: entry we've cleared to in the Hi receive ring
+ * rxLoCleared: entry we've cleared to in the Lo receive ring
+ * rxBuffReady: next entry we'll put a free buffer in
+ * respCleared: entry we've cleared to in the response ring
+ *
+ * txLoCleared: entry the NIC has cleared to in the Lo transmit ring
+ * txHiCleared: entry the NIC has cleared to in the Hi transmit ring
+ * rxLoReady: entry the NIC has filled to in the Lo receive ring
+ * rxBuffCleared: entry the NIC has cleared in the free buffer ring
+ * cmdCleared: entry the NIC has cleared in the command ring
+ * respReady: entry the NIC has filled to in the response ring
+ * rxHiReady: entry the NIC has filled to in the Hi receive ring
+ */
+struct typhoon_indexes {
+ /* The first four are written by the host, and read by the NIC */
+ volatile __le32 rxHiCleared;
+ volatile __le32 rxLoCleared;
+ volatile __le32 rxBuffReady;
+ volatile __le32 respCleared;
+
+ /* The remaining are written by the NIC, and read by the host */
+ volatile __le32 txLoCleared;
+ volatile __le32 txHiCleared;
+ volatile __le32 rxLoReady;
+ volatile __le32 rxBuffCleared;
+ volatile __le32 cmdCleared;
+ volatile __le32 respReady;
+ volatile __le32 rxHiReady;
+} __packed;
+
+/* The host<->Typhoon interface
+ * Our means of communicating where things are
+ *
+ * All values must be in little endian format for the 3XP
+ *
+ * ringIndex: 64 bit bus address of the index structure
+ * txLoAddr: 64 bit bus address of the Lo transmit ring
+ * txLoSize: size (in bytes) of the Lo transmit ring
+ * txHi*: as above for the Hi priority transmit ring
+ * rxLo*: as above for the Lo priority receive ring
+ * rxBuff*: as above for the free buffer ring
+ * cmd*: as above for the command ring
+ * resp*: as above for the response ring
+ * zeroAddr: 64 bit bus address of a zero word (for DMA)
+ * rxHi*: as above for the Hi Priority receive ring
+ *
+ * While there is room for 64 bit addresses, current versions of the 3XP
+ * only do 32 bit addresses, so the *Hi for each of the above will always
+ * be zero.
+ */
+struct typhoon_interface {
+ __le32 ringIndex;
+ __le32 ringIndexHi;
+ __le32 txLoAddr;
+ __le32 txLoAddrHi;
+ __le32 txLoSize;
+ __le32 txHiAddr;
+ __le32 txHiAddrHi;
+ __le32 txHiSize;
+ __le32 rxLoAddr;
+ __le32 rxLoAddrHi;
+ __le32 rxLoSize;
+ __le32 rxBuffAddr;
+ __le32 rxBuffAddrHi;
+ __le32 rxBuffSize;
+ __le32 cmdAddr;
+ __le32 cmdAddrHi;
+ __le32 cmdSize;
+ __le32 respAddr;
+ __le32 respAddrHi;
+ __le32 respSize;
+ __le32 zeroAddr;
+ __le32 zeroAddrHi;
+ __le32 rxHiAddr;
+ __le32 rxHiAddrHi;
+ __le32 rxHiSize;
+} __packed;
+
+/* The Typhoon transmit/fragment descriptor
+ *
+ * A packet is described by a packet descriptor, followed by option descriptors,
+ * if any, then one or more fragment descriptors.
+ *
+ * Packet descriptor:
+ * flags: Descriptor type
+ * len:i zero, or length of this packet
+ * addr*: 8 bytes of opaque data to the firmware -- for skb pointer
+ * processFlags: Determine offload tasks to perform on this packet.
+ *
+ * Fragment descriptor:
+ * flags: Descriptor type
+ * len:i length of this fragment
+ * addr: low bytes of DMA address for this part of the packet
+ * addrHi: hi bytes of DMA address for this part of the packet
+ * processFlags: must be zero
+ *
+ * TYPHOON_DESC_VALID is not mentioned in their docs, but their Linux
+ * driver uses it.
+ */
+struct tx_desc {
+ u8 flags;
+#define TYPHOON_TYPE_MASK 0x07
+#define TYPHOON_FRAG_DESC 0x00
+#define TYPHOON_TX_DESC 0x01
+#define TYPHOON_CMD_DESC 0x02
+#define TYPHOON_OPT_DESC 0x03
+#define TYPHOON_RX_DESC 0x04
+#define TYPHOON_RESP_DESC 0x05
+#define TYPHOON_OPT_TYPE_MASK 0xf0
+#define TYPHOON_OPT_IPSEC 0x00
+#define TYPHOON_OPT_TCP_SEG 0x10
+#define TYPHOON_CMD_RESPOND 0x40
+#define TYPHOON_RESP_ERROR 0x40
+#define TYPHOON_RX_ERROR 0x40
+#define TYPHOON_DESC_VALID 0x80
+ u8 numDesc;
+ __le16 len;
+ union {
+ struct {
+ __le32 addr;
+ __le32 addrHi;
+ } frag;
+ u64 tx_addr; /* opaque for hardware, for TX_DESC */
+ };
+ __le32 processFlags;
+#define TYPHOON_TX_PF_NO_CRC cpu_to_le32(0x00000001)
+#define TYPHOON_TX_PF_IP_CHKSUM cpu_to_le32(0x00000002)
+#define TYPHOON_TX_PF_TCP_CHKSUM cpu_to_le32(0x00000004)
+#define TYPHOON_TX_PF_TCP_SEGMENT cpu_to_le32(0x00000008)
+#define TYPHOON_TX_PF_INSERT_VLAN cpu_to_le32(0x00000010)
+#define TYPHOON_TX_PF_IPSEC cpu_to_le32(0x00000020)
+#define TYPHOON_TX_PF_VLAN_PRIORITY cpu_to_le32(0x00000040)
+#define TYPHOON_TX_PF_UDP_CHKSUM cpu_to_le32(0x00000080)
+#define TYPHOON_TX_PF_PAD_FRAME cpu_to_le32(0x00000100)
+#define TYPHOON_TX_PF_RESERVED cpu_to_le32(0x00000e00)
+#define TYPHOON_TX_PF_VLAN_MASK cpu_to_le32(0x0ffff000)
+#define TYPHOON_TX_PF_INTERNAL cpu_to_le32(0xf0000000)
+#define TYPHOON_TX_PF_VLAN_TAG_SHIFT 12
+} __packed;
+
+/* The TCP Segmentation offload option descriptor
+ *
+ * flags: descriptor type
+ * numDesc: must be 1
+ * mss_flags: bits 0-11 (little endian) are MSS, 12 is first TSO descriptor
+ * 13 is list TSO descriptor, set both if only one TSO
+ * respAddrLo: low bytes of address of the bytesTx field of this descriptor
+ * bytesTx: total number of bytes in this TSO request
+ * status: 0 on completion
+ */
+struct tcpopt_desc {
+ u8 flags;
+ u8 numDesc;
+ __le16 mss_flags;
+#define TYPHOON_TSO_FIRST cpu_to_le16(0x1000)
+#define TYPHOON_TSO_LAST cpu_to_le16(0x2000)
+ __le32 respAddrLo;
+ __le32 bytesTx;
+ __le32 status;
+} __packed;
+
+/* The IPSEC Offload descriptor
+ *
+ * flags: descriptor type
+ * numDesc: must be 1
+ * ipsecFlags: bit 0: 0 -- generate IV, 1 -- use supplied IV
+ * sa1, sa2: Security Association IDs for this packet
+ * reserved: set to 0
+ */
+struct ipsec_desc {
+ u8 flags;
+ u8 numDesc;
+ __le16 ipsecFlags;
+#define TYPHOON_IPSEC_GEN_IV cpu_to_le16(0x0000)
+#define TYPHOON_IPSEC_USE_IV cpu_to_le16(0x0001)
+ __le32 sa1;
+ __le32 sa2;
+ __le32 reserved;
+} __packed;
+
+/* The Typhoon receive descriptor (Updated by NIC)
+ *
+ * flags: Descriptor type, error indication
+ * numDesc: Always zero
+ * frameLen: the size of the packet received
+ * addr: low 32 bytes of the virtual addr passed in for this buffer
+ * addrHi: high 32 bytes of the virtual addr passed in for this buffer
+ * rxStatus: Error if set in flags, otherwise result of offload processing
+ * filterResults: results of filtering on packet, not used
+ * ipsecResults: Results of IPSEC processing
+ * vlanTag: the 801.2q TCI from the packet
+ */
+struct rx_desc {
+ u8 flags;
+ u8 numDesc;
+ __le16 frameLen;
+ u32 addr; /* opaque, comes from virtAddr */
+ u32 addrHi; /* opaque, comes from virtAddrHi */
+ __le32 rxStatus;
+#define TYPHOON_RX_ERR_INTERNAL cpu_to_le32(0x00000000)
+#define TYPHOON_RX_ERR_FIFO_UNDERRUN cpu_to_le32(0x00000001)
+#define TYPHOON_RX_ERR_BAD_SSD cpu_to_le32(0x00000002)
+#define TYPHOON_RX_ERR_RUNT cpu_to_le32(0x00000003)
+#define TYPHOON_RX_ERR_CRC cpu_to_le32(0x00000004)
+#define TYPHOON_RX_ERR_OVERSIZE cpu_to_le32(0x00000005)
+#define TYPHOON_RX_ERR_ALIGN cpu_to_le32(0x00000006)
+#define TYPHOON_RX_ERR_DRIBBLE cpu_to_le32(0x00000007)
+#define TYPHOON_RX_PROTO_MASK cpu_to_le32(0x00000003)
+#define TYPHOON_RX_PROTO_UNKNOWN cpu_to_le32(0x00000000)
+#define TYPHOON_RX_PROTO_IP cpu_to_le32(0x00000001)
+#define TYPHOON_RX_PROTO_IPX cpu_to_le32(0x00000002)
+#define TYPHOON_RX_VLAN cpu_to_le32(0x00000004)
+#define TYPHOON_RX_IP_FRAG cpu_to_le32(0x00000008)
+#define TYPHOON_RX_IPSEC cpu_to_le32(0x00000010)
+#define TYPHOON_RX_IP_CHK_FAIL cpu_to_le32(0x00000020)
+#define TYPHOON_RX_TCP_CHK_FAIL cpu_to_le32(0x00000040)
+#define TYPHOON_RX_UDP_CHK_FAIL cpu_to_le32(0x00000080)
+#define TYPHOON_RX_IP_CHK_GOOD cpu_to_le32(0x00000100)
+#define TYPHOON_RX_TCP_CHK_GOOD cpu_to_le32(0x00000200)
+#define TYPHOON_RX_UDP_CHK_GOOD cpu_to_le32(0x00000400)
+ __le16 filterResults;
+#define TYPHOON_RX_FILTER_MASK cpu_to_le16(0x7fff)
+#define TYPHOON_RX_FILTERED cpu_to_le16(0x8000)
+ __le16 ipsecResults;
+#define TYPHOON_RX_OUTER_AH_GOOD cpu_to_le16(0x0001)
+#define TYPHOON_RX_OUTER_ESP_GOOD cpu_to_le16(0x0002)
+#define TYPHOON_RX_INNER_AH_GOOD cpu_to_le16(0x0004)
+#define TYPHOON_RX_INNER_ESP_GOOD cpu_to_le16(0x0008)
+#define TYPHOON_RX_OUTER_AH_FAIL cpu_to_le16(0x0010)
+#define TYPHOON_RX_OUTER_ESP_FAIL cpu_to_le16(0x0020)
+#define TYPHOON_RX_INNER_AH_FAIL cpu_to_le16(0x0040)
+#define TYPHOON_RX_INNER_ESP_FAIL cpu_to_le16(0x0080)
+#define TYPHOON_RX_UNKNOWN_SA cpu_to_le16(0x0100)
+#define TYPHOON_RX_ESP_FORMAT_ERR cpu_to_le16(0x0200)
+ __be32 vlanTag;
+} __packed;
+
+/* The Typhoon free buffer descriptor, used to give a buffer to the NIC
+ *
+ * physAddr: low 32 bits of the bus address of the buffer
+ * physAddrHi: high 32 bits of the bus address of the buffer, always zero
+ * virtAddr: low 32 bits of the skb address
+ * virtAddrHi: high 32 bits of the skb address, always zero
+ *
+ * the virt* address is basically two 32 bit cookies, just passed back
+ * from the NIC
+ */
+struct rx_free {
+ __le32 physAddr;
+ __le32 physAddrHi;
+ u32 virtAddr;
+ u32 virtAddrHi;
+} __packed;
+
+/* The Typhoon command descriptor, used for commands and responses
+ *
+ * flags: descriptor type
+ * numDesc: number of descriptors following in this command/response,
+ * ie, zero for a one descriptor command
+ * cmd: the command
+ * seqNo: sequence number (unused)
+ * parm1: use varies by command
+ * parm2: use varies by command
+ * parm3: use varies by command
+ */
+struct cmd_desc {
+ u8 flags;
+ u8 numDesc;
+ __le16 cmd;
+#define TYPHOON_CMD_TX_ENABLE cpu_to_le16(0x0001)
+#define TYPHOON_CMD_TX_DISABLE cpu_to_le16(0x0002)
+#define TYPHOON_CMD_RX_ENABLE cpu_to_le16(0x0003)
+#define TYPHOON_CMD_RX_DISABLE cpu_to_le16(0x0004)
+#define TYPHOON_CMD_SET_RX_FILTER cpu_to_le16(0x0005)
+#define TYPHOON_CMD_READ_STATS cpu_to_le16(0x0007)
+#define TYPHOON_CMD_XCVR_SELECT cpu_to_le16(0x0013)
+#define TYPHOON_CMD_SET_MAX_PKT_SIZE cpu_to_le16(0x001a)
+#define TYPHOON_CMD_READ_MEDIA_STATUS cpu_to_le16(0x001b)
+#define TYPHOON_CMD_GOTO_SLEEP cpu_to_le16(0x0023)
+#define TYPHOON_CMD_SET_MULTICAST_HASH cpu_to_le16(0x0025)
+#define TYPHOON_CMD_SET_MAC_ADDRESS cpu_to_le16(0x0026)
+#define TYPHOON_CMD_READ_MAC_ADDRESS cpu_to_le16(0x0027)
+#define TYPHOON_CMD_VLAN_TYPE_WRITE cpu_to_le16(0x002b)
+#define TYPHOON_CMD_CREATE_SA cpu_to_le16(0x0034)
+#define TYPHOON_CMD_DELETE_SA cpu_to_le16(0x0035)
+#define TYPHOON_CMD_READ_VERSIONS cpu_to_le16(0x0043)
+#define TYPHOON_CMD_IRQ_COALESCE_CTRL cpu_to_le16(0x0045)
+#define TYPHOON_CMD_ENABLE_WAKE_EVENTS cpu_to_le16(0x0049)
+#define TYPHOON_CMD_SET_OFFLOAD_TASKS cpu_to_le16(0x004f)
+#define TYPHOON_CMD_HELLO_RESP cpu_to_le16(0x0057)
+#define TYPHOON_CMD_HALT cpu_to_le16(0x005d)
+#define TYPHOON_CMD_READ_IPSEC_INFO cpu_to_le16(0x005e)
+#define TYPHOON_CMD_GET_IPSEC_ENABLE cpu_to_le16(0x0067)
+#define TYPHOON_CMD_GET_CMD_LVL cpu_to_le16(0x0069)
+ u16 seqNo;
+ __le16 parm1;
+ __le32 parm2;
+ __le32 parm3;
+} __packed;
+
+/* The Typhoon response descriptor, see command descriptor for details
+ */
+struct resp_desc {
+ u8 flags;
+ u8 numDesc;
+ __le16 cmd;
+ __le16 seqNo;
+ __le16 parm1;
+ __le32 parm2;
+ __le32 parm3;
+} __packed;
+
+#define INIT_COMMAND_NO_RESPONSE(x, command) \
+ do { struct cmd_desc *_ptr = (x); \
+ memset(_ptr, 0, sizeof(struct cmd_desc)); \
+ _ptr->flags = TYPHOON_CMD_DESC | TYPHOON_DESC_VALID; \
+ _ptr->cmd = command; \
+ } while (0)
+
+/* We set seqNo to 1 if we're expecting a response from this command */
+#define INIT_COMMAND_WITH_RESPONSE(x, command) \
+ do { struct cmd_desc *_ptr = (x); \
+ memset(_ptr, 0, sizeof(struct cmd_desc)); \
+ _ptr->flags = TYPHOON_CMD_RESPOND | TYPHOON_CMD_DESC; \
+ _ptr->flags |= TYPHOON_DESC_VALID; \
+ _ptr->cmd = command; \
+ _ptr->seqNo = 1; \
+ } while (0)
+
+/* TYPHOON_CMD_SET_RX_FILTER filter bits (cmd.parm1)
+ */
+#define TYPHOON_RX_FILTER_DIRECTED cpu_to_le16(0x0001)
+#define TYPHOON_RX_FILTER_ALL_MCAST cpu_to_le16(0x0002)
+#define TYPHOON_RX_FILTER_BROADCAST cpu_to_le16(0x0004)
+#define TYPHOON_RX_FILTER_PROMISCOUS cpu_to_le16(0x0008)
+#define TYPHOON_RX_FILTER_MCAST_HASH cpu_to_le16(0x0010)
+
+/* TYPHOON_CMD_READ_STATS response format
+ */
+struct stats_resp {
+ u8 flags;
+ u8 numDesc;
+ __le16 cmd;
+ __le16 seqNo;
+ __le16 unused;
+ __le32 txPackets;
+ __le64 txBytes;
+ __le32 txDeferred;
+ __le32 txLateCollisions;
+ __le32 txCollisions;
+ __le32 txCarrierLost;
+ __le32 txMultipleCollisions;
+ __le32 txExcessiveCollisions;
+ __le32 txFifoUnderruns;
+ __le32 txMulticastTxOverflows;
+ __le32 txFiltered;
+ __le32 rxPacketsGood;
+ __le64 rxBytesGood;
+ __le32 rxFifoOverruns;
+ __le32 BadSSD;
+ __le32 rxCrcErrors;
+ __le32 rxOversized;
+ __le32 rxBroadcast;
+ __le32 rxMulticast;
+ __le32 rxOverflow;
+ __le32 rxFiltered;
+ __le32 linkStatus;
+#define TYPHOON_LINK_STAT_MASK cpu_to_le32(0x00000001)
+#define TYPHOON_LINK_GOOD cpu_to_le32(0x00000001)
+#define TYPHOON_LINK_BAD cpu_to_le32(0x00000000)
+#define TYPHOON_LINK_SPEED_MASK cpu_to_le32(0x00000002)
+#define TYPHOON_LINK_100MBPS cpu_to_le32(0x00000002)
+#define TYPHOON_LINK_10MBPS cpu_to_le32(0x00000000)
+#define TYPHOON_LINK_DUPLEX_MASK cpu_to_le32(0x00000004)
+#define TYPHOON_LINK_FULL_DUPLEX cpu_to_le32(0x00000004)
+#define TYPHOON_LINK_HALF_DUPLEX cpu_to_le32(0x00000000)
+ __le32 unused2;
+ __le32 unused3;
+} __packed;
+
+/* TYPHOON_CMD_XCVR_SELECT xcvr values (resp.parm1)
+ */
+#define TYPHOON_XCVR_10HALF cpu_to_le16(0x0000)
+#define TYPHOON_XCVR_10FULL cpu_to_le16(0x0001)
+#define TYPHOON_XCVR_100HALF cpu_to_le16(0x0002)
+#define TYPHOON_XCVR_100FULL cpu_to_le16(0x0003)
+#define TYPHOON_XCVR_AUTONEG cpu_to_le16(0x0004)
+
+/* TYPHOON_CMD_READ_MEDIA_STATUS (resp.parm1)
+ */
+#define TYPHOON_MEDIA_STAT_CRC_STRIP_DISABLE cpu_to_le16(0x0004)
+#define TYPHOON_MEDIA_STAT_COLLISION_DETECT cpu_to_le16(0x0010)
+#define TYPHOON_MEDIA_STAT_CARRIER_SENSE cpu_to_le16(0x0020)
+#define TYPHOON_MEDIA_STAT_POLARITY_REV cpu_to_le16(0x0400)
+#define TYPHOON_MEDIA_STAT_NO_LINK cpu_to_le16(0x0800)
+
+/* TYPHOON_CMD_SET_MULTICAST_HASH enable values (cmd.parm1)
+ */
+#define TYPHOON_MCAST_HASH_DISABLE cpu_to_le16(0x0000)
+#define TYPHOON_MCAST_HASH_ENABLE cpu_to_le16(0x0001)
+#define TYPHOON_MCAST_HASH_SET cpu_to_le16(0x0002)
+
+/* TYPHOON_CMD_CREATE_SA descriptor and settings
+ */
+struct sa_descriptor {
+ u8 flags;
+ u8 numDesc;
+ u16 cmd;
+ u16 seqNo;
+ u16 mode;
+#define TYPHOON_SA_MODE_NULL cpu_to_le16(0x0000)
+#define TYPHOON_SA_MODE_AH cpu_to_le16(0x0001)
+#define TYPHOON_SA_MODE_ESP cpu_to_le16(0x0002)
+ u8 hashFlags;
+#define TYPHOON_SA_HASH_ENABLE 0x01
+#define TYPHOON_SA_HASH_SHA1 0x02
+#define TYPHOON_SA_HASH_MD5 0x04
+ u8 direction;
+#define TYPHOON_SA_DIR_RX 0x00
+#define TYPHOON_SA_DIR_TX 0x01
+ u8 encryptionFlags;
+#define TYPHOON_SA_ENCRYPT_ENABLE 0x01
+#define TYPHOON_SA_ENCRYPT_DES 0x02
+#define TYPHOON_SA_ENCRYPT_3DES 0x00
+#define TYPHOON_SA_ENCRYPT_3DES_2KEY 0x00
+#define TYPHOON_SA_ENCRYPT_3DES_3KEY 0x04
+#define TYPHOON_SA_ENCRYPT_CBC 0x08
+#define TYPHOON_SA_ENCRYPT_ECB 0x00
+ u8 specifyIndex;
+#define TYPHOON_SA_SPECIFY_INDEX 0x01
+#define TYPHOON_SA_GENERATE_INDEX 0x00
+ u32 SPI;
+ u32 destAddr;
+ u32 destMask;
+ u8 integKey[20];
+ u8 confKey[24];
+ u32 index;
+ u32 unused;
+ u32 unused2;
+} __packed;
+
+/* TYPHOON_CMD_SET_OFFLOAD_TASKS bits (cmd.parm2 (Tx) & cmd.parm3 (Rx))
+ * This is all for IPv4.
+ */
+#define TYPHOON_OFFLOAD_TCP_CHKSUM cpu_to_le32(0x00000002)
+#define TYPHOON_OFFLOAD_UDP_CHKSUM cpu_to_le32(0x00000004)
+#define TYPHOON_OFFLOAD_IP_CHKSUM cpu_to_le32(0x00000008)
+#define TYPHOON_OFFLOAD_IPSEC cpu_to_le32(0x00000010)
+#define TYPHOON_OFFLOAD_BCAST_THROTTLE cpu_to_le32(0x00000020)
+#define TYPHOON_OFFLOAD_DHCP_PREVENT cpu_to_le32(0x00000040)
+#define TYPHOON_OFFLOAD_VLAN cpu_to_le32(0x00000080)
+#define TYPHOON_OFFLOAD_FILTERING cpu_to_le32(0x00000100)
+#define TYPHOON_OFFLOAD_TCP_SEGMENT cpu_to_le32(0x00000200)
+
+/* TYPHOON_CMD_ENABLE_WAKE_EVENTS bits (cmd.parm1)
+ */
+#define TYPHOON_WAKE_MAGIC_PKT cpu_to_le16(0x01)
+#define TYPHOON_WAKE_LINK_EVENT cpu_to_le16(0x02)
+#define TYPHOON_WAKE_ICMP_ECHO cpu_to_le16(0x04)
+#define TYPHOON_WAKE_ARP cpu_to_le16(0x08)
+
+/* These are used to load the firmware image on the NIC
+ */
+struct typhoon_file_header {
+ u8 tag[8];
+ __le32 version;
+ __le32 numSections;
+ __le32 startAddr;
+ __le32 hmacDigest[5];
+} __packed;
+
+struct typhoon_section_header {
+ __le32 len;
+ u16 checksum;
+ u16 reserved;
+ __le32 startAddr;
+} __packed;
+
+/* The Typhoon Register offsets
+ */
+#define TYPHOON_REG_SOFT_RESET 0x00
+#define TYPHOON_REG_INTR_STATUS 0x04
+#define TYPHOON_REG_INTR_ENABLE 0x08
+#define TYPHOON_REG_INTR_MASK 0x0c
+#define TYPHOON_REG_SELF_INTERRUPT 0x10
+#define TYPHOON_REG_HOST2ARM7 0x14
+#define TYPHOON_REG_HOST2ARM6 0x18
+#define TYPHOON_REG_HOST2ARM5 0x1c
+#define TYPHOON_REG_HOST2ARM4 0x20
+#define TYPHOON_REG_HOST2ARM3 0x24
+#define TYPHOON_REG_HOST2ARM2 0x28
+#define TYPHOON_REG_HOST2ARM1 0x2c
+#define TYPHOON_REG_HOST2ARM0 0x30
+#define TYPHOON_REG_ARM2HOST3 0x34
+#define TYPHOON_REG_ARM2HOST2 0x38
+#define TYPHOON_REG_ARM2HOST1 0x3c
+#define TYPHOON_REG_ARM2HOST0 0x40
+
+#define TYPHOON_REG_BOOT_DATA_LO TYPHOON_REG_HOST2ARM5
+#define TYPHOON_REG_BOOT_DATA_HI TYPHOON_REG_HOST2ARM4
+#define TYPHOON_REG_BOOT_DEST_ADDR TYPHOON_REG_HOST2ARM3
+#define TYPHOON_REG_BOOT_CHECKSUM TYPHOON_REG_HOST2ARM2
+#define TYPHOON_REG_BOOT_LENGTH TYPHOON_REG_HOST2ARM1
+
+#define TYPHOON_REG_DOWNLOAD_BOOT_ADDR TYPHOON_REG_HOST2ARM1
+#define TYPHOON_REG_DOWNLOAD_HMAC_0 TYPHOON_REG_HOST2ARM2
+#define TYPHOON_REG_DOWNLOAD_HMAC_1 TYPHOON_REG_HOST2ARM3
+#define TYPHOON_REG_DOWNLOAD_HMAC_2 TYPHOON_REG_HOST2ARM4
+#define TYPHOON_REG_DOWNLOAD_HMAC_3 TYPHOON_REG_HOST2ARM5
+#define TYPHOON_REG_DOWNLOAD_HMAC_4 TYPHOON_REG_HOST2ARM6
+
+#define TYPHOON_REG_BOOT_RECORD_ADDR_HI TYPHOON_REG_HOST2ARM2
+#define TYPHOON_REG_BOOT_RECORD_ADDR_LO TYPHOON_REG_HOST2ARM1
+
+#define TYPHOON_REG_TX_LO_READY TYPHOON_REG_HOST2ARM3
+#define TYPHOON_REG_CMD_READY TYPHOON_REG_HOST2ARM2
+#define TYPHOON_REG_TX_HI_READY TYPHOON_REG_HOST2ARM1
+
+#define TYPHOON_REG_COMMAND TYPHOON_REG_HOST2ARM0
+#define TYPHOON_REG_HEARTBEAT TYPHOON_REG_ARM2HOST3
+#define TYPHOON_REG_STATUS TYPHOON_REG_ARM2HOST0
+
+/* 3XP Reset values (TYPHOON_REG_SOFT_RESET)
+ */
+#define TYPHOON_RESET_ALL 0x7f
+#define TYPHOON_RESET_NONE 0x00
+
+/* 3XP irq bits (TYPHOON_REG_INTR{STATUS,ENABLE,MASK})
+ *
+ * Some of these came from OpenBSD, as the 3Com docs have it wrong
+ * (INTR_SELF) or don't list it at all (INTR_*_ABORT)
+ *
+ * Enabling irqs on the Heartbeat reg (ArmToHost3) gets you an irq
+ * about every 8ms, so don't do it.
+ */
+#define TYPHOON_INTR_HOST_INT 0x00000001
+#define TYPHOON_INTR_ARM2HOST0 0x00000002
+#define TYPHOON_INTR_ARM2HOST1 0x00000004
+#define TYPHOON_INTR_ARM2HOST2 0x00000008
+#define TYPHOON_INTR_ARM2HOST3 0x00000010
+#define TYPHOON_INTR_DMA0 0x00000020
+#define TYPHOON_INTR_DMA1 0x00000040
+#define TYPHOON_INTR_DMA2 0x00000080
+#define TYPHOON_INTR_DMA3 0x00000100
+#define TYPHOON_INTR_MASTER_ABORT 0x00000200
+#define TYPHOON_INTR_TARGET_ABORT 0x00000400
+#define TYPHOON_INTR_SELF 0x00000800
+#define TYPHOON_INTR_RESERVED 0xfffff000
+
+#define TYPHOON_INTR_BOOTCMD TYPHOON_INTR_ARM2HOST0
+
+#define TYPHOON_INTR_ENABLE_ALL 0xffffffef
+#define TYPHOON_INTR_ALL 0xffffffff
+#define TYPHOON_INTR_NONE 0x00000000
+
+/* The commands for the 3XP chip (TYPHOON_REG_COMMAND)
+ */
+#define TYPHOON_BOOTCMD_BOOT 0x00
+#define TYPHOON_BOOTCMD_WAKEUP 0xfa
+#define TYPHOON_BOOTCMD_DNLD_COMPLETE 0xfb
+#define TYPHOON_BOOTCMD_SEG_AVAILABLE 0xfc
+#define TYPHOON_BOOTCMD_RUNTIME_IMAGE 0xfd
+#define TYPHOON_BOOTCMD_REG_BOOT_RECORD 0xff
+
+/* 3XP Status values (TYPHOON_REG_STATUS)
+ */
+#define TYPHOON_STATUS_WAITING_FOR_BOOT 0x07
+#define TYPHOON_STATUS_SECOND_INIT 0x08
+#define TYPHOON_STATUS_RUNNING 0x09
+#define TYPHOON_STATUS_WAITING_FOR_HOST 0x0d
+#define TYPHOON_STATUS_WAITING_FOR_SEGMENT 0x10
+#define TYPHOON_STATUS_SLEEPING 0x11
+#define TYPHOON_STATUS_HALTED 0x14