...and another driver for Arctic-2, this time for Ethernet (which is actually on a debug board, no on the Arctic-2 itself). It's based on a RTL8019 chip and this driver was based on the ariadne2 driver.
diff -urN /home/dgibson/kernel/linuxppc_2_4_devel/drivers/net/Config.in linux-bartholomew/drivers/net/Config.in --- /home/dgibson/kernel/linuxppc_2_4_devel/drivers/net/Config.in 2002-12-04 10:44:50.000000000 +1100 +++ linux-bartholomew/drivers/net/Config.in 2002-12-12 16:50:02.000000000 +1100 @@ -43,6 +43,9 @@ if [ "$CONFIG_BEECH" = "y" ]; then tristate ' Beech onboard CS8900A Ethernet support' CONFIG_CS89x0 fi + if [ "$CONFIG_ARCTIC2" = "y" ]; then + tristate ' Arctic-II debug sled ethernet support' CONFIG_ARCTIC_ENET + fi if [ "$CONFIG_XILINX_OCP" = "y" ]; then tristate ' Xilinx on-chip ethernet' CONFIG_XILINX_ENET fi diff -urN /home/dgibson/kernel/linuxppc_2_4_devel/drivers/net/Makefile linux-bartholomew/drivers/net/Makefile --- /home/dgibson/kernel/linuxppc_2_4_devel/drivers/net/Makefile 2002-09-27 09:11:02.000000000 +1000 +++ linux-bartholomew/drivers/net/Makefile 2002-12-12 16:48:59.000000000 +1100 @@ -149,6 +149,7 @@ obj-$(CONFIG_NET_SB1250_MAC) += sb1250-mac.o obj-$(CONFIG_GT64260_ETH) += gt64260_eth.o obj-$(CONFIG_NPNET) += npnet.o +obj-$(CONFIG_ARCTIC_ENET) += arctic_enet.o 8390.o obj-$(CONFIG_PPP) += ppp_generic.o slhc.o obj-$(CONFIG_PPP_ASYNC) += ppp_async.o diff -urN /home/dgibson/kernel/linuxppc_2_4_devel/drivers/net/arctic_enet.c linux-bartholomew/drivers/net/arctic_enet.c --- /home/dgibson/kernel/linuxppc_2_4_devel/drivers/net/arctic_enet.c Thu Jan 01 10:00:00 1970 +++ linux-bartholomew/drivers/net/arctic_enet.c Fri Dec 13 14:03:36 2002 @@ -0,0 +1,658 @@ +/* + * IPE405 (IBM IAP 405 chip evaluation board) Debug Support Board + * Ehernet Driver + * (C) Copyright 2001 by S.nishino (jl04348 at jp.ibm.com) IBM-Japan + * + * ---------- Strategy ---------- + * + * This NIC is RTL8019AS, simply connected to External Bus Controller + * of IAP 405 chip. As many folks of 8390 based NIC, 8390 core driver + * is usable. luckily, the following driver is already available for + * Amiga zorro bus (however I don't know this architecture beyond + * below), this is modified based on this driver (ariadne2). + * + * ---------- original header ---------- + * Amiga Linux/m68k Ariadne II Ethernet Driver + * + * (C) Copyright 1998 by some Elitist 680x0 Users(TM) + * + * --------------------------------------------------------------------------- + * + * This program is based on all the other NE2000 drivers for Linux + * + * --------------------------------------------------------------------------- + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of the Linux + * distribution for more details. */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/irq.h> + + +#include <asm/system.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/ppc4xx_pic.h> +#if defined(CONFIG_ARCTIC2) +#include <platforms/arctic2.h> +#else +#error The driver only works on Arctic +#endif + +#include "8390.h" + + +#define ARCTIC_ENET_BASE8 (ARCTIC2_FPGA8_PADDR + 256*1024) +#define ARCTIC_ENET_BASE16 (ARCTIC2_FPGA16_PADDR + 0) + +#define ARCTIC_ENET_IOBASE 0x0300 /* io base offset from NIC region */ + +#define ARCTIC_ENET_IRQ 29 /* irq number in UIC */ +#define ARCTIC_ENET_IRQ_MASK (0x80000000 >> ARCTIC_ENET_IRQ) + +#define NE_BASE (ARCTIC_ENET_BASE8 + ARCTIC_ENET_IOBASE) +#define NE_BASE16 (ARCTIC_ENET_BASE16 + ARCTIC_ENET_IOBASE) + +/* 8390 register address */ +#define NE_CMD (0x00) +#define NE_DATAPORT (0x10) /* NatSemi-defined port window offset. */ +#define NE_DATAPORT16 (NE_DATAPORT / sizeof(u16)) +#define NE_RESET (0x1f) /* Issue a read to reset, a write to clear. */ +#define NE_IO_EXTENT (0x20) /* region extent */ + +#define NE_EN0_ISR (0x07) +#define NE_EN0_DCFG (0x0e) + +#define NE_EN0_RSARLO (0x08) +#define NE_EN0_RSARHI (0x09) +#define NE_EN0_RCNTLO (0x0a) +#define NE_EN0_RXCR (0x0c) +#define NE_EN0_TXCR (0x0d) +#define NE_EN0_RCNTHI (0x0b) +#define NE_EN0_IMR (0x0f) + +/* 8390 packet buffer page number */ +#define NESM_START_PG 0x40 /* First page of TX buffer */ +#define NESM_STOP_PG 0x80 /* Last page +1 of RX ring */ + +static u8 *iobase8; +static u16 *iobase16; + +static int arctic_enet_probe(struct net_device *dev); +static int arctic_enet_init(struct net_device *dev); + +static int arctic_enet_open(struct net_device *dev); +static int arctic_enet_close(struct net_device *dev); + +static void arctic_enet_reset_8390(struct net_device *dev); +static void arctic_enet_get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page); +static void arctic_enet_block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset); +static void arctic_enet_block_output(struct net_device *dev, + const int count, + const unsigned char *buf, + const int start_page); + +/* These macros will do something on Arctic-I if we ever add support + * for it back in */ +#define switch_16bit_bank() do { } while (0) +#define switch_8bit_bank() do { } while (0) + +void p_dump(unsigned char *p, int sz) +{ + int i; + unsigned char *wp; + + wp = p; + + printk("------ PACKET START : %d Bytes ------ \n", sz); + + for (i = 0; i < sz; i++) { + if (i % 16 == 0) { + printk("\n %04X: %02X ", i, *wp); + } else if (i % 16 == 15) { + printk("%02X", *wp); + } else { + printk("%02X ", *wp); + } + wp++; + } + + printk("------ PACKET END ------ \n"); +} + +/* Code for reading the MAC address from the Arctic ethernet based on + * similar code in PIBS */ + +static void __init writereg_9346(volatile u8 *iobase, u8 value) +{ + /* Switch to register page 3 */ + writeb(readb(iobase + NE_CMD) | 0xc0, iobase + NE_CMD); + writeb(value, iobase + 0x01); +} + +static u8 __init readreg_9346(volatile u8 *iobase) +{ + /* Switch to register page 3 */ + writeb(readb(iobase + NE_CMD) | 0xc0, iobase + NE_CMD); + return readb(iobase + 0x01); +} + +static void __init write_bit_9346(volatile u8 *iobase, u8 bit) +{ + u8 mask = ~0x06; + + writereg_9346(iobase, (readreg_9346(iobase) & mask) | bit); + udelay(1000); + writereg_9346(iobase, (readreg_9346(iobase) & mask) | bit | 0x04); + udelay(1000); +} + +static u8 __init read_bit_9346(volatile u8 *iobase) +{ + u8 bit; + u8 mask = ~0x05; + + mask = ~0x05; + writereg_9346(iobase, readreg_9346(iobase) & mask); + udelay(1000); + writereg_9346(iobase, (readreg_9346(iobase) & mask) | 0x04); + bit = readreg_9346(iobase) & 0x01; + udelay(1000); + + return bit; +} + +static u16 __init arctic_read_9346(volatile u8 *iobase, unsigned long addr) +{ + unsigned long flags; + int i; + u16 data; + + local_irq_save(flags); + + /* Put the chip into 8390 programming mode */ + writereg_9346(iobase, (readreg_9346(iobase) & ~0xc0) | 0x80); + udelay(1000); + + /* Send command (read 16-bit value) to EEPROM */ + /* Bring CS Low */ + writereg_9346(iobase, readreg_9346(iobase) & ~0x0f); + udelay(1000); + /* Bring CS High */ + writereg_9346(iobase, (readreg_9346(iobase) & ~0x0f) | 0x08); + udelay(1000); + + /* Send a 1 */ + write_bit_9346(iobase, 0x02); + /* Send opcode 0b10 */ + write_bit_9346(iobase, 0x02); + write_bit_9346(iobase, 0x00); + /* Send address to read */ + for (i = 0; i < 6; i++) { + if (addr & 0x20) + write_bit_9346(iobase, 0x02); + else + write_bit_9346(iobase, 0x00); + addr <<= 1; + } + + /* Read the value back, bit by bit */ + data = 0; + for (i = 0; i < 16; i++) { + data <<= 1; + if (read_bit_9346(iobase)) + data |= 0x1; + } + + /* Bring CS Low */ + writereg_9346(iobase, readreg_9346(iobase) & ~0x0f); + udelay(1000); + /* Bring the chip out of 8390 programming mode */ + writereg_9346(iobase, readreg_9346(iobase) & ~0xc0); + udelay(1000); + + /* Return to register page 0 */ + writeb(readb(iobase + NE_CMD) & ~0xc0, iobase + NE_CMD); + udelay(1000); + + local_irq_restore(flags); + + return data; +} + +static void __init arctic_get_macaddr(struct net_device *dev) +{ + u16 t0, t1, t2, v0, v1; + + t0 = arctic_read_9346(iobase8, 0); + t1 = arctic_read_9346(iobase8, 2); + t2 = arctic_read_9346(iobase8, 4); + v0 = arctic_read_9346(iobase8, 6); + v1 = arctic_read_9346(iobase8, 8); + + printk("arctic_enet: %04x-%04x-%04x (%04x/%04x)\n", + t0, t1, t2, v0, v1); + + if ( (v0 != 0x4d50) || (v1 != 0x5400) ) { + printk(KERN_WARNING "%s: MAC address is not set in EEPROM\n", dev->name); + return; + } + + printk("%s: MAC address from EEPROM is %04x:%04x:%04x\n", + dev->name, (unsigned)t0, (unsigned)t1, (unsigned)t2); + + dev->dev_addr[0] = t0 >> 8; + dev->dev_addr[1] = t0 & 0xff; + dev->dev_addr[2] = t1 >> 8; + dev->dev_addr[3] = t1 & 0xff; + dev->dev_addr[4] = t2 >> 8; + dev->dev_addr[5] = t2 & 0xff; +} + +int __init arctic_enet_probe(struct net_device *dev) +{ + unsigned long reset_start_time; + + switch_8bit_bank(); + /* Reset card. Who knows what dain-bramaged state it was left in. */ + reset_start_time = jiffies; + + writeb(readb(iobase8 + NE_RESET), iobase8 + NE_RESET); + + while ((readb(iobase8 + NE_EN0_ISR) & ENISR_RESET) == 0) + if (jiffies - reset_start_time > 2 * HZ / 100) { + printk("arctic_enet: not found (no reset ack).\n"); + return -ENODEV; + } + + writeb(0xff, iobase8 + NE_EN0_ISR); /* Ack all intr. */ + + arctic_get_macaddr(dev); + + printk("arctic_enet: found at 0x%08x/0x%08x, MAC address " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + NE_BASE, NE_BASE16, + dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2], + dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]); + + /* Hack to let 8390.c work properly - it assumes IO space + * addresses */ + dev->base_addr = (unsigned long)iobase8 - _IO_BASE; + dev->irq = ARCTIC_ENET_IRQ; + + return 0; +} + +static int __init arctic_enet_init(struct net_device *dev) +{ + static u32 arctic_enet_offsets[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + }; + + /* Since this irq is connected to uic as edge interrupt, its pending must be cleared. */ + /* FIXME: it would be nice to get rid of the direct reference + * to the 4xx irq structure */ + ppc4xx_pic->ack(dev->irq); + + /* Install the Interrupt handler */ + if (request_irq(dev->irq, ei_interrupt, SA_SHIRQ, dev->name, dev)) + return -EAGAIN; + + /* Allocate dev->priv and fill in 8390 specific dev fields. */ + if (ethdev_init(dev)) { + printk(" Unable to get memory for dev->priv.\n"); + return -ENOMEM; + } + + /* + * Fill 8390 specific member for 8390 core driver + */ + ei_status.name = "RTL8019AS"; + ei_status.tx_start_page = NESM_START_PG; + ei_status.stop_page = NESM_STOP_PG; + ei_status.word16 = 1; + ei_status.rx_start_page = NESM_START_PG + TX_PAGES; + + ei_status.reset_8390 = &arctic_enet_reset_8390; + ei_status.block_input = &arctic_enet_block_input; + ei_status.block_output = &arctic_enet_block_output; + ei_status.get_8390_hdr = &arctic_enet_get_8390_hdr; + ei_status.reg_offset = arctic_enet_offsets; + + NS8390_init(dev, 0); + return 0; +} + +static int arctic_enet_open(struct net_device *dev) +{ + int err; + err = ei_open(dev); + if (err) + return err; + + MOD_INC_USE_COUNT; + return 0; +} + +static int arctic_enet_close(struct net_device *dev) +{ + int err; + + err = ei_close(dev); + if (err) + return err; + + MOD_DEC_USE_COUNT; + return 0; +} + +/* Hard reset the card. This used to pause for the same period that a + 8390 reset command required, but that shouldn't be necessary. */ +static void arctic_enet_reset_8390(struct net_device *dev) +{ + unsigned long reset_start_time = jiffies; + + if (ei_debug > 1) + printk("resetting the 8390 t=%ld...", jiffies); + + writeb(readb(iobase8 + NE_RESET), iobase8 + NE_RESET); + + ei_status.txing = 0; + ei_status.dmaing = 0; + + /* This check _should_not_ be necessary, omit eventually. */ + while ((readb(iobase8 + NE_EN0_ISR) & ENISR_RESET) == 0) + if (jiffies - reset_start_time > 2 * HZ / 100) { + printk("%s: ne_reset_8390() did not complete.\n", + dev->name); + break; + } + writeb(ENISR_RESET, iobase8 + NE_EN0_ISR); /* Ack intr. */ +} + +/* Grab the 8390 specific header. Similar to the block_input routine, but + we don't need to be concerned with ring wrap as the header will be at + the start of a page, so we optimize accordingly. */ + +static void arctic_enet_get_8390_hdr(struct net_device *dev, + struct e8390_pkt_hdr *hdr, + int ring_page) +{ + int cnt; + u16 *ptrs; + unsigned char *ptrc; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_get_8390_hdr " + "[DMAstat:%d][irqlock:%d].\n", dev->name, + ei_status.dmaing, ei_status.irqlock); + return; + } + + ei_status.dmaing |= 0x01; + writeb(E8390_NODMA + E8390_PAGE0 + E8390_START, iobase8 + NE_CMD); + writeb(ENISR_RDC, iobase8 + NE_EN0_ISR); + writeb(sizeof(struct e8390_pkt_hdr), iobase8 + NE_EN0_RCNTLO); + writeb(0, iobase8 + NE_EN0_RCNTHI); + writeb(0, iobase8 + NE_EN0_RSARLO); /* On page boundary */ + writeb(ring_page, iobase8 + NE_EN0_RSARHI); + writeb(E8390_RREAD + E8390_START, iobase8 + NE_CMD); + + if (ei_status.word16) { + switch_16bit_bank(); + ptrs = (u16 *) hdr; + for (cnt = 0; cnt < (sizeof(struct e8390_pkt_hdr) >> 1); + cnt++) + *ptrs++ = in_be16((u16 *) (iobase16 + NE_DATAPORT16)); + switch_8bit_bank(); + } else { + + ptrc = (unsigned char *) hdr; + for (cnt = 0; cnt < sizeof(struct e8390_pkt_hdr); cnt++) + *ptrc++ = readb(iobase8 + NE_DATAPORT); + } + + + writeb(ENISR_RDC, iobase8 + NE_EN0_ISR); /* Ack intr. */ + + /* I am Big Endian, but received byte count is Little Endian. */ + hdr->count = le16_to_cpu(hdr->count); + + ei_status.dmaing &= ~0x01; +} + +/* Block input and output, similar to the Crynwr packet driver. If you + are porting to a new ethercard, look at the packet driver source for hints. + The NEx000 doesn't share the on-board packet memory -- you have to put + the packet out through the "remote DMA" dataport using writeb. */ + +static void arctic_enet_block_input(struct net_device *dev, int count, + struct sk_buff *skb, int ring_offset) +{ + char *buf = skb->data; + u16 *ptrs; + unsigned char *ptrc; + + int cnt; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_block_input " + "[DMAstat:%d][irqlock:%d].\n", + dev->name, ei_status.dmaing, ei_status.irqlock); + return; + } + ei_status.dmaing |= 0x01; + writeb(E8390_NODMA + E8390_PAGE0 + E8390_START, iobase8 + NE_CMD); + writeb(ENISR_RDC, iobase8 + NE_EN0_ISR); + writeb(count & 0xff, iobase8 + NE_EN0_RCNTLO); + writeb(count >> 8, iobase8 + NE_EN0_RCNTHI); + writeb(ring_offset & 0xff, iobase8 + NE_EN0_RSARLO); + writeb(ring_offset >> 8, iobase8 + NE_EN0_RSARHI); + writeb(E8390_RREAD + E8390_START, iobase8 + NE_CMD); + + + if (ei_status.word16) { + + switch_16bit_bank(); + + ptrs = (u16 *) buf; + for (cnt = 0; cnt < (count >> 1); cnt++) + /* At 16 bits mode, bus acts as Little Endian mode + That's swap is needed ??? */ + *ptrs++ = in_be16((u16 *) (iobase16 + NE_DATAPORT16)); + switch_8bit_bank(); + + if (count & 0x01) + buf[count - 1] = readb(iobase8 + NE_DATAPORT); + + } else { + + + ptrc = (unsigned char *) buf; + for (cnt = 0; cnt < count; cnt++) + *ptrc++ = readb(iobase8 + NE_DATAPORT); + } + + writeb(ENISR_RDC, iobase8 + NE_EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; +} + +static void arctic_enet_block_output(struct net_device *dev, int count, + const unsigned char *buf, + const int start_page) +{ + unsigned long dma_start; + u16 *ptrs; + unsigned char *ptrc; + int cnt; + + /* Round the count up for word writes. Do we need to do this? + What effect will an odd byte count have on the 8390? + I should check someday. */ + if (count & 0x01) + count++; + + /* This *shouldn't* happen. If it does, it's the last thing you'll see */ + if (ei_status.dmaing) { + printk("%s: DMAing conflict in ne_block_output." + "[DMAstat:%d][irqlock:%d]\n", dev->name, + ei_status.dmaing, ei_status.irqlock); + return; + } + ei_status.dmaing |= 0x01; + +#if 1 /* FIXME: not sure what this is for -dwg */ + writeb(0x42, iobase8 + EN0_RCNTLO); + writeb(0x00, iobase8 + EN0_RCNTHI); + writeb(0x42, iobase8 + EN0_RSARLO); + writeb(0x00, iobase8 + EN0_RSARHI); +#endif + /* We should already be in page 0, but to be safe... */ + writeb(E8390_PAGE0 + E8390_START + E8390_NODMA, iobase8 + NE_CMD); + + writeb(ENISR_RDC, iobase8 + NE_EN0_ISR); + + /* Now the normal output. */ + writeb(count & 0xff, iobase8 + NE_EN0_RCNTLO); + writeb(count >> 8, iobase8 + NE_EN0_RCNTHI); + writeb(0x00, iobase8 + NE_EN0_RSARLO); + writeb(start_page, iobase8 + NE_EN0_RSARHI); + + writeb(E8390_RWRITE + E8390_START, iobase8 + NE_CMD); + + if (ei_status.word16) { + switch_16bit_bank(); + + ptrs = (u16 *) buf; + for (cnt = 0; cnt < count >> 1; cnt++) { + /* At 16 bits mode, bus acts as Little Endian mode + That's swap is needed ??? */ + out_be16((u16 *) (iobase16 + NE_DATAPORT16), + *ptrs); + ptrs++; + } + + switch_8bit_bank(); + + } else { + ptrc = (unsigned char *) buf; + for (cnt = 0; cnt < count; cnt++) + writeb(*ptrc++, iobase8 + NE_DATAPORT); + } + + dma_start = jiffies; + + while ((readb(iobase8 + NE_EN0_ISR) & ENISR_RDC) == 0) + if (jiffies - dma_start > 2 * HZ / 100) { /* 20ms */ + printk("%s: timeout waiting for Tx RDC.\n", + dev->name); + arctic_enet_reset_8390(dev); + NS8390_init(dev, 1); + break; + } + + writeb(ENISR_RDC, iobase8 + NE_EN0_ISR); /* Ack intr. */ + ei_status.dmaing &= ~0x01; + return; +} + +static struct net_device arctic_enet_dev = { + .init = arctic_enet_init, + .open = arctic_enet_open, + .stop = arctic_enet_close, +}; + +int init_arctic_enet(void) +{ + struct net_device *dev = &arctic_enet_dev; + int rsvd8 = 0; + int rsvd16 = 0; + int err; + + /* First set up our IO regions */ + if (! request_mem_region(NE_BASE, NE_IO_EXTENT, "arctic_enet")) + goto fail; + rsvd8 = 1; + + iobase8 = ioremap(NE_BASE, NE_IO_EXTENT); + if (! iobase8) { + err = -EBUSY; + goto fail; + } + + if (NE_BASE16 != NE_BASE) { + if (! request_mem_region(NE_BASE16, NE_IO_EXTENT, "arctic_enet")) + goto fail; + rsvd16 = 1; + } + + iobase16 = ioremap(NE_BASE16, NE_IO_EXTENT); + if (! iobase16) { + err = -EBUSY; + goto fail; + } + + /* Configure IRQ */ + cli(); + mtdcr(DCRN_UIC0_TR, mfdcr(DCRN_UIC0_TR) | ARCTIC_ENET_IRQ_MASK); + mtdcr(DCRN_UIC0_PR, mfdcr(DCRN_UIC0_PR) | ARCTIC_ENET_IRQ_MASK); + mtdcr(DCRN_UIC0_SR, ARCTIC_ENET_IRQ_MASK); + sti(); + + err = arctic_enet_probe(dev); + if (err) { + printk(KERN_ERR "arctic_enet: No Arctic ethernet card found.\n"); + goto fail; + } + + err = register_netdev(dev); + if (err) + goto fail; + + return 0; + + fail: + if (iobase16) + iounmap(iobase16); + if (rsvd16) + release_mem_region(NE_BASE16, NE_IO_EXTENT); + if (iobase8) + iounmap(iobase8); + if (rsvd8) + release_mem_region(NE_BASE, NE_IO_EXTENT); + + return err; + +} + +void remove_arctic_enet(void) +{ + unregister_netdev(&arctic_enet_dev); + free_irq(ARCTIC_ENET_IRQ, &arctic_enet_dev); + + if (iobase16) { + iounmap(iobase16); + release_mem_region(NE_BASE16, NE_IO_EXTENT); + } + if (iobase8) { + iounmap(iobase8); + release_mem_region(NE_BASE, NE_IO_EXTENT); + } +} + +module_init(init_arctic_enet); +module_exit(remove_arctic_enet); -- David Gibson | For every complex problem there is a david at gibson.dropbear.id.au | solution which is simple, neat and | wrong. http://www.ozlabs.org/people/dgibson ** Sent via the linuxppc-embedded mail list. See http://lists.linuxppc.org/