I was working on a driver suspend/resume issue this week. The driver was originally developed without support for the Power Manager, but really it would work better if it could respond to requests from the Power Manger to change power states. No problem, I just went to Windows CE: Stream Interface Driver Power Management and cut and pasted some code and in no time had the driver ready to work with the Power Manger.
The next step was to advertise to the Power Manager that the driver wanted to be managed. Simple enough, except that I use the driver with quite a few BSPs and really wanted to have the driver changes contained within the driver itself. That way, I wouldn’t need to go to all of the BSPs and modify the registry settings to support the obvious way to advertise the power management interface, use of an IClass value.
So let’s look at the ways to advertise the interface to the Power Manager. To start, here is what Platform Builder Help has to say:
·         You can define the interface in the IClass value of the registry key used to activate the device.
·         You can define the IClass value in the Active registry key using the Init function of the device driver.
·         You can define the IClass value using the REGINI parameter for the ActivateDeviceEx function.
·         You can explicitly call the AdvertiseInterface function in the device driver.
 
Now let’s look at how to implement each one of these.
IClass Value in the Registry
This one is easy enough, just add an IClass value with a GUID for the data.
IF BSP_DRIVERSHELL
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\DriverShell]
    "Dll"="DriverShell.dll"
   "Order"=dword:4
    "Prefix"="XXX"
    "DeviceArrayIndex"=dword:1
    "IClass"="{A32942B7-920C-486b-B0E6-92A702A99B35}"
ENDIF
Now when the Device Manager loads the driver, it will advertise the interface for us.
IClass Value in the Active Key
This one takes a little more work because we need to add code to the driver. The good news for me is that this satisfies my requirement to consolidate the changes to the driver. We just need to add a little code to the XXX_Init() function from Windows CE: Stream Interface Driver Power Management:
DWORD XXX_Init(ULONG   RegistryPath)
{
                HKEY hKey;
                DRIVERSHELL_CONTEXT *pDriverContext;
 
                RETAILMSG( 1, (TEXT("XXX_Init\n")));
 
                pDriverContext = LocalAlloc( LMEM_FIXED, sizeof( DRIVERSHELL_CONTEXT ));
                if( pDriverContext == NULL )
                {
                                RETAILMSG( 1, (TEXT("XXX_Init failed, unable to allocate driver context\n")));
                                return FALSE;
                }
 
                if( ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,(LPCTSTR)RegistryPath,0,KEY_ALL_ACCESS,&hKey) )
                {
 
                                RegSetValueEx( hKey,
                                                TEXT("IClass"),
                                                0,
                                                REG_SZ,
                                                (BYTE *)PMCLASS_GENERIC_DEVICE,
                                                wcslen( PMCLASS_GENERIC_DEVICE ) * sizeof(TCHAR) );           
                                RegCloseKey (hKey);
                }
 
                hKey = OpenDeviceKey((LPCTSTR)RegistryPath);
                if ( !hKey ) {
                                RETAILMSG(1, (TEXT("Failed to open devkeypath,\r\n")));
                }
                else
                {
                                DWORD Type = REG_DWORD;
                                DWORD Data;
                                DWORD DataSize = sizeof( DWORD );
 
                                // Read values from registry if needed
                                if( ERROR_SUCCESS == RegQueryValueEx( hKey, TEXT("DeviceArrayIndex"), NULL, &Type, (LPBYTE)&Data, &DataSize ) )
                                                pDriverContext->Instance = Data;
 
                                RETAILMSG( 1, (TEXT("pDriverContext->Instance %d\n"), pDriverContext->Instance));
                                RegCloseKey (hKey);
                }
 
                // Init Power management
                pDriverContext->CurrentPowerState = D0;
                pDriverContext->hDDKPower = DDKPwr_Initialize(XXX_SetPowerState, (DWORD)pDriverContext , TRUE, 1000 );
 
                return (DWORD)pDriverContext;
}
Again, now the Device Manager will advertise the interface for us after XXX_Init() runs.
Pass the IClass into ActivateDeviceEx()
This solution assumes that the driver will be started from an application, or another driver, and that the caller will remember to pass in the IClass. Not a real good solution, but it is possible. The code looks like:
                HANDLE hDriver;
                REGINI ActiveKeyValues = { TEXT("IClass"), PMCLASS_GENERIC_DEVICE, wcslen( PMCLASS_GENERIC_DEVICE ) * sizeof(TCHAR) ), REG_SZ };
 
                hDriver = ActivateDeviceEx( (TEXT("Drivers\\Builtin\\DriverShell")),
                                                                                                &ActiveKeyValues,
                                                                                                1,
                                                                                                NULL
                                                                                                );
                if (hDriver != INVALID_HANDLE_VALUE)
                {
                                RETAILMSG( 1, (TEXT("Driver Shell activated\n")));
                                CloseHandle( FlashDriver );
                }
                else
                {
                                RETAILMSG( 1, (TEXT("Failed to activate Driver Shell %d\n"), GetLastError() ));
                }
Again, the Device Manager will advertise the interface after XXX_Init() runs. But this solution won’t work for me because it requires change outside of the driver itself.
Call AdvertiseInterface() from the driver
This looks like a simple solution, but it took me considerable time to figure out how to actually do it. Calling AdvertiseInterface() from within XXX_Init() succeeds, but the Power Manager doesn’t see the advertisement and therefore doesn’t manage the power states of the driver. That means that we need to come up with a solution that does work. I did considerable research on this and asked for help. Someone recommended writing an application to advertise the interface, and even provided me with:
NOTE: Don’t do this
#include <windows.h>
#include <pm.h>
 
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
 
                GUID PMStreadGuid = DEVCLASS_POWER_MANAGER_GUID;
                {
                                if( 0 == AdvertiseInterface( &PMStreadGuid, TEXT("XXX1:"), TRUE) )
                                {
                                                RETAILMSG( 1, (TEXT("AdvertiseInterface failed\r\n")));
                                }
                }
 
                return 0;
}
This almost looks like it works. The Power Manager even opens the driver and calls into the IOCTL_POWER_CAPABILITIES, but then nothing. It doesn’t work.
I noticed that there is a variation of AdvertiseInterface() that is available from the Device Manager, DmAdvertiseInterface(). The documentation for DmAdvertiseInterface() even suggests that it can be called from XXX_Init(). Maybe I am on to something. But then I read closer, the first parameter is a HANDLE that the documentation says is passed into XXX_Init() from the Device Manager. Really, it says that. But a look at the documentation for XXX_Init() shows that no such HANDLE is passed in (sent a note to the Docs team and moved on.)
But, the same person that offered help suggested that I try the Post Init Ioctl, and looking at the documentation for it tells me that the HANDLE for DmAdvertiseInterface() is passed in. So I gave it a try:
#define IOCTL_DRIVERSHELL_POSTINIT 0xB0
                case IOCTL_DRIVERSHELL_POSTINIT:
                                {
                                                POST_INIT_BUF *pBuf = (POST_INIT_BUF *)pBufIn;
                                                GUID PMStreadGuid = DEVCLASS_POWER_MANAGER_GUID;
                                                if( ERROR_SUCCESS != DmAdvertiseInterface( pBuf->p_hDevice, &PMStreadGuid, TEXT("XXX1:"), TRUE) )
                                                {
                                                                RETAILMSG( 1, (TEXT("DmAdvertiseInterface failed\r\n")));
                                                                bRet = FALSE;
                                                }
                                                else
                                                                bRet = TRUE;
                                }
                                break;
This works, and even works if I use AdvetiseInterface() instead. But, for me it doesn’t solve the problem because for it to work I need to add a value to the registry:
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\DriverShell]
                "IOCTL"=dword:B0
But, if this works because it runs just after XXX_Init(), maybe a thread that is started from XXX_Init() would work. So, I added the following code:
static DWORD WINAPI AdvertiseInterfaceThread(DWORD *NotUsed)
{
 
                GUID PMStreadGuid = DEVCLASS_POWER_MANAGER_GUID;
                RETAILMSG( 1, (TEXT("Calling AdvertiseInterface\r\n")));
                if( 0 == AdvertiseInterface( &PMStreadGuid, TEXT(""), TRUE) )
                {
                                RETAILMSG( 1, (TEXT("AdvertiseInterface failed\r\n")));
                }
 
                return TRUE;
}
 
DWORD XXX_Init(ULONG   RegistryPath)
{
                HKEY hKey;
                DRIVERSHELL_CONTEXT *pDriverContext;
                HANDLE hAIThread;
 
                ...
 
                RETAILMSG( 1, (TEXT("AdvertiseInterface failed\r\n")));
                // Start a thread that will wait on that event.
                hAIThread = CreateThread(        NULL,
                                                0,
                                                AdvertiseInterfaceThread,
                                                0,
                                                0,
                                                &ThreadId );
 
                if (hAIThread==NULL)
                {
                                RETAILMSG(1,(L" CAN_Init : error return because !hAIThread\r\n"));
                }
                CloseHandle( hAIThread );
 
                ...
 
}
And this also works.
So now I have several solutions to advertising the interface. For my problem, consolidating the code changes to the driver, saving the IClass in the Active key and the AdvertiseInterfaceThread() look to be viable options.
Copyright © 2009 – Bruce Eitman
All Rights Reserved