2005-04-17 06:20:36 +08:00
|
|
|
/* Intel EtherExpress 16 device driver for Linux
|
|
|
|
*
|
|
|
|
* Written by John Sullivan, 1995
|
|
|
|
* based on original code by Donald Becker, with changes by
|
|
|
|
* Alan Cox and Pauline Middelink.
|
|
|
|
*
|
|
|
|
* Support for 8-bit mode by Zoltan Szilagyi <zoltans@cs.arizona.edu>
|
|
|
|
*
|
|
|
|
* Many modifications, and currently maintained, by
|
|
|
|
* Philip Blundell <philb@gnu.org>
|
2008-10-14 10:01:08 +08:00
|
|
|
* Added the Compaq LTE Alan Cox <alan@lxorguk.ukuu.org.uk>
|
2008-02-03 22:36:24 +08:00
|
|
|
* Added MCA support Adam Fritzler
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
|
|
|
* Note - this driver is experimental still - it has problems on faster
|
|
|
|
* machines. Someone needs to sit down and go through it line by line with
|
|
|
|
* a databook...
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* The EtherExpress 16 is a fairly simple card, based on a shared-memory
|
|
|
|
* design using the i82586 Ethernet coprocessor. It bears no relationship,
|
|
|
|
* as far as I know, to the similarly-named "EtherExpress Pro" range.
|
|
|
|
*
|
|
|
|
* Historically, Linux support for these cards has been very bad. However,
|
|
|
|
* things seem to be getting better slowly.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* If your card is confused about what sort of interface it has (eg it
|
|
|
|
* persistently reports "10baseT" when none is fitted), running 'SOFTSET /BART'
|
|
|
|
* or 'SOFTSET /LISA' from DOS seems to help.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Here's the scoop on memory mapping.
|
|
|
|
*
|
|
|
|
* There are three ways to access EtherExpress card memory: either using the
|
|
|
|
* shared-memory mapping, or using PIO through the dataport, or using PIO
|
|
|
|
* through the "shadow memory" ports.
|
|
|
|
*
|
|
|
|
* The shadow memory system works by having the card map some of its memory
|
|
|
|
* as follows:
|
|
|
|
*
|
|
|
|
* (the low five bits of the SMPTR are ignored)
|
|
|
|
*
|
|
|
|
* base+0x4000..400f memory at SMPTR+0..15
|
|
|
|
* base+0x8000..800f memory at SMPTR+16..31
|
|
|
|
* base+0xc000..c007 dubious stuff (memory at SMPTR+16..23 apparently)
|
|
|
|
* base+0xc008..c00f memory at 0x0008..0x000f
|
|
|
|
*
|
|
|
|
* This last set (the one at c008) is particularly handy because the SCB
|
|
|
|
* lives at 0x0008. So that set of ports gives us easy random access to data
|
|
|
|
* in the SCB without having to mess around setting up pointers and the like.
|
|
|
|
* We always use this method to access the SCB (via the scb_xx() functions).
|
|
|
|
*
|
|
|
|
* Dataport access works by aiming the appropriate (read or write) pointer
|
|
|
|
* at the first address you're interested in, and then reading or writing from
|
|
|
|
* the dataport. The pointers auto-increment after each transfer. We use
|
|
|
|
* this for data transfer.
|
|
|
|
*
|
|
|
|
* We don't use the shared-memory system because it allegedly doesn't work on
|
|
|
|
* all cards, and because it's a bit more prone to go wrong (it's one more
|
|
|
|
* thing to configure...).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Known bugs:
|
|
|
|
*
|
|
|
|
* - The card seems to want to give us two interrupts every time something
|
|
|
|
* happens, where just one would be better.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
*
|
|
|
|
* Note by Zoltan Szilagyi 10-12-96:
|
|
|
|
*
|
|
|
|
* I've succeeded in eliminating the "CU wedged" messages, and hence the
|
|
|
|
* lockups, which were only occurring with cards running in 8-bit mode ("force
|
|
|
|
* 8-bit operation" in Intel's SoftSet utility). This version of the driver
|
|
|
|
* sets the 82586 and the ASIC to 8-bit mode at startup; it also stops the
|
|
|
|
* CU before submitting a packet for transmission, and then restarts it as soon
|
|
|
|
* as the process of handing the packet is complete. This is definitely an
|
|
|
|
* unnecessary slowdown if the card is running in 16-bit mode; therefore one
|
2006-09-14 01:24:59 +08:00
|
|
|
* should detect 16-bit vs 8-bit mode from the EEPROM settings and act
|
2005-04-17 06:20:36 +08:00
|
|
|
* accordingly. In 8-bit mode with this bugfix I'm getting about 150 K/s for
|
|
|
|
* ftp's, which is significantly better than I get in DOS, so the overhead of
|
|
|
|
* stopping and restarting the CU with each transmit is not prohibitive in
|
|
|
|
* practice.
|
|
|
|
*
|
|
|
|
* Update by David Woodhouse 11/5/99:
|
|
|
|
*
|
|
|
|
* I've seen "CU wedged" messages in 16-bit mode, on the Alpha architecture.
|
|
|
|
* I assume that this is because 16-bit accesses are actually handled as two
|
|
|
|
* 8-bit accesses.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef __alpha__
|
|
|
|
#define LOCKUP16 1
|
|
|
|
#endif
|
|
|
|
#ifndef LOCKUP16
|
|
|
|
#define LOCKUP16 0
|
|
|
|
#endif
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/fcntl.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/ioport.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/in.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/errno.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/netdevice.h>
|
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/skbuff.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/mca-legacy.h>
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
#include <linux/bitops.h>
|
2007-03-28 13:14:31 +08:00
|
|
|
#include <linux/jiffies.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
#include <asm/system.h>
|
|
|
|
#include <asm/io.h>
|
|
|
|
#include <asm/irq.h>
|
|
|
|
|
|
|
|
#ifndef NET_DEBUG
|
|
|
|
#define NET_DEBUG 4
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "eexpress.h"
|
|
|
|
|
|
|
|
#define EEXP_IO_EXTENT 16
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Private data declarations
|
|
|
|
*/
|
|
|
|
|
|
|
|
struct net_local
|
|
|
|
{
|
|
|
|
unsigned long last_tx; /* jiffies when last transmit started */
|
|
|
|
unsigned long init_time; /* jiffies when eexp_hw_init586 called */
|
|
|
|
unsigned short rx_first; /* first rx buf, same as RX_BUF_START */
|
|
|
|
unsigned short rx_last; /* last rx buf */
|
|
|
|
unsigned short rx_ptr; /* first rx buf to look at */
|
|
|
|
unsigned short tx_head; /* next free tx buf */
|
|
|
|
unsigned short tx_reap; /* first in-use tx buf */
|
|
|
|
unsigned short tx_tail; /* previous tx buf to tx_head */
|
|
|
|
unsigned short tx_link; /* last known-executing tx buf */
|
|
|
|
unsigned short last_tx_restart; /* set to tx_link when we
|
|
|
|
restart the CU */
|
|
|
|
unsigned char started;
|
|
|
|
unsigned short rx_buf_start;
|
|
|
|
unsigned short rx_buf_end;
|
|
|
|
unsigned short num_tx_bufs;
|
|
|
|
unsigned short num_rx_bufs;
|
|
|
|
unsigned char width; /* 0 for 16bit, 1 for 8bit */
|
|
|
|
unsigned char was_promisc;
|
|
|
|
unsigned char old_mc_count;
|
|
|
|
spinlock_t lock;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* This is the code and data that is downloaded to the EtherExpress card's
|
|
|
|
* memory at boot time.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned short start_code[] = {
|
|
|
|
/* 0x0000 */
|
|
|
|
0x0001, /* ISCP: busy - cleared after reset */
|
|
|
|
0x0008,0x0000,0x0000, /* offset,address (lo,hi) of SCB */
|
|
|
|
|
|
|
|
0x0000,0x0000, /* SCB: status, commands */
|
|
|
|
0x0000,0x0000, /* links to first command block,
|
|
|
|
first receive descriptor */
|
|
|
|
0x0000,0x0000, /* CRC error, alignment error counts */
|
|
|
|
0x0000,0x0000, /* out of resources, overrun error counts */
|
|
|
|
|
|
|
|
0x0000,0x0000, /* pad */
|
|
|
|
0x0000,0x0000,
|
|
|
|
|
|
|
|
/* 0x20 -- start of 82586 CU program */
|
|
|
|
#define CONF_LINK 0x20
|
2006-09-14 01:24:59 +08:00
|
|
|
0x0000,Cmd_Config,
|
2005-04-17 06:20:36 +08:00
|
|
|
0x0032, /* link to next command */
|
|
|
|
0x080c, /* 12 bytes follow : fifo threshold=8 */
|
|
|
|
0x2e40, /* don't rx bad frames
|
|
|
|
* SRDY/ARDY => ext. sync. : preamble len=8
|
|
|
|
* take addresses from data buffers
|
|
|
|
* 6 bytes/address
|
|
|
|
*/
|
|
|
|
0x6000, /* default backoff method & priority
|
|
|
|
* interframe spacing = 0x60 */
|
2006-09-14 01:24:59 +08:00
|
|
|
0xf200, /* slot time=0x200
|
2005-04-17 06:20:36 +08:00
|
|
|
* max collision retry = 0xf */
|
|
|
|
#define CONF_PROMISC 0x2e
|
2006-09-14 01:24:59 +08:00
|
|
|
0x0000, /* no HDLC : normal CRC : enable broadcast
|
2005-04-17 06:20:36 +08:00
|
|
|
* disable promiscuous/multicast modes */
|
|
|
|
0x003c, /* minimum frame length = 60 octets) */
|
|
|
|
|
|
|
|
0x0000,Cmd_SetAddr,
|
|
|
|
0x003e, /* link to next command */
|
|
|
|
#define CONF_HWADDR 0x38
|
|
|
|
0x0000,0x0000,0x0000, /* hardware address placed here */
|
|
|
|
|
|
|
|
0x0000,Cmd_MCast,
|
|
|
|
0x0076, /* link to next command */
|
|
|
|
#define CONF_NR_MULTICAST 0x44
|
2008-05-03 04:40:53 +08:00
|
|
|
0x0000, /* number of bytes in multicast address(es) */
|
2005-04-17 06:20:36 +08:00
|
|
|
#define CONF_MULTICAST 0x46
|
|
|
|
0x0000, 0x0000, 0x0000, /* some addresses */
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
0x0000, 0x0000, 0x0000,
|
|
|
|
|
|
|
|
#define CONF_DIAG_RESULT 0x76
|
|
|
|
0x0000, Cmd_Diag,
|
|
|
|
0x007c, /* link to next command */
|
|
|
|
|
|
|
|
0x0000,Cmd_TDR|Cmd_INT,
|
|
|
|
0x0084,
|
|
|
|
#define CONF_TDR_RESULT 0x82
|
|
|
|
0x0000,
|
|
|
|
|
|
|
|
0x0000,Cmd_END|Cmd_Nop, /* end of configure sequence */
|
|
|
|
0x0084 /* dummy link */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* maps irq number to EtherExpress magic value */
|
|
|
|
static char irqrmap[] = { 0,0,1,2,3,4,0,0,0,1,5,6,0,0,0,0 };
|
|
|
|
|
|
|
|
#ifdef CONFIG_MCA_LEGACY
|
|
|
|
/* mapping of the first four bits of the second POS register */
|
|
|
|
static unsigned short mca_iomap[] = {
|
|
|
|
0x270, 0x260, 0x250, 0x240, 0x230, 0x220, 0x210, 0x200,
|
|
|
|
0x370, 0x360, 0x350, 0x340, 0x330, 0x320, 0x310, 0x300
|
|
|
|
};
|
|
|
|
/* bits 5-7 of the second POS register */
|
|
|
|
static char mca_irqmap[] = { 12, 9, 3, 4, 5, 10, 11, 15 };
|
2006-09-14 01:24:59 +08:00
|
|
|
#endif
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Prototypes for Linux interface
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int eexp_open(struct net_device *dev);
|
|
|
|
static int eexp_close(struct net_device *dev);
|
|
|
|
static void eexp_timeout(struct net_device *dev);
|
|
|
|
static int eexp_xmit(struct sk_buff *buf, struct net_device *dev);
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t eexp_irq(int irq, void *dev_addr);
|
2005-04-17 06:20:36 +08:00
|
|
|
static void eexp_set_multicast(struct net_device *dev);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Prototypes for hardware access functions
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_rx_pio(struct net_device *dev);
|
|
|
|
static void eexp_hw_tx_pio(struct net_device *dev, unsigned short *buf,
|
|
|
|
unsigned short len);
|
|
|
|
static int eexp_hw_probe(struct net_device *dev,unsigned short ioaddr);
|
|
|
|
static unsigned short eexp_hw_readeeprom(unsigned short ioaddr,
|
|
|
|
unsigned char location);
|
|
|
|
|
|
|
|
static unsigned short eexp_hw_lasttxstat(struct net_device *dev);
|
|
|
|
static void eexp_hw_txrestart(struct net_device *dev);
|
|
|
|
|
|
|
|
static void eexp_hw_txinit (struct net_device *dev);
|
|
|
|
static void eexp_hw_rxinit (struct net_device *dev);
|
|
|
|
|
|
|
|
static void eexp_hw_init586 (struct net_device *dev);
|
|
|
|
static void eexp_setup_filter (struct net_device *dev);
|
|
|
|
|
|
|
|
static char *eexp_ifmap[]={"AUI", "BNC", "RJ45"};
|
|
|
|
enum eexp_iftype {AUI=0, BNC=1, TPE=2};
|
|
|
|
|
|
|
|
#define STARTED_RU 2
|
|
|
|
#define STARTED_CU 1
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Primitive hardware access functions.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static inline unsigned short scb_status(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return inw(dev->base_addr + 0xc008);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned short scb_rdcmd(struct net_device *dev)
|
|
|
|
{
|
|
|
|
return inw(dev->base_addr + 0xc00a);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void scb_command(struct net_device *dev, unsigned short cmd)
|
|
|
|
{
|
|
|
|
outw(cmd, dev->base_addr + 0xc00a);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void scb_wrcbl(struct net_device *dev, unsigned short val)
|
|
|
|
{
|
|
|
|
outw(val, dev->base_addr + 0xc00c);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void scb_wrrfa(struct net_device *dev, unsigned short val)
|
|
|
|
{
|
|
|
|
outw(val, dev->base_addr + 0xc00e);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void set_loopback(struct net_device *dev)
|
|
|
|
{
|
|
|
|
outb(inb(dev->base_addr + Config) | 2, dev->base_addr + Config);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void clear_loopback(struct net_device *dev)
|
|
|
|
{
|
|
|
|
outb(inb(dev->base_addr + Config) & ~2, dev->base_addr + Config);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned short int SHADOW(short int addr)
|
|
|
|
{
|
|
|
|
addr &= 0x1f;
|
|
|
|
if (addr > 0xf) addr += 0x3ff0;
|
|
|
|
return addr + 0x4000;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Linux interface
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* checks for presence of EtherExpress card
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int __init do_express_probe(struct net_device *dev)
|
|
|
|
{
|
|
|
|
unsigned short *port;
|
|
|
|
static unsigned short ports[] = { 0x240,0x300,0x310,0x270,0x320,0x340,0 };
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
int dev_irq = dev->irq;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
dev->if_port = 0xff; /* not set */
|
|
|
|
|
|
|
|
#ifdef CONFIG_MCA_LEGACY
|
|
|
|
if (MCA_bus) {
|
|
|
|
int slot = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Only find one card at a time. Subsequent calls
|
|
|
|
* will find others, however, proper multicard MCA
|
|
|
|
* probing and setup can't be done with the
|
|
|
|
* old-style Space.c init routines. -- ASF
|
|
|
|
*/
|
|
|
|
while (slot != MCA_NOTFOUND) {
|
|
|
|
int pos0, pos1;
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
slot = mca_find_unused_adapter(0x628B, slot);
|
|
|
|
if (slot == MCA_NOTFOUND)
|
|
|
|
break;
|
|
|
|
|
|
|
|
pos0 = mca_read_stored_pos(slot, 2);
|
|
|
|
pos1 = mca_read_stored_pos(slot, 3);
|
|
|
|
ioaddr = mca_iomap[pos1&0xf];
|
|
|
|
|
|
|
|
dev->irq = mca_irqmap[(pos1>>4)&0x7];
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* XXX: Transciever selection is done
|
2006-09-14 01:24:59 +08:00
|
|
|
* differently on the MCA version.
|
2005-04-17 06:20:36 +08:00
|
|
|
* How to get it to select something
|
|
|
|
* other than external/AUI is currently
|
|
|
|
* unknown. This code is just for looks. -- ASF
|
|
|
|
*/
|
|
|
|
if ((pos0 & 0x7) == 0x1)
|
|
|
|
dev->if_port = AUI;
|
|
|
|
else if ((pos0 & 0x7) == 0x5) {
|
|
|
|
if (pos1 & 0x80)
|
|
|
|
dev->if_port = BNC;
|
|
|
|
else
|
|
|
|
dev->if_port = TPE;
|
|
|
|
}
|
|
|
|
|
|
|
|
mca_set_adapter_name(slot, "Intel EtherExpress 16 MCA");
|
|
|
|
mca_set_adapter_procfn(slot, NULL, dev);
|
|
|
|
mca_mark_as_used(slot);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
if (ioaddr&0xfe00) {
|
|
|
|
if (!request_region(ioaddr, EEXP_IO_EXTENT, "EtherExpress"))
|
|
|
|
return -EBUSY;
|
|
|
|
err = eexp_hw_probe(dev,ioaddr);
|
|
|
|
release_region(ioaddr, EEXP_IO_EXTENT);
|
|
|
|
return err;
|
|
|
|
} else if (ioaddr)
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
for (port=&ports[0] ; *port ; port++ )
|
|
|
|
{
|
|
|
|
unsigned short sum = 0;
|
|
|
|
int i;
|
|
|
|
if (!request_region(*port, EEXP_IO_EXTENT, "EtherExpress"))
|
|
|
|
continue;
|
|
|
|
for ( i=0 ; i<4 ; i++ )
|
|
|
|
{
|
|
|
|
unsigned short t;
|
|
|
|
t = inb(*port + ID_PORT);
|
|
|
|
sum |= (t>>4) << ((t & 0x03)<<2);
|
|
|
|
}
|
|
|
|
if (sum==0xbaba && !eexp_hw_probe(dev,*port)) {
|
|
|
|
release_region(*port, EEXP_IO_EXTENT);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
release_region(*port, EEXP_IO_EXTENT);
|
|
|
|
dev->irq = dev_irq;
|
|
|
|
}
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef MODULE
|
|
|
|
struct net_device * __init express_probe(int unit)
|
|
|
|
{
|
|
|
|
struct net_device *dev = alloc_etherdev(sizeof(struct net_local));
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!dev)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
sprintf(dev->name, "eth%d", unit);
|
|
|
|
netdev_boot_setup_check(dev);
|
|
|
|
|
|
|
|
err = do_express_probe(dev);
|
2005-05-13 08:11:55 +08:00
|
|
|
if (!err)
|
|
|
|
return dev;
|
2005-04-17 06:20:36 +08:00
|
|
|
free_netdev(dev);
|
|
|
|
return ERR_PTR(err);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* open and initialize the adapter, ready for use
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int eexp_open(struct net_device *dev)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
|
|
|
|
#if NET_DEBUG > 6
|
|
|
|
printk(KERN_DEBUG "%s: eexp_open()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!dev->irq || !irqrmap[dev->irq])
|
|
|
|
return -ENXIO;
|
|
|
|
|
2007-10-29 17:46:16 +08:00
|
|
|
ret = request_irq(dev->irq, &eexp_irq, 0, dev->name, dev);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
if (!request_region(ioaddr, EEXP_IO_EXTENT, "EtherExpress")) {
|
|
|
|
printk(KERN_WARNING "EtherExpress io port %x, is busy.\n"
|
|
|
|
, ioaddr);
|
|
|
|
goto err_out1;
|
|
|
|
}
|
|
|
|
if (!request_region(ioaddr+0x4000, EEXP_IO_EXTENT, "EtherExpress shadow")) {
|
|
|
|
printk(KERN_WARNING "EtherExpress io port %x, is busy.\n"
|
|
|
|
, ioaddr+0x4000);
|
|
|
|
goto err_out2;
|
|
|
|
}
|
|
|
|
if (!request_region(ioaddr+0x8000, EEXP_IO_EXTENT, "EtherExpress shadow")) {
|
|
|
|
printk(KERN_WARNING "EtherExpress io port %x, is busy.\n"
|
|
|
|
, ioaddr+0x8000);
|
|
|
|
goto err_out3;
|
|
|
|
}
|
|
|
|
if (!request_region(ioaddr+0xc000, EEXP_IO_EXTENT, "EtherExpress shadow")) {
|
|
|
|
printk(KERN_WARNING "EtherExpress io port %x, is busy.\n"
|
|
|
|
, ioaddr+0xc000);
|
|
|
|
goto err_out4;
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (lp->width) {
|
|
|
|
printk("%s: forcing ASIC to 8-bit mode\n", dev->name);
|
|
|
|
outb(inb(dev->base_addr+Config)&~4, dev->base_addr+Config);
|
|
|
|
}
|
|
|
|
|
|
|
|
eexp_hw_init586(dev);
|
|
|
|
netif_start_queue(dev);
|
|
|
|
#if NET_DEBUG > 6
|
|
|
|
printk(KERN_DEBUG "%s: leaving eexp_open()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_out4:
|
|
|
|
release_region(ioaddr+0x8000, EEXP_IO_EXTENT);
|
|
|
|
err_out3:
|
|
|
|
release_region(ioaddr+0x4000, EEXP_IO_EXTENT);
|
|
|
|
err_out2:
|
|
|
|
release_region(ioaddr, EEXP_IO_EXTENT);
|
|
|
|
err_out1:
|
|
|
|
free_irq(dev->irq, dev);
|
|
|
|
return -EBUSY;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* close and disable the interface, leaving the 586 in reset.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int eexp_close(struct net_device *dev)
|
|
|
|
{
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
|
|
|
|
int irq = dev->irq;
|
|
|
|
|
|
|
|
netif_stop_queue(dev);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
outb(SIRQ_dis|irqrmap[irq],ioaddr+SET_IRQ);
|
|
|
|
lp->started = 0;
|
|
|
|
scb_command(dev, SCB_CUsuspend|SCB_RUsuspend);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
free_irq(irq,dev);
|
|
|
|
outb(i586_RST,ioaddr+EEPROM_Ctrl);
|
|
|
|
release_region(ioaddr, EEXP_IO_EXTENT);
|
|
|
|
release_region(ioaddr+0x4000, 16);
|
|
|
|
release_region(ioaddr+0x8000, 16);
|
|
|
|
release_region(ioaddr+0xc000, 16);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This gets called when a higher level thinks we are broken. Check that
|
|
|
|
* nothing has become jammed in the CU.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void unstick_cu(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
|
|
|
|
if (lp->started)
|
|
|
|
{
|
2007-03-28 13:14:31 +08:00
|
|
|
if (time_after(jiffies, dev->trans_start + 50))
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
if (lp->tx_link==lp->last_tx_restart)
|
|
|
|
{
|
|
|
|
unsigned short boguscount=200,rsst;
|
|
|
|
printk(KERN_WARNING "%s: Retransmit timed out, status %04x, resetting...\n",
|
|
|
|
dev->name, scb_status(dev));
|
|
|
|
eexp_hw_txinit(dev);
|
|
|
|
lp->last_tx_restart = 0;
|
|
|
|
scb_wrcbl(dev, lp->tx_link);
|
|
|
|
scb_command(dev, SCB_CUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
while (!SCB_complete(rsst=scb_status(dev)))
|
|
|
|
{
|
|
|
|
if (!--boguscount)
|
|
|
|
{
|
|
|
|
boguscount=200;
|
|
|
|
printk(KERN_WARNING "%s: Reset timed out status %04x, retrying...\n",
|
|
|
|
dev->name,rsst);
|
|
|
|
scb_wrcbl(dev, lp->tx_link);
|
|
|
|
scb_command(dev, SCB_CUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned short status = scb_status(dev);
|
|
|
|
if (SCB_CUdead(status))
|
|
|
|
{
|
|
|
|
unsigned short txstatus = eexp_hw_lasttxstat(dev);
|
|
|
|
printk(KERN_WARNING "%s: Transmit timed out, CU not active status %04x %04x, restarting...\n",
|
|
|
|
dev->name, status, txstatus);
|
|
|
|
eexp_hw_txrestart(dev);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
unsigned short txstatus = eexp_hw_lasttxstat(dev);
|
|
|
|
if (netif_queue_stopped(dev) && !txstatus)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: CU wedged, status %04x %04x, resetting...\n",
|
|
|
|
dev->name,status,txstatus);
|
|
|
|
eexp_hw_init586(dev);
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: transmit timed out\n", dev->name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2007-03-28 13:14:31 +08:00
|
|
|
if (time_after(jiffies, lp->init_time + 10))
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned short status = scb_status(dev);
|
|
|
|
printk(KERN_WARNING "%s: i82586 startup timed out, status %04x, resetting...\n",
|
|
|
|
dev->name, status);
|
|
|
|
eexp_hw_init586(dev);
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void eexp_timeout(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
unsigned long flags;
|
|
|
|
#endif
|
|
|
|
int status;
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
disable_irq(dev->irq);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Best would be to use synchronize_irq(); spin_lock() here
|
|
|
|
* lets make it work first..
|
|
|
|
*/
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
spin_lock_irqsave(&lp->lock, flags);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
status = scb_status(dev);
|
|
|
|
unstick_cu(dev);
|
|
|
|
printk(KERN_INFO "%s: transmit timed out, %s?\n", dev->name,
|
|
|
|
(SCB_complete(status)?"lost interrupt":
|
|
|
|
"board on fire"));
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->last_tx = jiffies;
|
|
|
|
if (!SCB_complete(status)) {
|
|
|
|
scb_command(dev, SCB_CUabort);
|
|
|
|
outb(0,dev->base_addr+SIGNAL_CA);
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
netif_wake_queue(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
spin_unlock_irqrestore(&lp->lock, flags);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Called to transmit a packet, or to allow us to right ourselves
|
|
|
|
* if the kernel thinks we've died.
|
|
|
|
*/
|
|
|
|
static int eexp_xmit(struct sk_buff *buf, struct net_device *dev)
|
|
|
|
{
|
|
|
|
short length = buf->len;
|
|
|
|
#ifdef CONFIG_SMP
|
2007-10-24 06:36:42 +08:00
|
|
|
struct net_local *lp = netdev_priv(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned long flags;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if NET_DEBUG > 6
|
|
|
|
printk(KERN_DEBUG "%s: eexp_xmit()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (buf->len < ETH_ZLEN) {
|
2006-06-23 17:06:41 +08:00
|
|
|
if (skb_padto(buf, ETH_ZLEN))
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
length = ETH_ZLEN;
|
|
|
|
}
|
|
|
|
|
|
|
|
disable_irq(dev->irq);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Best would be to use synchronize_irq(); spin_lock() here
|
|
|
|
* lets make it work first..
|
|
|
|
*/
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
spin_lock_irqsave(&lp->lock, flags);
|
|
|
|
#endif
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
unsigned short *data = (unsigned short *)buf->data;
|
|
|
|
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_bytes += length;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
eexp_hw_tx_pio(dev,data,length);
|
|
|
|
}
|
|
|
|
dev_kfree_skb(buf);
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
spin_unlock_irqrestore(&lp->lock, flags);
|
|
|
|
#endif
|
|
|
|
enable_irq(dev->irq);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Handle an EtherExpress interrupt
|
|
|
|
* If we've finished initializing, start the RU and CU up.
|
|
|
|
* If we've already started, reap tx buffers, handle any received packets,
|
|
|
|
* check to make sure we've not become wedged.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned short eexp_start_irq(struct net_device *dev,
|
|
|
|
unsigned short status)
|
|
|
|
{
|
|
|
|
unsigned short ack_cmd = SCB_ack(status);
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
if ((dev->flags & IFF_UP) && !(lp->started & STARTED_CU)) {
|
|
|
|
short diag_status, tdr_status;
|
|
|
|
while (SCB_CUstat(status)==2)
|
|
|
|
status = scb_status(dev);
|
|
|
|
#if NET_DEBUG > 4
|
|
|
|
printk("%s: CU went non-active (status %04x)\n",
|
|
|
|
dev->name, status);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
outw(CONF_DIAG_RESULT & ~31, ioaddr + SM_PTR);
|
|
|
|
diag_status = inw(ioaddr + SHADOW(CONF_DIAG_RESULT));
|
|
|
|
if (diag_status & 1<<11) {
|
2006-09-14 01:24:59 +08:00
|
|
|
printk(KERN_WARNING "%s: 82586 failed self-test\n",
|
2005-04-17 06:20:36 +08:00
|
|
|
dev->name);
|
|
|
|
} else if (!(diag_status & 1<<13)) {
|
|
|
|
printk(KERN_WARNING "%s: 82586 self-test failed to complete\n", dev->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
outw(CONF_TDR_RESULT & ~31, ioaddr + SM_PTR);
|
|
|
|
tdr_status = inw(ioaddr + SHADOW(CONF_TDR_RESULT));
|
|
|
|
if (tdr_status & (TDR_SHORT|TDR_OPEN)) {
|
|
|
|
printk(KERN_WARNING "%s: TDR reports cable %s at %d tick%s\n", dev->name, (tdr_status & TDR_SHORT)?"short":"broken", tdr_status & TDR_TIME, ((tdr_status & TDR_TIME) != 1) ? "s" : "");
|
2006-09-14 01:24:59 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
else if (tdr_status & TDR_XCVRPROBLEM) {
|
|
|
|
printk(KERN_WARNING "%s: TDR reports transceiver problem\n", dev->name);
|
|
|
|
}
|
|
|
|
else if (tdr_status & TDR_LINKOK) {
|
|
|
|
#if NET_DEBUG > 4
|
|
|
|
printk(KERN_DEBUG "%s: TDR reports link OK\n", dev->name);
|
|
|
|
#endif
|
|
|
|
} else {
|
|
|
|
printk("%s: TDR is ga-ga (status %04x)\n", dev->name,
|
|
|
|
tdr_status);
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->started |= STARTED_CU;
|
|
|
|
scb_wrcbl(dev, lp->tx_link);
|
|
|
|
/* if the RU isn't running, start it now */
|
|
|
|
if (!(lp->started & STARTED_RU)) {
|
|
|
|
ack_cmd |= SCB_RUstart;
|
|
|
|
scb_wrrfa(dev, lp->rx_buf_start);
|
|
|
|
lp->rx_ptr = lp->rx_buf_start;
|
|
|
|
lp->started |= STARTED_RU;
|
|
|
|
}
|
|
|
|
ack_cmd |= SCB_CUstart | 0x2000;
|
|
|
|
}
|
|
|
|
|
2006-09-14 01:24:59 +08:00
|
|
|
if ((dev->flags & IFF_UP) && !(lp->started & STARTED_RU) && SCB_RUstat(status)==4)
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->started|=STARTED_RU;
|
|
|
|
|
|
|
|
return ack_cmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void eexp_cmd_clear(struct net_device *dev)
|
|
|
|
{
|
|
|
|
unsigned long int oldtime = jiffies;
|
2007-04-28 23:05:43 +08:00
|
|
|
while (scb_rdcmd(dev) && (time_before(jiffies, oldtime + 10)));
|
2005-04-17 06:20:36 +08:00
|
|
|
if (scb_rdcmd(dev)) {
|
|
|
|
printk("%s: command didn't clear\n", dev->name);
|
|
|
|
}
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2007-10-29 17:46:16 +08:00
|
|
|
static irqreturn_t eexp_irq(int dummy, void *dev_info)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct net_device *dev = dev_info;
|
|
|
|
struct net_local *lp;
|
|
|
|
unsigned short ioaddr,status,ack_cmd;
|
|
|
|
unsigned short old_read_ptr, old_write_ptr;
|
|
|
|
|
|
|
|
lp = netdev_priv(dev);
|
|
|
|
ioaddr = dev->base_addr;
|
|
|
|
|
|
|
|
spin_lock(&lp->lock);
|
|
|
|
|
|
|
|
old_read_ptr = inw(ioaddr+READ_PTR);
|
|
|
|
old_write_ptr = inw(ioaddr+WRITE_PTR);
|
|
|
|
|
2007-11-24 10:50:34 +08:00
|
|
|
outb(SIRQ_dis|irqrmap[dev->irq], ioaddr+SET_IRQ);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
status = scb_status(dev);
|
|
|
|
|
|
|
|
#if NET_DEBUG > 4
|
|
|
|
printk(KERN_DEBUG "%s: interrupt (status %x)\n", dev->name, status);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (lp->started == (STARTED_CU | STARTED_RU)) {
|
|
|
|
|
|
|
|
do {
|
|
|
|
eexp_cmd_clear(dev);
|
|
|
|
|
|
|
|
ack_cmd = SCB_ack(status);
|
|
|
|
scb_command(dev, ack_cmd);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
|
|
|
|
eexp_cmd_clear(dev);
|
|
|
|
|
|
|
|
if (SCB_complete(status)) {
|
|
|
|
if (!eexp_hw_lasttxstat(dev)) {
|
|
|
|
printk("%s: tx interrupt but no status\n", dev->name);
|
|
|
|
}
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
|
|
|
|
if (SCB_rxdframe(status))
|
2005-04-17 06:20:36 +08:00
|
|
|
eexp_hw_rx_pio(dev);
|
|
|
|
|
|
|
|
status = scb_status(dev);
|
|
|
|
} while (status & 0xc000);
|
|
|
|
|
2006-09-14 01:24:59 +08:00
|
|
|
if (SCB_RUdead(status))
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: RU stopped: status %04x\n",
|
|
|
|
dev->name,status);
|
|
|
|
#if 0
|
|
|
|
printk(KERN_WARNING "%s: cur_rfd=%04x, cur_rbd=%04x\n", dev->name, lp->cur_rfd, lp->cur_rbd);
|
|
|
|
outw(lp->cur_rfd, ioaddr+READ_PTR);
|
|
|
|
printk(KERN_WARNING "%s: [%04x]\n", dev->name, inw(ioaddr+DATAPORT));
|
|
|
|
outw(lp->cur_rfd+6, ioaddr+READ_PTR);
|
|
|
|
printk(KERN_WARNING "%s: rbd is %04x\n", dev->name, rbd= inw(ioaddr+DATAPORT));
|
|
|
|
outw(rbd, ioaddr+READ_PTR);
|
|
|
|
printk(KERN_WARNING "%s: [%04x %04x] ", dev->name, inw(ioaddr+DATAPORT), inw(ioaddr+DATAPORT));
|
|
|
|
outw(rbd+8, ioaddr+READ_PTR);
|
|
|
|
printk("[%04x]\n", inw(ioaddr+DATAPORT));
|
|
|
|
#endif
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
#if 1
|
|
|
|
eexp_hw_rxinit(dev);
|
|
|
|
#else
|
|
|
|
lp->cur_rfd = lp->first_rfd;
|
|
|
|
#endif
|
|
|
|
scb_wrrfa(dev, lp->rx_buf_start);
|
|
|
|
scb_command(dev, SCB_RUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
2006-09-14 01:24:59 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
} else {
|
2006-09-14 01:24:59 +08:00
|
|
|
if (status & 0x8000)
|
2005-04-17 06:20:36 +08:00
|
|
|
ack_cmd = eexp_start_irq(dev, status);
|
|
|
|
else
|
|
|
|
ack_cmd = SCB_ack(status);
|
|
|
|
scb_command(dev, ack_cmd);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
}
|
|
|
|
|
|
|
|
eexp_cmd_clear(dev);
|
|
|
|
|
2007-11-24 10:50:34 +08:00
|
|
|
outb(SIRQ_en|irqrmap[dev->irq], ioaddr+SET_IRQ);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2006-09-14 01:24:59 +08:00
|
|
|
#if NET_DEBUG > 6
|
2005-04-17 06:20:36 +08:00
|
|
|
printk("%s: leaving eexp_irq()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
outw(old_read_ptr, ioaddr+READ_PTR);
|
|
|
|
outw(old_write_ptr, ioaddr+WRITE_PTR);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
spin_unlock(&lp->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hardware access functions
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set the cable type to use.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_set_interface(struct net_device *dev)
|
|
|
|
{
|
|
|
|
unsigned char oldval = inb(dev->base_addr + 0x300e);
|
|
|
|
oldval &= ~0x82;
|
|
|
|
switch (dev->if_port) {
|
|
|
|
case TPE:
|
|
|
|
oldval |= 0x2;
|
|
|
|
case BNC:
|
|
|
|
oldval |= 0x80;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
outb(oldval, dev->base_addr+0x300e);
|
|
|
|
mdelay(20);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Check all the receive buffers, and hand any received packets
|
|
|
|
* to the upper levels. Basic sanity check on each frame
|
|
|
|
* descriptor, though we don't bother trying to fix broken ones.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_rx_pio(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short rx_block = lp->rx_ptr;
|
|
|
|
unsigned short boguscount = lp->num_rx_bufs;
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
unsigned short status;
|
|
|
|
|
|
|
|
#if NET_DEBUG > 6
|
|
|
|
printk(KERN_DEBUG "%s: eexp_hw_rx()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
do {
|
|
|
|
unsigned short rfd_cmd, rx_next, pbuf, pkt_len;
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
outw(rx_block, ioaddr + READ_PTR);
|
|
|
|
status = inw(ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
if (FD_Done(status))
|
|
|
|
{
|
|
|
|
rfd_cmd = inw(ioaddr + DATAPORT);
|
|
|
|
rx_next = inw(ioaddr + DATAPORT);
|
|
|
|
pbuf = inw(ioaddr + DATAPORT);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
outw(pbuf, ioaddr + READ_PTR);
|
|
|
|
pkt_len = inw(ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
if (rfd_cmd!=0x0000)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: rfd_cmd not zero:0x%04x\n",
|
|
|
|
dev->name, rfd_cmd);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (pbuf!=rx_block+0x16)
|
|
|
|
{
|
2006-09-14 01:24:59 +08:00
|
|
|
printk(KERN_WARNING "%s: rfd and rbd out of sync 0x%04x 0x%04x\n",
|
2005-04-17 06:20:36 +08:00
|
|
|
dev->name, rx_block+0x16, pbuf);
|
|
|
|
continue;
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
else if ((pkt_len & 0xc000)!=0xc000)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: EOF or F not set on received buffer (%04x)\n",
|
|
|
|
dev->name, pkt_len & 0xc000);
|
|
|
|
continue;
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
else if (!FD_OK(status))
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (FD_CRC(status))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_crc_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (FD_Align(status))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_frame_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (FD_Resrc(status))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_fifo_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (FD_DMA(status))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_over_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (FD_Short(status))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_length_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
struct sk_buff *skb;
|
|
|
|
pkt_len &= 0x3fff;
|
|
|
|
skb = dev_alloc_skb(pkt_len+16);
|
|
|
|
if (skb == NULL)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: Memory squeeze, dropping packet\n",dev->name);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_dropped++;
|
2005-04-17 06:20:36 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
skb_reserve(skb, 2);
|
|
|
|
outw(pbuf+10, ioaddr+READ_PTR);
|
|
|
|
insw(ioaddr+DATAPORT, skb_put(skb,pkt_len),(pkt_len+1)>>1);
|
|
|
|
skb->protocol = eth_type_trans(skb,dev);
|
|
|
|
netif_rx(skb);
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.rx_packets++;
|
|
|
|
dev->stats.rx_bytes += pkt_len;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
outw(rx_block, ioaddr+WRITE_PTR);
|
|
|
|
outw(0, ioaddr+DATAPORT);
|
|
|
|
outw(0, ioaddr+DATAPORT);
|
|
|
|
rx_block = rx_next;
|
|
|
|
}
|
|
|
|
} while (FD_Done(status) && boguscount--);
|
|
|
|
lp->rx_ptr = rx_block;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Hand a packet to the card for transmission
|
|
|
|
* If we get here, we MUST have already checked
|
|
|
|
* to make sure there is room in the transmit
|
|
|
|
* buffer region.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_tx_pio(struct net_device *dev, unsigned short *buf,
|
|
|
|
unsigned short len)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
|
|
|
|
if (LOCKUP16 || lp->width) {
|
|
|
|
/* Stop the CU so that there is no chance that it
|
|
|
|
jumps off to a bogus address while we are writing the
|
2006-09-14 01:24:59 +08:00
|
|
|
pointer to the next transmit packet in 8-bit mode --
|
2005-04-17 06:20:36 +08:00
|
|
|
this eliminates the "CU wedged" errors in 8-bit mode.
|
2006-09-14 01:24:59 +08:00
|
|
|
(Zoltan Szilagyi 10-12-96) */
|
2005-04-17 06:20:36 +08:00
|
|
|
scb_command(dev, SCB_CUsuspend);
|
|
|
|
outw(0xFFFF, ioaddr+SIGNAL_CA);
|
|
|
|
}
|
|
|
|
|
|
|
|
outw(lp->tx_head, ioaddr + WRITE_PTR);
|
|
|
|
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
outw(Cmd_INT|Cmd_Xmit, ioaddr + DATAPORT);
|
|
|
|
outw(lp->tx_head+0x08, ioaddr + DATAPORT);
|
|
|
|
outw(lp->tx_head+0x0e, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
outw(lp->tx_head+0x08, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
outw(0x8000|len, ioaddr + DATAPORT);
|
|
|
|
outw(-1, ioaddr + DATAPORT);
|
|
|
|
outw(lp->tx_head+0x16, ioaddr + DATAPORT);
|
|
|
|
outw(0, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
outsw(ioaddr + DATAPORT, buf, (len+1)>>1);
|
|
|
|
|
|
|
|
outw(lp->tx_tail+0xc, ioaddr + WRITE_PTR);
|
|
|
|
outw(lp->tx_head, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
dev->trans_start = jiffies;
|
|
|
|
lp->tx_tail = lp->tx_head;
|
|
|
|
if (lp->tx_head==TX_BUF_START+((lp->num_tx_bufs-1)*TX_BUF_SIZE))
|
|
|
|
lp->tx_head = TX_BUF_START;
|
|
|
|
else
|
|
|
|
lp->tx_head += TX_BUF_SIZE;
|
|
|
|
if (lp->tx_head != lp->tx_reap)
|
|
|
|
netif_wake_queue(dev);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (LOCKUP16 || lp->width) {
|
|
|
|
/* Restart the CU so that the packet can actually
|
|
|
|
be transmitted. (Zoltan Szilagyi 10-12-96) */
|
|
|
|
scb_command(dev, SCB_CUresume);
|
|
|
|
outw(0xFFFF, ioaddr+SIGNAL_CA);
|
|
|
|
}
|
|
|
|
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_packets++;
|
2005-04-17 06:20:36 +08:00
|
|
|
lp->last_tx = jiffies;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Sanity check the suspected EtherExpress card
|
|
|
|
* Read hardware address, reset card, size memory and initialize buffer
|
2008-12-08 17:14:16 +08:00
|
|
|
* memory pointers. These are held in netdev_priv(), in case someone has more
|
2005-04-17 06:20:36 +08:00
|
|
|
* than one card in a machine.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int __init eexp_hw_probe(struct net_device *dev, unsigned short ioaddr)
|
|
|
|
{
|
|
|
|
unsigned short hw_addr[3];
|
|
|
|
unsigned char buswidth;
|
|
|
|
unsigned int memory_size;
|
|
|
|
int i;
|
|
|
|
unsigned short xsum = 0;
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
|
|
|
|
printk("%s: EtherExpress 16 at %#x ",dev->name,ioaddr);
|
|
|
|
|
|
|
|
outb(ASIC_RST, ioaddr+EEPROM_Ctrl);
|
|
|
|
outb(0, ioaddr+EEPROM_Ctrl);
|
|
|
|
udelay(500);
|
|
|
|
outb(i586_RST, ioaddr+EEPROM_Ctrl);
|
|
|
|
|
|
|
|
hw_addr[0] = eexp_hw_readeeprom(ioaddr,2);
|
|
|
|
hw_addr[1] = eexp_hw_readeeprom(ioaddr,3);
|
|
|
|
hw_addr[2] = eexp_hw_readeeprom(ioaddr,4);
|
|
|
|
|
|
|
|
/* Standard Address or Compaq LTE Address */
|
|
|
|
if (!((hw_addr[2]==0x00aa && ((hw_addr[1] & 0xff00)==0x0000)) ||
|
2006-09-14 01:24:59 +08:00
|
|
|
(hw_addr[2]==0x0080 && ((hw_addr[1] & 0xff00)==0x5F00))))
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
printk(" rejected: invalid address %04x%04x%04x\n",
|
|
|
|
hw_addr[2],hw_addr[1],hw_addr[0]);
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Calculate the EEPROM checksum. Carry on anyway if it's bad,
|
|
|
|
* though.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < 64; i++)
|
|
|
|
xsum += eexp_hw_readeeprom(ioaddr, i);
|
|
|
|
if (xsum != 0xbaba)
|
|
|
|
printk(" (bad EEPROM xsum 0x%02x)", xsum);
|
|
|
|
|
|
|
|
dev->base_addr = ioaddr;
|
|
|
|
for ( i=0 ; i<6 ; i++ )
|
|
|
|
dev->dev_addr[i] = ((unsigned char *)hw_addr)[5-i];
|
|
|
|
|
|
|
|
{
|
|
|
|
static char irqmap[]={0, 9, 3, 4, 5, 10, 11, 0};
|
|
|
|
unsigned short setupval = eexp_hw_readeeprom(ioaddr,0);
|
|
|
|
|
|
|
|
/* Use the IRQ from EEPROM if none was given */
|
|
|
|
if (!dev->irq)
|
|
|
|
dev->irq = irqmap[setupval>>13];
|
|
|
|
|
|
|
|
if (dev->if_port == 0xff) {
|
|
|
|
dev->if_port = !(setupval & 0x1000) ? AUI :
|
|
|
|
eexp_hw_readeeprom(ioaddr,5) & 0x1 ? TPE : BNC;
|
|
|
|
}
|
|
|
|
|
|
|
|
buswidth = !((setupval & 0x400) >> 10);
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(lp, 0, sizeof(struct net_local));
|
|
|
|
spin_lock_init(&lp->lock);
|
|
|
|
|
2006-09-14 01:24:59 +08:00
|
|
|
printk("(IRQ %d, %s connector, %d-bit bus", dev->irq,
|
2005-04-17 06:20:36 +08:00
|
|
|
eexp_ifmap[dev->if_port], buswidth?8:16);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!request_region(dev->base_addr + 0x300e, 1, "EtherExpress"))
|
|
|
|
return -EBUSY;
|
|
|
|
|
|
|
|
eexp_hw_set_interface(dev);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
release_region(dev->base_addr + 0x300e, 1);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Find out how much RAM we have on the card */
|
|
|
|
outw(0, dev->base_addr + WRITE_PTR);
|
|
|
|
for (i = 0; i < 32768; i++)
|
|
|
|
outw(0, dev->base_addr + DATAPORT);
|
|
|
|
|
|
|
|
for (memory_size = 0; memory_size < 64; memory_size++)
|
|
|
|
{
|
|
|
|
outw(memory_size<<10, dev->base_addr + READ_PTR);
|
|
|
|
if (inw(dev->base_addr+DATAPORT))
|
|
|
|
break;
|
|
|
|
outw(memory_size<<10, dev->base_addr + WRITE_PTR);
|
|
|
|
outw(memory_size | 0x5000, dev->base_addr+DATAPORT);
|
|
|
|
outw(memory_size<<10, dev->base_addr + READ_PTR);
|
|
|
|
if (inw(dev->base_addr+DATAPORT) != (memory_size | 0x5000))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Sort out the number of buffers. We may have 16, 32, 48 or 64k
|
|
|
|
* of RAM to play with.
|
|
|
|
*/
|
|
|
|
lp->num_tx_bufs = 4;
|
|
|
|
lp->rx_buf_end = 0x3ff6;
|
|
|
|
switch (memory_size)
|
|
|
|
{
|
|
|
|
case 64:
|
|
|
|
lp->rx_buf_end += 0x4000;
|
|
|
|
case 48:
|
|
|
|
lp->num_tx_bufs += 4;
|
|
|
|
lp->rx_buf_end += 0x4000;
|
|
|
|
case 32:
|
|
|
|
lp->rx_buf_end += 0x4000;
|
|
|
|
case 16:
|
|
|
|
printk(", %dk RAM)\n", memory_size);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
printk(") bad memory size (%dk).\n", memory_size);
|
|
|
|
return -ENODEV;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
lp->rx_buf_start = TX_BUF_START + (lp->num_tx_bufs*TX_BUF_SIZE);
|
|
|
|
lp->width = buswidth;
|
|
|
|
|
|
|
|
dev->open = eexp_open;
|
|
|
|
dev->stop = eexp_close;
|
|
|
|
dev->hard_start_xmit = eexp_xmit;
|
|
|
|
dev->set_multicast_list = &eexp_set_multicast;
|
|
|
|
dev->tx_timeout = eexp_timeout;
|
|
|
|
dev->watchdog_timeo = 2*HZ;
|
2005-05-13 08:11:55 +08:00
|
|
|
|
|
|
|
return register_netdev(dev);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read a word from the EtherExpress on-board serial EEPROM.
|
|
|
|
* The EEPROM contains 64 words of 16 bits.
|
|
|
|
*/
|
|
|
|
static unsigned short __init eexp_hw_readeeprom(unsigned short ioaddr,
|
|
|
|
unsigned char location)
|
|
|
|
{
|
|
|
|
unsigned short cmd = 0x180|(location&0x7f);
|
|
|
|
unsigned short rval = 0,wval = EC_CS|i586_RST;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
outb(EC_CS|i586_RST,ioaddr+EEPROM_Ctrl);
|
|
|
|
for (i=0x100 ; i ; i>>=1 )
|
|
|
|
{
|
|
|
|
if (cmd&i)
|
|
|
|
wval |= EC_Wr;
|
|
|
|
else
|
|
|
|
wval &= ~EC_Wr;
|
|
|
|
|
|
|
|
outb(wval,ioaddr+EEPROM_Ctrl);
|
|
|
|
outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
|
|
|
|
eeprom_delay();
|
|
|
|
outb(wval,ioaddr+EEPROM_Ctrl);
|
|
|
|
eeprom_delay();
|
|
|
|
}
|
|
|
|
wval &= ~EC_Wr;
|
|
|
|
outb(wval,ioaddr+EEPROM_Ctrl);
|
|
|
|
for (i=0x8000 ; i ; i>>=1 )
|
|
|
|
{
|
|
|
|
outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
|
|
|
|
eeprom_delay();
|
|
|
|
if (inb(ioaddr+EEPROM_Ctrl)&EC_Rd)
|
|
|
|
rval |= i;
|
|
|
|
outb(wval,ioaddr+EEPROM_Ctrl);
|
|
|
|
eeprom_delay();
|
|
|
|
}
|
|
|
|
wval &= ~EC_CS;
|
|
|
|
outb(wval|EC_Clk,ioaddr+EEPROM_Ctrl);
|
|
|
|
eeprom_delay();
|
|
|
|
outb(wval,ioaddr+EEPROM_Ctrl);
|
|
|
|
eeprom_delay();
|
|
|
|
return rval;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Reap tx buffers and return last transmit status.
|
|
|
|
* if ==0 then either:
|
|
|
|
* a) we're not transmitting anything, so why are we here?
|
|
|
|
* b) we've died.
|
|
|
|
* otherwise, Stat_Busy(return) means we've still got some packets
|
|
|
|
* to transmit, Stat_Done(return) means our buffers should be empty
|
|
|
|
* again
|
|
|
|
*/
|
|
|
|
|
|
|
|
static unsigned short eexp_hw_lasttxstat(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short tx_block = lp->tx_reap;
|
|
|
|
unsigned short status;
|
|
|
|
|
|
|
|
if (!netif_queue_stopped(dev) && lp->tx_head==lp->tx_reap)
|
|
|
|
return 0x0000;
|
|
|
|
|
|
|
|
do
|
|
|
|
{
|
|
|
|
outw(tx_block & ~31, dev->base_addr + SM_PTR);
|
|
|
|
status = inw(dev->base_addr + SHADOW(tx_block));
|
|
|
|
if (!Stat_Done(status))
|
|
|
|
{
|
|
|
|
lp->tx_link = tx_block;
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lp->last_tx_restart = 0;
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.collisions += Stat_NoColl(status);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!Stat_OK(status))
|
|
|
|
{
|
|
|
|
char *whatsup = NULL;
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_errors++;
|
2006-09-14 01:24:59 +08:00
|
|
|
if (Stat_Abort(status))
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_aborted_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (Stat_TNoCar(status)) {
|
|
|
|
whatsup = "aborted, no carrier";
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_carrier_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if (Stat_TNoCTS(status)) {
|
|
|
|
whatsup = "aborted, lost CTS";
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_carrier_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if (Stat_TNoDMA(status)) {
|
|
|
|
whatsup = "FIFO underran";
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_fifo_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if (Stat_TXColl(status)) {
|
|
|
|
whatsup = "aborted, too many collisions";
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_aborted_errors++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if (whatsup)
|
|
|
|
printk(KERN_INFO "%s: transmit %s\n",
|
|
|
|
dev->name, whatsup);
|
|
|
|
}
|
|
|
|
else
|
2007-10-04 08:41:50 +08:00
|
|
|
dev->stats.tx_packets++;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if (tx_block == TX_BUF_START+((lp->num_tx_bufs-1)*TX_BUF_SIZE))
|
|
|
|
lp->tx_reap = tx_block = TX_BUF_START;
|
|
|
|
else
|
|
|
|
lp->tx_reap = tx_block += TX_BUF_SIZE;
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
}
|
|
|
|
while (lp->tx_reap != lp->tx_head);
|
|
|
|
|
|
|
|
lp->tx_link = lp->tx_tail + 0x08;
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This should never happen. It is called when some higher routine detects
|
|
|
|
* that the CU has stopped, to try to restart it from the last packet we knew
|
|
|
|
* we were working on, or the idle loop if we had finished for the time.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_txrestart(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
|
|
|
|
lp->last_tx_restart = lp->tx_link;
|
|
|
|
scb_wrcbl(dev, lp->tx_link);
|
|
|
|
scb_command(dev, SCB_CUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
|
|
|
|
{
|
|
|
|
unsigned short boguscount=50,failcount=5;
|
|
|
|
while (!scb_status(dev))
|
|
|
|
{
|
|
|
|
if (!--boguscount)
|
|
|
|
{
|
|
|
|
if (--failcount)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: CU start timed out, status %04x, cmd %04x\n", dev->name, scb_status(dev), scb_rdcmd(dev));
|
|
|
|
scb_wrcbl(dev, lp->tx_link);
|
|
|
|
scb_command(dev, SCB_CUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
boguscount = 100;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: Failed to restart CU, resetting board...\n",dev->name);
|
|
|
|
eexp_hw_init586(dev);
|
|
|
|
netif_wake_queue(dev);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Writes down the list of transmit buffers into card memory. Each
|
|
|
|
* entry consists of an 82586 transmit command, followed by a jump
|
|
|
|
* pointing to itself. When we want to transmit a packet, we write
|
|
|
|
* the data into the appropriate transmit buffer and then modify the
|
|
|
|
* preceding jump to point at the new transmit command. This means that
|
|
|
|
* the 586 command unit is continuously active.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_txinit(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short tx_block = TX_BUF_START;
|
|
|
|
unsigned short curtbuf;
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
|
|
|
|
for ( curtbuf=0 ; curtbuf<lp->num_tx_bufs ; curtbuf++ )
|
|
|
|
{
|
|
|
|
outw(tx_block, ioaddr + WRITE_PTR);
|
|
|
|
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
outw(Cmd_INT|Cmd_Xmit, ioaddr + DATAPORT);
|
|
|
|
outw(tx_block+0x08, ioaddr + DATAPORT);
|
|
|
|
outw(tx_block+0x0e, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
outw(tx_block+0x08, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
outw(0x8000, ioaddr + DATAPORT);
|
|
|
|
outw(-1, ioaddr + DATAPORT);
|
|
|
|
outw(tx_block+0x16, ioaddr + DATAPORT);
|
|
|
|
outw(0x0000, ioaddr + DATAPORT);
|
|
|
|
|
|
|
|
tx_block += TX_BUF_SIZE;
|
|
|
|
}
|
|
|
|
lp->tx_head = TX_BUF_START;
|
|
|
|
lp->tx_reap = TX_BUF_START;
|
|
|
|
lp->tx_tail = tx_block - TX_BUF_SIZE;
|
|
|
|
lp->tx_link = lp->tx_tail + 0x08;
|
|
|
|
lp->rx_buf_start = tx_block;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write the circular list of receive buffer descriptors to card memory.
|
|
|
|
* The end of the list isn't marked, which means that the 82586 receive
|
|
|
|
* unit will loop until buffers become available (this avoids it giving us
|
|
|
|
* "out of resources" messages).
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_rxinit(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short rx_block = lp->rx_buf_start;
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
|
|
|
|
lp->num_rx_bufs = 0;
|
|
|
|
lp->rx_first = lp->rx_ptr = rx_block;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
lp->num_rx_bufs++;
|
|
|
|
|
|
|
|
outw(rx_block, ioaddr + WRITE_PTR);
|
|
|
|
|
|
|
|
outw(0, ioaddr + DATAPORT); outw(0, ioaddr+DATAPORT);
|
|
|
|
outw(rx_block + RX_BUF_SIZE, ioaddr+DATAPORT);
|
|
|
|
outw(0xffff, ioaddr+DATAPORT);
|
|
|
|
|
|
|
|
outw(0x0000, ioaddr+DATAPORT);
|
|
|
|
outw(0xdead, ioaddr+DATAPORT);
|
|
|
|
outw(0xdead, ioaddr+DATAPORT);
|
|
|
|
outw(0xdead, ioaddr+DATAPORT);
|
|
|
|
outw(0xdead, ioaddr+DATAPORT);
|
|
|
|
outw(0xdead, ioaddr+DATAPORT);
|
|
|
|
outw(0xdead, ioaddr+DATAPORT);
|
|
|
|
|
|
|
|
outw(0x0000, ioaddr+DATAPORT);
|
|
|
|
outw(rx_block + RX_BUF_SIZE + 0x16, ioaddr+DATAPORT);
|
|
|
|
outw(rx_block + 0x20, ioaddr+DATAPORT);
|
|
|
|
outw(0, ioaddr+DATAPORT);
|
|
|
|
outw(RX_BUF_SIZE-0x20, ioaddr+DATAPORT);
|
|
|
|
|
|
|
|
lp->rx_last = rx_block;
|
|
|
|
rx_block += RX_BUF_SIZE;
|
|
|
|
} while (rx_block <= lp->rx_buf_end-RX_BUF_SIZE);
|
|
|
|
|
|
|
|
|
|
|
|
/* Make first Rx frame descriptor point to first Rx buffer
|
|
|
|
descriptor */
|
|
|
|
outw(lp->rx_first + 6, ioaddr+WRITE_PTR);
|
|
|
|
outw(lp->rx_first + 0x16, ioaddr+DATAPORT);
|
|
|
|
|
|
|
|
/* Close Rx frame descriptor ring */
|
|
|
|
outw(lp->rx_last + 4, ioaddr+WRITE_PTR);
|
|
|
|
outw(lp->rx_first, ioaddr+DATAPORT);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Close Rx buffer descriptor ring */
|
|
|
|
outw(lp->rx_last + 0x16 + 2, ioaddr+WRITE_PTR);
|
|
|
|
outw(lp->rx_first + 0x16, ioaddr+DATAPORT);
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Un-reset the 586, and start the configuration sequence. We don't wait for
|
|
|
|
* this to finish, but allow the interrupt handler to start the CU and RU for
|
|
|
|
* us. We can't start the receive/transmission system up before we know that
|
|
|
|
* the hardware is configured correctly.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void eexp_hw_init586(struct net_device *dev)
|
|
|
|
{
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
#if NET_DEBUG > 6
|
|
|
|
printk("%s: eexp_hw_init586()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
lp->started = 0;
|
|
|
|
|
|
|
|
set_loopback(dev);
|
|
|
|
|
|
|
|
outb(SIRQ_dis|irqrmap[dev->irq],ioaddr+SET_IRQ);
|
|
|
|
|
|
|
|
/* Download the startup code */
|
|
|
|
outw(lp->rx_buf_end & ~31, ioaddr + SM_PTR);
|
|
|
|
outw(lp->width?0x0001:0x0000, ioaddr + 0x8006);
|
|
|
|
outw(0x0000, ioaddr + 0x8008);
|
|
|
|
outw(0x0000, ioaddr + 0x800a);
|
|
|
|
outw(0x0000, ioaddr + 0x800c);
|
|
|
|
outw(0x0000, ioaddr + 0x800e);
|
|
|
|
|
|
|
|
for (i = 0; i < (sizeof(start_code)); i+=32) {
|
|
|
|
int j;
|
|
|
|
outw(i, ioaddr + SM_PTR);
|
|
|
|
for (j = 0; j < 16; j+=2)
|
|
|
|
outw(start_code[(i+j)/2],
|
|
|
|
ioaddr+0x4000+j);
|
|
|
|
for (j = 0; j < 16; j+=2)
|
|
|
|
outw(start_code[(i+j+16)/2],
|
|
|
|
ioaddr+0x8000+j);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Do we want promiscuous mode or multicast? */
|
|
|
|
outw(CONF_PROMISC & ~31, ioaddr+SM_PTR);
|
|
|
|
i = inw(ioaddr+SHADOW(CONF_PROMISC));
|
2006-09-14 01:24:59 +08:00
|
|
|
outw((dev->flags & IFF_PROMISC)?(i|1):(i & ~1),
|
2005-04-17 06:20:36 +08:00
|
|
|
ioaddr+SHADOW(CONF_PROMISC));
|
|
|
|
lp->was_promisc = dev->flags & IFF_PROMISC;
|
|
|
|
#if 0
|
|
|
|
eexp_setup_filter(dev);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Write our hardware address */
|
|
|
|
outw(CONF_HWADDR & ~31, ioaddr+SM_PTR);
|
|
|
|
outw(((unsigned short *)dev->dev_addr)[0], ioaddr+SHADOW(CONF_HWADDR));
|
2006-09-14 01:24:59 +08:00
|
|
|
outw(((unsigned short *)dev->dev_addr)[1],
|
2005-04-17 06:20:36 +08:00
|
|
|
ioaddr+SHADOW(CONF_HWADDR+2));
|
|
|
|
outw(((unsigned short *)dev->dev_addr)[2],
|
|
|
|
ioaddr+SHADOW(CONF_HWADDR+4));
|
|
|
|
|
|
|
|
eexp_hw_txinit(dev);
|
|
|
|
eexp_hw_rxinit(dev);
|
|
|
|
|
|
|
|
outb(0,ioaddr+EEPROM_Ctrl);
|
|
|
|
mdelay(5);
|
|
|
|
|
|
|
|
scb_command(dev, 0xf000);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
|
|
|
|
outw(0, ioaddr+SM_PTR);
|
|
|
|
|
|
|
|
{
|
|
|
|
unsigned short rboguscount=50,rfailcount=5;
|
|
|
|
while (inw(ioaddr+0x4000))
|
|
|
|
{
|
|
|
|
if (!--rboguscount)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: i82586 reset timed out, kicking...\n",
|
|
|
|
dev->name);
|
|
|
|
scb_command(dev, 0);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
rboguscount = 100;
|
|
|
|
if (!--rfailcount)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: i82586 not responding, giving up.\n",
|
|
|
|
dev->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
scb_wrcbl(dev, CONF_LINK);
|
|
|
|
scb_command(dev, 0xf000|SCB_CUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
|
|
|
|
{
|
|
|
|
unsigned short iboguscount=50,ifailcount=5;
|
|
|
|
while (!scb_status(dev))
|
|
|
|
{
|
|
|
|
if (!--iboguscount)
|
|
|
|
{
|
|
|
|
if (--ifailcount)
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: i82586 initialization timed out, status %04x, cmd %04x\n",
|
|
|
|
dev->name, scb_status(dev), scb_rdcmd(dev));
|
|
|
|
scb_wrcbl(dev, CONF_LINK);
|
|
|
|
scb_command(dev, 0xf000|SCB_CUstart);
|
|
|
|
outb(0,ioaddr+SIGNAL_CA);
|
|
|
|
iboguscount = 100;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
printk(KERN_WARNING "%s: Failed to initialize i82586, giving up.\n",dev->name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
clear_loopback(dev);
|
|
|
|
outb(SIRQ_en|irqrmap[dev->irq],ioaddr+SET_IRQ);
|
|
|
|
|
|
|
|
lp->init_time = jiffies;
|
|
|
|
#if NET_DEBUG > 6
|
|
|
|
printk("%s: leaving eexp_hw_init586()\n", dev->name);
|
|
|
|
#endif
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void eexp_setup_filter(struct net_device *dev)
|
|
|
|
{
|
2008-05-03 04:40:53 +08:00
|
|
|
struct dev_mc_list *dmi;
|
2005-04-17 06:20:36 +08:00
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
int count = dev->mc_count;
|
|
|
|
int i;
|
|
|
|
if (count > 8) {
|
|
|
|
printk(KERN_INFO "%s: too many multicast addresses (%d)\n",
|
|
|
|
dev->name, count);
|
|
|
|
count = 8;
|
|
|
|
}
|
2006-09-14 01:24:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
outw(CONF_NR_MULTICAST & ~31, ioaddr+SM_PTR);
|
2008-05-03 04:40:53 +08:00
|
|
|
outw(6*count, ioaddr+SHADOW(CONF_NR_MULTICAST));
|
|
|
|
for (i = 0, dmi = dev->mc_list; i < count; i++, dmi = dmi->next) {
|
|
|
|
unsigned short *data;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!dmi) {
|
|
|
|
printk(KERN_INFO "%s: too few multicast addresses\n", dev->name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (dmi->dmi_addrlen != ETH_ALEN) {
|
|
|
|
printk(KERN_INFO "%s: invalid multicast address length given.\n", dev->name);
|
|
|
|
continue;
|
|
|
|
}
|
2008-05-03 04:40:53 +08:00
|
|
|
data = (unsigned short *)dmi->dmi_addr;
|
2005-04-17 06:20:36 +08:00
|
|
|
outw((CONF_MULTICAST+(6*i)) & ~31, ioaddr+SM_PTR);
|
|
|
|
outw(data[0], ioaddr+SHADOW(CONF_MULTICAST+(6*i)));
|
|
|
|
outw((CONF_MULTICAST+(6*i)+2) & ~31, ioaddr+SM_PTR);
|
|
|
|
outw(data[1], ioaddr+SHADOW(CONF_MULTICAST+(6*i)+2));
|
|
|
|
outw((CONF_MULTICAST+(6*i)+4) & ~31, ioaddr+SM_PTR);
|
|
|
|
outw(data[2], ioaddr+SHADOW(CONF_MULTICAST+(6*i)+4));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Set or clear the multicast filter for this adaptor.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
eexp_set_multicast(struct net_device *dev)
|
|
|
|
{
|
|
|
|
unsigned short ioaddr = dev->base_addr;
|
|
|
|
struct net_local *lp = netdev_priv(dev);
|
|
|
|
int kick = 0, i;
|
|
|
|
if ((dev->flags & IFF_PROMISC) != lp->was_promisc) {
|
|
|
|
outw(CONF_PROMISC & ~31, ioaddr+SM_PTR);
|
|
|
|
i = inw(ioaddr+SHADOW(CONF_PROMISC));
|
|
|
|
outw((dev->flags & IFF_PROMISC)?(i|1):(i & ~1),
|
|
|
|
ioaddr+SHADOW(CONF_PROMISC));
|
|
|
|
lp->was_promisc = dev->flags & IFF_PROMISC;
|
|
|
|
kick = 1;
|
|
|
|
}
|
|
|
|
if (!(dev->flags & IFF_PROMISC)) {
|
|
|
|
eexp_setup_filter(dev);
|
|
|
|
if (lp->old_mc_count != dev->mc_count) {
|
|
|
|
kick = 1;
|
|
|
|
lp->old_mc_count = dev->mc_count;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (kick) {
|
|
|
|
unsigned long oj;
|
|
|
|
scb_command(dev, SCB_CUsuspend);
|
|
|
|
outb(0, ioaddr+SIGNAL_CA);
|
|
|
|
outb(0, ioaddr+SIGNAL_CA);
|
|
|
|
#if 0
|
|
|
|
printk("%s: waiting for CU to go suspended\n", dev->name);
|
|
|
|
#endif
|
|
|
|
oj = jiffies;
|
|
|
|
while ((SCB_CUstat(scb_status(dev)) == 2) &&
|
2007-04-28 23:05:43 +08:00
|
|
|
(time_before(jiffies, oj + 2000)));
|
2005-04-17 06:20:36 +08:00
|
|
|
if (SCB_CUstat(scb_status(dev)) == 2)
|
|
|
|
printk("%s: warning, CU didn't stop\n", dev->name);
|
|
|
|
lp->started &= ~(STARTED_CU);
|
|
|
|
scb_wrcbl(dev, CONF_LINK);
|
|
|
|
scb_command(dev, SCB_CUstart);
|
|
|
|
outb(0, ioaddr+SIGNAL_CA);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MODULE stuff
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifdef MODULE
|
|
|
|
|
|
|
|
#define EEXP_MAX_CARDS 4 /* max number of cards to support */
|
|
|
|
|
|
|
|
static struct net_device *dev_eexp[EEXP_MAX_CARDS];
|
|
|
|
static int irq[EEXP_MAX_CARDS];
|
|
|
|
static int io[EEXP_MAX_CARDS];
|
|
|
|
|
|
|
|
module_param_array(io, int, NULL, 0);
|
|
|
|
module_param_array(irq, int, NULL, 0);
|
|
|
|
MODULE_PARM_DESC(io, "EtherExpress 16 I/O base address(es)");
|
|
|
|
MODULE_PARM_DESC(irq, "EtherExpress 16 IRQ number(s)");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
|
|
|
|
|
|
/* Ideally the user would give us io=, irq= for every card. If any parameters
|
|
|
|
* are specified, we verify and then use them. If no parameters are given, we
|
|
|
|
* autoprobe for one card only.
|
|
|
|
*/
|
2006-08-15 14:00:01 +08:00
|
|
|
int __init init_module(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
struct net_device *dev;
|
|
|
|
int this_dev, found = 0;
|
|
|
|
|
|
|
|
for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) {
|
|
|
|
dev = alloc_etherdev(sizeof(struct net_local));
|
|
|
|
dev->irq = irq[this_dev];
|
|
|
|
dev->base_addr = io[this_dev];
|
|
|
|
if (io[this_dev] == 0) {
|
|
|
|
if (this_dev)
|
|
|
|
break;
|
|
|
|
printk(KERN_NOTICE "eexpress.c: Module autoprobe not recommended, give io=xx.\n");
|
|
|
|
}
|
2005-05-13 08:11:55 +08:00
|
|
|
if (do_express_probe(dev) == 0) {
|
2005-04-17 06:20:36 +08:00
|
|
|
dev_eexp[this_dev] = dev;
|
|
|
|
found++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
printk(KERN_WARNING "eexpress.c: Failed to register card at 0x%x.\n", io[this_dev]);
|
|
|
|
free_netdev(dev);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (found)
|
|
|
|
return 0;
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
2006-06-15 06:50:53 +08:00
|
|
|
void __exit cleanup_module(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
int this_dev;
|
|
|
|
|
|
|
|
for (this_dev = 0; this_dev < EEXP_MAX_CARDS; this_dev++) {
|
|
|
|
struct net_device *dev = dev_eexp[this_dev];
|
|
|
|
if (dev) {
|
|
|
|
unregister_netdev(dev);
|
|
|
|
free_netdev(dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Local Variables:
|
|
|
|
* c-file-style: "linux"
|
|
|
|
* tab-width: 8
|
|
|
|
* End:
|
|
|
|
*/
|