server usb cycle update

This commit is contained in:
Brad Stein 2025-06-25 21:11:59 -05:00
parent 5cf25de966
commit c806faba6f

View File

@ -1,32 +1,7 @@
// server/src/usb_gadget.rs
use std::{fs::{self, OpenOptions}, io::Write, thread, time::Duration};
use anyhow::{Result, Context};
use tracing::{debug, error, info, warn};
fn wait_for_detach(udc_path: &str) {
for _ in 0..20 {
// as soon as the file is **empty**, the detach succeeded
if fs::read_to_string(udc_path).map(|s| s.trim().is_empty()).unwrap_or(false) {
debug!("🔌 UDC is now *detached*");
return;
}
thread::sleep(Duration::from_millis(50));
}
warn!("⏳ UDC did not detach within the expected time-out");
}
/// Like `wait_for_detach`, but the opposite condition.
fn wait_for_attach(udc_path: &str) {
for _ in 0..20 {
if fs::read_to_string(udc_path).map(|s| !s.trim().is_empty()).unwrap_or(false) {
debug!("🔌 UDC is *attached* again");
return;
}
thread::sleep(Duration::from_millis(50));
}
warn!("⏳ UDC did not attach within the expected time-out");
}
use anyhow::{Context, Result};
use tracing::{info, warn};
#[derive(Clone)]
pub struct UsbGadget {
@ -35,7 +10,6 @@ pub struct UsbGadget {
impl UsbGadget {
pub fn new(name: &'static str) -> Self {
// /sys/kernel/config/usb_gadget/<name>/UDC
Self {
udc_file: Box::leak(
format!("/sys/kernel/config/usb_gadget/{name}/UDC").into_boxed_str(),
@ -43,52 +17,59 @@ impl UsbGadget {
}
}
/// Force the host to re-enumerate our HID gadget.
/// Retries on EBUSY so that a transient “resource busy” never kills the whole process.
/// Read the controller name currently written to `<gadget>/UDC`
fn current_udc(&self) -> Result<String> {
Ok(fs::read_to_string(self.udc_file)?.trim().to_owned())
}
/// 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<()> {
info!("🔌 UDC-cycle: detaching gadget");
OpenOptions::new()
.write(true)
.open(self.udc_file)?
.write_all(b"")?; // empty string → detach
wait_for_detach(self.udc_file);
// Let the controller settle
thread::sleep(Duration::from_millis(200));
let udc_name = std::fs::read_dir("/sys/class/udc")?
.next()
.transpose()?
.context("no UDC present")?
.file_name();
.file_name()
.to_string_lossy()
.into_owned();
// Retry loop for the notoriously fragile re-attach
for attempt in 0..5 {
info!("🔌 UDC-cycle: re-attaching to {} (try #{attempt})", udc_name.to_string_lossy());
match OpenOptions::new()
/* DETACH */
if !self.current_udc()?.is_empty() {
info!("🔌 UDCcycle: detaching gadget");
OpenOptions::new()
.write(true)
.open(self.udc_file)?
.write_all(udc_name.to_str().unwrap().as_bytes())
{
Ok(()) => {
wait_for_attach(self.udc_file);
info!("🟢 USB-gadget cycled successfully");
return Ok(());
}
Err(e) if e.raw_os_error() == Some(libc::EBUSY) => {
warn!("🚧 UDC busy on re-attach retrying in 100 ms");
thread::sleep(Duration::from_millis(100));
continue;
}
Err(e) => {
error!("💥 unexpected error while re-attaching UDC: {e:?}");
return Err(e).context("re-attaching gadget to UDC");
}
}
.write_all(b"")?;
} else {
info!("🔌 Gadget already detached");
}
Self::wait_state(&udc_name, "not attached")?;
Err(anyhow::anyhow!(
"giving up after 5 attempts UDC remained busy"
))
/* ATTACH */
info!("🔌 UDCcycle: reattaching to {udc_name}");
OpenOptions::new()
.write(true)
.open(self.udc_file)?
.write_all(udc_name.as_bytes())?;
Self::wait_state(&udc_name, "configured")?;
info!("🔌 USBgadget cycled successfully 🎉");
Ok(())
}
}