At first glance, one would think that the reporting of a keypress on a power related (sleep, off, wake) button on your keyboard would be simple. I would have guessed that it worked something like this
- The key is pressed
- The key press is reported to the raw input thread (RIT) as a scan code
- The RIT then takes the appropriate power related action
USB KeyboardsLet’s start with the simple implementation. HIDClass.sys has a HIDP_PREPARSED_DATA for each top level collection. When the preparsed data is created when the collection is parsed, the parser looks for 3 usages on the generic usage page (0x01). These 3 usages are (as defined in hidusage.h)
- HID_USAGE_GENERIC_SYSCTL_POWER (0x81)
- HID_USAGE_GENERIC_SYSCTL_SLEEP (0x82)
- HID_USAGE_GENERIC_SYSCTL_WAKE (0x83)
PS2 KeyboardsPS2 (i8042prt.sys) is drastically different then HID (nearly all source for handling power buttons is in sysbtn.c). PS2 does not have a method for discovering the presence of different keys, so i8042prt must take a different approach. i8042prt must react to the keypress when it occurs. This means the first time a power button is pressed the following happens
- The ISR (I8042KeyboardInterruptService) is called and the scan code is read from the hardware. If the scancode is any of the 3 power keys (as defined in i8042prt.h and the industry spec) a DPC is queued after the keypress has been cached in the device extension
- KEYBOARD_POWER_CODE (0x5E)
- KEYBOARD_SLEEP_CODE (0x5F)
- KEYBOARD_WAKE_CODE (0x63)
- KEYBOARD_POWER_CODE (0x5E)
- In the DPC (I8xKeyboardSysButtonEventDpc), a work item is queued (registering a device interface requires PASSIVE_LEVEL)
- In the work item (I8xUpdateSysButtonCaps), register the GUID_DEVICE_SYS_BUTTON device interface
- Complete the IOCTL_GET_SYS_BUTTON_CAPS irp (I8xKeyboardGetSysButtonCaps) with the appropriate capability flags set. It is important to note that i8042prt only reports the specific capability which was discovered. If it reports extra button capabilities, the user may be presented with a choice of a power action that does not exist.
- Complete the IOCTL_GET_SYS_BUTTON_EVENT irp with the appropriate event.
OK, that was relatively simple :). The next time the same button is pressed, things are much simpler. A DPC is queued and the already pended IOCTL_GET_SYS_BUTTON_EVENT is completed. But happens if we know about the presence of one button and we detect a second button for the first time?
Again, a DPC is queued and a work item is queued from it. Instead of registering for the device interface, i8042prt fails the pended IOCTL_GET_SYS_BUTTON_EVENT, turns off the device interface and then turns it back on. i8042prt does this so that the power manager will requery the power capabilities. Once requeried, i8042prt will report the power button event. The power manager power button interface was not designed with capabilities changing after being reported the first time.
Finally, i8042prt will store the presence of these buttons in the keyboard’s devnode. This value is read when the keyboard is initially started and updated every time i8042prt detects a new power button (in the same work item that is used to register the device interface). This allows i8042prt to simplify the power button path once the button has been detected.