lesavka/server/src/usb_gadget.rs
2025-06-25 23:27:51 -05:00

136 lines
4.9 KiB
Rust
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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())
}
/// Busyloop (≤ `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 */
/// Hardreset the gadget → identical to a physical cable replug
pub fn cycle(&self) -> Result<()> {
/* 0ensure 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 gadgets 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 rebind {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 reattach gadget */
info!("🔌 reattaching 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 pullups
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 6s continuing (state = {last:?})");
Ok(())
} else {
Err(e)
}
})?;
info!("✅ USBgadget cycle complete");
Ok(())
}
/// helper: unbind + 300ms 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"))
}
}