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