patch-2.1.53 linux/drivers/net/com90xx.c

Next file: linux/drivers/net/defxx.c
Previous file: linux/drivers/net/com90io.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.1.52/linux/drivers/net/com90xx.c linux/drivers/net/com90xx.c
@@ -0,0 +1,1335 @@
+/* com90xx.c:
+	Derived from the original arcnet.c,
+	Written 1994-1996 by Avery Pennarun,
+	which was in turn derived from skeleton.c by Donald Becker.
+
+	Contact Avery at: apenwarr@foxnet.net or
+	RR #5 Pole Line Road, Thunder Bay, ON, Canada P7C 5M9
+
+	**********************
+
+	The original copyright of skeleton.c was as follows:
+
+	skeleton.c Written 1993 by Donald Becker.
+	Copyright 1993 United States Government as represented by the
+        Director, National Security Agency.  This software may only be used
+        and distributed according to the terms of the GNU Public License as
+        modified by SRC, incorporated herein by reference.
+
+	**********************
+
+	For more details, see drivers/net/arcnet.c
+
+	**********************
+*/
+
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/version.h>
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/interrupt.h>
+#include <linux/ptrace.h>
+#include <linux/ioport.h>
+#include <linux/in.h>
+#include <linux/malloc.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/if_arcnet.h>
+#include <linux/arcdevice.h>
+
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+
+#include <net/arp.h>
+
+/**************************************************************************/
+
+/* On a fast computer, the buffer copy from memory to the ARCnet card during
+ * a transmit can hog the bus just a little too long.  SLOW_XMIT_COPY
+ * replaces the fast memcpy() with a slower for() loop that seems to solve
+ * my problems with ftape.
+ *
+ * Probably a better solution would be to use memcpy_toio (more portable
+ * anyway) and modify that routine to support REALLY_SLOW_IO-style
+ * defines; ARCnet probably is not the only driver that can screw up an
+ * ftape DMA transfer.
+ *
+ * Turn this on if you have timing-sensitive DMA (ie. a tape drive) and
+ * would like to sacrifice a little bit of network speed to reduce tape
+ * write retries or some related problem.
+ */
+#undef SLOW_XMIT_COPY
+
+
+/* Define this to speed up the autoprobe by assuming if only one io port and
+ * shmem are left in the list at Stage 5, they must correspond to each
+ * other.
+ *
+ * This is undefined by default because it might not always be true, and the
+ * extra check makes the autoprobe even more careful.  Speed demons can turn
+ * it on - I think it should be fine if you only have one ARCnet card
+ * installed.
+ *
+ * If no ARCnet cards are installed, this delay never happens anyway and thus
+ * the option has no effect.
+ */
+#undef FAST_PROBE
+
+
+
+
+
+
+/* External functions from arcnet.c */
+
+
+
+#if ARCNET_DEBUG_MAX & D_SKB
+extern void arcnet_dump_skb(struct device *dev,struct sk_buff *skb,
+			    char *desc);
+#else
+#define arcnet_dump_skb(dev,skb,desc) ;
+#endif
+
+#if (ARCNET_DEBUG_MAX & D_RX) || (ARCNET_DEBUG_MAX & D_TX)
+extern void arcnet_dump_packet(struct device *dev,u_char *buffer,int ext,
+			       char *desc);
+#else
+#define arcnet_dump_packet(dev,buffer,ext,desc) ;
+#endif
+
+
+extern void arcnet_tx_done(struct device *dev, struct arcnet_local *lp);
+extern void arcnet_makename(char *device);
+extern void arcnet_interrupt(int irq,void *dev_id,struct pt_regs *regs);
+extern void arcnet_setup(struct device *dev);
+extern int arcnet_go_tx(struct device *dev,int enable_irq);
+extern void arcnetA_continue_tx(struct device *dev);
+extern void arcnet_rx(struct arcnet_local *lp, u_char *arcsoft, short length, int saddr, int daddr);
+extern void arcnet_use_count(int open);
+
+
+
+/* Internal function declarations */
+#ifdef MODULE 
+static 
+#endif
+       int arc90xx_probe(struct device *dev);
+static void arc90xx_rx(struct device *dev,int recbuf);
+static int arc90xx_found(struct device *dev,int ioaddr,int airq,u_long shmem); 
+static void arc90xx_inthandler (struct device *dev);
+static int arc90xx_reset (struct device *dev, int reset_delay);
+static void arc90xx_setmask (struct device *dev, u_char mask);
+static void arc90xx_command (struct device *dev, u_char command);
+static u_char arc90xx_status (struct device *dev);
+static void arc90xx_prepare_tx(struct device *dev,u_char *hdr,int hdrlen,
+		      char *data,int length,int daddr,int exceptA, int offset);
+static  void arc90xx_openclose(int open);
+
+
+/* Module parameters */
+
+#ifdef MODULE
+static int io=0x0;	/* <--- EDIT THESE LINES FOR YOUR CONFIGURATION */
+static int irq=0;	/* or use the insmod io= irq= shmem= options */
+static int shmem=0;
+static char *device;	/* use eg. device="arc1" to change name */
+
+MODULE_PARM(io, "i");
+MODULE_PARM(irq, "i");
+MODULE_PARM(shmem, "i");
+MODULE_PARM(device, "s");
+#else
+__initfunc(void com90xx_setup (char *str, int *ints));
+char __initdata com90xx_explicit=0;
+
+extern struct device arcnet_devs[];
+extern char arcnet_dev_names[][10];
+extern int arcnet_num_devs;
+
+#endif
+
+
+
+
+/* Handy defines for ARCnet specific stuff */
+
+/* The number of low I/O ports used by the ethercard. */
+#define ARCNET_TOTAL_SIZE	16
+
+	/* COM 9026 controller chip --> ARCnet register addresses */
+#define _INTMASK (ioaddr+0)	/* writable */
+#define _STATUS  (ioaddr+0)	/* readable */
+#define _COMMAND (ioaddr+1)	/* writable, returns random vals on read (?) */
+#define _RESET  (ioaddr+8)	/* software reset (on read) */
+#define _MEMDATA  (ioaddr+12)            /* Data port for IO-mapped memory */
+#define _ADDR_HI  (ioaddr+15)            /* Control registers for said */
+#define _ADDR_LO  (ioaddr+14)
+#define _CONFIG  (ioaddr+2)      /* Configuration register */
+
+#define RDDATAflag      0x00     /* Next access is a read/~write */
+
+  #define ARCSTATUS	inb(_STATUS)
+  #define ACOMMAND(cmd) outb((cmd),_COMMAND)
+  #define AINTMASK(msk)	outb((msk),_INTMASK)
+  #define SETCONF	outb(lp->config,_CONFIG)
+  #define ARCRESET	inb(_RESET)
+
+static const char *version =
+ "com90xx.c: v2.91 97/08/19 Avery Pennarun <apenwarr@bond.net> et al.\n";
+
+
+/****************************************************************************
+ *                                                                          *
+ * Probe and initialization                                                 *
+ *                                                                          *
+ ****************************************************************************/
+
+/* Check for an ARCnet network adaptor, and return '0' if one exists.
+ *  If dev->base_addr == 0, probe all likely locations.
+ *  If dev->base_addr == 1, always return failure.
+ *  If dev->base_addr == 2, allocate space for the device and return success
+ *  			    (detachable devices only).
+ *
+ * NOTE: the list of possible ports/shmems is static, so it is retained
+ * across calls to arcnet_probe.  So, if more than one ARCnet probe is made,
+ * values that were discarded once will not even be tried again.
+ *
+ * FIXME: grab all devices in one shot and eliminate the big static array.
+ */
+
+static int ports[(0x3f0 - 0x200) / 16 + 1] __initdata = { 0 };
+static u_long shmems[(0xFF800 - 0xA0000) / 2048 + 1] __initdata = { 0 };
+
+__initfunc(int arc90xx_probe(struct device *dev))
+{
+  static int init_once = 0;
+  static int numports=sizeof(ports)/sizeof(ports[0]),
+    numshmems=sizeof(shmems)/sizeof(shmems[0]);
+  
+  int count,status,delayval,ioaddr,numprint,airq,retval=-ENODEV,
+    openparen=0;
+  unsigned long airqmask;
+  int *port;
+  u_long *shmem;
+  
+  if (!init_once)
+    {
+      for (count=0x200; count<=0x3f0; count+=16)
+	ports[(count-0x200)/16] = count;
+      for (count=0xA0000; count<=0xFF800; count+=2048)
+	shmems[(count-0xA0000)/2048] = count;
+      init_once=1;
+    }
+  
+  BUGLVL(D_NORMAL) printk(version);
+  BUGMSG(D_DURING,"space used for probe buffers: %d+%d=%d bytes\n",
+	 sizeof(ports),sizeof(shmems),
+	 sizeof(ports)+sizeof(shmems));
+  
+ 
+  
+  BUGMSG(D_INIT,"given: base %lXh, IRQ %d, shmem %lXh\n",
+	 dev->base_addr,dev->irq,dev->mem_start);
+  
+  if (dev->base_addr > 0x1ff)	/* Check a single specified port */
+    {
+      ports[0]=dev->base_addr;
+      numports=1;
+    }
+  else if (dev->base_addr > 0)	/* Don't probe at all. */
+    return -ENXIO;
+  
+  if (dev->mem_start)
+    {
+      shmems[0]=dev->mem_start;
+      numshmems=1;
+    }
+  
+  
+  /* Stage 1: abandon any reserved ports, or ones with status==0xFF
+	 * (empty), and reset any others by reading the reset port.
+	 */
+  BUGMSG(D_INIT,"Stage 1: ");
+  numprint=0;
+  for (port = &ports[0]; port-ports<numports; port++)
+    {
+      numprint++;
+      if (numprint>8)
+	{
+	  BUGMSG2(D_INIT,"\n");
+	  BUGMSG(D_INIT,"Stage 1: ");
+	  numprint=1;
+	}
+      BUGMSG2(D_INIT,"%Xh ",*port);
+      
+      ioaddr=*port;
+      
+      if (check_region(*port, ARCNET_TOTAL_SIZE))
+	{
+	  BUGMSG2(D_INIT_REASONS,"(check_region)\n");
+	  BUGMSG(D_INIT_REASONS,"Stage 1: ");
+	  BUGLVL(D_INIT_REASONS) numprint=0;
+	  *port=ports[numports-1];
+	  numports--;
+	  port--;
+	  continue;
+	}
+      
+      if (ARCSTATUS == 0xFF)
+	{
+			BUGMSG2(D_INIT_REASONS,"(empty)\n");
+			BUGMSG(D_INIT_REASONS,"Stage 1: ");
+			BUGLVL(D_INIT_REASONS) numprint=0;
+			*port=ports[numports-1];
+			numports--;
+			port--;
+			continue;
+	}
+      
+      ARCRESET;	/* begin resetting card */
+
+      BUGMSG2(D_INIT_REASONS,"\n");
+      BUGMSG(D_INIT_REASONS,"Stage 1: ");
+      BUGLVL(D_INIT_REASONS) numprint=0;
+    }
+  BUGMSG2(D_INIT,"\n");
+  
+  if (!numports)
+    {
+      BUGMSG(D_NORMAL,"Stage 1: No ARCnet cards found.\n");
+      return -ENODEV;
+    }
+  
+  
+  /* Stage 2: we have now reset any possible ARCnet cards, so we can't
+   * do anything until they finish.  If D_INIT, print the list of
+   * cards that are left.
+   */
+	BUGMSG(D_INIT,"Stage 2: ");
+	numprint=0;
+	for (port = &ports[0]; port-ports<numports; port++)
+	  {
+	    numprint++;
+	    if (numprint>8)
+	      {
+		BUGMSG2(D_INIT,"\n");
+		BUGMSG(D_INIT,"Stage 2: ");
+		numprint=1;
+	      }
+	    BUGMSG2(D_INIT,"%Xh ",*port);
+	  }
+	BUGMSG2(D_INIT,"\n");
+	JIFFER(RESETtime);
+	
+	
+	/* Stage 3: abandon any shmem addresses that don't have the signature
+	 * 0xD1 byte in the right place, or are read-only.
+	 */
+	BUGMSG(D_INIT,"Stage 3: ");
+	numprint=0;
+	for (shmem = &shmems[0]; shmem-shmems<numshmems; shmem++)
+	  {
+	    u_long ptr;
+	    
+		numprint++;
+		if (numprint>8)
+		  {
+		    BUGMSG2(D_INIT,"\n");
+		    BUGMSG(D_INIT,"Stage 3: ");
+		    numprint=1;
+		  }
+		BUGMSG2(D_INIT,"%lXh ",*shmem);
+		
+		ptr=(u_long)(*shmem);
+		
+		if (readb(ptr) != TESTvalue)
+		{
+		  BUGMSG2(D_INIT_REASONS,"(mem=%02Xh, not %02Xh)\n",
+			  readb(ptr),TESTvalue);
+		  BUGMSG(D_INIT_REASONS,"Stage 3: ");
+		  BUGLVL(D_INIT_REASONS) numprint=0;
+		  *shmem=shmems[numshmems-1];
+		  numshmems--;
+		  shmem--;
+		  continue;
+		}
+		
+		/* By writing 0x42 to the TESTvalue location, we also make
+		 * sure no "mirror" shmem areas show up - if they occur
+		 * in another pass through this loop, they will be discarded
+		 * because *cptr != TESTvalue.
+		 */
+		writeb(0x42,ptr);
+		if (readb(ptr) != 0x42)
+		  {
+		    BUGMSG2(D_INIT_REASONS,"(read only)\n");
+		    BUGMSG(D_INIT_REASONS,"Stage 3: ");
+		    *shmem=shmems[numshmems-1];
+		    numshmems--;
+		    shmem--;
+		    continue;
+		  }
+		
+		BUGMSG2(D_INIT_REASONS,"\n");
+		BUGMSG(D_INIT_REASONS,"Stage 3: ");
+		BUGLVL(D_INIT_REASONS) numprint=0;
+	  }
+	BUGMSG2(D_INIT,"\n");
+	
+	if (!numshmems)
+	  {
+	    BUGMSG(D_NORMAL,"Stage 3: No ARCnet cards found.\n");
+	    return -ENODEV;
+	  }
+	
+	/* Stage 4: something of a dummy, to report the shmems that are
+	 * still possible after stage 3.
+	 */
+	BUGMSG(D_INIT,"Stage 4: ");
+	numprint=0;
+	for (shmem = &shmems[0]; shmem-shmems<numshmems; shmem++)
+	  {
+	    numprint++;
+	    if (numprint>8)
+	      {
+		BUGMSG2(D_INIT,"\n");
+		BUGMSG(D_INIT,"Stage 4: ");
+		numprint=1;
+	      }
+	    BUGMSG2(D_INIT,"%lXh ",*shmem);
+	  }
+	BUGMSG2(D_INIT,"\n");
+	
+	
+	/* Stage 5: for any ports that have the correct status, can disable
+	 * the RESET flag, and (if no irq is given) generate an autoirq,
+	 * register an ARCnet device.
+	 *
+	 * Currently, we can only register one device per probe, so quit
+	 * after the first one is found.
+	 */
+	BUGMSG(D_INIT,"Stage 5: ");
+	numprint=0;
+	for (port = &ports[0]; port-ports<numports; port++)
+	  {
+	    numprint++;
+	    if (numprint>8)
+	      {
+		BUGMSG2(D_INIT,"\n");
+		BUGMSG(D_INIT,"Stage 5: ");
+		numprint=1;
+	      }
+	    BUGMSG2(D_INIT,"%Xh ",*port);
+	    
+	    ioaddr=*port;
+	    status=ARCSTATUS;
+	    
+	    if ((status & 0x9D)
+		!= (NORXflag|RECONflag|TXFREEflag|RESETflag))
+	      {
+		BUGMSG2(D_INIT_REASONS,"(status=%Xh)\n",status);
+		BUGMSG(D_INIT_REASONS,"Stage 5: ");
+		BUGLVL(D_INIT_REASONS) numprint=0;
+		*port=ports[numports-1];
+		numports--;
+		port--;
+		continue;
+		}
+	    
+	    ACOMMAND(CFLAGScmd|RESETclear|CONFIGclear);
+	    status=ARCSTATUS;
+	    if (status & RESETflag)
+	      {
+		BUGMSG2(D_INIT_REASONS," (eternal reset, status=%Xh)\n",
+			status);
+		BUGMSG(D_INIT_REASONS,"Stage 5: ");
+		BUGLVL(D_INIT_REASONS) numprint=0;
+			*port=ports[numports-1];
+			numports--;
+			port--;
+			continue;
+	      }
+	    
+	    /* skip this completely if an IRQ was given, because maybe
+	     * we're on a machine that locks during autoirq!
+	     */
+		if (!dev->irq)
+		  {
+		    /* if we do this, we're sure to get an IRQ since the
+		     * card has just reset and the NORXflag is on until
+		     * we tell it to start receiving.
+		     */
+		    airqmask = probe_irq_on();
+		    AINTMASK(NORXflag);
+		    udelay(1);
+		    AINTMASK(0);
+		    airq = probe_irq_off(airqmask);
+
+		    if (airq<=0)
+		      {
+			BUGMSG2(D_INIT_REASONS,"(airq=%d)\n",airq);
+			BUGMSG(D_INIT_REASONS,"Stage 5: ");
+			BUGLVL(D_INIT_REASONS) numprint=0;
+			*port=ports[numports-1];
+			numports--;
+			port--;
+				continue;
+		      }
+		  }
+		else
+		{
+		  airq=dev->irq;
+		}
+		
+		BUGMSG2(D_INIT,"(%d,", airq);
+		openparen=1;
+		
+		/* Everything seems okay.  But which shmem, if any, puts
+		 * back its signature byte when the card is reset?
+		 *
+		 * If there are multiple cards installed, there might be
+		 * multiple shmems still in the list.
+		 */
+#ifdef FAST_PROBE
+		if (numports>1 || numshmems>1)
+		  {
+		    ARCRESET;
+		    JIFFER(RESETtime);
+		  }
+		else
+		  {
+		    /* just one shmem and port, assume they match */
+		    writeb(TESTvalue,shmems[0]);
+		  }
+#else
+		ARCRESET;
+		JIFFER(RESETtime);
+#endif
+		
+		
+		for (shmem = &shmems[0]; shmem-shmems<numshmems; shmem++)
+		  {
+		    u_long ptr;
+		    ptr=(u_long)(*shmem);
+		    
+		    if (readb(ptr) == TESTvalue)	/* found one */
+		      {
+			BUGMSG2(D_INIT,"%lXh)\n", *shmem);
+			openparen=0;
+			
+			/* register the card */
+			retval=arc90xx_found(dev,*port,airq,*shmem);
+			if (retval) openparen=0;
+			
+			/* remove shmem from the list */
+			*shmem=shmems[numshmems-1];
+			numshmems--;
+			
+			break;
+		      }
+		    else
+		      {
+			BUGMSG2(D_INIT_REASONS,"%Xh-", readb(ptr));
+		      }
+		  }
+		
+		if (openparen)
+		  {
+		    BUGMSG2(D_INIT,"no matching shmem)\n");
+		    BUGMSG(D_INIT_REASONS,"Stage 5: ");
+		    BUGLVL(D_INIT_REASONS) numprint=0;
+		  }
+		
+		*port=ports[numports-1];
+		numports--;
+		port--;
+		
+		if (!retval) break;
+	  }
+	BUGMSG(D_INIT_REASONS,"\n");
+	
+	/* Now put back TESTvalue on all leftover shmems.
+	 */
+	for (shmem = &shmems[0]; shmem-shmems<numshmems; shmem++)
+	  writeb(TESTvalue,*shmem);
+	
+	if (retval) BUGMSG(D_NORMAL,"Stage 5: No ARCnet cards found.\n");
+	return retval;
+}
+
+/* Set up the struct device associated with this card.  Called after
+ * probing succeeds.
+ */
+__initfunc(int arc90xx_found(struct device *dev,int ioaddr,int airq, u_long shmem))
+{
+  struct arcnet_local *lp;
+  u_long first_mirror,last_mirror;
+  int mirror_size;
+  
+  /* reserve the irq */
+  if (request_irq(airq,&arcnet_interrupt,0,"arcnet (90xx)",NULL))
+    {
+      BUGMSG(D_NORMAL,"Can't get IRQ %d!\n",airq);
+      return -ENODEV;
+    }
+  irq2dev_map[airq]=dev;
+  dev->irq=airq;
+  
+  
+  /* reserve the I/O region - guaranteed to work by check_region */
+  request_region(ioaddr,ARCNET_TOTAL_SIZE,"arcnet (90xx)");
+  dev->base_addr=ioaddr;
+  
+  /* find the real shared memory start/end points, including mirrors */
+#define BUFFER_SIZE (512)
+#define MIRROR_SIZE (BUFFER_SIZE*4)
+  
+  /* guess the actual size of one "memory mirror" - the number of
+	 * bytes between copies of the shared memory.  On most cards, it's
+	 * 2k (or there are no mirrors at all) but on some, it's 4k.
+	 */
+  mirror_size=MIRROR_SIZE;
+  if (readb(shmem)==TESTvalue
+      && readb(shmem-mirror_size)!=TESTvalue
+      && readb(shmem-2*mirror_size)==TESTvalue)
+    mirror_size*=2;
+  
+  first_mirror=last_mirror=shmem;
+  while (readb(first_mirror)==TESTvalue) first_mirror-=mirror_size;
+  first_mirror+=mirror_size;
+  
+  while (readb(last_mirror)==TESTvalue) last_mirror+=mirror_size;
+  last_mirror-=mirror_size;
+  
+  dev->mem_start=first_mirror;
+  dev->mem_end=last_mirror+MIRROR_SIZE-1;
+  dev->rmem_start=dev->mem_start+BUFFER_SIZE*0;
+  dev->rmem_end=dev->mem_start+BUFFER_SIZE*2-1;
+  
+	/* Initialize the rest of the device structure. */
+  
+  dev->priv = kmalloc(sizeof(struct arcnet_local), GFP_KERNEL);
+  if (dev->priv == NULL)
+    {
+      irq2dev_map[airq] = NULL;
+      free_irq(airq,NULL);
+      release_region(ioaddr,ARCNET_TOTAL_SIZE);
+      return -ENOMEM;
+    }
+  memset(dev->priv,0,sizeof(struct arcnet_local));
+  lp=(struct arcnet_local *)(dev->priv);
+  lp->card_type = ARC_90xx;
+  lp->card_type_str = "COM 90xx";
+  lp->arcnet_reset=arc90xx_reset;
+  lp->asetmask=arc90xx_setmask;
+  lp->astatus=arc90xx_status;
+  lp->acommand=arc90xx_command;
+  lp->openclose_device=arc90xx_openclose;
+  lp->prepare_tx=arc90xx_prepare_tx;
+  lp->inthandler=arc90xx_inthandler;
+  
+  
+  /* Fill in the fields of the device structure with generic
+   * values.
+   */
+  arcnet_setup(dev);
+  
+  /* And now fill particular fields with arcnet values */
+  dev->mtu=1500; /* completely arbitrary - agrees with ether, though */
+  dev->hard_header_len=sizeof(struct ClientData);
+  lp->sequence=1;
+  lp->recbuf=0;
+  
+  BUGMSG(D_DURING,"ClientData header size is %d.\n",
+	 sizeof(struct ClientData));
+  BUGMSG(D_DURING,"HardHeader size is %d.\n",
+	 sizeof(struct archdr));
+  
+  /* get and check the station ID from offset 1 in shmem */
+  lp->stationid = readb(first_mirror+1);
+  
+  if (lp->stationid==0)
+    BUGMSG(D_NORMAL,"WARNING!  Station address 00 is reserved "
+	   "for broadcasts!\n");
+  else if (lp->stationid==255)
+    BUGMSG(D_NORMAL,"WARNING!  Station address FF may confuse "
+	   "DOS networking programs!\n");
+  dev->dev_addr[0]=lp->stationid;
+  
+  BUGMSG(D_NORMAL,"ARCnet COM90xx: station %02Xh found at %03lXh, IRQ %d, "
+	 "ShMem %lXh (%ld*%xh).\n",
+	 lp->stationid,
+	 dev->base_addr, dev->irq, dev->mem_start,
+	 (dev->mem_end-dev->mem_start+1)/mirror_size,mirror_size);
+
+#ifdef CONFIG_ARCNET_COM90xx
+  /* OK. We're finished. Now do we need to probe for other cards? */
+
+  if (!com90xx_explicit)
+    {
+      /* We need to check whether there's another card here.
+       * Just add another COM90xx driver to the device chain.
+       */
+      
+      if (arcnet_num_devs < MAX_ARCNET_DEVS)
+	{
+	  arcnet_devs[arcnet_num_devs].next=dev->next;
+	  dev->next=&arcnet_devs[arcnet_num_devs];
+	  dev->next->name=(char *)&arcnet_dev_names[arcnet_num_devs];
+	  arcnet_num_devs++;
+	}
+      else
+	{
+	  while (dev->next) dev=dev->next;
+	  
+	  dev->next=kmalloc(sizeof(struct device), GFP_KERNEL);
+	  if (!dev->next)
+	    {
+	      BUGMSG(D_NORMAL, "No memory for allocating next device - no more "
+		     "will be probed for.\n");
+	      return 0;
+	    }
+	  memset(dev->next,0,sizeof(struct device));
+	  dev->next->name=kmalloc(10, GFP_KERNEL);
+	  if (!dev->next->name)
+	    {
+	      BUGMSG(D_NORMAL, "No memory for allocating next device - no more "
+		     "will be probed for.\n");
+	      kfree(dev->next);
+	      dev->next=NULL;
+	      return 0;
+	    }
+	}
+      arcnet_makename(dev->next->name);
+      dev->next->init=arc90xx_probe;      
+    }
+#endif
+
+  return 0;
+}
+
+
+
+/* Do a hardware reset on the card, and set up necessary registers.
+ *
+ * This should be called as little as possible, because it disrupts the
+ * token on the network (causes a RECON) and requires a significant delay.
+ *
+ * However, it does make sure the card is in a defined state.
+ */
+int arc90xx_reset(struct device *dev,int reset_delay)
+{
+	struct arcnet_local *lp=(struct arcnet_local *)dev->priv;
+	short ioaddr=dev->base_addr;
+	int delayval,recbuf=lp->recbuf;
+
+  if (reset_delay==3)
+    {
+      ARCRESET;
+      return 0;
+    }
+  
+	/* no IRQ's, please! */
+	lp->intmask=0;
+	SETMASK;
+
+	BUGMSG(D_INIT,"Resetting %s (status=%Xh)\n",
+			dev->name,ARCSTATUS);
+
+	if (reset_delay)
+	{
+		/* reset the card */
+		ARCRESET;
+		JIFFER(RESETtime);
+	}
+
+	ACOMMAND(CFLAGScmd|RESETclear); /* clear flags & end reset */
+	ACOMMAND(CFLAGScmd|CONFIGclear);
+
+	/* verify that the ARCnet signature byte is present */
+	if (readb(dev->mem_start) != TESTvalue)
+	{
+		BUGMSG(D_NORMAL,"reset failed: TESTvalue not present.\n");
+		return 1;
+	}
+
+	/* clear out status variables */
+	recbuf=lp->recbuf=0;
+	lp->txbuf=2;
+
+	/* enable extended (512-byte) packets */
+	ACOMMAND(CONFIGcmd|EXTconf);
+
+#ifndef SLOW_XMIT_COPY
+	/* clean out all the memory to make debugging make more sense :) */
+	BUGLVL(D_DURING)
+		memset_io(dev->mem_start,0x42,2048);
+#endif
+
+	/* and enable receive of our first packet to the first buffer */
+	EnableReceiver();
+
+	/* re-enable interrupts */
+	lp->intmask|=NORXflag;
+#ifdef DETECT_RECONFIGS
+	lp->intmask|=RECONflag;
+#endif
+	SETMASK;
+
+	/* done!  return success. */
+	return 0;
+}
+
+static void arc90xx_openclose(int open)
+{
+ if (open)
+   MOD_INC_USE_COUNT;
+ else
+   MOD_DEC_USE_COUNT;
+}
+
+
+static void arc90xx_setmask(struct device *dev, u_char mask)
+{
+  short ioaddr=dev->base_addr;
+
+  AINTMASK(mask);
+}
+
+static u_char arc90xx_status(struct device *dev)
+{
+  short ioaddr=dev->base_addr;
+
+  return ARCSTATUS;
+}
+
+static void arc90xx_command(struct device *dev, u_char cmd)
+{
+  short ioaddr=dev->base_addr;
+
+  ACOMMAND(cmd);
+}
+
+
+/* The actual interrupt handler routine - handle various IRQ's generated
+ * by the card.
+ */
+static void
+arc90xx_inthandler(struct device *dev)
+{
+  struct arcnet_local *lp=(struct arcnet_local *)dev->priv;
+  int ioaddr=dev->base_addr, status, boguscount = 3, didsomething;
+  
+  
+  AINTMASK(0);
+  
+  BUGMSG(D_DURING,"in arcnet_inthandler (status=%Xh, intmask=%Xh)\n",
+	 ARCSTATUS,lp->intmask);
+  
+  do
+    {
+      status = ARCSTATUS;
+		didsomething=0;
+		
+		
+		/* RESET flag was enabled - card is resetting and if RX
+		 * is disabled, it's NOT because we just got a packet.
+		 */
+		if (status & RESETflag)
+		  {
+		    BUGMSG(D_NORMAL,"spurious reset (status=%Xh)\n",
+			   status);
+		    arc90xx_reset(dev,0);
+		    
+		    /* all other flag values are just garbage */
+		    break;
+		  }
+		
+		
+		/* RX is inhibited - we must have received something. */
+		if (status & lp->intmask & NORXflag)
+		  {
+		    int recbuf=lp->recbuf=!lp->recbuf;
+		    
+		    BUGMSG(D_DURING,"receive irq (status=%Xh)\n",
+			   status);
+		    
+		    /* enable receive of our next packet */
+		    EnableReceiver();
+		    
+		    /* Got a packet. */
+		    arc90xx_rx(dev,!recbuf);
+			
+		    didsomething++;
+		  }
+		
+		/* it can only be an xmit-done irq if we're xmitting :) */
+		/*if (status&TXFREEflag && !lp->in_txhandler && lp->sending)*/
+		if (status & lp->intmask & TXFREEflag)
+		  {
+		    struct Outgoing *out=&(lp->outgoing);
+		    int was_sending=lp->sending;
+		    
+		    lp->intmask &= ~TXFREEflag;
+		    
+			lp->in_txhandler++;
+			if (was_sending) lp->sending--;
+			
+			BUGMSG(D_DURING,"TX IRQ (stat=%Xh, numsegs=%d, segnum=%d, skb=%ph)\n",
+			       status,out->numsegs,out->segnum,out->skb);
+			
+			if (was_sending && !(status&TXACKflag))
+			  {
+			    if (lp->lasttrans_dest != 0)
+			      {
+				BUGMSG(D_EXTRA,"transmit was not acknowledged! (status=%Xh, dest=%02Xh)\n",
+				       status,lp->lasttrans_dest);
+				lp->stats.tx_errors++;
+				lp->stats.tx_carrier_errors++;
+			      }
+			    else
+			      {
+				BUGMSG(D_DURING,"broadcast was not acknowledged; that's normal (status=%Xh, dest=%02Xh)\n",
+				       status,
+				       lp->lasttrans_dest);
+			      }
+			  }
+			
+			/* send packet if there is one */
+			arcnet_go_tx(dev,0);
+			didsomething++;
+			
+			if (lp->intx)
+			  {
+			    BUGMSG(D_DURING,"TXDONE while intx! (status=%Xh, intx=%d)\n",
+				   ARCSTATUS,lp->intx);
+			    lp->in_txhandler--;
+			    continue;
+			  }
+			
+			if (!lp->outgoing.skb)
+			  {
+			    BUGMSG(D_DURING,"TX IRQ done: no split to continue.\n");
+			    
+			    /* inform upper layers */
+			    if (!lp->txready) arcnet_tx_done(dev, lp);
+			    lp->in_txhandler--;
+			    continue;
+			  }
+			
+			/* if more than one segment, and not all segments
+			 * are done, then continue xmit.
+			 */
+			if (out->segnum<out->numsegs)
+			  arcnetA_continue_tx(dev);
+			arcnet_go_tx(dev,0);
+			
+			/* if segnum==numsegs, the transmission is finished;
+			 * free the skb.
+			 */
+			if (out->segnum>=out->numsegs)
+			  {
+			    /* transmit completed */
+			    out->segnum++;
+			    if (out->skb)
+			      {
+				lp->stats.tx_bytes += out->skb->len;
+				dev_kfree_skb(out->skb,FREE_WRITE);
+			      }
+			    out->skb=NULL;
+			    
+			    /* inform upper layers */
+			    if (!lp->txready) arcnet_tx_done(dev, lp);
+			  }
+			didsomething++;
+			
+			lp->in_txhandler--;
+		  }
+		else if (lp->txready && !lp->sending && !lp->intx)
+		  {
+		    BUGMSG(D_NORMAL,"recovery from silent TX (status=%Xh)\n",
+			   status);
+		    arcnet_go_tx(dev,0);
+		    didsomething++;
+		  }
+
+#ifdef DETECT_RECONFIGS
+		if (status & (lp->intmask) & RECONflag)
+		  {
+		    ACOMMAND(CFLAGScmd|CONFIGclear);
+		    lp->stats.tx_carrier_errors++;
+		    
+#ifdef SHOW_RECONFIGS
+		    BUGMSG(D_NORMAL,"Network reconfiguration detected (status=%Xh)\n",
+			   status);
+		    
+		    
+#endif /* SHOW_RECONFIGS */
+		    
+#ifdef RECON_THRESHOLD
+		    /* is the RECON info empty or old? */
+		    if (!lp->first_recon || !lp->last_recon ||
+			jiffies-lp->last_recon > HZ*10)
+		      {
+			if (lp->network_down)
+			  BUGMSG(D_NORMAL,"reconfiguration detected: cabling restored?\n");
+			lp->first_recon=lp->last_recon=jiffies;
+			lp->num_recons=lp->network_down=0;
+			
+			BUGMSG(D_DURING,"recon: clearing counters.\n");
+		      }
+		    else /* add to current RECON counter */
+		      {
+			lp->last_recon=jiffies;
+			lp->num_recons++;
+			
+			BUGMSG(D_DURING,"recon: counter=%d, time=%lds, net=%d\n",
+			       lp->num_recons,
+			       (lp->last_recon-lp->first_recon)/HZ,
+			       lp->network_down);
+			
+			/* if network is marked up;
+			 * and first_recon and last_recon are 60+ sec
+			 *   apart;
+			 * and the average no. of recons counted is
+			 *   > RECON_THRESHOLD/min;
+			 * then print a warning message.
+			 */
+			if (!lp->network_down
+			    && (lp->last_recon-lp->first_recon)<=HZ*60
+			    && lp->num_recons >= RECON_THRESHOLD)
+			  {
+			    lp->network_down=1;
+			    BUGMSG(D_NORMAL,"many reconfigurations detected: cabling problem?\n");
+			  }
+			else if (!lp->network_down
+				 && lp->last_recon-lp->first_recon > HZ*60)
+			  {
+			    /* reset counters if we've gone for
+			     * over a minute.
+			     */
+			    lp->first_recon=lp->last_recon;
+			    lp->num_recons=1;
+			  }
+		      }
+		  }
+		else if (lp->network_down && jiffies-lp->last_recon > HZ*10)
+		  {
+		    if (lp->network_down)
+		      BUGMSG(D_NORMAL,"cabling restored?\n");
+		    lp->first_recon=lp->last_recon=0;
+		    lp->num_recons=lp->network_down=0;
+		    
+		    BUGMSG(D_DURING,"not recon: clearing counters anyway.\n");
+#endif
+		  }
+#endif /* DETECT_RECONFIGS */
+    } while (--boguscount && didsomething);
+  
+  BUGMSG(D_DURING,"net_interrupt complete (status=%Xh, count=%d)\n",
+	 ARCSTATUS,boguscount);
+  BUGMSG(D_DURING,"\n");
+  
+  SETMASK;	/* put back interrupt mask */
+  
+}
+
+
+
+/* A packet has arrived; grab it from the buffers and pass it to the generic
+ * arcnet_rx routing to deal with it.
+ */ 
+
+static void
+arc90xx_rx(struct device *dev,int recbuf)
+{
+  struct arcnet_local *lp = (struct arcnet_local *)dev->priv;
+  int ioaddr=dev->base_addr;
+  union ArcPacket *arcpacket=
+    (union ArcPacket *)phys_to_virt(dev->mem_start+recbuf*512);
+  u_char *arcsoft;
+  short length,offset;
+  u_char daddr,saddr;
+  
+  lp->stats.rx_packets++;
+  
+  saddr=arcpacket->hardheader.source;
+  
+  /* if source is 0, it's a "used" packet! */
+  if (saddr==0)
+    {
+      BUGMSG(D_NORMAL,"discarding old packet. (status=%Xh)\n",
+	     ARCSTATUS);
+      lp->stats.rx_errors++;
+      return;
+    }
+  /* Set source address to zero to mark it as old */
+  
+  arcpacket->hardheader.source=0;
+  
+  daddr=arcpacket->hardheader.destination;
+  
+  if (arcpacket->hardheader.offset1) /* Normal Packet */
+    {
+      offset=arcpacket->hardheader.offset1;
+      arcsoft=&arcpacket->raw[offset];
+      length=256-offset;
+    }
+  else		/* ExtendedPacket or ExceptionPacket */
+    {
+      offset=arcpacket->hardheader.offset2;
+      arcsoft=&arcpacket->raw[offset];
+      
+      length=512-offset;
+    }
+  
+  
+  arcnet_rx(lp, arcsoft, length, saddr, daddr);
+
+
+  BUGLVL(D_RX) arcnet_dump_packet(lp->adev,arcpacket->raw,length>240,"rx");
+   
+
+#ifndef SLOW_XMIT_COPY
+  /* clean out the page to make debugging make more sense :) */
+  BUGLVL(D_DURING)
+    memset((void *)arcpacket->raw,0x42,512);
+#endif
+  
+}
+
+
+
+
+/* Given an skb, copy a packet into the ARCnet buffers for later transmission
+ * by arcnet_go_tx.
+ */
+static void
+arc90xx_prepare_tx(struct device *dev,u_char *hdr,int hdrlen,
+		char *data,int length,int daddr,int exceptA, int offset)
+{
+  struct arcnet_local *lp = (struct arcnet_local *)dev->priv;
+  union ArcPacket *arcpacket =
+    (union ArcPacket *)phys_to_virt(dev->mem_start+512*(lp->txbuf^1));
+  
+#ifdef SLOW_XMIT_COPY
+  char *iptr,*iend,*optr;
+#endif
+  
+  lp->txbuf=lp->txbuf^1;	/* XOR with 1 to alternate between 2 and 3 */
+  
+  length+=hdrlen;
+  
+  BUGMSG(D_TX,"arcnetAS_prep_tx: hdr:%ph, length:%d, data:%ph\n",
+	 hdr,length,data);
+  
+#ifndef SLOW_XMIT_COPY
+  /* clean out the page to make debugging make more sense :) */
+  BUGLVL(D_DURING)
+    memset_io(dev->mem_start+lp->txbuf*512,0x42,512);
+#endif
+  
+  arcpacket->hardheader.destination=daddr;
+  
+  /* load packet into shared memory */
+  if (length<=MTU)	/* Normal (256-byte) Packet */
+    arcpacket->hardheader.offset1=offset=offset?offset:256-length;
+  
+  else if (length>=MinTU || offset)	/* Extended (512-byte) Packet */
+    {
+       arcpacket->hardheader.offset1=0;
+       arcpacket->hardheader.offset2=offset=offset?offset:512-length;   
+    }
+  else if (exceptA)		/* RFC1201 Exception Packet */
+    {
+       arcpacket->hardheader.offset1=0;
+       arcpacket->hardheader.offset2=offset=512-length-4;
+       
+       /* exception-specific stuff - these four bytes
+	* make the packet long enough to fit in a 512-byte
+	* frame.
+	*/
+       
+       arcpacket->raw[offset+0]=hdr[0];
+       arcpacket->raw[offset+1]=0xFF; /* FF flag */
+       arcpacket->raw[offset+2]=0xFF; /* FF padding */
+       arcpacket->raw[offset+3]=0xFF; /* FF padding */
+       offset+=4;
+    }
+  else				/* "other" Exception packet */
+    {
+      /* RFC1051 - set 4 trailing bytes to 0 */
+      memset(&arcpacket->raw[508],0,4);
+      
+      /* now round up to MinTU */
+      arcpacket->hardheader.offset1=0;
+      arcpacket->hardheader.offset2=offset=512-MinTU;
+    }
+  
+  
+  /* copy the packet into ARCnet shmem
+   *  - the first bytes of ClientData header are skipped
+   */
+
+  memcpy((u_char*)arcpacket+offset, (u_char*)hdr,hdrlen);
+#ifdef SLOW_XMIT_COPY
+  for (iptr=data,iend=iptr+length-hdrlen,optr=(char *)arcpacket+offset+hdrlen;
+       iptr<iend; iptr++,optr++)
+    {
+      *optr=*iptr;
+      /*udelay(5);*/
+    }
+#else
+  memcpy((u_char*)arcpacket+offset+hdrlen, data,length-hdrlen);
+#endif
+   
+  BUGMSG(D_DURING,"transmitting packet to station %02Xh (%d bytes)\n",
+	 daddr,length);
+  
+  BUGLVL(D_TX) arcnet_dump_packet(dev,arcpacket->raw,length>MTU,"tx");
+  
+  lp->lastload_dest=daddr;
+  lp->txready=lp->txbuf;	/* packet is ready for sending */
+}
+
+
+/****************************************************************************
+ *                                                                          *
+ * Kernel Loadable Module Support                                           *
+ *                                                                          *
+ ****************************************************************************/
+
+
+#ifdef MODULE
+
+static char devicename[9] = "";
+static struct device thiscard = {
+  devicename, /* device name is inserted by linux/drivers/net/net_init.c */
+  0, 0, 0, 0,
+  0, 0,  /* I/O address, IRQ */
+  0, 0, 0, NULL, arc90xx_probe
+};
+
+
+int init_module(void)
+{
+  struct device *dev=&thiscard;
+  if (device)
+    strcpy(dev->name,device);
+  else arcnet_makename(dev->name);
+  
+  dev->base_addr=io;
+  
+  dev->irq=irq;
+  if (dev->irq==2) dev->irq=9;
+  
+  if (shmem)
+    {
+      dev->mem_start=shmem;
+      dev->mem_end=thiscard.mem_start+512*4-1;
+      dev->rmem_start=thiscard.mem_start+512*0;
+      dev->rmem_end=thiscard.mem_start+512*2-1;
+    }
+  
+  if (register_netdev(dev) != 0)
+    return -EIO;
+  arcnet_use_count(1);
+  return 0;
+}
+
+void cleanup_module(void)
+{
+	struct device *dev=&thiscard;
+	int ioaddr=dev->mem_start;
+
+	if (dev->start) (*dev->stop)(dev);
+
+	/* Flush TX and disable RX */
+	if (ioaddr)
+	{
+		AINTMASK(0);		/* disable IRQ's */
+		ACOMMAND(NOTXcmd);	/* stop transmit */
+		ACOMMAND(NORXcmd);	/* disable receive */
+
+#if defined(IO_MAPPED_BUFFERS) && !defined(COM20020)
+		/* Set the thing back to MMAP mode, in case the old
+        		   driver is loaded later */
+		outb( (inb(_CONFIG)&~IOMAPflag),_CONFIG);
+#endif
+	}
+
+	if (dev->irq)
+	{
+		irq2dev_map[dev->irq] = NULL;
+		free_irq(dev->irq,NULL);
+	}
+
+	if (dev->base_addr) release_region(dev->base_addr,ARCNET_TOTAL_SIZE);
+	unregister_netdev(dev);
+	kfree(dev->priv);
+	dev->priv = NULL;
+	arcnet_use_count(0);
+}
+#else
+
+__initfunc(void com90xx_setup (char *str, int *ints))
+{
+  struct device *dev;
+
+  if (arcnet_num_devs == MAX_ARCNET_DEVS)
+    {
+      printk("com90xx: Too many ARCnet devices registered (max %d).\n",
+	     MAX_ARCNET_DEVS);
+      return;
+    }
+  
+  if (!ints[0] && (!str || !*str))
+    {
+      printk("com90xx: Disabled.\n");
+      com90xx_explicit++;
+      return;
+    }
+
+  dev=&arcnet_devs[arcnet_num_devs];
+  
+  dev->dev_addr[3]=3;
+  dev->init=arc90xx_probe;
+
+  switch(ints[0])
+    {
+    case 4: /* ERROR */
+      printk("com20020: Too many arguments.\n");
+
+    case 3: /* Mem address */
+      dev->mem_start=ints[3];
+
+    case 2: /* IRQ */
+      dev->irq=ints[2];
+      
+    case 1: /* IO address */
+      dev->base_addr=ints[1];
+    }
+
+  dev->name = (char *)&arcnet_dev_names[arcnet_num_devs];
+  
+  if (str)
+    strncpy(dev->name, str, 9);
+
+  arcnet_num_devs++;
+}
+#endif /* MODULE */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov