linux/drivers/net/lp486e.c

1350 lines
33 KiB
C
Raw Normal View History

/* Intel Professional Workstation/panther ethernet driver */
/* lp486e.c: A panther 82596 ethernet driver for linux. */
/*
History and copyrights:
Driver skeleton
Written 1993 by Donald Becker.
Copyright 1993 United States Government as represented by the Director,
National Security Agency. This software may only be used and
distributed according to the terms of the GNU General Public License
as modified by SRC, incorporated herein by reference.
The author may be reached as becker@scyld.com, or C/O
Scyld Computing Corporation
410 Severn Ave., Suite 210
Annapolis MD 21403
Apricot
Written 1994 by Mark Evans.
This driver is for the Apricot 82596 bus-master interface
Modularised 12/94 Mark Evans
Professional Workstation
Derived from apricot.c by Ard van Breemen
<ard@murphy.nl>|<ard@cstmel.hobby.nl>|<ard@cstmel.nl.eu.org>
Credits:
Thanks to Murphy Software BV for letting me write this in their time.
Well, actually, I get payed doing this...
(Also: see http://www.murphy.nl for murphy, and my homepage ~ard for
more information on the Professional Workstation)
Present version
aeb@cwi.nl
*/
/*
There are currently two motherboards that I know of in the
professional workstation. The only one that I know is the
intel panther motherboard. -- ard
*/
/*
The pws is equipped with an intel 82596. This is a very intelligent controller
which runs its own micro-code. Communication with the hostprocessor is done
through linked lists of commands and buffers in the hostprocessors memory.
A complete description of the 82596 is available from intel. Search for
a file called "29021806.pdf". It is a complete description of the chip itself.
To use it for the pws some additions are needed regarding generation of
the PORT and CA signal, and the interrupt glue needed for a pc.
I/O map:
PORT SIZE ACTION MEANING
0xCB0 2 WRITE Lower 16 bits for PORT command
0xCB2 2 WRITE Upper 16 bits for PORT command, and issue of PORT command
0xCB4 1 WRITE Generation of CA signal
0xCB8 1 WRITE Clear interrupt glue
All other communication is through memory!
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/delay.h>
#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/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/bitops.h>
#include <asm/io.h>
#include <asm/dma.h>
#define DRV_NAME "lp486e"
/* debug print flags */
#define LOG_SRCDST 0x80000000
#define LOG_STATINT 0x40000000
#define LOG_STARTINT 0x20000000
#define i596_debug debug
static int i596_debug = 0;
static const char * const medianame[] = {
"10baseT", "AUI",
"10baseT-FD", "AUI-FD",
};
#define LP486E_TOTAL_SIZE 16
#define I596_NULL (0xffffffff)
#define CMD_EOL 0x8000 /* The last command of the list, stop. */
#define CMD_SUSP 0x4000 /* Suspend after doing cmd. */
#define CMD_INTR 0x2000 /* Interrupt after doing cmd. */
#define CMD_FLEX 0x0008 /* Enable flexible memory model */
enum commands {
CmdNOP = 0,
CmdIASetup = 1,
CmdConfigure = 2,
CmdMulticastList = 3,
CmdTx = 4,
CmdTDR = 5,
CmdDump = 6,
CmdDiagnose = 7
};
#if 0
static const char *CUcmdnames[8] = { "NOP", "IASetup", "Configure", "MulticastList",
"Tx", "TDR", "Dump", "Diagnose" };
#endif
/* Status word bits */
#define STAT_CX 0x8000 /* The CU finished executing a command
with the Interrupt bit set */
#define STAT_FR 0x4000 /* The RU finished receiving a frame */
#define STAT_CNA 0x2000 /* The CU left the active state */
#define STAT_RNR 0x1000 /* The RU left the active state */
#define STAT_ACK (STAT_CX | STAT_FR | STAT_CNA | STAT_RNR)
#define STAT_CUS 0x0700 /* Status of CU: 0: idle, 1: suspended,
2: active, 3-7: unused */
#define STAT_RUS 0x00f0 /* Status of RU: 0: idle, 1: suspended,
2: no resources, 4: ready,
10: no resources due to no more RBDs,
12: no more RBDs, other: unused */
#define STAT_T 0x0008 /* Bus throttle timers loaded */
#define STAT_ZERO 0x0807 /* Always zero */
#if 0
static char *CUstates[8] = {
"idle", "suspended", "active", 0, 0, 0, 0, 0
};
static char *RUstates[16] = {
"idle", "suspended", "no resources", 0, "ready", 0, 0, 0,
0, 0, "no RBDs", 0, "out of RBDs", 0, 0, 0
};
static void
i596_out_status(int status) {
int bad = 0;
char *s;
printk("status %4.4x:", status);
if (status == 0xffff)
printk(" strange..\n");
else {
if (status & STAT_CX)
printk(" CU done");
if (status & STAT_CNA)
printk(" CU stopped");
if (status & STAT_FR)
printk(" got a frame");
if (status & STAT_RNR)
printk(" RU stopped");
if (status & STAT_T)
printk(" throttled");
if (status & STAT_ZERO)
bad = 1;
s = CUstates[(status & STAT_CUS) >> 8];
if (!s)
bad = 1;
else
printk(" CU(%s)", s);
s = RUstates[(status & STAT_RUS) >> 4];
if (!s)
bad = 1;
else
printk(" RU(%s)", s);
if (bad)
printk(" bad status");
printk("\n");
}
}
#endif
/* Command word bits */
#define ACK_CX 0x8000
#define ACK_FR 0x4000
#define ACK_CNA 0x2000
#define ACK_RNR 0x1000
#define CUC_START 0x0100
#define CUC_RESUME 0x0200
#define CUC_SUSPEND 0x0300
#define CUC_ABORT 0x0400
#define RX_START 0x0010
#define RX_RESUME 0x0020
#define RX_SUSPEND 0x0030
#define RX_ABORT 0x0040
typedef u32 phys_addr;
static inline phys_addr
va_to_pa(void *x) {
return x ? virt_to_bus(x) : I596_NULL;
}
static inline void *
pa_to_va(phys_addr x) {
return (x == I596_NULL) ? NULL : bus_to_virt(x);
}
/* status bits for cmd */
#define CMD_STAT_C 0x8000 /* CU command complete */
#define CMD_STAT_B 0x4000 /* CU command in progress */
#define CMD_STAT_OK 0x2000 /* CU command completed without errors */
#define CMD_STAT_A 0x1000 /* CU command abnormally terminated */
struct i596_cmd { /* 8 bytes */
unsigned short status;
unsigned short command;
phys_addr pa_next; /* va_to_pa(struct i596_cmd *next) */
};
#define EOF 0x8000
#define SIZE_MASK 0x3fff
struct i596_tbd {
unsigned short size;
unsigned short pad;
phys_addr pa_next; /* va_to_pa(struct i596_tbd *next) */
phys_addr pa_data; /* va_to_pa(char *data) */
struct sk_buff *skb;
};
struct tx_cmd {
struct i596_cmd cmd;
phys_addr pa_tbd; /* va_to_pa(struct i596_tbd *tbd) */
unsigned short size;
unsigned short pad;
};
/* status bits for rfd */
#define RFD_STAT_C 0x8000 /* Frame reception complete */
#define RFD_STAT_B 0x4000 /* Frame reception in progress */
#define RFD_STAT_OK 0x2000 /* Frame received without errors */
#define RFD_STATUS 0x1fff
#define RFD_LENGTH_ERR 0x1000
#define RFD_CRC_ERR 0x0800
#define RFD_ALIGN_ERR 0x0400
#define RFD_NOBUFS_ERR 0x0200
#define RFD_DMA_ERR 0x0100 /* DMA overrun failure to acquire system bus */
#define RFD_SHORT_FRAME_ERR 0x0080
#define RFD_NOEOP_ERR 0x0040
#define RFD_TRUNC_ERR 0x0020
#define RFD_MULTICAST 0x0002 /* 0: destination had our address
1: destination was broadcast/multicast */
#define RFD_COLLISION 0x0001
/* receive frame descriptor */
struct i596_rfd {
unsigned short stat;
unsigned short cmd;
phys_addr pa_next; /* va_to_pa(struct i596_rfd *next) */
phys_addr pa_rbd; /* va_to_pa(struct i596_rbd *rbd) */
unsigned short count;
unsigned short size;
char data[1532];
};
#define RBD_EL 0x8000
#define RBD_P 0x4000
#define RBD_SIZEMASK 0x3fff
#define RBD_EOF 0x8000
#define RBD_F 0x4000
/* receive buffer descriptor */
struct i596_rbd {
unsigned short size;
unsigned short pad;
phys_addr pa_next; /* va_to_pa(struct i596_tbd *next) */
phys_addr pa_data; /* va_to_pa(char *data) */
phys_addr pa_prev; /* va_to_pa(struct i596_tbd *prev) */
/* Driver private part */
struct sk_buff *skb;
};
#define RX_RING_SIZE 64
#define RX_SKBSIZE (ETH_FRAME_LEN+10)
#define RX_RBD_SIZE 32
/* System Control Block - 40 bytes */
struct i596_scb {
u16 status; /* 0 */
u16 command; /* 2 */
phys_addr pa_cmd; /* 4 - va_to_pa(struct i596_cmd *cmd) */
phys_addr pa_rfd; /* 8 - va_to_pa(struct i596_rfd *rfd) */
u32 crc_err; /* 12 */
u32 align_err; /* 16 */
u32 resource_err; /* 20 */
u32 over_err; /* 24 */
u32 rcvdt_err; /* 28 */
u32 short_err; /* 32 */
u16 t_on; /* 36 */
u16 t_off; /* 38 */
};
/* Intermediate System Configuration Pointer - 8 bytes */
struct i596_iscp {
u32 busy; /* 0 */
phys_addr pa_scb; /* 4 - va_to_pa(struct i596_scb *scb) */
};
/* System Configuration Pointer - 12 bytes */
struct i596_scp {
u32 sysbus; /* 0 */
u32 pad; /* 4 */
phys_addr pa_iscp; /* 8 - va_to_pa(struct i596_iscp *iscp) */
};
/* Selftest and dump results - needs 16-byte alignment */
/*
* The size of the dump area is 304 bytes. When the dump is executed
* by the Port command an extra word will be appended to the dump area.
* The extra word is a copy of the Dump status word (containing the
* C, B, OK bits). [I find 0xa006, with a0 for C+OK and 6 for dump]
*/
struct i596_dump {
u16 dump[153]; /* (304 = 130h) + 2 bytes */
};
struct i596_private { /* aligned to a 16-byte boundary */
struct i596_scp scp; /* 0 - needs 16-byte alignment */
struct i596_iscp iscp; /* 12 */
struct i596_scb scb; /* 20 */
u32 dummy; /* 60 */
struct i596_dump dump; /* 64 - needs 16-byte alignment */
struct i596_cmd set_add;
char eth_addr[8]; /* directly follows set_add */
struct i596_cmd set_conf;
char i596_config[16]; /* directly follows set_conf */
struct i596_cmd tdr;
unsigned long tdr_stat; /* directly follows tdr */
int last_restart;
struct i596_rbd *rbd_list;
struct i596_rbd *rbd_tail;
struct i596_rfd *rx_tail;
struct i596_cmd *cmd_tail;
struct i596_cmd *cmd_head;
int cmd_backlog;
unsigned long last_cmd;
struct net_device_stats stats;
spinlock_t cmd_lock;
};
static char init_setup[14] = {
0x8E, /* length 14 bytes, prefetch on */
0xC8, /* default: fifo to 8, monitor off */
0x40, /* default: don't save bad frames (apricot.c had 0x80) */
0x2E, /* (default is 0x26)
No source address insertion, 8 byte preamble */
0x00, /* default priority and backoff */
0x60, /* default interframe spacing */
0x00, /* default slot time LSB */
0xf2, /* default slot time and nr of retries */
0x00, /* default various bits
(0: promiscuous mode, 1: broadcast disable,
2: encoding mode, 3: transmit on no CRS,
4: no CRC insertion, 5: CRC type,
6: bit stuffing, 7: padding) */
0x00, /* default carrier sense and collision detect */
0x40, /* default minimum frame length */
0xff, /* (default is 0xff, and that is what apricot.c has;
elp486.c has 0xfb: Enable crc append in memory.) */
0x00, /* default: not full duplex */
0x7f /* (default is 0x3f) multi IA */
};
static int i596_open(struct net_device *dev);
static int i596_start_xmit(struct sk_buff *skb, struct net_device *dev);
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
static irqreturn_t i596_interrupt(int irq, void *dev_id);
static int i596_close(struct net_device *dev);
static struct net_device_stats *i596_get_stats(struct net_device *dev);
static void i596_add_cmd(struct net_device *dev, struct i596_cmd *cmd);
static void print_eth(char *);
static void set_multicast_list(struct net_device *dev);
static void i596_tx_timeout(struct net_device *dev);
static int
i596_timeout(struct net_device *dev, char *msg, int ct) {
struct i596_private *lp;
int boguscnt = ct;
lp = (struct i596_private *) dev->priv;
while (lp->scb.command) {
if (--boguscnt == 0) {
printk("%s: %s timed out - stat %4.4x, cmd %4.4x\n",
dev->name, msg,
lp->scb.status, lp->scb.command);
return 1;
}
udelay(5);
barrier();
}
return 0;
}
static inline int
init_rx_bufs(struct net_device *dev, int num) {
struct i596_private *lp;
struct i596_rfd *rfd;
int i;
// struct i596_rbd *rbd;
lp = (struct i596_private *) dev->priv;
lp->scb.pa_rfd = I596_NULL;
for (i = 0; i < num; i++) {
rfd = kmalloc(sizeof(struct i596_rfd), GFP_KERNEL);
if (rfd == NULL)
break;
rfd->stat = 0;
rfd->pa_rbd = I596_NULL;
rfd->count = 0;
rfd->size = 1532;
if (i == 0) {
rfd->cmd = CMD_EOL;
lp->rx_tail = rfd;
} else {
rfd->cmd = 0;
}
rfd->pa_next = lp->scb.pa_rfd;
lp->scb.pa_rfd = va_to_pa(rfd);
lp->rx_tail->pa_next = lp->scb.pa_rfd;
}
#if 0
for (i = 0; i<RX_RBD_SIZE; i++) {
rbd = kmalloc(sizeof(struct i596_rbd), GFP_KERNEL);
if (rbd) {
rbd->pad = 0;
rbd->count = 0;
rbd->skb = dev_alloc_skb(RX_SKBSIZE);
if (!rbd->skb) {
printk("dev_alloc_skb failed");
}
rbd->next = rfd->rbd;
if (i) {
rfd->rbd->prev = rbd;
rbd->size = RX_SKBSIZE;
} else {
rbd->size = (RX_SKBSIZE | RBD_EL);
lp->rbd_tail = rbd;
}
rfd->rbd = rbd;
} else {
printk("Could not kmalloc rbd\n");
}
}
lp->rbd_tail->next = rfd->rbd;
#endif
return (i);
}
static inline void
remove_rx_bufs(struct net_device *dev) {
struct i596_private *lp;
struct i596_rfd *rfd;
lp = (struct i596_private *) dev->priv;
lp->rx_tail->pa_next = I596_NULL;
do {
rfd = pa_to_va(lp->scb.pa_rfd);
lp->scb.pa_rfd = rfd->pa_next;
kfree(rfd);
} while (rfd != lp->rx_tail);
lp->rx_tail = NULL;
#if 0
for (lp->rbd_list) {
}
#endif
}
#define PORT_RESET 0x00 /* reset 82596 */
#define PORT_SELFTEST 0x01 /* selftest */
#define PORT_ALTSCP 0x02 /* alternate SCB address */
#define PORT_DUMP 0x03 /* dump */
#define IOADDR 0xcb0 /* real constant */
#define IRQ 10 /* default IRQ - can be changed by ECU */
/* The 82596 requires two 16-bit write cycles for a port command */
static inline void
PORT(phys_addr a, unsigned int cmd) {
if (a & 0xf)
printk("lp486e.c: PORT: address not aligned\n");
outw(((a & 0xffff) | cmd), IOADDR);
outw(((a>>16) & 0xffff), IOADDR+2);
}
static inline void
CA(void) {
outb(0, IOADDR+4);
udelay(8);
}
static inline void
CLEAR_INT(void) {
outb(0, IOADDR+8);
}
#define SIZE(x) (sizeof(x)/sizeof((x)[0]))
#if 0
/* selftest or dump */
static void
i596_port_do(struct net_device *dev, int portcmd, char *cmdname) {
struct i596_private *lp = dev->priv;
u16 *outp;
int i, m;
memset((void *)&(lp->dump), 0, sizeof(struct i596_dump));
outp = &(lp->dump.dump[0]);
PORT(va_to_pa(outp), portcmd);
mdelay(30); /* random, unmotivated */
printk("lp486e i82596 %s result:\n", cmdname);
for (m = SIZE(lp->dump.dump); m && lp->dump.dump[m-1] == 0; m--)
;
for (i = 0; i < m; i++) {
printk(" %04x", lp->dump.dump[i]);
if (i%8 == 7)
printk("\n");
}
printk("\n");
}
#endif
static int
i596_scp_setup(struct net_device *dev) {
struct i596_private *lp = dev->priv;
int boguscnt;
/* Setup SCP, ISCP, SCB */
/*
* sysbus bits:
* only a single byte is significant - here 0x44
* 0x80: big endian mode (details depend on stepping)
* 0x40: 1
* 0x20: interrupt pin is active low
* 0x10: lock function disabled
* 0x08: external triggering of bus throttle timers
* 0x06: 00: 82586 compat mode, 01: segmented mode, 10: linear mode
* 0x01: unused
*/
lp->scp.sysbus = 0x00440000; /* linear mode */
lp->scp.pad = 0; /* must be zero */
lp->scp.pa_iscp = va_to_pa(&(lp->iscp));
/*
* The CPU sets the ISCP to 1 before it gives the first CA()
*/
lp->iscp.busy = 0x0001;
lp->iscp.pa_scb = va_to_pa(&(lp->scb));
lp->scb.command = 0;
lp->scb.status = 0;
lp->scb.pa_cmd = I596_NULL;
/* lp->scb.pa_rfd has been initialised already */
lp->last_cmd = jiffies;
lp->cmd_backlog = 0;
lp->cmd_head = NULL;
/*
* Reset the 82596.
* We need to wait 10 systemclock cycles, and
* 5 serial clock cycles.
*/
PORT(0, PORT_RESET); /* address part ignored */
udelay(100);
/*
* Before the CA signal is asserted, the default SCP address
* (0x00fffff4) can be changed to a 16-byte aligned value
*/
PORT(va_to_pa(&lp->scp), PORT_ALTSCP); /* change the scp address */
/*
* The initialization procedure begins when a
* Channel Attention signal is asserted after a reset.
*/
CA();
/*
* The ISCP busy is cleared by the 82596 after the SCB address is read.
*/
boguscnt = 100;
while (lp->iscp.busy) {
if (--boguscnt == 0) {
/* No i82596 present? */
printk("%s: i82596 initialization timed out\n",
dev->name);
return 1;
}
udelay(5);
barrier();
}
/* I find here boguscnt==100, so no delay was required. */
return 0;
}
static int
init_i596(struct net_device *dev) {
struct i596_private *lp;
if (i596_scp_setup(dev))
return 1;
lp = (struct i596_private *) dev->priv;
lp->scb.command = 0;
memcpy ((void *)lp->i596_config, init_setup, 14);
lp->set_conf.command = CmdConfigure;
i596_add_cmd(dev, (void *)&lp->set_conf);
memcpy ((void *)lp->eth_addr, dev->dev_addr, 6);
lp->set_add.command = CmdIASetup;
i596_add_cmd(dev, (struct i596_cmd *)&lp->set_add);
lp->tdr.command = CmdTDR;
i596_add_cmd(dev, (struct i596_cmd *)&lp->tdr);
if (lp->scb.command && i596_timeout(dev, "i82596 init", 200))
return 1;
lp->scb.command = RX_START;
CA();
barrier();
if (lp->scb.command && i596_timeout(dev, "Receive Unit start", 100))
return 1;
return 0;
}
/* Receive a single frame */
static inline int
i596_rx_one(struct net_device *dev, struct i596_private *lp,
struct i596_rfd *rfd, int *frames) {
if (rfd->stat & RFD_STAT_OK) {
/* a good frame */
int pkt_len = (rfd->count & 0x3fff);
struct sk_buff *skb = dev_alloc_skb(pkt_len);
(*frames)++;
if (rfd->cmd & CMD_EOL)
printk("Received on EOL\n");
if (skb == NULL) {
printk ("%s: i596_rx Memory squeeze, "
"dropping packet.\n", dev->name);
lp->stats.rx_dropped++;
return 1;
}
skb->dev = dev;
memcpy(skb_put(skb,pkt_len), rfd->data, pkt_len);
skb->protocol = eth_type_trans(skb,dev);
netif_rx(skb);
dev->last_rx = jiffies;
lp->stats.rx_packets++;
} else {
#if 0
printk("Frame reception error status %04x\n",
rfd->stat);
#endif
lp->stats.rx_errors++;
if (rfd->stat & RFD_COLLISION)
lp->stats.collisions++;
if (rfd->stat & RFD_SHORT_FRAME_ERR)
lp->stats.rx_length_errors++;
if (rfd->stat & RFD_DMA_ERR)
lp->stats.rx_over_errors++;
if (rfd->stat & RFD_NOBUFS_ERR)
lp->stats.rx_fifo_errors++;
if (rfd->stat & RFD_ALIGN_ERR)
lp->stats.rx_frame_errors++;
if (rfd->stat & RFD_CRC_ERR)
lp->stats.rx_crc_errors++;
if (rfd->stat & RFD_LENGTH_ERR)
lp->stats.rx_length_errors++;
}
rfd->stat = rfd->count = 0;
return 0;
}
static int
i596_rx(struct net_device *dev) {
struct i596_private *lp = (struct i596_private *) dev->priv;
struct i596_rfd *rfd;
int frames = 0;
while (1) {
rfd = pa_to_va(lp->scb.pa_rfd);
if (!rfd) {
printk(KERN_ERR "i596_rx: NULL rfd?\n");
return 0;
}
#if 1
if (rfd->stat && !(rfd->stat & (RFD_STAT_C | RFD_STAT_B)))
printk("SF:%p-%04x\n", rfd, rfd->stat);
#endif
if (!(rfd->stat & RFD_STAT_C))
break; /* next one not ready */
if (i596_rx_one(dev, lp, rfd, &frames))
break; /* out of memory */
rfd->cmd = CMD_EOL;
lp->rx_tail->cmd = 0;
lp->rx_tail = rfd;
lp->scb.pa_rfd = rfd->pa_next;
barrier();
}
return frames;
}
static void
i596_cleanup_cmd(struct net_device *dev) {
struct i596_private *lp;
struct i596_cmd *cmd;
lp = (struct i596_private *) dev->priv;
while (lp->cmd_head) {
cmd = (struct i596_cmd *)lp->cmd_head;
lp->cmd_head = pa_to_va(lp->cmd_head->pa_next);
lp->cmd_backlog--;
switch ((cmd->command) & 0x7) {
case CmdTx: {
struct tx_cmd *tx_cmd = (struct tx_cmd *) cmd;
struct i596_tbd * tx_cmd_tbd;
tx_cmd_tbd = pa_to_va(tx_cmd->pa_tbd);
dev_kfree_skb_any(tx_cmd_tbd->skb);
lp->stats.tx_errors++;
lp->stats.tx_aborted_errors++;
cmd->pa_next = I596_NULL;
kfree((unsigned char *)tx_cmd);
netif_wake_queue(dev);
break;
}
case CmdMulticastList: {
// unsigned short count = *((unsigned short *) (ptr + 1));
cmd->pa_next = I596_NULL;
kfree((unsigned char *)cmd);
break;
}
default: {
cmd->pa_next = I596_NULL;
break;
}
}
barrier();
}
if (lp->scb.command && i596_timeout(dev, "i596_cleanup_cmd", 100))
;
lp->scb.pa_cmd = va_to_pa(lp->cmd_head);
}
static void i596_reset(struct net_device *dev, struct i596_private *lp, int ioaddr) {
if (lp->scb.command && i596_timeout(dev, "i596_reset", 100))
;
netif_stop_queue(dev);
lp->scb.command = CUC_ABORT | RX_ABORT;
CA();
barrier();
/* wait for shutdown */
if (lp->scb.command && i596_timeout(dev, "i596_reset(2)", 400))
;
i596_cleanup_cmd(dev);
i596_rx(dev);
netif_start_queue(dev);
/*dev_kfree_skb(skb, FREE_WRITE);*/
init_i596(dev);
}
static void i596_add_cmd(struct net_device *dev, struct i596_cmd *cmd) {
struct i596_private *lp = dev->priv;
int ioaddr = dev->base_addr;
unsigned long flags;
cmd->status = 0;
cmd->command |= (CMD_EOL | CMD_INTR);
cmd->pa_next = I596_NULL;
spin_lock_irqsave(&lp->cmd_lock, flags);
if (lp->cmd_head) {
lp->cmd_tail->pa_next = va_to_pa(cmd);
} else {
lp->cmd_head = cmd;
if (lp->scb.command && i596_timeout(dev, "i596_add_cmd", 100))
;
lp->scb.pa_cmd = va_to_pa(cmd);
lp->scb.command = CUC_START;
CA();
}
lp->cmd_tail = cmd;
lp->cmd_backlog++;
lp->cmd_head = pa_to_va(lp->scb.pa_cmd);
spin_unlock_irqrestore(&lp->cmd_lock, flags);
if (lp->cmd_backlog > 16) {
int tickssofar = jiffies - lp->last_cmd;
if (tickssofar < HZ/4)
return;
printk(KERN_WARNING "%s: command unit timed out, status resetting.\n", dev->name);
i596_reset(dev, lp, ioaddr);
}
}
static int i596_open(struct net_device *dev)
{
int i;
i = request_irq(dev->irq, &i596_interrupt, IRQF_SHARED, dev->name, dev);
if (i) {
printk(KERN_ERR "%s: IRQ %d not free\n", dev->name, dev->irq);
return i;
}
if ((i = init_rx_bufs(dev, RX_RING_SIZE)) < RX_RING_SIZE)
printk(KERN_ERR "%s: only able to allocate %d receive buffers\n", dev->name, i);
if (i < 4) {
free_irq(dev->irq, dev);
return -EAGAIN;
}
netif_start_queue(dev);
init_i596(dev);
return 0; /* Always succeed */
}
static int i596_start_xmit (struct sk_buff *skb, struct net_device *dev) {
struct i596_private *lp = dev->priv;
struct tx_cmd *tx_cmd;
short length;
length = skb->len;
if (length < ETH_ZLEN) {
if (skb_padto(skb, ETH_ZLEN))
return 0;
length = ETH_ZLEN;
}
dev->trans_start = jiffies;
[PATCH] getting rid of all casts of k[cmz]alloc() calls Run this: #!/bin/sh for f in $(grep -Erl "\([^\)]*\) *k[cmz]alloc" *) ; do echo "De-casting $f..." perl -pi -e "s/ ?= ?\([^\)]*\) *(k[cmz]alloc) *\(/ = \1\(/" $f done And then go through and reinstate those cases where code is casting pointers to non-pointers. And then drop a few hunks which conflicted with outstanding work. Cc: Russell King <rmk@arm.linux.org.uk>, Ian Molton <spyro@f2s.com> Cc: Mikael Starvik <starvik@axis.com> Cc: Yoshinori Sato <ysato@users.sourceforge.jp> Cc: Roman Zippel <zippel@linux-m68k.org> Cc: Geert Uytterhoeven <geert@linux-m68k.org> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Kyle McMartin <kyle@mcmartin.ca> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jeff Dike <jdike@addtoit.com> Cc: Greg KH <greg@kroah.com> Cc: Jens Axboe <jens.axboe@oracle.com> Cc: Paul Fulghum <paulkf@microgate.com> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: Karsten Keil <kkeil@suse.de> Cc: Mauro Carvalho Chehab <mchehab@infradead.org> Cc: Jeff Garzik <jeff@garzik.org> Cc: James Bottomley <James.Bottomley@steeleye.com> Cc: Ian Kent <raven@themaw.net> Cc: Steven French <sfrench@us.ibm.com> Cc: David Woodhouse <dwmw2@infradead.org> Cc: Neil Brown <neilb@cse.unsw.edu.au> Cc: Jaroslav Kysela <perex@suse.cz> Cc: Takashi Iwai <tiwai@suse.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-12-13 08:35:56 +00:00
tx_cmd = kmalloc((sizeof (struct tx_cmd) + sizeof (struct i596_tbd)), GFP_ATOMIC);
if (tx_cmd == NULL) {
printk(KERN_WARNING "%s: i596_xmit Memory squeeze, dropping packet.\n", dev->name);
lp->stats.tx_dropped++;
dev_kfree_skb (skb);
} else {
struct i596_tbd *tx_cmd_tbd;
tx_cmd_tbd = (struct i596_tbd *) (tx_cmd + 1);
tx_cmd->pa_tbd = va_to_pa (tx_cmd_tbd);
tx_cmd_tbd->pa_next = I596_NULL;
tx_cmd->cmd.command = (CMD_FLEX | CmdTx);
tx_cmd->pad = 0;
tx_cmd->size = 0;
tx_cmd_tbd->pad = 0;
tx_cmd_tbd->size = (EOF | length);
tx_cmd_tbd->pa_data = va_to_pa (skb->data);
tx_cmd_tbd->skb = skb;
if (i596_debug & LOG_SRCDST)
print_eth (skb->data);
i596_add_cmd (dev, (struct i596_cmd *) tx_cmd);
lp->stats.tx_packets++;
}
return 0;
}
static void
i596_tx_timeout (struct net_device *dev) {
struct i596_private *lp = dev->priv;
int ioaddr = dev->base_addr;
/* Transmitter timeout, serious problems. */
printk(KERN_WARNING "%s: transmit timed out, status resetting.\n", dev->name);
lp->stats.tx_errors++;
/* Try to restart the adaptor */
if (lp->last_restart == lp->stats.tx_packets) {
printk ("Resetting board.\n");
/* Shutdown and restart */
i596_reset (dev, lp, ioaddr);
} else {
/* Issue a channel attention signal */
printk ("Kicking board.\n");
lp->scb.command = (CUC_START | RX_START);
CA();
lp->last_restart = lp->stats.tx_packets;
}
netif_wake_queue(dev);
}
static void print_eth(char *add)
{
int i;
printk ("Dest ");
for (i = 0; i < 6; i++)
printk(" %2.2X", (unsigned char) add[i]);
printk ("\n");
printk ("Source");
for (i = 0; i < 6; i++)
printk(" %2.2X", (unsigned char) add[i+6]);
printk ("\n");
printk ("type %2.2X%2.2X\n",
(unsigned char) add[12], (unsigned char) add[13]);
}
static int __init lp486e_probe(struct net_device *dev) {
struct i596_private *lp;
unsigned char eth_addr[6] = { 0, 0xaa, 0, 0, 0, 0 };
unsigned char *bios;
int i, j;
int ret = -ENOMEM;
static int probed;
if (probed)
return -ENODEV;
probed++;
if (!request_region(IOADDR, LP486E_TOTAL_SIZE, DRV_NAME)) {
printk(KERN_ERR "lp486e: IO address 0x%x in use\n", IOADDR);
return -EBUSY;
}
lp = (struct i596_private *) dev->priv;
spin_lock_init(&lp->cmd_lock);
/*
* Do we really have this thing?
*/
if (i596_scp_setup(dev)) {
ret = -ENODEV;
goto err_out_kfree;
}
dev->base_addr = IOADDR;
dev->irq = IRQ;
/*
* How do we find the ethernet address? I don't know.
* One possibility is to look at the EISA configuration area
* [0xe8000-0xe9fff]. This contains the ethernet address
* but not at a fixed address - things depend on setup options.
*
* If we find no address, or the wrong address, use
* ifconfig eth0 hw ether a1:a2:a3:a4:a5:a6
* with the value found in the BIOS setup.
*/
bios = bus_to_virt(0xe8000);
for (j = 0; j < 0x2000; j++) {
if (bios[j] == 0 && bios[j+1] == 0xaa && bios[j+2] == 0) {
printk("%s: maybe address at BIOS 0x%x:",
dev->name, 0xe8000+j);
for (i = 0; i < 6; i++) {
eth_addr[i] = bios[i+j];
printk(" %2.2X", eth_addr[i]);
}
printk("\n");
}
}
printk("%s: lp486e 82596 at %#3lx, IRQ %d,",
dev->name, dev->base_addr, dev->irq);
for (i = 0; i < 6; i++)
printk(" %2.2X", dev->dev_addr[i] = eth_addr[i]);
printk("\n");
/* The LP486E-specific entries in the device structure. */
dev->open = &i596_open;
dev->stop = &i596_close;
dev->hard_start_xmit = &i596_start_xmit;
dev->get_stats = &i596_get_stats;
dev->set_multicast_list = &set_multicast_list;
dev->watchdog_timeo = 5*HZ;
dev->tx_timeout = i596_tx_timeout;
#if 0
/* selftest reports 0x320925ae - don't know what that means */
i596_port_do(dev, PORT_SELFTEST, "selftest");
i596_port_do(dev, PORT_DUMP, "dump");
#endif
return 0;
err_out_kfree:
release_region(IOADDR, LP486E_TOTAL_SIZE);
return ret;
}
static inline void
i596_handle_CU_completion(struct net_device *dev,
struct i596_private *lp,
unsigned short status,
unsigned short *ack_cmdp) {
struct i596_cmd *cmd;
int frames_out = 0;
int commands_done = 0;
int cmd_val;
unsigned long flags;
spin_lock_irqsave(&lp->cmd_lock, flags);
cmd = lp->cmd_head;
while (lp->cmd_head && (lp->cmd_head->status & CMD_STAT_C)) {
cmd = lp->cmd_head;
lp->cmd_head = pa_to_va(lp->cmd_head->pa_next);
lp->cmd_backlog--;
commands_done++;
cmd_val = cmd->command & 0x7;
#if 0
printk("finished CU %s command (%d)\n",
CUcmdnames[cmd_val], cmd_val);
#endif
switch (cmd_val) {
case CmdTx:
{
struct tx_cmd *tx_cmd;
struct i596_tbd *tx_cmd_tbd;
tx_cmd = (struct tx_cmd *) cmd;
tx_cmd_tbd = pa_to_va(tx_cmd->pa_tbd);
frames_out++;
if (cmd->status & CMD_STAT_OK) {
if (i596_debug)
print_eth(pa_to_va(tx_cmd_tbd->pa_data));
} else {
lp->stats.tx_errors++;
if (i596_debug)
printk("transmission failure:%04x\n",
cmd->status);
if (cmd->status & 0x0020)
lp->stats.collisions++;
if (!(cmd->status & 0x0040))
lp->stats.tx_heartbeat_errors++;
if (cmd->status & 0x0400)
lp->stats.tx_carrier_errors++;
if (cmd->status & 0x0800)
lp->stats.collisions++;
if (cmd->status & 0x1000)
lp->stats.tx_aborted_errors++;
}
dev_kfree_skb_irq(tx_cmd_tbd->skb);
cmd->pa_next = I596_NULL;
kfree((unsigned char *)tx_cmd);
netif_wake_queue(dev);
break;
}
case CmdMulticastList:
cmd->pa_next = I596_NULL;
kfree((unsigned char *)cmd);
break;
case CmdTDR:
{
unsigned long status = *((unsigned long *) (cmd + 1));
if (status & 0x8000) {
if (i596_debug)
printk("%s: link ok.\n", dev->name);
} else {
if (status & 0x4000)
printk("%s: Transceiver problem.\n",
dev->name);
if (status & 0x2000)
printk("%s: Termination problem.\n",
dev->name);
if (status & 0x1000)
printk("%s: Short circuit.\n",
dev->name);
printk("%s: Time %ld.\n",
dev->name, status & 0x07ff);
}
}
default:
cmd->pa_next = I596_NULL;
lp->last_cmd = jiffies;
}
barrier();
}
cmd = lp->cmd_head;
while (cmd && (cmd != lp->cmd_tail)) {
cmd->command &= 0x1fff;
cmd = pa_to_va(cmd->pa_next);
barrier();
}
if (lp->cmd_head)
*ack_cmdp |= CUC_START;
lp->scb.pa_cmd = va_to_pa(lp->cmd_head);
spin_unlock_irqrestore(&lp->cmd_lock, flags);
}
static irqreturn_t
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
i596_interrupt (int irq, void *dev_instance) {
struct net_device *dev = (struct net_device *) dev_instance;
struct i596_private *lp;
unsigned short status, ack_cmd = 0;
int frames_in = 0;
lp = (struct i596_private *) dev->priv;
/*
* The 82596 examines the command, performs the required action,
* and then clears the SCB command word.
*/
if (lp->scb.command && i596_timeout(dev, "interrupt", 40))
;
/*
* The status word indicates the status of the 82596.
* It is modified only by the 82596.
*
* [So, we must not clear it. I find often status 0xffff,
* which is not one of the values allowed by the docs.]
*/
status = lp->scb.status;
#if 0
if (i596_debug) {
printk("%s: i596 interrupt, ", dev->name);
i596_out_status(status);
}
#endif
/* Impossible, but it happens - perhaps when we get
a receive interrupt but scb.pa_rfd is I596_NULL. */
if (status == 0xffff) {
printk("%s: i596_interrupt: got status 0xffff\n", dev->name);
goto out;
}
ack_cmd = (status & STAT_ACK);
if (status & (STAT_CX | STAT_CNA))
i596_handle_CU_completion(dev, lp, status, &ack_cmd);
if (status & (STAT_FR | STAT_RNR)) {
/* Restart the receive unit when it got inactive somehow */
if ((status & STAT_RNR) && netif_running(dev))
ack_cmd |= RX_START;
if (status & STAT_FR) {
frames_in = i596_rx(dev);
if (!frames_in)
printk("receive frame reported, but no frames\n");
}
}
/* acknowledge the interrupt */
/*
if ((lp->scb.pa_cmd != I596_NULL) && netif_running(dev))
ack_cmd |= CUC_START;
*/
if (lp->scb.command && i596_timeout(dev, "i596 interrupt", 100))
;
lp->scb.command = ack_cmd;
CLEAR_INT();
CA();
out:
return IRQ_HANDLED;
}
static int i596_close(struct net_device *dev) {
struct i596_private *lp = dev->priv;
netif_stop_queue(dev);
if (i596_debug)
printk("%s: Shutting down ethercard, status was %4.4x.\n",
dev->name, lp->scb.status);
lp->scb.command = (CUC_ABORT | RX_ABORT);
CA();
i596_cleanup_cmd(dev);
if (lp->scb.command && i596_timeout(dev, "i596_close", 200))
;
free_irq(dev->irq, dev);
remove_rx_bufs(dev);
return 0;
}
static struct net_device_stats * i596_get_stats(struct net_device *dev) {
struct i596_private *lp = dev->priv;
return &lp->stats;
}
/*
* Set or clear the multicast filter for this adaptor.
*/
static void set_multicast_list(struct net_device *dev) {
struct i596_private *lp = dev->priv;
struct i596_cmd *cmd;
if (i596_debug > 1)
printk ("%s: set multicast list %d\n",
dev->name, dev->mc_count);
if (dev->mc_count > 0) {
struct dev_mc_list *dmi;
char *cp;
[PATCH] getting rid of all casts of k[cmz]alloc() calls Run this: #!/bin/sh for f in $(grep -Erl "\([^\)]*\) *k[cmz]alloc" *) ; do echo "De-casting $f..." perl -pi -e "s/ ?= ?\([^\)]*\) *(k[cmz]alloc) *\(/ = \1\(/" $f done And then go through and reinstate those cases where code is casting pointers to non-pointers. And then drop a few hunks which conflicted with outstanding work. Cc: Russell King <rmk@arm.linux.org.uk>, Ian Molton <spyro@f2s.com> Cc: Mikael Starvik <starvik@axis.com> Cc: Yoshinori Sato <ysato@users.sourceforge.jp> Cc: Roman Zippel <zippel@linux-m68k.org> Cc: Geert Uytterhoeven <geert@linux-m68k.org> Cc: Ralf Baechle <ralf@linux-mips.org> Cc: Paul Mackerras <paulus@samba.org> Cc: Kyle McMartin <kyle@mcmartin.ca> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org> Cc: Martin Schwidefsky <schwidefsky@de.ibm.com> Cc: "David S. Miller" <davem@davemloft.net> Cc: Jeff Dike <jdike@addtoit.com> Cc: Greg KH <greg@kroah.com> Cc: Jens Axboe <jens.axboe@oracle.com> Cc: Paul Fulghum <paulkf@microgate.com> Cc: Alan Cox <alan@lxorguk.ukuu.org.uk> Cc: Karsten Keil <kkeil@suse.de> Cc: Mauro Carvalho Chehab <mchehab@infradead.org> Cc: Jeff Garzik <jeff@garzik.org> Cc: James Bottomley <James.Bottomley@steeleye.com> Cc: Ian Kent <raven@themaw.net> Cc: Steven French <sfrench@us.ibm.com> Cc: David Woodhouse <dwmw2@infradead.org> Cc: Neil Brown <neilb@cse.unsw.edu.au> Cc: Jaroslav Kysela <perex@suse.cz> Cc: Takashi Iwai <tiwai@suse.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2006-12-13 08:35:56 +00:00
cmd = kmalloc(sizeof(struct i596_cmd)+2+dev->mc_count*6, GFP_ATOMIC);
if (cmd == NULL) {
printk (KERN_ERR "%s: set_multicast Memory squeeze.\n", dev->name);
return;
}
cmd->command = CmdMulticastList;
*((unsigned short *) (cmd + 1)) = dev->mc_count * 6;
cp = ((char *)(cmd + 1))+2;
for (dmi = dev->mc_list; dmi != NULL; dmi = dmi->next) {
memcpy(cp, dmi,6);
cp += 6;
}
if (i596_debug & LOG_SRCDST)
print_eth (((char *)(cmd + 1)) + 2);
i596_add_cmd(dev, cmd);
} else {
if (lp->set_conf.pa_next != I596_NULL) {
return;
}
if (dev->mc_count == 0 && !(dev->flags & (IFF_PROMISC | IFF_ALLMULTI))) {
if (dev->flags & IFF_ALLMULTI)
dev->flags |= IFF_PROMISC;
lp->i596_config[8] &= ~0x01;
} else {
lp->i596_config[8] |= 0x01;
}
i596_add_cmd(dev, (struct i596_cmd *) &lp->set_conf);
}
}
MODULE_AUTHOR("Ard van Breemen <ard@cstmel.nl.eu.org>");
MODULE_DESCRIPTION("Intel Panther onboard i82596 driver");
MODULE_LICENSE("GPL");
static struct net_device *dev_lp486e;
static int full_duplex;
static int options;
static int io = IOADDR;
static int irq = IRQ;
module_param(debug, int, 0);
//module_param(max_interrupt_work, int, 0);
//module_param(reverse_probe, int, 0);
//module_param(rx_copybreak, int, 0);
module_param(options, int, 0);
module_param(full_duplex, int, 0);
static int __init lp486e_init_module(void) {
int err;
struct net_device *dev = alloc_etherdev(sizeof(struct i596_private));
if (!dev)
return -ENOMEM;
dev->irq = irq;
dev->base_addr = io;
err = lp486e_probe(dev);
if (err) {
free_netdev(dev);
return err;
}
err = register_netdev(dev);
if (err) {
release_region(dev->base_addr, LP486E_TOTAL_SIZE);
free_netdev(dev);
return err;
}
dev_lp486e = dev;
full_duplex = 0;
options = 0;
return 0;
}
static void __exit lp486e_cleanup_module(void) {
unregister_netdev(dev_lp486e);
release_region(dev_lp486e->base_addr, LP486E_TOTAL_SIZE);
free_netdev(dev_lp486e);
}
module_init(lp486e_init_module);
module_exit(lp486e_cleanup_module);