From c806faba6fef523cd91e1eaa58bdf10bcc5c65b8 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Wed, 25 Jun 2025 21:11:59 -0500 Subject: [PATCH] server usb cycle update --- server/src/usb_gadget.rs | 111 ++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 65 deletions(-) diff --git a/server/src/usb_gadget.rs b/server/src/usb_gadget.rs index cbe32ae..5461b37 100644 --- a/server/src/usb_gadget.rs +++ b/server/src/usb_gadget.rs @@ -1,32 +1,7 @@ // 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"); -} +use anyhow::{Context, Result}; +use tracing::{info, warn}; #[derive(Clone)] pub struct UsbGadget { @@ -35,7 +10,6 @@ pub struct UsbGadget { 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(), @@ -43,52 +17,59 @@ impl UsbGadget { } } - /// Force the host to re-enumerate our HID gadget. - /// Retries on EBUSY so that a transient “resource busy” never kills the whole process. + /// Read the controller name currently written to `/UDC` + fn current_udc(&self) -> Result { + Ok(fs::read_to_string(self.udc_file)?.trim().to_owned()) + } + + /// Helper: busy‑loop (max 10s) until the UDC `state` file equals `wanted`. + fn wait_state(udc: &str, wanted: &str) -> Result<()> { + let state_file = format!("/sys/class/udc/{udc}/state"); + for _ in 0..100 { + let state = fs::read_to_string(&state_file) + .unwrap_or_default() + .trim() + .to_owned(); + if state == wanted { + return Ok(()); + } + thread::sleep(Duration::from_millis(100)); + } + Err(anyhow::anyhow!("🚧 UDC state did not reach '{wanted}' in time")) + .with_context(|| format!("waiting for {wanted} in {state_file}")) + } + + /// Force the host to re‑enumerate our HID gadget *safely*. 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(); + .file_name() + .to_string_lossy() + .into_owned(); - // 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() + /*–––– DETACH ––––*/ + if !self.current_udc()?.is_empty() { + info!("🔌 UDC‑cycle: detaching gadget"); + 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"); - } - } + .write_all(b"")?; + } else { + info!("🔌 Gadget already detached"); } + Self::wait_state(&udc_name, "not attached")?; - Err(anyhow::anyhow!( - "giving up after 5 attempts – UDC remained busy" - )) + /*–––– ATTACH ––––*/ + info!("🔌 UDC‑cycle: re‑attaching to {udc_name}"); + OpenOptions::new() + .write(true) + .open(self.udc_file)? + .write_all(udc_name.as_bytes())?; + + Self::wait_state(&udc_name, "configured")?; + info!("🔌 USB‑gadget cycled successfully 🎉"); + Ok(()) } }