diff options
author | Ralf Baechle <ralf@linux-mips.org> | 1995-11-14 08:00:00 +0000 |
---|---|---|
committer | <ralf@linux-mips.org> | 1995-11-14 08:00:00 +0000 |
commit | e7c2a72e2680827d6a733931273a93461c0d8d1b (patch) | |
tree | c9abeda78ef7504062bb2e816bcf3e3c9d680112 /drivers/net/3c501.c | |
parent | ec6044459060a8c9ce7f64405c465d141898548c (diff) |
Import of Linux/MIPS 1.3.0
Diffstat (limited to 'drivers/net/3c501.c')
-rw-r--r-- | drivers/net/3c501.c | 213 |
1 files changed, 179 insertions, 34 deletions
diff --git a/drivers/net/3c501.c b/drivers/net/3c501.c index 5643c2d5e..3910587da 100644 --- a/drivers/net/3c501.c +++ b/drivers/net/3c501.c @@ -14,6 +14,65 @@ The author may be reached as becker@CESDIS.gsfc.nasa.gov, or C/O Center of Excellence in Space Data and Information Sciences Code 930.5, Goddard Space Flight Center, Greenbelt MD 20771 + + Fixed (again!) the missing interrupt locking on TX/RX shifting. + Alan Cox <Alan.Cox@linux.org> + + Removed calls to init_etherdev since they are no longer needed, and + cleaned up modularization just a bit. The driver still allows only + the default address for cards when loaded as a module, but that's + really less braindead than anyone using a 3c501 board. :) + 19950208 (invid@msen.com) + + Added traps for interrupts hitting the window as we clear and TX load + the board. Now getting 150K/second FTP with a 3c501 card. Still playing + with a TX-TX optimisation to see if we can touch 180-200K/second as seems + theoretically maximum. + 19950402 Alan Cox <Alan.Cox@linux.org> + + Some notes on this thing if you have to hack it. [Alan] + + 1] Some documentation is available from 3Com. Due to the boards age + standard responses when you ask for this will range from 'be serious' + to 'give it to a museum'. The documentation is incomplete and mostly + of historical interest anyway. + + 2] The basic system is a single buffer which can be used to receive or + transmit a packet. A third command mode exists when you are setting + things up. + + 3] If it's transmitting it's not receiving and vice versa. In fact the + time to get the board back into useful state after an operation is + quite large. + + 4] The driver works by keeping the board in receive mode waiting for a + packet to arrive. When one arrives it is copied out of the buffer + and delivered to the kernel. The card is reloaded and off we go. + + 5] When transmitting dev->tbusy is set and the card is reset (from + receive mode) [possibly losing a packet just received] to command + mode. A packet is loaded and transmit mode triggered. The interrupt + handler runs different code for transmit interrupts and can handle + returning to receive mode or retransmissions (yes you have to help + out with those too). + + Problems: + There are a wide variety of undocumented error returns from the card + and you basically have to kick the board and pray if they turn up. Most + only occur under extreme load or if you do something the board doesn't + like (eg touching a register at the wrong time). + + The driver is less efficient than it could be. It switches through + receive mode even if more transmits are queued. If this worries you buy + a real ethernet card. + + The combination of slow receive restart and no real multicast + filter makes the board unusable with a kernel compiled for IP + multicasting in a real multicast environment. Thats down to the board, + but even with no multicast programs running a multicast IP kernel is + in group 224.0.0.1 and you will therefore be listening to all multicasts. + One nv conference running over that ethernet and you can give up. + */ static char *version = @@ -25,6 +84,14 @@ static char *version = */ #include <linux/config.h> +#ifdef MODULE +#include <linux/module.h> +#include <linux/version.h> +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif + #include <linux/kernel.h> #include <linux/sched.h> #include <linux/ptrace.h> @@ -43,14 +110,6 @@ static char *version = #include <linux/etherdevice.h> #include <linux/skbuff.h> -#ifdef MODULE -#include <linux/module.h> -#include "../../tools/version.h" -#endif - -extern struct device *init_etherdev(struct device *dev, int sizeof_private, - unsigned long *mem_startp); - /* A zero-terminated list of I/O addresses to be probed. The 3c501 can be at many locations, but here are the popular ones. */ static unsigned int netcard_portlist[] = @@ -62,7 +121,7 @@ int el1_probe(struct device *dev); static int el1_probe1(struct device *dev, int ioaddr); static int el_open(struct device *dev); static int el_start_xmit(struct sk_buff *skb, struct device *dev); -static void el_interrupt(int reg_ptr); +static void el_interrupt(int irq, struct pt_regs *regs); static void el_receive(struct device *dev); static void el_reset(struct device *dev); static int el1_close(struct device *dev); @@ -81,6 +140,7 @@ struct net_local { struct enet_statistics stats; int tx_pkt_start; /* The length of the current Tx packet. */ int collisions; /* Tx collisions this packet */ + int loading; /* Spot buffer load collisions */ }; @@ -161,6 +221,8 @@ el1_probe(struct device *dev) static int el1_probe1(struct device *dev, int ioaddr) { + #ifndef MODULE + char *mname; /* Vendor name */ unsigned char station_addr[6]; int autoirq = 0; @@ -183,10 +245,7 @@ el1_probe1(struct device *dev, int ioaddr) return ENODEV; /* Grab the region so we can find the another board if autoIRQ fails. */ - snarf_region(ioaddr, EL1_IO_EXTENT); - - if (dev == NULL) - dev = init_etherdev(0, sizeof(struct net_local), 0); + request_region(ioaddr, EL1_IO_EXTENT,"3c501"); /* We auto-IRQ by shutting off the interrupt line and letting it float high. */ @@ -218,9 +277,13 @@ el1_probe1(struct device *dev, int ioaddr) if (autoirq) dev->irq = autoirq; - printk("%s: %s EtherLink at %#x, using %sIRQ %d, melting ethernet.\n", + printk("%s: %s EtherLink at %#lx, using %sIRQ %d.\n", dev->name, mname, dev->base_addr, autoirq ? "auto":"assigned ", dev->irq); + +#ifdef CONFIG_IP_MULTICAST + printk("WARNING: Use of the 3c501 in a multicast kernel is NOT recommended.\n"); +#endif if (el_debug) printk("%s", version); @@ -239,6 +302,7 @@ el1_probe1(struct device *dev, int ioaddr) /* Setup the generic properties */ ether_setup(dev); +#endif /* !MODULE */ return 0; } @@ -261,9 +325,7 @@ el_open(struct device *dev) dev->start = 1; outb(AX_RX, AX_CMD); /* Aux control, irq and receive enabled */ -#ifdef MODULE MOD_INC_USE_COUNT; -#endif return 0; } @@ -272,6 +334,7 @@ el_start_xmit(struct sk_buff *skb, struct device *dev) { struct net_local *lp = (struct net_local *)dev->priv; int ioaddr = dev->base_addr; + unsigned long flags; if (dev->tbusy) { if (jiffies - dev->trans_start < 20) { @@ -296,24 +359,51 @@ el_start_xmit(struct sk_buff *skb, struct device *dev) return 0; } + save_flags(flags); + /* Avoid incoming interrupts between us flipping tbusy and flipping + mode as the driver assumes tbusy is a faithful indicator of card + state */ + cli(); /* Avoid timer-based retransmission conflicts. */ if (set_bit(0, (void*)&dev->tbusy) != 0) + { + restore_flags(flags); printk("%s: Transmitter access conflict.\n", dev->name); + } else { int gp_start = 0x800 - (ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN); unsigned char *buf = skb->data; +load_it_again_sam: lp->tx_pkt_start = gp_start; lp->collisions = 0; + /* + * Command mode with status cleared should [in theory] + * mean no more interrupts can be pending on the card. + */ outb(AX_SYS, AX_CMD); inb(RX_STATUS); inb(TX_STATUS); - outw(0x00, RX_BUF_CLR); /* Set rx packet area to 0. */ - outw(gp_start, GP_LOW); - outsb(DATAPORT,buf,skb->len); - outw(gp_start, GP_LOW); - outb(AX_XMIT, AX_CMD); /* Trigger xmit. */ + + lp->loading=1; + + /* + * Turn interrupts back on while we spend a pleasant afternoon + * loading bytes into the board + */ + restore_flags(flags); + outw(0x00, RX_BUF_CLR); /* Set rx packet area to 0. */ + outw(gp_start, GP_LOW); /* aim - packet will be loaded into buffer start */ + outsb(DATAPORT,buf,skb->len); /* load buffer (usual thing each byte increments the pointer) */ + outw(gp_start, GP_LOW); /* the board reuses the same register */ + if(lp->loading==2) /* A receive upset our load, despite our best efforts */ + { + if(el_debug>2) + printk("%s: burped during tx load.\n", dev->name); + goto load_it_again_sam; /* Sigh... */ + } + outb(AX_XMIT, AX_CMD); /* fire ... Trigger xmit. */ dev->trans_start = jiffies; } @@ -327,9 +417,8 @@ el_start_xmit(struct sk_buff *skb, struct device *dev) /* The typical workload of the driver: Handle the ether interface interrupts. */ static void -el_interrupt(int reg_ptr) +el_interrupt(int irq, struct pt_regs *regs) { - int irq = pt_regs2irq(reg_ptr); struct device *dev = (struct device *)(irq2dev_map[irq]); struct net_local *lp; int ioaddr; @@ -349,8 +438,15 @@ el_interrupt(int reg_ptr) if (dev->interrupt) printk("%s: Reentering the interrupt driver!\n", dev->name); dev->interrupt = 1; + + lp->loading=2; /* So we can spot loading interruptions */ if (dev->tbusy) { + + /* + * Board in transmit mode. + */ + int txsr = inb(TX_STATUS); if (el_debug > 6) @@ -358,12 +454,19 @@ el_interrupt(int reg_ptr) inw(RX_LOW)); if ((axsr & 0x80) && (txsr & TX_READY) == 0) { + /* + * FIXME: is there a logic to whether to keep on trying or + * reset immediately ? + */ printk("%s: Unusual interrupt during Tx, txsr=%02x axsr=%02x" " gp=%03x rp=%03x.\n", dev->name, txsr, axsr, inw(ioaddr + EL1_DATAPTR), inw(ioaddr + EL1_RXPTR)); dev->tbusy = 0; mark_bh(NET_BH); } else if (txsr & TX_16COLLISIONS) { + /* + * Timed out + */ if (el_debug) printk("%s: Transmit failed 16 times, ethernet jammed?\n", dev->name); @@ -372,6 +475,9 @@ el_interrupt(int reg_ptr) } else if (txsr & TX_COLLISION) { /* Retrigger xmit. */ if (el_debug > 6) printk(" retransmitting after a collision.\n"); + /* + * Poor little chip can't reset its own start pointer + */ outb(AX_SYS, AX_CMD); outw(lp->tx_pkt_start, GP_LOW); outb(AX_XMIT, AX_CMD); @@ -379,26 +485,42 @@ el_interrupt(int reg_ptr) dev->interrupt = 0; return; } else { + /* + * It worked.. we will now fall through and receive + */ lp->stats.tx_packets++; if (el_debug > 6) printk(" Tx succeeded %s\n", (txsr & TX_RDY) ? "." : "but tx is busy!"); + /* + * This is safe the interrupt is atomic WRT itself. + */ dev->tbusy = 0; - mark_bh(NET_BH); + mark_bh(NET_BH); /* In case more to transmit */ } } else { + + /* + * In receive mode. + */ + int rxsr = inb(RX_STATUS); if (el_debug > 5) printk(" rxsr=%02x txsr=%02x rp=%04x", rxsr, inb(TX_STATUS), inw(RX_LOW)); - /* Just reading rx_status fixes most errors. */ + /* + * Just reading rx_status fixes most errors. + */ if (rxsr & RX_MISSED) lp->stats.rx_missed_errors++; if (rxsr & RX_RUNT) { /* Handled to avoid board lock-up. */ lp->stats.rx_length_errors++; if (el_debug > 5) printk(" runt.\n"); } else if (rxsr & RX_GOOD) { + /* + * Receive worked. + */ el_receive(dev); } else { /* Nothing? Something is broken! */ if (el_debug > 2) @@ -410,6 +532,9 @@ el_interrupt(int reg_ptr) printk(".\n"); } + /* + * Move into receive mode + */ outb(AX_RX, AX_CMD); outw(0x00, RX_BUF_CLR); inb(RX_STATUS); /* Be certain that interrupts are cleared. */ @@ -440,9 +565,17 @@ el_receive(struct device *dev) lp->stats.rx_over_errors++; return; } + + /* + * Command mode so we can empty the buffer + */ + outb(AX_SYS, AX_CMD); skb = alloc_skb(pkt_len, GFP_ATOMIC); + /* + * Start of frame + */ outw(0x00, GP_LOW); if (skb == NULL) { printk("%s: Memory squeeze, dropping packet.\n", dev->name); @@ -452,8 +585,14 @@ el_receive(struct device *dev) skb->len = pkt_len; skb->dev = dev; + /* + * The read increments through the bytes. The interrupt + * handler will fix the pointer when it returns to + * receive mode. + */ + insb(DATAPORT, skb->data, pkt_len); - + skb->protocol=eth_type_trans(skb,dev); netif_rx(skb); lp->stats.rx_packets++; } @@ -502,9 +641,7 @@ el1_close(struct device *dev) outb(AX_RESET, AX_CMD); /* Reset the chip */ irq2dev_map[dev->irq] = 0; -#ifdef MODULE MOD_DEC_USE_COUNT; -#endif return 0; } @@ -544,10 +681,15 @@ static struct device dev_3c501 = { 0, 0, 0, 0, 0x280, 5, 0, 0, 0, NULL, el1_probe }; + +int io=0x280; +int irq=5; int init_module(void) { + dev_3c501.irq=irq; + dev_3c501.base_addr=io; if (register_netdev(&dev_3c501) != 0) return -EIO; return 0; @@ -556,12 +698,15 @@ init_module(void) void cleanup_module(void) { - if (MOD_IN_USE) - printk("3c501: device busy, remove delayed\n"); - else - { - unregister_netdev(&dev_3c501); - } + /* No need to check MOD_IN_USE, as sys_delete_module() checks. */ + unregister_netdev(&dev_3c501); + + /* Free up the private structure, or leak memory :-) */ + kfree(dev_3c501.priv); + dev_3c501.priv = NULL; /* gets re-allocated by el1_probe1 */ + + /* If we don't do this, we can't re-insmod it later. */ + release_region(dev_3c501.base_addr, EL1_IO_EXTENT); } #endif /* MODULE */ |