521 lines
14 KiB
C
521 lines
14 KiB
C
|
/* 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 "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->data, 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 = skb->data;
|
|||
|
|
|||
|
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->data' points to the start of sk_buff data area. */
|
|||
|
memcpy(skb->data, (void*)dev->rmem_start,
|
|||
|
pkt_len);
|
|||
|
/* or */
|
|||
|
insw(ioaddr, skb->data, (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:
|
|||
|
*/
|