136 lines
4.9 KiB
Rust
136 lines
4.9 KiB
Rust
// server/src/usb_gadget.rs
|
||
use std::{fs::{self, OpenOptions}, io::Write, path::Path, thread, time::Duration};
|
||
use anyhow::{Context, Result};
|
||
use tracing::{info, warn, trace};
|
||
|
||
#[derive(Clone)]
|
||
pub struct UsbGadget {
|
||
udc_file: &'static str,
|
||
}
|
||
|
||
impl UsbGadget {
|
||
pub fn new(name: &'static str) -> Self {
|
||
Self {
|
||
udc_file: Box::leak(
|
||
format!("/sys/kernel/config/usb_gadget/{name}/UDC").into_boxed_str(),
|
||
),
|
||
}
|
||
}
|
||
|
||
/*–––– helpers ––––*/
|
||
|
||
/// 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()
|
||
.transpose()?
|
||
.context("no UDC present")?
|
||
.file_name()
|
||
.to_string_lossy()
|
||
.into_owned())
|
||
}
|
||
|
||
/// 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(());
|
||
}
|
||
thread::sleep(Duration::from_millis(50));
|
||
}
|
||
Err(anyhow::anyhow!("UDC never reached '{wanted}' (last = {:?})",
|
||
fs::read_to_string(&path).unwrap_or_default()))
|
||
}
|
||
|
||
/// Write `value` (plus “\n”) into a sysfs attribute
|
||
fn write_attr<P: AsRef<Path>>(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)?;
|
||
|
||
/* 4 – toggle gadget */
|
||
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 */
|
||
Self::wait_state(&ctrl, "configured", 6_000)
|
||
.or_else(|e| {
|
||
// If the host is physically absent (sleep / KVM paused)
|
||
// we allow 'not attached' and continue – we can still
|
||
// accept keyboard/mouse data and the host will enumerate
|
||
// later without another reset.
|
||
let last = fs::read_to_string(format!("/sys/class/udc/{ctrl}/state"))
|
||
.unwrap_or_default();
|
||
if last.trim() == "not attached" {
|
||
warn!("⚠️ host did not enumerate within 6 s – continuing (state = {last:?})");
|
||
Ok(())
|
||
} else {
|
||
Err(e)
|
||
}
|
||
})?;
|
||
|
||
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"))
|
||
}
|
||
}
|