server usb cycle update

This commit is contained in:
Brad Stein 2025-06-25 22:24:58 -05:00
parent ecb8775489
commit 0d7e401add
3 changed files with 79 additions and 66 deletions

View File

@ -107,7 +107,7 @@ Restart=always
Environment=RUST_LOG=lesavka_server=debug,lesavka_server::usb_gadget=trace Environment=RUST_LOG=lesavka_server=debug,lesavka_server::usb_gadget=trace
Environment=RUST_BACKTRACE=1 Environment=RUST_BACKTRACE=1
Restart=always Restart=always
RestartSec=3 RestartSec=5
StandardError=append:/tmp/lesavka-server.log StandardError=append:/tmp/lesavka-server.log
StartLimitIntervalSec=30 StartLimitIntervalSec=30
StartLimitBurst=10 StartLimitBurst=10

View File

@ -63,7 +63,7 @@ async fn open_with_retry(path: &str) -> anyhow::Result<tokio::fs::File> {
info!("✅ {path} opened on attempt #{attempt}"); info!("✅ {path} opened on attempt #{attempt}");
return Ok(f); 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}"); trace!("⏳ {path} busy, retry #{attempt}");
tokio::time::sleep(Duration::from_millis(50)).await; tokio::time::sleep(Duration::from_millis(50)).await;
} }

View File

@ -1,5 +1,5 @@
// server/src/usb_gadget.rs // 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 anyhow::{Context, Result};
use tracing::{info, warn, trace}; use tracing::{info, warn, trace};
@ -17,9 +17,11 @@ impl UsbGadget {
} }
} }
/// Return the first UDC name we find, e.g. `1000480000.usb` /* helpers */
fn controller() -> Result<String> {
Ok(std::fs::read_dir("/sys/class/udc")? /// Find the first controller in /sys/class/udc (e.g. `1000480000.usb`)
fn find_controller() -> Result<String> {
Ok(fs::read_dir("/sys/class/udc")?
.next() .next()
.transpose()? .transpose()?
.context("no UDC present")? .context("no UDC present")?
@ -28,72 +30,83 @@ impl UsbGadget {
.into_owned()) .into_owned())
} }
/// Read the controller name currently written to `<gadget>/UDC` /// Busyloop (≤ `limit_ms`) until `state` matches `wanted`
fn current_udc(&self) -> Result<String> { fn wait_state(ctrl: &str, wanted: &str, limit_ms: u64) -> Result<()> {
Ok(fs::read_to_string(self.udc_file)?.trim().to_owned()) let path = format!("/sys/class/udc/{ctrl}/state");
} for _ in 0..=limit_ms / 50 {
let s = fs::read_to_string(&path).unwrap_or_default();
/// Poll `/sys/class/udc/<name>/state` until it equals `wanted`. trace!("⏳ state={s:?}, want={wanted}");
fn wait_state(name: &str, wanted: &str, limit_ms: u64) -> Result<()> { if s.trim() == wanted {
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(()); return Ok(());
} }
trace!("⏳ wait_state polling UDC, retry #{attempt}");
thread::sleep(Duration::from_millis(50)); thread::sleep(Duration::from_millis(50));
} }
Err(anyhow::anyhow!("🚧 UDC never reached '{wanted}' (last state = {:?})", Err(anyhow::anyhow!("UDC never reached '{wanted}' (last = {:?})",
fs::read_to_string(&p).unwrap_or_default())) fs::read_to_string(&path).unwrap_or_default()))
} }
/// **Hardreset** the controller: /// Write `value` (plus “\n”) into a sysfs attribute
/// 1. detach gadget (`UDC=""`) fn write_attr<P: AsRef<Path>>(p: P, value: &str) -> Result<()> {
/// 2. unbind driver (`…/drivers/<udc>/unbind`) OpenOptions::new()
/// 3. bind again .write(true)
/// 4. reattach gadget .open(p)?
/// 5. wait until state == `configured` .write_all(format!("{value}\n").as_bytes())?;
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 notattached")?;
/*----- 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. reattach gadget to controller -------------------------*/
info!("🔌 reattaching 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(()) Ok(())
} }
/* public API */
/// Hardreset the gadget → identical to a physical cable replug
pub fn cycle(&self) -> Result<()> {
/* 0ensure 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 gadgets 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 rebind {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 reattach gadget */
info!("🔌 reattaching gadget to {ctrl}");
Self::write_attr(self.udc_file, &ctrl)?;
Self::wait_state(&ctrl, "configured", 6_000)?;
info!("✅ USBgadget cycle complete");
Ok(())
}
/// helper: unbind + 300ms 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"))
}
} }