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