| /* |
| * 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); |