4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
25 * Copyright 2016 Joyent, Inc.
29 #include <sys/mdb_modapi.h>
30 #include <mdb/mdb_ks.h>
32 #include <sys/usb/usba.h>
33 #include <sys/ddi_impldefs.h>
34 #include <sys/usb/usba/usba_types.h>
35 #include <sys/usb/usba/usba_impl.h>
36 #include <sys/usb/usba/hcdi_impl.h>
37 #include <sys/usb/hubd/hub.h>
38 #include <sys/usb/hubd/hubdvar.h>
40 #include <sys/sunndi.h>
48 extern uintptr_t mdb_usba_get_usba_device(uintptr_t);
49 extern uintptr_t mdb_usba_hcdi_get_hcdi(struct dev_info
*);
55 #define USB_DUMP_VERBOSE 0x01
56 #define USB_DUMP_ACTIVE_PIPES 0x02
58 /* Hardcoded slop factor designed into debug buf logic */
59 #define USB_DEBUG_SIZE_EXTRA_ALLOC 8
63 * Callback arg struct for find_dip (callback func used in usba_device2devinfo).
65 typedef struct usba_device2devinfo_data
{
66 uintptr_t u2d_target_usb_dev_p
; /* one we're looking for */
67 uintptr_t *u2d_dip_addr
; /* Where to store result */
68 boolean_t u2d_found
; /* Match found */
69 } usba_device2devinfo_cbdata_t
;
73 * Callback for usba_device2dip.
74 * Callback called from the devinfo_children walk invoked in usba_device2dip.
76 * For the current dip, get the (potential) pointer to its usba_device_t
78 * See if this pointer matches the address of the usba_device_t we're looking
79 * for (passed in as usb_dev_p). If so, stuff its value in u2d_dip_addr,
80 * and terminate the walk.
82 * - dip_addr is the address in core of the dip currently being processed by the
84 * - local_dip is a pointer to a copy of the struct dev_info in local memory
85 * - cb_data is the addr of the callback arg the walker was invoked with
86 * (passed through transparently from walk invoker).
89 * - WALK_NEXT on success (match not found yet)
90 * - WALK_ERR on errors.
91 * - WALK_DONE is returned, cb_data.found is set to TRUE, and
92 * *cb_data.u2d_dip_addr is set to the matched dip addr if a dip corresponding
93 * to the desired usba_device_t* is found.
97 find_dip(uintptr_t dip_addr
, const void *local_dip
, void *cb_arg
)
99 uintptr_t cur_usb_dev
;
100 usba_device2devinfo_cbdata_t
*cb_data
=
101 (usba_device2devinfo_cbdata_t
*)cb_arg
;
103 if ((cur_usb_dev
= mdb_usba_get_usba_device(dip_addr
)) == NULL
) {
105 * If there's no corresponding usba_device_t, this dip isn't
106 * a usb node. Might be an sd node. Ignore it.
112 if (cur_usb_dev
== cb_data
->u2d_target_usb_dev_p
) {
113 *cb_data
->u2d_dip_addr
= dip_addr
;
114 cb_data
->u2d_found
= TRUE
;
124 * Given a usba_device pointer, figure out which dip is associated with it.
125 * Relies on usba_device.usb_root_hub_dip being accurate.
127 * - usb_dev_addr is a pointer to a usba_device_t in core.
128 * - dip_addr is the address of a uintptr_t to receive the address in core
129 * of the found dip (if any).
132 * 0 on success (no match found)
133 * 1 on success (match found)
137 usba_device2dip(uintptr_t usb_dev_addr
, uintptr_t *dip_addr
)
139 usba_device_t usb_dev
;
140 usba_device2devinfo_cbdata_t cb_data
;
143 * Walk all USB children of the root hub devinfo.
144 * The callback func looks for a match on the usba_device address.
146 cb_data
.u2d_target_usb_dev_p
= usb_dev_addr
;
147 cb_data
.u2d_dip_addr
= dip_addr
;
148 cb_data
.u2d_found
= FALSE
;
150 if (mdb_vread(&usb_dev
, sizeof (usba_device_t
),
151 usb_dev_addr
) == -1) {
152 mdb_warn("failed to read usba_device struct");
158 * Walk devinfo children starting with the root hub node,
159 * looking for a match on the usba_device pointer (which is what
161 * Result is placed in cb_data.dip_addr.
163 if (mdb_pwalk("devinfo_children", find_dip
, &cb_data
,
164 (uintptr_t)usb_dev
.usb_root_hub_dip
) != 0) {
165 mdb_warn("failed to walk devinfo_children");
170 if (cb_data
.u2d_found
== TRUE
) {
180 * Generic walker usba_list_entry_t walker.
181 * Works for any usba_list_entry_t list.
184 usba_list_walk_init(mdb_walk_state_t
*wsp
)
186 /* Must have a start addr. */
187 if (wsp
->walk_addr
== NULL
) {
188 mdb_warn("not a global walk. Starting address required\n");
198 * Generic list walker step routine.
199 * NOTE: multiple walkers share this routine.
202 usba_list_walk_step(mdb_walk_state_t
*wsp
)
205 usba_list_entry_t list_entry
;
207 if (mdb_vread(&list_entry
, sizeof (usba_list_entry_t
),
208 (uintptr_t)wsp
->walk_addr
) == -1) {
209 mdb_warn("failed to read usba_list_entry_t at %p",
215 status
= wsp
->walk_callback(wsp
->walk_addr
, &list_entry
,
217 wsp
->walk_addr
= (uintptr_t)list_entry
.next
;
219 /* Check if we're at the last element */
220 if (wsp
->walk_addr
== NULL
) {
230 * usb_pipe_handle walker
231 * Given a pointer to a usba_device_t, walk the array of endpoint
233 * For each list, traverse the list, invoking the callback on each element.
235 * Note this function takes the address of a usba_device struct (which is
236 * easily obtainable), but actually traverses a sub-portion of the struct
237 * (which address is not so easily obtainable).
240 usb_pipe_handle_walk_init(mdb_walk_state_t
*wsp
)
242 if (wsp
->walk_addr
== NULL
) {
243 mdb_warn("not a global walk; usba_device_t required\n");
248 wsp
->walk_data
= mdb_alloc((sizeof (usba_ph_impl_t
)) * USBA_N_ENDPOINTS
,
252 * Read the usb_ph_list array into local memory.
253 * Set start address to first element/endpoint in usb_pipehandle_list
255 if (mdb_vread(wsp
->walk_data
,
256 (sizeof (usba_ph_impl_t
)) * USBA_N_ENDPOINTS
,
257 (uintptr_t)((size_t)(wsp
->walk_addr
) +
258 offsetof(usba_device_t
, usb_ph_list
))) == -1) {
259 mdb_warn("failed to read usb_pipehandle_list at %p",
272 usb_pipe_handle_walk_step(mdb_walk_state_t
*wsp
)
275 usba_ph_impl_t
*impl_list
= (usba_ph_impl_t
*)(wsp
->walk_data
);
276 intptr_t index
= (intptr_t)wsp
->walk_arg
;
278 /* Find the first valid endpoint, starting from where we left off. */
279 while ((index
< USBA_N_ENDPOINTS
) &&
280 (impl_list
[index
].usba_ph_data
== NULL
)) {
284 /* No more valid endpoints. */
285 if (index
>= USBA_N_ENDPOINTS
) {
290 status
= wsp
->walk_callback((uintptr_t)impl_list
[index
].usba_ph_data
,
291 wsp
->walk_data
, wsp
->walk_cbdata
);
293 /* Set up to start at next pipe handle next time. */
294 wsp
->walk_arg
= (void *)(index
+ 1);
301 * Given the address of a usba_pipe_handle_data_t, dump summary info.
305 usb_pipe_handle(uintptr_t addr
, uint_t flags
, int argc
, const mdb_arg_t
*argv
)
307 char *dir
, *type
, *state
;
308 usb_ep_descr_t ept_descr
;
309 usba_pipe_handle_data_t pipe_handle
;
310 usba_ph_impl_t ph_impl
;
312 if (!(flags
& DCMD_ADDRSPEC
)) {
317 if (mdb_vread(&pipe_handle
,
318 sizeof (usba_pipe_handle_data_t
), addr
) == -1) {
319 mdb_warn("failed to read pipe handle at %p", addr
);
324 if (mdb_vread(&ph_impl
, sizeof (usba_ph_impl_t
),
325 (uintptr_t)pipe_handle
.p_ph_impl
) == -1) {
328 switch (ph_impl
.usba_ph_state
) {
329 case USB_PIPE_STATE_CLOSED
:
333 case USB_PIPE_STATE_IDLE
:
337 case USB_PIPE_STATE_ACTIVE
:
341 case USB_PIPE_STATE_ERROR
:
345 case USB_PIPE_STATE_CLOSING
:
355 bcopy(&pipe_handle
.p_ep
, &ept_descr
, sizeof (usb_ep_descr_t
));
357 if (DCMD_HDRSPEC(flags
)) {
358 mdb_printf("\n %<u>%-3s %5s %3s %7s %-?s %-?s %-?s%</u>\n",
359 "EP", "TYPE ", "DIR", "STATE ", "P_HANDLE", "P_POLICY",
363 dir
= ((ept_descr
.bEndpointAddress
& USB_EP_DIR_MASK
) &
364 USB_EP_DIR_IN
) ? "In " : "Out";
365 switch (ept_descr
.bmAttributes
& USB_EP_ATTR_MASK
) {
366 case USB_EP_ATTR_CONTROL
:
370 case USB_EP_ATTR_ISOCH
:
374 case USB_EP_ATTR_BULK
:
378 case USB_EP_ATTR_INTR
:
387 mdb_printf(" %3d %5s %3s %7s %-?p %-?p %-?p\n",
388 ept_descr
.bEndpointAddress
& USB_EP_NUM_MASK
, type
, dir
, state
,
389 addr
, addr
+ offsetof(usba_pipe_handle_data_t
, p_policy
),
390 addr
+ offsetof(usba_pipe_handle_data_t
, p_ep
));
397 * usba_device walker:
399 * walks the chain of usba_device structs headed by usba_device_list in usba.c
400 * NOTE: It uses the generic list walk step routine usba_list_walk_step.
401 * No walk_fini routine is needed.
404 usba_device_walk_init(mdb_walk_state_t
*wsp
)
406 usba_list_entry_t list_entry
;
408 if (wsp
->walk_addr
!= NULL
) {
410 "global walk only. Must be invoked without an address\n");
415 if (mdb_readvar(&list_entry
, "usba_device_list") == -1) {
416 mdb_warn("failed to read usba_device_list");
421 /* List head is not part of usba_device_t, get first usba_device_t */
422 wsp
->walk_addr
= (uintptr_t)list_entry
.next
;
428 usba_hubd_walk_init(mdb_walk_state_t
*wsp
)
430 if (wsp
->walk_addr
!= 0) {
431 mdb_warn("hubd only supports global walks.\n");
435 if (mdb_layered_walk("usba_device", wsp
) == -1) {
436 mdb_warn("couldn't walk 'usba_device'");
444 * Getting the hub state is annoying. The root hubs are stored on dev_info_t
445 * while the normal hubs are stored as soft state.
448 usba_hubd_walk_step(mdb_walk_state_t
*wsp
)
452 struct dev_info dev_info
;
453 uintptr_t state_addr
;
455 if (mdb_vread(&ud
, sizeof (ud
), wsp
->walk_addr
) != sizeof (ud
)) {
456 mdb_warn("failed to read usba_device_t at %p", wsp
->walk_addr
);
460 if (ud
.usb_root_hubd
!= NULL
) {
461 if (mdb_vread(&hubd
, sizeof (hubd
),
462 (uintptr_t)ud
.usb_root_hubd
) != sizeof (hubd
)) {
463 mdb_warn("failed to read hubd at %p", ud
.usb_root_hubd
);
466 return (wsp
->walk_callback((uintptr_t)ud
.usb_root_hubd
, &hubd
,
470 if (ud
.usb_hubdi
== NULL
)
474 * For non-root hubs, the hubd_t is stored in the soft state. Figure out
475 * the instance from the dev_info_t and then get its soft state.
477 if (mdb_vread(&dev_info
, sizeof (struct dev_info
),
478 (uintptr_t)ud
.usb_dip
) != sizeof (struct dev_info
)) {
479 mdb_warn("failed to read dev_info_t for device %p at %p",
480 wsp
->walk_addr
, ud
.usb_dip
);
484 if (mdb_get_soft_state_byname("hubd_statep", dev_info
.devi_instance
,
485 &state_addr
, &hubd
, sizeof (hubd
)) == -1) {
486 mdb_warn("failed to read hubd soft state for instance %d from "
487 "usb device %p", dev_info
.devi_instance
, wsp
->walk_addr
);
491 return (wsp
->walk_callback(state_addr
, &hubd
, wsp
->walk_cbdata
));
496 * Given the address of a usba_device struct, dump summary info
497 * -v: Print more (verbose) info
498 * -p: Walk/dump all open pipes for this usba_device
502 usba_device(uintptr_t addr
, uint_t flags
, int argc
, const mdb_arg_t
*argv
)
505 char pathname
[MAXNAMELEN
];
506 char dname
[MODMAXNAMELEN
+ 1] = "<unatt>"; /* Driver name */
507 char drv_statep
[MODMAXNAMELEN
+ 10];
508 uint_t usb_flag
= NULL
;
509 boolean_t no_driver_attached
= FALSE
;
511 struct dev_info devinfo
;
513 if (!(flags
& DCMD_ADDRSPEC
)) {
515 if (mdb_walk_dcmd("usba_device", "usba_device", argc
,
517 mdb_warn("failed to walk usba_device");
525 if (mdb_getopts(argc
, argv
,
526 'p', MDB_OPT_SETBITS
, USB_DUMP_ACTIVE_PIPES
, &usb_flag
,
527 'v', MDB_OPT_SETBITS
, USB_DUMP_VERBOSE
, &usb_flag
, NULL
) != argc
) {
532 if (usb_flag
&& !(DCMD_HDRSPEC(flags
))) {
536 if (DCMD_HDRSPEC(flags
)) {
537 mdb_printf("%<u>%-15s %4s %-?s %-42s%</u>\n",
538 "NAME", "INST", "DIP", "PATH ");
541 status
= usba_device2dip(addr
, &dip_addr
);
544 * 0 = no error, no match
545 * 1 = no error, match
549 mdb_warn("error looking for dip for usba_device %p",
552 mdb_warn("failed to find dip for usba_device %p\n",
555 mdb_warn("dip and statep unobtainable\n");
560 /* Figure out what driver (name) is attached to this node. */
561 (void) mdb_devinfo2driver(dip_addr
, (char *)dname
, sizeof (dname
));
563 if (mdb_vread(&devinfo
, sizeof (struct dev_info
),
565 mdb_warn("failed to read devinfo");
570 if (!(DDI_CF2(&devinfo
))) {
571 no_driver_attached
= TRUE
;
574 (void) mdb_ddi_pathname(dip_addr
, pathname
, sizeof (pathname
));
575 mdb_printf("%-15s %2d %-?p %s\n", dname
, devinfo
.devi_instance
,
578 if (usb_flag
& USB_DUMP_VERBOSE
) {
580 uintptr_t statep
= NULL
;
582 char **config_cloud
, **conf_str_descr
;
583 usb_dev_descr_t usb_dev_descr
;
584 usba_device_t usba_device_struct
;
586 if (mdb_vread(&usba_device_struct
,
587 sizeof (usba_device_t
), addr
) == -1) {
588 mdb_warn("failed to read usba_device struct");
593 mdb_printf(" usba_device: %-16p\n\n", (usba_device_t
*)addr
);
595 if (mdb_vread(&usb_dev_descr
, sizeof (usb_dev_descr
),
596 (uintptr_t)usba_device_struct
.usb_dev_descr
) == -1) {
597 mdb_warn("failed to read usb_dev_descr_t struct");
602 mdb_printf("\n idVendor: 0x%04x idProduct: 0x%04x "
603 "usb_addr: 0x%02x\n", usb_dev_descr
.idVendor
,
604 usb_dev_descr
.idProduct
, usba_device_struct
.usb_addr
);
606 /* Get the string descriptor string into local space. */
607 string_descr
= (char *)mdb_alloc(USB_MAXSTRINGLEN
, UM_GC
);
609 if (usba_device_struct
.usb_mfg_str
== NULL
) {
610 (void) strcpy(string_descr
, "<No Manufacturer String>");
612 if (mdb_readstr(string_descr
, USB_MAXSTRINGLEN
,
613 (uintptr_t)usba_device_struct
.usb_mfg_str
) == -1) {
614 mdb_warn("failed to read manufacturer "
615 "string descriptor");
616 (void) strcpy(string_descr
, "???");
619 mdb_printf("\n Manufacturer String:\t%s\n", string_descr
);
621 if (usba_device_struct
.usb_product_str
== NULL
) {
622 (void) strcpy(string_descr
, "<No Product String>");
624 if (mdb_readstr(string_descr
, USB_MAXSTRINGLEN
,
625 (uintptr_t)usba_device_struct
.usb_product_str
) ==
627 mdb_warn("failed to read product string "
629 (void) strcpy(string_descr
, "???");
632 mdb_printf(" Product String:\t\t%s\n", string_descr
);
634 if (usba_device_struct
.usb_serialno_str
== NULL
) {
635 (void) strcpy(string_descr
, "<No SerialNumber String>");
637 if (mdb_readstr(string_descr
, USB_MAXSTRINGLEN
,
638 (uintptr_t)usba_device_struct
.usb_serialno_str
) ==
640 mdb_warn("failed to read serial number string "
642 (void) strcpy(string_descr
, "???");
645 mdb_printf(" SerialNumber String:\t%s\n", string_descr
);
647 if (no_driver_attached
) {
650 mdb_printf(" state_p: ");
653 * Given the dip, find the associated statep. The
654 * convention to generate this soft state anchor is:
655 * <driver_name>_statep
657 (void) mdb_snprintf(drv_statep
, sizeof (drv_statep
),
659 if (mdb_devinfo2statep(dip_addr
, drv_statep
,
661 mdb_warn("failed to find %s state struct for "
662 "dip %p", drv_statep
, dip_addr
);
666 mdb_printf("%-?p\n", statep
);
669 config_cloud
= (char **)mdb_alloc(sizeof (void *) *
670 usba_device_struct
.usb_n_cfgs
, UM_GC
);
672 conf_str_descr
= (char **)mdb_alloc(sizeof (void *) *
673 usba_device_struct
.usb_n_cfgs
, UM_GC
);
675 if ((usba_device_struct
.usb_cfg_array
) &&
676 (usba_device_struct
.usb_cfg_str_descr
)) {
677 if ((mdb_vread(config_cloud
, sizeof (void *) *
678 usba_device_struct
.usb_n_cfgs
,
679 (uintptr_t)usba_device_struct
.usb_cfg_array
) ==
680 -1) || (mdb_vread(conf_str_descr
, sizeof (void *)
681 * usba_device_struct
.usb_n_cfgs
, (uintptr_t)
682 usba_device_struct
.usb_cfg_str_descr
)) == -1) {
684 mdb_warn("failed to read config cloud "
689 mdb_printf("\n Device Config Clouds:\n"
690 " Index\tConfig\t\tConfiguration "
693 "--------------------\n");
695 for (i
= 0; i
< usba_device_struct
.usb_n_cfgs
;
697 if (mdb_readstr(string_descr
,
699 (uintptr_t)conf_str_descr
[i
]) ==
701 (void) strcpy(string_descr
,
705 mdb_printf(" %4d\t0x%p\t%s\n", i
,
706 config_cloud
[i
], string_descr
);
711 mdb_printf("\n Active configuration index: %d\n",
712 usba_device_struct
.usb_active_cfg_ndx
);
715 if (usb_flag
& USB_DUMP_ACTIVE_PIPES
) {
717 if (mdb_pwalk_dcmd("usb_pipe_handle", "usb_pipe_handle",
718 0, NULL
, addr
) == -1) {
719 mdb_warn("failed to walk usb_pipe_handle");
729 * Dump the contents of the usba_debug_buf, from the oldest to newest,
730 * wrapping around if necessary.
734 usba_debug_buf(uintptr_t addr
, uint_t flags
, int argc
, const mdb_arg_t
*argv
)
736 char *debug_buf_addr
; /* addr in core */
737 char *local_debug_buf
; /* local copy of buf */
742 if (flags
& DCMD_ADDRSPEC
) {
747 if (mdb_readvar(&being_cleared
, "usba_clear_debug_buf_flag") ==
749 mdb_warn("failed to read usba_clear_debug_buf_flag");
758 if (mdb_readvar(&debug_buf_addr
, "usba_debug_buf") == -1) {
759 mdb_warn("failed to read usba_debug_buf");
764 if (debug_buf_addr
== NULL
) {
765 mdb_warn("usba_debug_buf not allocated\n");
771 if (mdb_readvar(&debug_buf_size
, "usba_debug_buf_size") == -1) {
772 mdb_warn("failed to read usba_debug_buf_size");
777 debug_buf_size
+= USB_DEBUG_SIZE_EXTRA_ALLOC
;
778 local_debug_buf
= (char *)mdb_alloc(debug_buf_size
, UM_SLEEP
| UM_GC
);
780 if ((mdb_vread(local_debug_buf
, debug_buf_size
,
781 (uintptr_t)debug_buf_addr
)) == -1) {
782 mdb_warn("failed to read usba_debug_buf at %p",
787 local_debug_buf
[debug_buf_size
- 1] = '\0';
789 if (strlen(local_debug_buf
) == NULL
) {
794 if ((term_p
= strstr(local_debug_buf
, ">>>>")) == NULL
) {
795 mdb_warn("failed to find terminator \">>>>\"\n");
801 * Print the chunk of buffer from the terminator to the end.
802 * This will print a null string if no wrap has occurred yet.
804 mdb_printf("%s", term_p
+5); /* after >>>>\0 to end of buf */
805 mdb_printf("%s\n", local_debug_buf
); /* beg of buf to >>>>\0 */
812 usba_clear_debug_buf(
813 uintptr_t addr
, uint_t flags
, int argc
, const mdb_arg_t
*argv
)
817 /* stop the tracing */
818 if (mdb_writevar((void*)&clear
, "usba_clear_debug_buf_flag") == -1) {
819 mdb_warn("failed to set usba_clear_debug_buf_flag");
828 extern int prtusb(uintptr_t, uint_t
, int, const mdb_arg_t
*);
830 extern void prt_usb_usage(void);
833 * MDB module linkage information:
835 * We declare a list of structures describing our dcmds, and a function
836 * named _mdb_init to return a pointer to our module information.
838 static const mdb_dcmd_t dcmds
[] = {
839 { "usb_pipe_handle", ":",
840 "print a usb_pipe_handle struct", usb_pipe_handle
, NULL
},
841 { "usba_device", ": [-pv]",
842 "print summary info for a usba_device_t struct", usba_device
, NULL
},
843 { "usba_debug_buf", NULL
,
844 "print usba_debug_buf", usba_debug_buf
, NULL
},
845 { "usba_clear_debug_buf", NULL
,
846 "clear usba_debug_buf", usba_clear_debug_buf
, NULL
},
847 { "prtusb", "?[-t] [-v] [-i index]",
848 "print trees and descriptors for usba_device_t",
849 prtusb
, prt_usb_usage
},
853 static const mdb_walker_t walkers
[] = {
854 /* Generic list walker. */
855 { "usba_list_entry", "walk list of usba_list_entry_t structures",
856 usba_list_walk_init
, usba_list_walk_step
, NULL
, NULL
},
857 { "usb_pipe_handle", "walk USB pipe handles, given a usba_device_t ptr",
858 usb_pipe_handle_walk_init
, usb_pipe_handle_walk_step
, NULL
, NULL
},
859 { "usba_device", "walk global list of usba_device_t structures",
860 usba_device_walk_init
, usba_list_walk_step
, NULL
, NULL
},
861 { "hubd", "walk hubd instances", usba_hubd_walk_init
,
862 usba_hubd_walk_step
, NULL
, NULL
},
866 static const mdb_modinfo_t modinfo
= {
867 MDB_API_VERSION
, dcmds
, walkers
870 const mdb_modinfo_t
*