📄 3c505.c
字号:
/* * Linux Ethernet device driver for the 3Com Etherlink Plus (3C505) * By Craig Southeren, Juha Laiho and Philip Blundell * * 3c505.c This module implements an interface to the 3Com * Etherlink Plus (3c505) Ethernet card. Linux device * driver interface reverse engineered from the Linux 3C509 * device drivers. Some 3C505 information gleaned from * the Crynwr packet driver. Still this driver would not * be here without 3C505 technical reference provided by * 3Com. * * $Id: 3c505.c,v 1.10 1996/04/16 13:06:27 phil Exp $ * * Authors: Linux 3c505 device driver by * Craig Southeren, <craigs@ineluki.apana.org.au> * Final debugging by * Andrew Tridgell, <tridge@nimbus.anu.edu.au> * Auto irq/address, tuning, cleanup and v1.1.4+ kernel mods by * Juha Laiho, <jlaiho@ichaos.nullnet.fi> * Linux 3C509 driver by * Donald Becker, <becker@super.org> * (Now at <becker@scyld.com>) * Crynwr packet driver by * Krishnan Gopalan and Gregg Stefancik, * Clemson University Engineering Computer Operations. * Portions of the code have been adapted from the 3c505 * driver for NCSA Telnet by Bruce Orchard and later * modified by Warren Van Houten and krus@diku.dk. * 3C505 technical information provided by * Terry Murphy, of 3Com Network Adapter Division * Linux 1.3.0 changes by * Alan Cox <Alan.Cox@linux.org> * More debugging, DMA support, currently maintained by * Philip Blundell <philb@gnu.org> * Multicard/soft configurable dma channel/rev 2 hardware support * by Christopher Collins <ccollins@pcug.org.au> * Ethtool support (jgarzik), 11/17/2001 */#define DRV_NAME "3c505"#define DRV_VERSION "1.10a"/* Theory of operation: * * The 3c505 is quite an intelligent board. All communication with it is done * by means of Primary Command Blocks (PCBs); these are transferred using PIO * through the command register. The card has 256k of on-board RAM, which is * used to buffer received packets. It might seem at first that more buffers * are better, but in fact this isn't true. From my tests, it seems that * more than about 10 buffers are unnecessary, and there is a noticeable * performance hit in having more active on the card. So the majority of the * card's memory isn't, in fact, used. Sadly, the card only has one transmit * buffer and, short of loading our own firmware into it (which is what some * drivers resort to) there's nothing we can do about this. * * We keep up to 4 "receive packet" commands active on the board at a time. * When a packet comes in, so long as there is a receive command active, the * board will send us a "packet received" PCB and then add the data for that * packet to the DMA queue. If a DMA transfer is not already in progress, we * set one up to start uploading the data. We have to maintain a list of * backlogged receive packets, because the card may decide to tell us about * a newly-arrived packet at any time, and we may not be able to start a DMA * transfer immediately (ie one may already be going on). We can't NAK the * PCB, because then it would throw the packet away. * * Trying to send a PCB to the card at the wrong moment seems to have bad * effects. If we send it a transmit PCB while a receive DMA is happening, * it will just NAK the PCB and so we will have wasted our time. Worse, it * sometimes seems to interrupt the transfer. The majority of the low-level * code is protected by one huge semaphore -- "busy" -- which is set whenever * it probably isn't safe to do anything to the card. The receive routine * must gain a lock on "busy" before it can start a DMA transfer, and the * transmit routine must gain a lock before it sends the first PCB to the card. * The send_pcb() routine also has an internal semaphore to protect it against * being re-entered (which would be disastrous) -- this is needed because * several things can happen asynchronously (re-priming the receiver and * asking the card for statistics, for example). send_pcb() will also refuse * to talk to the card at all if a DMA upload is happening. The higher-level * networking code will reschedule a later retry if some part of the driver * is blocked. In practice, this doesn't seem to happen very often. *//* This driver may now work with revision 2.x hardware, since all the read * operations on the HCR have been removed (we now keep our own softcopy). * But I don't have an old card to test it on. * * This has had the bad effect that the autoprobe routine is now a bit * less friendly to other devices. However, it was never very good. * before, so I doubt it will hurt anybody. *//* The driver is a mess. I took Craig's and Juha's code, and hacked it firstly * to make it more reliable, and secondly to add DMA mode. Many things could * probably be done better; the concurrency protection is particularly awful. */#include <linux/module.h>#include <linux/kernel.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/spinlock.h>#include <linux/ethtool.h>#include <linux/delay.h>#include <linux/bitops.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/dma.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <linux/init.h>#include "3c505.h"/********************************************************* * * define debug messages here as common strings to reduce space * *********************************************************/static const char filename[] = __FILE__;static const char timeout_msg[] = "*** timeout at %s:%s (line %d) ***\n";#define TIMEOUT_MSG(lineno) \ printk(timeout_msg, filename,__FUNCTION__,(lineno))static const char invalid_pcb_msg[] ="*** invalid pcb length %d at %s:%s (line %d) ***\n";#define INVALID_PCB_MSG(len) \ printk(invalid_pcb_msg, (len),filename,__FUNCTION__,__LINE__)static char search_msg[] __initdata = KERN_INFO "%s: Looking for 3c505 adapter at address %#x...";static char stilllooking_msg[] __initdata = "still looking...";static char found_msg[] __initdata = "found.\n";static char notfound_msg[] __initdata = "not found (reason = %d)\n";static char couldnot_msg[] __initdata = KERN_INFO "%s: 3c505 not found\n";/********************************************************* * * various other debug stuff * *********************************************************/#ifdef ELP_DEBUGstatic int elp_debug = ELP_DEBUG;#elsestatic int elp_debug;#endif#define debug elp_debug/* * 0 = no messages (well, some) * 1 = messages when high level commands performed * 2 = messages when low level commands performed * 3 = messages when interrupts received *//***************************************************************** * * useful macros * *****************************************************************/#ifndef TRUE#define TRUE 1#endif#ifndef FALSE#define FALSE 0#endif/***************************************************************** * * List of I/O-addresses we try to auto-sense * Last element MUST BE 0! *****************************************************************/static int addr_list[] __initdata = {0x300, 0x280, 0x310, 0};/* Dma Memory related stuff */static unsigned long dma_mem_alloc(int size){ int order = get_order(size); return __get_dma_pages(GFP_KERNEL, order);}/***************************************************************** * * Functions for I/O (note the inline !) * *****************************************************************/static inline unsigned char inb_status(unsigned int base_addr){ return inb(base_addr + PORT_STATUS);}static inline int inb_command(unsigned int base_addr){ return inb(base_addr + PORT_COMMAND);}static inline void outb_control(unsigned char val, struct net_device *dev){ outb(val, dev->base_addr + PORT_CONTROL); ((elp_device *)(dev->priv))->hcr_val = val;}#define HCR_VAL(x) (((elp_device *)((x)->priv))->hcr_val)static inline void outb_command(unsigned char val, unsigned int base_addr){ outb(val, base_addr + PORT_COMMAND);}static inline unsigned int backlog_next(unsigned int n){ return (n + 1) % BACKLOG_SIZE;}/***************************************************************** * * useful functions for accessing the adapter * *****************************************************************//* * use this routine when accessing the ASF bits as they are * changed asynchronously by the adapter *//* get adapter PCB status */#define GET_ASF(addr) \ (get_status(addr)&ASF_PCB_MASK)static inline int get_status(unsigned int base_addr){ unsigned long timeout = jiffies + 10*HZ/100; register int stat1; do { stat1 = inb_status(base_addr); } while (stat1 != inb_status(base_addr) && time_before(jiffies, timeout)); if (time_after_eq(jiffies, timeout)) TIMEOUT_MSG(__LINE__); return stat1;}static inline void set_hsf(struct net_device *dev, int hsf){ elp_device *adapter = dev->priv; unsigned long flags; spin_lock_irqsave(&adapter->lock, flags); outb_control((HCR_VAL(dev) & ~HSF_PCB_MASK) | hsf, dev); spin_unlock_irqrestore(&adapter->lock, flags);}static int start_receive(struct net_device *, pcb_struct *);static inline void adapter_reset(struct net_device *dev){ unsigned long timeout; elp_device *adapter = dev->priv; unsigned char orig_hcr = adapter->hcr_val; outb_control(0, dev); if (inb_status(dev->base_addr) & ACRF) { do { inb_command(dev->base_addr); timeout = jiffies + 2*HZ/100; while (time_before_eq(jiffies, timeout) && !(inb_status(dev->base_addr) & ACRF)); } while (inb_status(dev->base_addr) & ACRF); set_hsf(dev, HSF_PCB_NAK); } outb_control(adapter->hcr_val | ATTN | DIR, dev); mdelay(10); outb_control(adapter->hcr_val & ~ATTN, dev); mdelay(10); outb_control(adapter->hcr_val | FLSH, dev); mdelay(10); outb_control(adapter->hcr_val & ~FLSH, dev); mdelay(10); outb_control(orig_hcr, dev); if (!start_receive(dev, &adapter->tx_pcb)) printk(KERN_ERR "%s: start receive command failed \n", dev->name);}/* Check to make sure that a DMA transfer hasn't timed out. This should * never happen in theory, but seems to occur occasionally if the card gets * prodded at the wrong time. */static inline void check_3c505_dma(struct net_device *dev){ elp_device *adapter = dev->priv; if (adapter->dmaing && time_after(jiffies, adapter->current_dma.start_time + 10)) { unsigned long flags, f; printk(KERN_ERR "%s: DMA %s timed out, %d bytes left\n", dev->name, adapter->current_dma.direction ? "download" : "upload", get_dma_residue(dev->dma)); spin_lock_irqsave(&adapter->lock, flags); adapter->dmaing = 0; adapter->busy = 0; f=claim_dma_lock(); disable_dma(dev->dma); release_dma_lock(f); if (adapter->rx_active) adapter->rx_active--; outb_control(adapter->hcr_val & ~(DMAE | TCEN | DIR), dev); spin_unlock_irqrestore(&adapter->lock, flags); }}/* Primitive functions used by send_pcb() */static inline unsigned int send_pcb_slow(unsigned int base_addr, unsigned char byte){ unsigned long timeout; outb_command(byte, base_addr); for (timeout = jiffies + 5*HZ/100; time_before(jiffies, timeout);) { if (inb_status(base_addr) & HCRE) return FALSE; } printk(KERN_WARNING "3c505: send_pcb_slow timed out\n"); return TRUE;}static inline unsigned int send_pcb_fast(unsigned int base_addr, unsigned char byte){ unsigned int timeout; outb_command(byte, base_addr); for (timeout = 0; timeout < 40000; timeout++) { if (inb_status(base_addr) & HCRE) return FALSE; } printk(KERN_WARNING "3c505: send_pcb_fast timed out\n"); return TRUE;}/* Check to see if the receiver needs restarting, and kick it if so */static inline void prime_rx(struct net_device *dev){ elp_device *adapter = dev->priv; while (adapter->rx_active < ELP_RX_PCBS && netif_running(dev)) { if (!start_receive(dev, &adapter->itx_pcb)) break; }}/***************************************************************** * * send_pcb * Send a PCB to the adapter. * * output byte to command reg --<--+ * wait until HCRE is non zero | * loop until all bytes sent -->--+ * set HSF1 and HSF2 to 1 * output pcb length * wait until ASF give ACK or NAK * set HSF1 and HSF2 to 0 * *****************************************************************//* This can be quite slow -- the adapter is allowed to take up to 40ms * to respond to the initial interrupt. * * We run initially with interrupts turned on, but with a semaphore set * so that nobody tries to re-enter this code. Once the first byte has * gone through, we turn interrupts off and then send the others (the * timeout is reduced to 500us). */static int send_pcb(struct net_device *dev, pcb_struct * pcb){ int i; unsigned long timeout; elp_device *adapter = dev->priv; unsigned long flags; check_3c505_dma(dev); if (adapter->dmaing && adapter->current_dma.direction == 0) return FALSE; /* Avoid contention */ if (test_and_set_bit(1, &adapter->send_pcb_semaphore)) { if (elp_debug >= 3) { printk(KERN_DEBUG "%s: send_pcb entered while threaded\n", dev->name); } return FALSE; } /* * load each byte into the command register and * wait for the HCRE bit to indicate the adapter * had read the byte */ set_hsf(dev, 0); if (send_pcb_slow(dev->base_addr, pcb->command)) goto abort; spin_lock_irqsave(&adapter->lock, flags); if (send_pcb_fast(dev->base_addr, pcb->length)) goto sti_abort; for (i = 0; i < pcb->length; i++) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -