From 19e187d5709449908506b5ca9f78c11d3c28e5a9 Mon Sep 17 00:00:00 2001 From: John Schock Date: Tue, 3 Oct 2023 12:05:11 -0700 Subject: [PATCH 1/5] Add HidIo protocol definition. - Adds a new protocol interface that defines a general abstraction for HID devices: Protocols/HidIo. Note: does not yet support HID keyboards. This is planned future work. - [x] Impacts functionality? Adds new input support functionality. - [ ] Impacts security? - [ ] Breaking change? - [ ] Includes tests? - [x] Includes documentation? - includes standard RustDocs. Pointer verified in preboot console (UEFI setup menu and Bitlocker Recovery). N/A --- HidPkg/HidPkg.dec | 4 + HidPkg/Include/Protocol/HidIo.h | 161 ++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 HidPkg/Include/Protocol/HidIo.h diff --git a/HidPkg/HidPkg.dec b/HidPkg/HidPkg.dec index 0f0f5705f3..c756d692af 100644 --- a/HidPkg/HidPkg.dec +++ b/HidPkg/HidPkg.dec @@ -24,6 +24,10 @@ # gHidPointerProtocolGuid = { 0x80b5ee6e, 0xcbd8, 0x43ae, { 0xb6, 0xac, 0x10, 0x91, 0x7b, 0x25, 0x35, 0xb7 }} + ## HID IO Protocol GUID + # {3EA93936-6BF4-49D6-AA50-D9F5B9AD8CFF} + gHidIoProtocolGuid = {0x3ea93936, 0x6bf4, 0x49d6, { 0xaa, 0x50, 0xd9, 0xf5, 0xb9, 0xad, 0x8c, 0xff}} + [Guids] gHidPkgTokenSpaceGuid = {0x347d3cd6, 0xdf7d, 0x4397, {0xa3, 0x7a, 0x4c, 0x0f, 0x46, 0xdb, 0xdb, 0xff}} diff --git a/HidPkg/Include/Protocol/HidIo.h b/HidPkg/Include/Protocol/HidIo.h new file mode 100644 index 0000000000..5873990999 --- /dev/null +++ b/HidPkg/Include/Protocol/HidIo.h @@ -0,0 +1,161 @@ +/** @file + HID I/O + + This protocol is used by code, typically drivers, running in the EFI + boot services environment to access HID devices. + + Copyright (c) Microsoft Corporation. All rights reserved. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef HID_IO_H__ +#define HID_IO_H__ + +typedef struct _HID_IO_PROTOCOL HID_IO_PROTOCOL; + +typedef enum { + InputReport = 1, + OutputReport = 2, + Feature = 3 +} HID_REPORT_TYPE; + +/** + Retrieve the HID Report Descriptor from the device. + + @param This A pointer to the HidIo Instance + @param ReportDescriptorSize On input, the size of the buffer allocated to hold the descriptor. + On output, the actual size of the descriptor. + May be set to zero to query the required size for the descriptor. + @param ReportDescriptorBuffer A pointer to the buffer to hold the descriptor. May be NULL if ReportDescriptorSize is + zero. + + @retval EFI_SUCCESS Report descriptor successfully returned. + @retval EFI_BUFFER_TOO_SMALL The provided buffer is not large enough to hold the descriptor. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval EFI_NOT_FOUND The device does not have a report descriptor. + @retval Other Unexpected error reading descriptor. +**/ +typedef +EFI_STATUS +(EFIAPI *HID_IO_GET_REPORT_DESCRIPTOR)( + IN HID_IO_PROTOCOL *This, + IN OUT UINTN *ReportDescriptorSize, + IN OUT VOID *ReportDescriptorBuffer + ); + +/** + Retrieves a single report from the device. + + @param This A pointer to the HidIo Instance + @param ReportId Specifies which report to return if the device supports multiple input reports. + Set to zero if ReportId is not present. + @param ReportType Indicates the type of report type to retrieve. 1-Input, 3-Feature. + @param ReportBufferSize Indicates the size of the provided buffer to receive the report. + @param ReportBuffer Pointer to the buffer to receive the report. + + @retval EFI_SUCCESS Report successfully returned. + @retval EFI_OUT_OF_RESOURCES The provided buffer is not large enough to hold the report. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval Other Unexpected error reading report. +**/ +typedef +EFI_STATUS +(EFIAPI *HID_IO_GET_REPORT)( + IN HID_IO_PROTOCOL *This, + IN UINT8 ReportId, + IN HID_REPORT_TYPE ReportType, + IN UINTN ReportBufferSize, + OUT VOID *ReportBuffer + ); + +/** + Sends a single report to the device. + + @param This A pointer to the HidIo Instance + @param ReportId Specifies which report to send if the device supports multiple input reports. + Set to zero if ReportId is not present. + @param ReportType Indicates the type of report type to retrieve. 2-Output, 3-Feature. + @param ReportBufferSize Indicates the size of the provided buffer holding the report to send. + @param ReportBuffer Pointer to the buffer holding the report to send. + + @retval EFI_SUCCESS Report successfully transmitted. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval Other Unexpected error transmitting report. +**/ +typedef +EFI_STATUS +(EFIAPI *HID_IO_SET_REPORT)( + IN HID_IO_PROTOCOL *This, + IN UINT8 ReportId, + IN HID_REPORT_TYPE ReportType, + IN UINTN ReportBufferSize, + IN VOID *ReportBuffer + ); + +/** + Report received callback function. + + @param ReportBufferSize Indicates the size of the provided buffer holding the received report. + @param ReportBuffer Pointer to the buffer holding the report. + @param Context Context provided when the callback was registered. + + @retval None +**/ +typedef +VOID +(EFIAPI *HID_IO_REPORT_CALLBACK)( + IN UINT16 ReportBufferSize, + IN VOID *ReportBuffer, + IN VOID *Context OPTIONAL + ); + +/** + Registers a callback function to receive asynchronous input reports from the device. + The device driver will do any necessary initialization to configure the device to send reports. + + @param This A pointer to the HidIo Instance + @param Callback Callback function to handle reports as they are received. + @param Context Context that will be provided to the callback function. + + @retval EFI_SUCCESS Callback successfully registered. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval EFI_ALREADY_STARTED Callback function is already registered. + @retval Other Unexpected error registering callback or initiating report generation from device. +**/ +typedef +EFI_STATUS +(EFIAPI *HID_IO_REGISTER_REPORT_CALLBACK)( + IN HID_IO_PROTOCOL *This, + IN HID_IO_REPORT_CALLBACK Callback, + IN VOID *Context OPTIONAL + ); + +/** + Unregisters a previously registered callback function. + The device driver will do any necessary initialization to configure the device to stop sending reports. + + @param This A pointer to the HidIo Instance + @param Callback Callback function to unregister. + + @retval EFI_SUCCESS Callback successfully unregistered. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval EFI_NOT_STARTED Callback function was not previously registered. + @retval Other Unexpected error unregistering report or disabling report generation from device. +**/ +typedef +EFI_STATUS +(EFIAPI *HID_IO_UNREGISTER_REPORT_CALLBACK)( + IN HID_IO_PROTOCOL *This, + IN HID_IO_REPORT_CALLBACK Callback + ); + +struct _HID_IO_PROTOCOL { + HID_IO_GET_REPORT_DESCRIPTOR GetReportDescriptor; + HID_IO_GET_REPORT GetReport; + HID_IO_SET_REPORT SetReport; + HID_IO_REGISTER_REPORT_CALLBACK RegisterReportCallback; + HID_IO_UNREGISTER_REPORT_CALLBACK UnregisterReportCallback; +}; + +#endif From 9f692b533ee15cedf370bc3c1be1be5914fdeb68 Mon Sep 17 00:00:00 2001 From: John Schock Date: Tue, 3 Oct 2023 12:11:34 -0700 Subject: [PATCH 2/5] Adds Rust protocol definition of HidIo. - [x] Impacts functionality? Adds new input support functionality. - [ ] Impacts security? - [ ] Breaking change? - [ ] Includes tests? - [x] Includes documentation? - includes standard RustDocs. Pointer verified in preboot console (UEFI setup menu and Bitlocker Recovery). N/A --- Cargo.toml | 3 +- HidPkg/Crates/HidIo/Cargo.toml | 13 +++ HidPkg/Crates/HidIo/src/lib.rs | 153 +++++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 HidPkg/Crates/HidIo/Cargo.toml create mode 100644 HidPkg/Crates/HidIo/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 8106b65785..aafbea654b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,8 @@ # Add packages that generate binaries here members = [ - "MsCorePkg/HelloWorldRustDxe", + "HidPkg/Crates/HidIo", + "MsCorePkg/HelloWorldRustDxe" ] # Add packages that generate libraries here diff --git a/HidPkg/Crates/HidIo/Cargo.toml b/HidPkg/Crates/HidIo/Cargo.toml new file mode 100644 index 0000000000..057fe8fe00 --- /dev/null +++ b/HidPkg/Crates/HidIo/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "HidIo" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "hid_io" +path = "src/lib.rs" + +[dependencies] +r-efi = {workspace=true} \ No newline at end of file diff --git a/HidPkg/Crates/HidIo/src/lib.rs b/HidPkg/Crates/HidIo/src/lib.rs new file mode 100644 index 0000000000..538ba76f18 --- /dev/null +++ b/HidPkg/Crates/HidIo/src/lib.rs @@ -0,0 +1,153 @@ +//! ## Summary +//! This protocol provides access to HID devices. +//! +//! ## License +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! +#![no_std] + +pub mod protocol { + use core::ffi::c_void; + + use r_efi::efi::{Guid, Status}; + + /// HidIo interface GUID: 3EA93936-6BF4-49D6-AA50-D9F5B9AD8CFF + pub const GUID: Guid = + Guid::from_fields(0x3ea93936, 0x6bf4, 0x49d6, 0xaa, 0x50, &[0xd9, 0xf5, 0xb9, 0xad, 0x8c, 0xff]); + + #[repr(C)] + pub enum HidReportType { + InputReport = 1, + OutputReport = 2, + Feature = 3, + } + + /// Retrieve the HID Report Descriptor from the device. + /// + /// # Arguments + /// + /// * `this` - A pointer to the HidIo Instance + /// * `report_descriptor_size` - On input, the size of the buffer allocated to hold the descriptor. On output, the + /// actual size of the descriptor. May be set to zero to query the required size for the + /// descriptor. + /// * `report_descriptor_buffer` - A pointer to the buffer to hold the descriptor. May be NULL if ReportDescriptorSize + /// is zero. + /// # Return values + /// * `Status::SUCCESS` - Report descriptor successfully returned. + /// * `Status::BUFFER_TOO_SMALL` - The provided buffer is not large enough to hold the descriptor. + /// * `Status::INVALID_PARAMETER` - Invalid input parameters. + /// * `Status::NOT_FOUND` - The device does not have a report descriptor. + /// * Other - Unexpected error reading descriptor. + /// + pub type HidIoGetReportDescriptor = extern "efiapi" fn( + this: *const Protocol, + report_descriptor_size: *mut usize, + report_descriptor_buffer: *mut c_void, + ) -> Status; + + /// Retrieves a single report from the device. + /// + /// # Arguments + /// + /// * `this` - A pointer to the HidIo Instance + /// * `report_id` - Specifies which report to return if the device supports multiple input reports. Set to zero if + /// ReportId is not present. + /// * `report_type` - Indicates the type of report type to retrieve. 1-Input, 3-Feature. + /// * `report_buffer_size` - Indicates the size of the provided buffer to receive the report. + /// * `report_buffer` - Pointer to the buffer to receive the report. + /// + /// # Return values + /// * `Status::SUCCESS` - Report successfully returned. + /// * `Status::OUT_OF_RESOURCES` - The provided buffer is not large enough to hold the report. + /// * `Status::INVALID_PARAMETER` - Invalid input parameters. + /// * Other - Unexpected error reading report. + /// + pub type HidIoGetReport = extern "efiapi" fn( + this: *const Protocol, + report_id: u8, + report_type: HidReportType, + report_buffer_size: usize, + report_buffer: *mut c_void, + ) -> Status; + + /// Sends a single report to the device. + /// + /// # Arguments + /// + /// * `this` - A pointer to the HidIo Instance + /// * `report_id` - Specifies which report to send if the device supports multiple input reports. Set to zero if + /// ReportId is not present. + /// * `report_type` - Indicates the type of report type to retrieve. 2-Output, 3-Feature. + /// * `report_buffer_size` - Indicates the size of the provided buffer holding the report to send. + /// * `report_buffer` - Pointer to the buffer holding the report to send. + /// + /// # Return values + /// * `Status::SUCCESS` - Report successfully transmitted. + /// * `Status::INVALID_PARAMETER` - Invalid input parameters. + /// * Other - Unexpected error transmitting report. + /// + pub type HidIoSetReport = extern "efiapi" fn( + this: *const Protocol, + report_id: u8, + report_type: HidReportType, + report_buffer_size: usize, + report_buffer: *mut c_void, + ) -> Status; + + /// Report received callback function. + /// + /// # Arguments + /// + /// * `report_buffer_size` - Indicates the size of the provided buffer holding the received report. + /// * `report_buffer` - Pointer to the buffer holding the report. + /// * `context` - Context provided when the callback was registered. + /// + pub type HidIoReportCallback = + extern "efiapi" fn(report_buffer_size: u16, report_buffer: *mut c_void, context: *mut c_void); + + /// Registers a callback function to receive asynchronous input reports from the device. The device driver will do any + /// necessary initialization to configure the device to send reports. + /// + /// # Arguments + /// + /// * `this` - A pointer to the HidIo Instance + /// * `callback` - Callback function to handle reports as they are received. + /// * `context` - Context that will be provided to the callback function. + /// + /// # Return values + /// * `Status::SUCCESS` - Callback successfully registered. + /// * `Status::INVALID_PARAMETER` - Invalid input parameters. + /// * `Status::ALREADY_STARTED` - Callback function is already registered. + /// * Other - Unexpected error registering callback or initiating report generation from device. + /// + pub type HidIoRegisterReportCallback = + extern "efiapi" fn(this: *const Protocol, callback: HidIoReportCallback, context: *mut c_void) -> Status; + + /// Unregisters a previously registered callback function. The device driver will do any necessary initialization to + /// configure the device to stop sending reports. + /// + /// # Arguments + /// + /// * `this` - A pointer to the HidIo Instance + /// * `callback` - Callback function to unregister. + /// + /// # Return values + /// * `Status::SUCCESS` - Callback successfully unregistered. + /// * `Status::INVALID_PARAMETER` - Invalid input parameters. + /// * `Status::NOT_STARTED` - Callback function was not previously registered. + /// * Other - Unexpected error unregistering report or disabling report generation from device. + /// + pub type HidIoUnregisterReportCallback = + extern "efiapi" fn(this: *const Protocol, callback: HidIoReportCallback) -> Status; + + /// The HID_IO protocol provides a set of services for interacting with a HID device. + #[repr(C)] + pub struct Protocol { + pub get_report_descriptor: HidIoGetReportDescriptor, + pub get_report: HidIoGetReport, + pub set_report: HidIoSetReport, + pub register_report_callback: HidIoRegisterReportCallback, + pub unregister_report_callback: HidIoUnregisterReportCallback, + } +} From 4f69eb3a645e35f1294bb8d457c2d339e6548cf7 Mon Sep 17 00:00:00 2001 From: John Schock Date: Tue, 3 Oct 2023 12:13:16 -0700 Subject: [PATCH 3/5] Adds Rust protocol definition for AbsolutePointer - [x] Impacts functionality? Adds new input support functionality. - [ ] Impacts security? - [ ] Breaking change? - [ ] Includes tests? - [x] Includes documentation? - includes standard RustDocs. Pointer verified in preboot console (UEFI setup menu and Bitlocker Recovery). N/A --- Cargo.toml | 1 + HidPkg/Crates/AbsolutePointer/Cargo.toml | 13 +++ HidPkg/Crates/AbsolutePointer/src/lib.rs | 114 +++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 HidPkg/Crates/AbsolutePointer/Cargo.toml create mode 100644 HidPkg/Crates/AbsolutePointer/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index aafbea654b..011dc0e9c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ # Add packages that generate binaries here members = [ + "HidPkg/Crates/AbsolutePointer", "HidPkg/Crates/HidIo", "MsCorePkg/HelloWorldRustDxe" ] diff --git a/HidPkg/Crates/AbsolutePointer/Cargo.toml b/HidPkg/Crates/AbsolutePointer/Cargo.toml new file mode 100644 index 0000000000..a0e7ce3645 --- /dev/null +++ b/HidPkg/Crates/AbsolutePointer/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "AbsolutePointer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "absolute_pointer" +path = "src/lib.rs" + +[dependencies] +r-efi = {workspace=true} \ No newline at end of file diff --git a/HidPkg/Crates/AbsolutePointer/src/lib.rs b/HidPkg/Crates/AbsolutePointer/src/lib.rs new file mode 100644 index 0000000000..1e02ec2630 --- /dev/null +++ b/HidPkg/Crates/AbsolutePointer/src/lib.rs @@ -0,0 +1,114 @@ +//! +//! ## Summary +//! This module defines the Absolute Pointer protocol. +//! +//! Refer to UEFI spec version 2.10 section 12.7. +//! +//! ## License +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! +//! +#![no_std] + +pub mod protocol { + use r_efi::efi::{Event, Guid, Status}; + + /// Absolute pointer interface GUID: 8D59D32B-C655-4AE9-9B15-F25904992A43 + pub const PROTOCOL_GUID: Guid = + Guid::from_fields(0x8D59D32B, 0xC655, 0x4AE9, 0x9B, 0x15, &[0xF2, 0x59, 0x04, 0x99, 0x2A, 0x43]); + + /// This function resets the pointer device hardware. As part of initialization process, the firmware/device will make + /// a quick but reasonable attempt to verify that the device is functioning. If the ExtendedVerification flag is TRUE + /// the firmware may take an extended amount of time to verify the device is operating on reset. Otherwise the reset + /// operation is to occur as quickly as possible. The hardware verification process is not defined by this + /// specification and is left up to the platform firmware or driver to implement. + /// + /// # Arguments + /// * `this` - A pointer to the AbsolutePointer Instance + /// * `extended_verification` - indicates whether extended reset is requested. + /// + /// # Return Values + /// * `Status::SUCCESS` - The device was reset + /// * `Status::DEVICE_ERROR` - The device is not functioning correctly and could not be reset. + /// + pub type AbsolutePointerReset = extern "efiapi" fn(this: *const Protocol, extended_verification: bool) -> Status; + + /// This function retrieves the current state of a pointer device. This includes information on the active state + /// associated with the pointer device and the current position of the axes associated with the pointer device. + /// If the state of the pointer device has not changed since the last call to GetState(), then EFI_NOT_READY is + /// returned. If the state of the pointer device has changed since the last call to GetState(), then the state + /// information is placed in State, and efi::Status::SUCCESS is returned. If a device error occurs while attempting to + /// retrieve the state information, then efi::Status::DEVICE_ERROR is returned. + /// + /// # Arguments + /// * `this` - A pointer to the AbsolutePointer Instance + /// * `state` - A pointer to the state information on the pointer device. + /// + /// # Return Values + /// * `Status::SUCCESS` - The state of the pointer device was returned in state. + /// * `Status::NOT_READY` - The state of the pointer device has not changed since the last call to this function. + /// * `Status::DEVICE_ERROR` - A device error occurred while attempting to retrieve the pointer device current state. + /// + pub type AbsolutePointerGetState = + extern "efiapi" fn(this: *const Protocol, state: *mut AbsolutePointerState) -> Status; + + /// Describes the current state of the pointer. + #[derive(Debug, Default, Clone, Copy)] + #[repr(C)] + pub struct AbsolutePointerState { + /// The unsigned position of the activation on the x-axis. If the absolute_min_x and the absolute_max_x fields of + /// the AbsolutePointerMode structure are both 0, then this pointer device does not support an x-axis, and this + /// field must be ignored. + pub current_x: u64, + /// The unsigned position of the activation on the y-axis. If the absolute_min_y and the absolute_max_y fields of + /// the AbsolutePointerMode structure are both 0, then this pointer device does not support an y-axis, and this + /// field must be ignored. + pub current_y: u64, + /// The unsigned position of the activation on the z-axis. If the absolute_min_z and the absolute_max_z fields of + /// the AbsolutePointerMode structure are both 0, then this pointer device does not support an z-axis, and this + /// field must be ignored. + pub current_z: u64, + /// Bits are set to 1 in this field to indicate that device buttons are active. + pub active_buttons: u32, + } + + /// Describes the mode of the pointer. + #[derive(Debug, Default, Clone, Copy)] + #[repr(C)] + pub struct AbsolutePointerMode { + pub absolute_min_x: u64, + /// The Absolute Minimum of the device on the x-axis + pub absolute_min_y: u64, + /// The Absolute Minimum of the device on the y-axis. + pub absolute_min_z: u64, + /// The Absolute Minimum of the device on the z-axis. + /// The Absolute Maximum of the device on the x-axis. If 0, and absolute_min_x is 0, then x-axis is unsupported. + pub absolute_max_x: u64, + /// The Absolute Maximum of the device on the y-axis. If 0, and absolute_min_y is 0, then y-axis is unsupported. + pub absolute_max_y: u64, + /// The Absolute Maximum of the device on the z-axis. If 0, and absolute_min_z is 0, then z-axis is unsupported. + pub absolute_max_z: u64, + /// Supported device attributes. + pub attributes: u32, + } + + /// If set in [`AbsolutePointerMode::attributes`], indicates this device supports an alternate button input. + pub const SUPPORTS_ALT_ACTIVE: u32 = 0x00000001; + /// If set in [`AbsolutePointerMode::attributes`], indicates this device returns pressure data in current_z. + pub const SUPPORTS_PRESSURE_AS_Z: u32 = 0x00000002; + + /// The EFI_ABSOLUTE_POINTER_PROTOCOL provides a set of services for a pointer device that can be used as an input + /// device from an application written to this specification. The services include the ability to: reset the pointer + /// device, retrieve the state of the pointer device, and retrieve the capabilities of the pointer device. The service + /// also provides certain data items describing the device. + #[derive(Debug)] + #[repr(C)] + pub struct Protocol { + pub reset: AbsolutePointerReset, + pub get_state: AbsolutePointerGetState, + /// Event to use with WaitForEvent() to wait for input from the pointer device. + pub wait_for_input: Event, + pub mode: *mut AbsolutePointerMode, + } +} From ae68584e90f9882eca41f3b1d61ebb5621a45c00 Mon Sep 17 00:00:00 2001 From: John Schock Date: Tue, 3 Oct 2023 13:14:57 -0700 Subject: [PATCH 4/5] Adds UsbHidDxe driver - written in C, provides an implementation of HidIo over USB. Note: does not yet support HID keyboards. This is planned future work. - [x] Impacts functionality? Adds new input support functionality. - [ ] Impacts security? - [ ] Breaking change? - [ ] Includes tests? - [x] Includes documentation? - includes standard RustDocs. Pointer verified in preboot console (UEFI setup menu and Bitlocker Recovery). N/A Add UsbHidDxe driver that produces HidIo instance on USB HID devices. --- HidPkg/HidPkg.dsc | 1 + HidPkg/UsbHidDxe/UsbHidDxe.c | 1072 ++++++++++++++++++++++++++++++++ HidPkg/UsbHidDxe/UsbHidDxe.inf | 42 ++ 3 files changed, 1115 insertions(+) create mode 100644 HidPkg/UsbHidDxe/UsbHidDxe.c create mode 100644 HidPkg/UsbHidDxe/UsbHidDxe.inf diff --git a/HidPkg/HidPkg.dsc b/HidPkg/HidPkg.dsc index f7126e159d..205d4a41df 100644 --- a/HidPkg/HidPkg.dsc +++ b/HidPkg/HidPkg.dsc @@ -55,6 +55,7 @@ HidPkg/HidMouseAbsolutePointerDxe/HidMouseAbsolutePointerDxe.inf HidPkg/UsbKbHidDxe/UsbKbHidDxe.inf HidPkg/UsbMouseHidDxe/UsbMouseHidDxe.inf + HidPkg/UsbHidDxe/UsbHidDxe.inf [BuildOptions] #force deprecated interfaces off diff --git a/HidPkg/UsbHidDxe/UsbHidDxe.c b/HidPkg/UsbHidDxe/UsbHidDxe.c new file mode 100644 index 0000000000..71d4257f99 --- /dev/null +++ b/HidPkg/UsbHidDxe/UsbHidDxe.c @@ -0,0 +1,1072 @@ +/** @file + USB HID Driver that manages USB HID devices and produces the HidIo protocol. + + USB HID Driver consumes USB I/O Protocol and Device Path Protocol, and produces + the HidIo protocol on USB HID devices. + + Copyright (c) Microsoft Corporation. All rights reserved. + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#define CLASS_HID 3 +#define SUBCLASS_BOOT 1 +#define PROTOCOL_KEYBOARD 1 + +#define USB_HID_DEV_SIGNATURE SIGNATURE_32('U','H','I','D') + +typedef struct { + UINT32 Signature; + HID_IO_PROTOCOL HidIo; + EFI_USB_IO_PROTOCOL *UsbIo; + EFI_USB_INTERFACE_DESCRIPTOR InterfaceDescriptor; + EFI_USB_ENDPOINT_DESCRIPTOR IntInEndpointDescriptor; + EFI_USB_HID_DESCRIPTOR *HidDescriptor; + UINTN ReportDescriptorLength; + VOID *ReportDescriptor; + HID_IO_REPORT_CALLBACK ReportCallback; + VOID *CallbackContext; + EFI_EVENT DelayedRecoveryEvent; +} USB_HID_DEV; + +#define USB_HID_DEV_FROM_HID_IO_PROTOCOL(a) \ + CR(a, USB_HID_DEV, HidIo, USB_HID_DEV_SIGNATURE) + +EFI_STATUS +InitiateAsyncInterruptInputTransfers ( + USB_HID_DEV *Device + ); + +EFI_STATUS +ShutdownAsyncInterruptInputTransfers ( + USB_HID_DEV *Device + ); + +/** + Retrieve the HID Report Descriptor from the device. + + @param This A pointer to the HidIo Instance + @param ReportDescriptorSize On input, the size of the buffer allocated to hold the descriptor. + On output, the actual size of the descriptor. + May be set to zero to query the required size for the descriptor. + @param ReportDescriptorBuffer A pointer to the buffer to hold the descriptor. May be NULL if ReportDescriptorSize is + zero. + + @retval EFI_SUCCESS Report descriptor successfully returned. + @retval EFI_BUFFER_TOO_SMALL The provided buffer is not large enough to hold the descriptor. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval EFI_NOT_FOUND The device does not have a report descriptor. + @retval Other Unexpected error reading descriptor. +**/ +EFI_STATUS +EFIAPI +HidGetReportDescriptor ( + IN HID_IO_PROTOCOL *This, + IN OUT UINTN *ReportDescriptorSize, + IN OUT VOID *ReportDescriptorBuffer + ) +{ + EFI_STATUS Status; + USB_HID_DEV *UsbHidDevice; + UINTN Index; + UINTN DescriptorLength; + + if ((This == NULL) || (ReportDescriptorSize == NULL)) { + return EFI_INVALID_PARAMETER; + } + + UsbHidDevice = USB_HID_DEV_FROM_HID_IO_PROTOCOL (This); + + if (UsbHidDevice->ReportDescriptorLength == 0) { + // Report Descriptor not yet read + + // Get report descriptor length from Hid Descriptor. + if (UsbHidDevice->HidDescriptor == NULL) { + ASSERT (UsbHidDevice->HidDescriptor != NULL); + return EFI_NOT_FOUND; + } + + for (Index = 0; Index < UsbHidDevice->HidDescriptor->NumDescriptors; Index++) { + if (UsbHidDevice->HidDescriptor->HidClassDesc[Index].DescriptorType == USB_DESC_TYPE_REPORT) { + break; + } + } + + if (Index == UsbHidDevice->HidDescriptor->NumDescriptors) { + return EFI_NOT_FOUND; + } + + // Index is set to the Report Descriptor index. + DescriptorLength = UsbHidDevice->HidDescriptor->HidClassDesc[Index].DescriptorLength; + UsbHidDevice->ReportDescriptor = AllocateZeroPool (DescriptorLength); + if (UsbHidDevice->ReportDescriptor == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = UsbGetReportDescriptor ( + UsbHidDevice->UsbIo, + UsbHidDevice->InterfaceDescriptor.InterfaceNumber, + (UINT16)DescriptorLength, + (UINT8 *)UsbHidDevice->ReportDescriptor + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + UsbHidDevice->ReportDescriptorLength = DescriptorLength; + } + + if (*ReportDescriptorSize < UsbHidDevice->ReportDescriptorLength) { + *ReportDescriptorSize = UsbHidDevice->ReportDescriptorLength; + return EFI_BUFFER_TOO_SMALL; + } + + if (ReportDescriptorBuffer == NULL) { + return EFI_INVALID_PARAMETER; + } + + CopyMem (ReportDescriptorBuffer, UsbHidDevice->ReportDescriptor, UsbHidDevice->ReportDescriptorLength); + + return EFI_SUCCESS; +} + +/** + Retrieves a single report from the device. + + @param This A pointer to the HidIo Instance + @param ReportId Specifies which report to return if the device supports multiple input reports. + Set to zero if ReportId is not present. + @param ReportType Indicates the type of report type to retrieve. 1-Input, 3-Feature. + @param ReportBufferSize Indicates the size of the provided buffer to receive the report. The max size is MAX_UINT16. + @param ReportBuffer Pointer to the buffer to receive the report. + + @retval EFI_SUCCESS Report successfully returned. + @retval EFI_OUT_OF_RESOURCES The provided buffer is not large enough to hold the report. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval Other Unexpected error reading report. +**/ +EFI_STATUS +EFIAPI +HidGetReport ( + IN HID_IO_PROTOCOL *This, + IN UINT8 ReportId, + IN HID_REPORT_TYPE ReportType, + IN UINTN ReportBufferSize, + OUT VOID *ReportBuffer + ) +{ + USB_HID_DEV *UsbHidDevice; + + if ((This == NULL) || (ReportBufferSize == 0) || (ReportBufferSize > MAX_UINT16) || (ReportBuffer == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // Only support Get_Report for Input or Feature reports. + if (!((ReportType == HID_INPUT_REPORT) || (ReportType == HID_FEATURE_REPORT))) { + return EFI_INVALID_PARAMETER; + } + + UsbHidDevice = USB_HID_DEV_FROM_HID_IO_PROTOCOL (This); + + return UsbGetReportRequest ( + UsbHidDevice->UsbIo, + UsbHidDevice->InterfaceDescriptor.InterfaceNumber, + ReportId, + (UINT8)ReportType, + (UINT16)ReportBufferSize, + (UINT8 *)ReportBuffer + ); +} + +/** + Sends a single report to the device. + + @param This A pointer to the HidIo Instance + @param ReportId Specifies which report to return if the device supports multiple input reports. + Set to zero if ReportId is not present. + @param ReportType Indicates the type of report type to retrieve. 2-Output, 3-Feature. + @param ReportBufferSize Indicates the size of the provided buffer holding the report to send. + @param ReportBuffer Pointer to the buffer holding the report to send. + + @retval EFI_SUCCESS Report successfully transmitted. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval Other Unexpected error transmitting report. +**/ +EFI_STATUS +EFIAPI +HidSetReport ( + IN HID_IO_PROTOCOL *This, + IN UINT8 ReportId, + IN HID_REPORT_TYPE ReportType, + IN UINTN ReportBufferSize, + IN VOID *ReportBuffer + ) +{ + USB_HID_DEV *UsbHidDevice; + + if ((This == NULL) || (ReportBufferSize == 0) || (ReportBufferSize > MAX_UINT16) || (ReportBuffer == NULL)) { + return EFI_INVALID_PARAMETER; + } + + // Only support Set_Report for Output or Feature reports. + if (!((ReportType == HID_OUTPUT_REPORT) || (ReportType == HID_FEATURE_REPORT))) { + return EFI_INVALID_PARAMETER; + } + + UsbHidDevice = USB_HID_DEV_FROM_HID_IO_PROTOCOL (This); + + // Note: This implementation does not support Set_Report via interrupt out pipes. + // Per HID 1.1, "operating systems that do not support HID Interrupt Out endpoints will route all Output reports + // through the control endpoint." + return UsbSetReportRequest ( + UsbHidDevice->UsbIo, + UsbHidDevice->InterfaceDescriptor.InterfaceNumber, + ReportId, + (UINT8)ReportType, + (UINT16)ReportBufferSize, + (UINT8 *)ReportBuffer + ); +} + +/** + Registers a callback function to receive asynchronous input reports from the device. + The device driver will do any necessary initialization to configure the device to send reports. + + @param This A pointer to the HidIo Instance + @param Callback Callback function to handle reports as they are received. + @param Context Context that will be provided to the callback function. + + @retval EFI_SUCCESS Callback successfully registered. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval EFI_ALREADY_STARTED Callback function is already registered. + @retval Other Unexpected error registering callback or initiating report generation from device. +**/ +EFI_STATUS +EFIAPI +HidRegisterReportCallback ( + IN HID_IO_PROTOCOL *This, + IN HID_IO_REPORT_CALLBACK Callback, + IN VOID *Context OPTIONAL + ) +{ + EFI_STATUS Status; + USB_HID_DEV *UsbHidDevice; + + if ((This == NULL) || (Callback == NULL)) { + return EFI_INVALID_PARAMETER; + } + + UsbHidDevice = USB_HID_DEV_FROM_HID_IO_PROTOCOL (This); + + if (UsbHidDevice->ReportCallback != NULL) { + return EFI_ALREADY_STARTED; + } + + UsbHidDevice->ReportCallback = Callback; + UsbHidDevice->CallbackContext = Context; + + Status = InitiateAsyncInterruptInputTransfers (UsbHidDevice); + ASSERT_EFI_ERROR (Status); + + return Status; +} + +/** + Unregisters a previously registered callback function. + The device driver will do any necessary initialization to configure the device to stop sending reports. + + @param This A pointer to the HidIo Instance + @param Callback Callback function to unregister. + + @retval EFI_SUCCESS Callback successfully unregistered. + @retval EFI_INVALID_PARAMETER Invalid input parameters. + @retval EFI_NOT_STARTED Callback function was not previously registered. + @retval Other Unexpected error unregistering report or disabling report generation from device. +**/ +EFI_STATUS +EFIAPI +HidUnregisterReportCallback ( + IN HID_IO_PROTOCOL *This, + IN HID_IO_REPORT_CALLBACK Callback + ) +{ + EFI_STATUS Status; + USB_HID_DEV *UsbHidDevice; + + if ((This == NULL) || (Callback == NULL)) { + return EFI_INVALID_PARAMETER; + } + + UsbHidDevice = USB_HID_DEV_FROM_HID_IO_PROTOCOL (This); + + if (UsbHidDevice->ReportCallback != Callback) { + return EFI_NOT_STARTED; + } + + Status = ShutdownAsyncInterruptInputTransfers (UsbHidDevice); + ASSERT_EFI_ERROR (Status); + + UsbHidDevice->ReportCallback = NULL; + + return Status; +} + +/** + Handles interrupt completion on USB with new HID report. + + @param Data Pointer to buffer containing data returned by USB interrupt completion. + @param DataLength Length of data buffer. + @param Context Context associated with transfer (points to UsbHidDevice that scheduled the transfer) + @param Result Indicates if there was an error on USB. + + @retval EFI_SUCCESS Interrupt handled and passed up the stack. + @retval EFI_DEVICE_ERROR There as an error with the transaction. The interrupt will be re-submitted after a + delay. + @retval Other Unexpected error handling the interrupt completion. +**/ +EFI_STATUS +EFIAPI +OnReportInterruptComplete ( + IN VOID *Data, + IN UINTN DataLength, + IN VOID *Context, + IN UINT32 Result + ) +{ + EFI_STATUS Status; + USB_HID_DEV *UsbHidDevice; + UINT32 UsbResult; + + UsbHidDevice = (USB_HID_DEV *)Context; + + if (Result != EFI_USB_NOERROR) { + if ((Result & EFI_USB_ERR_STALL) == EFI_USB_ERR_STALL) { + Status = UsbClearEndpointHalt ( + UsbHidDevice->UsbIo, + UsbHidDevice->IntInEndpointDescriptor.EndpointAddress, + &UsbResult + ); + ASSERT_EFI_ERROR (Status); + } + + // Delete & Submit this interrupt again + // Handler of DelayedRecoveryEvent triggered by timer will re-submit the interrupt. + Status = UsbHidDevice->UsbIo->UsbAsyncInterruptTransfer ( + UsbHidDevice->UsbIo, + UsbHidDevice->IntInEndpointDescriptor.EndpointAddress, + FALSE, + 0, + 0, + NULL, + NULL + ); + ASSERT_EFI_ERROR (Status); + + // Queue delayed recovery event. EFI_USB_INTERRUPT_DELAY is defined in USB standard for error handling. + Status = gBS->SetTimer ( + UsbHidDevice->DelayedRecoveryEvent, + TimerRelative, + EFI_USB_INTERRUPT_DELAY + ); + ASSERT_EFI_ERROR (Status); + + return EFI_DEVICE_ERROR; + } + + if (DataLength > MAX_UINT16) { + return EFI_DEVICE_ERROR; + } + + if ((DataLength == 0) || (Data == NULL)) { + return EFI_SUCCESS; + } + + if (UsbHidDevice->ReportCallback != NULL) { + UsbHidDevice->ReportCallback ( + (UINT16)DataLength, + Data, + UsbHidDevice->CallbackContext + ); + } + + return EFI_SUCCESS; +} + +/** + Delayed recovery handler. Invoked when there is a USB error with the async interrupt transfer that reads reports from + the endpoint device. Re-schedules the interrupt. + + @param Event Event associated with this callback. + @param Context Context associated with transfer (points to UsbHidDevice that scheduled the transfer) + + @retval None +**/ +VOID +EFIAPI +DelayedRecoveryHandler ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + EFI_STATUS Status; + USB_HID_DEV *UsbHidDev; + + UsbHidDev = (USB_HID_DEV *)Context; + + // Re-submit Asynchronous Interrupt Transfer for recovery. + Status = UsbHidDev->UsbIo->UsbAsyncInterruptTransfer ( + UsbHidDev->UsbIo, + UsbHidDev->IntInEndpointDescriptor.EndpointAddress, + TRUE, + UsbHidDev->IntInEndpointDescriptor.Interval, + UsbHidDev->IntInEndpointDescriptor.MaxPacketSize, + OnReportInterruptComplete, + UsbHidDev + ); + ASSERT_EFI_ERROR (Status); +} + +/** + Initiates input reports from the endpoint by scheduling an async interrupt transaction to poll the device. + + @param UsbHidDevice The UsbHidDevice instance that is initiating transfers. + + @retval EFI_SUCCESS Async Interrupt transfer to read input reports has been successfully initiated. + @retval Other Unexpected error initiating the transfer. +**/ +EFI_STATUS +InitiateAsyncInterruptInputTransfers ( + USB_HID_DEV *UsbHidDevice + ) +{ + EFI_STATUS Status; + + // Configure event for delayed recovery handler. + if (UsbHidDevice->DelayedRecoveryEvent != NULL) { + Status = gBS->CloseEvent (UsbHidDevice->DelayedRecoveryEvent); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + UsbHidDevice->DelayedRecoveryEvent = NULL; + } + + Status = gBS->CreateEvent ( + EVT_TIMER | EVT_NOTIFY_SIGNAL, + TPL_NOTIFY, + DelayedRecoveryHandler, + UsbHidDevice, + &UsbHidDevice->DelayedRecoveryEvent + ); + + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + return Status; + } + + // Start the async interrupt transfers for input reports. + Status = UsbHidDevice->UsbIo->UsbAsyncInterruptTransfer ( + UsbHidDevice->UsbIo, + UsbHidDevice->IntInEndpointDescriptor.EndpointAddress, + TRUE, + UsbHidDevice->IntInEndpointDescriptor.Interval, + UsbHidDevice->IntInEndpointDescriptor.MaxPacketSize, + OnReportInterruptComplete, + UsbHidDevice + ); + ASSERT_EFI_ERROR (Status); + + return Status; +} + +/** + Shuts down input reports from the endpoint by deleting the async interrupt transaction to poll the device. + + @param UsbHidDevice The UsbHidDevice instance that is initiating transfers. + + @retval EFI_SUCCESS Async Interrupt transfer to read input reports has been successfully deleted. + @retval Other Unexpected error deleting the transfer. +**/ +EFI_STATUS +ShutdownAsyncInterruptInputTransfers ( + USB_HID_DEV *UsbHidDevice + ) +{ + EFI_STATUS Status; + + // Stop the async transfers for input reports. + Status = UsbHidDevice->UsbIo->UsbAsyncInterruptTransfer ( + UsbHidDevice->UsbIo, + UsbHidDevice->IntInEndpointDescriptor.EndpointAddress, + FALSE, + 0, + 0, + NULL, + NULL + ); + + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + DEBUG ((DEBUG_WARN, "[%a] unexpected error shutting down async interrupt transfer: %r\n", __FUNCTION__, Status)); + } + + // Close the Delayed recovery event. + if (UsbHidDevice->DelayedRecoveryEvent != NULL) { + Status = gBS->CloseEvent (UsbHidDevice->DelayedRecoveryEvent); + if (EFI_ERROR (Status) && (Status != EFI_NOT_FOUND)) { + DEBUG ((DEBUG_WARN, "[%a] unexpected error closing delayed recovery event: %r\n", __FUNCTION__, Status)); + } + + UsbHidDevice->DelayedRecoveryEvent = NULL; + } + + return Status; +} + +/** + Indicates whether this is a USB hid device that this driver should manage. + + @param UsbIio UsbIo instance that can be used to perform the test. + + @retval TRUE This driver can support this device. + @retval FALSE This driver cannot support this device. +**/ +BOOLEAN +IsUsbHid ( + IN EFI_USB_IO_PROTOCOL *UsbIo + ) +{ + EFI_STATUS Status; + EFI_USB_INTERFACE_DESCRIPTOR InterfaceDescriptor; + + // + // Get the default interface descriptor + // + Status = UsbIo->UsbGetInterfaceDescriptor ( + UsbIo, + &InterfaceDescriptor + ); + + if (EFI_ERROR (Status)) { + return FALSE; + } + + // Note: for now we exclude keyboards as they are not yet supported by + // UefiHidDxe. ADO #3570600 covers implementing keyboard support. + if ((InterfaceDescriptor.InterfaceClass == CLASS_HID) && + (InterfaceDescriptor.InterfaceSubClass == SUBCLASS_BOOT) && + (InterfaceDescriptor.InterfaceProtocol == PROTOCOL_KEYBOARD) + ) + { + return FALSE; + } + + return (InterfaceDescriptor.InterfaceClass == CLASS_HID); +} + +/** + Retrieves the full HID descriptor for the given device. + + @param UsbIo UsbIo interface used to read the HID descriptor + @param Interface Interface identifier. + @param HidDescriptor Receives a pointer to a freshly allocated buffer containing the HID descriptor. Caller + is required to free. + + @retval EFI_SUCCESS Full HID descriptor returned in UsbGetFullHidDescriptor + @retval Other Unexpected error retrieving the full HID descriptor. +**/ +EFI_STATUS +UsbGetFullHidDescriptor ( + IN EFI_USB_IO_PROTOCOL *UsbIo, + IN UINT8 Interface, + OUT EFI_USB_HID_DESCRIPTOR **HidDescriptor + ) +{ + UINT32 Status; + EFI_STATUS Result; + EFI_USB_DEVICE_REQUEST Request; + EFI_USB_HID_DESCRIPTOR *TempDescriptor; + + // HID descriptor is variable length. + // First read enough of the descriptor to determine the full length. + TempDescriptor = AllocatePool (sizeof (EFI_USB_HID_DESCRIPTOR)); + if (TempDescriptor == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Request.RequestType = USB_HID_GET_DESCRIPTOR_REQ_TYPE; + Request.Request = USB_REQ_GET_DESCRIPTOR; + Request.Value = (UINT16)(USB_DESC_TYPE_HID << 8); + Request.Index = Interface; + Request.Length = (UINT16)sizeof (EFI_USB_HID_DESCRIPTOR); + + Result = UsbIo->UsbControlTransfer ( + UsbIo, + &Request, + EfiUsbDataIn, + PcdGet32 (PcdUsbTransferTimeoutValue), + TempDescriptor, + sizeof (EFI_USB_HID_DESCRIPTOR), + &Status + ); + if (EFI_ERROR (Result)) { + DEBUG ((DEBUG_WARN, "[%a] unexpected failure reading HID descriptor: %r, Status: 0x%x\n", __FUNCTION__, Result, Status)); + goto ErrorExit; + } + + // If the whole descriptor was read (i.e. there is only one HID class descriptor), then just return. + if (TempDescriptor->Length == sizeof (EFI_USB_HID_DESCRIPTOR)) { + *HidDescriptor = TempDescriptor; + return EFI_SUCCESS; + } + + // If the descriptor is somehow _less_ in size than expected, bail out with an error. + if (TempDescriptor->Length < sizeof (EFI_USB_HID_DESCRIPTOR)) { + DEBUG ((DEBUG_ERROR, "[%a]Invalid sizeof (EFI_USB_HID_DESCRIPTOR): %d\n", __FUNCTION__, sizeof (EFI_USB_HID_DESCRIPTOR))); + Result = EFI_DEVICE_ERROR; + goto ErrorExit; + } + + // If there is more than one HID class descriptor, allocate and re-read the whole descriptor. + TempDescriptor = ReallocatePool (sizeof (EFI_USB_HID_DESCRIPTOR), TempDescriptor->Length, TempDescriptor); + if (TempDescriptor == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Request.Length = (UINT16)TempDescriptor->Length; + Result = UsbIo->UsbControlTransfer ( + UsbIo, + &Request, + EfiUsbDataIn, + PcdGet32 (PcdUsbTransferTimeoutValue), + *HidDescriptor, + TempDescriptor->Length, + &Status + ); + + if (!EFI_ERROR (Result)) { + *HidDescriptor = TempDescriptor; + return EFI_SUCCESS; + } + +ErrorExit: + if (EFI_ERROR (Result) && (TempDescriptor != NULL)) { + FreePool (TempDescriptor); + } + + return Result; +} + +/** + Retrieves the descriptors for the given device. + + @param UsbHidDevice Pointer to the UsbHidDevice instance that will be updated with the descriptors. + + @retval EFI_SUCCESS UsbHidDevice context has been updated with the descriptor information. + @retval Other Unexpected error retrieving the descriptors. +**/ +EFI_STATUS +ReadDescriptors ( + IN USB_HID_DEV *UsbHidDevice + ) +{ + EFI_STATUS Status; + UINT8 Index; + EFI_USB_ENDPOINT_DESCRIPTOR EndpointDescriptor; + + ZeroMem (&UsbHidDevice->IntInEndpointDescriptor, sizeof (UsbHidDevice->IntInEndpointDescriptor)); + + Status = UsbHidDevice->UsbIo->UsbGetInterfaceDescriptor (UsbHidDevice->UsbIo, &UsbHidDevice->InterfaceDescriptor); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto ErrorExit; + } + + for (Index = 0; Index < UsbHidDevice->InterfaceDescriptor.NumEndpoints; Index++) { + Status = UsbHidDevice->UsbIo->UsbGetEndpointDescriptor (UsbHidDevice->UsbIo, Index, &EndpointDescriptor); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto ErrorExit; + } + + if ((EndpointDescriptor.Attributes & 0x03) == USB_ENDPOINT_INTERRUPT) { + if ((EndpointDescriptor.EndpointAddress & USB_ENDPOINT_DIR_IN) != 0) { + CopyMem (&UsbHidDevice->IntInEndpointDescriptor, &EndpointDescriptor, sizeof (EndpointDescriptor)); + break; + } + } + } + + // Interrupt In end point must be found. + if (UsbHidDevice->IntInEndpointDescriptor.Length == 0) { + Status = EFI_DEVICE_ERROR; + goto ErrorExit; + } + + Status = UsbGetFullHidDescriptor (UsbHidDevice->UsbIo, UsbHidDevice->InterfaceDescriptor.InterfaceNumber, &UsbHidDevice->HidDescriptor); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto ErrorExit; + } + +ErrorExit: + return Status; +} + +/** + Tests to see if this driver supports a given controller. If a child device is provided, + it further tests to see if this driver supports creating a handle for the specified child device. + + This function checks to see if the driver specified by This supports the device specified by + ControllerHandle. Drivers will typically use the device path attached to + ControllerHandle and/or the services from the bus I/O abstraction attached to + ControllerHandle to determine if the driver supports ControllerHandle. This function + may be called many times during platform initialization. In order to reduce boot times, the tests + performed by this function must be very small, and take as little time as possible to execute. This + function must not change the state of any hardware devices, and this function must be aware that the + device specified by ControllerHandle may already be managed by the same driver or a + different driver. This function must match its calls to AllocatePages() with FreePages(), + AllocatePool() with FreePool(), and OpenProtocol() with CloseProtocol(). + Because ControllerHandle may have been previously started by the same driver, if a protocol is + already in the opened state, then it must not be closed with CloseProtocol(). This is required + to guarantee the state of ControllerHandle is not modified by this function. + + @param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance. + @param[in] ControllerHandle The handle of the controller to test. This handle + must support a protocol interface that supplies + an I/O abstraction to the driver. + @param[in] RemainingDevicePath A pointer to the remaining portion of a device path. This + parameter is ignored by device drivers, and is optional for bus + drivers. For bus drivers, if this parameter is not NULL, then + the bus driver must determine if the bus controller specified + by ControllerHandle and the child controller specified + by RemainingDevicePath are both supported by this + bus driver. + + @retval EFI_SUCCESS The device specified by ControllerHandle and + RemainingDevicePath is supported by the driver specified by This. + @retval EFI_ALREADY_STARTED The device specified by ControllerHandle and + RemainingDevicePath is already being managed by the driver + specified by This. + @retval EFI_ACCESS_DENIED The device specified by ControllerHandle and + RemainingDevicePath is already being managed by a different + driver or an application that requires exclusive access. + Currently not implemented. + @retval EFI_UNSUPPORTED The device specified by ControllerHandle and + RemainingDevicePath is not supported by the driver specified by This. +**/ +EFI_STATUS +EFIAPI +UsbHidDriverBindingSupported ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE Controller, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + EFI_STATUS Status; + EFI_USB_IO_PROTOCOL *UsbIo; + + Status = gBS->OpenProtocol ( + Controller, + &gEfiUsbIoProtocolGuid, + (VOID **)&UsbIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Use the USB I/O Protocol interface to check whether Controller is + // a hid device that can be managed by this driver. + // + Status = EFI_SUCCESS; + if (!IsUsbHid (UsbIo)) { + Status = EFI_UNSUPPORTED; + } + + gBS->CloseProtocol ( + Controller, + &gEfiUsbIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + return Status; +} + +/** + Starts a device controller or a bus controller. + + The Start() function is designed to be invoked from the EFI boot service ConnectController(). + As a result, much of the error checking on the parameters to Start() has been moved into this + common boot service. It is legal to call Start() from other locations, + but the following calling restrictions must be followed, or the system behavior will not be deterministic. + 1. ControllerHandle must be a valid EFI_HANDLE. + 2. If RemainingDevicePath is not NULL, then it must be a pointer to a naturally aligned + EFI_DEVICE_PATH_PROTOCOL. + 3. Prior to calling Start(), the Supported() function for the driver specified by This must + have been called with the same calling parameters, and Supported() must have returned EFI_SUCCESS. + + @param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance. + @param[in] ControllerHandle The handle of the controller to start. This handle + must support a protocol interface that supplies + an I/O abstraction to the driver. + @param[in] RemainingDevicePath A pointer to the remaining portion of a device path. This + parameter is ignored by device drivers, and is optional for bus + drivers. For a bus driver, if this parameter is NULL, then handles + for all the children of Controller are created by this driver. + If this parameter is not NULL and the first Device Path Node is + not the End of Device Path Node, then only the handle for the + child device specified by the first Device Path Node of + RemainingDevicePath is created by this driver. + If the first Device Path Node of RemainingDevicePath is + the End of Device Path Node, no child handle is created by this + driver. + + @retval EFI_SUCCESS The device was started. + @retval EFI_DEVICE_ERROR The device could not be started due to a device error.Currently not implemented. + @retval EFI_OUT_OF_RESOURCES The request could not be completed due to a lack of resources. + @retval Others The driver failed to start the device. + +**/ +EFI_STATUS +EFIAPI +UsbHidDriverBindingStart ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE Controller, + IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath + ) +{ + EFI_STATUS Status; + EFI_TPL OldTpl; + EFI_USB_IO_PROTOCOL *UsbIo; + USB_HID_DEV *UsbHidDevice = NULL; + + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + + Status = gBS->OpenProtocol ( + Controller, + &gEfiUsbIoProtocolGuid, + (VOID **)&UsbIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_BY_DRIVER + ); + if (EFI_ERROR (Status)) { + goto ExitTpl; + } + + UsbHidDevice = AllocateZeroPool (sizeof (USB_HID_DEV)); + if (UsbHidDevice == NULL) { + ASSERT (UsbHidDevice != NULL); + Status = EFI_OUT_OF_RESOURCES; + goto ErrorExit; + } + + UsbHidDevice->Signature = USB_HID_DEV_SIGNATURE; + UsbHidDevice->UsbIo = UsbIo; + + UsbHidDevice->HidIo.GetReportDescriptor = HidGetReportDescriptor; + UsbHidDevice->HidIo.GetReport = HidGetReport; + UsbHidDevice->HidIo.SetReport = HidSetReport; + UsbHidDevice->HidIo.RegisterReportCallback = HidRegisterReportCallback; + UsbHidDevice->HidIo.UnregisterReportCallback = HidUnregisterReportCallback; + + Status = ReadDescriptors (UsbHidDevice); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto ErrorExit; + } + + Status = gBS->InstallProtocolInterface ( + &Controller, + &gHidIoProtocolGuid, + EFI_NATIVE_INTERFACE, + &UsbHidDevice->HidIo + ); + if (EFI_ERROR (Status)) { + ASSERT_EFI_ERROR (Status); + goto ErrorExit; + } + + // Success, but we still need to restore TPL. + goto ExitTpl; + +ErrorExit: + // best effort - nothing to do if this fails. + gBS->CloseProtocol ( + Controller, + &gEfiUsbIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + if (UsbHidDevice != NULL) { + if (UsbHidDevice->HidDescriptor != NULL) { + FreePool (UsbHidDevice->HidDescriptor); + } + + if (UsbHidDevice->ReportDescriptor != NULL) { + FreePool (UsbHidDevice->ReportDescriptor); + } + + FreePool (UsbHidDevice); + } + +ExitTpl: + gBS->RestoreTPL (OldTpl); + return Status; +} + +/** + Stops a device controller or a bus controller. + + The Stop() function is designed to be invoked from the EFI boot service DisconnectController(). + As a result, much of the error checking on the parameters to Stop() has been moved + into this common boot service. It is legal to call Stop() from other locations, + but the following calling restrictions must be followed, or the system behavior will not be deterministic. + 1. ControllerHandle must be a valid EFI_HANDLE that was used on a previous call to this + same driver's Start() function. + 2. The first NumberOfChildren handles of ChildHandleBuffer must all be a valid + EFI_HANDLE. In addition, all of these handles must have been created in this driver's + Start() function, and the Start() function must have called OpenProtocol() on + ControllerHandle with an Attribute of EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER. + + @param[in] This A pointer to the EFI_DRIVER_BINDING_PROTOCOL instance. + @param[in] ControllerHandle A handle to the device being stopped. The handle must + support a bus specific I/O protocol for the driver + to use to stop the device. + @param[in] NumberOfChildren The number of child device handles in ChildHandleBuffer. + @param[in] ChildHandleBuffer An array of child handles to be freed. May be NULL + if NumberOfChildren is 0. + + @retval EFI_SUCCESS The device was stopped. + @retval EFI_DEVICE_ERROR The device could not be stopped due to a device error. + +**/ +EFI_STATUS +EFIAPI +UsbHidDriverBindingStop ( + IN EFI_DRIVER_BINDING_PROTOCOL *This, + IN EFI_HANDLE Controller, + IN UINTN NumberOfChildren, + IN EFI_HANDLE *ChildHandleBuffer + ) +{ + EFI_STATUS Status; + EFI_TPL OldTpl; + HID_IO_PROTOCOL *HidIo; + USB_HID_DEV *UsbHidDevice = NULL; + + OldTpl = gBS->RaiseTPL (TPL_CALLBACK); + + Status = gBS->OpenProtocol ( + Controller, + &gHidIoProtocolGuid, + (VOID **)&HidIo, + This->DriverBindingHandle, + Controller, + EFI_OPEN_PROTOCOL_GET_PROTOCOL + ); + + if (EFI_ERROR (Status)) { + goto ExitTpl; + } + + UsbHidDevice = USB_HID_DEV_FROM_HID_IO_PROTOCOL (HidIo); + + Status = gBS->UninstallProtocolInterface ( + Controller, + &gHidIoProtocolGuid, + HidIo + ); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_WARN, "[%a] Failed to uninstall HidIo: %r\n", __FUNCTION__, Status)); + ASSERT_EFI_ERROR (Status); + } + + ShutdownAsyncInterruptInputTransfers (UsbHidDevice); + + gBS->CloseProtocol ( + Controller, + &gEfiUsbIoProtocolGuid, + This->DriverBindingHandle, + Controller + ); + + if (UsbHidDevice->HidDescriptor != NULL) { + FreePool (UsbHidDevice->HidDescriptor); + } + + if (UsbHidDevice->ReportDescriptor != NULL) { + FreePool (UsbHidDevice->ReportDescriptor); + } + + FreePool (UsbHidDevice); + +ExitTpl: + gBS->RestoreTPL (OldTpl); + return EFI_SUCCESS; +} + +EFI_DRIVER_BINDING_PROTOCOL gUsbHidDriverBinding = { + UsbHidDriverBindingSupported, + UsbHidDriverBindingStart, + UsbHidDriverBindingStop, + 1, + NULL, + NULL +}; + +/** + Entrypoint of USB HID Driver. + + This function is the entrypoint of USB Mouse Driver. It installs the Driver Binding + Protocol for managing USB HID devices. + + @param ImageHandle The firmware allocated handle for the EFI image. + @param SystemTable A pointer to the EFI System Table. + + @retval EFI_SUCCESS The entry point is executed successfully. + +**/ +EFI_STATUS +EFIAPI +UsbHidEntryPoint ( + IN EFI_HANDLE ImageHandle, + IN EFI_SYSTEM_TABLE *SystemTable + ) +{ + EFI_STATUS Status; + + Status = EfiLibInstallDriverBinding ( + ImageHandle, + SystemTable, + &gUsbHidDriverBinding, + ImageHandle + ); + + ASSERT_EFI_ERROR (Status); + + return EFI_SUCCESS; +} diff --git a/HidPkg/UsbHidDxe/UsbHidDxe.inf b/HidPkg/UsbHidDxe/UsbHidDxe.inf new file mode 100644 index 0000000000..a0a273884a --- /dev/null +++ b/HidPkg/UsbHidDxe/UsbHidDxe.inf @@ -0,0 +1,42 @@ +## @file +# USB HID Driver that manages USB HID devices and produces the HidIo protocol. +# +# USB HID Driver consumes USB I/O Protocol and Device Path Protocol, and produces +# the HidIo protocol on USB HID devices. +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +## + +[Defines] + INF_VERSION = 1.27 + BASE_NAME = UsbHidDxe + MODULE_UNI_FILE = UsbHidDxe.uni + FILE_GUID = 68EFF987-4871-4174-88CF-9877A0B1DBD3 + MODULE_TYPE = UEFI_DRIVER + VERSION_STRING = 1.0 + ENTRY_POINT = UsbHidEntryPoint + +[Sources] + UsbHidDxe.c + +[Packages] + MdePkg/MdePkg.dec + HidPkg/HidPkg.dec + +[LibraryClasses] + BaseMemoryLib + DebugLib + MemoryAllocationLib + UefiBootServicesTableLib + UefiDriverEntryPoint + UefiLib + UefiUsbLib + +[Protocols] + gHidIoProtocolGuid + gEfiUsbIoProtocolGuid + +[Pcd] + gEfiMdePkgTokenSpaceGuid.PcdUsbTransferTimeoutValue ## CONSUMES From 016e209e336753b898f487d1ba4e8fa9a3b8c8a6 Mon Sep 17 00:00:00 2001 From: John Schock Date: Tue, 3 Oct 2023 13:29:22 -0700 Subject: [PATCH 5/5] Adds UefiHidDxe driver - written in Rust, provides input report handling for HidIo pointer devices. Note: does not yet support HID keyboards. This is planned future work. - [x] Impacts functionality? Adds new input support functionality. - [ ] Impacts security? - [ ] Breaking change? - [ ] Includes tests? - [x] Includes documentation? - includes standard RustDocs. Pointer verified in preboot console (UEFI setup menu and Bitlocker Recovery). Assuming a project is setup to build rust modules generally, integration of the new stack is accomplished by: - Remove UsbMouseAbsolutePointerDxe - Add UsbHidDxe and UefiHidDxe to the build --- Cargo.toml | 7 + HidPkg/HidPkg.dsc | 1 + HidPkg/UefiHidDxe/Cargo.toml | 24 ++ HidPkg/UefiHidDxe/UefiHidDxe.inf | 22 + HidPkg/UefiHidDxe/src/driver_binding.rs | 149 +++++++ HidPkg/UefiHidDxe/src/hid.rs | 220 ++++++++++ HidPkg/UefiHidDxe/src/keyboard.rs | 45 +++ HidPkg/UefiHidDxe/src/main.rs | 60 +++ HidPkg/UefiHidDxe/src/pointer.rs | 517 ++++++++++++++++++++++++ 9 files changed, 1045 insertions(+) create mode 100644 HidPkg/UefiHidDxe/Cargo.toml create mode 100644 HidPkg/UefiHidDxe/UefiHidDxe.inf create mode 100644 HidPkg/UefiHidDxe/src/driver_binding.rs create mode 100644 HidPkg/UefiHidDxe/src/hid.rs create mode 100644 HidPkg/UefiHidDxe/src/keyboard.rs create mode 100644 HidPkg/UefiHidDxe/src/main.rs create mode 100644 HidPkg/UefiHidDxe/src/pointer.rs diff --git a/Cargo.toml b/Cargo.toml index 011dc0e9c6..cce800ee1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "HidPkg/Crates/AbsolutePointer", "HidPkg/Crates/HidIo", + "HidPkg/UefiHidDxe", "MsCorePkg/HelloWorldRustDxe" ] @@ -11,6 +12,12 @@ members = [ [workspace.dependencies] RustAdvancedLoggerDxe = {path = "AdvLoggerPkg/Crates/RustAdvancedLoggerDxe"} RustBootServicesAllocatorDxe = {path = "MsCorePkg/Crates/RustBootServicesAllocatorDxe"} +HidIo = {path = "HidPkg/Crates/HidIo"} +AbsolutePointer = {path = "HidPkg/Crates/AbsolutePointer"} + +hidparser = {git = "https://github.com/microsoft/mu_rust_hid.git", branch = "main"} r-efi = "4.0.0" +rustversion = "1.0.14" spin = "0.9.8" +memoffset = "0.9.0" \ No newline at end of file diff --git a/HidPkg/HidPkg.dsc b/HidPkg/HidPkg.dsc index 205d4a41df..55947da95b 100644 --- a/HidPkg/HidPkg.dsc +++ b/HidPkg/HidPkg.dsc @@ -56,6 +56,7 @@ HidPkg/UsbKbHidDxe/UsbKbHidDxe.inf HidPkg/UsbMouseHidDxe/UsbMouseHidDxe.inf HidPkg/UsbHidDxe/UsbHidDxe.inf + HidPkg/UefiHidDxe/UefiHidDxe.inf [BuildOptions] #force deprecated interfaces off diff --git a/HidPkg/UefiHidDxe/Cargo.toml b/HidPkg/UefiHidDxe/Cargo.toml new file mode 100644 index 0000000000..9cd7805811 --- /dev/null +++ b/HidPkg/UefiHidDxe/Cargo.toml @@ -0,0 +1,24 @@ +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +[package] +name = "UefiHidDxe" +version = "0.1.0" +edition = "2021" +authors = ["Microsoft"] + +[[bin]] +name = "UefiHidDxe" +path = "src/main.rs" +test = false + +[dependencies] +AbsolutePointer = {workspace=true} +HidIo = {workspace=true} +hidparser = {workspace=true} +memoffset = {workspace=true} +r-efi = {workspace=true} +rustversion = {workspace=true} +RustBootServicesAllocatorDxe = {workspace=true} +RustAdvancedLoggerDxe = {workspace=true} diff --git a/HidPkg/UefiHidDxe/UefiHidDxe.inf b/HidPkg/UefiHidDxe/UefiHidDxe.inf new file mode 100644 index 0000000000..78a69a4d42 --- /dev/null +++ b/HidPkg/UefiHidDxe/UefiHidDxe.inf @@ -0,0 +1,22 @@ +### @file +# +# UEFI HID - this driver consumes lower-level HID device support via the HidIo +# protocol abstraction and produces the UEFI spec input protocols for console support. +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# SPDX-License-Identifier: BSD-2-Clause-Patent +# +### + +[Defines] + INF_VERSION = 1.27 + BASE_NAME = UefiHidDxe + FILE_GUID = 90DC0565-643C-4119-A4F4-2B4EA198FB07 + MODULE_TYPE = DXE_DRIVER + RUST_MODULE = TRUE + +[Sources] + Cargo.toml + +[Depex] + TRUE diff --git a/HidPkg/UefiHidDxe/src/driver_binding.rs b/HidPkg/UefiHidDxe/src/driver_binding.rs new file mode 100644 index 0000000000..c07c28b127 --- /dev/null +++ b/HidPkg/UefiHidDxe/src/driver_binding.rs @@ -0,0 +1,149 @@ +//! Driver binding support for HID input driver. +//! +//! This module manages the UEFI Driver Binding for the HID input driver. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! + +use core::ffi::c_void; + +use alloc::boxed::Box; +use r_efi::{ + efi, + protocols::{device_path, driver_binding}, +}; +use rust_advanced_logger_dxe::{debugln, DEBUG_ERROR, DEBUG_INFO}; + +use crate::{hid, BOOT_SERVICES}; + +/// Initialize and install driver binding for this driver. +/// +/// Installs this drivers driver binding on the given image handle. +pub fn initialize_driver_binding(image_handle: efi::Handle) -> Result<(), efi::Status> { + let boot_services = unsafe { BOOT_SERVICES.as_mut().ok_or(efi::Status::NOT_READY)? }; + + let mut driver_binding_handle = image_handle; + let driver_binding_ptr = Box::into_raw(Box::new(driver_binding::Protocol { + supported: uefi_hid_driver_binding_supported, + start: uefi_hid_driver_binding_start, + stop: uefi_hid_driver_binding_stop, + version: 1, + image_handle: driver_binding_handle, + driver_binding_handle: driver_binding_handle, + })); + + let status = (boot_services.install_protocol_interface)( + core::ptr::addr_of_mut!(driver_binding_handle), + &driver_binding::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + efi::NATIVE_INTERFACE, + driver_binding_ptr as *mut c_void, + ); + + if status.is_error() { + return Err(status); + } + + Ok(()) +} + +// Checks whether the given controller supports HidIo. Implements driver_binding::supported. +extern "efiapi" fn uefi_hid_driver_binding_supported( + this: *mut driver_binding::Protocol, + controller: efi::Handle, + _remaining_device_path: *mut device_path::Protocol, +) -> efi::Status { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + let boot_services = unsafe { + match BOOT_SERVICES.as_mut() { + Some(boot_services) => boot_services, + None => return efi::Status::NOT_READY, + } + }; + + // retrieve a reference to the driver binding. + // Safety: the driver binding pointer passed here must point to the binding installed in initialize_driver_binding. + let driver_binding = unsafe { + match this.as_mut() { + Some(driver_binding) => driver_binding, + None => return efi::Status::INVALID_PARAMETER, + } + }; + + // Check to see if this controller is supported by attempting to open HidIo on it. + let mut hid_io_ptr: *mut hid_io::protocol::Protocol = core::ptr::null_mut(); + let status = (boot_services.open_protocol)( + controller, + &hid_io::protocol::GUID as *const efi::Guid as *mut efi::Guid, + core::ptr::addr_of_mut!(hid_io_ptr) as *mut *mut c_void, + driver_binding.driver_binding_handle, + controller, + efi::OPEN_PROTOCOL_BY_DRIVER, + ); + + // if HidIo could not be opened then it is either in use or not present. + if status.is_error() { + return status; + } + + // HidIo is available, so this controller is supported. Further checking that requires actual device interaction is + // done in uefi_hid_driver_binding_start. close the protocol used for the supported test and exit with success. + let status = (boot_services.close_protocol)( + controller, + &hid_io::protocol::GUID as *const efi::Guid as *mut efi::Guid, + driver_binding.driver_binding_handle, + controller, + ); + if status.is_error() { + debugln!(DEBUG_ERROR, "Unexpected error from CloseProtocol: {:?}", status); //message, but no further action to handle. + } + + efi::Status::SUCCESS +} + +// Start this driver managing given handle. Implements driver_binding::start. +extern "efiapi" fn uefi_hid_driver_binding_start( + this: *mut driver_binding::Protocol, + controller: efi::Handle, + _remaining_device_path: *mut device_path::Protocol, +) -> efi::Status { + // retrieve a reference to the driver binding. + // Safety: the driver binding pointer passed here must point to the binding installed in initialize_driver_binding. + let driver_binding = unsafe { + match this.as_mut() { + Some(driver_binding) => driver_binding, + None => return efi::Status::INVALID_PARAMETER, + } + }; + + // initialize the hid stack + let status = hid::initialize(controller, driver_binding); + if let Err(status) = status { + debugln!(DEBUG_INFO, "[hid::driver_binding_start] failed to initialize hid: {:x?}", status); + return status; + } + + efi::Status::SUCCESS +} + +// Stops this driver from managing the given handle. Implements driver_binding::stop. +extern "efiapi" fn uefi_hid_driver_binding_stop( + this: *mut driver_binding::Protocol, + controller: efi::Handle, + _num_children: usize, + _child_handle_buffer: *mut efi::Handle, +) -> efi::Status { + let driver_binding = unsafe { this.as_mut().expect("driver binding pointer is bad in uefi_hid_driver_binding_stop") }; + + //destroy the hid stack. + let status = hid::destroy(controller, driver_binding); + if let Err(status) = status { + debugln!(DEBUG_INFO, "[hid::driver_binding_stop] failed to destroy hid: {:x?}", status); + return status; + } + + efi::Status::SUCCESS +} diff --git a/HidPkg/UefiHidDxe/src/hid.rs b/HidPkg/UefiHidDxe/src/hid.rs new file mode 100644 index 0000000000..9ab7539ed5 --- /dev/null +++ b/HidPkg/UefiHidDxe/src/hid.rs @@ -0,0 +1,220 @@ +//! HID I/O support for HID input driver. +//! +//! This module manages interactions with the lower-layer drivers that produce the HidIo protocol. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! +use core::{ffi::c_void, slice::from_raw_parts}; + +use alloc::{boxed::Box, vec}; + +use r_efi::{efi, protocols::driver_binding, system}; +use rust_advanced_logger_dxe::{debugln, DEBUG_ERROR, DEBUG_WARN}; + +use crate::{keyboard, keyboard::KeyboardContext, pointer, pointer::PointerContext, BOOT_SERVICES}; + +pub struct HidContext { + hid_io: *mut hid_io::protocol::Protocol, + pub keyboard_context: *mut KeyboardContext, + pub pointer_context: *mut PointerContext, +} + +/// Initialize HID support +/// +/// Reads the HID Report Descriptor from the device, parse it, and initialize keyboard and pointer handlers for it. +pub fn initialize(controller: efi::Handle, driver_binding: &driver_binding::Protocol) -> Result<(), efi::Status> { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + // Caller should have ensured this, so just expect on failure. + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("BOOT_SERVICES not properly initialized") }; + + // retrieve the HidIo instance for the given controller. + let mut hid_io_ptr: *mut hid_io::protocol::Protocol = core::ptr::null_mut(); + let status = (boot_services.open_protocol)( + controller, + &hid_io::protocol::GUID as *const efi::Guid as *mut efi::Guid, + core::ptr::addr_of_mut!(hid_io_ptr) as *mut *mut c_void, + driver_binding.driver_binding_handle, + controller, + system::OPEN_PROTOCOL_BY_DRIVER, + ); + if status.is_error() { + debugln!(DEBUG_ERROR, "[hid::initialize] Unexpected error opening HidIo protocol: {:#?}", status); + return Err(status); + } + + let hid_io = unsafe { hid_io_ptr.as_mut().expect("bad hidio pointer.") }; + + //determine report descriptor size. + let mut report_descriptor_size: usize = 0; + let status = + (hid_io.get_report_descriptor)(hid_io_ptr, core::ptr::addr_of_mut!(report_descriptor_size), core::ptr::null_mut()); + + match status { + efi::Status::BUFFER_TOO_SMALL => (), + _ => { + let _ = release_hid_io(controller, driver_binding); + return Err(efi::Status::DEVICE_ERROR); + } + } + + //read report descriptor. + let mut report_descriptor_buffer = vec![0u8; report_descriptor_size]; + let report_descriptor_buffer_ptr = report_descriptor_buffer.as_mut_ptr(); + + let status = (hid_io.get_report_descriptor)( + hid_io_ptr, + core::ptr::addr_of_mut!(report_descriptor_size), + report_descriptor_buffer_ptr as *mut c_void, + ); + + if status.is_error() { + let _ = release_hid_io(controller, driver_binding); + return Err(status); + } + + // parse the descriptor + let descriptor = hidparser::parse_report_descriptor(&report_descriptor_buffer).map_err(|err| { + debugln!(DEBUG_WARN, "[hid::initialize] failed to parse report descriptor: {:x?}.", err); + let _ = release_hid_io(controller, driver_binding); + efi::Status::DEVICE_ERROR + })?; + + // create hid context + let hid_context_ptr = Box::into_raw(Box::new(HidContext { + hid_io: hid_io_ptr, + keyboard_context: core::ptr::null_mut(), + pointer_context: core::ptr::null_mut(), + })); + + //initialize report handlers + let keyboard = keyboard::initialize(controller, &descriptor, hid_context_ptr); + let pointer = pointer::initialize(controller, &descriptor, hid_context_ptr); + + if keyboard.is_err() && pointer.is_err() { + debugln!(DEBUG_WARN, "[hid::initialize] no devices supported"); + //no devices supported. + let _ = release_hid_io(controller, driver_binding); + unsafe { drop(Box::from_raw(hid_context_ptr)) }; + Err(efi::Status::UNSUPPORTED)?; + } + + // register for input reports + let status = (hid_io.register_report_callback)(hid_io_ptr, on_input_report, hid_context_ptr as *mut c_void); + + if status.is_error() { + debugln!(DEBUG_WARN, "[hid::initialize] failed to register for input reports: {:x?}", status); + let _ = destroy(controller, driver_binding); + return Err(status); + } + + Ok(()) +} + +// Handler function for input reports. Dispatches them to the keyboard and pointer modules for handling. +extern "efiapi" fn on_input_report(report_buffer_size: u16, report_buffer: *mut c_void, context: *mut c_void) { + let hid_context_ptr = context as *mut HidContext; + let hid_context = unsafe { hid_context_ptr.as_mut().expect("[hid::on_input_report: invalid context pointer") }; + + let report = unsafe { from_raw_parts(report_buffer as *mut u8, report_buffer_size as usize) }; + + let keyboard_context = unsafe { hid_context.keyboard_context.as_mut() }; + if let Some(keyboard_context) = keyboard_context { + keyboard_context.handler.process_input_report(report); + } + + let pointer_context = unsafe { hid_context.pointer_context.as_mut() }; + if let Some(pointer_context) = pointer_context { + pointer_context.handler.process_input_report(report); + } +} + +// Unregister report callback from HID layer to shutdown input reports. +fn shutdown_input_reports(hid_context: &mut HidContext) -> Result<(), efi::Status> { + let hid_io = + unsafe { hid_context.hid_io.as_mut().expect("hid_context has bad hid_io pointer in hid::shutdown_input_reports") }; + // shutdown input reports. + let status = (hid_io.unregister_report_callback)(hid_context.hid_io, on_input_report); + if status.is_error() { + debugln!(DEBUG_ERROR, "[hid::destroy] unexpected error from hid_io.unregister_report_callback: {:?}", status); + return Err(status); + } + Ok(()) +} + +//Shutdown Keyboard and Pointer handling. +fn shutdown_handlers(hid_context: &mut HidContext) -> Result<(), efi::Status> { + // shutdown keyboard. + let mut status = efi::Status::SUCCESS; + match keyboard::deinitialize(hid_context.keyboard_context) { + Err(err) if err != efi::Status::UNSUPPORTED => { + debugln!(DEBUG_ERROR, "[hid::destroy] unexpected error from keyboard::deinitialize: {:?}", err); + status = efi::Status::DEVICE_ERROR; + } + _ => (), + }; + + // shutdown pointer. + match pointer::deinitialize(hid_context.pointer_context) { + Err(err) if err != efi::Status::UNSUPPORTED => { + debugln!(DEBUG_ERROR, "[hid::destroy] unexpected error from pointer::deinitialize: {:?}", err); + status = efi::Status::DEVICE_ERROR; + } + _ => (), + }; + + if status.is_error() { + return Err(status); + } + + Ok(()) +} + +// Release HidIo instance by closing the HidIo protocol on the given controller. +fn release_hid_io(controller: efi::Handle, driver_binding: &driver_binding::Protocol) -> Result<(), efi::Status> { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + // Caller should have ensured this, so just expect on failure. + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("BOOT_SERVICES not properly initialized") }; + + // release HidIo + match (boot_services.close_protocol)( + controller, + &hid_io::protocol::GUID as *const efi::Guid as *mut efi::Guid, + driver_binding.driver_binding_handle, + controller, + ) { + efi::Status::SUCCESS => (), + err => { + debugln!(DEBUG_ERROR, "[hid::release_hid_io] unexpected error from boot_services.close_protocol: {:?}", err); + return Err(efi::Status::DEVICE_ERROR); + } + } + + Ok(()) +} + +/// Tears down HID support. +/// +/// De-initializes keyboard and pointer handlers and releases HidIo instance. +pub fn destroy(controller: efi::Handle, driver_binding: &driver_binding::Protocol) -> Result<(), efi::Status> { + let mut context = pointer::attempt_to_retrieve_hid_context(controller, driver_binding); + if context.is_err() { + context = keyboard::attempt_to_retrieve_hid_context(controller, driver_binding); + } + + let hid_context_ptr = context?; + let hid_context = unsafe { hid_context_ptr.as_mut().expect("invalid hid_context_ptr in hid::destroy") }; + + let _ = shutdown_input_reports(hid_context); + let _ = shutdown_handlers(hid_context); + let _ = release_hid_io(controller, driver_binding); + + // take back the hid_context (it will be released when it goes out of scope). + unsafe { drop(Box::from_raw(hid_context_ptr)) }; + + Ok(()) +} diff --git a/HidPkg/UefiHidDxe/src/keyboard.rs b/HidPkg/UefiHidDxe/src/keyboard.rs new file mode 100644 index 0000000000..fe779614b6 --- /dev/null +++ b/HidPkg/UefiHidDxe/src/keyboard.rs @@ -0,0 +1,45 @@ +//! Keyboard support for HID input driver. +//! +//! This module manages keyboard input for the HID input driver. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! + +use hidparser::ReportDescriptor; +use r_efi::{efi, protocols::driver_binding}; + +use crate::hid::HidContext; + +pub struct KeyboardContext { + pub handler: KeyboardHandler, +} + +pub struct KeyboardHandler {} + +impl KeyboardHandler { + pub fn process_input_report(&mut self, _report_buffer: &[u8]) { + todo!() + } +} + +pub fn initialize( + _controller: efi::Handle, + _descriptor: &ReportDescriptor, + _hid_context: *mut HidContext, +) -> Result<*mut KeyboardContext, efi::Status> { + Err(efi::Status::UNSUPPORTED) +} + +pub fn deinitialize(_context: *mut KeyboardContext) -> Result<(), efi::Status> { + Err(efi::Status::UNSUPPORTED) +} + +pub fn attempt_to_retrieve_hid_context( + _controller: efi::Handle, + _driver_binding: &driver_binding::Protocol, +) -> Result<*mut HidContext, efi::Status> { + Err(efi::Status::UNSUPPORTED) +} diff --git a/HidPkg/UefiHidDxe/src/main.rs b/HidPkg/UefiHidDxe/src/main.rs new file mode 100644 index 0000000000..8ab6678efd --- /dev/null +++ b/HidPkg/UefiHidDxe/src/main.rs @@ -0,0 +1,60 @@ +//! HID input driver for UEFI +//! +//! This crate provides input handlers for HID 1.1 compliant keyboards and pointers. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! + +#![no_std] +#![no_main] +#![allow(non_snake_case)] + +extern crate alloc; + +use core::panic::PanicInfo; + +use driver_binding::initialize_driver_binding; +use r_efi::{efi, system}; + +use rust_advanced_logger_dxe::{debugln, init_debug, DEBUG_ERROR}; +use rust_boot_services_allocator_dxe::GLOBAL_ALLOCATOR; + +mod driver_binding; +mod hid; +mod keyboard; +mod pointer; + +static mut BOOT_SERVICES: *mut system::BootServices = core::ptr::null_mut(); + +#[no_mangle] +pub extern "efiapi" fn efi_main(image_handle: efi::Handle, system_table: *const system::SystemTable) -> efi::Status { + // Safety: This block is unsafe because it assumes that system_table and (*system_table).boot_services are correct, + // and because it mutates/accesses the global BOOT_SERVICES static. + unsafe { + BOOT_SERVICES = (*system_table).boot_services; + GLOBAL_ALLOCATOR.init(BOOT_SERVICES); + init_debug(BOOT_SERVICES); + } + + let status = initialize_driver_binding(image_handle); + + if status.is_err() { + debugln!(DEBUG_ERROR, "[UefiHidMain]: failed to initialize driver binding.\n"); + } + + efi::Status::SUCCESS +} + +//Workaround for https://github.com/rust-lang/rust/issues/98254 +#[rustversion::before(1.73)] +#[no_mangle] +pub extern "efiapi" fn __chkstk() {} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + debugln!(DEBUG_ERROR, "Panic: {:?}", info); + loop {} +} diff --git a/HidPkg/UefiHidDxe/src/pointer.rs b/HidPkg/UefiHidDxe/src/pointer.rs new file mode 100644 index 0000000000..94d9ed144c --- /dev/null +++ b/HidPkg/UefiHidDxe/src/pointer.rs @@ -0,0 +1,517 @@ +//! Pointer support for HID input driver. +//! +//! This module manages pointer input for the HID input driver. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + vec::Vec, +}; +use core::ffi::c_void; +use hidparser::{ + report_data_types::{ReportId, Usage}, + ReportDescriptor, ReportField, VariableField, +}; +use memoffset::{offset_of, raw_field}; +use r_efi::{efi, protocols::driver_binding, system}; +use rust_advanced_logger_dxe::{debugln, DEBUG_INFO, DEBUG_WARN}; + +use crate::{hid::HidContext, BOOT_SERVICES}; + +// Usages supported by this driver. +const GENERIC_DESKTOP_X: u32 = 0x00010030; +const GENERIC_DESKTOP_Y: u32 = 0x00010031; +const GENERIC_DESKTOP_Z: u32 = 0x00010032; +const GENERIC_DESKTOP_WHEEL: u32 = 0x00010038; +const BUTTON_PAGE: u16 = 0x0009; +const BUTTON_MIN: u32 = 0x00090001; +const BUTTON_MAX: u32 = 0x00090020; //Per spec, the Absolute Pointer protocol supports a 32-bit button state field. + +// number of points on the X/Y axis for this implementation. +const AXIS_RESOLUTION: u64 = 1024; + +// Maps a given field to a routine that handles input from it. +#[derive(Debug, Clone)] +struct ReportFieldWithHandler { + field: VariableField, + report_handler: fn(&mut PointerHandler, field: VariableField, report: &[u8]), +} + +// Defines a report and the fields of interest within it. +#[derive(Debug, Default, Clone)] +struct PointerReportData { + report_id: Option, + report_size: usize, + relevant_fields: Vec, +} + +/// Context structure used to track data for this pointer device. +/// Safety: this structure is shared across FFI boundaries, and pointer arithmetic is used on its contents, so it must +/// remain #[repr(C)], and Rust aliasing and concurrency rules must be manually enforced. +#[derive(Debug)] +#[repr(C)] +pub struct PointerContext { + absolute_pointer: absolute_pointer::protocol::Protocol, + pub handler: PointerHandler, + controller: efi::Handle, + hid_context: *mut HidContext, +} + +/// Tracks all the input reports for this device as well as pointer state. +#[derive(Debug, Default)] +pub struct PointerHandler { + input_reports: BTreeMap, PointerReportData>, + supported_usages: BTreeSet, + report_id_present: bool, + state_changed: bool, + current_state: absolute_pointer::protocol::AbsolutePointerState, +} + +impl PointerHandler { + // processes a report descriptor and yields a PointerHandler instance if this descriptor describes input + // that can be handled by this PointerHandler. + fn process_descriptor(descriptor: &ReportDescriptor) -> Result { + let mut handler: PointerHandler = Default::default(); + let multiple_reports = descriptor.input_reports.len() > 1; + + for report in &descriptor.input_reports { + let mut report_data = + PointerReportData { report_id: report.report_id, report_size: report.size_in_bits / 8, ..Default::default() }; + + handler.report_id_present = report.report_id.is_some(); + + if multiple_reports && !handler.report_id_present { + //invalid to have None ReportId if multiple reports present. + Err(efi::Status::DEVICE_ERROR)?; + } + + for field in &report.fields { + match field { + ReportField::Variable(field) => { + match field.usage.into() { + GENERIC_DESKTOP_X => { + let field_handler = + ReportFieldWithHandler { field: field.clone(), report_handler: Self::x_axis_handler }; + report_data.relevant_fields.push(field_handler); + handler.supported_usages.insert(field.usage); + //debugln!(DEBUG_INFO, "x-axis field {:#?}", field); + } + GENERIC_DESKTOP_Y => { + let field_handler = + ReportFieldWithHandler { field: field.clone(), report_handler: Self::y_axis_handler }; + report_data.relevant_fields.push(field_handler); + handler.supported_usages.insert(field.usage); + //debugln!(DEBUG_INFO, "y-axis field {:#?}", field); + } + GENERIC_DESKTOP_Z | GENERIC_DESKTOP_WHEEL => { + let field_handler = + ReportFieldWithHandler { field: field.clone(), report_handler: Self::z_axis_handler }; + report_data.relevant_fields.push(field_handler); + handler.supported_usages.insert(field.usage); + //debugln!(DEBUG_INFO, "z-axis field {:#?}", field); + } + BUTTON_MIN..=BUTTON_MAX => { + let field_handler = + ReportFieldWithHandler { field: field.clone(), report_handler: Self::button_handler }; + report_data.relevant_fields.push(field_handler); + handler.supported_usages.insert(field.usage); + //debugln!(DEBUG_INFO, "button field {:#?}", field); + } + _ => (), //other usages irrelevant + } + } + _ => (), // other field types irrelevant + } + } + + if report_data.relevant_fields.len() > 0 { + handler.input_reports.insert(report_data.report_id, report_data); + } + } + + if handler.input_reports.len() > 0 { + Ok(handler) + } else { + debugln!(DEBUG_INFO, "No relevant fields for handler: {:#?}", handler); + Err(efi::Status::UNSUPPORTED) + } + } + + // Create PointerContext structure and install Absolute Pointer interface. + fn install_pointer_interfaces( + self, + controller: efi::Handle, + hid_context: *mut HidContext, + ) -> Result<*mut PointerContext, efi::Status> { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + // Caller should have ensured this, so just expect on failure. + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("BOOT_SERVICES not properly initialized") }; + + // Create pointer context. This context is shared across FFI boundary, so use Box::into_raw. + // After creation, the only way to access the context (including the handler instance) is via a raw pointer. + let context_ptr = Box::into_raw(Box::new(PointerContext { + absolute_pointer: absolute_pointer::protocol::Protocol { + reset: absolute_pointer_reset, + get_state: absolute_pointer_get_state, + mode: Box::into_raw(Box::new(self.initialize_mode())), + wait_for_input: core::ptr::null_mut(), + }, + handler: self, + controller: controller, + hid_context: hid_context, + })); + + let context = unsafe { context_ptr.as_mut().expect("freshly boxed context pointer is null.") }; + + // create event for wait_for_input. + let mut wait_for_pointer_input_event: efi::Event = core::ptr::null_mut(); + let status = (boot_services.create_event)( + system::EVT_NOTIFY_WAIT, + system::TPL_NOTIFY, + Some(wait_for_pointer), + context_ptr as *mut c_void, + core::ptr::addr_of_mut!(wait_for_pointer_input_event), + ); + if status.is_error() { + drop(unsafe { Box::from_raw(context_ptr) }); + return Err(status); + } + context.absolute_pointer.wait_for_input = wait_for_pointer_input_event; + + // install the absolute_pointer protocol. + let mut controller = controller; + let absolute_pointer_ptr = raw_field!(context_ptr, PointerContext, absolute_pointer); + let status = (boot_services.install_protocol_interface)( + core::ptr::addr_of_mut!(controller), + &absolute_pointer::protocol::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + efi::NATIVE_INTERFACE, + absolute_pointer_ptr as *mut c_void, + ); + + if status.is_error() { + let _ = deinitialize(context_ptr); + return Err(status); + } + + Ok(context_ptr) + } + + // Initializes the absolute_pointer mode structure. + fn initialize_mode(&self) -> absolute_pointer::protocol::AbsolutePointerMode { + let mut mode: absolute_pointer::protocol::AbsolutePointerMode = Default::default(); + + if self.supported_usages.contains(&Usage::from(GENERIC_DESKTOP_X)) { + mode.absolute_max_x = AXIS_RESOLUTION; + mode.absolute_min_x = 0; + } else { + debugln!(DEBUG_WARN, "No x-axis usages found in the report descriptor."); + } + + if self.supported_usages.contains(&Usage::from(GENERIC_DESKTOP_Y)) { + mode.absolute_max_y = AXIS_RESOLUTION; + mode.absolute_min_y = 0; + } else { + debugln!(DEBUG_WARN, "No y-axis usages found in the report descriptor."); + } + + if (self.supported_usages.contains(&Usage::from(GENERIC_DESKTOP_Z))) + || (self.supported_usages.contains(&Usage::from(GENERIC_DESKTOP_WHEEL))) + { + mode.absolute_max_z = AXIS_RESOLUTION; + mode.absolute_min_z = 0; + //TODO: Z-axis is interpreted as pressure data. This is for compat with reference implementation in C, but + //could consider e.g. looking for actual digitizer tip pressure usages or something. + mode.attributes = mode.attributes | 0x02; + } else { + debugln!(DEBUG_INFO, "No z-axis usages found in the report descriptor."); + } + + let button_count = self.supported_usages.iter().filter(|x| x.page() == BUTTON_PAGE).count(); + + if button_count > 1 { + mode.attributes = mode.attributes | 0x01; // alternate button exists. + } + + mode + } + + // Helper routine that handles projecting relative and absolute axis reports onto the fixed + // absolute report axis that this driver produces. + fn resolve_axis(current_value: u64, field: VariableField, report: &[u8]) -> Option { + if field.attributes.relative { + //for relative, just update and clamp the current state. + let new_value = current_value as i64 + field.field_value(report)?; + return Some(new_value.clamp(0, AXIS_RESOLUTION as i64) as u64); + } else { + //for absolute, project onto 0..AXIS_RESOLUTION + let mut new_value = field.field_value(report)?; + + //translate to zero. + new_value = new_value.checked_sub(i32::from(field.logical_minimum) as i64)?; + + //scale to AXIS_RESOLUTION + new_value = (new_value * AXIS_RESOLUTION as i64 * 1000) / (field.field_range()? as i64 * 1000); + + return Some(new_value.clamp(0, AXIS_RESOLUTION as i64) as u64); + } + } + + // handles x_axis inputs + fn x_axis_handler(&mut self, field: VariableField, report: &[u8]) { + if let Some(x_value) = Self::resolve_axis(self.current_state.current_x, field, report) { + self.current_state.current_x = x_value; + self.state_changed = true; + } + } + + // handles y_axis inputs + fn y_axis_handler(&mut self, field: VariableField, report: &[u8]) { + if let Some(y_value) = Self::resolve_axis(self.current_state.current_y, field, report) { + self.current_state.current_y = y_value; + self.state_changed = true; + } + } + + // handles z_axis inputs + fn z_axis_handler(&mut self, field: VariableField, report: &[u8]) { + if let Some(z_value) = Self::resolve_axis(self.current_state.current_z, field, report) { + self.current_state.current_z = z_value; + self.state_changed = true; + } + } + + // handles button inputs + fn button_handler(&mut self, field: VariableField, report: &[u8]) { + let shift: u32 = field.usage.into(); + if (shift < BUTTON_MIN) || (shift > BUTTON_MAX) { + return; + } + + if let Some(button_value) = field.field_value(report) { + let button_value = button_value as u32; + + let shift = shift - BUTTON_MIN; + if shift > u32::BITS { + return; + } + let button_value = button_value << shift; + + self.current_state.active_buttons = self.current_state.active_buttons + & !(1 << shift) // zero the relevant bit in the button state field. + | button_value; // or in the current button state into that bit position. + + self.state_changed = true; + } + } + + /// Processes the given input report buffer and handles input from it. + pub fn process_input_report(&mut self, report_buffer: &[u8]) { + if report_buffer.len() == 0 { + return; + } + + // determine whether report includes report id byte and adjust the buffer as needed. + let (report_id, report) = match self.report_id_present { + true => (Some(ReportId::from(&report_buffer[0..1])), &report_buffer[1..]), + false => (None, &report_buffer[0..]), + }; + + if report.len() == 0 { + return; + } + + if let Some(report_data) = self.input_reports.get(&report_id).cloned() { + if report.len() != report_data.report_size { + return; + } + + // hand the report data to the handler for each relevant field for field-specific processing. + for field in report_data.relevant_fields { + (field.report_handler)(self, field.field, report); + } + } + } + + // Uninstall the absolute pointer interface and free the Pointer context. + fn uninstall_pointer_interfaces(pointer_context: *mut PointerContext) -> Result<(), efi::Status> { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + // Caller should have ensured this, so just expect on failure. + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("BOOT_SERVICES not properly initialized") }; + + let absolute_pointer_ptr = raw_field!(pointer_context, PointerContext, absolute_pointer); + + let mut overall_status = efi::Status::SUCCESS; + + // close the wait_for_input event + let status = (boot_services.close_event)(unsafe { (*absolute_pointer_ptr).wait_for_input }); + if status.is_error() { + overall_status = status; + } + + // uninstall absolute pointer protocol + let status = (boot_services.uninstall_protocol_interface)( + unsafe { (*pointer_context).controller }, + &absolute_pointer::protocol::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + absolute_pointer_ptr as *mut c_void, + ); + if status.is_error() { + overall_status = status; + } + + // take back the context and mode raw pointers + let context = unsafe { Box::from_raw(pointer_context) }; + drop(unsafe { Box::from_raw(context.absolute_pointer.mode) }); + drop(context); + + if overall_status.is_error() { + Err(overall_status) + } else { + Ok(()) + } + } +} + +/// Initializes a pointer handler on the given `controller` to handle reports described by `descriptor`. +/// +/// If the device doesn't provide the necessary reports for pointers, or if there was an error processing the report +/// descriptor data or initializing the pointer handler or installing the absolute pointer protocol instance into the +/// protocol database, an error is returned. +/// +/// Otherwise, a [`PointerContext`] that can be used to interact with this handler is returned. See [`PointerContext`] +/// documentation for constraints on interactions with it. +pub fn initialize( + controller: efi::Handle, + descriptor: &ReportDescriptor, + hid_context_ptr: *mut HidContext, +) -> Result<*mut PointerContext, efi::Status> { + let handler = PointerHandler::process_descriptor(descriptor)?; + + let context = handler.install_pointer_interfaces(controller, hid_context_ptr)?; + + let hid_context = unsafe { hid_context_ptr.as_mut().expect("[pointer::initialize]: bad hid context pointer") }; + + hid_context.pointer_context = context; + + Ok(context) +} + +/// De-initializes a pointer handler described by `context` on the given `controller`. +pub fn deinitialize(context: *mut PointerContext) -> Result<(), efi::Status> { + if context.is_null() { + return Err(efi::Status::NOT_STARTED); + } + PointerHandler::uninstall_pointer_interfaces(context) +} + +/// Attempt to retrieve a *mut HidContext for the given controller by locating the absolute_pointer interface associated +/// with the controller (if any) and deriving a PointerContext from it (which contains a pointer to the HidContext). +pub fn attempt_to_retrieve_hid_context( + controller: efi::Handle, + driver_binding: &driver_binding::Protocol, +) -> Result<*mut HidContext, efi::Status> { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + // Caller should have ensured this, so just expect on failure. + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("BOOT_SERVICES not properly initialized") }; + + let mut absolute_pointer_ptr: *mut absolute_pointer::protocol::Protocol = core::ptr::null_mut(); + let status = (boot_services.open_protocol)( + controller, + &absolute_pointer::protocol::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + core::ptr::addr_of_mut!(absolute_pointer_ptr) as *mut *mut c_void, + driver_binding.driver_binding_handle, + controller, + system::OPEN_PROTOCOL_GET_PROTOCOL, + ); + + match status { + efi::Status::SUCCESS => { + // retrieve a reference to the pointer context. + // Safety: `absolute_pointer_ptr` must point to an instance of absolute_pointer that is contained in a + // PointerContext struct. The following is the equivalent of the `CR` (contained record) macro in the EDK2 C + // reference implementation. + let context_ptr = unsafe { (absolute_pointer_ptr as *mut u8).sub(offset_of!(PointerContext, absolute_pointer)) } + as *mut PointerContext; + return Ok(unsafe { (*context_ptr).hid_context }); + } + err => return Err(err), + } +} + +// event handler for wait_for_pointer event that is part of the absolute pointer interface. +extern "efiapi" fn wait_for_pointer(event: efi::Event, context: *mut c_void) { + // retrieve a reference to boot services. + // Safety: BOOT_SERVICES must have been initialized to point to the UEFI Boot Services table. + // Caller should have ensured this, so just expect on failure. + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("BOOT_SERVICES not properly initialized") }; + + // retrieve a reference to pointer context. + // Safety: Pointer Context must have been initialized before this event could fire, so just expect on failure. + let pointer_context = unsafe { (context as *mut PointerContext).as_mut().expect("Pointer Context is bad.") }; + + if pointer_context.handler.state_changed { + (boot_services.signal_event)(event); + } +} + +// resets the pointer state - part of the absolute pointer interface. +extern "efiapi" fn absolute_pointer_reset( + this: *const absolute_pointer::protocol::Protocol, + _extended_verification: bool, +) -> efi::Status { + if this.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the pointer context. + // Safety: `this` must point to an instance of absolute_pointer that is contained in a PointerContext struct. + // the following is the equivalent of the `CR` (contained record) macro in the EDK2 reference implementation. + let context_ptr = + unsafe { (this as *mut u8).sub(offset_of!(PointerContext, absolute_pointer)) } as *mut PointerContext; + + let pointer_context = unsafe { context_ptr.as_mut().expect("Context pointer is bad.") }; + + pointer_context.handler.current_state = Default::default(); + + //stick the pointer in the middle of the screen. + pointer_context.handler.current_state.current_x = AXIS_RESOLUTION / 2; + pointer_context.handler.current_state.current_y = AXIS_RESOLUTION / 2; + pointer_context.handler.current_state.current_z = 0; + + pointer_context.handler.state_changed = false; + + efi::Status::SUCCESS +} + +// returns the current pointer state in the `state` buffer provided by the caller - part of the absolute pointer +// interface. +extern "efiapi" fn absolute_pointer_get_state( + this: *const absolute_pointer::protocol::Protocol, + state: *mut absolute_pointer::protocol::AbsolutePointerState, +) -> efi::Status { + if this.is_null() || state.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the pointer context. + // Safety: `this` must point to an instance of absolute_pointer that is contained in a PointerContext struct. + // the following is the equivalent of the `CR` (contained record) macro in the EDK2 reference implementation. + let context_ptr = + unsafe { (this as *mut u8).sub(offset_of!(PointerContext, absolute_pointer)) } as *mut PointerContext; + + let pointer_context = unsafe { context_ptr.as_mut().expect("Context pointer is bad.") }; + + // Safety: state pointer is assumed to be a valid buffer to receive the current state. + if pointer_context.handler.state_changed { + unsafe { state.write(pointer_context.handler.current_state) }; + pointer_context.handler.state_changed = false; + efi::Status::SUCCESS + } else { + efi::Status::NOT_READY + } +}