2025-06-25 21:02:29 -05:00
|
|
|
|
// server/src/usb_gadget.rs
|
|
|
|
|
|
use std::{fs::{self, OpenOptions}, io::Write, thread, time::Duration};
|
2025-06-25 21:11:59 -05:00
|
|
|
|
use anyhow::{Context, Result};
|
2025-06-25 21:52:52 -05:00
|
|
|
|
use tracing::{info, warn, trace};
|
2025-06-24 23:48:06 -05:00
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
|
pub struct UsbGadget {
|
|
|
|
|
|
udc_file: &'static str,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-25 07:46:50 -05:00
|
|
|
|
impl UsbGadget {
|
|
|
|
|
|
pub fn new(name: &'static str) -> Self {
|
2025-06-25 21:02:29 -05:00
|
|
|
|
Self {
|
|
|
|
|
|
udc_file: Box::leak(
|
|
|
|
|
|
format!("/sys/kernel/config/usb_gadget/{name}/UDC").into_boxed_str(),
|
|
|
|
|
|
),
|
2025-06-24 23:48:06 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-25 21:52:52 -05:00
|
|
|
|
/// Return the first UDC name we find, e.g. `1000480000.usb`
|
|
|
|
|
|
fn controller() -> Result<String> {
|
|
|
|
|
|
Ok(std::fs::read_dir("/sys/class/udc")?
|
|
|
|
|
|
.next()
|
|
|
|
|
|
.transpose()?
|
|
|
|
|
|
.context("no UDC present")?
|
|
|
|
|
|
.file_name()
|
|
|
|
|
|
.to_string_lossy()
|
|
|
|
|
|
.into_owned())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-25 21:11:59 -05:00
|
|
|
|
/// 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())
|
|
|
|
|
|
}
|
2025-06-25 21:02:29 -05:00
|
|
|
|
|
2025-06-25 21:52:52 -05:00
|
|
|
|
/// Poll `/sys/class/udc/<name>/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 {
|
2025-06-25 21:11:59 -05:00
|
|
|
|
return Ok(());
|
|
|
|
|
|
}
|
2025-06-25 21:52:52 -05:00
|
|
|
|
trace!("⏳ wait_state polling UDC, retry #{attempt}");
|
|
|
|
|
|
thread::sleep(Duration::from_millis(50));
|
2025-06-25 21:11:59 -05:00
|
|
|
|
}
|
2025-06-25 21:52:52 -05:00
|
|
|
|
Err(anyhow::anyhow!("🚧 UDC never reached '{wanted}' (last state = {:?})",
|
|
|
|
|
|
fs::read_to_string(&p).unwrap_or_default()))
|
2025-06-25 21:11:59 -05:00
|
|
|
|
}
|
2025-06-24 23:48:06 -05:00
|
|
|
|
|
2025-06-25 21:52:52 -05:00
|
|
|
|
/// **Hard‑reset** the controller:
|
|
|
|
|
|
/// 1. detach gadget (`UDC=""`)
|
|
|
|
|
|
/// 2. un‑bind driver (`…/drivers/<udc>/unbind`)
|
|
|
|
|
|
/// 3. bind again
|
|
|
|
|
|
/// 4. re‑attach gadget
|
|
|
|
|
|
/// 5. wait until state == `configured`
|
2025-06-25 21:11:59 -05:00
|
|
|
|
pub fn cycle(&self) -> Result<()> {
|
2025-06-25 21:52:52 -05:00
|
|
|
|
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");
|
2025-06-25 21:02:29 -05:00
|
|
|
|
|
2025-06-25 21:52:52 -05:00
|
|
|
|
// Some kernels use dwc3 – try that if dwc2 is absent
|
|
|
|
|
|
let (unbind, bind) = if std::path::Path::new(&unbind).exists() {
|
|
|
|
|
|
(unbind, bind)
|
2025-06-25 21:11:59 -05:00
|
|
|
|
} else {
|
2025-06-25 21:52:52 -05:00
|
|
|
|
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())?;
|
2025-06-25 21:11:59 -05:00
|
|
|
|
|
2025-06-25 21:52:52 -05:00
|
|
|
|
/*----- 5. wait until host finished enumeration -------------------*/
|
|
|
|
|
|
Self::wait_state(&ctrl, "configured", 6_000)
|
|
|
|
|
|
.context("waiting for host to enumerate gadget")?;
|
2025-06-25 21:02:29 -05:00
|
|
|
|
|
2025-06-25 21:52:52 -05:00
|
|
|
|
info!("✅ USB gadget cycle complete; controller back to 'configured'");
|
2025-06-25 21:11:59 -05:00
|
|
|
|
Ok(())
|
2025-06-24 23:48:06 -05:00
|
|
|
|
}
|
|
|
|
|
|
}
|