root/usr/src/lib/cfgadm_plugins/scsi/common/cfga_list.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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include "cfga_scsi.h"

/* Structure for walking the tree */
typedef struct {
        apid_t          *apidp;
        char            *hba_logp;
        ldata_list_t    *listp;
        scfga_cmd_t     cmd;
        cfga_stat_t     chld_config;
        cfga_stat_t     hba_rstate;
        scfga_ret_t     ret;
        int             l_errno;
} scfga_list_t;

typedef struct {
        uint_t itype;
        const char *ntype;
        const char *name;
        const char *pathname;
} scfga_devtype_t;

/* The TYPE field is parseable and should not contain spaces */
#define SCFGA_BUS_TYPE          "scsi-bus"

/* Function prototypes */
static scfga_ret_t postprocess_list_data(const ldata_list_t *listp,
    scfga_cmd_t cmd, cfga_stat_t chld_config, int *np);
static int stat_dev(di_node_t node, void *arg);
static scfga_ret_t do_stat_bus(scfga_list_t *lap, int limited_bus_stat);
static int get_bus_state(di_node_t node, void *arg);

static scfga_ret_t do_stat_dev(const di_node_t node, const char *nodepath,
    scfga_list_t *lap, int limited_dev_stat);
static cfga_stat_t bus_devinfo_to_recep_state(uint_t bus_di_state);
static cfga_stat_t dev_devinfo_to_occupant_state(uint_t dev_di_state);
static char *get_device_type(di_node_t, dyncomp_t);
static void get_hw_info(di_node_t node, cfga_list_data_t *clp, dyncomp_t type);
static scfga_ret_t create_pathinfo_ldata(di_path_t pi_node, scfga_list_t *lap,
    int *l_errnop);


static scfga_devtype_t device_list[] = {
        { DTYPE_DIRECT,     DDI_NT_BLOCK_CHAN,  "disk",         "disk-path"},
        { DTYPE_DIRECT,     DDI_NT_BLOCK,       "disk",         "disk-path"},
        { DTYPE_DIRECT,     DDI_NT_BLOCK_WWN,   "disk",         "disk-path"},
        { DTYPE_DIRECT,     DDI_NT_BLOCK_FABRIC,    "disk",     "disk-path"},
        { DTYPE_DIRECT,     DDI_NT_BLOCK_SAS,   "disk",         "disk-path"},
        { DTYPE_SEQUENTIAL, DDI_NT_TAPE,        "tape",         "tape-path"},
        { DTYPE_PRINTER,    NULL,               "printer",      "printer-path"},
        { DTYPE_PROCESSOR,  NULL,               "processor",    "PRCS-path"},
        { DTYPE_WORM,       NULL,               "WORM",         "WORM-path"},
        { DTYPE_RODIRECT,   DDI_NT_CD_CHAN,     "CD-ROM",       "CD-ROM-path"},
        { DTYPE_RODIRECT,   DDI_NT_CD,          "CD-ROM",       "CD-ROM-path"},
        { DTYPE_SCANNER,    NULL,               "scanner",      "scanner-path"},
        { DTYPE_OPTICAL,    NULL,               "optical",      "optical-path"},
        { DTYPE_CHANGER,    NULL,               "med-changer",  "MEDCHGR-path"},
        { DTYPE_COMM,       NULL,               "comm-device",  "COMDEV-path"},
        { DTYPE_ARRAY_CTRL, NULL,               "array-ctrl",   "ARRCTRL-path"},
        { DTYPE_ESI,        NULL,               "ESI",          "ESI-path"}
};

#define N_DEVICE_TYPES  (sizeof (device_list) / sizeof (device_list[0]))

scfga_ret_t
do_list(
        apid_t *apidp,
        scfga_cmd_t cmd,
        ldata_list_t **llpp,
        int *nelemp,
        char **errstring)
{
        int n = -1, l_errno = 0, limited_bus_stat;
        walkarg_t u;
        scfga_list_t larg = {NULL};
        scfga_ret_t ret;
        int init_flag;

        assert(apidp->hba_phys != NULL && apidp->path != NULL);

        if (*llpp != NULL || *nelemp != 0) {
                return (SCFGA_ERR);
        }

        /* Create the HBA logid (also base component of logical ap_id) */
        ret = make_hba_logid(apidp->hba_phys, &larg.hba_logp, &l_errno);
        if (ret != SCFGA_OK) {
                cfga_err(errstring, l_errno, ERR_LIST, 0);
                return (SCFGA_ERR);
        }

        assert(larg.hba_logp != NULL);

        larg.cmd = cmd;
        larg.apidp = apidp;
        larg.hba_rstate = CFGA_STAT_NONE;


        /*
         * For all list commands, the bus  and 1 or more devices
         * needs to be stat'ed
         */

        /*
         * By default we use DINFOCACHE to get a "full" snapshot
         * This much faster than DINFOFORCE which actually
         * attaches devices. DINFOFORCE used only if caller
         * explicitly requests it via a private option.
         */
        init_flag = (apidp->flags & FLAG_USE_DIFORCE) ? DINFOFORCE : DINFOCACHE;
        limited_bus_stat = 0;

        switch (larg.cmd) {
                case SCFGA_STAT_DEV:
                        limited_bus_stat = 1; /* We need only bus state */
                        /*FALLTHRU*/
                case SCFGA_STAT_ALL:
                        break;
                case SCFGA_STAT_BUS:
                        /* limited_bus_stat = 0 and no DINFOCACHE/DINFOFORCE */
                        init_flag = 0;
                        break;
                default:
                        cfga_err(errstring, EINVAL, ERR_LIST, 0);
                        goto out;
        }

        /*
         * DINFOCACHE implies DINFOCPYALL. DINFOCPYALL shouldn't
         * be ORed with DINFOCACHE, else libdevinfo will return
         * error
         */
        if (init_flag != DINFOCACHE)
                init_flag |= DINFOCPYALL;

        if ((ret = do_stat_bus(&larg, limited_bus_stat)) != SCFGA_OK) {
                cfga_err(errstring, larg.l_errno, ERR_LIST, 0);
                goto out;
        }

#ifdef DEBUG
        if (limited_bus_stat) {
                assert(larg.listp == NULL);
        } else {
                assert(larg.listp != NULL);
        }
#endif

        /* Assume that the bus has no configured children */
        larg.chld_config = CFGA_STAT_UNCONFIGURED;

        /*
         * If stat'ing a specific device, we don't know if it exists yet.
         * If stat'ing a bus or a bus and child devices, we have at least the
         * bus stat data at this point.
         */
        if (larg.cmd == SCFGA_STAT_DEV) {
                larg.ret = SCFGA_APID_NOEXIST;
        } else {
                larg.ret = SCFGA_OK;
        }

        /* we need to stat at least 1 device for all commands */
        if (apidp->dyntype == PATH_APID) {
                /*
                 * When cmd is SCFGA_STAT_DEV and the ap id is pathinfo
                 * related.
                 */
                ret = walk_tree(apidp->hba_phys, &larg, init_flag, NULL,
                    SCFGA_WALK_PATH, &larg.l_errno);
        } else {
                /* we need to stat at least 1 device for all commands */
                u.node_args.flags = DI_WALK_CLDFIRST;
                u.node_args.fcn = stat_dev;

                /*
                 * Subtree is ALWAYS rooted at the HBA (not at the device) as
                 * otherwise deadlock may occur if bus is disconnected.
                 */
                ret = walk_tree(apidp->hba_phys, &larg, init_flag, &u,
                    SCFGA_WALK_NODE, &larg.l_errno);

                /*
                 * Check path info on the following conditions.
                 *
                 * - chld_config is still set to CFGA_STAT_UNCONFIGURED for
                 *   SCFGA_STAT_BUS cmd after walking any child node.
                 * - walking node succeeded for SCFGA_STAT_ALL cmd(Continue on
                 *   stating path info node).
                 * - apid is pathinfo associated and larg.ret is still set to
                 *   SCFGA_APID_NOEXIST for SCFGA_STAT_DEV cmd.
                 */
                if (((cmd == SCFGA_STAT_BUS) &&
                    (larg.chld_config == CFGA_STAT_UNCONFIGURED)) ||
                    ((cmd == SCFGA_STAT_ALL) && (ret == SCFGA_OK))) {
                        ret = walk_tree(apidp->hba_phys, &larg, init_flag, NULL,
                            SCFGA_WALK_PATH, &larg.l_errno);
                }
        }

        if (ret != SCFGA_OK || (ret = larg.ret) != SCFGA_OK) {
                if (ret != SCFGA_APID_NOEXIST) {
                        cfga_err(errstring, larg.l_errno, ERR_LIST, 0);
                }
                goto out;
        }

        assert(larg.listp != NULL);

        n = 0;
        ret = postprocess_list_data(larg.listp, cmd, larg.chld_config, &n);
        if (ret != SCFGA_OK) {
                cfga_err(errstring, 0, ERR_LIST, 0);
                ret = SCFGA_LIB_ERR;
                goto out;
        }

        *nelemp = n;
        *llpp = larg.listp;
        ret = SCFGA_OK;
        /* FALLTHROUGH */
out:
        if (ret != SCFGA_OK) list_free(&larg.listp);
        S_FREE(larg.hba_logp);
        return (ret);
}

static scfga_ret_t
postprocess_list_data(
        const ldata_list_t *listp,
        scfga_cmd_t cmd,
        cfga_stat_t chld_config,
        int *np)
{
        ldata_list_t *tmplp = NULL;
        cfga_list_data_t *hba_ldatap = NULL;
        int i;


        *np = 0;

        if (listp == NULL) {
                return (SCFGA_ERR);
        }

        hba_ldatap = NULL;
        tmplp = (ldata_list_t *)listp;
        for (i = 0; tmplp != NULL; tmplp = tmplp->next) {
                i++;
                if (GET_DYN(tmplp->ldata.ap_phys_id) == NULL) {
                        /* A bus stat data */
                        assert(GET_DYN(tmplp->ldata.ap_log_id) == NULL);
                        hba_ldatap = &tmplp->ldata;
#ifdef DEBUG
                } else {
                        assert(GET_DYN(tmplp->ldata.ap_log_id) != NULL);
#endif
                }
        }

        switch (cmd) {
        case SCFGA_STAT_DEV:
                if (i != 1 || hba_ldatap != NULL) {
                        return (SCFGA_LIB_ERR);
                }
                break;
        case SCFGA_STAT_BUS:
                if (i != 1 || hba_ldatap == NULL) {
                        return (SCFGA_LIB_ERR);
                }
                break;
        case SCFGA_STAT_ALL:
                if (i < 1 || hba_ldatap == NULL) {
                        return (SCFGA_LIB_ERR);
                }
                break;
        default:
                return (SCFGA_LIB_ERR);
        }

        *np = i;

        /* Fill in the occupant (child) state. */
        if (hba_ldatap != NULL) {
                hba_ldatap->ap_o_state = chld_config;
        }
        return (SCFGA_OK);
}

static int
stat_dev(di_node_t node, void *arg)
{
        scfga_list_t *lap = NULL;
        char *devfsp = NULL, *nodepath = NULL;
        size_t len = 0;
        int limited_dev_stat = 0, match_minor, rv;
        scfga_ret_t ret;

        lap = (scfga_list_t *)arg;

        /* Skip stub nodes */
        if (IS_STUB_NODE(node)) {
                return (DI_WALK_CONTINUE);
        }

        /* Skip partial nodes */
        if (!known_state(node)) {
                return (DI_WALK_CONTINUE);
        }

        devfsp = di_devfs_path(node);
        if (devfsp == NULL) {
                rv = DI_WALK_CONTINUE;
                goto out;
        }

        len = strlen(DEVICES_DIR) + strlen(devfsp) + 1;

        nodepath = calloc(1, len);
        if (nodepath == NULL) {
                lap->l_errno = errno;
                lap->ret = SCFGA_LIB_ERR;
                rv = DI_WALK_TERMINATE;
                goto out;
        }

        (void) snprintf(nodepath, len, "%s%s", DEVICES_DIR, devfsp);

        /* Skip node if it is HBA */
        match_minor = 0;
        if (!dev_cmp(lap->apidp->hba_phys, nodepath, match_minor)) {
                rv = DI_WALK_CONTINUE;
                goto out;
        }

        /* If stat'ing a specific device, is this that device */
        if (lap->cmd == SCFGA_STAT_DEV) {
                assert(lap->apidp->path != NULL);
                if (dev_cmp(lap->apidp->path, nodepath, match_minor)) {
                        rv = DI_WALK_CONTINUE;
                        goto out;
                }
        }

        /*
         * If stat'ing a bus only, we look at device nodes only to get
         * bus configuration status. So a limited stat will suffice.
         */
        if (lap->cmd == SCFGA_STAT_BUS) {
                limited_dev_stat = 1;
        } else {
                limited_dev_stat = 0;
        }

        /*
         * Ignore errors if stat'ing a bus or listing all
         */
        ret = do_stat_dev(node, nodepath, lap, limited_dev_stat);
        if (ret != SCFGA_OK) {
                if (lap->cmd == SCFGA_STAT_DEV) {
                        lap->ret = ret;
                        rv = DI_WALK_TERMINATE;
                } else {
                        rv = DI_WALK_CONTINUE;
                }
                goto out;
        }

        /* Are we done ? */
        rv = DI_WALK_CONTINUE;
        if (lap->cmd == SCFGA_STAT_BUS &&
            lap->chld_config == CFGA_STAT_CONFIGURED) {
                rv = DI_WALK_TERMINATE;
        } else if (lap->cmd == SCFGA_STAT_DEV) {
                /*
                 * If stat'ing a specific device, we are done at this point.
                 */
                lap->ret = SCFGA_OK;
                rv = DI_WALK_TERMINATE;
        }

        /*FALLTHRU*/
out:
        S_FREE(nodepath);
        if (devfsp != NULL) di_devfs_path_free(devfsp);
        return (rv);
}

/*
 * Create list date entry and add to ldata list.
 */
static scfga_ret_t
create_pathinfo_ldata(di_path_t pi_node, scfga_list_t *lap, int *l_errnop)
{
        ldata_list_t    *listp = NULL;
        cfga_list_data_t        *clp;
        di_node_t       client_node = DI_NODE_NIL;
        di_minor_t      minor;
        scfga_ret_t     ret;
        di_path_state_t pi_state;
        char            *dyncomp = NULL, *client_path = NULL;
        char            pathbuf[MAXPATHLEN], *client_devlink = NULL;
        int             match_minor;

        listp = calloc(1, sizeof (ldata_list_t));
        if (listp == NULL) {
                lap->l_errno = errno;
                return (SCFGA_LIB_ERR);
        }
        clp = &listp->ldata;
        ret = make_path_dyncomp(pi_node, &dyncomp, &lap->l_errno);
        if (ret != SCFGA_OK) {
                S_FREE(listp);
                return (ret);
        }

        client_node = di_path_client_node(pi_node);
        if (client_node == DI_NODE_NIL) {
                *l_errnop = errno;
                S_FREE(dyncomp);
                return (SCFGA_LIB_ERR);
        }

        /* Create logical and physical ap_id */
        (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s%s%s",
            lap->hba_logp, DYN_SEP, dyncomp);

        (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s%s%s",
            lap->apidp->hba_phys, DYN_SEP, dyncomp);

        S_FREE(dyncomp);

        /* ap class filled in by libcfgadm */
        clp->ap_class[0] = '\0';
        clp->ap_r_state = lap->hba_rstate;
        /* path info exist so set to configured. */
        clp->ap_o_state = CFGA_STAT_CONFIGURED;

        /* now fill up ap_info field with client dev link and instance #. */
        client_path = di_devfs_path(client_node);
        if (client_path) {
                /* get first minor node. */
                minor = di_minor_next(client_node, DI_MINOR_NIL);
                if (minor == DI_MINOR_NIL) {
                        match_minor = 0;
                        (void) snprintf(pathbuf, MAXPATHLEN, "%s:%s",
                            DEVICES_DIR, client_path);
                } else {
                        match_minor = 1;
                        (void) snprintf(pathbuf, MAXPATHLEN, "%s%s:%s",
                            DEVICES_DIR, client_path, di_minor_name(minor));
                }
                (void) physpath_to_devlink(pathbuf, &client_devlink, l_errnop,
                    match_minor);
                di_devfs_path_free(client_path);
        }

        if (client_devlink) {
                (void) snprintf(clp->ap_info, CFGA_INFO_LEN,
                    "%s: %s", "Client Device", client_devlink);
                S_FREE(client_devlink);
        }

        get_hw_info(client_node, clp, PATH_APID);

        if ((pi_state = di_path_state(pi_node)) == DI_PATH_STATE_OFFLINE) {
                clp->ap_o_state = CFGA_STAT_UNCONFIGURED;
        }

        if (pi_state == DI_PATH_STATE_FAULT) {
                clp->ap_cond = CFGA_COND_FAILED;
        } else {
                clp->ap_cond = CFGA_COND_UNKNOWN;
        }

        /* no way to determine state change */
        clp->ap_busy = 0;
        clp->ap_status_time = (time_t)-1;

        /* Link it in */
        listp->next = lap->listp;
        lap->listp = listp;

        return (SCFGA_OK);
}

/*
 * Routine to stat pathinfo nodes.
 *
 * No pathinfo founds returns a success.
 * When cmd is SCFGA_STAT_DEV, finds a matching pathinfo node and
 * and create ldata if found.
 * When cmd is SCFGA_STAT_ALL, create ldata for each pathinfo node.
 * When cmd is SCFGA_STAT_BUS, checks if any pathinfo exist.
 *
 * Return:
 *  0 for success
 *  -1 for failure.
 */
int
stat_path_info(
        di_node_t       root,
        void            *arg,
        int             *l_errnop)
{
        scfga_list_t    *lap = (scfga_list_t *)arg;
        di_path_t       pi_node;

        if (root == DI_NODE_NIL) {
                return (-1);
        }

        /*
         * when there is no path_info node return SCFGA_OK.
         */
        if (di_path_next_client(root, DI_PATH_NIL) == DI_PATH_NIL) {
                return (0);
        }

        if (lap->cmd == SCFGA_STAT_BUS) {
                lap->chld_config = CFGA_STAT_CONFIGURED;
                return (0);
        } else if (lap->cmd == SCFGA_STAT_DEV) {
                assert(lap->apidp->dyntype == PATH_APID);
                for (pi_node = di_path_next_client(root, DI_PATH_NIL); pi_node;
                    pi_node = di_path_next_client(root, pi_node)) {
                        /*
                         * NOTE: apidt_create() validated pathinfo apid so
                         * the apid should have a valid format.
                         */

                        /* check the length first. */
                        if (strlen(di_path_bus_addr(pi_node)) !=
                            strlen(lap->apidp->dyncomp)) {
                                continue;
                        }

                        /* check for full match. */
                        if (strcmp(di_path_bus_addr(pi_node),
                            lap->apidp->dyncomp)) {
                                continue;
                        }

                        /* found match, record information */
                        if (create_pathinfo_ldata(pi_node, lap,
                            l_errnop) == SCFGA_OK) {
                                lap->ret = SCFGA_OK;
                                return (0);
                        } else {
                                return (-1);
                        }
                }
        } else { /* cmd = STAT_ALL */
                /* set child config to configured */
                lap->chld_config = CFGA_STAT_CONFIGURED;
                for (pi_node = di_path_next_client(root, DI_PATH_NIL); pi_node;
                    pi_node = di_path_next_client(root, pi_node)) {
                        /* continue on even if there is an error on one path. */
                        (void) create_pathinfo_ldata(pi_node, lap, l_errnop);
                }
        }

        lap->ret = SCFGA_OK;
        return (0);

}

struct bus_state {
        int     b_state;
        int     b_retired;
        char    iconnect_type[16];
};

static scfga_ret_t
do_stat_bus(scfga_list_t *lap, int limited_bus_stat)
{
        cfga_list_data_t *clp = NULL;
        ldata_list_t *listp = NULL;
        int l_errno = 0;
        struct bus_state bstate = {0};
        walkarg_t u;
        scfga_ret_t ret;
        int i;
        char itypelower[MAXNAMELEN];

        assert(lap->hba_logp != NULL);

        /* Get bus state */
        u.node_args.flags = 0;
        u.node_args.fcn = get_bus_state;

        ret = walk_tree(lap->apidp->hba_phys, &bstate, DINFOPROP, &u,
            SCFGA_WALK_NODE, &l_errno);
        if (ret == SCFGA_OK) {
                lap->hba_rstate = bus_devinfo_to_recep_state(bstate.b_state);
        } else {
                lap->hba_rstate = CFGA_STAT_NONE;
        }

        if (limited_bus_stat) {
                /* We only want to know bus(receptacle) connect status */
                return (SCFGA_OK);
        }

        listp = calloc(1, sizeof (ldata_list_t));
        if (listp == NULL) {
                lap->l_errno = errno;
                return (SCFGA_LIB_ERR);
        }

        clp = &listp->ldata;

        (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s",
            lap->hba_logp);
        (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s",
            lap->apidp->hba_phys);

        clp->ap_class[0] = '\0';        /* Filled by libcfgadm */
        clp->ap_r_state = lap->hba_rstate;
        clp->ap_o_state = CFGA_STAT_NONE; /* filled in later by the plug-in */
        clp->ap_cond =
            (bstate.b_retired) ? CFGA_COND_FAILED : CFGA_COND_UNKNOWN;
        clp->ap_busy = 0;
        clp->ap_status_time = (time_t)-1;
        clp->ap_info[0] = '\0';

        if (bstate.iconnect_type) {
                /*
                 * For SPI type, keep the existing SCFGA_BUS_TYPE.
                 * For other types, the ap type will be scsi-'interconnct-type'.
                 */
                if (strcmp(bstate.iconnect_type, "SPI") == 0) {
                        (void) snprintf(clp->ap_type, sizeof (clp->ap_type),
                            "%s", SCFGA_BUS_TYPE);
                } else {
                        for (i = 0; i < strlen(bstate.iconnect_type); i++) {
                                itypelower[i] =
                                    tolower(bstate.iconnect_type[i]);
                        }
                        itypelower[i] = '\0';
                        (void) snprintf(clp->ap_type, sizeof (clp->ap_type),
                            "%s-%s", "scsi", itypelower);
                }
        }

        /* Link it in */
        listp->next = lap->listp;
        lap->listp = listp;

        return (SCFGA_OK);
}

static int
get_bus_state(di_node_t node, void *arg)
{
        struct bus_state *bsp = (struct bus_state *)arg;
        char *itype = NULL;

        bsp->b_state = di_state(node);
        bsp->b_retired = di_retired(node);
        (void) di_prop_lookup_strings(DDI_DEV_T_ANY,
            node, "initiator-interconnect-type", &itype);
        if (itype != NULL) {
                (void) strlcpy(bsp->iconnect_type, itype, 16);
        } else {
                bsp->iconnect_type[0] = '\0';
        }

        return (DI_WALK_TERMINATE);
}

static scfga_ret_t
do_stat_dev(
        const di_node_t node,
        const char *nodepath,
        scfga_list_t *lap,
        int limited_dev_stat)
{
        uint_t devinfo_state = 0;
        char *dyncomp = NULL;
        cfga_list_data_t *clp = NULL;
        ldata_list_t *listp = NULL;
        cfga_stat_t ostate;
        scfga_ret_t ret;

        assert(lap->apidp->hba_phys != NULL);
        assert(lap->hba_logp != NULL);

        devinfo_state = di_state(node);
        ostate = dev_devinfo_to_occupant_state(devinfo_state);

        /* If child device is configured, record it */
        if (ostate == CFGA_STAT_CONFIGURED) {
                lap->chld_config = CFGA_STAT_CONFIGURED;
        }

        if (limited_dev_stat) {
                /* We only want to know device config state */
                return (SCFGA_OK);
        }

        listp = calloc(1, sizeof (ldata_list_t));
        if (listp == NULL) {
                lap->l_errno = errno;
                return (SCFGA_LIB_ERR);
        }

        clp = &listp->ldata;

        /* Create the dynamic component */
        ret = make_dyncomp(node, nodepath, &dyncomp, &lap->l_errno);
        if (ret != SCFGA_OK) {
                S_FREE(listp);
                return (ret);
        }

        assert(dyncomp != NULL);

        /* Create logical and physical ap_id */
        (void) snprintf(clp->ap_log_id, sizeof (clp->ap_log_id), "%s%s%s",
            lap->hba_logp, DYN_SEP, dyncomp);

        (void) snprintf(clp->ap_phys_id, sizeof (clp->ap_phys_id), "%s%s%s",
            lap->apidp->hba_phys, DYN_SEP, dyncomp);

        S_FREE(dyncomp);

        clp->ap_class[0] = '\0'; /* Filled in by libcfgadm */
        clp->ap_r_state = lap->hba_rstate;
        clp->ap_o_state = ostate;
        clp->ap_cond = di_retired(node) ? CFGA_COND_FAILED : CFGA_COND_UNKNOWN;
        clp->ap_busy = 0; /* no way to determine state change */
        clp->ap_status_time = (time_t)-1;

        get_hw_info(node, clp, DEV_APID);

        /* Link it in */
        listp->next = lap->listp;
        lap->listp = listp;

        return (SCFGA_OK);
}

/* fill in device type, vid, pid from properties */
static void
get_hw_info(di_node_t node, cfga_list_data_t *clp, dyncomp_t type)
{
        char *cp = NULL;
        char *inq_vid, *inq_pid;
        char client_inst[MAXNAMELEN];

        /*
         * Fill in type information
         */
        cp = (char *)get_device_type(node, type);
        if (cp == NULL) {
                cp = (char *)GET_MSG_STR(ERR_UNAVAILABLE);
        }
        (void) snprintf(clp->ap_type, sizeof (clp->ap_type), "%s", S_STR(cp));

        if (type == DEV_APID) {
                /*
                 * Fill in vendor and product ID.
                 */
                if ((di_prop_lookup_strings(DDI_DEV_T_ANY, node,
                    "inquiry-product-id", &inq_pid) == 1) &&
                    (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
                    "inquiry-vendor-id", &inq_vid) == 1)) {
                        (void) snprintf(clp->ap_info, sizeof (clp->ap_info),
                            "%s %s", inq_vid, inq_pid);
                }
        } else {
                if ((di_driver_name(node) != NULL) &&
                    (di_instance(node) != -1)) {
                        if (clp->ap_info == NULL) {
                                (void) snprintf(client_inst, MAXNAMELEN - 1,
                                    "%s%d", di_driver_name(node),
                                    di_instance(node));
                                (void) snprintf(clp->ap_info, MAXNAMELEN - 1,
                                    "Client Device: %s", client_inst);
                        } else {
                                (void) snprintf(client_inst, MAXNAMELEN - 1,
                                    "(%s%d)", di_driver_name(node),
                                    di_instance(node));
                                (void) strlcat(clp->ap_info, client_inst,
                                    CFGA_INFO_LEN);
                        }
                }

        }
}

/*
 * Get dtype from "inquiry-device-type" property. If not present,
 * derive it from minor node type
 */
static char *
get_device_type(di_node_t node, dyncomp_t type)
{
        char *name = NULL;
        int *inq_dtype;
        int i;

        if (di_prop_find(DDI_DEV_T_ANY, node, "smp-device") != DI_PROP_NIL) {
                return ("smp");
        }

        /* first, derive type based on inquiry property */
        if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "inquiry-device-type",
            &inq_dtype) == 1) {
                int itype = (*inq_dtype) & DTYPE_MASK;

                for (i = 0; i < N_DEVICE_TYPES; i++) {
                        if (device_list[i].itype == DTYPE_UNKNOWN)
                                continue;
                        if (itype == device_list[i].itype) {
                                name = (type == DEV_APID) ?
                                    (char *)device_list[i].name :
                                    (char *)device_list[i].pathname;
                                break;
                        }
                }
        }

        /* if property fails, use minor nodetype */
        if (name == NULL) {
                char *nodetype;
                di_minor_t minor = di_minor_next(node, DI_MINOR_NIL);

                if ((minor != DI_MINOR_NIL) &&
                    ((nodetype = di_minor_nodetype(minor)) != NULL)) {
                        for (i = 0; i < N_DEVICE_TYPES; i++) {
                                if (device_list[i].ntype &&
                                    (strcmp(nodetype, device_list[i].ntype)
                                    == 0)) {
                                        name = (type == DEV_APID) ?
                                            (char *)device_list[i].name :
                                            (char *)device_list[i].pathname;
                                        break;
                                }
                        }
                }
        }

        if (name == NULL)       /* default to unknown */
                name = "unknown";
        return (name);
}

/* Transform linked list into an array */
scfga_ret_t
list_ext_postprocess(
        ldata_list_t            **llpp,
        int                     nelem,
        cfga_list_data_t        **ap_id_list,
        int                     *nlistp,
        char                    **errstring)
{
        cfga_list_data_t *ldatap = NULL;
        ldata_list_t *tmplp = NULL;
        int i = -1;

        *ap_id_list = NULL;
        *nlistp = 0;

        if (*llpp == NULL || nelem < 0) {
                return (SCFGA_LIB_ERR);
        }

        if (nelem == 0) {
                return (SCFGA_APID_NOEXIST);
        }

        ldatap = calloc(nelem, sizeof (cfga_list_data_t));
        if (ldatap == NULL) {
                cfga_err(errstring, errno, ERR_LIST, 0);
                return (SCFGA_LIB_ERR);
        }

        /* Extract the list_data structures from the linked list */
        tmplp = *llpp;
        for (i = 0; i < nelem && tmplp != NULL; i++) {
                ldatap[i] = tmplp->ldata;
                tmplp = tmplp->next;
        }

        if (i < nelem || tmplp != NULL) {
                S_FREE(ldatap);
                return (SCFGA_LIB_ERR);
        }

        *nlistp = nelem;
        *ap_id_list = ldatap;

        return (SCFGA_OK);
}

/*
 * Convert bus state to receptacle state
 */
static cfga_stat_t
bus_devinfo_to_recep_state(uint_t bus_di_state)
{
        if (bus_di_state & (DI_BUS_QUIESCED | DI_BUS_DOWN))
                return (CFGA_STAT_DISCONNECTED);

        return (CFGA_STAT_CONNECTED);
}

/*
 * Convert device state to occupant state
 */
static cfga_stat_t
dev_devinfo_to_occupant_state(uint_t dev_di_state)
{
        if (dev_di_state & (DI_DEVICE_OFFLINE | DI_DEVICE_DOWN))
                return (CFGA_STAT_UNCONFIGURED);

        if (!(dev_di_state & DI_DRIVER_DETACHED))
                return (CFGA_STAT_CONFIGURED);

        return (CFGA_STAT_NONE);
}