lesavka/server/src/gadget/sysfs_state.rs

151 lines
5.0 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.

impl UsbGadget {
fn sysfs_root() -> String {
env::var("LESAVKA_GADGET_SYSFS_ROOT").unwrap_or_else(|_| "/sys".to_string())
}
fn configfs_root() -> String {
env::var("LESAVKA_GADGET_CONFIGFS_ROOT")
.unwrap_or_else(|_| "/sys/kernel/config/usb_gadget".to_string())
}
pub fn new(name: &'static str) -> Self {
Self {
udc_file: Box::leak(
format!("{}/{}{}", Self::configfs_root(), name, "/UDC").into_boxed_str(),
),
}
}
pub fn state(ctrl: &str) -> anyhow::Result<String> {
let p = format!("{}/class/udc/{ctrl}/state", Self::sysfs_root());
Ok(std::fs::read_to_string(p)?.trim().to_owned())
}
pub fn current_controller_state() -> anyhow::Result<(String, String)> {
let ctrl = Self::find_controller()?;
let state = Self::state(&ctrl)?;
Ok((ctrl, state))
}
pub fn host_attached_state(state: &str) -> bool {
matches!(
state,
"configured" | "addressed" | "default" | "suspended" | "unknown"
)
}
pub fn host_enumerated_state(state: &str) -> bool {
matches!(state, "configured" | "addressed" | "default" | "suspended")
}
pub fn current_state_detail() -> String {
match Self::current_controller_state() {
Ok((ctrl, state)) => format!("UDC {ctrl} state={state}"),
Err(err) => format!("UDC state unavailable: {err:#}"),
}
}
/*---- helpers ----*/
/// Find the first controller in /sys/class/udc (e.g. `1000480000.usb`)
pub fn find_controller() -> Result<String> {
Ok(fs::read_dir(format!("{}/class/udc", Self::sysfs_root()))?
.next()
.transpose()?
.context("no UDC present")?
.file_name()
.to_string_lossy()
.into_owned())
}
/// Busy-loop (≤ `limit_ms`) until `state` matches `wanted`
fn wait_state(ctrl: &str, wanted: &str, limit_ms: u64) -> Result<()> {
let path = format!("{}/class/udc/{ctrl}/state", Self::sysfs_root());
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()
))
}
pub fn wait_state_any(ctrl: &str, limit_ms: u64) -> anyhow::Result<String> {
let path = format!("{}/class/udc/{ctrl}/state", Self::sysfs_root());
for _ in 0..=limit_ms / 50 {
if let Ok(s) = std::fs::read_to_string(&path) {
let s = s.trim();
if matches!(s, "configured" | "not attached") {
return Ok(s.to_owned());
}
}
std::thread::sleep(std::time::Duration::from_millis(50));
}
Err(anyhow::anyhow!(
"UDC state did not settle within {limit_ms}ms"
))
}
/// 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(())
}
// Wait (≤ `limit_ms`) until `/sys/class/udc/<ctrl>` exists again.
fn wait_udc_present(ctrl: &str, limit_ms: u64) -> Result<()> {
for _ in 0..=limit_ms / 50 {
if Path::new(&format!("{}/class/udc/{ctrl}", Self::sysfs_root())).exists() {
return Ok(());
}
thread::sleep(Duration::from_millis(50));
}
Err(anyhow::anyhow!(
"⚠️ UDC {ctrl} did not re-appear within {limit_ms}ms"
))
}
/// Scan platform devices when /sys/class/udc is empty
fn probe_platform_udc() -> Result<Option<String>> {
for entry in fs::read_dir(format!("{}/bus/platform/devices", Self::sysfs_root()))? {
let p = entry?.file_name().into_string().unwrap();
if p.ends_with(".usb") {
return Ok(Some(p));
}
}
Ok(None)
}
fn platform_driver(ctrl: &str) -> Option<String> {
fs::read_link(format!(
"{}/bus/platform/devices/{ctrl}/driver",
Self::sysfs_root()
))
.ok()
.and_then(|path| path.file_name().map(|name| name.to_string_lossy().into_owned()))
}
fn soft_connect_path(ctrl: &str) -> Option<String> {
let path = format!("{}/class/udc/{ctrl}/soft_connect", Self::sysfs_root());
if !Path::new(&path).exists() {
return None;
}
if env::var("LESAVKA_FORCE_SOFT_CONNECT").ok().as_deref() == Some("1") {
return Some(path);
}
if Self::platform_driver(ctrl).as_deref() == Some("dwc2") {
return None;
}
Some(path)
}
}