/*
 *	Digi RightSwitch SE-X loadable device driver for Linux
 *
 *	The RightSwitch is a 4 (EISA) or 6 (PCI) port etherswitch and
 *	a NIC on an internal board.
 *
 *	Author: Rick Richardson, rick@remotepoint.com
 *	Derived from the SVR4.2 (UnixWare) driver for the same card.
 *
 *	Copyright 1995-1996 Digi International Inc.
 *
 *	This software may be used and distributed according to the terms
 *	of the GNU General Public License, incorporated herein by reference.
 *
 *	For information on purchasing a RightSwitch SE-4 or SE-6
 *	board, please contact Digi's sales department at 1-612-912-3444
 *	or 1-800-DIGIBRD.  Outside the U.S., please check our Web page
 *	at http://www.dgii.com for sales offices worldwide.
 *
 *	OPERATION:
 *	When compiled as a loadable module, this driver can operate
 *	the board as either a 4/6 port switch with a 5th or 7th port
 *	that is a conventional NIC interface as far as the host is
 *	concerned, OR as 4/6 independent NICs.  To select multi-NIC
 *	mode, add "nicmode=1" on the insmod load line for the driver.
 *
 *	This driver uses the "dev" common ethernet device structure
 *	and a private "priv" (dev->priv) structure that contains
 *	mostly DGRS-specific information and statistics.  To keep
 *	the code for both the switch mode and the multi-NIC mode
 *	as similar as possible, I have introduced the concept of
 *	"dev0"/"priv0" and "devN"/"privN"  pointer pairs in subroutines
 *	where needed.  The first pair of pointers points to the
 *	"dev" and "priv" structures of the zeroth (0th) device
 *	interface associated with a board.  The second pair of
 *	pointers points to the current (Nth) device interface
 *	for the board: the one for which we are processing data.
 *
 *	In switch mode, the pairs of pointers are always the same,
 *	that is, dev0 == devN and priv0 == privN.  This is just
 *	like previous releases of this driver which did not support
 *	NIC mode.
 *
 *	In multi-NIC mode, the pairs of pointers may be different.
 *	We use the devN and privN pointers to reference just the
 *	name, port number, and statistics for the current interface.
 *	We use the dev0 and priv0 pointers to access the variables
 *	that control access to the board, such as board address
 *	and simulated 82596 variables.  This is because there is
 *	only one "fake" 82596 that serves as the interface to
 *	the board.  We do not want to try to keep the variables
 *	associated with this 82596 in sync across all devices.
 *
 *	This scheme works well.  As you will see, except for
 *	initialization, there is very little difference between
 *	the two modes as far as this driver is concerned.  On the
 *	receive side in NIC mode, the interrupt *always* comes in on
 *	the 0th interface (dev0/priv0).  We then figure out which
 *	real 82596 port it came in on from looking at the "chan"
 *	member that the board firmware adds at the end of each
 *	RBD (a.k.a. TBD). We get the channel number like this:
 *		int chan = ((I596_RBD *) S2H(cbp->xmit.tbdp))->chan;
 *
 *	On the transmit side in multi-NIC mode, we specify the
 *	output 82596 port by setting the new "dstchan" structure
 *	member that is at the end of the RFD, like this:
 *		priv0->rfdp->dstchan = privN->chan;
 *
 *	TODO:
 *	- Multi-NIC mode is not yet supported when the driver is linked
 *	  into the kernel.
 *	- Better handling of multicast addresses.
 *
 */

static char *version = "$Id: dgrs.c,v 1.13 2000/06/06 04:07:00 rick Exp $";

#include <linux/version.h>
#include <linux/module.h>

#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/malloc.h>
#include <linux/interrupt.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <asm/bitops.h>
#include <asm/io.h>
#include <asm/byteorder.h>

#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>

#include <linux/types.h>

/*
 *	API changed at linux version 2.1.0
 */
#if LINUX_VERSION_CODE >= 0x20100
	#include <asm/uaccess.h>
	#define IOREMAP(ADDR, LEN)		ioremap(ADDR, LEN)
	#define IOUNMAP(ADDR)			iounmap(ADDR)
	#define COPY_FROM_USER(DST,SRC,LEN)	copy_from_user(DST,SRC,LEN)
	#define COPY_TO_USER(DST,SRC,LEN)	copy_to_user(DST,SRC,LEN)
#else
	#include <linux/bios32.h>
	#define IOREMAP(ADDR, LEN)		vremap(ADDR, LEN)
	#define IOUNMAP(ADDR)			vfree(ADDR)
	#define COPY_FROM_USER(DST,SRC,LEN)	memcpy_fromfs(DST,SRC,LEN)
	#define COPY_TO_USER(DST,SRC,LEN)	memcpy_tofs(DST,SRC,LEN)
#endif

/*
 *	DGRS include files
 */
typedef unsigned char uchar;
typedef unsigned int bool;
#define vol volatile

#include "dgrs.h"
#include "dgrs_es4h.h"
#include "dgrs_plx9060.h"
#include "dgrs_i82596.h"
#include "dgrs_ether.h"
#include "dgrs_asstruct.h"
#include "dgrs_bcomm.h"

/*
 *	Firmware.  Compiled separately for local compilation,
 *	but #included for Linux distribution.
 */
#ifndef NOFW
	#include "dgrs_firmware.c"
#else
	extern int	dgrs_firmnum;
	extern char	dgrs_firmver[];
	extern char	dgrs_firmdate[];
	extern uchar	dgrs_code[];
	extern int	dgrs_ncode;
#endif

/*
 *	Linux out*() is backwards from all other operating systems
 */
#define	OUTB(ADDR, VAL)	outb(VAL, ADDR)
#define	OUTW(ADDR, VAL)	outw(VAL, ADDR)
#define	OUTL(ADDR, VAL)	outl(VAL, ADDR)

/*
 *	Macros to convert switch to host and host to switch addresses
 *	(assumes a local variable priv points to board dependent struct)
 */
#define	S2H(A)	( ((unsigned long)(A)&0x00ffffff) + priv0->vmem )
#define	S2HN(A)	( ((unsigned long)(A)&0x00ffffff) + privN->vmem )
#define	H2S(A)	( ((char *) (A) - priv0->vmem) + 0xA3000000 )

/*
 *	Convert a switch address to a "safe" address for use with the
 *	PLX 9060 DMA registers and the associated HW kludge that allows
 *	for host access of the DMA registers.
 */
#define	S2DMA(A)	( (unsigned long)(A) & 0x00ffffff)

/*
 *	"Space.c" variables, now settable from module interface
 *	Use the name below, minus the "dgrs_" prefix.  See init_module().
 */
int	dgrs_debug = 1;
int	dgrs_dma = 1;
int	dgrs_spantree = -1;
int	dgrs_hashexpire = -1;
uchar	dgrs_ipaddr[4] = { 0xff, 0xff, 0xff, 0xff};
uchar	dgrs_iptrap[4] = { 0xff, 0xff, 0xff, 0xff};
__u32	dgrs_ipxnet = -1;
int	dgrs_nicmode = 0;

/*
 *	Chain of device structures
 */
#ifdef MODULE
	static struct device *dgrs_root_dev = NULL;
#endif

/*
 *	Private per-board data structure (dev->priv)
 */
typedef struct
{
	/*
	 *	Stuff for generic ethercard I/F
	 */
	char			devname[8];	/* "ethN" string */
	struct device		*next_dev;
	struct net_device_stats	stats;

	/*
	 *	DGRS specific data
	 */
	char		*vmem;

        struct bios_comm *bcomm;        /* Firmware BIOS comm structure */
        PORT            *port;          /* Ptr to PORT[0] struct in VM */
        I596_SCB        *scbp;          /* Ptr to SCB struct in VM */
        I596_RFD        *rfdp;          /* Current RFD list */
        I596_RBD        *rbdp;          /* Current RBD list */

        volatile int    intrcnt;        /* Count of interrupts */

        /*
         *      SE-4 (EISA) board variables
         */
        uchar		is_reg;		/* EISA: Value for ES4H_IS reg */

        /*
         *      SE-6 (PCI) board variables
         *
         *      The PLX "expansion rom" space is used for DMA register
         *      access from the host on the SE-6.  These are the physical
         *      and virtual addresses of that space.
         */
        ulong		plxreg;		/* Phys address of PLX chip */
        char            *vplxreg;	/* Virtual address of PLX chip */
        ulong		plxdma;		/* Phys addr of PLX "expansion rom" */
        ulong volatile  *vplxdma;	/* Virtual addr of "expansion rom" */
        int             use_dma;        /* Flag: use DMA */
	DMACHAIN	*dmadesc_s;	/* area for DMA chains (SW addr.) */
	DMACHAIN	*dmadesc_h;	/* area for DMA chains (Host Virtual) */

	/*
	 *	Multi-NIC mode variables
	 *
	 *	All entries of the devtbl[] array are valid for the 0th
	 *	device (i.e. eth0, but not eth1...eth5).  devtbl[0] is
	 *	valid for all devices (i.e. eth0, eth1, ..., eth5).
	 */
	int		nports;		/* Number of physical ports (4 or 6) */
	int		chan;		/* Channel # (1-6) for this device */
	struct device	*devtbl[6];	/* Ptrs to N device structs */

} DGRS_PRIV;


/*
 *	reset or un-reset the IDT processor
 */
static void
proc_reset(struct device *dev0, int reset)
{
	DGRS_PRIV	*priv0 = (DGRS_PRIV *) dev0->priv;

	if (priv0->plxreg)
	{
		ulong		val;
		val = inl(dev0->base_addr + PLX_MISC_CSR);
		if (reset)
			val |= SE6_RESET;
		else
			val &= ~SE6_RESET;
		OUTL(dev0->base_addr + PLX_MISC_CSR, val);
	}
	else
	{
		OUTB(dev0->base_addr + ES4H_PC, reset ? ES4H_PC_RESET : 0);
	}
}

/*
 *	See if the board supports bus master DMA
 */
static int
check_board_dma(struct device *dev0)
{
	DGRS_PRIV	*priv0 = (DGRS_PRIV *) dev0->priv;
	ulong	x;

	/*
	 *	If Space.c says not to use DMA, or if its not a PLX based
	 *	PCI board, or if the expansion ROM space is not PCI
	 *	configured, then return false.
	 */
	if (!dgrs_dma || !priv0->plxreg || !priv0->plxdma)
		return (0);

	/*
	 *	Set the local address remap register of the "expansion rom"
	 *	area to 0x80000000 so that we can use it to access the DMA
	 *	registers from the host side.
	 */
	OUTL(dev0->base_addr + PLX_ROM_BASE_ADDR, 0x80000000);

	/*
	 * Set the PCI region descriptor to:
	 *      Space 0:
	 *              disable read-prefetch
	 *              enable READY
	 *              enable BURST
	 *              0 internal wait states
	 *      Expansion ROM: (used for host DMA register access)
	 *              disable read-prefetch
	 *              enable READY
	 *              disable BURST
	 *              0 internal wait states
	 */
	OUTL(dev0->base_addr + PLX_BUS_REGION, 0x49430343);

	/*
	 *	Now map the DMA registers into our virtual space
	 */
	priv0->vplxdma = (ulong *) IOREMAP (priv0->plxdma, 256);
	if (!priv0->vplxdma)
	{
		printk("%s: can't *remap() the DMA regs\n", dev0->name);
		return (0);
	}

	/*
	 *	Now test to see if we can access the DMA registers
	 *	If we write -1 and get back 1FFF, then we accessed the
	 *	DMA register.  Otherwise, we probably have an old board
	 *	and wrote into regular RAM.
	 */
	priv0->vplxdma[PLX_DMA0_MODE/4] = 0xFFFFFFFF;
	x = priv0->vplxdma[PLX_DMA0_MODE/4];
	if (x != 0x00001FFF)
		return (0);

	return (1);
}

/*
 *	Initiate DMA using PLX part on PCI board.  Spin the
 *	processor until completed.  All addresses are physical!
 *
 *	If pciaddr is NULL, then its a chaining DMA, and lcladdr is
 *	the address of the first DMA descriptor in the chain.
 *
 *	If pciaddr is not NULL, then its a single DMA.
 *
 *	In either case, "lcladdr" must have been fixed up to make
 *	sure the MSB isn't set using the S2DMA macro before passing
 *	the address to this routine.
 */
static int
do_plx_dma(
	struct device *dev,
	ulong pciaddr,
	ulong lcladdr,
	int len,
	int to_host
)
{
        int     	i;
        ulong   	csr;
	DGRS_PRIV	*priv = (DGRS_PRIV *) dev->priv;

	if (pciaddr)
	{
		/*
		 *	Do a single, non-chain DMA
		 */
		priv->vplxdma[PLX_DMA0_PCI_ADDR/4] = pciaddr;
		priv->vplxdma[PLX_DMA0_LCL_ADDR/4] = lcladdr;
		priv->vplxdma[PLX_DMA0_SIZE/4] = len;
		priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = to_host
					? PLX_DMA_DESC_TO_HOST
					: PLX_DMA_DESC_TO_BOARD;
		priv->vplxdma[PLX_DMA0_MODE/4] =
					  PLX_DMA_MODE_WIDTH32
					| PLX_DMA_MODE_WAITSTATES(0)
					| PLX_DMA_MODE_READY
					| PLX_DMA_MODE_NOBTERM
					| PLX_DMA_MODE_BURST
					| PLX_DMA_MODE_NOCHAIN;
	}
	else
	{
		/*
		 *	Do a chaining DMA
		 */
		priv->vplxdma[PLX_DMA0_MODE/4] =
					  PLX_DMA_MODE_WIDTH32
					| PLX_DMA_MODE_WAITSTATES(0)
					| PLX_DMA_MODE_READY
					| PLX_DMA_MODE_NOBTERM
					| PLX_DMA_MODE_BURST
					| PLX_DMA_MODE_CHAIN;
		priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = lcladdr;
	}

	priv->vplxdma[PLX_DMA_CSR/4] =
				PLX_DMA_CSR_0_ENABLE | PLX_DMA_CSR_0_START;

        /*
	 *	Wait for DMA to complete
	 */
        for (i = 0; i < 1000000; ++i)
        {
		/*
		 *	Spin the host CPU for 1 usec, so we don't thrash
		 *	the PCI bus while the PLX 9060 is doing DMA.
		 */
		udelay(1);

		csr = (volatile unsigned long) priv->vplxdma[PLX_DMA_CSR/4];

                if (csr & PLX_DMA_CSR_0_DONE)
                        break;
        }

        if ( ! (csr & PLX_DMA_CSR_0_DONE) )
        {
		printk("%s: DMA done never occurred. DMA disabled.\n",
			dev->name);
		priv->use_dma = 0;
                return 1;
        }
        return 0;
}

/*
 *	dgrs_rcv_frame()
 *
 *	Process a received frame.  This is called from the interrupt
 *	routine, and works for both switch mode and multi-NIC mode.
 *
 *	Note that when in multi-NIC mode, we want to always access the
 *	hardware using the dev and priv structures of the first port,
 *	so that we are using only one set of variables to maintain
 *	the board interface status, but we want to use the Nth port
 *	dev and priv structures to maintain statistics and to pass
 *	the packet up.
 *
 *	Only the first device structure is attached to the interrupt.
 *	We use the special "chan" variable at the end of the first RBD
 *	to select the Nth device in multi-NIC mode.
 *
 *	We currently do chained DMA on a per-packet basis when the
 *	packet is "long", and we spin the CPU a short time polling
 *	for DMA completion.  This avoids a second interrupt overhead,
 *	and gives the best performance for light traffic to the host.
 *
 *	However, a better scheme that could be implemented would be
 *	to see how many packets are outstanding for the host, and if
 *	the number is "large", create a long chain to DMA several
 *	packets into the host in one go.  In this case, we would set
 *	up some state variables to let the host CPU continue doing
 *	other things until a DMA completion interrupt comes along.
 */
void
dgrs_rcv_frame(
	struct device	*dev0,
	DGRS_PRIV	*priv0,
	I596_CB		*cbp
)
{
	int		len;
	I596_TBD	*tbdp;
	struct sk_buff	*skb;
	uchar		*putp;
	uchar		*p;
	struct device	*devN;
	DGRS_PRIV	*privN;

	/*
	 *	Determine Nth priv and dev structure pointers
	 */
	if (dgrs_nicmode)
	{	/* Multi-NIC mode */
		int chan = ((I596_RBD *) S2H(cbp->xmit.tbdp))->chan;

		devN = priv0->devtbl[chan-1];
		/*
		 * If devN is null, we got an interrupt before the I/F
		 * has been initialized.  Pitch the packet.
		 */
		if (devN == NULL)
			goto out;
		privN = (DGRS_PRIV *) devN->priv;
	}
	else
	{	/* Switch mode */
		devN = dev0;
		privN = priv0;
	}

	if (0) printk("%s: rcv len=%ld\n", devN->name, cbp->xmit.count);

	/*
	 *	Allocate a message block big enough to hold the whole frame
	 */
	len = cbp->xmit.count;
	if ((skb = dev_alloc_skb(len+5)) == NULL)
	{
		printk("%s: dev_alloc_skb failed for rcv buffer\n", devN->name);
		++privN->stats.rx_dropped;
		/* discarding the frame */
		goto out;
	}
	skb->dev = devN;
	skb_reserve(skb, 2);	/* Align IP header */

again:
	putp = p = skb_put(skb, len);

	/*
	 *	There are three modes here for doing the packet copy.
	 *	If we have DMA, and the packet is "long", we use the
	 *	chaining mode of DMA.  If it's shorter, we use single
	 *	DMA's.  Otherwise, we use memcpy().
	 */
	if (priv0->use_dma && priv0->dmadesc_h && len > 64)
	{
		/*
		 *	If we can use DMA and its a long frame, copy it using
		 *	DMA chaining.
		 */
		DMACHAIN	*ddp_h;	/* Host virtual DMA desc. pointer */
		DMACHAIN	*ddp_s;	/* Switch physical DMA desc. pointer */
		uchar		*phys_p;

		/*
		 *	Get the physical address of the STREAMS buffer.
		 *	NOTE: allocb() guarantees that the whole buffer
		 *	is in a single page if the length < 4096.
		 */
		phys_p = (uchar *) virt_to_phys(putp);

		ddp_h = priv0->dmadesc_h;
		ddp_s = priv0->dmadesc_s;
		tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp);
		for (;;)
		{
			int	count;
			int	amt;

			count = tbdp->count;
			amt = count & 0x3fff;
			if (amt == 0)
				break; /* For safety */
			if ( (p-putp) >= len)
			{
				printk("%s: cbp = %x\n", devN->name, H2S(cbp));
				proc_reset(dev0, 1);	/* Freeze IDT */
				break; /* For Safety */
			}

			ddp_h->pciaddr = (ulong) phys_p;
			ddp_h->lcladdr = S2DMA(tbdp->buf);
			ddp_h->len = amt;

			phys_p += amt;
			p += amt;

			if (count & I596_TBD_EOF)
			{
				ddp_h->next = PLX_DMA_DESC_TO_HOST
						| PLX_DMA_DESC_EOC;
				++ddp_h;
				break;
			}
			else
			{
				++ddp_s;
				ddp_h->next = PLX_DMA_DESC_TO_HOST
						| (ulong) ddp_s;
				tbdp = (I596_TBD *) S2H(tbdp->next);
				++ddp_h;
			}
		}
		if (ddp_h - priv0->dmadesc_h)
		{
			int	rc;

			rc = do_plx_dma(dev0,
				0, (ulong) priv0->dmadesc_s, len, 0);
			if (rc)
			{
				printk("%s: Chained DMA failure\n", devN->name);
				goto again;
			}
		}
	}
	else if (priv0->use_dma)
	{
		/*
		 *	If we can use DMA and its a shorter frame, copy it
		 *	using single DMA transfers.
		 */
		uchar		*phys_p;

		/*
		 *	Get the physical address of the STREAMS buffer.
		 *	NOTE: allocb() guarantees that the whole buffer
		 *	is in a single page if the length < 4096.
		 */
		phys_p = (uchar *) virt_to_phys(putp);

		tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp);
		for (;;)
		{
			int	count;
			int	amt;
			int	rc;

			count = tbdp->count;
			amt = count & 0x3fff;
			if (amt == 0)
				break; /* For safety */
			if ( (p-putp) >= len)
			{
				printk("%s: cbp = %x\n", devN->name, H2S(cbp));
				proc_reset(dev0, 1);	/* Freeze IDT */
				break; /* For Safety */
			}
			rc = do_plx_dma(dev0, (ulong) phys_p,
						S2DMA(tbdp->buf), amt, 1);
			if (rc)
			{
				memcpy(p, S2H(tbdp->buf), amt);
				printk("%s: Single DMA failed\n", devN->name);
			}
			phys_p += amt;
			p += amt;
			if (count & I596_TBD_EOF)
				break;
			tbdp = (I596_TBD *) S2H(tbdp->next);
		}
	}
	else
	{
		/*
		 *	Otherwise, copy it piece by piece using memcpy()
		 */
		tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp);
		for (;;)
		{
			int	count;
			int	amt;

			count = tbdp->count;
			amt = count & 0x3fff;
			if (amt == 0)
				break; /* For safety */
			if ( (p-putp) >= len)
			{
				printk("%s: cbp = %x\n", devN->name, H2S(cbp));
				proc_reset(dev0, 1);	/* Freeze IDT */
				break; /* For Safety */
			}
			memcpy(p, S2H(tbdp->buf), amt);
			p += amt;
			if (count & I596_TBD_EOF)
				break;
			tbdp = (I596_TBD *) S2H(tbdp->next);
		}
	}

	/*
	 *	Pass the frame to upper half
	 */
	skb->protocol = eth_type_trans(skb, devN);
	netif_rx(skb);
	++privN->stats.rx_packets;

out:
	cbp->xmit.status = I596_CB_STATUS_C | I596_CB_STATUS_OK;
}

/*
 *	Start transmission of a frame
 *
 *	The interface to the board is simple: we pretend that we are
 *	a fifth 82596 ethernet controller 'receiving' data, and copy the
 *	data into the same structures that a real 82596 would.  This way,
 *	the board firmware handles the host 'port' the same as any other.
 *
 *	NOTE: we do not use Bus master DMA for this routine.  Turns out
 *	that it is not needed.  Slave writes over the PCI bus are about
 *	as fast as DMA, due to the fact that the PLX part can do burst
 *	writes.  The same is not true for data being read from the board.
 *
 *	For multi-NIC mode, we tell the firmware the desired 82596
 *	output port by setting the special "dstchan" member at the
 *	end of the traditional 82596 RFD structure.
 */

static int dgrs_start_xmit(struct sk_buff *skb, struct device *devN)
{
	DGRS_PRIV	*privN = (DGRS_PRIV *) devN->priv;
	struct device	*dev0;
	DGRS_PRIV	*priv0;
	I596_RBD	*rbdp;
	int		count;
	int		i, len, amt;
#	define		mymin(A,B)	( (A) < (B) ? (A) : (B) )

	/*
	 *	Determine 0th priv and dev structure pointers
	 */
	if (dgrs_nicmode)
	{
		dev0 = privN->devtbl[0];
		priv0 = (DGRS_PRIV *) dev0->priv;
	}
	else
	{
		dev0 = devN;
		priv0 = privN;
	}

	if (dgrs_debug > 1)
		printk("%s: xmit len=%d\n", devN->name, (int) skb->len);

	devN->trans_start = jiffies;
	devN->tbusy = 0;

	if (priv0->rfdp->cmd & I596_RFD_EL)
	{	/* Out of RFD's */
		if (0) printk("%s: NO RFD's\n", devN->name);
		goto no_resources;
	}

	rbdp = priv0->rbdp;
	count = 0;
	priv0->rfdp->rbdp = (I596_RBD *) H2S(rbdp);

	i = 0; len = skb->len;
	for (;;)
	{
		if (rbdp->size & I596_RBD_EL)
		{	/* Out of RBD's */
			if (0) printk("%s: NO RBD's\n", devN->name);
			goto no_resources;
		}

		amt = mymin(len, rbdp->size - count);
		memcpy( (char *) S2H(rbdp->buf) + count, skb->data + i, amt);
		i += amt;
		count += amt;
		len -= amt;
		if (len == 0)
		{
			if (skb->len < 60)
				rbdp->count = 60 | I596_RBD_EOF;
			else
				rbdp->count = count | I596_RBD_EOF;
			rbdp = (I596_RBD *) S2H(rbdp->next);
			goto frame_done;
		}
		else if (count < 32)
		{
			/* More data to come, but we used less than 32
			 * bytes of this RBD.  Keep filling this RBD.
			 */
			{}	/* Yes, we do nothing here */
		}
		else
		{
			rbdp->count = count;
			rbdp = (I596_RBD *) S2H(rbdp->next);
			count = 0;
		}
	}

frame_done:
	priv0->rbdp = rbdp;
	if (dgrs_nicmode)
		priv0->rfdp->dstchan = privN->chan;
	priv0->rfdp->status = I596_RFD_C | I596_RFD_OK;
	priv0->rfdp = (I596_RFD *) S2H(priv0->rfdp->next);

	++privN->stats.tx_packets;

	dev_kfree_skb (skb);
	return (0);

no_resources:
	priv0->scbp->status |= I596_SCB_RNR;	/* simulate I82596 */
	return (-EAGAIN);
}

/*
 *	Open the interface
 */
static int
dgrs_open( struct device *dev )
{
	dev->tbusy = 0;
	dev->interrupt = 0;
	dev->start = 1;

#ifdef MODULE
	MOD_INC_USE_COUNT;
#endif

	return (0);
}

/*
 *	Close the interface
 */
static int dgrs_close( struct device *dev )
{
	dev->start = 0;
	dev->tbusy = 1;

#ifdef MODULE
	MOD_DEC_USE_COUNT;
#endif

	return (0);
}

/*
 *	Get statistics
 */
static struct net_device_stats *dgrs_get_stats( struct device *dev )
{
	DGRS_PRIV	*priv = (DGRS_PRIV *) dev->priv;

	return (&priv->stats);
}

/*
 *	Set multicast list and/or promiscuous mode
 */

static void dgrs_set_multicast_list( struct device *dev)
{
	DGRS_PRIV	*priv = (DGRS_PRIV *) dev->priv;

	priv->port->is_promisc = (dev->flags & IFF_PROMISC) ? 1 : 0;
}

/*
 *	Unique ioctl's
 */
static int dgrs_ioctl(struct device *devN, struct ifreq *ifr, int cmd)
{
	DGRS_PRIV	*privN = (DGRS_PRIV *) devN->priv;
	DGRS_IOCTL	ioc;
	int		i;

	if (cmd != DGRSIOCTL)
		return -EINVAL;

	if(COPY_FROM_USER(&ioc, ifr->ifr_data, sizeof(DGRS_IOCTL)))
		return -EFAULT;

	switch (ioc.cmd)
	{
		case DGRS_GETMEM:
			if (ioc.len != sizeof(ulong))
				return -EINVAL;
			if(COPY_TO_USER(ioc.data, &devN->mem_start, ioc.len))
				return -EFAULT;
			return (0);
		case DGRS_SETFILTER:
			if (!suser())
				return -EPERM;
			if (ioc.port > privN->bcomm->bc_nports)
				return -EINVAL;
			if (ioc.filter >= NFILTERS)
				return -EINVAL;
			if (ioc.len > privN->bcomm->bc_filter_area_len)
				return -EINVAL;

			/* Wait for old command to finish */
			for (i = 0; i < 1000; ++i)
			{
				if ( (volatile long) privN->bcomm->bc_filter_cmd <= 0 )
					break;
				udelay(1);
			}
			if (i >= 1000)
				return -EIO;

			privN->bcomm->bc_filter_port = ioc.port;
			privN->bcomm->bc_filter_num = ioc.filter;
			privN->bcomm->bc_filter_len = ioc.len;
	
			if (ioc.len)
			{
				if(COPY_FROM_USER(S2HN(privN->bcomm->bc_filter_area),
					ioc.data, ioc.len))
					return -EFAULT;
				privN->bcomm->bc_filter_cmd = BC_FILTER_SET;
			}
			else
				privN->bcomm->bc_filter_cmd = BC_FILTER_CLR;
			return(0);
		default:
			return -EOPNOTSUPP;
	}
}

/*
 *	Process interrupts
 *
 *	dev, priv will always refer to the 0th device in Multi-NIC mode.
 */

static void dgrs_intr(int irq, void *dev_id, struct pt_regs *regs)
{
	struct device	*dev0 = (struct device *) dev_id;
	DGRS_PRIV	*priv0 = (DGRS_PRIV *) dev0->priv;
	I596_CB		*cbp;
	int		cmd;
	int		i;

	++priv0->intrcnt;
	if (1) ++priv0->bcomm->bc_cnt[4];
	if (0)
	{
		static int cnt = 100;
		if (--cnt > 0)
		printk("%s: interrupt: irq %d\n", dev0->name, irq);
	}

	/*
	 *	Get 596 command
	 */
	cmd = priv0->scbp->cmd;

	/*
	 *	See if RU has been restarted
	 */
	if ( (cmd & I596_SCB_RUC) == I596_SCB_RUC_START)
	{
		if (0) printk("%s: RUC start\n", dev0->name);
		priv0->rfdp = (I596_RFD *) S2H(priv0->scbp->rfdp);
		priv0->rbdp = (I596_RBD *) S2H(priv0->rfdp->rbdp);
		priv0->scbp->status &= ~(I596_SCB_RNR|I596_SCB_RUS);
		/*
		 * Tell upper half (halves)
		 */
		if (dgrs_nicmode)
		{
			for (i = 0; i < priv0->nports; ++i)
				priv0->devtbl[i]->tbusy = 0;
		}
		else
			dev0->tbusy = 0;
		/* if (bd->flags & TX_QUEUED)
			DL_sched(bd, bdd); */
	}

	/*
	 *	See if any CU commands to process
	 */
	if ( (cmd & I596_SCB_CUC) != I596_SCB_CUC_START)
	{
		priv0->scbp->cmd = 0;	/* Ignore all other commands */
		goto ack_intr;
	}
	priv0->scbp->status &= ~(I596_SCB_CNA|I596_SCB_CUS);

	/*
	 *	Process a command
	 */
	cbp = (I596_CB *) S2H(priv0->scbp->cbp);
	priv0->scbp->cmd = 0;	/* Safe to clear the command */
	for (;;)
	{
		switch (cbp->nop.cmd & I596_CB_CMD)
		{
		case I596_CB_CMD_XMIT:
			dgrs_rcv_frame(dev0, priv0, cbp);
			break;
		default:
			cbp->nop.status = I596_CB_STATUS_C | I596_CB_STATUS_OK;
			break;
		}
		if (cbp->nop.cmd & I596_CB_CMD_EL)
			break;
		cbp = (I596_CB *) S2H(cbp->nop.next);
	}
	priv0->scbp->status |= I596_SCB_CNA;

	/*
	 * Ack the interrupt
	 */
ack_intr:
	if (priv0->plxreg)
		OUTL(dev0->base_addr + PLX_LCL2PCI_DOORBELL, 1);
}

/*
 *	Download the board firmware
 */
__initfunc(static int
dgrs_download(struct device *dev0))
{
	DGRS_PRIV	*priv0 = (DGRS_PRIV *) dev0->priv;
	int		is;
	int		i;

	static int	iv2is[16] = {
				0, 0, 0, ES4H_IS_INT3,
				0, ES4H_IS_INT5, 0, ES4H_IS_INT7,
				0, 0, ES4H_IS_INT10, ES4H_IS_INT11,
				ES4H_IS_INT12, 0, 0, ES4H_IS_INT15 };

	/*
	 * Map in the dual port memory
	 */
	priv0->vmem = IOREMAP(dev0->mem_start, 2048*1024);
	if (!priv0->vmem)
	{
		printk("%s: cannot map in board memory\n", dev0->name);
		return -ENXIO;
	}

	/*
	 *	Hold the processor and configure the board addresses
	 */
	if (priv0->plxreg)
	{	/* PCI bus */
		proc_reset(dev0, 1);
	}
	else
	{	/* EISA bus */
		is = iv2is[dev0->irq & 0x0f];
		if (!is)
		{
			printk("%s: Illegal IRQ %d\n", dev0->name, dev0->irq);
			return -ENXIO;
		}
		OUTB(dev0->base_addr + ES4H_AS_31_24,
			(uchar) (dev0->mem_start >> 24) );
		OUTB(dev0->base_addr + ES4H_AS_23_16,
			(uchar) (dev0->mem_start >> 16) );
		priv0->is_reg = ES4H_IS_LINEAR | is |
			((uchar) (dev0->mem_start >> 8) & ES4H_IS_AS15);
		OUTB(dev0->base_addr + ES4H_IS, priv0->is_reg);
		OUTB(dev0->base_addr + ES4H_EC, ES4H_EC_ENABLE);
		OUTB(dev0->base_addr + ES4H_PC, ES4H_PC_RESET);
		OUTB(dev0->base_addr + ES4H_MW, ES4H_MW_ENABLE | 0x00);
	}

	/*
	 *	See if we can do DMA on the SE-6
	 */
	priv0->use_dma = check_board_dma(dev0);
	if (priv0->use_dma)
		printk("%s: Bus Master DMA is enabled.\n", dev0->name);

	/*
	 * Load and verify the code at the desired address
	 */
	memcpy(priv0->vmem, dgrs_code, dgrs_ncode);	/* Load code */
	if (memcmp(priv0->vmem, dgrs_code, dgrs_ncode))
	{
		IOUNMAP(priv0->vmem);
		priv0->vmem = NULL;
		printk("%s: download compare failed\n", dev0->name);
		return -ENXIO;
	}

	/*
	 * Configurables
	 */
	priv0->bcomm = (struct bios_comm *) (priv0->vmem + 0x0100);
	priv0->bcomm->bc_nowait = 1;	/* Tell board to make printf not wait */
	priv0->bcomm->bc_squelch = 0;	/* Flag from Space.c */
	priv0->bcomm->bc_150ohm = 0;	/* Flag from Space.c */

	priv0->bcomm->bc_spew = 0;	/* Debug flag from Space.c */
	priv0->bcomm->bc_maxrfd = 0;	/* Debug flag from Space.c */
	priv0->bcomm->bc_maxrbd = 0;	/* Debug flag from Space.c */

	/*
	 * Tell board we are operating in switch mode (1) or in
	 * multi-NIC mode (2).
	 */
	priv0->bcomm->bc_host = dgrs_nicmode ? BC_MULTINIC : BC_SWITCH;

	/*
	 * Request memory space on board for DMA chains
	 */
	if (priv0->use_dma)
		priv0->bcomm->bc_hostarea_len = (2048/64) * 16;

	/*
	 * NVRAM configurables from Space.c
	 */
	priv0->bcomm->bc_spantree = dgrs_spantree;
	priv0->bcomm->bc_hashexpire = dgrs_hashexpire;
	memcpy(priv0->bcomm->bc_ipaddr, dgrs_ipaddr, 4);
	memcpy(priv0->bcomm->bc_iptrap, dgrs_iptrap, 4);
	memcpy(priv0->bcomm->bc_ipxnet, &dgrs_ipxnet, 4);

	/*
	 * Release processor, wait 8 seconds for board to initialize
	 */
	proc_reset(dev0, 0);

	for (i = jiffies + 8 * HZ; time_after(i, jiffies); )
	{
		if (priv0->bcomm->bc_status >= BC_RUN)
			break;
	}

	if (priv0->bcomm->bc_status < BC_RUN)
	{
		printk("%s: board not operating\n", dev0->name);
		return -ENXIO;
	}

	priv0->port = (PORT *) S2H(priv0->bcomm->bc_port);
	priv0->scbp = (I596_SCB *) S2H(priv0->port->scbp);
	priv0->rfdp = (I596_RFD *) S2H(priv0->scbp->rfdp);
	priv0->rbdp = (I596_RBD *) S2H(priv0->rfdp->rbdp);

	priv0->scbp->status = I596_SCB_CNA;	/* CU is idle */

	/*
	 *	Get switch physical and host virtual pointers to DMA
	 *	chaining area.  NOTE: the MSB of the switch physical
	 *	address *must* be turned off.  Otherwise, the HW kludge
	 *	that allows host access of the PLX DMA registers will
	 *	erroneously select the PLX registers.
	 */
	priv0->dmadesc_s = (DMACHAIN *) S2DMA(priv0->bcomm->bc_hostarea);
	if (priv0->dmadesc_s)
		priv0->dmadesc_h = (DMACHAIN *) S2H(priv0->dmadesc_s);
	else
		priv0->dmadesc_h = NULL;

	/*
	 *	Enable board interrupts
	 */
	if (priv0->plxreg)
	{	/* PCI bus */
		OUTL(dev0->base_addr + PLX_INT_CSR,
			inl(dev0->base_addr + PLX_INT_CSR)
			| PLX_PCI_DOORBELL_IE);	/* Enable intr to host */
		OUTL(dev0->base_addr + PLX_LCL2PCI_DOORBELL, 1);
	}
	else
	{	/* EISA bus */
	}

	return (0);
}

/*
 *	Probe (init) a board
 */
__initfunc(int
dgrs_probe1(struct device *dev))
{
	DGRS_PRIV	*priv = (DGRS_PRIV *) dev->priv;
	int		i;
	int		rc;

	printk("%s: Digi RightSwitch io=%lx mem=%lx irq=%d plx=%lx dma=%lx\n",
		dev->name, dev->base_addr, dev->mem_start, dev->irq,
		priv->plxreg, priv->plxdma);

	/*
	 *	Download the firmware and light the processor
	 */
	rc = dgrs_download(dev);
	if (rc)
	{
		return rc;
	}

	/*
	 * Get ether address of board
	 */
	printk("%s: Ethernet address", dev->name);
	memcpy(dev->dev_addr, priv->port->ethaddr, 6);
	for (i = 0; i < 6; ++i)
		printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]);
	printk("\n");

	if (dev->dev_addr[0] & 1)
	{
		printk("%s: Illegal Ethernet Address\n", dev->name);
		return (-ENXIO);
	}

	/*
	 *	ACK outstanding interrupts, hook the interrupt,
	 *	and verify that we are getting interrupts from the board.
	 */
	if (priv->plxreg)
		OUTL(dev->base_addr + PLX_LCL2PCI_DOORBELL, 1);
	rc = request_irq(dev->irq, &dgrs_intr, SA_SHIRQ, "RightSwitch", dev);
	if (rc)
		return (rc);

	priv->intrcnt = 0;
	for (i = jiffies + 2*HZ + HZ/2; time_after(i, jiffies); )
	{
		barrier();		/* gcc 2.95 needs this */
		if (priv->intrcnt >= 2)
			break;
	}
	if (priv->intrcnt < 2)
	{
		printk("%s: Not interrupting on IRQ %d (%d)\n",
				dev->name, dev->irq, priv->intrcnt);
		return (-ENXIO);
	}

	/*
	 *	Register the /proc/ioports information...
	 */
	request_region(dev->base_addr, 256, "RightSwitch");

	/*
	 *	Entry points...
	 */
	dev->open = &dgrs_open;
	dev->stop = &dgrs_close;
	dev->get_stats = &dgrs_get_stats;
	dev->hard_start_xmit = &dgrs_start_xmit;
	dev->set_multicast_list = &dgrs_set_multicast_list;
	dev->do_ioctl = &dgrs_ioctl;

	return (0);
}

__initfunc(int
dgrs_initclone(struct device *dev))
{
	DGRS_PRIV	*priv = (DGRS_PRIV *) dev->priv;
	int		i;

	printk("%s: Digi RightSwitch port %d ",
		dev->name, priv->chan);
	for (i = 0; i < 6; ++i)
		printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]);
	printk("\n");

	return (0);
}

__initfunc(static int
dgrs_found_device(
	struct device	*dev,
	int		io,
	ulong		mem,
	int		irq,
	ulong		plxreg,
	ulong		plxdma
))
{
	DGRS_PRIV	*priv;

	#ifdef MODULE
	{
		/* Allocate and fill new device structure. */
		int dev_size = sizeof(struct device) + sizeof(DGRS_PRIV);
		int i;

		dev = (struct device *) kmalloc(dev_size, GFP_KERNEL);
		memset(dev, 0, dev_size);
		dev->priv = ((void *)dev) + sizeof(struct device);
		priv = (DGRS_PRIV *)dev->priv;

		dev->name = priv->devname; /* An empty string. */
		dev->base_addr = io;
		dev->mem_start = mem;
		dev->mem_end = mem + 2048 * 1024 - 1;
		dev->irq = irq;
		priv->plxreg = plxreg;
		priv->plxdma = plxdma;
		priv->vplxdma = NULL;

		priv->chan = 1;
		priv->devtbl[0] = dev;

		dev->init = dgrs_probe1;

		ether_setup(dev);
		priv->next_dev = dgrs_root_dev;
		dgrs_root_dev = dev;
		if (register_netdev(dev) != 0)
			return -EIO;

		if ( !dgrs_nicmode )
			return (0);	/* Switch mode, we are done */

		/*
		 * Operating card as N separate NICs
		 */
		priv->nports = priv->bcomm->bc_nports;
		for (i = 1; i < priv->nports; ++i)
		{
			struct device	*devN;
			DGRS_PRIV	*privN;

			/* Allocate new dev and priv structures */
			devN = (struct device *) kmalloc(dev_size, GFP_KERNEL);

			/* Make it an exact copy of dev[0]... */
			memcpy(devN, dev, dev_size);
			devN->priv = ((void *)devN) + sizeof(struct device);
			privN = (DGRS_PRIV *)devN->priv;

			/* ... but seset devname to a NULL string */
			privN->devname[0] = 0;
			devN->name = privN->devname;

			/* ... and zero out VM areas */
			privN->vmem = 0;
			privN->vplxdma = 0;

			/* ... and zero out IRQ */
			devN->irq = 0;

			/* ... and base MAC address off address of 1st port */
			devN->dev_addr[5] += i;
			privN->chan = i+1;

			priv->devtbl[i] = devN;

			devN->init = dgrs_initclone;
			ether_setup(devN);
			privN->next_dev = dgrs_root_dev;
			dgrs_root_dev = devN;
			if (register_netdev(devN) != 0)
				return -EIO;
		}
	}
	#else
	{
		if (dev)
		{
			dev->priv = kmalloc(sizeof (DGRS_PRIV), GFP_KERNEL);
			memset(dev->priv, 0, sizeof (DGRS_PRIV));
		}
		dev = init_etherdev(dev, sizeof(DGRS_PRIV));
		priv = (DGRS_PRIV *)dev->priv;

		dev->base_addr = io;
		dev->mem_start = mem;
		dev->mem_end = mem + 2048 * 1024;
		dev->irq = irq;
		priv->plxreg = plxreg;
		priv->plxdma = plxdma;
		priv->vplxdma = NULL;

		priv->chan = 1;
		priv->devtbl[0] = dev;

		dgrs_probe1(dev);
	}
	#endif

	return (0);
}

/*
 *	Scan for all boards
 */
static int is2iv[8] __initdata = { 0, 3, 5, 7, 10, 11, 12, 15 };

__initfunc(static int
dgrs_scan(struct device *dev))
{
	int	cards_found = 0;
	uint	io;
	uint	mem;
	uint	irq;
	uint	plxreg;
	uint	plxdma;

	/*
	 *	First, check for PCI boards
	 */
	if (pci_present())
	{
		int pci_index = 0;

		for (; pci_index < 8; pci_index++)
		{
			uchar	pci_bus, pci_device_fn;
#if LINUX_VERSION_CODE < 0x20100
			uchar	pci_irq;
#else
			uint	pci_irq;
			struct pci_dev *pdev;
#endif
			uchar	pci_latency;
			ushort	pci_command;

			if (pcibios_find_device(SE6_PCI_VENDOR_ID,
							SE6_PCI_DEVICE_ID,
							pci_index, &pci_bus,
							&pci_device_fn))
					break;

#if LINUX_VERSION_CODE < 0x20100
			pcibios_read_config_byte(pci_bus, pci_device_fn,
					PCI_INTERRUPT_LINE, &pci_irq);
			pcibios_read_config_dword(pci_bus, pci_device_fn,
					PCI_BASE_ADDRESS_0, &plxreg);
			pcibios_read_config_dword(pci_bus, pci_device_fn,
					PCI_BASE_ADDRESS_1, &io);
			pcibios_read_config_dword(pci_bus, pci_device_fn,
					PCI_BASE_ADDRESS_2, &mem);
#else
			pdev = pci_find_slot(pci_bus, pci_device_fn);
			pci_irq = pdev->irq;
			plxreg = pdev->base_address[0];
			io = pdev->base_address[1];
			mem = pdev->base_address[2];
#endif
			pcibios_read_config_dword(pci_bus, pci_device_fn,
					0x30, &plxdma);
			irq = pci_irq;
			plxreg &= ~15;
			io &= ~3;
			mem &= ~15;
			plxdma &= ~15;

			/*
			 * On some BIOSES, the PLX "expansion rom" (used for DMA)
			 * address comes up as "0".  This is probably because
			 * the BIOS doesn't see a valid 55 AA ROM signature at
			 * the "ROM" start and zeroes the address.  To get
			 * around this problem the SE-6 is configured to ask
			 * for 4 MB of space for the dual port memory.  We then
			 * must set its range back to 2 MB, and use the upper
			 * half for DMA register access
			 */
			OUTL(io + PLX_SPACE0_RANGE, 0xFFE00000L);
			if (plxdma == 0)
				plxdma = mem + (2048L * 1024L);
			pcibios_write_config_dword(pci_bus, pci_device_fn,
					0x30, plxdma + 1);
			pcibios_read_config_dword(pci_bus, pci_device_fn,
					0x30, &plxdma);
			plxdma &= ~15;

			/*
			 * Get and check the bus-master and latency values.
			 * Some PCI BIOSes fail to set the master-enable bit,
			 * and the latency timer must be set to the maximum
			 * value to avoid data corruption that occurs when the
			 * timer expires during a transfer.  Yes, it's a bug.
			 */
			pcibios_read_config_word(pci_bus, pci_device_fn,
						 PCI_COMMAND, &pci_command);
			if ( ! (pci_command & PCI_COMMAND_MASTER))
			{
				printk("  Setting the PCI Master Bit!\n");
				pci_command |= PCI_COMMAND_MASTER;
				pcibios_write_config_word(pci_bus,
						pci_device_fn,
						PCI_COMMAND, pci_command);
			}
			pcibios_read_config_byte(pci_bus, pci_device_fn,
					 PCI_LATENCY_TIMER, &pci_latency);
			if (pci_latency != 255)
			{
				printk("  Overriding PCI latency timer: "
					"was %d, now is 255.\n", pci_latency);
				pcibios_write_config_byte(pci_bus,
						pci_device_fn,
						PCI_LATENCY_TIMER, 255);
			}

			dgrs_found_device(dev, io, mem, irq, plxreg, plxdma);

			dev = 0;
			cards_found++;
		}
	}

	/*
	 *	Second, check for EISA boards
	 */
	if (EISA_bus)
	{
		for (io = 0x1000; io < 0x9000; io += 0x1000)
		{
			if (inb(io+ES4H_MANUFmsb) != 0x10
				|| inb(io+ES4H_MANUFlsb) != 0x49
				|| inb(io+ES4H_PRODUCT) != ES4H_PRODUCT_CODE)
				continue;

			if ( ! (inb(io+ES4H_EC) & ES4H_EC_ENABLE) )
				continue; /* Not EISA configured */

			mem = (inb(io+ES4H_AS_31_24) << 24)
				+ (inb(io+ES4H_AS_23_16) << 16);

			irq = is2iv[ inb(io+ES4H_IS) & ES4H_IS_INTMASK ];

			dgrs_found_device(dev, io, mem, irq, 0L, 0L);

			dev = 0;
			++cards_found;
		}
	}

	return cards_found;
}

/*
 *	Module/driver initialization points.  Two ways, depending on
 *	whether we are a module or statically linked, ala Don Becker's
 *	3c59x driver.
 */

#ifdef MODULE

/*
 *	Variables that can be overriden from command line
 */
static int	debug = -1;
static int	dma = -1;
static int	hashexpire = -1;
static int	spantree = -1;
static int	ipaddr[4] = { -1 };
static int	iptrap[4] = { -1 };
static __u32	ipxnet = -1;
static int	nicmode = -1;

MODULE_PARM(debug, "i");
MODULE_PARM(dma, "i");
MODULE_PARM(hashexpire, "i");
MODULE_PARM(spantree, "i");
MODULE_PARM(ipaddr, "1-4i");
MODULE_PARM(iptrap, "1-4i");
MODULE_PARM(ipxnet, "i");
MODULE_PARM(nicmode, "i");

int
init_module(void)
{
	int	cards_found;
	int	i;

	/*
	 *	Command line variable overrides
	 *		debug=NNN
	 *		dma=0/1
	 *		spantree=0/1
	 *		hashexpire=NNN
	 *		ipaddr=A,B,C,D
	 *		iptrap=A,B,C,D
	 *		ipxnet=NNN
	 *		nicmode=NNN
	 */
	if (debug >= 0)
		dgrs_debug = debug;
	if (dma >= 0)
		dgrs_dma = dma;
	if (nicmode >= 0)
		dgrs_nicmode = nicmode;
	if (hashexpire >= 0)
		dgrs_hashexpire = hashexpire;
	if (spantree >= 0)
		dgrs_spantree = spantree;
	if (ipaddr[0] != -1)
		for (i = 0; i < 4; ++i)
			dgrs_ipaddr[i] = ipaddr[i];
	if (iptrap[0] != -1)
		for (i = 0; i < 4; ++i)
			dgrs_iptrap[i] = iptrap[i];
	if (ipxnet != -1)
		dgrs_ipxnet = htonl( ipxnet );

	if (dgrs_debug)
	{
		printk("dgrs: SW=%s FW=Build %d %s\n",
			version, dgrs_firmnum, dgrs_firmdate);
	}

	/*
	 *	Find and configure all the cards
	 */
	dgrs_root_dev = NULL;
	cards_found = dgrs_scan(0);

	return cards_found ? 0 : -ENODEV;
}

void
cleanup_module(void)
{
        while (dgrs_root_dev)
	{
		struct device	*next_dev;
		DGRS_PRIV	*priv;

		priv = (DGRS_PRIV *) dgrs_root_dev->priv;
                next_dev = priv->next_dev;
                unregister_netdev(dgrs_root_dev);

		proc_reset(priv->devtbl[0], 1);

		if (priv->vmem)
			IOUNMAP(priv->vmem);
		if (priv->vplxdma)
			IOUNMAP((uchar *) priv->vplxdma);

		release_region(dgrs_root_dev->base_addr, 256);

		if (dgrs_root_dev->irq)
			free_irq(dgrs_root_dev->irq, dgrs_root_dev);

                kfree(dgrs_root_dev);
                dgrs_root_dev = next_dev;
        }
}

#else

__initfunc(int
dgrs_probe(struct device *dev))
{
	int	cards_found;

	cards_found = dgrs_scan(dev);
	if (dgrs_debug && cards_found)
		printk("dgrs: SW=%s FW=Build %d %s\n",
			version, dgrs_firmnum, dgrs_firmdate);
	return cards_found ? 0 : -ENODEV;
}
#endif
