📄 3c509.c
字号:
/* 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 <andrewm@uow.edu.au> - 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 Davud 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.*/#define DRV_NAME "3c509"#define DRV_VERSION "1.19b"#define DRV_RELDATE "08Nov2002"/* A few values that may be tweaked. *//* Time in jiffies before concluding the transmitter is hung. */#define TX_TIMEOUT (400*HZ/1000)/* Maximum events (Rx packets, etc.) to handle at each interrupt. */static int max_interrupt_work = 10;#include <linux/module.h>#ifdef CONFIG_MCA#include <linux/mca.h>#endif#include <linux/isapnp.h>#include <linux/string.h>#include <linux/interrupt.h>#include <linux/errno.h>#include <linux/in.h>#include <linux/slab.h>#include <linux/ioport.h>#include <linux/init.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/pm.h>#include <linux/skbuff.h>#include <linux/delay.h> /* for udelay() */#include <linux/spinlock.h>#include <linux/ethtool.h>#include <linux/device.h>#include <linux/eisa.h>#include <linux/bitops.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/irq.h>static char version[] __initdata = DRV_NAME ".c:" DRV_VERSION " " DRV_RELDATE " becker@scyld.com\n";#if defined(CONFIG_PM) && (defined(CONFIG_MCA) || defined(CONFIG_EISA))#define EL3_SUSPEND#endif#ifdef EL3_DEBUGstatic int el3_debug = EL3_DEBUG;#elsestatic 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 mca/eisa probe routines can increment * it */static int el3_cards = 0;/* 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(SelectWindow + (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 { TotalReset = 0<<11, SelectWindow = 1<<11, StartCoax = 2<<11, RxDisable = 3<<11, RxEnable = 4<<11, RxReset = 5<<11, 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, StatsEnable = 21<<11, StatsDisable = 22<<11, StopCoax = 23<<11, PowerUp = 27<<11, PowerDown = 28<<11, PowerAuto = 29<<11};enum c509status { IntLatch = 0x0001, AdapterFailure = 0x0002, TxComplete = 0x0004, TxAvailable = 0x0008, RxComplete = 0x0010, RxEarly = 0x0020, IntReq = 0x0040, StatsFull = 0x0080, CmdBusy = 0x1000, };/* The SetRxFilter command accepts the following classes: */enum RxFilter { RxStation = 1, RxMulticast = 2, RxBroadcast = 4, RxProm = 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 64struct el3_private { struct net_device_stats stats; struct net_device *next_dev; spinlock_t lock; /* skb send-queue */ int head, size; struct sk_buff *queue[SKB_QUEUE_SIZE]; enum { EL3_MCA, EL3_PNP, EL3_EISA, } type; /* type of device */ struct device *dev;};static int id_port __initdata = 0x110; /* Start with 0x110 to avoid new sound cards.*/static struct net_device *el3_root_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 int 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);static void el3_down(struct net_device *dev);static void el3_up(struct net_device *dev);static const struct ethtool_ops ethtool_ops;#ifdef EL3_SUSPENDstatic 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 */#if defined(CONFIG_EISA) || defined(CONFIG_MCA)static int el3_device_remove (struct device *device);#endif#ifdef CONFIG_NET_POLL_CONTROLLERstatic void el3_poll_controller(struct net_device *dev);#endif#ifdef CONFIG_EISAstatic struct eisa_device_id el3_eisa_ids[] = { { "TCM5092" }, { "TCM5093" }, { "TCM5095" }, { "" }};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 = "3c509", .probe = el3_eisa_probe, .remove = __devexit_p (el3_device_remove), .suspend = el3_suspend, .resume = el3_resume, }};#endif#ifdef CONFIG_MCAstatic int el3_mca_probe(struct device *dev);static short el3_mca_adapter_ids[] __initdata = { 0x627c, 0x627d, 0x62db, 0x62f6, 0x62f7, 0x0000};static char *el3_mca_adapter_names[] __initdata = { "3Com 3c529 EtherLink III (10base2)", "3Com 3c529 EtherLink III (10baseT)", "3Com 3c529 EtherLink III (test mode)", "3Com 3c529 EtherLink III (TP or coax)", "3Com 3c529 EtherLink III (TP)", NULL};static struct mca_driver el3_mca_driver = { .id_table = el3_mca_adapter_ids, .driver = { .name = "3c529", .bus = &mca_bus_type, .probe = el3_mca_probe, .remove = __devexit_p(el3_device_remove), .suspend = el3_suspend, .resume = el3_resume, },};#endif /* CONFIG_MCA */#if defined(__ISAPNP__)static struct isapnp_device_id el3_isapnp_adapters[] __initdata = { { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('T', 'C', 'M'), ISAPNP_FUNCTION(0x5090), (long) "3Com Etherlink III (TP)" }, { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('T', 'C', 'M'), ISAPNP_FUNCTION(0x5091), (long) "3Com Etherlink III" }, { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('T', 'C', 'M'), ISAPNP_FUNCTION(0x5094), (long) "3Com Etherlink III (combo)" }, { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('T', 'C', 'M'), ISAPNP_FUNCTION(0x5095), (long) "3Com Etherlink III (TPO)" }, { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('T', 'C', 'M'), ISAPNP_FUNCTION(0x5098), (long) "3Com Etherlink III (TPC)" }, { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('P', 'N', 'P'), ISAPNP_FUNCTION(0x80f7), (long) "3Com Etherlink III compatible" }, { ISAPNP_ANY_ID, ISAPNP_ANY_ID, ISAPNP_VENDOR('P', 'N', 'P'), ISAPNP_FUNCTION(0x80f8), (long) "3Com Etherlink III compatible" }, { } /* terminate list */};static __be16 el3_isapnp_phys_addr[8][3];static int nopnp;#endif /* __ISAPNP__ *//* With the driver model introduction for EISA devices, both init * and cleanup have been split : * - EISA devices probe/remove starts in el3_eisa_probe/el3_device_remove * - MCA/ISA still use el3_probe * * Both call el3_common_init/el3_common_remove. */static int __init el3_common_init(struct net_device *dev){ struct el3_private *lp = netdev_priv(dev); int err; DECLARE_MAC_BUF(mac); const char *if_names[] = {"10baseT", "AUI", "undefined", "BNC"}; 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->open = &el3_open; dev->hard_start_xmit = &el3_start_xmit; dev->stop = &el3_close; dev->get_stats = &el3_get_stats; dev->set_multicast_list = &set_multicast_list; dev->tx_timeout = el3_tx_timeout; dev->watchdog_timeo = TX_TIMEOUT;#ifdef CONFIG_NET_POLL_CONTROLLER dev->poll_controller = el3_poll_controller;#endif SET_ETHTOOL_OPS(dev, ðtool_ops); err = register_netdev(dev); if (err) { printk(KERN_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; } printk(KERN_INFO "%s: 3c5x9 found at %#3.3lx, %s port, " "address %s, IRQ %d.\n", dev->name, dev->base_addr, if_names[(dev->if_port & 0x03)], print_mac(mac, dev->dev_addr), dev->irq); if (el3_debug > 0) printk(KERN_INFO "%s", version); return 0;}static void el3_common_remove (struct net_device *dev){ struct el3_private *lp = netdev_priv(dev); (void) lp; /* Keep gcc quiet... */#if defined(__ISAPNP__) if (lp->type == EL3_PNP) pnp_device_detach(to_pnp_dev(lp->dev));#endif unregister_netdev (dev); release_region(dev->base_addr, EL3_IO_EXTENT); free_netdev (dev);}static int __init el3_probe(int card_idx){ struct net_device *dev; struct el3_private *lp; short lrs_state = 0xff, i; int ioaddr, irq, if_port; __be16 phys_addr[3]; static int current_tag; int err = -ENODEV;#if defined(__ISAPNP__) static int pnp_cards; struct pnp_dev *idev = NULL; int pnp_found = 0; if (nopnp == 1) goto no_pnp; for (i=0; el3_isapnp_adapters[i].vendor != 0; i++) { int j; while ((idev = pnp_find_dev(NULL, el3_isapnp_adapters[i].vendor, el3_isapnp_adapters[i].function, idev))) { if (pnp_device_attach(idev) < 0) continue; if (pnp_activate_dev(idev) < 0) {__again: pnp_device_detach(idev); continue; } if (!pnp_port_valid(idev, 0) || !pnp_irq_valid(idev, 0)) goto __again; ioaddr = pnp_port_start(idev, 0); if (!request_region(ioaddr, EL3_IO_EXTENT, "3c509 PnP")) { pnp_device_detach(idev); return -EBUSY; } irq = pnp_irq(idev, 0); if (el3_debug > 3) printk ("ISAPnP reports %s at i/o 0x%x, irq %d\n", (char*) el3_isapnp_adapters[i].driver_data, ioaddr, irq); EL3WINDOW(0); for (j = 0; j < 3; j++) el3_isapnp_phys_addr[pnp_cards][j] = phys_addr[j] = htons(read_eeprom(ioaddr, j)); if_port = read_eeprom(ioaddr, 8) >> 14; dev = alloc_etherdev(sizeof (struct el3_private)); if (!dev) { release_region(ioaddr, EL3_IO_EXTENT); pnp_device_detach(idev); return -ENOMEM; } SET_NETDEV_DEV(dev, &idev->dev); pnp_cards++; netdev_boot_setup_check(dev); pnp_found = 1; goto found; } }no_pnp:#endif /* __ISAPNP__ */ /* Select an open I/O location at 0x1*0 to do contention select. */ for ( ; id_port < 0x200; id_port += 0x10) { if (!request_region(id_port, 1, "3c509")) continue; outb(0x00, id_port); outb(0xff, id_port); if (inb(id_port) & 0x01){ release_region(id_port, 1); break; } else release_region(id_port, 1); } if (id_port >= 0x200) { /* Rare -- do we really need a warning? */ printk(" WARNING: No I/O port available for 3c509 activation.\n"); return -ENODEV; } /* Next check for all ISA bus boards 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 -ENODEV; } /* 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)); }#if defined(__ISAPNP__) if (nopnp == 0) { /* The ISA PnP 3c509 cards respond to the ID sequence. This check is needed in order not to register them twice. */ for (i = 0; i < pnp_cards; i++) { if (phys_addr[0] == el3_isapnp_phys_addr[i][0] && phys_addr[1] == el3_isapnp_phys_addr[i][1] && phys_addr[2] == el3_isapnp_phys_addr[i][2]) { if (el3_debug > 3) printk("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); goto no_pnp; } } }#endif /* __ISAPNP__ */ { unsigned int iobase = id_read_eeprom(8); if_port = iobase >> 14; ioaddr = 0x200 + ((iobase & 0x1f) << 4); } irq = id_read_eeprom(9) >> 12; dev = alloc_etherdev(sizeof (struct el3_private)); if (!dev) return -ENOMEM; netdev_boot_setup_check(dev); /* Set passed-in IRQ or I/O Addr. */ if (dev->irq > 1 && dev->irq < 16) irq = dev->irq;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -