Home | History | Annotate | Download | only in common
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10  * or http://www.opensolaris.org/os/licensing.
     11  * See the License for the specific language governing permissions
     12  * and limitations under the License.
     13  *
     14  * When distributing Covered Code, include this CDDL HEADER in each
     15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16  * If applicable, add the following below this CDDL HEADER, with the
     17  * fields enclosed by brackets "[]" replaced with your own identifying
     18  * information: Portions Copyright [yyyy] [name of copyright owner]
     19  *
     20  * CDDL HEADER END
     21  */
     22 /*
     23  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     28 
     29 #include <ctype.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 #include <fcntl.h>
     34 #include <unistd.h>
     35 #include <libdevinfo.h>
     36 #include <errno.h>
     37 #include <libintl.h>
     38 #define	CFGA_PLUGIN_LIB
     39 #include <config_admin.h>
     40 #include "ap.h"
     41 #include <sys/obpdefs.h>
     42 #include <sys/processor.h>
     43 #include <sys/stat.h>
     44 #include <sys/sbd_ioctl.h>
     45 #include <sys/int_fmtio.h>
     46 
     47 static cfga_err_t
     48 ap_getncm(apd_t *a, sbd_comp_type_t type, int *ncm)
     49 {
     50 	sbd_ioctl_arg_t *ctl;
     51 	sbd_getncm_cmd_t *cp;
     52 
     53 	if (a->fd == -1 || a->ctl == NULL)
     54 		return (CFGA_LIB_ERROR);
     55 
     56 	ctl = (sbd_ioctl_arg_t *)a->ctl;
     57 	ctl->ic_type = type;
     58 	ctl->ic_name[0] = '\0';
     59 	ctl->ic_unit = 0;
     60 	ctl->i_len = 0;
     61 	ctl->i_opts = NULL;
     62 
     63 	DBG("ioctl(%d SBD_CMD_GETNCM, 0x%p)\n", a->fd, (void *)ctl);
     64 
     65 	if (ioctl(a->fd, SBD_CMD_GETNCM, ctl) == -1) {
     66 		ap_err(a, ERR_CMD_FAIL, CMD_GETNCM);
     67 		return (CFGA_ERROR);
     68 	}
     69 
     70 	cp = &ctl->i_cmd.cmd_getncm;
     71 
     72 	DBG("ncm(%d)=%d\n", type, cp->g_ncm);
     73 
     74 	if (ncm)
     75 		*ncm = cp->g_ncm;
     76 
     77 	return (CFGA_OK);
     78 }
     79 
     80 cfga_err_t
     81 ap_stat(apd_t *a, int all)
     82 {
     83 	int fd;
     84 	int ncm;
     85 	int select;
     86 	int stsize;
     87 	int oflag;
     88 	sbd_stat_cmd_t *sc;
     89 	sbd_ioctl_arg_t *ctl;
     90 	cfga_err_t rc;
     91 	sbd_stat_t *new_stat;
     92 
     93 	rc = CFGA_LIB_ERROR;
     94 
     95 	DBG("ap_stat(%s)\n", a->path);
     96 
     97 	/* Open the file descriptor if not already open */
     98 	if (a->fd == -1) {
     99 		DBG("open(%s)\n", a->path);
    100 		if (a->statonly != 0)
    101 			oflag = O_RDONLY;
    102 		else
    103 			oflag = O_RDWR;
    104 		if ((fd = open(a->path, oflag, 0)) == -1) {
    105 			ap_err(a, ERR_AP_INVAL);
    106 			return (rc);
    107 		}
    108 		a->fd = fd;
    109 	} else {
    110 		fd = a->fd;
    111 	}
    112 
    113 	if (a->ctl == NULL && (a->ctl = calloc(1, sizeof (*ctl))) == NULL) {
    114 		ap_err(a, ERR_CMD_FAIL, CMD_STATUS);
    115 		return (rc);
    116 	}
    117 
    118 	if (a->tgt == AP_BOARD) {
    119 		/*
    120 		 * The status target is the board. If we need to
    121 		 * return component data (to support the -a option),
    122 		 * get the number of components on the board.
    123 		 */
    124 		select = 0;
    125 		if (all) {
    126 			cfga_err_t r;
    127 			r = ap_getncm(a, SBD_COMP_NONE, &ncm);
    128 			if (r != CFGA_OK) {
    129 				return (r);
    130 			}
    131 		} else {
    132 			ncm = 0;
    133 		}
    134 	} else {
    135 		select = 1;
    136 		ncm = 1;
    137 	}
    138 
    139 	DBG("ncm=%d\n", ncm);
    140 
    141 	a->ncm = ncm;
    142 
    143 	/*
    144 	 * The status structure contains space for one component;
    145 	 * add the space for the other components if necessary.
    146 	 */
    147 	stsize = sizeof (sbd_stat_t);
    148 	if (ncm > 1)
    149 		stsize += ((ncm - 1) * sizeof (sbd_dev_stat_t));
    150 
    151 	if ((new_stat = realloc(a->stat, stsize)) == NULL) {
    152 		ap_err(a, ERR_CMD_FAIL, CMD_STATUS);
    153 		return (rc);
    154 	}
    155 
    156 	a->stat = new_stat;
    157 
    158 
    159 	ctl = (sbd_ioctl_arg_t *)a->ctl;
    160 	ctl->i_len = 0;
    161 	ctl->i_opts = NULL;
    162 	ctl->ic_type = SBD_COMP_NONE;
    163 	if (all)
    164 		ctl->i_flags |= SBD_FLAG_ALLCMP;
    165 	sc = &ctl->i_cmd.cmd_stat;
    166 	sc->s_statp = (caddr_t)a->stat;
    167 	sc->s_nbytes = stsize;
    168 
    169 	if (select) {
    170 		/*
    171 		 * The target is a specific component.  Pass its
    172 		 * name and unit number to the driver.  Set its
    173 		 * type to UNKNOWN since the plugin does not know
    174 		 * the type of the component specified by the user.
    175 		 */
    176 		ctl->ic_type = SBD_COMP_UNKNOWN;
    177 		ctl->ic_unit = a->cnum;
    178 		strcpy(ctl->ic_name, a->cname);
    179 	}
    180 
    181 	DBG("ioctl(%d SBD_CMD_STATUS, sc=0x%p sz=%d flags=%d",
    182 		fd, (void *)sc->s_statp, sc->s_nbytes, ctl->i_flags);
    183 	if (select)
    184 		DBG(" cname=<%s> cnum=%d", a->cname, a->cnum);
    185 	DBG(")\n");
    186 
    187 	if (ioctl(fd, SBD_CMD_STATUS, ctl) == -1) {
    188 		ap_err(a, ERR_CMD_FAIL, CMD_STATUS);
    189 		rc = CFGA_ERROR;
    190 	} else
    191 		rc = CFGA_OK;
    192 
    193 	DBG("ap_stat()=%d\n", rc);
    194 
    195 	return (rc);
    196 }
    197 
    198 /*
    199  * Convert a component to a target type.
    200  */
    201 static ap_target_t
    202 ap_cm_tgt(sbd_comp_type_t type)
    203 {
    204 	ap_target_t c;
    205 
    206 	switch (type) {
    207 	case SBD_COMP_CPU:
    208 		c = AP_CPU;
    209 		break;
    210 	case SBD_COMP_MEM:
    211 		c = AP_MEM;
    212 		break;
    213 	case SBD_COMP_IO:
    214 		c = AP_IO;
    215 		break;
    216 	case SBD_COMP_CMP:
    217 		c = AP_CMP;
    218 		break;
    219 	default:
    220 		c = AP_NONE;
    221 		break;
    222 	}
    223 
    224 	return (c);
    225 }
    226 
    227 cfga_err_t
    228 apd_init(apd_t *a, int all)
    229 {
    230 	int i;
    231 	char *cn, *dn;
    232 	sbd_stat_t *st;
    233 	sbd_dev_stat_t *dst;
    234 	cfga_err_t rc;
    235 
    236 	/*
    237 	 * Ideally, for board operations (other than status) it is not
    238 	 * necessary to issue the STATUS ioctl.  The call however allows a
    239 	 * final sanity check to ensure that the board number returned
    240 	 * by the driver matches the plugin's notion of the board number
    241 	 * as extracted from the ap_id.  If this check is not desirable,
    242 	 * we can change the code to issue the status call only when
    243 	 * necessary.  Note that for component operations, we need to do
    244 	 * the STATUS in order to figure out the component type and
    245 	 * validate the command/options accordingly. XXX
    246 	 */
    247 	if ((rc = ap_stat(a, all)) != CFGA_OK) {
    248 		ap_err(a, ERR_AP_INVAL);
    249 		return (rc);
    250 	}
    251 
    252 	st = (sbd_stat_t *)a->stat;
    253 
    254 	/*
    255 	 * Set the component count to the returned stat count.
    256 	 */
    257 	if (a->ncm > st->s_nstat) {
    258 
    259 		DBG("ncm=%d nstat=%d (truncated)\n", a->ncm, st->s_nstat);
    260 
    261 		a->ncm = st->s_nstat;
    262 	}
    263 
    264 	if (a->tgt == AP_BOARD) {
    265 
    266 		DBG("tgt=%d\n", a->tgt);
    267 
    268 		/*
    269 		 * Initialize the RCM module here so that it can record
    270 		 * the initial state of the capacity information.
    271 		 */
    272 		rc = ap_rcm_init(a);
    273 
    274 		return (rc);
    275 	}
    276 
    277 	a->tgt = AP_NONE;
    278 	cn = a->cname;
    279 
    280 	DBG("cname=<%s> cunit=<%d>\n", a->cname, a->cnum);
    281 
    282 	for (dst = st->s_stat, i = 0; i < st->s_nstat; i++, dst++) {
    283 
    284 		DBG("ds_name,ds_unit,ds_type=<%s,%d,%d> ",
    285 			dst->ds_name, dst->ds_unit, dst->ds_type);
    286 
    287 		if (dst->ds_unit != a->cnum)
    288 			continue;
    289 
    290 		/*
    291 		 * Consider the names matched if they are either
    292 		 * both absent or the same. It is conceivable that
    293 		 * a NULL component name be considered valid
    294 		 * by the driver.
    295 		 */
    296 		dn = dst->ds_name;
    297 
    298 		if ((dn == NULL && cn == NULL) ||
    299 		    (dn != NULL && cn != NULL && strcmp(dn, cn) == 0)) {
    300 			a->tgt = ap_cm_tgt(dst->ds_type);
    301 			a->cmstat = (void *)dst;
    302 
    303 			DBG("found ");
    304 
    305 			break;
    306 		}
    307 	}
    308 
    309 	DBG("tgt=%d\n", a->tgt);
    310 
    311 	if (a->tgt == AP_NONE) {
    312 		ap_err(a, ERR_CM_INVAL, a->cid);
    313 		return (CFGA_INVAL);
    314 	}
    315 
    316 	/*
    317 	 * Initialize the RCM module here so that it can record
    318 	 * the initial state of the capacity information.
    319 	 */
    320 	rc = ap_rcm_init(a);
    321 
    322 	return (rc);
    323 }
    324 
    325 void
    326 apd_free(apd_t *a)
    327 {
    328 	if (a == NULL)
    329 		return;
    330 
    331 	ap_rcm_fini(a);
    332 
    333 	if (a->fd != -1)
    334 		close(a->fd);
    335 
    336 	s_free(a->options);
    337 	s_free(a->path);
    338 	s_free(a->drv);
    339 	s_free(a->target);
    340 	s_free(a->cname);
    341 	s_free(a->ctl);
    342 	s_free(a->stat);
    343 
    344 	free(a);
    345 }
    346 
    347 apd_t *
    348 apd_alloc(const char *ap_id, cfga_flags_t flags, char **errstring,
    349 	struct cfga_msg *msgp, struct cfga_confirm *confp)
    350 {
    351 	apd_t *a;
    352 
    353 	if ((a = calloc(1, sizeof (*a))) == NULL)
    354 		return (NULL);
    355 
    356 	if (errstring != NULL)
    357 		*errstring = NULL;
    358 
    359 	a->fd = -1;
    360 	a->errstring = errstring;
    361 	a->msgp = msgp;
    362 	a->confp = confp;
    363 	a->class = "sbd";
    364 
    365 	if (flags & CFGA_FLAG_LIST_ALL)
    366 		ap_setopt(a, OPT_LIST_ALL);
    367 	if (flags & CFGA_FLAG_FORCE)
    368 		ap_setopt(a, OPT_FORCE);
    369 	if (flags & CFGA_FLAG_VERBOSE)
    370 		ap_setopt(a, OPT_VERBOSE);
    371 
    372 	if (ap_id == NULL || ap_parse(a, ap_id) == 0)
    373 		return (a);
    374 
    375 	apd_free(a);
    376 	return (NULL);
    377 }
    378 
    379 /*
    380  * The type field is defined to be parsable by cfgadm(1M): It
    381  * must not contain white space characters. This function
    382  * converts white space to underscore.
    383  */
    384 
    385 static void
    386 parsable_strncpy(char *op, const char *ip, size_t n)
    387 {
    388 	char c;
    389 
    390 	while (n-- > 0) {
    391 		c = *ip++;
    392 		if (isspace(c))
    393 			c = '_';
    394 		*op++ = c;
    395 		if (c == '\0')
    396 			break;
    397 	}
    398 }
    399 
    400 void
    401 ap_init(apd_t *a, cfga_list_data_t *ap)
    402 {
    403 	sbd_stat_t *st;
    404 
    405 	st = (sbd_stat_t *)a->stat;
    406 
    407 	DBG("ap_init bd=%d rs=%d os=%d type=<%s>\n",
    408 		a->bnum, st->s_rstate, st->s_ostate, st->s_type);
    409 
    410 	parsable_strncpy(ap->ap_type, st->s_type, sizeof (ap->ap_type));
    411 	ap->ap_r_state = (cfga_stat_t)st->s_rstate;
    412 	ap->ap_o_state = (cfga_stat_t)st->s_ostate;
    413 	ap->ap_cond = (cfga_cond_t)st->s_cond;
    414 	ap->ap_busy = (cfga_busy_t)st->s_busy;
    415 	ap->ap_status_time = st->s_time;
    416 	ap_info(a, ap->ap_info, AP_BOARD);
    417 }
    418 
    419 typedef struct {
    420 	int cmd;
    421 	int ioc;
    422 } ap_ioc_t;
    423 
    424 static ap_ioc_t
    425 ap_iocs[] =  {
    426 	{CMD_ASSIGN,	  SBD_CMD_ASSIGN	},
    427 	{CMD_POWERON,	  SBD_CMD_POWERON	},
    428 	{CMD_TEST,	  SBD_CMD_TEST		},
    429 	{CMD_CONNECT,	  SBD_CMD_CONNECT	},
    430 	{CMD_CONFIGURE,	  SBD_CMD_CONFIGURE	},
    431 	{CMD_UNCONFIGURE, SBD_CMD_UNCONFIGURE	},
    432 	{CMD_DISCONNECT,  SBD_CMD_DISCONNECT	},
    433 	{CMD_POWEROFF,	  SBD_CMD_POWEROFF	},
    434 	{CMD_STATUS,	  SBD_CMD_STATUS	},
    435 	{CMD_GETNCM,	  SBD_CMD_GETNCM	},
    436 	{CMD_UNASSIGN,	  SBD_CMD_UNASSIGN	},
    437 	{CMD_PASSTHRU,	  SBD_CMD_PASSTHRU	},
    438 	{CMD_NONE,	  0			}
    439 };
    440 
    441 static int
    442 ap_ioc(int cmd)
    443 {
    444 	ap_ioc_t *acp;
    445 
    446 	DBG("ap_ioc(%d)\n", cmd);
    447 
    448 	for (acp = ap_iocs; acp->cmd != CMD_NONE; acp++)
    449 		if (acp->cmd == cmd)
    450 			break;
    451 
    452 	DBG("ap_ioc(%d)=0x%x\n", cmd, acp->ioc);
    453 
    454 	return (acp->ioc);
    455 }
    456 
    457 cfga_err_t
    458 ap_suspend_query(apd_t *a, int cmd, int *check)
    459 {
    460 	int ioc;
    461 	sbd_dev_stat_t *dst;
    462 
    463 	/*
    464 	 * See if the a quiesce operation is required for
    465 	 * this command for any of the components.  If the
    466 	 * command does not map to an ioctl, then there is
    467 	 * nothing to do.
    468 	 */
    469 	if ((ioc = ap_ioc(cmd)) == 0)
    470 		return (CFGA_OK);
    471 	else if (a->tgt == AP_BOARD) {
    472 		int i;
    473 
    474 		dst = ((sbd_stat_t *)a->stat)->s_stat;
    475 
    476 		/*
    477 		 * See if any component requires a
    478 		 * OS suspension for this command.
    479 		 */
    480 		for (i = 0; i < a->ncm; i++, dst++)
    481 			if (SBD_CHECK_SUSPEND(ioc, dst->ds_suspend))
    482 				(*check)++;
    483 	} else {
    484 		dst = (sbd_dev_stat_t *)a->cmstat;
    485 		if (SBD_CHECK_SUSPEND(ioc, dst->ds_suspend))
    486 				(*check)++;
    487 	}
    488 
    489 	return (CFGA_OK);
    490 }
    491 
    492 cfga_err_t
    493 ap_platopts_check(apd_t *a, int first, int last)
    494 {
    495 	int c;
    496 	uint_t platopts;
    497 	sbd_stat_t *stat;
    498 	ap_opts_t *opts;
    499 
    500 	opts = &a->opts;
    501 	stat = (sbd_stat_t *)a->stat;
    502 	platopts = stat->s_platopts;
    503 
    504 
    505 	/*
    506 	 * If there are no platform options set then there
    507 	 * is no need to check this operation
    508 	 */
    509 	if (opts->platform == NULL)
    510 		return (CFGA_OK);
    511 
    512 	/*
    513 	 * Check if any of the steps in the sequence
    514 	 * allows for a platform option
    515 	 */
    516 	for (c = first; c <= last; c++)
    517 		/*
    518 		 * If the platopt is set it means that the platform does not
    519 		 * support options for this cmd
    520 		 */
    521 		if (SBD_CHECK_PLATOPTS(ap_ioc(c), platopts) == 0) {
    522 			return (CFGA_OK);
    523 		}
    524 
    525 	ap_err(a, ERR_OPT_INVAL, opts->platform);
    526 
    527 	return (CFGA_INVAL);
    528 }
    529 
    530 cfga_err_t
    531 ap_ioctl(apd_t *a, int cmd)
    532 {
    533 	int ioc;
    534 	sbd_ioctl_arg_t *ctl;
    535 
    536 	if (a->ctl == NULL && (a->ctl = calloc(1, sizeof (*ctl))) == NULL) {
    537 		ap_err(a, ERR_CMD_FAIL, cmd);
    538 		return (CFGA_LIB_ERROR);
    539 	}
    540 
    541 	ap_msg(a, MSG_ISSUE, cmd, a->target);
    542 
    543 	ctl = (sbd_ioctl_arg_t *)a->ctl;
    544 	ctl->i_flags = 0;
    545 	ctl->i_len = 0;
    546 	ctl->i_opts = NULL;
    547 
    548 	if (ap_getopt(a, OPT_FORCE))
    549 		ctl->i_flags |= SBD_FLAG_FORCE;
    550 	if (ap_getopt(a, OPT_SUSPEND_OK))
    551 		ctl->i_flags |= SBD_FLAG_QUIESCE_OKAY;
    552 
    553 	if (a->tgt == AP_BOARD)
    554 		ctl->ic_type = SBD_COMP_NONE;
    555 	else {
    556 		ctl->ic_type = SBD_COMP_UNKNOWN;
    557 		ctl->ic_unit = a->cnum;
    558 		strcpy(ctl->ic_name, a->cname);
    559 	}
    560 
    561 	if (!(ioc = ap_ioc(cmd))) {
    562 		ap_err(a, ERR_CMD_FAIL, cmd);
    563 		return (CFGA_LIB_ERROR);
    564 	}
    565 
    566 	/*
    567 	 * If this is a passthru command, pass all of its
    568 	 * options; otherwise, pass all options after the
    569 	 * platform keyword.
    570 	 */
    571 	if (cmd == CMD_PASSTHRU)
    572 		ctl->i_opts = a->options;
    573 	else {
    574 		/*
    575 		 * Only pass the platform option to the cmds that the platform
    576 		 * has specified as ok
    577 		 */
    578 		sbd_stat_t *stat;
    579 
    580 		stat = (sbd_stat_t *)a->stat;
    581 		if (SBD_CHECK_PLATOPTS(ioc, stat->s_platopts) == 0)
    582 			ctl->i_opts = a->opts.platform;
    583 	}
    584 
    585 	if (ctl->i_opts != NULL)
    586 		ctl->i_len = strlen(ctl->i_opts) + 1;
    587 
    588 	DBG("i_opts=%s\n", ctl->i_opts ? ctl->i_opts : "NULL");
    589 	DBG("i_flags=0x%x\n", ctl->i_flags);
    590 
    591 	if (ap_getopt(a, OPT_SIM)) {
    592 		ap_msg(a, MSG_DONE, cmd, a->target);
    593 		return (CFGA_OK);
    594 	}
    595 
    596 	if (ioctl(a->fd, ioc, ctl) == -1) {
    597 		ap_err(a, ERR_CMD_FAIL, cmd);
    598 		return (CFGA_ERROR);
    599 	}
    600 	ap_msg(a, MSG_DONE, cmd, a->target);
    601 
    602 	return (CFGA_OK);
    603 }
    604 
    605 /*
    606  * Return the error string corresponding to a given error code.
    607  * String table and error code sets are provided by sbd_etab.  This data
    608  * structure is automatically generated at compile time from the error
    609  * code and message text information in sbd_ioctl.h.
    610  */
    611 static char *
    612 mod_estr(int code)
    613 {
    614 	int i;
    615 	char *s;
    616 	extern sbd_etab_t sbd_etab[];
    617 	extern int sbd_etab_len;
    618 
    619 	s = NULL;
    620 
    621 	for (i = 0; i < sbd_etab_len; i++) {
    622 		sbd_etab_t *eptr = &sbd_etab[i];
    623 
    624 		if ((code >= eptr->t_base) && (code <= eptr->t_bnd)) {
    625 			int index;
    626 			char **t_text;
    627 
    628 			/*
    629 			 * Found it. Just extract the string
    630 			 */
    631 			index = code - eptr->t_base;
    632 			t_text = eptr->t_text;
    633 			s = strdup(t_text[index]);
    634 			break;
    635 		}
    636 	}
    637 
    638 	if (i == sbd_etab_len) {
    639 		char buf[32];
    640 
    641 		snprintf(buf, sizeof (buf), "error %d", code);
    642 		s = strdup(buf);
    643 	}
    644 
    645 	return (s);
    646 }
    647 
    648 char *
    649 ap_sys_err(apd_t *a, char **rp)
    650 {
    651 	int code;
    652 	char *p;
    653 	char *rsc;
    654 
    655 	sbd_ioctl_arg_t *ctl = (sbd_ioctl_arg_t *)a->ctl;
    656 
    657 	/*
    658 	 * The driver sets the errno to EIO if it returns
    659 	 * more detailed error info via e_code.  In all
    660 	 * other cases, use standard error text.
    661 	 */
    662 	if (ctl == NULL || errno != EIO) {
    663 		if ((p = strerror(errno)) != NULL)
    664 			p = strdup(p);
    665 		return (p);
    666 	}
    667 
    668 	code = ctl->ie_code;
    669 	rsc = ctl->ie_rsc;
    670 
    671 	if (code)
    672 		p = mod_estr(code);
    673 	else if ((p = strerror(errno)) != NULL)
    674 		p = strdup(p);
    675 
    676 	if (*rsc != '\0' && rp != NULL)
    677 		*rp = strdup(rsc);
    678 
    679 	return (p);
    680 }
    681 
    682 /*
    683  * cfgadm -o err=plugin-err,cmd=name,code=ecode -x errtest ap_id.
    684  */
    685 cfga_err_t
    686 ap_test_err(apd_t *a, const char *options)
    687 {
    688 	int err;
    689 	int cmd;
    690 	ap_opts_t *opts;
    691 	sbd_ioctl_arg_t ctl;
    692 
    693 	opts = &a->opts;
    694 	err = opts->err;
    695 	cmd = CMD_DISCONNECT;
    696 
    697 	DBG("ap_test_err(%d %d)\n", opts->code, opts->err);
    698 
    699 	switch (err) {
    700 	case ERR_CMD_INVAL:
    701 		ap_err(a, err, ap_cmd_name(cmd));
    702 		break;
    703 	case ERR_CMD_NOTSUPP:
    704 		ap_err(a, err, cmd);
    705 		break;
    706 	case ERR_CMD_FAIL:
    707 		errno = EIO;
    708 		ctl.i_err.e_code = opts->code;
    709 		*ctl.i_err.e_rsc = '\0';
    710 		a->ctl = &ctl;
    711 		ap_err(a, err, cmd);
    712 		a->ctl = NULL;
    713 		break;
    714 	case ERR_OPT_INVAL:
    715 		ap_err(a, err, options);
    716 		break;
    717 	case ERR_OPT_NOVAL:
    718 		ap_err(a, err, options);
    719 		break;
    720 	case ERR_AP_INVAL:
    721 		ap_err(a, err);
    722 		break;
    723 	case ERR_CM_INVAL:
    724 		ap_err(a, err, a->cid);
    725 		break;
    726 	case ERR_TRANS_INVAL:
    727 		ap_err(a, ERR_TRANS_INVAL, cmd);
    728 		break;
    729 	}
    730 
    731 	return (CFGA_LIB_ERROR);
    732 }
    733 
    734 static char *
    735 ap_help_topics[] = {
    736 	"\nSbd specific commands/options:\n\n",
    737 	"\tcfgadm [-o parsable] -l ap_id\n",
    738 	"\tcfgadm [-o unassign|nopoweroff] -c disconnect ap_id\n",
    739 	"\tcfgadm -t ap_id\n",
    740 	"\tcfgadm -x assign ap_id\n",
    741 	"\tcfgadm -x unassign ap_id\n",
    742 	"\tcfgadm -x poweron ap_id\n",
    743 	"\tcfgadm -x poweroff ap_id\n",
    744 	NULL
    745 };
    746 
    747 /*ARGSUSED*/
    748 cfga_err_t
    749 ap_help(struct cfga_msg *msgp, const char *options, cfga_flags_t flags)
    750 {
    751 	int len;
    752 	char **p;
    753 	char *q;
    754 
    755 	if (msgp == NULL || msgp->message_routine == NULL)
    756 		return (CFGA_OK);
    757 
    758 	for (p = ap_help_topics; *p != NULL; p++) {
    759 		if ((len = strlen(*p)) == 0)
    760 			continue;
    761 		if ((q = (char *)calloc(len + 1, 1)) == NULL)
    762 			continue;
    763 		strcpy(q, *p);
    764 		(*msgp->message_routine)(msgp->appdata_ptr, q);
    765 		free(q);
    766 	}
    767 
    768 	return (CFGA_OK);
    769 }
    770 
    771 static char *
    772 ap_dev_type(sbd_dev_stat_t *dst)
    773 {
    774 	char *type;
    775 
    776 	switch (dst->ds_type) {
    777 	case SBD_COMP_CPU:
    778 		type = "cpu";
    779 		break;
    780 	case SBD_COMP_MEM:
    781 		type = "memory";
    782 		break;
    783 	case SBD_COMP_IO:
    784 		type = "io";
    785 		break;
    786 	case SBD_COMP_CMP:
    787 		type = "cpu";
    788 		break;
    789 	default:
    790 		type = "other";
    791 		break;
    792 	}
    793 
    794 	DBG("ap_dev_type(%d)=%s\n", dst->ds_type, type);
    795 
    796 	return (type);
    797 }
    798 
    799 static sbd_dev_stat_t *
    800 ap_cm_stat(apd_t *a, int seq)
    801 {
    802 	sbd_stat_t *st;
    803 
    804 	if (seq == CM_DFLT)
    805 		return (a->cmstat);
    806 
    807 	st = (sbd_stat_t *)a->stat;
    808 	return (st->s_stat + seq);
    809 }
    810 
    811 char *
    812 ap_cm_devpath(apd_t *a, int seq)
    813 {
    814 	int len;
    815 	char *path;
    816 	char *devpath;
    817 	sbd_io_stat_t *dst;
    818 
    819 
    820 	/*
    821 	 * If no component sequence number is provided
    822 	 * default to the current target component.
    823 	 * Assume an io component so that we can get
    824 	 * the path if the component is indeed of type io.
    825 	 */
    826 	if (seq == -1)
    827 		dst = (sbd_io_stat_t *)a->cmstat;
    828 	else {
    829 		sbd_stat_t *st;
    830 		st = (sbd_stat_t *)a->stat;
    831 		dst = (sbd_io_stat_t *)st->s_stat + seq;
    832 	}
    833 
    834 	if (dst->is_type != SBD_COMP_IO)
    835 		path = NULL;
    836 	else
    837 		path = dst->is_pathname;
    838 
    839 	if (str_valid(path)) {
    840 		len = strlen(DEVDIR) + strlen(path) + 1;
    841 
    842 		if ((devpath = calloc(1, len)) == NULL)
    843 			return (NULL);
    844 
    845 		(void) snprintf(devpath, len, "%s%s", DEVDIR, path);
    846 	} else
    847 		devpath = NULL;
    848 
    849 	DBG("ap_cm_path(%d)=%s\n", seq, devpath ? devpath : "");
    850 
    851 	return (devpath);
    852 }
    853 
    854 void
    855 ap_cm_id(apd_t *a, int seq, char *id, size_t bufsize)
    856 {
    857 	int unit;
    858 	char *name;
    859 	sbd_dev_stat_t *dst;
    860 
    861 	dst = ap_cm_stat(a, seq);
    862 
    863 	unit = dst->ds_unit;
    864 	name = dst->ds_name;
    865 
    866 	/*
    867 	 * If the component has a unit number,
    868 	 * add it to the id, otherwise just use
    869 	 * the component's name.
    870 	 */
    871 	if (unit == -1)
    872 		(void) snprintf(id, bufsize, "%s", name);
    873 	else
    874 		(void) snprintf(id, bufsize, "%s%d", name, unit);
    875 
    876 	DBG("ap_cm_id(%d)=%s\n", seq, id);
    877 }
    878 
    879 /*
    880  * Convert a component to a target type.
    881  */
    882 ap_target_t
    883 ap_cm_type(apd_t *a, int seq)
    884 {
    885 	ap_target_t c;
    886 	sbd_dev_stat_t *dst;
    887 
    888 	dst = ap_cm_stat(a, seq);
    889 
    890 	switch (dst->ds_type) {
    891 	case SBD_COMP_CPU:
    892 		c = AP_CPU;
    893 		break;
    894 	case SBD_COMP_MEM:
    895 		c = AP_MEM;
    896 		break;
    897 	case SBD_COMP_IO:
    898 		c = AP_IO;
    899 		break;
    900 	case SBD_COMP_CMP:
    901 		c = AP_CMP;
    902 		break;
    903 	default:
    904 		c = AP_NONE;
    905 		break;
    906 	}
    907 
    908 	return (c);
    909 }
    910 
    911 int
    912 ap_cm_ncap(apd_t *a, int seq)
    913 {
    914 	sbd_dev_stat_t	*dst;
    915 	int		ncap;
    916 
    917 	dst = ap_cm_stat(a, seq);
    918 
    919 	switch (dst->ds_type) {
    920 	case SBD_COMP_CPU:
    921 	case SBD_COMP_MEM:
    922 	case SBD_COMP_IO:
    923 		ncap = 1;
    924 		break;
    925 	case SBD_COMP_CMP:
    926 		ncap = ((sbd_cmp_stat_t *)dst)->ps_ncores;
    927 		break;
    928 	default:
    929 		ncap = 0;
    930 		break;
    931 	}
    932 
    933 	return (ncap);
    934 }
    935 
    936 int
    937 ap_cm_capacity(apd_t *a, int seq, void *cap, int *ncap, cfga_stat_t *ostate)
    938 {
    939 	int i;
    940 	sbd_dev_stat_t *dst;
    941 	cfga_stat_t os;
    942 
    943 	if (cap == NULL)
    944 		return (0);
    945 
    946 	dst = ap_cm_stat(a, seq);
    947 	os = (cfga_stat_t)dst->ds_ostate;
    948 	if (os != CFGA_STAT_CONFIGURED && os != CFGA_STAT_UNCONFIGURED)
    949 		return (0);
    950 	if (ostate)
    951 		*ostate = os;
    952 
    953 	*ncap = 1;
    954 
    955 	switch (dst->ds_type) {
    956 	case SBD_COMP_CPU: {
    957 		sbd_cpu_stat_t *cpu = (sbd_cpu_stat_t *)dst;
    958 		*((processorid_t *)cap) = cpu->cs_cpuid;
    959 		break;
    960 	}
    961 	case SBD_COMP_MEM: {
    962 		sbd_mem_stat_t *mem = (sbd_mem_stat_t *)dst;
    963 		*((long *)cap) = mem->ms_totpages;
    964 		break;
    965 	}
    966 	case SBD_COMP_CMP: {
    967 		sbd_cmp_stat_t	*cmp = (sbd_cmp_stat_t *)dst;
    968 		processorid_t	*cpuid;
    969 
    970 		cpuid = (processorid_t *)cap;
    971 		for (i = 0; i < cmp->ps_ncores; i++) {
    972 			cpuid[i] = cmp->ps_cpuid[i];
    973 		}
    974 
    975 		*ncap = cmp->ps_ncores;
    976 		break;
    977 	}
    978 	default:
    979 		return (0);
    980 	}
    981 
    982 	DBG("ap_cm_capacity(%d)=(", seq);
    983 	for (i = 0; i < *ncap; i++) {
    984 		DBG("%d ", ((int *)cap)[i]);
    985 	}
    986 	DBG("%d)\n", *ostate);
    987 
    988 	return (1);
    989 }
    990 
    991 void
    992 ap_cm_init(apd_t *a, cfga_list_data_t *ap, int seq)
    993 {
    994 	char *type;
    995 	sbd_stat_t *st;
    996 	sbd_dev_stat_t *dst;
    997 
    998 	st = (sbd_stat_t *)a->stat;
    999 	dst = st->s_stat + seq;
   1000 	type = ap_dev_type(dst);
   1001 
   1002 	a->cmstat = (void *)dst;
   1003 
   1004 	DBG("ap_cm_init bd=%d rs=%d os=%d type=<%s> seq=%d\n",
   1005 		a->bnum, st->s_rstate, dst->ds_ostate, type, seq);
   1006 
   1007 	(void) strncpy(ap->ap_type, type, sizeof (ap->ap_type));
   1008 	ap->ap_r_state = (cfga_stat_t)st->s_rstate;
   1009 	ap->ap_o_state = (cfga_stat_t)dst->ds_ostate;
   1010 	ap->ap_cond = (cfga_cond_t)dst->ds_cond;
   1011 	ap->ap_busy = (cfga_busy_t)dst->ds_busy;
   1012 	ap->ap_status_time = dst->ds_time;
   1013 	ap_info(a, ap->ap_info, ap_cm_tgt(dst->ds_type));
   1014 }
   1015 
   1016 void
   1017 ap_state(apd_t *a, cfga_stat_t *rs, cfga_stat_t *os)
   1018 {
   1019 	sbd_stat_t *st;
   1020 	sbd_dev_stat_t *dst;
   1021 
   1022 	st = (sbd_stat_t *)a->stat;
   1023 	dst = (sbd_dev_stat_t *)a->cmstat;
   1024 
   1025 	if (rs != NULL) {
   1026 		if (a->tgt == AP_NONE)
   1027 			*rs = CFGA_STAT_NONE;
   1028 		else
   1029 			*rs = (cfga_stat_t)st->s_rstate;
   1030 	}
   1031 
   1032 	if (os != NULL) {
   1033 		if (a->tgt == AP_NONE)
   1034 			*os = CFGA_STAT_NONE;
   1035 		else if (a->tgt == AP_BOARD)
   1036 			*os = (cfga_stat_t)st->s_ostate;
   1037 		else
   1038 			*os = (cfga_stat_t)dst->ds_ostate;
   1039 	}
   1040 }
   1041 
   1042 #define	BI_POWERED		0
   1043 #define	BI_ASSIGNED		1
   1044 
   1045 static const char *
   1046 binfo[] = {
   1047 	"powered-on",
   1048 	", assigned"
   1049 };
   1050 
   1051 static const char *
   1052 binfo_parsable[] = {
   1053 	"powered-on",
   1054 	" assigned"
   1055 };
   1056 
   1057 static void
   1058 bd_info(apd_t *a, cfga_info_t info, int parsable)
   1059 {
   1060 	int i;
   1061 	int nsep;
   1062 	const char **p;
   1063 	sbd_stat_t *st;
   1064 	char *end = &info[sizeof (cfga_info_t)];
   1065 
   1066 	DBG("bd_info(%p)\n", (void *)info);
   1067 
   1068 	st = (sbd_stat_t *)a->stat;
   1069 
   1070 	if (parsable) {
   1071 		p = binfo_parsable;
   1072 		nsep = 1;
   1073 	} else {
   1074 		p = binfo;
   1075 		nsep = 2;
   1076 	}
   1077 
   1078 	i = nsep;
   1079 
   1080 	if (st->s_power) {
   1081 		info += snprintf(info, end - info, p[BI_POWERED]);
   1082 		i = 0;
   1083 	}
   1084 	if (st->s_assigned)
   1085 		info += snprintf(info, end - info, p[BI_ASSIGNED] + i);
   1086 }
   1087 
   1088 #define	CI_CPUID		0
   1089 #define	CI_SPEED		1
   1090 #define	CI_ECACHE		2
   1091 
   1092 static const char *
   1093 cpuinfo[] = {
   1094 	"cpuid %d",
   1095 	", speed %d MHz",
   1096 	", ecache %d MBytes"
   1097 };
   1098 
   1099 static const char *
   1100 cpuinfo_parsable[] = {
   1101 	"cpuid=%d",
   1102 	" speed=%d",
   1103 	" ecache=%d"
   1104 };
   1105 
   1106 static void
   1107 cpu_info(apd_t *a, cfga_info_t info, int parsable)
   1108 {
   1109 	const char **p;
   1110 	sbd_cpu_stat_t *dst;
   1111 	char *end = &info[sizeof (cfga_info_t)];
   1112 
   1113 	DBG("cpu_info(%p)\n", (void *)info);
   1114 
   1115 	dst = (sbd_cpu_stat_t *)a->cmstat;
   1116 
   1117 	if (parsable)
   1118 		p = cpuinfo_parsable;
   1119 	else
   1120 		p = cpuinfo;
   1121 
   1122 	info += snprintf(info, end - info, p[CI_CPUID], dst->cs_cpuid);
   1123 	info += snprintf(info, end - info, p[CI_SPEED], dst->cs_speed);
   1124 	info += snprintf(info, end - info, p[CI_ECACHE], dst->cs_ecache);
   1125 }
   1126 
   1127 #define	MI_ADDRESS		0
   1128 #define	MI_SIZE			1
   1129 #define	MI_PERMANENT		2
   1130 #define	MI_UNCONFIGURABLE	3
   1131 #define	MI_SOURCE		4
   1132 #define	MI_TARGET		5
   1133 #define	MI_DELETED		6
   1134 #define	MI_REMAINING		7
   1135 #define	MI_INTERLEAVE		8
   1136 
   1137 static const char *
   1138 meminfo_nonparsable[] = {
   1139 	"base address 0x%" PRIx64,
   1140 	", %lu KBytes total",
   1141 	", %lu KBytes permanent",
   1142 	", unconfigurable",
   1143 	", memory delete requested on %s",
   1144 	", memory delete in progress on %s",
   1145 	", %lu KBytes deleted",
   1146 	", %lu KBytes remaining",
   1147 	", inter board interleave"
   1148 };
   1149 
   1150 static const char *
   1151 meminfo_parsable[] = {
   1152 	"address=0x%" PRIx64,
   1153 	" size=%lu",
   1154 	" permanent=%lu",
   1155 	" unconfigurable",
   1156 	" source=%s",
   1157 	" target=%s",
   1158 	" deleted=%lu",
   1159 	" remaining=%lu",
   1160 	" inter-board-interleave"
   1161 };
   1162 
   1163 
   1164 #define	_K1	1024
   1165 
   1166 /*
   1167  * This function assumes pagesize > 1024 and that
   1168  * pagesize is a multiple of 1024.
   1169  */
   1170 static ulong_t
   1171 pages_to_kbytes(uint_t pgs)
   1172 {
   1173 	long pagesize;
   1174 
   1175 	pagesize = sysconf(_SC_PAGESIZE);
   1176 	return (pgs * (pagesize / _K1));
   1177 }
   1178 
   1179 static uint64_t
   1180 pages_to_bytes(uint_t pgs)
   1181 {
   1182 	long pagesize;
   1183 
   1184 	pagesize = sysconf(_SC_PAGESIZE);
   1185 	return ((uint64_t)pgs * pagesize);
   1186 }
   1187 
   1188 static void
   1189 mem_info(apd_t *a, cfga_info_t info, int parsable)
   1190 {
   1191 	const char **p;
   1192 	sbd_mem_stat_t *dst;
   1193 	int want_progress;
   1194 	char *end = &info[sizeof (cfga_info_t)];
   1195 
   1196 	DBG("mem_info(%p)\n", (void *)info);
   1197 
   1198 	dst = (sbd_mem_stat_t *)a->cmstat;
   1199 
   1200 	if (parsable)
   1201 		p = meminfo_parsable;
   1202 	else
   1203 		p = meminfo_nonparsable;
   1204 
   1205 	info += snprintf(info, end - info, p[MI_ADDRESS],
   1206 	    pages_to_bytes(dst->ms_basepfn));
   1207 	info += snprintf(info, end - info, p[MI_SIZE],
   1208 	    pages_to_kbytes(dst->ms_totpages));
   1209 
   1210 	if (dst->ms_noreloc_pages)
   1211 		info += snprintf(info, end - info, p[MI_PERMANENT],
   1212 		    pages_to_kbytes(dst->ms_noreloc_pages));
   1213 	if (!dst->ms_cage_enabled)
   1214 		info += snprintf(info, end - info, p[MI_UNCONFIGURABLE]);
   1215 	if (dst->ms_interleave)
   1216 		info += snprintf(info, end - info, p[MI_INTERLEAVE]);
   1217 
   1218 	/*
   1219 	 * If there is a valid peer physical ap_id specified,
   1220 	 * convert it to a logical id.
   1221 	 */
   1222 	want_progress = 0;
   1223 	if (str_valid(dst->ms_peer_ap_id)) {
   1224 		char *cm;
   1225 		char *peer;
   1226 		char physid[MAXPATHLEN];
   1227 		char logid[MAXPATHLEN];
   1228 
   1229 		(void) snprintf(physid, sizeof (physid), "%s%s",
   1230 		    DEVDIR, dst->ms_peer_ap_id);
   1231 
   1232 		/*
   1233 		 * Save the component portion of the physid and
   1234 		 * add it back after converting to logical format.
   1235 		 */
   1236 		if ((cm = strstr(physid, "::")) != NULL) {
   1237 			*cm = '\0';
   1238 			cm += 2;
   1239 		}
   1240 
   1241 		/* attempt to resolve to symlink */
   1242 		if (ap_symid(a, physid, logid, sizeof (logid)) == 0)
   1243 			peer = logid;
   1244 		else
   1245 			peer = physid;
   1246 
   1247 		if (dst->ms_peer_is_target) {
   1248 			info += snprintf(info, end - info, p[MI_TARGET], peer);
   1249 			if (cm)
   1250 				info += snprintf(info, end - info, "::%s", cm);
   1251 			want_progress = 1;
   1252 		} else {
   1253 			info += snprintf(info, end - info, p[MI_SOURCE], peer);
   1254 			if (cm)
   1255 				info += snprintf(info, end - info, "::%s", cm);
   1256 		}
   1257 	}
   1258 	if (want_progress ||
   1259 	    (dst->ms_detpages != 0 && dst->ms_detpages != dst->ms_totpages)) {
   1260 		info += snprintf(info, end - info, p[MI_DELETED],
   1261 		    pages_to_kbytes(dst->ms_detpages));
   1262 		info += snprintf(info, end - info, p[MI_REMAINING],
   1263 		    pages_to_kbytes(dst->ms_totpages -
   1264 		    dst->ms_detpages));
   1265 	}
   1266 }
   1267 
   1268 #define	II_DEVICE		0
   1269 #define	II_REFERENCED		1
   1270 
   1271 static const char *
   1272 ioinfo[] = {
   1273 	"device %s",
   1274 	", referenced"
   1275 };
   1276 
   1277 static const char *
   1278 ioinfo_parsable[] = {
   1279 	"device=%s",
   1280 	" referenced"
   1281 };
   1282 
   1283 static void
   1284 io_info(apd_t *a, cfga_info_t info, int parsable)
   1285 {
   1286 	const char **p;
   1287 	sbd_io_stat_t *dst;
   1288 	char *end = &info[sizeof (cfga_info_t)];
   1289 
   1290 	dst = (sbd_io_stat_t *)a->cmstat;
   1291 
   1292 	if (parsable)
   1293 		p = ioinfo_parsable;
   1294 	else
   1295 		p = ioinfo;
   1296 
   1297 	info += snprintf(info, end - info, p[II_DEVICE], dst->is_pathname);
   1298 	if (dst->is_referenced)
   1299 		info += snprintf(info, end - info, p[II_REFERENCED]);
   1300 }
   1301 
   1302 #define	PI_CPUID		0
   1303 #define	PI_CPUID_PAIR		1
   1304 #define	PI_CPUID_CONT		2
   1305 #define	PI_CPUID_LAST		3
   1306 #define	PI_SPEED		4
   1307 #define	PI_ECACHE		5
   1308 
   1309 static const char *
   1310 cmpinfo[] = {
   1311 	"cpuid %d",
   1312 	" and %d",
   1313 	", %d",
   1314 	", and %d",
   1315 	", speed %d MHz",
   1316 	", ecache %d MBytes"
   1317 };
   1318 
   1319 static const char *
   1320 cmpinfo_parsable[] = {
   1321 	"cpuid=%d",
   1322 	",%d",
   1323 	",%d",
   1324 	",%d",
   1325 	" speed=%d",
   1326 	" ecache=%d"
   1327 };
   1328 
   1329 static void
   1330 cmp_info(apd_t *a, cfga_info_t info, int parsable)
   1331 {
   1332 	int		i;
   1333 	int		last;
   1334 	const char	**p;
   1335 	sbd_cmp_stat_t	*dst;
   1336 	char *end = &info[sizeof (cfga_info_t)];
   1337 
   1338 	DBG("cmp_info(%p)\n", (void *)info);
   1339 
   1340 	dst = (sbd_cmp_stat_t *)a->cmstat;
   1341 
   1342 	if (parsable)
   1343 		p = cmpinfo_parsable;
   1344 	else
   1345 		p = cmpinfo;
   1346 
   1347 	/* Print the first cpuid */
   1348 	info += snprintf(info, end - info, p[PI_CPUID], dst->ps_cpuid[0]);
   1349 
   1350 	/*
   1351 	 * Print the middle cpuids, if necessary. Stop before
   1352 	 * the last one, since printing the last cpuid is a
   1353 	 * special case for the non parsable form.
   1354 	 */
   1355 	for (i = 1; i < (dst->ps_ncores - 1); i++) {
   1356 		info += snprintf(info, end - info, p[PI_CPUID_CONT],
   1357 		    dst->ps_cpuid[i]);
   1358 	}
   1359 
   1360 	/* Print the last cpuid, if necessary */
   1361 	if (dst->ps_ncores > 1) {
   1362 		last = (dst->ps_ncores == 2) ? PI_CPUID_PAIR : PI_CPUID_LAST;
   1363 		info += snprintf(info, end - info,
   1364 		    dgettext(TEXT_DOMAIN, p[last]), dst->ps_cpuid[i]);
   1365 	}
   1366 
   1367 	info += snprintf(info, end - info, p[PI_SPEED], dst->ps_speed);
   1368 	info += snprintf(info, end - info, p[PI_ECACHE], dst->ps_ecache);
   1369 }
   1370 
   1371 void
   1372 ap_info(apd_t *a, cfga_info_t info, ap_target_t tgt)
   1373 {
   1374 	int parsable = ap_getopt(a, OPT_PARSABLE);
   1375 
   1376 	DBG("ap_info(%p, %d)\n", (void *)info, parsable);
   1377 
   1378 	switch (tgt) {
   1379 	case AP_BOARD:
   1380 		bd_info(a, info, parsable);
   1381 		break;
   1382 	case AP_CPU:
   1383 		cpu_info(a, info, parsable);
   1384 		break;
   1385 	case AP_MEM:
   1386 		mem_info(a, info, parsable);
   1387 		break;
   1388 	case AP_IO:
   1389 		io_info(a, info, parsable);
   1390 		break;
   1391 	case AP_CMP:
   1392 		cmp_info(a, info, parsable);
   1393 		break;
   1394 	default:
   1395 		break;
   1396 	}
   1397 }
   1398