From 054eb036e4525cfed306b63137547bce5b4be175 Mon Sep 17 00:00:00 2001 From: soiamsoNG <83182235@qq.com> Date: Thu, 28 Mar 2024 12:35:48 +0000 Subject: [PATCH 1/5] Try find port type in parent device --- src/posix/enumerate.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/posix/enumerate.rs b/src/posix/enumerate.rs index eb9e403..3c65f2b 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -174,6 +174,24 @@ fn port_type(d: &libudev::Device) -> Result { Ok(SerialPortType::PciPort) } } + None => { + let p = d.parent().unwrap(); + let parent_driver = p.driver().unwrap().to_str().unwrap(); + let parent_subsystem = p.subsystem().unwrap().to_str().unwrap(); + + 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(), + serial_number: None, + manufacturer: None, + product: None, + })) + } else { + Ok(SerialPortType::Unknown) + } + } _ => Ok(SerialPortType::Unknown), } } From ec84edb6085252a9e77b6920ed07ce7e8fdca8d3 Mon Sep 17 00:00:00 2001 From: soiamsoNG <83182235@qq.com> Date: Sat, 30 Mar 2024 06:26:14 +0000 Subject: [PATCH 2/5] 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 7883456..91e0568 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 3c65f2b..339c142 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), } From a0998f425ad11780bef00201f3271b77f196ed9c Mon Sep 17 00:00:00 2001 From: soiamsoNG <83182235@qq.com> Date: Sat, 20 Jul 2024 12:03:37 +0800 Subject: [PATCH 3/5] Remove regex --- Cargo.toml | 1 - src/posix/enumerate.rs | 94 +++++++++++++++++++++++++++--------------- 2 files changed, 60 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 91e0568..7883456 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,6 @@ 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 339c142..b2cf5c7 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -3,7 +3,6 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]{ use std::ffi::OsStr; - use regex::Regex; } } @@ -215,41 +214,9 @@ fn port_type(d: &libudev::Device) -> Result { ) } - // 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, - #[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) + .as_deref() .and_then(parse_modalias) .map_or(Ok(SerialPortType::Unknown), |port_info| { Ok(SerialPortType::UsbPort(port_info)) @@ -259,6 +226,51 @@ fn port_type(d: &libudev::Device) -> Result { } } +// 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) +#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] +fn parse_modalias(moda: &str) -> Option { + // Find the start of the string, will start with "usb:" + let mod_start = moda.find("usb:v")?; + + // Tail to update while searching. + let mut mod_tail = moda.get(mod_start + 5..)?; + + // The next four characters should be hex values of the vendor. + let vid = mod_tail.get(..4)?; + mod_tail = mod_tail.get(4..)?; + + // The next portion we care about is the device product ID. + let pid_start = mod_tail.find('p')?; + let pid = mod_tail.get(pid_start + 1..pid_start + 5)?; + + Some(UsbPortInfo { + vid: u16::from_str_radix(vid, 16).ok()?, + pid: u16::from_str_radix(pid, 16).ok()?, + serial_number: None, + manufacturer: None, + product: None, + // Only attempt to find the interface if the feature is enabled. + #[cfg(feature = "usbportinfo-interface")] + interface: mod_tail.get(pid_start + 4..).and_then(|mod_tail| { + mod_tail.find("in").and_then(|i_start| { + mod_tail + .get(i_start + 2..i_start + 4) + .and_then(|interface| u8::from_str_radix(interface, 16).ok()) + }) + }), + }) +} + #[cfg(any(target_os = "ios", target_os = "macos"))] fn get_parent_device_by_type( device: io_object_t, @@ -679,3 +691,17 @@ cfg_if! { } } } + +#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] +#[test] +fn parser_modalias() { + const MODALIAS: &str = "usb:v303Ap1001d0101dcEFdsc02dp01ic02isc02ip00in0C"; + + let port_info = parse_modalias(MODALIAS).expect("parse failed"); + + assert_eq!(port_info.vid, 0x303A, "vendor parse invalid"); + assert_eq!(port_info.pid, 0x1001, "product parse invalid"); + + #[cfg(feature = "usbportinfo-interface")] + assert_eq!(port_info.interface, Some(0x0C), "interface parse invalid"); +} From ff1c8c30ec62f56e5c059459b0eee83617522b58 Mon Sep 17 00:00:00 2001 From: Christian Meusel Date: Thu, 25 Jul 2024 22:22:45 +0200 Subject: [PATCH 4/5] Un-nest port_type helper functions (on Linux) There is no special requirement for having them there and they are cluttering up the match statement. --- src/posix/enumerate.rs | 82 +++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/posix/enumerate.rs b/src/posix/enumerate.rs index b2cf5c7..4b39dc8 100644 --- a/src/posix/enumerate.rs +++ b/src/posix/enumerate.rs @@ -177,53 +177,51 @@ fn port_type(d: &libudev::Device) -> Result { Ok(SerialPortType::PciPort) } } - None => { - 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, - } - } - } + None => find_usb_interface_from_parents(d.parent()) + .and_then(get_modalias_from_device) + .as_deref() + .and_then(parse_modalias) + .map_or(Ok(SerialPortType::Unknown), |port_info| { + Ok(SerialPortType::UsbPort(port_info)) + }), + _ => Ok(SerialPortType::Unknown), + } +} + +#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] +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, } } - - Some(p) - } - - fn get_modalias_from_device(d: libudev::Device) -> Option { - Some( - d.property_value("MODALIAS") - .and_then(OsStr::to_str)? - .to_owned(), - ) } - - find_usb_interface_from_parents(d.parent()) - .and_then(get_modalias_from_device) - .as_deref() - .and_then(parse_modalias) - .map_or(Ok(SerialPortType::Unknown), |port_info| { - Ok(SerialPortType::UsbPort(port_info)) - }) } - _ => Ok(SerialPortType::Unknown), } + + Some(p) +} + +#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] +fn get_modalias_from_device(d: libudev::Device) -> Option { + Some( + d.property_value("MODALIAS") + .and_then(OsStr::to_str)? + .to_owned(), + ) } // MODALIAS = usb:v303Ap1001d0101dcEFdsc02dp01ic02isc02ip00in00 From bb4379739d0c572410b6cb2e1eb6b51d97f0e7ef Mon Sep 17 00:00:00 2001 From: Christian Meusel Date: Thu, 25 Jul 2024 23:03:58 +0200 Subject: [PATCH 5/5] Add changelog entry for MODALIAS as device information source --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28bc221..a70426e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ project adheres to [Semantic Versioning](https://semver.org/). * Add `IntoRawHandle` implementation for `COMPort` [#199](https://github.com/serialport/serialport-rs/pull/199) +* Add MODALIAS as additional source of information for USB devices on Linux + [#170](https://github.com/serialport/serialport-rs/pull/170) ### Changed ### Fixed