Skip to main content

usb_gadget/
udc.rs

1//! USB device controller (UDC).
2
3use 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/// USB device controller (UDC).
14///
15/// Call [`udcs`] to obtain the controllers available on the system.
16#[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    /// The name of the USB device controller.
29    pub fn name(&self) -> &OsStr {
30        self.dir.file_name().unwrap()
31    }
32
33    /// Indicates if an OTG A-Host supports HNP at an alternate port.
34    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    /// Indicates if an OTG A-Host supports HNP at this port.
39    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    /// Indicates if an OTG A-Host enabled HNP support.
44    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    /// Indicates the current negotiated speed at this port.
49    ///
50    /// `None` if unknown.
51    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    /// Indicates the maximum USB speed supported by this port.
56    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    /// Indicates that this port is the default Host on an OTG session but HNP was used to switch
61    /// roles.
62    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    /// Indicates that this port support OTG.
67    pub fn is_otg(&self) -> Result<bool> {
68        Ok(fs::read_to_string(self.dir.join("is_otg"))?.trim() != "0")
69    }
70
71    /// Indicates current state of the USB Device Controller.
72    ///
73    /// However not all USB Device Controllers support reporting all states.
74    pub fn state(&self) -> Result<UdcState> {
75        Ok(fs::read_to_string(self.dir.join("state"))?.trim().parse().unwrap_or_default())
76    }
77
78    /// Manually start Session Request Protocol (SRP).
79    pub fn start_srp(&self) -> Result<()> {
80        fs::write(self.dir.join("srp"), "1")
81    }
82
83    /// Connect or disconnect data pull-up resistors thus causing a logical connection to or
84    /// disconnection from the USB host.
85    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    /// The kernel driver managing this USB device controller, e.g. `dwc2`, `dwc3`,
90    /// `dummy_udc`, `musb-hdrc`, `cdns3`.
91    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    /// Name of currently running USB Gadget Driver.
100    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/// USB device controller (UDC) connection state.
112#[derive(
113    Default, Debug, strum::Display, strum::EnumString, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash,
114)]
115#[non_exhaustive]
116pub enum UdcState {
117    /// Not attached.
118    #[strum(serialize = "not attached")]
119    NotAttached,
120    /// Attached.
121    #[strum(serialize = "attached")]
122    Attached,
123    /// Powered.
124    #[strum(serialize = "powered")]
125    Powered,
126    /// Reconnecting.
127    #[strum(serialize = "reconnecting")]
128    Reconnecting,
129    /// Unauthenticated.
130    #[strum(serialize = "unauthenticated")]
131    Unauthenticated,
132    /// Default.
133    #[strum(serialize = "default")]
134    Default,
135    /// Addressed.
136    #[strum(serialize = "addressed")]
137    Addressed,
138    /// Configured.
139    #[strum(serialize = "configured")]
140    Configured,
141    /// Suspended.
142    #[strum(serialize = "suspended")]
143    Suspended,
144    /// Unknown state.
145    #[default]
146    #[strum(serialize = "UNKNOWN")]
147    Unknown,
148}
149
150/// Gets the available USB device controllers (UDCs) in the system.
151pub 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
171/// The default USB device controller (UDC) in the system by alphabetical sorting.
172///
173/// A not found error is returned if no UDC is present.
174pub 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}