Skip to main content

lsusb/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod args;
6mod descriptors;
7
8use crate::args::{Args, UsbDevice};
9use crate::descriptors::*;
10use anyhow::{Context, Result, format_err};
11use fidl_fuchsia_io as fio;
12use fuchsia_async::TimeoutExt;
13use fuchsia_sync::Mutex;
14use futures::TryStreamExt;
15use futures::future::{BoxFuture, FutureExt};
16use zx_status as zx;
17
18// This isn't actually unused, but rustc can't seem to tell otherwise.
19#[allow(unused_imports)]
20use zerocopy::{IntoBytes, Ref};
21
22pub async fn lsusb(usb_device_dir: fio::DirectoryProxy, args: Args) -> Result<()> {
23    if args.tree {
24        list_tree(&usb_device_dir, &args).await
25    } else {
26        list_devices(&usb_device_dir, &args).await
27    }
28}
29
30async fn list_devices(usb_device_dir: &fio::DirectoryProxy, args: &Args) -> Result<()> {
31    let mut stream = device_watcher::watch_for_files(usb_device_dir).await?;
32
33    println!("ID    VID:PID   SPEED  MANUFACTURER PRODUCT");
34
35    while let Some(filename) = stream
36        .try_next()
37        // This will wait forever, so if there are no more devices, lets stop waiting.
38        .on_timeout(std::time::Duration::from_millis(200), || Ok(None))
39        .await
40        .context("FIDL call to get next device returned an error")?
41    {
42        let filename =
43            filename.to_str().ok_or_else(|| format_err!("to_str for filename failed"))?;
44
45        let (device, server_end) =
46            fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_usb_device::DeviceMarker>();
47        usb_device_dir.open(
48            &filename,
49            fio::Flags::PROTOCOL_SERVICE,
50            &Default::default(),
51            server_end.into_channel(),
52        )?;
53
54        match list_device(&device, filename, 0, 0, &args).await {
55            Ok(()) => {}
56            Err(e) => eprintln!("Error: {:?}", e),
57        }
58    }
59    Ok(())
60}
61async fn list_device(
62    device: &fidl_fuchsia_hardware_usb_device::DeviceProxy,
63    filename: &str,
64    depth: usize,
65    max_depth: usize,
66    args: &Args,
67) -> Result<()> {
68    let devname = &format!("/dev/class/usb-device/{:03}", filename);
69
70    let device_desc_buf = device
71        .get_device_descriptor()
72        .on_timeout(std::time::Duration::from_millis(200), || {
73            Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
74        })
75        .await
76        .context(format!("DeviceGetDeviceDescriptor failed for {}", devname))?;
77
78    let device_desc = Ref::<_, DeviceDescriptor>::from_bytes(device_desc_buf.as_ref()).unwrap();
79
80    if let Some(UsbDevice { vendor_id, product_id }) = args.device {
81        // Return early if this isn't the device that was asked about.
82        if { device_desc.id_vendor } != vendor_id {
83            return Ok(());
84        }
85        if product_id.is_some() && { device_desc.id_product } != product_id.unwrap() {
86            return Ok(());
87        }
88    }
89
90    let speed = device
91        .get_device_speed()
92        .on_timeout(std::time::Duration::from_millis(200), || {
93            Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
94        })
95        .await
96        .context(format!("DeviceGetDeviceSpeed failed for {}", devname))?;
97
98    let string_manu_desc = get_string_descriptor(device, device_desc.i_manufacturer)
99        .on_timeout(std::time::Duration::from_millis(200), || Err(format_err!("Timeout")))
100        .await
101        .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
102
103    let string_prod_desc = get_string_descriptor(device, device_desc.i_product)
104        .on_timeout(std::time::Duration::from_millis(200), || Err(format_err!("Timeout")))
105        .await
106        .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
107
108    let left_pad = depth * 4;
109    let right_pad = (max_depth - depth) * 4;
110
111    println!(
112        "{0:left_pad$}{1:03}  {0:right_pad$}{2:04X}:{3:04X}  {4:<5}  {5} {6}",
113        "",
114        filename,
115        { device_desc.id_vendor },
116        { device_desc.id_product },
117        UsbSpeed(speed),
118        string_manu_desc,
119        string_prod_desc,
120        left_pad = left_pad,
121        right_pad = right_pad,
122    );
123
124    if args.verbose {
125        println!("Device Descriptor:");
126        println!("  {:<33}{}", "bLength", device_desc.b_length);
127        println!("  {:<33}{}", "bDescriptorType", device_desc.b_descriptor_type);
128        println!("  {:<33}{}.{}", "bcdUSB", device_desc.bcd_usb >> 8, device_desc.bcd_usb & 0xFF);
129        println!("  {:<33}{}", "bDeviceClass", device_desc.b_device_class);
130        println!("  {:<33}{}", "bDeviceSubClass", device_desc.b_device_sub_class);
131        println!("  {:<33}{}", "bDeviceProtocol", device_desc.b_device_protocol);
132        println!("  {:<33}{}", "bMaxPacketSize0", device_desc.b_max_packet_size0);
133        println!("  {:<33}{:#06X}", "idVendor", { device_desc.id_vendor });
134        println!("  {:<33}{:#06X}", "idProduct", { device_desc.id_product });
135        println!(
136            "  {:<33}{}.{}",
137            "bcdDevice",
138            device_desc.bcd_device >> 8,
139            device_desc.bcd_device & 0xFF
140        );
141        println!("  {:<33}{} {}", "iManufacturer", device_desc.i_manufacturer, string_manu_desc);
142        println!("  {:<33}{} {}", "iProduct", device_desc.i_product, string_prod_desc);
143
144        let serial_number = get_string_descriptor(device, device_desc.i_serial_number)
145            .on_timeout(std::time::Duration::from_millis(200), || Err(format_err!("Timeout")))
146            .await
147            .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
148
149        println!("  {:<33}{} {}", "iSerialNumber", device_desc.i_serial_number, serial_number);
150        println!("  {:<33}{}", "bNumConfigurations", device_desc.b_num_configurations);
151
152        let mut config = args.configuration;
153        if config.is_none() {
154            config = Some(
155                device
156                    .get_configuration()
157                    .on_timeout(std::time::Duration::from_millis(200), || {
158                        Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
159                    })
160                    .await
161                    .context(format!("DeviceGetConfiguration failed for {}", devname))?,
162            );
163        }
164
165        let (status, config_desc_data) = device
166            .get_configuration_descriptor(config.unwrap())
167            .on_timeout(std::time::Duration::from_millis(200), || {
168                Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
169            })
170            .await
171            .context(format!("DeviceGetConfigurationDescriptor failed for {}", devname))?;
172
173        zx::Status::ok(status)
174            .map_err(|e| return anyhow::anyhow!("Failed to get configuration descriptor: {}", e))?;
175
176        for descriptor in DescriptorIterator::new(&config_desc_data) {
177            match descriptor {
178                Descriptor::Config(config_desc) => {
179                    println!("{:>2}Configuration Descriptor:", "");
180                    println!("{:>4}{:<31}{}", "", "bLength", config_desc.b_length);
181                    println!("{:>4}{:<31}{}", "", "bDescriptorType", config_desc.b_descriptor_type);
182                    println!("{:>4}{:<31}{}", "", "wTotalLength", { config_desc.w_total_length });
183                    println!("{:>4}{:<31}{}", "", "bNumInterfaces", config_desc.b_num_interfaces);
184                    println!(
185                        "{:>4}{:<31}{}",
186                        "", "bConfigurationValue", config_desc.b_configuration_value
187                    );
188                    let config_str = get_string_descriptor(device, config_desc.i_configuration)
189                        .on_timeout(std::time::Duration::from_millis(200), || {
190                            Err(format_err!("Timeout"))
191                        })
192                        .await
193                        .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
194                    println!(
195                        "{:>4}{:<31}{} {}",
196                        "", "iConfiguration", config_desc.i_configuration, config_str
197                    );
198                    println!("{:>4}{:<31}{:#04X}", "", "bmAttributes", config_desc.bm_attributes);
199                    println!("{:>4}{:<31}{}", "", "bMaxPower", config_desc.b_max_power);
200                }
201                Descriptor::Interface(info) => {
202                    println!("{:>4}Interface Descriptor:", "");
203                    println!("{:>6}{:<29}{}", "", "bLength", info.b_length);
204                    println!("{:>6}{:<29}{}", "", "bDescriptorType", info.b_descriptor_type);
205                    println!("{:>6}{:<29}{}", "", "bInterfaceNumber", info.b_interface_number);
206                    println!("{:>6}{:<29}{}", "", "bAlternateSetting", info.b_alternate_setting);
207                    println!("{:>6}{:<29}{}", "", "bNumEndpoints", info.b_num_endpoints);
208                    println!("{:>6}{:<29}{}", "", "bInterfaceClass", info.b_interface_class);
209                    println!("{:>6}{:<29}{}", "", "bInterfaceSubClass", info.b_interface_sub_class);
210                    println!("{:>6}{:<29}{}", "", "bInterfaceProtocol", info.b_interface_protocol);
211
212                    let interface_str = get_string_descriptor(device, info.i_interface)
213                        .on_timeout(std::time::Duration::from_millis(200), || {
214                            Err(format_err!("Timeout"))
215                        })
216                        .await
217                        .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
218                    println!("{:>6}{:<29}{} {}", "", "iInterface", info.i_interface, interface_str);
219                }
220                Descriptor::Endpoint(info) => {
221                    println!("{:>6}Endpoint Descriptor:", "");
222                    println!("{:>8}{:<27}{}", "", "bLength", info.b_length);
223                    println!("{:>8}{:<27}{}", "", "bDescriptorType", info.b_descriptor_type);
224                    println!("{:>8}{:<27}{:#04X}", "", "bEndpointAddress", info.b_endpoint_address);
225                    println!("{:>8}{:<27}{:#04X}", "", "bmAttributes", info.bm_attributes);
226                    println!("{:>8}{:<27}{}", "", "wMaxPacketSize", { info.w_max_packet_size });
227                    println!("{:>8}{:<27}{}", "", "bInterval", info.b_interval);
228                }
229                Descriptor::Hid(descriptor) => {
230                    let info = descriptor.get();
231                    println!("{:>6}HID Descriptor:", "");
232                    println!("{:>8}{:<27}{}", "", "bLength", info.b_length);
233                    println!("{:>8}{:<27}{}", "", "bDescriptorType", info.b_descriptor_type);
234                    println!(
235                        "{:>8}{:<27}{}{}",
236                        "",
237                        "bcdHID",
238                        info.bcd_hid >> 8,
239                        info.bcd_hid & 0xFF
240                    );
241                    println!("{:>8}{:<27}{}", "", "bCountryCode", info.b_country_code);
242                    println!("{:>8}{:<27}{}", "", "bNumDescriptors", info.b_num_descriptors);
243                    for entry in descriptor {
244                        println!("{:>10}{:<25}{}", "", "bDescriptorType", entry.b_descriptor_type);
245                        println!("{:>10}{:<25}{}", "", "wDescriptorLength", {
246                            entry.w_descriptor_length
247                        });
248                    }
249                }
250                Descriptor::SsEpCompanion(info) => {
251                    println!("{:>8}SuperSpeed Endpoint Companion Descriptor:", "");
252                    println!("{:>10}{:<25}{}", "", "bLength", info.b_length);
253                    println!("{:>10}{:<25}{}", "", "bDescriptorType", info.b_descriptor_type);
254                    println!("{:>10}{:<25}{:#04X}", "", "bMaxBurst", info.b_max_burst);
255                    println!("{:>10}{:<25}{:#04X}", "", "bmAttributes", info.bm_attributes);
256                    println!("{:>10}{:<25}{}", "", "wBytesPerInterval", info.w_bytes_per_interval);
257                }
258                Descriptor::SsIsochEpCompanion(info) => {
259                    println!("{:>10}SuperSpeed Isochronous Endpoint Companion Descriptor:", "");
260                    println!("{:>12}{:<23}{}", "", "bLength", info.b_length);
261                    println!("{:>12}{:<23}{}", "", "bDescriptorType", info.b_descriptor_type);
262                    println!("{:>12}{:<23}{}", "", "wReserved", { info.w_reserved });
263                    println!("{:>12}{:<23}{}", "", "dwBytesPerInterval", {
264                        info.dw_bytes_per_interval
265                    });
266                }
267                Descriptor::InterfaceAssociation(info) => {
268                    println!("{:>12}Interface Association Descriptor:", "");
269                    println!("{:>14}{:<21}{}", "", "bLength", info.b_length);
270                    println!("{:>14}{:<21}{}", "", "bDescriptorType", info.b_descriptor_type);
271                    println!("{:>14}{:<21}{}", "", "bFirstInterface", info.b_first_interface);
272                    println!("{:>14}{:<21}{}", "", "bInterfaceCount", info.b_interface_count);
273                    println!("{:>14}{:<21}{}", "", "bFunctionClass", info.b_function_class);
274                    println!("{:>14}{:<21}{}", "", "bFunctionSubClass", info.b_function_sub_class);
275                    println!("{:>14}{:<21}{}", "", "bFunctionProtocol", info.b_function_protocol);
276                    println!("{:>14}{:<21}{}", "", "iFunction", info.i_function);
277                }
278                Descriptor::Unknown(buffer) => {
279                    println!("Unknown Descriptor:");
280                    println!("  {:<33}{}", "bLength", buffer[0]);
281                    println!("  {:<33}{}", "bDescriptorType", buffer[1]);
282                    println!("  {:X?}", buffer);
283                }
284            }
285        }
286    }
287    return Ok(());
288}
289
290struct DeviceNode {
291    pub device: fidl_fuchsia_hardware_usb_device::DeviceProxy,
292    pub filename: String,
293    pub device_id: u32,
294    pub hub_id: u32,
295    // Depth in tree, None if not computed yet.
296    // Mutex is used for interior mutability.
297    pub depth: Mutex<Option<usize>>,
298}
299
300impl DeviceNode {
301    fn get_depth(&self, devices: &[DeviceNode]) -> Result<usize> {
302        if let Some(depth) = self.depth.lock().clone() {
303            return Ok(depth);
304        }
305        if self.hub_id == 0 {
306            return Ok(0);
307        }
308        for device in devices.iter() {
309            if self.hub_id == device.device_id {
310                return device.get_depth(devices).map(|depth| depth + 1);
311            }
312        }
313        Err(format_err!("Hub not found for device"))
314    }
315}
316
317async fn list_tree(usb_device_dir: &fio::DirectoryProxy, args: &Args) -> Result<()> {
318    let mut stream = device_watcher::watch_for_files(usb_device_dir).await?;
319    let mut devices = Vec::new();
320
321    while let Some(filename) = stream
322        .try_next()
323        // This will wait forever, so if there are no more devices, lets stop waiting.
324        .on_timeout(std::time::Duration::from_millis(200), || Ok(None))
325        .await
326        .context("FIDL call to get next device returned an error")?
327    {
328        let filename =
329            filename.to_str().ok_or_else(|| format_err!("to_str for filename failed"))?;
330
331        let (device, server_end) =
332            fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_usb_device::DeviceMarker>();
333        usb_device_dir.open(
334            &filename,
335            fio::Flags::PROTOCOL_SERVICE,
336            &Default::default(),
337            server_end.into_channel(),
338        )?;
339
340        devices.push(get_device_info(device, filename).await?);
341    }
342
343    for device in devices.iter() {
344        let depth = device.get_depth(&devices)?;
345        *device.depth.lock() = Some(depth);
346    }
347
348    let max_depth = devices
349        .iter()
350        .filter_map(|device| device.depth.lock().clone())
351        .fold(0, std::cmp::max::<usize>);
352
353    print!("ID   ");
354    for _ in 0..max_depth {
355        print!("    ");
356    }
357    println!(" VID:PID   SPEED  MANUFACTURER PRODUCT");
358
359    do_list_tree(&devices, 0, max_depth, args).await
360}
361
362fn do_list_tree<'a>(
363    devices: &'a [DeviceNode],
364    hub_id: u32,
365    max_depth: usize,
366    args: &'a Args,
367) -> BoxFuture<'a, Result<()>> {
368    async move {
369        for device in devices.iter() {
370            if device.hub_id == hub_id {
371                let depth = device.depth.lock().unwrap().clone();
372                match list_device(&device.device, &device.filename, depth, max_depth, args).await {
373                    Ok(()) => {}
374                    Err(e) => eprintln!("Error: {:?}", e),
375                }
376                do_list_tree(devices, device.device_id, max_depth, args).await?;
377            }
378        }
379        Ok(())
380    }
381    .boxed()
382}
383
384async fn get_device_info(
385    device: fidl_fuchsia_hardware_usb_device::DeviceProxy,
386    filename: &str,
387) -> Result<DeviceNode> {
388    let devname = &format!("/dev/class/usb-device/{:03}", filename);
389
390    let device_id =
391        device.get_device_id().await.context(format!("GetDeviceId failed for {}", devname))?;
392
393    let hub_id =
394        device.get_hub_device_id().await.context(format!("GeHubId failed for {}", devname))?;
395
396    let filename = filename.to_string();
397    Ok(DeviceNode { device, filename, device_id, hub_id, depth: Mutex::new(None) })
398}
399
400async fn get_string_descriptor(
401    device: &fidl_fuchsia_hardware_usb_device::DeviceProxy,
402    desc_id: u8,
403) -> Result<String, anyhow::Error> {
404    match desc_id {
405        0 => return Ok(String::from("UNKNOWN")),
406        _ => {
407            return device.get_string_descriptor(desc_id, EN_US).await.map(
408                |(status, value, _)| {
409                    if zx::Status::ok(status).is_ok() {
410                        Ok(value)
411                    } else {
412                        Ok(String::from("UNKNOWN"))
413                    }
414                },
415            )?;
416        }
417    };
418}
419
420#[cfg(test)]
421mod test {
422
423    use super::*;
424    use futures::prelude::*;
425
426    async fn run_usb_server(
427        stream: fidl_fuchsia_hardware_usb_device::DeviceRequestStream,
428    ) -> Result<(), anyhow::Error> {
429        stream
430            .map(|result| result.context("failed request"))
431            .try_for_each(|request| async {
432                match request {
433                fidl_fuchsia_hardware_usb_device::DeviceRequest::GetDeviceSpeed {responder } => {
434                    responder.send(1)?;
435                }
436                fidl_fuchsia_hardware_usb_device::DeviceRequest::GetDeviceDescriptor {
437                        responder} => {
438                    let descriptor = DeviceDescriptor {
439                        b_length: std::mem::size_of::<DeviceDescriptor>() as u8,
440                        b_descriptor_type: 1,
441                        bcd_usb: 2,
442                        b_device_class: 3,
443                        b_device_sub_class: 4,
444                        b_device_protocol: 5,
445                        b_max_packet_size0: 6,
446                        id_vendor: 7,
447                        id_product: 8,
448                        bcd_device: 9,
449                        i_manufacturer: 10,
450                        i_product: 11,
451                        i_serial_number: 12,
452                        b_num_configurations: 2,
453                    };
454                    let mut array = [0; 18];
455                    array.copy_from_slice(descriptor.as_bytes());
456                    responder.send(&array)?;
457                }
458                fidl_fuchsia_hardware_usb_device::DeviceRequest::GetConfigurationDescriptor {
459                        config: _, responder} => {
460                    let total_length =
461                        std::mem::size_of::<ConfigurationDescriptor>() +
462                        std::mem::size_of::<InterfaceInfoDescriptor>() * 2;
463
464                    let config_descriptor = ConfigurationDescriptor {
465                        b_length: std::mem::size_of::<ConfigurationDescriptor>() as u8,
466                        b_descriptor_type: 0,
467                        w_total_length: total_length as u16,
468                        b_num_interfaces: 2,
469                        b_configuration_value: 0,
470                        i_configuration: 0,
471                        bm_attributes: 0,
472                        b_max_power: 0,
473                    };
474
475                    let interface_one = InterfaceInfoDescriptor {
476                        b_length: std::mem::size_of::<InterfaceInfoDescriptor>() as u8,
477                        b_descriptor_type: 4,
478                        b_interface_number: 1,
479                        b_alternate_setting: 0,
480                        b_num_endpoints: 0,
481                        b_interface_class: 1,
482                        b_interface_sub_class: 2,
483                        b_interface_protocol: 3,
484                        i_interface: 1,
485                    };
486
487                    let interface_two = InterfaceInfoDescriptor {
488                        b_length: std::mem::size_of::<InterfaceInfoDescriptor>() as u8,
489                        b_descriptor_type: 4,
490                        b_interface_number: 2,
491                        b_alternate_setting: 0,
492                        b_num_endpoints: 0,
493                        b_interface_class: 3,
494                        b_interface_sub_class: 4,
495                        b_interface_protocol: 5,
496                        i_interface: 2,
497                    };
498
499                    let mut vec = std::vec::Vec::new();
500                    vec.extend_from_slice(config_descriptor.as_bytes());
501                    vec.extend_from_slice(interface_one.as_bytes());
502                    vec.extend_from_slice(interface_two.as_bytes());
503
504                    responder.send(0, &vec)?;
505                }
506                fidl_fuchsia_hardware_usb_device::DeviceRequest::GetStringDescriptor {
507                        desc_id: _, lang_id: _, responder} => {
508                    responder.send(0, "<unknown>", 0)?;
509                }
510                fidl_fuchsia_hardware_usb_device::DeviceRequest::GetConfiguration {responder} => {
511                    responder.send(0)?;
512                }
513                _ => {
514                    return Err(anyhow::anyhow!("Unsupported function"));
515                }
516            }
517                Ok(())
518            })
519            .await?;
520        Ok(())
521    }
522
523    #[fuchsia::test]
524    async fn smoke_test() {
525        let (device, stream) = fidl::endpoints::create_proxy_and_stream::<
526            fidl_fuchsia_hardware_usb_device::DeviceMarker,
527        >();
528
529        let server_task = run_usb_server(stream).fuse();
530        let test_task = async move {
531            let args = Args { tree: false, verbose: true, configuration: None, device: None };
532            println!("ID    VID:PID   SPEED  MANUFACTURER PRODUCT");
533            list_device(&device, "", 0, 0, &args).await.unwrap();
534        }
535        .fuse();
536        futures::pin_mut!(server_task, test_task);
537        futures::select! {
538            result = server_task => {
539                panic!("Server task finished: {:?}", result);
540            },
541            () = test_task => {},
542        }
543    }
544}