/* * 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. */ #pragma ident "%Z%%M% %I% %E% SMI" #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); }