lesavka/server/src/usb_gadget.rs
2025-06-25 21:02:29 -05:00

95 lines
3.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

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, 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");
}
#[derive(Clone)]
pub struct UsbGadget {
udc_file: &'static str,
}
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(),
),
}
}
/// Force the host to re-enumerate our HID gadget.
/// Retries on EBUSY so that a transient “resource busy” never kills the whole process.
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();
// 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()
.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");
}
}
}
Err(anyhow::anyhow!(
"giving up after 5 attempts UDC remained busy"
))
}
}