[4540] in linux-net channel archive

home help back first fref pref prev next nref lref last post

3c505

daemon@ATHENA.MIT.EDU (Philip Blundell)
Thu Sep 26 22:35:04 1996

Date: 	Wed, 25 Sep 1996 18:09:45 +0100 (BST)
From: Philip Blundell <Philip.Blundell@pobox.com>
Reply-To: Philip Blundell <phil@tazenda.demon.co.uk>
To: linux-net@vger.rutgers.edu
cc: Dave Ewaldz <dme@ingersoll.com>, Alexander <avenco@online.ru>

Hi guys.

The attached diff (against kernel 2.0.21) is the latest version of the
3c505 ethercard driver.  I plan to send this to Linus for inclusion in
2.1.  If development on 2.0 continues, I may try to get a more minimalist
patch included in that kernel tree as well.

This patch does:

 - support for multiple cards, if anybody is that sick
 - DMA now run-time configurable
 - fix a DMA timing bug that killed slow machines
 - fix a DMA bounce-buffer problem that bit people with >16MB RAM
 - fix an uninitialised variable that caused booting up to be a lottery
 - support for old cards with write-only registers
 - code tidy-ups, cosmetic tweaks
 - probe code improved; autoIRQ failing is now less fatal

#3, #4 and #5 above were pretty much show-stoppers for some people, so
those probably want to get fixed in the "stable" 2.0 kernel. 

If you use one of these cards, please check it out.  If you've been using
an older patch that I sent to some people, you will need to back that out
before you apply this one.

phil
--
--- clean-linux/linux/drivers/net/3c505.c	Sun Aug 25 12:59:18 1996
+++ linux/drivers/net/3c505.c	Wed Sep 25 18:00:20 1996
@@ -30,11 +30,14 @@
  *                      Terry Murphy, of 3Com Network Adapter Division
  *              Linux 1.3.0 changes by
  *                      Alan Cox <Alan.Cox@linux.org>
- *              More debugging and DMA version by Philip Blundell
+ *              More debugging, DMA support, currently maintained by
+ *                      Philip Blundell <Philip.Blundell@pobox.com>
+ *              Multicard/soft configurable dma channel/rev 2 hardware support
+ *                      by Christopher Collins <ccollins@pcug.org.au>
  */
 
 /* Theory of operation:
-
+ *
  * The 3c505 is quite an intelligent board.  All communication with it is done
  * by means of Primary Command Blocks (PCBs); these are transferred using PIO
  * through the command register.  The card has 256k of on-board RAM, which is
@@ -42,7 +45,9 @@
  * are better, but in fact this isn't true.  From my tests, it seems that
  * more than about 10 buffers are unnecessary, and there is a noticeable
  * performance hit in having more active on the card.  So the majority of the
- * card's memory isn't, in fact, used.
+ * card's memory isn't, in fact, used.  Sadly, the card only has one transmit
+ * buffer and, short of loading our own firmware into it (which is what some
+ * drivers resort to) there's nothing we can do about this.
  *
  * We keep up to 4 "receive packet" commands active on the board at a time.
  * When a packet comes in, so long as there is a receive command active, the
@@ -71,11 +76,13 @@
  * is blocked.  In practice, this doesn't seem to happen very often.
  */
 
-/* This driver will not work with revision 2 hardware, because the host
- * control register is write-only.  It should be fairly easy to arrange to
- * keep our own soft-copy of the intended contents of this register, if
- * somebody has the time.  There may be firmware differences that cause
- * other problems, though, and I don't have an old card to test.
+/* This driver may now work with revision 2.x hardware, since all the read
+ * operations on the HCR have been removed (we now keep our own softcopy).
+ * But I don't have an old card to test it on.  
+ * 
+ * This has had the bad effect that the autoprobe routine is now a bit
+ * less friendly to other devices.  However, it was never very good.
+ * before, so I doubt it will hurt anybody.  
  */
 
 /* The driver is a mess.  I took Craig's and Juha's code, and hacked it firstly
@@ -104,9 +111,6 @@
 
 #include "3c505.h"
 
-#define ELP_DMA      6		/* DMA channel to use */
-#define ELP_RX_PCBS  4
-
 /*********************************************************
  *
  *  define debug messages here as common strings to reduce space
@@ -174,7 +178,7 @@
  * Last element MUST BE 0!
  *****************************************************************/
 
-const int addr_list[] = {0x300, 0x280, 0x310, 0};
+static const int addr_list[] = {0x300, 0x280, 0x310, 0};
 
 /* Dma Memory related stuff */
 
@@ -211,21 +215,19 @@
 	return inb(base_addr + PORT_STATUS);
 }
 
-static inline unsigned char inb_control(unsigned int base_addr)
-{
-	return inb(base_addr + PORT_CONTROL);
-}
-
 static inline int inb_command(unsigned int base_addr)
 {
 	return inb(base_addr + PORT_COMMAND);
 }
 
-static inline void outb_control(unsigned char val, unsigned int base_addr)
+static inline void outb_control(unsigned char val, struct device *dev)
 {
-	outb(val, base_addr + PORT_CONTROL);
+	outb(val, dev->base_addr + PORT_CONTROL);
+	((elp_device *)(dev->priv))->hcr_val = val;
 }
 
+#define HCR_VAL(x)   (((elp_device *)((x)->priv))->hcr_val)
+
 static inline void outb_command(unsigned char val, unsigned int base_addr)
 {
 	outb(val, base_addr + PORT_COMMAND);
@@ -241,48 +243,6 @@
 	outw(val, base_addr + PORT_DATA);
 }
 
-
-/*****************************************************************
- *
- *  structure to hold context information for adapter
- *
- *****************************************************************/
-
-#define DMA_BUFFER_SIZE  1600
-#define BACKLOG_SIZE      4
-
-typedef struct {
-	volatile short got[NUM_TRANSMIT_CMDS];	/* flags for command completion */
-	pcb_struct tx_pcb;	/* PCB for foreground sending */
-	pcb_struct rx_pcb;	/* PCB for foreground receiving */
-	pcb_struct itx_pcb;	/* PCB for background sending */
-	pcb_struct irx_pcb;	/* PCB for background receiving */
-	struct enet_statistics stats;
-
-	void *dma_buffer;
-
-	struct {
-		unsigned int length[BACKLOG_SIZE];
-		unsigned int in;
-		unsigned int out;
-	} rx_backlog;
-
-	struct {
-		unsigned int direction;
-		unsigned int length;
-		unsigned int copy_flag;
-		struct sk_buff *skb;
-		long int start_time;
-	} current_dma;
-
-	/* flags */
-	unsigned long send_pcb_semaphore;
-	unsigned int dmaing;
-	unsigned long busy;
-
-	unsigned int rx_active;  /* number of receive PCBs */
-} elp_device;
-
 static inline unsigned int backlog_next(unsigned int n)
 {
 	return (n + 1) % BACKLOG_SIZE;
@@ -315,10 +275,10 @@
 	return stat1;
 }
 
-static inline void set_hsf(unsigned int base_addr, int hsf)
+static inline void set_hsf(struct device *dev, int hsf)
 {
 	cli();
-	outb_control((inb_control(base_addr) & ~HSF_PCB_MASK) | hsf, base_addr);
+	outb_control((HCR_VAL(dev) & ~HSF_PCB_MASK) | hsf, dev);
 	sti();
 }
 
@@ -327,11 +287,10 @@
 inline static void adapter_reset(struct device *dev)
 {
 	int timeout;
-	unsigned char orig_hcr = inb_control(dev->base_addr);
-
 	elp_device *adapter = dev->priv;
+	unsigned char orig_hcr = adapter->hcr_val;
 
-	outb_control(0, dev->base_addr);
+	outb_control(0, dev);
 
 	if (inb_status(dev->base_addr) & ACRF) {
 		do {
@@ -339,29 +298,29 @@
 			timeout = jiffies + 2;
 			while ((jiffies <= timeout) && !(inb_status(dev->base_addr) & ACRF));
 		} while (inb_status(dev->base_addr) & ACRF);
-		set_hsf(dev->base_addr, HSF_PCB_NAK);
+		set_hsf(dev, HSF_PCB_NAK);
 	}
-	outb_control(inb_control(dev->base_addr) | ATTN | DIR, dev->base_addr);
+	outb_control(adapter->hcr_val | ATTN | DIR, dev);
 	timeout = jiffies + 1;
 	while (jiffies <= timeout);
-	outb_control(inb_control(dev->base_addr) & ~ATTN, dev->base_addr);
+	outb_control(adapter->hcr_val & ~ATTN, dev);
 	timeout = jiffies + 1;
 	while (jiffies <= timeout);
-	outb_control(inb_control(dev->base_addr) | FLSH, dev->base_addr);
+	outb_control(adapter->hcr_val | FLSH, dev);
 	timeout = jiffies + 1;
 	while (jiffies <= timeout);
-	outb_control(inb_control(dev->base_addr) & ~FLSH, dev->base_addr);
+	outb_control(adapter->hcr_val & ~FLSH, dev);
 	timeout = jiffies + 1;
 	while (jiffies <= timeout);
 
-	outb_control(orig_hcr, dev->base_addr);
+	outb_control(orig_hcr, dev);
 	if (!start_receive(dev, &adapter->tx_pcb))
 		printk("%s: start receive command failed \n", dev->name);
 }
 
-/* Check to make sure that a DMA transfer hasn't timed out.  This should never happen
- * in theory, but seems to occur occasionally if the card gets prodded at the wrong
- * time.
+/* Check to make sure that a DMA transfer hasn't timed out.  This should
+ * never happen in theory, but seems to occur occasionally if the card gets
+ * prodded at the wrong time.
  */
 static inline void check_dma(struct device *dev)
 {
@@ -376,7 +335,7 @@
 		disable_dma(dev->dma);
 		if (adapter->rx_active)
 			adapter->rx_active--;
-		outb_control(inb_control(dev->base_addr) & ~(DMAE | TCEN | DIR), dev->base_addr);
+		outb_control(adapter->hcr_val & ~(DMAE | TCEN | DIR), dev);
 		restore_flags(flags);
 	}
 }
@@ -463,7 +422,7 @@
 	 * wait for the HCRE bit to indicate the adapter
 	 * had read the byte
 	 */
-	set_hsf(dev->base_addr, 0);
+	set_hsf(dev, 0);
 
 	if (send_pcb_slow(dev->base_addr, pcb->command))
 		goto abort;
@@ -478,7 +437,7 @@
 			goto sti_abort;
 	}
 
-	outb_control(inb_control(dev->base_addr) | 3, dev->base_addr);	/* signal end of PCB */
+	outb_control(adapter->hcr_val | 3, dev);	/* signal end of PCB */
 	outb_command(2 + pcb->length, dev->base_addr);
 
 	/* now wait for the acknowledgement */
@@ -530,7 +489,7 @@
 
 	elp_device *adapter = dev->priv;
 
-	set_hsf(dev->base_addr, 0);
+	set_hsf(dev, 0);
 
 	/* get the command code */
 	timeout = jiffies + 2;
@@ -578,14 +537,14 @@
 	if (total_length != (pcb->length + 2)) {
 		if (elp_debug >= 2)
 			printk("%s: mangled PCB received\n", dev->name);
-		set_hsf(dev->base_addr, HSF_PCB_NAK);
+		set_hsf(dev, HSF_PCB_NAK);
 		return FALSE;
 	}
 
 	if (pcb->command == CMD_RECEIVE_PACKET_COMPLETE) {
 		if (set_bit(0, (void *) &adapter->busy)) {
 			if (backlog_next(adapter->rx_backlog.in) == adapter->rx_backlog.out) {
-				set_hsf(dev->base_addr, HSF_PCB_NAK);
+				set_hsf(dev, HSF_PCB_NAK);
 				printk("%s: PCB rejected, transfer in progress and backlog full\n", dev->name);
 				pcb->command = 0;
 				return TRUE;
@@ -594,7 +553,7 @@
 			}
 		}
 	}
-	set_hsf(dev->base_addr, HSF_PCB_ACK);
+	set_hsf(dev, HSF_PCB_ACK);
 	return TRUE;
 }
 
@@ -637,25 +596,27 @@
 {
 	int rlen;
 	elp_device *adapter = dev->priv;
-	unsigned long target;
+	void *target;
 	struct sk_buff *skb;
 
 	rlen = (len + 1) & ~1;
 	skb = dev_alloc_skb(rlen + 2);
 
-	adapter->current_dma.copy_flag = 0;
-
 	if (!skb) {
-	  printk("%s: memory squeeze, dropping packet\n", dev->name);
-	  target = virt_to_bus(adapter->dma_buffer);
+		printk("%s: memory squeeze, dropping packet\n", dev->name);
+		target = adapter->dma_buffer;
+		adapter->current_dma.target = NULL;
 	} else {
-	  skb_reserve(skb, 2);
-	  target = virt_to_bus(skb_put(skb, rlen));
-	  if ((target + rlen) >= MAX_DMA_ADDRESS) {
-	    target = virt_to_bus(adapter->dma_buffer);
-	    adapter->current_dma.copy_flag = 1;
-	  }
+		skb_reserve(skb, 2);
+		target = skb_put(skb, rlen);
+		if (virt_to_bus(target + rlen) >= MAX_DMA_ADDRESS) {
+			adapter->current_dma.target = target;
+			target = adapter->dma_buffer;
+		} else {
+			adapter->current_dma.target = NULL;
+		}
 	}
+
 	/* if this happens, we die */
 	if (set_bit(0, (void *) &adapter->dmaing))
 		printk("%s: rx blocked, DMA in progress, dir %d\n", dev->name, adapter->current_dma.direction);
@@ -665,18 +626,19 @@
 	adapter->current_dma.skb = skb;
 	adapter->current_dma.start_time = jiffies;
 
-	outb_control(inb_control(dev->base_addr) | DIR | TCEN | DMAE, dev->base_addr);
+	outb_control(adapter->hcr_val | DIR | TCEN | DMAE, dev);
 
 	disable_dma(dev->dma);
 	clear_dma_ff(dev->dma);
 	set_dma_mode(dev->dma, 0x04);	/* dma read */
-	set_dma_addr(dev->dma, target);
+	set_dma_addr(dev->dma, virt_to_bus(target));
 	set_dma_count(dev->dma, rlen);
 	enable_dma(dev->dma);
 
 	if (elp_debug >= 3) {
 		printk("%s: rx DMA transfer started\n", dev->name);
 	}
+
 	if (adapter->rx_active)
 		adapter->rx_active--;
 
@@ -729,15 +691,17 @@
 				printk("%s: %s DMA complete, status %02x\n", dev->name, adapter->current_dma.direction ? "tx" : "rx", inb_status(dev->base_addr));
 			}
 
-			outb_control(inb_control(dev->base_addr) & ~(DMAE | TCEN | DIR), dev->base_addr);
+			outb_control(adapter->hcr_val & ~(DMAE | TCEN | DIR),
+				     dev);
 			if (adapter->current_dma.direction) {
 				dev_kfree_skb(adapter->current_dma.skb, FREE_WRITE);
 			} else {
 				struct sk_buff *skb = adapter->current_dma.skb;
 				if (skb) {
 				  skb->dev = dev;
-				  if (adapter->current_dma.copy_flag) {
-				    memcpy(skb_put(skb, adapter->current_dma.length), adapter->dma_buffer, adapter->current_dma.length);
+				  if (adapter->current_dma.target) {
+				    /* have already done the skb_put() */
+				    memcpy(adapter->current_dma.target, adapter->dma_buffer, adapter->current_dma.length);
 				  }
 				  skb->protocol = eth_type_trans(skb,dev);
 				  netif_rx(skb);
@@ -929,7 +893,7 @@
 	/*
 	 * disable interrupts on the board
 	 */
-	outb_control(0x00, dev->base_addr);
+	outb_control(0, dev);
 
 	/*
 	 * clear any pending interrupts
@@ -982,12 +946,7 @@
 	/*
 	 * enable interrupts on the board
 	 */
-	outb_control(CMDE, dev->base_addr);
-
-	/*
-	 * device is now officially open!
-	 */
-	dev->start = 1;
+	outb_control(CMDE, dev);
 
 	/*
 	 * configure adapter memory: we need 10 multicast addresses, default==0
@@ -1032,7 +991,7 @@
 	}
 
 	/* enable burst-mode DMA */
-	outb(0x1, dev->base_addr + PORT_AUXDMA);
+	/* outb(0x1, dev->base_addr + PORT_AUXDMA); */
 
 	/*
 	 * queue receive commands to provide buffering
@@ -1041,6 +1000,11 @@
 	if (elp_debug >= 3)
 		printk("%s: %d receive PCBs active\n", dev->name, adapter->rx_active);
 
+	/*
+	 * device is now officially open!
+	 */
+	dev->start = 1;
+
 	MOD_INC_USE_COUNT;
 
 	return 0;		/* Always succeed */
@@ -1100,11 +1064,11 @@
 	cli();
 	disable_dma(dev->dma);
 	clear_dma_ff(dev->dma);
-	set_dma_mode(dev->dma, 0x08);	/* dma memory -> io */
+	set_dma_mode(dev->dma, 0x48);	/* dma memory -> io */
 	set_dma_addr(dev->dma, target);
 	set_dma_count(dev->dma, nlen);
+	outb_control(adapter->hcr_val | DMAE | TCEN, dev);
 	enable_dma(dev->dma);
-	outb_control(inb_control(dev->base_addr) | DMAE | TCEN, dev->base_addr);
 	if (elp_debug >= 3)
 		printk("%s: DMA transfer started\n", dev->name);
 
@@ -1247,7 +1211,7 @@
 	/*
 	 * disable interrupts on the board
 	 */
-	outb_control(0x00, dev->base_addr);
+	outb_control(0, dev);
 
 	/*
 	 *  flag transmitter as busy (i.e. not available)
@@ -1385,22 +1349,20 @@
 	int addr = dev->base_addr;
 	const char *name = dev->name;
 	long flags;
-	byte orig_HCR, orig_HSR;
+	byte orig_HSR;
 
 	if (check_region(addr, 0xf))
 		return -1;
 
-	orig_HCR = inb_control(addr);
 	orig_HSR = inb_status(addr);
 
 	if (elp_debug > 0)
 		printk(search_msg, name, addr);
 
-	if (((orig_HCR == 0xff) && (orig_HSR == 0xff)) ||
-	    ((orig_HCR & DIR) != (orig_HSR & DIR))) {
+	if (orig_HSR == 0xff) {
 		if (elp_debug > 0)
 			printk(notfound_msg, 1);
-		return -1;	/* It can't be 3c505 if HCR.DIR != HSR.DIR */
+		return -1;	
 	}
 	/* Enable interrupts - we need timers! */
 	save_flags(flags);
@@ -1409,34 +1371,32 @@
 	/* Wait for a while; the adapter may still be booting up */
 	if (elp_debug > 0)
 		printk(stilllooking_msg);
-	if (orig_HCR & DIR) {
+
+	if (orig_HSR & DIR) {
 		/* If HCR.DIR is up, we pull it down. HSR.DIR should follow. */
-		outb_control(orig_HCR & ~DIR, addr);
+		outb(0, dev->base_addr + PORT_CONTROL);
 		timeout = jiffies + 30;
 		while (jiffies < timeout);
 		restore_flags(flags);
 		if (inb_status(addr) & DIR) {
-			outb_control(orig_HCR, addr);
 			if (elp_debug > 0)
 				printk(notfound_msg, 2);
 			return -1;
 		}
 	} else {
 		/* If HCR.DIR is down, we pull it up. HSR.DIR should follow. */
-		outb_control(orig_HCR | DIR, addr);
+		outb(DIR, dev->base_addr + PORT_CONTROL);
 		timeout = jiffies + 30;
 		while (jiffies < timeout);
 		restore_flags(flags);
 		if (!(inb_status(addr) & DIR)) {
-			outb_control(orig_HCR, addr);
 			if (elp_debug > 0)
 				printk(notfound_msg, 3);
 			return -1;
 		}
 	}
 	/*
-	 * It certainly looks like a 3c505. If it has DMA enabled, it needs
-	 * a hard reset. Also, do a hard reset if selected at the compile time.
+	 * It certainly looks like a 3c505.
 	 */
 	if (elp_debug > 0)
 		printk(found_msg);
@@ -1516,8 +1476,10 @@
 		return -ENODEV;
         }
 
+	adapter->send_pcb_semaphore = 0;
+
 	for (tries1 = 0; tries1 < 3; tries1++) {
-		outb_control((inb_control(dev->base_addr) | CMDE) & ~DIR, dev->base_addr);
+		outb_control((adapter->hcr_val | CMDE) & ~DIR, dev);
 		/* First try to write just one byte, to see if the card is
 		 * responding at all normally.
 		 */
@@ -1549,8 +1511,8 @@
 					okay = 1;  /* It started */
 				}
 			} else {
-				/* Otherwise, it must just be in a strange state.  We probably
-				 * need to kick it.
+				/* Otherwise, it must just be in a strange
+				 * state.  We probably need to kick it.
 				 */
 				printk("3c505 is sulking\n");
 			}
@@ -1586,8 +1548,8 @@
 		 * and try again.
 		 */
 		printk(KERN_INFO "%s: resetting adapter\n", dev->name);
-		outb_control(inb_control(dev->base_addr) | FLSH | ATTN, dev->base_addr);
-		outb_control(inb_control(dev->base_addr) & ~(FLSH | ATTN), dev->base_addr);
+		outb_control(adapter->hcr_val | FLSH | ATTN, dev);
+		outb_control(adapter->hcr_val & ~(FLSH | ATTN), dev);
 	}
 	printk("%s: failed to initialise 3c505\n", dev->name);
 	return -ENODEV;
@@ -1597,15 +1559,14 @@
 		int rpt = autoirq_report(0);
 		if (dev->irq != rpt) {
 			printk("%s: warning, irq %d configured but %d detected\n", dev->name, dev->irq, rpt);
-			return -ENODEV;
 		}
 		/* if dev->irq == autoirq_report(0), all is well */
-	} else			/* No preset IRQ; just use what we can detect */
+	} else		       /* No preset IRQ; just use what we can detect */
 		dev->irq = autoirq_report(0);
-	switch (dev->irq) {	/* Legal, sane? */
+	switch (dev->irq) {    /* Legal, sane? */
 	case 0:
-		printk("%s: No IRQ reported by autoirq_report().\n", dev->name);
-		printk("%s: Check the jumpers of your 3c505 board.\n", dev->name);
+		printk("%s: IRQ probe failed: check 3c505 jumpers.\n",
+		       dev->name);
 		return -ENODEV;
 	case 1:
 	case 6:
@@ -1619,7 +1580,7 @@
 	 *  Now we have the IRQ number so we can disable the interrupts from
 	 *  the board until the board is opened.
 	 */
-	outb_control(inb_control(dev->base_addr) & ~CMDE, dev->base_addr);
+	outb_control(adapter->hcr_val & ~CMDE, dev);
 
 	/*
 	 * copy ethernet address into structure
@@ -1627,8 +1588,16 @@
 	for (i = 0; i < 6; i++)
 		dev->dev_addr[i] = adapter->rx_pcb.data.eth_addr[i];
 
-	/* set up the DMA channel */
-	dev->dma = ELP_DMA;
+	/* find a DMA channel */
+	if (!dev->dma) {
+		if (dev->mem_start) {
+			dev->dma = dev->mem_start & 7;
+		}
+		else {
+			printk(KERN_WARNING "%s: warning, DMA channel not specified, using default\n", dev->name); 
+			dev->dma = ELP_DMA;
+		}
+	}
 
 	/*
 	 * print remainder of startup message
@@ -1649,7 +1618,7 @@
 	    !receive_pcb(dev, &adapter->rx_pcb) ||
 	    (adapter->rx_pcb.command != CMD_ADAPTER_INFO_RESPONSE) ||
 	    (adapter->rx_pcb.length != 10)) {
-		printk("%s: not responding to second PCB\n", dev->name);
+		printk("not responding to second PCB\n");
 	}
 	printk("rev %d.%d, %dk\n", adapter->rx_pcb.data.info.major_vers, adapter->rx_pcb.data.info.minor_vers, adapter->rx_pcb.data.info.RAM_sz);
 
@@ -1687,46 +1656,61 @@
 }
 
 #ifdef MODULE
-static char devicename[9] = {0,};
-static struct device dev_3c505 =
+#define NAMELEN 9
+static char devicename[ELP_MAX_CARDS][NAMELEN] = {0,};
+static struct device dev_3c505[ELP_MAX_CARDS] =
 {
-	devicename,		/* device name is inserted by linux/drivers/net/net_init.c */
+	NULL,		/* device name is inserted by net_init.c */
 	0, 0, 0, 0,
 	0, 0,
 	0, 0, 0, NULL, elplus_probe};
 
-int io = 0x300;
-int irq = 0;
+static int io[ELP_MAX_CARDS] = { 0, };
+static int irq[ELP_MAX_CARDS] = { 0, };
+static int dma[ELP_MAX_CARDS] = { 0, };
 
 int init_module(void)
 {
-	if (io == 0)
-		printk("3c505: You should not use auto-probing with insmod!\n");
-	dev_3c505.base_addr = io;
-	dev_3c505.irq = irq;
-	if (register_netdev(&dev_3c505) != 0) {
-		return -EIO;
+	int this_dev, found = 0;
+
+	for (this_dev = 0; this_dev < ELP_MAX_CARDS; this_dev++) {
+		struct device *dev = &dev_3c505[this_dev];
+		dev->name = devicename[this_dev];
+		dev->irq = irq[this_dev];
+		dev->base_addr = io[this_dev];
+		if (dma[this_dev]) {
+			dev->dma = dma[this_dev];
+		} else {
+			dev->dma = ELP_DMA;
+			printk(KERN_WARNING "3c505.c: warning, using default DMA channel,\n");
+		}
+		if (io[this_dev] == 0) {
+			if (this_dev) break;
+			printk(KERN_NOTICE "3c505.c: module autoprobe not recommended, give io=xx.\n");
+		}
+		if (register_netdev(dev) != 0) {
+			printk(KERN_WARNING "3c505.c: Failed to register card at 0x%x.\n", io[this_dev]);
+			if (found != 0) return 0;
+			return -ENXIO;
+		}
+		found++;
 	}
 	return 0;
 }
 
 void cleanup_module(void)
 {
-	unregister_netdev(&dev_3c505);
-	kfree(dev_3c505.priv);
-	dev_3c505.priv = NULL;
-
-	/* If we don't do this, we can't re-insmod it later. */
-	release_region(dev_3c505.base_addr, ELP_IO_EXTENT);
+	int this_dev;
+        
+	for (this_dev = 0; this_dev < ELP_MAX_CARDS; this_dev++) {
+		struct device *dev = &dev_3c505[this_dev];
+		if (dev->priv != NULL) {
+			kfree(dev->priv);
+			dev->priv = NULL;
+			release_region(dev->base_addr, ELP_IO_EXTENT);
+			unregister_netdev(dev);
+		}
+	}
 }
 
 #endif				/* MODULE */
-
-
-/*
- * Local Variables:
- *  c-file-style: "linux"
- *  tab-width: 8
- *  compile-command: "gcc -D__KERNEL__ -I/discs/bibble/src/linux-1.3.69/include  -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -fno-strength-reduce -pipe -m486 -DCPU=486 -DMODULE  -c 3c505.c"
- * End:
- */
--- clean-linux/linux/drivers/net/3c505.h	Tue Aug 15 16:08:32 1995
+++ linux/drivers/net/3c505.h	Wed Sep 25 17:18:40 1996
@@ -4,6 +4,10 @@
  *
  *****************************************************************/
 
+#define ELP_DMA       6
+#define ELP_RX_PCBS   4
+#define ELP_MAX_CARDS 4
+
 /*
  * I/O register offsets
  */
@@ -243,3 +247,46 @@
 #define NO_LOOPBACK	0x00
 #define INT_LOOPBACK	0x08
 #define EXT_LOOPBACK	0x10
+
+/*****************************************************************
+ *
+ *  structure to hold context information for adapter
+ *
+ *****************************************************************/
+
+#define DMA_BUFFER_SIZE  1600
+#define BACKLOG_SIZE      4
+
+typedef struct {
+	volatile short got[NUM_TRANSMIT_CMDS];	/* flags for
+						   command completion */
+	pcb_struct tx_pcb;	/* PCB for foreground sending */
+	pcb_struct rx_pcb;	/* PCB for foreground receiving */
+	pcb_struct itx_pcb;	/* PCB for background sending */
+	pcb_struct irx_pcb;	/* PCB for background receiving */
+	struct enet_statistics stats;
+
+	void *dma_buffer;
+
+	struct {
+		unsigned int length[BACKLOG_SIZE];
+		unsigned int in;
+		unsigned int out;
+	} rx_backlog;
+
+	struct {
+		unsigned int direction;
+		unsigned int length;
+		struct sk_buff *skb;
+	        void *target;
+		long int start_time;
+	} current_dma;
+
+	/* flags */
+	unsigned long send_pcb_semaphore;
+	unsigned int dmaing;
+	unsigned long busy;
+
+	unsigned int rx_active;  /* number of receive PCBs */
+        volatile unsigned char hcr_val;  /* what we think the HCR contains */
+} elp_device;
--- clean-linux/linux/Documentation/networking/net-modules.txt	Mon May 13 23:55:50 1996
+++ linux/Documentation/networking/net-modules.txt	Wed Sep 25 17:25:55 1996
@@ -89,8 +89,9 @@
 	(Probes ports: 0x300, 0x310, 0x330, 0x350, 0x250, 0x280, 0x2A0, 0x2E0)
 
 3c505.c:
-	io = 0x300
+	io = 0
 	irq = 0
+	dma = 6         (not autoprobed)
 	(Probes ports: 0x300, 0x280, 0x310)
 
 3c507.c:
--- clean-linux/linux/Documentation/networking/3c505.txt	Mon May 13 23:55:50 1996
+++ linux/Documentation/networking/3c505.txt	Wed Sep 25 17:32:33 1996
@@ -1,26 +1,37 @@
 The 3Com Etherlink Plus (3c505) driver.
 
 This driver now uses DMA.  There is currently no support for PIO operation.
-The default DMA channel is 6, and is set at compile time.
+The default DMA channel is 6; this is _not_ autoprobed, so you must
+make sure you configure it correctly.  If loading the driver as a
+module, you can do this with "modprobe 3c505 dma=n".  If the driver is
+linked statically into the kernel, you must either use an "ether="
+statement on the command line, or change the definition of ELP_DMA in 3c505.h.
+
+The driver will warn you if it has to fall back on the compiled in
+default DMA channel. 
 
 If no base address is given at boot time, the driver will autoprobe
 ports 0x300, 0x280 and 0x310 (in that order).  If no IRQ is given, the driver
 will try to probe for it.
 
 The driver can be used as a loadable module.  See net-modules.txt for details
-of the parameters it can take.
-
-At the moment, the driver probably won't work with old (revision 2) hardware.
+of the parameters it can take.  
 
-There is one compile-time setting in the CONFIG file:
-ELP_DEBUG
- The driver debug level.  It's probably best to leave it at 0 most of the time.
- If you are having trouble, setting it to 1 may give you more information.
- Any higher setting is too verbose for most purposes.
+Theoretically, one instance of the driver can now run multiple cards,
+in the standard way (when loading a module, say "modprobe 3c505
+io=0x300,0x340 irq=10,11 dma=6,7" or whatever).  I have not tested
+this, though.
+
+The driver may now support revision 2 hardware; the dependency on
+being able to read the host control register has been removed.  This
+is also untested, since I don't have a suitable card.
+
+Known problems:
+ I still see "DMA upload timed out" messages from time to time.  These
+seem to be fairly non-fatal though.
+ The card is old and slow.
 
 To do:
- Support for old boards
- Make DMA configurable at run time
  Improve probe/setup code
  Test multicast and promiscuous operation
 
@@ -31,3 +42,5 @@
  IRQ/address detection, some changes) and this README by
  Juha Laiho <jlaiho@ichaos.nullnet.fi>.
  DMA mode, more fixes, etc, by Philip Blundell <pjb27@cam.ac.uk>
+ Multicard support, Software configurable DMA, etc., by
+ Christopher Collins <ccollins@pcug.org.au>


home help back first fref pref prev next nref lref last post