Implementing Your Device Driver
Implementing Your Device Driver
Abstract
This article helps you get started implementing a device driver for Windows Embedded
Compact 7. You learn how to create your device driver project from a sample stream
driver, add functionality to control your hardware, and implement device driver features
that are used by the OS and applications. You should read this article after reading the
introductory article Planning Your Device Driver
(http://go.microsoft.com/fwlink/?LinkID=210236) and before reading the companion
article Building and Testing Your Device Driver
(http://go.microsoft.com/fwlink/?LinkID=210199).
©2011 Microsoft
Implementing Your Device Driver 2
Introduction
After you are familiar with the material in the companion article Planning Your Device
Driver (http://go.microsoft.com/fwlink/?LinkID=210236), you can begin implementing
your device driver. The easiest way to begin this implementation is to start with an
existing sample device driver, modify it to fit your design, and add functionality to
support your hardware. When you have completed an initial device driver
implementation, refer to the article Building and Testing Your Device Driver
(http://go.microsoft.com/fwlink/?LinkID=210199) for the steps to build your device
driver.
Because each device driver can be very different in architecture, the interfaces it
supports, its size, the complexity of its implementation, and the kind of hardware it
supports, no single step-by-step procedure for device driver development applies to all
device drivers. Your device hardware may generate interrupts or may be a polled
device; it may support direct memory access (DMA), and you may be able to turn it on
and off for power management support. Applications may require your device driver to
manage multiple open instances across multiple devices, or your driver may support
only one open instance on one hardware device.
For this reason, this article focuses on the most frequently used scenarios:
• Porting a sample stream driver to start your device driver project.
• Managing device driver context.
• Adding support for interrupt handling.
• Marshaling data between caller and device driver address spaces.
• Adding support for DMA.
• Supporting device interfaces.
• Supporting power management.
Not all implementation tasks in this article may apply to your device driver. Just choose
from the topics in this article that apply to your particular device driver design. Each
topic provides pointers to Windows Embedded Compact 7 reference documentation that
explains the relevant APIs in more detail. Some topics refer you to a sample device
driver that provides a working example of the functionality that is explained in the
topic; you can study this example code to understand how to implement the
functionality you need while you learn how to use the APIs more effectively.
©2011 Microsoft
Implementing Your Device Driver 3
2. Copy the sample stream driver to your OS design. The sample stream driver is
located at %_WINCEROOT%\Platform\BSPTemplate\Src\Drivers\Streamdriver.
3. Rename the sample stream driver source files to names that correspond with the
functionality you intended for your device driver. You can rename the “SDT” prefix
of each function to a prefix that you choose for your device driver.
4. Edit the TARGETNAME and SOURCES values in the sources file so that your driver
corresponds with your new file names.
5. Verify that your driver entry points are correctly exposed to the build system. As
shown in the sample stream driver source, you should include each function in the
.def file and preface each function with an extern “C” declaration in the .cpp file.
6. Modify the source code for your driver to implement the stream interface functions.
You can start by implementing code to manage open and device contexts. For more
information about managing context, see Managing Device Driver Context in this
article.
7. Add functionality to control your hardware, including interrupt handling, DMA
processing, data marshaling, device interface support, and power management as
required. We recommend that you first create header files with constants and data
structures that represent your hardware before writing hardware-specific code. Note
that adding functionality to control your hardware is typically the bulk of the process
to implement your device driver.
8. Add code to connect the functionality you implement in step 7 to the stream
interfaces of your driver.
9. If your driver must support additional functionality beyond that which the stream
interface functions expose, you can choose to implement custom I/O control codes
in the IOControl function of your driver.
When you are ready to build your device driver, the article Building and Testing Your
Device Driver (http://go.microsoft.com/fwlink/?LinkID=210199) explains how to add
registry information for your driver to the Platform.reg file, how to modify Platform.bib
and build system files so that the OS design includes your driver, how to build a run-
time image with your device driver, and how to test your device driver. After you
complete steps 1-5 and have a basic driver shell, verify that you can build your device
driver, include it in a run-time image, and access it from a simple test application
before adding more functionality in steps 6-9.
device driver might support only one open instance and only one device, multiple open
instances on one device, one open instance per hardware device, or multiple open
instances across multiple devices. In each case, your device driver must correctly
manage each type of context separately.
// SDT_Init
//
// This function initializes the device driver. Its responsibility
// is to set the default state of the device and to allocate and
// initialize any global variables or resources.
//
// Upon success, this function returns a handle to the driver’s state
// (device context). This is passed to Open and Deinit when they are
// called.
SDT_DEV_CONTEXT* pDevContext =
(SDT_DEV_CONTEXT *)LocalAlloc(LPTR, sizeof(SDT_DEV_CONTEXT));
if (pDevContext == NULL)
{
SetLastError(ERROR_OUTOFMEMORY);
DEBUGMSG(ZONE_ERROR,
(TEXT(“SDT_Init(): cannot allocate memory!\r\n)));
goto cleanup;
}
©2011 Microsoft
Implementing Your Device Driver 5
if (!ReadRegistry(pContext, pDevContext))
{
DEBUGMSG(ZONE_WARNING,
(TEXT(“SDT_Init(): unable to read registry!)));
goto cleanup;
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// Initialize device hardware here
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
fOk = TRUE;
cleanup:
// If initialization fails, free the device context
// structure and prepare to return 0 (NULL).
// Otherwise, if initialization succeeds, return
// the device context.
In this example, when SDT_Init is called, it allocates a structure to hold the device
context, reads the registry key for the device (passed in the first parameter), and
initializes driver data structures with information from that registry key. After it
initializes the device hardware (using information stored in pDevContext), it returns
pDevContext to the caller. If the driver is unable to allocate a device context structure
for the device or if it detects an error while initializing hardware, it sets the appropriate
error code by calling SetLastError and returns 0 (zero) as the value of the device
context to indicate that SDT_Init did not succeed.
In this example, the device driver supports only one device context. If your device
driver supports multiple devices, and therefore, multiple device contexts, you can
modify this code to save multiple copies of pDevContext in a global device driver data
structure such as a linked list. For an example of how to save multiple open contexts in
a global device driver data structure, see the section Managing Open Context. You can
use this approach to manage multiple device contexts.
©2011 Microsoft
Implementing Your Device Driver 6
// SDT_Open
//
// This function prepares the device driver for reading, writing,
// or both. It creates a context that is returned to an application.
// The application uses this context to call into the driver to
// perform reads and writes.
//
// The driver may optionally allocate a new context each time this
// function is called. This allows multiple applications to use
// the driver simultaneously. The driver is responsible for tracking
// all of the contexts that it creates so that it can deallocate
// them when Close or Deinit are called.
//
DWORD SDT_Open(DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode)
{
DEBUGMSG(ZONE_INIT,
(TEXT(“SDT_Open(): device context %x.\r\n”), hDeviceContext));
if ( !hDeviceContext )
{
DEBUGMSG(ZONE_OPEN|ZONE_ERROR,
(TEXT(“SDT_Open(): uninitalized device!\r\n)));
SetLastError(ERROR_INVALID_HANDLE);
return(NULL);
}
SDT_DEVICE_CONTEXT* pDevice = (SDT_DEVICE_CONTEXT *)hDeviceContext;
//. . . . . . . . . . . . . . . . . . . . . . . . . .
// Verify that the hardware can support this
// additional open instance.
//. . . . . . . . . . . . . . . . . . . . . . . . . .
SDT_OPEN_CONTEXT* pOpenContext =
(SDT_OPEN_CONTEXT *)LocalAlloc(LPTR, sizeof(SDT_OPEN_CONTEXT));
if (pOpenContext == NULL)
{
SetLastError(ERROR_OUTOFMEMORY);
©2011 Microsoft
Implementing Your Device Driver 7
DEBUGMSG(ZONE_ERROR,
(TEXT(“SDT_Open(): cannot allocate memory!\r\n)));
return(NULL);
}
//. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
// Initialize the pOpenContext structure here and
// configure the hardware accordingly for this open instance.
//. . . . . . . . . . . . . . . . . . . . . . . . . . . . .
EnterCriticalSection(&(pDevice->OpenCS));
InsertHeadList(&pDevice->OpenList, pOpenContext);
LeaveCriticalSection(&(pDevice->OpenCS));
DEBUGMSG(ZONE_INIT,
(TEXT(“SDT_Open(): open context %x.\r\n”), pOpenContext));
return (DWORD) pOpenContext;
}
In this example, SDT_Open first checks that the device context the caller passes into
the first parameter is valid. If the device context is not valid, SDT_Open signals an
error and returns NULL. Because SDT_Init returns NULL if it is unable to create a device
context, this validation of the passed-in device context is an important check to include
in the SDT_Open function; the caller might simply pass the device context that is
returned from SDT_Init without checking for errors.
After SDT_Open verifies that the hardware can support the additional open instance (a
code sample is not included here because this verification is hardware-specific), it
allocates an SDT_OPEN_CONTEXT structure to store information about the open
instance. After configuring the hardware for this open operation and populating this
structure with information about the open instance (again, these details are hardware-
specific), it saves this open context in a linked list of open instances that are associated
with that device. The critical section protects the linked list in case multiple threads call
SDT_Open simultaneously. After InsertHeadList saves the open context in the list,
SDT_Open returns it to the caller. Of course, you don’t have to use a linked list here;
you can choose a different data structure to save and locate your open contexts.
©2011 Microsoft
Implementing Your Device Driver 8
A typical Windows Embedded Compact 7 device driver separates interrupt handling into
two components:
• An interrupt service routine (ISR)
• An interrupt service thread (IST)
The following sections describe these components in more detail.
The IST runs in a user mode process called the User Mode Driver Host. For more
information about the User Mode Driver Host, see User Mode Driver Framework
(http://go.microsoft.com/fwlink/?LinkID=210291) in Windows Embedded Compact 7
Documentation.
©2011 Microsoft
Implementing Your Device Driver 9
Servicing Interrupts
Figure 1 shows how the interrupt handling components interact with each other and the
device hardware during interrupt processing. Note that you implement only some of the
components that are shown in this diagram: you typically provide the device driver ISR,
platform dependent driver (PDD) lower-layer routines, and OAL routines, and Microsoft
provides the kernel exception and interrupt handling functionality in addition to a model
device driver (MDD) library that includes the device driver IST.
When an interrupt occurs, interrupt handling typically occurs in the following sequence,
although the details can vary depending on the CPU architecture.
1. When your device generates an interrupt, the microprocessor jumps to the kernel
exception handler.
2. The exception handler disables all interrupts of an equal or lower priority at the
microprocessor and then calls the appropriate ISR for that IRQ. If this IRQ is
associated with your device, the exception handler calls your driver ISR.
3. Your ISR services the interrupt. Typically, your ISR also masks the board-level
device interrupt so that the OS does not respond to the interrupt signal from your
device during interrupt processing.
4. After servicing the interrupt, your ISR returns a logical interrupt identifier to the
kernel interrupt handler.
©2011 Microsoft
Implementing Your Device Driver 10
5. The kernel interrupt handler receives the return value from your ISR, reenables all
interrupts at the microprocessor except for the current interrupt (which remains
masked at the board), and then signals the appropriate IST event.
6. The OS schedules the IST.
7. When the IST wakes, it works to complete the interrupt processing by calling
various lower-level driver routines to access the hardware. This process could
include moving data into a device buffer or interpreting status data from the device.
8. When the IST completes interrupt processing, it calls the InterruptDone function,
which in turn calls the OEMInterruptDone function in the OAL.
9. OEMInterruptDone reenables (unmasks) the current interrupt.
As described in step 4, your ISR services the device interrupt and translates the
interrupt into a SYSINTR. It then passes this logical identifier to the kernel as its return
value. Your ISR returns one of the return codes listed in Table 1 to notify the kernel
how to handle the interrupt.
Table 1: ISR Return Codes
Code Description
Each IRQ is associated with an ISR, and an ISR can respond to multiple IRQ sources.
When interrupts are enabled and an interrupt occurs, the kernel calls the registered ISR
for that interrupt. When finished, the ISR returns an interrupt identifier. The kernel
examines the returned interrupt identifier and sets the associated event. When the
kernel sets the event, the IST starts processing.
For more information about interrupt service routine registration, see HookInterrupt
(http://go.microsoft.com/fwlink/?LinkID=210361) in Windows Embedded Compact 7
Documentation.
The interrupt handler registration process registers an event that is associated with the
system interrupt SYSINTR. When your device driver starts, it creates an IST, and then
calls InterruptInitialize to register this event. The IST can then use
WaitForSingleObject to wait on this event and register it with the interrupt handler.
You can register your IST for one or more logical interrupts (SYSINTRs).
If you use the Microsoft implementation of the MDD for a particular driver, you do not
have to write code to register the interrupt. The MDD layer of the driver registers the
driver for interrupts. If you write a monolithic driver, you must implement code to
register the IST of the driver with the interrupt handler. To do this, use the
CreateEvent function to create an event, and then use the InterruptInitialize
function to associate the event with a SYSINTR.
If your device driver must stop processing an interrupt, call the InterruptDisable
function. When your driver calls this function, the interrupt handler removes the
association between the IST and the specified logical interrupt. The interrupt handler
accomplishes this by calling the OEMInterruptDisable function to turn off the
interrupt. If necessary, you can register for the interrupt again.
©2011 Microsoft
Implementing Your Device Driver 12
Function Description
©2011 Microsoft
Implementing Your Device Driver 13
Function Description
For more information about these functions, see the reference information for each
function in Windows Embedded Compact 7 Documentation.
Function Description
©2011 Microsoft
Implementing Your Device Driver 14
Function Description
Keep in mind that your device driver must explicitly map all the pointers that the data
structures contain. For example, DeviceIoControl buffers are often structures that
contain data, and some of this data could be pointers to other data structures. For more
information about buffer marshaling functions, see Kernel Buffer Marshaling Reference
(http://go.microsoft.com/fwlink/?LinkID=210258) in Windows Embedded Compact 7
Documentation.
// In XXX_IOControl
//
// CeOpenCallerBuffer generates g_pMappedEmbedded
©2011 Microsoft
Implementing Your Device Driver 15
// . . .
hr = CeAllocAsynchronousBuffer((PVOID *) &g_pMarshaled,
g_pMappedEmbedded, pInput->dwSize, ARG_I_PTR);
Hr = CeFreeAsynchronousBuffer((PVOID)g_pMarshaled,
g_pMappedEmbedded, pInput->dwSize, ARG_I_PTR);
// Call CeCloseCallerBuffer . . .
©2011 Microsoft
Implementing Your Device Driver 16
Function Description
DMA_ADAPTER_OBJECT AdapterObject;
AdapterObject.ObjectSize = sizeof(AdapterObject);
AdapterObject.InterfaceType = Internal;
AdapterObject.BusNumber = 0;
m_vuaBuf = (PBYTE)HalAllocateCommonBuffer(&AdapterObject,
cbBufSize, &m_paBuf, FALSE);
if (m_vuaBuf == NULL)
{
RETAILMSG(1,(L”unable to allocate %d bytes\r\n”, cbBufSize));
ret = ERROR_OUTOFMEMORY;
ASSERT(0);
HalFreeCommonBuffer(NULL, 0, m_paBuf, m_vuaBuf, FALSE);
goto cleanup;
}
// . . .
// Use the buffer
// . . .
©2011 Microsoft
Implementing Your Device Driver 17
In this example, m_vuaBuf is the DMA buffer virtual uncached address, cbBufSize is
the buffer length, in bytes, and m_paBuf is the buffer physical address. The last
parameter is set to FALSE to disable caching. When HalAllocateCommonBuffer
successfully returns, it allocates a shared buffer of locked, physically contiguous pages
that the microprocessor and the device can access simultaneously for DMA operations.
For an example that uses these common DMA functions for scatter/gather DMA, see the
sample ATAPI device driver source that is located in
%_WINCEROOT%\Public\Common\Oak\Drivers\Block\ATAPI. Note that to set up
scatter/gather DMA, you must simultaneously use multiple pairs of base addresses and
lengths as shown in the sample driver.
For more information about common DMA functions, see Ceddk.dll DMA Functions
(http://go.microsoft.com/fwlink/?LinkID=210250) in Windows Embedded Compact 7
Documentation.
Function Description
©2011 Microsoft
Implementing Your Device Driver 18
Function Description
©2011 Microsoft
Implementing Your Device Driver 19
Function Description
For more information about these functions, see the reference information for each
function in Windows Embedded Compact 7 Documentation.
Function Description
For a DMA example that uses these kernel functions, see the sample device driver
source that is located in %_WINCEROOT%\Public\Common\Oak\Drivers\USB\Hcd.
#define DEVCLASS_IFCNAME_STRING \
TEXT(“{12345678-1234-1234-1234567887654321}”)
#define DEVCLASS_IFCNAME_GUID \
{ 0x12345678, 0x1234, 0x1234, \
{ 0x12, 0x34, 0x56, 0x78, 0x87, 0x65, 0x43, 0x21 }}
Table 7 lists some of the predefined interfaces and the header file under
%_WINCEROOT% that defines each interface.
Table 7: Device Interfaces
BATTERY_DRIVER_CLASS Public\Common\OAK\Inc\Battery.h
BLOCK_DRIVER_GUID Public\Common\SDK\Inc\Storemgr.h
CDDA_MOUNT_GUID Public\Common\SDK\Inc\Storemgr.h
CDFS_MOUNT_GUID Public\Common\SDK\Inc\Storemgr.h
DEVCLASS_CARDSERV_GUID Public\Common\SDK\Inc\Cardserv.h
DEVCLASS_DISPLAY_GUID Public\Common\SDK\Inc\Winddi.h
DEVCLASS_KEYBOARD_GUID Public\Common\SDK\Inc\Keybd.h
DEVCLASS_STREAM_GUID Public\Common\SDK\Inc\Pnp.h
FATFS_MOUNT_GUID Public\Common\SDK\Inc\Storemgr.h
FSD_MOUNT_GUID Public\Common\SDK\Inc\Storemgr.h
NLED_DRIVER_CLASS Public\Common\SDK\Inc\Storemgr.h
PMCLASS_BLOCK_DEVICE Public\Common\SDK\Inc\Pm.h
PMCLASS_DISPLAY Public\Common\SDK\Inc\Pm.h
PMCLASS_GENERIC_DEVICE Public\Common\SDK\Inc\Pm.h
PMCLASS_NDIS_MINIPORT Public\Common\SDK\Inc\Pm.h
STORE_MOUNT_GUID Public\Common\SDK\Inc\Storemgr.h
STOREMGR_DRIVER_GUID Public\Common\SDK\Inc\Storemgr.h
UDFS_MOUNT_GUID Public\Common\SDK\Inc\Storemgr.h
Note You are not restricted to this list of predefined interface classes. You can
define your own interface classes.
©2011 Microsoft
Implementing Your Device Driver 21
©2011 Microsoft
Implementing Your Device Driver 22
Function Description
For more information about these IOCTLs, see Power Management I/O Controls
(http://go.microsoft.com/fwlink/?LinkID=210273) in Windows Embedded Compact 7
Documentation.
to process only DeviceIoControl calls from Power Manager. For more information
about RequestPowerNotifications, see RequestPowerNotifications
(http://go.microsoft.com/fwlink/?LinkID=210986) in Windows Embedded Compact 7
Documentation.
Device
power Registry key Description
state
A physical device does not have to support all the device power states that are shown
in Table 10. The only device power state that all devices must support is the full-on
state, D0. A driver that receives a request to enter a power state that its device
hardware does not support enters the next available power state that it supports. For
example, if Power Manager requests that your driver enter the D2 state and your device
does not support D2, your driver can instead transition the device to the state D3 or
©2011 Microsoft
Implementing Your Device Driver 24
D4. This transition is possible if Power Manager supports one of these states. If Power
Manager requests that your device enter the D3 state but your device cannot wake the
system, we recommend that your driver enters the D4 state by turning off the power
instead of staying in standby mode. These rules are intended to simplify device driver
implementation.
Power Manager maps system power states to the corresponding device power states.
For example, if a device supports only power states D0 and D4, Power Manager does
not immediately request that the device enter the D4 power state when it transitions
from the full-on power state. Power Manager waits until the system enters a system
power state in which D3 or D4 is configured as the maximum device power state for
that device. If D0, D1, or D2 is configured as the maximum power state, Power
Manager keeps the device at D0.
When your device driver starts, it puts the device in the full-on state D0. Before your
device driver is unloaded, it should put the device into D4 (the power off state), if
possible. If your device enters another device power state at startup, it can issue a
DevicePowerNotify request while processing IOCTL_POWER_CAPABILITIES.
For more information about device power states, see CEDEVICE_POWER_STATE
(http://go.microsoft.com/fwlink/?LinkID=210364) in Windows Embedded Compact 7
Documentation. For more information about IOCTL commands that Power Manager
issues to your device driver, see Power Management I/O Controls
(http://go.microsoft.com/fwlink/?LinkID=210273) in Windows Embedded Compact 7
Documentation.
Conclusion
This article explains the most frequent tasks that are required to implement a Windows
Embedded Compact 7 device driver, and provides pointers to sample code and
reference documentation to help you develop a working device driver. It helps you learn
how to start your device driver project from a sample stream driver. You then add
functionality to manage device context and open context when the OS and applications
initialize and open your device driver. This article explains how to service interrupts
from your device, register your interrupt handler with the OS, and handle shared
interrupts. It also helps you learn how to marshal data between address spaces, use
DMA to move data to and from your device, advertise device interfaces to the rest of
the system, and support power management IOCTL commands. With this knowledge,
and the information in companion articles Planning Your Device Driver
(http://go.microsoft.com/fwlink/?LinkID=210236) and Building and Testing Your Device
Driver (http://go.microsoft.com/fwlink/?LinkID=210199), you can develop a working,
full-featured device driver to support your device hardware in Windows Embedded
Compact 7.
Additional Resources
• Windows Embedded website (http://go.microsoft.com/fwlink/?LinkID=203338)
©2011 Microsoft
Implementing Your Device Driver 25
Copyright
This document is provided “as-is.” Information and views expressed in this document,
including URL and other Internet Web site references, may change without notice. You
bear the risk of using it.
This document does not provide you with any legal rights to any intellectual property in
any Microsoft product. You may copy and use this document for your internal, reference
purposes.
© 2011 Microsoft. All rights reserved.
©2011 Microsoft