| /* skeleton.c: A sample network driver core for linux. */ |
| /* |
| 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 Public License as modified by SRC, |
| incorporated herein by reference. |
| |
| The author may be reached as becker@super.org or |
| C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715 |
| |
| This file is an outline for writing a network device driver for the |
| the Linux operating system. |
| |
| To write (or understand) a driver, have a look at the "loopback.c" file to |
| get a feel of what is going on, and then use the code below as a skeleton |
| for the new driver. |
| |
| */ |
| |
| static char *version = |
| "skeleton.c:v0.05 11/16/93 Donald Becker (becker@super.org)\n"; |
| |
| /* Always include 'config.h' first in case the user wants to turn on |
| or override something. */ |
| #include <linux/config.h> |
| |
| /* |
| Sources: |
| List your sources of programming information to document that |
| the driver is your own creation, and give due credit to others |
| that contributed to the work. Remember that GNU project code |
| cannot use proprietary or trade secret information. Interface |
| definitions are generally considered non-copyrightable to the |
| extent that the same names and structures must be used to be |
| compatible. |
| |
| Finally, keep in mind that the Linux kernel is has an API, not |
| ABI. Proprietary object-code-only distributions are not permitted |
| under the GPL. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/types.h> |
| #include <linux/fcntl.h> |
| #include <linux/interrupt.h> |
| #include <linux/ptrace.h> |
| #include <linux/ioport.h> |
| #include <linux/in.h> |
| #include <linux/malloc.h> |
| #include <linux/string.h> |
| #include <asm/system.h> |
| #include <asm/bitops.h> |
| #include <asm/io.h> |
| #include <asm/dma.h> |
| #include <errno.h> |
| |
| #include "dev.h" |
| #include "iow.h" |
| #include "eth.h" |
| #include "skbuff.h" |
| #include "arp.h" |
| |
| #ifndef HAVE_AUTOIRQ |
| /* From auto_irq.c, in ioport.h for later versions. */ |
| extern void autoirq_setup(int waittime); |
| extern int autoirq_report(int waittime); |
| /* The map from IRQ number (as passed to the interrupt handler) to |
| 'struct device'. */ |
| 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 |
| |
| #ifndef HAVE_PORTRESERVE |
| #define check_region(ioaddr, size) 0 |
| #define snarf_region(ioaddr, size); do ; while (0) |
| #endif |
| |
| /* use 0 for production, 1 for verification, >2 for debug */ |
| #ifndef NET_DEBUG |
| #define NET_DEBUG 2 |
| #endif |
| static unsigned int net_debug = NET_DEBUG; |
| |
| /* Information that need to be kept for each board. */ |
| struct net_local { |
| struct enet_statistics stats; |
| long open_time; /* Useless example local info. */ |
| }; |
| |
| /* The number of low I/O ports used by the ethercard. */ |
| #define ETHERCARD_TOTAL_SIZE 16 |
| |
| /* The station (ethernet) address prefix, used for IDing the board. */ |
| #define SA_ADDR0 0x00 |
| #define SA_ADDR1 0x42 |
| #define SA_ADDR2 0x65 |
| |
| /* Index to functions, as function prototypes. */ |
| |
| extern int netcard_probe(struct device *dev); |
| |
| static int netcard_probe1(struct device *dev, short ioaddr); |
| static int net_open(struct device *dev); |
| static int net_send_packet(struct sk_buff *skb, struct device *dev); |
| static void net_interrupt(int reg_ptr); |
| static void net_rx(struct device *dev); |
| static int net_close(struct device *dev); |
| static struct enet_statistics *net_get_stats(struct device *dev); |
| #ifdef HAVE_MULTICAST |
| static void set_multicast_list(struct device *dev, int num_addrs, void *addrs); |
| #endif |
| |
| /* Example routines you must write ;->. */ |
| #define tx_done(dev) 1 |
| extern void hardware_send_packet(short ioaddr, char *buf, int length); |
| extern void chipset_init(struct device *dev, int startp); |
| |
| |
| /* Check for a network adaptor of this type, and return '0' iff one exists. |
| If dev->base_addr == 0, probe all likely locations. |
| If dev->base_addr == 1, always return failure. |
| If dev->base_addr == 2, alloate space for the device and return success |
| (detachable devices only). |
| */ |
| int |
| netcard_probe(struct device *dev) |
| { |
| int *port, ports[] = {0x300, 0x280, 0}; |
| int base_addr = dev->base_addr; |
| |
| if (base_addr > 0x1ff) /* Check a single specified location. */ |
| return netcard_probe1(dev, base_addr); |
| else if (base_addr > 0) /* Don't probe at all. */ |
| return ENXIO; |
| |
| for (port = &ports[0]; *port; port++) { |
| int ioaddr = *port; |
| if (check_region(ioaddr, ETHERCARD_TOTAL_SIZE)) |
| continue; |
| if (inb(ioaddr) != 0x57) |
| continue; |
| dev->base_addr = ioaddr; |
| if (netcard_probe1(dev, ioaddr) == 0) |
| return 0; |
| } |
| |
| dev->base_addr = base_addr; |
| return ENODEV; |
| } |
| |
| int netcard_probe1(struct device *dev, short ioaddr) |
| { |
| unsigned char station_addr[6]; |
| int i; |
| |
| /* Read the station address PROM. */ |
| for (i = 0; i < 6; i++) { |
| station_addr[i] = inb(ioaddr + i); |
| } |
| /* Check the first three octets of the S.A. for the manufactor's code. */ |
| if (station_addr[0] != SA_ADDR0 |
| || station_addr[1] != SA_ADDR1 || station_addr[2] != SA_ADDR2) { |
| return ENODEV; |
| } |
| |
| printk("%s: %s found at %#3x, IRQ %d.\n", dev->name, |
| "network card", dev->base_addr, dev->irq); |
| |
| #ifdef jumpered_interrupts |
| /* If this board has jumpered interrupts, snarf the interrupt vector |
| now. There is no point in waiting since no other device can use |
| the interrupt, and this marks the 'irqaction' as busy. */ |
| |
| if (dev->irq == -1) |
| ; /* Do nothing: a user-level program will set it. */ |
| else if (dev->irq < 2) { /* "Auto-IRQ" */ |
| autoirq_setup(0); |
| /* Trigger an interrupt here. */ |
| |
| dev->irq = autoirq_report(0); |
| if (net_debug >= 2) |
| printk(" autoirq is %d", dev->irq); |
| } else if (dev->irq == 2) |
| /* Fixup for users that don't know that IRQ 2 is really IRQ 9, |
| or don't know which one to set. */ |
| dev->irq = 9; |
| |
| { int irqval = request_irq(dev->irq, &net_interrupt); |
| if (irqval) { |
| printk ("%s: unable to get IRQ %d (irqval=%d).\n", dev->name, |
| dev->irq, irqval); |
| return EAGAIN; |
| } |
| } |
| #endif /* jumpered interrupt */ |
| |
| /* Grab the region so we can find another board if autoIRQ fails. */ |
| snarf_region(ioaddr, ETHERCARD_TOTAL_SIZE); |
| |
| if (net_debug) |
| printk(version); |
| |
| /* Initialize the device structure. */ |
| dev->priv = kmalloc(sizeof(struct net_local), GFP_KERNEL); |
| memset(dev->priv, 0, sizeof(struct net_local)); |
| |
| dev->open = net_open; |
| dev->stop = net_close; |
| dev->hard_start_xmit = net_send_packet; |
| dev->get_stats = net_get_stats; |
| #ifdef HAVE_MULTICAST |
| dev->set_multicast_list = &set_multicast_list; |
| #endif |
| |
| /* Fill in the fields of the device structure with ethernet-generic values. |
| This should be in a common file instead of per-driver. */ |
| 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. This is called (in the current kernel) |
| sometime after booting when the 'ifconfig' program is run. |
| |
| This routine should set everything up anew at each open, even |
| registers that "should" only need to be set once at boot, so that |
| there is non-reboot way to recover if something goes wrong. |
| */ |
| static int |
| net_open(struct device *dev) |
| { |
| struct net_local *lp = (struct net_local *)dev->priv; |
| int ioaddr = dev->base_addr; |
| |
| /* This is used if the interrupt line can turned off (shared). |
| See 3c503.c for an example of selecting the IRQ at config-time. */ |
| if (request_irq(dev->irq, &net_interrupt)) { |
| return -EAGAIN; |
| } |
| |
| |
| /* Always snarf a DMA channel after the IRQ. */ |
| if (request_dma(dev->dma)) { |
| free_irq(dev->irq); |
| return -EAGAIN; |
| } |
| irq2dev_map[dev->irq] = dev; |
| |
| /* Reset the hardware here. */ |
| /*chipset_init(dev, 1);*/ |
| outb(0x00, ioaddr); |
| lp->open_time = jiffies; |
| |
| dev->tbusy = 0; |
| dev->interrupt = 0; |
| dev->start = 1; |
| return 0; |
| } |
| |
| static int |
| net_send_packet(struct sk_buff *skb, struct device *dev) |
| { |
| struct net_local *lp = (struct net_local *)dev->priv; |
| int ioaddr = dev->base_addr; |
| |
| if (dev->tbusy) { |
| /* If we get here, some higher level has decided we are broken. |
| There should really be a "kick me" function call instead. */ |
| int tickssofar = jiffies - dev->trans_start; |
| if (tickssofar < 5) |
| return 1; |
| printk("%s: transmit timed out, %s?\n", dev->name, |
| tx_done(dev) ? "IRQ conflict" : "network cable problem"); |
| /* Try to restart the adaptor. */ |
| chipset_init(dev, 1); |
| dev->tbusy=0; |
| dev->trans_start = jiffies; |
| } |
| |
| /* If some higher layer thinks we've missed an tx-done interrupt |
| we are passed NULL. Caution: dev_tint() handles the cli()/sti() |
| itself. */ |
| if (skb == NULL) { |
| dev_tint(dev); |
| return 0; |
| } |
| |
| /* For ethernet, fill in the header. This should really be done by a |
| higher level, rather than duplicated for each ethernet adaptor. */ |
| if (!skb->arp && dev->rebuild_header(skb+1, dev)) { |
| skb->dev = dev; |
| arp_queue (skb); |
| return 0; |
| } |
| skb->arp=1; |
| |
| /* Block a timer-based transmit from overlapping. This could better be |
| done with atomic_swap(1, dev->tbusy), but set_bit() works as well. */ |
| if (set_bit(0, (void*)&dev->tbusy) != 0) |
| printk("%s: Transmitter access conflict.\n", dev->name); |
| else { |
| short length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; |
| unsigned char *buf = (void *)(skb+1); |
| |
| hardware_send_packet(ioaddr, buf, length); |
| dev->trans_start = jiffies; |
| } |
| if (skb->free) |
| kfree_skb (skb, FREE_WRITE); |
| |
| /* You might need to clean up and record Tx statistics here. */ |
| if (inw(ioaddr) == /*RU*/81) |
| lp->stats.tx_aborted_errors++; |
| |
| return 0; |
| } |
| |
| /* The typical workload of the driver: |
| Handle the network interface interrupts. */ |
| static void |
| net_interrupt(int reg_ptr) |
| { |
| int irq = -(((struct pt_regs *)reg_ptr)->orig_eax+2); |
| struct device *dev = (struct device *)(irq2dev_map[irq]); |
| struct net_local *lp; |
| int ioaddr, status, boguscount = 0; |
| |
| if (dev == NULL) { |
| printk ("net_interrupt(): irq %d for unknown device.\n", irq); |
| return; |
| } |
| dev->interrupt = 1; |
| |
| ioaddr = dev->base_addr; |
| lp = (struct net_local *)dev->priv; |
| status = inw(ioaddr + 0); |
| |
| do { |
| if (status /*& RX_INTR*/) { |
| /* Got a packet(s). */ |
| net_rx(dev); |
| } |
| if (status /*& TX_INTR*/) { |
| lp->stats.tx_packets++; |
| dev->tbusy = 0; |
| mark_bh(INET_BH); /* Inform upper layers. */ |
| } |
| if (status /*& COUNTERS_INTR*/) { |
| /* Increment the appropriate 'localstats' field. */ |
| lp->stats.tx_window_errors++; |
| } |
| } while (++boguscount < 20) ; |
| |
| return; |
| } |
| |
| /* We have a good packet(s), get it/them out of the buffers. */ |
| static void |
| net_rx(struct device *dev) |
| { |
| struct net_local *lp = (struct net_local *)dev->priv; |
| int ioaddr = dev->base_addr; |
| int boguscount = 10; |
| |
| do { |
| int status = inw(ioaddr); |
| int pkt_len = inw(ioaddr); |
| |
| if (pkt_len == 0) /* Read all the frames? */ |
| break; /* Done for now */ |
| |
| if (status & 0x40) { /* There was an error. */ |
| lp->stats.rx_errors++; |
| if (status & 0x20) lp->stats.rx_frame_errors++; |
| if (status & 0x10) lp->stats.rx_over_errors++; |
| if (status & 0x08) lp->stats.rx_crc_errors++; |
| if (status & 0x04) lp->stats.rx_fifo_errors++; |
| } else { |
| /* Malloc up new buffer. */ |
| int sksize = sizeof(struct sk_buff) + pkt_len; |
| struct sk_buff *skb; |
| |
| skb = alloc_skb(sksize, GFP_ATOMIC); |
| if (skb == NULL) { |
| printk("%s: Memory squeeze, dropping packet.\n", dev->name); |
| lp->stats.rx_dropped++; |
| break; |
| } |
| skb->mem_len = sksize; |
| skb->mem_addr = skb; |
| skb->len = pkt_len; |
| skb->dev = dev; |
| |
| /* 'skb+1' points to the start of sk_buff data area. */ |
| memcpy((unsigned char *) (skb + 1), (void*)dev->rmem_start, |
| pkt_len); |
| /* or */ |
| port_read(ioaddr, (void *)(skb+1), (pkt_len + 1) >> 1); |
| |
| #ifdef HAVE_NETIF_RX |
| netif_rx(skb); |
| #else |
| skb->lock = 0; |
| if (dev_rint((unsigned char*)skb, pkt_len, IN_SKBUFF, dev) != 0) { |
| kfree_s(skb, sksize); |
| lp->stats.rx_dropped++; |
| break; |
| } |
| #endif |
| lp->stats.rx_packets++; |
| } |
| } while (--boguscount); |
| |
| /* If any worth-while packets have been received, dev_rint() |
| has done a mark_bh(INET_BH) for us and will work on them |
| when we get to the bottom-half routine. */ |
| return; |
| } |
| |
| /* The inverse routine to net_open(). */ |
| static int |
| net_close(struct device *dev) |
| { |
| struct net_local *lp = (struct net_local *)dev->priv; |
| int ioaddr = dev->base_addr; |
| |
| lp->open_time = 0; |
| |
| dev->tbusy = 1; |
| dev->start = 0; |
| |
| /* Flush the Tx and disable Rx here. */ |
| |
| disable_dma(dev->dma); |
| |
| /* If not IRQ or DMA jumpered, free up the line. */ |
| outw(0x00, ioaddr+0); /* Release the physical interrupt line. */ |
| |
| free_irq(dev->irq); |
| free_dma(dev->dma); |
| |
| irq2dev_map[dev->irq] = 0; |
| |
| /* Update the statistics here. */ |
| |
| return 0; |
| |
| } |
| |
| /* Get the current statistics. This may be called with the card open or |
| closed. */ |
| static struct enet_statistics * |
| net_get_stats(struct device *dev) |
| { |
| struct net_local *lp = (struct net_local *)dev->priv; |
| short ioaddr = dev->base_addr; |
| |
| cli(); |
| /* Update the statistics from the device registers. */ |
| lp->stats.rx_missed_errors = inw(ioaddr+1); |
| sti(); |
| |
| return &lp->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) |
| { |
| short ioaddr = dev->base_addr; |
| if (num_addrs) { |
| outw(69, ioaddr); /* Enable promiscuous mode */ |
| } else |
| outw(99, ioaddr); /* Disable promiscuous mode, use normal mode */ |
| } |
| #endif |
| |
| /* |
| * Local variables: |
| * compile-command: "gcc -D__KERNEL__ -I/usr/src/linux/net/inet -Wall -Wstrict-prototypes -O6 -m486 -c skeleton.c" |
| * version-control: t |
| * kept-new-versions: 5 |
| * tab-width: 4 |
| * End: |
| */ |