/*
 * Copyright (c) 1999, 2002 Lennart Augustsson <augustss@netbsd.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>

#ifndef UICLASS_HID
#define UICLASS_HID UCLASS_HID
#define UICLASS_AUDIO UCLASS_AUDIO
#define UISUBCLASS_AUDIOCONTROL USUBCLASS_AUDIOCONTROL
#define UISUBCLASS_AUDIOSTREAM USUBCLASS_AUDIOSTREAM
#define UICLASS_CDC UCLASS_CDC
#define UICLASS_HUB UCLASS_HUB
#endif

#define USBDEV "/dev/usb0"

/* Backwards compatibility */
#ifndef UE_GET_DIR
#define UE_GET_DIR(a)	((a) & 0x80)
#define UE_DIR_IN	0x80
#define UE_DIR_OUT	0x00
#endif

#define NSTRINGS

int num = 0;

static int usbf, usbaddr;
void
setupstrings(int f, int addr)
{
	usbf = f;
	usbaddr = addr;
}

void
getstring(int si, char *s)
{
	struct usb_ctl_request req;
	int r, i, n;
	u_int16_t c;
	usb_string_descriptor_t us;

	if (si == 0 || num) {
		*s = 0;
		return;
	}
	req.ucr_addr = usbaddr;
	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	req.ucr_data = &us;
	USETW2(req.ucr_request.wValue, UDESC_STRING, si);
	USETW(req.ucr_request.wIndex, 0);
#ifdef NSTRINGS
	USETW(req.ucr_request.wLength, sizeof(usb_string_descriptor_t));
	req.ucr_flags = USBD_SHORT_XFER_OK;
#else
	USETW(req.ucr_request.wLength, 1);
	req.ucr_flags = 0;
#endif
	r = ioctl(usbf, USB_REQUEST, &req);
	if (r < 0) {
		fprintf(stderr, "getstring %d failed (error=%d)\n", si, errno);
		*s = 0;
		return;
	}
#ifndef NSTRINGS
	USETW(req.ucr_request.wLength, us.bLength);
	r = ioctl(usbf, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
#endif
	n = us.bLength / 2 - 1;
	for (i = 0; i < n; i++) {
		c = UGETW(us.bString[i]);
		if ((c & 0xff00) == 0)
			*s++ = c;
		else if ((c & 0x00ff) == 0)
			*s++ = c >> 8;
		else {
			sprintf(s, "\\u%04x", c);
			s += 6;
		}
	}
	*s++ = 0;
}

void
prunits(int f)
{
	struct usb_device_info di;
	int r, n, i;

	for(n = i = 0; i < USB_MAX_DEVICES; i++) {
		di.udi_addr = i;
		r = ioctl(f, USB_DEVICEINFO, &di);
		if (r == 0) {
			printf("USB device %d: %d\n", i, di.udi_class);
			n++;
		}
	}
	printf("%d USB devices found\n", n);
}

char *
descTypeName(int t)
{
	static char b[100];
	char *p = 0;

	switch (t) {
	case UDESC_DEVICE: p = "device"; break;
	case UDESC_CONFIG: p = "config"; break;
	case UDESC_STRING: p = "string"; break;
	case UDESC_INTERFACE: p = "interface"; break;
	case UDESC_ENDPOINT: p = "endpoint"; break;
	case 0x20: p = "cs_undefined"; break;
	case UDESC_CS_DEVICE: p = "cs_device"; break;
	case UDESC_CS_CONFIG: p = "cs_config"; break;
	case UDESC_CS_STRING: p = "cs_string"; break;
	case UDESC_CS_INTERFACE: p = "cs_interface"; break;
	case UDESC_CS_ENDPOINT: p = "cs_endpoint"; break;
	}
	if (p)
		sprintf(b, "%s(%d)", p, t);
	else
		sprintf(b, "%d", t);
	return b;
}

#define UDESCSUB_AC_HEADER 1
#define UDESCSUB_AC_INPUT 2
#define UDESCSUB_AC_OUTPUT 3
#define UDESCSUB_AC_MIXER 4
#define UDESCSUB_AC_SELECTOR 5
#define UDESCSUB_AC_FEATURE 6
#define UDESCSUB_AC_PROCESSING 7
#define UDESCSUB_AC_EXTENSION 8

#define UDESCSUB_AS_GENERAL 1
#define UDESCSUB_AS_FORMAT_TYPE 2
#define UDESCSUB_AS_FORMAT_SPECIFIC 3

char *
acSubTypeName(int t)
{
	static char b[100];
	char *p = 0;

	switch (t) {
	case 0: p = "ac_descriptor_undefined"; break;
	case 1: p = "header"; break;
	case 2: p = "input_terminal"; break;
	case 3: p = "output_terminal"; break;
	case 4: p = "mixer_unit"; break;
	case 5: p = "selector_unit"; break;
	case 6: p = "feature_unit"; break;
	case 7: p = "processing_unit"; break;
	case 8: p = "extension_unit"; break;
	}
	if (p)
		sprintf(b, "%s(%d)", p, t);
	else
		sprintf(b, "%d", t);
	return b;
}

char *
asSubTypeName(int t)
{
	static char b[100];
	char *p = 0;

	switch (t) {
	case 0: p = "as_descriptor_undefined"; break;
	case 1: p = "as_general"; break;
	case 2: p = "format_type"; break;
	case 3: p = "format_specific"; break;
	}
	if (p)
		sprintf(b, "%s(%d)", p, t);
	else
		sprintf(b, "%d", t);
	return b;
}

#define MAXSTR (127*6)
void
prdevd(usb_device_descriptor_t *d)
{
	char man[MAXSTR], prod[MAXSTR], ser[MAXSTR];
	getstring(d->iManufacturer, man);
	getstring(d->iProduct, prod);
	getstring(d->iSerialNumber, ser);
	if (d->bDescriptorType != UDESC_DEVICE) printf("weird descriptorType, should be %d\n", UDESC_DEVICE);
	printf("\
bLength=%d bDescriptorType=%s bcdUSB=%x.%02x bDeviceClass=%d bDeviceSubClass=%d\n\
bDeviceProtocol=%d bMaxPacketSize=%d idVendor=0x%04x idProduct=0x%04x bcdDevice=%x\n\
iManufacturer=%d(%s) iProduct=%d(%s) iSerialNumber=%d(%s) bNumConfigurations=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType), 
	       UGETW(d->bcdUSB) >> 8, UGETW(d->bcdUSB) & 0xff, d->bDeviceClass,
	       d->bDeviceSubClass, d->bDeviceProtocol, d->bMaxPacketSize,
	       UGETW(d->idVendor), UGETW(d->idProduct), UGETW(d->bcdDevice), 
	       d->iManufacturer, man,
	       d->iProduct, prod, d->iSerialNumber, ser,
	       d->bNumConfigurations);
}

void
prconfd(usb_config_descriptor_t *d)
{
	char conf[MAXSTR];
	getstring(d->iConfiguration, conf);
	if (d->bDescriptorType != UDESC_CONFIG) printf("weird descriptorType, should be %d\n", UDESC_CONFIG);
	printf("\
bLength=%d bDescriptorType=%s wTotalLength=%d bNumInterface=%d\n\
bConfigurationValue=%d iConfiguration=%d(%s) bmAttributes=%x bMaxPower=%d mA\n",
	       d->bLength, descTypeName(d->bDescriptorType), 
	       UGETW(d->wTotalLength),
	       d->bNumInterface, d->bConfigurationValue, d->iConfiguration,
	       conf,
	       d->bmAttributes, d->bMaxPower*2);
}

void
prifcd(usb_interface_descriptor_t *d)
{
	char ifc[MAXSTR];
	getstring(d->iInterface, ifc);
	if (d->bDescriptorType != UDESC_INTERFACE) printf("weird descriptorType, should be %d\n", UDESC_INTERFACE);
	printf("\
bLength=%d bDescriptorType=%s bInterfaceNumber=%d bAlternateSetting=%d\n\
bNumEndpoints=%d bInterfaceClass=%d bInterfaceSubClass=%d\n\
bInterfaceProtocol=%d iInterface=%d(%s)\n",
	       d->bLength, descTypeName(d->bDescriptorType), d->bInterfaceNumber,
	       d->bAlternateSetting, d->bNumEndpoints, d->bInterfaceClass,
	       d->bInterfaceSubClass, d->bInterfaceProtocol, 
	       d->iInterface, ifc);
}

char *xfernames[] = { "control", "isochronous", "bulk", "interrupt" };
char *xfertypes[] = { "", "-async", "-adaptive", "-sync" };

void
prendpd(usb_endpoint_descriptor_t *d)
{
	if (d->bDescriptorType != UDESC_ENDPOINT) printf("weird descriptorType, should be %d\n", UDESC_ENDPOINT);
	printf("\
bLength=%d bDescriptorType=%s bEndpointAddress=%d-%s\n\
bmAttributes=%s%s wMaxPacketSize=%d bInterval=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType),
	       d->bEndpointAddress & UE_ADDR,
	       UE_GET_DIR(d->bEndpointAddress) == UE_DIR_IN ? "in" : "out",
	       xfernames[d->bmAttributes & UE_XFERTYPE],
	       xfertypes[(d->bmAttributes >> 2) & UE_XFERTYPE],
	       UGETW(d->wMaxPacketSize), d->bInterval);
}

void
prhubd(usb_hub_descriptor_t *d)
{
	if (d->bDescriptorType != UDESC_HUB) printf("weird descriptorType, should be %d\n", UDESC_HUB);
	printf("\
bDescLength=%d bDescriptorType=%s bNbrPorts=%d wHubCharacteristics=%02x\n\
bPwrOn2PwrGood=%d bHubContrCurrent=%d DeviceRemovable=%x\n",
	       d->bDescLength, descTypeName(d->bDescriptorType), d->bNbrPorts,
	       UGETW(d->wHubCharacteristics), d->bPwrOn2PwrGood, d->bHubContrCurrent,
	       d->DeviceRemovable[0]);
}

void
prhidd(usb_hid_descriptor_t *d)
{
	int i;

	printf("\
bLength=%d bDescriptorType=%s bcdHID=%x.%02x bCountryCode=%d bNumDescriptors=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType), 
	       UGETW(d->bcdHID) >> 8,
	       UGETW(d->bcdHID) & 0xff, d->bCountryCode,
	       d->bNumDescriptors);
	for(i = 0; i < d->bNumDescriptors; i++) {
		printf("bDescriptorType[%d]=%s, wDescriptorLength[%d]=%d\n",
		       i, descTypeName(d->descrs[i].bDescriptorType),
		       i, UGETW(d->descrs[i].wDescriptorLength));
	}
}

#define UDESCSUB_CDC_HEADER	0
#define UDESCSUB_CDC_CM		1 /* Call Management */
#define UDESCSUB_CDC_ACM	2 /* Abstract Control Model */
#define UDESCSUB_CDC_DLM	3 /* Direct Line Management */
#define UDESCSUB_CDC_TRF	4 /* Telephone Ringer */
#define UDESCSUB_CDC_TCLSR	5 /* Telephone Call ... */
#define UDESCSUB_CDC_UNION	6
#define UDESCSUB_CDC_CS		7 /* Country Selection */
#define UDESCSUB_CDC_TOM	8 /* Telephone Operational Modes */
#define UDESCSUB_CDC_USBT	9 /* USB Terminal */

char *
descCDCSubtypeName(int s)
{
	static char buf[20];

	switch (s) {
	case UDESCSUB_CDC_HEADER: return "header";
	case UDESCSUB_CDC_CM: return "Call_Management";
	case UDESCSUB_CDC_ACM: return "Abstract_Control_Model";
	case UDESCSUB_CDC_UNION: return "union";
	default:
		sprintf(buf, "CDC_subtype_%d", s);
		return buf;
	}
}

struct usb_cdc_header_descriptor {
	uByte		bLength;
	uByte		bDescriptorType;
	uByte		bDescriptorSubtype;
	uWord		bcdCDC;
};

struct usb_cdc_cm_descriptor {
	uByte		bLength;
	uByte		bDescriptorType;
	uByte		bDescriptorSubtype;
	uByte		bmCapabilities;
	uByte		bDataInterface;
};

struct usb_cdc_acm_descriptor {
	uByte		bLength;
	uByte		bDescriptorType;
	uByte		bDescriptorSubtype;
	uByte		bmCapabilities;
};

struct usb_cdc_union_descriptor {
	uByte		bLength;
	uByte		bDescriptorType;
	uByte		bDescriptorSubtype;
	uByte		bMasterInterface;
	uByte		bSlaveInterface[1];
};

void
prcdcd(usb_descriptor_t *ud)
{
	if (ud->bDescriptorType != UDESC_CS_INTERFACE)
		printf("prcdcd: strange bDescriptorType=%d\n", 
		       ud->bDescriptorType);
	switch (ud->bDescriptorSubtype) {
	case UDESCSUB_CDC_HEADER:
	{
		struct usb_cdc_header_descriptor *d = (void *)ud;
		printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s\n\
bcdCDC=%x.%02x\n",
		       d->bLength, 
		       descTypeName(d->bDescriptorType), 
		       descCDCSubtypeName(d->bDescriptorSubtype), 
		       UGETW(d->bcdCDC) >> 8,
		       UGETW(d->bcdCDC) & 0xff);
		break;
	}
	case UDESCSUB_CDC_CM:
	{
		struct usb_cdc_cm_descriptor *d = (void *)ud;
		printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s\n\
bmCapabilities=0x%x bDataInterface=%d\n",
		       d->bLength, 
		       descTypeName(d->bDescriptorType), 
		       descCDCSubtypeName(d->bDescriptorSubtype), 
		       d->bmCapabilities,
		       d->bDataInterface);
		break;
	}
	case UDESCSUB_CDC_ACM:
	{
		struct usb_cdc_acm_descriptor *d = (void *)ud;
		printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s\n\
bmCapabilities=0x%x\n",
		       d->bLength, 
		       descTypeName(d->bDescriptorType), 
		       descCDCSubtypeName(d->bDescriptorSubtype), 
		       d->bmCapabilities);
		break;
	}
	case UDESCSUB_CDC_UNION:
	{
		struct usb_cdc_union_descriptor *d = (void *)ud;
		int i;
		printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s\n\
bMasterInterface=%d",
		       d->bLength, 
		       descTypeName(d->bDescriptorType), 
		       descCDCSubtypeName(d->bDescriptorSubtype), 
		       d->bMasterInterface);
		for (i = 0; i < d->bLength - 4; i++)
			printf(" bSlaveInterface%d=%d", 
			       i, d->bSlaveInterface[i]);
		printf("\n");
		break;
	}
	default:
		printf("prcdcd: unknown bDescriptorSubtype=%d\n",
		       ud->bDescriptorSubtype);
		break;
	}
}

void
prbits(int bits, char **strs, int n)
{
	int i;

	for(i = 0; i < n; i++, bits >>= 1)
		if (strs[i*2])
			printf("%s%s", i == 0 ? "" : ", ", strs[i*2 + (bits&1)]);
}

void
prreportd(u_char *d, int len)
{
	int ind;
	u_char *p;

#if 0
	for(i = 0; i < len; i++)
		printf("%02x ", d[i]);
	printf("\n");
#endif

	ind = 0;
	for(p = d; p < d + len;) {
		int bTag, bType, bSize;
		u_char *data;
		long dval;
		static char *gstr[] = {
			"Usage Page", 
			"Logical Min", "Logical Max",
			"Physical Min", "Physical Max",
			"Unit Exponent", "Unit",
			"Report size", "Report ID", 
			"Report count", 
			"Push", "Pop", 
			"??12", "??13", "??14", "??15"};
		static char *lstr[] = {
			"Usage",
			"Usage Min", "Usage Max",
			"Designator index",
			"Designator Min", "Designator Max",
			"??6", "String index",
			"String Min", "String Max",
			"Set delimiter",
			"??11", "??12", "??13", "??14", "??15"
		};
		static char *inputbits[] = {
			"Data", "Constant",
			"Array", "Variable",
			"Absolute", "Relative",
			"No wrap", "Wrap",
			"Linear", "Non linear",
			"Preferred state", "No Preferred",
			"No null position", "Null position",
			0, 0,
			"Bit field", "Bufferred bytes"
		};
		static char *outputbits[] = {
			"Data", "Constant",
			"Array", "Variable",
			"Absolute", "Relative",
			"No wrap", "Wrap",
			"Linear", "Non linear",
			"Preferred state", "No Preferred",
			"No null position", "Null position",
			"Non volatile", "Volatile",
			"Bit field", "Bufferred bytes"
		};
		static char *colls[] = {
			"Physical", "Application", "Logical"
		};

		/*printf("pos = %d\n", p - d);*/
		bSize = *p++;
		if (bSize == 0xfe) {
			/* long item */
			bSize = *p++;
			bSize |= *p++ << 8;
			bTag = *p++;
			data = p;
			p += bSize;
		} else {
			/* short item */
			bTag = bSize >> 4;
			bType = (bSize >> 2) & 3;
			bSize &= 3;
			if (bSize == 3) bSize = 4;
			data = p;
			p += bSize;
		}
		switch(bSize) {
		case 0:
			dval = 0;
			break;
		case 1:
			dval = *data++;
			break;
		case 2:
			dval = *data++;
			dval |= *data++ << 8;
			dval = dval;
			break;
		case 4:
			dval = *data++;
			dval |= *data++ << 8;
			dval |= *data++ << 16;
			dval |= *data++ << 24;
			break;
		default:
			printf("BAD LENGTH %d\n", bSize);
			break;
		}
#define INDENT printf("%*s", ind * 3, "")
		switch (bType) {
		case 0:		/* Main */
			switch (bTag) {
			case 8:
				INDENT;
				printf("Input (");
				prbits(dval, inputbits, 9);
				printf(")\n");
				break;
			case 9:
				INDENT;
				printf("Output (");
				prbits(dval, outputbits, 9);
				printf(")\n");
				break;
			case 10:
				INDENT;
				if (dval >= 0 && dval <= 2)
					printf("Collection (%s)\n", colls[dval]);
				else
					printf("Collection (%ld)\n", dval);
				ind++;
				break;
			case 11:
				INDENT;
				printf("Feature (");
				prbits(dval, outputbits, 9);
				printf(")\n");
				break;
			case 12:
				ind--;
				INDENT;
				printf("End Collection\n");
				break;
			default:
				INDENT;
				printf("??Main bType=%d\n", bTag);
				break;
			}
			break;
		case 1:		/* Global */
			INDENT;
			printf("%s(%ld)\n", gstr[bTag], dval);
			break;
		case 2:		/* Local */
			INDENT;
			printf("%s(%ld)\n", lstr[bTag], dval);
			break;
		default:
			INDENT;
			printf("default\n");
			break;
		}
	}
}

void
gethubdesc(int f, usb_hub_descriptor_t *d, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_CLASS_DEVICE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, USB_HUB_DESCRIPTOR_SIZE);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getdevicedesc(int f, usb_device_descriptor_t *d, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	USETW2(req.ucr_request.wValue, UDESC_DEVICE, 0);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, USB_DEVICE_DESCRIPTOR_SIZE);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getconfigdesc(int f, int i, usb_config_descriptor_t *d, int size, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	USETW2(req.ucr_request.wValue, UDESC_CONFIG, i);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, USB_CONFIG_DESCRIPTOR_SIZE);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	USETW2(req.ucr_request.wValue, UDESC_CONFIG, i);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, UGETW(d->wTotalLength));
	req.ucr_data = d;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
gethiddesc(int f, int i, usb_hid_descriptor_t *d, int size, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_INTERFACE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	USETW2(req.ucr_request.wValue, UDESC_HID, 0);
	USETW(req.ucr_request.wIndex, i);
	USETW(req.ucr_request.wLength, size);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getreportdesc(int f, int ifc, int no, char *d, int size, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_INTERFACE;
	req.ucr_request.bRequest = UR_GET_DESCRIPTOR;
	USETW2(req.ucr_request.wValue, UDESC_REPORT, no);
	USETW(req.ucr_request.wIndex, ifc);
	USETW(req.ucr_request.wLength, size);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getportstatus(int f, int i, usb_port_status_t *d, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_CLASS_OTHER;
	req.ucr_request.bRequest = UR_GET_STATUS;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, i);
	USETW(req.ucr_request.wLength, 4);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
gethubstatus(int f, usb_hub_status_t *d, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_CLASS_DEVICE;
	req.ucr_request.bRequest = UR_GET_STATUS;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, 4);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getconfiguration(int f, u_int8_t *d, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_CONFIG;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, 1);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getdevicestatus(int f, usb_status_t *d, int addr)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_DEVICE;
	req.ucr_request.bRequest = UR_GET_STATUS;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, 0);
	USETW(req.ucr_request.wLength, 2);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getinterfacestatus(int f, usb_status_t *d, int addr, int ifc)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_INTERFACE;
	req.ucr_request.bRequest = UR_GET_STATUS;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, ifc);
	USETW(req.ucr_request.wLength, 2);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
getendpointstatus(int f, usb_status_t *d, int addr, int endp)
{
	struct usb_ctl_request req;
	int r;

	req.ucr_addr = addr;
	req.ucr_request.bmRequestType = UT_READ_ENDPOINT;
	req.ucr_request.bRequest = UR_GET_STATUS;
	USETW(req.ucr_request.wValue, 0);
	USETW(req.ucr_request.wIndex, endp);
	USETW(req.ucr_request.wLength, 2);
	req.ucr_data = d;
	req.ucr_flags = 0;
	r = ioctl(f, USB_REQUEST, &req);
	if (r < 0)
		err(1, "USB_REQUEST");
}

void
usage(void)
{
	extern char *__progname;

	fprintf(stderr, "Usage: %s [-a addr] [-f device] [-d]\n", __progname);
	exit(1);
}

struct usb_audio_control_descriptor {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uWord	bcdADC;
	uWord	wTotalLength;
	uByte	bInCollection;
	uByte	baInterfaceNr[1];
};

struct usb_audio_streaming_interface_descriptor {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bTerminalLink;
	uByte	bDelay;
	uWord	wFormatTag;
};

struct usb_audio_streaming_endpoint_descriptor {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bmAttributes;
	uByte	bLockDelayUnits;
	uWord	wLockDelay;
};

struct usb_audio_descriptor {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
};	

struct usb_audio_streaming_type1_descriptor {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bFormatType;
	uByte	bNrChannels;
	uByte	bSubFrameSize;
	uByte	bBitResolution;
	uByte	bSamFreqType;
	uByte	tSamFreq[3];
};
	
struct usb_audio_input_terminal {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bTerminalId;
	uWord	wTerminalType;
	uByte	bAssocTerminal;
	uByte	bNrChannels;
	uWord	wChannelConfig;
	uByte	iChannelNames;
	uByte	iTerminal;
};

struct usb_audio_output_terminal {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bTerminalId;
	uWord	wTerminalType;
	uByte	bAssocTerminal;
	uByte	bSourceId;
	uByte	iTerminal;
};

struct usb_audio_feature_unit {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bUnitId;
	uByte	bSourceId;
	uByte	bControlSize;
	uByte	bmaControls[1];
};

struct usb_audio_mixer_unit {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bUnitId;
	uByte	bNrInPins;
	uByte	baSourceID[1];
	/* ... and more */
};

struct usb_audio_extension_unit {
	uByte	bLength;
	uByte	bDescriptorType;
	uByte	bDescriptorSubtype;
	uByte	bUnitId;
	uWord	wExtensionCode;
	uByte	bNrInPins;
	uByte	baSourceID[1];
	/* ... and more */
};

void
pracdesc(struct usb_audio_control_descriptor *d)
{
	int i;

	printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s bcdADC=%x.%02x\n\
wTotalLength=%d bInCollection=%x\n",
	       d->bLength, descTypeName(d->bDescriptorType), 
	       acSubTypeName(d->bDescriptorSubtype),
	       UGETW(d->bcdADC) >> 8, UGETW(d->bcdADC) & 0xff,
	       UGETW(d->wTotalLength), d->bInCollection);
	for (i = 0; i < d->bLength - 8; i++)
		printf("baInterfaceNr[%d]=%d\n", i, d->baInterfaceNr[i]);
}

void
prasigd(struct usb_audio_streaming_interface_descriptor *d)
{
	printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s\n\
bTerminalLink=%d bDelay=%d wFormatTag=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType),
	       asSubTypeName(d->bDescriptorSubtype),
	       d->bTerminalLink, d->bDelay,
	       UGETW(d->wFormatTag));
}

void
prasiepd(struct usb_audio_streaming_endpoint_descriptor *d)
{
	printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s bmAttributes=%x\n\
bLockDelayUnits=%d wLockDelay=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType),
	       asSubTypeName(d->bDescriptorSubtype),
	       d->bmAttributes, d->bLockDelayUnits,
	       UGETW(d->wLockDelay));
}


void
prast1d(struct usb_audio_streaming_type1_descriptor *d)
{
	int i, f;
	u_char *p;

	printf("\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%s\n\
bFormatType=%d bNrChannels=%d bSubFrameSize=%d\n\
bBitResolution=%d bSamFreqType=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType), 
	       asSubTypeName(d->bDescriptorSubtype),
	       d->bFormatType, d->bNrChannels, d->bSubFrameSize,
	       d->bBitResolution, d->bSamFreqType);
	p = d->tSamFreq;
#define GETSAMP(f,p) f = p[0] | (p[1] << 8) | (p[2] << 16), p+=3
	if (d->bSamFreqType == 0) {
		GETSAMP(f, p);
		printf("tSampLo=%d\n", f);
		GETSAMP(f, p);
		printf("tSampHi=%d\n", f);
	} else {
		for (i = 0; i < d->bSamFreqType; i++) {
			GETSAMP(f, p);
			printf("tSamFreq[%d]=%d\n", i, f);
		}
	}
}

void
pratd(struct usb_audio_descriptor *d)
{
	struct usb_audio_input_terminal *it;
	struct usb_audio_output_terminal *ot;
	struct usb_audio_feature_unit *fu;
	struct usb_audio_mixer_unit *mu;
	struct usb_audio_extension_unit *eu;
	char msg[1024];

	sprintf(msg, "\
bLength=%d bDescriptorType=%s bDescriptorSubtype=%d\n",
	       d->bLength, descTypeName(d->bDescriptorType), d->bDescriptorSubtype);
	switch (d->bDescriptorSubtype) {
	case UDESCSUB_AC_INPUT:
		it = (void *)d;
		printf("Input terminal descriptor\n%s", msg);
		printf("\
bTerminalId=%d wTerminalType=%d bAssocTerminal=%d\n\
bNrChannels=%d wChannelConfig=%04x\n\
iChannelNames=%d iTerminal=%d\n",
		       it->bTerminalId, UGETW(it->wTerminalType),
		       it->bAssocTerminal, it->bNrChannels, 
		       UGETW(it->wChannelConfig), it->iChannelNames,
		       it->iTerminal);
		break;
	case UDESCSUB_AC_OUTPUT:
		ot = (void *)d;
		printf("Output terminal descriptor\n%s", msg);
		printf("\
bTerminalId=%d wTerminalType=%d bAssocTerminal=%d\n\
bSourceId=%d iTerminal=%d\n",
		       ot->bTerminalId, UGETW(ot->wTerminalType),
		       ot->bAssocTerminal,
		       ot->bSourceId, ot->iTerminal);
		break;
	case UDESCSUB_AC_MIXER:
		mu = (void *)d;
		printf("Mixer unit descriptor\n%s", msg);
		printf("bUnitId=%d bNrInPins=%d\n",
		       mu->bUnitId, mu->bNrInPins);
		{
			u_char *src = mu->baSourceID;
			int i;
			printf("baSourceID=");
			for (i = 0; i < mu->bNrInPins; i++)
				printf(" %d", src[i]);
			printf("\n");
		}
		break;
	case UDESCSUB_AC_FEATURE:
		fu = (void *)d;
		printf("Feature unit descriptor\n%s", msg);
		printf("bUnitId=%d bSourceId=%d bControlSize=%d\n",
		       fu->bUnitId, fu->bSourceId, fu->bControlSize);
		{
			u_char *ctl = fu->bmaControls;
			int i, j, s;
			s = (fu->bLength - 6) / fu->bControlSize;
			for (i = 0; i < s; i++) {
				printf("bmaControls[%d]=", i);
				for (j = 0; j < fu->bControlSize; j++)
					printf("%02x", ctl[fu->bControlSize-j-1]);
				ctl += fu->bControlSize;
				printf("\n");
			}
		}
		break;
	case UDESCSUB_AC_EXTENSION:
		eu = (void *)d;
		printf("Extension unit descriptor\n%s", msg);
		printf("bUnitId=%d bNrInPins=%d wExtensionCode=%d\n",
		       eu->bUnitId, eu->bNrInPins, UGETW(eu->wExtensionCode));
		{
			u_char *src = eu->baSourceID;
			int i;
			printf("baSourceID=");
			for (i = 0; i < eu->bNrInPins; i++)
				printf(" %d", src[i]);
			printf("\n");
		}
		break;
	default:
		printf("Descriptor\n%s   ...\n", msg);
		break;
	}
}

int globf, globaddr;

void *
prdesc(void *p, int *class, int *subclass, int *iface, int conf)
{
	usb_descriptor_t *d = p;
	struct usb_audio_descriptor *ad;
	usb_interface_descriptor_t *id;
	
	switch (d->bDescriptorType) {
	case UDESC_DEVICE:
		printf("DEVICE descriptor:\n");
		prdevd(p);
		break;
	case UDESC_CONFIG:
		printf("CONFIGURATION descriptor:\n");
		prconfd(p);
		*iface = -1;
		break;
	case UDESC_INTERFACE:
		printf("INTERFACE descriptor %d:\n", ++*iface);
		prifcd(p);
		id = p;
		if (id->bInterfaceClass != 0) {
			*class = id->bInterfaceClass;
			*subclass = id->bInterfaceSubClass;
		}
		break;
	case UDESC_ENDPOINT:
		printf("ENDPOINT descriptor:\n");
		prendpd(p);
		break;
#if 0
	case UDESC_HUB:
		prhubd(p);
		break;
#endif
	case UDESC_CS_DEVICE:
		if (*class == UICLASS_HID) {
			usb_hid_descriptor_t *hid = p;
			int k;
			
			printf("HID descriptor:\n");
			prhidd(p);
			printf("\n");
			for(k = 0; k < hid->bNumDescriptors; k++) {
				int type, len;
				u_char buf[256];

				type = hid->descrs[k].bDescriptorType;
				len = UGETW(hid->descrs[k].wDescriptorLength);
				if (type == UDESC_REPORT) {
					getreportdesc(globf, *iface, k, buf, len, globaddr);
					printf("Report descriptor\n");
					prreportd(buf, len);
				} else if (type == UDESC_PHYSICAL) {
					printf("Physical descriptor ...\n");
				} else {
					printf("Unknown HID descriptor type %d\n", type);
				}
				printf("\n");
			}
		} else
			goto def;
		break;
	case UDESC_CS_INTERFACE:
		ad = p;
		if (*class == UICLASS_AUDIO) {
			if (*subclass == UISUBCLASS_AUDIOCONTROL) {
				switch (ad->bDescriptorSubtype) {
				case UDESCSUB_AC_HEADER:
					printf("AC interface descriptor\n");
					pracdesc(p);
					break;
				case UDESCSUB_AC_INPUT:
				case UDESCSUB_AC_OUTPUT:
				case UDESCSUB_AC_FEATURE:
				case UDESCSUB_AC_MIXER:
				case UDESCSUB_AC_EXTENSION:
					printf("AC unit descriptor\n");
					pratd(p);
					break;
				default:
					goto def;
				}
			} else if (*subclass == UISUBCLASS_AUDIOSTREAM) {
				switch (ad->bDescriptorSubtype) {
				case UDESCSUB_AS_GENERAL:
					prasigd(p);
					break;
				case UDESCSUB_AS_FORMAT_TYPE:
					prast1d(p);
					break;
				default:
					goto def;
				}
			} else
				goto def;
		} else if (*class == UICLASS_CDC) {
			switch (ad->bDescriptorSubtype) {
			case UDESCSUB_CDC_HEADER:
			case UDESCSUB_CDC_CM:
			case UDESCSUB_CDC_ACM:
			case UDESCSUB_CDC_UNION:
				printf("CDC INTERFACE descriptor:\n");
				prcdcd(p);
				break;
			default:
				goto def;
			}
		} else
			goto def;
		break;
	case UDESC_CS_ENDPOINT:
		ad = p;
		if (*class == UICLASS_AUDIO) {
			if (*subclass == UISUBCLASS_AUDIOCONTROL) {
				printf("CONTROL %d\n", ad->bDescriptorSubtype);
				goto def;
			} else if (*subclass == UISUBCLASS_AUDIOSTREAM) {
				switch (ad->bDescriptorSubtype) {
				case UDESCSUB_AS_GENERAL:
					prasiepd(p);
					break;
				default:
					goto def;
				}
			} else
				goto def;
		} else
			goto def;
		break;
	default:
	def:
		printf("Unknown descriptor (class %d/%d):\n", *class, *subclass);
		printf("bLength=%d bDescriptorType=%d bDescriptorSubtype=%d ...\n", d->bLength, 
		       d->bDescriptorType, d->bDescriptorSubtype
		       );
		break;
	}
	return (u_char *)p + d->bLength;
}
	

int
main(int argc, char **argv)
{
	int f, r, i;
	char *dev = USBDEV;
	usb_device_descriptor_t dd;
#define USB_CONFIGSPACE 1024
	struct usb_getconfigdesc {
		usb_config_descriptor_t ucd;
		u_char	filler[USB_CONFIGSPACE];
	} cd;
	u_char *p, *enddata;
	usb_hub_descriptor_t hd;
	usb_port_status_t ps;
	usb_hub_status_t hs;
	int ch;
	extern char *optarg;
	extern int optind;
	int disconly = 0, nodisc = 0;
	struct usb_device_info di;
	int addr;
	int doaddr = -1, si = -1;
	u_int8_t cconf;
	int iface;

	while ((ch = getopt(argc, argv, "a:f:dmns:")) != -1) {
		switch(ch) {
		case 'a':
			nodisc = 1;
			doaddr = atoi(optarg);
			break;
		case 'f':
			dev = optarg;
			break;
		case 'd':
			disconly = 1;
			break;
		case 'n':
			nodisc = 1;
			break;
		case 'm':
			num = 1;
			break;
		case 's':
			si = atoi(optarg);
			break;
		case '?':
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	f = open(dev, O_RDWR);
	if (f < 0)
		err(1, "%s", dev);
	globf = f;

	if (doaddr > 0 && si >= 0) {
		char buf[128];
		setupstrings(f, doaddr);
		getstring(si, buf);
		printf("string %d = '%s'\n", si, buf);
		exit(0);
	}

	if (!doaddr)
		prunits(f);
	if (!nodisc) {
		r = ioctl(f, USB_DISCOVER);
		if (r < 0)
			err(1, "USB_DISCOVER");
		prunits(f);
		if (disconly)
			exit(0);
	}

	for(addr = 0; addr < USB_MAX_DEVICES; addr++) {
		if (doaddr != -1 && addr != doaddr)
			continue;
		di.udi_addr = addr;
		r = ioctl(f, USB_DEVICEINFO, &di);
		if (r)
			continue;

		globaddr = addr;
		printf("DEVICE addr %d\n", addr);
		setupstrings(f, addr);
		getdevicedesc(f, &dd, addr);
		printf("DEVICE descriptor:\n");
		prdevd(&dd);
		printf("\n");
		/*getdevicestatus(f, &status, addr);
		 printf("Device status %04x\n", status);*/

		for(i = 0; i < dd.bNumConfigurations; i++) {
			int class, subclass;
			getconfigdesc(f, i, &cd.ucd, sizeof cd, addr);
			printf("CONFIGURATION descriptor %d:\n", i);
			prconfd(&cd.ucd);
			printf("\n");
			p = (char *)&cd + cd.ucd.bLength;
			enddata = (char *)&cd + UGETW(cd.ucd.wTotalLength);

			class = dd.bDeviceClass;
			subclass = dd.bDeviceSubClass;

			iface = -1;
			while (p < enddata) {
				p = prdesc(p, &class, &subclass, &iface, i);
				printf("\n");
			}

		}
		getconfiguration(f, &cconf, addr);
		printf("current configuration %d\n\n", cconf);
#if 1
		if (dd.bDeviceClass == UICLASS_HUB) {
			printf("HUB descriptor:\n");
			gethubdesc(f, &hd, addr);
			prhubd(&hd);
			printf("\n");
			gethubstatus(f, &hs, addr);
			printf("Hub status %04x %04x\n\n",
			       UGETW(hs.wHubStatus), UGETW(hs.wHubChange));
			for(i = 1; i <= hd.bNbrPorts; i++) {
				getportstatus(f, i, &ps, addr);
				printf("Port %d status=%04x change=%04x\n\n", i,
				       UGETW(ps.wPortStatus), UGETW(ps.wPortChange));
			}
		}
#endif
		printf("----------\n");
	}
	exit(0);
}


syntax highlighted by Code2HTML, v. 0.9.1