A device driver is nothing more than a
named entity that supports the basic I/O functions - read, write, get
config, and set config. Typically a device driver also uses and
manages interrupts from the device. While the interface is generic and
device driver independent, the actual driver implementation is
completely up to the device driver designer.
That said, the reason for using a device driver is to provide
access to a device from application code in as general purpose a
fashion as reasonable. Most driver writers are also concerned with
making this access as simple as possible while being as efficient
as possible.
Most device drivers are concerned with the movement of information,
for example data bytes along a serial interface, or packets in a
network. In order to make the most efficient use of system resources,
interrupts are used. This will allow other application processing
to take place while the data transfers are under way, with interrupts
used to indicate when various events have occurred. For example,
a serial port typically generates an interrupt after a character
has been sent “down the wire” and the interface
is ready for another. It makes sense to allow further application
processing while the data is being sent since this can take quite
a long time. The interrupt can be used to allow the driver to send
a character as soon as the current one is complete, without any
active participation by the application code.
The main building blocks for device drivers are found in the
include file: <cyg/io/devtab.h>
All device drivers in eCos are described
by a device table entry, using the cyg_devtab_entry_t type.
The entry should be created using the DEVTAB_ENTRY() macro,
like this:
For a layered device, the "C" string name of the
device this device is built upon.
handlers
A pointer to the I/O function "handlers" (see below).
init
A function called when eCos is initialized. This
function can query the device, setup hardware, etc.
lookup
A function called when cyg_io_lookup() is called
for this device.
priv
A placeholder for any device specific data
required by the driver.
The interface to the driver is through the handlers field. This is a pointer to
a set of functions which implement the various cyg_io_XXX()
routines. This table is defined by the macro:
The function called as a result of
cyg_io_write().
read
The function called as a result of
cyg_io_read().
get_config
The function called as a result of
cyg_io_get_config().
set_config
The function called as a result of
cyg_io_set_config().
When eCos is initialized (sometimes called
“boot” time), the init() function is called
for all devices in the system. The init() function is
allowed to return an error in which case the device will be placed
“off line” and all I/O requests to that device will be
considered in error.
The lookup() function is called whenever
the cyg_io_lookup() function
is called with this device name. The lookup function may cause the device
to come “on line” which would then allow I/O
operations to proceed. Future versions of the I/O system
will allow for other states, including power saving modes,
etc.
The standard serial driver supplied with
eCos is structured as a hardware independent
portion and a hardware dependent interface module. To add support for
a new serial port, the user should be able to use the existing
hardware independent portion and just add their own interface driver which handles the details of the
actual device. The user should have no need to change the hardware
independent portion.
The interfaces used by the serial driver and serial implementation
modules are contained in the file <cyg/io/serial.h>
Note: In the sections below we use the notation <<xx>> to
mean a module specific value, referred to as “xx” below.
Each serial device must have a “serial channel”.
This is a set of data which describes all operations on the device.
It also contains buffers, etc., if the device is to be buffered.
The serial channel is created by the macro:
bool (*putc)(serial_channel *priv, unsigned char
c)
This function sends one character to the interface. It should
return true if the character is actually consumed. It should
return false if there is no space in the interface
getc
unsigned char (*getc)(serial_channel *priv)
This function fetches one character from the interface. It will
be only called in a non-interrupt driven mode, thus it should
wait for a character by polling the device until ready.
This function is used to configure the port. It should return
true if the hardware is updated to match the desired
configuration. It should return false if the port cannot
support some parameter specified by the given
configuration. E.g. selecting 1.5 stop bits and 8 data bits is
invalid for most serial devices and should not be allowed.
start_xmit
void (*start_xmit)(serial_channel *priv)
In interrupt mode, turn on the transmitter and allow for
transmit interrupts.
The device interface module can execute functions in the
hardware independent driver via chan->callbacks.
These functions are available:
void (*serial_init)( serial_channel *chan )
This function is used to initialize the serial channel. It
is only required if the channel is being used in interrupt
mode.
void (*xmt_char)( serial_channel *chan )
This function would be called from an interrupt handler after a
transmit interrupt indicating that additional characters may be
sent. The upper driver will call the putc
function as appropriate to send more data to the device.
void (*rcv_char)( serial_channel *chan, unsigned char c )
This function is used to tell the driver that a character has arrived
at the interface. This function is typically called from the interrupt
handler.
Furthermore, if the device has a FIFO it should require the hardware
independent driver to provide block transfer functionality (driver CDL
should include "implements
CYGINT_IO_SERIAL_BLOCK_TRANSFER"). In that case, the following
functions are available as well:
Instead of calling xmt_char() to get a single
character for transmission at a time, the driver should call
data_xmt_req() in a loop, requesting character
blocks for transfer. Call with a space argument of how much space
there is available in the FIFO.
If the call returns true, the driver can read
chars_avail characters from
chars and copy them into the FIFO.
If the call returns false, there are
no more buffered characters and the driver should continue without
filling up the FIFO.
When all data has been unloaded, the
driver must call data_xmt_done().
Instead of calling rcv_char() with a single
character at a time, the driver should call
data_rcv_req() in a loop, requesting space to
unload the FIFO to. avail is the number of
characters the driver wishes to unload.
If the call returns true, the driver can copy
space_avail characters to
space.
If the call returns false, the input buffer is
full. It is up to the driver to decide what to do in that case
(callback functions for registering overflow are being planned for
later versions of the serial driver).
When all data has been unloaded, the driver must call
data_rcv_done().