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:

  • ne2000.c - The actual code for the driver
  • ne2000.h - The implementation header file (don't include this)
  • ne2k.h - The interface header.  This contains the procedures that you can call as well as the data structures and constants that you will need.
  • err.h - This includes all error codes and defines the return type.  If you choose to use C++ it has a nice class that does some nice operator overloading for you to use.

 


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:

  • LEN_ADDR - The length of the physical address of the card in bytes.
  • MAX_TX - The number of buffers to use for transmission.
  • MAX_PAGESPERPACKET - The maximum pages that can be used for one buffer.
  • TXPAGES - The number of pages reserved for transmission in the local buffer.
  • LEN_PROM - The length of the PROM on the card.
  • LEN_HEADER - The length of the Ethernet frame header

ne2000.h:

  • PSTART - The first page to be used for the circular buffer.
  • PSTOP - The last page to be used.
  • PSTARTW - The first page to be used if the card has a 16-bit bus.
  • PSTOPW - The last page if the card has an 16-bit wordlength.
  • MAX_LOAD - Maximum things to do in one ISR call per IRQ.
  • MAX_RX - Maximum pages to receive per call to nic_rx().
  • MIN_LENGTH - The minimum length of one packet.
  • MAX_LENGTH - The maximum length a packet can be in bytes.
  • TIMEOUT_DMAMATCH - Timeout for the DMA address mismatch checks.
  • TIMEOUT_TX - Timeout for the transmitter.

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:

  • iobase - The base I/O address of the NIC.
  • pstart - The first page in the circular receive buffer on the card.
  • pstop - The last page in the local circular buffer.
  • wordlength - 1 if the card has an 8-bit bus, 2 if it's 16-bit.
  • current_page - The current page in the local on-card buffer.
  • stat - Statistics for this card.
  • notify - A pointer to the event handler that you register in nic_register_notify().
  • tx_packet[] - An array of packet buffers used for transmission.
  • last_tx - A pointer to the transmission buffer.
  • busy - A semaphore for the nic_send_packet().   Note, there is a timeout.
  • send - An index in the tx_packet[] of the buffer currently being transmitted by the card.
  • sending - Marks if the NIC is sending or not

 

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.

  • len - The length of the data in this buffer.  You don't have to calculate this.
  • page - The page on the local buffer that corresponds with this buffer.  You don't have to worry about this.
  • count - The number of packet_data segments.
  • buf - An array of packet_data segments.

 

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.

  • len - The length of the data in this buffer.
  • ptr - The actual data.

 

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

  • rx_packets - Total number of received packets.
  • tx_buffered - Total number of packets that were buffered to be transmitted.
  • tx_packets - Total number of transmitted packets.
  • errors - A data structure that contains error statistics.

 

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.