URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [tags/] [before_ORP/] [uclinux/] [uClinux-2.0.x/] [drivers/] [net/] [dgrs.c] - Rev 901
Go to most recent revision | Compare with Previous | Blame | View Log
/* * 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@dgii.com, rick_richardson@dgii.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 independant NICs. To select multi-NIC * mode, add "nicmode=1" on the insmod load line for the driver. * * This driver uses the "dev" common ethernet device structure * and a private "priv" (dev->priv) structure that contains * mostly DGRS-specific information and statistics. To keep * the code for both the switch mode and the multi-NIC mode * as similar as possible, I have introduced the concept of * "dev0"/"priv0" and "devN"/"privN" pointer pairs in subroutines * where needed. The first pair of pointers points to the * "dev" and "priv" structures of the zeroth (0th) device * interface associated with a board. The second pair of * pointers points to the current (Nth) device interface * for the board: the one for which we are processing data. * * In switch mode, the pairs of pointers are always the same, * that is, dev0 == devN and priv0 == privN. This is just * like previous releases of this driver which did not support * NIC mode. * * In multi-NIC mode, the pairs of pointers may be different. * We use the devN and privN pointers to reference just the * name, port number, and statistics for the current interface. * We use the dev0 and priv0 pointers to access the variables * that control access to the board, such as board address * and simulated 82596 variables. This is because there is * only one "fake" 82596 that serves as the interface to * the board. We do not want to try to keep the variables * associated with this 82596 in sync across all devices. * * This scheme works well. As you will see, except for * initialization, there is very little difference between * the two modes as far as this driver is concerned. On the * receive side in NIC mode, the interrupt *always* comes in on * the 0th interface (dev0/priv0). We then figure out which * real 82596 port it came in on from looking at the "chan" * member that the board firmware adds at the end of each * RBD (a.k.a. TBD). We get the channel number like this: * int chan = ((I596_RBD *) S2H(cbp->xmit.tbdp))->chan; * * On the transmit side in multi-NIC mode, we specify the * output 82596 port by setting the new "dstchan" structure * member that is at the end of the RFD, like this: * priv0->rfdp->dstchan = privN->chan; * * TODO: * - Multi-NIC mode is not yet supported when the driver is linked * into the kernel. * - Better handling of multicast addresses. * */ static char *version = "$Id: dgrs.c,v 1.1.1.1 2001-09-10 07:44:21 simons Exp $"; #include <linux/version.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/string.h> #include <linux/delay.h> #include <linux/errno.h> #include <linux/ioport.h> #include <linux/malloc.h> #include <linux/interrupt.h> #include <linux/pci.h> #include <linux/bios32.h> #include <asm/bitops.h> #include <asm/io.h> #include <asm/byteorder.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/types.h> /* * API changed at linux version 2.1.0 */ #if LINUX_VERSION_CODE >= 0x20100 #include <asm/uaccess.h> #define IOREMAP(ADDR, LEN) ioremap(ADDR, LEN) #define IOUNMAP(ADDR) iounmap(ADDR) #define COPY_FROM_USER(DST,SRC,LEN) copy_from_user(DST,SRC,LEN) #define COPY_TO_USER(DST,SRC,LEN) copy_to_user(DST,SRC,LEN) #else #define IOREMAP(ADDR, LEN) vremap(ADDR, LEN) #define IOUNMAP(ADDR) vfree(ADDR) #define COPY_FROM_USER(DST,SRC,LEN) memcpy_fromfs(DST,SRC,LEN) #define COPY_TO_USER(DST,SRC,LEN) memcpy_tofs(DST,SRC,LEN) #endif /* * DGRS include files */ typedef unsigned char uchar; typedef unsigned int bool; #define vol volatile #include "dgrs.h" #include "dgrs_es4h.h" #include "dgrs_plx9060.h" #include "dgrs_i82596.h" #include "dgrs_ether.h" #include "dgrs_asstruct.h" #include "dgrs_bcomm.h" /* * Firmware. Compiled separately for local compilation, * but #included for Linux distribution. */ #ifndef NOFW #include "dgrs_firmware.c" #else extern int dgrs_firmnum; extern char dgrs_firmver[]; extern char dgrs_firmdate[]; extern uchar dgrs_code[]; extern int dgrs_ncode; #endif /* * Linux out*() is backwards from all other operating systems */ #define OUTB(ADDR, VAL) outb(VAL, ADDR) #define OUTW(ADDR, VAL) outw(VAL, ADDR) #define OUTL(ADDR, VAL) outl(VAL, ADDR) /* * Macros to convert switch to host and host to switch addresses * (assumes a local variable priv points to board dependent struct) */ #define S2H(A) ( ((unsigned long)(A)&0x00ffffff) + priv0->vmem ) #define S2HN(A) ( ((unsigned long)(A)&0x00ffffff) + privN->vmem ) #define H2S(A) ( ((char *) (A) - priv0->vmem) + 0xA3000000 ) /* * Convert a switch address to a "safe" address for use with the * PLX 9060 DMA registers and the associated HW kludge that allows * for host access of the DMA registers. */ #define S2DMA(A) ( (unsigned long)(A) & 0x00ffffff) /* * "Space.c" variables, now settable from module interface * Use the name below, minus the "dgrs_" prefix. See init_module(). */ int dgrs_debug = 1; int dgrs_dma = 1; int dgrs_spantree = -1; int dgrs_hashexpire = -1; uchar dgrs_ipaddr[4] = { 0xff, 0xff, 0xff, 0xff}; uchar dgrs_iptrap[4] = { 0xff, 0xff, 0xff, 0xff}; long dgrs_ipxnet = -1; int dgrs_nicmode = 0; /* * Chain of device structures */ #ifdef MODULE static struct device *dgrs_root_dev = NULL; #endif /* * Private per-board data structure (dev->priv) */ typedef struct { /* * Stuff for generic ethercard I/F */ char devname[8]; /* "ethN" string */ struct device *next_dev; struct enet_statistics 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 */ int intrcnt; /* Count of interrupts */ /* * SE-4 (EISA) board variables */ uchar is_reg; /* EISA: Value for ES4H_IS reg */ /* * SE-6 (PCI) board variables * * The PLX "expansion rom" space is used for DMA register * access from the host on the SE-6. These are the physical * and virtual addresses of that space. */ ulong plxreg; /* Phys address of PLX chip */ char *vplxreg; /* Virtual address of PLX chip */ ulong plxdma; /* Phys addr of PLX "expansion rom" */ ulong volatile *vplxdma; /* Virtual addr of "expansion rom" */ int use_dma; /* Flag: use DMA */ DMACHAIN *dmadesc_s; /* area for DMA chains (SW addr.) */ DMACHAIN *dmadesc_h; /* area for DMA chains (Host Virtual) */ /* * Multi-NIC mode variables * * All entries of the devtbl[] array are valid for the 0th * device (i.e. eth0, but not eth1...eth5). devtbl[0] is * valid for all devices (i.e. eth0, eth1, ..., eth5). */ int nports; /* Number of physical ports (4 or 6) */ int chan; /* Channel # (1-6) for this device */ struct device *devtbl[6]; /* Ptrs to N device structs */ } DGRS_PRIV; /* * reset or un-reset the IDT processor */ static void proc_reset(struct device *dev0, int reset) { DGRS_PRIV *priv0 = (DGRS_PRIV *) dev0->priv; if (priv0->plxreg) { ulong val; val = inl(dev0->base_addr + PLX_MISC_CSR); if (reset) val |= SE6_RESET; else val &= ~SE6_RESET; OUTL(dev0->base_addr + PLX_MISC_CSR, val); } else { OUTB(dev0->base_addr + ES4H_PC, reset ? ES4H_PC_RESET : 0); } } /* * See if the board supports bus master DMA */ static int check_board_dma(struct device *dev0) { DGRS_PRIV *priv0 = (DGRS_PRIV *) dev0->priv; ulong x; /* * If Space.c says not to use DMA, or if its not a PLX based * PCI board, or if the expansion ROM space is not PCI * configured, then return false. */ if (!dgrs_dma || !priv0->plxreg || !priv0->plxdma) return (0); /* * Set the local address remap register of the "expansion rom" * area to 0x80000000 so that we can use it to access the DMA * registers from the host side. */ OUTL(dev0->base_addr + PLX_ROM_BASE_ADDR, 0x80000000); /* * Set the PCI region descriptor to: * Space 0: * disable read-prefetch * enable READY * enable BURST * 0 internal wait states * Expansion ROM: (used for host DMA register access) * disable read-prefetch * enable READY * disable BURST * 0 internal wait states */ OUTL(dev0->base_addr + PLX_BUS_REGION, 0x49430343); /* * Now map the DMA registers into our virtual space */ priv0->vplxdma = (ulong *) IOREMAP (priv0->plxdma, 256); if (!priv0->vplxdma) { printk("%s: can't *remap() the DMA regs\n", dev0->name); return (0); } /* * Now test to see if we can access the DMA registers * If we write -1 and get back 1FFF, then we accessed the * DMA register. Otherwise, we probably have an old board * and wrote into regular RAM. */ priv0->vplxdma[PLX_DMA0_MODE/4] = 0xFFFFFFFF; x = priv0->vplxdma[PLX_DMA0_MODE/4]; if (x != 0x00001FFF) return (0); return (1); } /* * Initiate DMA using PLX part on PCI board. Spin the * processor until completed. All addresses are physical! * * If pciaddr is NULL, then its a chaining DMA, and lcladdr is * the address of the first DMA descriptor in the chain. * * If pciaddr is not NULL, then its a single DMA. * * In either case, "lcladdr" must have been fixed up to make * sure the MSB isn't set using the S2DMA macro before passing * the address to this routine. */ static int do_plx_dma( struct device *dev, ulong pciaddr, ulong lcladdr, int len, int to_host ) { int i; ulong csr; DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; if (pciaddr) { /* * Do a single, non-chain DMA */ priv->vplxdma[PLX_DMA0_PCI_ADDR/4] = pciaddr; priv->vplxdma[PLX_DMA0_LCL_ADDR/4] = lcladdr; priv->vplxdma[PLX_DMA0_SIZE/4] = len; priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = to_host ? PLX_DMA_DESC_TO_HOST : PLX_DMA_DESC_TO_BOARD; priv->vplxdma[PLX_DMA0_MODE/4] = PLX_DMA_MODE_WIDTH32 | PLX_DMA_MODE_WAITSTATES(0) | PLX_DMA_MODE_READY | PLX_DMA_MODE_NOBTERM | PLX_DMA_MODE_BURST | PLX_DMA_MODE_NOCHAIN; } else { /* * Do a chaining DMA */ priv->vplxdma[PLX_DMA0_MODE/4] = PLX_DMA_MODE_WIDTH32 | PLX_DMA_MODE_WAITSTATES(0) | PLX_DMA_MODE_READY | PLX_DMA_MODE_NOBTERM | PLX_DMA_MODE_BURST | PLX_DMA_MODE_CHAIN; priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = lcladdr; } priv->vplxdma[PLX_DMA_CSR/4] = PLX_DMA_CSR_0_ENABLE | PLX_DMA_CSR_0_START; /* * Wait for DMA to complete */ for (i = 0; i < 1000000; ++i) { /* * Spin the host CPU for 1 usec, so we don't thrash * the PCI bus while the PLX 9060 is doing DMA. */ udelay(1); csr = (volatile int) priv->vplxdma[PLX_DMA_CSR/4]; if (csr & PLX_DMA_CSR_0_DONE) break; } if ( ! (csr & PLX_DMA_CSR_0_DONE) ) { printk("%s: DMA done never occurred. DMA disabled.\n", dev->name); priv->use_dma = 0; return 1; } return 0; } /* * dgrs_rcv_frame() * * Process a received frame. This is called from the interrupt * routine, and works for both switch mode and multi-NIC mode. * * Note that when in multi-NIC mode, we want to always access the * hardware using the dev and priv structures of the first port, * so that we are using only one set of variables to maintain * the board interface status, but we want to use the Nth port * dev and priv structures to maintain statistics and to pass * the packet up. * * Only the first device structure is attached to the interrupt. * We use the special "chan" variable at the end of the first RBD * to select the Nth device in multi-NIC mode. * * We currently do chained DMA on a per-packet basis when the * packet is "long", and we spin the CPU a short time polling * for DMA completion. This avoids a second interrupt overhead, * and gives the best performance for light traffic to the host. * * However, a better scheme that could be implemented would be * to see how many packets are outstanding for the host, and if * the number is "large", create a long chain to DMA several * packets into the host in one go. In this case, we would set * up some state variables to let the host CPU continue doing * other things until a DMA completion interrupt comes along. */ void dgrs_rcv_frame( struct device *dev0, DGRS_PRIV *priv0, I596_CB *cbp ) { int len; I596_TBD *tbdp; struct sk_buff *skb; uchar *putp; uchar *p; struct device *devN; DGRS_PRIV *privN; /* * Determine Nth priv and dev structure pointers */ if (dgrs_nicmode) { /* Multi-NIC mode */ int chan = ((I596_RBD *) S2H(cbp->xmit.tbdp))->chan; devN = priv0->devtbl[chan-1]; /* * If devN is null, we got an interrupt before the I/F * has been initialized. Pitch the packet. */ if (devN == NULL) goto out; privN = (DGRS_PRIV *) devN->priv; } else { /* Switch mode */ devN = dev0; privN = priv0; } if (0) printk("%s: rcv len=%ld\n", devN->name, cbp->xmit.count); /* * Allocate a message block big enough to hold the whole frame */ len = cbp->xmit.count; if ((skb = dev_alloc_skb(len+5)) == NULL) { printk("%s: dev_alloc_skb failed for rcv buffer\n", devN->name); ++privN->stats.rx_dropped; /* discarding the frame */ goto out; } skb->dev = devN; skb_reserve(skb, 2); /* Align IP header */ again: putp = p = skb_put(skb, len); /* * There are three modes here for doing the packet copy. * If we have DMA, and the packet is "long", we use the * chaining mode of DMA. If its shorter, we use single * DMA's. Otherwise, we use memcpy(). */ if (priv0->use_dma && priv0->dmadesc_h && len > 64) { /* * If we can use DMA and its a long frame, copy it using * DMA chaining. */ DMACHAIN *ddp_h; /* Host virtual DMA desc. pointer */ DMACHAIN *ddp_s; /* Switch physical DMA desc. pointer */ uchar *phys_p; /* * Get the physical address of the STREAMS buffer. * NOTE: allocb() guarantees that the whole buffer * is in a single page if the length < 4096. */ phys_p = (uchar *) virt_to_phys(putp); ddp_h = priv0->dmadesc_h; ddp_s = priv0->dmadesc_s; tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); for (;;) { int count; int amt; count = tbdp->count; amt = count & 0x3fff; if (amt == 0) break; /* For safety */ if ( (p-putp) >= len) { printk("%s: cbp = %x\n", devN->name, H2S(cbp)); proc_reset(dev0, 1); /* Freeze IDT */ break; /* For Safety */ } ddp_h->pciaddr = (ulong) phys_p; ddp_h->lcladdr = S2DMA(tbdp->buf); ddp_h->len = amt; phys_p += amt; p += amt; if (count & I596_TBD_EOF) { ddp_h->next = PLX_DMA_DESC_TO_HOST | PLX_DMA_DESC_EOC; ++ddp_h; break; } else { ++ddp_s; ddp_h->next = PLX_DMA_DESC_TO_HOST | (ulong) ddp_s; tbdp = (I596_TBD *) S2H(tbdp->next); ++ddp_h; } } if (ddp_h - priv0->dmadesc_h) { int rc; rc = do_plx_dma(dev0, 0, (ulong) priv0->dmadesc_s, len, 0); if (rc) { printk("%s: Chained DMA failure\n", devN->name); goto again; } } } else if (priv0->use_dma) { /* * If we can use DMA and its a shorter frame, copy it * using single DMA transfers. */ uchar *phys_p; /* * Get the physical address of the STREAMS buffer. * NOTE: allocb() guarantees that the whole buffer * is in a single page if the length < 4096. */ phys_p = (uchar *) virt_to_phys(putp); tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); for (;;) { int count; int amt; int rc; count = tbdp->count; amt = count & 0x3fff; if (amt == 0) break; /* For safety */ if ( (p-putp) >= len) { printk("%s: cbp = %x\n", devN->name, H2S(cbp)); proc_reset(dev0, 1); /* Freeze IDT */ break; /* For Safety */ } rc = do_plx_dma(dev0, (ulong) phys_p, S2DMA(tbdp->buf), amt, 1); if (rc) { memcpy(p, S2H(tbdp->buf), amt); printk("%s: Single DMA failed\n", devN->name); } phys_p += amt; p += amt; if (count & I596_TBD_EOF) break; tbdp = (I596_TBD *) S2H(tbdp->next); } } else { /* * Otherwise, copy it piece by piece using memcpy() */ tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); for (;;) { int count; int amt; count = tbdp->count; amt = count & 0x3fff; if (amt == 0) break; /* For safety */ if ( (p-putp) >= len) { printk("%s: cbp = %x\n", devN->name, H2S(cbp)); proc_reset(dev0, 1); /* Freeze IDT */ break; /* For Safety */ } memcpy(p, S2H(tbdp->buf), amt); p += amt; if (count & I596_TBD_EOF) break; tbdp = (I596_TBD *) S2H(tbdp->next); } } /* * Pass the frame to upper half */ skb->protocol = eth_type_trans(skb, devN); netif_rx(skb); ++privN->stats.rx_packets; out: cbp->xmit.status = I596_CB_STATUS_C | I596_CB_STATUS_OK; } /* * Start transmission of a frame * * The interface to the board is simple: we pretend that we are * a fifth 82596 ethernet controller 'receiving' data, and copy the * data into the same structures that a real 82596 would. This way, * the board firmware handles the host 'port' the same as any other. * * NOTE: we do not use Bus master DMA for this routine. Turns out * that it is not needed. Slave writes over the PCI bus are about * as fast as DMA, due to the fact that the PLX part can do burst * writes. The same is not true for data being read from the board. * * For multi-NIC mode, we tell the firmware the desired 82596 * output port by setting the special "dstchan" member at the * end of the traditional 82596 RFD structure. */ static int dgrs_start_xmit(struct sk_buff *skb, struct device *devN) { DGRS_PRIV *privN = (DGRS_PRIV *) devN->priv; struct device *dev0; DGRS_PRIV *priv0; I596_RBD *rbdp; int count; int i, len, amt; # define mymin(A,B) ( (A) < (B) ? (A) : (B) ) /* * Determine 0th priv and dev structure pointers */ if (dgrs_nicmode) { dev0 = privN->devtbl[0]; priv0 = (DGRS_PRIV *) dev0->priv; } else { dev0 = devN; priv0 = privN; } if (dgrs_debug > 1) printk("%s: xmit len=%d\n", devN->name, (int) skb->len); devN->trans_start = jiffies; devN->tbusy = 0; if (priv0->rfdp->cmd & I596_RFD_EL) { /* Out of RFD's */ if (0) printk("%s: NO RFD's\n", devN->name); goto no_resources; } rbdp = priv0->rbdp; count = 0; priv0->rfdp->rbdp = (I596_RBD *) H2S(rbdp); i = 0; len = skb->len; for (;;) { if (rbdp->size & I596_RBD_EL) { /* Out of RBD's */ if (0) printk("%s: NO RBD's\n", devN->name); goto no_resources; } amt = mymin(len, rbdp->size - count); memcpy( (char *) S2H(rbdp->buf) + count, skb->data + i, amt); i += amt; count += amt; len -= amt; if (len == 0) { if (skb->len < 60) rbdp->count = 60 | I596_RBD_EOF; else rbdp->count = count | I596_RBD_EOF; rbdp = (I596_RBD *) S2H(rbdp->next); goto frame_done; } else if (count < 32) { /* More data to come, but we used less than 32 * bytes of this RBD. Keep filling this RBD. */ {} /* Yes, we do nothing here */ } else { rbdp->count = count; rbdp = (I596_RBD *) S2H(rbdp->next); count = 0; } } frame_done: priv0->rbdp = rbdp; if (dgrs_nicmode) priv0->rfdp->dstchan = privN->chan; priv0->rfdp->status = I596_RFD_C | I596_RFD_OK; priv0->rfdp = (I596_RFD *) S2H(priv0->rfdp->next); ++privN->stats.tx_packets; dev_kfree_skb (skb, FREE_WRITE); return (0); no_resources: priv0->scbp->status |= I596_SCB_RNR; /* simulate I82596 */ return (-EAGAIN); } /* * Open the interface */ static int dgrs_open( struct device *dev ) { dev->tbusy = 0; dev->interrupt = 0; dev->start = 1; #ifdef MODULE MOD_INC_USE_COUNT; #endif return (0); } /* * Close the interface */ static int dgrs_close( struct device *dev ) { dev->start = 0; dev->tbusy = 1; #ifdef MODULE MOD_DEC_USE_COUNT; #endif return (0); } /* * Get statistics */ static struct enet_statistics * dgrs_get_stats( struct device *dev ) { DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; return (&priv->stats); } /* * Set multicast list and/or promiscuous mode */ static void dgrs_set_multicast_list( struct device *dev) { DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; priv->port->is_promisc = (dev->flags & IFF_PROMISC) ? 1 : 0; } /* * Unique ioctl's */ static int dgrs_ioctl(struct device *devN, struct ifreq *ifr, int cmd) { DGRS_PRIV *privN = (DGRS_PRIV *) devN->priv; DGRS_IOCTL ioc; int i, rc; rc = verify_area(VERIFY_WRITE, ifr->ifr_data, sizeof(DGRS_IOCTL)); if (rc) return (rc); if (cmd != DGRSIOCTL) return -EINVAL; COPY_FROM_USER(&ioc, ifr->ifr_data, sizeof(DGRS_IOCTL)); switch (ioc.cmd) { case DGRS_GETMEM: if (ioc.len != sizeof(ulong)) return -EINVAL; rc = verify_area(VERIFY_WRITE, (void *) ioc.data, ioc.len); if (rc) return (rc); COPY_TO_USER(ioc.data, &devN->mem_start, ioc.len); return (0); case DGRS_SETFILTER: rc = verify_area(VERIFY_READ, (void *) ioc.data, ioc.len); if (rc) return (rc); 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 int) 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) { COPY_FROM_USER(S2HN(privN->bcomm->bc_filter_area), ioc.data, ioc.len); privN->bcomm->bc_filter_cmd = BC_FILTER_SET; } else privN->bcomm->bc_filter_cmd = BC_FILTER_CLR; return(0); default: return -EOPNOTSUPP; } } /* * Process interrupts * * dev, priv will always refer to the 0th device in Multi-NIC mode. */ static void dgrs_intr(int irq, void *dev_id, struct pt_regs *regs) { struct device *dev0 = (struct device *) dev_id; DGRS_PRIV *priv0 = (DGRS_PRIV *) dev0->priv; I596_CB *cbp; int cmd; int i; ++priv0->intrcnt; if (1) ++priv0->bcomm->bc_cnt[4]; if (0) { static int cnt = 100; if (--cnt > 0) printk("%s: interrupt: irq %d\n", dev0->name, irq); } /* * Get 596 command */ cmd = priv0->scbp->cmd; /* * See if RU has been restarted */ if ( (cmd & I596_SCB_RUC) == I596_SCB_RUC_START) { if (0) printk("%s: RUC start\n", dev0->name); priv0->rfdp = (I596_RFD *) S2H(priv0->scbp->rfdp); priv0->rbdp = (I596_RBD *) S2H(priv0->rfdp->rbdp); priv0->scbp->status &= ~(I596_SCB_RNR|I596_SCB_RUS); /* * Tell upper half (halves) */ if (dgrs_nicmode) { for (i = 0; i < priv0->nports; ++i) priv0->devtbl[i]->tbusy = 0; } else dev0->tbusy = 0; /* if (bd->flags & TX_QUEUED) DL_sched(bd, bdd); */ } /* * See if any CU commands to process */ if ( (cmd & I596_SCB_CUC) != I596_SCB_CUC_START) { priv0->scbp->cmd = 0; /* Ignore all other commands */ goto ack_intr; } priv0->scbp->status &= ~(I596_SCB_CNA|I596_SCB_CUS); /* * Process a command */ cbp = (I596_CB *) S2H(priv0->scbp->cbp); priv0->scbp->cmd = 0; /* Safe to clear the command */ for (;;) { switch (cbp->nop.cmd & I596_CB_CMD) { case I596_CB_CMD_XMIT: dgrs_rcv_frame(dev0, priv0, cbp); break; default: cbp->nop.status = I596_CB_STATUS_C | I596_CB_STATUS_OK; break; } if (cbp->nop.cmd & I596_CB_CMD_EL) break; cbp = (I596_CB *) S2H(cbp->nop.next); } priv0->scbp->status |= I596_SCB_CNA; /* * Ack the interrupt */ ack_intr: if (priv0->plxreg) OUTL(dev0->base_addr + PLX_LCL2PCI_DOORBELL, 1); } /* * Download the board firmware */ static int dgrs_download(struct device *dev0) { DGRS_PRIV *priv0 = (DGRS_PRIV *) dev0->priv; int is; int i; static int iv2is[16] = { 0, 0, 0, ES4H_IS_INT3, 0, ES4H_IS_INT5, 0, ES4H_IS_INT7, 0, 0, ES4H_IS_INT10, ES4H_IS_INT11, ES4H_IS_INT12, 0, 0, ES4H_IS_INT15 }; /* * Map in the dual port memory */ priv0->vmem = IOREMAP(dev0->mem_start, 2048*1024); if (!priv0->vmem) { printk("%s: cannot map in board memory\n", dev0->name); return -ENXIO; } /* * Hold the processor and configure the board addresses */ if (priv0->plxreg) { /* PCI bus */ proc_reset(dev0, 1); } else { /* EISA bus */ is = iv2is[dev0->irq & 0x0f]; if (!is) { printk("%s: Illegal IRQ %d\n", dev0->name, dev0->irq); return -ENXIO; } OUTB(dev0->base_addr + ES4H_AS_31_24, (uchar) (dev0->mem_start >> 24) ); OUTB(dev0->base_addr + ES4H_AS_23_16, (uchar) (dev0->mem_start >> 16) ); priv0->is_reg = ES4H_IS_LINEAR | is | ((uchar) (dev0->mem_start >> 8) & ES4H_IS_AS15); OUTB(dev0->base_addr + ES4H_IS, priv0->is_reg); OUTB(dev0->base_addr + ES4H_EC, ES4H_EC_ENABLE); OUTB(dev0->base_addr + ES4H_PC, ES4H_PC_RESET); OUTB(dev0->base_addr + ES4H_MW, ES4H_MW_ENABLE | 0x00); } /* * See if we can do DMA on the SE-6 */ priv0->use_dma = check_board_dma(dev0); if (priv0->use_dma) printk("%s: Bus Master DMA is enabled.\n", dev0->name); /* * Load and verify the code at the desired address */ memcpy(priv0->vmem, dgrs_code, dgrs_ncode); /* Load code */ if (memcmp(priv0->vmem, dgrs_code, dgrs_ncode)) { IOUNMAP(priv0->vmem); priv0->vmem = NULL; printk("%s: download compare failed\n", dev0->name); return -ENXIO; } /* * Configurables */ priv0->bcomm = (struct bios_comm *) (priv0->vmem + 0x0100); priv0->bcomm->bc_nowait = 1; /* Tell board to make printf not wait */ priv0->bcomm->bc_squelch = 0; /* Flag from Space.c */ priv0->bcomm->bc_150ohm = 0; /* Flag from Space.c */ priv0->bcomm->bc_spew = 0; /* Debug flag from Space.c */ priv0->bcomm->bc_maxrfd = 0; /* Debug flag from Space.c */ priv0->bcomm->bc_maxrbd = 0; /* Debug flag from Space.c */ /* * Tell board we are operating in switch mode (1) or in * multi-NIC mode (2). */ priv0->bcomm->bc_host = dgrs_nicmode ? BC_MULTINIC : BC_SWITCH; /* * Request memory space on board for DMA chains */ if (priv0->use_dma) priv0->bcomm->bc_hostarea_len = (2048/64) * 16; /* * NVRAM configurables from Space.c */ priv0->bcomm->bc_spantree = dgrs_spantree; priv0->bcomm->bc_hashexpire = dgrs_hashexpire; memcpy(priv0->bcomm->bc_ipaddr, dgrs_ipaddr, 4); memcpy(priv0->bcomm->bc_iptrap, dgrs_iptrap, 4); memcpy(priv0->bcomm->bc_ipxnet, &dgrs_ipxnet, 4); /* * Release processor, wait 8 seconds for board to initialize */ proc_reset(dev0, 0); for (i = jiffies + 8 * HZ; i > jiffies; ) { if (priv0->bcomm->bc_status >= BC_RUN) break; } if (priv0->bcomm->bc_status < BC_RUN) { printk("%s: board not operating\n", dev0->name); return -ENXIO; } priv0->port = (PORT *) S2H(priv0->bcomm->bc_port); priv0->scbp = (I596_SCB *) S2H(priv0->port->scbp); priv0->rfdp = (I596_RFD *) S2H(priv0->scbp->rfdp); priv0->rbdp = (I596_RBD *) S2H(priv0->rfdp->rbdp); priv0->scbp->status = I596_SCB_CNA; /* CU is idle */ /* * Get switch physical and host virtual pointers to DMA * chaining area. NOTE: the MSB of the switch physical * address *must* be turned off. Otherwise, the HW kludge * that allows host access of the PLX DMA registers will * erroneously select the PLX registers. */ priv0->dmadesc_s = (DMACHAIN *) S2DMA(priv0->bcomm->bc_hostarea); if (priv0->dmadesc_s) priv0->dmadesc_h = (DMACHAIN *) S2H(priv0->dmadesc_s); else priv0->dmadesc_h = NULL; /* * Enable board interrupts */ if (priv0->plxreg) { /* PCI bus */ OUTL(dev0->base_addr + PLX_INT_CSR, inl(dev0->base_addr + PLX_INT_CSR) | PLX_PCI_DOORBELL_IE); /* Enable intr to host */ OUTL(dev0->base_addr + PLX_LCL2PCI_DOORBELL, 1); } else { /* EISA bus */ } return (0); } /* * Probe (init) a board */ int dgrs_probe1(struct device *dev) { DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; int i; int rc; printk("%s: Digi RightSwitch io=%lx mem=%lx irq=%d plx=%lx dma=%lx\n", dev->name, dev->base_addr, dev->mem_start, dev->irq, priv->plxreg, priv->plxdma); /* * Download the firmware and light the processor */ rc = dgrs_download(dev); if (rc) { return rc; } /* * Get ether address of board */ printk("%s: Ethernet address", dev->name); memcpy(dev->dev_addr, priv->port->ethaddr, 6); for (i = 0; i < 6; ++i) printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]); printk("\n"); if (dev->dev_addr[0] & 1) { printk("%s: Illegal Ethernet Address\n", dev->name); return (-ENXIO); } /* * ACK outstanding interrupts, hook the interrupt, * and verify that we are getting interrupts from the board. */ if (priv->plxreg) OUTL(dev->base_addr + PLX_LCL2PCI_DOORBELL, 1); rc = request_irq(dev->irq, &dgrs_intr, 0, "RightSwitch", dev); if (rc) return (rc); priv->intrcnt = 0; for (i = jiffies + 2*HZ + HZ/2; i > jiffies; ) if (priv->intrcnt >= 2) break; if (priv->intrcnt < 2) { printk("%s: Not interrupting on IRQ %d (%d)\n", dev->name, dev->irq, priv->intrcnt); return (-ENXIO); } /* * Register the /proc/ioports information... */ request_region(dev->base_addr, 256, "RightSwitch"); /* * Entry points... */ dev->open = &dgrs_open; dev->stop = &dgrs_close; dev->get_stats = &dgrs_get_stats; dev->hard_start_xmit = &dgrs_start_xmit; dev->set_multicast_list = &dgrs_set_multicast_list; dev->do_ioctl = &dgrs_ioctl; return (0); } int dgrs_initclone(struct device *dev) { DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; int i; printk("%s: Digi RightSwitch port %d ", dev->name, priv->chan); for (i = 0; i < 6; ++i) printk("%c%2.2x", i ? ':' : ' ', dev->dev_addr[i]); printk("\n"); return (0); } static int dgrs_found_device( struct device *dev, int io, ulong mem, int irq, ulong plxreg, ulong plxdma ) { DGRS_PRIV *priv; #ifdef MODULE { int i; /* Allocate and fill new device structure. */ int dev_size = sizeof(struct device) + sizeof(DGRS_PRIV); dev = (struct device *) kmalloc(dev_size, GFP_KERNEL); memset(dev, 0, dev_size); dev->priv = ((void *)dev) + sizeof(struct device); priv = (DGRS_PRIV *)dev->priv; dev->name = priv->devname; /* An empty string. */ dev->base_addr = io; dev->mem_start = mem; dev->mem_end = mem + 2048 * 1024 - 1; dev->irq = irq; priv->plxreg = plxreg; priv->plxdma = plxdma; priv->vplxdma = NULL; priv->chan = 1; priv->devtbl[0] = dev; dev->init = dgrs_probe1; ether_setup(dev); priv->next_dev = dgrs_root_dev; dgrs_root_dev = dev; if (register_netdev(dev) != 0) return -EIO; if ( !dgrs_nicmode ) return (0); /* Switch mode, we are done */ /* * Operating card as N separate NICs */ priv->nports = priv->bcomm->bc_nports; for (i = 1; i < priv->nports; ++i) { struct device *devN; DGRS_PRIV *privN; /* Allocate new dev and priv structures */ devN = (struct device *) kmalloc(dev_size, GFP_KERNEL); /* Make it an exact copy of dev[0]... */ memcpy(devN, dev, dev_size); devN->priv = ((void *)devN) + sizeof(struct device); privN = (DGRS_PRIV *)devN->priv; /* ... but seset devname to a NULL string */ privN->devname[0] = 0; devN->name = privN->devname; /* ... and zero out VM areas */ privN->vmem = 0; privN->vplxdma = 0; /* ... and zero out IRQ */ devN->irq = 0; /* ... and base MAC address off address of 1st port */ devN->dev_addr[5] += i; privN->chan = i+1; priv->devtbl[i] = devN; devN->init = dgrs_initclone; ether_setup(devN); privN->next_dev = dgrs_root_dev; dgrs_root_dev = devN; if (register_netdev(devN) != 0) return -EIO; } } #else { if (dev) { dev->priv = kmalloc(sizeof (DGRS_PRIV), GFP_KERNEL); memset(dev->priv, 0, sizeof (DGRS_PRIV)); } dev = init_etherdev(dev, sizeof(DGRS_PRIV)); priv = (DGRS_PRIV *)dev->priv; dev->base_addr = io; dev->mem_start = mem; dev->mem_end = mem + 2048 * 1024; dev->irq = irq; priv->plxreg = plxreg; priv->plxdma = plxdma; priv->vplxdma = NULL; priv->chan = 1; priv->devtbl[0] = dev; dgrs_probe1(dev); } #endif return (0); } /* * Scan for all boards */ static int dgrs_scan(struct device *dev) { int cards_found = 0; uint io; uint mem; uint irq; uint plxreg; uint plxdma; /* * First, check for PCI boards */ if (pcibios_present()) { int pci_index = 0; for (; pci_index < 8; pci_index++) { uchar pci_bus, pci_device_fn; uchar pci_irq; uchar pci_latency; ushort pci_command; if (pcibios_find_device(SE6_PCI_VENDOR_ID, SE6_PCI_DEVICE_ID, pci_index, &pci_bus, &pci_device_fn)) break; pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_INTERRUPT_LINE, &pci_irq); pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_0, &plxreg); pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_1, &io); pcibios_read_config_dword(pci_bus, pci_device_fn, PCI_BASE_ADDRESS_2, &mem); pcibios_read_config_dword(pci_bus, pci_device_fn, 0x30, &plxdma); irq = pci_irq; plxreg &= ~15; io &= ~3; mem &= ~15; plxdma &= ~15; /* * On some BIOSES, the PLX "expansion rom" (used for DMA) * address comes up as "0". This is probably because * the BIOS doesn't see a valid 55 AA ROM signature at * the "ROM" start and zeroes the address. To get * around this problem the SE-6 is configured to ask * for 4 MB of space for the dual port memory. We then * must set its range back to 2 MB, and use the upper * half for DMA register access */ OUTL(io + PLX_SPACE0_RANGE, 0xFFE00000L); if (plxdma == 0) plxdma = mem + (2048L * 1024L); pcibios_write_config_dword(pci_bus, pci_device_fn, 0x30, plxdma + 1); pcibios_read_config_dword(pci_bus, pci_device_fn, 0x30, &plxdma); plxdma &= ~15; /* * Get and check the bus-master and latency values. * Some PCI BIOSes fail to set the master-enable bit, * and the latency timer must be set to the maximum * value to avoid data corruption that occurs when the * timer expires during a transfer. Yes, it's a bug. */ pcibios_read_config_word(pci_bus, pci_device_fn, PCI_COMMAND, &pci_command); if ( ! (pci_command & PCI_COMMAND_MASTER)) { printk(" Setting the PCI Master Bit!\n"); pci_command |= PCI_COMMAND_MASTER; pcibios_write_config_word(pci_bus, pci_device_fn, PCI_COMMAND, pci_command); } pcibios_read_config_byte(pci_bus, pci_device_fn, PCI_LATENCY_TIMER, &pci_latency); if (pci_latency != 255) { printk(" Overriding PCI latency timer: " "was %d, now is 255.\n", pci_latency); pcibios_write_config_byte(pci_bus, pci_device_fn, PCI_LATENCY_TIMER, 255); } dgrs_found_device(dev, io, mem, irq, plxreg, plxdma); dev = 0; cards_found++; } } /* * Second, check for EISA boards */ if (EISA_bus) { static int is2iv[8] = { 0, 3, 5, 7, 10, 11, 12, 15 }; for (io = 0x1000; io < 0x9000; io += 0x1000) { if (inb(io+ES4H_MANUFmsb) != 0x10 || inb(io+ES4H_MANUFlsb) != 0x49 || inb(io+ES4H_PRODUCT) != ES4H_PRODUCT_CODE) continue; if ( ! (inb(io+ES4H_EC) & ES4H_EC_ENABLE) ) continue; /* Not EISA configured */ mem = (inb(io+ES4H_AS_31_24) << 24) + (inb(io+ES4H_AS_23_16) << 16); irq = is2iv[ inb(io+ES4H_IS) & ES4H_IS_INTMASK ]; dgrs_found_device(dev, io, mem, irq, 0L, 0L); dev = 0; ++cards_found; } } return cards_found; } /* * Module/driver initialization points. Two ways, depending on * whether we are a module or statically linked, ala Don Becker's * 3c59x driver. */ #ifdef MODULE /* * Variables that can be overriden from command line */ static int debug = -1; static int dma = -1; static int hashexpire = -1; static int spantree = -1; static int ipaddr[4] = { -1 }; static int iptrap[4] = { -1 }; static long ipxnet = -1; static int nicmode = -1; int init_module(void) { int cards_found; int i; /* * Command line variable overrides * debug=NNN * dma=0/1 * spantree=0/1 * hashexpire=NNN * ipaddr=A,B,C,D * iptrap=A,B,C,D * ipxnet=NNN * nicmode=NNN */ if (debug >= 0) dgrs_debug = debug; if (dma >= 0) dgrs_dma = dma; if (nicmode >= 0) dgrs_nicmode = nicmode; if (hashexpire >= 0) dgrs_hashexpire = hashexpire; if (spantree >= 0) dgrs_spantree = spantree; if (ipaddr[0] != -1) for (i = 0; i < 4; ++i) dgrs_ipaddr[i] = ipaddr[i]; if (iptrap[0] != -1) for (i = 0; i < 4; ++i) dgrs_iptrap[i] = iptrap[i]; if (ipxnet != -1) dgrs_ipxnet = htonl( ipxnet ); if (dgrs_debug) { printk("dgrs: SW=%s FW=Build %d %s\n", version, dgrs_firmnum, dgrs_firmdate); } /* * Find and configure all the cards */ dgrs_root_dev = NULL; cards_found = dgrs_scan(0); return cards_found ? 0 : -ENODEV; } void cleanup_module(void) { while (dgrs_root_dev) { struct device *next_dev; DGRS_PRIV *priv; priv = (DGRS_PRIV *) dgrs_root_dev->priv; next_dev = priv->next_dev; unregister_netdev(dgrs_root_dev); proc_reset(priv->devtbl[0], 1); if (priv->vmem) IOUNMAP(priv->vmem); if (priv->vplxdma) IOUNMAP((uchar *) priv->vplxdma); release_region(dgrs_root_dev->base_addr, 256); if (dgrs_root_dev->irq) free_irq(dgrs_root_dev->irq, dgrs_root_dev); kfree(dgrs_root_dev); dgrs_root_dev = next_dev; } } #else int dgrs_probe(struct device *dev) { int cards_found; cards_found = dgrs_scan(dev); if (dgrs_debug && cards_found) printk("dgrs: SW=%s FW=Build %d %s\n", version, dgrs_firmnum, dgrs_firmdate); return cards_found ? 0 : -ENODEV; } #endif
Go to most recent revision | Compare with Previous | Blame | View Log