1#![warn(missing_docs)]
54#![cfg_attr(docsrs, feature(doc_cfg))]
55
56#[cfg(not(target_os = "linux"))]
57compile_error!("usb_gadget only supports Linux");
58
59use proc_mounts::MountIter;
60use std::{
61 ffi::OsStr,
62 fs,
63 io::{Error, ErrorKind, Result},
64 os::unix::prelude::OsStrExt,
65 path::{Path, PathBuf},
66 process::Command,
67 sync::OnceLock,
68};
69
70pub mod function;
71
72mod ioctl;
73
74mod gadget;
75pub use gadget::*;
76
77mod udc;
78pub use udc::*;
79
80mod lang;
81pub use lang::*;
82
83#[derive(
85 Default, Debug, strum::Display, strum::EnumString, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
86)]
87#[non_exhaustive]
88pub enum Speed {
89 #[default]
91 #[strum(serialize = "UNKNOWN")]
92 Unknown,
93 #[strum(serialize = "low-speed")]
95 LowSpeed,
96 #[strum(serialize = "full-speed")]
98 FullSpeed,
99 #[strum(serialize = "high-speed")]
101 HighSpeed,
102 #[strum(serialize = "super-speed")]
104 SuperSpeed,
105 #[strum(serialize = "super-speed-plus")]
107 SuperSpeedPlus,
108}
109
110fn hex_u8(value: u8) -> String {
112 format!("0x{:02x}", value)
113}
114
115fn hex_u8_noprefix(value: u8) -> String {
117 format!("{:02x}", value)
118}
119
120fn hex_u16(value: u16) -> String {
122 format!("0x{:04x}", value)
123}
124
125fn write_attr(path: &Path, value: impl AsRef<[u8]>) -> Result<()> {
130 fs::write(path, value).map_err(|err| match path.try_exists() {
131 Ok(false) => Error::new(
132 ErrorKind::Unsupported,
133 format!("attribute {} is not supported by the kernel", path.display()),
134 ),
135 _ => err,
136 })
137}
138
139fn configfs_dir() -> Result<PathBuf> {
141 for mount in MountIter::new()? {
142 let Ok(mount) = mount else { continue };
143 if mount.fstype == "configfs" {
144 return Ok(mount.dest);
145 }
146 }
147
148 Err(Error::new(ErrorKind::NotFound, "configfs is not mounted"))
149}
150
151fn trim_os_str(value: &OsStr) -> &OsStr {
153 let mut value = value.as_bytes();
154
155 while value.first() == Some(&b'\n') || value.first() == Some(&b' ') || value.first() == Some(&b'\0') {
156 value = &value[1..];
157 }
158
159 while value.last() == Some(&b'\n') || value.last() == Some(&b' ') || value.last() == Some(&b'\0') {
160 value = &value[..value.len() - 1];
161 }
162
163 OsStr::from_bytes(value)
164}
165
166fn request_module(name: impl AsRef<OsStr>) -> Result<()> {
168 let mut res = Command::new("modprobe").arg("-q").arg(name.as_ref()).output();
169
170 match res {
171 Err(err) if err.kind() == ErrorKind::NotFound => {
172 res = Command::new("/sbin/modprobe").arg("-q").arg(name.as_ref()).output();
173 }
174 _ => (),
175 }
176
177 match res {
178 Ok(out) if out.status.success() => Ok(()),
179 Ok(_) => Err(Error::other("modprobe failed")),
180 Err(err) => Err(err),
181 }
182}
183
184fn linux_version() -> Option<(u16, u16)> {
186 static VERSION: OnceLock<Result<(u16, u16)>> = OnceLock::new();
187 let version = VERSION.get_or_init(|| {
188 let uts = rustix::system::uname();
189
190 let release =
191 uts.release().to_str().map_err(|_| Error::new(ErrorKind::InvalidData, "invalid release string"))?;
192
193 let parts: Vec<&str> = release.split('.').collect();
194 if parts.len() < 2 {
195 return Err(Error::new(ErrorKind::InvalidData, "invalid kernel version"));
196 }
197
198 let major = parts[0].parse().map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
199 let minor = parts[1].parse().map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
200
201 Ok((major, minor))
202 });
203
204 match version {
205 Ok(version) => Some(*version),
206 Err(err) => {
207 log::warn!("failed to obtain Linux version: {err}");
208 None
209 }
210 }
211}
212
213#[cfg(test)]
214mod test {
215 #[test]
216 fn linux_version() {
217 let (major, minor) = super::linux_version().expect("failed to get Linux version");
218 println!("Linux {major}.{minor}");
219 }
220}