|
1 | 1 | use cfg_if::cfg_if; |
2 | 2 |
|
3 | | -#[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))] |
4 | | -use std::ffi::OsStr; |
| 3 | +cfg_if! { |
| 4 | + if #[cfg(all(target_os = "linux", not(target_env = "musl"), feature = "libudev"))]{ |
| 5 | + use std::ffi::OsStr; |
| 6 | + use regex::Regex; |
| 7 | + } |
| 8 | +} |
5 | 9 |
|
6 | 10 | cfg_if! { |
7 | 11 | if #[cfg(any(target_os = "ios", target_os = "macos"))] { |
@@ -175,22 +179,81 @@ fn port_type(d: &libudev::Device) -> Result<SerialPortType> { |
175 | 179 | } |
176 | 180 | } |
177 | 181 | None => { |
178 | | - let p = d.parent().unwrap(); |
179 | | - let parent_driver = p.driver().unwrap().to_str().unwrap(); |
180 | | - let parent_subsystem = p.subsystem().unwrap().to_str().unwrap(); |
| 182 | + fn find_usb_interface_from_parents( |
| 183 | + parent: Option<libudev::Device>, |
| 184 | + ) -> Option<libudev::Device> { |
| 185 | + let mut p = parent?; |
| 186 | + |
| 187 | + // limit the query depth |
| 188 | + for _ in 1..4 { |
| 189 | + match p.devtype() { |
| 190 | + None => match p.parent() { |
| 191 | + None => break, |
| 192 | + Some(x) => p = x, |
| 193 | + }, |
| 194 | + Some(s) => { |
| 195 | + if s.to_str()? == "usb_interface" { |
| 196 | + break; |
| 197 | + } else { |
| 198 | + match p.parent() { |
| 199 | + None => break, |
| 200 | + Some(x) => p = x, |
| 201 | + } |
| 202 | + } |
| 203 | + } |
| 204 | + } |
| 205 | + } |
181 | 206 |
|
182 | | - if parent_driver == "cdc_acm" && parent_subsystem == "usb" { |
183 | | - let product_code = p.property_value("PRODUCT").and_then(OsStr::to_str).unwrap(); |
184 | | - Ok(SerialPortType::UsbPort(UsbPortInfo { |
185 | | - vid: u16::from_str_radix(&product_code[0..4], 16).unwrap(), |
186 | | - pid: u16::from_str_radix(&product_code[5..9], 16).unwrap(), |
| 207 | + Some(p) |
| 208 | + } |
| 209 | + |
| 210 | + fn get_modalias_from_device(d: libudev::Device) -> Option<String> { |
| 211 | + Some( |
| 212 | + d.property_value("MODALIAS") |
| 213 | + .and_then(OsStr::to_str)? |
| 214 | + .to_owned(), |
| 215 | + ) |
| 216 | + } |
| 217 | + |
| 218 | + // MODALIAS = usb:v303Ap1001d0101dcEFdsc02dp01ic02isc02ip00in00 |
| 219 | + // v 303A (device vendor) |
| 220 | + // p 1001 (device product) |
| 221 | + // d 0101 (bcddevice) |
| 222 | + // dc EF (device class) |
| 223 | + // dsc 02 (device subclass) |
| 224 | + // dp 01 (device protocol) |
| 225 | + // ic 02 (interface class) |
| 226 | + // isc 02 (interface subclass) |
| 227 | + // ip 00 (interface protocol) |
| 228 | + // in 00 (interface number) |
| 229 | + fn parse_modalias(moda: String) -> Option<UsbPortInfo> { |
| 230 | + let re = Regex::new(concat!( |
| 231 | + r"usb:v(?P<vid>[[:xdigit:]]{4})", |
| 232 | + r"p(?P<pid>[[:xdigit:]]{4})", |
| 233 | + r".*", |
| 234 | + r"in(?P<in>[[:xdigit:]]{2})" |
| 235 | + )) |
| 236 | + .unwrap(); |
| 237 | + |
| 238 | + let caps = re.captures(moda.as_str())?; |
| 239 | + |
| 240 | + Some(UsbPortInfo { |
| 241 | + vid: u16::from_str_radix(&caps["vid"], 16).ok()?, |
| 242 | + pid: u16::from_str_radix(&caps["pid"], 16).ok()?, |
187 | 243 | serial_number: None, |
188 | 244 | manufacturer: None, |
189 | 245 | product: None, |
190 | | - })) |
191 | | - } else { |
192 | | - Ok(SerialPortType::Unknown) |
| 246 | + #[cfg(feature = "usbportinfo-interface")] |
| 247 | + interface: u8::from_str_radix(&caps["in"], 16).ok(), |
| 248 | + }) |
193 | 249 | } |
| 250 | + |
| 251 | + find_usb_interface_from_parents(d.parent()) |
| 252 | + .and_then(get_modalias_from_device) |
| 253 | + .and_then(parse_modalias) |
| 254 | + .map_or(Ok(SerialPortType::Unknown), |port_info| { |
| 255 | + Ok(SerialPortType::UsbPort(port_info)) |
| 256 | + }) |
194 | 257 | } |
195 | 258 | _ => Ok(SerialPortType::Unknown), |
196 | 259 | } |
|
0 commit comments