root/usr/src/lib/cfgadm_plugins/usb/common/cfga_usb.c
/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
 * Copyright 2022 Oxide Computer Company
 */

#include "cfga_usb.h"


/* function prototypes */
cfga_err_t              usb_err_msg(char **, cfga_usb_ret_t, const char *, int);
extern cfga_usb_ret_t   usb_rcm_offline(const char *, char **, char *,
                            cfga_flags_t);
extern cfga_usb_ret_t   usb_rcm_online(const char *, char **, char *,
                            cfga_flags_t);
extern cfga_usb_ret_t   usb_rcm_remove(const char *, char **, char *,
                            cfga_flags_t);
static int              usb_confirm(struct cfga_confirm *, char *);
static char             *usb_get_devicepath(const char *);

/*
 * This file contains the entry points to the plugin as defined in the
 * config_admin(3CFGADM) man page.
 */

/*
 * Set the version number for the cfgadm library's use.
 */
int cfga_version = CFGA_HSL_V2;

#define HELP_HEADER             1
#define HELP_CONFIG             2
#define HELP_RESET_SLOT         3
#define HELP_CONFIG_SLOT        4
#define HELP_UNKNOWN            5

/* Help messages */
static char *
usb_help[] = {
NULL,
"USB specific commands:\n",
" cfgadm -c [configure|unconfigure|disconnect] ap_id [ap_id...]\n",
" cfgadm -x usb_reset ap_id [ap_id...]\n",
" cfgadm -x usb_config -o config=<index of desired configuration>  ap_id\n",
"\tunknown command or option: ",
NULL
};      /* End help messages */

/* Error messages */
static msgcvt_t
usb_error_msgs[] = {
        /* CFGA_USB_OK  */
        { CVT, CFGA_OK, "ok" },

        /* CFGA_USB_UNKNOWN     */
        { CVT, CFGA_LIB_ERROR, "Unknown message; internal error" },

        /* CFGA_USB_INTERNAL_ERROR      */
        { CVT, CFGA_LIB_ERROR, "Internal error" },

        /* CFGA_USB_OPTIONS     */
        { CVT, CFGA_ERROR, "Hardware specific options not supported" },

        /* CFGA_USB_DYNAMIC_AP  */
        { CVT, CFGA_INVAL, "Dynamic attachment points not supported" },

        /* CFGA_USB_AP  */
        { CVT, CFGA_APID_NOEXIST, "" },

        /* CFGA_USB_PORT        */
        { CVT, CFGA_LIB_ERROR, "Cannot determine hub port number for " },

        /* CFGA_USB_DEVCTL      */
        { CVT, CFGA_ERROR, "Cannot issue devctl to " },

        /* CFGA_USB_NOT_CONNECTED       */
        { CVT, CFGA_INVAL, "No device connected to " },

        /* CFGA_USB_NOT_CONFIGURED      */
        { CVT, CFGA_INVAL, "No device configured to " },

        /* CFGA_USB_ALREADY_CONNECTED   */
        { CVT, CFGA_INSUFFICENT_CONDITION,
                "Device already connected; cannot connect again " },

        /* CFGA_USB_ALREADY_CONFIGURED  */
        { CVT, CFGA_INVAL, "device already configured for " },

        /* CFGA_USB_OPEN        */
        { CVT, CFGA_LIB_ERROR, "Cannot open " },

        /* CFGA_USB_IOCTL       */
        { CVT, CFGA_ERROR, "Driver ioctl failed " },

        /* CFGA_USB_BUSY        */
        { CVT, CFGA_SYSTEM_BUSY, "" },

        /* CFGA_USB_ALLOC_FAIL  */
        { CVT, CFGA_LIB_ERROR, "Memory allocation failure" },

        /* CFGA_USB_OPNOTSUPP   */
        { CVT, CFGA_OPNOTSUPP, "Operation not supported" },

        /* CFGA_USB_DEVLINK     */
        { CVT, CFGA_LIB_ERROR, "Could not find /dev/cfg link for " },

        /* CFGA_USB_STATE       */
        { CVT, CFGA_LIB_ERROR, "Internal error: Unrecognized ap state" },

        /* CFGA_USB_CONFIG_INVAL        */
        { CVT, CFGA_ERROR,
                "Specified configuration index unrecognized or exceeds "
                "maximum available" },

        /* CFGA_USB_PRIV        */
        { CVT, CFGA_PRIV, "" },

        /* CFGA_USB_NVLIST      */
        { CVT, CFGA_ERROR, "Internal error (nvlist)" },

        /* CFGA_USB_ZEROLEN     */
        { CVT, CFGA_ERROR, "Internal error (zerolength string)" },

        /* CFGA_USB_CONFIG_FILE */
        { CVT, CFGA_ERROR,
        "Cannot open/fstat/read USB system configuration file" },

        /* CFGA_USB_LOCK_FILE */
        { CVT, CFGA_ERROR, "Cannot lock USB system configuration file" },

        /* CFGA_USB_UNLOCK_FILE */
        { CVT, CFGA_ERROR, "Cannot unlock USB system configuration file" },

        /* CFGA_USB_ONE_CONFIG  */
        { CVT, CFGA_ERROR,
        "Operation not supported for devices with one configuration" },

        /* CFGA_USB_RCM_HANDLE Errors */
        { CVT, CFGA_ERROR, "cannot get RCM handle"},

        /* CFGA_USB_RCM_ONLINE */
        { CVT, CFGA_SYSTEM_BUSY,   "failed to online: "},

        /* CFGA_USB_RCM_OFFLINE */
        { CVT, CFGA_SYSTEM_BUSY,   "failed to offline: "},

        /* CFGA_USB_RCM_INFO */
        { CVT, CFGA_ERROR,   "failed to query: "}

};      /* End error messages */


/* ========================================================================= */
/*
 * The next two funcs imported verbatim from cfgadm_scsi.
 * physpath_to_devlink is the only func directly used by cfgadm_usb.
 * get_link supports it.
 */

/*
 * Routine to search the /dev directory or a subtree of /dev.
 */
static int
get_link(di_devlink_t devlink, void *arg)
{
        walk_link_t *larg = (walk_link_t *)arg;

        /*
         * When path is specified, it's the node path without minor
         * name. Therefore, the ../.. prefixes needs to be stripped.
         */
        if (larg->path) {
                char *content = (char *)di_devlink_content(devlink);
                char *start = strstr(content, "/devices/");

                /* line content must have minor node */
                if (start == NULL ||
                    strncmp(start, larg->path, larg->len) != 0 ||
                    start[larg->len] != ':') {

                        return (DI_WALK_CONTINUE);
                }
        }

        *(larg->linkpp) = strdup(di_devlink_path(devlink));

        return (DI_WALK_TERMINATE);
}


/* ARGSUSED */
static ucfga_ret_t
physpath_to_devlink(
        const char *basedir,
        const char *node_path,
        char **logpp,
        int *l_errnop,
        int match_minor)
{
        walk_link_t larg;
        di_devlink_handle_t hdl;
        char *minor_path;

        if ((hdl = di_devlink_init(NULL, 0)) == NULL) {
                *l_errnop = errno;
                return (UCFGA_LIB_ERR);
        }

        *logpp = NULL;
        larg.linkpp = logpp;
        if (match_minor) {
                minor_path = (char *)node_path + strlen("/devices");
                larg.path = NULL;
        } else {
                minor_path = NULL;
                larg.len = strlen(node_path);
                larg.path = (char *)node_path;
        }

        (void) di_devlink_walk(hdl, "^cfg/", minor_path, DI_PRIMARY_LINK,
            (void *)&larg, get_link);

        (void) di_devlink_fini(&hdl);

        if (*logpp == NULL) {
                *l_errnop = errno;
                return (UCFGA_LIB_ERR);
        }

        return (UCFGA_OK);
}


/* ========================================================================= */
/* Utilities */

/*
 * Given the index into a table (msgcvt_t) of messages, get the message
 * string, converting it to the proper locale if necessary.
 * NOTE: See cfga_usb.h
 */
static const char *
get_msg(uint_t msg_index, msgcvt_t *msg_tbl, uint_t tbl_size)
{
        if (msg_index >= tbl_size) {
                DPRINTF("get_error_msg: bad error msg index: %d\n", msg_index);
                msg_index = CFGA_USB_UNKNOWN;
        }

        return ((msg_tbl[msg_index].intl) ?
            dgettext(TEXT_DOMAIN, msg_tbl[msg_index].msgstr) :
            msg_tbl[msg_index].msgstr);
}


/*
 * Allocates and creates a message string (in *ret_str),
 * by concatenating all the (char *) args together, in order.
 * Last arg MUST be NULL.
 */
static void
set_msg(char **ret_str, ...)
{
        char    *str;
        size_t  total_len;
        va_list valist;

        va_start(valist, ret_str);

        total_len = (*ret_str == NULL) ? 0 : strlen(*ret_str);

        while ((str = va_arg(valist, char *)) != NULL) {
                size_t  len = strlen(str);
                char    *old_str = *ret_str;

                *ret_str = (char *)realloc(*ret_str, total_len + len + 1);
                if (*ret_str == NULL) {
                        /* We're screwed */
                        free(old_str);
                        DPRINTF("set_msg: realloc failed.\n");
                        va_end(valist);
                        return;
                }

                (void) strcpy(*ret_str + total_len, str);
                total_len += len;
        }

        va_end(valist);
}


/*
 * Error message handling.
 * For the rv passed in, looks up the corresponding error message string(s),
 * internationalized it if necessary, and concatenates it into a new
 * memory buffer, and points *errstring to it.
 * Note not all rvs will result in an error message return, as not all
 * error conditions warrant a USB-specific error message.
 *
 * Some messages may display ap_id or errno, which is why they are passed
 * in.
 */
cfga_err_t
usb_err_msg(char **errstring, cfga_usb_ret_t rv, const char *ap_id, int l_errno)
{
        if (errstring == NULL) {

                return (usb_error_msgs[rv].cfga_err);
        }

        /*
         * Generate the appropriate USB-specific error message(s) (if any).
         */
        switch (rv) {
        case CFGA_USB_OK:
        /* Special case - do nothing.  */
                break;

        case CFGA_USB_UNKNOWN:
        case CFGA_USB_DYNAMIC_AP:
        case CFGA_USB_INTERNAL_ERROR:
        case CFGA_USB_OPTIONS:
        case CFGA_USB_ALLOC_FAIL:
        case CFGA_USB_STATE:
        case CFGA_USB_CONFIG_INVAL:
        case CFGA_USB_PRIV:
        case CFGA_USB_OPNOTSUPP:
        /* These messages require no additional strings passed. */
                set_msg(errstring, ERR_STR(rv), NULL);
                break;

        case CFGA_USB_AP:
        case CFGA_USB_PORT:
        case CFGA_USB_NOT_CONNECTED:
        case CFGA_USB_NOT_CONFIGURED:
        case CFGA_USB_ALREADY_CONNECTED:
        case CFGA_USB_ALREADY_CONFIGURED:
        case CFGA_USB_BUSY:
        case CFGA_USB_DEVLINK:
        case CFGA_USB_RCM_HANDLE:
        case CFGA_USB_RCM_ONLINE:
        case CFGA_USB_RCM_OFFLINE:
        case CFGA_USB_RCM_INFO:
        case CFGA_USB_DEVCTL:
        /* These messages also print ap_id.  */
                (void) set_msg(errstring, ERR_STR(rv),
                    "ap_id: ", ap_id, "", NULL);
                break;

        case CFGA_USB_IOCTL:
        case CFGA_USB_NVLIST:
        case CFGA_USB_CONFIG_FILE:
        case CFGA_USB_ONE_CONFIG:
        /* These messages also print errno.  */
        {
                char *errno_str = l_errno ? strerror(l_errno) : "";

                set_msg(errstring, ERR_STR(rv), errno_str,
                    l_errno ? "\n" : "", NULL);
                break;
        }

        case CFGA_USB_OPEN:
        /* These messages also apid and errno.  */
        {
                char *errno_str = l_errno ? strerror(l_errno) : "";

                set_msg(errstring, ERR_STR(rv), "ap_id: ", ap_id, "\n",
                    errno_str, l_errno ? "\n" : "", NULL);
                break;
        }

        default:
                DPRINTF("usb_err_msg: Unrecognized message index: %d\n", rv);
                set_msg(errstring, ERR_STR(CFGA_USB_INTERNAL_ERROR), NULL);

        }       /* end switch */

        /*
         * Determine the proper error code to send back to the cfgadm library.
         */
        return (usb_error_msgs[rv].cfga_err);
}


/*
 * Ensure the ap_id passed is in the correct (physical ap_id) form:
 *     path/device:xx[.xx]+
 * where xx is a one or two-digit number.
 *
 * Note the library always calls the plugin with a physical ap_id.
 */
static int
verify_valid_apid(const char *ap_id)
{
        char    *l_ap_id;

        if (ap_id == NULL) {
                return (-1);
        }

        l_ap_id = strrchr(ap_id, *MINOR_SEP);
        l_ap_id++;

        if (strspn(l_ap_id, "0123456789.") != strlen(l_ap_id)) {
                /* Bad characters in the ap_id. */
                return (-1);
        }

        if (strstr(l_ap_id, "..") != NULL) {
                /* ap_id has 1..2 or more than 2 dots */
                return (-1);
        }

        return (0);
}


/*
 * Verify the params passed in are valid.
 */
static cfga_usb_ret_t
verify_params(
        const char *ap_id,
        const char *options,
        char **errstring)
{
        if (errstring != NULL) {
                *errstring = NULL;
        }

        if (options != NULL) {
                DPRINTF("verify_params: hardware-specific options not "
                    "supported.\n");
                return (CFGA_USB_OPTIONS);
        }

        /* Dynamic attachment points not supported (yet). */
        if (GET_DYN(ap_id) != NULL) {
                DPRINTF("verify_params: dynamic ap_id passed\n");
                return (CFGA_USB_DYNAMIC_AP);
        }

        if (verify_valid_apid(ap_id) != 0) {
                DPRINTF("verify_params: not a USB ap_id.\n");
                return (CFGA_USB_AP);
        }

        return (CFGA_USB_OK);
}


/*
 * Takes a validated ap_id and extracts the port number.
 */
static cfga_usb_ret_t
get_port_num(const char *ap_id, uint_t *port)
{
        char *port_nbr_str;
        char *temp;

        port_nbr_str = strrchr(ap_id, *MINOR_SEP) + strlen(MINOR_SEP);
        if ((temp = strrchr(ap_id, (int)*PORT_SEPERATOR)) != 0) {
                port_nbr_str = temp + strlen(PORT_SEPERATOR);
        }

        errno = 0;
        *port = strtol(port_nbr_str, NULL, 10);
        if (errno) {
                DPRINTF("get_port_num: conversion of port str failed\n");
                return (CFGA_USB_PORT);
        }

        return (CFGA_USB_OK);
}


/*
 * Pair of routines to set up for/clean up after a devctl_ap_* lib call.
 */
static void
cleanup_after_devctl_cmd(devctl_hdl_t devctl_hdl, nvlist_t *user_nvlist)
{
        if (user_nvlist != NULL) {
                nvlist_free(user_nvlist);
        }
        if (devctl_hdl != NULL) {
                devctl_release(devctl_hdl);
        }
}


static cfga_usb_ret_t
setup_for_devctl_cmd(const char *ap_id, devctl_hdl_t *devctl_hdl,
    nvlist_t **user_nvlistp, uint_t oflag)
{
        uint32_t        port;
        cfga_usb_ret_t  rv = CFGA_USB_OK;

        DPRINTF("setup_for_devctl_cmd: oflag=%d\n", oflag);

        /* Get a handle to the ap */
        if ((*devctl_hdl = devctl_ap_acquire((char *)ap_id, oflag)) == NULL) {
                DPRINTF("setup_for_devctl_cmd: devctl_ap_acquire failed with "
                    "errno: %d\n", errno);
                rv = CFGA_USB_DEVCTL;
                goto bailout;
        }

        /* Set up to pass port number down to driver */
        if (nvlist_alloc(user_nvlistp, NV_UNIQUE_NAME_TYPE, 0) != 0) {
                DPRINTF("setup_for_devctl: nvlist_alloc failed, errno: %d\n",
                    errno);
                *user_nvlistp = NULL;   /* Prevent possible incorrect free in */
                                        /* cleanup_after_devctl_cmd */
                rv = CFGA_USB_NVLIST;
                goto bailout;
        }

        if ((rv = get_port_num(ap_id, &port)) != CFGA_USB_OK) {
                DPRINTF("setup_for_devctl_cmd: get_port_num, errno: %d\n",
                    errno);
                goto bailout;
        }

        /* creates an int32_t entry */
        if (nvlist_add_int32(*user_nvlistp, PORT, port) == -1) {
                DPRINTF("setup_for_devctl_cmd: nvlist_add_int32 failed. "
                    "errno: %d\n", errno);
                rv = CFGA_USB_NVLIST;
                goto bailout;
        }

        return (rv);

bailout:
        cleanup_after_devctl_cmd(*devctl_hdl, *user_nvlistp);

        return (rv);
}


/*
 * Ensure that there's a device actually connected to the ap
 */
static cfga_usb_ret_t
device_configured(devctl_hdl_t hdl, nvlist_t *nvl, ap_rstate_t *rstate)
{
        cfga_usb_ret_t          rv;
        devctl_ap_state_t       devctl_ap_state;

        DPRINTF("device_configured:\n");
        if (devctl_ap_getstate(hdl, nvl, &devctl_ap_state) == -1) {
                DPRINTF("devctl_ap_getstate failed, errno: %d\n", errno);
                return (CFGA_USB_DEVCTL);
        }

        rv = CFGA_USB_ALREADY_CONFIGURED;
        *rstate = devctl_ap_state.ap_rstate;
        if (devctl_ap_state.ap_ostate != AP_OSTATE_CONFIGURED) {
                return (CFGA_USB_NOT_CONFIGURED);
        }

        return (rv);
}


/*
 * Ensure that there's a device actually connected to the ap
 */
static cfga_usb_ret_t
device_connected(devctl_hdl_t hdl, nvlist_t *list, ap_ostate_t *ostate)
{
        cfga_usb_ret_t          rv = CFGA_USB_ALREADY_CONNECTED;
        devctl_ap_state_t       devctl_ap_state;

        DPRINTF("device_connected:\n");

        if (devctl_ap_getstate(hdl, list, &devctl_ap_state) == -1) {
                DPRINTF("devctl_ap_getstate failed, errno: %d\n", errno);
                return (CFGA_USB_DEVCTL);
        }

        *ostate =  devctl_ap_state.ap_ostate;
        if (devctl_ap_state.ap_rstate != AP_RSTATE_CONNECTED) {
                return (CFGA_USB_NOT_CONNECTED);
        }

        return (rv);
}


/*
 * Given a subcommand to the DEVCTL_AP_CONTROL ioctl, rquest the size of
 * the data to be returned, allocate a buffer, then get the data.
 * Returns *descrp (which must be freed) and size.
 *
 * Note USB_DESCR_TYPE_STRING returns an ASCII NULL-terminated string,
 * not a string descr.
 */
cfga_usb_ret_t
do_control_ioctl(const char *ap_id, uint_t subcommand, uint_t arg,
    void **descrp, size_t *sizep)
{
        int                     fd = -1;
        uint_t                  port;
        uint32_t                local_size;
        cfga_usb_ret_t          rv = CFGA_USB_OK;
        struct hubd_ioctl_data  ioctl_data;

        assert(descrp != NULL);
        *descrp = NULL;
        assert(sizep != NULL);

        if ((rv = get_port_num(ap_id, &port)) != CFGA_USB_OK) {
                goto bailout;
        }

        if ((fd = open(ap_id, O_RDONLY)) == -1) {
                DPRINTF("do_control_ioctl: open failed: errno:%d\n", errno);
                rv = CFGA_USB_OPEN;
                if (errno == EBUSY) {
                        rv = CFGA_USB_BUSY;
                }
                goto bailout;
        }

        ioctl_data.cmd = subcommand;
        ioctl_data.port = port;
        ioctl_data.misc_arg = (uint_t)arg;

        /*
         * Find out how large a buf we need to get the data.
         *
         * Note the ioctls only accept/return a 32-bit int for a get_size
         * to avoid 32/64 and BE/LE issues.
         */
        ioctl_data.get_size = B_TRUE;
        ioctl_data.buf = (caddr_t)&local_size;
        ioctl_data.bufsiz = sizeof (local_size);

        if (ioctl(fd, DEVCTL_AP_CONTROL, &ioctl_data) != 0) {
                DPRINTF("do_control_ioctl: size ioctl failed: errno:%d\n",
                    errno);
                rv = CFGA_USB_IOCTL;
                goto bailout;
        }
        *sizep = local_size;

        if (subcommand == USB_DESCR_TYPE_STRING &&
            arg == HUBD_CFG_DESCR_STR && local_size == 0) {
                /* Zero-length data - nothing to do.  */
                rv = CFGA_USB_ZEROLEN;
                goto bailout;
        }
        if (subcommand == HUBD_REFRESH_DEVDB) {
                /* Already done - no data transfer; nothing left to do. */
                goto bailout;
        }

        if ((*descrp = malloc(*sizep)) == NULL) {
                DPRINTF("do_control_ioctl: malloc failed\n");
                rv = CFGA_USB_ALLOC_FAIL;
                goto bailout;
        }

        /* Get the data */
        ioctl_data.get_size = B_FALSE;
        ioctl_data.buf = *descrp;
        ioctl_data.bufsiz = *sizep;

        if (ioctl(fd, DEVCTL_AP_CONTROL, &ioctl_data) != 0) {
                DPRINTF("do_control_ioctl: ioctl failed: errno:%d\n",
                    errno);
                rv = CFGA_USB_IOCTL;
                goto bailout;
        }

        (void) close(fd);

        return (rv);


bailout:
        if (fd != -1) {
                (void) close(fd);
        }
        if (*descrp != NULL) {
                free(*descrp);
                *descrp = NULL;
        }

        if (rv == CFGA_USB_IOCTL && errno == EBUSY) {
                rv = CFGA_USB_BUSY;     /* Provide more useful msg */
        }

        return (rv);
}


/* ========================================================================= */
/*
 * Support funcs called directly from cfga_* entry points.
 */


/*
 * Invoked from cfga_private_func.
 * Modify the USB persistant configuration file so that the device
 * represented by ap_id will henceforth be initialized to the desired
 * configuration setting (configuration index).
 */
static cfga_usb_ret_t
set_configuration(const char *ap_id, uint_t config, char *driver,
    usb_dev_descr_t *descrp, char **errstring)
{
        char            *serial_no = NULL;
        char            *dev_path = NULL;
        char            *tmp;
        size_t          size;
        cfga_usb_ret_t  rv = CFGA_USB_OK;

        DPRINTF("set_configuration: ap_id: %s, config:%d\n", ap_id, config);

        /* Only one bNumConfigurations, don't allow this operation */
        if (descrp->bNumConfigurations == 1) {
                DPRINTF("device supports %d configurations\n",
                    descrp->bNumConfigurations);
                rv = CFGA_USB_ONE_CONFIG;
                goto bailout;
        }

        /* get the serial number string if it exists */
        if (descrp->iSerialNumber != 0) {
                if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_STRING,
                    HUBD_SERIALNO_STR, (void **)&serial_no, &size)) !=
                    CFGA_USB_OK) {
                        if (rv != CFGA_USB_ZEROLEN) {
                                DPRINTF("set_configuration: get serial "
                                    "no string failed\n");
                                goto bailout;
                        }
                }
        }

        dev_path = usb_get_devicepath(ap_id);
        if (dev_path == NULL) {
                DPRINTF("get device path failed\n");
                rv = CFGA_USB_DEVCTL;
                goto bailout;
        }

        DPRINTF("calling add_entry: vid: 0x%x pid:0x%x config:0x%x,",
            descrp->idVendor, descrp->idProduct, config);
        DPRINTF("serial_no: %s\n\tdev_path: %s\n\tdriver: %s\n", serial_no ?
            serial_no : "", dev_path ? dev_path : "", driver ? driver : "");

        /*
         * the devicepath should be an absolute path.
         * So, if path has leading "/devices" - nuke it.
         */
        if (strncmp(dev_path, "/devices/", 9) == 0) {
                tmp = dev_path + 8;
        } else {
                tmp = dev_path;
        }

        /* Save an entry in the USBCONF_FILE  */
        if ((rv = add_entry(
            "enable",           /* Always to "enable" */
            descrp->idVendor,   /* vendorId */
            descrp->idProduct,  /* ProductId */
            config,             /* new cfgndx */
            serial_no,          /* serial no string */
            tmp,                /* device path */
            driver,             /* Driver (optional) */
            errstring))
            != CFGA_USB_OK) {
                DPRINTF("set_configuration: add_entry failed\n");
                goto bailout;
        }

        /* Notify hubd that it needs to refresh its db.  */
        if ((rv = do_control_ioctl(ap_id, HUBD_REFRESH_DEVDB, 0,
            (void **)&dev_path, &size)) != CFGA_USB_OK) {
                DPRINTF("set_configuration: HUBD_REFRESH_DEVDB failed\n");
                goto bailout;
        }

bailout:
        if (dev_path) {
                free(dev_path);
        }
        if (serial_no) {
                free(serial_no);
        }

        return (rv);
}


/*
 * Invoked from cfga_private_func() and fill_in_ap_info().
 * Call into USBA and get the current configuration setting for this device,
 */
static cfga_usb_ret_t
get_config(const char *ap_id, uint_t *config)
{
        size_t          size;
        uint_t          *config_val = NULL;
        cfga_usb_ret_t  rv;

        if ((rv = do_control_ioctl(ap_id, HUBD_GET_CURRENT_CONFIG, 0,
            (void **)&config_val, &size)) != CFGA_USB_OK) {
                DPRINTF("get_config: get current config descr failed\n");
                goto bailout;
        }
        *config = *config_val;

bailout:
        free(config_val);
        return (rv);
}


/*
 * Invoked from cfga_private_func.
 * it does an unconfigure of the device followed by a configure,
 * thus essentially resetting the device.
 */
static cfga_usb_ret_t
reset_device(devctl_hdl_t devctl_hdl, nvlist_t *nvl)
{
        cfga_usb_ret_t  rv;

        DPRINTF("reset_device: \n");

        /*
         * Disconnect and reconfigure the device.
         * Note this forces the new default config to take effect.
         */
        if (devctl_ap_disconnect(devctl_hdl, nvl) != 0) {
                DPRINTF("devctl_ap_unconfigure failed, errno: %d\n", errno);
                rv = CFGA_USB_DEVCTL;
                if (errno == EBUSY) {
                        rv = CFGA_USB_BUSY;     /* Provide more useful msg */
                }

                return (rv);
        }

        if (devctl_ap_configure(devctl_hdl, nvl) != 0) {
                DPRINTF(" devctl_ap_configure failed, errno = %d\n", errno);
                return (CFGA_USB_DEVCTL);
        }

        return (CFGA_USB_OK);
}


/*
 * Called from cfga_list_ext.
 * Fills in the 'misc_info' field in the cfga buffer (displayed with -lv).
 */
static cfga_usb_ret_t
fill_in_ap_info(const char *ap_id, char *info_buf, size_t info_size)
{
        char                    *mfg_str = NULL;        /* iManufacturer */
        char                    *prod_str = NULL;       /* iProduct */
        char                    *serial_str = NULL;     /* iSerialNumber */
        char                    *cfg_descr = NULL;      /* iConfiguration */
        uint_t                  config;                 /* curr cfg index */
        size_t                  size;                   /* tmp stuff */
        boolean_t               flag;           /* wether to print ":" or not */
        boolean_t               free_mfg_str = B_FALSE;
        boolean_t               free_prod_str = B_FALSE;
        boolean_t               free_serial_str = B_FALSE;
        boolean_t               free_cfg_str = B_FALSE;
        cfga_usb_ret_t          rv = CFGA_USB_OK;
        usb_dev_descr_t         *dev_descrp = NULL;     /* device descriptor */

        DPRINTF("fill_in_ap_info:\n");

        if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_DEV, 0,
            (void **)&dev_descrp, &size)) != CFGA_USB_OK) {
                DPRINTF("fill_in_ap_info: get dev descr failed\n");
                return (rv);
        }

        /* iManufacturer */
        mfg_str = USB_UNDEF_STR;
        if (dev_descrp->iManufacturer != 0) {
                if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_STRING,
                    HUBD_MFG_STR, (void **)&mfg_str, &size)) != CFGA_USB_OK) {
                        if (rv == CFGA_USB_ZEROLEN) {
                                rv = CFGA_USB_OK;
                        } else {
                                DPRINTF("get iManufacturer failed\n");
                                goto bailout;
                        }
                }
                free_mfg_str = B_TRUE;
        }

        /* iProduct */
        prod_str = USB_UNDEF_STR;
        if (dev_descrp->iProduct != 0) {
                if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_STRING,
                    HUBD_PRODUCT_STR, (void **)&prod_str,
                    &size)) != CFGA_USB_OK) {
                        if (rv == CFGA_USB_ZEROLEN) {
                                rv = CFGA_USB_OK;
                        } else {
                                DPRINTF("getting iProduct failed\n");
                                goto bailout;
                        }
                }
                free_prod_str = B_TRUE;
        }

        /* iSerialNumber */
        serial_str = USB_UNDEF_STR;
        if (dev_descrp->iSerialNumber != 0) {
                if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_STRING,
                    HUBD_SERIALNO_STR, (void **)&serial_str,
                    &size)) != CFGA_USB_OK) {
                        if (rv == CFGA_USB_ZEROLEN) {
                                rv = CFGA_USB_OK;
                        } else {
                                DPRINTF("getting iSerialNumber failed\n");
                                goto bailout;
                        }
                }
                free_serial_str = B_TRUE;
        }

        /* Current conifguration */
        if ((rv = get_config(ap_id, &config)) != CFGA_USB_OK) {
                DPRINTF("get_config failed\n");
                goto bailout;
        }

        /* Configuration string descriptor */
        cfg_descr = USB_NO_CFG_STR;
        if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_STRING,
            HUBD_CFG_DESCR_STR, (void **)&cfg_descr, &size)) != CFGA_USB_OK) {
                if (rv == CFGA_USB_ZEROLEN) {
                        rv = CFGA_USB_OK;
                        flag = B_TRUE;
                } else {
                        DPRINTF("HUBD_CFG_DESCR_STR failed\n");
                        goto bailout;
                }
        }

        /* add ": " to output coz PSARC case says so */
        if ((cfg_descr != (char *)NULL) && rv != CFGA_USB_ZEROLEN) {
                flag = B_TRUE;
                free_cfg_str = B_TRUE;
        } else {
                flag = B_FALSE;
                cfg_descr = USB_NO_CFG_STR;
        }

        /* Dump local buf into passed-in buf. */
        (void) snprintf(info_buf, info_size,
            "Mfg: %s  Product: %s  Serial: %s  NConfigs: %d  Config: %d  %s%s",
            mfg_str, prod_str, serial_str,
            dev_descrp->bNumConfigurations, config,
            (flag == B_TRUE) ? ": " : "", cfg_descr);

bailout:
        if (dev_descrp) {
                free(dev_descrp);
        }

        if ((free_mfg_str == B_TRUE) && mfg_str) {
                free(mfg_str);
        }

        if ((free_prod_str == B_TRUE) && prod_str) {
                free(prod_str);
        }

        if ((free_serial_str == B_TRUE) && serial_str) {
                free(serial_str);
        }

        if ((free_cfg_str == B_TRUE) && cfg_descr) {
                free(cfg_descr);
        }

        return (rv);
}


/* ========================================================================== */
/* Entry points */


/*ARGSUSED*/
cfga_err_t
cfga_change_state(
        cfga_cmd_t state_change_cmd,
        const char *ap_id,
        const char *options,
        struct cfga_confirm *confp,
        struct cfga_msg *msgp,
        char **errstring,
        cfga_flags_t flags)
{
        int             ret;
        int             len;
        char            *msg;
        char            *devpath;
        nvlist_t        *nvl = NULL;
        ap_rstate_t     rstate;
        ap_ostate_t     ostate;
        devctl_hdl_t    hdl = NULL;
        cfga_usb_ret_t  rv = CFGA_USB_OK;

        DPRINTF("cfga_change_state:\n");

        if ((rv = verify_params(ap_id, options, errstring)) != CFGA_USB_OK) {
                (void) cfga_help(msgp, options, flags);
                goto bailout;
        }

        /*
         * All subcommands which can change state of device require
         * root privileges.
         */
        if (geteuid() != 0) {
                rv = CFGA_USB_PRIV;
                goto bailout;
        }

        if ((rv = setup_for_devctl_cmd(ap_id, &hdl, &nvl, 0)) !=
            CFGA_USB_OK) {
                goto bailout;
        }

        switch (state_change_cmd) {
        case CFGA_CMD_CONFIGURE:
                if ((rv = device_configured(hdl, nvl, &rstate)) !=
                    CFGA_USB_NOT_CONFIGURED) {
                        goto bailout;
                }

                if (rstate == AP_RSTATE_EMPTY) {
                        goto bailout;
                }
                rv = CFGA_USB_OK;       /* Other statuses don't matter */

                if (devctl_ap_configure(hdl, nvl) != 0) {
                        DPRINTF("cfga_change_state: devctl_ap_configure "
                            "failed.  errno: %d\n", errno);
                        rv = CFGA_USB_DEVCTL;
                }

                devpath = usb_get_devicepath(ap_id);
                if (devpath == NULL) {
                        int i;
                        /*
                         * try for some time as USB hotplug thread
                         * takes a while to create the path
                         * and then eventually give up
                         */
                        for (i = 0; i < 12 && (devpath == NULL); i++) {
                                (void) sleep(6);
                                devpath = usb_get_devicepath(ap_id);
                        }

                        if (devpath == NULL) {
                                DPRINTF("cfga_change_state: get device "
                                    "path failed i = %d\n", i);
                                rv = CFGA_USB_DEVCTL;
                                break;
                        }
                }
                S_FREE(devpath);
                break;
        case CFGA_CMD_UNCONFIGURE:
                if ((rv = device_connected(hdl, nvl, &ostate)) !=
                    CFGA_USB_ALREADY_CONNECTED) {
                        goto bailout;
                }

                /* check if it is already unconfigured */
                if ((rv = device_configured(hdl, nvl, &rstate)) ==
                    CFGA_USB_NOT_CONFIGURED) {
                        goto bailout;
                }
                rv = CFGA_USB_OK;       /* Other statuses don't matter */

                len = strlen(USB_CONFIRM_0) + strlen(USB_CONFIRM_1) +
                    strlen("Unconfigure") + strlen(ap_id);
                if ((msg = (char *)calloc(len + 3, 1)) != NULL) {
                        (void) snprintf(msg, len + 3, "Unconfigure %s%s\n%s",
                            USB_CONFIRM_0, ap_id, USB_CONFIRM_1);
                }
                if (!usb_confirm(confp, msg)) {
                        free(msg);
                        cleanup_after_devctl_cmd(hdl, nvl);
                        return (CFGA_NACK);
                }
                free(msg);

                devpath = usb_get_devicepath(ap_id);
                if (devpath == NULL) {
                        DPRINTF("cfga_change_state: get device path failed\n");
                        rv = CFGA_USB_DEVCTL;
                        break;
                }

                if ((rv = usb_rcm_offline(ap_id, errstring, devpath, flags)) !=
                    CFGA_USB_OK) {
                        break;
                }

                ret = devctl_ap_unconfigure(hdl, nvl);
                if (ret != 0) {
                        DPRINTF("cfga_change_state: devctl_ap_unconfigure "
                            "failed with errno: %d\n", errno);
                        rv = CFGA_USB_DEVCTL;
                        if (errno == EBUSY) {
                                rv = CFGA_USB_BUSY;
                        }
                        (void) usb_rcm_online(ap_id, errstring, devpath, flags);
                } else {
                        (void) usb_rcm_remove(ap_id, errstring, devpath, flags);
                }
                S_FREE(devpath);
                break;
        case CFGA_CMD_DISCONNECT:
                if ((rv = device_connected(hdl, nvl, &ostate)) !=
                    CFGA_USB_ALREADY_CONNECTED) {
                        /*
                         * special case handling for
                         * SLM based cfgadm disconnects
                         */
                        if (ostate == AP_OSTATE_UNCONFIGURED)
                                goto bailout;
                }
                rv = CFGA_USB_OK;       /* Other statuses don't matter */

                len = strlen(USB_CONFIRM_0) + strlen(USB_CONFIRM_1) +
                    strlen("Disconnect") + strlen(ap_id);
                if ((msg = (char *)calloc(len + 3, 1)) != NULL) {
                        (void) snprintf(msg, len + 3, "Disconnect %s%s\n%s",
                            USB_CONFIRM_0, ap_id, USB_CONFIRM_1);
                }
                if (!usb_confirm(confp, msg)) {
                        free(msg);
                        cleanup_after_devctl_cmd(hdl, nvl);
                        return (CFGA_NACK);
                }
                free(msg);

                devpath = usb_get_devicepath(ap_id);
                if (devpath == NULL) {
                        DPRINTF("cfga_change_state: get device path failed\n");
                        rv = CFGA_USB_DEVCTL;
                        break;
                }

                /* only call rcm_offline iff the state was CONFIGURED */
                if (ostate == AP_OSTATE_CONFIGURED) {
                        if ((rv = usb_rcm_offline(ap_id, errstring,
                            devpath, flags)) != CFGA_USB_OK) {
                                break;
                        }
                }

                ret = devctl_ap_disconnect(hdl, nvl);
                if (ret != 0) {
                        DPRINTF("cfga_change_state: devctl_ap_disconnect "
                            "failed with errno: %d\n", errno);
                        rv = CFGA_USB_DEVCTL;
                        if (errno == EBUSY) {
                                rv = CFGA_USB_BUSY;
                        }
                        if (ostate == AP_OSTATE_CONFIGURED) {
                                (void) usb_rcm_online(ap_id, errstring,
                                    devpath, flags);
                        }
                } else {
                        if (ostate == AP_OSTATE_CONFIGURED) {
                                (void) usb_rcm_remove(ap_id, errstring,
                                    devpath, flags);
                        }
                }
                S_FREE(devpath);
                break;
        case CFGA_CMD_CONNECT:
        case CFGA_CMD_LOAD:
        case CFGA_CMD_UNLOAD:
                (void) cfga_help(msgp, options, flags);
                rv = CFGA_USB_OPNOTSUPP;
                break;
        case CFGA_CMD_NONE:
        default:
                (void) cfga_help(msgp, options, flags);
                rv = CFGA_USB_INTERNAL_ERROR;
        }

bailout:
        cleanup_after_devctl_cmd(hdl, nvl);

        return (usb_err_msg(errstring, rv, ap_id, errno));
}


/*ARGSUSED*/
cfga_err_t
cfga_private_func(
        const char *func,
        const char *ap_id,
        const char *options,
        struct cfga_confirm *confp,
        struct cfga_msg *msgp,
        char **errstring,
        cfga_flags_t flags)
{
        int                     len;
        char                    *msg;
        nvlist_t                *list = NULL;
        ap_ostate_t             ostate;
        devctl_hdl_t            hdl = NULL;
        cfga_usb_ret_t          rv;
        usb_dev_descr_t         *dev_descrp = NULL;
        char                    *driver = NULL;

        DPRINTF("cfga_private_func:\n");

        if ((rv = verify_params(ap_id, NULL, errstring)) != CFGA_USB_OK) {
                (void) cfga_help(msgp, options, flags);
                return (usb_err_msg(errstring, rv, ap_id, errno));
        }

        /*
         * All subcommands which can change state of device require
         * root privileges.
         */
        if (geteuid() != 0) {
                rv = CFGA_USB_PRIV;
                goto bailout;
        }

        if (func == NULL) {
                rv = CFGA_USB_INTERNAL_ERROR;
                goto bailout;
        }

        if ((rv = setup_for_devctl_cmd(ap_id, &hdl, &list, 0)) !=
            CFGA_USB_OK) {
                goto bailout;
        }

        if ((rv = device_connected(hdl, list, &ostate)) !=
            CFGA_USB_ALREADY_CONNECTED) {
                goto bailout;
        }
        rv = CFGA_USB_OK;

        if (strcmp(func, RESET_DEVICE) == 0) {  /* usb_reset? */
                len = strlen(USB_CONFIRM_0) + strlen(USB_CONFIRM_1) +
                    strlen("Reset") + strlen(ap_id);
                if ((msg = (char *)calloc(len + 3, 1)) != NULL) {
                        (void) snprintf(msg, len + 3, "Reset %s%s\n%s",
                            USB_CONFIRM_0, ap_id, USB_CONFIRM_1);
                } else {
                        cleanup_after_devctl_cmd(hdl, list);
                        return (CFGA_NACK);
                }

                if (!usb_confirm(confp, msg)) {
                        cleanup_after_devctl_cmd(hdl, list);
                        return (CFGA_NACK);
                }

                if ((rv = reset_device(hdl, list)) != CFGA_USB_OK) {
                        goto bailout;
                }
        } else if (strncmp(func, USB_CONFIG, sizeof (USB_CONFIG)) == 0) {
                uint_t  config = 0;
                uint_t  actual_config;
                size_t  size;
                char    *subopts, *value;
                uint_t  cfg_opt_flag = B_FALSE;

                /* these are the only valid options */
                char *cfg_opts[] = {
                        "config",       /* 0 */
                        "drv",          /* 1 */
                        NULL
                };

                /* return error if no options are specified */
                subopts = (char *)options;
                if (subopts == (char *)NULL) {
                        DPRINTF("cfga_private_func: no options\n");
                        rv = CFGA_USB_OPNOTSUPP;
                        (void) cfga_help(msgp, options, flags);
                        goto bailout;
                }

                /* parse options specified */
                while (*subopts != '\0') {
                        switch (getsubopt(&subopts, cfg_opts, &value)) {
                        case 0: /* config */
                                if (value == NULL) {
                                        rv = CFGA_USB_OPNOTSUPP;
                                        (void) cfga_help(msgp,
                                            options, flags);
                                        goto bailout;
                                } else {
                                        errno = 0;
                                        config = strtol(value,
                                            (char **)NULL, 10);
                                        if (errno) {
                                                DPRINTF(
                                                    "config conversion"
                                                    "failed\n");
                                                rv =
                                                    CFGA_USB_CONFIG_INVAL;
                                                goto bailout;
                                        }
                                }
                                cfg_opt_flag = B_TRUE;
                                break;

                        case 1: /* drv */
                                if (value == NULL) {
                                        rv = CFGA_USB_OPNOTSUPP;
                                        (void) cfga_help(msgp,
                                            options, flags);
                                        goto bailout;
                                } else {
                                        S_FREE(driver);
                                        driver = strdup(value);
                                        if (driver == NULL) {
                                                rv =
                                                    CFGA_USB_INTERNAL_ERROR;
                                                goto bailout;
                                        }
                                }
                                break;

                        default:
                                rv = CFGA_USB_OPNOTSUPP;
                                (void) cfga_help(msgp, options, flags);
                                goto bailout;
                        }
                }

                /* config is mandatory */
                if (cfg_opt_flag != B_TRUE) {
                        rv = CFGA_USB_OPNOTSUPP;
                        (void) cfga_help(msgp, options, flags);
                        goto bailout;
                }
                DPRINTF("config = %x\n", config);

                len = strlen(USB_CONFIRM_0) + strlen(USB_CONFIRM_1) +
                    strlen("Setting") + strlen(ap_id) +
                    strlen("to USB configuration");
                /* len + 8 to account for config, \n and white space */
                if ((msg = (char *)calloc(len + 8, 1)) != NULL) {
                        (void) snprintf(msg, len + 8,
                            "Setting %s%s\nto USB configuration %d\n%s",
                            USB_CONFIRM_0, ap_id, config, USB_CONFIRM_1);
                } else {
                        rv = CFGA_USB_INTERNAL_ERROR;
                        goto bailout;
                }

                if (!usb_confirm(confp, msg)) {
                        S_FREE(driver);
                        cleanup_after_devctl_cmd(hdl, list);
                        return (CFGA_NACK);
                }

                /*
                 * Check that the option setting selected is in range.
                 */
                if ((rv = do_control_ioctl(ap_id, USB_DESCR_TYPE_DEV, 0,
                    (void **)&dev_descrp, &size)) != CFGA_USB_OK) {
                        DPRINTF("cfga_private_func: get dev descr failed\n");
                        goto bailout;
                }

                if (config > dev_descrp->bNumConfigurations - 1) {
                        DPRINTF("cfga_private_func: config index requested "
                            "(%d) exceeds bNumConfigurations - 1 (%d)\n",
                            config, dev_descrp->bNumConfigurations - 1);
                        rv = CFGA_USB_CONFIG_INVAL;
                        goto bailout;
                }

                /* Pass current setting to set_configuration */
                if ((rv = get_config(ap_id, &actual_config)) != CFGA_USB_OK) {
                        goto bailout;
                }

                /* check if they match - yes, then nothing to do */
                if (actual_config == config) {
                        DPRINTF("cfga_private_func: config index requested "
                            "(%d)  matches the actual config value %d\n",
                            config, actual_config);
                        rv = CFGA_USB_OK;
                        goto bailout;
                }

                /* Save the configuration settings  */
                if ((rv = set_configuration(ap_id, config, driver,
                    dev_descrp, errstring)) != CFGA_USB_OK) {
                        goto bailout;
                }

                /* Reset device to force new config to take effect */
                if ((rv = reset_device(hdl, list)) != CFGA_USB_OK) {
                        goto bailout;
                }

        } else {
                DPRINTF("cfga_private_func: unrecognized command.\n");
                (void) cfga_help(msgp, options, flags);
                errno = EINVAL;

                return (CFGA_INVAL);
        }

bailout:
        S_FREE(dev_descrp);
        S_FREE(driver);
        cleanup_after_devctl_cmd(hdl, list);

        return (usb_err_msg(errstring, rv, ap_id, errno));
}


/*ARGSUSED*/
cfga_err_t
cfga_test(
        const char *ap_id,
        const char *options,
        struct cfga_msg *msgp,
        char **errstring,
        cfga_flags_t flags)
{
        (void) cfga_help(msgp, options, flags);
        return (CFGA_OPNOTSUPP);
}


/*ARGSUSED*/
cfga_err_t
cfga_list_ext(
        const char *ap_id,
        cfga_list_data_t **ap_id_list,
        int *nlistp,
        const char *options,
        const char *listopts,
        char **errstring,
        cfga_flags_t flags)
{
        int                     l_errno;
        char                    *ap_id_log = NULL;
        size_t                  size;
        nvlist_t                *user_nvlist = NULL;
        devctl_hdl_t            devctl_hdl = NULL;
        cfga_usb_ret_t          rv = CFGA_USB_OK;
        devctl_ap_state_t       devctl_ap_state;

        DPRINTF("cfga_list_ext:\n");

        if ((rv = verify_params(ap_id, options, errstring)) != CFGA_USB_OK) {
                goto bailout;
        }

        if (ap_id_list == NULL || nlistp == NULL) {
                DPRINTF("cfga_list_ext: list = NULL or nlistp = NULL\n");
                rv = CFGA_USB_INTERNAL_ERROR;
                goto bailout;
        }

        /* Get ap status */
        if ((rv = setup_for_devctl_cmd(ap_id, &devctl_hdl, &user_nvlist,
            DC_RDONLY)) != CFGA_USB_OK) {
                goto bailout;
        }

        if (devctl_ap_getstate(devctl_hdl, user_nvlist, &devctl_ap_state) ==
            -1) {
                DPRINTF("cfga_list_ext: devctl_ap_getstate failed. errno: %d\n",
                    errno);
                cleanup_after_devctl_cmd(devctl_hdl, user_nvlist);
                rv = CFGA_USB_DEVCTL;
                goto bailout;
        }
        cleanup_after_devctl_cmd(devctl_hdl, user_nvlist);

        /*
         * Create cfga_list_data_t struct.
         */
        if ((*ap_id_list =
            (cfga_list_data_t *)malloc(sizeof (**ap_id_list))) == NULL) {
                DPRINTF("cfga_list_ext: malloc for cfga_list_data_t failed. "
                    "errno: %d\n", errno);
                rv = CFGA_USB_ALLOC_FAIL;
                goto bailout;
        }
        *nlistp = 1;


        /*
         * Rest of the code fills in the cfga_list_data_t struct.
         */

        /* Get /dev/cfg path to corresponding to the physical ap_id */
        /* Remember ap_id_log must be freed */
        rv = (cfga_usb_ret_t)physpath_to_devlink(CFGA_DEV_DIR, (char *)ap_id,
            &ap_id_log, &l_errno, MATCH_MINOR_NAME);
        if (rv != 0) {
                rv = CFGA_USB_DEVLINK;
                goto bailout;
        }
        assert(ap_id_log != NULL);

        /* Get logical ap-id corresponding to the physical */
        if (strstr(ap_id_log, CFGA_DEV_DIR) == NULL) {
                DPRINTF("cfga_list_ext: devlink doesn't contain /dev/cfg\n");
                rv = CFGA_USB_DEVLINK;
                goto bailout;
        }
        (void) strlcpy((*ap_id_list)->ap_log_id,
            /* Strip off /dev/cfg/ */ ap_id_log + strlen(CFGA_DEV_DIR)+ 1,
            sizeof ((*ap_id_list)->ap_log_id));
        free(ap_id_log);
        ap_id_log = NULL;

        (void) strlcpy((*ap_id_list)->ap_phys_id, ap_id,
            sizeof ((*ap_id_list)->ap_phys_id));

        switch (devctl_ap_state.ap_rstate) {
                case AP_RSTATE_EMPTY:
                        (*ap_id_list)->ap_r_state = CFGA_STAT_EMPTY;
                        break;
                case AP_RSTATE_DISCONNECTED:
                        (*ap_id_list)->ap_r_state = CFGA_STAT_DISCONNECTED;
                        break;
                case AP_RSTATE_CONNECTED:
                        (*ap_id_list)->ap_r_state = CFGA_STAT_CONNECTED;
                        break;
                default:
                        rv = CFGA_USB_STATE;
                        goto bailout;
        }

        switch (devctl_ap_state.ap_ostate) {
                case AP_OSTATE_CONFIGURED:
                        (*ap_id_list)->ap_o_state = CFGA_STAT_CONFIGURED;
                        break;
                case AP_OSTATE_UNCONFIGURED:
                        (*ap_id_list)->ap_o_state = CFGA_STAT_UNCONFIGURED;
                        break;
                default:
                        rv = CFGA_USB_STATE;
                        goto bailout;
        }

        switch (devctl_ap_state.ap_condition) {
                case AP_COND_OK:
                        (*ap_id_list)->ap_cond = CFGA_COND_OK;
                        break;
                case AP_COND_FAILING:
                        (*ap_id_list)->ap_cond = CFGA_COND_FAILING;
                        break;
                case AP_COND_FAILED:
                        (*ap_id_list)->ap_cond = CFGA_COND_FAILED;
                        break;
                case AP_COND_UNUSABLE:
                        (*ap_id_list)->ap_cond = CFGA_COND_UNUSABLE;
                        break;
                case AP_COND_UNKNOWN:
                        (*ap_id_list)->ap_cond = CFGA_COND_UNKNOWN;
                        break;
                default:
                        rv = CFGA_USB_STATE;
                        goto bailout;
        }

        (*ap_id_list)->ap_class[0] = '\0';      /* Filled by libcfgadm */
        (*ap_id_list)->ap_busy = devctl_ap_state.ap_in_transition;
        (*ap_id_list)->ap_status_time = devctl_ap_state.ap_last_change;
        (*ap_id_list)->ap_info[0] = '\0';

        if ((*ap_id_list)->ap_r_state == CFGA_STAT_CONNECTED) {
                char *str_p;
                size_t  str_len;

                /* Fill in the info for the -v option display.  */
                if ((rv = fill_in_ap_info(ap_id, (*ap_id_list)->ap_info,
                    sizeof ((*ap_id_list)->ap_info))) != CFGA_USB_OK) {
                        DPRINTF("cfga_list_ext: fill_in_ap_info failed\n");
                        goto bailout;
                }

                /* Fill in ap_type */
                if ((rv = do_control_ioctl(ap_id, HUBD_GET_CFGADM_NAME, 0,
                    (void **)&str_p, &size)) != CFGA_USB_OK) {
                        DPRINTF("cfga_list_ext: do_control_ioctl failed\n");
                        goto bailout;
                }

                (void) strcpy((*ap_id_list)->ap_type, "usb-");
                str_len = strlen((*ap_id_list)->ap_type);

                /*
                 * NOTE: In the cfgadm display the "Type" column is only 12
                 * chars long. Most USB devices can be displayed here with a
                 * "usb-" prefix. Only USB keyboard cannot be displayed in
                 * its entirety as "usb-keybaord" is 13 chars in length.
                 * It will show up as "usb-kbd".
                 */
                if (strncasecmp(str_p, "keyboard", 8) != 0) {
                        (void) strlcpy((*ap_id_list)->ap_type + str_len, str_p,
                            sizeof ((*ap_id_list)->ap_type) - str_len);
                } else {
                        (void) strlcpy((*ap_id_list)->ap_type + str_len, "kbd",
                            sizeof ((*ap_id_list)->ap_type) - str_len);
                }

                free(str_p);
        } else {
                (void) strcpy((*ap_id_list)->ap_type,
                    USB_CFGADM_DEFAULT_AP_TYPE);
        }

        return (usb_err_msg(errstring, rv, ap_id, errno));
bailout:
        if (*ap_id_list != NULL) {
                free(*ap_id_list);
        }
        if (ap_id_log != NULL) {
                free(ap_id_log);
        }

        return (usb_err_msg(errstring, rv, ap_id, errno));
}


/*
 * This routine accepts a variable number of message IDs and constructs
 * a corresponding error string which is printed via the message print routine
 * argument.
 */
static void
cfga_msg(struct cfga_msg *msgp, const char *str)
{
        int len;
        char *q;

        if (msgp == NULL || msgp->message_routine == NULL) {
                DPRINTF("cfga_msg: msg\n");
                return;
        }

        if ((len = strlen(str)) == 0) {
                DPRINTF("cfga_msg: null str\n");
                return;
        }

        if ((q = (char *)calloc(len + 1, 1)) == NULL) {
                DPRINTF("cfga_msg: null q\n");
                return;
        }

        (void) strcpy(q, str);
        (*msgp->message_routine)(msgp->appdata_ptr, q);

        free(q);
}


/* ARGSUSED */
cfga_err_t
cfga_help(struct cfga_msg *msgp, const char *options, cfga_flags_t flags)
{
        DPRINTF("cfga_help:\n");
        if (options) {
                cfga_msg(msgp, dgettext(TEXT_DOMAIN, usb_help[HELP_UNKNOWN]));
                cfga_msg(msgp, options);
        }

        cfga_msg(msgp, dgettext(TEXT_DOMAIN, usb_help[HELP_HEADER]));
        cfga_msg(msgp, usb_help[HELP_CONFIG]);
        cfga_msg(msgp, usb_help[HELP_RESET_SLOT]);
        cfga_msg(msgp, usb_help[HELP_CONFIG_SLOT]);

        return (CFGA_OK);
}


static int
usb_confirm(struct cfga_confirm *confp, char *msg)
{
        int rval;

        if (confp == NULL || confp->confirm == NULL) {
                return (0);
        }

        rval = (*confp->confirm)(confp->appdata_ptr, msg);
        DPRINTF("usb_confirm: %d\n", rval);

        return (rval);
}


static char *
usb_get_devicepath(const char *ap_id)
{
        char            *devpath = NULL;
        size_t          size;
        cfga_usb_ret_t  rv;

        rv = do_control_ioctl(ap_id, HUBD_GET_DEVICE_PATH, 0,
            (void **)&devpath, &size);

        if (rv == CFGA_USB_OK) {
                DPRINTF("usb_get_devicepath: get device path ioctl ok\n");
                return (devpath);
        } else {
                DPRINTF("usb_get_devicepath: get device path ioctl failed\n");
                return (NULL);
        }
}