This section provides a simple description of how to write a low-level,
hardware dependent ethernet driver.
There is a high-level driver (which is only code — with no state of
its own) that is part of the stack. There will be one or more low-level
drivers tied to the actual network hardware. Each of these drivers
contains one or more driver instances. The intent is that the
low-level drivers know nothing of the details of the stack that will be
using them. Thus, the same driver can be used by the
eCos
supported
TCP/IP
stack,
RedBoot,
or any other, with no changes.
A driver instance is contained within a
struct eth_drv_sc:
struct eth_hwr_funs {
// Initialize hardware (including startup)
void (*start)(struct eth_drv_sc *sc,
unsigned char *enaddr,
int flags);
// Shut down hardware
void (*stop)(struct eth_drv_sc *sc);
// Device control (ioctl pass-thru)
int (*control)(struct eth_drv_sc *sc,
unsigned long key,
void *data,
int data_length);
// Query - can a packet be sent?
int (*can_send)(struct eth_drv_sc *sc);
// Send a packet of data
void (*send)(struct eth_drv_sc *sc,
struct eth_drv_sg *sg_list,
int sg_len,
int total_len,
unsigned long key);
// Receive [unload] a packet of data
void (*recv)(struct eth_drv_sc *sc,
struct eth_drv_sg *sg_list,
int sg_len);
// Deliver data to/from device from/to stack memory space
// (moves lots of memcpy()s out of DSRs into thread)
void (*deliver)(struct eth_drv_sc *sc);
// Poll for interrupts/device service
void (*poll)(struct eth_drv_sc *sc);
// Get interrupt information from hardware driver
int (*int_vector)(struct eth_drv_sc *sc);
// Logical driver interface
struct eth_drv_funs *eth_drv, *eth_drv_old;
};
struct eth_drv_sc {
struct eth_hwr_funs *funs;
void *driver_private;
const char *dev_name;
int state;
struct arpcom sc_arpcom; /* ethernet common */
};
Note: If you have two instances of the same hardware, you only need one
struct eth_hwr_funs shared between them.
There is another structure which is used to communicate with the rest of
the stack:
Your driver does not create an instance of this
structure. It is provided for driver code to use in the
eth_drv member of the function record.
Its usage is described below in the Section called Upper Layer Functions
One more function completes the API with which your driver communicates
with the rest of the stack:
This function is designed so that it can be registered as the DSR for your
interrupt handler. It will awaken the
“Network Delivery Thread”
to call your deliver routine. See the Section called Deliver function.
You create an instance of struct eth_drv_sc
using the
ETH_DRV_SC()
macro which
sets up the structure, including the prototypes for the functions, etc.
By doing things this way, if the internal design of the ethernet drivers
changes (e.g. we need to add a new low-level implementation function),
existing drivers will no longer compile until updated. This is much
better than to have all of the definitions in the low-level drivers
themselves and have them be (quietly) broken if the interfaces change.
The “magic”
which gets the drivers started (and indeed, linked) is
similar to what is used for the I/O subsystem.
This is done using the
NETDEVTAB_ENTRY()
macro, which defines an initialization function
and the basic data structures for the low-level driver.
The device_instance
entry here would point to the struct eth_drv_sc
entry previously defined. This allows the network driver
setup to work with any class of driver, not just ethernet drivers. In
the future, there will surely be serial PPP
drivers, etc. These will
use the NETDEVTAB_ENTRY()
setup to create the basic driver, but they will
most likely be built on top of other high-level device driver layers.
To instantiate itself, and connect it to the system,
a hardware driver will have a template
(boilerplate) which looks something like this:
#include <cyg/infra/cyg_type.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/infra/diag.h>
#include <cyg/hal/drv_api.h>
#include <cyg/io/eth/netdev.h>
#include <cyg/io/eth/eth_drv.h>
ETH_DRV_SC(DRV_sc,
0, // No driver specific data needed
"eth0", // Name for this interface
HRDWR_start,
HRDWR_stop,
HRDWR_control,
HRDWR_can_send
HRDWR_send,
HRDWR_recv,
HRDWR_deliver,
HRDWR_poll,
HRDWR_int_vector
);
NETDEVTAB_ENTRY(DRV_netdev,
"DRV",
DRV_HRDWR_init,
&DRV_sc);
This, along with the referenced functions, completely define the driver.
Note: If one needed the same low-level driver to handle
multiple similar hardware interfaces, you would need multiple invocations
of the
ETH_DRV_SC()/NETDEVTAB_ENTRY()
macros. You would add a pointer
to some instance specific data, e.g. containing base addresses, interrupt
numbers, etc, where the