use std::{
ffi::{OsStr, OsString},
fmt, fs,
io::{Error, ErrorKind, Result},
os::unix::prelude::OsStringExt,
path::{Path, PathBuf},
};
use crate::{trim_os_str, Speed};
#[derive(Clone)]
pub struct Udc {
dir: PathBuf,
}
impl fmt::Debug for Udc {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Udc").field("name", &self.name()).finish()
}
}
impl Udc {
pub fn name(&self) -> &OsStr {
self.dir.file_name().unwrap()
}
pub fn a_alt_hnp_support(&self) -> Result<bool> {
Ok(fs::read_to_string(self.dir.join("a_alt_hnp_support"))?.trim() != "0")
}
pub fn a_hnp_support(&self) -> Result<bool> {
Ok(fs::read_to_string(self.dir.join("a_hnp_support"))?.trim() != "0")
}
pub fn b_hnp_enable(&self) -> Result<bool> {
Ok(fs::read_to_string(self.dir.join("b_hnp_enable"))?.trim() != "0")
}
pub fn current_speed(&self) -> Result<Speed> {
Ok(fs::read_to_string(self.dir.join("current_speed"))?.trim().parse().unwrap_or_default())
}
pub fn max_speed(&self) -> Result<Speed> {
Ok(fs::read_to_string(self.dir.join("maximum_speed"))?.trim().parse().unwrap_or_default())
}
pub fn is_a_peripheral(&self) -> Result<bool> {
Ok(fs::read_to_string(self.dir.join("is_a_peripheral"))?.trim() != "0")
}
pub fn is_otg(&self) -> Result<bool> {
Ok(fs::read_to_string(self.dir.join("is_otg"))?.trim() != "0")
}
pub fn state(&self) -> Result<UdcState> {
Ok(fs::read_to_string(self.dir.join("state"))?.trim().parse().unwrap_or_default())
}
pub fn start_srp(&self) -> Result<()> {
fs::write(self.dir.join("srp"), "1")
}
pub fn set_soft_connect(&self, connect: bool) -> Result<()> {
fs::write(self.dir.join("soft_connect"), if connect { "connect" } else { "disconnect" })
}
pub fn function(&self) -> Result<Option<OsString>> {
let data = OsString::from_vec(fs::read(self.dir.join("function"))?);
let data = trim_os_str(&data);
if data.is_empty() {
Ok(None)
} else {
Ok(Some(data.to_os_string()))
}
}
}
#[derive(
Default, Debug, strum::Display, strum::EnumString, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[non_exhaustive]
pub enum UdcState {
#[strum(serialize = "not attached")]
NotAttached,
#[strum(serialize = "attached")]
Attached,
#[strum(serialize = "powered")]
Powered,
#[strum(serialize = "reconnecting")]
Reconnecting,
#[strum(serialize = "unauthenticated")]
Unauthenticated,
#[strum(serialize = "default")]
Default,
#[strum(serialize = "addressed")]
Addressed,
#[strum(serialize = "configured")]
Configured,
#[strum(serialize = "suspended")]
Suspended,
#[default]
#[strum(serialize = "UNKNOWN")]
Unknown,
}
pub fn udcs() -> Result<Vec<Udc>> {
let class_dir = Path::new("/sys/class");
if !class_dir.is_dir() {
return Err(Error::new(ErrorKind::NotFound, "sysfs is not available"));
}
let udc_dir = class_dir.join("udc");
if !udc_dir.is_dir() {
return Ok(Vec::new());
}
let mut udcs = Vec::new();
for entry in fs::read_dir(&udc_dir)? {
let Ok(entry) = entry else { continue };
udcs.push(Udc { dir: entry.path() });
}
Ok(udcs)
}
pub fn default_udc() -> Result<Udc> {
let mut udcs = udcs()?;
udcs.sort_by_key(|udc| udc.name().to_os_string());
udcs.into_iter()
.next()
.ok_or_else(|| Error::new(ErrorKind::NotFound, "no USB device controller (UDC) available"))
}