From d02b5cb14fbb6ae894595dd3249508c13559993e Mon Sep 17 00:00:00 2001
From: Madhav <[email protected]>
Date: Mon, 20 Oct 2025 18:30:33 -0700
Subject: [PATCH] lsusb: Add -v (verbose) option for detailed device
 descriptors

The -v option is added to 'lsusb' to provide detailed output about connected USB devices, similar to standard 'lsusb -v'.

This verbose output includes:
- Device, Configuration, Interface, and Endpoint descriptors.
- Detailed information like bcdUSB, bDeviceClass, bMaxPacketSize0, idVendor/idProduct, MaxPower, transfer types, synchronization types, and usage types.
- Reads manufacturer, product, and serial strings from the device sysfs entries for better human readability.

This requires:
- Adding 'v' to the NEWTOY macro for lsusb.
- Implementing the list_usb_verbose() function to parse and display the descriptor binary data.
- Moving the 'usb_buses' global variable into the GLOBALS structure (TT.usb_buses) for consistency, as it's only used with the -t flag.
---
 toys/other/lsusb.c | 170 ++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 162 insertions(+), 8 deletions(-)

diff --git a/toys/other/lsusb.c b/toys/other/lsusb.c
index 48533b90..e5a82ba4 100644
--- a/toys/other/lsusb.c
+++ b/toys/other/lsusb.c
@@ -3,7 +3,7 @@
  * Copyright 2013 Andre Renaud <[email protected]>
  * Copyright 2013 Isaac Dunham <[email protected]>
 
-USE_LSUSB(NEWTOY(lsusb, "ti:", TOYFLAG_USR|TOYFLAG_BIN))
+USE_LSUSB(NEWTOY(lsusb, "vti:", TOYFLAG_USR|TOYFLAG_BIN))
 USE_LSPCI(NEWTOY(lspci, "eDmkn@x@i:", TOYFLAG_USR|TOYFLAG_BIN))
 
 config LSPCI
@@ -26,10 +26,11 @@ config LSUSB
   bool "lsusb"
   default y
   help
-    usage: lsusb [-ti]
+    usage: lsusb [-vti]
 
     List USB hosts/devices.
 
+    -v	Verbose
     -t	Tree format
     -i	ID database (default /etc/usb.ids[.gz])
 */
@@ -43,6 +44,7 @@ GLOBALS(
 
   void *ids, *class;
   int count;
+  struct usb_bus *usb_buses;
 )
 
 // Structures for tree display
@@ -193,9 +195,6 @@ static int list_usb(struct dirtree *new)
   return 0;
 }
 
-// Tree display functions
-static struct usb_bus *usb_buses = NULL;
-
 static char *readat(int dir, char *name)
 {
   off_t len = sizeof(toybuf);
@@ -203,6 +202,159 @@ static char *readat(int dir, char *name)
   return readfileat(dir, name, toybuf, &len) ? : "";
 }
 
+static void print_device_descriptor(unsigned char *p, int vid, int pid,
+  char *n1, char *n2, char *s_man, char *s_prod, char *s_ser)
+{
+  uint16_t bcdUSB = le16toh(*(uint16_t *)(p+2));
+  uint16_t bcdDevice = le16toh(*(uint16_t *)(p+12));
+  char *class_name = "", *sub_name = "";
+
+  printf("Device Descriptor:\n");
+  printf("  bLength                %2u\n", p[0]);
+  printf("  bDescriptorType         %2u\n", p[1]);
+  printf("  bcdUSB               %x.%02x\n", bcdUSB >> 8, bcdUSB & 0xff);
+  get_names((struct dev_ids *)TT.class, p[4], p[5], &class_name, &sub_name);
+
+  char *dev_proto_str = "";
+  if (p[4] == 9 && p[5] == 0 && p[6] == 1) dev_proto_str = "Single TT";
+  printf("  bDeviceClass            %d %s\n", p[4], class_name);
+  printf("  bDeviceSubClass         %d\n", p[5]);
+  printf("  bDeviceProtocol         %d %s\n", p[6], dev_proto_str);
+  printf("  bMaxPacketSize0        %2u\n", p[7]);
+  printf("  idVendor           0x%04x %s\n", vid, n1);
+  printf("  idProduct          0x%04x %s\n", pid, n2);
+  printf("  bcdDevice            %x.%02x\n", bcdDevice >> 8, bcdDevice & 0xff);
+  printf("  iManufacturer           %d %s\n", p[14], s_man);
+  printf("  iProduct                %d %s\n", p[15], s_prod);
+  printf("  iSerial                 %d %s\n", p[16], s_ser);
+  printf("  bNumConfigurations      %d\n", p[17]);
+}
+
+static void print_config_descriptor(unsigned char *p)
+{
+  uint16_t total_len = le16toh(*(uint16_t *)(p+2));
+
+  printf("  Configuration Descriptor:\n");
+  printf("    bLength                 %d\n", p[0]);
+  printf("    bDescriptorType         %d\n", p[1]);
+  printf("    wTotalLength       0x%04x\n", total_len);
+  printf("    bNumInterfaces          %d\n", p[4]);
+  printf("    bConfigurationValue     %d\n", p[5]);
+  printf("    iConfiguration          %d\n", p[6]);
+  printf("    bmAttributes         0x%02x\n", p[7]);
+  if (p[7] & 0x40) printf("      Self Powered\n");
+  if (p[7] & 0x20) printf("      Remote Wakeup\n");
+  printf("    MaxPower              %dmA\n", p[8]*2);
+}
+
+static void print_interface_descriptor(unsigned char *p)
+{
+  char *class_name = "", *sub_name = "";
+
+  get_names((struct dev_ids *)TT.class, p[5], p[6], &class_name, &sub_name);
+  char *if_proto_str = "";
+  if (p[5] == 9 && p[6] == 0 && p[7] == 0) if_proto_str = "Full speed (or root) hub";
+
+  printf("    Interface Descriptor:\n");
+  printf("      bLength                 %d\n", p[0]);
+  printf("      bDescriptorType         %d\n", p[1]);
+  printf("      bInterfaceNumber        %d\n", p[2]);
+  printf("      bAlternateSetting       %d\n", p[3]);
+  printf("      bNumEndpoints           %d\n", p[4]);
+  printf("      bInterfaceClass         %d %s\n", p[5], class_name);
+  printf("      bInterfaceSubClass      %d\n", p[6]);
+  printf("      bInterfaceProtocol      %d %s\n", p[7], if_proto_str);
+  printf("      iInterface              %d\n", p[8]);
+}
+
+static void print_endpoint_descriptor(unsigned char *p)
+{
+  uint16_t max_packet = le16toh(*(uint16_t *)(p+4));
+  int num_transactions = ((max_packet>>11)&3)+1;
+  int packet_size = max_packet & 0x7ff;
+  char *transfer_types[] = {"Control", "Isochronous", "Bulk", "Interrupt"};
+  char *synch_types[] = {"None", "Asynchronous", "Adaptive", "Synchronous"};
+  char *usage_types[] = {"Data", "Feedback", "Implicit Feedback", "Reserved"};
+
+  printf("      Endpoint Descriptor:\n");
+  printf("        bLength                 %d\n", p[0]);
+  printf("        bDescriptorType         %d\n", p[1]);
+  printf("        bEndpointAddress     0x%02x  EP %d %s\n", p[2], p[2] & 0x0f, (p[2] & 0x80) ? "IN" : "OUT");
+  printf("        bmAttributes            %d\n", p[3]);
+  printf("          Transfer Type            %s\n", transfer_types[p[3] & 0x03]);
+  printf("          Synch Type               %s\n", synch_types[(p[3] >> 2) & 0x03]);
+  printf("          Usage Type               %s\n", usage_types[(p[3] >> 4) & 0x03]);
+  printf("        wMaxPacketSize     0x%04x  %dx %d bytes\n", max_packet, num_transactions, packet_size);
+  printf("        bInterval              %d\n", p[6]);
+}
+
+static int list_usb_verbose(struct dirtree *new)
+{
+  int busnum = 0, devnum = 0, pid = 0, vid = 0;
+  char *n1, *n2;
+  char *path = NULL;
+  off_t file_len = sizeof(toybuf);
+  int len, fd;
+  unsigned char *descriptors_data;
+  unsigned char *p = (unsigned char *)toybuf;
+
+  if (!new->parent) return DIRTREE_RECURSE;
+  if (7 != scan_uevent(new, 3, (struct scanloop[]){{"BUSNUM=%u", &busnum, 0},
+    {"DEVNUM=%u", &devnum, 0}, {"PRODUCT=%x/%x", &pid, &vid}}))
+    return 0;
+
+  get_names(TT.ids, pid, vid, &n1, &n2);
+  printf("Bus %03d Device %03d: ID %04x:%04x %s %s\n",
+      busnum, devnum, pid, vid, n1, n2);
+
+  path = xmprintf("%s/descriptors", new->name);
+  if (!readfileat(dirtree_parentfd(new), path, toybuf, &file_len)) {
+    free(path);
+    return 0;
+  }
+  len = file_len;
+
+  // Copy descriptor data to a separate buffer to prevent it from being
+  // overwritten by subsequent calls to readat() which use toybuf.
+  memcpy(descriptors_data = xmalloc(len), toybuf, len);
+
+  char *s_man = NULL, *s_prod = NULL, *s_ser = NULL;
+  fd = openat(dirtree_parentfd(new), new->name, O_RDONLY);
+  if (fd >= 0) { // TODO: use readat() directly into s_man/s_prod/s_ser
+    s_man = xmprintf("%s", chomp(readat(fd, "manufacturer")));
+    s_prod = xmprintf("%s", chomp(readat(fd, "product")));
+    s_ser = xmprintf("%s", chomp(readat(fd, "serial")));
+    close(fd);
+  }
+
+  for (p = descriptors_data; p < descriptors_data + len; p += p[0]) {
+    if (!*p || p+*p > descriptors_data+len) break;
+    switch (p[1]) {
+    case 1:
+      // Pass empty string literal if xmprintf failed or file read failed
+      print_device_descriptor(p, pid, vid, n1, n2, s_man ? : "", s_prod ? : "", s_ser ? : "");
+      break;
+    case 2:
+      print_config_descriptor(p);
+      break;
+    case 4:
+      print_interface_descriptor(p);
+      break;
+    case 5:
+      print_endpoint_descriptor(p);
+      break;
+    }
+  }
+  xputc('\n');
+  free(s_man);
+  free(s_prod);
+  free(s_ser);
+  free(descriptors_data);
+  free(path);
+
+  return 0;
+}
+
 static struct usb_device *create_device(struct dirtree *node)
 {
   int fd = openat(dirtree_parentfd(node), node->name, O_RDONLY);
@@ -228,7 +380,7 @@ static struct usb_bus *find_or_create_bus(unsigned int busnum)
 {
   struct usb_bus *bus, **pp;
 
-  for (pp = &usb_buses; (bus = *pp); pp = &bus->next)
+  for (pp = &TT.usb_buses; (bus = *pp); pp = &bus->next)
     if (bus->busnum == busnum) return bus;
 
   bus = xzalloc(sizeof(*bus));
@@ -259,7 +411,7 @@ static int scan_usb_devices_tree(struct dirtree *node)
 {
   if (!node->parent) return DIRTREE_RECURSE;
   if (!strchr(node->name, ':'))
-    if (isdigit(*node->name) || !strncmp(node->name, "usb", 3)) 
+    if (isdigit(*node->name) || !strncmp(node->name, "usb", 3))
       add_device_to_tree(create_device(node));
 
   return 0;
@@ -291,11 +443,13 @@ void lsusb_main(void)
 
   if (FLAG(t)) {
     dirtree_read("/sys/bus/usb/devices/", scan_usb_devices_tree);
+  } else if (FLAG(v)) {
+    dirtree_read("/sys/bus/usb/devices/", list_usb_verbose);
   } else dirtree_read("/sys/bus/usb/devices/", list_usb);
   if (FLAG(t)) {
     struct usb_bus *bus;
 
-    for (bus = usb_buses; bus; bus = bus->next) {
+    for (bus = TT.usb_buses; bus; bus = bus->next) {
       printf("/:  Bus %02u.Port 1: Dev 1, Class=root_hub, Driver=hub/0p, 480M\n", bus->busnum);
       if (bus->first_child) print_device_tree(bus->first_child, 4);
     }
-- 
2.39.5

_______________________________________________
Toybox mailing list
[email protected]
http://lists.landley.net/listinfo.cgi/toybox-landley.net

Reply via email to