xref: /illumos-gate/usr/src/cmd/ccidadm/ccidadm.c (revision 8119dad84d6416f13557b0ba8e2aaf9064cbcfd3)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2019, Joyent, Inc.
14  * Copyright 2024 Oxide Computer Company
15  */
16 
17 /*
18  * Print out information about a CCID device.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <fcntl.h>
24 #include <err.h>
25 #include <stdlib.h>
26 #include <strings.h>
27 #include <unistd.h>
28 #include <ofmt.h>
29 #include <libgen.h>
30 #include <ctype.h>
31 #include <errno.h>
32 #include <limits.h>
33 #include <libcmdutils.h>
34 #include <fts.h>
35 #include <sys/ilstr.h>
36 
37 #include <sys/usb/clients/ccid/uccid.h>
38 #include <atr.h>
39 
40 #define	EXIT_USAGE	2
41 
42 static const char *ccidadm_pname;
43 
44 #define	CCID_ROOT	"/dev/ccid/"
45 
46 typedef enum {
47 	CCIDADM_LIST_DEVICE,
48 	CCIDADM_LIST_PRODUCT,
49 	CCIDADM_LIST_STATE,
50 	CCIDADM_LIST_TRANSPORT,
51 	CCIDADM_LIST_SUPPORTED,
52 } ccidadm_list_index_t;
53 
54 typedef struct ccidadm_pair {
55 	uint32_t	ccp_val;
56 	const char	*ccp_name;
57 } ccidadm_pair_t;
58 
59 typedef struct ccid_list_ofmt_arg {
60 	const char		*cloa_name;
61 	uccid_cmd_status_t	*cloa_status;
62 } ccid_list_ofmt_arg_t;
63 
64 /*
65  * Attempt to open a CCID slot specified by a user. In general, we expect that
66  * users will use a path like "ccid0/slot0". However, they may also specify a
67  * full path. If the card boolean is set to true, that means that they may have
68  * just specified "ccid0", so we need to try to open up the default slot.
69  */
70 static int
71 ccidadm_open(const char *base, boolean_t card)
72 {
73 	int fd;
74 	char buf[PATH_MAX];
75 
76 	/*
77 	 * If it's an absolute path, just try to open it.
78 	 */
79 	if (base[0] == '/') {
80 		return (open(base, O_RDWR));
81 	}
82 
83 	/*
84 	 * For a card, try to append slot0 first.
85 	 */
86 	if (card) {
87 		if (snprintf(buf, sizeof (buf), "%s/%s/slot0", CCID_ROOT,
88 		    base) >= sizeof (buf)) {
89 			errno = ENAMETOOLONG;
90 			return (-1);
91 		}
92 
93 		if ((fd = open(buf, O_RDWR)) >= 0) {
94 			return (fd);
95 		}
96 
97 		if (errno != ENOENT && errno != ENOTDIR) {
98 			return (fd);
99 		}
100 	}
101 
102 	if (snprintf(buf, sizeof (buf), "%s/%s", CCID_ROOT, base) >=
103 	    sizeof (buf)) {
104 		errno = ENAMETOOLONG;
105 		return (-1);
106 	}
107 
108 	return (open(buf, O_RDWR));
109 }
110 
111 static void
112 ccidadm_iter(boolean_t readeronly, boolean_t newline,
113     void(*cb)(int, const char *, void *), void *arg)
114 {
115 	FTS *fts;
116 	FTSENT *ent;
117 	char *const paths[] = { CCID_ROOT, NULL };
118 	int fd;
119 	boolean_t first = B_TRUE;
120 
121 	fts = fts_open(paths, FTS_LOGICAL | FTS_NOCHDIR, NULL);
122 	if (fts == NULL) {
123 		err(EXIT_FAILURE, "failed to create directory stream");
124 	}
125 
126 	while ((ent = fts_read(fts)) != NULL) {
127 		const char *name;
128 
129 		/* Skip the root and post-order dirs */
130 		if (ent->fts_level == 0 || ent->fts_info == FTS_DP) {
131 			continue;
132 		}
133 		if (readeronly && ent->fts_level != 1) {
134 			continue;
135 		} else if (!readeronly && ent->fts_level != 2) {
136 			continue;
137 		}
138 
139 		if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS) {
140 			warn("skipping %s, failed to get information: %s",
141 			    ent->fts_name, strerror(ent->fts_errno));
142 			continue;
143 		}
144 
145 		name = ent->fts_path + strlen(CCID_ROOT);
146 		if ((fd = ccidadm_open(name, readeronly)) < 0) {
147 			err(EXIT_FAILURE, "failed to open %s", name);
148 		}
149 
150 		if (!first && newline) {
151 			(void) printf("\n");
152 		}
153 		first = B_FALSE;
154 		cb(fd, name, arg);
155 		(void) close(fd);
156 	}
157 
158 	(void) fts_close(fts);
159 }
160 
161 static void
162 ccidadm_list_slot_status_str(uccid_cmd_status_t *ucs, ilstr_t *s)
163 {
164 	if (!(ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT)) {
165 		ilstr_append_str(s, "missing");
166 		return;
167 	}
168 
169 	if (!(ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE)) {
170 		ilstr_append_str(s, "un");
171 	}
172 
173 	ilstr_append_str(s, "activated");
174 }
175 
176 static void
177 ccidadm_list_slot_transport_str(uccid_cmd_status_t *ucs, ilstr_t *s)
178 {
179 	uint_t bits = CCID_CLASS_F_TPDU_XCHG | CCID_CLASS_F_SHORT_APDU_XCHG |
180 	    CCID_CLASS_F_EXT_APDU_XCHG;
181 
182 	switch (ucs->ucs_class.ccd_dwFeatures & bits) {
183 	case 0:
184 		ilstr_append_str(s, "character");
185 		break;
186 	case CCID_CLASS_F_TPDU_XCHG:
187 		ilstr_append_str(s, "TPDU");
188 		break;
189 	case CCID_CLASS_F_SHORT_APDU_XCHG:
190 	case CCID_CLASS_F_EXT_APDU_XCHG:
191 		ilstr_append_str(s, "APDU");
192 		break;
193 	default:
194 		ilstr_append_str(s, "unknown");
195 		break;
196 	}
197 
198 	if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) != 0) {
199 		switch (ucs->ucs_prot) {
200 		case UCCID_PROT_T0:
201 			ilstr_append_str(s, " (T=0)");
202 			break;
203 		case UCCID_PROT_T1:
204 			ilstr_append_str(s, " (T=1)");
205 			break;
206 		default:
207 			break;
208 		}
209 	}
210 }
211 
212 static void
213 ccidadm_list_slot_usable_str(uccid_cmd_status_t *ucs, ilstr_t *s)
214 {
215 	ccid_class_features_t feat;
216 	uint_t prot = CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG;
217 	uint_t param = CCID_CLASS_F_AUTO_PARAM_NEG | CCID_CLASS_F_AUTO_PPS;
218 	uint_t clock = CCID_CLASS_F_AUTO_BAUD | CCID_CLASS_F_AUTO_ICC_CLOCK;
219 
220 	feat = ucs->ucs_class.ccd_dwFeatures;
221 
222 	if ((feat & prot) == 0 ||
223 	    (feat & param) != param ||
224 	    (feat & clock) != clock) {
225 		ilstr_append_str(s, "un");
226 	}
227 
228 	ilstr_append_str(s, "supported");
229 }
230 
231 static boolean_t
232 ccidadm_list_ofmt_cb(ofmt_arg_t *ofmt, char *buf, uint_t buflen)
233 {
234 	ccid_list_ofmt_arg_t *cloa = ofmt->ofmt_cbarg;
235 	ilstr_t s;
236 
237 	ilstr_init_prealloc(&s, buf, buflen);
238 
239 	switch (ofmt->ofmt_id) {
240 	case CCIDADM_LIST_DEVICE:
241 		ilstr_append_str(&s, cloa->cloa_name);
242 		break;
243 	case CCIDADM_LIST_PRODUCT:
244 		ilstr_append_str(&s, cloa->cloa_status->ucs_product);
245 		break;
246 	case CCIDADM_LIST_STATE:
247 		ccidadm_list_slot_status_str(cloa->cloa_status, &s);
248 		break;
249 	case CCIDADM_LIST_TRANSPORT:
250 		ccidadm_list_slot_transport_str(cloa->cloa_status, &s);
251 		break;
252 	case CCIDADM_LIST_SUPPORTED:
253 		ccidadm_list_slot_usable_str(cloa->cloa_status, &s);
254 		break;
255 	default:
256 		return (B_FALSE);
257 	}
258 
259 	return (ilstr_errno(&s) == ILSTR_ERROR_OK);
260 }
261 
262 static void
263 ccidadm_list_slot(int slotfd, const char *name, void *arg)
264 {
265 	uccid_cmd_status_t ucs;
266 	ofmt_handle_t ofmt = arg;
267 	ccid_list_ofmt_arg_t cloa;
268 
269 	bzero(&ucs, sizeof (ucs));
270 	ucs.ucs_version = UCCID_CURRENT_VERSION;
271 
272 	if (ioctl(slotfd, UCCID_CMD_STATUS, &ucs) != 0) {
273 		err(EXIT_FAILURE, "failed to issue status ioctl to %s", name);
274 	}
275 
276 	if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) {
277 		(void) strlcpy(ucs.ucs_product, "<unknown>",
278 		    sizeof (ucs.ucs_product));
279 	}
280 
281 	cloa.cloa_name = name;
282 	cloa.cloa_status = &ucs;
283 	ofmt_print(ofmt, &cloa);
284 }
285 
286 static ofmt_field_t ccidadm_list_fields[] = {
287 	{ "PRODUCT",	24,	CCIDADM_LIST_PRODUCT,	ccidadm_list_ofmt_cb },
288 	{ "DEVICE",	16,	CCIDADM_LIST_DEVICE,	ccidadm_list_ofmt_cb },
289 	{ "CARD STATE",	12,	CCIDADM_LIST_STATE,	ccidadm_list_ofmt_cb },
290 	{ "TRANSPORT",	12,	CCIDADM_LIST_TRANSPORT,	ccidadm_list_ofmt_cb },
291 	{ "SUPPORTED",	12,	CCIDADM_LIST_SUPPORTED,	ccidadm_list_ofmt_cb },
292 	{ NULL,		0,	0,			NULL	}
293 };
294 
295 static void
296 ccidadm_do_list(int argc, char *argv[])
297 {
298 	ofmt_handle_t ofmt;
299 
300 	if (argc != 0) {
301 		errx(EXIT_USAGE, "list command does not take arguments\n");
302 	}
303 
304 	if (ofmt_open(NULL, ccidadm_list_fields, 0, 0, &ofmt) != OFMT_SUCCESS) {
305 		errx(EXIT_FAILURE, "failed to initialize ofmt state");
306 	}
307 
308 	ccidadm_iter(B_FALSE, B_FALSE, ccidadm_list_slot, ofmt);
309 	ofmt_close(ofmt);
310 }
311 
312 static void
313 ccidadm_list_usage(FILE *out)
314 {
315 	(void) fprintf(out, "\tlist\n");
316 }
317 
318 /*
319  * Print out logical information about the ICC's ATR. This includes information
320  * about what protocols it supports, required negotiation, etc.
321  */
322 static void
323 ccidadm_atr_props(uccid_cmd_status_t *ucs)
324 {
325 	int ret;
326 	atr_data_t *data;
327 	atr_protocol_t prots, defprot;
328 	boolean_t negotiate;
329 	atr_data_rate_choice_t rate;
330 	uint32_t bps;
331 
332 	if ((data = atr_data_alloc()) == NULL) {
333 		err(EXIT_FAILURE, "failed to allocate memory for "
334 		    "ATR data");
335 	}
336 
337 	ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data);
338 	if (ret != ATR_CODE_OK) {
339 		errx(EXIT_FAILURE, "failed to parse ATR data: %s",
340 		    atr_strerror(ret));
341 	}
342 
343 	prots = atr_supported_protocols(data);
344 	(void) printf("ICC supports protocol(s): ");
345 	if (prots == ATR_P_NONE) {
346 		(void) printf("none\n");
347 		atr_data_free(data);
348 		return;
349 	}
350 
351 	(void) printf("%s\n", atr_protocol_to_string(prots));
352 
353 	negotiate = atr_params_negotiable(data);
354 	defprot = atr_default_protocol(data);
355 
356 	if (negotiate) {
357 		(void) printf("Card protocol is negotiable; starts with "
358 		    "default %s parameters\n", atr_protocol_to_string(defprot));
359 	} else {
360 		(void) printf("Card protocol is not negotiable; starts with "
361 		    "specific %s parameters\n",
362 		    atr_protocol_to_string(defprot));
363 	}
364 
365 	/*
366 	 * For each supported protocol, figure out parameters we would
367 	 * negotiate. We only need to warn about auto-negotiation if this
368 	 * is TPDU or character and specific bits are missing.
369 	 */
370 	if (((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_SHORT_APDU_XCHG |
371 	    CCID_CLASS_F_EXT_APDU_XCHG)) == 0) &&
372 	    ((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_AUTO_PARAM_NEG |
373 	    CCID_CLASS_F_AUTO_PPS)) == 0)) {
374 		(void) printf("CCID/ICC require explicit TPDU parameter/PPS "
375 		    "negotiation\n");
376 	}
377 
378 	/*
379 	 * Determine which set of Di/Fi values we should use and how we should
380 	 * get there (note a reader may not have to set them).
381 	 */
382 	rate = atr_data_rate(data, &ucs->ucs_class, NULL, 0, &bps);
383 	switch (rate) {
384 	case ATR_RATE_USEDEFAULT:
385 		(void) printf("Reader will run ICC at the default (Di=1/Fi=1) "
386 		    "speed\n");
387 		break;
388 	case ATR_RATE_USEATR:
389 		(void) printf("Reader will run ICC at ICC's Di/Fi values\n");
390 		break;
391 	case ATR_RATE_USEATR_SETRATE:
392 		(void) printf("Reader will run ICC at ICC's Di/Fi values, but "
393 		    "must set data rate to %u bps\n", bps);
394 		break;
395 	case ATR_RATE_UNSUPPORTED:
396 		(void) printf("Reader cannot run ICC due to Di/Fi mismatch\n");
397 		break;
398 	default:
399 		(void) printf("Cannot determine Di/Fi rate, unexpected "
400 		    "value: %u\n", rate);
401 		break;
402 	}
403 	if (prots & ATR_P_T0) {
404 		uint8_t fi, di;
405 		atr_convention_t conv;
406 		atr_clock_stop_t clock;
407 
408 		fi = atr_fi_index(data);
409 		di = atr_di_index(data);
410 		conv = atr_convention(data);
411 		clock = atr_clock_stop(data);
412 		(void) printf("T=0 properties that would be negotiated:\n");
413 		(void) printf("  + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n",
414 		    fi, atr_fi_index_to_string(fi),
415 		    atr_fmax_index_to_string(fi));
416 		(void) printf("  + Di Index: %u (Di %s)\n", di,
417 		    atr_di_index_to_string(di));
418 		(void) printf("  + Clock Convention: %u (%s)\n", conv,
419 		    atr_convention_to_string(conv));
420 		(void) printf("  + Extra Guardtime: %u\n",
421 		    atr_extra_guardtime(data));
422 		(void) printf("  + WI: %u\n", atr_t0_wi(data));
423 		(void) printf("  + Clock Stop: %u (%s)\n", clock,
424 		    atr_clock_stop_to_string(clock));
425 	}
426 
427 	if (prots & ATR_P_T1) {
428 		uint8_t fi, di;
429 		atr_clock_stop_t clock;
430 		atr_t1_checksum_t cksum;
431 
432 		fi = atr_fi_index(data);
433 		di = atr_di_index(data);
434 		clock = atr_clock_stop(data);
435 		cksum = atr_t1_checksum(data);
436 		(void) printf("T=1 properties that would be negotiated:\n");
437 		(void) printf("  + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n",
438 		    fi, atr_fi_index_to_string(fi),
439 		    atr_fmax_index_to_string(fi));
440 		(void) printf("  + Di Index: %u (Di %s)\n", di,
441 		    atr_di_index_to_string(di));
442 		(void) printf("  + Checksum: %s\n",
443 		    cksum == ATR_T1_CHECKSUM_CRC ? "CRC" : "LRC");
444 		(void) printf("  + Extra Guardtime: %u\n",
445 		    atr_extra_guardtime(data));
446 		(void) printf("  + BWI: %u\n", atr_t1_bwi(data));
447 		(void) printf("  + CWI: %u\n", atr_t1_cwi(data));
448 		(void) printf("  + Clock Stop: %u (%s)\n", clock,
449 		    atr_clock_stop_to_string(clock));
450 		(void) printf("  + IFSC: %u\n", atr_t1_ifsc(data));
451 		(void) printf("  + CCID Supports NAD: %s\n",
452 		    ucs->ucs_class.ccd_dwFeatures & CCID_CLASS_F_ALTNAD_SUP ?
453 		    "yes" : "no");
454 	}
455 
456 	atr_data_free(data);
457 }
458 
459 static void
460 ccidadm_atr_verbose(uccid_cmd_status_t *ucs)
461 {
462 	int ret;
463 	atr_data_t *data;
464 
465 	if ((data = atr_data_alloc()) == NULL) {
466 		err(EXIT_FAILURE, "failed to allocate memory for "
467 		    "ATR data");
468 	}
469 
470 	ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data);
471 	if (ret != ATR_CODE_OK) {
472 		errx(EXIT_FAILURE, "failed to parse ATR data: %s",
473 		    atr_strerror(ret));
474 	}
475 	atr_data_dump(data, stdout);
476 	atr_data_free(data);
477 }
478 
479 typedef struct cciadm_atr_args {
480 	boolean_t caa_hex;
481 	boolean_t caa_props;
482 	boolean_t caa_verbose;
483 } ccidadm_atr_args_t;
484 
485 static void
486 ccidadm_atr_fetch(int fd, const char *name, void *arg)
487 {
488 	uccid_cmd_status_t ucs;
489 	ccidadm_atr_args_t *caa = arg;
490 
491 	bzero(&ucs, sizeof (ucs));
492 	ucs.ucs_version = UCCID_CURRENT_VERSION;
493 
494 	if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
495 		err(EXIT_FAILURE, "failed to issue status ioctl to %s",
496 		    name);
497 	}
498 
499 	if (ucs.ucs_atrlen == 0) {
500 		warnx("slot %s has no card inserted or activated", name);
501 		return;
502 	}
503 
504 	(void) printf("ATR for %s (%u bytes):\n", name, ucs.ucs_atrlen);
505 	if (caa->caa_props) {
506 		ccidadm_atr_props(&ucs);
507 	}
508 
509 	if (caa->caa_hex) {
510 		atr_data_hexdump(ucs.ucs_atr, ucs.ucs_atrlen, stdout);
511 	}
512 
513 	if (caa->caa_verbose) {
514 		ccidadm_atr_verbose(&ucs);
515 	}
516 }
517 
518 static void
519 ccidadm_do_atr(int argc, char *argv[])
520 {
521 	uint_t i;
522 	int c;
523 	ccidadm_atr_args_t caa;
524 
525 	bzero(&caa, sizeof (caa));
526 	optind = 0;
527 	while ((c = getopt(argc, argv, "vx")) != -1) {
528 		switch (c) {
529 		case 'v':
530 			caa.caa_verbose = B_TRUE;
531 			break;
532 		case 'x':
533 			caa.caa_hex = B_TRUE;
534 			break;
535 		case ':':
536 			errx(EXIT_USAGE, "Option -%c requires an argument\n",
537 			    optopt);
538 			break;
539 		case '?':
540 			errx(EXIT_USAGE, "Unknown option: -%c\n", optopt);
541 			break;
542 		}
543 	}
544 
545 	if (!caa.caa_verbose && !caa.caa_props && !caa.caa_hex) {
546 		caa.caa_props = B_TRUE;
547 	}
548 
549 	argc -= optind;
550 	argv += optind;
551 
552 	if (argc == 0) {
553 		ccidadm_iter(B_FALSE, B_TRUE, ccidadm_atr_fetch, &caa);
554 		return;
555 	}
556 
557 	for (i = 0; i < argc; i++) {
558 		int fd;
559 
560 		if ((fd = ccidadm_open(argv[i], B_FALSE)) < 0) {
561 			warn("failed to open %s", argv[i]);
562 			errx(EXIT_FAILURE, "valid CCID slot?");
563 		}
564 
565 		ccidadm_atr_fetch(fd, argv[i], &caa);
566 		(void) close(fd);
567 		if (i + 1 < argc) {
568 			(void) printf("\n");
569 		}
570 	}
571 }
572 
573 static void
574 ccidadm_atr_usage(FILE *out)
575 {
576 	(void) fprintf(out, "\tatr [-vx]\t[device] ...\n");
577 }
578 
579 static void
580 ccidadm_print_pairs(uint32_t val, ccidadm_pair_t *ccp)
581 {
582 	while (ccp->ccp_name != NULL) {
583 		if ((val & ccp->ccp_val) == ccp->ccp_val) {
584 			(void) printf("    + %s\n", ccp->ccp_name);
585 		}
586 		ccp++;
587 	}
588 }
589 
590 static ccidadm_pair_t ccidadm_p_protocols[] = {
591 	{ 0x01, "T=0" },
592 	{ 0x02, "T=1" },
593 	{ 0x0, NULL }
594 };
595 
596 static ccidadm_pair_t ccidadm_p_voltages[] = {
597 	{ CCID_CLASS_VOLT_5_0, "5.0 V" },
598 	{ CCID_CLASS_VOLT_3_0, "3.0 V" },
599 	{ CCID_CLASS_VOLT_1_8, "1.8 V" },
600 	{ 0x0, NULL }
601 };
602 
603 static ccidadm_pair_t ccidadm_p_syncprots[] = {
604 	{ 0x01, "2-Wire Support" },
605 	{ 0x02, "3-Wire Support" },
606 	{ 0x04, "I2C Support" },
607 	{ 0x0, NULL }
608 };
609 
610 static ccidadm_pair_t ccidadm_p_mechanical[] = {
611 	{ CCID_CLASS_MECH_CARD_ACCEPT, "Card Accept Mechanism" },
612 	{ CCID_CLASS_MECH_CARD_EJECT, "Card Eject Mechanism" },
613 	{ CCID_CLASS_MECH_CARD_CAPTURE, "Card Capture Mechanism" },
614 	{ CCID_CLASS_MECH_CARD_LOCK, "Card Lock/Unlock Mechanism" },
615 	{ 0x0, NULL }
616 };
617 
618 static ccidadm_pair_t ccidadm_p_features[] = {
619 	{ CCID_CLASS_F_AUTO_PARAM_ATR,
620 	    "Automatic parameter configuration based on ATR data" },
621 	{ CCID_CLASS_F_AUTO_ICC_ACTIVATE,
622 	    "Automatic activation on ICC insertion" },
623 	{ CCID_CLASS_F_AUTO_ICC_VOLTAGE, "Automatic ICC voltage selection" },
624 	{ CCID_CLASS_F_AUTO_ICC_CLOCK,
625 	    "Automatic ICC clock frequency change" },
626 	{ CCID_CLASS_F_AUTO_BAUD, "Automatic baud rate change" },
627 	{ CCID_CLASS_F_AUTO_PARAM_NEG,
628 	    "Automatic parameter negotiation by CCID" },
629 	{ CCID_CLASS_F_AUTO_PPS, "Automatic PPS made by CCID" },
630 	{ CCID_CLASS_F_ICC_CLOCK_STOP, "CCID can set ICC in clock stop mode" },
631 	{ CCID_CLASS_F_ALTNAD_SUP, "NAD value other than zero accepted" },
632 	{ CCID_CLASS_F_AUTO_IFSD, "Automatic IFSD exchange" },
633 	{ CCID_CLASS_F_TPDU_XCHG, "TPDU support" },
634 	{ CCID_CLASS_F_SHORT_APDU_XCHG, "Short APDU support" },
635 	{ CCID_CLASS_F_EXT_APDU_XCHG, "Short and Extended APDU support" },
636 	{ CCID_CLASS_F_WAKE_UP, "USB Wake Up signaling support" },
637 	{ 0x0, NULL }
638 };
639 
640 static ccidadm_pair_t ccidadm_p_pin[] = {
641 	{ CCID_CLASS_PIN_VERIFICATION, "PIN verification" },
642 	{ CCID_CLASS_PIN_MODIFICATION, "PIN modification" },
643 	{ 0x0, NULL }
644 };
645 
646 static void
647 ccidadm_reader_print(int fd, const char *name, void *unused __unused)
648 {
649 	uccid_cmd_status_t ucs;
650 	ccid_class_descr_t *cd;
651 	char nnbuf[NN_NUMBUF_SZ + 1];
652 
653 	bzero(&ucs, sizeof (uccid_cmd_status_t));
654 	ucs.ucs_version = UCCID_CURRENT_VERSION;
655 
656 	if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
657 		err(EXIT_FAILURE, "failed to issue status ioctl to %s",
658 		    name);
659 	}
660 
661 	cd = &ucs.ucs_class;
662 	(void) printf("Reader %s, CCID class v%u.%u device:\n", name,
663 	    CCID_VERSION_MAJOR(cd->ccd_bcdCCID),
664 	    CCID_VERSION_MINOR(cd->ccd_bcdCCID));
665 
666 	if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) {
667 		(void) strlcpy(ucs.ucs_product, "<unknown>",
668 		    sizeof (ucs.ucs_product));
669 	}
670 
671 	if ((ucs.ucs_status & UCCID_STATUS_F_SERIAL_VALID) == 0) {
672 		(void) strlcpy(ucs.ucs_serial, "<unknown>",
673 		    sizeof (ucs.ucs_serial));
674 	}
675 
676 	(void) printf("  Product: %s\n", ucs.ucs_product);
677 	(void) printf("  Serial: %s\n", ucs.ucs_serial);
678 	(void) printf("  Slots Present: %u\n", cd->ccd_bMaxSlotIndex + 1);
679 	(void) printf("  Maximum Busy Slots: %u\n", cd->ccd_bMaxCCIDBusySlots);
680 	(void) printf("  Supported Voltages:\n");
681 	ccidadm_print_pairs(cd->ccd_bVoltageSupport, ccidadm_p_voltages);
682 	(void) printf("  Supported Protocols:\n");
683 	ccidadm_print_pairs(cd->ccd_dwProtocols, ccidadm_p_protocols);
684 	nicenum_scale(cd->ccd_dwDefaultClock, 1000, nnbuf,
685 	    sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE);
686 	(void) printf("  Default Clock: %sHz\n", nnbuf);
687 	nicenum_scale(cd->ccd_dwMaximumClock, 1000, nnbuf,
688 	    sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE);
689 	(void) printf("  Maximum Clock: %sHz\n", nnbuf);
690 	(void) printf("  Supported Clock Rates: %u\n",
691 	    cd->ccd_bNumClockSupported);
692 	nicenum_scale(cd->ccd_dwDataRate, 1, nnbuf, sizeof (nnbuf),
693 	    NN_DIVISOR_1000 | NN_UNIT_SPACE);
694 	(void) printf("  Default Data Rate: %sbps\n", nnbuf);
695 	nicenum_scale(cd->ccd_dwMaxDataRate, 1, nnbuf, sizeof (nnbuf),
696 	    NN_DIVISOR_1000 | NN_UNIT_SPACE);
697 	(void) printf("  Maximum Data Rate: %sbps\n", nnbuf);
698 	(void) printf("  Supported Data Rates: %u\n",
699 	    cd->ccd_bNumDataRatesSupported);
700 	(void) printf("  Maximum IFSD (T=1 only): %u\n", cd->ccd_dwMaxIFSD);
701 	if (cd->ccd_dwSyncProtocols != 0) {
702 		(void) printf("  Synchronous Protocols Supported:\n");
703 		ccidadm_print_pairs(cd->ccd_dwSyncProtocols,
704 		    ccidadm_p_syncprots);
705 	}
706 	if (cd->ccd_dwMechanical != 0) {
707 		(void) printf("  Mechanical Features:\n");
708 		ccidadm_print_pairs(cd->ccd_dwMechanical, ccidadm_p_mechanical);
709 	}
710 	if (cd->ccd_dwFeatures != 0) {
711 		(void) printf("  Device Features:\n");
712 		ccidadm_print_pairs(cd->ccd_dwFeatures, ccidadm_p_features);
713 	}
714 	(void) printf("  Maximum Message Length: %u bytes\n",
715 	    cd->ccd_dwMaxCCIDMessageLength);
716 	if (cd->ccd_dwFeatures & CCID_CLASS_F_EXT_APDU_XCHG) {
717 		if (cd->ccd_bClassGetResponse == 0xff) {
718 			(void) printf("  Default Get Response Class: echo\n");
719 		} else {
720 			(void) printf("  Default Get Response Class: %u\n",
721 			    cd->ccd_bClassGetResponse);
722 		}
723 		if (cd->ccd_bClassEnvelope == 0xff) {
724 			(void) printf("  Default Envelope Class: echo\n");
725 		} else {
726 			(void) printf("  Default Envelope Class: %u\n",
727 			    cd->ccd_bClassEnvelope);
728 		}
729 	}
730 	if (cd->ccd_wLcdLayout != 0) {
731 		(void) printf("  %2ux%2u LCD present\n",
732 		    cd->ccd_wLcdLayout >> 8, cd->ccd_wLcdLayout & 0xff);
733 	}
734 
735 	if (cd->ccd_bPinSupport) {
736 		(void) printf("  Pin Support:\n");
737 		ccidadm_print_pairs(cd->ccd_bPinSupport, ccidadm_p_pin);
738 	}
739 }
740 
741 static void
742 ccidadm_do_reader(int argc, char *argv[])
743 {
744 	int i;
745 
746 	if (argc == 0) {
747 		ccidadm_iter(B_TRUE, B_TRUE, ccidadm_reader_print, NULL);
748 		return;
749 	}
750 
751 	for (i = 0; i < argc; i++) {
752 		int fd;
753 
754 		if ((fd = ccidadm_open(argv[i], B_TRUE)) < 0) {
755 			warn("failed to open %s", argv[i]);
756 			errx(EXIT_FAILURE, "valid ccid reader");
757 		}
758 
759 		ccidadm_reader_print(fd, argv[i], NULL);
760 		(void) close(fd);
761 		if (i + 1 < argc) {
762 			(void) printf("\n");
763 		}
764 	}
765 }
766 
767 static void
768 ccidadm_reader_usage(FILE *out)
769 {
770 	(void) fprintf(out, "\treader\t\t[reader] ...\n");
771 }
772 
773 typedef struct ccidadm_cmdtab {
774 	const char *cc_name;
775 	void (*cc_op)(int, char *[]);
776 	void (*cc_usage)(FILE *);
777 } ccidadm_cmdtab_t;
778 
779 static ccidadm_cmdtab_t ccidadm_cmds[] = {
780 	{ "list", ccidadm_do_list, ccidadm_list_usage },
781 	{ "atr", ccidadm_do_atr, ccidadm_atr_usage },
782 	{ "reader", ccidadm_do_reader, ccidadm_reader_usage },
783 	{ NULL }
784 };
785 
786 static int
787 ccidadm_usage(const char *format, ...)
788 {
789 	ccidadm_cmdtab_t *tab;
790 
791 	if (format != NULL) {
792 		va_list ap;
793 
794 		va_start(ap, format);
795 		(void) fprintf(stderr, "%s: ", ccidadm_pname);
796 		(void) vfprintf(stderr, format, ap);
797 		(void) fprintf(stderr, "\n");
798 		va_end(ap);
799 	}
800 
801 	(void) fprintf(stderr, "usage:  %s <subcommand> <args> ...\n\n",
802 	    ccidadm_pname);
803 	(void) fprintf(stderr, "Subcommands:\n");
804 	for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) {
805 		tab->cc_usage(stderr);
806 	}
807 
808 	return (EXIT_USAGE);
809 }
810 
811 int
812 main(int argc, char *argv[])
813 {
814 	ccidadm_cmdtab_t *tab;
815 
816 	ccidadm_pname = basename(argv[0]);
817 	if (argc < 2) {
818 		return (ccidadm_usage("missing required subcommand"));
819 	}
820 
821 	for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) {
822 		if (strcmp(argv[1], tab->cc_name) == 0) {
823 			argc -= 2;
824 			argv += 2;
825 			tab->cc_op(argc, argv);
826 			return (EXIT_SUCCESS);
827 		}
828 	}
829 
830 	return (ccidadm_usage("unknown command: %s", argv[1]));
831 }
832