diff --git a/server/src/usb_gadget.rs b/server/src/usb_gadget.rs index 7188838..228b55c 100644 --- a/server/src/usb_gadget.rs +++ b/server/src/usb_gadget.rs @@ -54,6 +54,17 @@ impl UsbGadget { Ok(()) } + // Wait (≤ `limit_ms`) until `/sys/class/udc/` exists again. + fn wait_udc_present(ctrl: &str, limit_ms: u64) -> Result<()> { + for _ in 0..=limit_ms / 50 { + if Path::new(&format!("/sys/class/udc/{ctrl}")).exists() { + return Ok(()); + } + thread::sleep(Duration::from_millis(50)); + } + Err(anyhow::anyhow!("⚠️ UDC {ctrl} did not re‑appear within {limit_ms} ms")) + } + /*–––– public API ––––*/ /// Hard‑reset the gadget → identical to a physical cable re‑plug @@ -80,19 +91,21 @@ impl UsbGadget { /* 2 – unbind / bind platform driver (dwc2/dwc3) */ Self::rebind_driver(&ctrl)?; + Self::wait_udc_present(&ctrl, 3_000) + .context("controller did not re‑appear after bind")?; /* 3 – re‑attach gadget */ info!("🔌 re‑attaching gadget to {ctrl}"); Self::write_attr(self.udc_file, &ctrl)?; - /* 4 – toggle gadget */ + /* 4 – assert pull‑up */ let sc = format!("/sys/class/udc/{ctrl}/soft_connect"); // toggle 0 → 1 to force the controller to assert pull‑ups Self::write_attr(&sc, "0")?; // guarantee clean edge thread::sleep(Duration::from_millis(50)); Self::write_attr(&sc, "1")?; - /* 4 – wait for gadget */ + /* 5 – wait for host (but tolerate sleep) */ Self::wait_state(&ctrl, "configured", 6_000) .or_else(|e| { // If the host is physically absent (sleep / KVM paused) @@ -123,11 +136,11 @@ impl UsbGadget { } info!("🔧 unbinding UDC driver ({drv})"); Self::write_attr(format!("{root}/unbind"), ctrl)?; - thread::sleep(Duration::from_millis(300)); + thread::sleep(Duration::from_millis(500)); info!("🔧 binding UDC driver ({drv})"); Self::write_attr(format!("{root}/bind"), ctrl)?; - thread::sleep(Duration::from_millis(100)); + thread::sleep(Duration::from_millis(500)); return Ok(()); } Err(anyhow::anyhow!("no dwc2/dwc3 driver nodes found"))