uefi_input2/hotplug.rs
1// Copyright (c) Bemly, January 2026
2// You may copy and distribute this file freely. Any queries and
3// complaints should be forwarded to bemly_@petalmail.com.
4// If you make any changes to this file, please do not distribute
5// the results under the name `bemly'.
6
7use alloc::vec::Vec;
8use core::ffi::c_void;
9use core::hint::spin_loop;
10use core::ptr::NonNull;
11use uefi::boot::{check_event, create_event, locate_handle_buffer, open_protocol_exclusive, register_protocol_notify, set_timer, ScopedProtocol, SearchType, TimerTrigger};
12use uefi::{Event, Identify, Result};
13use uefi_raw::table::boot::{EventType, Tpl};
14use crate::config::REFRESH_POSITIVE_INPUT_DEVICE_TIME;
15use crate::input::Input;
16
17/// input event notification wrapper
18#[allow(unused)]
19pub struct KeyboardHotPlugMonitor {
20 event: Event,
21 key: SearchType<'static>,
22}
23
24extern "efiapi" fn on_keyboard_hotplug(_event: Event, ctx: Option<NonNull<c_void>>) {
25 if let Some(ctx_ptr) = ctx {
26 let flag = ctx_ptr.as_ptr() as *mut bool;
27 unsafe { *flag = true; }
28 }
29}
30
31/// Warn: Unpredictable! Unsafe!
32/// This may cause the program to hang.
33impl KeyboardHotPlugMonitor {
34 /// enable global keyboard hotplug listener
35 /// dirty_flag_ptr: recommend static bool pointer
36 pub unsafe fn new(dirty_flag_ptr: NonNull<bool>) -> Result<Self> {
37 let ctx = dirty_flag_ptr.cast::<c_void>();
38
39 let event = unsafe {
40 create_event(
41 EventType::NOTIFY_SIGNAL,
42 Tpl::CALLBACK,
43 Some(on_keyboard_hotplug),
44 Some(ctx),
45 )?
46 };
47
48 let key = register_protocol_notify(
49 &Input::GUID,
50 &event,
51 )?;
52
53 Ok(Self { event, key })
54 }
55
56 /// Clears the keyboard list, effectively dropping all `ScopedProtocol` instances.
57 ///
58 /// This triggers the RAII cleanup logic: each `ScopedProtocol` will automatically
59 /// call the underlying UEFI `close_protocol` service upon being dropped. This is
60 /// essential to release exclusive hardware locks before a re-scan.
61 fn refresh_negative(existing_keyboards: &mut Vec<ScopedProtocol<Input>>) {
62 // Brute-force cleanup: Discard all ScopedProtocols without hesitation.
63 existing_keyboards.clear();
64
65 let Ok(handles) = locate_handle_buffer(SearchType::ByProtocol(&Input::GUID)) else { return; };
66
67 for handle in handles.iter() {
68 if let Ok(keyboard) = open_protocol_exclusive::<Input>(*handle) {
69 existing_keyboards.push(keyboard);
70 }
71 }
72 }
73
74 /// Scan for all handles
75 fn refresh_positive(existing_keyboards: &mut Vec<ScopedProtocol<Input>>) {
76 existing_keyboards.clear();
77
78 let Ok(handles) = locate_handle_buffer(SearchType::AllHandles) else { return; };
79
80 for handle in handles.iter() {
81 // Some handles are system ConIn, and some are physical USB; try them all.
82 if let Ok(keyboard) = open_protocol_exclusive::<Input>(*handle) {
83 existing_keyboards.push(keyboard);
84 }
85 }
86 }
87
88 /// support keyboard hotplug (notify + polling flag mode)
89 ///
90 /// #### param
91 /// - `init`: start register input callback function
92 /// - `update`: update keyboard callback function
93 /// - `tick`: main loop callback function
94 ///
95 /// #### Usage
96 /// ```rust,no_run
97 /// fn f(index: usize, input: &mut ScopedProtocol<Input>) -> Result { Ok(()) }
98 /// KeyboardHotPlugMonitor::notify_mode_loop(f, f, f)?;
99 /// ```
100 /// More see examples/test_input_hotplug.rs
101 pub unsafe fn notify_mode_loop<Init, Update, Loop, Res>
102 (mut init: Init, mut update: Update, mut tick: Loop) -> Result<Res>
103 where
104 Init: FnMut(usize, &mut ScopedProtocol<Input>) -> Result<Res>,
105 Update: FnMut(usize, &mut ScopedProtocol<Input>) -> Result<Res>,
106 Loop: FnMut(usize, &mut ScopedProtocol<Input>) -> Result<Res>,
107 {
108 let mut keyboard_dirty = false;
109 let mut keyboards: Vec<ScopedProtocol<Input>> = Vec::new();
110
111 // during initialization, scan the existing keyboard first.
112 Self::refresh_negative(&mut keyboards);
113
114 // start hot-plug monitoring
115 let flag_ptr = NonNull::from(&mut keyboard_dirty);
116
117 macro_rules! f {
118 ($f:ident) => {
119 for (i, k) in keyboards.iter_mut().enumerate() { $f(i, k)?; }
120 };
121 }
122
123 unsafe {
124 // `_monitor` lifetime cover the unsafe block
125 let _monitor = KeyboardHotPlugMonitor::new(flag_ptr)?;
126
127 // User init operation
128 f!(init);
129
130 loop {
131 spin_loop();
132
133 if keyboard_dirty {
134 // reset immediately to prevent any new (un)plugs
135 // from being missed during processing
136 keyboard_dirty = false;
137 Self::refresh_negative(&mut keyboards);
138
139 // User update operation
140 f!(update);
141 }
142
143 // User main loop operation
144 f!(tick);
145 }
146 }
147 }
148
149 /// support keyboard hotplug (pure polling mode)
150 ///
151 /// warn: Poor performance! frequent opening and closing of protocols.
152 ///
153 /// #### param
154 /// - `init`: start register input callback function
155 /// - `update`: update keyboard callback function
156 /// - `tick`: main loop callback function
157 ///
158 /// #### Usage
159 /// ```rust,no_run
160 /// fn f(index: usize, input: &mut ScopedProtocol<Input>) -> Result { Ok(()) }
161 /// KeyboardHotPlugMonitor::polling_mode_loop(f, f, f)?;
162 /// ```
163 /// More see examples/test_polling_hotplug.rs
164 ///
165 /// #### config
166 /// [`REFRESH_POSITIVE_INPUT_DEVICE_TIME::set`] configuration can be modified
167 /// before this [`KeyboardHotPlugMonitor::polling_mode_loop`] is used.
168 pub unsafe fn polling_mode_loop<Init, Update, Loop, Res>
169 (mut init: Init, mut update: Update, mut tick: Loop) -> Result<Res>
170 where
171 Init: FnMut(usize, &mut ScopedProtocol<Input>) -> Result<Res>,
172 Update: FnMut(usize, &mut ScopedProtocol<Input>) -> Result<Res>,
173 Loop: FnMut(usize, &mut ScopedProtocol<Input>) -> Result<Res>,
174 {
175 let mut keyboards: Vec<ScopedProtocol<Input>> = Vec::new();
176 // Initial device discovery
177 Self::refresh_positive(&mut keyboards);
178
179 // --- Timer Setup ---
180 // Create a timer event. In UEFI, timer events are used to signal intervals.
181 let timer_event = unsafe {
182 create_event(
183 EventType::TIMER,
184 Tpl::CALLBACK,
185 None, // No callback function needed, we will poll it
186 None,
187 )?
188 };
189
190 set_timer(
191 &timer_event,
192 TimerTrigger::Periodic(REFRESH_POSITIVE_INPUT_DEVICE_TIME::get()),
193 )?;
194
195 macro_rules! f {
196 ($f:ident) => {
197 for (i, k) in keyboards.iter_mut().enumerate() { $f(i, k)?; }
198 };
199 }
200
201 // Run initial user-defined init
202 f!(init);
203 loop {
204 // Perform the main loop tick for every keyboard
205 f!(tick);
206 spin_loop();
207
208 // Check if the timer has fired (non-blocking)
209 // check_event returns Ok(()) if signaled, or an Error if not yet signaled.
210 if let Ok(true) = unsafe { check_event(timer_event.unsafe_clone()) } {
211 Self::refresh_positive(&mut keyboards);
212 // Run initial user-defined update
213 f!(update);
214 }
215 }
216 }
217}
218