⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 3c505.c

📁 linux设备驱动开发详解(书代码)
💻 C
📖 第 1 页 / 共 4 页
字号:
/* * 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 + -