Porting the NE2000 driver to your OS | |
03-03-98 Introduction |
|
In order to be fully portable
with multiple operating systems, each with it's own style of device drivers, the NE2000
driver has been implemented as a collection of C procedures. These can be encapsulated in
any OS and should be fully portable. There are a few exceptions, such as assuming
that we have direct access to the I/O ports as well as a few procedures and variables that
must be defined in your OS. If you have any questions or comments, please feel free
to contact me. The NE2000 card is built using the DP8390 chipset by National
Semiconductor. For complete theory of operation please see the data sheets and
application notes they have available.
|
|
Included Files |
|
The code includes several header
files and one .c file. The ne2000.c file implements the actual code that drives the
network interface controller (NIC). It has a corresponding ne2000.h that is it's
header file. You should not include this as it is only for the implementation.
The rest of the .h files are for the interface and should be included. One important
thing to remember is that if you are using a C++ compiler you must #define __CPP_BINDING before you #include "ne2k.h" so that the symbols are defined using "C" linkage symbols. Here is a brief description of each file:
|
|
Constants |
|
The constants are defined in both
ne2k.h and ne2000.h. Most of them you won't need to worry about,
but they are all explained here: ne2k.h:
ne2000.h:
The rest of the constants are for I/O address offsets for the NE2000 card. There are also some that specify what the the registers are and what the different bits signify. See ne2000.h and the data sheets for more details.
|
|
Important Data Structures |
|
There are some data structures
that you will need to know about, and a few that are optional. I will outline them
all here.
|
|
snic | |
struct snic { int iobase; int pstart, pstop, wordlength, current_page; nic_stat stat; void (*notify)(packet_buffer *newpacket); packet_buffer tx_packet[MAX_TX], *last_tx; char busy, send, sending; }; This is the most important struct of them all. This represents your actual hardware card. For each NE2000 that is present, you need one of these. This enables you to support multiple cards with the same driver, you just pass different snic structures. One important data member is iobase. This must be set to NULL for an uninitialized card. This has to be done explicitly or the resultant behavior is unpredictable. That is the only data member that you have to deal with, are only used by the driver. The rest are as follows:
|
|
packet_buffer | |
struct packet_buffer { uint len; uint page; uint count; packet_data *buf; }; This is the structure that you will be using when you want to send a packet over the network. This supports that scatter gather by allowing each layer to simply add a packet_data to the packet_buffer.
|
|
packet_data | |
struct packet_data { uint len; char *ptr; }; This structure contains the actual data of the packet. This is also the structure that will be sent to you when we receive a notification. That is, when your notify() procedure is triggered.
|
|
nic_stat | |
struct nic_stat { long rx_packets; long tx_buffered, tx_packets; nic_error_stat errors; }; All of the statistics that the driver keeps track of are contained here
|
|
nic_error_stat | |
struct nic_error_stat { long frame_alignment, crc, missed_packets; long rx, rx_size, rx_dropped, rx_fifo, rx_overruns; long tx_collisions; long tx_aborts, tx_carrier, tx_fifo, tx_heartbeat, tx_window; }; All error statistics are tallied here. Remember to call nic_get_stats() instead of reading directly from the snic. This is so that it can be updated and contain valid data.
|
|
buffer_header | |
struct buffer_header { unsigned char status; unsigned char next; unsigned short count; /* length of header and packet */ }; The header for a buffer in the local on-card buffer conforms to this format.
|
|
Interface Procedures |
|
These are the procedures that act
as an interface to the nic. How they are used and any assumptions they make are
described below.
|
|
int nic_detect(int given); |
|
The use of this function is
optional. If you wish to use, or even allow auto-probing of I/O ports then you can
use this to detect an NE2000 card in your system. This may return a false positive
on other cards that use the DP8309 chipset. nic_init()
will however confirm the PROM signature and verify that is actually dealing with an NE2000
or clone. You can pass an I/O address to try as given, but it will also try some
common default addresses. It will return the I/O base of the card if one is found or
else ERRNOTFOUND if it doesn't find one. The default I/O address
probed are 0x300, 0x280, 0x320, 0x340,
and 0x360. Note, this will only find the first NE2000 card on the
system, there are no provisions to detect multiple cards on a system.
|
|
int nic_init(snic* nic, int addr, unsigned char *prom, unsigned char *manual); |
|
When calling this to initialize
the card make sure that nic->iobase is set to NULL! addr should
be set to the I/O base of the card you are initializing, such as what you received from
calling nic_detect(). Before calling this you must allocate an array of
length LEN_PROM to pass as prom. The nic_init()
will copy the contents of the PROM into this buffer for the convenience of the upper
layers. You should set manual to NULL. We return NOERR
if successful, or ERRNOTFOUND if the signature in PROM does not
match the 0x57 of an NE2000 or clone.
|
|
void nic_register_notify(snic *nic, void (*newnotify)(packet_data *newpacket)) |
|
Call this with an initialized snic
and a function pointer to your notify() in order to register it as the event
handler the driver will call upon reception of a packet off the network. Note, this
passes a packet_data, and not a packet_buffer.
|
|
void nic_start(snic *nic, int promiscuous); |
|
After the card is initialized
with nic_init() it is fully prepared to receive and send packets, but is not
actually started. In order to start the card up just call nic_start().
Pass it a 1 for promiscuous to receive all packets on the network, both
broadcast and normal packets sent to any physical address; otherwise set it to 0.
Normal mode of operation is not promiscuous.
|
|
void nic_stop(snic *nic); |
|
Use this to stop the card from
receiving packets. Untested.
|
|
void nic_isr(snic *nic); |
|
Your OS must provide the
functionality for acknowledging an IRQ. When it gets the appropriate IRQ for the
NIC, simply call nic_isr(). That's it!
|
|
nic_stat nic_get_stats(snic *nic); |
|
Call this to get current and
up-to-date statistics on the card.
|
|
void nic_stat_clear(nic_stat *that); |
|
Use this to clear the statistics
for the card and reset all fields to zero. Call it in the following manner:nic_stat_clear(nic->stat);
|
|
int nic_send_packet(snic *nic, packet_buffer*); |
|
After preparing a valid packet_buffer
you can use this function to transmit it. It will buffer the data and trigger the
transmitter on the card. Since the driver uses more than one transmission buffer
(see MAX_TX) you can immediately start sending another packet to the card with
the FIFO while it is transmitting the first one over the physical media. If the
packet you send is longer than MAX_LENGTH it will return ERRTOOLONG.
If the packet is too short it will automatically pad it with 0s. If someone
is currently using the DMA to send a packet then nic->busy is set as a
semaphore and will time out by TIMEOUT_TX if the transmitter is stalled and
return an ERRTIMEOUT. You may wish to try to reinitialize
the card if this occurs. If nic_send() fails we return ERR.
It will also spin on a semaphore if someone else is using nic_send_packet(),
if it times out then it will return ERRTIMEOUT.
|
|
Provided Procedures |
|
Your must provide these procedures
yourself. They are required by the driver implementation. They are declared in
ne2k.h.
|
|
packet_buffer *alloc_buffer(uint count); |
|
The driver will call this whenever
it needs a buffer.
|
|
packet_data *alloc_buffer_data(uint size); |
|
The driver will call this
whenever it needs a simple buffer of packet_data. It could be called from nic_rx().
Now, nic_rx() is called from nic_isr() which is the interrupt
handler. Because of this you may not wish to use dynamic memory allocation from
within an ISR and instead use a pool of available buffers or some other means. The
method chosen is up to you. You must make available a memory area of size size
and set packet_data->ptr to point to it.
|
|
void free_buffer(packet_buffer *ptr); |
|
Simply have this deallocate the
buffer or mark it as available or whatever your implementation does. It should also
go through and free all of the packet_data's contained within the packet_buffer.
|
|
void free_buffer_data(packet_data *ptr); |
|
Simply have this deallocate the
buffer or mark it as available or whatever your implementation does.
|
|
void cleanup_buffer(packet_buffer *ptr); |
|
This should go through and delete
of otherwise cleanup the packet_datas within the packet_buffer. It
should not, however, delete the packet_buffer passed to it. This must
remain available to the driver. Technical note: The driver core maintains an array of packets currently being transmitted. When one is finished being send over the wire a nic_tx interrupt is generated. If it needs to retransmit, then the packet is available otherwise it cleans up the buffer. Because the buffer is in an array this proc is required.
|
|
void notify(packet_data *ptr); |
|
Register this as the event handler
by calling nic_register_notify(). Whenever the card receives a packet it
packages it up and passes it on to this function. You can do what you wish to the
packet after you receive it, but remember that you control the buffer's scope. That
means you have to free up the buffer by calling free_buffer() when you are done
with it.
|
|
Implementation Procedures |
|
These procedures are declared in ne2000.h
and are only used by the driver implementation. Thus, you don't have
to worry about them.
|
|
int nic_probe(int addr); |
|
nic_detect() calls this
to probe an addr in I/O space for a DP8390 card.
|
|
int nic_dump_prom(snic *nic, unsigned char *prom); |
|
This dumps the PROM
on the card into the provided buffer. It returns the wordlength of the card's bus.
|
|
void nic_overrun(snic *nic); |
|
If the circular local receive
buffer overruns, then an interrupt is generated which calls nic_overrun().
We have to fix it in the exact way that National Semiconductor specifies in the
datasheets. The National Semiconductor datasheets say to wait at least 1.6ms before
kicking the card.
|
|
void nic_tx(snic *nic); |
|
This interrupt handler is called
when the transmitter on the card has finished putting the packet over the network.
It calls cleanup_buffer() on the finished one and then looks to see if any other
packets have been buffered and are ready to send. If so, it starts the transmitter
up again. In this way we can achieve higher bandwidth.
|
|
void nic_tx_err(snic *nic); |
|
If an error occurs while the NIC
is attempting to transmit a packet this interrupt is generated. We simply tally up
the error and if the packet was aborted, we attempt to send it again.
|
|
void nic_rx(snic *nic); |
|
When the NIC receives a packet off
the network it generates this interrupt and we package it up using alloc_buffer_data()
and hand it off to the upper layers by calling nic->notify().
|
|
void nic_counters(snic *nic); |
|
When the card's counter tallies
overflow it generates this interrupt which clears the card's registers and updates the
drivers counters. This is also called when the user asks for current statistics with
nic_get_stats().
|
|
void nic_get_header(snic *nic, uint page, buffer_header *header); |
|
The driver uses this function to
read the header from a chain of pages in the local buffer.
|
|
int nic_send(snic *nic, uint buf); |
|
This is used exclusively by the
core driver and will trigger the card's transmitter to start once the buffer has been sent
to the card using the DMA. It can return either NOERR or ERRTIMEOUT.
|
|
void nic_block_input(snic *nic, unsigned char *buf, uint len, uint offset); |
|
A general purpose procedure used
to read from the local memory using the card's DMA.
|
|
void nic_block_output(snic *nic, packet_buffer *pkt); |
|
This is comparable to nic_block_input()
except it writes instead of reading and can only be used at page boundaries. This is
responsible for padding packets that are too short
|
|
External Procedures / Variables |
|
These are procedures or variables
that must be provided by your OS or the encapsulating driver.
|
|
extern void kprintf(int prio, char *, ...); |
|
The driver calls this in order to
display error or debugging information. You do not have to actually display this
information. Instead you could send it to a log file or just ignore it.
|
|
extern void idle(); |
|
Whenever the driver is spinning on
a semaphore it calls idle() so the OS can switch tasks and do something useful
during that time.
|
|
extern unsigned long ticks; |
|
A global variable that the driver
assumes is being incremented by the timer interrupt. This is used for timeouts and
no check is made for overflowing. The length of a tick in time is unspecified.
Work need to be done to fix this situation.
|
|
Conclusion |
|
This has been an attempt to fully
document my NE2000 driver. Hopefully you should be able to port this to any
operating system with minimal difficulties. This code was originally written for my
own operating system, see http://www. If you have any questions
or comments I can be reached at site@xnet.com or drarmstr@uiuc.edu. Good luck!
|
|
© Copyright 1997, Douglas Armstrong. All rights reserved. There are no warranties stated or implied. I can not be held responsible if this document or driver cause any damages. |