diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1995-11-14 08:00:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1995-11-14 08:00:00 +0000 |
commit | e7c2a72e2680827d6a733931273a93461c0d8d1b (patch) | |
tree | c9abeda78ef7504062bb2e816bcf3e3c9d680112 /drivers/net/de4x5.c | |
parent | ec6044459060a8c9ce7f64405c465d141898548c (diff) |
Import of Linux/MIPS 1.3.0
Diffstat (limited to 'drivers/net/de4x5.c')
-rw-r--r-- | drivers/net/de4x5.c | 2513 |
1 files changed, 2513 insertions, 0 deletions
diff --git a/drivers/net/de4x5.c b/drivers/net/de4x5.c new file mode 100644 index 000000000..6abd17d5e --- /dev/null +++ b/drivers/net/de4x5.c @@ -0,0 +1,2513 @@ +/* de4x5.c: A DIGITAL DE425/DE434/DE435 ethernet driver for linux. + + Copyright 1994, 1995 Digital Equipment Corporation. + + This software may be used and distributed according to the terms of + the GNU Public License, incorporated herein by reference. + + This driver is written for the Digital Equipment Corporation series + of EtherWORKS ethernet cards: + + DE425 TP/COAX EISA + DE434 TP PCI + DE435 TP/COAX/AUI PCI + DE500 10/100 PCI Fasternet + + The driver has been tested on a relatively busy network using the DE425, + DE434, DE435 and DE500 cards and benchmarked with 'ttcp': it transferred + 16M of data to a DECstation 5000/200 as follows: + + TCP UDP + TX RX TX RX + DE425 1030k 997k 1170k 1128k + DE434 1063k 995k 1170k 1125k + DE435 1063k 995k 1170k 1125k + DE500 1063k 998k 1170k 1125k in 10Mb/s mode + + All values are typical (in kBytes/sec) from a sample of 4 for each + measurement. Their error is +/-20k on a quiet (private) network and also + depend on what load the CPU has. + + The author may be reached as davies@wanton.lkg.dec.com or Digital + Equipment Corporation, 550 King Street, Littleton MA 01460. + + ========================================================================= + This driver has been written substantially from scratch, although its + inheritance of style and stack interface from 'ewrk3.c' and in turn from + Donald Becker's 'lance.c' should be obvious. + + Upto 15 EISA cards can be supported under this driver, limited primarily + by the available IRQ lines. I have checked different configurations of + multiple depca, EtherWORKS 3 cards and de4x5 cards and have not found a + problem yet (provided you have at least depca.c v0.38) ... + + PCI support has been added to allow the driver to work with the DE434 + and DE435 cards. The I/O accesses are a bit of a kludge due to the + differences in the EISA and PCI CSR address offsets from the base + address. + + The ability to load this driver as a loadable module has been included + and used extensively during the driver development (to save those long + reboot sequences). Loadable module support under PCI has been achieved + by letting any I/O address less than 0x1000 be assigned as: + + 0xghh + + where g is the bus number (usually 0 until the BIOS's get fixed) + hh is the device number (max is 32 per bus). + + Essentially, the I/O address and IRQ information are ignored and filled + in later by the PCI BIOS during the PCI probe. Note that the board + should be in the system at boot time so that its I/O address and IRQ are + allocated by the PCI BIOS automatically. The special case of device 0 on + bus 0 is not allowed as the probe will think you're autoprobing a + module. + + To utilise this ability, you have to do 8 things: + + 0) have a copy of the loadable modules code installed on your system. + 1) copy de4x5.c from the /linux/drivers/net directory to your favourite + temporary directory. + 2) edit the source code near line 1945 to reflect the I/O address and + IRQ you're using, or assign these when loading by: + + insmod de4x5.o irq=x io=y + + 3) compile de4x5.c, but include -DMODULE in the command line to ensure + that the correct bits are compiled (see end of source code). + 4) if you are wanting to add a new card, goto 5. Otherwise, recompile a + kernel with the de4x5 configuration turned off and reboot. + 5) insmod de4x5.o + 6) run the net startup bits for your new eth?? interface manually + (usually /etc/rc.inet[12] at boot time). + 7) enjoy! + + Note that autoprobing is not allowed in loadable modules - the system is + already up and running and you're messing with interrupts. + + To unload a module, turn off the associated interface + 'ifconfig eth?? down' then 'rmmod de4x5'. + + Automedia detection is included so that in principal you can disconnect + from, e.g. TP, reconnect to BNC and things will still work (after a + pause whilst the driver figures out where its media went). My tests + using ping showed that it appears to work.... + + A compile time switch to allow Zynx recognition has been added. This + "feature" is in no way supported nor tested in this driver and the user + may use it at his/her sole discretion. I have had 2 conflicting reports + that my driver will or won't work with Zynx. Try Donald Becker's + 'tulip.c' if this driver doesn't work for you. I will not be supporting + Zynx cards since I have no information on them and can't test them in a + system. + + TO DO: + ------ + + + Revision History + ---------------- + + Version Date Description + + 0.1 17-Nov-94 Initial writing. ALPHA code release. + 0.2 13-Jan-95 Added PCI support for DE435's. + 0.21 19-Jan-95 Added auto media detection. + 0.22 10-Feb-95 Fix interrupt handler call <chris@cosy.sbg.ac.at>. + Fix recognition bug reported by <bkm@star.rl.ac.uk>. + Add request/release_region code. + Add loadable modules support for PCI. + Clean up loadable modules support. + 0.23 28-Feb-95 Added DC21041 and DC21140 support. + Fix missed frame counter value and initialisation. + Fixed EISA probe. + 0.24 11-Apr-95 Change delay routine to use <linux/udelay>. + Change TX_BUFFS_AVAIL macro. + Change media autodetection to allow manual setting. + Completed DE500 (DC21140) support. + 0.241 18-Apr-95 Interim release without DE500 Autosense Algorithm. + + ========================================================================= +*/ + +static char *version = "de4x5.c:v0.241 4/18/95 davies@wanton.lkg.dec.com\n"; + +#include <linux/config.h> +#ifdef MODULE +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif /* MODULE */ + +#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/ioport.h> +#include <linux/malloc.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <asm/bitops.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/segment.h> + +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> + +#include <linux/time.h> +#include <linux/types.h> +#include <linux/unistd.h> + +#include "de4x5.h" + +#ifdef DE4X5_DEBUG +static int de4x5_debug = DE4X5_DEBUG; +#else +static int de4x5_debug = 1; +#endif + +#ifdef DE4X5_AUTOSENSE /* Should be done on a per adapter basis */ +static int de4x5_autosense = DE4X5_AUTOSENSE; +#else +static int de4x5_autosense = AUTO; /* Do auto media/mode sensing */ +#endif + +/* +** Ethernet PROM defines +*/ +#define PROBE_LENGTH 32 +#define ETH_PROM_SIG 0xAA5500FFUL + +/* +** Ethernet Info +*/ +#define PKT_BUF_SZ 1544 /* Buffer size for each Tx/Rx buffer */ +#define MAX_PKT_SZ 1514 /* Maximum ethernet packet length */ +#define MAX_DAT_SZ 1500 /* Maximum ethernet data length */ +#define MIN_DAT_SZ 1 /* Minimum ethernet data length */ +#define PKT_HDR_LEN 14 /* Addresses and data length info */ + +#define CRC_POLYNOMIAL_BE 0x04c11db7UL /* Ethernet CRC, big endian */ +#define CRC_POLYNOMIAL_LE 0xedb88320UL /* Ethernet CRC, little endian */ + +/* +** EISA bus defines +*/ +#define DE4X5_EISA_IO_PORTS 0x0c00 /* I/O port base address, slot 0 */ +#define DE4X5_EISA_TOTAL_SIZE 0xfff /* I/O address extent */ + +#define MAX_EISA_SLOTS 16 +#define EISA_SLOT_INC 0x1000 + +#define DE4X5_SIGNATURE {"DE425",""} +#define DE4X5_NAME_LENGTH 8 + +/* +** PCI Bus defines +*/ +#define PCI_MAX_BUS_NUM 8 +#define DE4X5_PCI_TOTAL_SIZE 0x80 /* I/O address extent */ + +/* +** Memory Alignment. Each descriptor is 4 longwords long. To force a +** particular alignment on the TX descriptor, adjust DESC_SKIP_LEN and +** DESC_ALIGN. ALIGN aligns the start address of the private memory area +** and hence the RX descriptor ring's first entry. +*/ +#define ALIGN4 ((u_long)4 - 1) /* 1 longword align */ +#define ALIGN8 ((u_long)8 - 1) /* 2 longword align */ +#define ALIGN16 ((u_long)16 - 1) /* 4 longword align */ +#define ALIGN32 ((u_long)32 - 1) /* 8 longword align */ +#define ALIGN64 ((u_long)64 - 1) /* 16 longword align */ +#define ALIGN128 ((u_long)128 - 1) /* 32 longword align */ + +#define ALIGN ALIGN32 /* Keep the DC21040 happy... */ +#define CACHE_ALIGN CAL_16LONG +#define DESC_SKIP_LEN DSL_0 /* Must agree with DESC_ALIGN */ +/*#define DESC_ALIGN u_long dummy[4]; / * Must agree with DESC_SKIP_LEN */ +#define DESC_ALIGN + +#ifndef IS_NOT_DEC /* See README.de4x5 for using this */ +static int is_not_dec = 0; +#else +static int is_not_dec = 1; +#endif + +/* +** DE4X5 IRQ ENABLE/DISABLE +*/ +static u_long irq_mask = IMR_SEM | IMR_RIM | IMR_RUM | IMR_TIM | IMR_TUM ; + +static u_long irq_en = IMR_NIM | IMR_AIM; + +#define ENABLE_IRQs { \ + imr |= irq_en;\ + outl(imr, DE4X5_IMR); /* Enable the IRQs */\ +} + +#define DISABLE_IRQs {\ + imr = inl(DE4X5_IMR);\ + imr &= ~irq_en;\ + outl(imr, DE4X5_IMR); /* Disable the IRQs */\ +} + +#define UNMASK_IRQs {\ + imr |= irq_mask;\ + outl(imr, DE4X5_IMR); /* Unmask the IRQs */\ +} + +#define MASK_IRQs {\ + imr = inl(DE4X5_IMR);\ + imr &= ~irq_mask;\ + outl(imr, DE4X5_IMR); /* Mask the IRQs */\ +} + +/* +** DE4X5 START/STOP +*/ +#define START_DE4X5 {\ + omr = inl(DE4X5_OMR);\ + omr |= OMR_ST | OMR_SR;\ + outl(omr, DE4X5_OMR); /* Enable the TX and/or RX */\ +} + +#define STOP_DE4X5 {\ + omr = inl(DE4X5_OMR);\ + omr &= ~(OMR_ST|OMR_SR);\ + outl(omr, DE4X5_OMR); /* Disable the TX and/or RX */ \ +} + +/* +** DE4X5 SIA RESET +*/ +#define RESET_SIA outl(0, DE4X5_SICR); /* Reset SIA connectivity regs */ + +/* +** SROM Structure +*/ +struct de4x5_srom { + char reserved[18]; + char version; + char num_adapters; + char ieee_addr[6]; + char info[100]; + short chksum; +}; + +/* +** DE4X5 Descriptors. Make sure that all the RX buffers are contiguous +** and have sizes of both a power of 2 and a multiple of 4. +** A size of 256 bytes for each buffer was chosen because over 90% of +** all packets in our network are <256 bytes long and 64 longword alignment +** is possible. +*/ +#define NUM_RX_DESC 8 /* Number of RX descriptors */ +#define NUM_TX_DESC 8 /* Number of TX descriptors */ +#define BUFF_ALLOC_RETRIES 10 /* In case of memory shortage */ +#define RX_BUFF_SZ 1536 /* Power of 2 for kmalloc and */ + /* Multiple of 4 for DC21040 */ + +struct de4x5_desc { + volatile long status; + u_long des1; + char *buf; + char *next; + DESC_ALIGN +}; + +/* +** The DE4X5 private structure +*/ +#define DE4X5_PKT_STAT_SZ 16 +#define DE4X5_PKT_BIN_SZ 128 /* Should be >=100 unless you + increase DE4X5_PKT_STAT_SZ */ + +struct de4x5_private { + char adapter_name[80]; /* Adapter name */ + struct de4x5_desc rx_ring[NUM_RX_DESC]; /* RX descriptor ring */ + struct de4x5_desc tx_ring[NUM_TX_DESC]; /* TX descriptor ring */ + struct sk_buff *skb[NUM_TX_DESC]; /* TX skb for freeing when sent */ + int rx_new, rx_old; /* RX descriptor ring pointers */ + int tx_new, tx_old; /* TX descriptor ring pointers */ + char setup_frame[SETUP_FRAME_LEN]; /* Holds MCA and PA info. */ + struct enet_statistics stats; /* Public stats */ + struct { + unsigned long bins[DE4X5_PKT_STAT_SZ]; /* Private stats counters */ + unsigned long unicast; + unsigned long multicast; + unsigned long broadcast; + unsigned long excessive_collisions; + unsigned long tx_underruns; + unsigned long excessive_underruns; + } pktStats; + char rxRingSize; + char txRingSize; + char bus; /* EISA or PCI */ + u_short chipset; /* DC21040, DC21041 or DC21140 */ + long media; /* Media (eg TP), mode (eg 100B)*/ + int autosense; /* Allow/disallow autosensing */ + int tx_enable; /* Enable descriptor polling */ + char lostMedia; /* Possibly lost media */ + int setup_f; /* Setup frame filtering type */ +}; + + +/* +** The transmit ring full condition is described by the tx_old and tx_new +** pointers by: +** tx_old = tx_new Empty ring +** tx_old = tx_new+1 Full ring +** tx_old+txRingSize = tx_new+1 Full ring (wrapped condition) +*/ +#define TX_BUFFS_AVAIL ((lp->tx_old<=lp->tx_new)?\ + lp->tx_old+lp->txRingSize-lp->tx_new-1:\ + lp->tx_old -lp->tx_new-1) +/*#define TX_BUFFS_AVAIL ((lp->tx_ring[lp->tx_new].status)>=0 ? 1 : 0)*/ + +/* +** Public Functions +*/ +static int de4x5_open(struct device *dev); +static int de4x5_queue_pkt(struct sk_buff *skb, struct device *dev); +static void de4x5_interrupt(int irq, struct pt_regs *regs); +static int de4x5_close(struct device *dev); +static struct enet_statistics *de4x5_get_stats(struct device *dev); +static void set_multicast_list(struct device *dev, int num_addrs, void *addrs); +static int de4x5_ioctl(struct device *dev, struct ifreq *rq, int cmd); + +/* +** Private functions +*/ +static int de4x5_hw_init(struct device *dev, short iobase); +static int de4x5_init(struct device *dev); +static int de4x5_rx(struct device *dev); +static int de4x5_tx(struct device *dev); + +static int autoconf_media(struct device *dev); +static void create_packet(struct device *dev, char *frame, int len); +static void dce_us_delay(u_long usec); +static void dce_ms_delay(u_long msec); +static void load_packet(struct device *dev, char *buf, u_long flags, struct sk_buff *skb); +static void dc21040_autoconf(struct device *dev); +static void dc21041_autoconf(struct device *dev); +static void dc21140_autoconf(struct device *dev); +static long test_media(struct device *dev, long irqs, long irq_mask, long csr13, long csr14, long csr15, long msec); +static long ping_media(struct device *dev); +static void reset_init_sia(struct device *dev, long sicr, long strr, long sigr); +static int test_ans(struct device *dev, long irqs, long irq_mask, long msec); +static void load_ms_timer(struct device *dev, u_long msec); +static int EISA_signature(char *name, long eisa_id); +static int DevicePresent(short iobase); +static short srom_rd(u_short address, u_char offset); +static void srom_latch(u_long command, u_short address); +static void srom_command(u_long command, u_short address); +static void srom_address(u_long command, u_short address, u_char offset); +static short srom_data(u_long command, u_short address); +/*static void srom_busy(u_long command, u_short address);*/ +static void sendto_srom(u_long command, u_short addr); +static long getfrom_srom(u_short addr); +static void SetMulticastFilter(struct device *dev, int num_addrs, char *addrs); +static int aprom_crc (struct device *dev); + +static void eisa_probe(struct device *dev, short iobase); +static void pci_probe(struct device *dev, short iobase); +static struct device *alloc_device(struct device *dev, int iobase); +static char *build_setup_frame(struct device *dev, int mode); + +#ifdef MODULE +int init_module(void); +void cleanup_module(void); +static int autoprobed = 1, loading_module = 1; +# else +static unsigned char de4x5_irq[] = {5,9,10,11}; +static int autoprobed = 0, loading_module = 0; +#endif /* MODULE */ + +static char name[DE4X5_NAME_LENGTH + 1]; +static int num_de4x5s = 0, num_eth = 0; + +/* +** Kludge to get around the fact that the CSR addresses have different +** offsets in the PCI and EISA boards. Also note that the ethernet address +** PROM is accessed differently. +*/ +static struct bus_type { + int bus; + int device; + int chipset; + struct de4x5_srom srom; + int autosense; +} bus; + +/* +** Miscellaneous defines... +*/ +#define RESET_DE4X5 {\ + long i;\ + i=inl(DE4X5_BMR);\ + outl(i | BMR_SWR, DE4X5_BMR);\ + outl(i, DE4X5_BMR);\ + for (i=0;i<5;i++) inl(DE4X5_BMR);\ +} + + + +int de4x5_probe(struct device *dev) +{ + int tmp = num_de4x5s, iobase = dev->base_addr; + int status = -ENODEV; + + if ((iobase == 0) && loading_module){ + printk("Autoprobing is not supported when loading a module based driver.\n"); + status = -EIO; + } else { /* First probe for the Ethernet */ + /* Address PROM pattern */ + eisa_probe(dev, iobase); + pci_probe(dev, iobase); + + if ((tmp == num_de4x5s) && (iobase != 0)) { + printk("%s: de4x5_probe() cannot find device at 0x%04x.\n", dev->name, + iobase); + } + + /* + ** Walk the device list to check that at least one device + ** initialised OK + */ + for (; (dev->priv == NULL) && (dev->next != NULL); dev = dev->next); + + if (dev->priv) status = 0; + if (iobase == 0) autoprobed = 1; + } + + return status; +} + +static int +de4x5_hw_init(struct device *dev, short iobase) +{ + struct bus_type *lp = &bus; + int tmpbus, tmpchs, i, j, status=0; + char *tmp; + u_long nicsr; + + /* Ensure we're not sleeping */ + if (lp->chipset == DC21041) { + outl(0, PCI_CFDA); + dce_ms_delay(10); + } + + RESET_DE4X5; + + if (((nicsr=inl(DE4X5_STS)) & (STS_TS | STS_RS)) == 0) { + /* + ** Now find out what kind of DC21040/DC21041/DC21140 board we have. + */ + if (lp->bus == PCI) { + if (!is_not_dec) { + if ((lp->chipset == DC21040) || (lp->chipset == DC21041)) { + strcpy(name, "DE435"); + } else if (lp->chipset == DC21140) { + strcpy(name, "DE500"); /* Must read the SROM here! */ + } + } else { + strcpy(name, "UNKNOWN"); + } + } else { + EISA_signature(name, EISA_ID0); + } + + if (*name != '\0') { /* found a board signature */ + dev->base_addr = iobase; + + if (lp->bus == EISA) { + printk("%s: %s at %#3x (EISA slot %d)", + dev->name, name, (u_short)iobase, (((u_short)iobase>>12)&0x0f)); + } else { /* PCI port address */ + printk("%s: %s at %#3x (PCI device %d)", dev->name, name, (u_short)iobase,lp->device); + } + + printk(", h/w address "); + status = aprom_crc(dev); + for (i = 0; i < ETH_ALEN - 1; i++) { /* get the ethernet addr. */ + printk("%2.2x:", dev->dev_addr[i]); + } + printk("%2.2x,\n", dev->dev_addr[i]); + + tmpbus = lp->bus; + tmpchs = lp->chipset; + + if (status == 0) { + struct de4x5_private *lp; + + /* + ** Reserve a section of kernel memory for the adapter + ** private area and the TX/RX descriptor rings. + */ + dev->priv = (void *) kmalloc(sizeof(struct de4x5_private) + ALIGN, + GFP_KERNEL); + /* + ** Align to a longword boundary + */ + dev->priv = (void *)(((u_long)dev->priv + ALIGN) & ~ALIGN); + lp = (struct de4x5_private *)dev->priv; + memset(dev->priv, 0, sizeof(struct de4x5_private)); + lp->bus = tmpbus; + lp->chipset = tmpchs; + + /* + ** Choose autosensing + */ + if (de4x5_autosense & AUTO) { + lp->autosense = AUTO; + } else { + if (lp->chipset != DC21140) { + if ((lp->chipset == DC21040) && (de4x5_autosense & TP_NW)) { + de4x5_autosense = TP; + } + if ((lp->chipset == DC21041) && (de4x5_autosense & BNC_AUI)) { + de4x5_autosense = BNC; + } + lp->autosense = de4x5_autosense & 0x001f; + } else { + lp->autosense = de4x5_autosense & 0x00c0; + } + } + + sprintf(lp->adapter_name,"%s (%s)", name, dev->name); + request_region(iobase, (lp->bus == PCI ? DE4X5_PCI_TOTAL_SIZE : + DE4X5_EISA_TOTAL_SIZE), + lp->adapter_name); + + /* + ** Allocate contiguous receive buffers, long word aligned. + ** This could be a possible memory leak if the private area + ** is ever hosed. + */ + for (tmp=NULL, j=0; (j<BUFF_ALLOC_RETRIES) && (tmp==NULL); j++) { + if ((tmp = (void *)kmalloc(RX_BUFF_SZ * NUM_RX_DESC + ALIGN, + GFP_KERNEL)) != NULL) { + tmp = (char *)(((u_long) tmp + ALIGN) & ~ALIGN); + for (i=0; i<NUM_RX_DESC; i++) { + lp->rx_ring[i].status = 0; + lp->rx_ring[i].des1 = RX_BUFF_SZ; + lp->rx_ring[i].buf = tmp + i * RX_BUFF_SZ; + lp->rx_ring[i].next = NULL; + } + } + } + + if (tmp != NULL) { + lp->rxRingSize = NUM_RX_DESC; + lp->txRingSize = NUM_TX_DESC; + + /* Write the end of list marker to the descriptor lists */ + lp->rx_ring[lp->rxRingSize - 1].des1 |= RD_RER; + lp->tx_ring[lp->txRingSize - 1].des1 |= TD_TER; + + /* Tell the adapter where the TX/RX rings are located. */ + outl((long)lp->rx_ring, DE4X5_RRBA); + outl((long)lp->tx_ring, DE4X5_TRBA); + + lp->tx_enable = TRUE; + + if (dev->irq < 2) { +#ifndef MODULE + unsigned char irqnum; + u_long omr; + autoirq_setup(0); + + omr = inl(DE4X5_OMR); + outl(IMR_AIM|IMR_RUM, DE4X5_IMR); /* Unmask RUM interrupt */ + outl(OMR_SR | omr, DE4X5_OMR); /* Start RX w/no descriptors */ + + irqnum = autoirq_report(1); + if (!irqnum) { + printk(" and failed to detect IRQ line.\n"); + status = -ENXIO; + } else { + for (dev->irq=0,i=0; (i<sizeof(de4x5_irq)) && (!dev->irq); i++) { + if (irqnum == de4x5_irq[i]) { + dev->irq = irqnum; + printk(" and uses IRQ%d.\n", dev->irq); + } + } + + if (!dev->irq) { + printk(" but incorrect IRQ line detected.\n"); + status = -ENXIO; + } + } + + outl(0, DE4X5_IMR); /* Re-mask RUM interrupt */ + +#endif /* MODULE */ + } else { + printk(" and requires IRQ%d (not probed).\n", dev->irq); + } + } else { + printk("%s: Kernel could not allocate RX buffer memory.\n", + dev->name); + status = -ENXIO; + } + if (status) release_region(iobase, (lp->bus == PCI ? + DE4X5_PCI_TOTAL_SIZE : + DE4X5_EISA_TOTAL_SIZE)); + } else { + printk(" which has an Ethernet PROM CRC error.\n"); + status = -ENXIO; + } + } else { + status = -ENXIO; + } + } else { + status = -ENXIO; + } + + if (!status) { + if (de4x5_debug > 0) { + printk(version); + } + + /* The DE4X5-specific entries in the device structure. */ + dev->open = &de4x5_open; + dev->hard_start_xmit = &de4x5_queue_pkt; + dev->stop = &de4x5_close; + dev->get_stats = &de4x5_get_stats; +#ifdef HAVE_MULTICAST + dev->set_multicast_list = &set_multicast_list; +#endif + dev->do_ioctl = &de4x5_ioctl; + + dev->mem_start = 0; + + /* Fill in the generic field of the device structure. */ + ether_setup(dev); + + /* Let the adapter sleep to save power */ + if (lp->chipset == DC21041) { + outl(0, DE4X5_SICR); + outl(CFDA_PSM, PCI_CFDA); + } + } else { /* Incorrectly initialised hardware */ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + if (lp) { + kfree_s(lp->rx_ring[0].buf, RX_BUFF_SZ * NUM_RX_DESC + ALIGN); + } + if (dev->priv) { + kfree_s(dev->priv, sizeof(struct de4x5_private) + ALIGN); + dev->priv = NULL; + } + } + + return status; +} + + +static int +de4x5_open(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + short iobase = dev->base_addr; + int i, status = 0; + long imr, omr, sts; + + /* + ** Wake up the adapter + */ + if (lp->chipset == DC21041) { + outl(0, PCI_CFDA); + dce_ms_delay(10); + } + + if (request_irq(dev->irq, (void *)de4x5_interrupt, 0, lp->adapter_name)) { + printk("de4x5_open(): Requested IRQ%d is busy\n",dev->irq); + status = -EAGAIN; + } else { + + irq2dev_map[dev->irq] = dev; + /* + ** Re-initialize the DE4X5... + */ + status = de4x5_init(dev); + + if (de4x5_debug > 1){ + printk("%s: de4x5 open with irq %d\n",dev->name,dev->irq); + printk("\tphysical address: "); + for (i=0;i<6;i++){ + printk("%2.2x:",(short)dev->dev_addr[i]); + } + printk("\n"); + printk("Descriptor head addresses:\n"); + printk("\t0x%8.8lx 0x%8.8lx\n",(long)lp->rx_ring,(long)lp->tx_ring); + printk("Descriptor addresses:\nRX: "); + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8lx ",(long)&lp->rx_ring[i].status); + } + } + printk("...0x%8.8lx\n",(long)&lp->rx_ring[i].status); + printk("TX: "); + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8lx ", (long)&lp->tx_ring[i].status); + } + } + printk("...0x%8.8lx\n", (long)&lp->tx_ring[i].status); + printk("Descriptor buffers:\nRX: "); + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8lx ",(long)lp->rx_ring[i].buf); + } + } + printk("...0x%8.8lx\n",(long)lp->rx_ring[i].buf); + printk("TX: "); + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + printk("0x%8.8lx ", (long)lp->tx_ring[i].buf); + } + } + printk("...0x%8.8lx\n", (long)lp->tx_ring[i].buf); + printk("Ring size: \nRX: %d\nTX: %d\n", + (short)lp->rxRingSize, + (short)lp->txRingSize); + printk("\tstatus: %d\n", status); + } + + if (!status) { + dev->tbusy = 0; + dev->start = 1; + dev->interrupt = UNMASK_INTERRUPTS; + dev->trans_start = jiffies; + + START_DE4X5; + + /* Unmask and enable DE4X5 board interrupts */ + imr = 0; + UNMASK_IRQs; + + /* Reset any pending (stale) interrupts */ + sts = inl(DE4X5_STS); + outl(sts, DE4X5_STS); + + ENABLE_IRQs; + } + if (de4x5_debug > 1) { + printk("\tsts: 0x%08x\n", inl(DE4X5_STS)); + printk("\tbmr: 0x%08x\n", inl(DE4X5_BMR)); + printk("\timr: 0x%08x\n", inl(DE4X5_IMR)); + printk("\tomr: 0x%08x\n", inl(DE4X5_OMR)); + printk("\tsisr: 0x%08x\n", inl(DE4X5_SISR)); + printk("\tsicr: 0x%08x\n", inl(DE4X5_SICR)); + printk("\tstrr: 0x%08x\n", inl(DE4X5_STRR)); + printk("\tsigr: 0x%08x\n", inl(DE4X5_SIGR)); + } + } + + MOD_INC_USE_COUNT; + + return status; +} + +/* +** Initialize the DE4X5 operating conditions. NB: a chip problem with the +** DC21140 requires using perfect filtering mode for that chip. Since I can't +** see why I'd want > 14 multicast addresses, I may change all chips to use +** the perfect filtering mode. Keep the DMA burst length at 8: there seems +** to be data corruption problems if it is larger (UDP errors seen from a +** ttcp source). +*/ +static int +de4x5_init(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + short iobase = dev->base_addr; + int status = 0; + long i, j, bmr, omr; + + /* Lock out other processes whilst setting up the hardware */ + set_bit(0, (void *)&dev->tbusy); + + RESET_DE4X5; + + bmr = inl(DE4X5_BMR); + bmr |= PBL_8 | DESC_SKIP_LEN | CACHE_ALIGN; + outl(bmr, DE4X5_BMR); + + if (lp->chipset != DC21140) { + omr = TR_96; + lp->setup_f = HASH_PERF; + } else { + omr = OMR_SDP | OMR_SF; + lp->setup_f = PERFECT; + } + outl((long)lp->rx_ring, DE4X5_RRBA); + outl((long)lp->tx_ring, DE4X5_TRBA); + + lp->rx_new = lp->rx_old = 0; + lp->tx_new = lp->tx_old = 0; + + for (i = 0; i < lp->rxRingSize; i++) { + lp->rx_ring[i].status = R_OWN; + } + + for (i = 0; i < lp->txRingSize; i++) { + lp->tx_ring[i].status = 0; + } + + /* Build the setup frame depending on filtering mode */ + SetMulticastFilter(dev, 0, NULL); + + if (lp->chipset != DC21140) { + load_packet(dev, lp->setup_frame, HASH_F|TD_SET|SETUP_FRAME_LEN, NULL); + } else { + load_packet(dev, lp->setup_frame, PERFECT_F|TD_SET|SETUP_FRAME_LEN, NULL); + } + outl(omr|OMR_ST, DE4X5_OMR); + + /* Poll for completion of setup frame (interrupts are disabled for now) */ + for (j=0, i=0;(i<100) && (j==0);i++) { + if (lp->tx_ring[lp->tx_new].status >= 0) j=1; + } + outl(omr, DE4X5_OMR); /* Stop everything! */ + + if (i == 100) { + printk("%s: Setup frame timed out, status %08x\n", dev->name, + inl(DE4X5_STS)); + status = -EIO; + } + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + lp->tx_old = lp->tx_new; + + /* Autoconfigure the connected port */ + if (autoconf_media(dev) == 0) { + status = -EIO; + } + + return 0; +} + +/* +** Writes a socket buffer address to the next available transmit descriptor +*/ +static int +de4x5_queue_pkt(struct sk_buff *skb, struct device *dev) +{ + volatile struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + int status = 0; + u_long imr, omr, sts; + + sts = inl(DE4X5_STS); + + /* + ** Transmitter timeout, possibly serious problems. + ** The 'lostMedia' threshold accounts for transient errors that + ** were noticed when switching media. + */ + if (dev->tbusy || (lp->lostMedia > LOST_MEDIA_THRESHOLD)) { + int tickssofar = jiffies - dev->trans_start; + if (tickssofar < 10 && (lp->lostMedia <= LOST_MEDIA_THRESHOLD)) { + status = -1; + } else { + printk("%s: transmit timed out, status %08x, tbusy:%d, lostMedia:%d tickssofar:%d, resetting.\n",dev->name, inl(DE4X5_STS), dev->tbusy, lp->lostMedia, tickssofar); + + /* Stop and reset the TX and RX... */ + STOP_DE4X5; + status = de4x5_init(dev); + + /* Unmask DE4X5 board interrupts */ + if (!status) { + /* Start here to clean stale interrupts later */ + dev->interrupt = UNMASK_INTERRUPTS; + dev->start = 1; + dev->tbusy = 0; + dev->trans_start = jiffies; + + START_DE4X5; + + /* Unmask DE4X5 board interrupts */ + imr = 0; + UNMASK_IRQs; + + /* Clear any pending (stale) interrupts */ + sts = inl(DE4X5_STS); + outl(sts, DE4X5_STS); + + ENABLE_IRQs; + } else { + printk("%s: hardware initialisation failure, status %08x.\n", + dev->name, inl(DE4X5_STS)); + } + } + } else if (skb == NULL) { + dev_tint(dev); + } else if (skb->len > 0) { + + /* + ** Block a timer-based transmit from overlapping. This could better be + ** done with atomic_swap(1, dev->tbusy), but set_bit() works as well. + */ + if (set_bit(0, (void*)&dev->tbusy) != 0) + printk("%s: Transmitter access conflict.\n", dev->name); + + if (TX_BUFFS_AVAIL) { /* Fill in a Tx ring entry */ + load_packet(dev, skb->data, TD_IC | TD_LS | TD_FS | skb->len, skb); + if (lp->tx_enable) { + outl(POLL_DEMAND, DE4X5_TPD); /* Start the TX */ + } + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; /* Ensure a wrap */ + + dev->trans_start = jiffies; + } + + if (TX_BUFFS_AVAIL) { + dev->tbusy = 0; /* Another pkt may be queued */ + } + } + + return status; +} + +/* +** The DE4X5 interrupt handler. +*/ +static void +de4x5_interrupt(int irq, struct pt_regs *regs) +{ + struct device *dev = (struct device *)(irq2dev_map[irq]); + struct de4x5_private *lp; + int iobase; + u_long imr, omr, sts; + + if (dev == NULL) { + printk ("de4x5_interrupt(): irq %d for unknown device.\n", irq); + } else { + lp = (struct de4x5_private *)dev->priv; + iobase = dev->base_addr; + + if (dev->interrupt) + printk("%s: Re-entering the interrupt handler.\n", dev->name); + + dev->interrupt = MASK_INTERRUPTS; + + /* + ** Get the interrupt information and disable them. + ** The device read will ensure pending buffers are flushed + ** in intermediate PCI bridges, so that the posted interrupt + ** has some real data to work with. + */ + sts = inl(DE4X5_STS); + MASK_IRQs; + + outl(sts, DE4X5_STS); /* Reset the board interrupts */ + + if (sts & (STS_RI | STS_RU)) /* Rx interrupt (packet[s] arrived) */ + de4x5_rx(dev); + + if (sts & (STS_TI | STS_TU)) /* Tx interrupt (packet sent) */ + de4x5_tx(dev); + + if (sts & STS_SE) { /* Bus Error */ + STOP_DE4X5; + printk("%s: Fatal bus error occurred, sts=0x%08lx, device stopped.\n", + dev->name, sts); + } + + if (TX_BUFFS_AVAIL && dev->tbusy) {/* Any resources available? */ + dev->tbusy = 0; /* Clear TX busy flag */ + mark_bh(NET_BH); + } + + dev->interrupt = UNMASK_INTERRUPTS; + + UNMASK_IRQs; + } + + return; +} + +static int +de4x5_rx(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i, entry; + volatile long status; + char *buf; + + for (entry = lp->rx_new; lp->rx_ring[entry].status >= 0;entry = lp->rx_new) { + status = lp->rx_ring[entry].status; + + if (status & RD_FS) { /* Remember the start of frame */ + lp->rx_old = entry; + } + + if (status & RD_LS) { /* Valid frame status */ + if (status & RD_ES) { /* There was an error. */ + lp->stats.rx_errors++; /* Update the error stats. */ + if (status & (RD_RF | RD_TL)) lp->stats.rx_frame_errors++; + if (status & RD_CE) lp->stats.rx_crc_errors++; + if (status & RD_OF) lp->stats.rx_fifo_errors++; + } else { /* A valid frame received */ + struct sk_buff *skb; + short pkt_len = (short)(lp->rx_ring[entry].status >> 16) - 4; + + if ((skb = alloc_skb(pkt_len, GFP_ATOMIC)) != NULL) { + skb->len = pkt_len; + skb->dev = dev; + + if (entry < lp->rx_old) { /* Wrapped buffer */ + short len = (lp->rxRingSize - lp->rx_old) * RX_BUFF_SZ; + memcpy(skb->data, lp->rx_ring[lp->rx_old].buf, len); + memcpy(skb->data + len, lp->rx_ring[0].buf, pkt_len - len); + } else { /* Linear buffer */ + memcpy(skb->data, lp->rx_ring[lp->rx_old].buf, pkt_len); + } + + /* + ** Notify the upper protocol layers that there is another + ** packet to handle + */ + skb->protocol=eth_type_trans(skb,dev); + netif_rx(skb); + + /* + ** Update stats + */ + lp->stats.rx_packets++; + for (i=1; i<DE4X5_PKT_STAT_SZ-1; i++) { + if (pkt_len < (i*DE4X5_PKT_BIN_SZ)) { + lp->pktStats.bins[i]++; + i = DE4X5_PKT_STAT_SZ; + } + } + buf = skb->data; /* Look at the dest addr */ + if (buf[0] & 0x01) { /* Multicast/Broadcast */ + if ((*(long *)&buf[0] == -1) && (*(short *)&buf[4] == -1)) { + lp->pktStats.broadcast++; + } else { + lp->pktStats.multicast++; + } + } else if ((*(long *)&buf[0] == *(long *)&dev->dev_addr[0]) && + (*(short *)&buf[4] == *(short *)&dev->dev_addr[4])) { + lp->pktStats.unicast++; + } + + lp->pktStats.bins[0]++; /* Duplicates stats.rx_packets */ + if (lp->pktStats.bins[0] == 0) { /* Reset counters */ + memset((char *)&lp->pktStats, 0, sizeof(lp->pktStats)); + } + } else { + printk("%s: Insufficient memory; nuking packet.\n", dev->name); + lp->stats.rx_dropped++; /* Really, deferred. */ + break; + } + } + + /* Change buffer ownership for this last frame, back to the adapter */ + for (; lp->rx_old!=entry; lp->rx_old=(++lp->rx_old)%lp->rxRingSize) { + lp->rx_ring[lp->rx_old].status = R_OWN; + } + lp->rx_ring[entry].status = R_OWN; + } + + /* + ** Update entry information + */ + lp->rx_new = (++lp->rx_new) % lp->rxRingSize; + } + + return 0; +} + +/* +** Buffer sent - check for TX buffer errors. +*/ +static int +de4x5_tx(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int entry, iobase = dev->base_addr; + long status; + + for (entry = lp->tx_old; entry != lp->tx_new; entry = lp->tx_old) { + status = lp->tx_ring[entry].status; + if (status < 0) { /* Buffer not sent yet */ + break; + } else if (status & TD_ES) { /* An error happened */ + lp->stats.tx_errors++; + if (status & TD_NC) lp->stats.tx_carrier_errors++; + if (status & TD_LC) lp->stats.tx_window_errors++; + if (status & TD_UF) lp->stats.tx_fifo_errors++; + if (status & TD_LC) lp->stats.collisions++; + if (status & TD_EC) lp->pktStats.excessive_collisions++; + if (status & TD_DE) lp->stats.tx_aborted_errors++; + + if (status & (TD_LO | TD_NC | TD_EC | TD_LF)) { + lp->lostMedia++; + } else { + outl(POLL_DEMAND, DE4X5_TPD); /* Restart a stalled TX */ + } + } else { /* Packet sent */ + lp->stats.tx_packets++; + lp->lostMedia = 0; /* Remove transient problem */ + } + /* Free the buffer if it's not a setup frame. */ + if (lp->skb[entry] != NULL) { + dev_kfree_skb(lp->skb[entry], FREE_WRITE); + } + + /* Update all the pointers */ + lp->tx_old = (++lp->tx_old) % lp->txRingSize; + } + + return 0; +} + +static int +de4x5_close(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + u_long imr, omr; + + dev->start = 0; + dev->tbusy = 1; + + if (de4x5_debug > 1) { + printk("%s: Shutting down ethercard, status was %8.8x.\n", + dev->name, inl(DE4X5_STS)); + } + + /* + ** We stop the DE4X5 here... mask interrupts and stop TX & RX + */ + DISABLE_IRQs; + + STOP_DE4X5; + + /* + ** Free the associated irq + */ + free_irq(dev->irq); + irq2dev_map[dev->irq] = 0; + + MOD_DEC_USE_COUNT; + + /* Put the adapter to sleep to save power */ + if (lp->chipset == DC21041) { + outl(0, DE4X5_SICR); + outl(CFDA_PSM, PCI_CFDA); + } + + return 0; +} + +static struct enet_statistics * +de4x5_get_stats(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + + lp->stats.rx_missed_errors = (int) (inl(DE4X5_MFC) & (MFC_OVFL | MFC_CNTR)); + + return &lp->stats; +} + +static void load_packet(struct device *dev, char *buf, u_long flags, struct sk_buff *skb) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + + lp->tx_ring[lp->tx_new].buf = buf; + lp->tx_ring[lp->tx_new].des1 &= TD_TER; + lp->tx_ring[lp->tx_new].des1 |= flags; + lp->skb[lp->tx_new] = skb; + lp->tx_ring[lp->tx_new].status = T_OWN; + + return; +} +/* +** Set or clear the multicast filter for this adaptor. +** num_addrs == -1 Promiscuous mode, receive all packets - not supported. +** Use the ioctls. +** num_addrs == 0 Normal mode, clear multicast list +** num_addrs > 0 Multicast mode, receive normal and MC packets, and do +** best-effort filtering. +** num_addrs == HASH_TABLE_LEN +** Set all multicast bits (pass all multicasts). +*/ +static void +set_multicast_list(struct device *dev, int num_addrs, void *addrs) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + + /* First, double check that the adapter is open */ + if (irq2dev_map[dev->irq] != NULL) { + if (num_addrs >= 0) { + SetMulticastFilter(dev, num_addrs, (char *)addrs); + if (lp->setup_f == HASH_PERF) { + load_packet(dev, lp->setup_frame, TD_IC | HASH_F | TD_SET | + SETUP_FRAME_LEN, NULL); + } else { + load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | + SETUP_FRAME_LEN, NULL); + } + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + outl(POLL_DEMAND, DE4X5_TPD); /* Start the TX */ + dev->trans_start = jiffies; + } + } + + return; +} + +/* +** Calculate the hash code and update the logical address filter +** from a list of ethernet multicast addresses. +** Little endian crc one liner from Matt Thomas, DEC. +*/ +static void SetMulticastFilter(struct device *dev, int num_addrs, char *addrs) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i, j, bit, byte, iobase = dev->base_addr; + u_short hashcode; + u_long crc, omr, poly = CRC_POLYNOMIAL_LE; + char *pa; + + omr = inl(DE4X5_OMR); + pa = build_setup_frame(dev, ALL); /* Build the basic frame */ + + if (lp->setup_f == HASH_PERF) { + if (num_addrs == HASH_TABLE_LEN) { /* Pass all multicasts */ + omr |= OMR_PM; + } else { + omr &= ~OMR_PM; + /* Now update the MCA table */ + for (i=0;i<num_addrs;i++) { /* for each address in the list */ + if ((*addrs & 0x01) == 1) { /* multicast address? */ + crc = 0xffffffff; /* init CRC for each address */ + for (byte=0;byte<ETH_ALEN;byte++) {/* for each address byte */ + /* process each address bit */ + for (bit = *addrs++,j=0;j<8;j++, bit>>=1) { + crc = (crc >> 1) ^ (((crc ^ bit) & 0x01) ? poly : 0); + } + } + hashcode = crc & HASH_BITS; /* hashcode is 9 LSb of CRC */ + + byte = hashcode >> 3; /* bit[3-8] -> byte in filter */ + bit = 1 << (hashcode & 0x07); /* bit[0-2] -> bit in byte */ + + byte <<= 1; /* calc offset into setup frame */ + if (byte & 0x02) { + byte -= 1; + } + lp->setup_frame[byte] |= bit; + + } else { /* skip this address */ + addrs += ETH_ALEN; + } + } + } + } else { /* Perfect filtering */ + omr &= ~OMR_PM; + for (j=0; j<num_addrs; j++) { + for (i=0; i<ETH_ALEN; i++) { + *(pa + (i&1)) = *addrs++; + if (i & 0x01) pa += 4; + } + } + } + outl(omr, DE4X5_OMR); + + return; +} + +/* +** EISA bus I/O device probe. Probe from slot 1 since slot 0 is usually +** the motherboard. Upto 15 EISA devices are supported. +*/ +static void eisa_probe(struct device *dev, short ioaddr) +{ + int i, maxSlots; + int status; + u_short vendor, device, iobase; + struct bus_type *lp = &bus; + char name[DE4X5_STRLEN]; + long cfid; + + if (!ioaddr && autoprobed) return ; /* Been here before ! */ + if ((ioaddr < 0x1000) && (ioaddr > 0)) return; /* PCI MODULE special */ + + lp->bus = EISA; + + if (ioaddr == 0) { /* Autoprobing */ + iobase = EISA_SLOT_INC; /* Get the first slot address */ + i = 1; + maxSlots = MAX_EISA_SLOTS; + } else { /* Probe a specific location */ + iobase = ioaddr; + i = (ioaddr >> 12); + maxSlots = i + 1; + } + + for (status = -ENODEV; (i<maxSlots) && (dev!=NULL); i++, iobase+=EISA_SLOT_INC) { + if (EISA_signature(name, EISA_ID)) { + cfid = inl(PCI_CFID); + device = (u_short)(cfid >> 16); + vendor = (u_short) cfid; + + lp->bus = EISA; + lp->chipset = device; + if (DevicePresent(EISA_APROM) == 0) { + /* Write the PCI Configuration Registers */ + outl(PCI_COMMAND_IO | PCI_COMMAND_MASTER, PCI_CFCS); + outl(0x00004000, PCI_CFLT); + outl((u_long)iobase, PCI_CBIO); + + if (check_region(iobase, DE4X5_EISA_TOTAL_SIZE) == 0) { + if ((dev = alloc_device(dev, iobase)) != NULL) { + if ((status = de4x5_hw_init(dev, iobase)) == 0) { + num_de4x5s++; + } + num_eth++; + } + } else if (autoprobed) { + printk("%s: region already allocated at 0x%04x.\n", dev->name, iobase); + } + } + } + } + + return; +} + +/* +** PCI bus I/O device probe +*/ +#define PCI_DEVICE (dev_num << 3) +#define PCI_LAST_DEV 32 + +static void pci_probe(struct device *dev, short ioaddr) + +{ + u_char irq; + u_short pb, dev_num, dev_last; + u_short vendor, device, status; + u_long class, iobase; + struct bus_type *lp = &bus; + + if (!ioaddr && autoprobed) return ; /* Been here before ! */ + + if (pcibios_present()) { + lp->bus = PCI; + + if (ioaddr < 0x1000) { + pb = (u_short)(ioaddr >> 8); + dev_num = (u_short)(ioaddr & 0xff); + } else { + pb = 0; + dev_num = 0; + } + if (ioaddr > 0) { + dev_last = (dev_num < PCI_LAST_DEV) ? dev_num + 1 : PCI_LAST_DEV; + } else { + dev_last = PCI_LAST_DEV; + } + + for (; (dev_num < dev_last) && (dev != NULL); dev_num++) { + pcibios_read_config_dword(pb, PCI_DEVICE, PCI_CLASS_REVISION, &class); + if (class != 0xffffffff) { + pcibios_read_config_word(pb, PCI_DEVICE, PCI_VENDOR_ID, &vendor); + pcibios_read_config_word(pb, PCI_DEVICE, PCI_DEVICE_ID, &device); + if (is_DC21040 || is_DC21041 || is_DC21140) { + /* Set the device number information */ + lp->device = dev_num; + + /* Set the chipset information */ + lp->chipset = device; + + /* Get the board I/O address */ + pcibios_read_config_dword(pb, PCI_DEVICE, PCI_BASE_ADDRESS_0, &iobase); + iobase &= CBIO_MASK; + + /* Fetch the IRQ to be used */ + pcibios_read_config_byte(pb, PCI_DEVICE, PCI_INTERRUPT_LINE, &irq); + + /* Enable I/O Accesses and Bus Mastering */ + pcibios_read_config_word(pb, PCI_DEVICE, PCI_COMMAND, &status); + status |= PCI_COMMAND_IO | PCI_COMMAND_MASTER; + pcibios_write_config_word(pb, PCI_DEVICE, PCI_COMMAND, status); + + /* If there is a device and I/O region is open, initialise dev. */ + if ((DevicePresent(DE4X5_APROM) == 0) || is_not_dec) { + if (check_region(iobase, DE4X5_PCI_TOTAL_SIZE) == 0) { + if ((dev = alloc_device(dev, iobase)) != NULL) { + dev->irq = irq; + if ((status = de4x5_hw_init(dev, iobase)) == 0) { + num_de4x5s++; + } + num_eth++; + } + } else if (autoprobed) { + printk("%s: region already allocated at 0x%04x.\n", dev->name, (u_short)iobase); + } + } + } + } + } + } + + return; +} + +/* +** Allocate the device by pointing to the next available space in the +** device structure. Should one not be available, it is created. +*/ +static struct device *alloc_device(struct device *dev, int iobase) +{ + int addAutoProbe = 0; + struct device *tmp = NULL, *ret; + int (*init)(struct device *) = NULL; + + /* + ** Check the device structures for an end of list or unused device + */ + if (!loading_module) { + while (dev->next != NULL) { + if ((dev->base_addr == 0xffe0) || (dev->base_addr == 0)) break; + dev = dev->next; /* walk through eth device list */ + num_eth++; /* increment eth device number */ + } + + /* + ** If an autoprobe is requested for another device, we must re-insert + ** the request later in the list. Remember the current position first. + */ + if ((dev->base_addr == 0) && (num_de4x5s > 0)) { + addAutoProbe++; + tmp = dev->next; /* point to the next device */ + init = dev->init; /* remember the probe function */ + } + + /* + ** If at end of list and can't use current entry, malloc one up. + ** If memory could not be allocated, print an error message. + */ + if ((dev->next == NULL) && + !((dev->base_addr == 0xffe0) || (dev->base_addr == 0))){ + dev->next = (struct device *)kmalloc(sizeof(struct device) + 8, + GFP_KERNEL); + + dev = dev->next; /* point to the new device */ + if (dev == NULL) { + printk("eth%d: Device not initialised, insufficient memory\n", + num_eth); + } else { + /* + ** If the memory was allocated, point to the new memory area + ** and initialize it (name, I/O address, next device (NULL) and + ** initialisation probe routine). + */ + dev->name = (char *)(dev + sizeof(struct device)); + if (num_eth > 9999) { + sprintf(dev->name,"eth????"); /* New device name */ + } else { + sprintf(dev->name,"eth%d", num_eth);/* New device name */ + } + dev->base_addr = iobase; /* assign the io address */ + dev->next = NULL; /* mark the end of list */ + dev->init = &de4x5_probe; /* initialisation routine */ + num_de4x5s++; + } + } + ret = dev; /* return current struct, or NULL */ + + /* + ** Now figure out what to do with the autoprobe that has to be inserted. + ** Firstly, search the (possibly altered) list for an empty space. + */ + if (ret != NULL) { + if (addAutoProbe) { + for (; (tmp->next!=NULL) && (tmp->base_addr!=0xffe0); tmp=tmp->next); + + /* + ** If no more device structures and can't use the current one, malloc + ** one up. If memory could not be allocated, print an error message. + */ + if ((tmp->next == NULL) && !(tmp->base_addr == 0xffe0)) { + tmp->next = (struct device *)kmalloc(sizeof(struct device) + 8, + GFP_KERNEL); + tmp = tmp->next; /* point to the new device */ + if (tmp == NULL) { + printk("%s: Insufficient memory to extend the device list.\n", + dev->name); + } else { + /* + ** If the memory was allocated, point to the new memory area + ** and initialize it (name, I/O address, next device (NULL) and + ** initialisation probe routine). + */ + tmp->name = (char *)(tmp + sizeof(struct device)); + if (num_eth > 9999) { + sprintf(tmp->name,"eth????"); /* New device name */ + } else { + sprintf(tmp->name,"eth%d", num_eth);/* New device name */ + } + tmp->base_addr = 0; /* re-insert the io address */ + tmp->next = NULL; /* mark the end of list */ + tmp->init = init; /* initialisation routine */ + } + } else { /* structure already exists */ + tmp->base_addr = 0; /* re-insert the io address */ + } + } + } + } else { + ret = dev; + } + + return ret; +} + +/* +** Auto configure the media here rather than setting the port at compile +** time. This routine is called by de4x5_init() when a loss of media is +** detected (excessive collisions, loss of carrier, no carrier or link fail +** [TP]) to check whether the user has been sneaky and changed the port on us. +*/ +static int autoconf_media(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + + if (lp->chipset == DC21040) { + lp->media = (lp->autosense == AUTO ? TP : lp->autosense); + dc21040_autoconf(dev); + } else if (lp->chipset == DC21041) { + lp->media = (lp->autosense == AUTO ? TP_NW : lp->autosense); + dc21041_autoconf(dev); + } else if (lp->chipset == DC21140) { + /* Force 10Mb/s (_100Mb for 100Mb/s) */ + lp->media = (lp->autosense == AUTO ? _10Mb : lp->autosense); + dc21140_autoconf(dev); + } + + if (de4x5_debug >= 1 ) { + if (lp->chipset != DC21140) { + printk("%s: Media is %s\n",dev->name, + (lp->media == NC ? "unconnected!" : + (lp->media == TP ? "TP." : + (lp->media == ANS ? "TP/Nway." : + (lp->media == BNC ? "BNC." : + (lp->media == AUI ? "AUI." : + "BNC/AUI." + )))))); + } else { + printk("%s: Mode is forced to %s\n",dev->name, + (lp->media == NC ? "link down.": + (lp->media == _100Mb ? "100Mb/s." : + (lp->media == _10Mb ? "10Mb/s." : + "\?\?\?" + )))); + } + } + + if (lp->media) { + lp->lostMedia = 0; + inl(DE4X5_MFC); /* Zero the lost frames counter */ + } + dce_ms_delay(10); + + return (lp->media); +} + +static void dc21040_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + u_long i, sisr = 0, linkBad; + u_long t_3s = 3000; + + switch (lp->media) { + case TP: + reset_init_sia(dev, 0x8f01, 0xffff, 0x0000); + for (linkBad=1,i=0;(i<t_3s) && linkBad && !(sisr & SISR_NCR);i++) { + if (((sisr = inl(DE4X5_SISR)) & SISR_LKF) == 0) linkBad = 0; + dce_ms_delay(1); + } + if (linkBad && (lp->autosense == AUTO)) { + lp->media = BNC_AUI; + dc21040_autoconf(dev); + } + break; + + case BNC: + case AUI: + case BNC_AUI: + reset_init_sia(dev, 0x8f09, 0x0705, 0x0006); + dce_ms_delay(330); + linkBad = ping_media(dev); + if (linkBad && (lp->autosense == AUTO)) { + lp->media = NC; + dc21040_autoconf(dev); + } + break; + + case NC: + reset_init_sia(dev, 0x8f01, 0xffff, 0x0000); + break; + } + + return; +} + +/* +** Autoconfigure the media when using the DC21041. AUI needs to be tested +** before BNC, because the BNC port will indicate activity if it's not +** terminated correctly. The only way to test for that is to place a loopback +** packet onto the network and watch for errors. +*/ +static void dc21041_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + u_long sts, irqs, irq_mask, omr; + + switch (lp->media) { + case TP_NW: + omr = inl(DE4X5_OMR); /* Set up full duplex for the autonegotiate */ + outl(omr | OMR_FD, DE4X5_OMR); + irqs = STS_LNF | STS_LNP; + irq_mask = IMR_LFM | IMR_LPM; + sts = test_media(dev, irqs, irq_mask, 0xef01, 0xffff, 0x0008, 2400); + if (sts & STS_LNP) { + lp->media = ANS; + } else { + lp->media = AUI; + } + dc21041_autoconf(dev); + break; + + case ANS: + irqs = STS_LNP; + irq_mask = IMR_LPM; + sts = test_ans(dev, irqs, irq_mask, 3000); + if (!(sts & STS_LNP) && (lp->autosense == AUTO)) { + lp->media = TP; + dc21041_autoconf(dev); + } + break; + + case TP: + omr = inl(DE4X5_OMR); /* Set up half duplex for TP */ + outl(omr & ~OMR_FD, DE4X5_OMR); + irqs = STS_LNF | STS_LNP; + irq_mask = IMR_LFM | IMR_LPM; + sts = test_media(dev, irqs, irq_mask, 0xef01, 0xff3f, 0x0008, 2400); + if (!(sts & STS_LNP) && (lp->autosense == AUTO)) { + if (inl(DE4X5_SISR) & SISR_NRA) { /* Non selected port activity */ + lp->media = AUI; + } else { + lp->media = BNC; + } + dc21041_autoconf(dev); + } + break; + + case AUI: + omr = inl(DE4X5_OMR); /* Set up half duplex for AUI */ + outl(omr & ~OMR_FD, DE4X5_OMR); + irqs = 0; + irq_mask = 0; + sts = test_media(dev, irqs, irq_mask, 0xef09, 0xf7fd, 0x000e, 1000); + if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) { + lp->media = BNC; + dc21041_autoconf(dev); + } + break; + + case BNC: + omr = inl(DE4X5_OMR); /* Set up half duplex for BNC */ + outl(omr & ~OMR_FD, DE4X5_OMR); + irqs = 0; + irq_mask = 0; + sts = test_media(dev, irqs, irq_mask, 0xef09, 0xf7fd, 0x0006, 1000); + if (!(inl(DE4X5_SISR) & SISR_SRA) && (lp->autosense == AUTO)) { + lp->media = NC; + } else { /* Ensure media connected */ + if (ping_media(dev)) lp->media = NC; + } + break; + + case NC: + omr = inl(DE4X5_OMR); /* Set up full duplex for the autonegotiate */ + outl(omr | OMR_FD, DE4X5_OMR); + reset_init_sia(dev, 0xef01, 0xffff, 0x0008);/* Initialise the SIA */ + break; + } + + return; +} + +/* +** Reduced feature version (temporary I hope) +*/ +static void dc21140_autoconf(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + u_long omr; + + switch(lp->media) { + case _100Mb: /* Set 100Mb/s, MII Port with PCS Function and Scrambler */ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR)); + outl(omr | OMR_PS | OMR_HBD | OMR_PCS | OMR_SCR, DE4X5_OMR); + outl(GEP_FDXD | GEP_MODE, DE4X5_GEP); + break; + + case _10Mb: /* Set conventional 10Mb/s ENDEC interface */ + omr = (inl(DE4X5_OMR) & ~(OMR_PS | OMR_HBD | OMR_TTM | OMR_PCS | OMR_SCR)); + outl(omr | OMR_TTM, DE4X5_OMR); + outl(GEP_FDXD, DE4X5_GEP); + break; + } + + return; +} + +static long test_media(struct device *dev, long irqs, long irq_mask, long csr13, long csr14, long csr15, long msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + long sts, time, csr12; + + reset_init_sia(dev, csr13, csr14, csr15); + + /* Set link_fail_inhibit_timer */ + load_ms_timer(dev, msec); + + /* clear all pending interrupts */ + sts = inl(DE4X5_STS); + outl(sts, DE4X5_STS); + + /* clear csr12 NRA and SRA bits */ + csr12 = inl(DE4X5_SISR); + outl(csr12, DE4X5_SISR); + + /* Poll for timeout - timer interrupt doesn't work correctly */ + do { + time = inl(DE4X5_GPT) & GPT_VAL; + sts = inl(DE4X5_STS); + } while ((time != 0) && !(sts & irqs)); + + sts = inl(DE4X5_STS); + + return sts; +} + +/* +** Send a packet onto the media and watch for send errors that indicate the +** media is bad or unconnected. +*/ +static long ping_media(struct device *dev) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int entry, iobase = dev->base_addr; + char frame[64]; + long i, linkBad, omr; + u_long t_3s = 3000; + + create_packet(dev, frame, sizeof(frame)); + + entry = lp->tx_new; /* Remember the ring position */ + load_packet(dev, frame, TD_LS | TD_FS | sizeof(frame),NULL); + + omr = inl(DE4X5_OMR); + outl(omr|OMR_ST, DE4X5_OMR); + + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + lp->tx_old = lp->tx_new; + + /* Poll for completion of frame (interrupts are disabled for now)... */ + for (linkBad=1,i=0;(i<t_3s) && linkBad;i++) { + if ((inl(DE4X5_SISR) & SISR_NCR) == 1) break; + if (lp->tx_ring[entry].status >= 0) linkBad=0; + dce_ms_delay(1); + } + outl(omr, DE4X5_OMR); + + return ((linkBad || (lp->tx_ring[entry].status & TD_ES)) ? 1 : 0); +} + +/* +** Check the Auto Negotiation State. Return OK when a link pass interrupt +** is received and the auto-negotiation status is NWAY OK. +*/ +static int test_ans(struct device *dev, long irqs, long irq_mask, long msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + long sts, ans; + + outl(irq_mask, DE4X5_IMR); + + /* Set timeout limit */ + load_ms_timer(dev, msec); + + /* clear all pending interrupts */ + sts = inl(DE4X5_STS); + outl(sts, DE4X5_STS); + + /* Poll for interrupts */ + do { + ans = inl(DE4X5_SISR) & SISR_ANS; + sts = inl(DE4X5_STS); + } while (!(sts & irqs) && (ans ^ ANS_NWOK) != 0); + + return ((sts & STS_LNP) && ((ans ^ ANS_NWOK) == 0) ? STS_LNP : 0); +} + +/* +** +*/ +static void reset_init_sia(struct device *dev, long sicr, long strr, long sigr) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + + RESET_SIA; + outl(sigr, DE4X5_SIGR); + outl(strr, DE4X5_STRR); + outl(sicr, DE4X5_SICR); + + return; +} + +/* +** Load the timer on the DC21041 and 21140. Max time is 13.42 secs. +*/ +static void load_ms_timer(struct device *dev, u_long msec) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int iobase = dev->base_addr; + + outl((long)(msec * 10000)/2048, DE4X5_GPT); + + return; +} + +/* +** Create an Ethernet packet with an invalid CRC +*/ +static void create_packet(struct device *dev, char *frame, int len) +{ + int i; + char *buf = frame; + + for (i=0; i<ETH_ALEN; i++) { /* Use this source address */ + *buf++ = dev->dev_addr[i]; + } + for (i=0; i<ETH_ALEN; i++) { /* Use this destination address */ + *buf++ = dev->dev_addr[i]; + } + + *buf++ = 0; /* Packet length (2 bytes) */ + *buf++ = 1; + + return; +} + +/* +** Known delay in microseconds +*/ +static void dce_us_delay(u_long usec) +{ + udelay(usec); + + return; +} + +/* +** Known delay in milliseconds, in millisecond steps. +*/ +static void dce_ms_delay(u_long msec) +{ + u_long i; + + for (i=0; i<msec; i++) { + dce_us_delay(1000); + } + + return; +} + + +/* +** Look for a particular board name in the EISA configuration space +*/ +int EISA_signature(char *name, long eisa_id) +{ + unsigned long i; + char *signatures[] = DE4X5_SIGNATURE; + char ManCode[DE4X5_STRLEN]; + union { + long ID; + char Id[4]; + } Eisa; + int status = 0; + + *name = '\0'; + Eisa.ID = inl(eisa_id); + + ManCode[0]=(((Eisa.Id[0]>>2)&0x1f)+0x40); + ManCode[1]=(((Eisa.Id[1]&0xe0)>>5)+((Eisa.Id[0]&0x03)<<3)+0x40); + ManCode[2]=(((Eisa.Id[2]>>4)&0x0f)+0x30); + ManCode[3]=((Eisa.Id[2]&0x0f)+0x30); + ManCode[4]=(((Eisa.Id[3]>>4)&0x0f)+0x30); + ManCode[5]='\0'; + + for (i=0;(*signatures[i] != '\0') && (*name == '\0');i++) { + if (strstr(ManCode, signatures[i]) != NULL) { + strcpy(name,ManCode); + status = 1; + } + } + + return status; /* return the device name string */ +} + +/* +** Look for a special sequence in the Ethernet station address PROM that +** is common across all DIGITAL network adapter products. +** +** Search the Ethernet address ROM for the signature. Since the ROM address +** counter can start at an arbitrary point, the search must include the entire +** probe sequence length plus the (length_of_the_signature - 1). +** Stop the search IMMEDIATELY after the signature is found so that the +** PROM address counter is correctly positioned at the start of the +** ethernet address for later read out. +*/ + +static int DevicePresent(short aprom_addr) +{ + union { + struct { + u_long a; + u_long b; + } llsig; + char Sig[sizeof(long) << 1]; + } dev; + char data; + long i, j, tmp; + short sigLength; + int status = 0; + struct bus_type *lp = &bus; + + dev.llsig.a = ETH_PROM_SIG; + dev.llsig.b = ETH_PROM_SIG; + sigLength = sizeof(long) << 1; + + if (lp->chipset == DC21040) { + for (i=0,j=0;(j<sigLength) && (i<PROBE_LENGTH+sigLength-1);i++) { + if (lp->bus == PCI) { + while ((tmp = inl(aprom_addr)) < 0); + data = (char)tmp; + } else { + data = inb(aprom_addr); + } + if (dev.Sig[j] == data) { /* track signature */ + j++; + } else { /* lost signature; begin search again */ + if (data == dev.Sig[0]) { + j=1; + } else { + j=0; + } + } + } + + if (j!=sigLength) { + status = -ENODEV; /* search failed */ + } + + } else { /* use new srom */ + short *p = (short *)&lp->srom; + for (i=0; i<(sizeof(struct de4x5_srom)>>1); i++) { + *p++ = srom_rd(aprom_addr, i); + } + } + + return status; +} + +static int aprom_crc(struct device *dev) +{ + int iobase = dev->base_addr; + long i, k, tmp; + unsigned short j,chksum; + unsigned char status = 0; + struct bus_type *lp = &bus; + + for (i=0,k=0,j=0;j<3;j++) { + k <<= 1 ; + if (k > 0xffff) k-=0xffff; + + if (lp->bus == PCI) { + if (lp->chipset == DC21040) { + while ((tmp = inl(DE4X5_APROM)) < 0); + k += (u_char) tmp; + dev->dev_addr[i++] = (u_char) tmp; + while ((tmp = inl(DE4X5_APROM)) < 0); + k += (u_short) (tmp << 8); + dev->dev_addr[i++] = (u_char) tmp; + } else { + dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++; + dev->dev_addr[i] = (u_char) lp->srom.ieee_addr[i]; i++; + } + } else { + k += (u_char) (tmp = inb(EISA_APROM)); + dev->dev_addr[i++] = (u_char) tmp; + k += (u_short) ((tmp = inb(EISA_APROM)) << 8); + dev->dev_addr[i++] = (u_char) tmp; + } + + if (k > 0xffff) k-=0xffff; + } + if (k == 0xffff) k=0; + + if (lp->bus == PCI) { + if (lp->chipset == DC21040) { + while ((tmp = inl(DE4X5_APROM)) < 0); + chksum = (u_char) tmp; + while ((tmp = inl(DE4X5_APROM)) < 0); + chksum |= (u_short) (tmp << 8); + if (k != chksum) status = -1; + } + } else { + chksum = (u_char) inb(EISA_APROM); + chksum |= (u_short) (inb(EISA_APROM) << 8); + if (k != chksum) status = -1; + } + + + return status; +} + +/* +** SROM Read +*/ +static short srom_rd(u_short addr, u_char offset) +{ + sendto_srom(SROM_RD | SROM_SR, addr); + + srom_latch(SROM_RD | SROM_SR | DT_CS, addr); + srom_command(SROM_RD | SROM_SR | DT_IN | DT_CS, addr); + srom_address(SROM_RD | SROM_SR | DT_CS, addr, offset); + + return srom_data(SROM_RD | SROM_SR | DT_CS, addr); +} + +static void srom_latch(u_long command, u_short addr) +{ + sendto_srom(command, addr); + sendto_srom(command | DT_CLK, addr); + sendto_srom(command, addr); + + return; +} + +static void srom_command(u_long command, u_short addr) +{ + srom_latch(command, addr); + srom_latch(command, addr); + srom_latch((command & 0x0000ff00) | DT_CS, addr); + + return; +} + +static void srom_address(u_long command, u_short addr, u_char offset) +{ + long i; + char a; + + a = (char)(offset << 2); + for (i=0; i<6; i++, a <<= 1) { + srom_latch(command | ((a < 0) ? DT_IN : 0), addr); + } + dce_us_delay(1); + + i = (getfrom_srom(addr) >> 3) & 0x01; + if (i != 0) { + printk("Bad SROM address phase.....\n"); + } + + return; +} + +static short srom_data(u_long command, u_short addr) +{ + int i; + short word = 0; + long tmp; + + for (i=0; i<16; i++) { + sendto_srom(command | DT_CLK, addr); + tmp = getfrom_srom(addr); + sendto_srom(command, addr); + + word = (word << 1) | ((tmp >> 3) & 0x01); + } + + sendto_srom(command & 0x0000ff00, addr); + + return word; +} + +/* +static void srom_busy(u_long command, u_short addr) +{ + sendto_srom((command & 0x0000ff00) | DT_CS, addr); + + while (!((getfrom_srom(addr) >> 3) & 0x01)) { + dce_ms_delay(1); + } + + sendto_srom(command & 0x0000ff00, addr); + + return; +} +*/ + +static void sendto_srom(u_long command, u_short addr) +{ + outl(command, addr); + dce_us_delay(1); + + return; +} + +static long getfrom_srom(u_short addr) +{ + long tmp; + + tmp = inl(addr); + dce_us_delay(1); + + return tmp; +} + +static char *build_setup_frame(struct device *dev, int mode) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + int i; + char *pa = lp->setup_frame; + + /* Initialise the setup frame */ + if (mode == ALL) { + memset(lp->setup_frame, 0, SETUP_FRAME_LEN); + } + + if (lp->setup_f == HASH_PERF) { + for (pa=lp->setup_frame+IMPERF_PA_OFFSET, i=0; i<ETH_ALEN; i++) { + *(pa + i) = dev->dev_addr[i]; /* Host address */ + if (i & 0x01) pa += 2; + } + *(lp->setup_frame + (HASH_TABLE_LEN >> 3) - 3) = 0x80; /* B'cast address */ + } else { + for (i=0; i<ETH_ALEN; i++) { /* Host address */ + *(pa + (i&1)) = dev->dev_addr[i]; + if (i & 0x01) pa += 4; + } + for (i=0; i<ETH_ALEN; i++) { /* Broadcast address */ + *(pa + (i&1)) = (char) 0xff; + if (i & 0x01) pa += 4; + } + } + + return pa; /* Points to the next entry */ +} + +/* +** Perform IOCTL call functions here. Some are privileged operations and the +** effective uid is checked in those cases. +*/ +static int de4x5_ioctl(struct device *dev, struct ifreq *rq, int cmd) +{ + struct de4x5_private *lp = (struct de4x5_private *)dev->priv; + struct de4x5_ioctl *ioc = (struct de4x5_ioctl *) &rq->ifr_data; + int i, j, iobase = dev->base_addr, status = 0; + u_long omr; + union { + unsigned char addr[(HASH_TABLE_LEN * ETH_ALEN)]; + unsigned short sval[(HASH_TABLE_LEN * ETH_ALEN) >> 1]; + unsigned long lval[(HASH_TABLE_LEN * ETH_ALEN) >> 2]; + } tmp; + + switch(ioc->cmd) { + case DE4X5_GET_HWADDR: /* Get the hardware address */ + for (i=0; i<ETH_ALEN; i++) { + tmp.addr[i] = dev->dev_addr[i]; + } + ioc->len = ETH_ALEN; + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + + break; + case DE4X5_SET_HWADDR: /* Set the hardware address */ + if (suser()) { + memcpy_fromfs(tmp.addr,ioc->data,ETH_ALEN); + for (i=0; i<ETH_ALEN; i++) { + dev->dev_addr[i] = tmp.addr[i]; + } + build_setup_frame(dev, PHYS_ADDR_ONLY); + /* Set up the descriptor and give ownership to the card */ + while (set_bit(0, (void *)&dev->tbusy) != 0); /* Wait for lock to free */ + if (lp->setup_f == HASH_PERF) { + load_packet(dev, lp->setup_frame, TD_IC | HASH_F | TD_SET | + SETUP_FRAME_LEN, NULL); + } else { + load_packet(dev, lp->setup_frame, TD_IC | PERFECT_F | TD_SET | + SETUP_FRAME_LEN, NULL); + } + lp->tx_new = (++lp->tx_new) % lp->txRingSize; + outl(POLL_DEMAND, DE4X5_TPD); /* Start the TX */ + dev->tbusy = 0; /* Unlock the TX ring */ + + } else { + status = -EPERM; + } + + break; + case DE4X5_SET_PROM: /* Set Promiscuous Mode */ + if (suser()) { + omr = inl(DE4X5_OMR); + omr |= OMR_PR; + outl(omr, DE4X5_OMR); + } else { + status = -EPERM; + } + + break; + case DE4X5_CLR_PROM: /* Clear Promiscuous Mode */ + if (suser()) { + omr = inl(DE4X5_OMR); + omr &= ~OMR_PR; + outb(omr, DE4X5_OMR); + } else { + status = -EPERM; + } + + break; + case DE4X5_SAY_BOO: /* Say "Boo!" to the kernel log file */ + printk("%s: Boo!\n", dev->name); + + break; + case DE4X5_GET_MCA: /* Get the multicast address table */ + ioc->len = (HASH_TABLE_LEN >> 3); + memcpy_tofs(ioc->data, lp->setup_frame, 192); + + break; + case DE4X5_SET_MCA: /* Set a multicast address */ + if (suser()) { + if (ioc->len != HASH_TABLE_LEN) { /* MCA changes */ + memcpy_fromfs(tmp.addr, ioc->data, ETH_ALEN * ioc->len); + } + set_multicast_list(dev, ioc->len, tmp.addr); + } else { + status = -EPERM; + } + + break; + case DE4X5_CLR_MCA: /* Clear all multicast addresses */ + if (suser()) { + set_multicast_list(dev, 0, NULL); + } else { + status = -EPERM; + } + + break; + case DE4X5_MCA_EN: /* Enable pass all multicast addressing */ + if (suser()) { + omr = inl(DE4X5_OMR); + omr |= OMR_PM; + outl(omr, DE4X5_OMR); + } else { + status = -EPERM; + } + + break; + case DE4X5_GET_STATS: /* Get the driver statistics */ + cli(); + memcpy_tofs(ioc->data, &lp->pktStats, sizeof(lp->pktStats)); + ioc->len = DE4X5_PKT_STAT_SZ; + sti(); + + break; + case DE4X5_CLR_STATS: /* Zero out the driver statistics */ + if (suser()) { + cli(); + memset(&lp->pktStats, 0, sizeof(lp->pktStats)); + sti(); + } else { + status = -EPERM; + } + + break; + case DE4X5_GET_OMR: /* Get the OMR Register contents */ + tmp.addr[0] = inl(DE4X5_OMR); + memcpy_tofs(ioc->data, tmp.addr, 1); + + break; + case DE4X5_SET_OMR: /* Set the OMR Register contents */ + if (suser()) { + memcpy_fromfs(tmp.addr, ioc->data, 1); + outl(tmp.addr[0], DE4X5_OMR); + } else { + status = -EPERM; + } + + break; + case DE4X5_GET_REG: /* Get the DE4X5 Registers */ + tmp.lval[0] = inl(DE4X5_STS); + tmp.lval[1] = inl(DE4X5_BMR); + tmp.lval[2] = inl(DE4X5_IMR); + tmp.lval[3] = inl(DE4X5_OMR); + tmp.lval[4] = inl(DE4X5_SISR); + tmp.lval[5] = inl(DE4X5_SICR); + tmp.lval[6] = inl(DE4X5_STRR); + tmp.lval[7] = inl(DE4X5_SIGR); + memcpy_tofs(ioc->data, tmp.addr, 32); + + break; + +#define DE4X5_DUMP 0x0f /* Dump the DE4X5 Status */ + + case DE4X5_DUMP: + j = 0; + tmp.addr[j++] = dev->irq; + for (i=0; i<ETH_ALEN; i++) { + tmp.addr[j++] = dev->dev_addr[i]; + } + tmp.addr[j++] = lp->rxRingSize; + tmp.lval[j>>2] = (long)lp->rx_ring; j+=4; + tmp.lval[j>>2] = (long)lp->tx_ring; j+=4; + + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4; + } + } + tmp.lval[j>>2] = (long)&lp->rx_ring[i].status; j+=4; + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4; + } + } + tmp.lval[j>>2] = (long)&lp->tx_ring[i].status; j+=4; + + for (i=0;i<lp->rxRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (long)lp->rx_ring[i].buf; j+=4; + } + } + tmp.lval[j>>2] = (long)lp->rx_ring[i].buf; j+=4; + for (i=0;i<lp->txRingSize-1;i++){ + if (i < 3) { + tmp.lval[j>>2] = (long)lp->tx_ring[i].buf; j+=4; + } + } + tmp.lval[j>>2] = (long)lp->tx_ring[i].buf; j+=4; + + for (i=0;i<lp->rxRingSize;i++){ + tmp.lval[j>>2] = lp->rx_ring[i].status; j+=4; + } + for (i=0;i<lp->txRingSize;i++){ + tmp.lval[j>>2] = lp->tx_ring[i].status; j+=4; + } + + tmp.lval[j>>2] = inl(DE4X5_STS); j+=4; + tmp.lval[j>>2] = inl(DE4X5_BMR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_IMR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_OMR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_SISR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_SICR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_STRR); j+=4; + tmp.lval[j>>2] = inl(DE4X5_SIGR); j+=4; + + tmp.addr[j++] = lp->txRingSize; + tmp.addr[j++] = dev->tbusy; + + ioc->len = j; + memcpy_tofs(ioc->data, tmp.addr, ioc->len); + + break; + default: + status = -EOPNOTSUPP; + } + + return status; +} + +#ifdef MODULE +char kernel_version[] = UTS_RELEASE; +static struct device thisDE4X5 = { + " ", /* device name inserted by /linux/drivers/net/net_init.c */ + 0, 0, 0, 0, + 0x2000, 10, /* I/O address, IRQ */ + 0, 0, 0, NULL, de4x5_probe }; + +int io=0x000b; /* <--- EDIT THESE LINES FOR YOUR CONFIGURATION */ +int irq=10; /* or use the insmod io= irq= options */ + +int +init_module(void) +{ + thisDE4X5.base_addr=io; + thisDE4X5.irq=irq; + if (register_netdev(&thisDE4X5) != 0) + return -EIO; + return 0; +} + +void +cleanup_module(void) +{ + struct de4x5_private *lp = (struct de4x5_private *) thisDE4X5.priv; + + if (MOD_IN_USE) { + printk("%s: device busy, remove delayed\n",thisDE4X5.name); + } else { + release_region(thisDE4X5.base_addr, (lp->bus == PCI ? + DE4X5_PCI_TOTAL_SIZE : + DE4X5_EISA_TOTAL_SIZE)); + if (lp) { + kfree_s(lp->rx_ring[0].buf, RX_BUFF_SZ * NUM_RX_DESC + ALIGN); + } + kfree_s(thisDE4X5.priv, sizeof(struct de4x5_private) + ALIGN); + thisDE4X5.priv = NULL; + + unregister_netdev(&thisDE4X5); + } +} +#endif /* MODULE */ + + +/* + * Local variables: + * kernel-compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de4x5.c" + * + * module-compile-command: "gcc -D__KERNEL__ -DMODULE -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O2 -m486 -c de4x5.c" + * End: + */ + + |