http://www.kalinskyassociates.com/Wpaper4.html
Architecture of Device I/O Drivers
Many
embedded systems developers will tell you that writing a device driver consists
of a lot of "bit-bashing and
register-twiddling" to convince some
ornery unit of hardware to submit to the control of driver software.
You‘ve got to
get every one of a myriad of details right -- the
bits, the sequences, the timing -- or else that chunk of hardware will
just
refuse to do its thing. Traditionally, the focus in writing
device drivers has been at this nuts-and-bolts level. But I
would
like to take a somewhat different, higher-level view of device driver
software.
These days more and more embedded systems software developers
take advantage of the services of a real-time
operating system ("RTOS")
to structure their application software. An embedded application software
system can be
organized as a collection of "chunks" of concurrent
software. The RTOS is involved in both scheduling the
"chunks"
and allowing them to communicate cleanly with one another.
In different operating systems, the concurrent "chunks"
of
software might be given different names like ‘threads‘ or ‘tasks‘. In most RTOSs
they are called ‘tasks‘ and ‘Interrupt
Service Routines‘ ("ISRs"), so
these are the terms we will use in this paper. An "ISR" is a concurrent
"chunk" of
software that executes in direct response to an interrupt,
while a ‘task‘ is a concurrent "chunk" of software that
executes in
direct response to a software event such as the arrival of a message. The
device driver designs shown in
this paper assume that you are using an
RTOS.
A device driver itself is a collection of functions that are
programmed to make a hardware device perform some
input/output-related
("I/O") activities. A driver might contain an "initialization" function, a
"read" function, a "write"
function, etc. Device drivers that
work with hardware devices that deliver interrupts also include the ISRs for
those
interrupts as an additional component of the device driver.
The functions of a device driver can be called by
application
software tasks that would like to get some hardware I/O-related activities to
happen.
MUTUAL
EXCLUSION OF DEVICE ACCESS
Often,
one of the most basic requirements of a device driver is the need to ensure that
only one application task at a
time can request an input or output
operation on a specific device. For example, if you‘ve got an application
task that
needs a temperature measurement in units of degrees Celsius
and another task that uses degrees Kelvin, you had
better make sure
that only one task at a time is asking for a temperature measurement.
An
easy way to ensure this is with a semaphore, operating in binary fashion.
Make sure that each application task
obtains the semaphore‘s
token before initiating a temperature measurement. And make sure that it
releases the
semaphore‘s token at the end of the temperature
measurement.
Usually, this can be done right inside the device driver.
The driver function requests the semaphore‘s token as soon
as it
is called by an application task. And after the completion of the I/O
operation, the driver function releases the
semaphore‘s
token.
For devices that may be thought of as "session-oriented",
exclusive access must be granted for an entire "session"
which may
consist of many individual I/O operations. For example, a single task
might want to print an entire page of
text, whereas the printer
driver‘s output function can print only a single line of text. In such a
case, a driver‘s ‘open
session‘ operation would request the semaphore
token. And a ‘close session‘ operation would return the
semaphore
token. [The "session" semaphore would initially contain
one semaphore token, to indicate "session available".] Any
other task
attempting to ‘open session‘ while the semaphore‘s token is unavailable, would
be denied access to the
driver
software.
SYNCHRONOUS
VS. ASYNCHRONOUS I/O MODELS
A
much larger question in structuring a device driver, is the question of
synchronous versus asynchronous driver
operation. To put it
another way: Do
you want the application task that called the device driver to wait for the
result
of the I/O operation that it asked for? ... Or do you want the
application task to continue to run while the device
driver is doing
its I/O operation?
The
device driver‘s structure will be quite different for each of these
alternatives.
SYNCHRONOUS
I/O DRIVERS
In
a synchronous driver, the application task that called the device driver will
wait for the result of the I/O operation that
it asked for.
This
does not mean that your entire application will stop and wait while the driver
is working with the I/O hardware to
perform the I/O operation.
Other tasks will be allowed to continue working, at the same time that the
I/O hardware is
working. Only the task that actually called the
driver function will be forced to wait for the completion of the
I/O
operation.
Synchronous drivers are often simpler in their
design than other drivers. They are built around a mechanism
for
preventing the requesting task from executing while the driver and
I/O hardware are working; and then releasing the
requesting task for
continued execution when the driver and I/O hardware have completed their
work.
This can be done with a (binary) semaphore. [Please note that
this is a different semaphore than the binary
semaphore described
earlier for purposes of mutual exclusion of tasks. So a synchronous driver
might actually
contain 2 (or more) binary semaphores.] At driver
initialization time, this new semaphore would be created but not
given
any semaphore tokens. An attempt to get a semaphore token when none is
available would force the
requesting software to stop executing and
become blocked.
This is achieved straightforwardly, since each driver
function is essentially a subroutine of the requesting task. So,
for
example, if a task calls a driver via a "read" call, the driver
function implementing the call would act as a subroutine of
the caller
task. And if this driver function attempts to get a semaphore token that
is not present, the driver function
would be blocked from continuing
execution; and together with it, the requesting task would be put into a blocked
(or
“waiting”) state.
We can see this in Figure_1 below.
The requesting task is shown on the left as a light blue rectangle with
rounded
corners. It calls the driver‘s "read" function, shown as
a yellow rectangle (named "DevRead()"). This call can
be
either through the RTOS, or bypassing the RTOS (shown as a
black-dotted rectangle). The driver‘s "read" function will
request the
device hardware to perform a "read" operation, and then it will attempt to get a
token from the semaphore
to its right. Since the semaphore
initially has no tokens, the driver "read" function, and hence the task to its
left, will
become blocked To the right of the semaphore, is a pink
ellipse representing an ISR. The "lightning" symbol
represents
the hardware interrupt that triggers execution of the ISR. When
device hardware completes the requested
"read" operation, it will
deliver an interrupt that triggers this ISR, that will put a token into the
semaphore. This is the
semaphore token for which the entire left side
of the diagram is waiting, so the left side of the diagram will
then
become un-blocked and will proceed to fetch the newly-read data
from the hardware.

The
ISR is considered part of the driver. Its job is to execute when an
interrupt arrives announcing that the hardware
has completed its work.
[For more background information about interrupts and ISRs, attend
our course "Introduction
to Embedded Systems and
Software".] In
this example, the interrupt announces that the hardware has
completed
reading a new input. When the ISR executes, its main
responsibilities are to handle the immediate needs of the
device
hardware, and then to create a new semaphore token (out of "thin air", if you
will) and to put it into the
semaphore upon which all the rest of the
software here is waiting. The arrival of the semaphore token releases
all
the software on the left from waiting in the blocked state; and
when it resumes executing it can take the final results of
the hardware
I/O operation and begin processing them.
The driver function (shown as
the yellow rectangle near the center of Figure_1), does the logic described in
the
following pseudocode when called by a task:
DevRead_function:
BEGIN
Start
IO Device Read Operation;
Get
Synchronizer Semaphore Token (Waiting
OK);
/*
Wait for Semaphore Token */
Get
Device Status and Data;
Give Device
Information to Requesting
Task;
END
The
ISR has very simple logic:
DevRead_ISR:
BEGIN
Calm
down the hardware
device;
Put
a Token into Synchronizer
Semaphore;
END
Now
let‘s complicate the situation.....
ASYNCHRONOUS
I/O DRIVERS
In
an asynchronous driver, the
application task that called the device driver may continue executing, without
waiting for
the result of the I/O operation it requested.
This
is true parallelism, even in a single-CPU hardware environment. A task can
continue to execute at the same time
that hardware is executing the I/O
operation that the task requested.
Asynchronous drivers are more
complex in their design than other drivers. In some cases,
an asynchronous driver
might
be unnecessary "overkill". You need to ask yourself the
question, "If
my task requests an I/O operation
through a driver, then what work can
it usefully do before that I/O operation is
done?" Occasionally
the answer
may be "No,
I need the I/O completed before my task can usefully do anything
else.".
Such an answer says that an
asynchronous driver is overkill: In
this case, a synchronous driver would do just
fine (see Figure_1).
For example, say you‘re designing a driver for an
input device. What can a task do with that input before it‘s ready??
Most often, not much of anything!
But perhaps we can set things
up so that every time a task asks the device driver for a new input, two things
happen:
(a) The driver asks the I/O hardware to start getting a new input;
and
(b) The driver gives the requesting task the last previous input to work
on in the meanwhile.
Sometimes this may be a useful way to work. So
Figure_2 shows what the design of such a driver would look
like:

The
new ladder-like symbol appearing here represents a message queue. It‘s a
place to store information about one
or more previous inputs. The
driver‘s "read" function can get a previous input from the queue, to give to the
requesting
task. And the ISR will put new input into this queue
whenever it gets one.
If device hardware will create new inputs only when
requested to do so by the driver‘s "read" function, then a queue
of
maximum length 1 message is sufficient. If, on the other hand,
device hardware is "free-running" so that it can
create
new inputs even when not requested to do so by explicit software
request, then the queue should be assigned a
length sufficient to hold
rapid bursts of inputs.
The driver‘s "read" function does the logic
described in the following pseudocode when called by a task:
DevReadAsync_function:
BEGIN
Get
Message from the Queue (Waiting
OK);
/*
Wait only if Queue is Empty
*/
Start
new IO Device Read
Operation;
Give
old Device Information to Requesting
Task;
END
The
ISR has this logic:
DevReadAsync_ISR:
BEGIN
Calm
down the hardware
device;
Get
Data/Status Information from
Hardware;
Package
this Information into a
Message
Put
Message into the
Queue;
END
In
order for this to work, a driver initialization function needs to create the
Message Queue that is at the heart of
this
driver.
LATEST
INPUT ONLY ASYNCHRONOUS DRIVER
If
a hardware input device is free to deliver inputs even when not explicitly
requested by software, the asynchronous
design we have just seen might
in some cases not be what is desired. Sometimes what is desired is the
latest input
only. The problem with the design in Figure_2 is
that old inputs would queue up in the Message Queue.
Requesting
tasks could be fed very old inputs, while newer inputs
would languish in the message queue.
So for a free-running input device,
a latest-input-only driver architecture could be that shown in Figure_3
below.

This
design shows a protected shared data area at its center. The shared data
area, shown as a pair of parallel
horizontal
lines, always contains the latest input value. It is protected from data
corruption and access collisions by its
associated (binary)
semaphore.
Whenever the ISR is triggered by a new interrupt, it gets new
input data from the hardware, and overwrites the
previous content of
the shared data area. In order to do this cleanly, the ISR must obtain
access permission from the
associated semaphore.
Whenever a task
requests input data from the driver, the driver "read" function software reads
it from the shared data
area, after obtaining access permission from
the associated semaphore. Since old input data are overwritten by
the
ISR, the data being read and fed to the requesting task are always
the "freshest" data available.
The driver "read" function does the
following when called by a task:
DevReadLatest_function:
BEGIN
Get
Shared Data Access Semaphore (Waiting
OK);
/*
Wait for Semaphore Token
*/
Read
latest input from Shared Data
area;
Release
Shared Data Access
Semaphore;
Pass
input data on to requesting
task;
END
The ISR does
this:
DevReadLatest_ISR:
BEGIN
Calm
down the hardware
device;
Get
Shared Data Access Semaphore (No
Waiting);
/*
ISRs should never wait */
IF
Semaphore OKs
access
THEN
Get
Data/Status Information from
Hardware;
Write
latest input data to Shared Data
area;
Release
Shared Data Access
Semaphore;
ELSE
...
ENDIF
END
In rare instances, this ISR will
be unable to obtain the semaphore it needs to access the Shared Data area.
These
will be instances of the two ‘sides‘ of the design trying
to access the Shared Data area simultaneously. In these
instances, the
ISR should not attempt to write into the Shared Data area, as that would very
likely cause corrupted data
to be delivered to the requesting task.
The device driver architect will need to decide how the ISR will
handle
unavailability of access to the Shared Data
area.
SERIAL
INPUT DATA SPOOLER
Often
a hardware input device can deliver large amounts of input data freely to a
computer without its being explicitly
requested to do so by software.
In the next design model, would like to capture and process all of the
incoming
information, without losing any -- even if it is arriving in
irregular large bursts. An example of this is the arrival of
data
packets from a communications network.
Another example is
the arrival of character strings from a serial line. Every character
arriving via serial line is a byte of
incoming data, announced to the
CPU by a separate interrupt.
Message queues are good for buffering
irregular bursts. However a message queue might impose too
much
performance penalty on a driver if it were to hold each arriving
character in a separate message. So perhaps it would
be better to
use a message queue to hold pointers to larger buffers that would contain
complete character strings. If
the serial line never delivers
strings of length greater than ‘S‘ characters, then buffers of length ‘S‘ bytes
can be used
for all strings.
Many real-time operating systems
have a Memory Pools service that can manage large numbers of RAM
memory
buffers of standard sizes. The ISR part of the driver
could "borrow" a buffer from a Pool of appropriate buffer size,
and
fill that buffer with a character string. And then put a
pointer to that buffer into the Message Queue, for transfer to
the
non-ISR part of the driver (left half of the diagram). We see
this pictured in Figure_4 below.

The
driver‘s "read" function does the following when called by a
task:
DevReadSpool_function:
BEGIN
Get
Message from the Queue (Waiting
OK);
/*
Wait only if Queue is Empty
*/
Extract string
information from
message;
Give
string to Requesting
Task;
Return
buffer to its Memory
Pool;
/*
When buffer no longer needed */
END
The
ISR has this logic:
DevReadSpool_ISR:
BEGIN
Calm
down the hardware
device;
Get new
character from Hardware;
IF
in the middle of a
string
THEN
Put new character into existing
buffer
ELSE
/* Need to start on a new string and buffer
*/
Put
existing buffer pointer into a
message;
Put
buffered character count into this
message;
Put
this message into
Queue;
Request
new buffer from
Pool;
/*
ISRs should never wait */
Put
new character into beginning of new
buffer;
ENDIF
END
In some instances, this ISR will
be unable to obtain the memory buffer it needs from the Pool, to hold a new
character
string. The device driver architect will need to decide how
to handle unavailability of buffer memory. In other
instances,
the ISR will be unable to send its message since the Queue
may be full. The device driver architect needs to design
a
solution to this as well.
OUTPUT
DATA SPOOLER
For
many output devices, asynchronous drivers have clear advantages over synchronous
drivers. While the
asynchronous driver "write" function is
working with its I/O device hardware to complete one output operation,
the
requesting task can already be preparing for the next output
operation.
For example, a task may be preparing strings of text for
printing while at the same time the printer driver is printing
out
previously prepared strings. This sort of driver is often
used to allow numerous tasks to prepare and queue up their
outputs.
Queuing of outputs is typically in FIFO order.
The driver design
shown in Figure_5 below for an asynchronous printer driver, is quite similar to
the device input
spooler shown earlier. Two differences are the
directions of access to the Message Queue and Memory
Pool.

The
function "DevWrite()" part of the driver does the
following when called by a task:
DevWriteSpool_function:
BEGIN
Request
new buffer from Memory
Pool;
Fill
buffer with string for
printing;
Put
buffer pointer into a
message;
Put
character count into this
message;
Put
this message into
Queue;
END
The ISR has this
logic:
DevWriteSpool_ISR:
BEGIN
Calm
down the hardware
device;
IF
in the middle of a
string
THEN
Send the next character to
printer
ELSE
/* Need to start on a new string
*/
Return
previous buffer to its Memory
Pool;
/*
When old string no longer needed
*/
Get
new Message from the
Queue;
/*
ISRs should never wait */
Get
new string pointer from
message;
Get
new character count from
message;
Send
first character to
printer;
ENDIF
END
CAUTION
!!
This driver design pretty much
follows the pattern set by the previous driver designs. It seems like it
ought to work
pretty well, just like the previous designs will work
pretty well if you use them in appropriate situations. But this
one
will fail
miserably.
The only positive thing
that can be said of this driver design, is that it will
continue working once it‘s
been
successfully started, as long as its message queue continues to
contain output (printing) requests.
But it‘s got a built-in assumption
that is probably not going to always remain true in your embedded system.
The
assumption is that the Message Queue that brings new buffers
to the ISR, will always have at least one message in it.
In other
words, it will continue to work assuming that there‘s always something new that
needs to be printed.
What will go wrong if there‘s nothing new that needs
to be printed?? Well, after printing the last character that
needs
to be printed, the ISR will try to get the next message from the
queue. But at that time the queue will be empty, since
there‘s no
"next message" waiting. So the ISR will exit without sending a new
character to hardware. Remember, an
ISR is not permitted to wait for a
message (or for anything else, for that matter). And so the hardware won‘t
deliver
another interrupt, since it hasn‘t been asked to do anything
new. [On output devices, an interrupt usually means
"I‘m
done doing the
previous output, and I‘m ready for a new one."]
And so the interrupt service routine will never get to
run
again.
Even if new messages later get queued up for printing, the ISR
won‘t run again. And so the new messages will not
get handled by
the ISR. And so printing will never get started again, if we use a driver that‘s
structured in the way
shown in Figure_5.
Please note that this
driver design will also run into a similar problem when the driver tries to
begin running for the
first time. In other words, it will be
"dead in the
water" the
first time it tries to do some output, and it will never
succeed in
getting started with its first output to the hardware device.
But don‘t
cross out this part of the paper quite yet. We‘ll put a small change into
this driver design model, and get a
pretty similar driver design that
does work properly.
"PRIMING
THE PUMP" IN AN OUTPUT DATA SPOOLER
We
can revive the failed Output Data Spooler driver design just described, and make
it work properly, by breaking the
ISR apart into two pieces.
The
first piece of the ISR is the part previously described as "Calm down the
hardware device". This is all sorts of
device hardware-specific
activity that needs to be done when each interrupt arrives, to make sure that
the device is
working properly and will work properly on the next I/O
operation. Let‘s now call it "Handle
Output Done".
The second piece of the
ISR is all of the remaining ISR logic. Its job is to send out the next
character to hardware, and
also to make sure that the proper characters
are being readied for subsequent output. This second piece will
be
called "Set
Up Next Output".
See
Figure_6 below, showing "Handle Output Done" and "Set Up Next Output" after
they‘ve been separated..
Normally while characters are being printed, the
first piece of the ISR can simply call the second piece every time
it
runs. This was the driver design we studied in the previous
section. The "Handle Output Done" part of the ISR calls
"Set Up
Next Output" each time it runs. But that design ran into trouble.
And the trouble was that there was no way to
run just "Set Up
Next Output" if an interrupt didn‘t arrive to run the "Handle Output Done" part
of the ISR first.
Breaking the ISR‘s logic into two pieces can help,
because it will allow us to call "Set Up Next Output" from
"Handle
Output Done" when interrupts are coming in. And it will
allow us to call "Set Up Next Output" in some other way when
interrupts
are not coming in.
Designers refer to calling "Set Up Next Output" in
these other ways as "Priming
the Pump".
When interrupts are
"dead" and "Set Up Next Output" is called by
non-ISR software, it checks if there‘s a message queued up to be
printed.
Then "Set Up Next Output" will send the first character to the
printer and (as if by magic) the hardware will come alive
and respond
with an interrupt (to announce that it finished printing that first character).
Once that first new interrupt
arrives, the ISR begins to run in
normal fashion again, with "Handle Output Done"s calling "Set Up Next Output"
exactly
as originally designed.
But a question remains:
How does a chunk of non-ISR software, like the driver‘s
"write" function, know whether
or
not it needs to call "Set Up Next Output"? The answer is that "Set
Up Next Output" can detect when no more interrupts
will be arriving and
the driver is about to "die". It detects this indirectly, by detecting
that the Message Queue that feeds
it character strings for printing is
empty at a time when a new character is needed to continue printing. Since "Set
Up
Next Output" is usually run as part of an ISR, it cannot wait for
messages on this Queue. So it‘s got to do something
else.
One thing it can do is to put a token into a Semaphore set up
especially for this purpose. This is a signal to any
other
interested chunk of software, that no interrupts are expected. And so this
chunk of software needs to call "Set Up
Next Output" directly in order
to "Prime
the Pump".
An example of such a
driver architecture is shown in Figure_6. It‘s a repaired output data
spooler, where the "write"
function of the driver
may sometimes need to call "Set Up Next Output" directly in order to
"Prime
the Pump".

The
"write"
function of the driver does the following when called by a
task:
DevWriteSpoolBetter_function:
BEGIN
Request
new buffer from Memory
Pool;
Fill
buffer with string for
printing;
Put
buffer pointer into a
message;
Put
character count into this
message;
Put
this message into
Queue;
IF
Get ‘Device Stalled‘ Semaphore (Without
Waiting);
THEN
/*
Interrupts stalled */
Call
‘Set Up Next Output‘
directly
ENDIF
END
The ISR "Handle Output
Done"
has this very simple logic:
DevHandleOutputDone_ISR:
BEGIN
Calm
down the hardware
device;
Call
‘Set Up Next Output‘
END
And
"Set Up
Next Output"
itself looks like:
Set_Up_Next_Output:
BEGIN
IF
in the middle of a
string
THEN
Send the next character to
printer
ELSE
/* Need to start on a new string
*/
Return
previous buffer to its Memory
Pool;
/*
When old string no longer needed
*/
Get
new Message from the Queue (Without
Waiting);
IF
there is no message
queued
THEN
Set ‘Device Stalled‘ Semaphore to
1
/*
Interrupts stalled */
ELSE
Get
new string pointer from
message;
Get
character count from
message;
Send
first character to
printer;
ENDIF
ENDIF
END
This driver will recover from
situations where there‘s nothing to print for a while. The semaphore named
‘Device
Stalled‘ that has been added here gives the signal to
"Prime
the Pump".
CONCLUSION
This has been just a short
introduction to the world of device driver architecture. Depending on the
nature of your
hardware and your I/O requirements, things can get more
complex in the architecture of both synchronous and
asynchronous device
drivers. [For more information about device driver architectures and
detailed driver design,
attend our advanced course "Designing
Device Drivers for Embedded Systems".]
END.
http://realtimepartner.com/articles/device-driver-design.html
Device Driver
Design
What is a Device?
A device is a piece of hardware that you can program via registers
inside the device
so the device can perform the specific functionality that you need in
your system.
Today many devices may be part of the CPU like a serial interface, a
network interface etc.
But many devices may also be individual chips on your board like a
DUART, network device, disc etc.
What is a Device Driver?
A device driver contains all the software routines that are needed to
be able to use the device.
Typically a device driver contains a number of main routines like a
initialization routine,
that is used to setup the device, a reading routine that is used to be
able to read data from the device,
and a write routine to be able to write data to the device.
The device driver may be either interrupt driven or just used as a
polling routine.

Basic Device Driver Interface
Many RTOSs have a standard interface to the device driver like
create ( ), open ( ), read ( ), write ( ), ioctl ( ).
Other RTOSs may have their own propriety interface with the same type
of functions
but with different names and a faster access time to the device.
As many RTOSs offer an interface to be able to call the device drivers,
the device drivers
and the application do not have to be linked together.
This makes it easier to be able to create hardware independent
applications.
Design of Device Driver
When you design your system it is very good if you can split up the
software into two parts,
one that is hardware independent and one that is hardware dependent,
to make it easier to replace one piece of the hardware without having
to change the whole application.
In the hardware dependent part you should include:
- Initialization routines for the
hardware
- Device drivers
- Interrupt Service
Routines
The device drivers can then be called from the application using RTOS
standard calls.
The RTOS creates during its own initialization tables that contain
function pointers to all the device driver‘s routines.
But as device drivers are initialized after the RTOS has been
initialized you can in your device driver use the functionality of the
RTOS.
When you design your system, you also have to specify which type of
device driver design you need.
Should the device driver be interrupt driven, which is most common
today, or should the application be polling the device?
It of course depends on the device itself, but also on your deadlines
in your system.
But you also need to specify if your device driver should called
synchronously or asynchronously.
Synchronous Device Driver
When a task calls a synchronous device driver it means that the task
will wait until the device has some data that it can give to the task,
see figure 2. In this example the task is blocked on a semaphore until
the driver has been able to read any data from the device.

Asynchronous Device Driver
When a task calls an asynchronous device driver it means that the task
will only
check if the device has some data that it can give to the task, see
figure 3.
In this example the task is just checking if there is a message in the
queue.
The device driver can independently of the task send data into
queue.

Latest Input Only
Sometimes a task only wants to read the actual data from the device,
e.g. the speed of a motor or the actual temperature of the oil.
So in this case will not the design shown in figure 3 work as in that
design data is queued.
So instead of using a queue for the data, we use a shared memory area
protected by a semaphore, see figure 4.

Serial Input and Output Data Spooler
If the device driver should be able to handle blocks of data by itself,
the device driver needs to have internal buffers for storing data.
Two examples of a design like this are shown in figure 5 &
6.


Writing a Device Driver
So should you write a device driver from scratch when you need one?
Hopefully not, as it
can be a very hard job to do that and time consuming. So try to find
one or a similar one
instead. Here is what you should do:
- Check with RTOS vendor if they
have the driver or a similar one
- Check with the chip vendor if
they have a driver
- Search on Internet
- If you can‘t find one, try to
find a similar one
- If you find a similar one or one
for another OS, then it is normally not too much job to make it work for your
RTOS
- Only in worst case you must write
it from scratch
Architecture of Device I/O Drivers
原文:http://www.cnblogs.com/shangdawei/p/3525294.html