diff --git a/server/src/usb_gadget.rs b/server/src/usb_gadget.rs index 5461b37..56dee32 100644 --- a/server/src/usb_gadget.rs +++ b/server/src/usb_gadget.rs @@ -1,7 +1,7 @@ // server/src/usb_gadget.rs use std::{fs::{self, OpenOptions}, io::Write, thread, time::Duration}; use anyhow::{Context, Result}; -use tracing::{info, warn}; +use tracing::{info, warn, trace}; #[derive(Clone)] pub struct UsbGadget { @@ -17,59 +17,83 @@ impl UsbGadget { } } - /// 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<()> { - let udc_name = std::fs::read_dir("/sys/class/udc")? + /// Return the first UDC name we find, e.g. `1000480000.usb` + fn controller() -> Result { + Ok(std::fs::read_dir("/sys/class/udc")? .next() .transpose()? .context("no UDC present")? .file_name() .to_string_lossy() - .into_owned(); + .into_owned()) + } - /*–––– DETACH ––––*/ - if !self.current_udc()?.is_empty() { - info!("🔌 UDC‑cycle: detaching gadget"); - OpenOptions::new() - .write(true) - .open(self.udc_file)? - .write_all(b"")?; - } else { - info!("🔌 Gadget already detached"); + /// Read the controller name currently written to `/UDC` + fn current_udc(&self) -> Result { + Ok(fs::read_to_string(self.udc_file)?.trim().to_owned()) + } + + /// Poll `/sys/class/udc//state` until it equals `wanted`. + fn wait_state(name: &str, wanted: &str, limit_ms: u64) -> Result<()> { + let p = format!("/sys/class/udc/{name}/state"); + let tries = limit_ms / 50; + for attempt in 0..tries { + let state = fs::read_to_string(&p).unwrap_or_default(); + if state.trim() == wanted { + return Ok(()); + } + trace!("⏳ wait_state polling UDC, retry #{attempt}"); + thread::sleep(Duration::from_millis(50)); } - Self::wait_state(&udc_name, "not attached")?; + Err(anyhow::anyhow!("🚧 UDC never reached '{wanted}' (last state = {:?})", + fs::read_to_string(&p).unwrap_or_default())) + } - /*–––– ATTACH ––––*/ - info!("🔌 UDC‑cycle: re‑attaching to {udc_name}"); - OpenOptions::new() - .write(true) - .open(self.udc_file)? - .write_all(udc_name.as_bytes())?; + /// **Hard‑reset** the controller: + /// 1. detach gadget (`UDC=""`) + /// 2. un‑bind driver (`…/drivers//unbind`) + /// 3. bind again + /// 4. re‑attach gadget + /// 5. wait until state == `configured` + pub fn cycle(&self) -> Result<()> { + let ctrl = Self::controller()?; - Self::wait_state(&udc_name, "configured")?; - info!("🔌 USB‑gadget cycled successfully 🎉"); + /*----- 1. detach gadget from UDC --------------------------------*/ + info!("🔌 detaching gadget from controller"); + OpenOptions::new().write(true).open(self.udc_file)?.write_all(b"")?; + Self::wait_state(&ctrl, "not attached", 3_000) + .context("waiting for controller to report not‑attached")?; + + /*----- 2./3. unbind / bind the UDC driver -----------------------*/ + let drv_dir = "/sys/bus/platform/drivers/dwc2"; // rk35xx & raspi + let unbind = format!("{drv_dir}/unbind"); + let bind = format!("{drv_dir}/bind"); + + // Some kernels use dwc3 – try that if dwc2 is absent + let (unbind, bind) = if std::path::Path::new(&unbind).exists() { + (unbind, bind) + } else { + let d3 = "/sys/bus/platform/drivers/dwc3"; + (format!("{d3}/unbind"), format!("{d3}/bind")) + }; + + info!("🔧 unbinding UDC driver"); + OpenOptions::new().write(true).open(&unbind)?.write_all(ctrl.as_bytes())?; + thread::sleep(Duration::from_millis(300)); + + info!("🔧 binding UDC driver"); + OpenOptions::new().write(true).open(&bind)?.write_all(ctrl.as_bytes())?; + thread::sleep(Duration::from_millis(100)); + + /*----- 4. re‑attach gadget to controller -------------------------*/ + info!("🔌 re‑attaching gadget to {ctrl}"); + OpenOptions::new().write(true).open(self.udc_file)?.write_all(ctrl.as_bytes())?; + + /*----- 5. wait until host finished enumeration -------------------*/ + Self::wait_state(&ctrl, "configured", 6_000) + .context("waiting for host to enumerate gadget")?; + + info!("✅ USB gadget cycle complete; controller back to 'configured'"); Ok(()) } }