Geoff Chappell - Software Analyst
In Windows 10 and higher, the PPM_DRIVER_DISPATCH_TABLE (formally _PPM_DRIVER_DISPATCH_TABLE) has the addresses of many routines to call in the kernel for various aspects of Processor Power Management (PPM). The intended callers are PPM drivers for different types of processor. Mostly, these routines tell the kernel of processor-specific details, which often includes that the drivers provide routines for the kernel to call back. Separation of these details from the kernel into drivers such as might be written differently for or by the different manufacturers of processors allows the kernel to offer PPM as an abstracted feature and the manufacturers to compete on some sort of level playing field. Or so might be the theory…
The PPM_DRIVER_DISPATCH_TABLE structure is not documented. To say it is obscure would be an understatement: what Google has to say of it today, 8th November 2022, is that
Your search - "PPM_DRIVER_DISPATCH_TABLE" - did not match any documents.
Bing is no different:
There are no results for "PPM_DRIVER_DISPATCH_TABLE"
I labour the point for two reasons. First, there is some small surprise, for although Microsoft never has gone near to documenting the PPM_DRIVER_DISPATCH_TABLE, the structure is not so deeply internal that its name and members have never escaped from Microsoft’s vaults or from non-disclosure agreements with the manufacturers of processors or whatever else might keep it secret.
A C-language definition was published with the original and Version 1511 editions of the Windows Driver Kit (WDK) for Windows 10. Its disclosure there, in a header named NTOSP.H, was surely an oversight and has not been repeated, but published it was. One might think this publication would get at least the structure’s name into the sort of automated indexing that passes as the Internet’s helpfulness to Window programmers for knowing which programming elements are in which headers with which changes between versions.
Microsoft has also published the practical equivalent of a C-language definition for later versions of Windows 10 too. By this I mean type information in public symbol files. For this structure, the symbol files to examine are not for the kernel but for PPM drivers. Again, this is apparently just enough unusual to be missed by websites that automate the reconstruction of C-language definitions from public symbol files as reference material for revese engineers.
That the structure shows in symbol files for only a handful of specialised drivers points to my second reason for passing more comment than usual on the structure’s absence from the searched Internet. The structure has no intended usefulness outside Microsoft except to the manufacturers of processors that run Windows. Microsoft has no small history of differential support for these manufacturers. There is arguably a public interest in knowing that the interface is available to those manufacturers on equal terms (or that if Microsoft writes the PPM drivers based on information from the manufacturers then they all get equal attention). Yet there is no sunlight here.
To obtain a PPM_DRIVER_DISPATCH_TABLE, a PPM driver calls the ZwPowerInformation function with ProcessorStateHandler (0x07) as the information level and provides a sufficiently large output buffer. Success copies the kernel’s PPM_DRIVER_DISPATCH_TABLE into the output buffer.
See that the kernel’s table of routines for PPM is not directly exposed, e.g., by exporting its address as done for the HAL_DISPATCH and HAL_PRIVATE_DISPATCH tables. Each PPM driver that asks gets only its own copy. PPM drivers’ calls to the kernel go through function pointers that are wherever the PPM drivers have cared to store their copy.
See also that the necessary information level, ProcessorStateHandler, is not named for its provision of a dispatch table. This is because it’s an old name for old work. This information level’s original use, in Windows 2000 when ZwPowerInformation was new, was indeed to supply the kernel with a per-processor state handler. Contemporaneous editions of NTPOAPI.H defined a PROCESSOR_STATE_HANDLER structure to pass in the input buffer. Windows XP elaborated with a ProcessorStateHandler2 information level that takes a PROCESSOR_STATE_HANDLER2 for input. The two co-existed until ProcessorStateHandler was discontinued—retained but failing trivially—in Windows Server 2003 SP1 and then ProcessorStateHandler2 was discontinued in Windows Vista. That ProcessorStateHandler is meaningful for Windows 10 is a revival, apparently just for this new architecture but without its new purpose bringing it a new name. Microsoft’s documentation of the POWER_INFORMATION_LEVEL online today, 8th November 2022, says of ProcessorStateHandler no more than “Indicates the processor state handler.” Again, no sunlight.
Though the structure is shared between modules, the coupling is very tight. Known PPM drivers expect an exact match with the kernel. The structure varies even between the roughly half-yearly releases that are here taken as the Windows 10 equivalent of what used to be named a service pack. Though the structure has for some years now varied only by appending, the first few releases have insertions and changes of type. The following changes of size are known:
Version | Size (x86) | Size (x64) |
---|---|---|
10.0 to 1511 | 0x5C | 0xB8 |
1607 | 0x60 | 0xC0 |
1703 to 1803 | 0x64 | 0xC8 |
1809 | 0x70 | 0xE0 |
1903 to 2004 | 0x78 | 0x0110 |
The sizes in the preceding table and the offsets, names and types in the next are from public symbol files for PROCESSR.SYS, i.e., the default PPM driver, starting with Windows 10.
Offset (x86) | Offset (x64) | Definition | Versions |
---|---|---|---|
0x00 | 0x00 |
ULONG InterfaceVersion; |
10.0 and higher |
0x04 | 0x08 |
NTSTATUS (*RegisterPerfStates) ( PROCESSOR_PERF_STATES *); |
10.0 and higher |
0x08 | 0x10 |
VOID (*UpdatePerfStates) ( PROCESSOR_PERF_STATES_UPDATE *); |
1607 and higher |
0x08 (10.0 to 1511); 0x0C |
0x10 (10.0 to 1511); 0x18 |
NTSTATUS (*RegisterPerfCap) ( PROCESSOR_CAP *); |
10.0 and higher |
0x0C (10.0 to 1511); 0x10 |
0x18 (10.0 to 1511); 0x20 |
NTSTATUS (*RegisterSpmSettings) ( HANDLE); |
10.0 and higher |
0x10 (10.0 to 1511); 0x14 |
0x20 (10.0 to 1511); 0x28 |
NTSTATUS (*RegisterIdleStates) ( PROCESSOR_IDLE_STATES_EX *); |
10.0 and higher |
0x14 (10.0 to 1511); 0x18 |
0x28 (10.0 to 1511); 0x30 |
NTSTATUS (*RegisterIdleDomains) ( PROCESSOR_IDLE_DOMAINS const *); |
10.0 and higher |
0x18 (10.0 to 1511); 0x1C |
0x30 (10.0 to 1511); 0x38 |
NTSTATUS (*RegisterPlatformStates) ( PLATFORM_IDLE_STATES *); |
10.0 and higher |
0x1C (10.0 to 1511); 0x20 |
0x38 (10.0 to 1511); 0x40 |
NTSTATUS (*RegisterCoordinatedStates) ( COORDINATED_IDLE_STATES *; |
10.0 and higher |
0x20 (10.0 to 1511); 0x24 |
0x40 (10.0 to 1511); 0x48 |
NTSTATUS (*RegisterVetoList) ( PREREGISTERED_VETO_LIST *); |
10.0 and higher |
0x24 (10.0 to 1511); 0x28 |
0x48 (10.0 to 1511); 0x50 |
NTSTATUS (*RemoveVetoBias) VOID); |
10.0 and higher |
0x28 (10.0 to 1511); 0x2C |
0x50 (10.0 to 1511); 0x58 |
NTSTATUS (*UpdateProcessorIdleVeto) ( PROCESSOR_IDLE_VETO *); |
10.0 and higher |
0x2C (10.0 to 1511); 0x30 |
0x58 (10.0 to 1511); 0x60 |
NTSTATUS (*UpdatePlatformIdleVeto) ( PLATFORM_IDLE_VETO *); |
10.0 and higher |
0x30 (10.0 to 1511); 0x34 |
0x60 (10.0 to 1511); 0x68 |
NTSTATUS (*RegisterPerfStatesHv) ( PROCESSOR_PERF_STATES_HV const *); |
10.0 and higher |
0x34 (10.0 to 1511); 0x38 |
0x68 (10.0 to 1511); 0x70 |
NTSTATUS (*RegisterPerfCapHv) ( PROCESSOR_PERF_CAP_HV const *; |
10.0 and higher |
0x38 (10.0 to 1511); 0x3C |
0x70 (10.0 to 1511); 0x78 |
NTSTATUS (*RegisterIdleStatesHv) ( PROCESSOR_IDLE_STATES_HV const *); |
10.0 and higher |
0x3C (10.0 to 1511); 0x40 |
0x78 (10.0 to 1511); 0x80 |
NTSTATUS (*RegisterPerfStatesCountersHv) ( PROCESSOR_PERF_STATES_COUNTERS_HV const *); |
10.0 and higher |
0x40 (10.0 to 1511); 0x44 |
0x80 (10.0 to 1511); 0x88 |
NTSTATUS (*SetProcessorPep) ( PVOID); |
10.0 and higher |
0x44 (10.0 to 1511); 0x48 |
0x88 (10.0 to 1511); 0x90 |
NTSTATUS (*ParkPereferenceNotification) ( PVOID, PEP_PPM_PARK_SELECTION_V2 *); |
10.0 and higher |
0x48 (10.0 to 1511); 0x4C |
0x90 (10.0 to 1511); 0x98 |
NTSTATUS (*ParkMaskNotification) ( PVOID, PEP_PPM_PARK_MASK *); |
10.0 and higher |
0x4C (10.0 to 1511); 0x50 |
0x98 (10.0 to 1511); 0xA0 |
NTSTATUS (*IdleSelectNotification) ( PVOID, PEP_PPM_IDLE_SELECT *); |
10.0 and higher |
0x50 (10.0 to 1511); 0x54 |
0xA0 (10.0 to 1511); 0xA8 |
NTSTATUS (*QueryPlatformStateNotification) ( PVOID, PEP_PPM_QUERY_PLATFORM_STATE *, BOOLEAN); |
10.0 and higher |
0x54 (10.0 to 1511); 0x58 |
0xA8 (10.0 to 1511); 0xB0 |
NTSTATUS (*QueryCoordinatedDependencyNotification) ( PVOID, PEP_PPM_QUERY_COORDINATED_DEPENDENCY *); |
10.0 and higher |
0x5C | 0xB8 |
NTSTATUS (*NotifyLpiCoordinatedStatesNotification) ( PVOID, PEP_PPM_LPI_COORDINATED_STATES *; |
1703 and higher |
0x58 (10.0 to 1511); 0x5C (1607); 0x60 |
0xB0 (10.0 to 1511); 0xB8 (1607); 0xC0 |
VOID (*RegisterEnergyEstimation) ( PROCESSOR_COMPUTE_ENERGY_ROUTINE *, PROCESSOR_SNAP_ENERGYCOUNTERS_ROUTINE *); |
10.0 and higher |
0x64 | 0xC8 |
NTSTATUS (*RequestProcessorHalt) ( ULONG, PVOID, PROCESSOR_HALT_ROUTINE *); |
1809 and higher |
0x68 | 0xD0 |
UCHAR (*GetHgsEnablementStatus) (VOID); |
1809 and higher |
0x6C | 0xD8 |
VOID (*DispatchHgsInterrupt) ( VOID); |
1809 and higher |
0xE0 |
NTSTATUS (*ReadHiddenProcessorMsr) ( ULONG, ULONG, ULONG64 *; |
1903 and higher | |
0xE8 |
NTSTATUS (*WriteHiddenProcessorMsr) ( ULONG, ULONG, ULONG64, ULONG64); |
1903 and higher | |
0xF0 |
NTSTATUS (*ReadHiddenProcessorIoPort) ( ULONG, USHORT, USHORT, ULONG *; |
1903 and higher | |
0xF8 |
NTSTATUS (*WriteHiddenProcessorIoPort) ( ULONG, USHORT, USHORT, ULONG, ULONG); |
1903 and higher | |
0x70 | 0x0100 |
ULONG (*QueryPackageId) ( ULONG); |
1903 and higher |
0x74 | 0x0108 |
ULONG (*QueryPackageProcessorCount) ( ULONG); |
1903 and higher |
The InterfaceVersion is presumaby intended to distinguish not just different layouts of the structure but different behaviour by the routines that are pointed to by the other members. PPM drivers check that the table they receive has the InterfaceVersion they expect. Except for a slow start, successive versions of the kernel reliably increase their tables’ InterfaceVersion:
All the other members are function pointers. That Microsoft defines types for these function pointers is known for the two versions for which Microsoft has published NTOSP.H. The naming has an obvious convention to it, such that typedef names might be guessed with high confidence for members that are added in later versions. Even so, I prefer to presents the arguments with the members, except in the few cases where the arguments are themselves function pointers.
The RegisterEnergyEstimation member points to a routine whose two arguments are both function pointers. The first argument changed type between the original Windows 10 and Version 1511. It is
typedef VOID FASTCALL PROCESSOR_COMPUTE_ENERGY_ROUTINE ( ULONG, ULONG64, ULONG64, ULONG, ULONG64 *);
in Version 1511 and higher, but the original Windows 10 has the very different
typedef VOID FASTCALL PROCESSOR_COMPUTE_ENERGY_ROUTINE ( ULONG64 *, ULONG64 *, ULONG *, ULONG64 *);
Note that this variation is not signalled by a change of either the typedef name or the InterfaceVersion. The second argument’s type is
typedef VOID FASTCALL PROCESSOR_SNAP_ENERGYCOUNTERS_ROUTINE ( ULONG, BOOLEAN, BOOLEAN);
Version 1809 added another member, RequestProcessorHalt, for which the address routine itself takes the address of a routine among its arguments. Microsoft’s name for this routine’s type is well-known from WDM.H, starting with Windows 8:
typedef NTSTATUS PROCESSOR_HALT_ROUTINE ( PVOID);
The RequestProcessorHalt member is distincitive for having no known implementation. Both the x86 and x64 kernels in versions 1809 to 2004 inclusive and higher have NULL for this member.