view usr/src/lib/cfgadm_plugins/scsi/common/cfga_list.c @ 10696:cd0f390dd9e2

PSARC 2008/672 thebe SAS/SATA driver PSARC 2008/755 ddi_ssoft_state(9F) and ddi_isoft_state(9F) PSARC 2008/764 Cfgadm SCSI-Plugin MPxIO Support PSARC 2009/125 scsi_device property interfaces 6726110 pmcs driver (driver for thebe) 6726867 SCSAv3
author dh142964 <David.Hollister@Sun.COM>
date Wed, 30 Sep 2009 13:40:27 -0600
parents 3564e28dd582
children
line wrap: on
line source

/*
 * 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);
}