?? dgrs.c
字號(hào):
/* * 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 independent NICs. To select multi-NIC * mode, add "nicmode=1" on the insmod load line for the driver. * * This driver uses the "dev" common ethernet device structure * and a private "priv" (dev->priv) structure that contains * mostly DGRS-specific information and statistics. To keep * the code for both the switch mode and the multi-NIC mode * as similar as possible, I have introduced the concept of * "dev0"/"priv0" and "devN"/"privN" pointer pairs in subroutines * where needed. The first pair of pointers points to the * "dev" and "priv" structures of the zeroth (0th) device * interface associated with a board. The second pair of * pointers points to the current (Nth) device interface * for the board: the one for which we are processing data. * * In switch mode, the pairs of pointers are always the same, * that is, dev0 == devN and priv0 == privN. This is just * like previous releases of this driver which did not support * NIC mode. * * In multi-NIC mode, the pairs of pointers may be different. * We use the devN and privN pointers to reference just the * name, port number, and statistics for the current interface. * We use the dev0 and priv0 pointers to access the variables * that control access to the board, such as board address * and simulated 82596 variables. This is because there is * only one "fake" 82596 that serves as the interface to * the board. We do not want to try to keep the variables * associated with this 82596 in sync across all devices. * * This scheme works well. As you will see, except for * initialization, there is very little difference between * the two modes as far as this driver is concerned. On the * receive side in NIC mode, the interrupt *always* comes in on * the 0th interface (dev0/priv0). We then figure out which * real 82596 port it came in on from looking at the "chan" * member that the board firmware adds at the end of each * RBD (a.k.a. TBD). We get the channel number like this: * int chan = ((I596_RBD *) S2H(cbp->xmit.tbdp))->chan; * * On the transmit side in multi-NIC mode, we specify the * output 82596 port by setting the new "dstchan" structure * member that is at the end of the RFD, like this: * priv0->rfdp->dstchan = privN->chan; * * TODO: * - Multi-NIC mode is not yet supported when the driver is linked * into the kernel. * - Better handling of multicast addresses. * */static char *version = "$Id: dgrs.c,v 1.12 1996/12/21 13:43:58 rick Exp $";#include <linux/version.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/sched.h>#include <linux/string.h>#include <linux/delay.h>#include <linux/errno.h>#include <linux/ioport.h>#include <linux/malloc.h>#include <linux/interrupt.h>#include <linux/pci.h>#include <linux/init.h>#include <asm/bitops.h>#include <asm/io.h>#include <asm/byteorder.h>#include <linux/netdevice.h>#include <linux/etherdevice.h>#include <linux/skbuff.h>#include <linux/types.h>/* * API changed at linux version 2.1.0 */#if LINUX_VERSION_CODE >= 0x20100 #include <asm/uaccess.h> #define IOREMAP(ADDR, LEN) ioremap(ADDR, LEN) #define IOUNMAP(ADDR) iounmap(ADDR) #define COPY_FROM_USER(DST,SRC,LEN) copy_from_user(DST,SRC,LEN) #define COPY_TO_USER(DST,SRC,LEN) copy_to_user(DST,SRC,LEN)#else #include <linux/bios32.h> #define IOREMAP(ADDR, LEN) vremap(ADDR, LEN) #define IOUNMAP(ADDR) vfree(ADDR) #define COPY_FROM_USER(DST,SRC,LEN) memcpy_fromfs(DST,SRC,LEN) #define COPY_TO_USER(DST,SRC,LEN) memcpy_tofs(DST,SRC,LEN)#endif/* * DGRS include files */typedef unsigned char uchar;typedef unsigned int bool;#define vol volatile#include "dgrs.h"#include "dgrs_es4h.h"#include "dgrs_plx9060.h"#include "dgrs_i82596.h"#include "dgrs_ether.h"#include "dgrs_asstruct.h"#include "dgrs_bcomm.h"/* * Firmware. Compiled separately for local compilation, * but #included for Linux distribution. */#ifndef NOFW #include "dgrs_firmware.c"#else extern int dgrs_firmnum; extern char dgrs_firmver[]; extern char dgrs_firmdate[]; extern uchar dgrs_code[]; extern int dgrs_ncode;#endif/* * Linux out*() is backwards from all other operating systems */#define OUTB(ADDR, VAL) outb(VAL, ADDR)#define OUTW(ADDR, VAL) outw(VAL, ADDR)#define OUTL(ADDR, VAL) outl(VAL, ADDR)/* * Macros to convert switch to host and host to switch addresses * (assumes a local variable priv points to board dependent struct) */#define S2H(A) ( ((unsigned long)(A)&0x00ffffff) + priv0->vmem )#define S2HN(A) ( ((unsigned long)(A)&0x00ffffff) + privN->vmem )#define H2S(A) ( ((char *) (A) - priv0->vmem) + 0xA3000000 )/* * Convert a switch address to a "safe" address for use with the * PLX 9060 DMA registers and the associated HW kludge that allows * for host access of the DMA registers. */#define S2DMA(A) ( (unsigned long)(A) & 0x00ffffff)/* * "Space.c" variables, now settable from module interface * Use the name below, minus the "dgrs_" prefix. See init_module(). */int dgrs_debug = 1;int dgrs_dma = 1;int dgrs_spantree = -1;int dgrs_hashexpire = -1;uchar dgrs_ipaddr[4] = { 0xff, 0xff, 0xff, 0xff};uchar dgrs_iptrap[4] = { 0xff, 0xff, 0xff, 0xff};__u32 dgrs_ipxnet = -1;int dgrs_nicmode = 0;/* * Chain of device structures */#ifdef MODULE static struct device *dgrs_root_dev = NULL;#endif/* * Private per-board data structure (dev->priv) */typedef struct /* * Stuff for generic ethercard I/F */ char devname[8]; /* "ethN" string */ struct device *next_dev; struct net_device_stats stats; /* * DGRS specific data */ char *vmem; struct bios_comm *bcomm; /* Firmware BIOS comm structure */ PORT *port; /* Ptr to PORT[0] struct in VM */ I596_SCB *scbp; /* Ptr to SCB struct in VM */ I596_RFD *rfdp; /* Current RFD list */ I596_RBD *rbdp; /* Current RBD list */ 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 voidproc_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 intcheck_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 intdo_plx_dma( struct device *dev, ulong pciaddr, ulong lcladdr, int len, int to_host){ int i; ulong csr; DGRS_PRIV *priv = (DGRS_PRIV *) dev->priv; if (pciaddr) { /* * Do a single, non-chain DMA */ priv->vplxdma[PLX_DMA0_PCI_ADDR/4] = pciaddr; priv->vplxdma[PLX_DMA0_LCL_ADDR/4] = lcladdr; priv->vplxdma[PLX_DMA0_SIZE/4] = len; priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = to_host ? PLX_DMA_DESC_TO_HOST : PLX_DMA_DESC_TO_BOARD; priv->vplxdma[PLX_DMA0_MODE/4] = PLX_DMA_MODE_WIDTH32 | PLX_DMA_MODE_WAITSTATES(0) | PLX_DMA_MODE_READY | PLX_DMA_MODE_NOBTERM | PLX_DMA_MODE_BURST | PLX_DMA_MODE_NOCHAIN; } else { /* * Do a chaining DMA */ priv->vplxdma[PLX_DMA0_MODE/4] = PLX_DMA_MODE_WIDTH32 | PLX_DMA_MODE_WAITSTATES(0) | PLX_DMA_MODE_READY | PLX_DMA_MODE_NOBTERM | PLX_DMA_MODE_BURST | PLX_DMA_MODE_CHAIN; priv->vplxdma[PLX_DMA0_DESCRIPTOR/4] = lcladdr; } priv->vplxdma[PLX_DMA_CSR/4] = PLX_DMA_CSR_0_ENABLE | PLX_DMA_CSR_0_START; /* * Wait for DMA to complete */ for (i = 0; i < 1000000; ++i) { /* * Spin the host CPU for 1 usec, so we don't thrash * the PCI bus while the PLX 9060 is doing DMA. */ udelay(1); csr = (volatile unsigned long) priv->vplxdma[PLX_DMA_CSR/4]; if (csr & PLX_DMA_CSR_0_DONE) break; } if ( ! (csr & PLX_DMA_CSR_0_DONE) ) { printk("%s: DMA done never occurred. DMA disabled.\n", dev->name); priv->use_dma = 0; return 1; } return 0;}/* * dgrs_rcv_frame() * * Process a received frame. This is called from the interrupt * routine, and works for both switch mode and multi-NIC mode. * * Note that when in multi-NIC mode, we want to always access the * hardware using the dev and priv structures of the first port, * so that we are using only one set of variables to maintain * the board interface status, but we want to use the Nth port * dev and priv structures to maintain statistics and to pass * the packet up. * * Only the first device structure is attached to the interrupt. * We use the special "chan" variable at the end of the first RBD * to select the Nth device in multi-NIC mode. * * We currently do chained DMA on a per-packet basis when the * packet is "long", and we spin the CPU a short time polling * for DMA completion. This avoids a second interrupt overhead, * and gives the best performance for light traffic to the host. * * However, a better scheme that could be implemented would be * to see how many packets are outstanding for the host, and if * the number is "large", create a long chain to DMA several * packets into the host in one go. In this case, we would set * up some state variables to let the host CPU continue doing * other things until a DMA completion interrupt comes along. */voiddgrs_rcv_frame( struct device *dev0, DGRS_PRIV *priv0, I596_CB *cbp){ int len; I596_TBD *tbdp; struct sk_buff *skb; uchar *putp; uchar *p; struct device *devN; DGRS_PRIV *privN; /* * Determine Nth priv and dev structure pointers */ if (dgrs_nicmode) { /* Multi-NIC mode */ int chan = ((I596_RBD *) S2H(cbp->xmit.tbdp))->chan; devN = priv0->devtbl[chan-1]; /* * If devN is null, we got an interrupt before the I/F * has been initialized. Pitch the packet. */ if (devN == NULL) goto out; privN = (DGRS_PRIV *) devN->priv; } else { /* Switch mode */ devN = dev0; privN = priv0; } if (0) printk("%s: rcv len=%ld\n", devN->name, cbp->xmit.count); /* * Allocate a message block big enough to hold the whole frame */ len = cbp->xmit.count; if ((skb = dev_alloc_skb(len+5)) == NULL) { printk("%s: dev_alloc_skb failed for rcv buffer\n", devN->name); ++privN->stats.rx_dropped; /* discarding the frame */ goto out; } skb->dev = devN; skb_reserve(skb, 2); /* Align IP header */again: putp = p = skb_put(skb, len); /* * There are three modes here for doing the packet copy. * If we have DMA, and the packet is "long", we use the * chaining mode of DMA. If it's shorter, we use single * DMA's. Otherwise, we use memcpy(). */ if (priv0->use_dma && priv0->dmadesc_h && len > 64) { /* * If we can use DMA and its a long frame, copy it using * DMA chaining. */ DMACHAIN *ddp_h; /* Host virtual DMA desc. pointer */ DMACHAIN *ddp_s; /* Switch physical DMA desc. pointer */ uchar *phys_p; /* * Get the physical address of the STREAMS buffer. * NOTE: allocb() guarantees that the whole buffer * is in a single page if the length < 4096. */ phys_p = (uchar *) virt_to_phys(putp); ddp_h = priv0->dmadesc_h; ddp_s = priv0->dmadesc_s; tbdp = (I596_TBD *) S2H(cbp->xmit.tbdp); for (;;) { int count; int amt; count = tbdp->count; amt = count & 0x3fff; if (amt == 0)
?? 快捷鍵說(shuō)明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -