New Features of Windows CE 6.x Drivers
It’s been a while since I last updated this blog. This time I’m posting an “ancient” article — not classical Chinese, but something that’s been sitting on my hard drive for a long time, introducing what’s new in Windows CE 6 drivers compared to before.
1. Access Check
Access checking is used to verify whether a function’s caller has sufficient permissions to access the memory passed to the function. Access checking is necessary to prevent malicious applications from using drivers to access resources requiring privileged access. Device drivers in Windows Embedded CE 6.0 run in kernel space, making them privileged programs that can access many system resources, while user-mode applications cannot. If an application uses a device driver to read or write system memory, the driver effectively grants the application high access privileges. Therefore, performing access checks in device drivers protects the OS memory from malicious applications.
In Windows CE 5.0, device drivers used the MapCallerPtr API to perform access checks on pointer parameters and nested pointer parameters.
struct MyStruct { UCHAR *pEmbedded; DWORD dwSize; };
// Windows CE 5.0 and prior versions
// In XXX_IOControl...
MyStruct *p = (MyStruct*) pInput;
g_pMappedEmbedded = MapCallerPtr(p->pEmbedded);
// Fail if g_pMappedEmbedded == NULL ...
In Windows Embedded CE 6.0, device drivers only need to check whether nested pointer parameters are valid. The difference is that Windows Embedded CE 6.0 requires using the CeOpenCallerBuffer API to check whether the calling process has access permissions to a particular memory region.
// Now in the New CE Version
// In XXX_IOControl...
hr = CeOpenCallerBuffer((PVOID*)&g_pMappedEmbedded, pInput->pEmbedded,
pInput->dwSize, ARG_I_PTR, FALSE);
// Fail if FAILED(hr) == true
// When done with pointer...
hr = CeCloseCallerBuffer((PVOID)g_pMappedEmbedded, pInput->pEmbedded,
pInput->dwSize, ARG_I_PTR );
2. Marshalling
A kernel-mode thread’s access to user-space memory can be divided into two types: synchronous access and asynchronous access.
Synchronous access means that when accessing a memory region, the kernel-mode thread is within the caller’s context. Asynchronous access is the opposite — a thread owned by a device driver wants to access a memory region located in another process’s address space.
For example, an application calls WriteFile API to write data to an SD card. The write request eventually goes through the OS kernel to the SD card device driver, which performs the write. But I/O operations are slow. To maximize CPU utilization, the kernel often reschedules another thread. If the scheduled thread is in the same process space as the current application, no process switch occurs, and access to the data is synchronous. Conversely, if the scheduled thread is not in the current process space, a process switch occurs, and the data region becomes invalid for the driver’s thread — this is asynchronous access.
Asynchronous access requires a technique called Marshalling to handle the accessed data region.
In Windows CE 5.0, all user-mode processes shared the bottom virtual address space. When Slot 0 was replaced with a different process, because all processes’ virtual address spaces were visible to the device driver, only a simple offset from the pointer to the data block was needed.
In Windows Embedded CE 6.0, each user-mode process has its own unique virtual address space, and each process’s virtual address space is protected. Marshalling a memory block is no longer simply offsetting a pointer. Two approaches can be used: copying the memory block for safe access (copying), or mapping a new virtual address to the corresponding physical address so the data is referenced by two pointers (aliasing).
Marshalling a memory region requires distinguishing between synchronous and asynchronous access. In Windows CE 5.0, synchronous access requires no extra work — just call MapCallerPtr API to marshall the nested pointer’s address. For asynchronous access, a thread has certain access permissions for each Slot, so you must call SetProcPermissions first to obtain the calling process’s Slot access permissions, then call MapCallerPtr to marshall the memory to be accessed asynchronously.
// Windows CE 5.0 and prior versions
// In XXX_IOControl...
// Synchronous access:
g_pMappedEmbedded = MapCallerPtr(p->pEmbedded);
// ...
// Asynchronous access:
DWORD dwOrigPermissions = SetProcPermissions(TRUE);
g_pMappedEmbedded = MapCallerPtr(p->pEmbedded);
// ...
SetProcPermissions(dwOrigPermissions);
In Windows Embedded CE 6.0, the new APIs CeOpenCallerBuffer and CeCloseCallerBuffer are used to handle both synchronous and asynchronous access uniformly.
3. Other New Features
Additional new features in Windows Embedded CE 6.0 drivers include:
- WDDM (Windows Display Driver Model) support — a new display driver architecture similar to Windows Vista’s WDDM, supporting more advanced graphics features.
- Power Manager — a new power management framework replacing the old system.
These new features make Windows Embedded CE 6.0 more powerful, more secure, and bring it closer to the desktop Windows driver model.
(Note: The original Chinese post contained extensive inline HTML/CSS formatting from Word, which has been simplified for readability.)