From 5ce8f511e672cab18d23ba651f46e4ab5c36af7c Mon Sep 17 00:00:00 2001 From: soiamsoNG <83182235@qq.com> Date: Sat, 30 Mar 2024 06:26:14 +0000 Subject: [PATCH] Try find port info from usb_interface 's MODALIAS --- Cargo.toml | 1 + src/posix/enumerate.rs | 89 ++++++++++++++++++++++++++++++++++++------ 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a8c1ec9c..592abdcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ nix = { version = "0.26", default-features = false, features = ["fs", "ioctl", " [target.'cfg(all(target_os = "linux", not(target_env = "musl")))'.dependencies] libudev = { version = "0.3.0", optional = true } unescaper = "0.1.3" +regex = "1.5.5" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation-sys = "0.8.4" diff --git a/src/posix/enumerate.rs b/src/posix/enumerate.rs index 76f17148..57e8a89d 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -1,7 +1,11 @@ use cfg_if::cfg_if; -#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] -use std::ffi::OsStr; +cfg_if! { + if #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]{ + use std::ffi::OsStr; + use regex::Regex; + } +} cfg_if! { if #[cfg(any(target_os = "ios", target_os = "macos"))] { @@ -175,22 +179,81 @@ fn port_type(d: &libudev::Device) -> Result { } } None => { - let p = d.parent().unwrap(); - let parent_driver = p.driver().unwrap().to_str().unwrap(); - let parent_subsystem = p.subsystem().unwrap().to_str().unwrap(); + fn find_usb_interface_from_parents( + parent: Option, + ) -> Option { + let mut p = parent?; + + // limit the query depth + for _ in 1..4 { + match p.devtype() { + None => match p.parent() { + None => break, + Some(x) => p = x, + }, + Some(s) => { + if s.to_str()? == "usb_interface" { + break; + } else { + match p.parent() { + None => break, + Some(x) => p = x, + } + } + } + } + } - if parent_driver == "cdc_acm" && parent_subsystem == "usb" { - let product_code = p.property_value("PRODUCT").and_then(OsStr::to_str).unwrap(); - Ok(SerialPortType::UsbPort(UsbPortInfo { - vid: u16::from_str_radix(&product_code[0..4], 16).unwrap(), - pid: u16::from_str_radix(&product_code[5..9], 16).unwrap(), + Some(p) + } + + fn get_modalias_from_device(d: libudev::Device) -> Option { + Some( + d.property_value("MODALIAS") + .and_then(OsStr::to_str)? + .to_owned(), + ) + } + + // MODALIAS = usb:v303Ap1001d0101dcEFdsc02dp01ic02isc02ip00in00 + // v 303A (device vendor) + // p 1001 (device product) + // d 0101 (bcddevice) + // dc EF (device class) + // dsc 02 (device subclass) + // dp 01 (device protocol) + // ic 02 (interface class) + // isc 02 (interface subclass) + // ip 00 (interface protocol) + // in 00 (interface number) + fn parse_modalias(moda: String) -> Option { + let re = Regex::new(concat!( + r"usb:v(?P[[:xdigit:]]{4})", + r"p(?P[[:xdigit:]]{4})", + r".*", + r"in(?P[[:xdigit:]]{2})" + )) + .unwrap(); + + let caps = re.captures(moda.as_str())?; + + Some(UsbPortInfo { + vid: u16::from_str_radix(&caps["vid"], 16).ok()?, + pid: u16::from_str_radix(&caps["pid"], 16).ok()?, serial_number: None, manufacturer: None, product: None, - })) - } else { - Ok(SerialPortType::Unknown) + #[cfg(feature = "usbportinfo-interface")] + interface: u8::from_str_radix(&caps["in"], 16).ok(), + }) } + + find_usb_interface_from_parents(d.parent()) + .and_then(get_modalias_from_device) + .and_then(parse_modalias) + .map_or(Ok(SerialPortType::Unknown), |port_info| { + Ok(SerialPortType::UsbPort(port_info)) + }) } _ => Ok(SerialPortType::Unknown), }