//! HID Function implementation for USB Gadget use std::path::{Path, PathBuf}; use tracing::debug; use super::configfs::{create_dir, create_symlink, remove_dir, remove_file, write_bytes, write_file}; use super::function::{FunctionMeta, GadgetFunction}; use super::report_desc::{KEYBOARD_WITH_LED, MOUSE_ABSOLUTE, MOUSE_RELATIVE}; use crate::error::Result; /// HID function type #[derive(Debug, Clone)] pub enum HidFunctionType { /// Keyboard with LED feedback support /// Uses 2 endpoints: IN (reports) + OUT (LED status) Keyboard, /// Relative mouse (traditional mouse movement) /// Uses 1 endpoint: IN MouseRelative, /// Absolute mouse (touchscreen-like positioning) /// Uses 1 endpoint: IN MouseAbsolute, } impl HidFunctionType { /// Get endpoints required for this function type pub fn endpoints(&self) -> u8 { match self { HidFunctionType::Keyboard => 2, // IN + OUT for LED HidFunctionType::MouseRelative => 1, HidFunctionType::MouseAbsolute => 1, } } /// Get HID protocol pub fn protocol(&self) -> u8 { match self { HidFunctionType::Keyboard => 1, // Keyboard HidFunctionType::MouseRelative => 2, // Mouse HidFunctionType::MouseAbsolute => 2, // Mouse } } /// Get HID subclass pub fn subclass(&self) -> u8 { match self { HidFunctionType::Keyboard => 1, // Boot interface HidFunctionType::MouseRelative => 1, // Boot interface HidFunctionType::MouseAbsolute => 0, // No boot interface (absolute not in boot protocol) } } /// Get report length in bytes pub fn report_length(&self) -> u8 { match self { HidFunctionType::Keyboard => 8, HidFunctionType::MouseRelative => 4, HidFunctionType::MouseAbsolute => 6, } } /// Get report descriptor pub fn report_desc(&self) -> &'static [u8] { match self { HidFunctionType::Keyboard => KEYBOARD_WITH_LED, HidFunctionType::MouseRelative => MOUSE_RELATIVE, HidFunctionType::MouseAbsolute => MOUSE_ABSOLUTE, } } /// Get description pub fn description(&self) -> &'static str { match self { HidFunctionType::Keyboard => "Keyboard", HidFunctionType::MouseRelative => "Relative Mouse", HidFunctionType::MouseAbsolute => "Absolute Mouse", } } } /// HID Function for USB Gadget #[derive(Debug, Clone)] pub struct HidFunction { /// Instance number (usb0, usb1, ...) instance: u8, /// Function type func_type: HidFunctionType, /// Cached function name (avoids repeated allocation) name: String, } impl HidFunction { /// Create a keyboard function pub fn keyboard(instance: u8) -> Self { Self { instance, func_type: HidFunctionType::Keyboard, name: format!("hid.usb{}", instance), } } /// Create a relative mouse function pub fn mouse_relative(instance: u8) -> Self { Self { instance, func_type: HidFunctionType::MouseRelative, name: format!("hid.usb{}", instance), } } /// Create an absolute mouse function pub fn mouse_absolute(instance: u8) -> Self { Self { instance, func_type: HidFunctionType::MouseAbsolute, name: format!("hid.usb{}", instance), } } /// Get function path in gadget fn function_path(&self, gadget_path: &Path) -> PathBuf { gadget_path.join("functions").join(self.name()) } /// Get expected device path (e.g., /dev/hidg0) pub fn device_path(&self) -> PathBuf { PathBuf::from(format!("/dev/hidg{}", self.instance)) } } impl GadgetFunction for HidFunction { fn name(&self) -> &str { &self.name } fn endpoints_required(&self) -> u8 { self.func_type.endpoints() } fn meta(&self) -> FunctionMeta { FunctionMeta { name: self.name().to_string(), description: self.func_type.description().to_string(), endpoints: self.endpoints_required(), enabled: true, } } fn create(&self, gadget_path: &Path) -> Result<()> { let func_path = self.function_path(gadget_path); create_dir(&func_path)?; // Set HID parameters write_file(&func_path.join("protocol"), &self.func_type.protocol().to_string())?; write_file(&func_path.join("subclass"), &self.func_type.subclass().to_string())?; write_file(&func_path.join("report_length"), &self.func_type.report_length().to_string())?; // For keyboard, enable OUT endpoint for LED feedback // no_out_endpoint: 0 = enable OUT endpoint, 1 = disable if matches!(self.func_type, HidFunctionType::Keyboard) { let no_out_path = func_path.join("no_out_endpoint"); if no_out_path.exists() || func_path.exists() { // Try to write, ignore error if file doesn't exist yet let _ = write_file(&no_out_path, "0"); } } // Write report descriptor write_bytes(&func_path.join("report_desc"), self.func_type.report_desc())?; debug!("Created HID function: {} at {}", self.name(), func_path.display()); Ok(()) } fn link(&self, config_path: &Path, gadget_path: &Path) -> Result<()> { let func_path = self.function_path(gadget_path); let link_path = config_path.join(self.name()); if !link_path.exists() { create_symlink(&func_path, &link_path)?; debug!("Linked HID function {} to config", self.name()); } Ok(()) } fn unlink(&self, config_path: &Path) -> Result<()> { let link_path = config_path.join(self.name()); remove_file(&link_path)?; debug!("Unlinked HID function {}", self.name()); Ok(()) } fn cleanup(&self, gadget_path: &Path) -> Result<()> { let func_path = self.function_path(gadget_path); remove_dir(&func_path)?; debug!("Cleaned up HID function {}", self.name()); Ok(()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_hid_function_types() { assert_eq!(HidFunctionType::Keyboard.endpoints(), 2); assert_eq!(HidFunctionType::MouseRelative.endpoints(), 1); assert_eq!(HidFunctionType::MouseAbsolute.endpoints(), 1); assert_eq!(HidFunctionType::Keyboard.report_length(), 8); assert_eq!(HidFunctionType::MouseRelative.report_length(), 4); assert_eq!(HidFunctionType::MouseAbsolute.report_length(), 6); } #[test] fn test_hid_function_names() { let kb = HidFunction::keyboard(0); assert_eq!(kb.name(), "hid.usb0"); assert_eq!(kb.device_path(), PathBuf::from("/dev/hidg0")); let mouse = HidFunction::mouse_relative(1); assert_eq!(mouse.name(), "hid.usb1"); } }