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 + } +}