diff --git a/desc-file.c b/desc-file.c new file mode 100644 index 0000000..036f76f --- /dev/null +++ b/desc-file.c @@ -0,0 +1,293 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * USB descriptor file parsing + * + * Copyright (C) 2026 Google LLC + */ + +#include +#include +#include +#include +#include +#include "desc-file.h" + +/* + * parse_config_descriptor - Parse a configuration descriptor from a buffer. + * @buf: The buffer containing the configuration descriptor. + * @size: The size of the buffer. + * + * This function parses a raw configuration descriptor from a buffer and + * populates a libusb_config_descriptor structure. + * + * Returns a pointer to the allocated configuration descriptor, or NULL on failure. + */ +static struct libusb_config_descriptor *parse_config_descriptor(const unsigned char *buf, int size) +{ + struct libusb_config_descriptor *cfg; + struct libusb_interface *ifc = NULL; + struct libusb_interface_descriptor *alt = NULL; + struct libusb_endpoint_descriptor *ep = NULL; + + if (size < 9 || buf[1] != LIBUSB_DT_CONFIG) + return NULL; + + cfg = calloc(1, sizeof(*cfg)); + if (!cfg) { + perror("calloc"); + return NULL; + } + + cfg->bLength = buf[0]; + cfg->bDescriptorType = buf[1]; + cfg->wTotalLength = buf[2] | (buf[3] << 8); + cfg->bNumInterfaces = buf[4]; + cfg->bConfigurationValue = buf[5]; + cfg->iConfiguration = buf[6]; + cfg->bmAttributes = buf[7]; + cfg->MaxPower = buf[8]; + + if (cfg->bNumInterfaces > 0) { + cfg->interface = calloc(cfg->bNumInterfaces, sizeof(*cfg->interface)); + if (!cfg->interface) { + perror("calloc"); + desc_file_free_config_descriptor(cfg); + return NULL; + } + } + + buf += cfg->bLength; + size -= cfg->bLength; + + while (size >= 2) { + unsigned char len = buf[0]; + unsigned char type = buf[1]; + void *tmp; + int ep_idx; + + if (len > size || len < 2) + break; + + switch (type) { + case LIBUSB_DT_INTERFACE: + if (buf[2] >= cfg->bNumInterfaces) + break; + ifc = (struct libusb_interface *)&cfg->interface[buf[2]]; + ifc->num_altsetting++; + tmp = realloc((void *)ifc->altsetting, ifc->num_altsetting * sizeof(*ifc->altsetting)); + if (!tmp) { + perror("realloc"); + desc_file_free_config_descriptor(cfg); + return NULL; + } + ifc->altsetting = tmp; + alt = (struct libusb_interface_descriptor *)&ifc->altsetting[ifc->num_altsetting - 1]; + memset(alt, 0, sizeof(*alt)); + alt->bLength = buf[0]; + alt->bDescriptorType = buf[1]; + alt->bInterfaceNumber = buf[2]; + alt->bAlternateSetting = buf[3]; + alt->bNumEndpoints = buf[4]; + alt->bInterfaceClass = buf[5]; + alt->bInterfaceSubClass = buf[6]; + alt->bInterfaceProtocol = buf[7]; + alt->iInterface = buf[8]; + if (alt->bNumEndpoints > 0) { + alt->endpoint = calloc(alt->bNumEndpoints, sizeof(*alt->endpoint)); + if (!alt->endpoint) { + perror("calloc"); + desc_file_free_config_descriptor(cfg); + return NULL; + } + } + ep = NULL; + break; + + case LIBUSB_DT_ENDPOINT: + if (!(alt && alt->endpoint)) + break; + ep_idx = 0; + /* Find next free endpoint slot */ + while (ep_idx < alt->bNumEndpoints && alt->endpoint[ep_idx].bLength) + ep_idx++; + if (ep_idx < alt->bNumEndpoints) { + ep = (struct libusb_endpoint_descriptor *)&alt->endpoint[ep_idx]; + ep->bLength = buf[0]; + ep->bDescriptorType = buf[1]; + ep->bEndpointAddress = buf[2]; + ep->bmAttributes = buf[3]; + ep->wMaxPacketSize = buf[4] | (buf[5] << 8); + ep->bInterval = buf[6]; + ep->bRefresh = (len > 7) ? buf[7] : 0; + ep->bSynchAddress = (len > 8) ? buf[8] : 0; + } + break; + + default: + /* Extra descriptors */ + if (ep) { + tmp = realloc((void *)ep->extra, ep->extra_length + len); + if (!tmp) { + perror("realloc"); + desc_file_free_config_descriptor(cfg); + return NULL; + } + ep->extra = tmp; + memcpy((void *)(ep->extra + ep->extra_length), buf, len); + ep->extra_length += len; + } else if (alt) { + tmp = realloc((void *)alt->extra, alt->extra_length + len); + if (!tmp) { + perror("realloc"); + desc_file_free_config_descriptor(cfg); + return NULL; + } + alt->extra = tmp; + memcpy((void *)(alt->extra + alt->extra_length), buf, len); + alt->extra_length += len; + } else if (cfg) { + tmp = realloc((void *)cfg->extra, cfg->extra_length + len); + if (!tmp) { + perror("realloc"); + desc_file_free_config_descriptor(cfg); + return NULL; + } + cfg->extra = tmp; + memcpy((void *)(cfg->extra + cfg->extra_length), buf, len); + cfg->extra_length += len; + } + break; + } + + buf += len; + size -= len; + } + + return cfg; +} + +/* + * desc_file_free_config_descriptor - Free a configuration descriptor. + * @cfg: The configuration descriptor to free. + */ +void desc_file_free_config_descriptor(struct libusb_config_descriptor *cfg) +{ + if (!cfg) + return; + for (int i = 0; i < cfg->bNumInterfaces; i++) { + struct libusb_interface *ifc = (struct libusb_interface *)&cfg->interface[i]; + if (!ifc) + continue; + for (int j = 0; j < ifc->num_altsetting; j++) { + struct libusb_interface_descriptor *alt = + (struct libusb_interface_descriptor *)&ifc->altsetting[j]; + if (!alt) + continue; + for (int k = 0; k < alt->bNumEndpoints; k++) { + struct libusb_endpoint_descriptor *ep = + (struct libusb_endpoint_descriptor *)&alt->endpoint[k]; + if (!ep) + continue; + if (ep->extra) + free((void *)ep->extra); + } + if (alt->endpoint) + free((void *)alt->endpoint); + if (alt->extra) + free((void *)alt->extra); + } + if (ifc->altsetting) + free((void *)ifc->altsetting); + } + if (cfg->interface) + free((void *)cfg->interface); + if (cfg->extra) + free((void *)cfg->extra); + free(cfg); +} + +/* + * desc_file_get_device_descriptor - Get a device descriptor from a file. + * @fd: The file descriptor to read from. + * @desc: The device descriptor to populate. + * + * Returns 0 on success, or -1 on failure. + */ +int desc_file_get_device_descriptor(int fd, struct libusb_device_descriptor *desc) +{ + unsigned char buf[18]; + int n; + + n = read(fd, buf, sizeof(buf)); + if (n < (int)sizeof(buf)) { + fprintf(stderr, "File too short for device descriptor\n"); + return -1; + } + + /* Populate device descriptor */ + desc->bLength = buf[0]; + desc->bDescriptorType = buf[1]; + desc->bcdUSB = buf[2] | (buf[3] << 8); + desc->bDeviceClass = buf[4]; + desc->bDeviceSubClass = buf[5]; + desc->bDeviceProtocol = buf[6]; + desc->bMaxPacketSize0 = buf[7]; + desc->idVendor = buf[8] | (buf[9] << 8); + desc->idProduct = buf[10] | (buf[11] << 8); + desc->bcdDevice = buf[12] | (buf[13] << 8); + desc->iManufacturer = buf[14]; + desc->iProduct = buf[15]; + desc->iSerialNumber = buf[16]; + desc->bNumConfigurations = buf[17]; + + return 0; +} + +/* + * desc_file_get_next_config_descriptor - Get the next config descriptor from a file. + * @fd: The file descriptor to read from. + * + * Returns a pointer to the allocated configuration descriptor, or NULL on failure. + */ +struct libusb_config_descriptor *desc_file_get_next_config_descriptor(int fd) +{ + unsigned char config_header[9]; + struct libusb_config_descriptor *config; + unsigned char *config_buf; + uint16_t total_length; + ssize_t n; + + n = read(fd, config_header, sizeof(config_header)); + if (n < (ssize_t)sizeof(config_header)) { + if (n > 0) + fprintf(stderr, "File too short for config header\n"); + return NULL; + } + + total_length = config_header[2] | (config_header[3] << 8); + config_buf = malloc(total_length); + if (!config_buf) { + perror("malloc"); + return NULL; + } + + memcpy(config_buf, config_header, sizeof(config_header)); + if (total_length > sizeof(config_header)) { + n = read(fd, config_buf + sizeof(config_header), total_length - sizeof(config_header)); + if (n < (ssize_t)(total_length - sizeof(config_header))) { + fprintf(stderr, "File too short for config\n"); + free(config_buf); + return NULL; + } + } + + config = parse_config_descriptor(config_buf, total_length); + if (!config) { + fprintf(stderr, "Failed to parse config descriptor\n"); + free(config_buf); + return NULL; + } + + free(config_buf); + return config; +} diff --git a/desc-file.h b/desc-file.h new file mode 100644 index 0000000..6359fe9 --- /dev/null +++ b/desc-file.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * USB descriptor file parsing + * + * Copyright (C) 2026 Google LLC + */ + +#ifndef _DESC_FILE_H +#define _DESC_FILE_H + +#include + +/** + * desc_file_free_config_descriptor: + * @cfg: The configuration descriptor to free. + * + * This function frees a configuration descriptor and all its sub-descriptors. + */ +void desc_file_free_config_descriptor(struct libusb_config_descriptor *cfg); + +/** + * desc_file_get_device_descriptor: + * @fd: File descriptor to read from. + * @desc: Pointer to a device descriptor to populate. + * + * Reads a USB device descriptor from a file descriptor. + * + * Returns 0 on success, or -1 on failure. + */ +int desc_file_get_device_descriptor(int fd, struct libusb_device_descriptor *desc); + +/** + * desc_file_get_next_config_descriptor: + * @fd: File descriptor to read from. + * + * Reads the next configuration descriptor from a file descriptor. + * + * Returns a pointer to the allocated configuration descriptor, or NULL on failure. + */ +struct libusb_config_descriptor *desc_file_get_next_config_descriptor(int fd); + +#endif /* _DESC_FILE_H */ diff --git a/lsusb.c b/lsusb.c index 374dcf5..9965dba 100644 --- a/lsusb.c +++ b/lsusb.c @@ -25,6 +25,7 @@ #include "usbmisc.h" #include "desc-defs.h" #include "desc-dump.h" +#include "desc-file.h" #include @@ -249,6 +250,41 @@ static void dump_junk(const unsigned char *buf, const char *indent, unsigned int printf("\n"); } +static void dump_device_speed(libusb_device *dev) +{ + const char *negotiated_speed; + + if (!dev) + return; + + switch (libusb_get_device_speed(dev)) { + case LIBUSB_SPEED_LOW: + negotiated_speed = "Low Speed (1Mbps)"; + break; + case LIBUSB_SPEED_FULL: + negotiated_speed = "Full Speed (12Mbps)"; + break; + case LIBUSB_SPEED_HIGH: + negotiated_speed = "High Speed (480Mbps)"; + break; + case LIBUSB_SPEED_SUPER: + negotiated_speed = "SuperSpeed (5Gbps)"; + break; + case LIBUSB_SPEED_SUPER_PLUS: + negotiated_speed = "SuperSpeed+ (10Gbps)"; + break; + case LIBUSB_SPEED_SUPER_PLUS_X2: + negotiated_speed = "SuperSpeed++ (20Gbps)"; + break; + case LIBUSB_SPEED_UNKNOWN: + default: + negotiated_speed = "Unknown"; + break; + } + + printf("Negotiated speed: %s\n", negotiated_speed); +} + /* * General config descriptor dump */ @@ -262,47 +298,21 @@ static void dump_device( char cls[128], subcls[128], proto[128]; char mfg[128] = {0}, prod[128] = {0}, serial[128] = {0}; char sysfs_name[PATH_MAX]; - const char *negotiated_speed; - get_vendor_product_with_fallback(vendor, sizeof(vendor), - product, sizeof(product), dev); + get_vendor_product_with_fallback(vendor, sizeof(vendor), product, sizeof(product), dev, descriptor); get_class_string(cls, sizeof(cls), descriptor->bDeviceClass); get_subclass_string(subcls, sizeof(subcls), descriptor->bDeviceClass, descriptor->bDeviceSubClass); get_protocol_string(proto, sizeof(proto), descriptor->bDeviceClass, descriptor->bDeviceSubClass, descriptor->bDeviceProtocol); - if (get_sysfs_name(sysfs_name, sizeof(sysfs_name), dev) >= 0) { + if (dev && get_sysfs_name(sysfs_name, sizeof(sysfs_name), dev) >= 0) { read_sysfs_prop(mfg, sizeof(mfg), sysfs_name, "manufacturer"); read_sysfs_prop(prod, sizeof(prod), sysfs_name, "product"); read_sysfs_prop(serial, sizeof(serial), sysfs_name, "serial"); } - switch (libusb_get_device_speed(dev)) { - case LIBUSB_SPEED_LOW: - negotiated_speed = "Low Speed (1Mbps)"; - break; - case LIBUSB_SPEED_FULL: - negotiated_speed = "Full Speed (12Mbps)"; - break; - case LIBUSB_SPEED_HIGH: - negotiated_speed = "High Speed (480Mbps)"; - break; - case LIBUSB_SPEED_SUPER: - negotiated_speed = "SuperSpeed (5Gbps)"; - break; - case LIBUSB_SPEED_SUPER_PLUS: - negotiated_speed = "SuperSpeed+ (10Gbps)"; - break; - case LIBUSB_SPEED_SUPER_PLUS_X2: - negotiated_speed = "SuperSpeed++ (20Gbps)"; - break; - case LIBUSB_SPEED_UNKNOWN: - default: - negotiated_speed = "Unknown"; - break; - } - printf("Negotiated speed: %s\n", negotiated_speed); + dump_device_speed(dev); printf("Device Descriptor:\n" " bLength %5u\n" @@ -3750,8 +3760,7 @@ static int dump_one_device(libusb_context *ctx, const char *path) return 1; } libusb_get_device_descriptor(dev, &desc); - get_vendor_product_with_fallback(vendor, sizeof(vendor), - product, sizeof(product), dev); + get_vendor_product_with_fallback(vendor, sizeof(vendor), product, sizeof(product), dev, &desc); printf("Device: ID %04x:%04x %s %s\n", desc.idVendor, desc.idProduct, vendor, @@ -3814,8 +3823,7 @@ static int list_devices(libusb_context *ctx, int busnum, int devnum, int vendori continue; status = 0; - get_vendor_product_with_fallback(vendor, sizeof(vendor), - product, sizeof(product), dev); + get_vendor_product_with_fallback(vendor, sizeof(vendor), product, sizeof(product), dev, &desc); if (verblevel > 0) printf("\n"); @@ -3833,6 +3841,44 @@ static int list_devices(libusb_context *ctx, int busnum, int devnum, int vendori return status; } +/* ---------------------------------------------------------------------- */ + +static int dump_descriptors_file(const char *path) +{ + int fd; + struct libusb_device_descriptor desc; + char vendor[128], product[128]; + + fd = open(path, O_RDONLY); + if (fd == -1) { + perror("open"); + return 1; + } + + if (desc_file_get_device_descriptor(fd, &desc) < 0) { + close(fd); + return 1; + } + + get_vendor_product_with_fallback(vendor, sizeof(vendor), product, sizeof(product), NULL, &desc); + + printf("Device: ID %04x:%04x %s %s\n", desc.idVendor, desc.idProduct, vendor, product); + + dump_device(NULL, &desc); + + for (int i = 0; i < desc.bNumConfigurations; i++) { + struct libusb_config_descriptor *config; + + config = desc_file_get_next_config_descriptor(fd); + if (!config) + break; + dump_config(NULL, config, desc.bcdUSB); + desc_file_free_config_descriptor(config); + } + + close(fd); + return 0; +} /* ---------------------------------------------------------------------- */ @@ -3849,15 +3895,14 @@ int main(int argc, char *argv[]) int c, err = 0; unsigned int treemode = 0; int bus = -1, devnum = -1, vendor = -1, product = -1; - const char *devdump = NULL; + const char *devdump = NULL, *descfile = NULL; int help = 0; char *cp; int status; setlocale(LC_CTYPE, ""); - while ((c = getopt_long(argc, argv, "D:vtP:p:s:d:Vh", - long_options, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "D:vtP:p:s:d:VhF:", long_options, NULL)) != EOF) { switch (c) { case 'V': printf("lsusb (" PACKAGE_NAME ") " VERSION "\n"); @@ -3867,7 +3912,7 @@ int main(int argc, char *argv[]) break; case 'h': - help=1; + help = 1; break; case 't': @@ -3905,6 +3950,10 @@ int main(int argc, char *argv[]) devdump = optarg; break; + case 'F': + descfile = optarg; + break; + case '?': default: err++; @@ -3913,24 +3962,25 @@ int main(int argc, char *argv[]) } if (err || argc > optind || help) { fprintf(stderr, "Usage: lsusb [options]...\n" - "List USB devices\n" - " -v, --verbose\n" - " Increase verbosity (show descriptors)\n" - " -s [[bus]:][devnum]\n" - " Show only devices with specified device and/or\n" - " bus numbers (in decimal)\n" - " -d vendor:[product]\n" - " Show only devices with the specified vendor and\n" - " product ID numbers (in hexadecimal)\n" - " -D device\n" - " Selects which device lsusb will examine\n" - " -t, --tree\n" - " Dump the physical USB device hierarchy as a tree\n" - " -V, --version\n" - " Show version of program\n" - " -h, --help\n" - " Show usage and help\n" - ); + "List USB devices\n" + " -v, --verbose\n" + " Increase verbosity (show descriptors)\n" + " -s [[bus]:][devnum]\n" + " Show only devices with specified device and/or\n" + " bus numbers (in decimal)\n" + " -d vendor:[product]\n" + " Show only devices with the specified vendor and\n" + " product ID numbers (in hexadecimal)\n" + " -D device\n" + " Selects which device lsusb will examine\n" + " -F file\n" + " Read descriptors from a file\n" + " -t, --tree\n" + " Dump the physical USB device hierarchy as a tree\n" + " -V, --version\n" + " Show version of program\n" + " -h, --help\n" + " Show usage and help\n"); if (help && !err) return 0; else @@ -3956,7 +4006,9 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - if (devdump) + if (descfile) + status = dump_descriptors_file(descfile); + else if (devdump) status = dump_one_device(ctx, devdump); else status = list_devices(ctx, bus, devnum, vendor, product); diff --git a/man/lsusb.8 b/man/lsusb.8 index b018343..be4c4de 100644 --- a/man/lsusb.8 +++ b/man/lsusb.8 @@ -45,6 +45,12 @@ The device file should be something like /dev/bus/usb/001/001. This option displays detailed information like the \fB-v\fP option; you must be root to do this. .TP +.B \-F \fIfile\fP +Read descriptors from a file instead of scanning the /dev/bus/usb directory. +The file is typically obtained by reading it from +/sys/bus/usb/devices/[device-bus-id]/descriptors. +This option displays detailed information like the \fB-v\fP option. +.TP .BR \-t ", " \-\-tree Tells .I lsusb diff --git a/meson.build b/meson.build index 904446a..77347de 100644 --- a/meson.build +++ b/meson.build @@ -85,6 +85,8 @@ install_man(['man/lsusb.8', 'man/lsusb.py.1', 'man/usb-devices.1', 'man/usbhid-d lsusb_sources = [ 'desc-defs.c', 'desc-defs.h', + 'desc-file.c', + 'desc-file.h', 'desc-dump.c', 'desc-dump.h', 'lsusb-t.c', diff --git a/names.c b/names.c index 6c25404..e3cf095 100644 --- a/names.c +++ b/names.c @@ -207,28 +207,23 @@ int get_subclass_string(char *buf, size_t size, uint8_t cls, uint8_t subcls) * either or both are not present, instead populate those from the device's * own string descriptors. */ -void get_vendor_product_with_fallback(char *vendor, int vendor_len, - char *product, int product_len, - libusb_device *dev) +void get_vendor_product_with_fallback(char *vendor, int vendor_len, char *product, int product_len, libusb_device *dev, + struct libusb_device_descriptor *desc) { - struct libusb_device_descriptor desc; char sysfs_name[PATH_MAX]; bool have_vendor, have_product; - libusb_get_device_descriptor(dev, &desc); - /* set to "[unknown]" by default unless something below finds a string */ strncpy(vendor, "[unknown]", vendor_len); strncpy(product, "[unknown]", product_len); - have_vendor = !!get_vendor_string(vendor, vendor_len, desc.idVendor); - have_product = !!get_product_string(product, product_len, - desc.idVendor, desc.idProduct); + have_vendor = !!get_vendor_string(vendor, vendor_len, desc->idVendor); + have_product = !!get_product_string(product, product_len, desc->idVendor, desc->idProduct); if (have_vendor && have_product) return; - if (get_sysfs_name(sysfs_name, sizeof(sysfs_name), dev) >= 0) { + if (dev && get_sysfs_name(sysfs_name, sizeof(sysfs_name), dev) >= 0) { if (!have_vendor) read_sysfs_prop(vendor, vendor_len, sysfs_name, "manufacturer"); if (!have_product) diff --git a/names.h b/names.h index eb8e14d..f485b47 100644 --- a/names.h +++ b/names.h @@ -31,9 +31,8 @@ extern int get_vendor_string(char *buf, size_t size, uint16_t vid); extern int get_product_string(char *buf, size_t size, uint16_t vid, uint16_t pid); extern int get_class_string(char *buf, size_t size, uint8_t cls); extern int get_subclass_string(char *buf, size_t size, uint8_t cls, uint8_t subcls); -extern void get_vendor_product_with_fallback(char *vendor, int vendor_len, - char *product, int product_len, - libusb_device *dev); +extern void get_vendor_product_with_fallback(char *vendor, int vendor_len, char *product, int product_len, + libusb_device *dev, struct libusb_device_descriptor *desc); extern int names_init(void); extern void names_exit(void);