7. Make a new run-time image in Debug mode.
If you want to work with the Release version of the run-time image, you must change the DEBUGMSG statements in the driver code to RETAILMSG statements to output the driver messages.
8. Open the generated Nk.bin in the flat release directory to verify that it contains String.dll and registry entries in the HKEY_LOCAL_MACHINE\Drivers\BuiltIn\String subkey to load the driver at startup.
9. Load the generated image on the Device Emulator.
10. Open the Modules window after the image starts by pressing CTRL+ALT+U, or open the Debug menu in Visual Studio, point to Windows, and then select Modules. Verify that the system has loaded string.dll, as illustrated in Figure 6-11.
Figure 6-11 Modules window with loaded string driver
► Access the Driver from an Application
1. Create a new WCE Console Application subproject as part of your OS design by using the Windows Embedded CE Subproject Wizard. Select WCE Console Application and the template A Simple Windows Embedded CE Console Application.
2. Modify the subproject image settings to exclude the subproject from the image by right clicking the OS design name in the solution view and selecting Properties.
3. Include <windows.h> and <winioctl.h>
4. Add code to the application to open an instance of the driver by using CreateFile. For the second CreateFile parameter (dwDesiredAccess), pass in GENERIC_READ | GENERIC_WRITE. For the fifth parameter (dwCreationDisposition), pass in OPEN_EXISTING. If you open the driver with the $device naming convention, be sure to escape the slashes and not include the colon at the end of the filename.
HANDLE hDrv = CreateFile(L"\\$device\\STR1",
GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
5. Copy the IOCTL header file (String_ioctl.h) from the string driver folder to the new application's folder and include it in the source code file.
6. Declare an instance of a PARMS_STRING structure defined in String_iocontrol.h, included along with the rest of the sample string driver, to enable applications to store a string in the driver using the following code:
PARMS_STRING stringToStore;
wcscpy_s(stringToStore.szString, STR_MAX_STRING_LENGTH,
L"Hello, driver!");
7. Use a DeviceIoControl call with an I/O control code of IOCTL_STRING_SET to store this string in the driver.
8. Run the application by building it and selecting Run Programs from the Target menu.
9. The Debug window should show the message Stored String "Hello, driver!" Successfully when you run the application, as shown in Figure 6-12.
Figure 6-12 A debug message from the string driver
► Adding Power Management Support
1. Turn off and detach from the Device Emulator.
2. Add the IClass for generic power management devices with the following line to the string driver's registry key in String.reg:
"IClass"=multi_sz:"{A32942B7-920C-486b-B0E6-92A702A99B35}"
3. Add the power management code that you can find in the StringDriverPowerCode.txt file under \Labs\StringDriver\Power on the companion CD to the string driver's IOControl function to support IOCTL_POWER_GET, IOCTL_POWER_SET, and IOCTL_POWER_CAPABILITIES.
4. Add code to the string driver's device context so it stores its current power state:
CEDEVICE_POWER_STATE CurrentDx;
5. Add the header <pm.h> to the application, and add calls to SetDevicePower with the name of the string driver and different power states; for example:
SetDevicePower(L"STR1:", POWER_NAME, D2);
6. Run the application again, and observe the power state-related debug messages when Power Manager changes the string driver's power state, as shown in Figure 6-13.
Figure 6-13 Power management-related debug messages from the string driver
Chapter Review
Windows Embedded CE 6.0 is extraordinarily modular in its design and supports ARM-, MIPS-, SH4-, and x86-based boards in a multitude of hardware configurations. The CE kernel contains the core OS code and the platform-specific code resides in the OAL and in device drivers. In fact, device drivers are the largest part of the BSP for an OS design. Rather than accessing the hardware directly, the operating system loads the corresponding device drivers, and then uses the functions and I/O services that these drivers provide.
Windows Embedded CE device drivers are DLLs that adhere to a well-known API so that the operating system can load them. Native CE drivers interface with GWES while stream drivers interface with Device Manager. Stream drivers implement the stream interface API so that their resources can be exposed as special file system resources. Applications can use the standard file system APIs to interact with these drivers. The stream interface API also includes support for IOCTL handlers, which come in handy if you want to integrate a driver with Power Manager. For example, Power Manager calls XXX_IOControl with an IOControl code of IOCTL_POWER_SET passing in the requested device power state.
Native and stream drivers can feature a monolithic or layered design. The layered design splits the device driver logic in an MDD and a PDD part, which helps to increase the reusability of the code. The layered design also facilitates driver updates. Windows Embedded CE also features a flexible interrupt-handling architecture based on ISRs and ISTs. The ISR's main task is to identify the interrupt source and notify the kernel with a SYNTINR value about the IST to run. The IST performs the majority of the processing, such as time-consuming buffer copying processes.
In general, you have two options to load a driver under Windows Embedded CE 6.0. You can add the driver's registry settings to the BuiltIn registry key to start the driver automatically during the boot process or you load the driver automatically in a call to ActivateDeviceEx. Depending on the driver's registry entries, you can run a driver in kernel mode or user mode. Windows Embedded CE 6.0 includes a user-mode driver host process and a Reflector service that enables most kernel-mode drivers to run in user mode without code modifications. Because device drivers run in different process spaces than applications on Windows Embedded CE 6.0, you must marshal the data in either a mapping of physical memory sections or copying process to facilitate communication. It is imperative to validate and marshal embedded pointers by calling CeOpenCallerBuffer and CeAllocAsynchronousBuffer and properly handling asynchronous buffer access so that a user application cannot exploit a kernel-mode driver to take over the system.
Key Terms
Do you know what these key terms mean? You can check your answers by looking up the terms in the glossary at the end of the book.
■ IRQ
■ SYSINTR
■ IST
■ ISR
■ User mode
■ Marshaling
■ Stream interface
■ Native interface
■ PDD
■ MDD
■ Monolithic
■ Bus-agnostic
Suggested Practice