By David G. Heil, CalAmp
Device driver writers possess a special blend of software and hardware
skills (among other things). They need to write highly structured and
elegant code as well as debug down to the register level of the
hardware. They have to do their tasks with less then optimal debug
hardware. They often make do without JTAG or Ethernet debug tools and
fix tough problems with just their wit and a GPIO line. If you have the
tenacity to write device drivers read on.
Writing device drivers for Windows CE is very similar to creating
them on other operating systems. The operating system provides a
handful of driver models that define the functions your driver must
expose and their operation. What sets Windows CE apart is the rich set
of driver frameworks that it provides. These allow driver writers to
concentrate on the hardware specific bits while the framework handles
the common tasks like queue management and interrupt routing.
Model Behavior
A device driver abstracts the functionality of a physical or virtual
device and manages the operation of these devices. Examples of physical
devices are network adapters, timers, and universal asynchronous
receiver-transmitters (UARTs). An example of a virtual device is a file
system. Implementing a device driver allows the functionality of your
device to be exposed to applications and other parts of the operating
system. Windows CE device drivers are trusted modules but they do not
have to run in kernel mode.
There are three main processes in Windows CE that load device
drivers. Each process expects the driver to conform to a particular
driver mode. The first process loaded by the kernel is filesys.exe, the
file system process. This process loads file system drivers which must
conform to the file system driver model.
After the file system and registry are loaded the device manager
process, device.exe, is loaded. This process loads the majority of the
device drivers in the system and they all expose the stream driver
interface. Lastly, the graphics, windowing, and events subsystem
(gwes.exe) runs and loads specialized drivers like display and keyboard
drivers. These drivers conform to specialized driver models for their
particular function. The following Table 1 below summarizes the three
system processes that load device drivers.
 |
| Table
1 |
Windows CE supports the concept of installable file systems. When a
device is attached such as a CompactFlash or SD/MMC drive the operating
system will parse the partitions for the file system type and load the
appropriate driver.
A file system driver (FSD) is a dynamic-link library (DLL) that
exports file system entry points that map to standard Windows CE file
system functions, such as CreateFile and CreateDirectory. When an
application calls a file system function, and the function references a
file on a volume registered by the FSD, the FSD Manager maps the call
to the FSD. The FSD Manager manages all system interactions with
installable FSDs.
The operating system provides a template for functions that you need
to develop for a file system. The FSD may export the full range of
functions entry points or it can exclude particular file system
functions if you do not want that function to be available.
For example, to prevent applications from creating or destroying
directories inside volumes belonging to your FSD, do not include
CreateDirectory and RemoveDirectory in your FSD. The FSD Manager
automatically supplies stub functions for any functions that you choose
not to supply for your file system. These stub functions return an
error code. As a result, if an application tries to create or remove a
directory on a volume that belongs to your FSD, the call fails.
In addition to exporting entry points for the file system functions,
an FSD must export entry points for the FSD_MountDisk and
FSD_UnmountDisk functions.
The Device Manager calls FSD_MountDisk
when
a target device for which that FSD has been registered is inserted or
removed. The FSD_MountDisk
and FSD_UnmountDisk entry
points are each
passed a value that uniquely identifies the disk being mounted. This
value is passed back to the FSD Manager services that query, read, and
write to that disk.
Stream Interface Drivers
The most common driver model in Windows CE is the stream interface
driver. This driver model has roots in the earliest implementations of
Unix. A stream driver exports functions to open, close, read, write,
seek, or control the underlying hardware.
The stream interface is appropriate for any I/O device that can be
thought of logically as a data source or a data sink. That is, any
peripheral that produces or consumes streams of data as its primary
function is a good candidate to expose the stream interface. A good
example is a serial port device. An example of a device that does not
produce or consume data in the traditional sense would be a display
device, and indeed, the stream interface is not exposed for controlling
display hardware.
A stream interface driver receives commands from the Device Manager
and from applications by means of file system calls. The driver
encapsulates all of the information that is necessary to translate
those commands into appropriate actions on the devices that it
controls.
All stream interface drivers, whether they manage built-in devices
or installable devices, or whether they are loaded at boot time or
loaded dynamically, have similar interactions with other system
components.
The following illustrations show the interactions between system
components for a generic stream interface driver that manages a
built-in device, and for a stream interface driver for a PC Card Client
device. Figure 1, below, shows
the architecture for stream interface
drivers for built-in devices that are loaded by the Device Manager at
boot time.
 |
| Figure
1 |
Block Drivers
A block driver allows reads and writes of the underlying hardware
resource through fixed sized block of data. Block devices do not allow
you to read or write individual bytes of data. Common block sizes are
512 bytes, 1 kilobyte (KB), 2 KB, and 4 KB. Block devices are ideally
suited for mass storage and persistent storage applications, such as
disk drives or nonvolatile RAM disks.
Some common types of block devices are hard disks and Advanced
Technology Attachment (ATA) flash memory disks in miniature card, PC
Card, and Compact Flash card form factors.
Drivers for block devices generally expose the stream interface, and
the block devices appear as ordinary disk drives. Applications access
files on a block device through standard file APIs such as CreateFile
and ReadFile. The application
calls the ReadFile function using a
handle to a file that is stored on the block device.
The FAT file system for example, translates the read request to
logical blocks. The FAT file system searches the buffer cache for the
requested blocks. If these are not present, it issues an IOControl
request to the corresponding block device driver to read bytes from the
block device. Then, the block device driver receives the IOControl
request, and then fulfills the request by accessing the block device
through one of its low-level interfaces.
The FAT file system implementation supports block devices. The FAT
file system does not read or write to block devices directly; it uses
underlying block device drivers for all access to block device
hardware. The block device driver must present the block device to the
FAT file system as a block-oriented device. Block device drivers
transparently manage or emulate ordinary disk drives, so applications
do not need to behave differently when reading and writing files to the
block device.
The FAT file system provides an abstraction between files in the
application name space, such as \Storage
Card\Excel Docs\Expense
report.pxl, and devices in the device name space, such as DSK1:. Block
device drivers must respond to the I/O control codes shown in
Table 2
below to interface properly with the FAT file system.
 |
| Table
2 |
Bus Drivers
A bus driver is any software that loads other drivers. In this sense
device.exe is considered the "root" bus driver. A bus driver can be
thought of as having a hierarchical structure starting with the root
bus driver. Bus drivers have one or more of these responsibilities:
1. Managing physical busses,
such as PC Card, USB, or PCI.
2. Loading drivers on a
physical bus that the bus driver does not directly manage.
An example is the Bus Enumerator (regenum.dll),
which is a bus driver
that loads built-in drivers and PCI bus drivers.
3. Calling ActivateDeviceEx
directly to load a device driver.
4. The loaded device driver
might manage hardware indirectly through another
device driver.
To allow access to a device driver with CreateFile, bus drivers
should provide the Device Manager with enough information to create
bus-relative names and enable device handles. They should also provide
enough information to identify themselves to drivers and applications
for bus control (IOCTL)
operations.
A bus driver can specify a busrelative
name for the driver, but for
drivers and applications to be able to access the bus driver for bus
control operations, the bus driver must expose a stream interface. The
$bus namespace provides a way to perform operations on a device that
are different from typical device operations. By controlling access to
the $bus namespace, you can
also provide security for your system.
Bus Agnostic Drivers
A bus agnostic driver is written without knowledge where the underlying
hardware device that it manages is located. The device may reside on
the local microprocessor bus or it may be on a CompactFlash card
inserted into the system.
A bus agnostic driver does not call functions that are specific to a
hardware platform. Each driver gets its resources from the registry,
requests configuration information from its parent bus driver, sets
power states through its parent bus driver, and translates bus
addresses to system addresses through its parent bus driver. Typically,
you can migrate bus agnostic drivers more easily between hardware
platforms than other types of drivers.
A bus agnostic driver must adhere to the following tasks to
determine the location of its hardware resources in the system:
1. Open the device key by
calling the OpenDeviceKey function.
2. Obtain ISR information by
calling the DDKReg_GetIsrInfo function.
3. Obtain hardware I/O or
memory window information by calling the DDKReg_GetWindowInfo function.
4. Obtain a hardware and by
calling the HalTranslateBusAddress function to translate the
bus-specific address to a system physical address.
5. Obtain a virtual address by
calling the MnMapIoSpace function to map the system physical address.
6. Reset your hardware and
disable the interrupt.
7. Load the installable ISR by
calling the LoadIntChainHandler function with information obtained from
DDKReg_GetIsrInfo. You should assume that your ISR will access the card
register to identify the interrupt. TransBusAddrToStatic is needed to
map the system physical address for hardware.
8. Begin the IST and enable the
interrupt.
Layered vs. Monolithic Drivers
Most device drivers consist of platform or hardware specific code and
model or device type specific code. Windows CE refers to these two
pieces as the platform dependent driver (PDD) and the model dependent
driver (MDD).
This is a slight misnomer because the two pieces are actually
software modules or a way of organizing the source code. It is not
until you link the two pieces together do you truly have a "driver". If
a device driver is organized in this way it is referred to as a layered
driver.
A monolithic driver is one that does not separate the source code in
this fashion. Because the monolithic driver does not define an API
isolating the MDD and PDD modules it contains less code and is
generally faster (more efficient). However, this does make the driver
more difficult to maintain and less portable to future versions of
Windows CE.
The obvious advantage of the MDD/PDD model is that it encourages
code reuse. The device type or device model code is theoretically the
same regardless of the underlying hardware.
For example all serial drivers will need to process receive and
transmit buffers and handle flow control logic. These operations are
independent of the hardware and should be isolated into a MDD module
for code reuse. Because the MDD module is at the topmost layer of the
driver it exposes the stream interface to the Device Manager. Some
examples of tasks performed by the MDD module are:
1. Contain code that is
common to all drivers of a given type.
2. Call PDD functions to access
the hardware.
3. Link to the PDD layer and
define the device driver service provider interface (DDSI) functions
that the MDD expects to call in that layer.
4. Expose device driver
interface (DDI) functions to the operating system (OS). Other parts of
the OS can call these functions. Related devices can share the same
DDI. Monolithic drivers also expose the DDI functions.
5. Handle interrupt processing.
6. Provide for reuse by
developers.
7. Can link to multiple PDDs.
8. Generally require no
changes. (If changed, you might have trouble migrating drivers to
future versions.)
9. Contain any interrupt
service threads (ISTs).
The PDD module has the following characteristics:
1. Consist of hardware
platform specific code.
2. Might require modification
for your hardware platform.
3. Are designed to work with
specific MDD implementations.
4. Expose the DDSI functions
that the MDD calls. (Monolithic drivers do not expose the DDSI
functions.)
Windows CE provides many MDD modules for a variety of device types.
Serial Drivers
The serial port driver handles any I/O devices that behave like serial
ports, including those based on 16450 and 16550 universal asynchronous
receiver-transmitter (UART) chips and those that use direct memory
access (DMA).
Many hardware platforms have devices of this type, including
ordinary 9-pin serial ports, infrared I/O ports, and PC Card serial
devices, such as modems. If multiple types of serial ports exist on a
hardware platform, you can either create several different serial
drivers, one for each serial port type, or create several different
lower layers (PDD) and link them to a single upper layer (MDD). This
creates one multipurpose serial driver. Creating separate drivers can
simplify debugging and maintenance because each driver supports only
one type of port. Creating a multipurpose driver, such as the sample
serial port driver, is more complex but gives a small memory savings.
Because the functionality of serial ports maps to the functionality
provided by standard stream interface functions, the serial port driver
uses the stream interface as its device interface. The serial port
driver is a classic example of the MDD/PDD model. The MDD module
supplied with Windows CE strives to handle all of the
device-independent serial port functions leaving the PDD module as
simple as possible.
Serial port devices make extensive use of I/O control codes to set
and query various attributes of a serial port. For example, there are
I/O control codes for setting and clearing the Data Terminal Ready
(DTR) line, for purging any undelivered data and for getting the status
of a modem device. Therefore, you should support as many serial I/O
control codes as possible in your implementation of COM_IOControl.
Audio Drivers
Window CE supports two driver models for audio devices: the MDD/PDD
model and the unified audio model (UAM). The MDD/PDD model is suitable
for only the simplest of audio devices.
Because the MDD is designed to support the most common subset of
functions that audio devices require there is bound to be functionality
on more complex audio devices that is not covered. Instead of forcing
developers to modify the MDD in that situation the unified audio model
was created. A driver that conforms to the UAM is a structured
monolithic driver. The UAM gives the developer complete control over
the audio device but in a structured manner. Figure 2a and 2b below
illustrate the subtle differences in the two driver models.
 |
| Figure
2a |
 |
| Figure
2b |
Network Drivers
Window CE supports the same network driver interface specification
(NDIS) that the desktop version of Windows supports. This makes porting
network device drivers from the desktop to Windows CE very
straightforward. NDIS also provides a pair of abstraction layers that
connect network drivers to an overlying protocol stack, such as TCP/IP
or Infrared Data Association (IrDA) and an underlying network adapter.
NDIS performs a set of external functions for network adapter
drivers, such as registering and intercepting hardware interrupts and
communicating with underlying network adapters. As shown in Figure 3
below, NDIS provides driver interfaces (API) for accessing the
upper-layer network stack and for accessing hardware. This high level
of structure makes NDIS drivers very portable.
 |
| Figure
3 |
The only thing constant is change
Windows CE 6.0 introduces user-mode drivers. These drivers run in the
user space of memory and it is very difficult for these drivers to
crash the system. The traditional kernel-mode drivers share the same
memory space as the kernel and other drivers and could potentially
overwrite the memory of other processes and crash the system.
The following text is excerpted from the release notes included with
the beta.
The new virtual
memory architecture has
significant implications for device drivers. Previous versions of the
OS used special processes to add functionality to the base kernel. For
example, one process managed loadable device drivers, another managed
registry and file systems, another managed the windowing system,
another managed system services, and so forth. In Windows CE 6 Beta,
many system services execute in the context of the kernel. This
improves the performance of intra-kernel calls that, in previous
releases, required multiple traps into the kernel.
In Windows CE 6
Beta, device
drivers need to be aware of any process that passes them pointers to
memory buffers. In previous versions, each process had a unique virtual
address space, so this was not a significant consideration. In Windows
CE 6 Beta, a particular user-mode address might be valid in multiple
processes. Changes to drivers are documented in detail in the Help. In
many cases, drivers from previous releases are relatively easy to port
to Windows CE 6 Beta OS.
Windows CE 6.0 is now in beta testing and will likely be released
sometime in 2006.
David Heil is
chief engineer at CalAmp Solutions Corp.
| This article is excerpted from a paper of
the same name presented at
the Embedded Systems Conference Boston 2006. Used with permission of
the Embedded Systems Conference. For more information, please visit www.embedded.com/esc/boston/ |