When porting or creating a BSP to a new platform, we often need to make change to OEMIoControl or HAL IOCTL handler for more specific. Since Microsoft introduced PQOAL in CE 5.0 and more and more BSP today leverages PQOAL to simplify the OAL, we no longer define the OEMIoControl directly. It is somehow analogous to migrate from pure Windows SDK to MFC; people starts to define those MFC handlers and forgot the WinMain and the big message loop.
If you ever take a look at the interface between OAL and Kernel, PUBLIC\COMMON\OAK\INC\oemglobal.h, the pfnOEMIoctl is still there just as the entry point of Windows Program is WinMain since day one.
(For those may argue about pfnOEMIoctl is not OEMIoControl, I will encourage you to dig into PRIVATE\WINCEOS\COREOS\NK\OEMMAIN\oemglobal.c which initialized pfnOEMIoctl to OEMIoControl. The interface is just to split OAL and Kernel which no longer linked to one executable file in CE 6, all of the function signature is still identical)
So let's trace into PQOAL to realize how it implements OEMIoControl and how can we override an IOCTL handler we interest.
First thing to know is the entry point (just as finding the WinMain in MFC), OEMIoControl is defined in PLATFORM\COMMON\SRC\COMMON\IOCTL\ioctl.c. Basically, it does nothing special but scan a pre-defined IOCTL table, g_oalIoCtlTable, and then execute the handler. (The highlight part) Other than that is just for error handling and the use of critical section to serialize the function.
BOOL OEMIoControl(
DWORD code, VOID *pInBuffer, DWORD inSize, VOID *pOutBuffer, DWORD outSize,
DWORD *pOutSize
) {
BOOL rc = FALSE;
UINT32 i;
...
// Search the IOCTL table for the requested code.
for (i = 0; g_oalIoCtlTable[i].pfnHandler != NULL; i++) {
if (g_oalIoCtlTable[i].code == code) break;
}
// Indicate unsupported code
if (g_oalIoCtlTable[i].pfnHandler == NULL) {
NKSetLastError(ERROR_NOT_SUPPORTED);
OALMSG(OAL_IOCTL, (
L"OEMIoControl: Unsupported Code 0x%x - device 0x%04x func %d\r\n",
code, code >> 16, (code >> 2)&0x0FFF
));
goto cleanUp;
}
// Take critical section if required (after postinit & no flag)
if (
g_ioctlState.postInit &&
(g_oalIoCtlTable[i].flags & OAL_IOCTL_FLAG_NOCS) == 0
) {
// Take critical section
EnterCriticalSection(&g_ioctlState.cs);
}
// Execute the handler
rc = g_oalIoCtlTable[i].pfnHandler(
code, pInBuffer, inSize, pOutBuffer, outSize, pOutSize
);
// Release critical section if it was taken above
if (
g_ioctlState.postInit &&
(g_oalIoCtlTable[i].flags & OAL_IOCTL_FLAG_NOCS) == 0
) {
// Release critical section
LeaveCriticalSection(&g_ioctlState.cs);
}
cleanUp:
OALMSG(OAL_IOCTL&&OAL_FUNC, (L"-OEMIoControl(rc = %d)\r\n", rc ));
return rc;
}
Where is the g_oalIoCtlTable? It is defined in your BSP. Let's use DeviceEmulator BSP as an example.
The PLATFORM\DEVICEEMULATOR\SRC\OAL\OALLIB\ioctl.c defines the table as
const OAL_IOCTL_HANDLER g_oalIoCtlTable[] = {
#include "ioctl_tab.h"
};
And that leads to PLATFORM\DEVICEEMULATOR\SRC\INC\ioctl_tab.h which defined some of IOCTL handler but others are defined in oal_ioctl_tab.h which is under PLATFORM\COMMON\SRC\INC\. Finally, we got the full table body! (Just like tracing MFC, always jumping back and forth). The format of table is very straight forward, IOCTL code, Flags and Handler Function
// IOCTL CODE, Flags Handler Function
//------------------------------------------------------------------------------
{ IOCTL_HAL_INITREGISTRY, 0, OALIoCtlHalInitRegistry },
{ IOCTL_HAL_INIT_RTC, 0, OALIoCtlHalInitRTC },
{ IOCTL_HAL_REBOOT, 0, OALIoCtlHalReboot },
The PQOAL scans through the table until it find a matched IOCTL code, then invokes the handler function.
Since it scans the table from the top which means if we define TWO handler with same IOCTL code, the first one is always invoked with no exception. Now back to the PLATFORM\DEVICEEMULATOR\SRC\INC\ioctl_tab.h, with the following table
{ IOCTL_HAL_INITREGISTRY, 0, OALIoCtlDeviceEmulatorHalInitRegistry },
...
#include <oal_ioctl_tab.h>
Note the IOCTL_HAL_INITREGISTRY handler are defined in both BSP's local ioctl_tab.h and the common oal_ioctl_tab.h, but due to BSP's local handler comes before "#include <oal_ioctl_tab.h>" so we know the OALIoCtlDeviceEmulatorHalInitRegistry always get called.
In this example, the DeviceEmulator BSP overrides the IOCTL_HAL_INITREGISTRY handler from OALIoCtlHalInitRegistry to OALIoCtlDeviceEmulatorHalInitRegistry by manipulating the g_oalIoCtlTable table. (In some point of view, it is similar to message map in MFC) Please be aware, when you override an IOCTL handler in PQOAL, you may want to clone the original implementation to your BSP and change to meet your need. It is recommended and save you the redundant works but remember to rename the handler function (Just like the DeviceEmulator it changes the name of OALIoCtlHalInitRegistry to OALIoCtlDeviceEmulatorHalInitRegistry). If you don't change the name, linker may not be happy (due to name conflict) and the more important is by using different handler name, you could always redirect the handler back to original one. (It is like the concept of OOP that calling a function in base class; still not so clear? I am goinf to show you soon!)
The OALIoCtlDeviceEmulatorHalInitRegistry setups DeviceEmulator specific registry settings and in the end, if everything goes well, it calls the OALIoCtlHalInitRegistry (PLATFORM\COMMON\SRC\COMMON\IOCTL\reginit.c) to do the rest.
if(fOk) {
fOk = OALIoCtlHalInitRegistry(code, pInpBuffer, inpSize, pOutBuffer,
outSize, pOutSize);
}
Now you got the picture, whenever you want to override an IOCTL hadnler that is implemented in PQOAL just
Clone the handler function to your BSP as a template.
Simple name change for the handler function, and a name change in the IOCTL table header file that maps the IOCTL with the function
Implement your IOCTL handler and whenever you need to redirect it back just calling the original handler function.
It is the standard way of implementing a custom IOCTL and most Microsoft developers prefer. The mapping of
IOCTL routine to IOCTL code is platform specific - you control the header file that does that mapping.