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