patch-2.4.20 linux-2.4.20/drivers/net/ewrk3.c
Next file: linux-2.4.20/drivers/net/fealnx.c
Previous file: linux-2.4.20/drivers/net/epic100.c
Back to the patch index
Back to the overall index
- Lines: 643
- Date:
Thu Nov 28 15:53:13 2002
- Orig file:
linux-2.4.19/drivers/net/ewrk3.c
- Orig date:
Fri Aug 2 17:39:44 2002
diff -urN linux-2.4.19/drivers/net/ewrk3.c linux-2.4.20/drivers/net/ewrk3.c
@@ -76,6 +76,7 @@
kernel with the ewrk3 configuration turned off and reboot.
5) insmod ewrk3.o
[Alan Cox: Changed this so you can insmod ewrk3.o irq=x io=y]
+ [Adam Kropelin: now accepts irq=x1,x2 io=y1,y2 for multiple cards]
6) run the net startup bits for your new eth?? interface manually
(usually /etc/rc.inet[12] at boot time).
7) enjoy!
@@ -130,9 +131,14 @@
Add new multicasting code.
0.41 20-Jan-96 Fix IRQ set up problem reported by
<kenneth@bbs.sas.ntu.ac.sg>.
- 0.42 22-Apr-96 Fix alloc_device() bug <jari@markkus2.fimr.fi>
- 0.43 16-Aug-96 Update alloc_device() to conform to de4x5.c
- 0.44 08-Nov-01 use library crc32 functions <Matt_Domsch@dell.com>
+ 0.42 22-Apr-96 Fix alloc_device() bug <jari@markkus2.fimr.fi>
+ 0.43 16-Aug-96 Update alloc_device() to conform to de4x5.c
+ 0.44 08-Nov-01 use library crc32 functions <Matt_Domsch@dell.com>
+ 0.45 19-Jul-02 fix unaligned access on alpha <martin@bruli.net>
+ 0.46 10-Oct-02 Multiple NIC support when module <akropel1@rochester.rr.com>
+ 0.47 18-Oct-02 ethtool support <akropel1@rochester.rr.com>
+ 0.48 18-Oct-02 cli/sti removal for 2.5 <vda@port.imtp.ilyichevsk.odessa.ua>
+ ioctl locking, signature search cleanup <akropel1@rochester.rr.com>
=========================================================================
*/
@@ -142,7 +148,6 @@
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/string.h>
-#include <linux/ptrace.h>
#include <linux/errno.h>
#include <linux/ioport.h>
#include <linux/slab.h>
@@ -158,6 +163,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
+#include <linux/ethtool.h>
#include <linux/time.h>
#include <linux/types.h>
@@ -166,8 +172,11 @@
#include "ewrk3.h"
+#define DRV_NAME "ewrk3"
+#define DRV_VERSION "0.48"
+
static char version[] __initdata =
-"ewrk3.c:v0.43a 2001/02/04 davies@maniac.ultranet.com\n";
+DRV_NAME ":v" DRV_VERSION " 2002/10/18 davies@maniac.ultranet.com\n";
#ifdef EWRK3_DEBUG
static int ewrk3_debug = EWRK3_DEBUG;
@@ -196,6 +205,7 @@
#define EWRK3_IOP_INC 0x20 /* I/O address increment */
#define EWRK3_TOTAL_SIZE 0x20 /* required I/O address length */
+/* If you change this, remember to also change MODULE_PARM array limits */
#ifndef MAX_NUM_EWRK3S
#define MAX_NUM_EWRK3S 21
#endif
@@ -254,26 +264,29 @@
#define EWRK3_PKT_BIN_SZ 128 /* Should be >=100 unless you
increase EWRK3_PKT_STAT_SZ */
+struct ewrk3_stats {
+ u32 bins[EWRK3_PKT_STAT_SZ];
+ u32 unicast;
+ u32 multicast;
+ u32 broadcast;
+ u32 excessive_collisions;
+ u32 tx_underruns;
+ u32 excessive_underruns;
+};
+
struct ewrk3_private {
char adapter_name[80]; /* Name exported to /proc/ioports */
u_long shmem_base; /* Shared memory start address */
u_long shmem_length; /* Shared memory window length */
struct net_device_stats stats; /* Public stats */
- struct {
- u32 bins[EWRK3_PKT_STAT_SZ]; /* Private stats counters */
- u32 unicast;
- u32 multicast;
- u32 broadcast;
- u32 excessive_collisions;
- u32 tx_underruns;
- u32 excessive_underruns;
- } pktStats;
+ struct ewrk3_stats pktStats; /* Private stats counters */
u_char irq_mask; /* Adapter IRQ mask bits */
u_char mPage; /* Maximum 2kB Page number */
u_char lemac; /* Chip rev. level */
u_char hard_strapped; /* Don't allow a full open */
u_char txc; /* Transmit cut through */
u_char *mctbl; /* Pointer to the multicast table */
+ u_char led_mask; /* Used to reserve LED access for ethtool */
spinlock_t hw_lock;
};
@@ -529,6 +542,7 @@
lp->shmem_length = shmem_length;
lp->lemac = lemac;
lp->hard_strapped = hard_strapped;
+ lp->led_mask = CR_LED;
spin_lock_init(&lp->hw_lock);
lp->mPage = 64;
@@ -555,8 +569,10 @@
if (dev->irq < 2) {
#ifndef MODULE
u_char irqnum;
+ unsigned long irq_mask, delay;
+
- autoirq_setup(0);
+ irq_mask = probe_irq_on();
/*
** Trigger a TNE interrupt.
@@ -567,7 +583,9 @@
irqnum = irq[((icr & IRQ_SEL) >> 4)];
- dev->irq = autoirq_report(1);
+ delay = jiffies + HZ/50;
+ while (time_before(jiffies, delay)) ;
+ dev->irq = probe_irq_off(irq_mask);
if ((dev->irq) && (irqnum == dev->irq)) {
printk(" and uses IRQ%d.\n", dev->irq);
} else {
@@ -895,7 +913,7 @@
DISABLE_IRQs;
cr = inb(EWRK3_CR);
- cr |= CR_LED;
+ cr |= lp->led_mask;
outb(cr, EWRK3_CR);
if (csr & CSR_RNE) /* Rx interrupt (packet[s] arrived) */
@@ -920,12 +938,13 @@
}
/* Unmask the EWRK3 board interrupts and turn off the LED */
- cr &= ~CR_LED;
+ cr &= ~(lp->led_mask);
outb(cr, EWRK3_CR);
ENABLE_IRQs;
spin_unlock(&lp->hw_lock);
}
+/* Called with lp->hw_lock held */
static int ewrk3_rx(struct net_device *dev)
{
struct ewrk3_private *lp = (struct ewrk3_private *) dev->priv;
@@ -1004,12 +1023,13 @@
}
p = skb->data; /* Look at the dest addr */
if (p[0] & 0x01) { /* Multicast/Broadcast */
- if ((*(s32 *) & p[0] == -1) && (*(s16 *) & p[4] == -1)) {
+ if ((*(s16 *) & p[0] == -1) && (*(s16 *) & p[2] == -1) && (*(s16 *) & p[4] == -1)) {
lp->pktStats.broadcast++;
} else {
lp->pktStats.multicast++;
}
- } else if ((*(s32 *) & p[0] == *(s32 *) & dev->dev_addr[0]) &&
+ } else if ((*(s16 *) & p[0] == *(s16 *) & dev->dev_addr[0]) &&
+ (*(s16 *) & p[2] == *(s16 *) & dev->dev_addr[2]) &&
(*(s16 *) & p[4] == *(s16 *) & dev->dev_addr[4])) {
lp->pktStats.unicast++;
}
@@ -1050,8 +1070,9 @@
}
/*
- ** Buffer sent - check for TX buffer errors.
- */
+** Buffer sent - check for TX buffer errors.
+** Called with lp->hw_lock held
+*/
static int ewrk3_tx(struct net_device *dev)
{
struct ewrk3_private *lp = (struct ewrk3_private *) dev->priv;
@@ -1459,27 +1480,20 @@
*/
static void __init EthwrkSignature(char *name, char *eeprom_image)
{
- u_long i, j, k;
+ int i;
char *signatures[] = EWRK3_SIGNATURE;
- strcpy(name, "");
- for (i = 0; *signatures[i] != '\0' && *name == '\0'; i++) {
- for (j = EEPROM_PNAME7, k = 0; j <= EEPROM_PNAME0 && k < strlen(signatures[i]); j++) {
- if (signatures[i][k] == eeprom_image[j]) { /* track signature */
- k++;
- } else { /* lost signature; begin search again */
- k = 0;
- }
- }
- if (k == strlen(signatures[i])) {
- for (k = 0; k < EWRK3_STRLEN; k++) {
- name[k] = eeprom_image[EEPROM_PNAME7 + k];
- name[EWRK3_STRLEN] = '\0';
- }
- }
- }
+ for (i=0; *signatures[i] != '\0'; i++)
+ if( !strncmp(eeprom_image+EEPROM_PNAME7, signatures[i], strlen(signatures[i])) )
+ break;
- return; /* return the device name string */
+ if (*signatures[i] != '\0') {
+ memcpy(name, eeprom_image+EEPROM_PNAME7, EWRK3_STRLEN);
+ name[EWRK3_STRLEN] = '\0';
+ } else
+ name[0] = '\0';
+
+ return;
}
/*
@@ -1615,6 +1629,197 @@
return status; /* return the device name string */
}
+static int ewrk3_ethtool_ioctl(struct net_device *dev, void *useraddr)
+{
+ struct ewrk3_private *lp = (struct ewrk3_private *) dev->priv;
+ u_long iobase = dev->base_addr;
+ u32 ethcmd;
+
+ if (get_user(ethcmd, (u32 *)useraddr))
+ return -EFAULT;
+
+ switch (ethcmd) {
+
+ /* Get driver info */
+ case ETHTOOL_GDRVINFO: {
+ struct ethtool_drvinfo info = { ETHTOOL_GDRVINFO };
+ int fwrev = Read_EEPROM(dev->base_addr, EEPROM_REVLVL);
+
+ strcpy(info.driver, DRV_NAME);
+ strcpy(info.version, DRV_VERSION);
+ sprintf(info.fw_version, "%d", fwrev);
+ strcpy(info.bus_info, "N/A");
+ info.eedump_len = EEPROM_MAX;
+ if (copy_to_user(useraddr, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+
+ /* Get settings */
+ case ETHTOOL_GSET: {
+ struct ethtool_cmd ecmd = { ETHTOOL_GSET };
+ u_char cr = inb(EWRK3_CR);
+
+ switch (lp->adapter_name[4]) {
+ case '3': /* DE203 */
+ ecmd.supported = SUPPORTED_BNC;
+ ecmd.port = PORT_BNC;
+ break;
+
+ case '4': /* DE204 */
+ ecmd.supported = SUPPORTED_TP;
+ ecmd.port = PORT_TP;
+ break;
+
+ case '5': /* DE205 */
+ ecmd.supported = SUPPORTED_TP | SUPPORTED_BNC | SUPPORTED_AUI;
+ ecmd.autoneg = !(cr & CR_APD);
+ /*
+ ** Port is only valid if autoneg is disabled
+ ** and even then we don't know if AUI is jumpered.
+ */
+ if (!ecmd.autoneg)
+ ecmd.port = (cr & CR_PSEL) ? PORT_BNC : PORT_TP;
+ break;
+ }
+
+ ecmd.supported |= SUPPORTED_10baseT_Half;
+ ecmd.speed = SPEED_10;
+ ecmd.duplex = DUPLEX_HALF;
+
+ if (copy_to_user(useraddr, &ecmd, sizeof(ecmd)))
+ return -EFAULT;
+ return 0;
+ }
+
+ /* Set settings */
+ case ETHTOOL_SSET: {
+ struct ethtool_cmd ecmd;
+ u_char cr;
+ u_long flags;
+
+ /* DE205 is the only card with anything to set */
+ if (lp->adapter_name[4] != '5')
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&ecmd, useraddr, sizeof(ecmd)))
+ return -EFAULT;
+
+ /* Sanity-check parameters */
+ if (ecmd.speed != SPEED_10)
+ return -EINVAL;
+ if (ecmd.port != PORT_TP && ecmd.port != PORT_BNC)
+ return -EINVAL; /* AUI is not software-selectable */
+ if (ecmd.transceiver != XCVR_INTERNAL)
+ return -EINVAL;
+ if (ecmd.duplex != DUPLEX_HALF)
+ return -EINVAL;
+ if (ecmd.phy_address != 0)
+ return -EINVAL;
+
+ spin_lock_irqsave(&lp->hw_lock, flags);
+ cr = inb(EWRK3_CR);
+
+ /* If Autoneg is set, change to Auto Port mode */
+ /* Otherwise, disable Auto Port and set port explicitly */
+ if (ecmd.autoneg) {
+ cr &= ~CR_APD;
+ } else {
+ cr |= CR_APD;
+ if (ecmd.port == PORT_TP)
+ cr &= ~CR_PSEL; /* Force TP */
+ else
+ cr |= CR_PSEL; /* Force BNC */
+ }
+
+ /* Commit the changes */
+ outb(cr, EWRK3_CR);
+
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
+ if (copy_to_user(useraddr, &ecmd, sizeof(ecmd)))
+ return -EFAULT;
+ return 0;
+ }
+
+ /* Get link status */
+ case ETHTOOL_GLINK: {
+ struct ethtool_value edata = { ETHTOOL_GLINK };
+ u_char cmr = inb(EWRK3_CMR);
+
+ /* DE203 has BNC only and link status does not apply */
+ if (lp->adapter_name[4] == '3')
+ return -EOPNOTSUPP;
+
+ /* On DE204 this is always valid since TP is the only port. */
+ /* On DE205 this reflects TP status even if BNC or AUI is selected. */
+ edata.data = !(cmr & CMR_LINK);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+ }
+
+#ifdef BROKEN
+ /* Blink LED for identification */
+ case ETHTOOL_PHYS_ID: {
+ struct ethtool_value edata;
+ u_long flags;
+ long delay, ret;
+ u_char cr;
+ int count;
+ wait_queue_head_t wait;
+
+ init_waitqueue_head(&wait);
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ /* Toggle LED 4x per second */
+ delay = HZ >> 2;
+ count = edata.data << 2;
+
+ spin_lock_irqsave(&lp->hw_lock, flags);
+
+ /* Bail if a PHYS_ID is already in progress */
+ if (lp->led_mask == 0) {
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
+ return -EBUSY;
+ }
+
+ /* Prevent ISR from twiddling the LED */
+ lp->led_mask = 0;
+
+ while (count--) {
+ /* Toggle the LED */
+ cr = inb(EWRK3_CR);
+ outb(cr ^ CR_LED, EWRK3_CR);
+
+ /* Wait a little while */
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
+ ret = delay;
+ __wait_event_interruptible_timeout(wait, 0, ret);
+ spin_lock_irqsave(&lp->hw_lock, flags);
+
+ /* Exit if we got a signal */
+ if (ret == -ERESTARTSYS)
+ goto out;
+ }
+
+ ret = 0;
+out:
+ lp->led_mask = CR_LED;
+ cr = inb(EWRK3_CR);
+ outb(cr & ~CR_LED, EWRK3_CR);
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
+ return ret;
+ }
+#endif /* BROKEN */
+
+ }
+
+ return -EOPNOTSUPP;
+}
+
/*
** Perform IOCTL call functions here. Some are privileged operations and the
** effective uid is checked in those cases.
@@ -1626,6 +1831,7 @@
u_long iobase = dev->base_addr;
int i, j, status = 0;
u_char csr;
+ unsigned long flags;
union ewrk3_addr {
u_char addr[HASH_TABLE_LEN * ETH_ALEN];
u_short val[(HASH_TABLE_LEN * ETH_ALEN) >> 1];
@@ -1633,6 +1839,14 @@
union ewrk3_addr *tmp;
+ /* ethtool IOCTLs are handled elsewhere */
+ if (cmd == SIOCETHTOOL)
+ return ewrk3_ethtool_ioctl(dev, (void *)rq->ifr_data);
+
+ /* Other than ethtool, all we handle are private IOCTLs */
+ if (cmd != EWRK3IOCTL)
+ return -EOPNOTSUPP;
+
tmp = kmalloc(sizeof(union ewrk3_addr), GFP_KERNEL);
if(tmp==NULL)
return -ENOMEM;
@@ -1649,21 +1863,26 @@
case EWRK3_SET_HWADDR: /* Set the hardware address */
if (capable(CAP_NET_ADMIN)) {
+ spin_lock_irqsave(&lp->hw_lock, flags);
csr = inb(EWRK3_CSR);
csr |= (CSR_TXD | CSR_RXD);
outb(csr, EWRK3_CSR); /* Disable the TX and RX */
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
if (copy_from_user(tmp->addr, ioc->data, ETH_ALEN)) {
status = -EFAULT;
break;
}
+ spin_lock_irqsave(&lp->hw_lock, flags);
for (i = 0; i < ETH_ALEN; i++) {
dev->dev_addr[i] = tmp->addr[i];
outb(tmp->addr[i], EWRK3_PAR0 + i);
}
+ csr = inb(EWRK3_CSR);
csr &= ~(CSR_TXD | CSR_RXD); /* Enable the TX and RX */
outb(csr, EWRK3_CSR);
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
} else {
status = -EPERM;
}
@@ -1671,10 +1890,12 @@
break;
case EWRK3_SET_PROM: /* Set Promiscuous Mode */
if (capable(CAP_NET_ADMIN)) {
+ spin_lock_irqsave(&lp->hw_lock, flags);
csr = inb(EWRK3_CSR);
csr |= CSR_PME;
csr &= ~CSR_MCE;
outb(csr, EWRK3_CSR);
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
} else {
status = -EPERM;
}
@@ -1682,16 +1903,18 @@
break;
case EWRK3_CLR_PROM: /* Clear Promiscuous Mode */
if (capable(CAP_NET_ADMIN)) {
+ spin_lock_irqsave(&lp->hw_lock, flags);
csr = inb(EWRK3_CSR);
csr &= ~CSR_PME;
outb(csr, EWRK3_CSR);
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
} else {
status = -EPERM;
}
break;
case EWRK3_GET_MCA: /* Get the multicast address table */
- spin_lock_irq(&lp->hw_lock);
+ spin_lock_irqsave(&lp->hw_lock, flags);
if (lp->shmem_length == IO_ONLY) {
outb(0, EWRK3_IOPR);
outw(PAGE0_HTE, EWRK3_PIR1);
@@ -1702,7 +1925,7 @@
outb(0, EWRK3_MPR);
isa_memcpy_fromio(tmp->addr, lp->shmem_base + PAGE0_HTE, (HASH_TABLE_LEN >> 3));
}
- spin_unlock_irq(&lp->hw_lock);
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
ioc->len = (HASH_TABLE_LEN >> 3);
if (copy_to_user(ioc->data, tmp->addr, ioc->len))
@@ -1736,28 +1959,37 @@
break;
case EWRK3_MCA_EN: /* Enable multicast addressing */
if (capable(CAP_NET_ADMIN)) {
+ spin_lock_irqsave(&lp->hw_lock, flags);
csr = inb(EWRK3_CSR);
csr |= CSR_MCE;
csr &= ~CSR_PME;
outb(csr, EWRK3_CSR);
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
} else {
status = -EPERM;
}
break;
- case EWRK3_GET_STATS: /* Get the driver statistics */
- cli();
- ioc->len = sizeof(lp->pktStats);
- if (copy_to_user(ioc->data, &lp->pktStats, ioc->len))
- status = -EFAULT;
- sti();
+ case EWRK3_GET_STATS: { /* Get the driver statistics */
+ struct ewrk3_stats *tmp_stats =
+ kmalloc(sizeof(lp->pktStats), GFP_KERNEL);
+ if (!tmp_stats) return -ENOMEM;
+
+ spin_lock_irqsave(&lp->hw_lock, flags);
+ memcpy(tmp_stats, &lp->pktStats, sizeof(lp->pktStats));
+ spin_unlock_irqrestore(&lp->hw_lock, flags);
+ ioc->len = sizeof(lp->pktStats);
+ if (copy_to_user(ioc->data, tmp_stats, sizeof(lp->pktStats)))
+ status = -EFAULT;
+ kfree(tmp_stats);
break;
+ }
case EWRK3_CLR_STATS: /* Zero out the driver statistics */
if (capable(CAP_NET_ADMIN)) {
- cli();
+ spin_lock_irqsave(&lp->hw_lock, flags);
memset(&lp->pktStats, 0, sizeof(lp->pktStats));
- sti();
+ spin_unlock_irqrestore(&lp->hw_lock,flags);
} else {
status = -EPERM;
}
@@ -1843,35 +2075,61 @@
}
#ifdef MODULE
-static struct net_device thisEthwrk;
-static int io = 0x300; /* <--- EDIT THESE LINES FOR YOUR CONFIGURATION */
-static int irq = 5; /* or use the insmod io= irq= options */
-
-MODULE_PARM(io, "i");
-MODULE_PARM(irq, "i");
-MODULE_PARM_DESC(io, "EtherWORKS 3 I/O base address");
-MODULE_PARM_DESC(irq, "EtherWORKS 3 IRQ number");
+static struct net_device *ewrk3_devs[MAX_NUM_EWRK3S];
+static int ndevs;
+static int io[MAX_NUM_EWRK3S+1] = { 0x300, 0, }; /* <--- EDIT THESE LINES FOR YOUR CONFIGURATION */
+static int irq[MAX_NUM_EWRK3S+1] = { 5, 0, }; /* or use the insmod io= irq= options */
+
+/* '21' below should really be 'MAX_NUM_EWRK3S' */
+MODULE_PARM(io, "0-21i");
+MODULE_PARM(irq, "0-21i");
+MODULE_PARM_DESC(io, "EtherWORKS 3 I/O base address(es)");
+MODULE_PARM_DESC(irq, "EtherWORKS 3 IRQ number(s)");
int init_module(void)
{
- thisEthwrk.base_addr = io;
- thisEthwrk.irq = irq;
- thisEthwrk.init = ewrk3_probe;
- if (register_netdev(&thisEthwrk) != 0)
- return -EIO;
- return 0;
+ int i=0;
+
+ while( io[i] && irq[i] ) {
+ ewrk3_devs[ndevs] = kmalloc(sizeof(struct net_device), GFP_KERNEL);
+ if (!ewrk3_devs[ndevs])
+ goto error;
+ memset(ewrk3_devs[ndevs], 0, sizeof(struct net_device));
+ ewrk3_devs[ndevs]->base_addr = io[i];
+ ewrk3_devs[ndevs]->irq = irq[i];
+ ewrk3_devs[ndevs]->init = ewrk3_probe;
+
+ if (register_netdev(ewrk3_devs[ndevs]) == 0)
+ ndevs++;
+ else
+ kfree(ewrk3_devs[ndevs]);
+
+ i++;
+ }
+
+ return ndevs ? 0 : -EIO;
+
+error:
+ cleanup_module();
+ return -ENOMEM;
}
void cleanup_module(void)
{
- unregister_netdev(&thisEthwrk);
- if (thisEthwrk.priv) {
- kfree(thisEthwrk.priv);
- thisEthwrk.priv = NULL;
- }
- thisEthwrk.irq = 0;
+ int i;
- release_region(thisEthwrk.base_addr, EWRK3_TOTAL_SIZE);
+ for( i=0; i<ndevs; i++ ) {
+ unregister_netdev(ewrk3_devs[i]);
+ if (ewrk3_devs[i]->priv) {
+ kfree(ewrk3_devs[i]->priv);
+ ewrk3_devs[i]->priv = NULL;
+ }
+ ewrk3_devs[i]->irq = 0;
+
+ release_region(ewrk3_devs[i]->base_addr, EWRK3_TOTAL_SIZE);
+ kfree(ewrk3_devs[i]);
+ ewrk3_devs[i] = NULL;
+ }
}
#endif /* MODULE */
MODULE_LICENSE("GPL");
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)