mirror of
https://github.com/torvalds/linux.git
synced 2024-12-20 18:11:47 +00:00
1218 lines
28 KiB
C
1218 lines
28 KiB
C
|
/*
|
||
|
net-3-driver for the SKNET MCA-based cards
|
||
|
|
||
|
This is an extension to the Linux operating system, and is covered by the
|
||
|
same GNU General Public License that covers that work.
|
||
|
|
||
|
Copyright 1999 by Alfred Arnold (alfred@ccac.rwth-aachen.de,
|
||
|
alfred.arnold@lancom.de)
|
||
|
|
||
|
This driver is based both on the 3C523 driver and the SK_G16 driver.
|
||
|
|
||
|
paper sources:
|
||
|
'PC Hardware: Aufbau, Funktionsweise, Programmierung' by
|
||
|
Hans-Peter Messmer for the basic Microchannel stuff
|
||
|
|
||
|
'Linux Geraetetreiber' by Allesandro Rubini, Kalle Dalheimer
|
||
|
for help on Ethernet driver programming
|
||
|
|
||
|
'Ethernet/IEEE 802.3 Family 1992 World Network Data Book/Handbook' by AMD
|
||
|
for documentation on the AM7990 LANCE
|
||
|
|
||
|
'SKNET Personal Technisches Manual', Version 1.2 by Schneider&Koch
|
||
|
for documentation on the Junior board
|
||
|
|
||
|
'SK-NET MC2+ Technical Manual", Version 1.1 by Schneider&Koch for
|
||
|
documentation on the MC2 bord
|
||
|
|
||
|
A big thank you to the S&K support for providing me so quickly with
|
||
|
documentation!
|
||
|
|
||
|
Also see http://www.syskonnect.com/
|
||
|
|
||
|
Missing things:
|
||
|
|
||
|
-> set debug level via ioctl instead of compile-time switches
|
||
|
-> I didn't follow the development of the 2.1.x kernels, so my
|
||
|
assumptions about which things changed with which kernel version
|
||
|
are probably nonsense
|
||
|
|
||
|
History:
|
||
|
May 16th, 1999
|
||
|
startup
|
||
|
May 22st, 1999
|
||
|
added private structure, methods
|
||
|
begun building data structures in RAM
|
||
|
May 23nd, 1999
|
||
|
can receive frames, send frames
|
||
|
May 24th, 1999
|
||
|
modularized initialization of LANCE
|
||
|
loadable as module
|
||
|
still Tx problem :-(
|
||
|
May 26th, 1999
|
||
|
MC2 works
|
||
|
support for multiple devices
|
||
|
display media type for MC2+
|
||
|
May 28th, 1999
|
||
|
fixed problem in GetLANCE leaving interrupts turned off
|
||
|
increase TX queue to 4 packets to improve send performance
|
||
|
May 29th, 1999
|
||
|
a few corrections in statistics, caught rcvr overruns
|
||
|
reinitialization of LANCE/board in critical situations
|
||
|
MCA info implemented
|
||
|
implemented LANCE multicast filter
|
||
|
Jun 6th, 1999
|
||
|
additions for Linux 2.2
|
||
|
Dec 25th, 1999
|
||
|
unfortunately there seem to be newer MC2+ boards that react
|
||
|
on IRQ 3/5/9/10 instead of 3/5/10/11, so we have to autoprobe
|
||
|
in questionable cases...
|
||
|
Dec 28th, 1999
|
||
|
integrated patches from David Weinehall & Bill Wendling for 2.3
|
||
|
kernels (isa_...functions). Things are defined in a way that
|
||
|
it still works with 2.0.x 8-)
|
||
|
Dec 30th, 1999
|
||
|
added handling of the remaining interrupt conditions. That
|
||
|
should cure the spurious hangs.
|
||
|
Jan 30th, 2000
|
||
|
newer kernels automatically probe more than one board, so the
|
||
|
'startslot' as a variable is also needed here
|
||
|
June 1st, 2000
|
||
|
added changes for recent 2.3 kernels
|
||
|
|
||
|
*************************************************************************/
|
||
|
|
||
|
#include <linux/kernel.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/errno.h>
|
||
|
#include <linux/ioport.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/delay.h>
|
||
|
#include <linux/time.h>
|
||
|
#include <linux/mca-legacy.h>
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/netdevice.h>
|
||
|
#include <linux/etherdevice.h>
|
||
|
#include <linux/skbuff.h>
|
||
|
#include <linux/bitops.h>
|
||
|
|
||
|
#include <asm/processor.h>
|
||
|
#include <asm/io.h>
|
||
|
|
||
|
#define _SK_MCA_DRIVER_
|
||
|
#include "sk_mca.h"
|
||
|
|
||
|
/* ------------------------------------------------------------------------
|
||
|
* global static data - not more since we can handle multiple boards and
|
||
|
* have to pack all state info into the device struct!
|
||
|
* ------------------------------------------------------------------------ */
|
||
|
|
||
|
static char *MediaNames[Media_Count] =
|
||
|
{ "10Base2", "10BaseT", "10Base5", "Unknown" };
|
||
|
|
||
|
static unsigned char poly[] =
|
||
|
{ 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0,
|
||
|
1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0
|
||
|
};
|
||
|
|
||
|
/* ------------------------------------------------------------------------
|
||
|
* private subfunctions
|
||
|
* ------------------------------------------------------------------------ */
|
||
|
|
||
|
/* dump parts of shared memory - only needed during debugging */
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
static void dumpmem(struct net_device *dev, u32 start, u32 len)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
int z;
|
||
|
|
||
|
for (z = 0; z < len; z++) {
|
||
|
if ((z & 15) == 0)
|
||
|
printk("%04x:", z);
|
||
|
printk(" %02x", readb(priv->base + start + z));
|
||
|
if ((z & 15) == 15)
|
||
|
printk("\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* print exact time - ditto */
|
||
|
|
||
|
static void PrTime(void)
|
||
|
{
|
||
|
struct timeval tv;
|
||
|
|
||
|
do_gettimeofday(&tv);
|
||
|
printk("%9d:%06d: ", tv.tv_sec, tv.tv_usec);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* deduce resources out of POS registers */
|
||
|
|
||
|
static void __init getaddrs(int slot, int junior, int *base, int *irq,
|
||
|
skmca_medium * medium)
|
||
|
{
|
||
|
u_char pos0, pos1, pos2;
|
||
|
|
||
|
if (junior) {
|
||
|
pos0 = mca_read_stored_pos(slot, 2);
|
||
|
*base = ((pos0 & 0x0e) << 13) + 0xc0000;
|
||
|
*irq = ((pos0 & 0x10) >> 4) + 10;
|
||
|
*medium = Media_Unknown;
|
||
|
} else {
|
||
|
/* reset POS 104 Bits 0+1 so the shared memory region goes to the
|
||
|
configured area between 640K and 1M. Afterwards, enable the MC2.
|
||
|
I really don't know what rode SK to do this... */
|
||
|
|
||
|
mca_write_pos(slot, 4,
|
||
|
mca_read_stored_pos(slot, 4) & 0xfc);
|
||
|
mca_write_pos(slot, 2,
|
||
|
mca_read_stored_pos(slot, 2) | 0x01);
|
||
|
|
||
|
pos1 = mca_read_stored_pos(slot, 3);
|
||
|
pos2 = mca_read_stored_pos(slot, 4);
|
||
|
*base = ((pos1 & 0x07) << 14) + 0xc0000;
|
||
|
switch (pos2 & 0x0c) {
|
||
|
case 0:
|
||
|
*irq = 3;
|
||
|
break;
|
||
|
case 4:
|
||
|
*irq = 5;
|
||
|
break;
|
||
|
case 8:
|
||
|
*irq = -10;
|
||
|
break;
|
||
|
case 12:
|
||
|
*irq = -11;
|
||
|
break;
|
||
|
}
|
||
|
*medium = (pos2 >> 6) & 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* check for both cards:
|
||
|
When the MC2 is turned off, it was configured for more than 15MB RAM,
|
||
|
is disabled and won't get detected using the standard probe. We
|
||
|
therefore have to scan the slots manually :-( */
|
||
|
|
||
|
static int __init dofind(int *junior, int firstslot)
|
||
|
{
|
||
|
int slot;
|
||
|
unsigned int id;
|
||
|
|
||
|
for (slot = firstslot; slot < MCA_MAX_SLOT_NR; slot++) {
|
||
|
id = mca_read_stored_pos(slot, 0)
|
||
|
+ (((unsigned int) mca_read_stored_pos(slot, 1)) << 8);
|
||
|
|
||
|
*junior = 0;
|
||
|
if (id == SKNET_MCA_ID)
|
||
|
return slot;
|
||
|
*junior = 1;
|
||
|
if (id == SKNET_JUNIOR_MCA_ID)
|
||
|
return slot;
|
||
|
}
|
||
|
return MCA_NOTFOUND;
|
||
|
}
|
||
|
|
||
|
/* reset the whole board */
|
||
|
|
||
|
static void ResetBoard(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
|
||
|
writeb(CTRL_RESET_ON, priv->ctrladdr);
|
||
|
udelay(10);
|
||
|
writeb(CTRL_RESET_OFF, priv->ctrladdr);
|
||
|
}
|
||
|
|
||
|
/* wait for LANCE interface to become not busy */
|
||
|
|
||
|
static int WaitLANCE(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
int t = 0;
|
||
|
|
||
|
while ((readb(priv->ctrladdr) & STAT_IO_BUSY) ==
|
||
|
STAT_IO_BUSY) {
|
||
|
udelay(1);
|
||
|
if (++t > 1000) {
|
||
|
printk("%s: LANCE access timeout", dev->name);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/* set LANCE register - must be atomic */
|
||
|
|
||
|
static void SetLANCE(struct net_device *dev, u16 addr, u16 value)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
unsigned long flags;
|
||
|
|
||
|
/* disable interrupts */
|
||
|
|
||
|
spin_lock_irqsave(&priv->lock, flags);
|
||
|
|
||
|
/* wait until no transfer is pending */
|
||
|
|
||
|
WaitLANCE(dev);
|
||
|
|
||
|
/* transfer register address to RAP */
|
||
|
|
||
|
writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_RAP, priv->ctrladdr);
|
||
|
writew(addr, priv->ioregaddr);
|
||
|
writeb(IOCMD_GO, priv->cmdaddr);
|
||
|
udelay(1);
|
||
|
WaitLANCE(dev);
|
||
|
|
||
|
/* transfer data to register */
|
||
|
|
||
|
writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_DATA, priv->ctrladdr);
|
||
|
writew(value, priv->ioregaddr);
|
||
|
writeb(IOCMD_GO, priv->cmdaddr);
|
||
|
udelay(1);
|
||
|
WaitLANCE(dev);
|
||
|
|
||
|
/* reenable interrupts */
|
||
|
|
||
|
spin_unlock_irqrestore(&priv->lock, flags);
|
||
|
}
|
||
|
|
||
|
/* get LANCE register */
|
||
|
|
||
|
static u16 GetLANCE(struct net_device *dev, u16 addr)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
unsigned long flags;
|
||
|
unsigned int res;
|
||
|
|
||
|
/* disable interrupts */
|
||
|
|
||
|
spin_lock_irqsave(&priv->lock, flags);
|
||
|
|
||
|
/* wait until no transfer is pending */
|
||
|
|
||
|
WaitLANCE(dev);
|
||
|
|
||
|
/* transfer register address to RAP */
|
||
|
|
||
|
writeb(CTRL_RESET_OFF | CTRL_RW_WRITE | CTRL_ADR_RAP, priv->ctrladdr);
|
||
|
writew(addr, priv->ioregaddr);
|
||
|
writeb(IOCMD_GO, priv->cmdaddr);
|
||
|
udelay(1);
|
||
|
WaitLANCE(dev);
|
||
|
|
||
|
/* transfer data from register */
|
||
|
|
||
|
writeb(CTRL_RESET_OFF | CTRL_RW_READ | CTRL_ADR_DATA, priv->ctrladdr);
|
||
|
writeb(IOCMD_GO, priv->cmdaddr);
|
||
|
udelay(1);
|
||
|
WaitLANCE(dev);
|
||
|
res = readw(priv->ioregaddr);
|
||
|
|
||
|
/* reenable interrupts */
|
||
|
|
||
|
spin_unlock_irqrestore(&priv->lock, flags);
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
/* build up descriptors in shared RAM */
|
||
|
|
||
|
static void InitDscrs(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
u32 bufaddr;
|
||
|
|
||
|
/* Set up Tx descriptors. The board has only 16K RAM so bits 16..23
|
||
|
are always 0. */
|
||
|
|
||
|
bufaddr = RAM_DATABASE;
|
||
|
{
|
||
|
LANCE_TxDescr descr;
|
||
|
int z;
|
||
|
|
||
|
for (z = 0; z < TXCOUNT; z++) {
|
||
|
descr.LowAddr = bufaddr;
|
||
|
descr.Flags = 0;
|
||
|
descr.Len = 0xf000;
|
||
|
descr.Status = 0;
|
||
|
memcpy_toio(priv->base + RAM_TXBASE +
|
||
|
(z * sizeof(LANCE_TxDescr)), &descr,
|
||
|
sizeof(LANCE_TxDescr));
|
||
|
memset_io(priv->base + bufaddr, 0, RAM_BUFSIZE);
|
||
|
bufaddr += RAM_BUFSIZE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* do the same for the Rx descriptors */
|
||
|
|
||
|
{
|
||
|
LANCE_RxDescr descr;
|
||
|
int z;
|
||
|
|
||
|
for (z = 0; z < RXCOUNT; z++) {
|
||
|
descr.LowAddr = bufaddr;
|
||
|
descr.Flags = RXDSCR_FLAGS_OWN;
|
||
|
descr.MaxLen = -RAM_BUFSIZE;
|
||
|
descr.Len = 0;
|
||
|
memcpy_toio(priv->base + RAM_RXBASE +
|
||
|
(z * sizeof(LANCE_RxDescr)), &descr,
|
||
|
sizeof(LANCE_RxDescr));
|
||
|
memset_io(priv->base + bufaddr, 0, RAM_BUFSIZE);
|
||
|
bufaddr += RAM_BUFSIZE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* calculate the hash bit position for a given multicast address
|
||
|
taken more or less directly from the AMD datasheet... */
|
||
|
|
||
|
static void UpdateCRC(unsigned char *CRC, int bit)
|
||
|
{
|
||
|
int j;
|
||
|
|
||
|
/* shift CRC one bit */
|
||
|
|
||
|
memmove(CRC + 1, CRC, 32 * sizeof(unsigned char));
|
||
|
CRC[0] = 0;
|
||
|
|
||
|
/* if bit XOR controlbit = 1, set CRC = CRC XOR polynomial */
|
||
|
|
||
|
if (bit ^ CRC[32])
|
||
|
for (j = 0; j < 32; j++)
|
||
|
CRC[j] ^= poly[j];
|
||
|
}
|
||
|
|
||
|
static unsigned int GetHash(char *address)
|
||
|
{
|
||
|
unsigned char CRC[33];
|
||
|
int i, byte, hashcode;
|
||
|
|
||
|
/* a multicast address has bit 0 in the first byte set */
|
||
|
|
||
|
if ((address[0] & 1) == 0)
|
||
|
return -1;
|
||
|
|
||
|
/* initialize CRC */
|
||
|
|
||
|
memset(CRC, 1, sizeof(CRC));
|
||
|
|
||
|
/* loop through address bits */
|
||
|
|
||
|
for (byte = 0; byte < 6; byte++)
|
||
|
for (i = 0; i < 8; i++)
|
||
|
UpdateCRC(CRC, (address[byte] >> i) & 1);
|
||
|
|
||
|
/* hashcode is the 6 least significant bits of the CRC */
|
||
|
|
||
|
hashcode = 0;
|
||
|
for (i = 0; i < 6; i++)
|
||
|
hashcode = (hashcode << 1) + CRC[i];
|
||
|
return hashcode;
|
||
|
}
|
||
|
|
||
|
/* feed ready-built initialization block into LANCE */
|
||
|
|
||
|
static void InitLANCE(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
|
||
|
/* build up descriptors. */
|
||
|
|
||
|
InitDscrs(dev);
|
||
|
|
||
|
/* next RX descriptor to be read is the first one. Since the LANCE
|
||
|
will start from the beginning after initialization, we have to
|
||
|
reset out pointers too. */
|
||
|
|
||
|
priv->nextrx = 0;
|
||
|
|
||
|
/* no TX descriptors active */
|
||
|
|
||
|
priv->nexttxput = priv->nexttxdone = priv->txbusy = 0;
|
||
|
|
||
|
/* set up the LANCE bus control register - constant for SKnet boards */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR3,
|
||
|
CSR3_BSWAP_OFF | CSR3_ALE_LOW | CSR3_BCON_HOLD);
|
||
|
|
||
|
/* write address of initialization block into LANCE */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR1, RAM_INITBASE & 0xffff);
|
||
|
SetLANCE(dev, LANCE_CSR2, (RAM_INITBASE >> 16) & 0xff);
|
||
|
|
||
|
/* we don't get ready until the LANCE has read the init block */
|
||
|
|
||
|
netif_stop_queue(dev);
|
||
|
|
||
|
/* let LANCE read the initialization block. LANCE is ready
|
||
|
when we receive the corresponding interrupt. */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR0, CSR0_INEA | CSR0_INIT);
|
||
|
}
|
||
|
|
||
|
/* stop the LANCE so we can reinitialize it */
|
||
|
|
||
|
static void StopLANCE(struct net_device *dev)
|
||
|
{
|
||
|
/* can't take frames any more */
|
||
|
|
||
|
netif_stop_queue(dev);
|
||
|
|
||
|
/* disable interrupts, stop it */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR0, CSR0_STOP);
|
||
|
}
|
||
|
|
||
|
/* initialize card and LANCE for proper operation */
|
||
|
|
||
|
static void InitBoard(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
LANCE_InitBlock block;
|
||
|
|
||
|
/* Lay out the shared RAM - first we create the init block for the LANCE.
|
||
|
We do not overwrite it later because we need it again when we switch
|
||
|
promiscous mode on/off. */
|
||
|
|
||
|
block.Mode = 0;
|
||
|
if (dev->flags & IFF_PROMISC)
|
||
|
block.Mode |= LANCE_INIT_PROM;
|
||
|
memcpy(block.PAdr, dev->dev_addr, 6);
|
||
|
memset(block.LAdrF, 0, sizeof(block.LAdrF));
|
||
|
block.RdrP = (RAM_RXBASE & 0xffffff) | (LRXCOUNT << 29);
|
||
|
block.TdrP = (RAM_TXBASE & 0xffffff) | (LTXCOUNT << 29);
|
||
|
|
||
|
memcpy_toio(priv->base + RAM_INITBASE, &block, sizeof(block));
|
||
|
|
||
|
/* initialize LANCE. Implicitly sets up other structures in RAM. */
|
||
|
|
||
|
InitLANCE(dev);
|
||
|
}
|
||
|
|
||
|
/* deinitialize card and LANCE */
|
||
|
|
||
|
static void DeinitBoard(struct net_device *dev)
|
||
|
{
|
||
|
/* stop LANCE */
|
||
|
|
||
|
StopLANCE(dev);
|
||
|
|
||
|
/* reset board */
|
||
|
|
||
|
ResetBoard(dev);
|
||
|
}
|
||
|
|
||
|
/* probe for device's irq */
|
||
|
|
||
|
static int __init ProbeIRQ(struct net_device *dev)
|
||
|
{
|
||
|
unsigned long imaskval, njiffies, irq;
|
||
|
u16 csr0val;
|
||
|
|
||
|
/* enable all interrupts */
|
||
|
|
||
|
imaskval = probe_irq_on();
|
||
|
|
||
|
/* initialize the board. Wait for interrupt 'Initialization done'. */
|
||
|
|
||
|
ResetBoard(dev);
|
||
|
InitBoard(dev);
|
||
|
|
||
|
njiffies = jiffies + HZ;
|
||
|
do {
|
||
|
csr0val = GetLANCE(dev, LANCE_CSR0);
|
||
|
}
|
||
|
while (((csr0val & CSR0_IDON) == 0) && (jiffies != njiffies));
|
||
|
|
||
|
/* turn of interrupts again */
|
||
|
|
||
|
irq = probe_irq_off(imaskval);
|
||
|
|
||
|
/* if we found something, ack the interrupt */
|
||
|
|
||
|
if (irq)
|
||
|
SetLANCE(dev, LANCE_CSR0, csr0val | CSR0_IDON);
|
||
|
|
||
|
/* back to idle state */
|
||
|
|
||
|
DeinitBoard(dev);
|
||
|
|
||
|
return irq;
|
||
|
}
|
||
|
|
||
|
/* ------------------------------------------------------------------------
|
||
|
* interrupt handler(s)
|
||
|
* ------------------------------------------------------------------------ */
|
||
|
|
||
|
/* LANCE has read initialization block -> start it */
|
||
|
|
||
|
static u16 irqstart_handler(struct net_device *dev, u16 oldcsr0)
|
||
|
{
|
||
|
/* now we're ready to transmit */
|
||
|
|
||
|
netif_wake_queue(dev);
|
||
|
|
||
|
/* reset IDON bit, start LANCE */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_IDON | CSR0_STRT);
|
||
|
return GetLANCE(dev, LANCE_CSR0);
|
||
|
}
|
||
|
|
||
|
/* did we lose blocks due to a FIFO overrun ? */
|
||
|
|
||
|
static u16 irqmiss_handler(struct net_device *dev, u16 oldcsr0)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
|
||
|
/* update statistics */
|
||
|
|
||
|
priv->stat.rx_fifo_errors++;
|
||
|
|
||
|
/* reset MISS bit */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_MISS);
|
||
|
return GetLANCE(dev, LANCE_CSR0);
|
||
|
}
|
||
|
|
||
|
/* receive interrupt */
|
||
|
|
||
|
static u16 irqrx_handler(struct net_device *dev, u16 oldcsr0)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
LANCE_RxDescr descr;
|
||
|
unsigned int descraddr;
|
||
|
|
||
|
/* run through queue until we reach a descriptor we do not own */
|
||
|
|
||
|
descraddr = RAM_RXBASE + (priv->nextrx * sizeof(LANCE_RxDescr));
|
||
|
while (1) {
|
||
|
/* read descriptor */
|
||
|
memcpy_fromio(&descr, priv->base + descraddr,
|
||
|
sizeof(LANCE_RxDescr));
|
||
|
|
||
|
/* if we reach a descriptor we do not own, we're done */
|
||
|
if ((descr.Flags & RXDSCR_FLAGS_OWN) != 0)
|
||
|
break;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
PrTime();
|
||
|
printk("Receive packet on descr %d len %d\n", priv->nextrx,
|
||
|
descr.Len);
|
||
|
#endif
|
||
|
|
||
|
/* erroneous packet ? */
|
||
|
if ((descr.Flags & RXDSCR_FLAGS_ERR) != 0) {
|
||
|
priv->stat.rx_errors++;
|
||
|
if ((descr.Flags & RXDSCR_FLAGS_CRC) != 0)
|
||
|
priv->stat.rx_crc_errors++;
|
||
|
else if ((descr.Flags & RXDSCR_FLAGS_CRC) != 0)
|
||
|
priv->stat.rx_frame_errors++;
|
||
|
else if ((descr.Flags & RXDSCR_FLAGS_OFLO) != 0)
|
||
|
priv->stat.rx_fifo_errors++;
|
||
|
}
|
||
|
|
||
|
/* good packet ? */
|
||
|
else {
|
||
|
struct sk_buff *skb;
|
||
|
|
||
|
skb = dev_alloc_skb(descr.Len + 2);
|
||
|
if (skb == NULL)
|
||
|
priv->stat.rx_dropped++;
|
||
|
else {
|
||
|
memcpy_fromio(skb_put(skb, descr.Len),
|
||
|
priv->base +
|
||
|
descr.LowAddr, descr.Len);
|
||
|
skb->dev = dev;
|
||
|
skb->protocol = eth_type_trans(skb, dev);
|
||
|
skb->ip_summed = CHECKSUM_NONE;
|
||
|
priv->stat.rx_packets++;
|
||
|
priv->stat.rx_bytes += descr.Len;
|
||
|
netif_rx(skb);
|
||
|
dev->last_rx = jiffies;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* give descriptor back to LANCE */
|
||
|
descr.Len = 0;
|
||
|
descr.Flags |= RXDSCR_FLAGS_OWN;
|
||
|
|
||
|
/* update descriptor in shared RAM */
|
||
|
memcpy_toio(priv->base + descraddr, &descr,
|
||
|
sizeof(LANCE_RxDescr));
|
||
|
|
||
|
/* go to next descriptor */
|
||
|
priv->nextrx++;
|
||
|
descraddr += sizeof(LANCE_RxDescr);
|
||
|
if (priv->nextrx >= RXCOUNT) {
|
||
|
priv->nextrx = 0;
|
||
|
descraddr = RAM_RXBASE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* reset RINT bit */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_RINT);
|
||
|
return GetLANCE(dev, LANCE_CSR0);
|
||
|
}
|
||
|
|
||
|
/* transmit interrupt */
|
||
|
|
||
|
static u16 irqtx_handler(struct net_device *dev, u16 oldcsr0)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
LANCE_TxDescr descr;
|
||
|
unsigned int descraddr;
|
||
|
|
||
|
/* check descriptors at most until no busy one is left */
|
||
|
|
||
|
descraddr =
|
||
|
RAM_TXBASE + (priv->nexttxdone * sizeof(LANCE_TxDescr));
|
||
|
while (priv->txbusy > 0) {
|
||
|
/* read descriptor */
|
||
|
memcpy_fromio(&descr, priv->base + descraddr,
|
||
|
sizeof(LANCE_TxDescr));
|
||
|
|
||
|
/* if the LANCE still owns this one, we've worked out all sent packets */
|
||
|
if ((descr.Flags & TXDSCR_FLAGS_OWN) != 0)
|
||
|
break;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
PrTime();
|
||
|
printk("Send packet done on descr %d\n", priv->nexttxdone);
|
||
|
#endif
|
||
|
|
||
|
/* update statistics */
|
||
|
if ((descr.Flags & TXDSCR_FLAGS_ERR) == 0) {
|
||
|
priv->stat.tx_packets++;
|
||
|
priv->stat.tx_bytes++;
|
||
|
} else {
|
||
|
priv->stat.tx_errors++;
|
||
|
if ((descr.Status & TXDSCR_STATUS_UFLO) != 0) {
|
||
|
priv->stat.tx_fifo_errors++;
|
||
|
InitLANCE(dev);
|
||
|
}
|
||
|
else
|
||
|
if ((descr.Status & TXDSCR_STATUS_LCOL) !=
|
||
|
0) priv->stat.tx_window_errors++;
|
||
|
else if ((descr.Status & TXDSCR_STATUS_LCAR) != 0)
|
||
|
priv->stat.tx_carrier_errors++;
|
||
|
else if ((descr.Status & TXDSCR_STATUS_RTRY) != 0)
|
||
|
priv->stat.tx_aborted_errors++;
|
||
|
}
|
||
|
|
||
|
/* go to next descriptor */
|
||
|
priv->nexttxdone++;
|
||
|
descraddr += sizeof(LANCE_TxDescr);
|
||
|
if (priv->nexttxdone >= TXCOUNT) {
|
||
|
priv->nexttxdone = 0;
|
||
|
descraddr = RAM_TXBASE;
|
||
|
}
|
||
|
priv->txbusy--;
|
||
|
}
|
||
|
|
||
|
/* reset TX interrupt bit */
|
||
|
|
||
|
SetLANCE(dev, LANCE_CSR0, oldcsr0 | CSR0_TINT);
|
||
|
oldcsr0 = GetLANCE(dev, LANCE_CSR0);
|
||
|
|
||
|
/* at least one descriptor is freed. Therefore we can accept
|
||
|
a new one */
|
||
|
/* inform upper layers we're in business again */
|
||
|
|
||
|
netif_wake_queue(dev);
|
||
|
|
||
|
return oldcsr0;
|
||
|
}
|
||
|
|
||
|
/* general interrupt entry */
|
||
|
|
||
|
static irqreturn_t irq_handler(int irq, void *device, struct pt_regs *regs)
|
||
|
{
|
||
|
struct net_device *dev = (struct net_device *) device;
|
||
|
u16 csr0val;
|
||
|
|
||
|
/* read CSR0 to get interrupt cause */
|
||
|
|
||
|
csr0val = GetLANCE(dev, LANCE_CSR0);
|
||
|
|
||
|
/* in case we're not meant... */
|
||
|
|
||
|
if ((csr0val & CSR0_INTR) == 0)
|
||
|
return IRQ_NONE;
|
||
|
|
||
|
#if 0
|
||
|
set_bit(LINK_STATE_RXSEM, &dev->state);
|
||
|
#endif
|
||
|
|
||
|
/* loop through the interrupt bits until everything is clear */
|
||
|
|
||
|
do {
|
||
|
if ((csr0val & CSR0_IDON) != 0)
|
||
|
csr0val = irqstart_handler(dev, csr0val);
|
||
|
if ((csr0val & CSR0_RINT) != 0)
|
||
|
csr0val = irqrx_handler(dev, csr0val);
|
||
|
if ((csr0val & CSR0_MISS) != 0)
|
||
|
csr0val = irqmiss_handler(dev, csr0val);
|
||
|
if ((csr0val & CSR0_TINT) != 0)
|
||
|
csr0val = irqtx_handler(dev, csr0val);
|
||
|
if ((csr0val & CSR0_MERR) != 0) {
|
||
|
SetLANCE(dev, LANCE_CSR0, csr0val | CSR0_MERR);
|
||
|
csr0val = GetLANCE(dev, LANCE_CSR0);
|
||
|
}
|
||
|
if ((csr0val & CSR0_BABL) != 0) {
|
||
|
SetLANCE(dev, LANCE_CSR0, csr0val | CSR0_BABL);
|
||
|
csr0val = GetLANCE(dev, LANCE_CSR0);
|
||
|
}
|
||
|
}
|
||
|
while ((csr0val & CSR0_INTR) != 0);
|
||
|
|
||
|
#if 0
|
||
|
clear_bit(LINK_STATE_RXSEM, &dev->state);
|
||
|
#endif
|
||
|
return IRQ_HANDLED;
|
||
|
}
|
||
|
|
||
|
/* ------------------------------------------------------------------------
|
||
|
* driver methods
|
||
|
* ------------------------------------------------------------------------ */
|
||
|
|
||
|
/* MCA info */
|
||
|
|
||
|
static int skmca_getinfo(char *buf, int slot, void *d)
|
||
|
{
|
||
|
int len = 0, i;
|
||
|
struct net_device *dev = (struct net_device *) d;
|
||
|
skmca_priv *priv;
|
||
|
|
||
|
/* can't say anything about an uninitialized device... */
|
||
|
|
||
|
if (dev == NULL)
|
||
|
return len;
|
||
|
priv = netdev_priv(dev);
|
||
|
|
||
|
/* print info */
|
||
|
|
||
|
len += sprintf(buf + len, "IRQ: %d\n", priv->realirq);
|
||
|
len += sprintf(buf + len, "Memory: %#lx-%#lx\n", dev->mem_start,
|
||
|
dev->mem_end - 1);
|
||
|
len +=
|
||
|
sprintf(buf + len, "Transceiver: %s\n",
|
||
|
MediaNames[priv->medium]);
|
||
|
len += sprintf(buf + len, "Device: %s\n", dev->name);
|
||
|
len += sprintf(buf + len, "MAC address:");
|
||
|
for (i = 0; i < 6; i++)
|
||
|
len += sprintf(buf + len, " %02x", dev->dev_addr[i]);
|
||
|
buf[len++] = '\n';
|
||
|
buf[len] = 0;
|
||
|
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
/* open driver. Means also initialization and start of LANCE */
|
||
|
|
||
|
static int skmca_open(struct net_device *dev)
|
||
|
{
|
||
|
int result;
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
|
||
|
/* register resources - only necessary for IRQ */
|
||
|
result =
|
||
|
request_irq(priv->realirq, irq_handler,
|
||
|
SA_SHIRQ | SA_SAMPLE_RANDOM, "sk_mca", dev);
|
||
|
if (result != 0) {
|
||
|
printk("%s: failed to register irq %d\n", dev->name,
|
||
|
dev->irq);
|
||
|
return result;
|
||
|
}
|
||
|
dev->irq = priv->realirq;
|
||
|
|
||
|
/* set up the card and LANCE */
|
||
|
|
||
|
InitBoard(dev);
|
||
|
|
||
|
/* set up flags */
|
||
|
|
||
|
netif_start_queue(dev);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* close driver. Shut down board and free allocated resources */
|
||
|
|
||
|
static int skmca_close(struct net_device *dev)
|
||
|
{
|
||
|
/* turn off board */
|
||
|
DeinitBoard(dev);
|
||
|
|
||
|
/* release resources */
|
||
|
if (dev->irq != 0)
|
||
|
free_irq(dev->irq, dev);
|
||
|
dev->irq = 0;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* transmit a block. */
|
||
|
|
||
|
static int skmca_tx(struct sk_buff *skb, struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
LANCE_TxDescr descr;
|
||
|
unsigned int address;
|
||
|
int tmplen, retval = 0;
|
||
|
unsigned long flags;
|
||
|
|
||
|
/* if we get called with a NULL descriptor, the Ethernet layer thinks
|
||
|
our card is stuck an we should reset it. We'll do this completely: */
|
||
|
|
||
|
if (skb == NULL) {
|
||
|
DeinitBoard(dev);
|
||
|
InitBoard(dev);
|
||
|
return 0; /* don't try to free the block here ;-) */
|
||
|
}
|
||
|
|
||
|
/* is there space in the Tx queue ? If no, the upper layer gave us a
|
||
|
packet in spite of us not being ready and is really in trouble.
|
||
|
We'll do the dropping for him: */
|
||
|
if (priv->txbusy >= TXCOUNT) {
|
||
|
priv->stat.tx_dropped++;
|
||
|
retval = -EIO;
|
||
|
goto tx_done;
|
||
|
}
|
||
|
|
||
|
/* get TX descriptor */
|
||
|
address = RAM_TXBASE + (priv->nexttxput * sizeof(LANCE_TxDescr));
|
||
|
memcpy_fromio(&descr, priv->base + address, sizeof(LANCE_TxDescr));
|
||
|
|
||
|
/* enter packet length as 2s complement - assure minimum length */
|
||
|
tmplen = skb->len;
|
||
|
if (tmplen < 60)
|
||
|
tmplen = 60;
|
||
|
descr.Len = 65536 - tmplen;
|
||
|
|
||
|
/* copy filler into RAM - in case we're filling up...
|
||
|
we're filling a bit more than necessary, but that doesn't harm
|
||
|
since the buffer is far larger... */
|
||
|
if (tmplen > skb->len) {
|
||
|
char *fill = "NetBSD is a nice OS too! ";
|
||
|
unsigned int destoffs = 0, l = strlen(fill);
|
||
|
|
||
|
while (destoffs < tmplen) {
|
||
|
memcpy_toio(priv->base + descr.LowAddr +
|
||
|
destoffs, fill, l);
|
||
|
destoffs += l;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* do the real data copying */
|
||
|
memcpy_toio(priv->base + descr.LowAddr, skb->data, skb->len);
|
||
|
|
||
|
/* hand descriptor over to LANCE - this is the first and last chunk */
|
||
|
descr.Flags =
|
||
|
TXDSCR_FLAGS_OWN | TXDSCR_FLAGS_STP | TXDSCR_FLAGS_ENP;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
PrTime();
|
||
|
printk("Send packet on descr %d len %d\n", priv->nexttxput,
|
||
|
skb->len);
|
||
|
#endif
|
||
|
|
||
|
/* one more descriptor busy */
|
||
|
|
||
|
spin_lock_irqsave(&priv->lock, flags);
|
||
|
|
||
|
priv->nexttxput++;
|
||
|
if (priv->nexttxput >= TXCOUNT)
|
||
|
priv->nexttxput = 0;
|
||
|
priv->txbusy++;
|
||
|
|
||
|
/* are we saturated ? */
|
||
|
|
||
|
if (priv->txbusy >= TXCOUNT)
|
||
|
netif_stop_queue(dev);
|
||
|
|
||
|
/* write descriptor back to RAM */
|
||
|
memcpy_toio(priv->base + address, &descr, sizeof(LANCE_TxDescr));
|
||
|
|
||
|
/* if no descriptors were active, give the LANCE a hint to read it
|
||
|
immediately */
|
||
|
|
||
|
if (priv->txbusy == 0)
|
||
|
SetLANCE(dev, LANCE_CSR0, CSR0_INEA | CSR0_TDMD);
|
||
|
|
||
|
spin_unlock_irqrestore(&priv->lock, flags);
|
||
|
|
||
|
tx_done:
|
||
|
|
||
|
dev_kfree_skb(skb);
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
/* return pointer to Ethernet statistics */
|
||
|
|
||
|
static struct net_device_stats *skmca_stats(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
|
||
|
return &(priv->stat);
|
||
|
}
|
||
|
|
||
|
/* switch receiver mode. We use the LANCE's multicast filter to prefilter
|
||
|
multicast addresses. */
|
||
|
|
||
|
static void skmca_set_multicast_list(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
LANCE_InitBlock block;
|
||
|
|
||
|
/* first stop the LANCE... */
|
||
|
StopLANCE(dev);
|
||
|
|
||
|
/* ...then modify the initialization block... */
|
||
|
memcpy_fromio(&block, priv->base + RAM_INITBASE, sizeof(block));
|
||
|
if (dev->flags & IFF_PROMISC)
|
||
|
block.Mode |= LANCE_INIT_PROM;
|
||
|
else
|
||
|
block.Mode &= ~LANCE_INIT_PROM;
|
||
|
|
||
|
if (dev->flags & IFF_ALLMULTI) { /* get all multicasts */
|
||
|
memset(block.LAdrF, 0xff, sizeof(block.LAdrF));
|
||
|
} else { /* get selected/no multicasts */
|
||
|
|
||
|
struct dev_mc_list *mptr;
|
||
|
int code;
|
||
|
|
||
|
memset(block.LAdrF, 0, sizeof(block.LAdrF));
|
||
|
for (mptr = dev->mc_list; mptr != NULL; mptr = mptr->next) {
|
||
|
code = GetHash(mptr->dmi_addr);
|
||
|
block.LAdrF[(code >> 3) & 7] |= 1 << (code & 7);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
memcpy_toio(priv->base + RAM_INITBASE, &block, sizeof(block));
|
||
|
|
||
|
/* ...then reinit LANCE with the correct flags */
|
||
|
InitLANCE(dev);
|
||
|
}
|
||
|
|
||
|
/* ------------------------------------------------------------------------
|
||
|
* hardware check
|
||
|
* ------------------------------------------------------------------------ */
|
||
|
|
||
|
static int startslot; /* counts through slots when probing multiple devices */
|
||
|
|
||
|
static void cleanup_card(struct net_device *dev)
|
||
|
{
|
||
|
skmca_priv *priv = netdev_priv(dev);
|
||
|
DeinitBoard(dev);
|
||
|
if (dev->irq != 0)
|
||
|
free_irq(dev->irq, dev);
|
||
|
iounmap(priv->base);
|
||
|
mca_mark_as_unused(priv->slot);
|
||
|
mca_set_adapter_procfn(priv->slot, NULL, NULL);
|
||
|
}
|
||
|
|
||
|
struct net_device * __init skmca_probe(int unit)
|
||
|
{
|
||
|
struct net_device *dev;
|
||
|
int force_detect = 0;
|
||
|
int junior, slot, i;
|
||
|
int base = 0, irq = 0;
|
||
|
skmca_priv *priv;
|
||
|
skmca_medium medium;
|
||
|
int err;
|
||
|
|
||
|
/* can't work without an MCA bus ;-) */
|
||
|
|
||
|
if (MCA_bus == 0)
|
||
|
return ERR_PTR(-ENODEV);
|
||
|
|
||
|
dev = alloc_etherdev(sizeof(skmca_priv));
|
||
|
if (!dev)
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
|
||
|
if (unit >= 0) {
|
||
|
sprintf(dev->name, "eth%d", unit);
|
||
|
netdev_boot_setup_check(dev);
|
||
|
}
|
||
|
|
||
|
SET_MODULE_OWNER(dev);
|
||
|
|
||
|
/* start address of 1 --> forced detection */
|
||
|
|
||
|
if (dev->mem_start == 1)
|
||
|
force_detect = 1;
|
||
|
|
||
|
/* search through slots */
|
||
|
|
||
|
base = dev->mem_start;
|
||
|
irq = dev->base_addr;
|
||
|
for (slot = startslot; (slot = dofind(&junior, slot)) != -1; slot++) {
|
||
|
/* deduce card addresses */
|
||
|
|
||
|
getaddrs(slot, junior, &base, &irq, &medium);
|
||
|
|
||
|
/* slot already in use ? */
|
||
|
|
||
|
if (mca_is_adapter_used(slot))
|
||
|
continue;
|
||
|
|
||
|
/* were we looking for something different ? */
|
||
|
|
||
|
if (dev->irq && dev->irq != irq)
|
||
|
continue;
|
||
|
if (dev->mem_start && dev->mem_start != base)
|
||
|
continue;
|
||
|
|
||
|
/* found something that matches */
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* nothing found ? */
|
||
|
|
||
|
if (slot == -1) {
|
||
|
free_netdev(dev);
|
||
|
return (base || irq) ? ERR_PTR(-ENXIO) : ERR_PTR(-ENODEV);
|
||
|
}
|
||
|
|
||
|
/* make procfs entries */
|
||
|
|
||
|
if (junior)
|
||
|
mca_set_adapter_name(slot,
|
||
|
"SKNET junior MC2 Ethernet Adapter");
|
||
|
else
|
||
|
mca_set_adapter_name(slot, "SKNET MC2+ Ethernet Adapter");
|
||
|
mca_set_adapter_procfn(slot, (MCA_ProcFn) skmca_getinfo, dev);
|
||
|
|
||
|
mca_mark_as_used(slot);
|
||
|
|
||
|
/* announce success */
|
||
|
printk("%s: SKNet %s adapter found in slot %d\n", dev->name,
|
||
|
junior ? "Junior MC2" : "MC2+", slot + 1);
|
||
|
|
||
|
priv = netdev_priv(dev);
|
||
|
priv->base = ioremap(base, 0x4000);
|
||
|
if (!priv->base) {
|
||
|
mca_set_adapter_procfn(slot, NULL, NULL);
|
||
|
mca_mark_as_unused(slot);
|
||
|
free_netdev(dev);
|
||
|
return ERR_PTR(-ENOMEM);
|
||
|
}
|
||
|
|
||
|
priv->slot = slot;
|
||
|
priv->macbase = priv->base + 0x3fc0;
|
||
|
priv->ioregaddr = priv->base + 0x3ff0;
|
||
|
priv->ctrladdr = priv->base + 0x3ff2;
|
||
|
priv->cmdaddr = priv->base + 0x3ff3;
|
||
|
priv->medium = medium;
|
||
|
memset(&priv->stat, 0, sizeof(struct net_device_stats));
|
||
|
spin_lock_init(&priv->lock);
|
||
|
|
||
|
/* set base + irq for this device (irq not allocated so far) */
|
||
|
dev->irq = 0;
|
||
|
dev->mem_start = base;
|
||
|
dev->mem_end = base + 0x4000;
|
||
|
|
||
|
/* autoprobe ? */
|
||
|
if (irq < 0) {
|
||
|
int nirq;
|
||
|
|
||
|
printk
|
||
|
("%s: ambigous POS bit combination, must probe for IRQ...\n",
|
||
|
dev->name);
|
||
|
nirq = ProbeIRQ(dev);
|
||
|
if (nirq <= 0)
|
||
|
printk("%s: IRQ probe failed, assuming IRQ %d",
|
||
|
dev->name, priv->realirq = -irq);
|
||
|
else
|
||
|
priv->realirq = nirq;
|
||
|
} else
|
||
|
priv->realirq = irq;
|
||
|
|
||
|
/* set methods */
|
||
|
dev->open = skmca_open;
|
||
|
dev->stop = skmca_close;
|
||
|
dev->hard_start_xmit = skmca_tx;
|
||
|
dev->do_ioctl = NULL;
|
||
|
dev->get_stats = skmca_stats;
|
||
|
dev->set_multicast_list = skmca_set_multicast_list;
|
||
|
dev->flags |= IFF_MULTICAST;
|
||
|
|
||
|
/* copy out MAC address */
|
||
|
for (i = 0; i < 6; i++)
|
||
|
dev->dev_addr[i] = readb(priv->macbase + (i << 1));
|
||
|
|
||
|
/* print config */
|
||
|
printk("%s: IRQ %d, memory %#lx-%#lx, "
|
||
|
"MAC address %02x:%02x:%02x:%02x:%02x:%02x.\n",
|
||
|
dev->name, priv->realirq, dev->mem_start, dev->mem_end - 1,
|
||
|
dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
|
||
|
dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
|
||
|
printk("%s: %s medium\n", dev->name, MediaNames[priv->medium]);
|
||
|
|
||
|
/* reset board */
|
||
|
|
||
|
ResetBoard(dev);
|
||
|
|
||
|
startslot = slot + 1;
|
||
|
|
||
|
err = register_netdev(dev);
|
||
|
if (err) {
|
||
|
cleanup_card(dev);
|
||
|
free_netdev(dev);
|
||
|
dev = ERR_PTR(err);
|
||
|
}
|
||
|
return dev;
|
||
|
}
|
||
|
|
||
|
/* ------------------------------------------------------------------------
|
||
|
* modularization support
|
||
|
* ------------------------------------------------------------------------ */
|
||
|
|
||
|
#ifdef MODULE
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|
||
|
#define DEVMAX 5
|
||
|
|
||
|
static struct net_device *moddevs[DEVMAX];
|
||
|
|
||
|
int init_module(void)
|
||
|
{
|
||
|
int z;
|
||
|
|
||
|
startslot = 0;
|
||
|
for (z = 0; z < DEVMAX; z++) {
|
||
|
struct net_device *dev = skmca_probe(-1);
|
||
|
if (IS_ERR(dev))
|
||
|
break;
|
||
|
moddevs[z] = dev;
|
||
|
}
|
||
|
if (!z)
|
||
|
return -EIO;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void cleanup_module(void)
|
||
|
{
|
||
|
int z;
|
||
|
|
||
|
for (z = 0; z < DEVMAX; z++) {
|
||
|
struct net_device *dev = moddevs[z];
|
||
|
if (dev) {
|
||
|
unregister_netdev(dev);
|
||
|
cleanup_card(dev);
|
||
|
free_netdev(dev);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif /* MODULE */
|