usb.c revision 4610e4a00999c6d2291b3fc263926b890ec500a5
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <stddef.h>
#include <sys/mdb_modapi.h>
#include <mdb/mdb_ks.h>
#include <sys/usb/usba.h>
#include <sys/ddi_impldefs.h>
#include <sys/usb/usba/usba_types.h>
#include <sys/usb/usba/usba_impl.h>
#include <sys/usb/usba/hcdi_impl.h>
#include <sys/file.h>
#include <sys/sunndi.h>
#include <unistd.h>
/*
* Prototypes
*/
/* usba.c */
extern uintptr_t mdb_usba_get_usba_device(uintptr_t);
extern uintptr_t mdb_usba_hcdi_get_hcdi(struct dev_info *);
/*
* Defines
*/
/* dcmd options */
#define USB_DUMP_VERBOSE 0x01
#define USB_DUMP_ACTIVE_PIPES 0x02
/* Hardcoded slop factor designed into debug buf logic */
#define USB_DEBUG_SIZE_EXTRA_ALLOC 8
/*
* Callback arg struct for find_dip (callback func used in usba_device2devinfo).
*/
typedef struct usba_device2devinfo_data {
uintptr_t u2d_target_usb_dev_p; /* one we're looking for */
uintptr_t *u2d_dip_addr; /* Where to store result */
boolean_t u2d_found; /* Match found */
} usba_device2devinfo_cbdata_t;
/*
* Callback for usba_device2dip.
* Callback called from the devinfo_children walk invoked in usba_device2dip.
*
* For the current dip, get the (potential) pointer to its usba_device_t
* struct.
* See if this pointer matches the address of the usba_device_t we're looking
* for (passed in as usb_dev_p). If so, stuff its value in u2d_dip_addr,
* and terminate the walk.
*
* - dip_addr is the address in core of the dip currently being processed by the
* walk
* - local_dip is a pointer to a copy of the struct dev_info in local memory
* - cb_data is the addr of the callback arg the walker was invoked with
* (passed through transparently from walk invoker).
*
* Returns:
* - WALK_NEXT on success (match not found yet)
* - WALK_ERR on errors.
* - WALK_DONE is returned, cb_data.found is set to TRUE, and
* *cb_data.u2d_dip_addr is set to the matched dip addr if a dip corresponding
* to the desired usba_device_t* is found.
*/
/*ARGSUSED*/
static int
find_dip(uintptr_t dip_addr, const void *local_dip, void *cb_arg)
{
uintptr_t cur_usb_dev;
usba_device2devinfo_cbdata_t *cb_data =
(usba_device2devinfo_cbdata_t *)cb_arg;
if ((cur_usb_dev = mdb_usba_get_usba_device(dip_addr)) == NULL) {
/*
* If there's no corresponding usba_device_t, this dip isn't
* a usb node. Might be an sd node. Ignore it.
*/
return (WALK_NEXT);
}
if (cur_usb_dev == cb_data->u2d_target_usb_dev_p) {
*cb_data->u2d_dip_addr = dip_addr;
cb_data->u2d_found = TRUE;
return (WALK_DONE);
}
return (WALK_NEXT);
}
/*
* Given a usba_device pointer, figure out which dip is associated with it.
* Relies on usba_device.usb_root_hub_dip being accurate.
*
* - usb_dev_addr is a pointer to a usba_device_t in core.
* - dip_addr is the address of a uintptr_t to receive the address in core
* of the found dip (if any).
*
* Returns:
* 0 on success (no match found)
* 1 on success (match found)
* -1 on errors.
*/
static int
usba_device2dip(uintptr_t usb_dev_addr, uintptr_t *dip_addr)
{
usba_device_t usb_dev;
usba_device2devinfo_cbdata_t cb_data;
/*
* Walk all USB children of the root hub devinfo.
* The callback func looks for a match on the usba_device address.
*/
cb_data.u2d_target_usb_dev_p = usb_dev_addr;
cb_data.u2d_dip_addr = dip_addr;
cb_data.u2d_found = FALSE;
if (mdb_vread(&usb_dev, sizeof (usba_device_t),
usb_dev_addr) == -1) {
mdb_warn("failed to read usba_device struct");
return (-1);
}
/*
* Walk devinfo children starting with the root hub node,
* looking for a match on the usba_device pointer (which is what
* find_dip does).
* Result is placed in cb_data.dip_addr.
*/
if (mdb_pwalk("devinfo_children", find_dip, &cb_data,
(uintptr_t)usb_dev.usb_root_hub_dip) != 0) {
mdb_warn("failed to walk devinfo_children");
return (-1);
}
if (cb_data.u2d_found == TRUE) {
return (1);
}
return (0);
}
/*
* Generic walker usba_list_entry_t walker.
* Works for any usba_list_entry_t list.
*/
int
usba_list_walk_init(mdb_walk_state_t *wsp)
{
/* Must have a start addr. */
if (wsp->walk_addr == NULL) {
mdb_warn("not a global walk. Starting address required\n");
return (WALK_ERR);
}
return (WALK_NEXT);
}
/*
* Generic list walker step routine.
* NOTE: multiple walkers share this routine.
*/
int
usba_list_walk_step(mdb_walk_state_t *wsp)
{
int status;
usba_list_entry_t list_entry;
if (mdb_vread(&list_entry, sizeof (usba_list_entry_t),
(uintptr_t)wsp->walk_addr) == -1) {
mdb_warn("failed to read usba_list_entry_t at %p",
wsp->walk_addr);
return (WALK_ERR);
}
status = wsp->walk_callback(wsp->walk_addr, &list_entry,
wsp->walk_cbdata);
wsp->walk_addr = (uintptr_t)list_entry.next;
/* Check if we're at the last element */
if (wsp->walk_addr == NULL) {
return (WALK_DONE);
}
return (status);
}
/*
* usb_pipe_handle walker
* Given a pointer to a usba_device_t, walk the array of endpoint
* pipe_handle lists.
* For each list, traverse the list, invoking the callback on each element.
*
* Note this function takes the address of a usba_device struct (which is
* easily obtainable), but actually traverses a sub-portion of the struct
* (which address is not so easily obtainable).
*/
int
usb_pipe_handle_walk_init(mdb_walk_state_t *wsp)
{
if (wsp->walk_addr == NULL) {
mdb_warn("not a global walk; usba_device_t required\n");
return (WALK_ERR);
}
wsp->walk_data = mdb_alloc((sizeof (usba_ph_impl_t)) * USBA_N_ENDPOINTS,
UM_SLEEP | UM_GC);
/*
* Read the usb_ph_list array into local memory.
* Set start address to first element/endpoint in usb_pipehandle_list
*/
if (mdb_vread(wsp->walk_data,
(sizeof (usba_ph_impl_t)) * USBA_N_ENDPOINTS,
(uintptr_t)((size_t)(wsp->walk_addr) +
offsetof(usba_device_t, usb_ph_list))) == -1) {
mdb_warn("failed to read usb_pipehandle_list at %p",
wsp->walk_addr);
return (WALK_ERR);
}
wsp->walk_arg = 0;
return (WALK_NEXT);
}
int
usb_pipe_handle_walk_step(mdb_walk_state_t *wsp)
{
int status;
usba_ph_impl_t *impl_list = (usba_ph_impl_t *)(wsp->walk_data);
intptr_t index = (intptr_t)wsp->walk_arg;
/* Find the first valid endpoint, starting from where we left off. */
while ((index < USBA_N_ENDPOINTS) &&
(impl_list[index].usba_ph_data == NULL)) {
index++;
}
/* No more valid endpoints. */
if (index >= USBA_N_ENDPOINTS) {
return (WALK_DONE);
}
status = wsp->walk_callback((uintptr_t)impl_list[index].usba_ph_data,
wsp->walk_data, wsp->walk_cbdata);
/* Set up to start at next pipe handle next time. */
wsp->walk_arg = (void *)(index + 1);
return (status);
}
/*
* Given the address of a usba_pipe_handle_data_t, dump summary info.
*/
/*ARGSUSED*/
int
usb_pipe_handle(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
char *dir, *type, *state;
usb_ep_descr_t ept_descr;
usba_pipe_handle_data_t pipe_handle;
usba_ph_impl_t ph_impl;
if (!(flags & DCMD_ADDRSPEC)) {
return (DCMD_USAGE);
}
if (mdb_vread(&pipe_handle,
sizeof (usba_pipe_handle_data_t), addr) == -1) {
mdb_warn("failed to read pipe handle at %p", addr);
return (DCMD_ERR);
}
if (mdb_vread(&ph_impl, sizeof (usba_ph_impl_t),
(uintptr_t)pipe_handle.p_ph_impl) == -1) {
state = "*******";
} else {
switch (ph_impl.usba_ph_state) {
case USB_PIPE_STATE_CLOSED:
state = "CLOSED ";
break;
case USB_PIPE_STATE_IDLE:
state = "IDLE ";
break;
case USB_PIPE_STATE_ACTIVE:
state = "ACTIVE ";
break;
case USB_PIPE_STATE_ERROR:
state = "ERROR ";
break;
case USB_PIPE_STATE_CLOSING:
state = "CLOSING";
break;
default:
state = "ILLEGAL";
break;
}
}
bcopy(&pipe_handle.p_ep, &ept_descr, sizeof (usb_ep_descr_t));
if (DCMD_HDRSPEC(flags)) {
mdb_printf("\n %<u>%-3s %5s %3s %7s %-?s %-?s %-?s%</u>\n",
"EP", "TYPE ", "DIR", "STATE ", "P_HANDLE", "P_POLICY",
"EP DESCR");
}
dir = ((ept_descr.bEndpointAddress & USB_EP_DIR_MASK) &
USB_EP_DIR_IN) ? "In " : "Out";
switch (ept_descr.bmAttributes & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
type = "Cntrl";
break;
case USB_EP_ATTR_ISOCH:
type = "Isoch";
break;
case USB_EP_ATTR_BULK:
type = "Bulk ";
break;
case USB_EP_ATTR_INTR:
type = "Intr ";
break;
default:
type = "*****";
break;
}
mdb_printf(" %3d %5s %3s %7s %-?p %-?p %-?p\n",
ept_descr.bEndpointAddress & USB_EP_NUM_MASK, type, dir, state,
addr, addr + offsetof(usba_pipe_handle_data_t, p_policy),
addr + offsetof(usba_pipe_handle_data_t, p_ep));
return (DCMD_OK);
}
/*
* usba_device walker:
*
* walks the chain of usba_device structs headed by usba_device_list in usba.c
* NOTE: It uses the generic list walk step routine usba_list_walk_step.
* No walk_fini routine is needed.
*/
int
usba_device_walk_init(mdb_walk_state_t *wsp)
{
usba_list_entry_t list_entry;
if (wsp->walk_addr != NULL) {
mdb_warn(
"global walk only. Must be invoked without an address\n");
return (WALK_ERR);
}
if (mdb_readvar(&list_entry, "usba_device_list") == -1) {
mdb_warn("failed to read usba_device_list");
return (WALK_ERR);
}
/* List head is not part of usba_device_t, get first usba_device_t */
wsp->walk_addr = (uintptr_t)list_entry.next;
return (WALK_NEXT);
}
/*
* usba_device dcmd
* Given the address of a usba_device struct, dump summary info
* -v: Print more (verbose) info
* -p: Walk/dump all open pipes for this usba_device
*/
/*ARGSUSED*/
int
usba_device(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
int status;
char pathname[MAXNAMELEN];
char dname[MODMAXNAMELEN + 1] = "<unatt>"; /* Driver name */
char drv_statep[MODMAXNAMELEN+ 10];
uint_t usb_flag = NULL;
boolean_t no_driver_attached = FALSE;
uintptr_t dip_addr;
struct dev_info devinfo;
if (!(flags & DCMD_ADDRSPEC)) {
/* Global walk */
if (mdb_walk_dcmd("usba_device", "usba_device", argc,
argv) == -1) {
mdb_warn("failed to walk usba_device");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (mdb_getopts(argc, argv,
'p', MDB_OPT_SETBITS, USB_DUMP_ACTIVE_PIPES, &usb_flag,
'v', MDB_OPT_SETBITS, USB_DUMP_VERBOSE, &usb_flag, NULL) != argc) {
return (DCMD_USAGE);
}
if (usb_flag && !(DCMD_HDRSPEC(flags))) {
mdb_printf("\n");
}
if (DCMD_HDRSPEC(flags)) {
mdb_printf("%<u>%-15s %4s %-?s %-42s%</u>\n",
"NAME", "INST", "DIP", "PATH ");
}
status = usba_device2dip(addr, &dip_addr);
/*
* -1 = error
* 0 = no error, no match
* 1 = no error, match
*/
if (status != 1) {
if (status == -1) {
mdb_warn("error looking for dip for usba_device %p",
addr);
} else {
mdb_warn("failed to find dip for usba_device %p\n",
addr);
}
mdb_warn("dip and statep unobtainable\n");
return (DCMD_ERR);
}
/* Figure out what driver (name) is attached to this node. */
(void) mdb_devinfo2driver(dip_addr, (char *)dname, sizeof (dname));
if (mdb_vread(&devinfo, sizeof (struct dev_info),
dip_addr) == -1) {
mdb_warn("failed to read devinfo");
return (DCMD_ERR);
}
if (!(DDI_CF2(&devinfo))) {
no_driver_attached = TRUE;
}
(void) mdb_ddi_pathname(dip_addr, pathname, sizeof (pathname));
mdb_printf("%-15s %2d %-?p %s\n", dname, devinfo.devi_instance,
dip_addr, pathname);
if (usb_flag & USB_DUMP_VERBOSE) {
int i;
uintptr_t statep = NULL;
char *string_descr;
char **config_cloud, **conf_str_descr;
usb_dev_descr_t usb_dev_descr;
usba_device_t usba_device_struct;
if (mdb_vread(&usba_device_struct,
sizeof (usba_device_t), addr) == -1) {
mdb_warn("failed to read usba_device struct");
return (DCMD_ERR);
}
mdb_printf(" usba_device: %-16p\n\n", (usba_device_t *)addr);
if (mdb_vread(&usb_dev_descr, sizeof (usb_dev_descr),
(uintptr_t)usba_device_struct.usb_dev_descr) == -1) {
mdb_warn("failed to read usb_dev_descr_t struct");
return (DCMD_ERR);
}
mdb_printf("\n idVendor: 0x%04x idProduct: 0x%04x "
"usb_addr: 0x%02x\n", usb_dev_descr.idVendor,
usb_dev_descr.idProduct, usba_device_struct.usb_addr);
/* Get the string descriptor string into local space. */
string_descr = (char *)mdb_alloc(USB_MAXSTRINGLEN, UM_GC);
if (usba_device_struct.usb_mfg_str == NULL) {
(void) strcpy(string_descr, "<No Manufacturer String>");
} else {
if (mdb_readstr(string_descr, USB_MAXSTRINGLEN,
(uintptr_t)usba_device_struct.usb_mfg_str) == -1) {
mdb_warn("failed to read manufacturer "
"string descriptor");
(void) strcpy(string_descr, "???");
}
}
mdb_printf("\n Manufacturer String:\t%s\n", string_descr);
if (usba_device_struct.usb_product_str == NULL) {
(void) strcpy(string_descr, "<No Product String>");
} else {
if (mdb_readstr(string_descr, USB_MAXSTRINGLEN,
(uintptr_t)usba_device_struct.usb_product_str) ==
-1) {
mdb_warn("failed to read product string "
"descriptor");
(void) strcpy(string_descr, "???");
}
}
mdb_printf(" Product String:\t\t%s\n", string_descr);
if (usba_device_struct.usb_serialno_str == NULL) {
(void) strcpy(string_descr, "<No SerialNumber String>");
} else {
if (mdb_readstr(string_descr, USB_MAXSTRINGLEN,
(uintptr_t)usba_device_struct.usb_serialno_str) ==
-1) {
mdb_warn("failed to read serial number string "
"descriptor");
(void) strcpy(string_descr, "???");
}
}
mdb_printf(" SerialNumber String:\t%s\n", string_descr);
if (no_driver_attached) {
mdb_printf("\n");
} else {
mdb_printf(" state_p: ");
/*
* Given the dip, find the associated statep. The
* convention to generate this soft state anchor is:
* <driver_name>_statep
*/
(void) mdb_snprintf(drv_statep, sizeof (drv_statep),
"%s_statep", dname);
if (mdb_devinfo2statep(dip_addr, drv_statep,
&statep) == -1) {
mdb_warn("failed to find %s state struct for "
"dip %p", drv_statep, dip_addr);
return (DCMD_ERR);
}
mdb_printf("%-?p\n", statep);
}
config_cloud = (char **)mdb_alloc(sizeof (void *) *
usba_device_struct.usb_n_cfgs, UM_GC);
conf_str_descr = (char **)mdb_alloc(sizeof (void *) *
usba_device_struct.usb_n_cfgs, UM_GC);
if ((usba_device_struct.usb_cfg_array) &&
(usba_device_struct.usb_cfg_str_descr)) {
if ((mdb_vread(config_cloud, sizeof (void *) *
usba_device_struct.usb_n_cfgs,
(uintptr_t)usba_device_struct.usb_cfg_array) ==
-1) || (mdb_vread(conf_str_descr, sizeof (void *)
* usba_device_struct.usb_n_cfgs, (uintptr_t)
usba_device_struct.usb_cfg_str_descr)) == -1) {
mdb_warn("failed to read config cloud pointers");
} else {
mdb_printf("\n Device Config Clouds:\n"
" Index\tConfig\t\tConfiguration "
"String\n"
" -----\t------\t\t"
"--------------------\n");
for (i = 0; i < usba_device_struct.usb_n_cfgs;
i++) {
if (mdb_readstr(string_descr,
USB_MAXSTRINGLEN,
(uintptr_t)conf_str_descr[i]) ==
-1) {
(void) strcpy(string_descr,
"<No Configuration "
"String>");
}
mdb_printf(" %4d\t0x%p\t%s\n", i,
config_cloud[i], string_descr);
}
}
}
mdb_printf("\n Active configuration index: %d\n",
usba_device_struct.usb_active_cfg_ndx);
}
if (usb_flag & USB_DUMP_ACTIVE_PIPES) {
if (mdb_pwalk_dcmd("usb_pipe_handle", "usb_pipe_handle",
0, NULL, addr) == -1) {
mdb_warn("failed to walk usb_pipe_handle");
return (DCMD_ERR);
}
}
return (DCMD_OK);
}
/*
* Dump the contents of the usba_debug_buf, from the oldest to newest,
* wrapping around if necessary.
*/
/*ARGSUSED*/
int
usba_debug_buf(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
char *debug_buf_addr; /* addr in core */
char *local_debug_buf; /* local copy of buf */
int debug_buf_size;
char *term_p;
int being_cleared;
if (flags & DCMD_ADDRSPEC) {
return (DCMD_USAGE);
}
if (mdb_readvar(&being_cleared, "usba_clear_debug_buf_flag") ==
-1) {
mdb_warn("failed to read usba_clear_debug_buf_flag");
return (DCMD_ERR);
}
if (being_cleared) {
return (DCMD_OK);
}
if (mdb_readvar(&debug_buf_addr, "usba_debug_buf") == -1) {
mdb_warn("failed to read usba_debug_buf");
return (DCMD_ERR);
}
if (debug_buf_addr == NULL) {
mdb_warn("usba_debug_buf not allocated\n");
return (DCMD_OK);
}
if (mdb_readvar(&debug_buf_size, "usba_debug_buf_size") == -1) {
mdb_warn("failed to read usba_debug_buf_size");
return (DCMD_ERR);
}
debug_buf_size += USB_DEBUG_SIZE_EXTRA_ALLOC;
local_debug_buf = (char *)mdb_alloc(debug_buf_size, UM_SLEEP | UM_GC);
if ((mdb_vread(local_debug_buf, debug_buf_size,
(uintptr_t)debug_buf_addr)) == -1) {
mdb_warn("failed to read usba_debug_buf at %p",
local_debug_buf);
return (DCMD_ERR);
}
local_debug_buf[debug_buf_size - 1] = '\0';
if (strlen(local_debug_buf) == NULL) {
return (DCMD_OK);
}
if ((term_p = strstr(local_debug_buf, ">>>>")) == NULL) {
mdb_warn("failed to find terminator \">>>>\"\n");
return (DCMD_ERR);
}
/*
* Print the chunk of buffer from the terminator to the end.
* This will print a null string if no wrap has occurred yet.
*/
mdb_printf("%s", term_p+5); /* after >>>>\0 to end of buf */
mdb_printf("%s\n", local_debug_buf); /* beg of buf to >>>>\0 */
return (DCMD_OK);
}
/*ARGSUSED*/
int
usba_clear_debug_buf(
uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
int clear = 1;
/* stop the tracing */
if (mdb_writevar((void*)&clear, "usba_clear_debug_buf_flag") == -1) {
mdb_warn("failed to set usba_clear_debug_buf_flag");
return (DCMD_ERR);
}
return (DCMD_OK);
}
/*
* MDB module linkage information:
*
* We declare a list of structures describing our dcmds, and a function
* named _mdb_init to return a pointer to our module information.
*/
static const mdb_dcmd_t dcmds[] = {
{ "usb_pipe_handle", ":",
"print a usb_pipe_handle struct", usb_pipe_handle, NULL},
{ "usba_device", ": [-pv]",
"print summary info for a usba_device_t struct", usba_device, NULL},
{ "usba_debug_buf", NULL,
"print usba_debug_buf", usba_debug_buf, NULL},
{ "usba_clear_debug_buf", NULL,
"clear usba_debug_buf", usba_clear_debug_buf, NULL},
{ NULL }
};
static const mdb_walker_t walkers[] = {
/* Generic list walker. */
{ "usba_list_entry", "walk list of usba_list_entry_t structures",
usba_list_walk_init, usba_list_walk_step, NULL, NULL },
{ "usb_pipe_handle", "walk USB pipe handles, given a usba_device_t ptr",
usb_pipe_handle_walk_init, usb_pipe_handle_walk_step, NULL, NULL },
{ "usba_device", "walk global list of usba_device_t structures",
usba_device_walk_init, usba_list_walk_step, NULL, NULL },
{ NULL }
};
static const mdb_modinfo_t modinfo = {
MDB_API_VERSION, dcmds, walkers
};
const mdb_modinfo_t *
_mdb_init(void)
{
return (&modinfo);
}