/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/mdb_modapi.h>


#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_types.h>
#include <sys/usb/clients/hid/hid.h>
#include <sys/usb/clients/hidparser/hidparser.h>
#include <sys/usb/clients/hidparser/hidparser_impl.h>
#include <sys/usb/usba/genconsole.h>
#include <sys/usb/clients/hid/hidvar.h>


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

/* extenal definition */

typedef struct mdb_ctf_id {
	void *_opaque[2];
} mdb_ctf_id_t;

extern int mdb_ctf_lookup_by_name(const char *, mdb_ctf_id_t *);

extern int mdb_devinfo2driver(uintptr_t, char *, size_t);

extern int mdb_devinfo2statep(uintptr_t, char *, uintptr_t *);

extern char *mdb_ddi_pathname(uintptr_t, char *, size_t);


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

/* internal definition */

#define	OPT_TREE	0x01
#define	OPT_VERB	0x02

#define	STRLEN		256
#define	BYTE_OFFSET	8


typedef	struct usb_descr_item {
	uint_t	nlen;	/* if it's an byte array, nlen += BYTE_OFFSET */
	char	*name;	/* descriptor item name */
} usb_descr_item_t;

/* define the known descriptor items */
static usb_descr_item_t usb_cfg_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{2, "wTotalLength"},
	{1, "bNumInterfaces"},
	{1, "bConfigurationValue"},
	{1, "iConfiguration"},
	{1, "bmAttributes"},
	{1, "bMaxPower"},
};
static uint_t usb_cfg_item = 8;

static usb_descr_item_t usb_ia_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bFirstInterface"},
	{1, "bInterfaceCount"},
	{1, "bFunctionClass"},
	{1, "bFunctionSubClass"},
	{1, "bFunctionProtocol"},
	{1, "iFunction"},
};
static uint_t usb_ia_item = 8;

static usb_descr_item_t usb_if_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bInterfaceNumber"},
	{1, "bAlternateSetting"},
	{1, "bNumEndpoints"},
	{1, "bInterfaceClass"},
	{1, "bInterfaceSubClass"},
	{1, "bInterfaceProtocol"},
	{1, "iInterface"},
};
static uint_t usb_if_item = 9;

static usb_descr_item_t usb_ep_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bEndpointAddress"},
	{1, "bmAttributes"},
	{2, "wMaxPacketSize"},
	{1, "bInterval"},
};
static uint_t usb_ep_item = 6;

static usb_descr_item_t usb_qlf_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{2, "bcdUSB"},
	{1, "bDeviceClass"},
	{1, "bDeviceSubClass"},
	{1, "bDeviceProtocol"},
	{1, "bMaxPacketSize0"},
	{1, "bNumConfigurations"},
	{1, "bReserved"},
};
static uint_t usb_qlf_item = 9;

static usb_descr_item_t usb_str_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bString"},
};
static uint_t usb_str_item = 3;

static usb_descr_item_t usb_hid_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{2, "bcdHID"},
	{1, "bCountryCode"},
	{1, "bNumDescriptors"},
	{1, "bReportDescriptorType"},
	{2, "wReportDescriptorLength"},
};
static uint_t usb_hid_item = 7;

static usb_descr_item_t usb_ac_header_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{2, "bcdADC"},
	{2, "wTotalLength"},
	{1, "blnCollection"},
	{1, "baInterfaceNr"},
};
static uint_t usb_ac_header_item = 7;

static usb_descr_item_t usb_ac_input_term_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bTerminalID"},
	{2, "wTerminalType"},
	{1, "bAssocTerminal"},
	{1, "bNrChannels"},
	{2, "wChannelConfig"},
	{1, "iChannelNames"},
	{1, "iTerminal"},
};
static uint_t usb_ac_input_term_item = 10;

static usb_descr_item_t usb_ac_output_term_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bTerminalID"},
	{2, "wTerminalType"},
	{1, "bAssocTerminal"},
	{1, "bSourceID"},
	{1, "iTerminal"},
};
static uint_t usb_ac_output_term_item = 8;

static usb_descr_item_t usb_ac_mixer_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{1, "bNrInPins"},
	{1, "baSourceID"},
};
static uint_t usb_ac_mixer_item = 6;

static usb_descr_item_t usb_ac_selector_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{1, "bNrInPins"},
	{1, "baSourceID"},
};
static uint_t usb_ac_selector_item = 6;

static usb_descr_item_t usb_ac_feature_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{1, "bSourceID"},
	{1, "bControlSize"},
	{1, "bmaControls"},
};
static uint_t usb_ac_feature_item = 7;

static usb_descr_item_t usb_ac_processing_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{1, "wProcessType"},
	{1, "bNrInPins"},
	{1, "baSourceID"},
};
static uint_t usb_ac_processing_item = 7;

static usb_descr_item_t usb_ac_extension_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "wExtensionCode"},
	{1, "bUnitID"},
	{1, "bNrInPins"},
	{1, "baSourceID"},
};
static uint_t usb_ac_extension_item = 7;

static usb_descr_item_t usb_as_ep_descr[] = {
	{1, "blength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bmAttributes"},
	{1, "bLockDelayUnits"},
	{2, "wLockDelay"},
};
static uint_t usb_as_ep_item = 6;

static usb_descr_item_t usb_as_if_descr[] = {
	{1, "blength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bTerminalLink"},
	{1, "bDelay"},
	{2, "wFormatTag"},
};
static uint_t usb_as_if_item = 6;

static usb_descr_item_t usb_as_format_descr[] = {
	{1, "blength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bFormatType"},
	{1, "bNrChannels"},
	{1, "bSubFrameSize"},
	{1, "bBitResolution"},
	{1, "bSamFreqType"},
	{1, "bSamFreqs"},
};
static uint_t usb_as_format_item = 9;

static usb_descr_item_t usb_vc_header_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubtype"},
	{2, "bcdUVC"},
	{2, "wTotalLength"},
	{4, "dwClockFrequency"},
	{1, "bInCollection"},
};
static uint_t usb_vc_header_item = 7;

static usb_descr_item_t usb_vc_input_term_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bTerminalID"},
	{2, "wTerminalType"},
	{1, "AssocTerminal"},
	{1, "iTerminal"},
};
static uint_t usb_vc_input_term_item = 7;

static usb_descr_item_t usb_vc_output_term_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bTerminalID"},
	{2, "wTerminalType"},
	{1, "AssocTerminal"},
	{1, "bSourceID"},
	{1, "iTerminal"},
};
static uint_t usb_vc_output_term_item = 8;

static usb_descr_item_t usb_vc_processing_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{1, "bSourceID"},
	{2, "wMaxMultiplier"},
	{1, "bControlSize"},
	{1, "bmControls"},
};
static uint_t usb_vc_processing_item = 8;

static usb_descr_item_t usb_vc_selector_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{1, "bNrInPins"},
};
static uint_t usb_vc_selector_item = 5;

static usb_descr_item_t usb_vc_extension_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bUnitID"},
	{16 + BYTE_OFFSET, "guidExtensionCode[16]"},
	{1, "bNumControls"},
	{1, "bNrInPins"},
};
static uint_t usb_vc_extension_item = 7;

static usb_descr_item_t usb_vs_input_header_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bNumFormats"},
	{2, "wTotalLength"},
	{1, "bEndpointAddress"},
	{1, "bmInfo"},
	{1, "bTerminalLink"},
	{1, "bStillCaptureMethod"},
	{1, "bTriggerSupport"},
	{1, "bTriggerUsage"},
	{1, "bControlSize"},
	{1, "bmaControls"},
};
static uint_t usb_vs_input_header_item = 13;

static usb_descr_item_t usb_vs_output_header_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bNumFormats"},
	{2, "wTotalLength"},
	{1, "bEndpointAddress"},
	{1, "bTerminalLink"},
	{1, "bControlSize"},
	{1, "bmaControls"},
};
static uint_t usb_vs_output_header_item = 9;

static usb_descr_item_t usb_vs_still_image_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bEndpointAddress"},
	{1, "bNumImageSizePatterns"},
	{2, "wWidth"},
	{2, "wHeight"},
};
static uint_t usb_vs_still_image_item = 7;

static usb_descr_item_t usb_vs_color_matching_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubtype"},
	{1, "bColorPrimaries"},
	{1, "bTransferCharacteristics"},
	{1, "bMatrixCoefficients"},
};
static uint_t usb_vs_color_matching_item = 6;

static usb_descr_item_t usb_vs_2frame_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bFrameIndex"},
	{1, "bmCapabilities"},
	{2, "wWidth"},
	{2, "wHeight"},
	{4, "dwMinBitRate"},
	{4, "dwMaxBitRate"},
	{4, "dwMaxVideoFrameBufferSize"},
	{4, "dwDefaultFrameInterval"},
	{1, "bFrameIntervalType"},
};
static uint_t usb_vs_2frame_item = 12;

static usb_descr_item_t usb_vs_format_mjpeg_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bFormatIndex"},
	{1, "bNumFrameDescriptors"},
	{1, "bmFlags"},
	{1, "bDefaultFrameIndex"},
	{1, "bAspectRatioX"},
	{1, "bAspectRatioY"},
	{1, "bmInterlaceFlags"},
	{1, "bCopyProtect"},
};
static uint_t usb_vs_format_mjpeg_item = 11;

static usb_descr_item_t usb_vs_format_uncps_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bFormatIndex"},
	{1, "bNumFrameDescriptors"},
	{16 + BYTE_OFFSET, "guidFormat[16]"},
	{1, "bBitsPerPixel"},
	{1, "bDefaultFrameIndex"},
	{1, "bAspectRatioX"},
	{1, "bAspectRatioY"},
	{1, "bmInterlaceFlags"},
	{1, "bCopyProtect"},
};
static uint_t usb_vs_format_uncps_item = 12;

static usb_descr_item_t usb_vs_format_mp2ts_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bFormatIndex"},
	{1, "bDataOffset"},
	{1, "bPacketLength"},
	{1, "bStrideLength"},
	{16 + BYTE_OFFSET, "guidStrideFormat[16]"},
};
static uint_t usb_vs_format_mp2ts_item = 8;

static usb_descr_item_t usb_vs_format_dv_descr[] = {
	{1, "bLength"},
	{1, "bDescriptorType"},
	{1, "bDescriptorSubType"},
	{1, "bFormatIndex"},
	{4, "dwMaxVideoFrameBufferSize"},
	{1, "bFormatType"},
};
static uint_t usb_vs_format_dv_item = 6;


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

typedef struct hci_state {
	void			*hci_dip;
	uint_t			hci_instance;
	void			*hci_hcdi_ops;
	uint_t			hci_flags;
	uint16_t		vendor_id;
	uint16_t		device_id;
} hci_state_t;

static int prt_usb_tree(uintptr_t paddr, uint_t flag);

static int prt_usb_tree_node(uintptr_t paddr);

static void prt_usb_hid_item(uintptr_t paddr);

static void prt_usb_hid_item_params(entity_item_t *item);

static void prt_usb_hid_item_attrs(uintptr_t paddr);

static void prt_usb_hid_item_tags(uint_t tag);

static void prt_usb_hid_item_data(uintptr_t paddr, uint_t len);

static int prt_usb_desc(uintptr_t usb_cfg, uint_t cfg_len);

static int prt_usb_ac_desc(uintptr_t paddr, uint_t nlen);

static int prt_usb_as_desc(uintptr_t paddr, uint_t nlen);

static int prt_usb_vc_desc(uintptr_t paddr, uint_t nlen);

static int prt_usb_vs_desc(uintptr_t paddr, uint_t nlen);

static int print_descr(uintptr_t, uint_t, usb_descr_item_t *, uint_t);

static int print_struct(uintptr_t, uint_t, mdb_arg_t *);

static int prt_usb_buf(uintptr_t, uint_t);


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

/* exported functions */

void prt_usb_usage(void);

int prtusb(uintptr_t, uint_t, int, const mdb_arg_t *);

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

/* help of prtusb */
void
prt_usb_usage(void)
{
	mdb_printf("%-8s : %s\n", "-v", "print all descriptors");
	mdb_printf("%-8s : %s\n", "-t", "print device trees");
	mdb_printf("%-8s : %s\n", "-i index", "print the device by index");
}

/* the entry of ::prtusb */
int
prtusb(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
	static int count = 1;
	uint64_t sel_num = 0;
	uint_t usb_flag = 0;
	usba_device_t usb_dev;
	usb_dev_descr_t dev_desc;
	struct dev_info usb_dip;
	char strbuf[STRLEN];

	/* print all usba devices if no address assigned */
	if (!(flags & DCMD_ADDRSPEC)) {
		if (mdb_walk_dcmd("usba_device", "prtusb", argc, argv) == -1) {
			mdb_warn("failed to walk usba_device");

			return (DCMD_ERR);
		}

		return (DCMD_OK);
	}

	/* for the first device, print head */
	if (DCMD_HDRSPEC(flags)) {
		count = 1;
		mdb_printf("%<u>%-8s%-12s%-6s%-16s%-12s%-20s%</u>\n",
		    "INDEX", "DRIVER", "INST", "NODE", "VID.PID", "PRODUCT");
	}

	if (mdb_getopts(argc, argv,
	    'i', MDB_OPT_UINT64, &sel_num,
	    't', MDB_OPT_SETBITS, OPT_TREE, &usb_flag,
	    'v', MDB_OPT_SETBITS, OPT_VERB, &usb_flag, NULL) != argc) {

		return (DCMD_USAGE);
	}

	if (mdb_vread(&usb_dev, sizeof (usba_device_t), addr) == -1) {
		mdb_warn("Failed to read usba_device!\n");

		return (DCMD_ERR);
	}

	if (mdb_vread(&usb_dip, sizeof (struct dev_info),
	    (uintptr_t)usb_dev.usb_dip) == -1) {
		mdb_warn("Failed to read dev_info!\n");

		return (DCMD_ERR);
	}

	/* process the "-i" */
	if (sel_num && sel_num != count) {
		count++;

		return (DCMD_OK);
	}

	/* index number of device node  */
	mdb_printf("%-8x", count++);

	/* driver and instance */
	mdb_devinfo2driver((uintptr_t)usb_dev.usb_dip, strbuf, STRLEN);
	mdb_printf("%-12s%-6d", strbuf, usb_dip.devi_instance);

	/* node name */
	if (mdb_readstr(strbuf, STRLEN,
	    (uintptr_t)usb_dip.devi_node_name) != -1) {

		mdb_printf("%-16s", strbuf);
	} else {

		mdb_printf("%-16s", "No Node Name");
	}

	/* vid.pid */
	if (mdb_vread(&dev_desc, sizeof (usb_dev_descr_t),
	    (uintptr_t)usb_dev.usb_dev_descr) != -1) {

		mdb_printf("%04x.%04x   ",
		    dev_desc.idVendor, dev_desc.idProduct);
	}

	/* product string */
	if (mdb_readstr(strbuf, STRLEN,
	    (uintptr_t)usb_dev.usb_product_str) != -1) {

		mdb_printf("%s\n", strbuf);
	} else {

		mdb_printf("%s\n", "No Product String");
	}

	/* tree, print usb device tree info */
	if (usb_flag & OPT_TREE) {

		mdb_printf("\nusba_device: 0x%x\n", addr);

		mdb_printf("mfg_prod_sn: ");
		if (mdb_readstr(strbuf, STRLEN,
		    (uintptr_t)usb_dev.usb_mfg_str) != -1) {
			mdb_printf("%s - ", strbuf);
		} else {
			mdb_printf("NULL - ");
		}
		if (mdb_readstr(strbuf, STRLEN,
		    (uintptr_t)usb_dev.usb_product_str) != -1) {
			mdb_printf("%s - ", strbuf);
		} else {
			mdb_printf("NULL -");
		}
		if (mdb_readstr(strbuf, STRLEN,
		    (uintptr_t)usb_dev.usb_serialno_str) != -1) {
			mdb_printf("%s", strbuf);
		} else {
			mdb_printf("NULL");
		}

		mdb_printf("\n\n");
		prt_usb_tree((uintptr_t)usb_dev.usb_dip, 0);
	}

	/* verbose, print all descriptors */
	if (usb_flag & OPT_VERB) {
		int i;
		uintptr_t cfg_buf;
		uint16_t cfg_len;

		mdb_printf("\n");

		/* device descriptor */
		prt_usb_desc((uintptr_t)usb_dev.usb_dev_descr, 18);

		/* config cloud descriptors */
		if (usb_dev.usb_n_cfgs == 1) {
			mdb_inc_indent(4);
			mdb_printf("-- Active Config Index 0\n");
			mdb_dec_indent(4);
			prt_usb_desc((uintptr_t)usb_dev.usb_cfg,
			    usb_dev.usb_cfg_length);
		} else {
			/* multiple configs */
			for (i = 0; i < usb_dev.usb_n_cfgs; i++) {

				if ((mdb_vread(&cfg_len, sizeof (uint16_t),
				    (uintptr_t)(usb_dev.usb_cfg_array_len + i))
				    != -1) &&
				    (mdb_vread(&cfg_buf, sizeof (uintptr_t),
				    (uintptr_t)(usb_dev.usb_cfg_array + i))
				    != -1)) {
					mdb_inc_indent(4);
					if (cfg_buf ==
					    (uintptr_t)usb_dev.usb_cfg) {
						mdb_printf("-- Active Config"
						    " Index %x\n", i);
					} else {
						mdb_printf("-- Inactive Config"
						    " Index %x\n", i);
					}
					mdb_dec_indent(4);

					prt_usb_desc(cfg_buf, cfg_len);
				}
			}
		}
	}

	if (usb_flag) {

		mdb_printf("%<u>%-72s%</u>\n", " ");
	}

	return (DCMD_OK);
}

/* print the info required by "-t" */
static int
prt_usb_tree(uintptr_t paddr, uint_t flag)
{
	struct dev_info usb_dip;

	if (mdb_vread(&usb_dip, sizeof (struct dev_info), paddr) == -1) {
		mdb_warn("prt_usb_tree: Failed to read dev_info!\n");

		return (DCMD_ERR);
	}

	prt_usb_tree_node(paddr);

	if (usb_dip.devi_child) {

		mdb_printf("{\n");
		mdb_inc_indent(4);
		prt_usb_tree((uintptr_t)usb_dip.devi_child, 1);
		mdb_dec_indent(4);
		mdb_printf("}\n\n");
	}

	if (usb_dip.devi_sibling && flag == 1) {
		/* print the sibling if flag == 1 */

		prt_usb_tree((uintptr_t)usb_dip.devi_sibling, 1);
	}

	return (DCMD_OK);
}

static int
prt_usb_tree_node(uintptr_t paddr)
{
	struct dev_info usb_dip;
	uintptr_t statep;
	uint_t errlevel;
	char driver_name[STRLEN] = "";
	char strbuf[STRLEN] = "";

	if (mdb_vread(&usb_dip, sizeof (struct dev_info), paddr) == -1) {
		mdb_warn("prt_usb_tree_node: Failed to read dev_info!\n");

		return (DCMD_ERR);
	}

	/* node name */
	if (mdb_readstr(strbuf, STRLEN,
		(uintptr_t)usb_dip.devi_node_name) != -1) {
		mdb_printf("%s, ", strbuf);
	} else {
		mdb_printf("%s, ", "node_name");
	}

	/* instance */
	mdb_printf("instance #%d ", usb_dip.devi_instance);

	/* driver name */
	if (DDI_CF2(&usb_dip)) {

		mdb_devinfo2driver(paddr, driver_name, STRLEN);
		mdb_printf("(driver name: %s)\n", driver_name);
	} else {

		mdb_printf("(driver not attached)\n");
	}

	/* device path */
	mdb_ddi_pathname(paddr, strbuf, STRLEN);
	mdb_printf("  %s\n", strbuf);

	/* dip addr */
	mdb_printf("  dip: 0x%x\n", paddr);

	/* softe_sate */
	mdb_snprintf(strbuf, STRLEN, "%s_statep", driver_name);
	if (mdb_devinfo2statep(paddr, strbuf, &statep) != -1) {
		mdb_printf("  %s: 0x%x\n", strbuf, statep);
	}

	/* error level */
	mdb_snprintf(strbuf, STRLEN, "%s_errlevel", driver_name);
	if (mdb_readvar(&errlevel, strbuf) != -1) {
		mdb_printf("  %s: 0x%x\n", strbuf, errlevel);
	}

	if (strcmp(driver_name, "ehci") == 0) {
		mdb_arg_t argv[] = {
		    {MDB_TYPE_STRING, {"ehci_state_t"}},
		    {MDB_TYPE_STRING, {"ehci_root_hub.rh_descr"}}
		};
		mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv);
	}

	if (strcmp(driver_name, "ohci") == 0) {
		mdb_arg_t argv[] = {
		    {MDB_TYPE_STRING, {"ohci_state_t"}},
		    {MDB_TYPE_STRING, {"ohci_root_hub.rh_descr"}}
		};
		mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv);
	}

	if (strcmp(driver_name, "uhci") == 0) {
		mdb_arg_t argv[] = {
		    {MDB_TYPE_STRING, {"uhci_state_t"}},
		    {MDB_TYPE_STRING, {"uhci_root_hub.rh_descr"}}
		};
		mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv);
	}

	if (strcmp(driver_name, "hubd") == 0) {
		mdb_arg_t argv[] = {
		    {MDB_TYPE_STRING, {"hubd_t"}},
		    {MDB_TYPE_STRING, {"h_hub_descr"}}
		};
		mdb_call_dcmd("print", statep, DCMD_ADDRSPEC, 2, argv);
	}

	if (strcmp(driver_name, "hid") == 0) {
		hid_state_t hidp;

		if (mdb_vread(&hidp, sizeof (hid_state_t), statep) != -1) {
			hidparser_handle hid_report;

			if (mdb_vread(&hid_report, sizeof (hidparser_handle),
			    (uintptr_t)hidp.hid_report_descr) != -1) {

				mdb_inc_indent(2);

				mdb_printf("\n");
				prt_usb_hid_item((uintptr_t)
				    hid_report.hidparser_handle_parse_tree);

				mdb_dec_indent(2);
			}
		}
	}

	mdb_printf("\n");

	return (DCMD_OK);
}

/* print hid report descriptor */
static void
prt_usb_hid_item(uintptr_t paddr)
{
	entity_item_t item;
	if (mdb_vread(&item, sizeof (entity_item_t), paddr) != -1) {

		prt_usb_hid_item_attrs((uintptr_t)item.entity_item_attributes);
		prt_usb_hid_item_params(&item);

		if (item.info.child) {
			mdb_inc_indent(4);
			prt_usb_hid_item((uintptr_t)item.info.child);
			mdb_dec_indent(4);
		}

		if (item.entity_item_right_sibling) {
			prt_usb_hid_item((uintptr_t)
			    item.entity_item_right_sibling);
		}
	}
}

static void
prt_usb_hid_item_params(entity_item_t *item)
{
	switch (item->entity_item_type) {
	case 0x80:
		mdb_printf("INPUT ");

		break;
	case 0x90:
		mdb_printf("OUTPUT ");

		break;
	case 0xA0:
		mdb_printf("COLLECTION ");

		break;
	case 0xB0:
		mdb_printf("FEATURE ");

		break;
	case 0xC0:
		mdb_printf("END_COLLECTION ");

		break;
	default:
		mdb_printf("MAIN_ITEM ");

		break;
	}

	prt_usb_hid_item_data((uintptr_t)item->entity_item_params,
	    item->entity_item_params_leng);

	mdb_printf("\n");
}

static void
prt_usb_hid_item_attrs(uintptr_t paddr)
{
	entity_attribute_t attr;

	if (mdb_vread(&attr, sizeof (entity_attribute_t), paddr) != -1) {

		prt_usb_hid_item_tags(attr.entity_attribute_tag);
		prt_usb_hid_item_data((uintptr_t)attr.entity_attribute_value,
		    attr.entity_attribute_length);

		mdb_printf("\n");

		if (attr.entity_attribute_next) {
			prt_usb_hid_item_attrs((uintptr_t)
			    attr.entity_attribute_next);
		}
	}
}

static void
prt_usb_hid_item_data(uintptr_t paddr, uint_t len)
{
	char data[4];
	int i;

	if (len > 4) {
		mdb_warn("Incorrect entity_item_length: 0x%x\n", len);

		return;
	}

	if (mdb_vread(data, len, paddr) != -1) {

		mdb_printf("( ");
		for (i = 0; i < len; i++) {
			mdb_printf("0x%02x ", data[i] & 0xff);
		}
		mdb_printf(")");
	}
}

static void
prt_usb_hid_item_tags(uint_t tag)
{
	switch (tag) {
	case 0x04:
		mdb_printf("usage page ");

		break;
	case 0x14:
		mdb_printf("logical minimum ");

		break;
	case 0x24:
		mdb_printf("logical maximum ");

		break;
	case 0x34:
		mdb_printf("physical minimum ");

		break;
	case 0x44:
		mdb_printf("physical maximum ");

		break;
	case 0x54:
		mdb_printf("exponent ");

		break;
	case 0x64:
		mdb_printf("unit ");

		break;
	case 0x74:
		mdb_printf("report size ");

		break;
	case 0x84:
		mdb_printf("report id ");

		break;
	case 0x94:
		mdb_printf("report count ");

		break;
	case 0x08:
		mdb_printf("usage ");

		break;
	case 0x18:
		mdb_printf("usage min ");

		break;
	case 0x28:
		mdb_printf("usage max ");

		break;

	default:
		mdb_printf("tag ");
	}
}

/* print the info required by "-v" */
static int
prt_usb_desc(uintptr_t usb_cfg, uint_t cfg_len)
{
	uintptr_t paddr = usb_cfg;
	uintptr_t pend = usb_cfg + cfg_len;
	uchar_t desc_type, nlen;
	usb_if_descr_t usb_if;
	ulong_t indent = 0;

	mdb_arg_t argv = {MDB_TYPE_STRING, {"usb_dev_descr_t"}};

	if (mdb_vread(&nlen, 1, paddr) == -1) {

		return (DCMD_ERR);
	}
	while ((paddr + nlen <= pend) && (nlen > 0)) {
		if (mdb_vread(&desc_type, 1, paddr + 1) == -1) {

			return (DCMD_ERR);
		}

		switch (desc_type) {
		case USB_DESCR_TYPE_DEV:
			mdb_printf("Device Descriptor\n");
			print_struct(paddr, nlen, &argv);

			break;
		case USB_DESCR_TYPE_CFG:
			indent = 4;
			mdb_inc_indent(indent);
			mdb_printf("Configuration Descriptor\n");
			print_descr(paddr, nlen, usb_cfg_descr, usb_cfg_item);
			mdb_dec_indent(indent);

			break;
		case USB_DESCR_TYPE_STRING:
			mdb_printf("String Descriptor\n");
			print_descr(paddr, nlen, usb_str_descr, usb_str_item);

			break;
		case USB_DESCR_TYPE_IF:
			indent = 8;
			mdb_inc_indent(indent);
			mdb_printf("Interface Descriptor\n");
			print_descr(paddr, nlen, usb_if_descr, usb_if_item);
			mdb_dec_indent(indent);
			mdb_vread(&usb_if, sizeof (usb_if_descr_t), paddr);

			break;
		case USB_DESCR_TYPE_EP:
			indent = 8;
			mdb_inc_indent(indent);
			mdb_printf("Endpoint Descriptor\n");
			print_descr(paddr, nlen, usb_ep_descr, usb_ep_item);
			mdb_dec_indent(indent);

			break;
		case USB_DESCR_TYPE_DEV_QLF:
			mdb_printf("Device_Qualifier Descriptor\n");
			print_descr(paddr, nlen, usb_qlf_descr, usb_qlf_item);

			break;
		case USB_DESCR_TYPE_OTHER_SPEED_CFG:
			indent = 4;
			mdb_inc_indent(indent);
			mdb_printf("Other_Speed_Configuration Descriptor\n");
			print_descr(paddr, nlen, usb_cfg_descr, usb_cfg_item);
			mdb_dec_indent(indent);

			break;
		case USB_DESCR_TYPE_IA:
			indent = 6;
			mdb_inc_indent(indent);
			mdb_printf("Interface_Association Descriptor\n");
			print_descr(paddr, nlen, usb_ia_descr, usb_ia_item);
			mdb_dec_indent(indent);

			break;
		case 0x21:	/* hid descriptor */
			indent = 12;
			mdb_inc_indent(indent);
			mdb_printf("HID Descriptor\n");
			print_descr(paddr, nlen, usb_hid_descr, usb_hid_item);
			mdb_dec_indent(indent);

			break;
		case 0x24:	/* class specific interfce descriptor */
			indent = 12;
			mdb_inc_indent(indent);
			if (usb_if.bInterfaceClass == 1 &&
			    usb_if.bInterfaceSubClass == 1) {
				mdb_printf("AudioControl_Interface: ");
				prt_usb_ac_desc(paddr, nlen);

			} else if (usb_if.bInterfaceClass == 1 &&
			    usb_if.bInterfaceSubClass == 2) {
				mdb_printf("AudioStream_Interface: ");
				prt_usb_as_desc(paddr, nlen);

			} else if (usb_if.bInterfaceClass == 0x0E &&
			    usb_if.bInterfaceSubClass == 1) {
				mdb_printf("VideoControl_Interface: ");
				prt_usb_vc_desc(paddr, nlen);


			} else if (usb_if.bInterfaceClass == 0x0E &&
			    usb_if.bInterfaceSubClass == 2) {
				mdb_printf("VideoStream_Interface: ");
				prt_usb_vs_desc(paddr, nlen);

			} else {
				mdb_printf("Unknown_Interface:"
				    "0x%x\n", desc_type);
				prt_usb_buf(paddr, nlen);
			}
			mdb_dec_indent(indent);

			break;
		case 0x25:	/* class specific endpoint descriptor */
			indent = 12;
			mdb_inc_indent(indent);
			if (usb_if.bInterfaceClass == 0x01) {
				mdb_printf("AudioEndpoint:\n");
				print_descr(paddr, nlen,
				    usb_as_ep_descr, usb_as_ep_item);

			} else if (usb_if.bInterfaceClass == 0x0E) {
				mdb_printf("VideoEndpoint:\n");
				print_descr(paddr, nlen,
				    usb_ep_descr, usb_ep_item);

			} else {
				mdb_printf("Unknown_Endpoint:"
				    "0x%x\n", desc_type);
				prt_usb_buf(paddr, nlen);
			}
			mdb_dec_indent(indent);

			break;
		default:
			mdb_inc_indent(indent);
			mdb_printf("Unknown Descriptor: 0x%x\n", desc_type);
			prt_usb_buf(paddr, nlen);
			mdb_dec_indent(indent);

			break;
		}

		paddr += nlen;
		if (mdb_vread(&nlen, 1, paddr) == -1) {

			return (DCMD_ERR);
		}
	};

	return (DCMD_OK);
}


/* print audio class specific control descriptor */
static int
prt_usb_ac_desc(uintptr_t addr, uint_t nlen)
{
	uchar_t sub_type;

	if (mdb_vread(&sub_type, 1, addr + 2) == -1) {

		return (DCMD_ERR);
	}
	switch (sub_type) {
	case 0x01:
		mdb_printf("header Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_header_descr, usb_ac_header_item);

		break;
	case 0x02:
		mdb_printf("input_terminal Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_input_term_descr, usb_ac_input_term_item);

		break;
	case 0x03:
		mdb_printf("output_terminal Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_output_term_descr, usb_ac_output_term_item);

		break;
	case 0x04:
		mdb_printf("mixer_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_mixer_descr, usb_ac_mixer_item);

		break;
	case 0x05:
		mdb_printf("selector_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_selector_descr, usb_ac_selector_item);

		break;
	case 0x06:
		mdb_printf("feature_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_feature_descr, usb_ac_feature_item);

		break;
	case 0x07:
		mdb_printf("processing_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_processing_descr, usb_ac_processing_item);

		break;
	case 0x08:
		mdb_printf("extension_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_ac_extension_descr, usb_ac_extension_item);

		break;
	default:
		mdb_printf("Unknown AC sub-descriptor 0x%x\n", sub_type);
		prt_usb_buf(addr, nlen);

		break;
	}

	return (DCMD_OK);
}

/* print audio class specific stream descriptor */
static int
prt_usb_as_desc(uintptr_t addr, uint_t nlen)
{
	uchar_t sub_type;

	if (mdb_vread(&sub_type, 1, addr + 2) == -1) {

		return (DCMD_ERR);
	}
	switch (sub_type) {
	case 0x01:
		mdb_printf("general_interface Descriptor\n");
		print_descr(addr, nlen,
		    usb_as_if_descr, usb_as_if_item);

		break;
	case 0x02:
		mdb_printf("format_type Descriptor\n");
		print_descr(addr, nlen,
		    usb_as_format_descr, usb_as_format_item);

		break;
	default:
		mdb_printf("Unknown AS sub-descriptor 0x%x\n", sub_type);
		prt_usb_buf(addr, nlen);

		break;
	}

	return (DCMD_OK);
}

/* print video class specific control descriptor */
static int
prt_usb_vc_desc(uintptr_t addr, uint_t nlen)
{
	uchar_t sub_type;

	if (mdb_vread(&sub_type, 1, addr + 2) == -1) {

		return (DCMD_ERR);
	}
	switch (sub_type) {
	case 0x01:
		mdb_printf("header Descriptor\n");
		print_descr(addr, nlen,
		    usb_vc_header_descr, usb_vc_header_item);

		break;
	case 0x02:
		mdb_printf("input_terminal Descriptor\n");
		print_descr(addr, nlen,
		    usb_vc_input_term_descr, usb_vc_input_term_item);

		break;
	case 0x03:
		mdb_printf("output_terminal Descriptor\n");
		print_descr(addr, nlen,
		    usb_vc_output_term_descr, usb_vc_output_term_item);

		break;
	case 0x04:
		mdb_printf("selector_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_vc_selector_descr, usb_vc_selector_item);

		break;
	case 0x05:
		mdb_printf("processing_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_vc_processing_descr, usb_vc_processing_item);

		break;
	case 0x06:
		mdb_printf("extension_unit Descriptor\n");
		print_descr(addr, nlen,
		    usb_vc_extension_descr, usb_vc_extension_item);

		break;
	default:
		mdb_printf("Unknown VC sub-descriptor 0x%x\n", sub_type);
		prt_usb_buf(addr, nlen);

		break;
	}

	return (DCMD_OK);
}

/* print video class specific stream descriptor */
static int
prt_usb_vs_desc(uintptr_t addr, uint_t nlen)
{
	uchar_t sub_type;

	if (mdb_vread(&sub_type, 1, addr + 2) == -1) {

		return (DCMD_ERR);
	}
	switch (sub_type) {
	case 0x01:
		mdb_printf("input_header Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_input_header_descr, usb_vs_input_header_item);

		break;
	case 0x02:
		mdb_printf("output_header Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_output_header_descr, usb_vs_output_header_item);

		break;
	case 0x03:
		mdb_printf("still_image_frame Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_still_image_descr, usb_vs_still_image_item);

		break;
	case 0x04:
		mdb_printf("format_uncompressed Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_format_uncps_descr, usb_vs_format_uncps_item);

		break;
	case 0x05:
		mdb_printf("frame_uncompressed Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_2frame_descr, usb_vs_2frame_item);

		break;
	case 0x06:
		mdb_printf("format_mjpeg Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_format_mjpeg_descr, usb_vs_format_mjpeg_item);

		break;
	case 0x07:
		mdb_printf("frame_mjpeg Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_2frame_descr, usb_vs_2frame_item);

		break;
	case 0x0A:
		mdb_printf("format_mpeg2ts Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_format_mp2ts_descr, usb_vs_format_mp2ts_item);

		break;
	case 0x0C:
		mdb_printf("format_dv Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_format_dv_descr, usb_vs_format_dv_item);

		break;
	case 0x0D:
		mdb_printf("color_matching Descriptor\n");
		print_descr(addr, nlen,
		    usb_vs_color_matching_descr, usb_vs_color_matching_item);

		break;
	default:
		mdb_printf("Unknown VS sub-descriptor 0x%x\n", sub_type);
		prt_usb_buf(addr, nlen);

		break;
	}

	return (DCMD_OK);
}

/* parse and print the descriptor items */
static int
print_descr(uintptr_t addr, uint_t nlen, usb_descr_item_t *item, uint_t nitem)
{
	int i, j;
	uint8_t buf[8];
	uint64_t value;
	uintptr_t paddr = addr;
	usb_descr_item_t *p = item;

	mdb_printf("{");
	for (i = 0; (i < nitem) && (paddr < addr + nlen); i++) {
		mdb_printf("\n    %s =", p->name);
		switch (p->nlen) {
		case 1:		/* uint8_t */
			if (mdb_vread(buf, 1, paddr) == -1) {

				return (DCMD_ERR);
			}
			value =  buf[0];

			break;
		case 2:		/* uint16_t */
			if (mdb_vread(buf, 2, paddr) == -1) {

				return (DCMD_ERR);
			}
			value = buf[0] | (buf[1] << 8);

			break;
		case 4:		/* uint32_t */
			if (mdb_vread(buf, 4, paddr) == -1) {

				return (DCMD_ERR);
			}
			value = buf[0] | (buf[1] << 8) |
				(buf[2] << 16) | (buf[3] << 24);

			break;
		case 8:		/* uint64_t */
			if (mdb_vread(buf, 8, paddr) == -1) {

				return (DCMD_ERR);
			}
			value =	buf[4] | (buf[5] << 8) |
				(buf[6] << 16) | (buf[7] << 24);
			value = buf[0] | (buf[1] << 8) |
				(buf[2] << 16) | (buf[3] << 24) |
				(value << 32);

			break;
		default:	/* byte array */
			value = 0;
			/* print an array instead of a value */
			for (j = 0; j < p->nlen - BYTE_OFFSET; j++) {
				if (mdb_vread(buf, 1, paddr + j) == -1) {

					break;
				}
				mdb_printf(" 0x%x", buf[0]);
			}

			break;
		}

		if (p->nlen > BYTE_OFFSET) {
			paddr += p->nlen - BYTE_OFFSET;
		} else {
			mdb_printf(" 0x%x", value);
			paddr += p->nlen;
		}

		p++;
	}

	/* print the unresolved bytes */
	if (paddr < addr + nlen) {
		mdb_printf("\n    ... =");
	}
	while (paddr < addr + nlen) {
		if (mdb_vread(buf, 1, paddr++) == -1) {

			break;
		}
		mdb_printf(" 0x%x", buf[0]);
	}
	mdb_printf("\n}\n");

	return (DCMD_OK);
}

/* print the buffer as a struct */
static int
print_struct(uintptr_t addr, uint_t nlen, mdb_arg_t *arg)
{
	mdb_ctf_id_t id;
	if (mdb_ctf_lookup_by_name(arg->a_un.a_str, &id) == 0) {

		mdb_call_dcmd("print", addr, DCMD_ADDRSPEC, 1, arg);
	} else {

		prt_usb_buf(addr, nlen);
	}

	return (DCMD_OK);
}

/* print the buffer as a byte array */
static int
prt_usb_buf(uintptr_t addr, uint_t nlen)
{
	int i;
	uchar_t val;

	mdb_printf("{\n");
	for (i = 0; i < nlen; i++) {
		if (mdb_vread(&val, 1, addr + i) == -1) {

			break;
		}
		mdb_printf("%02x ", val);
	}
	if (nlen) {
		mdb_printf("\n");
	}
	mdb_printf("}\n");

	return (DCMD_OK);
}