Skip to content

Commit 87cd2b4

Browse files
committed
lsusb: Add -F flag to read descriptors from a file
Add a new flag that allows dumping a USB descriptor file in a human-readable format. This is particularly useful for analyzing descriptor dumps collected from systems where lsusb cannot be run directly, such as during hardware bring-up or on non-Linux platforms. The descriptor file is typically obtained from: /sys/bus/usb/devices/[device-bus-id]/descriptors The normal workflow will be something like: HOST # scp target:/sys/bus/usb/devices/X-X/descriptors . HOST # lsusb -F descriptors Signed-off-by: Ricardo Ribalda <ribalda@google.com>
1 parent ad50416 commit 87cd2b4

5 files changed

Lines changed: 408 additions & 6 deletions

File tree

desc-file.c

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* USB descriptor file parsing
4+
*
5+
* Copyright (C) 2026 Google LLC
6+
*/
7+
8+
#include <stdlib.h>
9+
#include <string.h>
10+
#include <stdio.h>
11+
#include <unistd.h>
12+
#include <libusb.h>
13+
#include "desc-file.h"
14+
15+
/*
16+
* parse_config_descriptor - Parse a configuration descriptor from a buffer.
17+
* @buf: The buffer containing the configuration descriptor.
18+
* @size: The size of the buffer.
19+
*
20+
* This function parses a raw configuration descriptor from a buffer and
21+
* populates a libusb_config_descriptor structure.
22+
*
23+
* Returns a pointer to the allocated configuration descriptor, or NULL on failure.
24+
*/
25+
static struct libusb_config_descriptor *parse_config_descriptor(const unsigned char *buf, int size)
26+
{
27+
struct libusb_config_descriptor *cfg;
28+
struct libusb_interface *ifc = NULL;
29+
struct libusb_interface_descriptor *alt = NULL;
30+
struct libusb_endpoint_descriptor *ep = NULL;
31+
32+
if (size < 9 || buf[1] != LIBUSB_DT_CONFIG)
33+
return NULL;
34+
35+
cfg = calloc(1, sizeof(*cfg));
36+
if (!cfg) {
37+
perror("calloc");
38+
return NULL;
39+
}
40+
41+
cfg->bLength = buf[0];
42+
cfg->bDescriptorType = buf[1];
43+
cfg->wTotalLength = buf[2] | (buf[3] << 8);
44+
cfg->bNumInterfaces = buf[4];
45+
cfg->bConfigurationValue = buf[5];
46+
cfg->iConfiguration = buf[6];
47+
cfg->bmAttributes = buf[7];
48+
cfg->MaxPower = buf[8];
49+
50+
if (cfg->bNumInterfaces > 0) {
51+
cfg->interface = calloc(cfg->bNumInterfaces, sizeof(*cfg->interface));
52+
if (!cfg->interface) {
53+
perror("calloc");
54+
desc_file_free_config_descriptor(cfg);
55+
return NULL;
56+
}
57+
}
58+
59+
buf += cfg->bLength;
60+
size -= cfg->bLength;
61+
62+
while (size >= 2) {
63+
unsigned char len = buf[0];
64+
unsigned char type = buf[1];
65+
void *tmp;
66+
int ep_idx;
67+
68+
if (len > size || len < 2)
69+
break;
70+
71+
switch (type) {
72+
case LIBUSB_DT_INTERFACE:
73+
if (buf[2] >= cfg->bNumInterfaces)
74+
break;
75+
ifc = (struct libusb_interface *)&cfg->interface[buf[2]];
76+
ifc->num_altsetting++;
77+
tmp = realloc((void *)ifc->altsetting, ifc->num_altsetting * sizeof(*ifc->altsetting));
78+
if (!tmp) {
79+
perror("realloc");
80+
desc_file_free_config_descriptor(cfg);
81+
return NULL;
82+
}
83+
ifc->altsetting = tmp;
84+
alt = (struct libusb_interface_descriptor *)&ifc->altsetting[ifc->num_altsetting - 1];
85+
memset(alt, 0, sizeof(*alt));
86+
alt->bLength = buf[0];
87+
alt->bDescriptorType = buf[1];
88+
alt->bInterfaceNumber = buf[2];
89+
alt->bAlternateSetting = buf[3];
90+
alt->bNumEndpoints = buf[4];
91+
alt->bInterfaceClass = buf[5];
92+
alt->bInterfaceSubClass = buf[6];
93+
alt->bInterfaceProtocol = buf[7];
94+
alt->iInterface = buf[8];
95+
if (alt->bNumEndpoints > 0) {
96+
alt->endpoint = calloc(alt->bNumEndpoints, sizeof(*alt->endpoint));
97+
if (!alt->endpoint) {
98+
perror("calloc");
99+
desc_file_free_config_descriptor(cfg);
100+
return NULL;
101+
}
102+
}
103+
ep = NULL;
104+
break;
105+
106+
case LIBUSB_DT_ENDPOINT:
107+
if (!(alt && alt->endpoint))
108+
break;
109+
ep_idx = 0;
110+
/* Find next free endpoint slot */
111+
while (ep_idx < alt->bNumEndpoints && alt->endpoint[ep_idx].bLength)
112+
ep_idx++;
113+
if (ep_idx < alt->bNumEndpoints) {
114+
ep = (struct libusb_endpoint_descriptor *)&alt->endpoint[ep_idx];
115+
ep->bLength = buf[0];
116+
ep->bDescriptorType = buf[1];
117+
ep->bEndpointAddress = buf[2];
118+
ep->bmAttributes = buf[3];
119+
ep->wMaxPacketSize = buf[4] | (buf[5] << 8);
120+
ep->bInterval = buf[6];
121+
ep->bRefresh = (len > 7) ? buf[7] : 0;
122+
ep->bSynchAddress = (len > 8) ? buf[8] : 0;
123+
}
124+
break;
125+
126+
default:
127+
/* Extra descriptors */
128+
if (ep) {
129+
tmp = realloc((void *)ep->extra, ep->extra_length + len);
130+
if (!tmp) {
131+
perror("realloc");
132+
desc_file_free_config_descriptor(cfg);
133+
return NULL;
134+
}
135+
ep->extra = tmp;
136+
memcpy((void *)(ep->extra + ep->extra_length), buf, len);
137+
ep->extra_length += len;
138+
} else if (alt) {
139+
tmp = realloc((void *)alt->extra, alt->extra_length + len);
140+
if (!tmp) {
141+
perror("realloc");
142+
desc_file_free_config_descriptor(cfg);
143+
return NULL;
144+
}
145+
alt->extra = tmp;
146+
memcpy((void *)(alt->extra + alt->extra_length), buf, len);
147+
alt->extra_length += len;
148+
} else if (cfg) {
149+
tmp = realloc((void *)cfg->extra, cfg->extra_length + len);
150+
if (!tmp) {
151+
perror("realloc");
152+
desc_file_free_config_descriptor(cfg);
153+
return NULL;
154+
}
155+
cfg->extra = tmp;
156+
memcpy((void *)(cfg->extra + cfg->extra_length), buf, len);
157+
cfg->extra_length += len;
158+
}
159+
break;
160+
}
161+
162+
buf += len;
163+
size -= len;
164+
}
165+
166+
return cfg;
167+
}
168+
169+
/*
170+
* desc_file_free_config_descriptor - Free a configuration descriptor.
171+
* @cfg: The configuration descriptor to free.
172+
*/
173+
void desc_file_free_config_descriptor(struct libusb_config_descriptor *cfg)
174+
{
175+
if (!cfg)
176+
return;
177+
for (int i = 0; i < cfg->bNumInterfaces; i++) {
178+
struct libusb_interface *ifc = (struct libusb_interface *)&cfg->interface[i];
179+
if (!ifc)
180+
continue;
181+
for (int j = 0; j < ifc->num_altsetting; j++) {
182+
struct libusb_interface_descriptor *alt =
183+
(struct libusb_interface_descriptor *)&ifc->altsetting[j];
184+
if (!alt)
185+
continue;
186+
for (int k = 0; k < alt->bNumEndpoints; k++) {
187+
struct libusb_endpoint_descriptor *ep =
188+
(struct libusb_endpoint_descriptor *)&alt->endpoint[k];
189+
if (!ep)
190+
continue;
191+
if (ep->extra)
192+
free((void *)ep->extra);
193+
}
194+
if (alt->endpoint)
195+
free((void *)alt->endpoint);
196+
if (alt->extra)
197+
free((void *)alt->extra);
198+
}
199+
if (ifc->altsetting)
200+
free((void *)ifc->altsetting);
201+
}
202+
if (cfg->interface)
203+
free((void *)cfg->interface);
204+
if (cfg->extra)
205+
free((void *)cfg->extra);
206+
free(cfg);
207+
}
208+
209+
/*
210+
* desc_file_get_device_descriptor - Get a device descriptor from a file.
211+
* @fd: The file descriptor to read from.
212+
* @desc: The device descriptor to populate.
213+
*
214+
* Returns 0 on success, or -1 on failure.
215+
*/
216+
int desc_file_get_device_descriptor(int fd, struct libusb_device_descriptor *desc)
217+
{
218+
unsigned char buf[18];
219+
int n;
220+
221+
n = read(fd, buf, sizeof(buf));
222+
if (n < (int) sizeof(buf)) {
223+
fprintf(stderr, "File too short for device descriptor\n");
224+
return -1;
225+
}
226+
227+
/* Populate device descriptor */
228+
desc->bLength = buf[0];
229+
desc->bDescriptorType = buf[1];
230+
desc->bcdUSB = buf[2] | (buf[3] << 8);
231+
desc->bDeviceClass = buf[4];
232+
desc->bDeviceSubClass = buf[5];
233+
desc->bDeviceProtocol = buf[6];
234+
desc->bMaxPacketSize0 = buf[7];
235+
desc->idVendor = buf[8] | (buf[9] << 8);
236+
desc->idProduct = buf[10] | (buf[11] << 8);
237+
desc->bcdDevice = buf[12] | (buf[13] << 8);
238+
desc->iManufacturer = buf[14];
239+
desc->iProduct = buf[15];
240+
desc->iSerialNumber = buf[16];
241+
desc->bNumConfigurations = buf[17];
242+
243+
return 0;
244+
}
245+
246+
/*
247+
* desc_file_get_next_config_descriptor - Get the next config descriptor from a file.
248+
* @fd: The file descriptor to read from.
249+
*
250+
* Returns a pointer to the allocated configuration descriptor, or NULL on failure.
251+
*/
252+
struct libusb_config_descriptor *desc_file_get_next_config_descriptor(int fd)
253+
{
254+
unsigned char config_header[9];
255+
struct libusb_config_descriptor *config;
256+
unsigned char *config_buf;
257+
uint16_t total_length;
258+
ssize_t n;
259+
260+
n = read(fd, config_header, sizeof(config_header));
261+
if (n < (ssize_t) sizeof(config_header)) {
262+
if (n > 0)
263+
fprintf(stderr, "File too short for config header\n");
264+
return NULL;
265+
}
266+
267+
total_length = config_header[2] | (config_header[3] << 8);
268+
config_buf = malloc(total_length);
269+
if (!config_buf) {
270+
perror("malloc");
271+
return NULL;
272+
}
273+
274+
memcpy(config_buf, config_header, sizeof(config_header));
275+
if (total_length > sizeof(config_header)) {
276+
n = read(fd, config_buf + sizeof(config_header), total_length - sizeof(config_header));
277+
if (n < (ssize_t)(total_length - sizeof(config_header))) {
278+
fprintf(stderr, "File too short for config\n");
279+
free(config_buf);
280+
return NULL;
281+
}
282+
}
283+
284+
config = parse_config_descriptor(config_buf, total_length);
285+
if (!config) {
286+
fprintf(stderr, "Failed to parse config descriptor\n");
287+
free(config_buf);
288+
return NULL;
289+
}
290+
291+
free(config_buf);
292+
return config;
293+
}

desc-file.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* USB descriptor file parsing
4+
*
5+
* Copyright (C) 2026 Google LLC
6+
*/
7+
8+
#ifndef _DESC_FILE_H
9+
#define _DESC_FILE_H
10+
11+
#include <libusb.h>
12+
13+
/**
14+
* desc_file_free_config_descriptor:
15+
* @cfg: The configuration descriptor to free.
16+
*
17+
* This function frees a configuration descriptor and all its sub-descriptors.
18+
*/
19+
void desc_file_free_config_descriptor(struct libusb_config_descriptor *cfg);
20+
21+
/**
22+
* desc_file_get_device_descriptor:
23+
* @fd: File descriptor to read from.
24+
* @desc: Pointer to a device descriptor to populate.
25+
*
26+
* Reads a USB device descriptor from a file descriptor.
27+
*
28+
* Returns 0 on success, or -1 on failure.
29+
*/
30+
int desc_file_get_device_descriptor(int fd, struct libusb_device_descriptor *desc);
31+
32+
/**
33+
* desc_file_get_next_config_descriptor:
34+
* @fd: File descriptor to read from.
35+
*
36+
* Reads the next configuration descriptor from a file descriptor.
37+
*
38+
* Returns a pointer to the allocated configuration descriptor, or NULL on failure.
39+
*/
40+
struct libusb_config_descriptor *desc_file_get_next_config_descriptor(int fd);
41+
42+
#endif /* _DESC_FILE_H */

0 commit comments

Comments
 (0)