/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <libdevinfo.h>
#include <errno.h>
#include <libintl.h>
#define	CFGA_PLUGIN_LIB
#include <config_admin.h>
#include "ap.h"
#include <sys/obpdefs.h>
#include <sys/processor.h>
#include <sys/stat.h>
#include <sys/sbd_ioctl.h>
#include <sys/int_fmtio.h>

static cfga_err_t
ap_getncm(apd_t *a, sbd_comp_type_t type, int *ncm)
{
	sbd_ioctl_arg_t *ctl;
	sbd_getncm_cmd_t *cp;

	if (a->fd == -1 || a->ctl == NULL)
		return (CFGA_LIB_ERROR);

	ctl = (sbd_ioctl_arg_t *)a->ctl;
	ctl->ic_type = type;
	ctl->ic_name[0] = '\0';
	ctl->ic_unit = 0;
	ctl->i_len = 0;
	ctl->i_opts = NULL;

	DBG("ioctl(%d SBD_CMD_GETNCM, 0x%p)\n", a->fd, (void *)ctl);

	if (ioctl(a->fd, SBD_CMD_GETNCM, ctl) == -1) {
		ap_err(a, ERR_CMD_FAIL, CMD_GETNCM);
		return (CFGA_ERROR);
	}

	cp = &ctl->i_cmd.cmd_getncm;

	DBG("ncm(%d)=%d\n", type, cp->g_ncm);

	if (ncm)
		*ncm = cp->g_ncm;

	return (CFGA_OK);
}

cfga_err_t
ap_stat(apd_t *a, int all)
{
	int fd;
	int ncm;
	int select;
	int stsize;
	int oflag;
	sbd_stat_cmd_t *sc;
	sbd_ioctl_arg_t *ctl;
	cfga_err_t rc;
	sbd_stat_t *new_stat;

	rc = CFGA_LIB_ERROR;

	DBG("ap_stat(%s)\n", a->path);

	/* Open the file descriptor if not already open */
	if (a->fd == -1) {
		DBG("open(%s)\n", a->path);
		if (a->statonly != 0)
			oflag = O_RDONLY;
		else
			oflag = O_RDWR;
		if ((fd = open(a->path, oflag, 0)) == -1) {
			ap_err(a, ERR_AP_INVAL);
			return (rc);
		}
		a->fd = fd;
	} else {
		fd = a->fd;
	}

	if (a->ctl == NULL && (a->ctl = calloc(1, sizeof (*ctl))) == NULL) {
		ap_err(a, ERR_CMD_FAIL, CMD_STATUS);
		return (rc);
	}

	if (a->tgt == AP_BOARD) {
		/*
		 * The status target is the board. If we need to
		 * return component data (to support the -a option),
		 * get the number of components on the board.
		 */
		select = 0;
		if (all) {
			cfga_err_t r;
			r = ap_getncm(a, SBD_COMP_NONE, &ncm);
			if (r != CFGA_OK) {
				return (r);
			}
		} else {
			ncm = 0;
		}
	} else {
		select = 1;
		ncm = 1;
	}

	DBG("ncm=%d\n", ncm);

	a->ncm = ncm;

	/*
	 * The status structure contains space for one component;
	 * add the space for the other components if necessary.
	 */
	stsize = sizeof (sbd_stat_t);
	if (ncm > 1)
		stsize += ((ncm - 1) * sizeof (sbd_dev_stat_t));

	if ((new_stat = realloc(a->stat, stsize)) == NULL) {
		ap_err(a, ERR_CMD_FAIL, CMD_STATUS);
		return (rc);
	}

	a->stat = new_stat;


	ctl = (sbd_ioctl_arg_t *)a->ctl;
	ctl->i_len = 0;
	ctl->i_opts = NULL;
	ctl->ic_type = SBD_COMP_NONE;
	if (all)
		ctl->i_flags |= SBD_FLAG_ALLCMP;
	sc = &ctl->i_cmd.cmd_stat;
	sc->s_statp = (caddr_t)a->stat;
	sc->s_nbytes = stsize;

	if (select) {
		/*
		 * The target is a specific component.  Pass its
		 * name and unit number to the driver.  Set its
		 * type to UNKNOWN since the plugin does not know
		 * the type of the component specified by the user.
		 */
		ctl->ic_type = SBD_COMP_UNKNOWN;
		ctl->ic_unit = a->cnum;
		strcpy(ctl->ic_name, a->cname);
	}

	DBG("ioctl(%d SBD_CMD_STATUS, sc=0x%p sz=%d flags=%d",
		fd, (void *)sc->s_statp, sc->s_nbytes, ctl->i_flags);
	if (select)
		DBG(" cname=<%s> cnum=%d", a->cname, a->cnum);
	DBG(")\n");

	if (ioctl(fd, SBD_CMD_STATUS, ctl) == -1) {
		ap_err(a, ERR_CMD_FAIL, CMD_STATUS);
		rc = CFGA_ERROR;
	} else
		rc = CFGA_OK;

	DBG("ap_stat()=%d\n", rc);

	return (rc);
}

/*
 * Convert a component to a target type.
 */
static ap_target_t
ap_cm_tgt(sbd_comp_type_t type)
{
	ap_target_t c;

	switch (type) {
	case SBD_COMP_CPU:
		c = AP_CPU;
		break;
	case SBD_COMP_MEM:
		c = AP_MEM;
		break;
	case SBD_COMP_IO:
		c = AP_IO;
		break;
	case SBD_COMP_CMP:
		c = AP_CMP;
		break;
	default:
		c = AP_NONE;
		break;
	}

	return (c);
}

cfga_err_t
apd_init(apd_t *a, int all)
{
	int i;
	char *cn, *dn;
	sbd_stat_t *st;
	sbd_dev_stat_t *dst;
	cfga_err_t rc;

	/*
	 * Ideally, for board operations (other than status) it is not
	 * necessary to issue the STATUS ioctl.  The call however allows a
	 * final sanity check to ensure that the board number returned
	 * by the driver matches the plugin's notion of the board number
	 * as extracted from the ap_id.  If this check is not desirable,
	 * we can change the code to issue the status call only when
	 * necessary.  Note that for component operations, we need to do
	 * the STATUS in order to figure out the component type and
	 * validate the command/options accordingly. XXX
	 */
	if ((rc = ap_stat(a, all)) != CFGA_OK) {
		ap_err(a, ERR_AP_INVAL);
		return (rc);
	}

	st = (sbd_stat_t *)a->stat;

	/*
	 * Set the component count to the returned stat count.
	 */
	if (a->ncm > st->s_nstat) {

		DBG("ncm=%d nstat=%d (truncated)\n", a->ncm, st->s_nstat);

		a->ncm = st->s_nstat;
	}

	if (a->tgt == AP_BOARD) {

		DBG("tgt=%d\n", a->tgt);

		/*
		 * Initialize the RCM module here so that it can record
		 * the initial state of the capacity information.
		 */
		rc = ap_rcm_init(a);

		return (rc);
	}

	a->tgt = AP_NONE;
	cn = a->cname;

	DBG("cname=<%s> cunit=<%d>\n", a->cname, a->cnum);

	for (dst = st->s_stat, i = 0; i < st->s_nstat; i++, dst++) {

		DBG("ds_name,ds_unit,ds_type=<%s,%d,%d> ",
			dst->ds_name, dst->ds_unit, dst->ds_type);

		if (dst->ds_unit != a->cnum)
			continue;

		/*
		 * Consider the names matched if they are either
		 * both absent or the same. It is conceivable that
		 * a NULL component name be considered valid
		 * by the driver.
		 */
		dn = dst->ds_name;

		if ((dn == NULL && cn == NULL) ||
		    (dn != NULL && cn != NULL && strcmp(dn, cn) == 0)) {
			a->tgt = ap_cm_tgt(dst->ds_type);
			a->cmstat = (void *)dst;

			DBG("found ");

			break;
		}
	}

	DBG("tgt=%d\n", a->tgt);

	if (a->tgt == AP_NONE) {
		ap_err(a, ERR_CM_INVAL, a->cid);
		return (CFGA_INVAL);
	}

	/*
	 * Initialize the RCM module here so that it can record
	 * the initial state of the capacity information.
	 */
	rc = ap_rcm_init(a);

	return (rc);
}

void
apd_free(apd_t *a)
{
	if (a == NULL)
		return;

	ap_rcm_fini(a);

	if (a->fd != -1)
		close(a->fd);

	s_free(a->options);
	s_free(a->path);
	s_free(a->drv);
	s_free(a->target);
	s_free(a->cname);
	s_free(a->ctl);
	s_free(a->stat);

	free(a);
}

apd_t *
apd_alloc(const char *ap_id, cfga_flags_t flags, char **errstring,
	struct cfga_msg *msgp, struct cfga_confirm *confp)
{
	apd_t *a;

	if ((a = calloc(1, sizeof (*a))) == NULL)
		return (NULL);

	if (errstring != NULL)
		*errstring = NULL;

	a->fd = -1;
	a->errstring = errstring;
	a->msgp = msgp;
	a->confp = confp;
	a->class = "sbd";

	if (flags & CFGA_FLAG_LIST_ALL)
		ap_setopt(a, OPT_LIST_ALL);
	if (flags & CFGA_FLAG_FORCE)
		ap_setopt(a, OPT_FORCE);
	if (flags & CFGA_FLAG_VERBOSE)
		ap_setopt(a, OPT_VERBOSE);

	if (ap_id == NULL || ap_parse(a, ap_id) == 0)
		return (a);

	apd_free(a);
	return (NULL);
}

/*
 * The type field is defined to be parsable by cfgadm(1M): It
 * must not contain white space characters. This function
 * converts white space to underscore.
 */

static void
parsable_strncpy(char *op, const char *ip, size_t n)
{
	char c;

	while (n-- > 0) {
		c = *ip++;
		if (isspace(c))
			c = '_';
		*op++ = c;
		if (c == '\0')
			break;
	}
}

void
ap_init(apd_t *a, cfga_list_data_t *ap)
{
	sbd_stat_t *st;

	st = (sbd_stat_t *)a->stat;

	DBG("ap_init bd=%d rs=%d os=%d type=<%s>\n",
		a->bnum, st->s_rstate, st->s_ostate, st->s_type);

	parsable_strncpy(ap->ap_type, st->s_type, sizeof (ap->ap_type));
	ap->ap_r_state = (cfga_stat_t)st->s_rstate;
	ap->ap_o_state = (cfga_stat_t)st->s_ostate;
	ap->ap_cond = (cfga_cond_t)st->s_cond;
	ap->ap_busy = (cfga_busy_t)st->s_busy;
	ap->ap_status_time = st->s_time;
	ap_info(a, ap->ap_info, AP_BOARD);
}

typedef struct {
	int cmd;
	int ioc;
} ap_ioc_t;

static ap_ioc_t
ap_iocs[] =  {
	{CMD_ASSIGN,	  SBD_CMD_ASSIGN	},
	{CMD_POWERON,	  SBD_CMD_POWERON	},
	{CMD_TEST,	  SBD_CMD_TEST		},
	{CMD_CONNECT,	  SBD_CMD_CONNECT	},
	{CMD_CONFIGURE,	  SBD_CMD_CONFIGURE	},
	{CMD_UNCONFIGURE, SBD_CMD_UNCONFIGURE	},
	{CMD_DISCONNECT,  SBD_CMD_DISCONNECT	},
	{CMD_POWEROFF,	  SBD_CMD_POWEROFF	},
	{CMD_STATUS,	  SBD_CMD_STATUS	},
	{CMD_GETNCM,	  SBD_CMD_GETNCM	},
	{CMD_UNASSIGN,	  SBD_CMD_UNASSIGN	},
	{CMD_PASSTHRU,	  SBD_CMD_PASSTHRU	},
	{CMD_NONE,	  0			}
};

static int
ap_ioc(int cmd)
{
	ap_ioc_t *acp;

	DBG("ap_ioc(%d)\n", cmd);

	for (acp = ap_iocs; acp->cmd != CMD_NONE; acp++)
		if (acp->cmd == cmd)
			break;

	DBG("ap_ioc(%d)=0x%x\n", cmd, acp->ioc);

	return (acp->ioc);
}

cfga_err_t
ap_suspend_query(apd_t *a, int cmd, int *check)
{
	int ioc;
	sbd_dev_stat_t *dst;

	/*
	 * See if the a quiesce operation is required for
	 * this command for any of the components.  If the
	 * command does not map to an ioctl, then there is
	 * nothing to do.
	 */
	if ((ioc = ap_ioc(cmd)) == 0)
		return (CFGA_OK);
	else if (a->tgt == AP_BOARD) {
		int i;

		dst = ((sbd_stat_t *)a->stat)->s_stat;

		/*
		 * See if any component requires a
		 * OS suspension for this command.
		 */
		for (i = 0; i < a->ncm; i++, dst++)
			if (SBD_CHECK_SUSPEND(ioc, dst->ds_suspend))
				(*check)++;
	} else {
		dst = (sbd_dev_stat_t *)a->cmstat;
		if (SBD_CHECK_SUSPEND(ioc, dst->ds_suspend))
				(*check)++;
	}

	return (CFGA_OK);
}

cfga_err_t
ap_platopts_check(apd_t *a, int first, int last)
{
	int c;
	uint_t platopts;
	sbd_stat_t *stat;
	ap_opts_t *opts;

	opts = &a->opts;
	stat = (sbd_stat_t *)a->stat;
	platopts = stat->s_platopts;


	/*
	 * If there are no platform options set then there
	 * is no need to check this operation
	 */
	if (opts->platform == NULL)
		return (CFGA_OK);

	/*
	 * Check if any of the steps in the sequence
	 * allows for a platform option
	 */
	for (c = first; c <= last; c++)
		/*
		 * If the platopt is set it means that the platform does not
		 * support options for this cmd
		 */
		if (SBD_CHECK_PLATOPTS(ap_ioc(c), platopts) == 0) {
			return (CFGA_OK);
		}

	ap_err(a, ERR_OPT_INVAL, opts->platform);

	return (CFGA_INVAL);
}

cfga_err_t
ap_ioctl(apd_t *a, int cmd)
{
	int ioc;
	sbd_ioctl_arg_t *ctl;

	if (a->ctl == NULL && (a->ctl = calloc(1, sizeof (*ctl))) == NULL) {
		ap_err(a, ERR_CMD_FAIL, cmd);
		return (CFGA_LIB_ERROR);
	}

	ap_msg(a, MSG_ISSUE, cmd, a->target);

	ctl = (sbd_ioctl_arg_t *)a->ctl;
	ctl->i_flags = 0;
	ctl->i_len = 0;
	ctl->i_opts = NULL;

	if (ap_getopt(a, OPT_FORCE))
		ctl->i_flags |= SBD_FLAG_FORCE;
	if (ap_getopt(a, OPT_SUSPEND_OK))
		ctl->i_flags |= SBD_FLAG_QUIESCE_OKAY;

	if (a->tgt == AP_BOARD)
		ctl->ic_type = SBD_COMP_NONE;
	else {
		ctl->ic_type = SBD_COMP_UNKNOWN;
		ctl->ic_unit = a->cnum;
		strcpy(ctl->ic_name, a->cname);
	}

	if (!(ioc = ap_ioc(cmd))) {
		ap_err(a, ERR_CMD_FAIL, cmd);
		return (CFGA_LIB_ERROR);
	}

	/*
	 * If this is a passthru command, pass all of its
	 * options; otherwise, pass all options after the
	 * platform keyword.
	 */
	if (cmd == CMD_PASSTHRU)
		ctl->i_opts = a->options;
	else {
		/*
		 * Only pass the platform option to the cmds that the platform
		 * has specified as ok
		 */
		sbd_stat_t *stat;

		stat = (sbd_stat_t *)a->stat;
		if (SBD_CHECK_PLATOPTS(ioc, stat->s_platopts) == 0)
			ctl->i_opts = a->opts.platform;
	}

	if (ctl->i_opts != NULL)
		ctl->i_len = strlen(ctl->i_opts) + 1;

	DBG("i_opts=%s\n", ctl->i_opts ? ctl->i_opts : "NULL");
	DBG("i_flags=0x%x\n", ctl->i_flags);

	if (ap_getopt(a, OPT_SIM)) {
		ap_msg(a, MSG_DONE, cmd, a->target);
		return (CFGA_OK);
	}

	if (ioctl(a->fd, ioc, ctl) == -1) {
		ap_err(a, ERR_CMD_FAIL, cmd);
		return (CFGA_ERROR);
	}
	ap_msg(a, MSG_DONE, cmd, a->target);

	return (CFGA_OK);
}

/*
 * Return the error string corresponding to a given error code.
 * String table and error code sets are provided by sbd_etab.  This data
 * structure is automatically generated at compile time from the error
 * code and message text information in sbd_ioctl.h.
 */
static char *
mod_estr(int code)
{
	int i;
	char *s;
	extern sbd_etab_t sbd_etab[];
	extern int sbd_etab_len;

	s = NULL;

	for (i = 0; i < sbd_etab_len; i++) {
		sbd_etab_t *eptr = &sbd_etab[i];

		if ((code >= eptr->t_base) && (code <= eptr->t_bnd)) {
			int index;
			char **t_text;

			/*
			 * Found it. Just extract the string
			 */
			index = code - eptr->t_base;
			t_text = eptr->t_text;
			s = strdup(t_text[index]);
			break;
		}
	}

	if (i == sbd_etab_len) {
		char buf[32];

		snprintf(buf, sizeof (buf), "error %d", code);
		s = strdup(buf);
	}

	return (s);
}

char *
ap_sys_err(apd_t *a, char **rp)
{
	int code;
	char *p;
	char *rsc;

	sbd_ioctl_arg_t *ctl = (sbd_ioctl_arg_t *)a->ctl;

	/*
	 * The driver sets the errno to EIO if it returns
	 * more detailed error info via e_code.  In all
	 * other cases, use standard error text.
	 */
	if (ctl == NULL || errno != EIO) {
		if ((p = strerror(errno)) != NULL)
			p = strdup(p);
		return (p);
	}

	code = ctl->ie_code;
	rsc = ctl->ie_rsc;

	if (code)
		p = mod_estr(code);
	else if ((p = strerror(errno)) != NULL)
		p = strdup(p);

	if (*rsc != '\0' && rp != NULL)
		*rp = strdup(rsc);

	return (p);
}

/*
 * cfgadm -o err=plugin-err,cmd=name,code=ecode -x errtest ap_id.
 */
cfga_err_t
ap_test_err(apd_t *a, const char *options)
{
	int err;
	int cmd;
	ap_opts_t *opts;
	sbd_ioctl_arg_t ctl;

	opts = &a->opts;
	err = opts->err;
	cmd = CMD_DISCONNECT;

	DBG("ap_test_err(%d %d)\n", opts->code, opts->err);

	switch (err) {
	case ERR_CMD_INVAL:
		ap_err(a, err, ap_cmd_name(cmd));
		break;
	case ERR_CMD_NOTSUPP:
		ap_err(a, err, cmd);
		break;
	case ERR_CMD_FAIL:
		errno = EIO;
		ctl.i_err.e_code = opts->code;
		*ctl.i_err.e_rsc = '\0';
		a->ctl = &ctl;
		ap_err(a, err, cmd);
		a->ctl = NULL;
		break;
	case ERR_OPT_INVAL:
		ap_err(a, err, options);
		break;
	case ERR_OPT_NOVAL:
		ap_err(a, err, options);
		break;
	case ERR_AP_INVAL:
		ap_err(a, err);
		break;
	case ERR_CM_INVAL:
		ap_err(a, err, a->cid);
		break;
	case ERR_TRANS_INVAL:
		ap_err(a, ERR_TRANS_INVAL, cmd);
		break;
	}

	return (CFGA_LIB_ERROR);
}

static char *
ap_help_topics[] = {
	"\nSbd specific commands/options:\n\n",
	"\tcfgadm [-o parsable] -l ap_id\n",
	"\tcfgadm [-o unassign|nopoweroff] -c disconnect ap_id\n",
	"\tcfgadm -t ap_id\n",
	"\tcfgadm -x assign ap_id\n",
	"\tcfgadm -x unassign ap_id\n",
	"\tcfgadm -x poweron ap_id\n",
	"\tcfgadm -x poweroff ap_id\n",
	NULL
};

/*ARGSUSED*/
cfga_err_t
ap_help(struct cfga_msg *msgp, const char *options, cfga_flags_t flags)
{
	int len;
	char **p;
	char *q;

	if (msgp == NULL || msgp->message_routine == NULL)
		return (CFGA_OK);

	for (p = ap_help_topics; *p != NULL; p++) {
		if ((len = strlen(*p)) == 0)
			continue;
		if ((q = (char *)calloc(len + 1, 1)) == NULL)
			continue;
		strcpy(q, *p);
		(*msgp->message_routine)(msgp->appdata_ptr, q);
		free(q);
	}

	return (CFGA_OK);
}

static char *
ap_dev_type(sbd_dev_stat_t *dst)
{
	char *type;

	switch (dst->ds_type) {
	case SBD_COMP_CPU:
		type = "cpu";
		break;
	case SBD_COMP_MEM:
		type = "memory";
		break;
	case SBD_COMP_IO:
		type = "io";
		break;
	case SBD_COMP_CMP:
		type = "cpu";
		break;
	default:
		type = "other";
		break;
	}

	DBG("ap_dev_type(%d)=%s\n", dst->ds_type, type);

	return (type);
}

static sbd_dev_stat_t *
ap_cm_stat(apd_t *a, int seq)
{
	sbd_stat_t *st;

	if (seq == CM_DFLT)
		return (a->cmstat);

	st = (sbd_stat_t *)a->stat;
	return (st->s_stat + seq);
}

char *
ap_cm_devpath(apd_t *a, int seq)
{
	int len;
	char *path;
	char *devpath;
	sbd_io_stat_t *dst;


	/*
	 * If no component sequence number is provided
	 * default to the current target component.
	 * Assume an io component so that we can get
	 * the path if the component is indeed of type io.
	 */
	if (seq == -1)
		dst = (sbd_io_stat_t *)a->cmstat;
	else {
		sbd_stat_t *st;
		st = (sbd_stat_t *)a->stat;
		dst = (sbd_io_stat_t *)st->s_stat + seq;
	}

	if (dst->is_type != SBD_COMP_IO)
		path = NULL;
	else
		path = dst->is_pathname;

	if (str_valid(path)) {
		len = strlen(DEVDIR) + strlen(path) + 1;

		if ((devpath = calloc(1, len)) == NULL)
			return (NULL);

		(void) snprintf(devpath, len, "%s%s", DEVDIR, path);
	} else
		devpath = NULL;

	DBG("ap_cm_path(%d)=%s\n", seq, devpath ? devpath : "");

	return (devpath);
}

void
ap_cm_id(apd_t *a, int seq, char *id, size_t bufsize)
{
	int unit;
	char *name;
	sbd_dev_stat_t *dst;

	dst = ap_cm_stat(a, seq);

	unit = dst->ds_unit;
	name = dst->ds_name;

	/*
	 * If the component has a unit number,
	 * add it to the id, otherwise just use
	 * the component's name.
	 */
	if (unit == -1)
		(void) snprintf(id, bufsize, "%s", name);
	else
		(void) snprintf(id, bufsize, "%s%d", name, unit);

	DBG("ap_cm_id(%d)=%s\n", seq, id);
}

/*
 * Convert a component to a target type.
 */
ap_target_t
ap_cm_type(apd_t *a, int seq)
{
	ap_target_t c;
	sbd_dev_stat_t *dst;

	dst = ap_cm_stat(a, seq);

	switch (dst->ds_type) {
	case SBD_COMP_CPU:
		c = AP_CPU;
		break;
	case SBD_COMP_MEM:
		c = AP_MEM;
		break;
	case SBD_COMP_IO:
		c = AP_IO;
		break;
	case SBD_COMP_CMP:
		c = AP_CMP;
		break;
	default:
		c = AP_NONE;
		break;
	}

	return (c);
}

int
ap_cm_ncap(apd_t *a, int seq)
{
	sbd_dev_stat_t	*dst;
	int		ncap;

	dst = ap_cm_stat(a, seq);

	switch (dst->ds_type) {
	case SBD_COMP_CPU:
	case SBD_COMP_MEM:
	case SBD_COMP_IO:
		ncap = 1;
		break;
	case SBD_COMP_CMP:
		ncap = ((sbd_cmp_stat_t *)dst)->ps_ncores;
		break;
	default:
		ncap = 0;
		break;
	}

	return (ncap);
}

int
ap_cm_capacity(apd_t *a, int seq, void *cap, int *ncap, cfga_stat_t *ostate)
{
	int i;
	sbd_dev_stat_t *dst;
	cfga_stat_t os;

	if (cap == NULL)
		return (0);

	dst = ap_cm_stat(a, seq);
	os = (cfga_stat_t)dst->ds_ostate;
	if (os != CFGA_STAT_CONFIGURED && os != CFGA_STAT_UNCONFIGURED)
		return (0);
	if (ostate)
		*ostate = os;

	*ncap = 1;

	switch (dst->ds_type) {
	case SBD_COMP_CPU: {
		sbd_cpu_stat_t *cpu = (sbd_cpu_stat_t *)dst;
		*((processorid_t *)cap) = cpu->cs_cpuid;
		break;
	}
	case SBD_COMP_MEM: {
		sbd_mem_stat_t *mem = (sbd_mem_stat_t *)dst;
		*((long *)cap) = mem->ms_totpages;
		break;
	}
	case SBD_COMP_CMP: {
		sbd_cmp_stat_t	*cmp = (sbd_cmp_stat_t *)dst;
		processorid_t	*cpuid;

		cpuid = (processorid_t *)cap;
		for (i = 0; i < cmp->ps_ncores; i++) {
			cpuid[i] = cmp->ps_cpuid[i];
		}

		*ncap = cmp->ps_ncores;
		break;
	}
	default:
		return (0);
	}

	DBG("ap_cm_capacity(%d)=(", seq);
	for (i = 0; i < *ncap; i++) {
		DBG("%d ", ((int *)cap)[i]);
	}
	DBG("%d)\n", *ostate);

	return (1);
}

void
ap_cm_init(apd_t *a, cfga_list_data_t *ap, int seq)
{
	char *type;
	sbd_stat_t *st;
	sbd_dev_stat_t *dst;

	st = (sbd_stat_t *)a->stat;
	dst = st->s_stat + seq;
	type = ap_dev_type(dst);

	a->cmstat = (void *)dst;

	DBG("ap_cm_init bd=%d rs=%d os=%d type=<%s> seq=%d\n",
		a->bnum, st->s_rstate, dst->ds_ostate, type, seq);

	(void) strncpy(ap->ap_type, type, sizeof (ap->ap_type));
	ap->ap_r_state = (cfga_stat_t)st->s_rstate;
	ap->ap_o_state = (cfga_stat_t)dst->ds_ostate;
	ap->ap_cond = (cfga_cond_t)dst->ds_cond;
	ap->ap_busy = (cfga_busy_t)dst->ds_busy;
	ap->ap_status_time = dst->ds_time;
	ap_info(a, ap->ap_info, ap_cm_tgt(dst->ds_type));
}

void
ap_state(apd_t *a, cfga_stat_t *rs, cfga_stat_t *os)
{
	sbd_stat_t *st;
	sbd_dev_stat_t *dst;

	st = (sbd_stat_t *)a->stat;
	dst = (sbd_dev_stat_t *)a->cmstat;

	if (rs != NULL) {
		if (a->tgt == AP_NONE)
			*rs = CFGA_STAT_NONE;
		else
			*rs = (cfga_stat_t)st->s_rstate;
	}

	if (os != NULL) {
		if (a->tgt == AP_NONE)
			*os = CFGA_STAT_NONE;
		else if (a->tgt == AP_BOARD)
			*os = (cfga_stat_t)st->s_ostate;
		else
			*os = (cfga_stat_t)dst->ds_ostate;
	}
}

#define	BI_POWERED		0
#define	BI_ASSIGNED		1

static const char *
binfo[] = {
	"powered-on",
	", assigned"
};

static const char *
binfo_parsable[] = {
	"powered-on",
	" assigned"
};

static void
bd_info(apd_t *a, cfga_info_t info, int parsable)
{
	int i;
	int nsep;
	const char **p;
	sbd_stat_t *st;
	char *end = &info[sizeof (cfga_info_t)];

	DBG("bd_info(%p)\n", (void *)info);

	st = (sbd_stat_t *)a->stat;

	if (parsable) {
		p = binfo_parsable;
		nsep = 1;
	} else {
		p = binfo;
		nsep = 2;
	}

	i = nsep;

	if (st->s_power) {
		info += snprintf(info, end - info, p[BI_POWERED]);
		i = 0;
	}
	if (st->s_assigned)
		info += snprintf(info, end - info, p[BI_ASSIGNED] + i);
}

#define	CI_CPUID		0
#define	CI_SPEED		1
#define	CI_ECACHE		2

static const char *
cpuinfo[] = {
	"cpuid %d",
	", speed %d MHz",
	", ecache %d MBytes"
};

static const char *
cpuinfo_parsable[] = {
	"cpuid=%d",
	" speed=%d",
	" ecache=%d"
};

static void
cpu_info(apd_t *a, cfga_info_t info, int parsable)
{
	const char **p;
	sbd_cpu_stat_t *dst;
	char *end = &info[sizeof (cfga_info_t)];

	DBG("cpu_info(%p)\n", (void *)info);

	dst = (sbd_cpu_stat_t *)a->cmstat;

	if (parsable)
		p = cpuinfo_parsable;
	else
		p = cpuinfo;

	info += snprintf(info, end - info, p[CI_CPUID], dst->cs_cpuid);
	info += snprintf(info, end - info, p[CI_SPEED], dst->cs_speed);
	info += snprintf(info, end - info, p[CI_ECACHE], dst->cs_ecache);
}

#define	MI_ADDRESS		0
#define	MI_SIZE			1
#define	MI_PERMANENT		2
#define	MI_UNCONFIGURABLE	3
#define	MI_SOURCE		4
#define	MI_TARGET		5
#define	MI_DELETED		6
#define	MI_REMAINING		7
#define	MI_INTERLEAVE		8

static const char *
meminfo_nonparsable[] = {
	"base address 0x%" PRIx64,
	", %lu KBytes total",
	", %lu KBytes permanent",
	", unconfigurable",
	", memory delete requested on %s",
	", memory delete in progress on %s",
	", %lu KBytes deleted",
	", %lu KBytes remaining",
	", inter board interleave"
};

static const char *
meminfo_parsable[] = {
	"address=0x%" PRIx64,
	" size=%lu",
	" permanent=%lu",
	" unconfigurable",
	" source=%s",
	" target=%s",
	" deleted=%lu",
	" remaining=%lu",
	" inter-board-interleave"
};


#define	_K1	1024

/*
 * This function assumes pagesize > 1024 and that
 * pagesize is a multiple of 1024.
 */
static ulong_t
pages_to_kbytes(uint_t pgs)
{
	long pagesize;

	pagesize = sysconf(_SC_PAGESIZE);
	return (pgs * (pagesize / _K1));
}

static uint64_t
pages_to_bytes(uint_t pgs)
{
	long pagesize;

	pagesize = sysconf(_SC_PAGESIZE);
	return ((uint64_t)pgs * pagesize);
}

static void
mem_info(apd_t *a, cfga_info_t info, int parsable)
{
	const char **p;
	sbd_mem_stat_t *dst;
	int want_progress;
	char *end = &info[sizeof (cfga_info_t)];

	DBG("mem_info(%p)\n", (void *)info);

	dst = (sbd_mem_stat_t *)a->cmstat;

	if (parsable)
		p = meminfo_parsable;
	else
		p = meminfo_nonparsable;

	info += snprintf(info, end - info, p[MI_ADDRESS],
	    pages_to_bytes(dst->ms_basepfn));
	info += snprintf(info, end - info, p[MI_SIZE],
	    pages_to_kbytes(dst->ms_totpages));

	if (dst->ms_noreloc_pages)
		info += snprintf(info, end - info, p[MI_PERMANENT],
		    pages_to_kbytes(dst->ms_noreloc_pages));
	if (!dst->ms_cage_enabled)
		info += snprintf(info, end - info, p[MI_UNCONFIGURABLE]);
	if (dst->ms_interleave)
		info += snprintf(info, end - info, p[MI_INTERLEAVE]);

	/*
	 * If there is a valid peer physical ap_id specified,
	 * convert it to a logical id.
	 */
	want_progress = 0;
	if (str_valid(dst->ms_peer_ap_id)) {
		char *cm;
		char *peer;
		char physid[MAXPATHLEN];
		char logid[MAXPATHLEN];

		(void) snprintf(physid, sizeof (physid), "%s%s",
		    DEVDIR, dst->ms_peer_ap_id);

		/*
		 * Save the component portion of the physid and
		 * add it back after converting to logical format.
		 */
		if ((cm = strstr(physid, "::")) != NULL) {
			*cm = '\0';
			cm += 2;
		}

		/* attempt to resolve to symlink */
		if (ap_symid(a, physid, logid, sizeof (logid)) == 0)
			peer = logid;
		else
			peer = physid;

		if (dst->ms_peer_is_target) {
			info += snprintf(info, end - info, p[MI_TARGET], peer);
			if (cm)
				info += snprintf(info, end - info, "::%s", cm);
			want_progress = 1;
		} else {
			info += snprintf(info, end - info, p[MI_SOURCE], peer);
			if (cm)
				info += snprintf(info, end - info, "::%s", cm);
		}
	}
	if (want_progress ||
	    (dst->ms_detpages != 0 && dst->ms_detpages != dst->ms_totpages)) {
		info += snprintf(info, end - info, p[MI_DELETED],
		    pages_to_kbytes(dst->ms_detpages));
		info += snprintf(info, end - info, p[MI_REMAINING],
		    pages_to_kbytes(dst->ms_totpages -
		    dst->ms_detpages));
	}
}

#define	II_DEVICE		0
#define	II_REFERENCED		1

static const char *
ioinfo[] = {
	"device %s",
	", referenced"
};

static const char *
ioinfo_parsable[] = {
	"device=%s",
	" referenced"
};

static void
io_info(apd_t *a, cfga_info_t info, int parsable)
{
	const char **p;
	sbd_io_stat_t *dst;
	char *end = &info[sizeof (cfga_info_t)];

	dst = (sbd_io_stat_t *)a->cmstat;

	if (parsable)
		p = ioinfo_parsable;
	else
		p = ioinfo;

	info += snprintf(info, end - info, p[II_DEVICE], dst->is_pathname);
	if (dst->is_referenced)
		info += snprintf(info, end - info, p[II_REFERENCED]);
}

#define	PI_CPUID		0
#define	PI_CPUID_PAIR		1
#define	PI_CPUID_CONT		2
#define	PI_CPUID_LAST		3
#define	PI_SPEED		4
#define	PI_ECACHE		5

static const char *
cmpinfo[] = {
	"cpuid %d",
	" and %d",
	", %d",
	", and %d",
	", speed %d MHz",
	", ecache %d MBytes"
};

static const char *
cmpinfo_parsable[] = {
	"cpuid=%d",
	",%d",
	",%d",
	",%d",
	" speed=%d",
	" ecache=%d"
};

static void
cmp_info(apd_t *a, cfga_info_t info, int parsable)
{
	int		i;
	int		last;
	const char	**p;
	sbd_cmp_stat_t	*dst;
	char *end = &info[sizeof (cfga_info_t)];

	DBG("cmp_info(%p)\n", (void *)info);

	dst = (sbd_cmp_stat_t *)a->cmstat;

	if (parsable)
		p = cmpinfo_parsable;
	else
		p = cmpinfo;

	/* Print the first cpuid */
	info += snprintf(info, end - info, p[PI_CPUID], dst->ps_cpuid[0]);

	/*
	 * Print the middle cpuids, if necessary. Stop before
	 * the last one, since printing the last cpuid is a
	 * special case for the non parsable form.
	 */
	for (i = 1; i < (dst->ps_ncores - 1); i++) {
		info += snprintf(info, end - info, p[PI_CPUID_CONT],
		    dst->ps_cpuid[i]);
	}

	/* Print the last cpuid, if necessary */
	if (dst->ps_ncores > 1) {
		last = (dst->ps_ncores == 2) ? PI_CPUID_PAIR : PI_CPUID_LAST;
		info += snprintf(info, end - info,
		    dgettext(TEXT_DOMAIN, p[last]), dst->ps_cpuid[i]);
	}

	info += snprintf(info, end - info, p[PI_SPEED], dst->ps_speed);
	info += snprintf(info, end - info, p[PI_ECACHE], dst->ps_ecache);
}

void
ap_info(apd_t *a, cfga_info_t info, ap_target_t tgt)
{
	int parsable = ap_getopt(a, OPT_PARSABLE);

	DBG("ap_info(%p, %d)\n", (void *)info, parsable);

	switch (tgt) {
	case AP_BOARD:
		bd_info(a, info, parsable);
		break;
	case AP_CPU:
		cpu_info(a, info, parsable);
		break;
	case AP_MEM:
		mem_info(a, info, parsable);
		break;
	case AP_IO:
		io_info(a, info, parsable);
		break;
	case AP_CMP:
		cmp_info(a, info, parsable);
		break;
	default:
		break;
	}
}