From 0d7e401addba81349249c9eeb466e85e085baebf Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Wed, 25 Jun 2025 22:24:58 -0500 Subject: [PATCH] server usb cycle update --- scripts/install-server.sh | 2 +- server/src/main.rs | 2 +- server/src/usb_gadget.rs | 141 +++++++++++++++++++++----------------- 3 files changed, 79 insertions(+), 66 deletions(-) diff --git a/scripts/install-server.sh b/scripts/install-server.sh index dcfce63..7782095 100755 --- a/scripts/install-server.sh +++ b/scripts/install-server.sh @@ -107,7 +107,7 @@ Restart=always Environment=RUST_LOG=lesavka_server=debug,lesavka_server::usb_gadget=trace Environment=RUST_BACKTRACE=1 Restart=always -RestartSec=3 +RestartSec=5 StandardError=append:/tmp/lesavka-server.log StartLimitIntervalSec=30 StartLimitBurst=10 diff --git a/server/src/main.rs b/server/src/main.rs index 733afcc..a4020ee 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -63,7 +63,7 @@ async fn open_with_retry(path: &str) -> anyhow::Result { info!("✅ {path} opened on attempt #{attempt}"); return Ok(f); } - Err(e) if e.kind() == std::io::ErrorKind::Other /* EBUSY */ => { + Err(e) if e.raw_os_error() == Some(libc::EBUSY) => { trace!("⏳ {path} busy, retry #{attempt}"); tokio::time::sleep(Duration::from_millis(50)).await; } diff --git a/server/src/usb_gadget.rs b/server/src/usb_gadget.rs index 56dee32..dec0dd9 100644 --- a/server/src/usb_gadget.rs +++ b/server/src/usb_gadget.rs @@ -1,5 +1,5 @@ // server/src/usb_gadget.rs -use std::{fs::{self, OpenOptions}, io::Write, thread, time::Duration}; +use std::{fs::{self, OpenOptions}, io::Write, path::Path, thread, time::Duration}; use anyhow::{Context, Result}; use tracing::{info, warn, trace}; @@ -17,9 +17,11 @@ impl UsbGadget { } } - /// Return the first UDC name we find, e.g. `1000480000.usb` - fn controller() -> Result { - Ok(std::fs::read_dir("/sys/class/udc")? + /*–––– helpers ––––*/ + + /// Find the first controller in /sys/class/udc (e.g. `1000480000.usb`) + fn find_controller() -> Result { + Ok(fs::read_dir("/sys/class/udc")? .next() .transpose()? .context("no UDC present")? @@ -28,72 +30,83 @@ impl UsbGadget { .into_owned()) } - /// 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 { + /// Busy‑loop (≤ `limit_ms`) until `state` matches `wanted` + fn wait_state(ctrl: &str, wanted: &str, limit_ms: u64) -> Result<()> { + let path = format!("/sys/class/udc/{ctrl}/state"); + for _ in 0..=limit_ms / 50 { + let s = fs::read_to_string(&path).unwrap_or_default(); + trace!("⏳ state={s:?}, want={wanted}"); + if s.trim() == wanted { return Ok(()); } - trace!("⏳ wait_state polling UDC, retry #{attempt}"); thread::sleep(Duration::from_millis(50)); } - Err(anyhow::anyhow!("🚧 UDC never reached '{wanted}' (last state = {:?})", - fs::read_to_string(&p).unwrap_or_default())) + Err(anyhow::anyhow!("UDC never reached '{wanted}' (last = {:?})", + fs::read_to_string(&path).unwrap_or_default())) } - /// **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()?; - - /*----- 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'"); + /// Write `value` (plus “\n”) into a sysfs attribute + fn write_attr>(p: P, value: &str) -> Result<()> { + OpenOptions::new() + .write(true) + .open(p)? + .write_all(format!("{value}\n").as_bytes())?; Ok(()) } + + /*–––– public API ––––*/ + + /// Hard‑reset the gadget → identical to a physical cable re‑plug + pub fn cycle(&self) -> Result<()> { + /* 0 – ensure we *know* the controller even after a previous crash */ + let ctrl = match Self::find_controller() { + Ok(c) => c, + Err(_) => { + // try to recover by reading the last name from the gadget’s UDC file + let last = fs::read_to_string(self.udc_file)?.trim().to_owned(); + if last.is_empty() { + return Err(anyhow::anyhow!("no UDC present and UDC file is empty")); + } + warn!("⚠️ UDC missing, attempting to re‑bind {last}"); + Self::rebind_driver(&last)?; + last + } + }; + + /* 1 – detach gadget */ + info!("🔌 detaching gadget from {ctrl}"); + Self::write_attr(self.udc_file, "")?; + Self::wait_state(&ctrl, "not attached", 3_000)?; + + /* 2 – unbind / bind platform driver (dwc2/dwc3) */ + Self::rebind_driver(&ctrl)?; + + /* 3 – re‑attach gadget */ + info!("🔌 re‑attaching gadget to {ctrl}"); + Self::write_attr(self.udc_file, &ctrl)?; + Self::wait_state(&ctrl, "configured", 6_000)?; + + info!("✅ USB‑gadget cycle complete"); + Ok(()) + } + + /// helper: unbind + 300 ms reset + bind + fn rebind_driver(ctrl: &str) -> Result<()> { + let cand = ["dwc2", "dwc3"]; // cover RasPi, RK, AM62, … + for drv in cand { + let root = format!("/sys/bus/platform/drivers/{drv}"); + if !Path::new(&root).exists() { + continue; + } + info!("🔧 unbinding UDC driver ({drv})"); + Self::write_attr(format!("{root}/unbind"), ctrl)?; + thread::sleep(Duration::from_millis(300)); + + info!("🔧 binding UDC driver ({drv})"); + Self::write_attr(format!("{root}/bind"), ctrl)?; + thread::sleep(Duration::from_millis(100)); + return Ok(()); + } + Err(anyhow::anyhow!("no dwc2/dwc3 driver nodes found")) + } }