1use std::{
4 ffi::{OsStr, OsString},
5 fmt, fs,
6 io::{Error, ErrorKind, Result},
7 os::unix::prelude::OsStringExt,
8 path::{Path, PathBuf},
9};
10
11use crate::{trim_os_str, Speed};
12
13#[derive(Clone)]
17pub struct Udc {
18 dir: PathBuf,
19}
20
21impl fmt::Debug for Udc {
22 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23 f.debug_struct("Udc").field("name", &self.name()).finish()
24 }
25}
26
27impl Udc {
28 pub fn name(&self) -> &OsStr {
30 self.dir.file_name().unwrap()
31 }
32
33 pub fn a_alt_hnp_support(&self) -> Result<bool> {
35 Ok(fs::read_to_string(self.dir.join("a_alt_hnp_support"))?.trim() != "0")
36 }
37
38 pub fn a_hnp_support(&self) -> Result<bool> {
40 Ok(fs::read_to_string(self.dir.join("a_hnp_support"))?.trim() != "0")
41 }
42
43 pub fn b_hnp_enable(&self) -> Result<bool> {
45 Ok(fs::read_to_string(self.dir.join("b_hnp_enable"))?.trim() != "0")
46 }
47
48 pub fn current_speed(&self) -> Result<Speed> {
52 Ok(fs::read_to_string(self.dir.join("current_speed"))?.trim().parse().unwrap_or_default())
53 }
54
55 pub fn max_speed(&self) -> Result<Speed> {
57 Ok(fs::read_to_string(self.dir.join("maximum_speed"))?.trim().parse().unwrap_or_default())
58 }
59
60 pub fn is_a_peripheral(&self) -> Result<bool> {
63 Ok(fs::read_to_string(self.dir.join("is_a_peripheral"))?.trim() != "0")
64 }
65
66 pub fn is_otg(&self) -> Result<bool> {
68 Ok(fs::read_to_string(self.dir.join("is_otg"))?.trim() != "0")
69 }
70
71 pub fn state(&self) -> Result<UdcState> {
75 Ok(fs::read_to_string(self.dir.join("state"))?.trim().parse().unwrap_or_default())
76 }
77
78 pub fn start_srp(&self) -> Result<()> {
80 fs::write(self.dir.join("srp"), "1")
81 }
82
83 pub fn set_soft_connect(&self, connect: bool) -> Result<()> {
86 fs::write(self.dir.join("soft_connect"), if connect { "connect" } else { "disconnect" })
87 }
88
89 pub fn driver(&self) -> Result<OsString> {
92 let target = fs::read_link(self.dir.join("device/driver"))?;
93 target
94 .file_name()
95 .map(|n| n.to_os_string())
96 .ok_or_else(|| Error::new(ErrorKind::NotFound, "UDC driver symlink has no file name"))
97 }
98
99 pub fn function(&self) -> Result<Option<OsString>> {
101 let data = OsString::from_vec(fs::read(self.dir.join("function"))?);
102 let data = trim_os_str(&data);
103 if data.is_empty() {
104 Ok(None)
105 } else {
106 Ok(Some(data.to_os_string()))
107 }
108 }
109}
110
111#[derive(
113 Default, Debug, strum::Display, strum::EnumString, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
114)]
115#[non_exhaustive]
116pub enum UdcState {
117 #[strum(serialize = "not attached")]
119 NotAttached,
120 #[strum(serialize = "attached")]
122 Attached,
123 #[strum(serialize = "powered")]
125 Powered,
126 #[strum(serialize = "reconnecting")]
128 Reconnecting,
129 #[strum(serialize = "unauthenticated")]
131 Unauthenticated,
132 #[strum(serialize = "default")]
134 Default,
135 #[strum(serialize = "addressed")]
137 Addressed,
138 #[strum(serialize = "configured")]
140 Configured,
141 #[strum(serialize = "suspended")]
143 Suspended,
144 #[default]
146 #[strum(serialize = "UNKNOWN")]
147 Unknown,
148}
149
150pub fn udcs() -> Result<Vec<Udc>> {
152 let class_dir = Path::new("/sys/class");
153 if !class_dir.is_dir() {
154 return Err(Error::new(ErrorKind::NotFound, "sysfs is not available"));
155 }
156
157 let udc_dir = class_dir.join("udc");
158 if !udc_dir.is_dir() {
159 return Ok(Vec::new());
160 }
161
162 let mut udcs = Vec::new();
163 for entry in fs::read_dir(&udc_dir)? {
164 let Ok(entry) = entry else { continue };
165 udcs.push(Udc { dir: entry.path() });
166 }
167
168 Ok(udcs)
169}
170
171pub fn default_udc() -> Result<Udc> {
175 let mut udcs = udcs()?;
176 udcs.sort_by_key(|udc| udc.name().to_os_string());
177 udcs.into_iter()
178 .next()
179 .ok_or_else(|| Error::new(ErrorKind::NotFound, "no USB device controller (UDC) available"))
180}