/*
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <gelf.h>
#include <sys/mdb_modapi.h>
#include <mdb/mdb_ks.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_types.h>
#include <sys/usb/hcd/uhci/uhci.h>
#include <sys/usb/hcd/uhci/uhcid.h>
#include <sys/usb/hcd/uhci/uhciutil.h>
#define UHCI_TD 0
#define UHCI_QH 1
/* Prototypes */
int uhci_td(uintptr_t, uint_t, int, const mdb_arg_t *);
int uhci_qh(uintptr_t, uint_t, int, const mdb_arg_t *);
int uhci_td_walk_init(mdb_walk_state_t *);
int uhci_td_walk_step(mdb_walk_state_t *);
int uhci_qh_walk_init(mdb_walk_state_t *);
int uhci_qh_walk_step(mdb_walk_state_t *);
/*
* Callback for find_uhci_statep (called back from walk "softstate" in
* find_uhci_statep).
*
* - uhci_instancep is the value of the current pointer in the array of soft
* state instance pointers (see i_ddi_soft_state in ddi_impldefs.h)
* - local_ss is a pointer to the copy of the i_ddi_soft_state in local space
* - cb_arg is a pointer to the cb arg (an instance of state_find_data).
*
* For the current uchi_state_t*, see if the td address is in its pool.
*
* 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.fic_uhci_statep is filled in with the contents of the state
* struct in core. This forces the walk to terminate.
*/
typedef struct find_instance_struct {
void *fic_td_qh; /* td/qh we want uhci instance for */
boolean_t fic_td_or_qh; /* which one td_qh points to */
boolean_t fic_found;
uhci_state_t *fic_uhci_statep; /* buffer uhci_state's written into */
} find_instance_cb_t;
/*ARGSUSED*/
static int
find_uhci_instance(uintptr_t uhci_instancep, const void *local_ss, void *cb_arg)
{
int td_pool_size, qh_pool_size;
find_instance_cb_t *cb_data = (find_instance_cb_t *)cb_arg;
uhci_state_t *uhcip = cb_data->fic_uhci_statep;
if (mdb_vread(cb_data->fic_uhci_statep, sizeof (uhci_state_t),
uhci_instancep) == -1) {
mdb_warn("failed to read uhci_state at %p", uhci_instancep);
return (-1);
}
if (mdb_readsym(&td_pool_size, sizeof (int), "uhci_td_pool_size") ==
-1) {
mdb_warn("failed to read uhci_td_pool_size");
return (-1);
}
if (mdb_readsym(&qh_pool_size, sizeof (int), "uhci_qh_pool_size") ==
-1) {
mdb_warn("failed to read uhci_td_pool_size");
return (-1);
}
/*
* See if the addr is within the appropriate pool for this instance.
*/
if ((cb_data->fic_td_or_qh == UHCI_TD &&
((uhci_td_t *)cb_data->fic_td_qh >= uhcip->uhci_td_pool_addr &&
(uhci_td_t *)cb_data->fic_td_qh <= (uhcip->uhci_td_pool_addr +
td_pool_size - sizeof (uhci_td_t)))) ||
(cb_data->fic_td_or_qh == UHCI_QH &&
((queue_head_t *)cb_data->fic_td_qh >= uhcip->uhci_qh_pool_addr &&
(queue_head_t *)cb_data->fic_td_qh <= (uhcip->uhci_qh_pool_addr +
qh_pool_size - sizeof (queue_head_t))))) {
/* td/qh address is within pool for this instance of uhci. */
cb_data->fic_found = TRUE;
return (WALK_DONE);
}
return (WALK_NEXT);
}
/*
* Figure out which instance of uhci owns a td/qh.
*
* - td_qh: a pointer to a uhci td or qh
* - td_or_qh: a flag indicating which it is (td/qh),
* - uhci_statep, pointer to a uhci_state_t, to be filled in with data from
* the found instance of uhci_state_t.
*
* Only works for Cntl/Interrupt tds/qhs; others are dynamically allocated
* and so cannot be found with this method.
*
* Returns 0 on success (no match found), 1 on success (match found),
* -1 on errors.
*/
static int
find_uhci_statep(void *td_qh, boolean_t td_or_qh, uhci_state_t *uhci_statep)
{
find_instance_cb_t cb_data;
uintptr_t uhci_ss;
if (uhci_statep == NULL) {
mdb_warn("failed to find uhci statep: "
"NULL uhci_statep param\n");
return (-1);
}
cb_data.fic_td_qh = td_qh;
cb_data.fic_td_or_qh = td_or_qh;
cb_data.fic_found = FALSE;
cb_data.fic_uhci_statep = uhci_statep;
if (mdb_readsym(&uhci_ss, sizeof (uhci_statep),
"uhci_statep") == -1) {
mdb_warn("failed to read uhci_statep");
return (-1);
}
/*
* Walk all instances of uhci.
* The callback func checks if td_qh belongs to a given instance
* of uhci.
*/
if (mdb_pwalk("softstate", find_uhci_instance, &cb_data,
uhci_ss) != 0) {
mdb_warn("failed to walk softstate");
return (-1);
}
if (cb_data.fic_found == TRUE) {
return (1);
}
return (0);
}
/*
* Dump a UHCI TD (transaction descriptor);
* or (-d) the chain of TDs starting with the one specified.
*/
int
uhci_td(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
uint_t depth_flag = FALSE;
uhci_state_t uhci_state, *uhcip = &uhci_state;
uhci_td_t td;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
if (addr & ~QH_LINK_PTR_MASK) {
mdb_warn("address must be on a 16-byte boundary.\n");
return (DCMD_ERR);
}
if (mdb_getopts(argc, argv,
'd', MDB_OPT_SETBITS, TRUE, &depth_flag,
NULL) != argc) {
return (DCMD_USAGE);
}
if (depth_flag) {
if (mdb_pwalk_dcmd("uhci_td", "uhci_td", 0, NULL, addr) == -1) {
mdb_warn("failed to walk 'uhci_td'");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (find_uhci_statep((void *)addr, UHCI_TD, uhcip) != 1) {
mdb_warn("failed to find uhci_statep");
return (DCMD_ERR);
}
if (mdb_vread(&td, sizeof (td), addr) != sizeof (td)) {
mdb_warn("failed to read td at vaddr %p", addr);
return (DCMD_ERR);
}
mdb_printf("\n UHCI td struct at (vaddr) %08x:\n", addr);
if (!(td.link_ptr & HC_END_OF_LIST) && td.link_ptr != NULL) {
mdb_printf(" link_ptr (paddr) : %-8x "
"(vaddr) : %p\n",
td.link_ptr,
/* Note: uhcip needed by TD_VADDR macro */
TD_VADDR(td.link_ptr & QH_LINK_PTR_MASK));
} else {
mdb_printf(" link_ptr (paddr) : %-8x\n",
td.link_ptr);
}
mdb_printf(" td_dword2 : %08x\n", td.dw2);
mdb_printf(" td_dword3 : %08x\n", td.dw3);
mdb_printf(" buffer_address : %08x\n", td.buffer_address);
mdb_printf(" qh_td_prev : %?p "
"tw_td_next : %?p\n",
td.qh_td_prev, td.tw_td_next);
mdb_printf(" outst_td_prev : %?p "
"outst_td_next : %?p\n",
td.outst_td_prev, td.outst_td_next);
mdb_printf(" tw : %?p "
"flag : %02x\n", td.tw, td.flag);
mdb_printf(" isoc_next : %?p "
"isoc_prev : %0x\n", td.isoc_next, td.isoc_prev);
mdb_printf(" isoc_pkt_index : %0x "
"startingframe: %0x\n", td.isoc_pkt_index, td.starting_frame);
if (td.link_ptr == NULL) {
mdb_printf(" --> Link pointer = NULL\n");
return (DCMD_ERR);
} else {
/* Inform user if link is to a TD or QH. */
if (td.link_ptr & HC_END_OF_LIST) {
mdb_printf(" "
"--> Link pointer invalid (terminate bit set).\n");
} else {
if ((td.link_ptr & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) {
mdb_printf(" "
"--> Link pointer points to a QH.\n");
} else {
mdb_printf(" "
"--> Link pointer points to a TD.\n");
}
}
}
return (DCMD_OK);
}
/*
* Dump a UHCI QH (queue head).
* -b walk/dump the chian of QHs starting with the one specified.
* -d also dump the chain of TDs starting with the one specified.
*/
int
uhci_qh(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
{
uint_t breadth_flag = FALSE, depth_flag = FALSE;
uhci_state_t uhci_state, *uhcip = &uhci_state;
queue_head_t qh;
if (!(flags & DCMD_ADDRSPEC))
return (DCMD_USAGE);
if (addr & ~QH_LINK_PTR_MASK) {
mdb_warn("address must be on a 16-byte boundary.\n");
return (DCMD_ERR);
}
if (mdb_getopts(argc, argv,
'b', MDB_OPT_SETBITS, TRUE, &breadth_flag,
'd', MDB_OPT_SETBITS, TRUE, &depth_flag,
NULL) != argc) {
return (DCMD_USAGE);
}
if (breadth_flag) {
uint_t new_argc = 0;
mdb_arg_t new_argv[1];
if (depth_flag) {
new_argc = 1;
new_argv[0].a_type = MDB_TYPE_STRING;
new_argv[0].a_un.a_str = "-d";
}
if ((mdb_pwalk_dcmd("uhci_qh", "uhci_qh", new_argc, new_argv,
addr)) != 0) {
mdb_warn("failed to walk 'uhci_qh'");
return (DCMD_ERR);
}
return (DCMD_OK);
}
if (find_uhci_statep((void *)addr, UHCI_QH, uhcip) != 1) {
mdb_warn("failed to find uhci_statep");
return (DCMD_ERR);
}
if (mdb_vread(&qh, sizeof (qh), addr) != sizeof (qh)) {
mdb_warn("failed to read qh at vaddr %p", addr);
return (DCMD_ERR);
}
mdb_printf("\n UHCI qh struct at (vaddr) %08x:\n", addr);
if (!(qh.link_ptr & HC_END_OF_LIST) && qh.link_ptr != NULL) {
mdb_printf(" link_ptr (paddr) : %08x "
"(vaddr) : %p\n",
qh.link_ptr,
/* Note: uhcip needed by QH_VADDR macro */
QH_VADDR(qh.link_ptr & QH_LINK_PTR_MASK));
} else {
mdb_printf(
" link_ptr (paddr) : %08x\n",
qh.link_ptr);
}
if (!(qh.element_ptr & HC_END_OF_LIST) && qh.element_ptr != NULL) {
mdb_printf(" element_ptr (paddr) : %08x "
"(vaddr) : %p\n",
qh.element_ptr,
/* Note: uhcip needed by TD_VADDR macro */
TD_VADDR(qh.element_ptr & QH_LINK_PTR_MASK));
} else {
mdb_printf(
" element_ptr (paddr) : %08x\n", qh.element_ptr);
}
mdb_printf(" node : %04x "
"flag : %04x\n",
qh.node, qh.qh_flag);
mdb_printf(" prev_qh : %?p "
"td_tailp : %?p\n",
qh.prev_qh, qh.td_tailp);
mdb_printf(" bulk_xfer_isoc_info : %?p\n", qh.bulk_xfer_info);
if (qh.link_ptr == NULL) {
mdb_printf(" --> Link pointer = NULL\n");
return (DCMD_ERR);
} else {
/* Inform user if next link is a TD or QH. */
if (qh.link_ptr & HC_END_OF_LIST) {
mdb_printf(" "
"--> Link pointer invalid (terminate bit set).\n");
} else {
if ((qh.link_ptr & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) {
mdb_printf(" "
"--> Link pointer points to a QH.\n");
} else {
/* Should never happen. */
mdb_warn(" "
"--> Link pointer points to a TD.\n");
return (DCMD_ERR);
}
}
}
if (qh.element_ptr == NULL) {
mdb_printf(" element_ptr = NULL\n");
return (DCMD_ERR);
} else {
/* Inform user if next element is a TD or QH. */
if (qh.element_ptr & HC_END_OF_LIST) {
mdb_printf(" "
"-->Element pointer invalid (terminate bit set)."
"\n");
return (DCMD_OK);
} else {
if ((qh.element_ptr & HC_QUEUE_HEAD) == HC_QUEUE_HEAD) {
mdb_printf(" "
"--> Element pointer points to a QH.\n");
/* Should never happen in UHCI implementation */
return (DCMD_ERR);
} else {
mdb_printf(" "
"--> Element pointer points to a TD.\n");
}
}
}
/*
* If the user specified the -d (depth) option,
* dump all TDs linked to this TD via the element_ptr.
*/
if (depth_flag) {
/* Traverse and display all the TDs in the chain */
if (mdb_pwalk_dcmd("uhci_td", "uhci_td", argc, argv,
(uintptr_t)(TD_VADDR(qh.element_ptr &
QH_LINK_PTR_MASK))) == -1) {
mdb_warn("failed to walk 'uhci_td'");
return (DCMD_ERR);
}
}
return (DCMD_OK);
}
/*
* Walk a list of UHCI Transaction Descriptors (td's).
* Stop at the end of the list, or if the next element in the list is a
* queue head (qh).
* User must specify the address of the first td to look at.
*/
int
uhci_td_walk_init(mdb_walk_state_t *wsp)
{
if (wsp->walk_addr == NULL) {
return (DCMD_USAGE);
}
wsp->walk_data = mdb_alloc(sizeof (uhci_td_t), UM_SLEEP | UM_GC);
wsp->walk_arg = mdb_alloc(sizeof (uhci_state_t), UM_SLEEP | UM_GC);
/*
* Read the uhci_state_t for the instance of uhci
* using this td address into buf pointed to by walk_arg.
*/
if (find_uhci_statep((void *)wsp->walk_addr, UHCI_TD,
wsp->walk_arg) != 1) {
mdb_warn("failed to find uhci_statep");
return (WALK_ERR);
}
return (WALK_NEXT);
}
/*
* At each step, read a TD into our private storage, and then invoke
* the callback function. We terminate when we reach a QH, or
* link_ptr is NULL.
*/
int
uhci_td_walk_step(mdb_walk_state_t *wsp)
{
int status;
uhci_state_t *uhcip = (uhci_state_t *)wsp->walk_arg;
if (mdb_vread(wsp->walk_data, sizeof (uhci_td_t), wsp->walk_addr)
== -1) {
mdb_warn("failed to read td at %p", wsp->walk_addr);
return (WALK_DONE);
}
status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data,
wsp->walk_cbdata);
/* Next td. */
wsp->walk_addr = ((uhci_td_t *)wsp->walk_data)->link_ptr;
/* Check if we're at the last element */
if (wsp->walk_addr == NULL || wsp->walk_addr & HC_END_OF_LIST)
return (WALK_DONE);
/* Make sure next element is a TD. If a QH, stop. */
if (((((uhci_td_t *)wsp->walk_data)->link_ptr) & HC_QUEUE_HEAD)
== HC_QUEUE_HEAD) {
return (WALK_DONE);
}
/* Strip terminate etc. bits. */
wsp->walk_addr &= QH_LINK_PTR_MASK; /* there is no TD_LINK_PTR_MASK */
if (wsp->walk_addr == NULL)
return (WALK_DONE);
/*
* Convert link_ptr paddr to vaddr
* Note: uhcip needed by TD_VADDR macro
*/
wsp->walk_addr = (uintptr_t)TD_VADDR(wsp->walk_addr);
return (status);
}
/*
* Walk a list of UHCI Queue Heads (qh's).
* Stop at the end of the list, or if the next element in the list is a
* Transaction Descriptor (td).
* User must specify the address of the first qh to look at.
*/
int
uhci_qh_walk_init(mdb_walk_state_t *wsp)
{
if (wsp->walk_addr == NULL)
return (DCMD_USAGE);
wsp->walk_data = mdb_alloc(sizeof (queue_head_t), UM_SLEEP | UM_GC);
wsp->walk_arg = mdb_alloc(sizeof (uhci_state_t), UM_SLEEP | UM_GC);
/*
* Read the uhci_state_t for the instance of uhci
* using this td address into buf pointed to by walk_arg.
*/
if (find_uhci_statep((void *)wsp->walk_addr, UHCI_QH,
(uhci_state_t *)wsp->walk_arg) != 1) {
mdb_warn("failed to find uhci_statep");
return (WALK_ERR);
}
return (WALK_NEXT);
}
/*
* At each step, read a QH into our private storage, and then invoke
* the callback function. We terminate when we reach a QH, or
* link_ptr is NULL.
*/
int
uhci_qh_walk_step(mdb_walk_state_t *wsp)
{
int status;
uhci_state_t *uhcip = (uhci_state_t *)wsp->walk_arg;
if (wsp->walk_addr == NULL) /* Should never occur */
return (WALK_DONE);
if (mdb_vread(wsp->walk_data, sizeof (queue_head_t), wsp->walk_addr)
== -1) {
mdb_warn("failure reading qh at %p", wsp->walk_addr);
return (WALK_DONE);
}
status = wsp->walk_callback(wsp->walk_addr, wsp->walk_data,
wsp->walk_cbdata);
/* Next QH. */
wsp->walk_addr = ((queue_head_t *)wsp->walk_data)->link_ptr;
/* Check if we're at the last element */
if (wsp->walk_addr == NULL || wsp->walk_addr & HC_END_OF_LIST) {
return (WALK_DONE);
}
/* Make sure next element is a QH. If a TD, stop. */
if (((((queue_head_t *)wsp->walk_data)->link_ptr) & HC_QUEUE_HEAD)
!= HC_QUEUE_HEAD) {
return (WALK_DONE);
}
/* Strip terminate etc. bits. */
wsp->walk_addr &= QH_LINK_PTR_MASK;
if (wsp->walk_addr == NULL)
return (WALK_DONE);
/*
* Convert link_ptr paddr to vaddr
* Note: uhcip needed by QH_VADDR macro
*/
wsp->walk_addr = (uintptr_t)QH_VADDR(wsp->walk_addr);
return (status);
}
/*
* 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[] = {
{ "uhci_td", ": [-d]", "print UHCI TD", uhci_td, NULL },
{ "uhci_qh", ": [-bd]", "print UHCI QH", uhci_qh, NULL},
{ NULL }
};
static const mdb_walker_t walkers[] = {
{ "uhci_td", "walk list of UHCI TD structures",
uhci_td_walk_init, uhci_td_walk_step, NULL,
NULL },
{ "uhci_qh", "walk list of UHCI QH structures",
uhci_qh_walk_init, uhci_qh_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);
}