xref: /illumos-gate/usr/src/lib/cfgadm_plugins/sbd/common/ap.c (revision 88f8b78a88cbdc6d8c1af5c3e54bc49d25095c98)
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 2004 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 <assert.h>
30 #include <ctype.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <macros.h>
36 #include <dirent.h>
37 #include <libgen.h>
38 #include <libdevinfo.h>
39 #define	CFGA_PLUGIN_LIB
40 #include <config_admin.h>
41 #include "ap.h"
42 
43 int
44 ap_symid(apd_t *a, char *apid, char *symid, size_t bufsize)
45 {
46 	int n;
47 	int rc;
48 	char path[MAXPATHLEN];
49 	char *p;
50 	DIR *dirp;
51 	struct dirent *dp;
52 
53 	*symid = '\0';
54 	n = sprintf(path, "/dev/cfg/");
55 	rc = -1;
56 
57 	if ((dirp = opendir(path)) == NULL)
58 		return (rc);
59 
60 	p = path + n;
61 
62 	while ((dp = readdir(dirp)) != NULL) {
63 		char buf[MAXPATHLEN];
64 		char *cp;
65 		size_t len;
66 
67 		*p = '\0';
68 		(void) strcat(path, dp->d_name);
69 		if ((len = readlink(path, buf, sizeof (buf))) == (size_t)-1)
70 			continue;
71 		buf[len] = '\0';
72 
73 		len = strlen("../");
74 		cp = buf;
75 		while (strncmp(cp, "../", len) == 0)
76 			cp += len;
77 		if (cp != buf)
78 			cp--;	/* Get the '/' */
79 
80 		if (strcmp(cp, apid) == 0) {
81 			snprintf(symid, bufsize, "%s", dp->d_name);
82 			rc = 0;
83 			break;
84 		}
85 	}
86 
87 	closedir(dirp);
88 	return (rc);
89 }
90 
91 char *
92 ap_logid(apd_t *a, char *apid)
93 {
94 	int n;
95 	char *buf;
96 
97 	if ((buf = calloc(1, MAXPATHLEN)) == NULL)
98 		return (NULL);
99 
100 	/*
101 	 * Look for a symlink.  On any error, fallback to
102 	 * driver and instance based logical ap_ids.
103 	 */
104 	if (ap_symid(a, apid, buf, MAXPATHLEN) == 0)
105 		n = strlen(buf);
106 	else
107 		n = snprintf(buf, MAXPATHLEN, "%s%d:%s",
108 		    a->drv, a->inst, a->minor);
109 	/*
110 	 * Append the dynamic portion, if any.
111 	 */
112 	if (a->cid != NULL)
113 		(void) snprintf(&buf[n], MAXPATHLEN - n, "::%s", a->cid);
114 
115 	return (buf);
116 }
117 
118 int
119 ap_parse(apd_t *a, const char *ap_id)
120 {
121 	int i;
122 	int rc;
123 	int phys;
124 	char c;
125 	char *s;
126 	char *p;
127 	char *q;
128 	char *base;
129 	int len;
130 	char *t;
131 
132 	if (a == NULL)
133 		return (-1);
134 
135 	a->cnum = -1;
136 	a->bnum = -1;
137 	a->inst = -1;
138 	a->apid = ap_id;
139 	rc = ERR_NONE;
140 
141 	if (!str_valid(ap_id)) {
142 		rc = ERR_AP_INVAL;
143 		goto done;
144 	}
145 
146 	if ((a->path = strdup(ap_id)) == NULL) {
147 		rc = ERR_NOMEM;
148 		goto done;
149 	}
150 
151 	/*
152 	 * For a physical ap_id, look only at the base part.
153 	 * For a logical/symbolic one, use the entire ap_id.
154 	 */
155 	if (strncmp(a->path, DEVDIR, strlen(DEVDIR)) == 0) {
156 		phys = 1;
157 		base = strrchr((const char *)a->path, '/') + 1;
158 	} else {
159 		phys = 0;
160 		base = a->path;
161 		if ((a->target = strdup(a->path)) == NULL) {
162 			rc = ERR_NOMEM;
163 			goto done;
164 		}
165 	}
166 
167 	if ((s = strchr(base, ':')) == NULL || s[1] == ':') {
168 		/*
169 		 * No ':' found, or got a '::'.  If this is a physical
170 		 * ap_id, it must have a minor separtor ':' which must
171 		 * appear before the dynamic part (starting with '::').
172 		 * For a symbolic ap_id, skip looking for driver/minor
173 		 * names.
174 		 */
175 		if (phys) {
176 			rc = ERR_AP_INVAL;
177 			goto done;
178 		} else
179 			s = base;
180 	} else {
181 		/*
182 		 * Look for driver name/instance only up to the first ':',
183 		 * i.e. up to the minor node name.
184 		 */
185 		*s = '\0';
186 
187 		if ((p = strchr(base, '@')) != NULL) {
188 			/*
189 			 * Get the driver name/instance.
190 			 */
191 			*p = '\0';
192 			if ((a->drv = strdup(base)) == NULL) {
193 				rc = ERR_NOMEM;
194 				goto done;
195 			}
196 			*p++ = '@';
197 
198 			i = strtol(p, &q, 10);
199 			if (q > p)
200 				a->inst = i;
201 		}
202 
203 		*s++ = ':';
204 		a->minor = s;
205 	}
206 
207 	/*
208 	 * Need to go to the end of the string before the :: if any
209 	 * If the string is null then we are done
210 	 */
211 	t = strstr(s, "::");
212 	if (t != NULL)
213 		len = strlen(t);
214 	else
215 		len = 0;
216 
217 	s += (strlen(s) - len);
218 
219 	p = s;
220 
221 	if (*p == '\0')
222 		a->tgt = AP_BOARD;
223 	else if (strncmp(p, "::", 2) != 0) {
224 		rc = ERR_AP_INVAL;
225 		goto done;
226 	} else {
227 		/*
228 		 * Save the component id.
229 		 */
230 		*p++ = '\0';
231 		*p++ = '\0';
232 		a->cid = p;
233 	}
234 
235 	/*
236 	 * Get the operation target, e.g. slot0, slot0::cpu0.
237 	 * At this point, a->path points to the /devices path
238 	 * minus the dynamic part, for a physical ap_id. In
239 	 * the case of a logical ap_id, the target is already
240 	 * initialized above.
241 	 */
242 	if (phys != 0 && (a->target = ap_logid(a, a->path)) == NULL) {
243 		rc = ERR_NOMEM;
244 		goto done;
245 	}
246 
247 	if (a->tgt == AP_BOARD)
248 		goto done;
249 
250 	while ((*p != '\0') && !isdigit(*p))
251 		p++;
252 
253 	/*
254 	 * Get the component unit number, if present.
255 	 */
256 	i = strtol(p, &s, 10);
257 	/*
258 	 * There must be no characters after the unit number.
259 	 */
260 	if (*s != '\0') {
261 		rc = ERR_CM_INVAL;
262 		goto done;
263 	}
264 	if (s > p) {
265 		/*
266 		 * Disallow leading zeroes, e.g. cpu00, cpu01, cpu001.
267 		 * If there are 2 or more digits and the first is a zero,
268 		 * we fail.
269 		 */
270 		if ((s-p) >= 2 && *p == '0') {
271 			rc = ERR_CM_INVAL;
272 			goto done;
273 		}
274 		a->cnum = i;
275 	}
276 
277 	c = *p;
278 	*p = '\0';
279 	if ((a->cname = strdup(a->cid)) == NULL)
280 		rc = ERR_NOMEM;
281 	*p = c;
282 done:
283 	switch (rc) {
284 	case ERR_NONE:
285 		break;
286 	case ERR_CM_INVAL:
287 		ap_err(a, ERR_CM_INVAL, a->cid);
288 		break;
289 	default:
290 		ap_err(a, rc);
291 		break;
292 	}
293 
294 	DBG("path=<%s> ", a->path ? a->path : "");
295 	DBG("drv=<%s> inst=%d minor=<%s> ",
296 		a->drv ? a->drv : "", a->inst, a->minor ? a->minor : "");
297 	DBG("target=<%s>\n", a->target ? a->target : "");
298 	DBG("cid=<%s> ", a->cid ? a->cid : "");
299 	DBG("cname=<%s> ", a->cname ? a->cname : "");
300 	DBG("cnum=%d\n", a->cnum);
301 	DBG("tgt=%d opts=%x\n", a->tgt, a->opts.flags);
302 
303 	return (rc == ERR_NONE? 0 :  -1);
304 }
305 
306 /*
307  * Command table.
308  *
309  * The first set of commands in the table are in sequencing order,
310  * for example, the first group starts with assign and ends with
311  * configure.  command sequencer relies on this ordering.
312  */
313 static char *
314 ap_cmd_names[] = {
315 	"assign",
316 	"poweron",
317 	"test",
318 	"connect",
319 	"configure",
320 	"notify online",
321 	"notify add capacity",
322 	"suspend check",
323 	"request suspend",
324 	"request delete capacity",
325 	"request offline",
326 	"unconfigure",
327 	"notify remove",
328 	"notify capacity change",
329 	"disconnect",
330 	"poweroff",
331 	"unassign",
332 	"notify resume",
333 	"status",
334 	"getncm",
335 	"passthru",
336 	"help",
337 	"errtest",
338 	NULL
339 };
340 
341 char *
342 ap_cmd_name(int i)
343 {
344 	return (ap_cmd_names[min(i, CMD_NONE)]);
345 }
346 
347 static char *
348 ap_opt_names[] = {
349 	"unassign",
350 	"skip",
351 	"parsable",
352 	"nopoweroff",
353 	"code",
354 	"mid",
355 	"err",
356 	"platform",
357 	"sim",
358 	NULL
359 };
360 
361 char *
362 ap_opt_name(int i)
363 {
364 	return (ap_opt_names[i]);
365 }
366 
367 /*
368  * Command descriptor.
369  *
370  * Each command has a (command) mask specifying the AP target classes
371  * it operates on, e.g. the assign command applies only to boards.
372  * In addition each AP target class has a separate option mask specifying
373  * which command options are valid for that target class.
374  * A global value mask specifies which options require values.
375  */
376 typedef struct {
377 	int cmd;
378 	uint_t cmask;
379 	uint_t omask[AP_NCLASS];
380 } ap_cmd_t;
381 
382 /*
383  * Command option definitions.
384  */
385 #define	SHFT(i)	((uint_t)1 << (i))
386 #define	NULOPT	0
387 #define	ALLOPT	0xffffffff
388 #define	CMNOPT	(SHFT(OPT_VERBOSE)|SHFT(OPT_PLATFORM)|SHFT(OPT_SIM))
389 #define	CMFOPT	(CMNOPT|SHFT(OPT_FORCE))
390 #define	STSOPT	(CMNOPT|SHFT(OPT_PARSABLE))
391 #define	BRDDCN	(CMNOPT|SHFT(OPT_UNASSIGN)|SHFT(OPT_NOPOWEROFF))
392 
393 #define	BRD	SHFT(AP_BOARD)
394 #define	BIO	SHFT(AP_BOARD)|SHFT(AP_IO)
395 #define	ALL	(BRD|SHFT(AP_CPU)|SHFT(AP_MEM)|SHFT(AP_IO)|SHFT(AP_CMP))
396 
397 static ap_cmd_t
398 ap_cmds[] = {
399 	/*
400 	 *	cmd		cmd	 board	 cpu	 mem	 io	cmp
401 	 *			cmask	 omask	 omask   omask   omask	omask
402 	 */
403 	{CMD_ASSIGN,		BRD, 0, CMNOPT, NULOPT, NULOPT, NULOPT, NULOPT},
404 	{CMD_UNASSIGN,		BRD, 0, CMNOPT, NULOPT, NULOPT, NULOPT, NULOPT},
405 	{CMD_POWERON,		BRD, 0, CMNOPT, NULOPT, NULOPT, NULOPT, NULOPT},
406 	{CMD_POWEROFF,		BRD, 0, CMNOPT, NULOPT, NULOPT, NULOPT, NULOPT},
407 	{CMD_CONNECT,		BRD, 0, CMFOPT, NULOPT, NULOPT, NULOPT, NULOPT},
408 	{CMD_DISCONNECT,	BRD, 0, BRDDCN, NULOPT, NULOPT, NULOPT, NULOPT},
409 	{CMD_CONFIGURE,		ALL, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
410 	{CMD_UNCONFIGURE,	ALL, 0, CMFOPT, CMFOPT, CMFOPT, CMFOPT, CMNOPT},
411 	{CMD_RCM_OFFLINE,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
412 	{CMD_RCM_ONLINE,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
413 	{CMD_RCM_SUSPEND,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
414 	{CMD_RCM_RESUME,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
415 	{CMD_RCM_CAP_ADD,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
416 	{CMD_RCM_CAP_DEL,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
417 	{CMD_RCM_CAP_NOTIFY,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
418 	{CMD_RCM_REMOVE,	BIO, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
419 	{CMD_TEST,		BRD, 0, CMFOPT, NULOPT, NULOPT, NULOPT, NULOPT},
420 	{CMD_STATUS,		ALL, 0, STSOPT, STSOPT, STSOPT, STSOPT, STSOPT},
421 	{CMD_GETNCM,		BRD, 0, CMNOPT, NULOPT, NULOPT, NULOPT, NULOPT},
422 	{CMD_PASSTHRU,		ALL, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
423 	{CMD_HELP,		ALL, 0, CMNOPT, CMNOPT, CMNOPT, CMNOPT, CMNOPT},
424 	{CMD_ERRTEST,		ALL, 0, ALLOPT, ALLOPT, ALLOPT, ALLOPT, ALLOPT},
425 	{CMD_NONE,		0,   0,	0,	0,	0,	0,	0    }
426 };
427 
428 /*
429  * Global mask for options that require values.
430  */
431 #define	AP_VMASK (\
432 	SHFT(OPT_CODE)|SHFT(OPT_MID)|SHFT(OPT_ERR)| \
433 	SHFT(OPT_PLATFORM)|SHFT(OPT_SKIP))
434 
435 #if SBD_DEBUG
436 void
437 ap_cmds_dump()
438 {
439 	int i;
440 	ap_cmd_t *acp;
441 
442 	dbg("vmask=0x%x\n", AP_VMASK);
443 	dbg("%23s%5s%5s%9s%9s%9s%9s%9s\n",
444 	    "cmd", "msk", "none", "brd", "cpu", "mem", "io", "cmp");
445 
446 	for (acp = ap_cmds; acp->cmd != CMD_NONE; acp++) {
447 		dbg("%23s%5x%5x", ap_cmd_name(acp->cmd), acp->cmask,
448 		    acp->omask[AP_NONE]);
449 		for (i = AP_BOARD; i < AP_NCLASS; i++) {
450 			dbg("%9x", acp->omask[i]);
451 		}
452 		dbg("\n");
453 	}
454 }
455 #endif
456 
457 int
458 ap_state_cmd(cfga_cmd_t i, int *cmd)
459 {
460 	int c;
461 	int rc;
462 
463 	rc = CFGA_OK;
464 
465 	switch (i) {
466 	case CFGA_CMD_CONNECT:
467 		c = CMD_CONNECT;
468 		break;
469 	case CFGA_CMD_DISCONNECT:
470 		c = CMD_DISCONNECT;
471 		break;
472 	case CFGA_CMD_CONFIGURE:
473 		c = CMD_CONFIGURE;
474 		break;
475 	case CFGA_CMD_UNCONFIGURE:
476 		c = CMD_UNCONFIGURE;
477 		break;
478 	case CFGA_CMD_LOAD:
479 	case CFGA_CMD_UNLOAD:
480 		rc = CFGA_OPNOTSUPP;
481 		c = CMD_NONE;
482 		break;
483 	default:
484 		rc = CFGA_INVAL;
485 		c = CMD_NONE;
486 		break;
487 	}
488 
489 	*cmd = c;
490 
491 	return (rc);
492 }
493 
494 static int
495 ap_cmd(char *name)
496 {
497 	int i;
498 	char **p;
499 
500 	if (name == NULL)
501 		return (CMD_NONE);
502 
503 	for (i = 0, p = ap_cmd_names; *p != NULL; p++, i++)
504 		if (strcmp(*p, name) == 0)
505 			break;
506 	if (*p == NULL)
507 		i = CMD_NONE;
508 
509 	return (i);
510 }
511 
512 static int
513 ap_opt_parse(apd_t *a, ap_cmd_t *acp, const char *options)
514 {
515 	char *optstr;
516 	ap_opts_t *opts;
517 
518 	/*
519 	 * Set default values.
520 	 */
521 	opts = &a->opts;
522 	opts->mid = (char *)a->class;
523 	opts->err = ERR_CMD_FAIL;
524 
525 	if (options == NULL)
526 		return (0);
527 
528 	if ((optstr = strdup(options)) == NULL) {
529 		ap_err(a, ERR_NOMEM);
530 		return (-1);
531 	}
532 
533 	a->options = optstr;
534 
535 	if (acp->cmd == CMD_PASSTHRU)
536 		return (0);
537 
538 	while (*optstr != '\0') {
539 		int i;
540 		int opt;
541 		int omask;
542 		char *p;
543 		char *value;
544 		char *optname;
545 
546 		value = NULL;
547 		opt = getsubopt(&optstr, ap_opt_names, &value);
548 
549 		DBG("opt=%d\n", opt);
550 
551 		if (opt == -1) {
552 			ap_err(a, ERR_OPT_INVAL, value);
553 			return (-1);
554 		}
555 
556 		optname = ap_opt_names[opt];
557 		omask = acp->omask[a->tgt];
558 
559 		i = mask(opt) & omask;
560 
561 		DBG("tgt=%d opt=%x omask=%x\n", a->tgt, mask(opt), omask);
562 
563 		if (i == 0) {
564 			ap_err(a, ERR_OPT_INVAL, optname);
565 			return (-1);
566 		}
567 
568 		/*
569 		 * Check whether the option requires a value.
570 		 */
571 		i = mask(opt) & AP_VMASK;
572 		if (i != 0 && value == NULL) {
573 			ap_err(a, ERR_OPT_NOVAL, optname);
574 			return (-1);
575 		} else if (i == 0 && value != NULL) {
576 			ap_err(a, ERR_OPT_VAL, optname);
577 			return (-1);
578 		}
579 
580 		if (value == NULL)
581 			assert(opt != OPT_CODE);	/* XXX prefix */
582 
583 		/*
584 		 * Set the options's value.
585 		 */
586 		switch (opt) {
587 		case OPT_SIM:
588 		case OPT_PARSABLE:
589 		case OPT_UNASSIGN:
590 			break;
591 		case OPT_CODE:
592 			i = strtol(value, &p, 10);
593 			if (p > value)
594 				opts->code = i;
595 			break;
596 		case OPT_MID:
597 			opts->mid = value;
598 			break;
599 		case OPT_ERR:
600 			i = strtol(value, &p, 10);
601 			if (p > value)
602 				opts->err = i;
603 			break;
604 		case OPT_NOPOWEROFF:
605 			i = ap_cmd("poweroff");
606 			opts->skip |= mask(i);
607 			break;
608 		case OPT_SKIP:	/* for debugging */
609 			/*
610 			 * The skip value may be a ':' separated
611 			 * list of steps (commands) to be skipped
612 			 * during sequencing.
613 			 */
614 			for (p = strtok(value, ":"); p != NULL;
615 				p = strtok(NULL, ":")) {
616 				if ((i = ap_cmd(p)) == CMD_NONE) {
617 					ap_err(a, ERR_CMD_INVAL, p);
618 					return (-1);
619 				}
620 				opts->skip |= mask(i);
621 			}
622 			break;
623 		case OPT_PLATFORM:
624 			opts->platform = value;
625 			break;
626 		default:
627 			ap_err(a, ERR_OPT_INVAL, optname);
628 			return (-1);
629 		}
630 
631 		ap_setopt(a, opt);
632 	}
633 
634 	return (0);
635 }
636 
637 static ap_cmd_t *
638 ap_cmdp(int cmd)
639 {
640 	ap_cmd_t *acp;
641 
642 	for (acp = ap_cmds; acp->cmd != CMD_NONE; acp++)
643 		if (acp->cmd == cmd)
644 			break;
645 
646 	if (acp->cmd == CMD_NONE)
647 		return (NULL);
648 
649 	return (acp);
650 }
651 
652 cfga_err_t
653 ap_cmd_parse(apd_t *a, const char *f, const char *options, int *cmd)
654 {
655 	int c;
656 	int all;
657 	int tgt;
658 	int target;
659 	ap_cmd_t *acp;
660 	cfga_err_t rc;
661 
662 #ifdef	_SBD_DEBUG
663 	ap_cmds_dump();
664 #endif
665 
666 	rc = CFGA_INVAL;
667 
668 	if ((c = ap_cmd((char *)f)) == CMD_NONE ||
669 	    (acp = ap_cmdp(c)) == NULL) {
670 		ap_err(a, ERR_CMD_INVAL, f);
671 		return (rc);
672 	}
673 
674 	/*
675 	 * Change a->statonly to 1, if the case is CMD_STATUS.  We are only
676 	 * wanting to read the devices and no more
677 	 */
678 	/*
679 	 * Get the status for all components if either the list all
680 	 * option being specified or if we are configuring/unconfiguring
681 	 * the board.  The latter is needed for the RCM interface.
682 	 */
683 	switch (c) {
684 	case CMD_STATUS:
685 		all = ap_getopt(a, OPT_LIST_ALL);
686 		a->statonly = 1;
687 		break;
688 	case CMD_CONFIGURE:
689 	case CMD_UNCONFIGURE:
690 	case CMD_CONNECT:
691 	case CMD_DISCONNECT:
692 		all = (a->tgt == AP_BOARD);
693 		a->statonly = 0;
694 		break;
695 	default:
696 		all = 0;
697 		a->statonly = 0;
698 		break;
699 	}
700 
701 	if ((rc = apd_init(a, all)) != CFGA_OK)
702 		return (rc);
703 
704 	rc = CFGA_INVAL;
705 
706 	/*
707 	 * Get the target here in case it is a component in which
708 	 * case its type is known after the initialization.
709 	 */
710 	tgt = a->tgt;
711 	target = mask(tgt);
712 
713 	DBG("cmd=%s(%d) tmask=0x%x cmask=0x%x omask=0x%x\n",
714 		ap_cmd_name(c), c, target,
715 		acp->cmask, acp->omask[tgt]);
716 
717 	if ((acp->cmask & target) == 0)
718 		ap_err(a, ERR_CMD_NOTSUPP, c);
719 	else if (options != NULL && acp->omask[tgt] == 0)
720 		ap_err(a, ERR_OPT_INVAL, options);
721 	else if (ap_opt_parse(a, acp, options) != -1) {
722 		if (c == CMD_STATUS)
723 			rc = ap_platopts_check(a, c, c);
724 		else
725 			rc = CFGA_OK;
726 	}
727 
728 	if (cmd)
729 		*cmd = c;
730 
731 	return (rc);
732 }
733 
734 int
735 ap_cnt(apd_t *a)
736 {
737 	int cnt;
738 
739 	if ((a->tgt == AP_BOARD) && ap_getopt(a, OPT_LIST_ALL))
740 		cnt = a->ncm + 1;
741 	else
742 		cnt = 1;
743 
744 	return (cnt);
745 }
746