server usb cycle update

This commit is contained in:
Brad Stein 2025-06-25 21:52:52 -05:00
parent d4261c8383
commit ecb8775489

View File

@ -1,7 +1,7 @@
// 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, thread, time::Duration};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use tracing::{info, warn}; use tracing::{info, warn, trace};
#[derive(Clone)] #[derive(Clone)]
pub struct UsbGadget { pub struct UsbGadget {
@ -17,59 +17,83 @@ impl UsbGadget {
} }
} }
/// Read the controller name currently written to `<gadget>/UDC` /// Return the first UDC name we find, e.g. `1000480000.usb`
fn current_udc(&self) -> Result<String> { fn controller() -> Result<String> {
Ok(fs::read_to_string(self.udc_file)?.trim().to_owned()) Ok(std::fs::read_dir("/sys/class/udc")?
}
/// Helper: busyloop (max 10s) until the UDC `state` file equals `wanted`.
fn wait_state(udc: &str, wanted: &str) -> Result<()> {
let state_file = format!("/sys/class/udc/{udc}/state");
for _ in 0..100 {
let state = fs::read_to_string(&state_file)
.unwrap_or_default()
.trim()
.to_owned();
if state == wanted {
return Ok(());
}
thread::sleep(Duration::from_millis(100));
}
Err(anyhow::anyhow!("🚧 UDC state did not reach '{wanted}' in time"))
.with_context(|| format!("waiting for {wanted} in {state_file}"))
}
/// Force the host to reenumerate our HID gadget *safely*.
pub fn cycle(&self) -> Result<()> {
let udc_name = std::fs::read_dir("/sys/class/udc")?
.next() .next()
.transpose()? .transpose()?
.context("no UDC present")? .context("no UDC present")?
.file_name() .file_name()
.to_string_lossy() .to_string_lossy()
.into_owned(); .into_owned())
}
/* DETACH */ /// Read the controller name currently written to `<gadget>/UDC`
if !self.current_udc()?.is_empty() { fn current_udc(&self) -> Result<String> {
info!("🔌 UDCcycle: detaching gadget"); Ok(fs::read_to_string(self.udc_file)?.trim().to_owned())
OpenOptions::new() }
.write(true)
.open(self.udc_file)? /// Poll `/sys/class/udc/<name>/state` until it equals `wanted`.
.write_all(b"")?; fn wait_state(name: &str, wanted: &str, limit_ms: u64) -> Result<()> {
} else { let p = format!("/sys/class/udc/{name}/state");
info!("🔌 Gadget already detached"); 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(());
}
trace!("⏳ wait_state polling UDC, retry #{attempt}");
thread::sleep(Duration::from_millis(50));
} }
Self::wait_state(&udc_name, "not attached")?; Err(anyhow::anyhow!("🚧 UDC never reached '{wanted}' (last state = {:?})",
fs::read_to_string(&p).unwrap_or_default()))
}
/* ATTACH */ /// **Hardreset** the controller:
info!("🔌 UDCcycle: reattaching to {udc_name}"); /// 1. detach gadget (`UDC=""`)
OpenOptions::new() /// 2. unbind driver (`…/drivers/<udc>/unbind`)
.write(true) /// 3. bind again
.open(self.udc_file)? /// 4. reattach gadget
.write_all(udc_name.as_bytes())?; /// 5. wait until state == `configured`
pub fn cycle(&self) -> Result<()> {
let ctrl = Self::controller()?;
Self::wait_state(&udc_name, "configured")?; /*----- 1. detach gadget from UDC --------------------------------*/
info!("🔌 USBgadget cycled successfully 🎉"); 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(())
} }
} }