/*****************************************************************************/

/*
 *      lsusb.c  --  lspci like utility for the USB bus
 *
 *      Copyright (C) 1999, 2000, 2001, 2003
 *        Thomas Sailer (t.sailer@alumni.ethz.ch)
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 */

/*****************************************************************************/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdarg.h>
#include <usb.h>

#include "names.h"
#include "devtree.h"
#include "usbmisc.h"

#define _GNU_SOURCE
#include <getopt.h>


#define USB_DT_CS_INTERFACE		0x24
#define USB_DT_CS_ENDPOINT		0x25

#define VERBLEVEL_DEFAULT 0	/* 0 gives lspci behaviour; 1, lsusb-0.9 */

#define CTRL_RETRIES	 2
#define CTRL_TIMEOUT	(5*1000)	/* milliseconds */

static const char *procbususb = "/proc/bus/usb";
static unsigned int verblevel = VERBLEVEL_DEFAULT;
static int do_report_desc = 1;

static void dump_interface(struct usb_dev_handle *dev, struct usb_interface *interface);
static void dump_endpoint(struct usb_dev_handle *dev, struct usb_interface_descriptor *interface, struct usb_endpoint_descriptor *endpoint);
static void dump_audiocontrol_interface(struct usb_dev_handle *dev, unsigned char *buf);
static void dump_audiostreaming_interface(struct usb_dev_handle *dev, unsigned char *buf);
static void dump_midistreaming_interface(struct usb_dev_handle *dev, unsigned char *buf);
static char *dump_comm_descriptor(struct usb_dev_handle *dev, unsigned char *buf, char *indent);
static void dump_hid_device(struct usb_dev_handle *dev, struct usb_interface_descriptor *interface, unsigned char *buf);
static void dump_audiostreaming_endpoint(struct usb_dev_handle *dev, unsigned char *buf);
static void dump_midistreaming_endpoint(struct usb_dev_handle *dev, unsigned char *buf);
static void dump_hub(char *prefix, unsigned char *p, int has_tt);
static void dump_ccid_device(unsigned char *buf);

/* ---------------------------------------------------------------------- */

static unsigned int convert_le_u32 (const unsigned char *buf)
{
        return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); 
}

/* ---------------------------------------------------------------------- */

int lprintf(unsigned int vl, const char *format, ...)
{
        va_list ap;
        int r;

        if (vl > verblevel)
                return 0;
        va_start(ap, format);
	r = vfprintf(stderr, format, ap);
        va_end(ap);
        if (!vl)
                exit(1);
        return r;
}

/* ---------------------------------------------------------------------- */

static int get_string(struct usb_dev_handle *dev, char* buf, size_t size, u_int8_t id)
{
	int ret;
	
	if (id) {
		ret = usb_get_string_simple(dev, id, buf, size);
		if (ret <= 0) {
			buf[0] = 0;
			return 0;
		}
		else
			return ret;
		
	} 
	else {
		buf[0] = 0;
		return 0;
	}
}

static int get_vendor_string(char *buf, size_t size, u_int16_t vid)
{
	const char *cp;

	if (size < 1)
		return 0;
	*buf = 0;
	if (!(cp = names_vendor(vid)))
		return 0;
	return snprintf(buf, size, "%s", cp);
}

static int get_product_string(char *buf, size_t size, u_int16_t vid, u_int16_t pid)
{
	const char *cp;

	if (size < 1)
		return 0;
	*buf = 0;
	if (!(cp = names_product(vid, pid)))
		return 0;
	return snprintf(buf, size, "%s", cp);
}

static int get_class_string(char *buf, size_t size, u_int8_t cls)
{
	const char *cp;

	if (size < 1)
		return 0;
	*buf = 0;
	if (!(cp = names_class(cls)))
		return 0;
	return snprintf(buf, size, "%s", cp);
}

static int get_subclass_string(char *buf, size_t size, u_int8_t cls, u_int8_t subcls)
{
	const char *cp;

	if (size < 1)
		return 0;
	*buf = 0;
	if (!(cp = names_subclass(cls, subcls)))
		return 0;
	return snprintf(buf, size, "%s", cp);
}

static int get_protocol_string(char *buf, size_t size, u_int8_t cls, u_int8_t subcls, u_int8_t proto)
{
	const char *cp;

	if (size < 1)
		return 0;
	*buf = 0;
	if (!(cp = names_protocol(cls, subcls, proto)))
		return 0;
	return snprintf(buf, size, "%s", cp);
}

static int get_audioterminal_string(char *buf, size_t size, u_int16_t termt)
{
	const char *cp;

	if (size < 1)
		return 0;
	*buf = 0;
	if (!(cp = names_audioterminal(termt)))
		return 0;
	return snprintf(buf, size, "%s", cp);
}

/* ---------------------------------------------------------------------- */

static void dump_junk2(unsigned char *buf, unsigned int len)
{
	unsigned int i;

	for (i = 0; i < len; i++)
		printf(" %02x", buf[i]);
	printf("\n");
}

static void dump_junk(unsigned char *buf, const char *indent, unsigned int len)
{
	unsigned int i;

	if (buf[0] <= len)
		return;
	printf("%sjunk at descriptor end:", indent);
	for (i = len; i < buf[0]; i++)
		printf(" %02x", buf[i]);
	printf("\n");
}

/*
 * General config descriptor dump
 */

static void dump_device(struct usb_dev_handle *dev, struct usb_device_descriptor *descriptor, unsigned int flags)
{
	char vendor[128], product[128];
	char cls[128], subcls[128], proto[128];
	char mfg[128], prod[128], serial[128];

	get_vendor_string(vendor, sizeof(vendor), descriptor->idVendor);
	get_product_string(product, sizeof(product), descriptor->idVendor, descriptor->idProduct);
	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);
	get_string(dev, mfg, sizeof(mfg), descriptor->iManufacturer);
	get_string(dev, prod, sizeof(prod), descriptor->iProduct);
	get_string(dev, serial, sizeof(serial), descriptor->iSerialNumber);
	printf("Device Descriptor:\n"
	       "  bLength             %5u\n"
	       "  bDescriptorType     %5u\n"
	       "  bcdUSB              %2x.%02x\n"
	       "  bDeviceClass        %5u %s\n"
	       "  bDeviceSubClass     %5u %s\n"
	       "  bDeviceProtocol     %5u %s\n"
	       "  bMaxPacketSize0     %5u\n"
	       "  idVendor           0x%04x %s\n"
	       "  idProduct          0x%04x %s\n"
	       "  bcdDevice           %2x.%02x\n"
	       "  iManufacturer       %5u %s\n"
	       "  iProduct            %5u %s\n"
	       "  iSerial             %5u %s\n"
	       "  bNumConfigurations  %5u\n",
	       descriptor->bLength, descriptor->bDescriptorType, descriptor->bcdUSB >> 8, descriptor->bcdUSB & 0xff, 
	       descriptor->bDeviceClass, cls, descriptor->bDeviceSubClass, subcls, descriptor->bDeviceProtocol, proto, 
	       descriptor->bMaxPacketSize0, descriptor->idVendor, vendor, descriptor->idProduct, product, 
	       descriptor->bcdDevice >> 8, descriptor->bcdDevice & 0xff, descriptor->iManufacturer, mfg,
	       descriptor->iProduct, prod, descriptor->iSerialNumber, serial, descriptor->bNumConfigurations);
}

static void dump_config(struct usb_dev_handle *dev, struct usb_config_descriptor *config)
{
	char cfg[128];
	int i;
	
	get_string(dev, cfg, sizeof(cfg), config->iConfiguration);
	printf("  Configuration Descriptor:\n"
	       "    bLength             %5u\n"
	       "    bDescriptorType     %5u\n"
	       "    wTotalLength        %5u\n"
	       "    bNumInterfaces      %5u\n"
	       "    bConfigurationValue %5u\n"
	       "    iConfiguration      %5u %s\n"
	       "    bmAttributes         0x%02x\n",
	       config->bLength, config->bDescriptorType, config->wTotalLength,
	       config->bNumInterfaces, config->bConfigurationValue, config->iConfiguration,
	       cfg, config->bmAttributes);
	if (config->bmAttributes & 0x40)
		printf("      Self Powered\n");
	if (config->bmAttributes & 0x20)
		printf("      Remote Wakeup\n");
	printf("    MaxPower            %5umA\n", config->MaxPower * 2);

	for (i = 0 ; i < config->bNumInterfaces ; i++)
		dump_interface(dev, &config->interface[i]);
}

static void dump_altsetting(struct usb_dev_handle *dev, struct usb_interface_descriptor *interface)
{
	char cls[128], subcls[128], proto[128];
	char ifstr[128];
	
	char *buf;
	int size, i;

	get_class_string(cls, sizeof(cls), interface->bInterfaceClass);
	get_subclass_string(subcls, sizeof(subcls), interface->bInterfaceClass, interface->bInterfaceSubClass);
	get_protocol_string(proto, sizeof(proto), interface->bInterfaceClass, interface->bInterfaceSubClass, interface->bInterfaceProtocol);
	get_string(dev, ifstr, sizeof(ifstr), interface->iInterface);
	printf("    Interface Descriptor:\n"
	       "      bLength             %5u\n"
	       "      bDescriptorType     %5u\n"
	       "      bInterfaceNumber    %5u\n"
	       "      bAlternateSetting   %5u\n"
	       "      bNumEndpoints       %5u\n"
	       "      bInterfaceClass     %5u %s\n"
	       "      bInterfaceSubClass  %5u %s\n"
	       "      bInterfaceProtocol  %5u %s\n"
	       "      iInterface          %5u %s\n",
	       interface->bLength, interface->bDescriptorType, interface->bInterfaceNumber,
	       interface->bAlternateSetting, interface->bNumEndpoints, interface->bInterfaceClass, cls,
	       interface->bInterfaceSubClass, subcls, interface->bInterfaceProtocol, proto,
	       interface->iInterface, ifstr);

	for (i = 0 ; i < interface->bNumEndpoints ; i++)
		dump_endpoint(dev, interface, &interface->endpoint[i]);
	
	if (interface->extralen)
	{
		size = interface->extralen;
		buf = interface->extra;
		while (size >= 2 * sizeof(u_int8_t))
		{
			if (buf[0] < 2) {
				fprintf(stderr, "invalid descriptor length of %d\n", buf[0]);
				return;
			}
			else if (buf[1] == USB_DT_CS_INTERFACE)
			{
				switch(interface->bInterfaceClass) {
					case USB_CLASS_AUDIO:
						switch(interface->bInterfaceSubClass) {
							case 1:
								dump_audiocontrol_interface(dev, buf);
								break;
							case 2:
								dump_audiostreaming_interface(dev, buf);
								break;
							case 3:
								dump_midistreaming_interface(dev, buf);
								break;
						}
						break;
					case USB_CLASS_COMM:
						dump_comm_descriptor(dev, buf, "        ");
						break;
				}
			}
			else if (buf[1] == USB_DT_HID)
			{
				if (interface->bInterfaceClass == USB_CLASS_HID)
					dump_hid_device(dev, interface, buf);
				if (interface->bInterfaceClass == 0x0b)
					dump_ccid_device(buf);
			}
			size -= buf[0];
			buf += buf[0];
		}
	}
}

static void dump_interface(struct usb_dev_handle *dev, struct usb_interface *interface)
{
	int i;
	
	for (i = 0; i < interface->num_altsetting; i++)
		dump_altsetting(dev, &interface->altsetting[i]);
}

static void dump_endpoint(struct usb_dev_handle *dev, struct usb_interface_descriptor *interface, struct usb_endpoint_descriptor *endpoint)
{
	static const char *typeattr[] = { "Control", "Isochronous", "Bulk", "Interrupt" };
	static const char *syncattr[] = { "None", "Asynchronous", "Adaptive", "Synchronous" };
	static const char *usage[] = { "Data", "Feedback", "Implicit feedback Data", "(reserved)" };
	static const char *hb[] = { "once", "twice", "three times", "(?\?)" };
	char *buf;
	int size;

	printf("      Endpoint Descriptor:\n"
	       "        bLength             %5u\n"
	       "        bDescriptorType     %5u\n"
	       "        bEndpointAddress     0x%02x  EP %u %s\n"
	       "        bmAttributes        %5u\n"
	       "          Transfer Type            %s\n"
	       "          Synch Type               %s\n"
	       "          Usage Type               %s\n"
	       "        wMaxPacketSize     0x%04x  bytes %d %s\n"
	       "        bInterval           %5u\n",
	       endpoint->bLength, endpoint->bDescriptorType, endpoint->bEndpointAddress, endpoint->bEndpointAddress & 0x0f, 
	       (endpoint->bEndpointAddress & 0x80) ? "IN" : "OUT", endpoint->bmAttributes, 
	       typeattr[endpoint->bmAttributes & 3], syncattr[(endpoint->bmAttributes >> 2) & 3], 
	       usage[(endpoint->bmAttributes >> 4) & 3], endpoint->wMaxPacketSize,
	       endpoint->wMaxPacketSize & 0x3ff, hb[(endpoint->wMaxPacketSize >> 1) & 3],
	       endpoint->bInterval);
	/* only for audio endpoints */
	if (endpoint->bLength == 9) 
	printf("        bRefresh            %5u\n"
	       "        bSynchAddress       %5u\n",
	       endpoint->bRefresh, endpoint->bSynchAddress);
	
	if (endpoint->extralen)
	{
		size = endpoint->extralen;
		buf = endpoint->extra;
		while (size >= 2 * sizeof(u_int8_t))
		{
			if (buf[0] < 2) {
				fprintf(stderr, "invalid descriptor length of %d\n", buf[0]);
				return;
			} else if (buf[1] == USB_DT_CS_ENDPOINT) {
				if (interface->bInterfaceClass == 1 && interface->bInterfaceSubClass == 2)
					dump_audiostreaming_endpoint(dev, buf);
				else if (interface->bInterfaceClass == 1 && interface->bInterfaceSubClass == 3)
					dump_midistreaming_endpoint(dev, buf);
			} else if (buf[1] == USB_DT_HUB) {
				dump_hub("       ", buf, 0);
			}
			size -= buf[0];
			buf += buf[0];
		}
	}
}

/* ---------------------------------------------------------------------- */

/*
 * Audio Class descriptor dump
 */

static void dump_audiocontrol_interface(struct usb_dev_handle *dev, unsigned char *buf)
{
	static const char *chconfig[] = {
		"Left Front (L)", "Right Front (R)", "Center Front (C)", "Low Freqency Enhancement (LFE)",
		"Left Surround (LS)", "Right Surround (RS)", "Left of Center (LC)", "Right of Center (RC)",
		"Surround (S)", "Side Left (SL)", "Side Right (SR)", "Top (T)"
	};
	static const char *chftrcontrols[] = {
		"Mute", "Volume", "Bass", "Mid", "Treble", "Graphic Equalizer", "Automatic Gain", "Delay", "Bass Boost", "Loudness"
	};
	unsigned int i, chcfg, j, k, N, termt;
	char chnames[128], term[128], termts[128];

	if (buf[1] != USB_DT_CS_INTERFACE)
		printf("      Warning: Invalid descriptor\n");
	else if (buf[0] < 3)
		printf("      Warning: Descriptor too short\n");
	printf("      AudioControl Interface Descriptor:\n"
	       "        bLength             %5u\n"
	       "        bDescriptorType     %5u\n"
	       "        bDescriptorSubtype  %5u ",
	       buf[0], buf[1], buf[2]);
	switch (buf[2]) {
	case 0x01:  /* HEADER */
		printf("(HEADER)\n");
		if (buf[0] < 8+buf[7])
			printf("      Warning: Descriptor too short\n");
		printf("        bcdADC              %2x.%02x\n"
		       "        wTotalLength        %5u\n"
		       "        bInCollection       %5u\n",
		       buf[4], buf[3], buf[5] | (buf[6] << 8), buf[7]);
		for(i = 0; i < buf[7]; i++)
			printf("        baInterfaceNr(%2u)   %5u\n", i, buf[8+i]);
		dump_junk(buf, "        ", 8+buf[7]);
		break;

	case 0x02:  /* INPUT_TERMINAL */
		printf("(INPUT_TERMINAL)\n");
		get_string(dev, chnames, sizeof(chnames), buf[10]);
		get_string(dev, term, sizeof(term), buf[11]);
		termt = buf[4] | (buf[5] << 8);
		get_audioterminal_string(termts, sizeof(termts), termt);
		if (buf[0] < 12)
			printf("      Warning: Descriptor too short\n");
		chcfg = buf[8] | (buf[9] << 8);
		printf("        bTerminalID         %5u\n"
		       "        wTerminalType      0x%04x %s\n"
		       "        bAssocTerminal      %5u\n"
		       "        bNrChannels         %5u\n"
		       "        wChannelConfig     0x%04x\n",
		       buf[3], termt, termts, buf[6], buf[7], chcfg);
		for (i = 0; i < 12; i++)
			if ((chcfg >> i) & 1)
				printf("          %s\n", chconfig[i]);
		printf("        iChannelNames       %5u %s\n"
		       "        iTerminal           %5u %s\n",
		       buf[10], chnames, buf[11], term);	
		dump_junk(buf, "        ", 12);
		break;

	case 0x03:  /* OUTPUT_TERMINAL */
		printf("(OUTPUT_TERMINAL)\n");
		get_string(dev, term, sizeof(term), buf[8]);
		termt = buf[4] | (buf[5] << 8);
		get_audioterminal_string(termts, sizeof(termts), termt);
		if (buf[0] < 9)
			printf("      Warning: Descriptor too short\n");
		printf("        bTerminalID         %5u\n"
		       "        wTerminalType      0x%04x %s\n"
		       "        bAssocTerminal      %5u\n"
		       "        bSourceID           %5u\n"
		       "        iTerminal           %5u %s\n",
		       buf[3], termt, termts, buf[6], buf[7], buf[8], term);	
		dump_junk(buf, "        ", 9);
		break;

	case 0x04:  /* MIXER_UNIT */
		printf("(MIXER_UNIT)\n");
		j = buf[4];
		k = buf[j+5];
		if (j == 0 || k == 0) { 
		  printf("      Warning: mixer with %5u input and %5u output channels.\n", j, k);
		  N = 0;
		} else {
		  N = 1+(j*k-1)/8;
		}
		get_string(dev, chnames, sizeof(chnames), buf[8+j]);
		get_string(dev, term, sizeof(term), buf[9+j+N]);
		if (buf[0] < 10+j+N)
			printf("      Warning: Descriptor too short\n");
		chcfg = buf[6+j] | (buf[7+j] << 8);
		printf("        bUnitID             %5u\n"
		       "        bNrInPins           %5u\n",
		       buf[3], buf[4]);
		for (i = 0; i < j; i++)
			printf("        baSourceID(%2u)      %5u\n", i, buf[5+i]);
		printf("        bNrChannels         %5u\n"
		       "        wChannelConfig     0x%04x\n",
		       buf[5+j], chcfg);
		for (i = 0; i < 12; i++)
			if ((chcfg >> i) & 1)
				printf("          %s\n", chconfig[i]);
		printf("        iChannelNames       %5u %s\n",
		       buf[8+j], chnames);
		for (i = 0; i < N; i++)
			printf("        bmControls         0x%02x\n", buf[9+j+i]);
		printf("        iMixer              %5u %s\n", buf[9+j+N], term);	
		dump_junk(buf, "        ", 10+j+N);
		break;

	case 0x05:  /* SELECTOR_UNIT */
		printf("(SELECTOR_UNIT)\n");
		if (buf[0] < 6+buf[4])
			printf("      Warning: Descriptor too short\n");
		get_string(dev, term, sizeof(term), buf[5+buf[4]]);

		printf("        bUnitID             %5u\n"
		       "        bNrInPins           %5u\n",
		       buf[3], buf[4]);
		for (i = 0; i < buf[4]; i++)
			printf("        baSource(%2u)        %5u\n", i, buf[5+i]);
		printf("        iSelector           %5u %s\n",
		       buf[5+buf[4]], term);
		dump_junk(buf, "        ", 6+buf[4]);
		break;

	case 0x06:  /* FEATURE_UNIT */
		printf("(FEATURE_UNIT)\n");
		j = buf[5];
		if (!j)
			j = 1;
		k = (buf[0] - 7) / j;
		if (buf[0] < 7+buf[5]*k)
			printf("      Warning: Descriptor too short\n");
		get_string(dev, term, sizeof(term), buf[6+buf[5]*k]);
		printf("        bUnitID             %5u\n"
		       "        bSourceID           %5u\n"
		       "        bControlSize        %5u\n",
		       buf[3], buf[4], buf[5]);
		for (i = 0; i < k; i++) {
			chcfg = buf[6+buf[5]*i];
			if (buf[5] > 1)
				chcfg |= (buf[7+buf[5]*i] << 8);
			for (j = 0; j < buf[5]; j++)
				printf("        bmaControls(%2u)      0x%02x\n", i, buf[6+buf[5]*i+j]);
			for (j = 0; j < 10; j++)
				if ((chcfg >> j) & 1)
					printf("          %s\n", chftrcontrols[j]);
		}
		printf("        iFeature            %5u %s\n", buf[6+buf[5]*k], term);
		dump_junk(buf, "        ", 7+buf[5]*k);
		break;

	case 0x07:  /* PROCESSING_UNIT */
		printf("(PROCESSING_UNIT)\n");
		j = buf[6];
		k = buf[11+j];
		get_string(dev, chnames, sizeof(chnames), buf[10+j]);
		get_string(dev, term, sizeof(term), buf[12+j+k]);
		chcfg = buf[8+j] | (buf[9+j] << 8);
		if (buf[0] < 13+j+k)
			printf("      Warning: Descriptor too short\n");
		printf("        bUnitID             %5u\n"
		       "        wProcessType        %5u\n"
		       "        bNrPins             %5u\n",
		       buf[3], buf[4] | (buf[5] << 8), buf[6]);
		for (i = 0; i < j; i++)
			printf("        baSourceID(%2u)      %5u\n", i, buf[7+i]);
		printf("        bNrChannels         %5u\n"
		       "        wChannelConfig     0x%04x\n", buf[7+j], chcfg);
		for (i = 0; i < 12; i++)
			if ((chcfg >> i) & 1)
				printf("          %s\n", chconfig[i]);
		printf("        iChannelNames       %5u %s\n"
		       "        bControlSize        %5u\n", buf[10+j], chnames, buf[11+j]);
		for (i = 0; i < k; i++)
			printf("        bmControls(%2u)       0x%02x\n", i, buf[12+j+i]);
		if (buf[12+j] & 1)
			printf("          Enable Processing\n");
		printf("        iProcessing         %5u %s\n"
		       "        Process-Specific    ", buf[12+j+k], term);
		dump_junk2(buf+(13+j+k), buf[0]-(13+j+k));
		break;

	case 0x08:  /* EXTENSION_UNIT */
		printf("(EXTENSION_UNIT)\n");
		j = buf[6];
		k = buf[11+j];
		get_string(dev, chnames, sizeof(chnames), buf[10+j]);
		get_string(dev, term, sizeof(term), buf[12+j+k]);
		chcfg = buf[8+j] | (buf[9+j] << 8);
		if (buf[0] < 13+j+k)
			printf("      Warning: Descriptor too short\n");
		printf("        bUnitID             %5u\n"
		       "        wExtensionCode      %5u\n"
		       "        bNrPins             %5u\n",
		       buf[3], buf[4] | (buf[5] << 8), buf[6]);
		for (i = 0; i < j; i++)
			printf("        baSourceID(%2u)      %5u\n", i, buf[7+i]);
		printf("        bNrChannels         %5u\n"
		       "        wChannelConfig      %5u\n", buf[7+j], chcfg);
		for (i = 0; i < 12; i++)
			if ((chcfg >> i) & 1)
				printf("          %s\n", chconfig[i]);
		printf("        iChannelNames       %5u %s\n"
		       "        bControlSize        %5u\n", buf[10+j], chnames, buf[11+j]);
		for (i = 0; i < k; i++)
			printf("        bmControls(%2u)       0x%02x\n", i, buf[12+j+i]);
		if (buf[12+j] & 1)
			printf("          Enable Processing\n");
		printf("        iExtension          %5u %s\n",
		       buf[12+j+k], term);
		dump_junk(buf, "        ", 13+j+k);
		break;

	default:
		printf("(unknown)\n"
		       "        Invalid desc subtype:");
		dump_junk2(buf+3, buf[0]-3);
		break;
	}
}

static void dump_audiostreaming_interface(struct usb_dev_handle *dev, unsigned char *buf)
{
	static const char *fmtItag[] = { "TYPE_I_UNDEFINED", "PCM", "PCM8", "IEEE_FLOAT", "ALAW", "MULAW" };
	static const char *fmtIItag[] = { "TYPE_II_UNDEFINED", "MPEG", "AC-3" };
	static const char *fmtIIItag[] = { "TYPE_III_UNDEFINED", "IEC1937_AC-3", "IEC1937_MPEG-1_Layer1", 
					   "IEC1937_MPEG-Layer2/3/NOEXT", "IEC1937_MPEG-2_EXT",
					   "IEC1937_MPEG-2_Layer1_LS", "IEC1937_MPEG-2_Layer2/3_LS" };
	unsigned int i, j, fmttag;
	const char *fmtptr = "undefined";

	if (buf[1] != USB_DT_CS_INTERFACE)
		printf("      Warning: Invalid descriptor\n");
	else if (buf[0] < 3)
		printf("      Warning: Descriptor too short\n");
	printf("      AudioStreaming Interface Descriptor:\n"
	       "        bLength             %5u\n"
	       "        bDescriptorType     %5u\n"
	       "        bDescriptorSubtype  %5u ",
	       buf[0], buf[1], buf[2]);
	switch (buf[2]) {
	case 0x01: /* AS_GENERAL */
		printf("(AS_GENERAL)\n");
		if (buf[0] < 7)
			printf("      Warning: Descriptor too short\n");
		fmttag = buf[5] | (buf[6] << 8);
		if (fmttag >= 0 && fmttag <= 5)
			fmtptr = fmtItag[fmttag];
		else if (fmttag >= 0x1000 && fmttag <= 0x1002)
			fmtptr = fmtIItag[fmttag & 0xfff];
		else if (fmttag >= 0x2000 && fmttag <= 0x2006)
			fmtptr = fmtIIItag[fmttag & 0xfff];
		printf("        bTerminalLink       %5u\n"
		       "        bDelay              %5u frames\n"
		       "        wFormatTag          %5u %s\n",
		       buf[3], buf[4], fmttag, fmtptr);
		dump_junk(buf, "        ", 7);
		break;

	case 0x02: /* FORMAT_TYPE */
		printf("(FORMAT_TYPE)\n");
		if (buf[0] < 8)
			printf("      Warning: Descriptor too short\n");
		printf("        bFormatType         %5u ", buf[3]);
		switch (buf[3]) {
		case 0x01: /* FORMAT_TYPE_I */
			printf("(FORMAT_TYPE_I)\n");
			j = buf[7] ? (buf[7]*3+8) : 14;
			if (buf[0] < j)
				printf("      Warning: Descriptor too short\n");
			printf("        bNrChannels         %5u\n"
			       "        bSubframeSize       %5u\n"
			       "        bBitResolution      %5u\n"
			       "        bSamFreqType        %5u %s\n",
			       buf[4], buf[5], buf[6], buf[7], buf[7] ? "Discrete" : "Continuous");
			if (!buf[7])
				printf("        tLowerSamFreq     %7u\n"
				       "        tUpperSamFreq     %7u\n",
				       buf[8] | (buf[9] << 8) | (buf[10] << 16), buf[11] | (buf[12] << 8) | (buf[13] << 16));
			else
				for (i = 0; i < buf[7]; i++)
					printf("        tSamFreq[%2u]      %7u\n", i,
					       buf[8+3*i] | (buf[9+3*i] << 8) | (buf[10+3*i] << 16));
			dump_junk(buf, "        ", j);
			break;

		case 0x02: /* FORMAT_TYPE_II */
			printf("(FORMAT_TYPE_II)\n");
			j = buf[8] ? (buf[7]*3+9) : 15;
			if (buf[0] < j)
				printf("      Warning: Descriptor too short\n");
			printf("        wMaxBitRate         %5u\n"
			       "        wSamplesPerFrame    %5u\n"
			       "        bSamFreqType        %5u %s\n",
			       buf[4] | (buf[5] << 8), buf[6] | (buf[7] << 8), buf[8], buf[8] ? "Discrete" : "Continuous");
			if (!buf[8])
				printf("        tLowerSamFreq     %7u\n"
				       "        tUpperSamFreq     %7u\n",
				       buf[9] | (buf[10] << 8) | (buf[11] << 16), buf[12] | (buf[13] << 8) | (buf[14] << 16));
			else
				for (i = 0; i < buf[8]; i++)
					printf("        tSamFreq[%2u]      %7u\n", i,
					       buf[9+3*i] | (buf[10+3*i] << 8) | (buf[11+3*i] << 16));
			dump_junk(buf, "        ", j);
			break;

		case 0x03: /* FORMAT_TYPE_III */
			printf("(FORMAT_TYPE_III)\n");
			j = buf[7] ? (buf[7]*3+8) : 14;
			if (buf[0] < j)
				printf("      Warning: Descriptor too short\n");
			printf("        bNrChannels         %5u\n"
			       "        bSubframeSize       %5u\n"
			       "        bBitResolution      %5u\n"
			       "        bSamFreqType        %5u %s\n",
			       buf[4], buf[5], buf[6], buf[7], buf[7] ? "Discrete" : "Continuous");
			if (!buf[7])
				printf("        tLowerSamFreq     %7u\n"
				       "        tUpperSamFreq     %7u\n",
				       buf[8] | (buf[9] << 8) | (buf[10] << 16), buf[11] | (buf[12] << 8) | (buf[13] << 16));
			else
				for (i = 0; i < buf[7]; i++)
					printf("        tSamFreq[%2u]      %7u\n", i,
					       buf[8+3*i] | (buf[9+3*i] << 8) | (buf[10+3*i] << 16));
			dump_junk(buf, "        ", j);
			break;

		default:
			printf("(unknown)\n"
			       "        Invalid desc format type:");
			dump_junk2(buf+4, buf[0]-4);
		}
		break;

	case 0x03: /* FORMAT_SPECIFIC */
		printf("(FORMAT_SPECIFIC)\n");
		if (buf[0] < 5)
			printf("      Warning: Descriptor too short\n");
		fmttag = buf[3] | (buf[4] << 8);
		if (fmttag >= 0 && fmttag <= 5)
			fmtptr = fmtItag[fmttag];
		else if (fmttag >= 0x1000 && fmttag <= 0x1002)
			fmtptr = fmtIItag[fmttag & 0xfff];
		else if (fmttag >= 0x2000 && fmttag <= 0x2006)
			fmtptr = fmtIIItag[fmttag & 0xfff];
		printf("        wFormatTag          %5u %s\n", fmttag, fmtptr);
		switch (fmttag) {
		case 0x1001: /* MPEG */
			if (buf[0] < 8)
				printf("      Warning: Descriptor too short\n");
			printf("        bmMPEGCapabilities 0x%04x\n",
			       buf[5] | (buf[6] << 8));
			if (buf[5] & 0x01)
				printf("          Layer I\n");
			if (buf[5] & 0x02)
				printf("          Layer II\n");
			if (buf[5] & 0x04)
				printf("          Layer III\n");
			if (buf[5] & 0x08)
				printf("          MPEG-1 only\n");
			if (buf[5] & 0x10)
				printf("          MPEG-1 dual-channel\n");
			if (buf[5] & 0x20)
				printf("          MPEG-2 second stereo\n");
			if (buf[5] & 0x40)
				printf("          MPEG-2 7.1 channel augmentation\n");
			if (buf[5] & 0x80)
				printf("          Adaptive multi-channel prediction\n");
			printf("          MPEG-2 multilingual support: ");
			switch (buf[6] & 3) {
			case 0:
				printf("Not supported\n");
				break;
			
			case 1:
				printf("Supported at Fs\n");
				break;
			
			case 2:
				printf("Reserved\n");
				break;
			
			default:
				printf("Supported at Fs and 1/2Fs\n");
				break;
			}
			printf("        bmMPEGFeatures       0x%02x\n", buf[7]);
			printf("          Internal Dynamic Range Control: ");
			switch ((buf[7] << 4) & 3) {
			case 0:
				printf("not supported\n");
				break;
			
			case 1:
				printf("supported but not scalable\n");
				break;
			
			case 2:
				printf("scalable, common boost and cut scaling value\n");
				break;
			
			default:
				printf("scalable, separate boost and cut scaling value\n");
				break;
			}
			dump_junk(buf, "        ", 8);
			break;

		case 0x1002: /* AC-3 */
			if (buf[0] < 10)
				printf("      Warning: Descriptor too short\n");
			printf("        bmBSID         0x%08x\n"
			       "        bmAC3Features        0x%02x\n",
			       buf[5] | (buf[6] << 8) | (buf[7] << 16) | (buf[8] << 24), buf[9]);
			if (buf[9] & 0x0)
				printf("          RF mode\n");
			if (buf[9] & 0x0)
				printf("          Line mode\n");
			if (buf[9] & 0x0)
				printf("          Custom0 mode\n");
			if (buf[9] & 0x0)
				printf("          Custom1 mode\n");
			printf("          Internal Dynamic Range Control: ");
			switch ((buf[9] >> 4) & 3) {
			case 0:
				printf("not supported\n");
				break;
				
			case 1:
				printf("supported but not scalable\n");
				break;
				
			case 2:
				printf("scalable, common boost and cut scaling value\n");
				break;
				
			default:
				printf("scalable, separate boost and cut scaling value\n");
				break;
			}
			dump_junk(buf, "        ", 8);
			break;

		default:
			printf("(unknown)\n"
			       "        Invalid desc format type:");
			dump_junk2(buf+4, buf[0]-4);
		}
		break;

	default:
		printf("        Invalid desc subtype:");
		dump_junk2(buf+3, buf[0]-3);
		break;
	}
}

static void dump_audiostreaming_endpoint(struct usb_dev_handle *dev, unsigned char *buf)
{
	static const char *lockdelunits[] = { "Undefined", "Milliseconds", "Decoded PCM samples", "Reserved" };
	unsigned int lckdelidx;

	if (buf[1] != USB_DT_CS_ENDPOINT)
		printf("      Warning: Invalid descriptor\n");
	else if (buf[0] < 7)
		printf("      Warning: Descriptor too short\n");
	printf("        AudioControl Endpoint Descriptor:\n"
	       "          bLength             %5u\n"
	       "          bDescriptorType     %5u\n"
	       "          bDescriptorSubtype  %5u (%s)\n"
	       "          bmAttributes         0x%02x\n",
	       buf[0], buf[1], buf[2], buf[2] == 1 ? "EP_GENERAL" : "invalid", buf[3]);
	if (buf[3] & 1)
		printf("            Sampling Frequency\n");
	if (buf[3] & 2)
		printf("            Pitch\n");
	if (buf[3] & 128)
		printf("            MaxPacketsOnly\n");
	lckdelidx = buf[4];
	if (lckdelidx > 3)
		lckdelidx = 3;
	printf("          bLockDelayUnits     %5u %s\n"
	       "          wLockDelay          %5u %s\n",
	       buf[4], lockdelunits[lckdelidx], buf[5] | (buf[6] << 8), lockdelunits[lckdelidx]);
	dump_junk(buf, "        ", 7);
}

static void dump_midistreaming_interface(struct usb_dev_handle *dev, unsigned char *buf)
{
	static const char *jacktypes[] = {"Undefined", "Embedded", "External"};
	char jackstr[128];
	unsigned int j, tlength, capssize;
	unsigned long caps;

	if (buf[1] != USB_DT_CS_INTERFACE)
		printf("      Warning: Invalid descriptor\n");
	else if (buf[0] < 3)
		printf("      Warning: Descriptor too short\n");
	printf( "      MIDIStreaming Interface Descriptor:\n"
		"        bLength             %5u\n"
		"        bDescriptorType     %5u\n"
		"        bDescriptorSubtype  %5u ",
		buf[0], buf[1], buf[2]);
	switch (buf[2]) {
	case 0x01:
		printf("(HEADER)\n");
		if (buf[0] < 7)
			printf("      Warning: Descriptor too short\n");
		tlength = buf[5] | (buf[6] << 8);
		printf( "        bcdADC              %2x.%02x\n"
			"        wTotalLength        %5u\n",
			buf[4], buf[3], tlength);
		dump_junk(buf, "        ", 7);
		break;

	case 0x02:
		printf("(MIDI_IN_JACK)\n");
		if (buf[0] < 6)
			printf("      Warning: Descriptor too short\n");
		get_string(dev, jackstr, sizeof(jackstr), buf[5]);
		printf( "        bJackType           %5u %s\n"
			"        bJackID             %5u\n"
			"        iJack               %5u %s\n",
			buf[3], buf[3] < 3 ? jacktypes[buf[3]] : "Invalid",
			buf[4], buf[5], jackstr);
		dump_junk(buf, "        ", 6);
		break;

	case 0x03:
		printf("(MIDI_OUT_JACK)\n");
		if (buf[0] < 9)
			printf("      Warning: Descriptor too short\n");
		printf( "        bJackType           %5u %s\n"
			"        bJackID             %5u\n"
			"        bNrInputPins        %5u\n",
			buf[3], buf[3] < 3 ? jacktypes[buf[3]] : "Invalid",
			buf[4], buf[5]);
		for (j=0; j < buf[5]; j++) {
			printf( "        baSourceID(%2u)      %5u\n"
				"        BaSourcePin(%2u)     %5u\n",
				j, buf[2*j+6], j, buf[2*j+7]);
		}
		j = 6+buf[5]*2; /* midi10.pdf says, incorrectly: 5+2*p */
		get_string(dev, jackstr, sizeof(jackstr), buf[j]);
		printf( "        iJack               %5u %s\n",
			buf[j], jackstr);
		dump_junk(buf, "        ", j+1);
		break;

	case 0x04:
		printf("(ELEMENT)\n");
		if (buf[0] < 12)
			printf("      Warning: Descriptor too short\n");
		printf( "        bElementID          %5u\n"
			"        bNrInputPins        %5u\n",
			buf[3], buf[4]);
		for(j=0; j < buf[4]; j++) {
		printf( "        baSourceID(%2u)      %5u\n"
			"        BaSourcePin(%2u)     %5u\n",
			j, buf[2*j+5], j, buf[2*j+6]);
		}
		j = 5+buf[4]*2;
		printf( "        bNrOutputPins       %5u\n"
			"        bInTerminalLink     %5u\n"
			"        bOutTerminalLink    %5u\n"
			"        bElCapsSize         %5u\n",
			buf[j], buf[j+1], buf[j+2], buf[j+3]);
		capssize = buf[j+3];
		caps = 0;
		for(j=0; j < capssize; j++) {
			caps |= (buf[j+9+buf[4]*2] << (8*j));
		}
		printf( "        bmElementCaps  0x%08lx\n", caps);
		if (caps & 0x01)
			printf( "          Undefined\n");
		if (caps & 0x02)
			printf( "          MIDI Clock\n");
		if (caps & 0x04)
			printf( "          MTC (MIDI Time Code)\n");
		if (caps & 0x08)
			printf( "          MMC (MIDI Machine Control)\n");
		if (caps & 0x10)
			printf( "          GM1 (General MIDI v.1)\n");
		if (caps & 0x20)
			printf( "          GM2 (General MIDI v.2)\n");
		if (caps & 0x40)
			printf( "          GS MIDI Extension\n");
		if (caps & 0x80)
			printf( "          XG MIDI Extension\n");
		if (caps & 0x100)
			printf( "          EFX\n");
		if (caps & 0x200)
			printf( "          MIDI Patch Bay\n");
		if (caps & 0x400)
			printf( "          DLS1 (Downloadable Sounds Level 1)\n");
		if (caps & 0x800)
			printf( "          DLS2 (Downloadable Sounds Level 2)\n");
		j = 9+2*buf[4]+capssize;
		get_string(dev, jackstr, sizeof(jackstr), buf[j]);
		printf( "        iElement            %5u %s\n", buf[j], jackstr);
		dump_junk(buf, "        ", j+1);
		break;

	default:
		printf("\n        Invalid desc subtype: ");
		dump_junk2(buf+3, buf[0]-3);
		break;
	}
}

static void dump_midistreaming_endpoint(struct usb_dev_handle *dev, unsigned char *buf)
{
	unsigned int j;

	if (buf[1] != USB_DT_CS_ENDPOINT)
		printf("      Warning: Invalid descriptor\n");
	else if (buf[0] < 5)
		printf("      Warning: Descriptor too short\n");
	printf("        MIDIStreaming Endpoint Descriptor:\n"
	       "          bLength             %5u\n"
	       "          bDescriptorType     %5u\n"
	       "          bDescriptorSubtype  %5u (%s)\n"
	       "          bNumEmbMIDIJack     %5u\n",
	       buf[0], buf[1], buf[2], buf[2] == 1 ? "GENERAL" : "Invalid", buf[3]);
	for (j=0; j<buf[3]; j++) {
		printf("          baAssocJackID(%2u)   %5u\n", j, buf[4+j]);
	}
	dump_junk(buf, "          ", 4+buf[3]);
}

static void dump_hub(char *prefix, unsigned char *p, int has_tt)
{
	unsigned int l, i, j;
	unsigned int wHubChar = (p[4] << 8) | p[3];
	
	printf("%sHub Descriptor:\n", prefix);
	printf("%s  bLength             %3u\n", prefix, p[0]);
	printf("%s  bDescriptorType     %3u\n", prefix, p[1]);
	printf("%s  nNbrPorts           %3u\n", prefix, p[2]); 
	printf("%s  wHubCharacteristic 0x%04x\n", prefix, wHubChar);
	switch (wHubChar & 0x03) {
	case 0:
		printf("%s    Ganged power switching\n", prefix);
		break;
	case 1:
		printf("%s    Per-port power switching\n", prefix);
		break;
	default:
		printf("%s    No power switching (usb 1.0)\n", prefix);
		break;
	}
	if (wHubChar & 0x04)
		printf("%s    Compound device\n", prefix);
	switch ((wHubChar >> 3) & 0x03) {
	case 0:
		printf("%s    Ganged overcurrent protection\n", prefix);
		break;
	case 1:
		printf("%s    Per-port overcurrent protection\n", prefix);
		break;
	default:
		printf("%s    No overcurrent protection\n", prefix);
		break;
	}
	if (has_tt) {
		l = (wHubChar >> 5) & 0x03;
		printf("%s    TT think time %d FS bits\n", prefix, (l + 1) * 8);
	}
	if (wHubChar & (1<<7))
		printf("%s    Port indicators\n", prefix);
	printf("%s  bPwrOn2PwrGood      %3u * 2 milli seconds\n", prefix, p[5]);
	printf("%s  bHubContrCurrent    %3u milli Ampere\n", prefix, p[6]);
	l= (p[2] >> 3) + 1; /* this determines the variable number of bytes following */
	printf("%s  DeviceRemovable   ", prefix);
	for(i = 0; i < l; i++) 
		printf(" 0x%02x", p[7+i]);
	printf("\n%s  PortPwrCtrlMask   ", prefix);
 	for(j = 0; j < l; j++)
 		printf(" 0x%02x ", p[7+i+j]);
 	printf("\n");
}

static void dump_ccid_device(unsigned char *buf)
{
	unsigned int j, tlength, capssize;
	unsigned long caps;
        unsigned int us;

	if (buf[0] < 54) {
		printf("      Warning: Descriptor too short\n");
                return;
        }
	printf("      ChipCard Interface Descriptor:\n"
	       "        bLength             %5u\n"
	       "        bDescriptorType     %5u\n"
	       "        bcdCCID             %2x.%02x",
	       buf[0], buf[1], buf[3], buf[2]);
        if (buf[3] != 1 || buf[2] != 0) 
                fputs("  (Warning: Only accurate for version 1.0)", stdout);
        putchar('\n');

        printf("        nMaxSlotIndex       %5u\n"
                "        bVoltageSupport     %5u  %s\n",
                buf[4],
                buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V"
                         : buf[5] == 3? "1.8V":"?"));

        us = convert_le_u32 (buf+6);
        printf("        dwProtocols         %5u ", us);
        if ((us & 1))
                fputs(" T=0", stdout);
        if ((us & 2))
                fputs(" T=1", stdout);
        if ((us & ~3))
                fputs(" (Invalid values detected)", stdout);
        putchar('\n');

        us = convert_le_u32(buf+10);
        printf("        dwDefaultClock      %5u\n", us);
        us = convert_le_u32(buf+14);
        printf("        dwMaxiumumClock     %5u\n", us);
        printf("        bNumClockSupported  %5u\n", buf[18]);
        us = convert_le_u32(buf+19);
        printf("        dwDataRate        %7u bps\n", us);
        us = convert_le_u32(buf+23);
        printf("        dwMaxDataRate     %7u bps\n", us);
        printf("        bNumDataRatesSupp.  %5u\n", buf[27]);
        
        us = convert_le_u32(buf+28);
        printf("        dwMaxIFSD           %5u\n", us);

        us = convert_le_u32(buf+32);
        printf("        dwSyncProtocols  %08X ", us);
        if ((us&1))
                fputs(" 2-wire", stdout);
        if ((us&2))
                fputs(" 3-wire", stdout);
        if ((us&4))
                fputs(" I2C", stdout);
        putchar('\n');

        us = convert_le_u32(buf+36);
        printf("        dwMechanical     %08X ", us);
        if ((us & 1))
                fputs(" accept", stdout);
        if ((us & 2))
                fputs(" eject", stdout);
        if ((us & 4))
                fputs(" capture", stdout);
        if ((us & 8))
                fputs(" lock", stdout);
        putchar('\n');

        us = convert_le_u32(buf+40);
        printf("        dwFeatures       %08X\n", us);
        if ((us & 0x0002))
		fputs("          Auto configuration based on ATR\n",stdout);
        if ((us & 0x0004))
		fputs("          Auto activation on insert\n",stdout);
        if ((us & 0x0008))
		fputs("          Auto voltage selection\n",stdout);
        if ((us & 0x0010))
		fputs("          Auto clock change\n",stdout);
        if ((us & 0x0020))
		fputs("          Auto baud rate change\n",stdout);
        if ((us & 0x0040))
		fputs("          Auto parameter negotation made by CCID\n",stdout);
        else if ((us & 0x0080))
		fputs("          Auto PPS made by CCID\n",stdout);
        else if ((us & (0x0040 | 0x0080)))
		fputs("        WARNING: conflicting negotation features\n",stdout);

        if ((us & 0x0100))
		fputs("          CCID can set ICC in clock stop mode\n",stdout);
        if ((us & 0x0200))
		fputs("          NAD value other than 0x00 accpeted\n",stdout);
        if ((us & 0x0400))
		fputs("          Auto IFSD exchange\n",stdout);

        if ((us & 0x00010000))
		fputs("          TPDU level exchange\n",stdout);
        else if ((us & 0x00020000))
		fputs("          Short APDU level exchange\n",stdout);
        else if ((us & 0x00040000))
		fputs("          Short and extended APDU level exchange\n",stdout);
        else if ((us & 0x00070000))
		fputs("        WARNING: conflicting exchange levels\n",stdout);

        us = convert_le_u32(buf+44);
        printf("        dwMaxCCIDMsgLen     %5u\n", us);

        printf("        bClassGetResponse    ");
        if (buf[48] == 0xff)
                fputs("echo\n", stdout);
        else
                printf("  %02X\n", buf[48]);

        printf("        bClassEnvelope       ");
        if (buf[49] == 0xff)
                fputs("echo\n", stdout);
        else
                printf("  %02X\n", buf[48]);

        printf("        wlcdLayout           ");
        if (!buf[50] && !buf[51])
		fputs("none\n", stdout);
        else
		printf("%u cols %u lines\n", buf[50], buf[51]);
        
        printf("        bPINSupport         %5u ", buf[52]);
        if ((buf[52] & 1))
		fputs(" verification", stdout);
        if ((buf[52] & 2))
		fputs(" modification", stdout);
        putchar('\n');
        
        printf("        bMaxCCIDBusySlots   %5u\n", buf[53]);

        if (buf[0] > 54) {
                fputs("        junk             ", stdout);
                dump_junk2(buf+54, buf[0]-54);
        }
}

/* ---------------------------------------------------------------------- */

/*
 * HID descriptor
 */

static void dump_report_desc(unsigned char *b, int l)
{
        unsigned int t, j, bsize, btag, btype, data = 0xffff, hut = 0xffff;
	int i;
	char *types[4] = { "Main", "Global", "Local", "reserved" };
	char indent[] = "                            ";

	printf("	  Report Descriptor: (length is %d)\n", l);
	for(i = 0; i < l; ) {
		t = b[i];
		bsize = b[i] & 0x03;
		if (bsize == 3)
                        bsize = 4;
		btype = b[i] & (0x03 << 2);
		btag = b[i] & ~0x03; /* 2 LSB bits encode length */
		printf("            Item(%-6s): %s, data=", types[btype>>2], names_reporttag(btag));
		if (bsize > 0) {
			printf(" [ ");
			data = 0;
			for(j = 0; j < bsize; j++) {
				printf("0x%02x ", b[i+1+j]);
				data += (b[i+1+j] << (8*j));
			}
			printf("] %d", data);
		} else
                        printf("none");
		printf("\n");
		switch(btag) {
		case 0x04 : /* Usage Page */
                        printf("%s%s\n", indent, names_huts(data));
			hut = data;
                        break;
                        
		case 0x08 : /* Usage */
		case 0x18 : /* Usage Minimum */
		case 0x28 : /* Usage Maximum */
			printf("%s%s\n", indent, names_hutus((hut << 16) + data));
			break;
                        
		case 0xa0 : /* Collection */
			printf("%s", indent);
			switch (data) {
			case 0x00:
                                printf("Physical\n");
                                break;
                                
			case 0x01:
                                printf("Application\n");
                                break;
                                
			case 0x02:
                                printf("Logical\n");
                                break;
                                
			default:
				if(data & 0x80)
					printf("Vendor defined\n");
				else
					printf("Reserved for future use.\n");
			}
			break;
		case 0x80: /* Input */
		case 0x90: /* Output */
		case 0xb0: /* Feature */
			printf("%s%s %s %s %s %s\n%s%s %s %s %s\n",
			       indent,
			       data & 0x01 ? "Constant": "Data",
			       data & 0x02 ? "Variable": "Array",
			       data & 0x04 ? "Relative": "Absolute",
			       data & 0x08 ? "Wrap" : "No_Wrap",
			       data & 0x10 ? "Non_Linear": "Linear",
			       indent,
			       data & 0x20 ? "No_Preferred_State": "Preferred_State",
			       data & 0x40 ? "Null_State": "No_Null_Position",
			       data & 0x80 ? "Volatile": "Non_Volatile",
			       data &0x100 ? "Buffered Bytes": "Bitfield"
				);
		}
		i += 1 + bsize;
	}
}

static void dump_hid_device(struct usb_dev_handle *dev, struct usb_interface_descriptor *interface, unsigned char *buf)
{
	unsigned int i, len;
	int n;
	unsigned char dbuf[8192];

	if (buf[1] != USB_DT_HID)
		printf("      Warning: Invalid descriptor\n");
	else if (buf[0] < 6+3*buf[5])
		printf("      Warning: Descriptor too short\n");
	printf("        HID Device Descriptor:\n"
	       "          bLength             %5u\n"
	       "          bDescriptorType     %5u\n"
	       "          bcdHID              %2x.%02x\n"
	       "          bCountryCode        %5u\n"
	       "          bNumDescriptors     %5u\n",
	       buf[0], buf[1], buf[3], buf[2], buf[4], buf[5]);
	for (i = 0; i < buf[5]; i++)
		printf("          bDescriptorType     %5u %s\n"
		       "          wDescriptorLength   %5u\n", 
		       buf[6+3*i], names_hid(buf[6+3*i]), buf[7+3*i] | (buf[8+3*i] << 8));
	dump_junk(buf, "        ", 6+3*buf[5]);
	if (!do_report_desc)
		return;
	
	for (i = 0; i < buf[5]; i++) {
		if (buf[6+3*i] != USB_DT_REPORT) /* we are just interested in report descriptors*/
			continue;
		len = buf[7+3*i] | (buf[8+3*i] << 8);
		if (len > sizeof(dbuf)) {
			printf("report descriptor too long\n");
			continue;
		}
		if ((n = usb_control_msg(dev, 
					 USB_ENDPOINT_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE, 
					 USB_REQ_GET_DESCRIPTOR, 
					 (USB_DT_REPORT << 8), 
					 interface->bInterfaceNumber,
					 dbuf, 
					 len, 
					 CTRL_TIMEOUT)) > 0)
			dump_report_desc(dbuf, n);
	}
}

static char *dump_comm_descriptor(struct usb_dev_handle *dev, unsigned char *buf, char *indent)
{
	int		tmp;
	char		str [128];

	switch (buf[2]) {
	case 0:
		if (buf[0] != 5)
			goto bad;
		printf("%sCDC Header:\n"
		       "%s  bcdCDC               %x.%02x\n",
		       indent,
		       indent, buf[4], buf[3]);
		break;
	case 0x01:		/* call management functional desc */
		if (buf [0] != 5)
			goto bad;
		printf("%sCDC Call Management:\n"
		       "%s  bmCapabilities       0x%02x\n",
		       indent,
		       indent, buf[3]);
		if (buf[3] & 0x01)
			printf("%s    call management\n", indent);
		if (buf[3] & 0x02)
			printf("%s    use DataInterface\n", indent);
		printf("%s  bDataInterface        %d\n", indent, buf[4]);
		break;
	case 0x02:		/* acm functional desc */
		if (buf [0] != 4)
			goto bad;
		printf("%sCDC ACM:\n"
		       "%s  bmCapabilities       %02x\n",
		       indent,
		       indent, buf[3]);
		if (buf[3] & 0x08)
			printf("%s    connection notifications\n", indent);
		if (buf[3] & 0x04)
			printf("%s    sends break\n", indent);
		if (buf[3] & 0x02)
			printf("%s    line coding and serial state\n", indent);
		if (buf[3] & 0x01)
			printf("%s    get/set/clear comm features\n", indent);
		break;
	case 0x06:		/* union desc */
		if (buf [0] < 5)
			goto bad;
		printf("%sCDC Union:\n"
		       "%s  bMasterInterface        %d\n"
		       "%s  bSlaveInterface         ",
		       indent,
		       indent, buf [3],
		       indent);
		for (tmp = 4; tmp < buf [0]; tmp++)
			printf("%d ", buf [tmp]);
		printf("\n");
		break;
	case 0x0f:		/* ethernet functional desc */
		if (buf [0] != 13)
			goto bad;
		get_string(dev, str, sizeof str, buf[3]);
		tmp = buf [7] << 8;
		tmp |= buf [6]; tmp <<= 8;
		tmp |= buf [5]; tmp <<= 8;
		tmp |= buf [4];
		printf("%sCDC Ethernet:\n"
		       "%s  iMacAddress             %d %s\n"
		       "%s  bmEthernetStatistics    0x%08x\n",
		       indent,
		       indent, buf[3], (buf[3] && *str) ? str : "(?\?)",
		       indent, tmp);
		/* FIXME dissect ALL 28 bits */
		printf("%s  wMaxSegmentSize         %d\n"
		       "%s  wNumberMCFilters        0x%04x\n"
		       "%s  bNumberPowerFilters     %d\n",
		       indent, (buf[9]<<8)|buf[8],
		       indent, (buf[11]<<8)|buf[10],
		       indent, buf[12]);
		break;
		/* FIXME there are about a dozen more descriptor types */
	default:
		return "unsupported comm descriptor";
	}
	return 0;

  bad:
	return "corrupt comm descriptor";
}

/* ---------------------------------------------------------------------- */

static void dumpdev(struct usb_device *dev, unsigned int flags)
{
	struct usb_dev_handle *udev;
	int i;

	udev = usb_open(dev);
	if (dev->config) {
		dump_device(udev, &dev->descriptor, flags);
		for (i = 0 ; i < dev->descriptor.bNumConfigurations ; i++) {
			dump_config(udev, &dev->config[i]);
		}
		usb_close(udev);
	}
	else
		fprintf(stderr, "Couldn't retrieve descriptors\n");
}

/* ---------------------------------------------------------------------- */

static int dump_one_device(const char *path, unsigned int flags)
{
	struct usb_device *dev;
	char vendor[128], product[128];

	dev = get_usb_device(path);
	if (!dev) {
		fprintf(stderr, "Cannot open %s\n", path);
		return 1;
	}
	get_vendor_string(vendor, sizeof(vendor), dev->descriptor.idVendor);
	get_product_string(product, sizeof(product), dev->descriptor.idVendor, dev->descriptor.idProduct);
	printf("Device: ID %04x:%04x %s %s\n", dev->descriptor.idVendor, 
					       dev->descriptor.idProduct, 
					       vendor, 
					       product);
	dumpdev(dev, flags);
	return 0;
}

static int list_devices(int busnum, int devnum, int vendorid, int productid, unsigned int flags)
{
	struct usb_bus *bus;
	struct usb_device *dev;
	char vendor[128], product[128];
	int status;

	status=1; /* 1 device not found, 0 device found */
	
	for (bus = usb_busses; bus; bus = bus->next) {
		if (busnum != -1 && strtoul(bus->dirname, NULL, 0) != busnum)
			continue;
		for (dev = bus->devices; dev; dev = dev->next) {
			if (devnum != -1 && strtoul(dev->filename, NULL, 0) != devnum)
				continue;
			if ((vendorid != -1 && vendorid != dev->descriptor.idVendor) || (productid != -1 && productid != dev->descriptor.idProduct))
				continue;
			status = 0;
			get_vendor_string(vendor, sizeof(vendor), dev->descriptor.idVendor);
			get_product_string(product, sizeof(product), dev->descriptor.idVendor, dev->descriptor.idProduct);
			if (verblevel > 0)
				printf("\n");
			printf("Bus %s Device %s: ID %04x:%04x %s %s\n", bus->dirname, 
					                                 dev->filename, 
									 dev->descriptor.idVendor, 
									 dev->descriptor.idProduct, 
									 vendor, 
									 product);
			if (verblevel > 0)
				dumpdev(dev, flags);
		}
	}
	return(status);
}

/* ---------------------------------------------------------------------- */

void devtree_busconnect(struct usbbusnode *bus)
{
}

void devtree_busdisconnect(struct usbbusnode *bus)
{
}

void devtree_devconnect(struct usbdevnode *dev)
{
}

void devtree_devdisconnect(struct usbdevnode *dev)
{
}

static int treedump(void)
{ 
	int fd;
	char buf[512];

	snprintf(buf, sizeof(buf), "%s/devices", procbususb);
	if ((fd = open(buf, O_RDONLY)) == -1) {
                fprintf(stderr, "cannot open %s, %s (%d)\n", buf, strerror(errno), errno);
                return(1);
	}
	devtree_parsedevfile(fd);
	close(fd);
        devtree_dump();
	return(0);
}

/* ---------------------------------------------------------------------- */

int main(int argc, char *argv[])
{
        static const struct option long_options[] = {
                { "version", 0, 0, 'V' },
		{ "verbose", 0, 0, 'v' },
                { 0, 0, 0, 0 }
        };
	int c, err = 0;
	unsigned int allowctrlmsg = 0, treemode = 0;
	int bus = -1, devnum = -1, vendor = -1, product = -1;
	const char *devdump = NULL;
	char *cp;
	int status;

	while ((c = getopt_long(argc, argv, "D:vxtP:p:s:d:V", long_options, NULL)) != EOF) {
		switch(c) {
                case 'V':
                        printf("lsusb (" PACKAGE ")  " VERSION "\n");
                        exit(0);
                        
		case 'v':
			verblevel++;
			break;

		case 'x':
			allowctrlmsg = 1;
			break;

		case 't':
			treemode = 1;
			break;

		case 's':
			cp = strchr(optarg, ':');
			if (cp) {
				*cp++ = 0;
				if (*optarg)
					bus = strtoul(optarg, NULL, 0);
				if (*cp)
					devnum = strtoul(cp, NULL, 0);
			} else {
				if (*optarg)
					devnum = strtoul(optarg, NULL, 0);
			}
			break;

		case 'd':
			cp = strchr(optarg, ':');
			if (!cp) {
				err++;
				break;
			}
			*cp++ = 0;
			if (*optarg)
				vendor = strtoul(optarg, NULL, 0);
			if (*cp)
				product = strtoul(cp, NULL, 0);
			break;

		case 'D':
			devdump = optarg;
			break;

		default:
			err++;
			break;
		}
	}
	if (err || argc > optind) {
                fprintf(stderr, "Usage: lsusb [options]...\n"
			"List all USB devices\n"
			"\n"
			"OPTIONS\n"
			"  -v\n"
			"      Increase verbosity\n"
			"  -s [[bus]:][devnum]\n"
			"      Show only devices in specified bus and/or devnum\n"
			"  -d vendor:[product]\n"
			"      Show only devices with the specified vendor and product ID\n"
			"  -D device\n"
			"      Selects which device lsusb will examine\n"
			"  -t\n"
			"      Dump the physical USB device hierarchy as a tree\n"
			"  -v, --version\n"
			"      Show version of program\n"
			"\n");
		exit(1);
	}
	if ((err = names_init("./usb.ids")) != 0)
		if ((err = names_init(USBIDS_FILE)) != 0) {
			printf("Error, cannot open USBIDS File \"%s\", %s\n", USBIDS_FILE, strerror(err));
			exit(1);
	}
	status = 0;

	usb_init();

	usb_find_busses();
	usb_find_devices();
	      
	
	if (treemode) {
		/* treemode requires at least verblevel 1 */
 		verblevel += 1 - VERBLEVEL_DEFAULT;
		status = treedump();
	} else if (devdump)
		status = dump_one_device(devdump, 0);
	else
		status = list_devices(bus, devnum, vendor, product, 0);
	return status;
}
