mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
![Arnaldo Carvalho de Melo](/assets/img/avatar_default.png)
To clearly state the intent of copying from linear sk_buffs, _offset being a overly long variant but interesting for the sake of saving some bytes. Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
1615 lines
38 KiB
C
1615 lines
38 KiB
C
/*
|
|
* 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;
|
|
#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_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);
|
|
skb_copy_from_linear_data_offset(skb, i, S2H(rbdp->buf) + count, 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 net_device *dev0 = dev_id;
|
|
DGRS_PRIV *priv0 = 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 const 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, IRQF_SHARED, "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),
|
|
};
|
|
#else
|
|
static struct pci_driver dgrs_pci_driver = {};
|
|
#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 err;
|
|
|
|
/*
|
|
* 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
|
|
err = eisa_driver_register(&dgrs_eisa_driver);
|
|
if (err)
|
|
return err;
|
|
#endif
|
|
err = pci_register_driver(&dgrs_pci_driver);
|
|
if (err)
|
|
return err;
|
|
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);
|