diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-02 12:17:24 -0700 |
| commit | 3f1fe48a36b0b6722dc3fd421d93512bac138e9a (patch) | |
| tree | b767d7f6e26bc64334d3f14f8422d187239dc45d /drivers/net/ethernet/3com | |
| download | linux-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.c | 1543 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/3c59x.c | 3357 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/Kconfig | 71 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/Makefile | 8 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/typhoon.c | 2609 | ||||
| -rw-r--r-- | drivers/net/ethernet/3com/typhoon.h | 624 |
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 = ðtool_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, ðtool_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 |
