PPM_DRIVER_DISPATCH_TABLE

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…

Documentation Status

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.

Usage

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.

Variability

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

Layout

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.