// server/src/usb_gadget.rs use std::{fs::{self, OpenOptions}, io::Write, 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(), ), } } /// Return the first UDC name we find, e.g. `1000480000.usb` fn controller() -> Result { Ok(std::fs::read_dir("/sys/class/udc")? .next() .transpose()? .context("no UDC present")? .file_name() .to_string_lossy() .into_owned()) } /// Read the controller name currently written to `/UDC` fn current_udc(&self) -> Result { Ok(fs::read_to_string(self.udc_file)?.trim().to_owned()) } /// Poll `/sys/class/udc//state` until it equals `wanted`. fn wait_state(name: &str, wanted: &str, limit_ms: u64) -> Result<()> { 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(()); } trace!("⏳ wait_state polling UDC, retry #{attempt}"); thread::sleep(Duration::from_millis(50)); } Err(anyhow::anyhow!("🚧 UDC never reached '{wanted}' (last state = {:?})", fs::read_to_string(&p).unwrap_or_default())) } /// **Hard‑reset** the controller: /// 1. detach gadget (`UDC=""`) /// 2. un‑bind driver (`…/drivers//unbind`) /// 3. bind again /// 4. re‑attach gadget /// 5. wait until state == `configured` 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(()) } }