From 22011420bde4e643ece940aeede894df021144b0 Mon Sep 17 00:00:00 2001 From: John Schock Date: Mon, 6 Nov 2023 18:19:24 -0800 Subject: [PATCH] Add HID Keyboard support to UefiHidDxe (#347) ## Description Adds HID keyboard support to UefiHidDxe input driver. - [x] Impacts functionality? - Adds keyboard support. - [ ] Impacts security? - [ ] Breaking change? - [ ] Includes tests? - [x] Includes documentation? - standard rustdocs ## How This Was Tested Tested with USB keyboard support in QEMU. Early iterations also tested in hardware. ## Integration Instructions Platforms will need to add `HiiKeyboardLayout = {path = "HidPkg/Crates/HiiKeyboardLayout"}` to the `[workspace.dependencies]` in their cargo.toml if not already present. --- Cargo.toml | 2 +- HidPkg/Crates/HiiKeyboardLayout/src/lib.rs | 6 +- HidPkg/UefiHidDxe/Cargo.toml | 1 + HidPkg/UefiHidDxe/src/driver_binding.rs | 3 +- HidPkg/UefiHidDxe/src/hid.rs | 2 +- HidPkg/UefiHidDxe/src/key_queue.rs | 606 ++++++++++++ HidPkg/UefiHidDxe/src/keyboard.rs | 1019 +++++++++++++++++++- HidPkg/UefiHidDxe/src/main.rs | 3 + 8 files changed, 1623 insertions(+), 19 deletions(-) create mode 100644 HidPkg/UefiHidDxe/src/key_queue.rs diff --git a/Cargo.toml b/Cargo.toml index a920563c5f..2d18013d26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,8 @@ members = [ RustAdvancedLoggerDxe = {path = "AdvLoggerPkg/Crates/RustAdvancedLoggerDxe"} RustBootServicesAllocatorDxe = {path = "MsCorePkg/Crates/RustBootServicesAllocatorDxe"} HidIo = {path = "HidPkg/Crates/HidIo"} - hidparser = {git = "https://github.com/microsoft/mu_rust_hid.git", branch = "main"} +HiiKeyboardLayout = {path = "HidPkg/Crates/HiiKeyboardLayout"} r-efi = "4.3.0" rustversion = "1.0.14" diff --git a/HidPkg/Crates/HiiKeyboardLayout/src/lib.rs b/HidPkg/Crates/HiiKeyboardLayout/src/lib.rs index a3a1669362..fb492651cc 100644 --- a/HidPkg/Crates/HiiKeyboardLayout/src/lib.rs +++ b/HidPkg/Crates/HiiKeyboardLayout/src/lib.rs @@ -42,6 +42,10 @@ use r_efi::{ }; use scroll::{ctx, Pread, Pwrite}; +/// GUID for default keyboard layout. +pub const DEFAULT_KEYBOARD_LAYOUT_GUID: efi::Guid = + efi::Guid::from_fields(0x3a4d7a7c, 0x18a, 0x4b42, 0x81, 0xb3, &[0xdc, 0x10, 0xe3, 0xb5, 0x91, 0xbd]); + /// HII Keyboard Package List /// Refer to UEFI spec version 2.10 section 33.3.1.2 which defines the generic header structure. This implementation /// only supports HII Keyboard Packages; other HII package types (or mixes) are not supported. @@ -465,7 +469,7 @@ macro_rules! key_descriptor { #[rustfmt::skip] pub fn get_default_keyboard_layout() -> HiiKeyboardLayout { HiiKeyboardLayout { - guid: efi::Guid::from_fields(0x3a4d7a7c, 0x18a, 0x4b42, 0x81, 0xb3, &[0xdc, 0x10, 0xe3, 0xb5, 0x91, 0xbd]), + guid: DEFAULT_KEYBOARD_LAYOUT_GUID, keys: vec![ key!(EfiKey::C1, 'a', 'A', '\0', '\0', NULL_MODIFIER, AFFECTED_BY_STANDARD_SHIFT | AFFECTED_BY_CAPS_LOCK), key!(EfiKey::B5, 'b', 'B', '\0', '\0', NULL_MODIFIER, AFFECTED_BY_STANDARD_SHIFT | AFFECTED_BY_CAPS_LOCK), diff --git a/HidPkg/UefiHidDxe/Cargo.toml b/HidPkg/UefiHidDxe/Cargo.toml index 145759ab01..2bb3968468 100644 --- a/HidPkg/UefiHidDxe/Cargo.toml +++ b/HidPkg/UefiHidDxe/Cargo.toml @@ -16,6 +16,7 @@ test = false [dependencies] HidIo = {workspace=true} hidparser = {workspace=true} +HiiKeyboardLayout = {workspace=true} memoffset = {workspace=true} r-efi = {workspace=true} rustversion = {workspace=true} diff --git a/HidPkg/UefiHidDxe/src/driver_binding.rs b/HidPkg/UefiHidDxe/src/driver_binding.rs index c07c28b127..98edcaf66b 100644 --- a/HidPkg/UefiHidDxe/src/driver_binding.rs +++ b/HidPkg/UefiHidDxe/src/driver_binding.rs @@ -32,7 +32,7 @@ pub fn initialize_driver_binding(image_handle: efi::Handle) -> Result<(), efi::S stop: uefi_hid_driver_binding_stop, version: 1, image_handle: driver_binding_handle, - driver_binding_handle: driver_binding_handle, + driver_binding_handle, })); let status = (boot_services.install_protocol_interface)( @@ -43,6 +43,7 @@ pub fn initialize_driver_binding(image_handle: efi::Handle) -> Result<(), efi::S ); if status.is_error() { + drop(unsafe { Box::from_raw(driver_binding_ptr) }); return Err(status); } diff --git a/HidPkg/UefiHidDxe/src/hid.rs b/HidPkg/UefiHidDxe/src/hid.rs index 9ab7539ed5..f2703595b6 100644 --- a/HidPkg/UefiHidDxe/src/hid.rs +++ b/HidPkg/UefiHidDxe/src/hid.rs @@ -17,7 +17,7 @@ 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 hid_io: *mut hid_io::protocol::Protocol, pub keyboard_context: *mut KeyboardContext, pub pointer_context: *mut PointerContext, } diff --git a/HidPkg/UefiHidDxe/src/key_queue.rs b/HidPkg/UefiHidDxe/src/key_queue.rs new file mode 100644 index 0000000000..26e2da0485 --- /dev/null +++ b/HidPkg/UefiHidDxe/src/key_queue.rs @@ -0,0 +1,606 @@ +//! Key queue support for HID driver. +//! +//! This module manages a queue of pending keystrokes and current active keyboard state and provides support for +//! translating between HID usages and EFI keyboard primitives. +//! +//! ## License +//! +//! Copyright (c) Microsoft Corporation. All rights reserved. +//! SPDX-License-Identifier: BSD-2-Clause-Patent +//! + +use alloc::{ + collections::{BTreeSet, VecDeque}, + vec::Vec, +}; +use hidparser::report_data_types::Usage; +use hii_keyboard_layout::{EfiKey, HiiKey, HiiKeyDescriptor, HiiKeyboardLayout, HiiNsKeyDescriptor}; +use r_efi::{ + efi, + protocols::{self, hii_database::*, simple_text_input::InputKey, simple_text_input_ex::*}, +}; +use rust_advanced_logger_dxe::{debugln, DEBUG_WARN}; + +use crate::RUNTIME_SERVICES; + +// The set of HID usages that represent modifier keys this driver is interested in. +#[rustfmt::skip] +const KEYBOARD_MODIFIERS: &[u16] = &[ + LEFT_CONTROL_MODIFIER, RIGHT_CONTROL_MODIFIER, LEFT_SHIFT_MODIFIER, RIGHT_SHIFT_MODIFIER, LEFT_ALT_MODIFIER, + RIGHT_ALT_MODIFIER, LEFT_LOGO_MODIFIER, RIGHT_LOGO_MODIFIER, MENU_MODIFIER, PRINT_MODIFIER, SYS_REQUEST_MODIFIER, + ALT_GR_MODIFIER]; + +// The set of HID usages that represent modifier keys that toggle state (as opposed to remain active while pressed). +const TOGGLE_MODIFIERS: &[u16] = &[NUM_LOCK_MODIFIER, CAPS_LOCK_MODIFIER, SCROLL_LOCK_MODIFIER]; + +// Control, Shift, and Alt modifiers. +const CTRL_MODIFIERS: &[u16] = &[LEFT_CONTROL_MODIFIER, RIGHT_CONTROL_MODIFIER]; +const SHIFT_MODIFIERS: &[u16] = &[LEFT_SHIFT_MODIFIER, RIGHT_SHIFT_MODIFIER]; +const ALT_MODIFIERS: &[u16] = &[LEFT_ALT_MODIFIER, RIGHT_ALT_MODIFIER]; + +// Defines whether a key stroke represents a key being pressed (KeyDown) or released (KeyUp) +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum KeyAction { + // Key is being pressed + KeyUp, + // Key is being released + KeyDown, +} + +// A wrapper for the KeyData type that allows definition of the Ord trait and aditional registration matching logic. +#[derive(Debug, Clone)] +pub(crate) struct OrdKeyData(pub protocols::simple_text_input_ex::KeyData); + +impl Ord for OrdKeyData { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + let e = self.0.key.unicode_char.cmp(&other.0.key.unicode_char); + if !e.is_eq() { + return e; + } + let e = self.0.key.scan_code.cmp(&other.0.key.scan_code); + if !e.is_eq() { + return e; + } + let e = self.0.key_state.key_shift_state.cmp(&other.0.key_state.key_shift_state); + if !e.is_eq() { + return e; + } + return self.0.key_state.key_toggle_state.cmp(&other.0.key_state.key_toggle_state); + } +} + +impl PartialOrd for OrdKeyData { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for OrdKeyData { + fn eq(&self, other: &Self) -> bool { + self.cmp(other).is_eq() + } +} + +impl Eq for OrdKeyData {} + +impl OrdKeyData { + // Returns whether this key matches the given registration. Note that this is not a straight compare - UEFI spec + // allows for some degree of wildcard matching. Refer to UEFI spec 2.10 section 12.2.5. + pub(crate) fn matches_registered_key(&self, registration: &Self) -> bool { + // assign names here for brevity below. + let self_char = self.0.key.unicode_char; + let self_scan = self.0.key.scan_code; + let self_shift = self.0.key_state.key_shift_state; + let self_toggle = self.0.key_state.key_toggle_state; + let register_char = registration.0.key.unicode_char; + let register_scan = registration.0.key.scan_code; + let register_shift = registration.0.key_state.key_shift_state; + let register_toggle = registration.0.key_state.key_toggle_state; + + //char and scan must match (per the reference implementation in the EDK2 C code). + if !(register_char == self_char && register_scan == self_scan) { + return false; + } + + //shift state must be zero or must match. + if !(register_shift == 0 || register_shift == self_shift) { + return false; + } + + //toggle state must be zero or must match. + if !(register_toggle == 0 || register_toggle == self_toggle) { + return false; + } + return true; + } +} + +// This structure manages the queue of pending keystrokes +#[derive(Debug, Default)] +pub(crate) struct KeyQueue { + layout: Option, + active_modifiers: BTreeSet, + active_ns_key: Option, + partial_key_support_active: bool, + key_queue: VecDeque, + registered_keys: BTreeSet, + notified_key_queue: VecDeque, +} + +impl KeyQueue { + // resets the KeyQueue to initial state + pub(crate) fn reset(&mut self) { + self.active_modifiers.clear(); + self.active_ns_key = None; + self.partial_key_support_active = false; + self.key_queue.clear(); + } + + // Processes the given keystroke and updates the KeyQueue accordingly. + pub(crate) fn keystroke(&mut self, key: Usage, action: KeyAction) { + let Some(ref active_layout) = self.layout else { + //nothing to do if no layout. This is unexpected: layout should be initialized with default if not present. + debugln!(DEBUG_WARN, "key_queue::keystroke: Received keystroke without layout."); + return; + }; + + let Some(efi_key) = usage_to_efi_key(key) else { + //unsupported key usage, nothing to do. + return; + }; + + // Check if it is a dependent key of a currently active "non-spacing" (ns) key. + // Non-spacing key handling is described in UEFI spec 2.10 section 33.2.4.3. + let mut current_descriptor: Option = None; + if let Some(ref ns_key) = self.active_ns_key { + for descriptor in &ns_key.dependent_keys { + if descriptor.key == efi_key { + // found a dependent key for a previously active ns key. + // de-activate the ns key and process the dependent descriptor. + current_descriptor = Some(*descriptor); + self.active_ns_key = None; + break; + } + } + } + + // If it is not a dependent key of a currently active ns key, then check if it is a regular or ns key. + if current_descriptor.is_none() { + for key in &active_layout.keys { + match key { + HiiKey::Key(descriptor) => { + if descriptor.key == efi_key { + current_descriptor = Some(*descriptor); + break; + } + } + HiiKey::NsKey(ns_descriptor) => { + if ns_descriptor.descriptor.key == efi_key { + // if it is an ns_key, set it as the active ns key, and no further processing is needed. + self.active_ns_key = Some(ns_descriptor.clone()); + return; + } + } + } + } + } + + if current_descriptor.is_none() { + return; //could not find descriptor, nothing to do. + } + + let current_descriptor = current_descriptor.unwrap(); + + //handle modifiers that are active as long as they are pressed + if KEYBOARD_MODIFIERS.contains(¤t_descriptor.modifier) { + match action { + KeyAction::KeyUp => { + self.active_modifiers.remove(¤t_descriptor.modifier); + } + KeyAction::KeyDown => { + self.active_modifiers.insert(current_descriptor.modifier); + } + } + } + + //handle modifiers that toggle each time the key is pressed. + if TOGGLE_MODIFIERS.contains(¤t_descriptor.modifier) && action == KeyAction::KeyDown { + if self.active_modifiers.contains(¤t_descriptor.modifier) { + self.active_modifiers.remove(¤t_descriptor.modifier); + } else { + self.active_modifiers.insert(current_descriptor.modifier); + } + } + + //handle ctrl-alt-delete + if CTRL_MODIFIERS.iter().any(|x| self.active_modifiers.contains(x)) + && ALT_MODIFIERS.iter().any(|x| self.active_modifiers.contains(x)) + && current_descriptor.modifier == DELETE_MODIFIER + { + debugln!(DEBUG_WARN, "Ctrl-Alt-Del pressed, resetting system."); + if let Some(runtime_services) = unsafe { RUNTIME_SERVICES.as_mut() } { + (runtime_services.reset_system)(efi::RESET_WARM, efi::Status::SUCCESS, 0, core::ptr::null_mut()); + } + panic!("Reset failed."); + } + + if action == KeyAction::KeyUp { + //nothing else to do. + return; + } + + // process the keystroke to construct a KeyData item to add to the queue. + let mut key_data = protocols::simple_text_input_ex::KeyData { + key: InputKey { + unicode_char: current_descriptor.unicode, + scan_code: modifier_to_scan(current_descriptor.modifier), + }, + ..Default::default() + }; + + // retrieve relevant modifier state that may need to be applied to the key data. + let shift_active = SHIFT_MODIFIERS.iter().any(|x| self.active_modifiers.contains(x)); + let alt_gr_active = self.active_modifiers.contains(&ALT_GR_MODIFIER); + let caps_lock_active = self.active_modifiers.contains(&CAPS_LOCK_MODIFIER); + let num_lock_active = self.active_modifiers.contains(&NUM_LOCK_MODIFIER); + + // Apply the shift modifier if needed. Track whether it was applied as the shift modifier is removed from the key + // state if it was applied here. + let mut shift_applied: bool = false; + if (current_descriptor.affected_attribute & AFFECTED_BY_STANDARD_SHIFT) != 0 { + if shift_active { + //shift active + if alt_gr_active { + key_data.key.unicode_char = current_descriptor.shifted_alt_gr_unicode; + } else { + key_data.key.unicode_char = current_descriptor.shifted_unicode; + } + shift_applied = true; + } else { + //not shifted. + if alt_gr_active { + key_data.key.unicode_char = current_descriptor.alt_gr_unicode; + } + } + } + + // if capslock is active, then invert the shift state of the key. + if (current_descriptor.affected_attribute & AFFECTED_BY_CAPS_LOCK) != 0 && caps_lock_active { + //Note: reference EDK2 implementation does not apply capslock to alt_gr. + if key_data.key.unicode_char == current_descriptor.unicode { + key_data.key.unicode_char = current_descriptor.shifted_unicode; + } else if key_data.key.unicode_char == current_descriptor.shifted_unicode { + key_data.key.unicode_char = current_descriptor.unicode; + } + } + + // for the num pad, numlock (and shift state) controls whether a number key or a control key (e.g. arrow) is queued. + if (current_descriptor.affected_attribute & AFFECTED_BY_NUM_LOCK) != 0 { + if num_lock_active && !shift_active { + key_data.key.scan_code = SCAN_NULL; + } else { + key_data.key.unicode_char = 0x0000; + } + } + + //special handling for unicode 0x1B (ESC). + if key_data.key.unicode_char == 0x01B && key_data.key.scan_code == SCAN_NULL { + key_data.key.scan_code = SCAN_ESC; + key_data.key.unicode_char = 0x0000; + } + + if !self.partial_key_support_active && key_data.key.unicode_char == 0 && key_data.key.scan_code == SCAN_NULL { + return; // no further processing required if there is no key or scancode and partial support is not active. + } + + //initialize key state from active modifiers + key_data.key_state = self.init_key_state(); + + // if shift was applied above, then remove shift from key state. See UEFI spec 2.10 section 12.2.3. + if shift_applied { + key_data.key_state.key_shift_state &= !(LEFT_SHIFT_PRESSED | RIGHT_SHIFT_PRESSED); + } + + // if a callback has been registered matching this key, enqueue it in the callback queue. + if self.is_registered_key(key_data) { + self.notified_key_queue.push_back(key_data); + } + + // enqueue the key data. + self.key_queue.push_back(key_data); + } + + fn is_registered_key(&self, current_key: KeyData) -> bool { + for registered_key in &self.registered_keys { + if OrdKeyData(current_key).matches_registered_key(registered_key) { + return true; + } + } + return false; + } + + // Creates a KeyState instance initialized based on the current modifier state. + pub(crate) fn init_key_state(&self) -> KeyState { + let mut key_state: KeyState = Default::default(); + + key_state.key_shift_state = SHIFT_STATE_VALID; + key_state.key_toggle_state = TOGGLE_STATE_VALID | KEY_STATE_EXPOSED; + + let key_shift_state = &mut key_state.key_shift_state; + let key_toggle_state = &mut key_state.key_toggle_state; + + for modifier in &self.active_modifiers { + match *modifier { + LEFT_CONTROL_MODIFIER => *key_shift_state |= LEFT_CONTROL_PRESSED, + RIGHT_CONTROL_MODIFIER => *key_shift_state |= RIGHT_CONTROL_PRESSED, + LEFT_ALT_MODIFIER => *key_shift_state |= LEFT_ALT_PRESSED, + RIGHT_ALT_MODIFIER => *key_shift_state |= RIGHT_ALT_PRESSED, + LEFT_SHIFT_MODIFIER => *key_shift_state |= LEFT_SHIFT_PRESSED, + RIGHT_SHIFT_MODIFIER => *key_shift_state |= RIGHT_SHIFT_PRESSED, + LEFT_LOGO_MODIFIER => *key_shift_state |= LEFT_LOGO_PRESSED, + RIGHT_LOGO_MODIFIER => *key_shift_state |= RIGHT_LOGO_PRESSED, + MENU_MODIFIER => *key_shift_state |= MENU_KEY_PRESSED, + SYS_REQUEST_MODIFIER | PRINT_MODIFIER => *key_shift_state |= SYS_REQ_PRESSED, + SCROLL_LOCK_MODIFIER => *key_toggle_state |= SCROLL_LOCK_ACTIVE, + NUM_LOCK_MODIFIER => *key_toggle_state |= NUM_LOCK_ACTIVE, + CAPS_LOCK_MODIFIER => *key_toggle_state |= CAPS_LOCK_ACTIVE, + _ => (), + } + } + key_state + } + + // pops and returns the front of the key queue + pub(crate) fn pop_key(&mut self) -> Option { + self.key_queue.pop_front() + } + + // returns a copy of the key at the front of the queue + pub(crate) fn peek_key(&self) -> Option { + self.key_queue.front().cloned() + } + + // pops and returns the front of the notify queue + pub(crate) fn pop_notifiy_key(&mut self) -> Option { + self.notified_key_queue.pop_front() + } + + // returns a copy of the key at the front of the notify queue + pub(crate) fn peek_notify_key(&self) -> Option { + self.key_queue.front().cloned() + } + + // set the key toggle state. This allows control of scroll/caps/num locks, as well as whether partial key state is + // exposed. + pub(crate) fn set_key_toggle_state(&mut self, toggle_state: KeyToggleState) { + if (toggle_state & SCROLL_LOCK_ACTIVE) != 0 { + self.active_modifiers.insert(SCROLL_LOCK_MODIFIER); + } else { + self.active_modifiers.remove(&SCROLL_LOCK_MODIFIER); + } + + if (toggle_state & NUM_LOCK_ACTIVE) != 0 { + self.active_modifiers.insert(NUM_LOCK_MODIFIER); + } else { + self.active_modifiers.remove(&NUM_LOCK_MODIFIER); + } + + if (toggle_state & CAPS_LOCK_ACTIVE) != 0 { + self.active_modifiers.insert(CAPS_LOCK_MODIFIER); + } else { + self.active_modifiers.remove(&CAPS_LOCK_MODIFIER); + } + + self.partial_key_support_active = (toggle_state & KEY_STATE_EXPOSED) != 0; + } + + // Returns a vector of HID usages corresponding to the active LEDs based on the active modifier state. + pub(crate) fn get_active_leds(&self) -> Vec { + self.active_modifiers.iter().cloned().filter_map(|x| modifer_to_led_usage(x)).collect() + } + + // Returns the current keyboard layout that the KeyQueue is using. + pub(crate) fn get_layout(&self) -> Option { + self.layout.clone() + } + + // Sets the current keyboard layout that the KeyQueue should use. + pub(crate) fn set_layout(&mut self, new_layout: Option) { + self.layout = new_layout; + } + + // Add a registration key for notifications; if a keystroke matches this key data, it will be added to the notify + // queue in addition to the normal key queue. + pub(crate) fn add_notify_key(&mut self, key_data: OrdKeyData) { + self.registered_keys.insert(key_data); + } + + // Remove a previously added notify key; keystrokes matching this key data will no longer be added to the notify + // queue. + pub(crate) fn remove_notify_key(&mut self, key_data: &OrdKeyData) { + self.registered_keys.remove(key_data); + } +} + +// Helper routine that converts a HID Usage to the corresponding EfiKey. +fn usage_to_efi_key(usage: Usage) -> Option { + //Refer to UEFI spec version 2.10 figure 34.3 + match usage.into() { + 0x00070001..=0x00070003 => None, //Keyboard error codes. + 0x00070004 => Some(EfiKey::C1), + 0x00070005 => Some(EfiKey::B5), + 0x00070006 => Some(EfiKey::B3), + 0x00070007 => Some(EfiKey::C3), + 0x00070008 => Some(EfiKey::D3), + 0x00070009 => Some(EfiKey::C4), + 0x0007000A => Some(EfiKey::C5), + 0x0007000B => Some(EfiKey::C6), + 0x0007000C => Some(EfiKey::D8), + 0x0007000D => Some(EfiKey::C7), + 0x0007000E => Some(EfiKey::C8), + 0x0007000F => Some(EfiKey::C9), + 0x00070010 => Some(EfiKey::B7), + 0x00070011 => Some(EfiKey::B6), + 0x00070012 => Some(EfiKey::D9), + 0x00070013 => Some(EfiKey::D10), + 0x00070014 => Some(EfiKey::D1), + 0x00070015 => Some(EfiKey::D4), + 0x00070016 => Some(EfiKey::C2), + 0x00070017 => Some(EfiKey::D5), + 0x00070018 => Some(EfiKey::D7), + 0x00070019 => Some(EfiKey::B4), + 0x0007001A => Some(EfiKey::D2), + 0x0007001B => Some(EfiKey::B2), + 0x0007001C => Some(EfiKey::D6), + 0x0007001D => Some(EfiKey::B1), + 0x0007001E => Some(EfiKey::E1), + 0x0007001F => Some(EfiKey::E2), + 0x00070020 => Some(EfiKey::E3), + 0x00070021 => Some(EfiKey::E4), + 0x00070022 => Some(EfiKey::E5), + 0x00070023 => Some(EfiKey::E6), + 0x00070024 => Some(EfiKey::E7), + 0x00070025 => Some(EfiKey::E8), + 0x00070026 => Some(EfiKey::E9), + 0x00070027 => Some(EfiKey::E10), + 0x00070028 => Some(EfiKey::Enter), + 0x00070029 => Some(EfiKey::Esc), + 0x0007002A => Some(EfiKey::BackSpace), + 0x0007002B => Some(EfiKey::Tab), + 0x0007002C => Some(EfiKey::SpaceBar), + 0x0007002D => Some(EfiKey::E11), + 0x0007002E => Some(EfiKey::E12), + 0x0007002F => Some(EfiKey::D11), + 0x00070030 => Some(EfiKey::D12), + 0x00070031 => Some(EfiKey::D13), + 0x00070032 => Some(EfiKey::C12), + 0x00070033 => Some(EfiKey::C10), + 0x00070034 => Some(EfiKey::C11), + 0x00070035 => Some(EfiKey::E0), + 0x00070036 => Some(EfiKey::B8), + 0x00070037 => Some(EfiKey::B9), + 0x00070038 => Some(EfiKey::B10), + 0x00070039 => Some(EfiKey::CapsLock), + 0x0007003A => Some(EfiKey::F1), + 0x0007003B => Some(EfiKey::F2), + 0x0007003C => Some(EfiKey::F3), + 0x0007003D => Some(EfiKey::F4), + 0x0007003E => Some(EfiKey::F5), + 0x0007003F => Some(EfiKey::F6), + 0x00070040 => Some(EfiKey::F7), + 0x00070041 => Some(EfiKey::F8), + 0x00070042 => Some(EfiKey::F9), + 0x00070043 => Some(EfiKey::F10), + 0x00070044 => Some(EfiKey::F11), + 0x00070045 => Some(EfiKey::F12), + 0x00070046 => Some(EfiKey::Print), + 0x00070047 => Some(EfiKey::SLck), + 0x00070048 => Some(EfiKey::Pause), + 0x00070049 => Some(EfiKey::Ins), + 0x0007004A => Some(EfiKey::Home), + 0x0007004B => Some(EfiKey::PgUp), + 0x0007004C => Some(EfiKey::Del), + 0x0007004D => Some(EfiKey::End), + 0x0007004E => Some(EfiKey::PgDn), + 0x0007004F => Some(EfiKey::RightArrow), + 0x00070050 => Some(EfiKey::LeftArrow), + 0x00070051 => Some(EfiKey::DownArrow), + 0x00070052 => Some(EfiKey::UpArrow), + 0x00070053 => Some(EfiKey::NLck), + 0x00070054 => Some(EfiKey::Slash), + 0x00070055 => Some(EfiKey::Asterisk), + 0x00070056 => Some(EfiKey::Minus), + 0x00070057 => Some(EfiKey::Plus), + 0x00070058 => Some(EfiKey::Enter), + 0x00070059 => Some(EfiKey::One), + 0x0007005A => Some(EfiKey::Two), + 0x0007005B => Some(EfiKey::Three), + 0x0007005C => Some(EfiKey::Four), + 0x0007005D => Some(EfiKey::Five), + 0x0007005E => Some(EfiKey::Six), + 0x0007005F => Some(EfiKey::Seven), + 0x00070060 => Some(EfiKey::Eight), + 0x00070061 => Some(EfiKey::Nine), + 0x00070062 => Some(EfiKey::Zero), + 0x00070063 => Some(EfiKey::Period), + 0x00070064 => Some(EfiKey::B0), + 0x00070065 => Some(EfiKey::A4), + 0x00070066..=0x000700DF => None, // not used by EFI keyboard layout. + 0x000700E0 => Some(EfiKey::LCtrl), + 0x000700E1 => Some(EfiKey::LShift), + 0x000700E2 => Some(EfiKey::LAlt), + 0x000700E3 => Some(EfiKey::A0), + 0x000700E4 => Some(EfiKey::RCtrl), + 0x000700E5 => Some(EfiKey::RShift), + 0x000700E6 => Some(EfiKey::A2), + 0x000700E7 => Some(EfiKey::A3), + _ => None, // all other usages not used by EFI keyboard layout. + } +} + +//These should be defined in r_efi::protocols::simple_text_input +const SCAN_NULL: u16 = 0x0000; +const SCAN_UP: u16 = 0x0001; +const SCAN_DOWN: u16 = 0x0002; +const SCAN_RIGHT: u16 = 0x0003; +const SCAN_LEFT: u16 = 0x0004; +const SCAN_HOME: u16 = 0x0005; +const SCAN_END: u16 = 0x0006; +const SCAN_INSERT: u16 = 0x0007; +const SCAN_DELETE: u16 = 0x0008; +const SCAN_PAGE_UP: u16 = 0x0009; +const SCAN_PAGE_DOWN: u16 = 0x000A; +const SCAN_F1: u16 = 0x000B; +const SCAN_F2: u16 = 0x000C; +const SCAN_F3: u16 = 0x000D; +const SCAN_F4: u16 = 0x000E; +const SCAN_F5: u16 = 0x000F; +const SCAN_F6: u16 = 0x0010; +const SCAN_F7: u16 = 0x0011; +const SCAN_F8: u16 = 0x0012; +const SCAN_F9: u16 = 0x0013; +const SCAN_F10: u16 = 0x0014; +const SCAN_F11: u16 = 0x0015; +const SCAN_F12: u16 = 0x0016; +const SCAN_ESC: u16 = 0x0017; +const SCAN_PAUSE: u16 = 0x0048; + +// helper routine that converts the given modifier to the corresponding SCAN code +fn modifier_to_scan(modifier: u16) -> u16 { + match modifier { + INSERT_MODIFIER => SCAN_INSERT, + DELETE_MODIFIER => SCAN_DELETE, + PAGE_DOWN_MODIFIER => SCAN_PAGE_DOWN, + PAGE_UP_MODIFIER => SCAN_PAGE_UP, + HOME_MODIFIER => SCAN_HOME, + END_MODIFIER => SCAN_END, + LEFT_ARROW_MODIFIER => SCAN_LEFT, + RIGHT_ARROW_MODIFIER => SCAN_RIGHT, + DOWN_ARROW_MODIFIER => SCAN_DOWN, + UP_ARROW_MODIFIER => SCAN_UP, + FUNCTION_KEY_ONE_MODIFIER => SCAN_F1, + FUNCTION_KEY_TWO_MODIFIER => SCAN_F2, + FUNCTION_KEY_THREE_MODIFIER => SCAN_F3, + FUNCTION_KEY_FOUR_MODIFIER => SCAN_F4, + FUNCTION_KEY_FIVE_MODIFIER => SCAN_F5, + FUNCTION_KEY_SIX_MODIFIER => SCAN_F6, + FUNCTION_KEY_SEVEN_MODIFIER => SCAN_F7, + FUNCTION_KEY_EIGHT_MODIFIER => SCAN_F8, + FUNCTION_KEY_NINE_MODIFIER => SCAN_F9, + FUNCTION_KEY_TEN_MODIFIER => SCAN_F10, + FUNCTION_KEY_ELEVEN_MODIFIER => SCAN_F11, + FUNCTION_KEY_TWELVE_MODIFIER => SCAN_F12, + PAUSE_MODIFIER => SCAN_PAUSE, + _ => SCAN_NULL, + } +} + +// helper routine that converts the given modifer to the corresponding HID Usage. +fn modifer_to_led_usage(modifier: u16) -> Option { + match modifier { + NUM_LOCK_MODIFIER => Some(Usage::from(0x00080001)), + CAPS_LOCK_MODIFIER => Some(Usage::from(0x00080002)), + SCROLL_LOCK_MODIFIER => Some(Usage::from(0x00080003)), + _ => None, + } +} diff --git a/HidPkg/UefiHidDxe/src/keyboard.rs b/HidPkg/UefiHidDxe/src/keyboard.rs index fe779614b6..9624bb3ded 100644 --- a/HidPkg/UefiHidDxe/src/keyboard.rs +++ b/HidPkg/UefiHidDxe/src/keyboard.rs @@ -8,38 +8,1027 @@ //! SPDX-License-Identifier: BSD-2-Clause-Patent //! -use hidparser::ReportDescriptor; -use r_efi::{efi, protocols::driver_binding}; +use core::ffi::c_void; -use crate::hid::HidContext; +use alloc::{ + boxed::Box, + collections::{BTreeMap, BTreeSet}, + vec, + vec::Vec, +}; +use hidparser::{ + report_data_types::{ReportId, Usage}, + ArrayField, ReportDescriptor, ReportField, VariableField, +}; +use hii_keyboard_layout::HiiKeyboardLayout; +use memoffset::{offset_of, raw_field}; +use r_efi::{ + efi, hii, + protocols::{ + self, driver_binding, + simple_text_input_ex::{KeyData, LEFT_SHIFT_PRESSED, RIGHT_SHIFT_PRESSED}, + }, + system, +}; +use rust_advanced_logger_dxe::{debugln, DEBUG_ERROR, DEBUG_WARN}; +use crate::{ + hid::HidContext, + key_queue::{self, OrdKeyData}, + BOOT_SERVICES, +}; + +// usages supported by this module +const KEYBOARD_MODIFIER_USAGE_MIN: u32 = 0x000700E0; +const KEYBOARD_MODIFIER_USAGE_MAX: u32 = 0x000700E7; +const KEYBOARD_USAGE_MIN: u32 = 0x00070001; +const KEYBOARD_USAGE_MAX: u32 = 0x00070065; +const LED_USAGE_MIN: u32 = 0x00080001; +const LED_USAGE_MAX: u32 = 0x00080005; + +// maps a given field to a routine that handles input from it. +#[derive(Debug, Clone)] +struct ReportFieldWithHandler { + field: T, + report_handler: fn(handler: &mut KeyboardHandler, field: T, report: &[u8]), +} + +// maps a given field to a routine that builds output reports from it. +#[derive(Debug, Clone)] +struct ReportFieldBuilder { + field: T, + field_builder: fn(&mut KeyboardHandler, field: T, report: &mut [u8]), +} + +// Defines an input report and the fields of interest in it. +#[derive(Debug, Default, Clone)] +struct KeyboardReportData { + report_id: Option, + report_size: usize, + relevant_variable_fields: Vec>, + relevant_array_fields: Vec>, +} + +// Defines an output report and the fields of interest in it. +#[derive(Debug, Default, Clone)] +struct KeyboardOutputReportBuilder { + report_id: Option, + report_size: usize, + relevant_variable_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. +#[repr(C)] pub struct KeyboardContext { + simple_text_in: protocols::simple_text_input::Protocol, + simple_text_in_ex: protocols::simple_text_input_ex::Protocol, pub handler: KeyboardHandler, + controller: efi::Handle, + hid_context: *mut HidContext, } -pub struct KeyboardHandler {} +/// Tracks all the input reports for this device as well as pointer state. +#[derive(Debug)] +pub struct KeyboardHandler { + input_reports: BTreeMap, KeyboardReportData>, + output_builders: Vec, + report_id_present: bool, + last_keys: BTreeSet, + current_keys: BTreeSet, + led_state: BTreeSet, + key_queue: key_queue::KeyQueue, + notification_callbacks: BTreeMap, + next_notify_handle: usize, + key_notify_event: efi::Event, + hid_io: *mut hid_io::protocol::Protocol, +} impl KeyboardHandler { - pub fn process_input_report(&mut self, _report_buffer: &[u8]) { - todo!() + // processes a report descriptor and yields a KeyboardHandler instance if this descriptor describes input + // that can be handled by this KeyboardHandler. + fn process_descriptor(descriptor: &ReportDescriptor) -> Result { + let mut handler: KeyboardHandler = KeyboardHandler { + input_reports: BTreeMap::new(), + output_builders: Vec::new(), + report_id_present: false, + last_keys: BTreeSet::new(), + current_keys: BTreeSet::new(), + led_state: BTreeSet::new(), + key_queue: Default::default(), + notification_callbacks: BTreeMap::new(), + next_notify_handle: 0, + key_notify_event: core::ptr::null_mut(), + hid_io: core::ptr::null_mut(), + }; + + let multiple_reports = + descriptor.input_reports.len() > 1 || descriptor.output_reports.len() > 1 || descriptor.features.len() > 1; + + for report in &descriptor.input_reports { + let mut report_data = KeyboardReportData { report_id: report.report_id, ..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)?; + } + + report_data.report_size = report.size_in_bits.div_ceil(8); + + for field in &report.fields { + match field { + //Variable fields (typically used for modifier Usages) + ReportField::Variable(field) => { + match field.usage.into() { + KEYBOARD_MODIFIER_USAGE_MIN..=KEYBOARD_MODIFIER_USAGE_MAX => { + report_data.relevant_variable_fields.push(ReportFieldWithHandler:: { + field: field.clone(), + report_handler: Self::handle_variable_key, + }) + } + _ => (), // other usages irrelevant. + } + } + //Array fields (typically used for key strokes) + ReportField::Array(field) => { + for usage_list in &field.usage_list { + if usage_list.contains(Usage::from(KEYBOARD_USAGE_MIN)) + || usage_list.contains(Usage::from(KEYBOARD_USAGE_MAX)) + { + report_data.relevant_array_fields.push(ReportFieldWithHandler:: { + field: field.clone(), + report_handler: Self::handle_array_key, + }); + break; + } + } + } + ReportField::Padding(_) => (), // padding irrelevant. + } + } + if report_data.relevant_variable_fields.len() > 0 || report_data.relevant_array_fields.len() > 0 { + handler.input_reports.insert(report_data.report_id, report_data); + } + } + + for report in &descriptor.output_reports { + let mut report_builder = KeyboardOutputReportBuilder { report_id: report.report_id, ..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)?; + } + + report_builder.report_size = report.size_in_bits / 8; + if (report.size_in_bits % 8) != 0 { + report_builder.report_size += 1; + } + + for field in &report.fields { + match field { + //Variable fields in output reports (typically used for LEDs). + ReportField::Variable(field) => { + match field.usage.into() { + LED_USAGE_MIN..=LED_USAGE_MAX => { + report_builder.relevant_variable_fields.push( + ReportFieldBuilder { + field: field.clone(), + field_builder: Self::build_led_report + } + ) + }, + _=> (), //other usages irrelevant. + } + }, + ReportField::Array(_) | // No support for array field report outputs; could be added if required. + ReportField::Padding(_) => (), // padding fields irrelevant. + } + } + if report_builder.relevant_variable_fields.len() > 0 { + handler.output_builders.push(report_builder); + } + } + + if handler.input_reports.len() > 0 || handler.output_builders.len() > 0 { + Ok(handler) + } else { + Err(efi::Status::UNSUPPORTED) + } + } + + //Create the Keyboard Context and install the SimpleTextIn/SimpleTextInEx interfaces. + fn install_keyboard_interfaces( + self, + controller: efi::Handle, + hid_context: *mut HidContext, + ) -> Result<*mut KeyboardContext, 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 keyboard 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 raw pointer. + let context_ptr = Box::into_raw(Box::new(KeyboardContext { + handler: self, + simple_text_in: protocols::simple_text_input::Protocol { + reset: simple_text_in_reset, + read_key_stroke: simple_text_in_read_key_stroke, + wait_for_key: core::ptr::null_mut(), + }, + simple_text_in_ex: protocols::simple_text_input_ex::Protocol { + reset: simple_text_in_ex_reset, + read_key_stroke_ex: simple_text_in_ex_read_key_stroke, + wait_for_key_ex: core::ptr::null_mut(), + set_state: simple_text_in_ex_set_state, + register_key_notify: simple_text_in_ex_register_key_notify, + unregister_key_notify: simple_text_in_ex_unregister_key_notify, + }, + controller, + hid_context, + })); + + let context = unsafe { context_ptr.as_mut().expect("freshly boxed context pointer is null") }; + //create wait_for_key events. + let mut wait_for_key_event: efi::Event = core::ptr::null_mut(); + let status = (boot_services.create_event)( + system::EVT_NOTIFY_WAIT, + system::TPL_NOTIFY, + Some(wait_for_key), + context_ptr as *mut c_void, + core::ptr::addr_of_mut!(wait_for_key_event), + ); + if status.is_error() { + drop(unsafe { Box::from_raw(context_ptr) }); + return Err(status); + } + context.simple_text_in.wait_for_key = wait_for_key_event; + + let mut wait_for_key_ex_event: efi::Event = core::ptr::null_mut(); + let status = (boot_services.create_event)( + system::EVT_NOTIFY_WAIT, + system::TPL_NOTIFY, + Some(wait_for_key), + context_ptr as *mut c_void, + core::ptr::addr_of_mut!(wait_for_key_ex_event), + ); + if status.is_error() { + (boot_services.close_event)(wait_for_key_event); + drop(unsafe { Box::from_raw(context_ptr) }); + return Err(status); + } + context.simple_text_in_ex.wait_for_key_ex = wait_for_key_ex_event; + + //create deferred dispatch event for key notifies. Key notify callbacks are required to be invoked at TPL_CALLBACK + //per UEFI spec 2.10 section 12.2.5. The keyboard handler interfaces may run at a higher TPL, so this event is used + //to dispatch the key notifies at the required TPL level. + let mut key_notify_event: efi::Event = core::ptr::null_mut(); + let status = (boot_services.create_event)( + system::EVT_NOTIFY_SIGNAL, + system::TPL_CALLBACK, + Some(process_key_notifies), + context_ptr as *mut c_void, + core::ptr::addr_of_mut!(key_notify_event), + ); + if status.is_error() { + (boot_services.close_event)(wait_for_key_event); + (boot_services.close_event)(wait_for_key_ex_event); + drop(unsafe { Box::from_raw(context_ptr) }); + return Err(status); + } + context.handler.key_notify_event = key_notify_event; + + //install simple_text_in and simple_text_in_ex + let mut controller = controller; + let simple_text_in_ptr = raw_field!(context_ptr, KeyboardContext, simple_text_in); + let status = (boot_services.install_protocol_interface)( + core::ptr::addr_of_mut!(controller), + &protocols::simple_text_input::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + efi::NATIVE_INTERFACE, + simple_text_in_ptr as *mut c_void, + ); + + if status.is_error() { + let _ = deinitialize(context_ptr); + return Err(status); + } + + let simple_text_in_ex_ptr = raw_field!(context_ptr, KeyboardContext, simple_text_in_ex); + let status = (boot_services.install_protocol_interface)( + core::ptr::addr_of_mut!(controller), + &protocols::simple_text_input_ex::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + efi::NATIVE_INTERFACE, + simple_text_in_ex_ptr as *mut c_void, + ); + + if status.is_error() { + let _ = deinitialize(context_ptr); + return Err(status); + } + + context.handler.hid_io = unsafe { (*hid_context).hid_io }; + + Ok(context_ptr) + } + + // Uninstall the SimpleTextIn and SimpleTextInEx interfaces and free the Keyboard context. + fn uninstall_keyboard_interfaces(keyboard_context: *mut KeyboardContext) -> 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 simple_text_in_ptr = raw_field!(keyboard_context, KeyboardContext, simple_text_in); + let simple_text_in_ex_ptr = raw_field!(keyboard_context, KeyboardContext, simple_text_in_ex); + + let mut overall_status = efi::Status::SUCCESS; + + // close the wait_for_key events + let status = (boot_services.close_event)(unsafe { (*simple_text_in_ptr).wait_for_key }); + if status.is_error() { + overall_status = status; + } + let status = (boot_services.close_event)(unsafe { (*simple_text_in_ex_ptr).wait_for_key_ex }); + if status.is_error() { + overall_status = status; + } + let status = (boot_services.close_event)(unsafe { (*keyboard_context).handler.key_notify_event }); + if status.is_error() { + overall_status = status; + } + + //uninstall the protocol interfaces + let status = (boot_services.uninstall_protocol_interface)( + unsafe { (*keyboard_context).controller }, + &protocols::simple_text_input::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + simple_text_in_ptr as *mut c_void, + ); + if status.is_error() { + overall_status = status; + } + let status = (boot_services.uninstall_protocol_interface)( + unsafe { (*keyboard_context).controller }, + &protocols::simple_text_input_ex::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + simple_text_in_ex_ptr as *mut c_void, + ); + if status.is_error() { + overall_status = status; + } + + // take back the context raw pointer + drop(unsafe { Box::from_raw(keyboard_context) }); + + if overall_status.is_error() { + Err(overall_status) + } else { + Ok(()) + } + } + + // helper routine to handle variable keyboard input report fields + fn handle_variable_key(&mut self, field: VariableField, report: &[u8]) { + match field.field_value(report) { + Some(x) if x != 0 => { + self.current_keys.insert(field.usage); + } + None | Some(_) => (), + } + } + + // helper routine to handle array keyboard input report fields + fn handle_array_key(&mut self, field: ArrayField, report: &[u8]) { + match field.field_value(report) { + Some(index) if index != 0 => { + let mut index = (index as u32 - u32::from(field.logical_minimum)) as usize; + let usage = field.usage_list.iter().find_map(|x| { + let range_size = (x.end() - x.start()) as usize; + if index <= range_size { + x.range().nth(index) + } else { + index = index - range_size as usize; + None + } + }); + if let Some(usage) = usage { + self.current_keys.insert(Usage::from(usage)); + } + } + None | Some(_) => (), + } + } + + // process the given input report buffer and handle 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; + } + + //reset currently active keys to empty set. + self.current_keys.clear(); + + // hand the report data to the handler for each relevant field for field-specific processing. + for field in report_data.relevant_variable_fields { + (field.report_handler)(self, field.field, report); + } + + for field in report_data.relevant_array_fields { + (field.report_handler)(self, field.field, report); + } + + //check if any key state has changed. + if self.last_keys != self.current_keys { + // process keys that are not in both sets: that is the set of keys that have changed. + // XOR on the sets yields a set of keys that are in either last or current keys, but not both. + + // Modifier keys need to be processed first so that normal key processing includes modifiers that showed up in + // the same report. The key sets are sorted by Usage, and modifier keys all have higher usages than normal keys + // - so use a reverse iterator to process the modifier keys first. + for changed_key in (&self.last_keys ^ &self.current_keys).into_iter().rev() { + if self.last_keys.contains(&changed_key) { + //In the last key list, but not in current. This is a key release. + self.key_queue.keystroke(changed_key, key_queue::KeyAction::KeyUp); + } else { + //Not in last, so must be in current. This is a key press. + self.key_queue.keystroke(changed_key, key_queue::KeyAction::KeyDown); + } + } + //after processing all the key strokes, check if any keys were pressed that should trigger the notifier callback + //and if so, signal the event to trigger notify processing at the appropriate TPL. + if self.key_queue.peek_notify_key().is_some() { + let boot_services = unsafe { BOOT_SERVICES.as_mut().expect("bad boot services pointer") }; + (boot_services.signal_event)(self.key_notify_event); + } + + //after processing all the key strokes, send updated LED state if required. + self.generate_led_output_report(); + } + //after all key handling is complete for this report, update the last key set to match the current key set. + self.last_keys = self.current_keys.clone(); + } + } + + //helper routine that updates the fields in the given report buffer for the given field (called for each field for + //every LED usage that was discovered in the output report descriptor). + fn build_led_report(&mut self, field: VariableField, report: &mut [u8]) { + let status = field.set_field_value(self.led_state.contains(&field.usage).into(), report); + if status.is_err() { + debugln!(DEBUG_WARN, "keyobard::build_led_report: failed to set field value: {:?}", status); + } + } + + //generates and sends an output report for each output report in the report descriptor that indicated an LED usage + //for an LED supported by this driver. + pub fn generate_led_output_report(&mut self) { + let hid_io = unsafe { self.hid_io.as_mut().expect("bad hidio pointer") }; + let current_leds: BTreeSet = self.key_queue.get_active_leds().iter().cloned().collect(); + if current_leds != self.led_state { + self.led_state = current_leds; + for output_builder in self.output_builders.clone() { + let mut report_buffer = vec![0u8; output_builder.report_size]; + for field_builder in &output_builder.relevant_variable_fields { + (field_builder.field_builder)(self, field_builder.field.clone(), report_buffer.as_mut_slice()); + } + let report_id: u32 = output_builder.report_id.unwrap_or(0.into()).into(); + let status = (hid_io.set_report)( + self.hid_io, + report_id as u8, + hid_io::protocol::HidReportType::OutputReport, + report_buffer.len(), + report_buffer.as_mut_ptr() as *mut c_void, + ); + if status.is_error() { + debugln!(DEBUG_WARN, "keyboard::generate_led_report: Set Report failed: {:?}", status); + } + } + } + } + + // indicates whether this keyboard handler has been initialized with a layout. + pub(crate) fn is_layout_installed(&self) -> bool { + self.key_queue.get_layout().is_some() + } + + // sets the layout to be used by this keyboard handler. + pub(crate) fn set_layout(&mut self, new_layout: Option) { + self.key_queue.set_layout(new_layout); } } +/// Initializes a keyboard handler on the given `controller` to handle reports described by `descriptor`. +/// +/// If the device doesn't provide the necessary reports for keyboards, or if there was an error processing the report +/// descriptor data or initializing the pointer handler or installing the simple text protocol instances into the +/// protocol database, an error is returned. +/// +/// Otherwise, a [`KeyboardContext`] that can be used to interact with this handler is returned. See [`KeyboardContext`] +/// documentation for constraints on interactions with it. pub fn initialize( - _controller: efi::Handle, - _descriptor: &ReportDescriptor, - _hid_context: *mut HidContext, + controller: efi::Handle, + descriptor: &ReportDescriptor, + hid_context_ptr: *mut HidContext, ) -> Result<*mut KeyboardContext, efi::Status> { - Err(efi::Status::UNSUPPORTED) + let handler = KeyboardHandler::process_descriptor(descriptor)?; + + let context = handler.install_keyboard_interfaces(controller, hid_context_ptr)?; + + if let Err(err) = initialize_keyboard_layout(context) { + let _ = deinitialize(context); + return Err(err); + } + + let hid_context = unsafe { hid_context_ptr.as_mut().expect("[keyboard::initialize]: bad hid context pointer") }; + + hid_context.keyboard_context = context; + + Ok(context) } -pub fn deinitialize(_context: *mut KeyboardContext) -> Result<(), efi::Status> { - Err(efi::Status::UNSUPPORTED) +/// De-initializes a keyboard handler described by `context` on the given `controller`. +pub fn deinitialize(context: *mut KeyboardContext) -> Result<(), efi::Status> { + if context.is_null() { + return Err(efi::Status::NOT_STARTED); + } + KeyboardHandler::uninstall_keyboard_interfaces(context) } +/// Attempt to retrieve a *mut HidContext for the given controller by locating the simple text input interfaces +/// 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, + controller: efi::Handle, + driver_binding: &driver_binding::Protocol, ) -> Result<*mut HidContext, efi::Status> { - Err(efi::Status::UNSUPPORTED) + // 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 simple_text_in_ptr: *mut protocols::simple_text_input::Protocol = core::ptr::null_mut(); + let status = (boot_services.open_protocol)( + controller, + &protocols::simple_text_input::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + core::ptr::addr_of_mut!(simple_text_in_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: `this` must point to an instance of simple_text_in that is contained in a KeyboardContext struct. + // the following is the equivalent of the `CR` (contained record) macro in the EDK2 reference implementation. + let context_ptr = unsafe { (simple_text_in_ptr as *mut u8).sub(offset_of!(KeyboardContext, simple_text_in)) } + as *mut KeyboardContext; + return Ok(unsafe { (*context_ptr).hid_context }); + } + err => return Err(err), + } +} + +// Event handler function for the wait_for_key_event that is part of the simple text input interfaces. The same handler +// is used for both simple_text_in and simple_text_in_ex. +extern "efiapi" fn wait_for_key(event: efi::Event, context: *mut c_void) { + // retrieve a reference to the + let keyboard_context = unsafe { (context as *mut KeyboardContext).as_mut().expect("Invalid context pointer") }; + + // 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 old_tpl = (boot_services.raise_tpl)(system::TPL_NOTIFY); + + loop { + if let Some(key_data) = keyboard_context.handler.key_queue.peek_key() { + //skip partials + if key_data.key.unicode_char == 0 && key_data.key.scan_code == 0 { + // consume (and ignore) the partial stroke. + let _ = keyboard_context.handler.key_queue.pop_key(); + continue; + } + //live non-partial key at front of queue; so signal event. + (boot_services.signal_event)(event); + } + break; + } + + (boot_services.restore_tpl)(old_tpl); +} + +// Event callback function for handling registered key notifications. Iterates over the queue of keys to be notified, +// and invokes the registered callback function for each of those keys. +extern "efiapi" fn process_key_notifies(_event: efi::Event, context: *mut c_void) { + let keyboard_context = unsafe { (context as *mut KeyboardContext).as_mut().expect("Invalid context pointer") }; + + // 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") }; + + // loop through all the keys in the notification function queue and invoke the associated callback if found + loop { + //Safety: access to the key_queue needs to be at TPL_NOTIFY to ensure mutual exclusion, but this routine runs at + //TPL_CALLBACK. So raise TPL to TPL_NOTIFY for the queue pop and callback function search. + let old_tpl = (boot_services.raise_tpl)(system::TPL_NOTIFY); + + let (key, callback) = match keyboard_context.handler.key_queue.pop_notifiy_key() { + Some(key) => { + let mut result = (None, None); + for (registered_key, callback) in keyboard_context.handler.notification_callbacks.values() { + if OrdKeyData(key).matches_registered_key(registered_key) { + result = (Some(key), Some(callback)); + break; + } + } + result + } + None => (None, None), + }; + //restore TPL back to TPL_CALLBACK before actually invoking the callback. + (boot_services.restore_tpl)(old_tpl); + + //Invoke the callback, if found. + //Safety: this assumes that a caller doesn't "unregister" a callback and render the pointer invalid at TPL_NOTIFY + //between the search above that returns the callback pointer and the actual callback invocation here. This is a + //spec requirement (see UEFI spec 2.10 table 7.3). + if let (Some(mut key), Some(callback)) = (key, callback) { + let key_data_ptr = &mut key as *mut KeyData; + let _status = callback(key_data_ptr); + } else { + return; + } + } +} + +// resets the keyboard state - part of the simple_text_in protocol interface. +extern "efiapi" fn simple_text_in_reset( + this: *mut protocols::simple_text_input::Protocol, + _extended_verification: efi::Boolean, +) -> efi::Status { + if this.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + keyboard_context.handler.last_keys.clear(); + keyboard_context.handler.current_keys.clear(); + keyboard_context.handler.key_queue.reset(); + + efi::Status::SUCCESS +} + +// reads a key stroke - part of the simple_text_in protocol interface. +extern "efiapi" fn simple_text_in_read_key_stroke( + this: *mut protocols::simple_text_input::Protocol, + key: *mut protocols::simple_text_input::InputKey, +) -> efi::Status { + if this.is_null() || key.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + loop { + if let Some(mut key_data) = keyboard_context.handler.key_queue.pop_key() { + // skip partials + if key_data.key.unicode_char == 0 && key_data.key.scan_code == 0 { + continue; + } + //translate ctrl-alpha to corresponding control value. ctrl-a = 0x0001, ctrl-z = 0x001A + if (key_data.key_state.key_shift_state & (LEFT_SHIFT_PRESSED | RIGHT_SHIFT_PRESSED)) != 0 { + if key_data.key.unicode_char >= 0x0061 && key_data.key.unicode_char <= 0x007a { + //'a' to 'z' + key_data.key.unicode_char = (key_data.key.unicode_char - 0x0061) + 1; + } + if key_data.key.unicode_char >= 0x0041 && key_data.key.unicode_char <= 0x005a { + //'A' to 'Z' + key_data.key.unicode_char = (key_data.key.unicode_char - 0x0041) + 1; + } + } + unsafe { key.write(key_data.key) } + return efi::Status::SUCCESS; + } else { + return efi::Status::NOT_READY; + } + } +} + +// resets the keyboard state - part of the simple_text_in_ex protocol interface. +extern "efiapi" fn simple_text_in_ex_reset( + this: *mut protocols::simple_text_input_ex::Protocol, + _extended_verification: efi::Boolean, +) -> efi::Status { + if this.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in_ex)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + keyboard_context.handler.last_keys.clear(); + keyboard_context.handler.current_keys.clear(); + keyboard_context.handler.key_queue.reset(); + + efi::Status::SUCCESS +} + +// reads a key stroke - part of the simple_text_in_ex protocol interface. +extern "efiapi" fn simple_text_in_ex_read_key_stroke( + this: *mut protocols::simple_text_input_ex::Protocol, + key_data: *mut protocols::simple_text_input_ex::KeyData, +) -> efi::Status { + if this.is_null() || key_data.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in_ex)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + if let Some(key) = keyboard_context.handler.key_queue.pop_key() { + unsafe { key_data.write(key) }; + efi::Status::SUCCESS + } else { + let mut key: KeyData = Default::default(); + key.key_state = keyboard_context.handler.key_queue.init_key_state(); + unsafe { key_data.write(key) }; + efi::Status::NOT_READY + } +} + +// sets the keyboard state - part of the simple_text_in_ex protocol interface. +extern "efiapi" fn simple_text_in_ex_set_state( + this: *mut protocols::simple_text_input_ex::Protocol, + key_toggle_state: *mut protocols::simple_text_input_ex::KeyToggleState, +) -> efi::Status { + if this.is_null() || key_toggle_state.is_null() { + return efi::Status::INVALID_PARAMETER; + } + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in_ex)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + let key_toggle_state = unsafe { key_toggle_state.as_mut().expect("Invalid key toggle state pointer") }; + + keyboard_context.handler.key_queue.set_key_toggle_state(*key_toggle_state); + + keyboard_context.handler.generate_led_output_report(); + + efi::Status::SUCCESS +} + +// registers a key notification callback function - part of the simple_text_in_ex protocol interface. +extern "efiapi" fn simple_text_in_ex_register_key_notify( + this: *mut protocols::simple_text_input_ex::Protocol, + key_data_ptr: *mut protocols::simple_text_input_ex::KeyData, + key_notification_function: protocols::simple_text_input_ex::KeyNotifyFunction, + notify_handle: *mut *mut c_void, +) -> efi::Status { + //TODO: should check key_notification_function against NULL, but the RUST way to do that would be to declare it + //as Option - which would require a change to r_efi to do properly. + if this.is_null() || key_data_ptr.is_null() || notify_handle.is_null() { + return efi::Status::INVALID_PARAMETER; + } + + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in_ex)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + let key_data = OrdKeyData(*unsafe { key_data_ptr.as_mut().expect("Bad key_data_ptr") }); + + for (handle, entry) in &keyboard_context.handler.notification_callbacks { + if entry.0 == key_data && entry.1 == key_notification_function { + //if callback already exists, just return current handle + unsafe { notify_handle.write(*handle as *mut c_void) }; + return efi::Status::SUCCESS; + } + } + + // key_data/callback combo doesn't already exist; create a new registration for it. + keyboard_context.handler.next_notify_handle += 1; + + keyboard_context + .handler + .notification_callbacks + .insert(keyboard_context.handler.next_notify_handle, (key_data.clone(), key_notification_function)); + + keyboard_context.handler.key_queue.add_notify_key(key_data); + + efi::Status::SUCCESS +} + +// unregisters a key notification callback function - part of the simple_text_in_ex protocol interface. +extern "efiapi" fn simple_text_in_ex_unregister_key_notify( + this: *mut protocols::simple_text_input_ex::Protocol, + notification_handle: *mut c_void, +) -> efi::Status { + if this.is_null() || notification_handle as usize == 0 { + return efi::Status::INVALID_PARAMETER; + } + + // retrieve a reference to the keyboard context. + // Safety: `this` must point to an instance of simple_text)input that is contained in a KeyboardContext 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!(KeyboardContext, simple_text_in_ex)) } as *mut KeyboardContext; + + let keyboard_context = unsafe { context_ptr.as_mut().expect("Invalid context pointer") }; + + let handle = notification_handle as usize; + + let entry = keyboard_context.handler.notification_callbacks.remove(&handle); + let Some(entry) = entry else { + return efi::Status::INVALID_PARAMETER; + }; + + let other_handlers_exist = + keyboard_context.handler.notification_callbacks.values().any(|(key, _callback)| *key == entry.0); + + if !other_handlers_exist { + keyboard_context.handler.key_queue.remove_notify_key(&entry.0); + } + + efi::Status::SUCCESS +} + +// Initializes keyboard layout support. Creates an event to fire a callback when a new keyboard layout is installed +// into HII database, and then installs a default keyboard layout if one is not already present. +pub(crate) fn initialize_keyboard_layout(context_ptr: *mut KeyboardContext) -> 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") }; + + //create layout update event. + let mut layout_change_event: efi::Event = core::ptr::null_mut(); + let status = (boot_services.create_event_ex)( + system::EVT_NOTIFY_SIGNAL, + system::TPL_NOTIFY, + Some(on_layout_update), + context_ptr as *mut c_void, + &protocols::hii_database::SET_KEYBOARD_LAYOUT_EVENT_GUID, + core::ptr::addr_of_mut!(layout_change_event), + ); + if status.is_error() { + Err(status)?; + } + + //signal event to pick up any existing layout. + let status = (boot_services.signal_event)(layout_change_event); + if status.is_error() { + Err(status)?; + } + + //if no layout installed, install the default layout. + if !unsafe { (*context_ptr).handler.is_layout_installed() } { + install_default_layout(boot_services)?; + } + + Ok(()) +} + +// Installs a default keyboard layout into the HII database. +pub(crate) fn install_default_layout(boot_services: &mut system::BootServices) -> Result<(), efi::Status> { + let mut hii_database_protocol_ptr: *mut protocols::hii_database::Protocol = core::ptr::null_mut(); + + let status = (boot_services.locate_protocol)( + &protocols::hii_database::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + core::ptr::null_mut(), + core::ptr::addr_of_mut!(hii_database_protocol_ptr) as *mut *mut c_void, + ); + + if status.is_error() { + debugln!( + DEBUG_ERROR, + "keyboard::install_default_layout: Could not locate hii_database protocol to install keyboard layout: {:x?}", + status + ); + Err(status)?; + } + + let hii_database_protocol = + unsafe { hii_database_protocol_ptr.as_mut().expect("Bad pointer returned from successful locate protocol.") }; + + let mut hii_handle: hii::Handle = core::ptr::null_mut(); + let status = (hii_database_protocol.new_package_list)( + hii_database_protocol_ptr, + hii_keyboard_layout::get_default_keyboard_pkg_list_buffer().as_ptr() as *const hii::PackageListHeader, + core::ptr::null_mut(), + core::ptr::addr_of_mut!(hii_handle), + ); + + if status.is_error() { + debugln!(DEBUG_ERROR, "keyboard::install_default_layout: Failed to install keyboard layout package: {:x?}", status); + Err(status)?; + } + + let status = (hii_database_protocol.set_keyboard_layout)( + hii_database_protocol_ptr, + &hii_keyboard_layout::DEFAULT_KEYBOARD_LAYOUT_GUID as *const efi::Guid as *mut efi::Guid, + ); + + if status.is_error() { + debugln!(DEBUG_ERROR, "keyboard::install_default_layout: Failed to set keyboard layout: {:x?}", status); + Err(status)?; + } + + Ok(()) +} + +// Event callback that is fired on keyboard layout update event in HII. +extern "efiapi" fn on_layout_update(_event: efi::Event, context: *mut c_void) { + let keyboard_context = unsafe { (context as *mut KeyboardContext).as_mut().expect("Bad context pointer") }; + + // 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 hii_database_protocol_ptr: *mut protocols::hii_database::Protocol = core::ptr::null_mut(); + let status = (boot_services.locate_protocol)( + &protocols::hii_database::PROTOCOL_GUID as *const efi::Guid as *mut efi::Guid, + core::ptr::null_mut(), + core::ptr::addr_of_mut!(hii_database_protocol_ptr) as *mut *mut c_void, + ); + + if status.is_error() { + //nothing to do if there is no hii protocol. + return; + } + + let hii_database_protocol = + unsafe { hii_database_protocol_ptr.as_mut().expect("Bad pointer returned from successful locate protocol.") }; + + let mut keyboard_layout_buffer = vec![0u8; 4096]; + let mut layout_buffer_len: u16 = keyboard_layout_buffer.len() as u16; + + let status = (hii_database_protocol.get_keyboard_layout)( + hii_database_protocol_ptr, + core::ptr::null_mut(), + &mut layout_buffer_len as *mut u16, + keyboard_layout_buffer.as_mut_ptr() as *mut protocols::hii_database::KeyboardLayout<0>, + ); + + if status.is_error() { + return; + } + + let keyboard_layout = hii_keyboard_layout::keyboard_layout_from_buffer(&keyboard_layout_buffer); + match keyboard_layout { + Ok(keyboard_layout) => { + keyboard_context.handler.set_layout(Some(keyboard_layout)); + } + Err(_) => { + debugln!(DEBUG_WARN, "keyboard::on_layout_update: Could not parse keyboard layout buffer."); + return; + } + } } diff --git a/HidPkg/UefiHidDxe/src/main.rs b/HidPkg/UefiHidDxe/src/main.rs index 8ab6678efd..7d1eb2ee85 100644 --- a/HidPkg/UefiHidDxe/src/main.rs +++ b/HidPkg/UefiHidDxe/src/main.rs @@ -24,10 +24,12 @@ use rust_boot_services_allocator_dxe::GLOBAL_ALLOCATOR; mod driver_binding; mod hid; +mod key_queue; mod keyboard; mod pointer; static mut BOOT_SERVICES: *mut system::BootServices = core::ptr::null_mut(); +static mut RUNTIME_SERVICES: *mut system::RuntimeServices = core::ptr::null_mut(); #[no_mangle] pub extern "efiapi" fn efi_main(image_handle: efi::Handle, system_table: *const system::SystemTable) -> efi::Status { @@ -35,6 +37,7 @@ pub extern "efiapi" fn efi_main(image_handle: efi::Handle, system_table: *const // and because it mutates/accesses the global BOOT_SERVICES static. unsafe { BOOT_SERVICES = (*system_table).boot_services; + RUNTIME_SERVICES = (*system_table).runtime_services; GLOBAL_ALLOCATOR.init(BOOT_SERVICES); init_debug(BOOT_SERVICES); }