|  | /* | 
|  | *	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. | 
|  | * | 
|  | *	Fixes: | 
|  | *	Arnaldo Carvalho de Melo <acme@conectiva.com.br> - 11/01/2001 | 
|  | *	- fix dgrs_found_device wrt checking kmalloc return and | 
|  | *	rollbacking the partial steps of the whole process when | 
|  | *	one of the devices can't be allocated. Fix SET_MODULE_OWNER | 
|  | *	on the loop to use devN instead of repeated calls to dev. | 
|  | * | 
|  | *	davej <davej@suse.de> - 9/2/2001 | 
|  | *	- Enable PCI device before reading ioaddr/irq | 
|  | * | 
|  | */ | 
|  |  | 
|  | #include <linux/module.h> | 
|  | #include <linux/eisa.h> | 
|  | #include <linux/kernel.h> | 
|  | #include <linux/string.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/errno.h> | 
|  | #include <linux/ioport.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/interrupt.h> | 
|  | #include <linux/pci.h> | 
|  | #include <linux/init.h> | 
|  | #include <linux/netdevice.h> | 
|  | #include <linux/etherdevice.h> | 
|  | #include <linux/skbuff.h> | 
|  | #include <linux/bitops.h> | 
|  |  | 
|  | #include <asm/io.h> | 
|  | #include <asm/byteorder.h> | 
|  | #include <asm/uaccess.h> | 
|  |  | 
|  | static char version[] __initdata = | 
|  | "$Id: dgrs.c,v 1.13 2000/06/06 04:07:00 rick Exp $"; | 
|  |  | 
|  | /* | 
|  | *	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" | 
|  |  | 
|  | #ifdef CONFIG_PCI | 
|  | static struct pci_device_id dgrs_pci_tbl[] = { | 
|  | { SE6_PCI_VENDOR_ID, SE6_PCI_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, }, | 
|  | { }			/* Terminating entry */ | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(pci, dgrs_pci_tbl); | 
|  | #endif | 
|  |  | 
|  | #ifdef CONFIG_EISA | 
|  | static struct eisa_device_id dgrs_eisa_tbl[] = { | 
|  | { "DBI0A01" }, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(eisa, dgrs_eisa_tbl); | 
|  | #endif | 
|  |  | 
|  | MODULE_LICENSE("GPL"); | 
|  |  | 
|  |  | 
|  | /* | 
|  | *	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(). | 
|  | */ | 
|  | static int	dgrs_debug = 1; | 
|  | static int	dgrs_dma = 1; | 
|  | static int	dgrs_spantree = -1; | 
|  | static int	dgrs_hashexpire = -1; | 
|  | static uchar	dgrs_ipaddr[4] = { 0xff, 0xff, 0xff, 0xff}; | 
|  | static uchar	dgrs_iptrap[4] = { 0xff, 0xff, 0xff, 0xff}; | 
|  | static __u32	dgrs_ipxnet = -1; | 
|  | static int	dgrs_nicmode; | 
|  |  | 
|  | /* | 
|  | *	Private per-board data structure (dev->priv) | 
|  | */ | 
|  | typedef struct | 
|  | { | 
|  | /* | 
|  | *	Stuff for generic ethercard I/F | 
|  | */ | 
|  | 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 net_device	*devtbl[6];	/* Ptrs to N device structs */ | 
|  |  | 
|  | } DGRS_PRIV; | 
|  |  | 
|  |  | 
|  | /* | 
|  | *	reset or un-reset the IDT processor | 
|  | */ | 
|  | static void | 
|  | proc_reset(struct net_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 net_device *dev0) | 
|  | { | 
|  | DGRS_PRIV	*priv0 = (DGRS_PRIV *) dev0->priv; | 
|  | ulong	x; | 
|  |  | 
|  | /* | 
|  | *	If Space.c says not to use DMA, or if it's 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) { | 
|  | iounmap((void *)priv0->vplxdma); | 
|  | 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 it's a chaining DMA, and lcladdr is | 
|  | *	the address of the first DMA descriptor in the chain. | 
|  | * | 
|  | *	If pciaddr is not NULL, then it's 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 net_device *dev, | 
|  | ulong pciaddr, | 
|  | ulong lcladdr, | 
|  | int len, | 
|  | int to_host | 
|  | ) | 
|  | { | 
|  | int     	i; | 
|  | ulong   	csr = 0; | 
|  | 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. | 
|  | */ | 
|  | static void | 
|  | dgrs_rcv_frame( | 
|  | struct net_device	*dev0, | 
|  | DGRS_PRIV	*priv0, | 
|  | I596_CB		*cbp | 
|  | ) | 
|  | { | 
|  | int		len; | 
|  | I596_TBD	*tbdp; | 
|  | struct sk_buff	*skb; | 
|  | uchar		*putp; | 
|  | uchar		*p; | 
|  | struct net_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 it's 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 = %lx\n", devN->name, (long) 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 it's 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 = %lx\n", devN->name, (long) 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 = %lx\n", devN->name, (long) 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); | 
|  | devN->last_rx = jiffies; | 
|  | ++privN->stats.rx_packets; | 
|  | privN->stats.rx_bytes += len; | 
|  |  | 
|  | 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 net_device *devN) | 
|  | { | 
|  | DGRS_PRIV	*privN = (DGRS_PRIV *) devN->priv; | 
|  | struct net_device	*dev0; | 
|  | DGRS_PRIV	*priv0; | 
|  | I596_RBD	*rbdp; | 
|  | int		count; | 
|  | int		i, len, amt; | 
|  |  | 
|  | /* | 
|  | *	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; | 
|  | netif_start_queue(devN); | 
|  |  | 
|  | 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 = min_t(unsigned int, 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 net_device *dev ) | 
|  | { | 
|  | netif_start_queue(dev); | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* | 
|  | *	Close the interface | 
|  | */ | 
|  | static int dgrs_close( struct net_device *dev ) | 
|  | { | 
|  | netif_stop_queue(dev); | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* | 
|  | *	Get statistics | 
|  | */ | 
|  | static struct net_device_stats *dgrs_get_stats( struct net_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 net_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 net_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 (!capable(CAP_NET_ADMIN)) | 
|  | 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 irqreturn_t dgrs_intr(int irq, void *dev_id, struct pt_regs *regs) | 
|  | { | 
|  | struct net_device	*dev0 = (struct net_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) | 
|  | netif_wake_queue (priv0->devtbl[i]); | 
|  | } | 
|  | else | 
|  | netif_wake_queue (dev0); | 
|  | /* 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); | 
|  |  | 
|  | return IRQ_HANDLED; | 
|  | } | 
|  |  | 
|  | /* | 
|  | *	Download the board firmware | 
|  | */ | 
|  | static int __init | 
|  | dgrs_download(struct net_device *dev0) | 
|  | { | 
|  | DGRS_PRIV	*priv0 = (DGRS_PRIV *) dev0->priv; | 
|  | int		is; | 
|  | unsigned long	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); | 
|  | iounmap(priv0->vmem); | 
|  | priv0->vmem = NULL; | 
|  | 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); ) | 
|  | { | 
|  | barrier();		/* Gcc 2.95 needs this */ | 
|  | if (priv0->bcomm->bc_status >= BC_RUN) | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (priv0->bcomm->bc_status < BC_RUN) | 
|  | { | 
|  | printk("%s: board not operating\n", dev0->name); | 
|  | iounmap(priv0->vmem); | 
|  | priv0->vmem = NULL; | 
|  | 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 | 
|  | */ | 
|  | static int __init | 
|  | dgrs_probe1(struct net_device *dev) | 
|  | { | 
|  | DGRS_PRIV	*priv = (DGRS_PRIV *) dev->priv; | 
|  | unsigned long	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) | 
|  | goto err_out; | 
|  |  | 
|  | /* | 
|  | * 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); | 
|  | rc = -ENXIO; | 
|  | goto err_out; | 
|  | } | 
|  |  | 
|  | /* | 
|  | *	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) | 
|  | goto err_out; | 
|  |  | 
|  | priv->intrcnt = 0; | 
|  | for (i = jiffies + 2*HZ + HZ/2; time_after(i, jiffies); ) | 
|  | { | 
|  | cpu_relax(); | 
|  | if (priv->intrcnt >= 2) | 
|  | break; | 
|  | } | 
|  | if (priv->intrcnt < 2) | 
|  | { | 
|  | printk(KERN_ERR "%s: Not interrupting on IRQ %d (%d)\n", | 
|  | dev->name, dev->irq, priv->intrcnt); | 
|  | rc = -ENXIO; | 
|  | goto err_free_irq; | 
|  | } | 
|  |  | 
|  | /* | 
|  | *	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 rc; | 
|  |  | 
|  | err_free_irq: | 
|  | free_irq(dev->irq, dev); | 
|  | err_out: | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int __init | 
|  | dgrs_initclone(struct net_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); | 
|  | } | 
|  |  | 
|  | static struct net_device * __init | 
|  | dgrs_found_device( | 
|  | int		io, | 
|  | ulong		mem, | 
|  | int		irq, | 
|  | ulong		plxreg, | 
|  | ulong		plxdma, | 
|  | struct device   *pdev | 
|  | ) | 
|  | { | 
|  | DGRS_PRIV *priv; | 
|  | struct net_device *dev; | 
|  | int i, ret = -ENOMEM; | 
|  |  | 
|  | dev = alloc_etherdev(sizeof(DGRS_PRIV)); | 
|  | if (!dev) | 
|  | goto err0; | 
|  |  | 
|  | priv = (DGRS_PRIV *)dev->priv; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | SET_MODULE_OWNER(dev); | 
|  | SET_NETDEV_DEV(dev, pdev); | 
|  |  | 
|  | ret = dgrs_probe1(dev); | 
|  | if (ret) | 
|  | goto err1; | 
|  |  | 
|  | ret = register_netdev(dev); | 
|  | if (ret) | 
|  | goto err2; | 
|  |  | 
|  | if ( !dgrs_nicmode ) | 
|  | return dev;	/* 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 net_device	*devN; | 
|  | DGRS_PRIV	*privN; | 
|  | /* Allocate new dev and priv structures */ | 
|  | devN = alloc_etherdev(sizeof(DGRS_PRIV)); | 
|  | ret = -ENOMEM; | 
|  | if (!devN) | 
|  | goto fail; | 
|  |  | 
|  | /* Don't copy the network device structure! */ | 
|  |  | 
|  | /* copy the priv structure of dev[0] */ | 
|  | privN = (DGRS_PRIV *)devN->priv; | 
|  | *privN = *priv; | 
|  |  | 
|  | /* ... and zero out VM areas */ | 
|  | privN->vmem = NULL; | 
|  | privN->vplxdma = NULL; | 
|  | /* ... and zero out IRQ */ | 
|  | devN->irq = 0; | 
|  | /* ... and base MAC address off address of 1st port */ | 
|  | devN->dev_addr[5] += i; | 
|  |  | 
|  | ret = dgrs_initclone(devN); | 
|  | if (ret) | 
|  | goto fail; | 
|  |  | 
|  | SET_MODULE_OWNER(devN); | 
|  | SET_NETDEV_DEV(dev, pdev); | 
|  |  | 
|  | ret = register_netdev(devN); | 
|  | if (ret) { | 
|  | free_netdev(devN); | 
|  | goto fail; | 
|  | } | 
|  | privN->chan = i+1; | 
|  | priv->devtbl[i] = devN; | 
|  | } | 
|  | return dev; | 
|  |  | 
|  | fail: | 
|  | while (i >= 0) { | 
|  | struct net_device *d = priv->devtbl[i--]; | 
|  | unregister_netdev(d); | 
|  | free_netdev(d); | 
|  | } | 
|  |  | 
|  | err2: | 
|  | free_irq(dev->irq, dev); | 
|  | err1: | 
|  | free_netdev(dev); | 
|  | err0: | 
|  | return ERR_PTR(ret); | 
|  | } | 
|  |  | 
|  | static void __devexit dgrs_remove(struct net_device *dev) | 
|  | { | 
|  | DGRS_PRIV *priv = dev->priv; | 
|  | int i; | 
|  |  | 
|  | unregister_netdev(dev); | 
|  |  | 
|  | for (i = 1; i < priv->nports; ++i) { | 
|  | struct net_device *d = priv->devtbl[i]; | 
|  | if (d) { | 
|  | unregister_netdev(d); | 
|  | free_netdev(d); | 
|  | } | 
|  | } | 
|  |  | 
|  | proc_reset(priv->devtbl[0], 1); | 
|  |  | 
|  | if (priv->vmem) | 
|  | iounmap(priv->vmem); | 
|  | if (priv->vplxdma) | 
|  | iounmap((uchar *) priv->vplxdma); | 
|  |  | 
|  | if (dev->irq) | 
|  | free_irq(dev->irq, dev); | 
|  |  | 
|  | for (i = 1; i < priv->nports; ++i) { | 
|  | if (priv->devtbl[i]) | 
|  | unregister_netdev(priv->devtbl[i]); | 
|  | } | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_PCI | 
|  | static int __init dgrs_pci_probe(struct pci_dev *pdev, | 
|  | const struct pci_device_id *ent) | 
|  | { | 
|  | struct net_device *dev; | 
|  | int err; | 
|  | uint	io; | 
|  | uint	mem; | 
|  | uint	irq; | 
|  | uint	plxreg; | 
|  | uint	plxdma; | 
|  |  | 
|  | /* | 
|  | * 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. | 
|  | */ | 
|  | err = pci_enable_device(pdev); | 
|  | if (err) | 
|  | return err; | 
|  | err = pci_request_regions(pdev, "RightSwitch"); | 
|  | if (err) | 
|  | return err; | 
|  |  | 
|  | pci_set_master(pdev); | 
|  |  | 
|  | plxreg = pci_resource_start (pdev, 0); | 
|  | io = pci_resource_start (pdev, 1); | 
|  | mem = pci_resource_start (pdev, 2); | 
|  | pci_read_config_dword(pdev, 0x30, &plxdma); | 
|  | irq = pdev->irq; | 
|  | 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); | 
|  | pci_write_config_dword(pdev, 0x30, plxdma + 1); | 
|  | pci_read_config_dword(pdev, 0x30, &plxdma); | 
|  | plxdma &= ~15; | 
|  |  | 
|  | dev = dgrs_found_device(io, mem, irq, plxreg, plxdma, &pdev->dev); | 
|  | if (IS_ERR(dev)) { | 
|  | pci_release_regions(pdev); | 
|  | return PTR_ERR(dev); | 
|  | } | 
|  |  | 
|  | pci_set_drvdata(pdev, dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __devexit dgrs_pci_remove(struct pci_dev *pdev) | 
|  | { | 
|  | struct net_device *dev = pci_get_drvdata(pdev); | 
|  |  | 
|  | dgrs_remove(dev); | 
|  | pci_release_regions(pdev); | 
|  | free_netdev(dev); | 
|  | } | 
|  |  | 
|  | static struct pci_driver dgrs_pci_driver = { | 
|  | .name = "dgrs", | 
|  | .id_table = dgrs_pci_tbl, | 
|  | .probe = dgrs_pci_probe, | 
|  | .remove = __devexit_p(dgrs_pci_remove), | 
|  | }; | 
|  | #endif | 
|  |  | 
|  |  | 
|  | #ifdef CONFIG_EISA | 
|  | static int is2iv[8] __initdata = { 0, 3, 5, 7, 10, 11, 12, 15 }; | 
|  |  | 
|  | static int __init dgrs_eisa_probe (struct device *gendev) | 
|  | { | 
|  | struct net_device *dev; | 
|  | struct eisa_device *edev = to_eisa_device(gendev); | 
|  | uint	io = edev->base_addr; | 
|  | uint	mem; | 
|  | uint	irq; | 
|  | int 	rc = -ENODEV; /* Not EISA configured */ | 
|  |  | 
|  | if (!request_region(io, 256, "RightSwitch")) { | 
|  | printk(KERN_ERR "dgrs: eisa io 0x%x, which is busy.\n", io); | 
|  | return -EBUSY; | 
|  | } | 
|  |  | 
|  | if ( ! (inb(io+ES4H_EC) & ES4H_EC_ENABLE) ) | 
|  | goto err_out; | 
|  |  | 
|  | mem = (inb(io+ES4H_AS_31_24) << 24) | 
|  | + (inb(io+ES4H_AS_23_16) << 16); | 
|  |  | 
|  | irq = is2iv[ inb(io+ES4H_IS) & ES4H_IS_INTMASK ]; | 
|  |  | 
|  | dev = dgrs_found_device(io, mem, irq, 0L, 0L, gendev); | 
|  | if (IS_ERR(dev)) { | 
|  | rc = PTR_ERR(dev); | 
|  | goto err_out; | 
|  | } | 
|  |  | 
|  | gendev->driver_data = dev; | 
|  | return 0; | 
|  | err_out: | 
|  | release_region(io, 256); | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static int __devexit dgrs_eisa_remove(struct device *gendev) | 
|  | { | 
|  | struct net_device *dev = gendev->driver_data; | 
|  |  | 
|  | dgrs_remove(dev); | 
|  |  | 
|  | release_region(dev->base_addr, 256); | 
|  |  | 
|  | free_netdev(dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct eisa_driver dgrs_eisa_driver = { | 
|  | .id_table = dgrs_eisa_tbl, | 
|  | .driver = { | 
|  | .name = "dgrs", | 
|  | .probe = dgrs_eisa_probe, | 
|  | .remove = __devexit_p(dgrs_eisa_remove), | 
|  | } | 
|  | }; | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | *	Variables that can be overriden from module 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_param(debug, int, 0); | 
|  | module_param(dma, int, 0); | 
|  | module_param(hashexpire, int, 0); | 
|  | module_param(spantree, int, 0); | 
|  | module_param_array(ipaddr, int, NULL, 0); | 
|  | module_param_array(iptrap, int, NULL, 0); | 
|  | module_param(ipxnet, int, 0); | 
|  | module_param(nicmode, int, 0); | 
|  | MODULE_PARM_DESC(debug, "Digi RightSwitch enable debugging (0-1)"); | 
|  | MODULE_PARM_DESC(dma, "Digi RightSwitch enable BM DMA (0-1)"); | 
|  | MODULE_PARM_DESC(nicmode, "Digi RightSwitch operating mode (1: switch, 2: multi-NIC)"); | 
|  |  | 
|  | static int __init dgrs_init_module (void) | 
|  | { | 
|  | int	i; | 
|  | int eisacount = 0, pcicount = 0; | 
|  |  | 
|  | /* | 
|  | *	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(KERN_INFO "dgrs: SW=%s FW=Build %d %s\nFW Version=%s\n", | 
|  | version, dgrs_firmnum, dgrs_firmdate, dgrs_firmver); | 
|  | } | 
|  |  | 
|  | /* | 
|  | *	Find and configure all the cards | 
|  | */ | 
|  | #ifdef CONFIG_EISA | 
|  | eisacount = eisa_driver_register(&dgrs_eisa_driver); | 
|  | if (eisacount < 0) | 
|  | return eisacount; | 
|  | #endif | 
|  | #ifdef CONFIG_PCI | 
|  | pcicount = pci_register_driver(&dgrs_pci_driver); | 
|  | if (pcicount) | 
|  | return pcicount; | 
|  | #endif | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void __exit dgrs_cleanup_module (void) | 
|  | { | 
|  | #ifdef CONFIG_EISA | 
|  | eisa_driver_unregister (&dgrs_eisa_driver); | 
|  | #endif | 
|  | #ifdef CONFIG_PCI | 
|  | pci_unregister_driver (&dgrs_pci_driver); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | module_init(dgrs_init_module); | 
|  | module_exit(dgrs_cleanup_module); |