1pub mod args;
6mod descriptors;
7
8use crate::args::{Args, UsbDevice};
9use crate::descriptors::*;
10use anyhow::{format_err, Context, Result};
11use fuchsia_async::TimeoutExt;
12use futures::future::{BoxFuture, FutureExt};
13use futures::TryStreamExt;
14use std::sync::Mutex;
15use {fidl_fuchsia_io as fio, zx_status as zx};
16
17#[allow(unused_imports)]
19use zerocopy::{IntoBytes, Ref};
20
21pub async fn lsusb(usb_device_dir: fio::DirectoryProxy, args: Args) -> Result<()> {
22 if args.tree {
23 list_tree(&usb_device_dir, &args).await
24 } else {
25 list_devices(&usb_device_dir, &args).await
26 }
27}
28
29async fn list_devices(usb_device_dir: &fio::DirectoryProxy, args: &Args) -> Result<()> {
30 let mut stream = device_watcher::watch_for_files(usb_device_dir).await?;
31
32 println!("ID VID:PID SPEED MANUFACTURER PRODUCT");
33
34 while let Some(filename) = stream
35 .try_next()
36 .on_timeout(std::time::Duration::from_millis(200), || Ok(None))
38 .await
39 .context("FIDL call to get next device returned an error")?
40 {
41 let filename =
42 filename.to_str().ok_or_else(|| format_err!("to_str for filename failed"))?;
43
44 let (device, server_end) =
45 fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_usb_device::DeviceMarker>();
46 usb_device_dir.open(
47 &filename,
48 fio::Flags::PROTOCOL_SERVICE,
49 &Default::default(),
50 server_end.into_channel(),
51 )?;
52
53 match list_device(&device, filename, 0, 0, &args).await {
54 Ok(()) => {}
55 Err(e) => eprintln!("Error: {:?}", e),
56 }
57 }
58 Ok(())
59}
60async fn list_device(
61 device: &fidl_fuchsia_hardware_usb_device::DeviceProxy,
62 filename: &str,
63 depth: usize,
64 max_depth: usize,
65 args: &Args,
66) -> Result<()> {
67 let devname = &format!("/dev/class/usb-device/{:03}", filename);
68
69 let device_desc_buf = device
70 .get_device_descriptor()
71 .on_timeout(std::time::Duration::from_millis(200), || {
72 Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
73 })
74 .await
75 .context(format!("DeviceGetDeviceDescriptor failed for {}", devname))?;
76
77 let device_desc = Ref::<_, DeviceDescriptor>::from_bytes(device_desc_buf.as_ref()).unwrap();
78
79 if let Some(UsbDevice { vendor_id, product_id }) = args.device {
80 if { device_desc.id_vendor } != vendor_id {
82 return Ok(());
83 }
84 if product_id.is_some() && { device_desc.id_product } != product_id.unwrap() {
85 return Ok(());
86 }
87 }
88
89 let speed = device
90 .get_device_speed()
91 .on_timeout(std::time::Duration::from_millis(200), || {
92 Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
93 })
94 .await
95 .context(format!("DeviceGetDeviceSpeed failed for {}", devname))?;
96
97 let string_manu_desc = get_string_descriptor(device, device_desc.i_manufacturer)
98 .on_timeout(std::time::Duration::from_millis(200), || Err(format_err!("Timeout")))
99 .await
100 .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
101
102 let string_prod_desc = get_string_descriptor(device, device_desc.i_product)
103 .on_timeout(std::time::Duration::from_millis(200), || Err(format_err!("Timeout")))
104 .await
105 .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
106
107 let left_pad = depth * 4;
108 let right_pad = (max_depth - depth) * 4;
109
110 println!(
111 "{0:left_pad$}{1:03} {0:right_pad$}{2:04X}:{3:04X} {4:<5} {5} {6}",
112 "",
113 filename,
114 { device_desc.id_vendor },
115 { device_desc.id_product },
116 UsbSpeed(speed),
117 string_manu_desc,
118 string_prod_desc,
119 left_pad = left_pad,
120 right_pad = right_pad,
121 );
122
123 if args.verbose {
124 println!("Device Descriptor:");
125 println!(" {:<33}{}", "bLength", device_desc.b_length);
126 println!(" {:<33}{}", "bDescriptorType", device_desc.b_descriptor_type);
127 println!(" {:<33}{}.{}", "bcdUSB", device_desc.bcd_usb >> 8, device_desc.bcd_usb & 0xFF);
128 println!(" {:<33}{}", "bDeviceClass", device_desc.b_device_class);
129 println!(" {:<33}{}", "bDeviceSubClass", device_desc.b_device_sub_class);
130 println!(" {:<33}{}", "bDeviceProtocol", device_desc.b_device_protocol);
131 println!(" {:<33}{}", "bMaxPacketSize0", device_desc.b_max_packet_size0);
132 println!(" {:<33}{:#06X}", "idVendor", { device_desc.id_vendor });
133 println!(" {:<33}{:#06X}", "idProduct", { device_desc.id_product });
134 println!(
135 " {:<33}{}.{}",
136 "bcdDevice",
137 device_desc.bcd_device >> 8,
138 device_desc.bcd_device & 0xFF
139 );
140 println!(" {:<33}{} {}", "iManufacturer", device_desc.i_manufacturer, string_manu_desc);
141 println!(" {:<33}{} {}", "iProduct", device_desc.i_product, string_prod_desc);
142
143 let serial_number = get_string_descriptor(device, device_desc.i_serial_number)
144 .on_timeout(std::time::Duration::from_millis(200), || Err(format_err!("Timeout")))
145 .await
146 .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
147
148 println!(" {:<33}{} {}", "iSerialNumber", device_desc.i_serial_number, serial_number);
149 println!(" {:<33}{}", "bNumConfigurations", device_desc.b_num_configurations);
150
151 let mut config = args.configuration;
152 if config.is_none() {
153 config = Some(
154 device
155 .get_configuration()
156 .on_timeout(std::time::Duration::from_millis(200), || {
157 Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
158 })
159 .await
160 .context(format!("DeviceGetConfiguration failed for {}", devname))?,
161 );
162 }
163
164 let (status, config_desc_data) = device
165 .get_configuration_descriptor(config.unwrap())
166 .on_timeout(std::time::Duration::from_millis(200), || {
167 Err(fidl::Error::ClientRead(zx::Status::TIMED_OUT.into()))
168 })
169 .await
170 .context(format!("DeviceGetConfigurationDescriptor failed for {}", devname))?;
171
172 zx::Status::ok(status)
173 .map_err(|e| return anyhow::anyhow!("Failed to get configuration descriptor: {}", e))?;
174
175 for descriptor in DescriptorIterator::new(&config_desc_data) {
176 match descriptor {
177 Descriptor::Config(config_desc) => {
178 println!("{:>2}Configuration Descriptor:", "");
179 println!("{:>4}{:<31}{}", "", "bLength", config_desc.b_length);
180 println!("{:>4}{:<31}{}", "", "bDescriptorType", config_desc.b_descriptor_type);
181 println!("{:>4}{:<31}{}", "", "wTotalLength", { config_desc.w_total_length });
182 println!("{:>4}{:<31}{}", "", "bNumInterfaces", config_desc.b_num_interfaces);
183 println!(
184 "{:>4}{:<31}{}",
185 "", "bConfigurationValue", config_desc.b_configuration_value
186 );
187 let config_str = get_string_descriptor(device, config_desc.i_configuration)
188 .on_timeout(std::time::Duration::from_millis(200), || {
189 Err(format_err!("Timeout"))
190 })
191 .await
192 .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
193 println!(
194 "{:>4}{:<31}{} {}",
195 "", "iConfiguration", config_desc.i_configuration, config_str
196 );
197 println!("{:>4}{:<31}{:#04X}", "", "bmAttributes", config_desc.bm_attributes);
198 println!("{:>4}{:<31}{}", "", "bMaxPower", config_desc.b_max_power);
199 }
200 Descriptor::Interface(info) => {
201 println!("{:>4}Interface Descriptor:", "");
202 println!("{:>6}{:<29}{}", "", "bLength", info.b_length);
203 println!("{:>6}{:<29}{}", "", "bDescriptorType", info.b_descriptor_type);
204 println!("{:>6}{:<29}{}", "", "bInterfaceNumber", info.b_interface_number);
205 println!("{:>6}{:<29}{}", "", "bAlternateSetting", info.b_alternate_setting);
206 println!("{:>6}{:<29}{}", "", "bNumEndpoints", info.b_num_endpoints);
207 println!("{:>6}{:<29}{}", "", "bInterfaceClass", info.b_interface_class);
208 println!("{:>6}{:<29}{}", "", "bInterfaceSubClass", info.b_interface_sub_class);
209 println!("{:>6}{:<29}{}", "", "bInterfaceProtocol", info.b_interface_protocol);
210
211 let interface_str = get_string_descriptor(device, info.i_interface)
212 .on_timeout(std::time::Duration::from_millis(200), || {
213 Err(format_err!("Timeout"))
214 })
215 .await
216 .context(format!("DeviceGetStringDescriptor failed for {}", devname))?;
217 println!("{:>6}{:<29}{} {}", "", "iInterface", info.i_interface, interface_str);
218 }
219 Descriptor::Endpoint(info) => {
220 println!("{:>6}Endpoint Descriptor:", "");
221 println!("{:>8}{:<27}{}", "", "bLength", info.b_length);
222 println!("{:>8}{:<27}{}", "", "bDescriptorType", info.b_descriptor_type);
223 println!("{:>8}{:<27}{:#04X}", "", "bEndpointAddress", info.b_endpoint_address);
224 println!("{:>8}{:<27}{:#04X}", "", "bmAttributes", info.bm_attributes);
225 println!("{:>8}{:<27}{}", "", "wMaxPacketSize", { info.w_max_packet_size });
226 println!("{:>8}{:<27}{}", "", "bInterval", info.b_interval);
227 }
228 Descriptor::Hid(descriptor) => {
229 let info = descriptor.get();
230 println!("{:>6}HID Descriptor:", "");
231 println!("{:>8}{:<27}{}", "", "bLength", info.b_length);
232 println!("{:>8}{:<27}{}", "", "bDescriptorType", info.b_descriptor_type);
233 println!(
234 "{:>8}{:<27}{}{}",
235 "",
236 "bcdHID",
237 info.bcd_hid >> 8,
238 info.bcd_hid & 0xFF
239 );
240 println!("{:>8}{:<27}{}", "", "bCountryCode", info.b_country_code);
241 println!("{:>8}{:<27}{}", "", "bNumDescriptors", info.b_num_descriptors);
242 for entry in descriptor {
243 println!("{:>10}{:<25}{}", "", "bDescriptorType", entry.b_descriptor_type);
244 println!("{:>10}{:<25}{}", "", "wDescriptorLength", {
245 entry.w_descriptor_length
246 });
247 }
248 }
249 Descriptor::SsEpCompanion(info) => {
250 println!("{:>8}SuperSpeed Endpoint Companion Descriptor:", "");
251 println!("{:>10}{:<25}{}", "", "bLength", info.b_length);
252 println!("{:>10}{:<25}{}", "", "bDescriptorType", info.b_descriptor_type);
253 println!("{:>10}{:<25}{:#04X}", "", "bMaxBurst", info.b_max_burst);
254 println!("{:>10}{:<25}{:#04X}", "", "bmAttributes", info.bm_attributes);
255 println!("{:>10}{:<25}{}", "", "wBytesPerInterval", info.w_bytes_per_interval);
256 }
257 Descriptor::SsIsochEpCompanion(info) => {
258 println!("{:>10}SuperSpeed Isochronous Endpoint Companion Descriptor:", "");
259 println!("{:>12}{:<23}{}", "", "bLength", info.b_length);
260 println!("{:>12}{:<23}{}", "", "bDescriptorType", info.b_descriptor_type);
261 println!("{:>12}{:<23}{}", "", "wReserved", { info.w_reserved });
262 println!("{:>12}{:<23}{}", "", "dwBytesPerInterval", {
263 info.dw_bytes_per_interval
264 });
265 }
266 Descriptor::InterfaceAssociation(info) => {
267 println!("{:>12}Interface Association Descriptor:", "");
268 println!("{:>14}{:<21}{}", "", "bLength", info.b_length);
269 println!("{:>14}{:<21}{}", "", "bDescriptorType", info.b_descriptor_type);
270 println!("{:>14}{:<21}{}", "", "bFirstInterface", info.b_first_interface);
271 println!("{:>14}{:<21}{}", "", "bInterfaceCount", info.b_interface_count);
272 println!("{:>14}{:<21}{}", "", "bFunctionClass", info.b_function_class);
273 println!("{:>14}{:<21}{}", "", "bFunctionSubClass", info.b_function_sub_class);
274 println!("{:>14}{:<21}{}", "", "bFunctionProtocol", info.b_function_protocol);
275 println!("{:>14}{:<21}{}", "", "iFunction", info.i_function);
276 }
277 Descriptor::Unknown(buffer) => {
278 println!("Unknown Descriptor:");
279 println!(" {:<33}{}", "bLength", buffer[0]);
280 println!(" {:<33}{}", "bDescriptorType", buffer[1]);
281 println!(" {:X?}", buffer);
282 }
283 }
284 }
285 }
286 return Ok(());
287}
288
289struct DeviceNode {
290 pub device: fidl_fuchsia_hardware_usb_device::DeviceProxy,
291 pub filename: String,
292 pub device_id: u32,
293 pub hub_id: u32,
294 pub depth: Mutex<Option<usize>>,
297}
298
299impl DeviceNode {
300 fn get_depth(&self, devices: &[DeviceNode]) -> Result<usize> {
301 if let Some(depth) = self.depth.lock().unwrap().clone() {
302 return Ok(depth);
303 }
304 if self.hub_id == 0 {
305 return Ok(0);
306 }
307 for device in devices.iter() {
308 if self.hub_id == device.device_id {
309 return device.get_depth(devices).map(|depth| depth + 1);
310 }
311 }
312 Err(format_err!("Hub not found for device"))
313 }
314}
315
316async fn list_tree(usb_device_dir: &fio::DirectoryProxy, args: &Args) -> Result<()> {
317 let mut stream = device_watcher::watch_for_files(usb_device_dir).await?;
318 let mut devices = Vec::new();
319
320 while let Some(filename) = stream
321 .try_next()
322 .on_timeout(std::time::Duration::from_millis(200), || Ok(None))
324 .await
325 .context("FIDL call to get next device returned an error")?
326 {
327 let filename =
328 filename.to_str().ok_or_else(|| format_err!("to_str for filename failed"))?;
329
330 let (device, server_end) =
331 fidl::endpoints::create_proxy::<fidl_fuchsia_hardware_usb_device::DeviceMarker>();
332 usb_device_dir.open(
333 &filename,
334 fio::Flags::PROTOCOL_SERVICE,
335 &Default::default(),
336 server_end.into_channel(),
337 )?;
338
339 devices.push(get_device_info(device, filename).await?);
340 }
341
342 for device in devices.iter() {
343 let depth = device.get_depth(&devices)?;
344 *device.depth.lock().unwrap() = Some(depth);
345 }
346
347 let max_depth = devices
348 .iter()
349 .filter_map(|device| device.depth.lock().unwrap().clone())
350 .fold(0, std::cmp::max::<usize>);
351
352 print!("ID ");
353 for _ in 0..max_depth {
354 print!(" ");
355 }
356 println!(" VID:PID SPEED MANUFACTURER PRODUCT");
357
358 do_list_tree(&devices, 0, max_depth, args).await
359}
360
361fn do_list_tree<'a>(
362 devices: &'a [DeviceNode],
363 hub_id: u32,
364 max_depth: usize,
365 args: &'a Args,
366) -> BoxFuture<'a, Result<()>> {
367 async move {
368 for device in devices.iter() {
369 if device.hub_id == hub_id {
370 let depth = device.depth.lock().unwrap().unwrap().clone();
371 match list_device(&device.device, &device.filename, depth, max_depth, args).await {
372 Ok(()) => {}
373 Err(e) => eprintln!("Error: {:?}", e),
374 }
375 do_list_tree(devices, device.device_id, max_depth, args).await?;
376 }
377 }
378 Ok(())
379 }
380 .boxed()
381}
382
383async fn get_device_info(
384 device: fidl_fuchsia_hardware_usb_device::DeviceProxy,
385 filename: &str,
386) -> Result<DeviceNode> {
387 let devname = &format!("/dev/class/usb-device/{:03}", filename);
388
389 let device_id =
390 device.get_device_id().await.context(format!("GetDeviceId failed for {}", devname))?;
391
392 let hub_id =
393 device.get_hub_device_id().await.context(format!("GeHubId failed for {}", devname))?;
394
395 let filename = filename.to_string();
396 Ok(DeviceNode { device, filename, device_id, hub_id, depth: Mutex::new(None) })
397}
398
399async fn get_string_descriptor(
400 device: &fidl_fuchsia_hardware_usb_device::DeviceProxy,
401 desc_id: u8,
402) -> Result<String, anyhow::Error> {
403 match desc_id {
404 0 => return Ok(String::from("UNKNOWN")),
405 _ => {
406 return device.get_string_descriptor(desc_id, EN_US).await.map(
407 |(status, value, _)| {
408 if zx::Status::ok(status).is_ok() {
409 Ok(value)
410 } else {
411 Ok(String::from("UNKNOWN"))
412 }
413 },
414 )?
415 }
416 };
417}
418
419#[cfg(test)]
420mod test {
421
422 use super::*;
423 use fuchsia_async as fasync;
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 #[fasync::run_singlethreaded(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}