Skip to content

Commit

Permalink
linux: support getting usb port info without libudev
Browse files Browse the repository at this point in the history
Support getting USB port info such as VID and PID when libudev is
disabled. This reads this information out of /sys/.

The method of discovery was based on pySerial, which does something
similar.

Signed-off-by: Sean Cross <[email protected]>
  • Loading branch information
xobs committed Oct 5, 2024
1 parent 2ab058c commit 24823aa
Showing 1 changed file with 57 additions and 8 deletions.
65 changes: 57 additions & 8 deletions src/posix/enumerate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@ cfg_if! {
target_os = "macos"
))]
use crate::SerialPortType;
#[cfg(any(
target_os = "ios",
all(target_os = "linux", not(target_env = "musl"), feature = "libudev"),
target_os = "macos"
))]
#[cfg(any(target_os = "ios", target_os = "linux", target_os = "macos"))]
use crate::UsbPortInfo;
#[cfg(any(
target_os = "android",
Expand Down Expand Up @@ -609,11 +605,62 @@ cfg_if! {
use std::io::Read;
use std::path::Path;

fn read_file_to_trimmed_string(dir: &Path, file: &str) -> Option<String> {
let dir = dir.join(file);
let mut s = String::new();
File::open(dir).ok()?.read_to_string(&mut s).ok()?;
Some(s.trim().to_owned())
}

fn read_file_to_u16(dir: &Path, file: &str) -> Option<u16> {
u16::from_str_radix(&read_file_to_trimmed_string(dir, file)?, 16).ok()
}

#[cfg(feature = "usbportinfo-interface")]
fn read_file_to_u8(dir: &Path, file: &str) -> Option<u8> {
u8::from_str_radix(&read_file_to_trimmed_string(dir, file)?, 16).ok()
}

fn read_usb_port_info(device_path: &Path) -> Option<SerialPortType> {

let device_path = device_path
.canonicalize()
.ok()?;
let subsystem = device_path.join("subsystem").canonicalize().ok()?;
let subsystem = subsystem.file_name()?.to_string_lossy();

let usb_interface_path = if subsystem == "usb-serial" {
device_path.parent()?
} else if subsystem == "usb" {
&device_path
} else {
return None;
};
let usb_device_path = usb_interface_path.parent()?;

let vid = read_file_to_u16(&usb_device_path, &"idVendor")?;
let pid = read_file_to_u16(&usb_device_path, &"idProduct")?;
#[cfg(feature = "usbportinfo-interface")]
let interface = read_file_to_u8(&usb_interface_path, &"bInterfaceNumber");
let serial_number = read_file_to_trimmed_string(&usb_device_path, &"serial");
let product = read_file_to_trimmed_string(&usb_device_path, &"product");
let manufacturer = read_file_to_trimmed_string(&usb_device_path, &"manufacturer");
Some(SerialPortType::UsbPort(UsbPortInfo {
vid,
pid,
serial_number,
manufacturer,
product,
#[cfg(feature = "usbportinfo-interface")]
interface,
}))
}

/// Scans `/sys/class/tty` for serial devices (on Linux systems without libudev).
pub fn available_ports() -> Result<Vec<SerialPortInfo>> {
let mut vec = Vec::new();
let sys_path = Path::new("/sys/class/tty/");
let device_path = Path::new("/dev");
let dev_path = Path::new("/dev");
let mut s;
for path in sys_path.read_dir().expect("/sys/class/tty/ doesn't exist on this system") {
let raw_path = path?.path().clone();
Expand All @@ -624,6 +671,8 @@ cfg_if! {
continue;
}

let port_type = read_usb_port_info(&path).unwrap_or(SerialPortType::Unknown);

path.push("driver_override");
if path.is_file() {
s = String::new();
Expand All @@ -639,14 +688,14 @@ cfg_if! {
//
// See https://github.com/serialport/serialport-rs/issues/66 for details.
if let Some(file_name) = raw_path.file_name() {
let device_file = device_path.join(file_name);
let device_file = dev_path.join(file_name);
if !device_file.exists() {
continue;
}

vec.push(SerialPortInfo {
port_name: device_file.to_string_lossy().to_string(),
port_type: SerialPortType::Unknown,
port_type,
});
}
}
Expand Down

0 comments on commit 24823aa

Please sign in to comment.