📄 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.1 1999/04/26 05:51:48 tb 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> * 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 and DMA version by Philip Blundell *//* 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. * * 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 will not work with revision 2 hardware, because the host * control register is write-only. It should be fairly easy to arrange to * keep our own soft-copy of the intended contents of this register, if * somebody has the time. There may be firmware differences that cause * other problems, though, and I don't have an old card to test. *//* 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/sched.h>#include <linux/string.h>#include <linux/interrupt.h>#include <linux/ptrace.h>#include <linux/errno.h>#include <linux/in.h>#include <linux/malloc.h>#include <linux/ioport.h>#include <asm/bitops.h>#include <asm/io.h>#include <asm/dma.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include "3c505.h"#define ELP_DMA 6 /* DMA channel to use */#define ELP_RX_PCBS 4/********************************************************* * * 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 const char *search_msg = "%s: Looking for 3c505 adapter at address %#x...";static const char *stilllooking_msg = "still looking...";static const char *found_msg = "found.\n";static const char *notfound_msg = "not found (reason = %d)\n";static const char *couldnot_msg = "%s: 3c505 not found\n";/********************************************************* * * various other debug stuff * *********************************************************/#ifdef ELP_DEBUGstatic const int elp_debug = ELP_DEBUG;#elsestatic const int elp_debug = 0;#endif/* * 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! *****************************************************************/const int addr_list[] = {0x300, 0x280, 0x310, 0};/* Dma Memory related stuff *//* Pure 2^n version of get_order */static inline int __get_order(unsigned long size){ int order; size = (size - 1) >> (PAGE_SHIFT - 1); order = -1; do { size >>= 1; order++; } while (size); return order;}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 unsigned char inb_control(unsigned int base_addr){ return inb(base_addr + PORT_CONTROL);}static inline int inb_command(unsigned int base_addr){ return inb(base_addr + PORT_COMMAND);}static inline void outb_control(unsigned char val, unsigned int base_addr){ outb(val, base_addr + PORT_CONTROL);}static inline void outb_command(unsigned char val, unsigned int base_addr){ outb(val, base_addr + PORT_COMMAND);}static inline unsigned int inw_data(unsigned int base_addr){ return inw(base_addr + PORT_DATA);}static inline void outw_data(unsigned int val, unsigned int base_addr){ outw(val, base_addr + PORT_DATA);}/***************************************************************** * * structure to hold context information for adapter * *****************************************************************/#define DMA_BUFFER_SIZE 1600#define BACKLOG_SIZE 4typedef struct { volatile short got[NUM_TRANSMIT_CMDS]; /* flags for command completion */ pcb_struct tx_pcb; /* PCB for foreground sending */ pcb_struct rx_pcb; /* PCB for foreground receiving */ pcb_struct itx_pcb; /* PCB for background sending */ pcb_struct irx_pcb; /* PCB for background receiving */ struct enet_statistics stats; void *dma_buffer; struct { unsigned int length[BACKLOG_SIZE]; unsigned int in; unsigned int out; } rx_backlog; struct { unsigned int direction; unsigned int length; unsigned int copy_flag; struct sk_buff *skb; long int start_time; } current_dma; /* flags */ unsigned long send_pcb_semaphore; unsigned int dmaing; unsigned long busy; unsigned int rx_active; /* number of receive PCBs */} elp_device;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){ int timeout = jiffies + 10; register int stat1; do { stat1 = inb_status(base_addr); } while (stat1 != inb_status(base_addr) && jiffies < timeout); if (jiffies >= timeout) TIMEOUT_MSG(__LINE__); return stat1;}static inline void set_hsf(unsigned int base_addr, int hsf){ cli(); outb_control((inb_control(base_addr) & ~HSF_PCB_MASK) | hsf, base_addr); sti();}static int start_receive(struct device *, pcb_struct *);inline static void adapter_reset(struct device *dev){ int timeout; unsigned char orig_hcr = inb_control(dev->base_addr); elp_device *adapter = dev->priv; outb_control(0, dev->base_addr); if (inb_status(dev->base_addr) & ACRF) { do { inb_command(dev->base_addr); timeout = jiffies + 2; while ((jiffies <= timeout) && !(inb_status(dev->base_addr) & ACRF)); } while (inb_status(dev->base_addr) & ACRF); set_hsf(dev->base_addr, HSF_PCB_NAK); } outb_control(inb_control(dev->base_addr) | ATTN | DIR, dev->base_addr); timeout = jiffies + 1; while (jiffies <= timeout); outb_control(inb_control(dev->base_addr) & ~ATTN, dev->base_addr); timeout = jiffies + 1; while (jiffies <= timeout); outb_control(inb_control(dev->base_addr) | FLSH, dev->base_addr); timeout = jiffies + 1; while (jiffies <= timeout); outb_control(inb_control(dev->base_addr) & ~FLSH, dev->base_addr); timeout = jiffies + 1; while (jiffies <= timeout); outb_control(orig_hcr, dev->base_addr); if (!start_receive(dev, &adapter->tx_pcb)) printk("%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_dma(struct device *dev){ elp_device *adapter = dev->priv; if (adapter->dmaing && (jiffies > (adapter->current_dma.start_time + 10))) { unsigned long flags; printk("%s: DMA %s timed out, %d bytes left\n", dev->name, adapter->current_dma.direction ? "download" : "upload", get_dma_residue(dev->dma)); save_flags(flags); cli(); adapter->dmaing = 0; adapter->busy = 0; disable_dma(dev->dma); if (adapter->rx_active) adapter->rx_active--; outb_control(inb_control(dev->base_addr) & ~(DMAE | TCEN | DIR), dev->base_addr); restore_flags(flags); }}/* Primitive functions used by send_pcb() */static inline unsigned int send_pcb_slow(unsigned int base_addr, unsigned char byte){ unsigned int timeout; outb_command(byte, base_addr); for (timeout = jiffies + 5; jiffies < timeout;) { if (inb_status(base_addr) & HCRE) return FALSE; } printk("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("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 device *dev){ elp_device *adapter = dev->priv; while (adapter->rx_active < ELP_RX_PCBS && dev->start) { 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -