server usb cycle update
This commit is contained in:
parent
ecb8775489
commit
0d7e401add
@ -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
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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`
|
/// Busy‑loop (≤ `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()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// **Hard‑reset** 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. un‑bind driver (`…/drivers/<udc>/unbind`)
|
OpenOptions::new()
|
||||||
/// 3. bind again
|
.write(true)
|
||||||
/// 4. re‑attach 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 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(())
|
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"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user