1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2008 Hans Petter Selasky. All rights reserved.
5 * Copyright (c) 2024 Baptiste Daroussin <bapt@FreeBSD.org>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/queue.h>
30
31#include <stdio.h>
32#include <stdlib.h>
33#include <stdint.h>
34#include <err.h>
35#include <string.h>
36#include <pwd.h>
37#include <grp.h>
38#include <ctype.h>
39
40#include <libusb20.h>
41#include <libusb20_desc.h>
42
43#include "dump.h"
44#include "pathnames.h"
45
46#define	DUMP0(n,type,field,...) dump_field(pdev, "  ", #field, n->field);
47#define	DUMP1(n,type,field,...) dump_field(pdev, "    ", #field, n->field);
48#define	DUMP2(n,type,field,...) dump_field(pdev, "      ", #field, n->field);
49#define	DUMP3(n,type,field,...) dump_field(pdev, "        ", #field, n->field);
50
51struct usb_product_info {
52	STAILQ_ENTRY(usb_product_info)	link;
53	int				id;
54	char				*desc;
55};
56
57struct usb_vendor_info {
58	STAILQ_ENTRY(usb_vendor_info)	link;
59	STAILQ_HEAD(,usb_product_info)	devs;
60	int				id;
61	char				*desc;
62};
63
64STAILQ_HEAD(usb_vendors, usb_vendor_info);
65
66const char *
67dump_mode(uint8_t value)
68{
69	if (value == LIBUSB20_MODE_HOST)
70		return ("HOST");
71	return ("DEVICE");
72}
73
74const char *
75dump_speed(uint8_t value)
76{
77	;				/* style fix */
78	switch (value) {
79	case LIBUSB20_SPEED_LOW:
80		return ("LOW (1.5Mbps)");
81	case LIBUSB20_SPEED_FULL:
82		return ("FULL (12Mbps)");
83	case LIBUSB20_SPEED_HIGH:
84		return ("HIGH (480Mbps)");
85	case LIBUSB20_SPEED_VARIABLE:
86		return ("VARIABLE (52-480Mbps)");
87	case LIBUSB20_SPEED_SUPER:
88		return ("SUPER (5.0Gbps)");
89	default:
90		break;
91	}
92	return ("UNKNOWN ()");
93}
94
95const char *
96dump_power_mode(uint8_t value)
97{
98	;				/* style fix */
99	switch (value) {
100	case LIBUSB20_POWER_OFF:
101		return ("OFF");
102	case LIBUSB20_POWER_ON:
103		return ("ON");
104	case LIBUSB20_POWER_SAVE:
105		return ("SAVE");
106	case LIBUSB20_POWER_SUSPEND:
107		return ("SUSPEND");
108	case LIBUSB20_POWER_RESUME:
109		return ("RESUME");
110	default:
111		return ("UNKNOWN");
112	}
113}
114
115static void
116dump_field(struct libusb20_device *pdev, const char *plevel,
117    const char *field, uint32_t value)
118{
119	uint8_t temp_string[256];
120
121	printf("%s%s = 0x%04x ", plevel, field, value);
122
123	if (strlen(plevel) == 8) {
124		/* Endpoint Descriptor */
125
126		if (strcmp(field, "bEndpointAddress") == 0) {
127			if (value & 0x80)
128				printf(" <IN>\n");
129			else
130				printf(" <OUT>\n");
131			return;
132		}
133		if (strcmp(field, "bmAttributes") == 0) {
134			switch (value & 0x03) {
135			case 0:
136				printf(" <CONTROL>\n");
137				break;
138			case 1:
139				switch (value & 0x0C) {
140				case 0x00:
141					printf(" <ISOCHRONOUS>\n");
142					break;
143				case 0x04:
144					printf(" <ASYNC-ISOCHRONOUS>\n");
145					break;
146				case 0x08:
147					printf(" <ADAPT-ISOCHRONOUS>\n");
148					break;
149				default:
150					printf(" <SYNC-ISOCHRONOUS>\n");
151					break;
152				}
153				break;
154			case 2:
155				printf(" <BULK>\n");
156				break;
157			default:
158				printf(" <INTERRUPT>\n");
159				break;
160			}
161			return;
162		}
163	}
164	if ((field[0] == 'i') && (field[1] != 'd')) {
165		/* Indirect String Descriptor */
166		if (value == 0) {
167			printf(" <no string>\n");
168			return;
169		}
170		if (libusb20_dev_req_string_simple_sync(pdev, value,
171		    temp_string, sizeof(temp_string))) {
172			printf(" <retrieving string failed>\n");
173			return;
174		}
175		printf(" <%s>\n", temp_string);
176		return;
177	}
178	if (strlen(plevel) == 2 || strlen(plevel) == 6) {
179
180		/* Device and Interface Descriptor class codes */
181
182		if (strcmp(field, "bInterfaceClass") == 0 ||
183		    strcmp(field, "bDeviceClass") == 0) {
184
185			switch (value) {
186			case 0x00:
187				printf(" <Probed by interface class>\n");
188				break;
189			case 0x01:
190				printf(" <Audio device>\n");
191				break;
192			case 0x02:
193				printf(" <Communication device>\n");
194				break;
195			case 0x03:
196				printf(" <HID device>\n");
197				break;
198			case 0x05:
199				printf(" <Physical device>\n");
200				break;
201			case 0x06:
202				printf(" <Still imaging>\n");
203				break;
204			case 0x07:
205				printf(" <Printer device>\n");
206				break;
207			case 0x08:
208				printf(" <Mass storage>\n");
209				break;
210			case 0x09:
211				printf(" <HUB>\n");
212				break;
213			case 0x0A:
214				printf(" <CDC-data>\n");
215				break;
216			case 0x0B:
217				printf(" <Smart card>\n");
218				break;
219			case 0x0D:
220				printf(" <Content security>\n");
221				break;
222			case 0x0E:
223				printf(" <Video device>\n");
224				break;
225			case 0x0F:
226				printf(" <Personal healthcare>\n");
227				break;
228			case 0x10:
229				printf(" <Audio and video device>\n");
230				break;
231			case 0x11:
232				printf(" <Billboard device>\n");
233				break;
234			case 0xDC:
235				printf(" <Diagnostic device>\n");
236				break;
237			case 0xE0:
238				printf(" <Wireless controller>\n");
239				break;
240			case 0xEF:
241				printf(" <Miscellaneous device>\n");
242				break;
243			case 0xFE:
244				printf(" <Application specific>\n");
245				break;
246			case 0xFF:
247				printf(" <Vendor specific>\n");
248				break;
249			default:
250				printf(" <Unknown>\n");
251				break;
252			}
253			return;
254		}
255	}
256	/* No additional information */
257	printf("\n");
258}
259
260static void
261dump_extra(struct libusb20_me_struct *str, const char *plevel)
262{
263	const uint8_t *ptr;
264	uint8_t x;
265
266	ptr = NULL;
267
268	while ((ptr = libusb20_desc_foreach(str, ptr))) {
269		printf("\n" "%sAdditional Descriptor\n\n", plevel);
270		printf("%sbLength = 0x%02x\n", plevel, ptr[0]);
271		printf("%sbDescriptorType = 0x%02x\n", plevel, ptr[1]);
272		if (ptr[0] > 1)
273			printf("%sbDescriptorSubType = 0x%02x\n",
274			    plevel, ptr[2]);
275		printf("%s RAW dump: ", plevel);
276		for (x = 0; x != ptr[0]; x++) {
277			if ((x % 8) == 0) {
278				printf("\n%s 0x%02x | ", plevel, x);
279			}
280			printf("0x%02x%s", ptr[x],
281			    (x != (ptr[0] - 1)) ? ", " : (x % 8) ? "\n" : "");
282		}
283		printf("\n");
284	}
285}
286
287static void
288dump_endpoint(struct libusb20_device *pdev,
289    struct libusb20_endpoint *ep)
290{
291	struct LIBUSB20_ENDPOINT_DESC_DECODED *edesc;
292
293	edesc = &ep->desc;
294	LIBUSB20_ENDPOINT_DESC(DUMP3, edesc);
295	dump_extra(&ep->extra, "  " "  " "  ");
296}
297
298static void
299dump_iface(struct libusb20_device *pdev,
300    struct libusb20_interface *iface)
301{
302	struct LIBUSB20_INTERFACE_DESC_DECODED *idesc;
303	uint8_t z;
304
305	idesc = &iface->desc;
306	LIBUSB20_INTERFACE_DESC(DUMP2, idesc);
307	dump_extra(&iface->extra, "  " "  " "  ");
308
309	for (z = 0; z != iface->num_endpoints; z++) {
310		printf("\n     Endpoint %u\n", z);
311		dump_endpoint(pdev, iface->endpoints + z);
312	}
313}
314
315static struct usb_vendors *
316load_vendors(void)
317{
318	const char *dbf;
319	FILE *db = NULL;
320	struct usb_vendor_info *cv;
321	struct usb_product_info *cd;
322	struct usb_vendors *usb_vendors;
323	char buf[1024], str[1024];
324	char *ch;
325	int id;
326
327	usb_vendors = malloc(sizeof(*usb_vendors));
328	if (usb_vendors == NULL)
329		err(1, "out of memory");
330	STAILQ_INIT(usb_vendors);
331	if ((dbf = getenv("USB_VENDOR_DATABASE")) != NULL)
332		db = fopen(dbf, "r");
333	if (db == NULL) {
334		dbf = _PATH_LUSBVDB;
335		if ((db = fopen(dbf, "r")) == NULL) {
336			dbf = _PATH_USBVDB;
337			if ((db = fopen(dbf, "r")) == NULL)
338				return (usb_vendors);
339		}
340	}
341	cv = NULL;
342	cd = NULL;
343
344	for (;;) {
345		if (fgets(buf, sizeof(buf), db) == NULL)
346			break;
347
348		if ((ch = strchr(buf, '#')) != NULL)
349			*ch = '\0';
350		if (ch == buf)
351			continue;
352		ch = strchr(buf, '\0') - 1;
353		while (ch > buf && isspace(*ch))
354			*ch-- = '\0';
355		if (ch <= buf)
356			continue;
357
358		/* Can't handle subvendor / subdevice entries yet */
359		if (buf[0] == '\t' && buf[1] == '\t')
360			continue;
361
362		/* Check for vendor entry */
363		if (buf[0] != '\t' && sscanf(buf, "%04x %[^\n]", &id, str) == 2) {
364			if ((id == 0) || (strlen(str) < 1))
365				continue;
366			if ((cv = malloc(sizeof(struct usb_vendor_info))) == NULL)
367				err(1, "out of memory");
368			if ((cv->desc = strdup(str)) == NULL)
369				err(1, "out of memory");
370			cv->id = id;
371			STAILQ_INIT(&cv->devs);
372			STAILQ_INSERT_TAIL(usb_vendors, cv, link);
373			continue;
374		}
375
376		/* Check for device entry */
377		if (buf[0] == '\t' && sscanf(buf + 1, "%04x %[^\n]", &id, str) == 2) {
378			if ((id == 0) || (strlen(str) < 1))
379				continue;
380			if (cv == NULL)
381				continue;
382			if ((cd = malloc(sizeof(struct usb_product_info))) == NULL)
383				err(1, "out of memory");
384			if ((cd->desc = strdup(str)) == NULL)
385				err(1, "out of memory");
386			cd->id = id;
387			STAILQ_INSERT_TAIL(&cv->devs, cd, link);
388			continue;
389		}
390	}
391	if (ferror(db))
392		err(1, "error reading the usb id db");
393
394	fclose(db);
395	/* cleanup */
396	return (usb_vendors);
397}
398
399static char *
400_device_desc(struct libusb20_device *pdev)
401{
402	static struct usb_vendors *usb_vendors = NULL;
403	char *desc = NULL;
404	const char *vendor = NULL, *product = NULL;
405	uint16_t vid = libusb20_dev_get_device_desc(pdev)->idVendor;
406	uint16_t pid = libusb20_dev_get_device_desc(pdev)->idProduct;
407	struct usb_vendor_info *vi;
408	struct usb_product_info *pi;
409
410	if (usb_vendors == NULL)
411		usb_vendors = load_vendors();
412
413	STAILQ_FOREACH(vi, usb_vendors, link) {
414		if (vi->id == vid) {
415			vendor = vi->desc;
416			break;
417		}
418	}
419	if (vi != NULL) {
420		STAILQ_FOREACH(pi, &vi->devs, link) {
421			if (pi->id == pid) {
422				product = pi->desc;
423				break;
424			}
425		}
426	}
427	if (vendor == NULL || product == NULL)
428		return (NULL);
429
430	asprintf(&desc, "ugen%u.%u: <%s %s> at usbus%u",
431			libusb20_dev_get_bus_number(pdev),
432			libusb20_dev_get_address(pdev),
433			product, vendor,
434			libusb20_dev_get_bus_number(pdev));
435
436
437	return (desc);
438}
439
440void
441dump_device_info(struct libusb20_device *pdev, uint8_t show_ifdrv)
442{
443	char buf[128];
444	uint8_t n;
445	unsigned int usage;
446	char *desc;
447
448	usage = libusb20_dev_get_power_usage(pdev);
449
450	desc = _device_desc(pdev);
451
452	printf("%s, cfg=%u md=%s spd=%s pwr=%s (%umA)\n",
453	    desc ? desc : libusb20_dev_get_desc(pdev),
454	    libusb20_dev_get_config_index(pdev),
455	    dump_mode(libusb20_dev_get_mode(pdev)),
456	    dump_speed(libusb20_dev_get_speed(pdev)),
457	    dump_power_mode(libusb20_dev_get_power_mode(pdev)),
458	    usage);
459	free(desc);
460
461	if (!show_ifdrv)
462		return;
463
464	for (n = 0; n != 255; n++) {
465		if (libusb20_dev_get_iface_desc(pdev, n, buf, sizeof(buf)))
466			break;
467		if (buf[0] == 0)
468			continue;
469		printf("ugen%u.%u.%u: %s\n",
470		    libusb20_dev_get_bus_number(pdev),
471		    libusb20_dev_get_address(pdev), n, buf);
472	}
473}
474
475void
476dump_be_quirk_names(struct libusb20_backend *pbe)
477{
478	struct libusb20_quirk q;
479	uint16_t x;
480	int error;
481
482	memset(&q, 0, sizeof(q));
483
484	printf("\nDumping list of supported quirks:\n\n");
485
486	for (x = 0; x != 0xFFFF; x++) {
487
488		error = libusb20_be_get_quirk_name(pbe, x, &q);
489		if (error) {
490			if (x == 0) {
491				printf("No quirk names - maybe the USB quirk "
492				    "module has not been loaded.\n");
493			}
494			break;
495		}
496		if (strcmp(q.quirkname, "UQ_NONE"))
497			printf("%s\n", q.quirkname);
498	}
499	printf("\n");
500}
501
502void
503dump_be_dev_quirks(struct libusb20_backend *pbe)
504{
505	struct libusb20_quirk q;
506	uint16_t x;
507	int error;
508
509	memset(&q, 0, sizeof(q));
510
511	printf("\nDumping current device quirks:\n\n");
512
513	for (x = 0; x != 0xFFFF; x++) {
514
515		error = libusb20_be_get_dev_quirk(pbe, x, &q);
516		if (error) {
517			if (x == 0) {
518				printf("No device quirks - maybe the USB quirk "
519				    "module has not been loaded.\n");
520			}
521			break;
522		}
523		if (strcmp(q.quirkname, "UQ_NONE")) {
524			printf("VID=0x%04x PID=0x%04x REVLO=0x%04x "
525			    "REVHI=0x%04x QUIRK=%s\n",
526			    q.vid, q.pid, q.bcdDeviceLow,
527			    q.bcdDeviceHigh, q.quirkname);
528		}
529	}
530	printf("\n");
531}
532
533void
534dump_device_desc(struct libusb20_device *pdev)
535{
536	struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
537
538	ddesc = libusb20_dev_get_device_desc(pdev);
539	LIBUSB20_DEVICE_DESC(DUMP0, ddesc);
540}
541
542void
543dump_config(struct libusb20_device *pdev, uint8_t all_cfg)
544{
545	struct LIBUSB20_CONFIG_DESC_DECODED *cdesc;
546	struct LIBUSB20_DEVICE_DESC_DECODED *ddesc;
547	struct libusb20_config *pcfg = NULL;
548	uint8_t cfg_index;
549	uint8_t cfg_index_end;
550	uint8_t x;
551	uint8_t y;
552
553	ddesc = libusb20_dev_get_device_desc(pdev);
554
555	if (all_cfg) {
556		cfg_index = 0;
557		cfg_index_end = ddesc->bNumConfigurations;
558	} else {
559		cfg_index = libusb20_dev_get_config_index(pdev);
560		cfg_index_end = cfg_index + 1;
561	}
562
563	for (; cfg_index != cfg_index_end; cfg_index++) {
564
565		pcfg = libusb20_dev_alloc_config(pdev, cfg_index);
566		if (!pcfg) {
567			continue;
568		}
569		printf("\n Configuration index %u\n\n", cfg_index);
570		cdesc = &(pcfg->desc);
571		LIBUSB20_CONFIG_DESC(DUMP1, cdesc);
572		dump_extra(&(pcfg->extra), "  " "  ");
573
574		for (x = 0; x != pcfg->num_interface; x++) {
575			printf("\n    Interface %u\n", x);
576			dump_iface(pdev, pcfg->interface + x);
577			printf("\n");
578			for (y = 0; y != (pcfg->interface + x)->num_altsetting; y++) {
579				printf("\n    Interface %u Alt %u\n", x, y + 1);
580				dump_iface(pdev,
581				    (pcfg->interface + x)->altsetting + y);
582				printf("\n");
583			}
584		}
585		printf("\n");
586		free(pcfg);
587	}
588}
589
590void
591dump_string_by_index(struct libusb20_device *pdev, uint8_t str_index)
592{
593	char *pbuf;
594	uint8_t n;
595	uint8_t len;
596
597	pbuf = malloc(256);
598	if (pbuf == NULL)
599		err(1, "out of memory");
600
601	if (str_index == 0) {
602		/* language table */
603		if (libusb20_dev_req_string_sync(pdev,
604		    str_index, 0, pbuf, 256)) {
605			printf("STRING_0x%02x = <read error>\n", str_index);
606		} else {
607			printf("STRING_0x%02x = ", str_index);
608			len = (uint8_t)pbuf[0];
609			for (n = 0; n != len; n++) {
610				printf("0x%02x%s", (uint8_t)pbuf[n],
611				    (n != (len - 1)) ? ", " : "");
612			}
613			printf("\n");
614		}
615	} else {
616		/* ordinary string */
617		if (libusb20_dev_req_string_simple_sync(pdev,
618		    str_index, pbuf, 256)) {
619			printf("STRING_0x%02x = <read error>\n", str_index);
620		} else {
621			printf("STRING_0x%02x = <%s>\n", str_index, pbuf);
622		}
623	}
624	free(pbuf);
625}
626
627void
628dump_device_stats(struct libusb20_device *pdev)
629{
630	struct libusb20_device_stats st;
631
632	if (libusb20_dev_get_stats(pdev, &st)) {
633		printf("{}\n");
634	} else {
635		printf("{\n"
636		    "    UE_CONTROL_OK       : %llu\n"
637		    "    UE_ISOCHRONOUS_OK   : %llu\n"
638		    "    UE_BULK_OK          : %llu\n"
639		    "    UE_INTERRUPT_OK     : %llu\n"
640		    "    UE_CONTROL_FAIL     : %llu\n"
641		    "    UE_ISOCHRONOUS_FAIL : %llu\n"
642		    "    UE_BULK_FAIL        : %llu\n"
643		    "    UE_INTERRUPT_FAIL   : %llu\n"
644		    "}\n",
645		    (unsigned long long)st.xfer_ok[0],
646		    (unsigned long long)st.xfer_ok[1],
647		    (unsigned long long)st.xfer_ok[2],
648	            (unsigned long long)st.xfer_ok[3],
649		    (unsigned long long)st.xfer_fail[0],
650		    (unsigned long long)st.xfer_fail[1],
651		    (unsigned long long)st.xfer_fail[2],
652		    (unsigned long long)st.xfer_fail[3]);
653	}
654}
655