// server/src/usb_gadget.rs use std::{fs::{self, OpenOptions}, io::Write, thread, time::Duration}; use anyhow::{Result, Context}; use tracing::{debug, error, info, warn}; fn wait_for_detach(udc_path: &str) { for _ in 0..20 { // as soon as the file is **empty**, the detach succeeded if fs::read_to_string(udc_path).map(|s| s.trim().is_empty()).unwrap_or(false) { debug!("πŸ”Œ UDC is now *detached*"); return; } thread::sleep(Duration::from_millis(50)); } warn!("⏳ UDC did not detach within the expected time-out"); } /// Like `wait_for_detach`, but the opposite condition. fn wait_for_attach(udc_path: &str) { for _ in 0..20 { if fs::read_to_string(udc_path).map(|s| !s.trim().is_empty()).unwrap_or(false) { debug!("πŸ”Œ UDC is *attached* again"); return; } thread::sleep(Duration::from_millis(50)); } warn!("⏳ UDC did not attach within the expected time-out"); } #[derive(Clone)] pub struct UsbGadget { udc_file: &'static str, } impl UsbGadget { pub fn new(name: &'static str) -> Self { // /sys/kernel/config/usb_gadget//UDC Self { udc_file: Box::leak( format!("/sys/kernel/config/usb_gadget/{name}/UDC").into_boxed_str(), ), } } /// Force the host to re-enumerate our HID gadget. /// Retries on EBUSY so that a transient β€œresource busy” never kills the whole process. pub fn cycle(&self) -> Result<()> { info!("πŸ”Œ UDC-cycle: detaching gadget"); OpenOptions::new() .write(true) .open(self.udc_file)? .write_all(b"")?; // empty string β†’ detach wait_for_detach(self.udc_file); // Let the controller settle thread::sleep(Duration::from_millis(200)); let udc_name = std::fs::read_dir("/sys/class/udc")? .next() .transpose()? .context("no UDC present")? .file_name(); // Retry loop for the notoriously fragile re-attach for attempt in 0..5 { info!("πŸ”Œ UDC-cycle: re-attaching to {} (try #{attempt})", udc_name.to_string_lossy()); match OpenOptions::new() .write(true) .open(self.udc_file)? .write_all(udc_name.to_str().unwrap().as_bytes()) { Ok(()) => { wait_for_attach(self.udc_file); info!("🟒 USB-gadget cycled successfully"); return Ok(()); } Err(e) if e.raw_os_error() == Some(libc::EBUSY) => { warn!("🚧 UDC busy on re-attach – retrying in 100 ms"); thread::sleep(Duration::from_millis(100)); continue; } Err(e) => { error!("πŸ’₯ unexpected error while re-attaching UDC: {e:?}"); return Err(e).context("re-attaching gadget to UDC"); } } } Err(anyhow::anyhow!( "giving up after 5 attempts – UDC remained busy" )) } }