Skip to main content

usb_gadget/
lib.rs

1//! This library allows implementation of USB peripherals, so called **USB gadgets**,
2//! on Linux devices that have a USB device controller (UDC).
3//! Both, pre-defined USB functions and fully custom implementations of the USB
4//! interface are supported.
5//!
6//! The following pre-defined USB functions, implemented by kernel drivers, are supported:
7//!
8//! * [network interface]: CDC ECM, ECM subset, EEM, NCM, RNDIS
9//! * [serial port]: CDC ACM, generic
10//! * [human interface device (HID)]
11//! * [mass-storage device (MSD)]
12//! * [printer device]
13//! * [musical instrument digital interface (MIDI)]
14//! * [audio device]: UAC1 and UAC2
15//! * [video device] (UVC)
16//!
17//! In addition fully [custom USB functions] can be implemented in user-mode Rust code.
18//!
19//! [network interface]: function::net
20//! [serial port]: function::serial
21//! [human interface device (HID)]: function::hid
22//! [mass-storage device (MSD)]: function::msd
23//! [printer device]: function::printer
24//! [musical instrument digital interface (MIDI)]: function::midi
25//! [audio device]: function::audio
26//! [video device]: function::video
27//! [custom USB functions]: function::custom
28//!
29//! Support for OS-specific descriptors and WebUSB is also provided.
30//!
31//! ### Requirements
32//!
33//! A USB device controller (UDC) supported by Linux is required.
34//!
35//! The Linux kernel configuration options `CONFIG_USB_GADGET` and `CONFIG_USB_CONFIGFS`
36//! need to be enabled.
37//!
38//! root permissions are required to configure USB gadgets and
39//! the `configfs` filesystem needs to be mounted.
40//!
41//! ### Usage
42//!
43//! Start defining a USB gadget by calling [`Gadget::new`].
44//! When the gadget is fully specified, call [`Gadget::bind`] to register it with
45//! a [USB device controller (UDC)](Udc).
46
47#![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/// USB speed.
77#[derive(
78    Default, Debug, strum::Display, strum::EnumString, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
79)]
80#[non_exhaustive]
81pub enum Speed {
82    /// Unknown speed.
83    #[default]
84    #[strum(serialize = "UNKNOWN")]
85    Unknown,
86    /// USB 1.0: 1.5 Mbit/s.
87    #[strum(serialize = "low-speed")]
88    LowSpeed,
89    /// USB 1.0: 12 Mbit/s.
90    #[strum(serialize = "full-speed")]
91    FullSpeed,
92    /// USB 2.0: 480 Mbit/s.
93    #[strum(serialize = "high-speed")]
94    HighSpeed,
95    /// USB 3.0: 5 Gbit/s.
96    #[strum(serialize = "super-speed")]
97    SuperSpeed,
98    /// USB 3.1: 10 Gbit/s.
99    #[strum(serialize = "super-speed-plus")]
100    SuperSpeedPlus,
101}
102
103/// 8-bit value to hexadecimal notation.
104fn hex_u8(value: u8) -> String {
105    format!("0x{:02x}", value)
106}
107
108/// 8-bit value to hexadecimal notation without 0x prefix.
109fn hex_u8_noprefix(value: u8) -> String {
110    format!("{:02x}", value)
111}
112
113/// 16-bit value to hexadecimal notation.
114fn hex_u16(value: u16) -> String {
115    format!("0x{:04x}", value)
116}
117
118/// Returns where configfs is mounted.
119fn 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
130/// Trims an OsStr.
131fn 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
145/// Request a kernel module to be loaded.
146fn 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
163/// Gets the Linux kernel version.
164fn 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}