| /* 3c501.c: A 3Com 3c501 ethernet driver for linux. */ |
| /* |
| Copyright (C) 1992,1993 Donald Becker |
| |
| Copyright 1993 United States Government as represented by the |
| Director, National Security Agency. This software may be used and |
| distributed according to the terms of the GNU Public License, |
| incorporated herein by reference. |
| |
| This is a device driver for the 3Com Etherlink 3c501. |
| Do not purchase this card, even as a joke. It's performance is horrible, |
| and it breaks in many ways. |
| |
| The Author may be reached as becker@super.org or |
| C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715 |
| I'll only accept bug fixes, not reports, for the 3c501 driver. |
| */ |
| |
| static char *version = |
| "3c501.c:v13.13 1993 Donald Becker (becker@super.org).\n"; |
| |
| /* |
| Braindamage remaining: |
| The 3c501 board. |
| */ |
| |
| #include <linux/config.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/ptrace.h> |
| #include <linux/fcntl.h> |
| #include <linux/ioport.h> |
| #include <linux/interrupt.h> |
| #include <linux/malloc.h> |
| #include <linux/ioport.h> |
| #include <asm/bitops.h> |
| #include <asm/io.h> |
| #include <errno.h> |
| |
| #include "dev.h" |
| #include "eth.h" |
| #include "skbuff.h" |
| #include "arp.h" |
| |
| #ifndef HAVE_AUTOIRQ |
| /* From auto_irq.c, should be in a *.h file. */ |
| extern void autoirq_setup(int waittime); |
| extern int autoirq_report(int waittime); |
| extern struct device *irq2dev_map[16]; |
| #endif |
| |
| #ifndef HAVE_ALLOC_SKB |
| #define alloc_skb(size, priority) (struct sk_buff *) kmalloc(size,priority) |
| #define kfree_skbmem(addr, size) kfree_s(addr,size); |
| #endif |
| |
| |
| /* Index to functions. */ |
| int el1_probe(struct device *dev); |
| 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_receive(struct device *dev); |
| static void el_reset(struct device *dev); |
| static int el1_close(struct device *dev); |
| static struct enet_statistics *el1_get_stats(struct device *dev); |
| #ifdef HAVE_MULTICAST |
| static void set_multicast_list(struct device *dev, int num_addrs, void *addrs); |
| #endif |
| |
| #define EL_NAME "EtherLink 3c501" |
| |
| #ifndef EL_DEBUG |
| #define EL_DEBUG 2 /* use 0 for production, 1 for devel., >2 for debug */ |
| #endif /* Anything above 5 is wordy death! */ |
| static int el_debug = EL_DEBUG; |
| static int el_base; |
| static struct device *eldev; /* Only for consistency checking. */ |
| |
| /* We could easily have this struct kmalloc()ed per-board, but |
| who would want more than one 3c501?. */ |
| static struct { |
| struct enet_statistics stats; |
| int tx_pkt_start; /* The length of the current Tx packet. */ |
| int collisions; /* Tx collisions this packet */ |
| } el_status; /* This should be stored per-board */ |
| |
| |
| #define RX_STATUS (el_base + 0x06) |
| #define RX_CMD RX_STATUS |
| #define TX_STATUS (el_base + 0x07) |
| #define TX_CMD TX_STATUS |
| #define GP_LOW (el_base + 0x08) |
| #define GP_HIGH (el_base + 0x09) |
| #define RX_BUF_CLR (el_base + 0x0A) |
| #define RX_LOW (el_base + 0x0A) |
| #define RX_HIGH (el_base + 0x0B) |
| #define SAPROM (el_base + 0x0C) |
| #define AX_STATUS (el_base + 0x0E) |
| #define AX_CMD AX_STATUS |
| #define DATAPORT (el_base + 0x0F) |
| #define TX_RDY 0x08 /* In TX_STATUS */ |
| |
| #define EL1_DATAPTR 0x08 |
| #define EL1_RXPTR 0x0A |
| #define EL1_SAPROM 0x0C |
| #define EL1_DATAPORT 0x0f |
| |
| /* Writes to the ax command register. */ |
| #define AX_OFF 0x00 /* Irq off, buffer access on */ |
| #define AX_SYS 0x40 /* Load the buffer */ |
| #define AX_XMIT 0x44 /* Transmit a packet */ |
| #define AX_RX 0x48 /* Receive a packet */ |
| #define AX_LOOP 0x0C /* Loopback mode */ |
| #define AX_RESET 0x80 |
| |
| /* Normal receive mode written to RX_STATUS. We must intr on short packets |
| to avoid bogus rx lockups. */ |
| #define RX_NORM 0xA8 /* 0x68 == all addrs, 0xA8 only to me. */ |
| #define RX_PROM 0x68 /* Senior Prom, uhmm promiscuous mode. */ |
| #define RX_MULT 0xE8 /* Accept multicast packets. */ |
| #define TX_NORM 0x0A /* Interrupt on everything that might hang the chip */ |
| |
| /* TX_STATUS register. */ |
| #define TX_COLLISION 0x02 |
| #define TX_16COLLISIONS 0x04 |
| #define TX_READY 0x08 |
| |
| #define RX_RUNT 0x08 |
| #define RX_MISSED 0x01 /* Missed a packet due to 3c501 braindamage. */ |
| #define RX_GOOD 0x30 /* Good packet 0x20, or simple overflow 0x10. */ |
| |
| |
| int |
| el1_probe(struct device *dev) |
| { |
| int i; |
| int ioaddr; |
| unsigned char station_addr[6]; |
| int autoirq = 0; |
| |
| eldev = dev; /* Store for debugging. */ |
| el_base = dev->base_addr; |
| |
| if (el_base < 0x40) /* Invalid? Probe for it. */ |
| el_base = 0x280; |
| |
| ioaddr = el_base; |
| |
| /* Read the station address PROM data from the special port. */ |
| for (i = 0; i < 6; i++) { |
| outw(i, ioaddr + EL1_DATAPTR); |
| station_addr[i] = inb(ioaddr + EL1_SAPROM); |
| } |
| /* Check the first three octets of the S.A. for 3Com's code. */ |
| if (station_addr[0] != 0x02 || station_addr[1] != 0x60 |
| || station_addr[2] != 0x8c) { |
| return ENODEV; |
| } |
| |
| #ifdef HAVE_PORTRESERVE |
| /* Grab the region so we can find the another board if autoIRQ fails. */ |
| snarf_region(ioaddr, 16); |
| #endif |
| |
| /* We auto-IRQ by shutting off the interrupt line and letting it float |
| high. */ |
| if (dev->irq < 2) { |
| |
| autoirq_setup(2); |
| |
| inb(RX_STATUS); /* Clear pending interrupts. */ |
| inb(TX_STATUS); |
| outb(AX_LOOP + 1, AX_CMD); |
| |
| outb(0x00, AX_CMD); |
| |
| autoirq = autoirq_report(1); |
| |
| if (autoirq == 0) { |
| printk("%s: 3c501 probe failed to detect IRQ line.\n", dev->name); |
| return EAGAIN; |
| } |
| dev->irq = autoirq; |
| } |
| |
| outb(AX_RESET+AX_LOOP, AX_CMD); /* Loopback mode. */ |
| |
| dev->base_addr = el_base; |
| memcpy(dev->dev_addr, station_addr, ETH_ALEN); |
| if (dev->mem_start & 0xf) |
| el_debug = dev->mem_start & 0x7; |
| |
| printk("%s: 3c501 EtherLink at %#x, using %sIRQ %d, melting ethernet.\n", |
| dev->name, dev->base_addr, autoirq ? "auto":"assigned ", dev->irq); |
| |
| if (el_debug) |
| printk("%s", version); |
| |
| /* The EL1-specific entries in the device structure. */ |
| dev->open = &el_open; |
| dev->hard_start_xmit = &el_start_xmit; |
| dev->stop = &el1_close; |
| dev->get_stats = &el1_get_stats; |
| #ifdef HAVE_MULTICAST |
| dev->set_multicast_list = &set_multicast_list; |
| #endif |
| |
| /* Fill in the generic field of the device structure. */ |
| for (i = 0; i < DEV_NUMBUFFS; i++) |
| dev->buffs[i] = NULL; |
| |
| dev->hard_header = eth_header; |
| dev->add_arp = eth_add_arp; |
| dev->queue_xmit = dev_queue_xmit; |
| dev->rebuild_header = eth_rebuild_header; |
| dev->type_trans = eth_type_trans; |
| |
| dev->type = ARPHRD_ETHER; |
| dev->hard_header_len = ETH_HLEN; |
| dev->mtu = 1500; /* eth_mtu */ |
| dev->addr_len = ETH_ALEN; |
| for (i = 0; i < ETH_ALEN; i++) { |
| dev->broadcast[i]=0xff; |
| } |
| |
| /* New-style flags. */ |
| dev->flags = IFF_BROADCAST; |
| dev->family = AF_INET; |
| dev->pa_addr = 0; |
| dev->pa_brdaddr = 0; |
| dev->pa_mask = 0; |
| dev->pa_alen = sizeof(unsigned long); |
| |
| return 0; |
| } |
| |
| /* Open/initialize the board. */ |
| static int |
| el_open(struct device *dev) |
| { |
| |
| if (el_debug > 2) |
| printk("%s: Doing el_open()...", dev->name); |
| |
| if (request_irq(dev->irq, &el_interrupt)) { |
| if (el_debug > 2) |
| printk("interrupt busy, exiting el_open().\n"); |
| return -EAGAIN; |
| } |
| irq2dev_map[dev->irq] = dev; |
| |
| el_reset(dev); |
| |
| dev->start = 1; |
| |
| outb(AX_RX, AX_CMD); /* Aux control, irq and receive enabled */ |
| if (el_debug > 2) |
| printk("finished el_open().\n"); |
| return (0); |
| } |
| |
| static int |
| el_start_xmit(struct sk_buff *skb, struct device *dev) |
| { |
| |
| if (dev->tbusy) { |
| if (jiffies - dev->trans_start < 20) { |
| if (el_debug > 2) |
| printk(" transmitter busy, deferred.\n"); |
| return 1; |
| } |
| if (el_debug) |
| printk ("%s: transmit timed out, txsr %#2x axsr=%02x rxsr=%02x.\n", |
| dev->name, inb(TX_STATUS), inb(AX_STATUS), inb(RX_STATUS)); |
| el_status.stats.tx_errors++; |
| #ifdef oldway |
| el_reset(dev); |
| #else |
| outb(TX_NORM, TX_CMD); |
| outb(RX_NORM, RX_CMD); |
| outb(AX_OFF, AX_CMD); /* Just trigger a false interrupt. */ |
| #endif |
| outb(AX_RX, AX_CMD); /* Aux control, irq and receive enabled */ |
| dev->tbusy = 0; |
| dev->trans_start = jiffies; |
| } |
| |
| if (skb == NULL) { |
| dev_tint(dev); |
| return 0; |
| } |
| |
| /* Fill in the ethernet header. */ |
| if (!skb->arp && dev->rebuild_header(skb->data, dev)) { |
| skb->dev = dev; |
| arp_queue (skb); |
| return 0; |
| } |
| skb->arp=1; |
| |
| if (skb->len <= 0) |
| return 0; |
| |
| if (el_debug > 2) |
| printk("%s: el_start_xmit(%d)...", dev->name, skb->len); |
| |
| /* Avoid timer-based retransmission conflicts. */ |
| if (set_bit(0, (void*)&dev->tbusy) != 0) |
| 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; |
| |
| el_status.tx_pkt_start = gp_start; |
| el_status.collisions = 0; |
| |
| outb(AX_SYS, AX_CMD); |
| inb(RX_STATUS); |
| inb(TX_STATUS); |
| outb(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. */ |
| dev->trans_start = jiffies; |
| } |
| |
| if (el_debug > 2) |
| printk(" queued xmit.\n"); |
| if (skb->free) |
| kfree_skb (skb, FREE_WRITE); |
| return 0; |
| } |
| |
| |
| /* The typical workload of the driver: |
| Handle the ether interface interrupts. */ |
| static void |
| el_interrupt(int reg_ptr) |
| { |
| int irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2); |
| /*struct device *dev = (struct device *)(irq2dev_map[irq]);*/ |
| struct device *dev = eldev; |
| int axsr; /* Aux. status reg. */ |
| short ioaddr; |
| |
| if (eldev->irq != irq) { |
| printk (EL_NAME ": irq %d for unknown device\n", irq); |
| return; |
| } |
| |
| ioaddr = dev->base_addr; |
| |
| axsr = inb(AX_STATUS); |
| |
| if (el_debug > 3) |
| printk("%s: el_interrupt() aux=%#02x", dev->name, axsr); |
| if (dev->interrupt) |
| printk("%s: Reentering the interrupt driver!\n", dev->name); |
| dev->interrupt = 1; |
| |
| if (dev->tbusy) { |
| int txsr = inb(TX_STATUS); |
| |
| if (el_debug > 6) |
| printk(" txsr=%02x gp=%04x rp=%04x", txsr, inw(GP_LOW), |
| inw(RX_LOW)); |
| |
| if ((axsr & 0x80) && (txsr & TX_READY) == 0) { |
| 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(INET_BH); |
| } else if (txsr & TX_16COLLISIONS) { |
| if (el_debug) |
| printk("%s: Transmit failed 16 times, ethernet jammed?\n", |
| dev->name); |
| outb(AX_SYS, AX_CMD); |
| el_status.stats.tx_aborted_errors++; |
| } else if (txsr & TX_COLLISION) { /* Retrigger xmit. */ |
| if (el_debug > 6) |
| printk(" retransmitting after a collision.\n"); |
| outb(AX_SYS, AX_CMD); |
| outw(el_status.tx_pkt_start, GP_LOW); |
| outb(AX_XMIT, AX_CMD); |
| el_status.stats.collisions++; |
| dev->interrupt = 0; |
| return; |
| } else { |
| el_status.stats.tx_packets++; |
| if (el_debug > 6) |
| printk(" Tx succeeded %s\n", |
| (txsr & TX_RDY) ? "." : "but tx is busy!"); |
| dev->tbusy = 0; |
| mark_bh(INET_BH); |
| } |
| } else { |
| 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. */ |
| if (rxsr & RX_MISSED) |
| el_status.stats.rx_missed_errors++; |
| if (rxsr & RX_RUNT) { /* Handled to avoid board lock-up. */ |
| el_status.stats.rx_length_errors++; |
| if (el_debug > 5) printk(" runt.\n"); |
| } else if (rxsr & RX_GOOD) { |
| el_receive(eldev); |
| } else { /* Nothing? Something is broken! */ |
| if (el_debug > 2) |
| printk("%s: No packet seen, rxsr=%02x **resetting 3c501***\n", |
| dev->name, rxsr); |
| el_reset(eldev); |
| } |
| if (el_debug > 3) |
| printk(".\n"); |
| } |
| |
| outb(AX_RX, AX_CMD); |
| outb(0x00, RX_BUF_CLR); |
| inb(RX_STATUS); /* Be certain that interrupts are cleared. */ |
| inb(TX_STATUS); |
| dev->interrupt = 0; |
| return; |
| } |
| |
| |
| /* We have a good packet. Well, not really "good", just mostly not broken. |
| We must check everything to see if it is good. */ |
| static void |
| el_receive(struct device *dev) |
| { |
| int sksize, pkt_len; |
| struct sk_buff *skb; |
| |
| pkt_len = inw(RX_LOW); |
| |
| if (el_debug > 4) |
| printk(" el_receive %d.\n", pkt_len); |
| |
| if ((pkt_len < 60) || (pkt_len > 1536)) { |
| if (el_debug) |
| printk("%s: bogus packet, length=%d\n", dev->name, pkt_len); |
| el_status.stats.rx_over_errors++; |
| return; |
| } |
| outb(AX_SYS, AX_CMD); |
| |
| sksize = sizeof(struct sk_buff) + pkt_len; |
| skb = alloc_skb(sksize, GFP_ATOMIC); |
| outw(0x00, GP_LOW); |
| if (skb == NULL) { |
| printk("%s: Memory squeeze, dropping packet.\n", dev->name); |
| el_status.stats.rx_dropped++; |
| return; |
| } else { |
| skb->mem_len = sksize; |
| skb->mem_addr = skb; |
| skb->len = pkt_len; |
| skb->dev = dev; |
| |
| insb(DATAPORT, skb->data, pkt_len); |
| |
| #ifdef HAVE_NETIF_RX |
| netif_rx(skb); |
| #else |
| skb->lock = 0; |
| if (dev_rint((unsigned char*)skb, pkt_len, IN_SKBUFF, dev) != 0) { |
| kfree_skbmem(skb, sksize); |
| lp->stats.rx_dropped++; |
| break; |
| } |
| #endif |
| el_status.stats.rx_packets++; |
| } |
| return; |
| } |
| |
| static void |
| el_reset(struct device *dev) |
| { |
| if (el_debug> 2) |
| printk("3c501 reset..."); |
| outb(AX_RESET, AX_CMD); /* Reset the chip */ |
| outb(AX_LOOP, AX_CMD); /* Aux control, irq and loopback enabled */ |
| { |
| int i; |
| for (i = 0; i < 6; i++) /* Set the station address. */ |
| outb(dev->dev_addr[i], el_base + i); |
| } |
| |
| outb(0, RX_BUF_CLR); /* Set rx packet area to 0. */ |
| cli(); /* Avoid glitch on writes to CMD regs */ |
| outb(TX_NORM, TX_CMD); /* tx irq on done, collision */ |
| outb(RX_NORM, RX_CMD); /* Set Rx commands. */ |
| inb(RX_STATUS); /* Clear status. */ |
| inb(TX_STATUS); |
| dev->interrupt = 0; |
| dev->tbusy = 0; |
| sti(); |
| } |
| |
| static int |
| el1_close(struct device *dev) |
| { |
| int ioaddr = dev->base_addr; |
| |
| if (el_debug > 2) |
| printk("%s: Shutting down ethercard at %#x.\n", dev->name, ioaddr); |
| |
| dev->tbusy = 1; |
| dev->start = 0; |
| |
| /* Free and disable the IRQ. */ |
| free_irq(dev->irq); |
| outb(AX_RESET, AX_CMD); /* Reset the chip */ |
| irq2dev_map[dev->irq] = 0; |
| |
| return 0; |
| } |
| |
| static struct enet_statistics * |
| el1_get_stats(struct device *dev) |
| { |
| return &el_status.stats; |
| } |
| |
| #ifdef HAVE_MULTICAST |
| /* Set or clear the multicast filter for this adaptor. |
| num_addrs == -1 Promiscuous mode, receive all packets |
| num_addrs == 0 Normal mode, clear multicast list |
| num_addrs > 0 Multicast mode, receive normal and MC packets, and do |
| best-effort filtering. |
| */ |
| static void |
| set_multicast_list(struct device *dev, int num_addrs, void *addrs) |
| { |
| if (num_addrs > 0) { |
| outb(RX_MULT, RX_CMD); |
| inb(RX_STATUS); /* Clear status. */ |
| } else if (num_addrs < 0) { |
| outb(RX_PROM, RX_CMD); |
| inb(RX_STATUS); |
| } else { |
| outb(RX_NORM, RX_CMD); |
| inb(RX_STATUS); |
| } |
| } |
| #endif |
| |
| /* |
| * Local variables: |
| * compile-command: "gcc -D__KERNEL__ -Wall -Wstrict-prototypes -O6 -fomit-frame-pointer -m486 -c -o 3c501.o 3c501.c" |
| * kept-new-versions: 5 |
| * End: |
| */ |