summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Rosenfeld <hans.rosenfeld@joyent.com>2017-07-01 05:38:06 +0000
committerHans Rosenfeld <hans.rosenfeld@joyent.com>2020-03-27 20:09:43 +0100
commita61ed2ce7a86a4d6428f2a83eb4739fae945447e (patch)
treef9fbcbacc85a9ab1881c1a0447a0c712f93c43d1
parentae6d4bc342613e6a5dc7b84b03ecdb0cc9cf7d26 (diff)
downloadillumos-joyent-a61ed2ce7a86a4d6428f2a83eb4739fae945447e.tar.gz
12258 Need native CCID driver
Contributed by: Robert Mustacchi <rm@joyent.com> Reviewed by: John Levon <john.levon@joyent.com> Reviewed by: Robert Mustacchi <rm@fingolfin.org> Approved by: Garrett D'Amore <garrett@damore.org>
-rw-r--r--usr/src/cmd/Makefile1
-rw-r--r--usr/src/cmd/ccidadm/Makefile48
-rw-r--r--usr/src/cmd/ccidadm/ccidadm.c844
-rw-r--r--usr/src/cmd/devfsadm/cfg_link.c29
-rw-r--r--usr/src/cmd/devfsadm/cfg_link.h4
-rw-r--r--usr/src/cmd/devfsadm/usb_link.c36
-rw-r--r--usr/src/cmd/mdb/common/modules/usba/prtusb.c35
-rw-r--r--usr/src/common/ccid/atr.c1603
-rw-r--r--usr/src/common/ccid/atr.h198
-rw-r--r--usr/src/lib/Makefile2
-rw-r--r--usr/src/lib/cfgadm_plugins/Makefile5
-rw-r--r--usr/src/lib/cfgadm_plugins/ccid/Makefile67
-rw-r--r--usr/src/lib/cfgadm_plugins/ccid/Makefile.com67
-rw-r--r--usr/src/lib/cfgadm_plugins/ccid/amd64/Makefile21
-rw-r--r--usr/src/lib/cfgadm_plugins/ccid/common/cfga_ccid.c420
-rw-r--r--usr/src/lib/cfgadm_plugins/ccid/common/mapfile-vers42
-rw-r--r--usr/src/lib/cfgadm_plugins/ccid/i386/Makefile20
-rw-r--r--usr/src/lib/libcmdutils/common/nicenum.c16
-rw-r--r--usr/src/lib/libcmdutils/libcmdutils.h3
-rw-r--r--usr/src/lib/libpcsc/Makefile43
-rw-r--r--usr/src/lib/libpcsc/Makefile.com32
-rw-r--r--usr/src/lib/libpcsc/amd64/Makefile19
-rw-r--r--usr/src/lib/libpcsc/common/libpcsc.c615
-rw-r--r--usr/src/lib/libpcsc/common/mapfile-vers50
-rw-r--r--usr/src/lib/libpcsc/common/winscard.h144
-rw-r--r--usr/src/lib/libpcsc/common/wintypes.h50
-rw-r--r--usr/src/lib/libpcsc/i386/Makefile18
-rw-r--r--usr/src/man/man1m/Makefile1
-rw-r--r--usr/src/man/man1m/ccidadm.1m190
-rw-r--r--usr/src/man/man7d/Makefile1
-rw-r--r--usr/src/man/man7d/ccid.7d558
-rw-r--r--usr/src/man/man7i/Makefile2
-rw-r--r--usr/src/pkg/manifests/driver-misc-ccid.mf57
-rw-r--r--usr/src/pkg/manifests/system-library-libpcsc.mf32
-rw-r--r--usr/src/pkg/manifests/system-test-ostest.mf17
-rw-r--r--usr/src/test/os-tests/runfiles/default.run8
-rw-r--r--usr/src/test/os-tests/tests/Makefile1
-rw-r--r--usr/src/test/os-tests/tests/uccid/Makefile82
-rw-r--r--usr/src/test/os-tests/tests/uccid/atrparse.c731
-rw-r--r--usr/src/test/os-tests/tests/uccid/excl-badread.c80
-rw-r--r--usr/src/test/os-tests/tests/uccid/excl-basic.c65
-rw-r--r--usr/src/test/os-tests/tests/uccid/excl-close.c81
-rw-r--r--usr/src/test/os-tests/tests/uccid/excl-loop.c84
-rw-r--r--usr/src/test/os-tests/tests/uccid/excl-nonblock.c99
-rw-r--r--usr/src/test/os-tests/tests/uccid/excl-reset.c65
-rw-r--r--usr/src/test/os-tests/tests/uccid/modify.c203
-rw-r--r--usr/src/test/os-tests/tests/uccid/notxn-poll.c57
-rw-r--r--usr/src/test/os-tests/tests/uccid/pollin.c64
-rw-r--r--usr/src/test/os-tests/tests/uccid/pollout.c68
-rw-r--r--usr/src/test/os-tests/tests/uccid/status.c97
-rw-r--r--usr/src/test/os-tests/tests/uccid/txn-pollerr.c88
-rw-r--r--usr/src/test/os-tests/tests/uccid/yk-poll.c108
-rw-r--r--usr/src/test/os-tests/tests/uccid/yk-readonly.c100
-rw-r--r--usr/src/test/os-tests/tests/uccid/yk.c78
-rw-r--r--usr/src/uts/common/Makefile.files2
-rw-r--r--usr/src/uts/common/Makefile.rules8
-rw-r--r--usr/src/uts/common/io/usb/clients/ccid/ccid.c4420
-rw-r--r--usr/src/uts/common/sys/Makefile7
-rw-r--r--usr/src/uts/common/sys/Makefile.syshdrs6
-rw-r--r--usr/src/uts/common/sys/sunddi.h2
-rw-r--r--usr/src/uts/common/sys/usb/clients/ccid/ccid.h311
-rw-r--r--usr/src/uts/common/sys/usb/clients/ccid/uccid.h133
-rw-r--r--usr/src/uts/intel/Makefile.intel1
-rw-r--r--usr/src/uts/intel/ccid/Makefile42
64 files changed, 12369 insertions, 12 deletions
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index 6f3591dc46..cbfe2700e6 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -84,6 +84,7 @@ COMMON_SUBDIRS= \
cal \
captoinfo \
cat \
+ ccidadm \
cdrw \
cfgadm \
checkeq \
diff --git a/usr/src/cmd/ccidadm/Makefile b/usr/src/cmd/ccidadm/Makefile
new file mode 100644
index 0000000000..d1d9221c80
--- /dev/null
+++ b/usr/src/cmd/ccidadm/Makefile
@@ -0,0 +1,48 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+PROG= ccidadm
+
+include ../Makefile.cmd
+include ../Makefile.ctf
+
+CFLAGS += $(CCVERBOSE)
+LDLIBS += -lofmt -lcmdutils
+SRCS = ccidadm.c atr.c
+OBJS = $(SRCS:%.c=%.o)
+CPPFLAGS += -I$(SRC)/common/ccid
+
+.KEEP_STATE:
+
+$(PROG): $(OBJS)
+ $(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+%.o: %.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+%.o: $(SRC)/common/ccid/%.c
+ $(COMPILE.c) $<
+ $(POST_PROCESS_O)
+
+all: $(PROG)
+
+install: all $(ROOTUSRSBINPROG)
+
+clean:
+ $(RM) $(OBJS) $(PROG)
+
+include ../Makefile.targ
diff --git a/usr/src/cmd/ccidadm/ccidadm.c b/usr/src/cmd/ccidadm/ccidadm.c
new file mode 100644
index 0000000000..6309ecfcc8
--- /dev/null
+++ b/usr/src/cmd/ccidadm/ccidadm.c
@@ -0,0 +1,844 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Print out information about a CCID device.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <err.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <unistd.h>
+#include <ofmt.h>
+#include <libgen.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <libcmdutils.h>
+#include <fts.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+#include <atr.h>
+
+#define EXIT_USAGE 2
+
+static const char *ccidadm_pname;
+
+#define CCID_ROOT "/dev/ccid/"
+
+typedef enum {
+ CCIDADM_LIST_DEVICE,
+ CCIDADM_LIST_PRODUCT,
+ CCIDADM_LIST_STATE,
+ CCIDADM_LIST_TRANSPORT,
+ CCIDADM_LIST_SUPPORTED,
+} ccidadm_list_index_t;
+
+typedef struct ccidadm_pair {
+ uint32_t ccp_val;
+ const char *ccp_name;
+} ccidadm_pair_t;
+
+typedef struct ccid_list_ofmt_arg {
+ const char *cloa_name;
+ uccid_cmd_status_t *cloa_status;
+} ccid_list_ofmt_arg_t;
+
+/*
+ * Attempt to open a CCID slot specified by a user. In general, we expect that
+ * users will use a path like "ccid0/slot0". However, they may also specify a
+ * full path. If the card boolean is set to true, that means that they may have
+ * just specified "ccid0", so we need to try to open up the default slot.
+ */
+static int
+ccidadm_open(const char *base, boolean_t card)
+{
+ int fd;
+ char buf[PATH_MAX];
+
+ /*
+ * If it's an absolute path, just try to open it.
+ */
+ if (base[0] == '/') {
+ return (open(base, O_RDWR));
+ }
+
+ /*
+ * For a card, try to append slot0 first.
+ */
+ if (card) {
+ if (snprintf(buf, sizeof (buf), "%s/%s/slot0", CCID_ROOT,
+ base) >= sizeof (buf)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+
+ if ((fd = open(buf, O_RDWR)) >= 0) {
+ return (fd);
+ }
+
+ if (errno != ENOENT && errno != ENOTDIR) {
+ return (fd);
+ }
+ }
+
+ if (snprintf(buf, sizeof (buf), "%s/%s", CCID_ROOT, base) >=
+ sizeof (buf)) {
+ errno = ENAMETOOLONG;
+ return (-1);
+ }
+
+ return (open(buf, O_RDWR));
+}
+
+static void
+ccidadm_iter(boolean_t readeronly, boolean_t newline,
+ void(*cb)(int, const char *, void *), void *arg)
+{
+ FTS *fts;
+ FTSENT *ent;
+ char *const paths[] = { CCID_ROOT, NULL };
+ int fd;
+ boolean_t first = B_TRUE;
+
+ fts = fts_open(paths, FTS_LOGICAL | FTS_NOCHDIR, NULL);
+ if (fts == NULL) {
+ err(EXIT_FAILURE, "failed to create directory stream");
+ }
+
+ while ((ent = fts_read(fts)) != NULL) {
+ const char *name;
+
+ /* Skip the root and post-order dirs */
+ if (ent->fts_level == 0 || ent->fts_info == FTS_DP) {
+ continue;
+ }
+ if (readeronly && ent->fts_level != 1) {
+ continue;
+ } else if (!readeronly && ent->fts_level != 2) {
+ continue;
+ }
+
+ if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS) {
+ warn("skipping %s, failed to get information: %s",
+ ent->fts_name, strerror(ent->fts_errno));
+ continue;
+ }
+
+ name = ent->fts_path + strlen(CCID_ROOT);
+ if ((fd = ccidadm_open(name, readeronly)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", name);
+ }
+
+ if (!first && newline) {
+ (void) printf("\n");
+ }
+ first = B_FALSE;
+ cb(fd, name, arg);
+ (void) close(fd);
+ }
+
+ (void) fts_close(fts);
+}
+
+static void
+ccidadm_list_slot_status_str(uccid_cmd_status_t *ucs, char *buf, uint_t buflen)
+{
+ if (!(ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT)) {
+ (void) snprintf(buf, buflen, "missing");
+ return;
+ }
+
+ if (ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) {
+ (void) snprintf(buf, buflen, "activated");
+ return;
+ }
+
+ (void) snprintf(buf, buflen, "unactivated");
+}
+
+static boolean_t
+ccidadm_list_slot_transport_str(uccid_cmd_status_t *ucs, char *buf,
+ uint_t buflen)
+{
+ const char *prot;
+ const char *tran;
+ uint_t bits = CCID_CLASS_F_TPDU_XCHG | CCID_CLASS_F_SHORT_APDU_XCHG |
+ CCID_CLASS_F_EXT_APDU_XCHG;
+
+ switch (ucs->ucs_class.ccd_dwFeatures & bits) {
+ case 0:
+ tran = "character";
+ break;
+ case CCID_CLASS_F_TPDU_XCHG:
+ tran = "TPDU";
+ break;
+ case CCID_CLASS_F_SHORT_APDU_XCHG:
+ case CCID_CLASS_F_EXT_APDU_XCHG:
+ tran = "APDU";
+ break;
+ default:
+ tran = "unknown";
+ break;
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) != 0) {
+ switch (ucs->ucs_prot) {
+ case UCCID_PROT_T0:
+ prot = " (T=0)";
+ break;
+ case UCCID_PROT_T1:
+ prot = " (T=1)";
+ break;
+ default:
+ prot = "";
+ break;
+ }
+ } else {
+ prot = "";
+ }
+
+ return (snprintf(buf, buflen, "%s%s", tran, prot) < buflen);
+}
+
+static boolean_t
+ccidadm_list_slot_usable_str(uccid_cmd_status_t *ucs, char *buf,
+ uint_t buflen)
+{
+ const char *un = "";
+ ccid_class_features_t feat;
+ uint_t prot = CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG;
+ uint_t param = CCID_CLASS_F_AUTO_PARAM_NEG | CCID_CLASS_F_AUTO_PPS;
+ uint_t clock = CCID_CLASS_F_AUTO_BAUD | CCID_CLASS_F_AUTO_ICC_CLOCK;
+
+ feat = ucs->ucs_class.ccd_dwFeatures;
+
+ if ((feat & prot) == 0 ||
+ (feat & param) != param ||
+ (feat & clock) != clock) {
+ un = "un";
+ }
+
+ return (snprintf(buf, buflen, "%ssupported", un) < buflen);
+}
+
+static boolean_t
+ccidadm_list_ofmt_cb(ofmt_arg_t *ofmt, char *buf, uint_t buflen)
+{
+ ccid_list_ofmt_arg_t *cloa = ofmt->ofmt_cbarg;
+
+ switch (ofmt->ofmt_id) {
+ case CCIDADM_LIST_DEVICE:
+ if (snprintf(buf, buflen, "%s", cloa->cloa_name) >= buflen) {
+ return (B_FALSE);
+ }
+ break;
+ case CCIDADM_LIST_PRODUCT:
+ if (snprintf(buf, buflen, "%s",
+ cloa->cloa_status->ucs_product) >= buflen) {
+ return (B_FALSE);
+ }
+ break;
+ case CCIDADM_LIST_STATE:
+ ccidadm_list_slot_status_str(cloa->cloa_status, buf, buflen);
+ break;
+ case CCIDADM_LIST_TRANSPORT:
+ return (ccidadm_list_slot_transport_str(cloa->cloa_status, buf,
+ buflen));
+ break;
+ case CCIDADM_LIST_SUPPORTED:
+ return (ccidadm_list_slot_usable_str(cloa->cloa_status, buf,
+ buflen));
+ break;
+ default:
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+static void
+ccidadm_list_slot(int slotfd, const char *name, void *arg)
+{
+ uccid_cmd_status_t ucs;
+ ofmt_handle_t ofmt = arg;
+ ccid_list_ofmt_arg_t cloa;
+
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+
+ if (ioctl(slotfd, UCCID_CMD_STATUS, &ucs) != 0) {
+ err(EXIT_FAILURE, "failed to issue status ioctl to %s", name);
+ }
+
+ if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) {
+ (void) strlcpy(ucs.ucs_product, "<unknown>",
+ sizeof (ucs.ucs_product));
+ }
+
+ cloa.cloa_name = name;
+ cloa.cloa_status = &ucs;
+ ofmt_print(ofmt, &cloa);
+}
+
+static ofmt_field_t ccidadm_list_fields[] = {
+ { "PRODUCT", 24, CCIDADM_LIST_PRODUCT, ccidadm_list_ofmt_cb },
+ { "DEVICE", 16, CCIDADM_LIST_DEVICE, ccidadm_list_ofmt_cb },
+ { "CARD STATE", 12, CCIDADM_LIST_STATE, ccidadm_list_ofmt_cb },
+ { "TRANSPORT", 12, CCIDADM_LIST_TRANSPORT, ccidadm_list_ofmt_cb },
+ { "SUPPORTED", 12, CCIDADM_LIST_SUPPORTED, ccidadm_list_ofmt_cb },
+ { NULL, 0, 0, NULL }
+};
+
+static void
+ccidadm_do_list(int argc, char *argv[])
+{
+ ofmt_handle_t ofmt;
+
+ if (argc != 0) {
+ errx(EXIT_USAGE, "list command does not take arguments\n");
+ }
+
+ if (ofmt_open(NULL, ccidadm_list_fields, 0, 0, &ofmt) != OFMT_SUCCESS) {
+ errx(EXIT_FAILURE, "failed to initialize ofmt state");
+ }
+
+ ccidadm_iter(B_FALSE, B_FALSE, ccidadm_list_slot, ofmt);
+ ofmt_close(ofmt);
+}
+
+static void
+ccidadm_list_usage(FILE *out)
+{
+ (void) fprintf(out, "\tlist\n");
+}
+
+/*
+ * Print out logical information about the ICC's ATR. This includes information
+ * about what protocols it supports, required negotiation, etc.
+ */
+static void
+ccidadm_atr_props(uccid_cmd_status_t *ucs)
+{
+ int ret;
+ atr_data_t *data;
+ atr_protocol_t prots, defprot;
+ boolean_t negotiate;
+ atr_data_rate_choice_t rate;
+ uint32_t bps;
+
+ if ((data = atr_data_alloc()) == NULL) {
+ err(EXIT_FAILURE, "failed to allocate memory for "
+ "ATR data");
+ }
+
+ ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data);
+ if (ret != ATR_CODE_OK) {
+ errx(EXIT_FAILURE, "failed to parse ATR data: %s",
+ atr_strerror(ret));
+ }
+
+ prots = atr_supported_protocols(data);
+ (void) printf("ICC supports protocol(s): ");
+ if (prots == ATR_P_NONE) {
+ (void) printf("none\n");
+ atr_data_free(data);
+ return;
+ }
+
+ (void) printf("%s\n", atr_protocol_to_string(prots));
+
+ negotiate = atr_params_negotiable(data);
+ defprot = atr_default_protocol(data);
+
+ if (negotiate) {
+ (void) printf("Card protocol is negotiable; starts with "
+ "default %s parameters\n", atr_protocol_to_string(defprot));
+ } else {
+ (void) printf("Card protocol is not negotiable; starts with "
+ "specific %s parameters\n",
+ atr_protocol_to_string(defprot));
+ }
+
+ /*
+ * For each supported protocol, figure out parameters we would
+ * negotiate. We only need to warn about auto-negotiation if this
+ * is TPDU or character and specific bits are missing.
+ */
+ if (((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_SHORT_APDU_XCHG |
+ CCID_CLASS_F_EXT_APDU_XCHG)) == 0) &&
+ ((ucs->ucs_class.ccd_dwFeatures & (CCID_CLASS_F_AUTO_PARAM_NEG |
+ CCID_CLASS_F_AUTO_PPS)) == 0)) {
+ (void) printf("CCID/ICC require explicit TPDU parameter/PPS "
+ "negotiation\n");
+ }
+
+ /*
+ * Determine which set of Di/Fi values we should use and how we should
+ * get there (note a reader may not have to set them).
+ */
+ rate = atr_data_rate(data, &ucs->ucs_class, NULL, 0, &bps);
+ switch (rate) {
+ case ATR_RATE_USEDEFAULT:
+ (void) printf("Reader will run ICC at the default (Di=1/Fi=1) "
+ "speed\n");
+ break;
+ case ATR_RATE_USEATR:
+ (void) printf("Reader will run ICC at ICC's Di/Fi values\n");
+ break;
+ case ATR_RATE_USEATR_SETRATE:
+ (void) printf("Reader will run ICC at ICC's Di/Fi values, but "
+ "must set data rate to %u bps\n", bps);
+ break;
+ case ATR_RATE_UNSUPPORTED:
+ (void) printf("Reader cannot run ICC due to Di/Fi mismatch\n");
+ break;
+ default:
+ (void) printf("Cannot determine Di/Fi rate, unexpected "
+ "value: %u\n", rate);
+ break;
+ }
+ if (prots & ATR_P_T0) {
+ uint8_t fi, di;
+ atr_convention_t conv;
+ atr_clock_stop_t clock;
+
+ fi = atr_fi_index(data);
+ di = atr_di_index(data);
+ conv = atr_convention(data);
+ clock = atr_clock_stop(data);
+ (void) printf("T=0 properties that would be negotiated:\n");
+ (void) printf(" + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n",
+ fi, atr_fi_index_to_string(fi),
+ atr_fmax_index_to_string(fi));
+ (void) printf(" + Di Index: %u (Di %s)\n", di,
+ atr_di_index_to_string(di));
+ (void) printf(" + Clock Convention: %u (%s)\n", conv,
+ atr_convention_to_string(conv));
+ (void) printf(" + Extra Guardtime: %u\n",
+ atr_extra_guardtime(data));
+ (void) printf(" + WI: %u\n", atr_t0_wi(data));
+ (void) printf(" + Clock Stop: %u (%s)\n", clock,
+ atr_clock_stop_to_string(clock));
+ }
+
+ if (prots & ATR_P_T1) {
+ uint8_t fi, di;
+ atr_clock_stop_t clock;
+ atr_t1_checksum_t cksum;
+
+ fi = atr_fi_index(data);
+ di = atr_di_index(data);
+ clock = atr_clock_stop(data);
+ cksum = atr_t1_checksum(data);
+ (void) printf("T=1 properties that would be negotiated:\n");
+ (void) printf(" + Fi/Fmax Index: %u (Fi %s/Fmax %s MHz)\n",
+ fi, atr_fi_index_to_string(fi),
+ atr_fmax_index_to_string(fi));
+ (void) printf(" + Di Index: %u (Di %s)\n", di,
+ atr_di_index_to_string(di));
+ (void) printf(" + Checksum: %s\n",
+ cksum == ATR_T1_CHECKSUM_CRC ? "CRC" : "LRC");
+ (void) printf(" + Extra Guardtime: %u\n",
+ atr_extra_guardtime(data));
+ (void) printf(" + BWI: %u\n", atr_t1_bwi(data));
+ (void) printf(" + CWI: %u\n", atr_t1_cwi(data));
+ (void) printf(" + Clock Stop: %u (%s)\n", clock,
+ atr_clock_stop_to_string(clock));
+ (void) printf(" + IFSC: %u\n", atr_t1_ifsc(data));
+ (void) printf(" + CCID Supports NAD: %s\n",
+ ucs->ucs_class.ccd_dwFeatures & CCID_CLASS_F_ALTNAD_SUP ?
+ "yes" : "no");
+ }
+
+ atr_data_free(data);
+}
+
+static void
+ccidadm_atr_verbose(uccid_cmd_status_t *ucs)
+{
+ int ret;
+ atr_data_t *data;
+
+ if ((data = atr_data_alloc()) == NULL) {
+ err(EXIT_FAILURE, "failed to allocate memory for "
+ "ATR data");
+ }
+
+ ret = atr_parse(ucs->ucs_atr, ucs->ucs_atrlen, data);
+ if (ret != ATR_CODE_OK) {
+ errx(EXIT_FAILURE, "failed to parse ATR data: %s",
+ atr_strerror(ret));
+ }
+ atr_data_dump(data, stdout);
+ atr_data_free(data);
+}
+
+typedef struct cciadm_atr_args {
+ boolean_t caa_hex;
+ boolean_t caa_props;
+ boolean_t caa_verbose;
+} ccidadm_atr_args_t;
+
+static void
+ccidadm_atr_fetch(int fd, const char *name, void *arg)
+{
+ uccid_cmd_status_t ucs;
+ ccidadm_atr_args_t *caa = arg;
+
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+
+ if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
+ err(EXIT_FAILURE, "failed to issue status ioctl to %s",
+ name);
+ }
+
+ if (ucs.ucs_atrlen == 0) {
+ warnx("slot %s has no card inserted or activated", name);
+ return;
+ }
+
+ (void) printf("ATR for %s (%u bytes):\n", name, ucs.ucs_atrlen);
+ if (caa->caa_props) {
+ ccidadm_atr_props(&ucs);
+ }
+
+ if (caa->caa_hex) {
+ atr_data_hexdump(ucs.ucs_atr, ucs.ucs_atrlen, stdout);
+ }
+
+ if (caa->caa_verbose) {
+ ccidadm_atr_verbose(&ucs);
+ }
+}
+
+static void
+ccidadm_do_atr(int argc, char *argv[])
+{
+ uint_t i;
+ int c;
+ ccidadm_atr_args_t caa;
+
+ bzero(&caa, sizeof (caa));
+ optind = 0;
+ while ((c = getopt(argc, argv, "vx")) != -1) {
+ switch (c) {
+ case 'v':
+ caa.caa_verbose = B_TRUE;
+ break;
+ case 'x':
+ caa.caa_hex = B_TRUE;
+ break;
+ case ':':
+ errx(EXIT_USAGE, "Option -%c requires an argument\n",
+ optopt);
+ break;
+ case '?':
+ errx(EXIT_USAGE, "Unknown option: -%c\n", optopt);
+ break;
+ }
+ }
+
+ if (!caa.caa_verbose && !caa.caa_props && !caa.caa_hex) {
+ caa.caa_props = B_TRUE;
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 0) {
+ ccidadm_iter(B_FALSE, B_TRUE, ccidadm_atr_fetch, &caa);
+ return;
+ }
+
+ for (i = 0; i < argc; i++) {
+ int fd;
+
+ if ((fd = ccidadm_open(argv[i], B_FALSE)) < 0) {
+ warn("failed to open %s", argv[i]);
+ errx(EXIT_FAILURE, "valid CCID slot?");
+ }
+
+ ccidadm_atr_fetch(fd, argv[i], &caa);
+ (void) close(fd);
+ if (i + 1 < argc) {
+ (void) printf("\n");
+ }
+ }
+}
+
+static void
+ccidadm_atr_usage(FILE *out)
+{
+ (void) fprintf(out, "\tatr [-vx]\t[device] ...\n");
+}
+
+static void
+ccidadm_print_pairs(uint32_t val, ccidadm_pair_t *ccp)
+{
+ while (ccp->ccp_name != NULL) {
+ if ((val & ccp->ccp_val) == ccp->ccp_val) {
+ (void) printf(" + %s\n", ccp->ccp_name);
+ }
+ ccp++;
+ }
+}
+
+static ccidadm_pair_t ccidadm_p_protocols[] = {
+ { 0x01, "T=0" },
+ { 0x02, "T=1" },
+ { 0x0, NULL }
+};
+
+static ccidadm_pair_t ccidadm_p_voltages[] = {
+ { CCID_CLASS_VOLT_5_0, "5.0 V" },
+ { CCID_CLASS_VOLT_3_0, "3.0 V" },
+ { CCID_CLASS_VOLT_1_8, "1.8 V" },
+ { 0x0, NULL }
+};
+
+static ccidadm_pair_t ccidadm_p_syncprots[] = {
+ { 0x01, "2-Wire Support" },
+ { 0x02, "3-Wire Support" },
+ { 0x04, "I2C Support" },
+ { 0x0, NULL }
+};
+
+static ccidadm_pair_t ccidadm_p_mechanical[] = {
+ { CCID_CLASS_MECH_CARD_ACCEPT, "Card Accept Mechanism" },
+ { CCID_CLASS_MECH_CARD_EJECT, "Card Eject Mechanism" },
+ { CCID_CLASS_MECH_CARD_CAPTURE, "Card Capture Mechanism" },
+ { CCID_CLASS_MECH_CARD_LOCK, "Card Lock/Unlock Mechanism" },
+ { 0x0, NULL }
+};
+
+static ccidadm_pair_t ccidadm_p_features[] = {
+ { CCID_CLASS_F_AUTO_PARAM_ATR,
+ "Automatic parameter configuration based on ATR data" },
+ { CCID_CLASS_F_AUTO_ICC_ACTIVATE,
+ "Automatic activation on ICC insertion" },
+ { CCID_CLASS_F_AUTO_ICC_VOLTAGE, "Automatic ICC voltage selection" },
+ { CCID_CLASS_F_AUTO_ICC_CLOCK,
+ "Automatic ICC clock frequency change" },
+ { CCID_CLASS_F_AUTO_BAUD, "Automatic baud rate change" },
+ { CCID_CLASS_F_AUTO_PARAM_NEG,
+ "Automatic parameter negotiation by CCID" },
+ { CCID_CLASS_F_AUTO_PPS, "Automatic PPS made by CCID" },
+ { CCID_CLASS_F_ICC_CLOCK_STOP, "CCID can set ICC in clock stop mode" },
+ { CCID_CLASS_F_ALTNAD_SUP, "NAD value other than zero accepted" },
+ { CCID_CLASS_F_AUTO_IFSD, "Automatic IFSD exchange" },
+ { CCID_CLASS_F_TPDU_XCHG, "TPDU support" },
+ { CCID_CLASS_F_SHORT_APDU_XCHG, "Short APDU support" },
+ { CCID_CLASS_F_EXT_APDU_XCHG, "Short and Extended APDU support" },
+ { CCID_CLASS_F_WAKE_UP, "USB Wake Up signaling support" },
+ { 0x0, NULL }
+};
+
+static ccidadm_pair_t ccidadm_p_pin[] = {
+ { CCID_CLASS_PIN_VERIFICATION, "PIN verification" },
+ { CCID_CLASS_PIN_MODIFICATION, "PIN modification" },
+ { 0x0, NULL }
+};
+
+static void
+ccidadm_reader_print(int fd, const char *name, void *unused __unused)
+{
+ uccid_cmd_status_t ucs;
+ ccid_class_descr_t *cd;
+ char nnbuf[NN_NUMBUF_SZ + 1];
+
+ bzero(&ucs, sizeof (uccid_cmd_status_t));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+
+ if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
+ err(EXIT_FAILURE, "failed to issue status ioctl to %s",
+ name);
+ }
+
+ cd = &ucs.ucs_class;
+ (void) printf("Reader %s, CCID class v%u.%u device:\n", name,
+ CCID_VERSION_MAJOR(cd->ccd_bcdCCID),
+ CCID_VERSION_MINOR(cd->ccd_bcdCCID));
+
+ if ((ucs.ucs_status & UCCID_STATUS_F_PRODUCT_VALID) == 0) {
+ (void) strlcpy(ucs.ucs_product, "<unknown>",
+ sizeof (ucs.ucs_product));
+ }
+
+ if ((ucs.ucs_status & UCCID_STATUS_F_SERIAL_VALID) == 0) {
+ (void) strlcpy(ucs.ucs_serial, "<unknown>",
+ sizeof (ucs.ucs_serial));
+ }
+
+ (void) printf(" Product: %s\n", ucs.ucs_product);
+ (void) printf(" Serial: %s\n", ucs.ucs_serial);
+ (void) printf(" Slots Present: %u\n", cd->ccd_bMaxSlotIndex + 1);
+ (void) printf(" Maximum Busy Slots: %u\n", cd->ccd_bMaxCCIDBusySlots);
+ (void) printf(" Supported Voltages:\n");
+ ccidadm_print_pairs(cd->ccd_bVoltageSupport, ccidadm_p_voltages);
+ (void) printf(" Supported Protocols:\n");
+ ccidadm_print_pairs(cd->ccd_dwProtocols, ccidadm_p_protocols);
+ nicenum_scale(cd->ccd_dwDefaultClock, 1000, nnbuf,
+ sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE);
+ (void) printf(" Default Clock: %sHz\n", nnbuf);
+ nicenum_scale(cd->ccd_dwMaximumClock, 1000, nnbuf,
+ sizeof (nnbuf), NN_DIVISOR_1000 | NN_UNIT_SPACE);
+ (void) printf(" Maximum Clock: %sHz\n", nnbuf);
+ (void) printf(" Supported Clock Rates: %u\n",
+ cd->ccd_bNumClockSupported);
+ nicenum_scale(cd->ccd_dwDataRate, 1, nnbuf, sizeof (nnbuf),
+ NN_DIVISOR_1000 | NN_UNIT_SPACE);
+ (void) printf(" Default Data Rate: %sbps\n", nnbuf);
+ nicenum_scale(cd->ccd_dwMaxDataRate, 1, nnbuf, sizeof (nnbuf),
+ NN_DIVISOR_1000 | NN_UNIT_SPACE);
+ (void) printf(" Maximum Data Rate: %sbps\n", nnbuf);
+ (void) printf(" Supported Data Rates: %u\n",
+ cd->ccd_bNumDataRatesSupported);
+ (void) printf(" Maximum IFSD (T=1 only): %u\n", cd->ccd_dwMaxIFSD);
+ if (cd->ccd_dwSyncProtocols != 0) {
+ (void) printf(" Synchronous Protocols Supported:\n");
+ ccidadm_print_pairs(cd->ccd_dwSyncProtocols,
+ ccidadm_p_syncprots);
+ }
+ if (cd->ccd_dwMechanical != 0) {
+ (void) printf(" Mechanical Features:\n");
+ ccidadm_print_pairs(cd->ccd_dwMechanical, ccidadm_p_mechanical);
+ }
+ if (cd->ccd_dwFeatures != 0) {
+ (void) printf(" Device Features:\n");
+ ccidadm_print_pairs(cd->ccd_dwFeatures, ccidadm_p_features);
+ }
+ (void) printf(" Maximum Message Length: %u bytes\n",
+ cd->ccd_dwMaxCCIDMessageLength);
+ if (cd->ccd_dwFeatures & CCID_CLASS_F_EXT_APDU_XCHG) {
+ if (cd->ccd_bClassGetResponse == 0xff) {
+ (void) printf(" Default Get Response Class: echo\n");
+ } else {
+ (void) printf(" Default Get Response Class: %u\n",
+ cd->ccd_bClassGetResponse);
+ }
+ if (cd->ccd_bClassEnvelope == 0xff) {
+ (void) printf(" Default Envelope Class: echo\n");
+ } else {
+ (void) printf(" Default Envelope Class: %u\n",
+ cd->ccd_bClassEnvelope);
+ }
+ }
+ if (cd->ccd_wLcdLayout != 0) {
+ (void) printf(" %2ux%2u LCD present\n",
+ cd->ccd_wLcdLayout >> 8, cd->ccd_wLcdLayout & 0xff);
+ }
+
+ if (cd->ccd_bPinSupport) {
+ (void) printf(" Pin Support:\n");
+ ccidadm_print_pairs(cd->ccd_bPinSupport, ccidadm_p_pin);
+ }
+}
+
+static void
+ccidadm_do_reader(int argc, char *argv[])
+{
+ int i;
+
+ if (argc == 0) {
+ ccidadm_iter(B_TRUE, B_TRUE, ccidadm_reader_print, NULL);
+ return;
+ }
+
+ for (i = 0; i < argc; i++) {
+ int fd;
+
+ if ((fd = ccidadm_open(argv[i], B_TRUE)) < 0) {
+ warn("failed to open %s", argv[i]);
+ errx(EXIT_FAILURE, "valid ccid reader");
+ }
+
+ ccidadm_reader_print(fd, argv[i], NULL);
+ (void) close(fd);
+ if (i + 1 < argc) {
+ (void) printf("\n");
+ }
+ }
+}
+
+static void
+ccidadm_reader_usage(FILE *out)
+{
+ (void) fprintf(out, "\treader\t\t[reader] ...\n");
+}
+
+typedef struct ccidadm_cmdtab {
+ const char *cc_name;
+ void (*cc_op)(int, char *[]);
+ void (*cc_usage)(FILE *);
+} ccidadm_cmdtab_t;
+
+static ccidadm_cmdtab_t ccidadm_cmds[] = {
+ { "list", ccidadm_do_list, ccidadm_list_usage },
+ { "atr", ccidadm_do_atr, ccidadm_atr_usage },
+ { "reader", ccidadm_do_reader, ccidadm_reader_usage },
+ { NULL }
+};
+
+static int
+ccidadm_usage(const char *format, ...)
+{
+ ccidadm_cmdtab_t *tab;
+
+ if (format != NULL) {
+ va_list ap;
+
+ va_start(ap, format);
+ (void) fprintf(stderr, "%s: ", ccidadm_pname);
+ (void) vfprintf(stderr, format, ap);
+ (void) fprintf(stderr, "\n");
+ va_end(ap);
+ }
+
+ (void) fprintf(stderr, "usage: %s <subcommand> <args> ...\n\n",
+ ccidadm_pname);
+ (void) fprintf(stderr, "Subcommands:\n");
+ for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) {
+ tab->cc_usage(stderr);
+ }
+
+ return (EXIT_USAGE);
+}
+
+int
+main(int argc, char *argv[])
+{
+ ccidadm_cmdtab_t *tab;
+
+ ccidadm_pname = basename(argv[0]);
+ if (argc < 2) {
+ return (ccidadm_usage("missing required subcommand"));
+ }
+
+ for (tab = ccidadm_cmds; tab->cc_name != NULL; tab++) {
+ if (strcmp(argv[1], tab->cc_name) == 0) {
+ argc -= 2;
+ argv += 2;
+ tab->cc_op(argc, argv);
+ return (EXIT_SUCCESS);
+ }
+ }
+
+ return (ccidadm_usage("unknown command: %s", argv[1]));
+}
diff --git a/usr/src/cmd/devfsadm/cfg_link.c b/usr/src/cmd/devfsadm/cfg_link.c
index e7229325ac..4415ad55ce 100644
--- a/usr/src/cmd/devfsadm/cfg_link.c
+++ b/usr/src/cmd/devfsadm/cfg_link.c
@@ -20,6 +20,7 @@
*/
/*
+ * Copyright 2019, Joyent, Inc.
* Copyright 2016 Nexenta Systems, Inc. All rights reserved.
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
@@ -59,6 +60,7 @@ static int pci_cfg_creat_cb(di_minor_t minor, di_node_t node);
static int ib_cfg_creat_cb(di_minor_t minor, di_node_t node);
static int sata_cfg_creat_cb(di_minor_t minor, di_node_t node);
static int sdcard_cfg_creat_cb(di_minor_t minor, di_node_t node);
+static int ccid_cfg_creat_cb(di_minor_t minor, di_node_t node);
static di_node_t pci_cfg_chassis_node(di_node_t, di_prom_handle_t);
static char *pci_cfg_slotname(di_node_t, di_prom_handle_t, minor_t);
@@ -120,6 +122,9 @@ static devfsadm_create_t cfg_create_cbt[] = {
},
{ "attachment-point", DDI_NT_SDCARD_ATTACHMENT_POINT, NULL,
TYPE_EXACT, ILEVEL_0, sdcard_cfg_creat_cb
+ },
+ { "attachment-point", DDI_NT_CCID_ATTACHMENT_POINT, NULL,
+ TYPE_EXACT, ILEVEL_0, ccid_cfg_creat_cb
}
};
@@ -153,6 +158,9 @@ static devfsadm_remove_t cfg_remove_cbt[] = {
{ "attachment-point", SDCARD_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS,
ILEVEL_0, devfsadm_rm_all
},
+ { "attachment-point", CCID_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS,
+ ILEVEL_0, devfsadm_rm_all
+ }
};
DEVFSADM_REMOVE_INIT_V0(cfg_remove_cbt);
@@ -1246,3 +1254,24 @@ serid_printable(uint64_t *seridp)
return (1);
}
+
+/*
+ * Create a link for cfgadm that points back to the normal ccid links in
+ * /dev/ccid.
+ */
+static int
+ccid_cfg_creat_cb(di_minor_t minor, di_node_t node)
+{
+ const char *minor_nm;
+ char cfg_path[MAXPATHLEN];
+
+ if ((minor_nm = di_minor_name(minor)) == NULL) {
+ return (DEVFSADM_CONTINUE);
+ }
+
+ (void) snprintf(cfg_path, sizeof (cfg_path), "%s/ccid%d/%s",
+ CFG_DIRNAME, di_instance(node), minor_nm);
+
+ (void) devfsadm_mklink(cfg_path, node, minor, 0);
+ return (DEVFSADM_CONTINUE);
+}
diff --git a/usr/src/cmd/devfsadm/cfg_link.h b/usr/src/cmd/devfsadm/cfg_link.h
index e2bb24864d..7c3cf13eb8 100644
--- a/usr/src/cmd/devfsadm/cfg_link.h
+++ b/usr/src/cmd/devfsadm/cfg_link.h
@@ -22,6 +22,9 @@
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
/* private devlink info interfaces */
@@ -43,6 +46,7 @@ extern "C" {
#define SDCARD_CFG_LINK_RE "^cfg/sdcard[0-9]+/[0-9]+$"
#define PCI_CFG_PATH_LINK_RE \
"^cfg/(.*(pci[0-9]|pcie[0-9]|Slot[0-9]|\\<pci\\>|\\<pcie\\>).*)$"
+#define CCID_CFG_LINK_RE "^cfg/ccid[0-9]+/slot[0-9]+$"
#define CFG_DIRNAME "cfg"
diff --git a/usr/src/cmd/devfsadm/usb_link.c b/usr/src/cmd/devfsadm/usb_link.c
index ac6a8447fb..b233f85daf 100644
--- a/usr/src/cmd/devfsadm/usb_link.c
+++ b/usr/src/cmd/devfsadm/usb_link.c
@@ -21,6 +21,9 @@
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
#include <devfsadm.h>
#include <stdio.h>
@@ -37,6 +40,8 @@ static int usb_process(di_minor_t minor, di_node_t node);
static void ugen_create_link(char *p_path, char *node_name,
di_node_t node, di_minor_t minor);
+static void ccid_create_link(char *p_path, char *node_name,
+ di_node_t node, di_minor_t minor);
/* Rules for creating links */
@@ -81,6 +86,8 @@ static devfsadm_create_t usb_cbt[] = {
ILEVEL_0, usb_process },
{ "usb", DDI_NT_NEXUS, "hwahc", DRV_EXACT|TYPE_EXACT,
ILEVEL_0, usb_process },
+ { "usb", DDI_NT_CCID_ATTACHMENT_POINT, "ccid", DRV_EXACT|TYPE_EXACT,
+ ILEVEL_0, usb_process },
};
/* For debug printing (-V filter) */
@@ -105,6 +112,7 @@ DEVFSADM_CREATE_INIT_V0(usb_cbt);
#define USB_LINK_RE_WHOST "^usb/whost[0-9]+$"
#define USB_LINK_RE_HWARC "^usb/hwarc[0-9]+$"
#define USB_LINK_RE_WUSB_CA "^usb/wusb_ca[0-9]+$"
+#define USB_LINK_RE_CCID "^ccid/ccid[0-9]+/slot[0-9]+$"
/* Rules for removing links */
static devfsadm_remove_t usb_remove_cbt[] = {
@@ -138,7 +146,9 @@ static devfsadm_remove_t usb_remove_cbt[] = {
{ "usb", USB_LINK_RE_HWARC, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
devfsadm_rm_all },
{ "usb", USB_LINK_RE_WUSB_CA, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
- devfsadm_rm_all }
+ devfsadm_rm_all },
+ { "usb", USB_LINK_RE_CCID, RM_POST | RM_HOT | RM_ALWAYS, ILEVEL_0,
+ devfsadm_rm_all }
};
/*
@@ -306,6 +316,14 @@ usb_process(di_minor_t minor, di_node_t node)
return (DEVFSADM_CONTINUE);
}
+ if (strcmp(di_minor_nodetype(minor), DDI_NT_CCID_ATTACHMENT_POINT) ==
+ 0) {
+ ccid_create_link(p_path, minor_nm, node, minor);
+ free(l_path);
+ free(p_path);
+ return (DEVFSADM_CONTINUE);
+ }
+
/* Figure out which rules to apply */
switch (index) {
case DRIVER_HUBD:
@@ -493,3 +511,19 @@ ugen_create_link(char *p_path, char *node_name,
free(buf);
}
+
+/*
+ * Create a CCID related link.
+ */
+static void
+ccid_create_link(char *p_path, char *minor_nm, di_node_t node, di_minor_t minor)
+{
+ char l_path[MAXPATHLEN];
+
+ (void) snprintf(l_path, sizeof (l_path), "ccid/ccid%d/%s",
+ di_instance(node), minor_nm);
+
+ devfsadm_print(debug_mid, "mklink %s -> %s\n", l_path, p_path);
+
+ (void) devfsadm_mklink(l_path, node, minor, 0);
+}
diff --git a/usr/src/cmd/mdb/common/modules/usba/prtusb.c b/usr/src/cmd/mdb/common/modules/usba/prtusb.c
index 9f6ffc4177..aed8bf8001 100644
--- a/usr/src/cmd/mdb/common/modules/usba/prtusb.c
+++ b/usr/src/cmd/mdb/common/modules/usba/prtusb.c
@@ -22,12 +22,12 @@
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*
- * Copyright 2016 Joyent, Inc.
+ * Copyright 2019 Joyent, Inc.
*/
#include <sys/mdb_modapi.h>
-
+#include <sys/sysmacros.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usba_types.h>
@@ -484,6 +484,32 @@ static usb_descr_item_t usb_vs_format_dv_descr[] = {
};
static uint_t usb_vs_format_dv_item = 6;
+static usb_descr_item_t usb_ccid_descr[] = {
+ {1, "bLength"},
+ {1, "bDescriptorType"},
+ {2, "bcdCCID"},
+ {1, "bMaxSlotIndex"},
+ {1, "bVoltageSupport"},
+ {4, "dwProtocols"},
+ {4, "dwDefaultClock"},
+ {4, "dwMaximumClock"},
+ {1, "bNumClockSupported"},
+ {4, "dwDataRate"},
+ {4, "dwMaxDataRate"},
+ {1, "bNumDataRatesSupported"},
+ {4, "dwMaxIFSD"},
+ {4, "dwSyncProtocols"},
+ {4, "dwMechanical"},
+ {4, "dwFeatures"},
+ {4, "dwMaxCCIDMessageLength"},
+ {1, "bClassGetResponse"},
+ {1, "bClassEnvelope"},
+ {2, "wLcdLayout"},
+ {1, "bPinSupport"},
+ {1, "bMaxCCIDBusySlots"}
+};
+static uint_t usb_ccid_item = ARRAY_SIZE(usb_ccid_descr);
+
/* ****************************************************************** */
@@ -1131,6 +1157,11 @@ prt_usb_desc(uintptr_t usb_cfg, uint_t cfg_len)
mdb_printf("WA Descriptor\n");
print_descr(paddr, nlen, usb_wa_descr,
usb_wa_item);
+ } else if (usb_if.bInterfaceClass == USB_CLASS_CCID &&
+ usb_if.bInterfaceSubClass == 0x0) {
+ mdb_printf("CCID Descriptor\n");
+ print_descr(paddr, nlen, usb_ccid_descr,
+ usb_ccid_item);
} else {
mdb_printf("HID Descriptor\n");
print_descr(paddr, nlen, usb_hid_descr,
diff --git a/usr/src/common/ccid/atr.c b/usr/src/common/ccid/atr.c
new file mode 100644
index 0000000000..d43aa585c5
--- /dev/null
+++ b/usr/src/common/ccid/atr.c
@@ -0,0 +1,1603 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * ATR parsing routines shared between userland (ccidadm) and the kernel (CCID
+ * driver)
+ */
+
+#include "atr.h"
+#include <sys/debug.h>
+#include <sys/sysmacros.h>
+
+#ifdef _KERNEL
+#include <sys/inttypes.h>
+#include <sys/sunddi.h>
+#include <sys/kmem.h>
+#else
+#include <inttypes.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#endif
+
+/*
+ * The ATR must have at least 2 bytes and then may have up to 33 bytes. The
+ * first byte is always TS and the second required byte is T0.
+ */
+#define ATR_TS_IDX 0
+#define ATR_T0_IDX 1
+
+/*
+ * There are two valid values for TS. It must either be 0x3F or 0x3B. This is
+ * required per ISO/IEC 7816-3:2006 section 8.1.
+ */
+#define ATR_TS_INVERSE 0x3F
+#define ATR_TS_DIRECT 0x3B
+
+/*
+ * After TS, each word is used to indicate a combination of protocol and the
+ * number of bits defined for that protocol. The lower nibble is treated as the
+ * protocol. The upper nibble is treated to indicate which of four defined words
+ * are present. These are usually referred to as TA, TB, TC, and TD. TD is
+ * always used to indicate the next protocol and the number of bytes present for
+ * that. T0 works in a similar way, except that it defines the number of
+ * historical bytes present in its protocol section and then it refers to a set
+ * of pre-defined global bytes that may be present.
+ */
+#define ATR_TD_PROT(x) ((x) & 0x0f)
+#define ATR_TD_NBITS(x) (((x) & 0xf0) >> 4)
+#define ATR_TA_MASK 0x1
+#define ATR_TB_MASK 0x2
+#define ATR_TC_MASK 0x4
+#define ATR_TD_MASK 0x8
+
+#define ATR_TA1_FTABLE(x) (((x) & 0xf0) >> 4)
+#define ATR_TA1_DITABLE(x) ((x) & 0x0f)
+
+#define ATR_TA2_CANCHANGE(x) (((x) & 0x80) == 0)
+#define ATR_TA2_HONORTA1(x) (((x) & 0x10) == 0)
+#define ATR_TA2_PROTOCOL(x) ((x) & 0x0f)
+
+/*
+ * When the checksum is required in the ATR, each byte must XOR to zero.
+ */
+#define ATR_CKSUM_TARGET 0
+
+/*
+ * Maximum number of historic ATR bytes. This is limited by the fact that it's a
+ * 4-bit nibble.
+ */
+#define ATR_HISTORICAL_MAX 15
+
+/*
+ * The maximum number of TA, TB, TC, and TD levels that can be encountered in a
+ * given structure. In the best case, there are 30 bytes available (TS, T0, and
+ * TCK use the others). Given that each one of these needs 4 bytes to be
+ * represented, the maximum number of layers that can fit is seven.
+ */
+#define ATR_TI_MAX 7
+
+/*
+ * Defined protocol values. See ISO/IEC 7816-3:2006 8.2.3 for this list.
+ * Reserved values are noted but not defined.
+ */
+#define ATR_PROTOCOL_T0 0
+#define ATR_PROTOCOL_T1 1
+
+#define ATR_T1_TB0_CWI(x) ((x) & 0x0f)
+#define ATR_T1_TB0_BWI(x) (((x) & 0xf0) >> 4)
+#define ATR_T1_TC0_CRC(x) (((x) & 0x01) != 0)
+
+/*
+ * T=2 and T=3 are reserved for future full-duplex operation.
+ * T=4 is reserved for enhanced half-duplex character transmission.
+ * T=5-13 are reserved for future use by ISO/IEC JTC 1/SC 17.
+ * T=14 is for protocols not standardized by ISO/IEC JTC 1/SC 17.
+ */
+#define ATR_PROTOCOL_T15 15
+
+#define ATR_T15_TA0_CLOCK(x) (((x) & 0xc0) >> 6)
+#define ATR_T15_TA0_VOLTAGE(x) ((x) & 0x3f)
+
+#define ATR_T15_TB0_SPU_STANDARD(x) (((x & 0x80)) != 0)
+
+/*
+ * Various definitions for the configuration of historical data. This comes from
+ * ISO/IEC 7816-4:2013 Section 12.1.1.
+ */
+
+/*
+ * The first historical byte is used to indicate the encoding of the data. Only
+ * values 0x00, 0x80-0x8f are defined. All others are proprietary. 0x81-0x8f are
+ * reserved for future use.
+ */
+#define ATR_HIST_CAT_MAND_STATUS 0x00
+#define ATR_HIST_CAT_TLV_STATUS 0x80
+#define ATR_HIST_CAT_RFU_MIN 0x81
+#define ATR_HIST_CAT_RFU_MAX 0x8f
+
+/*
+ * From ISO/IEC 7816-3:2006 Section 8.3.
+ *
+ * The default value for Fi is 372 which is table entry 1. The default value for
+ * Di is 1, which is table entry 1.
+ */
+#define ATR_FI_DEFAULT_INDEX 1
+#define ATR_DI_DEFAULT_INDEX 1
+#define ATR_EXTRA_GUARDTIME_DEFAULT 0
+
+/*
+ * From ISO/IEC 7816-3:2006 Section 10.2.
+ */
+#define ATR_T0_WI_DEFAULT 10
+
+/*
+ * From ISO/IEC 7816-3:2006 Section 11.4.3.
+ */
+#define ATR_T1_CWI_DEFAULT 13
+
+/*
+ * From ISO/IEC 7816-3:2006 Section 11.4.3.
+ */
+#define ATR_T1_BWI_DEFAULT 4
+
+/*
+ * From ISO/IEC 7816-3:2006 Section 11.4.2.
+ */
+#define ATR_T1_IFSC_DEFAULT 32
+
+/*
+ * From ISO/IEC 7816-3:2006 Section 11.4.4
+ */
+#define ATR_T1_CHECKSUM_DEFAULT ATR_T1_CHECKSUM_LRC
+
+/*
+ * Definitions for PPS construction. These are derived from ISO/IEC 7816-3:2006
+ * section 9, Protocol and parameters selection.
+ */
+#define PPS_LEN_MIN 3 /* PPSS, PPS0, PCK */
+#define PPS_LEN_MAX PPS_BUFFER_MAX
+#define PPS_PPSS_INDEX 0
+#define PPS_PPSS_VAL 0xff
+#define PPS_PPS0_INDEX 0x01
+#define PPS_PPS0_PROT(x) ((x) & 0x0f)
+#define PPS_PPS0_PPS1 (1 << 4)
+#define PPS_PPS0_PPS2 (1 << 5)
+#define PPS_PPS0_PPS3 (1 << 6)
+#define PPS_PPS1_SETVAL(f, d) ((((f) & 0x0f) << 4) | ((d) & 0x0f))
+
+/*
+ * This enum and subsequent structure is used to represent a single level of
+ * 'T'. This includes the possibility for all three values to be set and records
+ * the protocol.
+ */
+typedef enum atr_ti_flags {
+ ATR_TI_HAVE_TA = 1 << 0,
+ ATR_TI_HAVE_TB = 1 << 1,
+ ATR_TI_HAVE_TC = 1 << 2,
+ ATR_TI_HAVE_TD = 1 << 3
+} atr_ti_flags_t;
+
+typedef struct atr_ti {
+ uint8_t atrti_protocol;
+ uint8_t atrti_ti_val;
+ uint8_t atrti_td_idx;
+ atr_ti_flags_t atrti_flags;
+ uint8_t atrti_ta;
+ uint8_t atrti_tb;
+ uint8_t atrti_tc;
+ uint8_t atrti_td;
+} atr_ti_t;
+
+typedef enum atr_flags {
+ ATR_F_USES_DIRECT = 1 << 0,
+ ATR_F_USES_INVERSE = 1 << 1,
+ ATR_F_HAS_CHECKSUM = 1 << 2,
+ ATR_F_VALID = 1 << 3
+} atr_flags_t;
+
+
+struct atr_data {
+ atr_flags_t atr_flags;
+ uint8_t atr_nti;
+ atr_ti_t atr_ti[ATR_TI_MAX];
+ uint8_t atr_nhistoric;
+ uint8_t atr_historic[ATR_HISTORICAL_MAX];
+ uint8_t atr_cksum;
+ uint8_t atr_raw[ATR_LEN_MAX];
+ uint8_t atr_nraw;
+};
+
+/*
+ * These tables maps the bit values for Fi from 7816-3:2006 section 8.3 Table 7.
+ */
+static uint_t atr_fi_valtable[16] = {
+ 372, /* 0000 */
+ 372, /* 0001 */
+ 558, /* 0010 */
+ 744, /* 0011 */
+ 1116, /* 0100 */
+ 1488, /* 0101 */
+ 1860, /* 0110 */
+ 0, /* 0111 */
+ 0, /* 1000 */
+ 512, /* 1001 */
+ 768, /* 1010 */
+ 1024, /* 1011 */
+ 1536, /* 1100 */
+ 2048, /* 1101 */
+ 0, /* 1110 */
+ 0 /* 1111 */
+};
+
+static const char *atr_fi_table[16] = {
+ "372", /* 0000 */
+ "372", /* 0001 */
+ "558", /* 0010 */
+ "744", /* 0011 */
+ "1116", /* 0100 */
+ "1488", /* 0101 */
+ "1860", /* 0110 */
+ "RFU", /* 0111 */
+ "RFU", /* 1000 */
+ "512", /* 1001 */
+ "768", /* 1010 */
+ "1024", /* 1011 */
+ "1536", /* 1100 */
+ "2048", /* 1101 */
+ "RFU", /* 1110 */
+ "RFU", /* 1111 */
+};
+
+/*
+ * This table maps the bit values for f(max) from 7816-3:2006 section 8.3
+ * Table 7.
+ */
+static const char *atr_fmax_table[16] = {
+ "4", /* 0000 */
+ "5", /* 0001 */
+ "6", /* 0010 */
+ "8", /* 0011 */
+ "12", /* 0100 */
+ "16", /* 0101 */
+ "20", /* 0110 */
+ "-", /* 0111 */
+ "-", /* 1000 */
+ "5", /* 1001 */
+ "7.5", /* 1010 */
+ "10", /* 1011 */
+ "15", /* 1100 */
+ "20", /* 1101 */
+ "-", /* 1110 */
+ "-", /* 1111 */
+};
+
+/*
+ * This table maps the bit values for Di from 7816-3:2006 section 8.3 Table 8.
+ */
+static uint_t atr_di_valtable[16] = {
+ 0, /* 0000 */
+ 1, /* 0001 */
+ 2, /* 0010 */
+ 4, /* 0011 */
+ 8, /* 0100 */
+ 16, /* 0101 */
+ 32, /* 0110 */
+ 64, /* 0111 */
+ 12, /* 1000 */
+ 20, /* 1001 */
+ 0, /* 1010 */
+ 0, /* 1011 */
+ 0, /* 1100 */
+ 0, /* 1101 */
+ 0, /* 1110 */
+ 0 /* 1111 */
+};
+
+static const char *atr_di_table[16] = {
+ "RFU", /* 0000 */
+ "1", /* 0001 */
+ "2", /* 0010 */
+ "4", /* 0011 */
+ "8", /* 0100 */
+ "16", /* 0101 */
+ "32", /* 0110 */
+ "64", /* 0111 */
+ "12", /* 1000 */
+ "20", /* 1001 */
+ "RFU", /* 1010 */
+ "RFU", /* 1011 */
+ "RFU", /* 1100 */
+ "RFU", /* 1101 */
+ "RFU", /* 1110 */
+ "RFU", /* 1111 */
+};
+
+/*
+ * This table maps the bit values for the clock stop indicator from 7816-3:2006
+ * section 8.3 Table 9.
+ */
+static const char *atr_clock_table[4] = {
+ "disallowed", /* 00 */
+ "signal low", /* 01 */
+ "signal high", /* 10 */
+ "signal low or high" /* 11 */
+};
+
+uint_t
+atr_fi_index_to_value(uint8_t val)
+{
+ if (val >= ARRAY_SIZE(atr_fi_valtable)) {
+ return (0);
+ }
+
+ return (atr_fi_valtable[val]);
+}
+
+const char *
+atr_fi_index_to_string(uint8_t val)
+{
+ if (val >= ARRAY_SIZE(atr_fi_table)) {
+ return ("<invalid>");
+ }
+
+ return (atr_fi_table[val]);
+}
+
+const char *
+atr_fmax_index_to_string(uint8_t val)
+{
+ if (val >= ARRAY_SIZE(atr_fmax_table)) {
+ return ("<invalid>");
+ }
+
+ return (atr_fmax_table[val]);
+}
+
+uint_t
+atr_di_index_to_value(uint8_t val)
+{
+ if (val >= ARRAY_SIZE(atr_di_valtable)) {
+ return (0);
+ }
+
+ return (atr_di_valtable[val]);
+}
+const char *
+atr_di_index_to_string(uint8_t val)
+{
+ if (val >= ARRAY_SIZE(atr_di_table)) {
+ return ("<invalid>");
+ }
+
+ return (atr_di_table[val]);
+}
+
+const char *
+atr_clock_stop_to_string(atr_clock_stop_t val)
+{
+ if (val >= ARRAY_SIZE(atr_clock_table)) {
+ return ("<invalid>");
+ }
+
+ return (atr_clock_table[val]);
+}
+
+const char *
+atr_protocol_to_string(atr_protocol_t prot)
+{
+ if (prot == ATR_P_NONE) {
+ return ("none");
+ }
+
+ if ((prot & ATR_P_T0) == ATR_P_T0) {
+ return ("T=0");
+ } else if ((prot & ATR_P_T1) == ATR_P_T1) {
+ return ("T=1");
+ } else {
+ return ("T=0, T=1");
+ }
+}
+
+const char *
+atr_convention_to_string(atr_convention_t conv)
+{
+ if (conv == ATR_CONVENTION_DIRECT) {
+ return ("direct");
+ } else if (conv == ATR_CONVENTION_INVERSE) {
+ return ("inverse");
+ } else {
+ return ("<invalid convention>");
+ }
+}
+
+const char *
+atr_strerror(atr_parsecode_t code)
+{
+ switch (code) {
+ case ATR_CODE_OK:
+ return ("ATR parsed successfully");
+ case ATR_CODE_TOO_SHORT:
+ return ("Specified buffer too short");
+ case ATR_CODE_TOO_LONG:
+ return ("Specified buffer too long");
+ case ATR_CODE_INVALID_TS:
+ return ("ATR has invalid TS byte value");
+ case ATR_CODE_OVERRUN:
+ return ("ATR data requires more bytes than provided");
+ case ATR_CODE_UNDERRUN:
+ return ("ATR data did not use all provided bytes");
+ case ATR_CODE_CHECKSUM_ERROR:
+ return ("ATR data did not checksum correctly");
+ case ATR_CODE_INVALID_TD1:
+ return ("ATR data has invalid protocol in TD1");
+ default:
+ return ("Unknown Parse Code");
+ }
+}
+
+static uint_t
+atr_count_cbits(uint8_t x)
+{
+ uint_t ret = 0;
+
+ if (x & ATR_TA_MASK)
+ ret++;
+ if (x & ATR_TB_MASK)
+ ret++;
+ if (x & ATR_TC_MASK)
+ ret++;
+ if (x & ATR_TD_MASK)
+ ret++;
+ return (ret);
+}
+
+/*
+ * Parse out ATR values. Focus on only parsing it and not interpreting it.
+ * Interpretation should be done in other functions that can walk over the data
+ * and be more protocol-aware.
+ */
+atr_parsecode_t
+atr_parse(const uint8_t *buf, size_t len, atr_data_t *data)
+{
+ uint_t nhist, cbits, ncbits, idx, Ti, prot;
+ uint_t ncksum = 0;
+ atr_ti_t *atp;
+
+ /*
+ * Zero out data in case someone's come back around for another loop on
+ * the same data.
+ */
+ bzero(data, sizeof (atr_data_t));
+
+ if (len < ATR_LEN_MIN) {
+ return (ATR_CODE_TOO_SHORT);
+ }
+
+ if (len > ATR_LEN_MAX) {
+ return (ATR_CODE_TOO_LONG);
+ }
+
+ if (buf[ATR_TS_IDX] != ATR_TS_INVERSE &&
+ buf[ATR_TS_IDX] != ATR_TS_DIRECT) {
+ return (ATR_CODE_INVALID_TS);
+ }
+
+ bcopy(buf, data->atr_raw, len);
+ data->atr_nraw = len;
+
+ if (buf[ATR_TS_IDX] == ATR_TS_DIRECT) {
+ data->atr_flags |= ATR_F_USES_DIRECT;
+ } else {
+ data->atr_flags |= ATR_F_USES_INVERSE;
+ }
+
+ /*
+ * The protocol of T0 is the number of historical bits present.
+ */
+ nhist = ATR_TD_PROT(buf[ATR_T0_IDX]);
+ cbits = ATR_TD_NBITS(buf[ATR_T0_IDX]);
+ idx = ATR_T0_IDX + 1;
+ ncbits = atr_count_cbits(cbits);
+
+ /*
+ * Ti is used to track the current iteration of T[A,B,C,D] that we are
+ * on, as the ISO/IEC standard suggests. The way that values are
+ * interpreted depends on the value of Ti.
+ *
+ * When Ti is one, TA, TB, and TC represent global properties. TD's
+ * protocol represents the preferred protocol.
+ *
+ * When Ti is two, TA, TB, and TC also represent global properties.
+ * However, TC only has meaning if the protocol is T=0.
+ *
+ * When Ti is 15, it indicates more global properties.
+ *
+ * For all other values of Ti, the meaning depends on the protocol in
+ * question and they are all properties specific to that protocol.
+ */
+ Ti = 1;
+ /*
+ * Initialize prot to an invalid protocol to help us deal with the
+ * normal workflow and make sure that we don't mistakenly do anything.
+ */
+ prot = UINT32_MAX;
+ for (;;) {
+ atp = &data->atr_ti[data->atr_nti];
+ data->atr_nti++;
+ ASSERT3U(data->atr_nti, <=, ATR_TI_MAX);
+
+ /*
+ * Make sure that we have enough space to read all the cbits.
+ * idx points to the first cbit, which could also potentially be
+ * over the length of the buffer. This is why we subtract one
+ * from idx when doing the calculation.
+ */
+ if (idx - 1 + ncbits >= len) {
+ return (ATR_CODE_OVERRUN);
+ }
+
+ ASSERT3U(Ti, !=, 0);
+
+ /*
+ * At the moment we opt to ignore reserved protocols.
+ */
+ atp->atrti_protocol = prot;
+ atp->atrti_ti_val = Ti;
+ atp->atrti_td_idx = idx - 1;
+
+ if (cbits & ATR_TA_MASK) {
+ atp->atrti_flags |= ATR_TI_HAVE_TA;
+ atp->atrti_ta = buf[idx];
+ idx++;
+ }
+
+ if (cbits & ATR_TB_MASK) {
+ atp->atrti_flags |= ATR_TI_HAVE_TB;
+ atp->atrti_tb = buf[idx];
+ idx++;
+ }
+
+ if (cbits & ATR_TC_MASK) {
+ atp->atrti_flags |= ATR_TI_HAVE_TC;
+ atp->atrti_tc = buf[idx];
+ idx++;
+ }
+
+ if (cbits & ATR_TD_MASK) {
+ atp->atrti_flags |= ATR_TI_HAVE_TD;
+ atp->atrti_td = buf[idx];
+ cbits = ATR_TD_NBITS(buf[idx]);
+ prot = ATR_TD_PROT(buf[idx]);
+ ncbits = atr_count_cbits(cbits);
+ if (prot != 0)
+ ncksum = 1;
+
+ /*
+ * T=15 is not allowed in TD1 per 8.2.3.
+ */
+ if (Ti == 1 && prot == 0xf)
+ return (ATR_CODE_INVALID_TD1);
+
+ idx++;
+ /*
+ * Encountering TD means that once we take the next loop
+ * and we need to increment Ti.
+ */
+ Ti++;
+ } else {
+ break;
+ }
+ }
+
+ /*
+ * We've parsed all of the cbits. At this point, we should take into
+ * account all of the historical bits and potentially the checksum.
+ */
+ if (idx - 1 + nhist + ncksum >= len) {
+ return (ATR_CODE_OVERRUN);
+ }
+
+ if (idx + nhist + ncksum != len) {
+ return (ATR_CODE_UNDERRUN);
+ }
+
+ if (nhist > 0) {
+ data->atr_nhistoric = nhist;
+ bcopy(&buf[idx], data->atr_historic, nhist);
+ }
+
+ if (ncksum > 0) {
+ size_t i;
+ uint8_t val;
+
+ /*
+ * Per ISO/IEC 7816-3:2006 Section 8.2.5 the checksum is all
+ * bytes excluding TS. Therefore, we must start at byte 1.
+ */
+ for (val = 0, i = 1; i < len; i++) {
+ val ^= buf[i];
+ }
+
+ if (val != ATR_CKSUM_TARGET) {
+ return (ATR_CODE_CHECKSUM_ERROR);
+ }
+ data->atr_flags |= ATR_F_HAS_CHECKSUM;
+ data->atr_cksum = buf[len - 1];
+ }
+
+ data->atr_flags |= ATR_F_VALID;
+ return (ATR_CODE_OK);
+}
+
+uint8_t
+atr_fi_default_index(void)
+{
+ return (ATR_FI_DEFAULT_INDEX);
+}
+
+uint8_t
+atr_di_default_index(void)
+{
+ return (ATR_DI_DEFAULT_INDEX);
+}
+
+/*
+ * Parse the data to determine which protocols are supported in this atr data.
+ * Based on this, users can come and ask us to fill in protocol information.
+ */
+atr_protocol_t
+atr_supported_protocols(atr_data_t *data)
+{
+ uint_t i;
+ atr_protocol_t prot;
+
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return (ATR_P_NONE);
+
+ /*
+ * Based on 8.2.3 of ISO/IEC 7816-3:2006, if TD1 is present, then that
+ * indicates the first protocol. However, if it is not present, then
+ * that implies that T=0 is the only supported protocol. Otherwise, all
+ * protocols are referenced in ascending order. The first entry in
+ * atr_ti refers to data from T0, so the protocol in the second entry
+ * would have the TD1 data.
+ */
+ if (data->atr_nti < 2) {
+ return (ATR_P_T0);
+ }
+
+ prot = ATR_P_NONE;
+ for (i = 0; i < data->atr_nti; i++) {
+ switch (data->atr_ti[i].atrti_protocol) {
+ case ATR_PROTOCOL_T0:
+ prot |= ATR_P_T0;
+ break;
+ case ATR_PROTOCOL_T1:
+ prot |= ATR_P_T1;
+ break;
+ default:
+ /*
+ * T=15 is not a protocol, and all other protocol values
+ * are currently reserved for future use.
+ */
+ continue;
+ }
+ }
+
+ /*
+ * It's possible we've found nothing specific in the above loop (for
+ * example, only T=15 global bits were found). In that case, the card
+ * defaults to T=0.
+ */
+ if (prot == ATR_P_NONE)
+ prot = ATR_P_T0;
+
+ return (prot);
+}
+
+boolean_t
+atr_params_negotiable(atr_data_t *data)
+{
+ /* If for some reason we're called with invalid data, assume it's not */
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return (B_FALSE);
+
+
+ /*
+ * Whether or not we're negotiable is in the second global page, so atr
+ * index 1. If TA2 is missing, then the card always is negotiable.
+ */
+ if (data->atr_nti < 2 ||
+ (data->atr_ti[1].atrti_flags & ATR_TI_HAVE_TA) == 0) {
+ return (B_TRUE);
+ }
+
+ if (ATR_TA2_CANCHANGE(data->atr_ti[1].atrti_ta)) {
+ return (B_TRUE);
+ }
+
+ return (B_FALSE);
+}
+
+atr_protocol_t
+atr_default_protocol(atr_data_t *data)
+{
+ uint8_t prot;
+
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return (ATR_P_NONE);
+ /*
+ * If we don't have an TA2 byte, then the system defaults to T=0.
+ */
+ if (data->atr_nti < 2) {
+ return (ATR_P_T0);
+ }
+
+ /*
+ * If TA2 is present, then it encodes the default protocol. Otherwise,
+ * we have to grab the protocol value from TD1, which is called the
+ * 'first offered protocol'.
+ */
+ if ((data->atr_ti[1].atrti_flags & ATR_TI_HAVE_TA) != 0) {
+ prot = ATR_TA2_PROTOCOL(data->atr_ti[1].atrti_ta);
+ } else {
+ prot = data->atr_ti[1].atrti_protocol;
+ }
+
+ switch (prot) {
+ case ATR_PROTOCOL_T0:
+ return (ATR_P_T0);
+ case ATR_PROTOCOL_T1:
+ return (ATR_P_T1);
+ default:
+ return (ATR_P_NONE);
+ }
+}
+
+uint8_t
+atr_fi_index(atr_data_t *data)
+{
+ if (data->atr_nti < 1) {
+ return (ATR_FI_DEFAULT_INDEX);
+ }
+
+ /*
+ * If TA is specified, it is present in TA1. TA2 may override its
+ * presence, so if it is here, check that first to determine whether or
+ * not we should check TA1.
+ */
+ if (data->atr_nti >= 2 &&
+ (data->atr_ti[1].atrti_flags & ATR_TI_HAVE_TA) != 0) {
+ if (!ATR_TA2_HONORTA1(data->atr_ti[1].atrti_ta)) {
+ return (ATR_FI_DEFAULT_INDEX);
+ }
+ }
+
+ if ((data->atr_ti[0].atrti_flags & ATR_TI_HAVE_TA) != 0) {
+ return (ATR_TA1_FTABLE(data->atr_ti[0].atrti_ta));
+ }
+
+ return (ATR_FI_DEFAULT_INDEX);
+}
+
+uint8_t
+atr_di_index(atr_data_t *data)
+{
+ if (data->atr_nti < 1) {
+ return (ATR_DI_DEFAULT_INDEX);
+ }
+
+ /*
+ * If TA is specified, it is present in TA1. TA2 may override its
+ * presence, so if it is here, check that first to determine whether or
+ * not we should check TA1.
+ */
+ if (data->atr_nti >= 2 &&
+ (data->atr_ti[1].atrti_flags & ATR_TI_HAVE_TA) != 0) {
+ if (!ATR_TA2_HONORTA1(data->atr_ti[1].atrti_ta)) {
+ return (ATR_DI_DEFAULT_INDEX);
+ }
+ }
+
+ if ((data->atr_ti[0].atrti_flags & ATR_TI_HAVE_TA) != 0) {
+ return (ATR_TA1_DITABLE(data->atr_ti[0].atrti_ta));
+ }
+
+ return (ATR_DI_DEFAULT_INDEX);
+}
+
+atr_convention_t
+atr_convention(atr_data_t *data)
+{
+ if ((data->atr_flags & ATR_F_USES_DIRECT) != 0) {
+ return (ATR_CONVENTION_DIRECT);
+ }
+ return (ATR_CONVENTION_INVERSE);
+}
+
+uint8_t
+atr_extra_guardtime(atr_data_t *data)
+{
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return (ATR_EXTRA_GUARDTIME_DEFAULT);
+
+ if (data->atr_nti >= 1 &&
+ (data->atr_ti[0].atrti_flags & ATR_TI_HAVE_TC) != 0) {
+ return (data->atr_ti[0].atrti_tc);
+ }
+
+ return (ATR_EXTRA_GUARDTIME_DEFAULT);
+}
+
+uint8_t
+atr_t0_wi(atr_data_t *data)
+{
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return (ATR_T0_WI_DEFAULT);
+
+ /*
+ * This is stored in the optional global byte in TC2; however, it only
+ * applies to T=0.
+ */
+ if (data->atr_nti >= 2 &&
+ data->atr_ti[1].atrti_protocol == ATR_PROTOCOL_T0 &&
+ (data->atr_ti[1].atrti_flags & ATR_TI_HAVE_TC) != 0) {
+ return (data->atr_ti[1].atrti_tc);
+ }
+
+ return (ATR_T0_WI_DEFAULT);
+}
+
+uint8_t
+atr_t1_cwi(atr_data_t *data)
+{
+ uint8_t i;
+
+ if (data->atr_nti <= 2) {
+ return (ATR_T1_CWI_DEFAULT);
+ }
+
+ for (i = 2; i < data->atr_nti; i++) {
+ if (data->atr_ti[i].atrti_protocol == ATR_PROTOCOL_T1) {
+ if ((data->atr_ti[i].atrti_flags & ATR_TI_HAVE_TB) !=
+ 0) {
+ uint8_t tb = data->atr_ti[i].atrti_tb;
+ return (ATR_T1_TB0_CWI(tb));
+ }
+
+ return (ATR_T1_CWI_DEFAULT);
+ }
+ }
+
+ return (ATR_T1_CWI_DEFAULT);
+}
+
+atr_clock_stop_t
+atr_clock_stop(atr_data_t *data)
+{
+ uint8_t i;
+
+ for (i = 0; i < data->atr_nti; i++) {
+ if (data->atr_ti[i].atrti_protocol == ATR_PROTOCOL_T15) {
+ if ((data->atr_ti[i].atrti_flags & ATR_TI_HAVE_TA) !=
+ 0) {
+ uint8_t ta = data->atr_ti[i].atrti_ta;
+ return (ATR_T15_TA0_CLOCK(ta));
+ }
+
+ return (ATR_CLOCK_STOP_NONE);
+ }
+ }
+
+ return (ATR_CLOCK_STOP_NONE);
+}
+
+atr_t1_checksum_t
+atr_t1_checksum(atr_data_t *data)
+{
+ uint8_t i;
+
+ if (data->atr_nti <= 2) {
+ return (ATR_T1_CHECKSUM_DEFAULT);
+ }
+
+ for (i = 2; i < data->atr_nti; i++) {
+ if (data->atr_ti[i].atrti_protocol == ATR_PROTOCOL_T1) {
+ if ((data->atr_ti[i].atrti_flags & ATR_TI_HAVE_TC) !=
+ 0) {
+ if (ATR_T1_TC0_CRC(data->atr_ti[i].atrti_tc)) {
+ return (ATR_T1_CHECKSUM_CRC);
+ } else {
+ return (ATR_T1_CHECKSUM_LRC);
+ }
+ }
+
+ return (ATR_T1_CHECKSUM_DEFAULT);
+ }
+ }
+
+ return (ATR_T1_CHECKSUM_DEFAULT);
+
+}
+
+uint8_t
+atr_t1_bwi(atr_data_t *data)
+{
+ uint8_t i;
+
+ if (data->atr_nti <= 2) {
+ return (ATR_T1_BWI_DEFAULT);
+ }
+
+ for (i = 2; i < data->atr_nti; i++) {
+ if (data->atr_ti[i].atrti_protocol == ATR_PROTOCOL_T1) {
+ if ((data->atr_ti[i].atrti_flags & ATR_TI_HAVE_TB) !=
+ 0) {
+ uint8_t tb = data->atr_ti[i].atrti_tb;
+ return (ATR_T1_TB0_BWI(tb));
+ }
+
+ return (ATR_T1_BWI_DEFAULT);
+ }
+ }
+
+ return (ATR_T1_BWI_DEFAULT);
+}
+
+uint8_t
+atr_t1_ifsc(atr_data_t *data)
+{
+ uint8_t i;
+
+ if (data->atr_nti <= 2) {
+ return (ATR_T1_IFSC_DEFAULT);
+ }
+
+ for (i = 2; i < data->atr_nti; i++) {
+ if (data->atr_ti[i].atrti_protocol == ATR_PROTOCOL_T1) {
+ if ((data->atr_ti[i].atrti_flags & ATR_TI_HAVE_TA) !=
+ 0) {
+ return (data->atr_ti[i].atrti_ta);
+ }
+
+ return (ATR_T1_IFSC_DEFAULT);
+ }
+ }
+
+ return (ATR_T1_IFSC_DEFAULT);
+}
+
+/*
+ * Attempt to determine which set of data rates we should be able to use for a
+ * given class of protocol. Here we want to do the calculation based on the CCID
+ * specification, section 9.4.x. To use these higher rates we need:
+ *
+ * + Reader's data rate > frequency * Di / Fi.
+ *
+ * To determine which rate and frequency we use, we look at the reader's
+ * features. If the reader supports both the Automatic baud rate and automatic
+ * ICC clock frequency change, then we use the _maximum_ rate. Otherwise we will
+ * indicate that we can use the ATR's properties, but will require changing the
+ * default data rate.
+ *
+ * Now, some ICC devices are not negotiable. In those cases, we'll see if we can
+ * fit it in with either the default or maximum data rates. If not, then we'll
+ * not be able to support this card.
+ *
+ * There are two wrinkles that exist in this. The first is supported frequencies
+ * and data rates. If there are no additional data rates supported, then all of
+ * the data rates between the default and max are supported. If not, then only
+ * those specified in the data rates array are supported.
+ *
+ * The second hurdle is that we need to do this division and try and avoid the
+ * pitfalls of floating point arithmetic, as floating point is not allowed in
+ * the kernel (and this is shared). Importantly that means only integers are
+ * allowed here.
+ */
+atr_data_rate_choice_t
+atr_data_rate(atr_data_t *data, ccid_class_descr_t *class, uint32_t *rates,
+ uint_t nrates, uint32_t *dataratep)
+{
+ uint_t nfeats = CCID_CLASS_F_AUTO_ICC_CLOCK | CCID_CLASS_F_AUTO_BAUD;
+ uint8_t di, fi;
+ uint_t dival, fival;
+ boolean_t autospeed, negotiable, exprates;
+ uint64_t maxval, defval;
+
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return (ATR_RATE_UNSUPPORTED);
+
+ di = atr_di_index(data);
+ fi = atr_fi_index(data);
+ dival = atr_di_index_to_value(di);
+ fival = atr_fi_index_to_value(fi);
+ autospeed = (class->ccd_dwFeatures & nfeats) == nfeats;
+ exprates = class->ccd_bNumDataRatesSupported != 0;
+ negotiable = atr_params_negotiable(data);
+
+ /*
+ * We don't support cards with fixed rates at this time as it's not
+ * clear what that rate should be. If it's negotiable, we'll let them
+ * run at the default. Otherwise, we have to fail the request until
+ * we implement the logic to search their data rates.
+ */
+ if (exprates) {
+ if (negotiable) {
+ return (ATR_RATE_USEDEFAULT);
+ }
+ return (ATR_RATE_UNSUPPORTED);
+ }
+
+ /*
+ * This indicates that the card gave us values that were reserved for
+ * future use. If we could negotiate it, then just stick with the
+ * default paramters. Otherwise, return that we can't support this ICC.
+ */
+ if (dival == 0 || fival == 0) {
+ if (negotiable)
+ return (ATR_RATE_USEDEFAULT);
+ return (ATR_RATE_UNSUPPORTED);
+ }
+
+ /*
+ * Calculate the maximum and default values.
+ */
+ maxval = class->ccd_dwMaximumClock * 1000;
+ maxval *= dival;
+ maxval /= fival;
+
+ defval = class->ccd_dwDefaultClock * 1000;
+ defval *= dival;
+ defval /= fival;
+
+ /*
+ * We're allowed any set of data rates between the default and the
+ * maximum. Check if the maximum data rate will work for either the
+ * default or maximum clock. If so, then we can use the cards rates.
+ *
+ * To account for the fact that we may have had a fractional value,
+ * we require a strict greater than comparison.
+ */
+ if ((uint64_t)class->ccd_dwMaxDataRate > maxval ||
+ (uint64_t)class->ccd_dwMaxDataRate > defval) {
+ if (autospeed) {
+ return (ATR_RATE_USEATR);
+ }
+ }
+
+ /*
+ * If the CCID reader can't handle the ICC's proposed rates, then fall
+ * back to the defaults if we're allowed to negotiate. Otherwise, we're
+ * not able to use this ICC.
+ */
+ if (negotiable) {
+ return (ATR_RATE_USEDEFAULT);
+ }
+
+ return (ATR_RATE_UNSUPPORTED);
+}
+
+void
+atr_data_reset(atr_data_t *data)
+{
+ bzero(data, sizeof (*data));
+}
+
+#ifdef _KERNEL
+atr_data_t *
+atr_data_alloc(void)
+{
+ return (kmem_zalloc(sizeof (atr_data_t), KM_SLEEP));
+}
+
+void
+atr_data_free(atr_data_t *data)
+{
+ kmem_free(data, sizeof (atr_data_t));
+}
+
+/*
+ * Make sure that the response we got from the ICC is valid. It must pass
+ * checksum and have the PPSS value set correctly. The protocol must match
+ * what we requested; however, the PPS1-3 bits are a bit different. They may
+ * only be set in the response if we set them in the request. However, they
+ * do not have to be set in the response.
+ */
+boolean_t
+atr_pps_valid(void *reqbuf, size_t reqlen, void *respbuf, size_t resplen)
+{
+ uint8_t val, i, reqidx, respidx;
+ uint8_t *req = reqbuf, *resp = respbuf;
+
+ if (resplen > PPS_LEN_MAX || resplen < PPS_LEN_MIN)
+ return (B_FALSE);
+
+ /*
+ * Before we validate the data, make sure the checksum is valid.
+ */
+ for (i = 0, val = 0; i < resplen; i++) {
+ val ^= resp[i];
+ }
+
+ /* Checksum failure */
+ if (val != 0) {
+ return (B_FALSE);
+ }
+
+ /*
+ * We should always have PPSS echoed back as we set it.
+ */
+ if (resp[PPS_PPSS_INDEX] != PPS_PPSS_VAL) {
+ return (B_FALSE);
+ }
+
+ /*
+ * Go through and make sure the number of bytes present makes sense for
+ * the number of bits set in PPS1.
+ */
+ val = PPS_LEN_MIN;
+ if (resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS1)
+ val++;
+ if (resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS2)
+ val++;
+ if (resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS3)
+ val++;
+ if (val != resplen)
+ return (B_FALSE);
+
+ /*
+ * Now we've finally verified that the response is syntactically valid.
+ * We must go through and make sure that it is semantically valid.
+ */
+ if (PPS_PPS0_PROT(req[PPS_PPS0_INDEX]) !=
+ PPS_PPS0_PROT(resp[PPS_PPS0_INDEX])) {
+ return (B_FALSE);
+ }
+
+ /*
+ * When checking the PPS bit and extensions, we first check in the
+ * response as a bit in the request is allowed to not be in the
+ * response. But not the opposite way around. We also have to keep track
+ * of the fact that the index for values will vary.
+ */
+ reqidx = respidx = PPS_PPS0_INDEX + 1;
+ if ((resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS1) != 0) {
+ if ((req[PPS_PPS0_INDEX] & PPS_PPS0_PPS1) == 0) {
+ return (B_FALSE);
+ }
+
+ if (req[reqidx] != resp[respidx]) {
+ return (B_FALSE);
+ }
+
+ reqidx++;
+ respidx++;
+ } else if ((req[PPS_PPS0_INDEX] & PPS_PPS0_PPS1) != 0) {
+ reqidx++;
+ }
+
+ if ((resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS2) != 0) {
+ if ((req[PPS_PPS0_INDEX] & PPS_PPS0_PPS2) == 0) {
+ return (B_FALSE);
+ }
+
+ if (req[reqidx] != resp[respidx]) {
+ return (B_FALSE);
+ }
+
+ reqidx++;
+ respidx++;
+ } else if ((req[PPS_PPS0_INDEX] & PPS_PPS0_PPS2) != 0) {
+ reqidx++;
+ }
+
+ if ((resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS3) != 0) {
+ /*
+ * At this time, we never specify PPS3 in a request. Therefore
+ * if it is present in the response, treat this as an invalid
+ * request.
+ */
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+uint_t
+atr_pps_generate(uint8_t *buf, size_t buflen, atr_protocol_t prot,
+ boolean_t pps1, uint8_t fi, uint8_t di, boolean_t pps2, uint8_t spu)
+{
+ uint8_t protval, cksum, i;
+ uint_t len = 0;
+
+ if (buflen < PPS_BUFFER_MAX)
+ return (0);
+
+ buf[PPS_PPSS_INDEX] = PPS_PPSS_VAL;
+ switch (prot) {
+ case ATR_P_T0:
+ protval = 0;
+ break;
+ case ATR_P_T1:
+ protval = 1;
+ break;
+ default:
+ return (0);
+ }
+
+ buf[PPS_PPS0_INDEX] = PPS_PPS0_PROT(protval);
+ len = 2;
+ if (pps1) {
+ buf[PPS_PPS0_INDEX] |= PPS_PPS0_PPS1;
+ buf[len++] = PPS_PPS1_SETVAL(fi, di);
+ }
+
+ if (pps2) {
+ buf[PPS_PPS0_INDEX] |= PPS_PPS0_PPS2;
+ buf[len++] = spu;
+ }
+
+ /*
+ * The checksum must xor to zero.
+ */
+ for (i = 0, cksum = 0; i < len; i++) {
+ cksum ^= buf[i];
+ }
+ buf[len++] = cksum;
+ return (len);
+}
+
+/*
+ * The caller of this wants to know if the Fi/Di values that they proposed were
+ * accepted. The caller must have already called atr_pps_valid(). At this point,
+ * we can say that the value was accepted if the PPS1 bit is set.
+ */
+boolean_t
+atr_pps_fidi_accepted(void *respbuf, size_t len)
+{
+ uint8_t *resp = respbuf;
+ return ((resp[PPS_PPS0_INDEX] & PPS_PPS0_PPS1) != 0);
+}
+
+#else /* !_KERNEL */
+atr_data_t *
+atr_data_alloc(void)
+{
+ return (calloc(1, sizeof (atr_data_t)));
+}
+
+void
+atr_data_free(atr_data_t *data)
+{
+ if (data == NULL)
+ return;
+ free(data);
+}
+
+/*
+ * This table maps the bit values for Fi from 7816-3:2006 section 8.3 Table 9.
+ * The table is up to 6 bits wide. Entries not present are RFU. We use NULL as a
+ * sentinel to indicate that.
+ */
+static const char *atr_voltage_table[64] = {
+ NULL, /* 00 0000 */
+ "5V", /* 00 0001 */
+ "3V", /* 00 0010 */
+ "5V, 3V", /* 00 0011 */
+ "1.5V", /* 00 0100 */
+ NULL, /* 00 0101 */
+ "3V, 1.5V", /* 00 0110 */
+ "5V, 3V, 1.5V" /* 00 0111 */
+};
+
+static void
+atr_data_dump_ta(atr_ti_t *atp, FILE *out, uint_t level)
+{
+ uint8_t ta;
+
+ if (!(atp->atrti_flags & ATR_TI_HAVE_TA)) {
+ return;
+ }
+
+ ta = atp->atrti_ta;
+ (void) fprintf(out, " %c%c%c+-> TA%u 0x%02x",
+ atp->atrti_flags & ATR_TI_HAVE_TD ? '|' : ' ',
+ atp->atrti_flags & ATR_TI_HAVE_TC ? '|' : ' ',
+ atp->atrti_flags & ATR_TI_HAVE_TB ? '|' : ' ',
+ atp->atrti_ti_val, ta);
+ switch (atp->atrti_ti_val) {
+ case 1:
+ (void) fprintf(out, "; Fi: %s, F(max): %s MHz, Di: %s",
+ atr_fi_table[ATR_TA1_FTABLE(ta)],
+ atr_fmax_table[ATR_TA1_FTABLE(ta)],
+ atr_di_table[ATR_TA1_DITABLE(ta)]);
+ break;
+ case 2:
+ (void) fprintf(out, "; ICC in %s mode; %shonoring TA1; default "
+ "T=%u",
+ ATR_TA2_CANCHANGE(ta) ? "negotiable" : "specific",
+ ATR_TA2_HONORTA1(ta) ? "" : "not ",
+ ATR_TA2_PROTOCOL(ta));
+ break;
+ default:
+ switch (atp->atrti_protocol) {
+ case ATR_PROTOCOL_T1:
+ if (level != 0)
+ break;
+ if (ta == 0 || ta == 0xff) {
+ (void) fprintf(out, "; IFSC: RFU");
+ } else {
+ (void) fprintf(out, "; IFSC: %u", ta);
+ }
+ break;
+ case ATR_PROTOCOL_T15:
+ if (level != 0)
+ break;
+ (void) fprintf(out, "; Clock stop: %s, Supported "
+ "Voltage: %s",
+ atr_clock_table[ATR_T15_TA0_CLOCK(ta)],
+ atr_voltage_table[ATR_T15_TA0_VOLTAGE(ta)] != NULL ?
+ atr_voltage_table[ATR_T15_TA0_VOLTAGE(ta)] : "RFU");
+ break;
+ default:
+ break;
+ }
+ }
+ (void) fprintf(out, "\n");
+}
+
+static void
+atr_data_dump_tb(atr_ti_t *atp, FILE *out, uint_t level)
+{
+ uint8_t tb;
+
+ if (!(atp->atrti_flags & ATR_TI_HAVE_TB)) {
+ return;
+ }
+
+ tb = atp->atrti_tb;
+ (void) fprintf(out, " %c%c+--> TB%u 0x%02x",
+ atp->atrti_flags & ATR_TI_HAVE_TD ? '|' : ' ',
+ atp->atrti_flags & ATR_TI_HAVE_TC ? '|' : ' ',
+ atp->atrti_ti_val, tb);
+ switch (atp->atrti_ti_val) {
+ case 1:
+ case 2:
+ (void) fprintf(out, "; deprecated");
+ break;
+ default:
+ switch (atp->atrti_protocol) {
+ case ATR_PROTOCOL_T1:
+ if (level != 0)
+ break;
+ (void) fprintf(out, "; CWI: %u, BWI: %u\n",
+ ATR_T1_TB0_CWI(tb),
+ ATR_T1_TB0_BWI(tb));
+ break;
+ case ATR_PROTOCOL_T15:
+ if (level != 0)
+ break;
+ (void) fprintf(out, "; SPU: %s", tb == 0 ? "not used" :
+ ATR_T15_TB0_SPU_STANDARD(tb) ? "standard" :
+ "proprietary");
+ break;
+ default:
+ break;
+ }
+ }
+ (void) fprintf(out, "\n");
+}
+
+static void
+atr_data_dump_tc(atr_ti_t *atp, FILE *out, uint_t level)
+{
+ uint8_t tc;
+
+ if (!(atp->atrti_flags & ATR_TI_HAVE_TC)) {
+ return;
+ }
+
+ tc = atp->atrti_tc;
+ (void) fprintf(out, " %c+---> TC%u 0x%02x",
+ atp->atrti_flags & ATR_TI_HAVE_TD ? '|' : ' ',
+ atp->atrti_ti_val, tc);
+
+ switch (atp->atrti_ti_val) {
+ case 1:
+ (void) fprintf(out, "; Extra Guard Time Integer: %u", tc);
+ break;
+ case 2:
+ if (atp->atrti_protocol != ATR_PROTOCOL_T0) {
+ (void) fprintf(out, "; illegal value -- only valid for "
+ "T=0");
+ } else {
+ (void) fprintf(out, "; Waiting Time Integer: %u", tc);
+ }
+ break;
+ default:
+ switch (atp->atrti_protocol) {
+ case ATR_PROTOCOL_T1:
+ if (level != 0)
+ break;
+ (void) fprintf(out, "; Error Detection Code: %s",
+ ATR_T1_TC0_CRC(tc) ? "CRC" : "LRC");
+ break;
+ default:
+ break;
+ }
+ }
+ (void) fprintf(out, "\n");
+}
+
+void
+atr_data_hexdump(const uint8_t *buf, size_t nbytes, FILE *out)
+{
+ size_t i, j;
+
+ /* Print out the header */
+ (void) fprintf(out, "%*s 0", 4, "");
+ for (i = 1; i < 16; i++) {
+ if (i % 4 == 0 && i % 16 != 0) {
+ (void) fprintf(out, " ");
+ }
+
+ (void) fprintf(out, "%2x", i);
+ }
+ (void) fprintf(out, " 0123456789abcdef\n");
+
+ /* Print out data */
+ for (i = 0; i < nbytes; i++) {
+
+ if (i % 16 == 0) {
+ (void) fprintf(out, "%04x: ", i);
+ }
+
+ if (i % 4 == 0 && i % 16 != 0) {
+ (void) fprintf(out, " ");
+ }
+
+ (void) fprintf(out, "%02x", buf[i]);
+
+ if (i % 16 == 15 || i + 1 == nbytes) {
+ for (j = (i % 16) + 1; j < 16; j++) {
+ if (j % 4 == 0 && j % 16 != 0) {
+ (void) fprintf(out, " ");
+ }
+
+ (void) fprintf(out, " ");
+ }
+
+ (void) fprintf(out, " ");
+ for (j = i - (i % 16); j <= i; j++) {
+ (void) fprintf(out, "%c",
+ isprint(buf[j]) ? buf[j] : '.');
+ }
+ (void) printf("\n");
+ }
+ }
+}
+
+static void
+atr_data_hexdump_historical(atr_data_t *data, FILE *out)
+{
+ (void) fprintf(out, "Dumping raw historical bytes\n");
+
+ atr_data_hexdump(data->atr_historic, data->atr_nhistoric, out);
+}
+
+static void
+atr_data_dump_historical(atr_data_t *data, FILE *out)
+{
+ uint8_t cat;
+
+ (void) fprintf(out, "Historic Data: %u bytes", data->atr_nhistoric);
+ if (data->atr_nhistoric == 0) {
+ (void) fprintf(out, "\n");
+ return;
+ }
+
+ cat = data->atr_historic[0];
+ (void) fprintf(out, "; format (0x%02x) ", cat);
+ if (cat == ATR_HIST_CAT_MAND_STATUS) {
+ (void) fprintf(out, "card status, not shown");
+ } else if (cat == ATR_HIST_CAT_TLV_STATUS) {
+ (void) fprintf(out, "COMPACT-TLV, not shown");
+ } else if (cat >= ATR_HIST_CAT_RFU_MIN && cat <= ATR_HIST_CAT_RFU_MAX) {
+ (void) fprintf(out, "reserved\n");
+ atr_data_hexdump_historical(data, out);
+ return;
+ } else {
+ (void) fprintf(out, "proprietary\n");
+ atr_data_hexdump_historical(data, out);
+ return;
+ }
+}
+
+void
+atr_data_dump(atr_data_t *data, FILE *out)
+{
+ uint8_t i, level;
+ if ((data->atr_flags & ATR_F_VALID) == 0)
+ return;
+
+ (void) fprintf(out, "TS 0x%02u - ", data->atr_raw[0]);
+ if (data->atr_flags & ATR_F_USES_DIRECT) {
+ (void) fprintf(out, "direct convention\n");
+ } else {
+ (void) fprintf(out, "inverse convention\n");
+ }
+
+ level = 0;
+ for (i = 0; i < data->atr_nti; i++) {
+ atr_ti_t *atp = &data->atr_ti[i];
+
+ /*
+ * Various protocols may appear multiple times, indicating
+ * different sets of bits each time. When dealing with T0 and
+ * TD1, the protocol doesn't matter. Otherwise if we have the
+ * same value, we should increment this.
+ */
+ if (i <= 2) {
+ level = 0;
+ } else if (atp->atrti_protocol ==
+ data->atr_ti[i - 1].atrti_protocol) {
+ level++;
+ } else {
+ level = 0;
+ }
+
+ if (i == 0) {
+ (void) fprintf(out, "T0 ");
+ } else {
+ (void) fprintf(out, "TD%u ", i);
+ }
+ (void) fprintf(out, "0x%02x\n",
+ data->atr_raw[atp->atrti_td_idx]);
+ (void) fprintf(out, " |+-> ");
+ if (i == 0) {
+ (void) fprintf(out, "%u historical bytes\n",
+ data->atr_nhistoric);
+ } else {
+ (void) fprintf(out, "protocol T=%u\n",
+ atp->atrti_protocol);
+ }
+ (void) fprintf(out, " v\n");
+ (void) fprintf(out, " 0r%u%u%u%u\n",
+ atp->atrti_flags & ATR_TI_HAVE_TD ? 1 : 0,
+ atp->atrti_flags & ATR_TI_HAVE_TC ? 1 : 0,
+ atp->atrti_flags & ATR_TI_HAVE_TB ? 1 : 0,
+ atp->atrti_flags & ATR_TI_HAVE_TA ? 1 : 0);
+
+ atr_data_dump_ta(atp, out, level);
+ atr_data_dump_tb(atp, out, level);
+ atr_data_dump_tc(atp, out, level);
+ if (atp->atrti_flags & ATR_TI_HAVE_TD) {
+ (void) fprintf(out, " v\n");
+ }
+ }
+
+ atr_data_dump_historical(data, out);
+
+ if (data->atr_flags & ATR_F_HAS_CHECKSUM) {
+ (void) fprintf(out, "TCK 0x%02x\n", data->atr_cksum);
+ } else {
+ (void) fprintf(out, "TCK ----; Checksum not present\n");
+ }
+
+}
+#endif /* _KERNEL */
diff --git a/usr/src/common/ccid/atr.h b/usr/src/common/ccid/atr.h
new file mode 100644
index 0000000000..50c01a44cb
--- /dev/null
+++ b/usr/src/common/ccid/atr.h
@@ -0,0 +1,198 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+#ifndef _ATR_H
+#define _ATR_H
+
+/*
+ * Parse Answer-To-Reset values. This header file is private to illumos and
+ * should not be shipped or used by applications.
+ *
+ * This is based on ISO/IEC 7816-3:2006. It has been designed such that if newer
+ * revisions come out that define reserved values, they will be ignored until
+ * this code is updated.
+ */
+
+#include <sys/types.h>
+#include <sys/usb/clients/ccid/ccid.h>
+#ifndef _KERNEL
+#include <stdio.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The ATR must have at least 2 bytes and then may have up to 33 bytes.
+ */
+#define ATR_LEN_MIN 2
+#define ATR_LEN_MAX 33
+
+typedef enum atr_parsecode {
+ ATR_CODE_OK = 0,
+ ATR_CODE_TOO_SHORT,
+ ATR_CODE_TOO_LONG,
+ ATR_CODE_INVALID_TS,
+ ATR_CODE_OVERRUN,
+ ATR_CODE_UNDERRUN,
+ ATR_CODE_CHECKSUM_ERROR,
+ ATR_CODE_INVALID_TD1
+} atr_parsecode_t;
+
+typedef enum atr_protocol {
+ ATR_P_NONE = 0,
+ ATR_P_T0 = 1 << 0,
+ ATR_P_T1 = 1 << 1
+} atr_protocol_t;
+
+typedef enum atr_convention {
+ ATR_CONVENTION_DIRECT = 0x00,
+ ATR_CONVENTION_INVERSE = 0x01
+} atr_convention_t;
+
+typedef enum atr_clock_stop {
+ ATR_CLOCK_STOP_NONE = 0x00,
+ ATR_CLOCK_STOP_LOW = 0x01,
+ ATR_CLOCK_STOP_HI = 0x02,
+ ATR_CLOCK_STOP_BOTH = 0x03
+} atr_clock_stop_t;
+
+typedef enum atr_data_rate_choice {
+ /*
+ * Indicates that the reader cannot support the data rate needed for the
+ * ICC.
+ */
+ ATR_RATE_UNSUPPORTED = 0x00,
+ /*
+ * Indicates that the reader supports the ICC present, but must run at
+ * the protocol's default rate (Di index = Fi index = 1)
+ */
+ ATR_RATE_USEDEFAULT = 0x01,
+ /*
+ * The reader supports the Di/Fi values that the ICC proposed in its ATR
+ * and no action beyond setting the parameters of the reader is required
+ * (this may be automatic depending on the reader's dwFeatures).
+ */
+ ATR_RATE_USEATR = 0x02,
+ /*
+ * The reader can use the features of the ATR specified. However, it
+ * must change the data rate or frequency that the card is running at to
+ * proceed.
+ */
+ ATR_RATE_USEATR_SETRATE = 0x03
+} atr_data_rate_choice_t;
+
+typedef enum atr_t1_checksum {
+ ATR_T1_CHECKSUM_LRC = 0x00,
+ ATR_T1_CHECKSUM_CRC = 0x01
+} atr_t1_checksum_t;
+
+typedef struct atr_data atr_data_t;
+
+/*
+ * Allocate and free ATR data.
+ */
+extern atr_data_t *atr_data_alloc(void);
+extern void atr_data_free(atr_data_t *);
+
+/*
+ * Reset an allocated ATR data to be ready to parse something else.
+ */
+extern void atr_data_reset(atr_data_t *);
+
+/*
+ * Parse the ATR data into an opaque structure that organizes the data and
+ * allows for various queries to be made on it later.
+ */
+extern atr_parsecode_t atr_parse(const uint8_t *, size_t, atr_data_t *data);
+extern const char *atr_strerror(atr_parsecode_t);
+
+/*
+ * Get an eumeration of supported protocols in this ATR data. Note that if a
+ * reserved protocol is encountered, we may not report it as we don't know of it
+ * at this time.
+ */
+extern atr_protocol_t atr_supported_protocols(atr_data_t *);
+
+/*
+ * Based on the ATR determine what the default protocol is and whether or not it
+ * supports negotiation. When a ICC is not negotiable, it will always start up
+ * with a specific protocol and parameters based on the ATR and be ready to use.
+ * Otherwise, the card will be in a negotiable mode and be set to a default set
+ * of parameters.
+ */
+extern boolean_t atr_params_negotiable(atr_data_t *);
+extern atr_protocol_t atr_default_protocol(atr_data_t *);
+
+/*
+ * Protocol default values.
+ */
+extern uint8_t atr_fi_default_index(void);
+extern uint8_t atr_di_default_index(void);
+
+/*
+ * Obtain the table indexes that should be used by the device.
+ */
+extern uint8_t atr_fi_index(atr_data_t *);
+extern uint8_t atr_di_index(atr_data_t *);
+extern atr_convention_t atr_convention(atr_data_t *);
+extern uint8_t atr_extra_guardtime(atr_data_t *);
+extern uint8_t atr_t0_wi(atr_data_t *);
+extern atr_t1_checksum_t atr_t1_checksum(atr_data_t *);
+extern uint8_t atr_t1_bwi(atr_data_t *);
+extern uint8_t atr_t1_cwi(atr_data_t *);
+extern atr_clock_stop_t atr_clock_stop(atr_data_t *);
+extern uint8_t atr_t1_ifsc(atr_data_t *);
+
+/*
+ * Use this function to determine what set of Di and Fi values should be used by
+ * a reader, based on the parameters from the ATR and the reader's cclass.
+ */
+extern atr_data_rate_choice_t atr_data_rate(atr_data_t *, ccid_class_descr_t *,
+ uint32_t *, uint_t, uint32_t *);
+
+#ifndef _KERNEL
+extern void atr_data_hexdump(const uint8_t *, size_t, FILE *);
+extern void atr_data_dump(atr_data_t *, FILE *);
+#endif
+
+/*
+ * String and table index values.
+ */
+extern const char *atr_protocol_to_string(atr_protocol_t);
+extern uint_t atr_fi_index_to_value(uint8_t);
+extern const char *atr_fi_index_to_string(uint8_t);
+extern const char *atr_fmax_index_to_string(uint8_t);
+extern uint_t atr_di_index_to_value(uint8_t);
+extern const char *atr_di_index_to_string(uint8_t);
+extern const char *atr_clock_stop_to_string(atr_clock_stop_t);
+extern const char *atr_convention_to_string(atr_convention_t);
+
+/*
+ * Functions for generating and testing PPS values. Before calling
+ * atr_pps_fidi_accepted(), one must call atr_pps_valid().
+ */
+#define PPS_BUFFER_MAX 6
+extern uint_t atr_pps_generate(uint8_t *, size_t, atr_protocol_t, boolean_t,
+ uint8_t, uint8_t, boolean_t, uint8_t);
+extern boolean_t atr_pps_valid(void *, size_t, void *, size_t);
+extern boolean_t atr_pps_fidi_accepted(void *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ATR_H */
diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile
index ae663cdc63..7a7ab70dbc 100644
--- a/usr/src/lib/Makefile
+++ b/usr/src/lib/Makefile
@@ -179,6 +179,7 @@ SUBDIRS += \
libofmt \
libpam \
libpcidb \
+ libpcsc \
libpctx \
libpicl \
libpicltree \
@@ -437,6 +438,7 @@ HDRSUBDIRS= \
libofmt \
libpam \
libpcidb \
+ libpcsc \
libpctx \
libpicl \
libpicltree \
diff --git a/usr/src/lib/cfgadm_plugins/Makefile b/usr/src/lib/cfgadm_plugins/Makefile
index 00d258b20a..8fa7f73df7 100644
--- a/usr/src/lib/cfgadm_plugins/Makefile
+++ b/usr/src/lib/cfgadm_plugins/Makefile
@@ -20,13 +20,14 @@
#
#
# Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright 2019 Joyent, Inc.
#
# lib/cfgadm_plugins/Makefile
#
include $(SRC)/Makefile.master
-COMMON_SUBDIRS= scsi pci usb ib fp shp sbd
+COMMON_SUBDIRS= scsi pci usb ib fp shp sbd ccid
sparc_SUBDIRS= ac sysctrl
i386_SUBDIRS= sata
@@ -37,7 +38,7 @@ ALL_SUBDIRS= $(COMMON_SUBDIRS) $(sparc_SUBDIRS) $(i386_SUBDIRS)
MSGSUBDIRS= $(ALL_SUBDIRS)
-all:= TARGET= all
+all:= TARGET= all
install:= TARGET= install
clean:= TARGET= clean
clobber:= TARGET= clobber
diff --git a/usr/src/lib/cfgadm_plugins/ccid/Makefile b/usr/src/lib/cfgadm_plugins/ccid/Makefile
new file mode 100644
index 0000000000..7d4af31c7d
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/ccid/Makefile
@@ -0,0 +1,67 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+include ../../Makefile.lib
+
+$(INTEL_BLD)SUBDIRS = $(MACH) $(BUILD64) $(MACH64)
+
+all := TARGET= all
+clean := TARGET= clean
+clobber := TARGET= clobber
+delete := TARGET= delete
+install := TARGET= install
+_msg := TARGET= _msg
+package := TARGET= package
+
+SED= sed
+GREP= grep
+
+.KEEP_STATE:
+
+all clean delete install package: $(SUBDIRS)
+clobber: $(SUBDIRS)
+ $(RM) $(POFILE) $(POFILES)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+#
+# We don't build any gettext libraries here
+#
+_msg:
+
+$(POFILE): $(POFILES)
+ $(RM) $@
+ $(CAT) $(POFILES) > $@
+
+$(POFILES):
+ $(RM) messages.po
+ $(XGETTEXT) $(XGETFLAGS) `$(GREP) -l gettext */*.[ch]`
+ $(SED) -e '/^# msg/d' -e '/^domain/d' messages.po > $@
+ $(RM) messages.po
+
+FRC:
diff --git a/usr/src/lib/cfgadm_plugins/ccid/Makefile.com b/usr/src/lib/cfgadm_plugins/ccid/Makefile.com
new file mode 100644
index 0000000000..30a2ce1527
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/ccid/Makefile.com
@@ -0,0 +1,67 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#
+# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+LIBRARY= ccid.a
+VERS= .1
+
+OBJECTS= cfga_ccid.o
+
+# include library definitions
+include ../../../Makefile.lib
+
+SRCDIR = ../common
+ROOTLIBDIR= $(ROOT)/usr/lib/cfgadm
+ROOTLIBDIR64= $(ROOTLIBDIR)/$(MACH64)
+
+LIBS= $(DYNLIB)
+
+CFLAGS += $(CCVERBOSE)
+CFLAGS64 += $(CCVERBOSE)
+
+LDLIBS += -lc
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+# Install rules
+
+$(ROOTLIBDIR)/%: % $(ROOTLIBDIR)
+ $(INS.file)
+
+$(ROOTLIBDIR64)/%: % $(ROOTLIBDIR64)
+ $(INS.file)
+
+$(ROOTLIBDIR) $(ROOTLIBDIR64):
+ $(INS.dir)
+
+# include library targets
+include ../../../Makefile.targ
+
+objs/%.o pics/%.o: ../common/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
diff --git a/usr/src/lib/cfgadm_plugins/ccid/amd64/Makefile b/usr/src/lib/cfgadm_plugins/ccid/amd64/Makefile
new file mode 100644
index 0000000000..f2eb3c97a2
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/ccid/amd64/Makefile
@@ -0,0 +1,21 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+include ../Makefile.com
+include ../../../Makefile.lib.64
+
+.KEEP_STATE:
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
diff --git a/usr/src/lib/cfgadm_plugins/ccid/common/cfga_ccid.c b/usr/src/lib/cfgadm_plugins/ccid/common/cfga_ccid.c
new file mode 100644
index 0000000000..3a44580306
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/ccid/common/cfga_ccid.c
@@ -0,0 +1,420 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * CCID cfgadm plugin
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+#define CFGA_PLUGIN_LIB
+#include <config_admin.h>
+
+int cfga_version = CFGA_HSL_V2;
+
+static cfga_err_t
+cfga_ccid_error(cfga_err_t err, char **errp, const char *fmt, ...)
+{
+ va_list ap;
+
+ if (errp == NULL)
+ return (err);
+
+ /*
+ * Try to format a string. However because we have to return allocated
+ * memory, if this fails, then we have no error.
+ */
+ va_start(ap, fmt);
+ (void) vasprintf(errp, fmt, ap);
+ va_end(ap);
+
+ return (err);
+}
+
+cfga_err_t
+cfga_ccid_modify(uccid_cmd_icc_modify_t *modify, const char *ap,
+ struct cfga_confirm *confp, struct cfga_msg *msgp, char **errp,
+ boolean_t force)
+{
+ int fd;
+ uccid_cmd_status_t ucs;
+ uccid_cmd_txn_begin_t begin;
+ boolean_t held = B_FALSE;
+
+ /*
+ * Check ap is valid by doing a status request.
+ */
+ if ((fd = open(ap, O_RDWR)) < 0) {
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp,
+ "failed to open %s: %s", ap, strerror(errno)));
+ }
+
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+
+ if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
+ int e = errno;
+ if (errno == ENODEV) {
+ (void) close(fd);
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp,
+ "ap %s going away", ap));
+ }
+ (void) close(fd);
+ return (cfga_ccid_error(CFGA_ERROR, errp,
+ "ioctl on ap %s failed: %s", ap, strerror(e)));
+ }
+
+ /*
+ * Attempt to get a hold. If we cannot obtain a hold, we will not
+ * perform this unless the user has said we should force this.
+ */
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ begin.uct_flags = UCCID_TXN_DONT_BLOCK;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ if (errno != EBUSY) {
+ int e = errno;
+ (void) close(fd);
+ return (cfga_ccid_error(CFGA_ERROR, errp, "failed to "
+ "begin ccid transaction on ap %s: %s", ap,
+ strerror(e)));
+ }
+
+ /*
+ * If the user didn't force this operation, prompt if we would
+ * interfere.
+ */
+ if (!force) {
+ int confirm = 0;
+ const char *prompt = "CCID slot is held exclusively "
+ "by another program. Proceeding may interrupt "
+ "their functionality. Continue?";
+ if (confp != NULL && confp->appdata_ptr != NULL) {
+ confirm = (*confp->confirm)(confp->appdata_ptr,
+ prompt);
+ }
+
+ if (confirm == 0) {
+ (void) close(fd);
+ return (CFGA_NACK);
+ }
+ }
+ } else {
+ held = B_TRUE;
+ }
+
+ if (ioctl(fd, UCCID_CMD_ICC_MODIFY, modify) != 0) {
+ int e = errno;
+ (void) close(fd);
+ return (cfga_ccid_error(CFGA_ERROR, errp,
+ "failed to modify state on ap %s: %s", ap,
+ strerror(e)));
+ }
+
+ if (held) {
+ uccid_cmd_txn_end_t end;
+
+ bzero(&end, sizeof (end));
+ end.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_flags = UCCID_TXN_END_RELEASE;
+
+ if (ioctl(fd, UCCID_CMD_TXN_END, &end) != 0) {
+ int e = errno;
+ (void) close(fd);
+ return (cfga_ccid_error(CFGA_ERROR, errp, "failed to "
+ "end transaction on ap %s: %s", ap,
+ strerror(e)));
+ }
+ }
+
+ (void) close(fd);
+ return (CFGA_OK);
+
+}
+
+cfga_err_t
+cfga_change_state(cfga_cmd_t cmd, const char *ap, const char *opts,
+ struct cfga_confirm *confp, struct cfga_msg *msgp, char **errp,
+ cfga_flags_t flags)
+{
+ uccid_cmd_icc_modify_t modify;
+
+ if (errp != NULL) {
+ *errp = NULL;
+ }
+
+ if (ap == NULL) {
+ return (cfga_ccid_error(CFGA_APID_NOEXIST, errp, NULL));
+ }
+
+ if (opts != NULL) {
+ return (cfga_ccid_error(CFGA_ERROR, errp,
+ "hardware specific options are not supported"));
+ }
+
+ bzero(&modify, sizeof (modify));
+ modify.uci_version = UCCID_CURRENT_VERSION;
+ switch (cmd) {
+ case CFGA_CMD_CONFIGURE:
+ modify.uci_action = UCCID_ICC_POWER_ON;
+ break;
+ case CFGA_CMD_UNCONFIGURE:
+ modify.uci_action = UCCID_ICC_POWER_OFF;
+ break;
+ default:
+ (void) cfga_help(msgp, opts, flags);
+ return (CFGA_OPNOTSUPP);
+ }
+
+ return (cfga_ccid_modify(&modify, ap, confp, msgp, errp,
+ (flags & CFGA_FLAG_FORCE) != 0));
+}
+
+cfga_err_t
+cfga_private_func(const char *function, const char *ap, const char *opts,
+ struct cfga_confirm *confp, struct cfga_msg *msgp, char **errp,
+ cfga_flags_t flags)
+{
+ uccid_cmd_icc_modify_t modify;
+
+ if (errp != NULL) {
+ *errp = NULL;
+ }
+
+ if (function == NULL) {
+ return (CFGA_ERROR);
+ }
+
+ if (ap == NULL) {
+ return (cfga_ccid_error(CFGA_APID_NOEXIST, errp, NULL));
+ }
+
+ if (opts != NULL) {
+ return (cfga_ccid_error(CFGA_ERROR, errp,
+ "hardware specific options are not supported"));
+ }
+
+ if (strcmp(function, "warm_reset") != 0) {
+ return (CFGA_OPNOTSUPP);
+ }
+
+ bzero(&modify, sizeof (modify));
+ modify.uci_version = UCCID_CURRENT_VERSION;
+ modify.uci_action = UCCID_ICC_WARM_RESET;
+
+ return (cfga_ccid_modify(&modify, ap, confp, msgp, errp,
+ (flags & CFGA_FLAG_FORCE) != 0));
+}
+
+/*
+ * We don't support the test entry point for CCID.
+ */
+cfga_err_t
+cfga_test(const char *ap, const char *opts, struct cfga_msg *msgp, char **errp,
+ cfga_flags_t flags)
+{
+ (void) cfga_help(msgp, opts, flags);
+ return (CFGA_OPNOTSUPP);
+}
+
+static void
+cfga_ccid_fill_info(const uccid_cmd_status_t *ucs, char *buf, size_t len)
+{
+ const char *product, *serial, *tran, *prot;
+ uint_t bits = CCID_CLASS_F_TPDU_XCHG | CCID_CLASS_F_SHORT_APDU_XCHG |
+ CCID_CLASS_F_EXT_APDU_XCHG;
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_PRODUCT_VALID) != 0) {
+ product = ucs->ucs_product;
+ } else {
+ product = "<unknown>";
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_SERIAL_VALID) != 0) {
+ serial = ucs->ucs_serial;
+ } else {
+ serial = "<unknown>";
+ }
+
+ switch (ucs->ucs_class.ccd_dwFeatures & bits) {
+ case 0:
+ tran = "Character";
+ break;
+ case CCID_CLASS_F_TPDU_XCHG:
+ tran = "TPDU";
+ break;
+ case CCID_CLASS_F_SHORT_APDU_XCHG:
+ case CCID_CLASS_F_EXT_APDU_XCHG:
+ tran = "APDU";
+ break;
+ default:
+ tran = "Unknown";
+ break;
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) != 0) {
+ switch (ucs->ucs_prot) {
+ case UCCID_PROT_T0:
+ prot = " (T=0)";
+ break;
+ case UCCID_PROT_T1:
+ prot = " (T=1)";
+ break;
+ default:
+ prot = "<unknown>";
+ break;
+ }
+ } else {
+ prot = "<unknown>";
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) != 0) {
+ (void) snprintf(buf, len, "Product: %s Serial: %s "
+ "Transport: %s Protocol: %s", product, serial,
+ tran, prot);
+ } else {
+ (void) snprintf(buf, len, "Product: %s Serial: %s ",
+ product, serial);
+ }
+}
+
+cfga_err_t
+cfga_list_ext(const char *ap, struct cfga_list_data **ap_list, int *nlist,
+ const char *opts, const char *listopts, char **errp, cfga_flags_t flags)
+{
+ int fd;
+ uccid_cmd_status_t ucs;
+ struct cfga_list_data *cld;
+
+ if (errp != NULL) {
+ *errp = NULL;
+ }
+
+ if (ap == NULL) {
+ return (cfga_ccid_error(CFGA_APID_NOEXIST, errp, NULL));
+ }
+
+ if (opts != NULL) {
+ return (cfga_ccid_error(CFGA_ERROR, errp,
+ "hardware specific options are not supported"));
+ }
+
+ if ((fd = open(ap, O_RDWR)) < 0) {
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp,
+ "failed to open %s: %s", ap, strerror(errno)));
+ }
+
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+
+ if (ioctl(fd, UCCID_CMD_STATUS, &ucs) != 0) {
+ int e = errno;
+ (void) close(fd);
+ if (e == ENODEV) {
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp,
+ "ap %s going away", ap));
+ }
+ return (cfga_ccid_error(CFGA_ERROR, errp,
+ "ioctl on ap %s failed: %s", ap, strerror(e)));
+ }
+ (void) close(fd);
+
+ if ((cld = calloc(1, sizeof (*cld))) == NULL) {
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp, "failed to "
+ "allocate memory for list entry"));
+ }
+
+ if (snprintf(cld->ap_log_id, sizeof (cld->ap_log_id), "ccid%d/slot%u",
+ ucs.ucs_instance, ucs.ucs_slot) >= sizeof (cld->ap_log_id)) {
+ free(cld);
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp, "ap %s logical id"
+ " was too large", ap));
+ }
+
+ if (strlcpy(cld->ap_phys_id, ap, sizeof (cld->ap_phys_id)) >=
+ sizeof (cld->ap_phys_id)) {
+ free(cld);
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp,
+ "ap %s physical id was too long", ap));
+ }
+
+ cld->ap_class[0] = '\0';
+
+ if ((ucs.ucs_status & UCCID_STATUS_F_CARD_PRESENT) != 0) {
+ cld->ap_r_state = CFGA_STAT_CONNECTED;
+ if ((ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE) != 0) {
+ cld->ap_o_state = CFGA_STAT_CONFIGURED;
+ } else {
+ cld->ap_o_state = CFGA_STAT_UNCONFIGURED;
+ }
+ } else {
+ cld->ap_r_state = CFGA_STAT_EMPTY;
+ cld->ap_o_state = CFGA_STAT_UNCONFIGURED;
+ }
+
+ /*
+ * We should probably have a way to indicate that there's an error when
+ * the ICC is basically foobar'd. We should also allow the status ioctl
+ * to know that the slot is resetting or something else is going on.
+ */
+ if ((ucs.ucs_class.ccd_dwFeatures &
+ (CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG)) == 0) {
+ cld->ap_cond = CFGA_COND_UNUSABLE;
+ } else {
+ cld->ap_cond = CFGA_COND_OK;
+ }
+ cld->ap_busy = 0;
+ cld->ap_status_time = (time_t)-1;
+ cfga_ccid_fill_info(&ucs, cld->ap_info, sizeof (cld->ap_info));
+ if (strlcpy(cld->ap_type, "icc", sizeof (cld->ap_type)) >=
+ sizeof (cld->ap_type)) {
+ free(cld);
+ return (cfga_ccid_error(CFGA_LIB_ERROR, errp,
+ "ap %s type overflowed ICC field", ap));
+ }
+
+ *ap_list = cld;
+ *nlist = 1;
+ return (CFGA_OK);
+}
+
+cfga_err_t
+cfga_help(struct cfga_msg *msgp, const char *opts, cfga_flags_t flags)
+{
+ (void) (*msgp->message_routine)(msgp, "CCID specific commands:\n");
+ (void) (*msgp->message_routine)(msgp,
+ " cfgadm -c [configure|unconfigure] ap_id [ap_id...]\n");
+ (void) (*msgp->message_routine)(msgp,
+ " cfgadm -x warm_reset ap_id [ap_id...]\n");
+
+ return (CFGA_OK);
+}
+
+int
+cfga_ap_id_cmp(const cfga_ap_log_id_t ap_id1, const cfga_ap_log_id_t ap_id2)
+{
+ return (strcmp(ap_id1, ap_id2));
+}
diff --git a/usr/src/lib/cfgadm_plugins/ccid/common/mapfile-vers b/usr/src/lib/cfgadm_plugins/ccid/common/mapfile-vers
new file mode 100644
index 0000000000..7c7f7d2c0e
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/ccid/common/mapfile-vers
@@ -0,0 +1,42 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+#
+# MAPFILE HEADER START
+#
+# WARNING: STOP NOW. DO NOT MODIFY THIS FILE.
+# Object versioning must comply with the rules detailed in
+#
+# usr/src/lib/README.mapfiles
+#
+# You should not be making modifications here until you've read the most current
+# copy of that file. If you need help, contact a gatekeeper for guidance.
+#
+# MAPFILE HEADER END
+#
+
+$mapfile_version 2
+
+SYMBOL_VERSION SUNWprivate_1.1 {
+ global:
+ cfga_change_state;
+ cfga_help;
+ cfga_list_ext;
+ cfga_private_func;
+ cfga_test;
+ cfga_version;
+ local:
+ *;
+};
diff --git a/usr/src/lib/cfgadm_plugins/ccid/i386/Makefile b/usr/src/lib/cfgadm_plugins/ccid/i386/Makefile
new file mode 100644
index 0000000000..66d7a51776
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/ccid/i386/Makefile
@@ -0,0 +1,20 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+include ../Makefile.com
+
+.KEEP_STATE:
+
+install: all $(ROOTLIBS) $(ROOTLINKS)
diff --git a/usr/src/lib/libcmdutils/common/nicenum.c b/usr/src/lib/libcmdutils/common/nicenum.c
index 8e3202f792..a9161c422c 100644
--- a/usr/src/lib/libcmdutils/common/nicenum.c
+++ b/usr/src/lib/libcmdutils/common/nicenum.c
@@ -22,6 +22,7 @@
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2017 Jason king
+ * Copyright 2019, Joyent, Inc.
*/
#include <stdio.h>
@@ -44,8 +45,13 @@ nicenum_scale(uint64_t n, size_t units, char *buf, size_t buflen,
uint64_t divisor = 1;
int index = 0;
int rc = 0;
+ const char *spc = "";
char u;
+ if (flags & NN_UNIT_SPACE) {
+ spc = " ";
+ }
+
if (units == 0)
units = 1;
@@ -84,17 +90,17 @@ nicenum_scale(uint64_t n, size_t units, char *buf, size_t buflen,
u = " KMGTPE"[index];
if (index == 0) {
- rc = snprintf(buf, buflen, "%llu", n);
+ rc = snprintf(buf, buflen, "%llu%s", n, spc);
} else if (n % divisor == 0) {
/*
* If this is an even multiple of the base, always display
* without any decimal precision.
*/
- rc = snprintf(buf, buflen, "%llu%c", n / divisor, u);
+ rc = snprintf(buf, buflen, "%llu%s%c", n / divisor, spc, u);
} else {
/*
* We want to choose a precision that reflects the best choice
- * for fitting in 5 characters. This can get rather tricky
+ * for fitting in the buffer. This can get rather tricky
* when we have numbers that are very close to an order of
* magnitude. For example, when displaying 10239 (which is
* really 9.999K), we want only a single place of precision
@@ -104,8 +110,8 @@ nicenum_scale(uint64_t n, size_t units, char *buf, size_t buflen,
*/
int i;
for (i = 2; i >= 0; i--) {
- if ((rc = snprintf(buf, buflen, "%.*f%c", i,
- (double)n / divisor, u)) <= 5)
+ if ((rc = snprintf(buf, buflen, "%.*f%s%c", i,
+ (double)n / divisor, spc, u)) <= (buflen - 1))
break;
}
}
diff --git a/usr/src/lib/libcmdutils/libcmdutils.h b/usr/src/lib/libcmdutils/libcmdutils.h
index c9a61aab4d..54c025c9d9 100644
--- a/usr/src/lib/libcmdutils/libcmdutils.h
+++ b/usr/src/lib/libcmdutils/libcmdutils.h
@@ -26,7 +26,7 @@
* Copyright (c) 2013 RackTop Systems.
*/
/*
- * Copyright 2018 Joyent, Inc.
+ * Copyright 2019 Joyent, Inc.
*/
/*
@@ -163,6 +163,7 @@ extern int findnextuid(uid_t, uid_t, uid_t *);
extern int findnextgid(gid_t, gid_t, gid_t *);
#define NN_DIVISOR_1000 (1U << 0)
+#define NN_UNIT_SPACE (1U << 1)
/* Minimum size for the output of nicenum, including NULL */
#define NN_NUMBUF_SZ (6)
diff --git a/usr/src/lib/libpcsc/Makefile b/usr/src/lib/libpcsc/Makefile
new file mode 100644
index 0000000000..b5fdecb2e4
--- /dev/null
+++ b/usr/src/lib/libpcsc/Makefile
@@ -0,0 +1,43 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+include ../Makefile.lib
+
+HDRS = wintypes.h winscard.h
+HDRDIR = common
+SUBDIRS = $(MACH)
+$(BUILD64)SUBDIRS += $(MACH64)
+
+all := TARGET = all
+clean := TARGET = clean
+clobber := TARGET = clobber
+install := TARGET = install
+
+.KEEP_STATE:
+
+all clean clobber: $(SUBDIRS)
+
+install: $(SUBDIRS) install_h
+
+install_h: $(ROOTHDRS)
+
+check: $(CHECKHDRS)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
+include ../Makefile.targ
diff --git a/usr/src/lib/libpcsc/Makefile.com b/usr/src/lib/libpcsc/Makefile.com
new file mode 100644
index 0000000000..7bc91ef667
--- /dev/null
+++ b/usr/src/lib/libpcsc/Makefile.com
@@ -0,0 +1,32 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+LIBRARY = libpcsc.a
+VERS = .1
+OBJECTS = libpcsc.o
+
+include ../../Makefile.lib
+
+LIBS = $(DYNLIB)
+LDLIBS += -lc
+CPPFLAGS += -I../common
+
+SRCDIR = ../common
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+include ../../Makefile.targ
diff --git a/usr/src/lib/libpcsc/amd64/Makefile b/usr/src/lib/libpcsc/amd64/Makefile
new file mode 100644
index 0000000000..a32567f965
--- /dev/null
+++ b/usr/src/lib/libpcsc/amd64/Makefile
@@ -0,0 +1,19 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+include ../Makefile.com
+include ../../Makefile.lib.64
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
diff --git a/usr/src/lib/libpcsc/common/libpcsc.c b/usr/src/lib/libpcsc/common/libpcsc.c
new file mode 100644
index 0000000000..41d646e8b1
--- /dev/null
+++ b/usr/src/lib/libpcsc/common/libpcsc.c
@@ -0,0 +1,615 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <fts.h>
+#include <errno.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/debug.h>
+#include <sys/filio.h>
+#include <sys/usb/clients/ccid/uccid.h>
+
+#include <winscard.h>
+
+/*
+ * Implementation of the PCSC library leveraging the uccid framework.
+ */
+
+/*
+ * The library handle is basically unused today. We keep this around such that
+ * consumers which expect to receive a non-NULL opaque handle have something
+ * they can use.
+ */
+typedef struct pcsc_hdl {
+ hrtime_t pcsc_create_time;
+} pcsc_hdl_t;
+
+typedef struct pcsc_card {
+ int pcc_fd;
+} pcsc_card_t;
+
+/*
+ * Required globals
+ */
+SCARD_IO_REQUEST g_rgSCardT0Pci = {
+ SCARD_PROTOCOL_T0,
+ 0
+};
+
+SCARD_IO_REQUEST g_rgSCardT1Pci = {
+ SCARD_PROTOCOL_T1,
+ 0
+};
+
+SCARD_IO_REQUEST g_rgSCardRawPci = {
+ SCARD_PROTOCOL_RAW,
+ 0
+};
+
+const char *
+pcsc_stringify_error(const LONG err)
+{
+ switch (err) {
+ case SCARD_S_SUCCESS:
+ return ("no error");
+ case SCARD_F_INTERNAL_ERROR:
+ return ("internal error");
+ case SCARD_E_CANCELLED:
+ return ("request cancelled");
+ case SCARD_E_INVALID_HANDLE:
+ return ("invalid handle");
+ case SCARD_E_INVALID_PARAMETER:
+ return ("invalid parameter");
+ case SCARD_E_NO_MEMORY:
+ return ("no memory");
+ case SCARD_E_INSUFFICIENT_BUFFER:
+ return ("buffer was insufficiently sized");
+ case SCARD_E_INVALID_VALUE:
+ return ("invalid value passed");
+ case SCARD_E_UNKNOWN_READER:
+ return ("unknown reader");
+ case SCARD_E_TIMEOUT:
+ return ("timeout occurred");
+ case SCARD_E_SHARING_VIOLATION:
+ return ("sharing violation");
+ case SCARD_E_NO_SMARTCARD:
+ return ("no smartcard present");
+ case SCARD_E_UNKNOWN_CARD:
+ return ("unknown ICC");
+ case SCARD_E_PROTO_MISMATCH:
+ return ("protocol mismatch");
+ case SCARD_F_COMM_ERROR:
+ return ("communication error");
+ case SCARD_F_UNKNOWN_ERROR:
+ return ("unknown error");
+ case SCARD_E_READER_UNAVAILABLE:
+ return ("reader unavailable");
+ case SCARD_E_NO_SERVICE:
+ return ("service error");
+ case SCARD_E_UNSUPPORTED_FEATURE:
+ return ("ICC requires unsupported feature");
+ case SCARD_E_NO_READERS_AVAILABLE:
+ return ("no readers avaiable");
+ case SCARD_W_UNSUPPORTED_CARD:
+ return ("ICC unsupported");
+ case SCARD_W_UNPOWERED_CARD:
+ return ("ICC is not powered");
+ case SCARD_W_RESET_CARD:
+ return ("ICC was reset");
+ case SCARD_W_REMOVED_CARD:
+ return ("ICC has been removed");
+ default:
+ return ("unknown error");
+ }
+}
+
+
+/*
+ * This is called when a caller wishes to open a new Library context.
+ */
+LONG
+SCardEstablishContext(DWORD scope, LPCVOID unused0, LPCVOID unused1,
+ LPSCARDCONTEXT outp)
+{
+ pcsc_hdl_t *hdl;
+
+ if (outp == NULL) {
+ return (SCARD_E_INVALID_PARAMETER);
+ }
+
+ if (scope != SCARD_SCOPE_SYSTEM) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ hdl = calloc(1, sizeof (pcsc_hdl_t));
+ if (hdl == NULL) {
+ return (SCARD_E_NO_MEMORY);
+ }
+
+ hdl->pcsc_create_time = gethrtime();
+ *outp = hdl;
+ return (SCARD_S_SUCCESS);
+}
+
+/*
+ * This is called to free a library context from a client.
+ */
+LONG
+SCardReleaseContext(SCARDCONTEXT hdl)
+{
+ free(hdl);
+ return (SCARD_S_SUCCESS);
+}
+
+/*
+ * This is called to release memory allocated by the library. No, it doesn't
+ * make sense to take a const pointer when being given memory to free. It just
+ * means we have to cast it, but remember: this isn't our API.
+ */
+LONG
+SCardFreeMemory(SCARDCONTEXT unused, LPCVOID mem)
+{
+ free((void *)mem);
+ return (SCARD_S_SUCCESS);
+}
+
+/*
+ * This is called by a caller to get a list of readers that exist in the system.
+ * If lenp is set to SCARD_AUTOALLOCATE, then we are responsible for dealing
+ * with this memory.
+ */
+LONG
+SCardListReaders(SCARDCONTEXT unused, LPCSTR groups, LPSTR bufp, LPDWORD lenp)
+{
+ FTS *fts;
+ FTSENT *ent;
+ char *const root[] = { "/dev/ccid", NULL };
+ char *ubuf;
+ char **readers;
+ uint32_t len, ulen, npaths, nalloc, off, i;
+ int ret;
+
+ if (groups != NULL || lenp == NULL) {
+ return (SCARD_E_INVALID_PARAMETER);
+ }
+
+ fts = fts_open(root, FTS_LOGICAL | FTS_NOCHDIR, NULL);
+ if (fts == NULL) {
+ switch (errno) {
+ case ENOENT:
+ case ENOTDIR:
+ return (SCARD_E_NO_READERS_AVAILABLE);
+ case ENOMEM:
+ case EAGAIN:
+ return (SCARD_E_NO_MEMORY);
+ default:
+ return (SCARD_E_NO_SERVICE);
+ }
+ }
+
+ npaths = nalloc = 0;
+ /*
+ * Account for the NUL we'll have to place at the end of this.
+ */
+ len = 1;
+ readers = NULL;
+ while ((ent = fts_read(fts)) != NULL) {
+ size_t plen;
+
+ if (ent->fts_level != 2 || ent->fts_info == FTS_DP)
+ continue;
+
+ if (ent->fts_info == FTS_ERR || ent->fts_info == FTS_NS)
+ continue;
+
+ if (S_ISCHR(ent->fts_statp->st_mode) == 0)
+ continue;
+
+ plen = strlen(ent->fts_path) + 1;
+ if (UINT32_MAX - len <= plen) {
+ /*
+ * I mean, it's true. But I wish I could just give you
+ * EOVERFLOW.
+ */
+ ret = SCARD_E_INSUFFICIENT_BUFFER;
+ goto out;
+ }
+
+ if (npaths == nalloc) {
+ char **tmp;
+
+ nalloc += 8;
+ tmp = reallocarray(readers, nalloc, sizeof (char *));
+ if (tmp == NULL) {
+ ret = SCARD_E_NO_MEMORY;
+ goto out;
+ }
+ readers = tmp;
+ }
+ readers[npaths] = strdup(ent->fts_path);
+ npaths++;
+ len += plen;
+ }
+
+ if (npaths == 0) {
+ ret = SCARD_E_NO_READERS_AVAILABLE;
+ goto out;
+ }
+
+ ulen = *lenp;
+ *lenp = len;
+ if (ulen != SCARD_AUTOALLOCATE) {
+ if (bufp == NULL) {
+ ret = SCARD_S_SUCCESS;
+ goto out;
+ }
+
+ if (ulen < len) {
+ ret = SCARD_E_INSUFFICIENT_BUFFER;
+ goto out;
+ }
+
+ ubuf = bufp;
+ } else {
+ char **bufpp;
+ if (bufp == NULL) {
+ ret = SCARD_E_INVALID_PARAMETER;
+ goto out;
+ }
+
+ ubuf = malloc(ulen);
+ if (ubuf == NULL) {
+ ret = SCARD_E_NO_MEMORY;
+ goto out;
+ }
+
+ bufpp = (void *)bufp;
+ *bufpp = ubuf;
+ }
+ ret = SCARD_S_SUCCESS;
+
+ for (off = 0, i = 0; i < npaths; i++) {
+ size_t slen = strlen(readers[i]) + 1;
+ bcopy(readers[i], ubuf + off, slen);
+ off += slen;
+ VERIFY3U(off, <=, len);
+ }
+ VERIFY3U(off, ==, len - 1);
+ ubuf[off] = '\0';
+out:
+ for (i = 0; i < npaths; i++) {
+ free(readers[i]);
+ }
+ free(readers);
+ (void) fts_close(fts);
+ return (ret);
+}
+
+static LONG
+uccid_status_helper(int fd, DWORD prots, uccid_cmd_status_t *ucs)
+{
+ /*
+ * Get the status of this slot and find out information about the slot.
+ * We need to see if there's an ICC present and if it matches the
+ * current protocol. If not, then we have to fail this.
+ */
+ bzero(ucs, sizeof (uccid_cmd_status_t));
+ ucs->ucs_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_STATUS, ucs) != 0) {
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_CARD_PRESENT) == 0) {
+ return (SCARD_W_REMOVED_CARD);
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_CARD_ACTIVE) == 0) {
+ return (SCARD_W_UNPOWERED_CARD);
+ }
+
+ if ((ucs->ucs_status & UCCID_STATUS_F_PARAMS_VALID) == 0) {
+ return (SCARD_W_UNSUPPORTED_CARD);
+ }
+
+ if ((ucs->ucs_prot & prots) == 0) {
+ return (SCARD_E_PROTO_MISMATCH);
+ }
+
+ return (0);
+}
+
+LONG
+SCardConnect(SCARDCONTEXT hdl, LPCSTR reader, DWORD mode, DWORD prots,
+ LPSCARDHANDLE iccp, LPDWORD protp)
+{
+ LONG ret;
+ uccid_cmd_status_t ucs;
+ pcsc_card_t *card;
+
+ if (reader == NULL) {
+ return (SCARD_E_UNKNOWN_READER);
+ }
+
+ if (iccp == NULL || protp == NULL) {
+ return (SCARD_E_INVALID_PARAMETER);
+ }
+
+ if (mode != SCARD_SHARE_SHARED) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if ((prots & ~(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
+ SCARD_PROTOCOL_RAW | SCARD_PROTOCOL_T15)) != 0) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if ((prots & (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) == 0) {
+ return (SCARD_E_UNSUPPORTED_FEATURE);
+ }
+
+ if ((card = malloc(sizeof (*card))) == NULL) {
+ return (SCARD_E_NO_MEMORY);
+ }
+
+ if ((card->pcc_fd = open(reader, O_RDWR)) < 0) {
+ free(card);
+ switch (errno) {
+ case ENOENT:
+ return (SCARD_E_UNKNOWN_READER);
+ default:
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+ }
+
+ if ((ret = uccid_status_helper(card->pcc_fd, prots, &ucs)) != 0)
+ goto cleanup;
+
+ *protp = ucs.ucs_prot;
+ *iccp = card;
+ return (SCARD_S_SUCCESS);
+cleanup:
+ (void) close(card->pcc_fd);
+ free(card);
+ return (ret);
+}
+
+LONG
+SCardDisconnect(SCARDHANDLE arg, DWORD disposition)
+{
+ pcsc_card_t *card = arg;
+
+ if (arg == NULL) {
+ return (SCARD_E_INVALID_HANDLE);
+ }
+
+ if (disposition != SCARD_LEAVE_CARD) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if (close(card->pcc_fd) != 0) {
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+
+ free(card);
+ return (SCARD_S_SUCCESS);
+}
+
+LONG
+SCardBeginTransaction(SCARDHANDLE arg)
+{
+ uccid_cmd_txn_begin_t txn;
+ pcsc_card_t *card = arg;
+
+ if (card == NULL) {
+ return (SCARD_E_INVALID_HANDLE);
+ }
+
+ /*
+ * The semantics of pcsc are that this operation does not block, but
+ * instead fails if we cannot grab it immediately.
+ */
+ bzero(&txn, sizeof (uccid_cmd_txn_begin_t));
+ txn.uct_version = UCCID_CURRENT_VERSION;
+ txn.uct_flags = UCCID_TXN_DONT_BLOCK;
+
+ if (ioctl(card->pcc_fd, UCCID_CMD_TXN_BEGIN, &txn) != 0) {
+ VERIFY3S(errno, !=, EFAULT);
+ switch (errno) {
+ case ENODEV:
+ return (SCARD_E_READER_UNAVAILABLE);
+ case EEXIST:
+ /*
+ * This is an odd case. It's used to tell us that we
+ * already have it. For now, just treat it as success.
+ */
+ return (SCARD_S_SUCCESS);
+ case EBUSY:
+ return (SCARD_E_SHARING_VIOLATION);
+ /*
+ * EINPROGRESS is a weird case. It means that we were trying to
+ * grab a hold while another instance using the same handle was.
+ * For now, treat it as an unknown error.
+ */
+ case EINPROGRESS:
+ case EINTR:
+ default:
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+ }
+ return (SCARD_S_SUCCESS);
+}
+
+LONG
+SCardEndTransaction(SCARDHANDLE arg, DWORD state)
+{
+ uccid_cmd_txn_end_t txn;
+ pcsc_card_t *card = arg;
+
+ if (card == NULL) {
+ return (SCARD_E_INVALID_HANDLE);
+ }
+
+ bzero(&txn, sizeof (uccid_cmd_txn_end_t));
+ txn.uct_version = UCCID_CURRENT_VERSION;
+
+ switch (state) {
+ case SCARD_LEAVE_CARD:
+ txn.uct_flags = UCCID_TXN_END_RELEASE;
+ break;
+ case SCARD_RESET_CARD:
+ txn.uct_flags = UCCID_TXN_END_RESET;
+ break;
+ case SCARD_UNPOWER_CARD:
+ case SCARD_EJECT_CARD:
+ default:
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if (ioctl(card->pcc_fd, UCCID_CMD_TXN_END, &txn) != 0) {
+ VERIFY3S(errno, !=, EFAULT);
+ switch (errno) {
+ case ENODEV:
+ return (SCARD_E_READER_UNAVAILABLE);
+ case ENXIO:
+ return (SCARD_E_SHARING_VIOLATION);
+ default:
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+ }
+ return (SCARD_S_SUCCESS);
+}
+
+LONG
+SCardReconnect(SCARDHANDLE arg, DWORD mode, DWORD prots, DWORD init,
+ LPDWORD protp)
+{
+ uccid_cmd_status_t ucs;
+ pcsc_card_t *card = arg;
+ LONG ret;
+
+ if (card == NULL) {
+ return (SCARD_E_INVALID_HANDLE);
+ }
+
+ if (protp == NULL) {
+ return (SCARD_E_INVALID_PARAMETER);
+ }
+
+ if (mode != SCARD_SHARE_SHARED) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if ((prots & ~(SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1 |
+ SCARD_PROTOCOL_RAW | SCARD_PROTOCOL_T15)) != 0) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if ((prots & (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1)) == 0) {
+ return (SCARD_E_UNSUPPORTED_FEATURE);
+ }
+
+ if (init != SCARD_LEAVE_CARD) {
+ return (SCARD_E_INVALID_VALUE);
+ }
+
+ if ((ret = uccid_status_helper(card->pcc_fd, prots, &ucs)) != 0)
+ return (ret);
+
+ *protp = ucs.ucs_prot;
+ return (SCARD_S_SUCCESS);
+}
+
+LONG
+SCardTransmit(SCARDHANDLE arg, const SCARD_IO_REQUEST *sendreq,
+ LPCBYTE sendbuf, DWORD sendlen, SCARD_IO_REQUEST *recvreq, LPBYTE recvbuf,
+ LPDWORD recvlenp)
+{
+ int len;
+ ssize_t ret;
+ pcsc_card_t *card = arg;
+
+ if (card == NULL) {
+ return (SCARD_E_INVALID_HANDLE);
+ }
+
+ /*
+ * Ignore sendreq / recvreq.
+ */
+ if (sendbuf == NULL || recvbuf == NULL || recvlenp == NULL) {
+ return (SCARD_E_INVALID_PARAMETER);
+ }
+
+ /*
+ * The CCID write will always consume all data or none.
+ */
+ ret = write(card->pcc_fd, sendbuf, sendlen);
+ if (ret == -1) {
+ switch (errno) {
+ case E2BIG:
+ return (SCARD_E_INVALID_PARAMETER);
+ case ENODEV:
+ return (SCARD_E_READER_UNAVAILABLE);
+ case EACCES:
+ case EBUSY:
+ return (SCARD_E_SHARING_VIOLATION);
+ case ENXIO:
+ return (SCARD_W_REMOVED_CARD);
+ case EFAULT:
+ return (SCARD_E_INVALID_PARAMETER);
+ case ENOMEM:
+ default:
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+ }
+ ASSERT3S(ret, ==, sendlen);
+
+ /*
+ * Now, we should be able to block in read.
+ */
+ ret = read(card->pcc_fd, recvbuf, *recvlenp);
+ if (ret == -1) {
+ switch (errno) {
+ case EINVAL:
+ case EOVERFLOW:
+ /*
+ * This means that we need to update len with the real
+ * one.
+ */
+ if (ioctl(card->pcc_fd, FIONREAD, &len) != 0) {
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+ *recvlenp = len;
+ return (SCARD_E_INSUFFICIENT_BUFFER);
+ case ENODEV:
+ return (SCARD_E_READER_UNAVAILABLE);
+ case EACCES:
+ case EBUSY:
+ return (SCARD_E_SHARING_VIOLATION);
+ case EFAULT:
+ return (SCARD_E_INVALID_PARAMETER);
+ case ENODATA:
+ default:
+ return (SCARD_F_UNKNOWN_ERROR);
+ }
+ }
+
+ *recvlenp = ret;
+
+ return (SCARD_S_SUCCESS);
+}
diff --git a/usr/src/lib/libpcsc/common/mapfile-vers b/usr/src/lib/libpcsc/common/mapfile-vers
new file mode 100644
index 0000000000..5a3786670a
--- /dev/null
+++ b/usr/src/lib/libpcsc/common/mapfile-vers
@@ -0,0 +1,50 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+#
+# MAPFILE HEADER START
+#
+# WARNING: STOP NOW. DO NOT MODIFY THIS FILE.
+# Object versioning must comply with the rules detailed in
+#
+# usr/src/lib/README.mapfiles
+#
+# You should not be making modifications here until you've read the most current
+# copy of that file. If you need help, contact a gatekeeper for guidance.
+#
+# MAPFILE HEADER END
+#
+
+$mapfile_version 2
+
+SYMBOL_VERSION SUNWprivate {
+ global:
+ g_rgSCardRawPci;
+ g_rgSCardT0Pci;
+ g_rgSCardT1Pci;
+ SCardEstablishContext;
+ SCardReleaseContext;
+ SCardFreeMemory;
+ SCardListReaders;
+ SCardConnect;
+ SCardDisconnect;
+ SCardBeginTransaction;
+ SCardEndTransaction;
+ SCardReconnect;
+ SCardTransmit;
+ pcsc_stringify_error;
+ local:
+ *;
+};
diff --git a/usr/src/lib/libpcsc/common/winscard.h b/usr/src/lib/libpcsc/common/winscard.h
new file mode 100644
index 0000000000..bec4960040
--- /dev/null
+++ b/usr/src/lib/libpcsc/common/winscard.h
@@ -0,0 +1,144 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019 Joyent, Inc.
+ */
+
+#ifndef _WINSCARD_H
+#define _WINSCARD_H
+
+/*
+ * This library provides a compatibility interface with programs designed
+ * against the PC SmartCard Library. This originates from Microsoft and has been
+ * used on a few different forms over the years by folks. The purpose of this
+ * library is for compatibility.
+ *
+ * At the time of this writing, Microsofts API documentation can be found here:
+ * https://docs.microsoft.com/en-us/windows/win32/api/winscard/
+ *
+ * New consumers should not use this library and instead should leverage
+ * ccid(7D) instead.
+ */
+
+#include <stdint.h>
+#include <wintypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * This is a departure from the PCSC system which defines this as a LONG,
+ * which is the same size on 32bit and 64bit Windows (ILP32 and LLP64).
+ * We need to use the real native pointer size for the context handle as
+ * it wouldn't fit into a LONG on our LP64 platform.
+ */
+typedef void *SCARDCONTEXT;
+typedef void **PSCARDCONTEXT;
+typedef void **LPSCARDCONTEXT;
+typedef void *SCARDHANDLE;
+typedef void **PSCARDHANDLE;
+typedef void **LPSCARDHANDLE;
+
+/*
+ * Conventionally this is supposed to be packed.
+ */
+#pragma pack(1)
+typedef struct {
+ unsigned long dwProtocol;
+ unsigned long cbPciLength;
+} SCARD_IO_REQUEST, *PSCARD_IO_REQUEST, *LPSCARD_IO_REQUEST;
+#pragma pack()
+
+extern SCARD_IO_REQUEST g_rgSCardT0Pci, g_rgSCardT1Pci, g_rgSCardRawPci;
+#define SCARD_PCI_T0 (&g_rgSCardT0Pci)
+#define SCARD_PCI_T1 (&g_rgSCardT1Pci)
+#define SCARD_PCI_RAW (&g_rgSCardRawPci)
+
+/*
+ * Return values and error codes. We strive to use the same error codes as
+ * Microsoft.
+ */
+#define SCARD_S_SUCCESS ((LONG)0x00000000)
+#define SCARD_F_INTERNAL_ERROR ((LONG)0x80100001)
+#define SCARD_E_CANCELLED ((LONG)0x80100002)
+#define SCARD_E_INVALID_HANDLE ((LONG)0x80100003)
+#define SCARD_E_INVALID_PARAMETER ((LONG)0x80100004)
+#define SCARD_E_NO_MEMORY ((LONG)0x80100006)
+#define SCARD_E_INSUFFICIENT_BUFFER ((LONG)0x80100008)
+#define SCARD_E_UNKNOWN_READER ((LONG)0x80100009)
+#define SCARD_E_TIMEOUT ((LONG)0x8010000a)
+#define SCARD_E_SHARING_VIOLATION ((LONG)0x8010000b)
+#define SCARD_E_NO_SMARTCARD ((LONG)0x8010000c)
+#define SCARD_E_UNKNOWN_CARD ((LONG)0x8010000d)
+#define SCARD_E_PROTO_MISMATCH ((LONG)0x8010000f)
+#define SCARD_E_INVALID_VALUE ((LONG)0x80100011)
+#define SCARD_F_COMM_ERROR ((LONG)0x80100013)
+#define SCARD_F_UNKNOWN_ERROR ((LONG)0x80100014)
+#define SCARD_E_READER_UNAVAILABLE ((LONG)0x80100017)
+#define SCARD_E_NO_SERVICE ((LONG)0x8010001D)
+#define SCARD_E_UNSUPPORTED_FEATURE ((LONG)0x80100022)
+#define SCARD_E_NO_READERS_AVAILABLE ((LONG)0x8010002E)
+#define SCARD_W_UNSUPPORTED_CARD ((LONG)0x80100065)
+#define SCARD_W_UNPOWERED_CARD ((LONG)0x80100067)
+#define SCARD_W_RESET_CARD ((LONG)0x80100068)
+#define SCARD_W_REMOVED_CARD ((LONG)0x80100069)
+
+#define SCARD_SCOPE_USER 0x0000
+#define SCARD_SCOPE_TERMINAL 0x0001
+#define SCARD_SCOPE_SYSTEM 0x0002
+#define SCARD_SCOPE_GLOBAL 0x0003
+
+#define SCARD_SHARE_EXCLUSIVE 0x0001
+#define SCARD_SHARE_SHARED 0x0002
+#define SCARD_SHARE_DIRECT 0x0003
+
+#define SCARD_PROTOCOL_T0 0x0001
+#define SCARD_PROTOCOL_T1 0x0002
+#define SCARD_PROTOCOL_RAW 0x0004
+#define SCARD_PROTOCOL_T15 0x0008
+
+#define SCARD_LEAVE_CARD 0x0000
+#define SCARD_RESET_CARD 0x0001
+#define SCARD_UNPOWER_CARD 0x0002
+#define SCARD_EJECT_CARD 0x0003
+
+/*
+ * This is used to indicate that the framework should allocate memory.
+ */
+#define SCARD_AUTOALLOCATE UINT32_MAX
+
+extern LONG SCardEstablishContext(DWORD, LPCVOID, LPCVOID, LPSCARDCONTEXT);
+extern LONG SCardReleaseContext(SCARDCONTEXT);
+
+extern LONG SCardListReaders(SCARDCONTEXT, LPCSTR, LPSTR, LPDWORD);
+
+extern LONG SCardFreeMemory(SCARDCONTEXT, LPCVOID);
+
+extern LONG SCardConnect(SCARDCONTEXT, LPCSTR, DWORD, DWORD, LPSCARDHANDLE,
+ LPDWORD);
+extern LONG SCardDisconnect(SCARDHANDLE, DWORD);
+
+extern LONG SCardBeginTransaction(SCARDHANDLE);
+extern LONG SCardEndTransaction(SCARDHANDLE, DWORD);
+extern LONG SCardReconnect(SCARDHANDLE, DWORD, DWORD, DWORD, LPDWORD);
+
+extern LONG SCardTransmit(SCARDHANDLE, const SCARD_IO_REQUEST *, LPCBYTE,
+ DWORD, SCARD_IO_REQUEST *, LPBYTE, LPDWORD);
+
+extern const char *pcsc_stringify_error(const LONG);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WINSCARD_H */
diff --git a/usr/src/lib/libpcsc/common/wintypes.h b/usr/src/lib/libpcsc/common/wintypes.h
new file mode 100644
index 0000000000..9bb50a87cc
--- /dev/null
+++ b/usr/src/lib/libpcsc/common/wintypes.h
@@ -0,0 +1,50 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+#ifndef _WINTYPES_H
+#define _WINTYPES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * While we don't want to, this expects that we have Win32 style type names.
+ * Deal with conversions between Win32 and reality. Remember that Windows is an
+ * ILP32 system, but it is a LLP64 system.
+ */
+
+typedef uint8_t BYTE;
+typedef uint8_t *LPBYTE;
+typedef const uint8_t *LPCBYTE;
+typedef const void *LPCVOID;
+typedef uint32_t DWORD;
+typedef uint32_t *LPDWORD;
+typedef int32_t LONG;
+typedef char *LPSTR;
+typedef const char *LPCSTR;
+
+/*
+ * Include a few deprecated types because folks still use them.
+ */
+typedef char *LPTSTR;
+typedef const char *LPCTSTR;
+typedef char *LPCWSTR;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WINTYPES_H */
diff --git a/usr/src/lib/libpcsc/i386/Makefile b/usr/src/lib/libpcsc/i386/Makefile
new file mode 100644
index 0000000000..e4bd71f624
--- /dev/null
+++ b/usr/src/lib/libpcsc/i386/Makefile
@@ -0,0 +1,18 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019 Joyent, Inc.
+#
+
+include ../Makefile.com
+
+install: all $(ROOTLIBS) $(ROOTLINKS)
diff --git a/usr/src/man/man1m/Makefile b/usr/src/man/man1m/Makefile
index 53be64fca8..9f01ad7606 100644
--- a/usr/src/man/man1m/Makefile
+++ b/usr/src/man/man1m/Makefile
@@ -60,6 +60,7 @@ _MANFILES= 6to4relay.1m \
busstat.1m \
captoinfo.1m \
catman.1m \
+ ccidadm.1m \
cfgadm.1m \
cfgadm_cardbus.1m \
cfgadm_fp.1m \
diff --git a/usr/src/man/man1m/ccidadm.1m b/usr/src/man/man1m/ccidadm.1m
new file mode 100644
index 0000000000..d1a09e25cf
--- /dev/null
+++ b/usr/src/man/man1m/ccidadm.1m
@@ -0,0 +1,190 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source. A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2019 Joyent, Inc.
+.\"
+.Dd December 17, 2019
+.Dt CCIDADM 1M
+.Os
+.Sh NAME
+.Nm ccidadm
+.Nd CCID administration utility
+.Sh SYNOPSIS
+.Nm
+.Cm list
+.Nm
+.Cm atr
+.Op Fl pvx
+.Op Ar device
+.Nm
+.Cm reader
+.Op Ar device
+.Sh DESCRIPTION
+The
+.Nm
+utility can be used to list the CCID controllers and their slots known to the
+.Xr ccid 7D
+driver, query the features and capabilities of a CCID controller, and print
+the ATR of an ICC (integrated circuit card) that is inserted in a slot on an
+CCID controller.
+.Pp
+The information returned by the hardware is printed by
+.Nm
+in a human-readable form were applicable.
+.Sh ARGUMENTS
+.Nm
+expects the following kinds of arguments:
+.Bl -tag -width "device"
+.It Ar command
+Any command
+.Nm
+understands.
+See section
+.Sx COMMANDS
+for more information.
+.It Ar device
+Specifies a CCID reader or a slot, either as absolute path to the device node
+or in a short-hand form.
+The short-hand form consists of the reader instance, specified by the driver
+name
+.Qq ccid
+followed by the instance number of the reader, and optionally a slot instance
+separated by a
+.Qq / ,
+consisting of the word
+.Qq slot
+followed by the slot number.
+Here's an example for slot 1 on ccid reader 5:
+.Qq ccid5/slot1
+.El
+.Sh COMMANDS
+.Bl -tag -width ""
+.It Xo
+.Nm
+.Cm list
+.Xc
+Lists the CCID controllers and their slots known to the system and prints their
+product name, device node, card state, and the transport protocol in use.
+.It Xo
+.Nm
+.Cm atr
+.Op Fl pvx
+.Op Ar device
+.Xc
+Prints the ATR of an ICC that is inserted in the specified slot.
+If a device is specified it must refer to a certain slot.
+If no device is specified the command will print the ATR of all inserted slots
+in the system.
+A human-readable summary of the ATR data is printed when no flags are given.
+The following options can be used to alter the output of the
+.Cm atr
+command:
+.Bl -tag -width Ds
+.It Fl v
+Verbose output, the individual bytes of the ATR are printed and decoded
+in a human-readable form.
+Additionally the historic data in the ATR is printed as a hexadecimal dump.
+.It Fl x
+The complete ATR is printed as a hexadecimal dump.
+.El
+.It Xo
+.Nm
+.Cm reader
+.Op Ar device
+.Xc
+Print the capabilities of the specified CCID reader.
+Specifying slot number is not required but can optionally be specified.
+If no device is given, the command will print the capabilities of all attached
+CCID readers.
+.El
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, 1 on any error opening or accessing the device, and
+2 if no command or an unknown command are given.
+.Sh EXAMPLES
+.Bl -tag -width ""
+.It Sy Example 1: List all CCID devices
+.Bd -literal
+# ccidadm list
+PRODUCT DEVICE CARD STATE TRANSPORT SUPPORTED
+Yubikey 4 OTP+U2F+CCID ccid0/slot0 activated APDU (T=1) supported
+Yubikey 4 OTP+U2F+CCID ccid1/slot0 unactivated APDU supported
+Smart Card Reader USB ccid2/slot0 missing TPDU unsupported
+Smart Card Reader USB ccid3/slot0 unactivated TPDU unsupported
+.Ed
+.It Sy Example 2: Get the ATR of a Yubikey
+.Bd -literal
+# ccidadm atr ccid0/slot0
+ATR for ccid0/slot0 (18 bytes):
+ICC supports protocol(s): T=1
+Card protocol is negotiable; starts with default T=1 parameters
+Reader will run ICC at ICC's Di/Fi values
+T=1 properties that would be negotiated:
+ + Fi/Fmax Index: 1 (Fi 372/Fmax 5 MHz)
+ + Di Index: 3 (Di 4)
+ + Checksum: LRC
+ + Extra Guardtime: 0
+ + BWI: 1
+ + CWI: 5
+ + Clock Stop: 0 (disallowed)
+ + IFSC: 254
+ + CCID Supports NAD: no
+.Ed
+.It Sy Example 2: Get capabilities of a Smart Card Reader
+.Bd -literal
+# ccidadm reader ccid3
+Reader ccid3, CCID class v1.0 device:
+ Product: Smart Card Reader USB
+ Serial: <unknown>
+ Slots Present: 1
+ Maximum Busy Slots: 1
+ Supported Voltages:
+ + 5.0 V
+ + 3.0 V
+ + 1.8 V
+ Supported Protocols:
+ + T=0
+ + T=1
+ Default Clock: 3.69 MHz
+ Maximum Clock: 3.69 MHz
+ Supported Clock Rates: 1
+ Default Data Rate: 9.92 Kbps
+ Maximum Data Rate: 318 Kbps
+ Supported Data Rates: 19
+ Maximum IFSD (T=1 only): 254
+ Synchronous Protocols Supported:
+ + 2-Wire Support
+ + 3-Wire Support
+ + I2C Support
+ Device Features:
+ + Automatic ICC clock frequency change
+ + Automatic baud rate change
+ + Automatic PPS made by CCID
+ + CCID can set ICC in clock stop mode
+ + NAD value other than zero accepted
+ + TPDU support
+ Maximum Message Length: 271 bytes
+.Ed
+.El
+.Sh INTERFACE STABILITY
+The command line interface of
+.Nm
+is
+.Sy Evolving .
+The output of
+.Nm
+is
+.Sy Not-an-Interface
+and may change any time.
+.Sh SEE ALSO
+.Xr cfgadm 1M ,
+.Xr ccid 7D
diff --git a/usr/src/man/man7d/Makefile b/usr/src/man/man7d/Makefile
index 4d261578a3..af84577d10 100644
--- a/usr/src/man/man7d/Makefile
+++ b/usr/src/man/man7d/Makefile
@@ -35,6 +35,7 @@ _MANFILES= aac.7d \
bge.7d \
blkdev.7d \
bnxe.7d \
+ ccid.7d \
chxge.7d \
console.7d \
cpuid.7d \
diff --git a/usr/src/man/man7d/ccid.7d b/usr/src/man/man7d/ccid.7d
new file mode 100644
index 0000000000..f5158b0d0d
--- /dev/null
+++ b/usr/src/man/man7d/ccid.7d
@@ -0,0 +1,558 @@
+.\"
+.\" This file and its contents are supplied under the terms of the
+.\" Common Development and Distribution License ("CDDL"), version 1.0.
+.\" You may only use this file in accordance with the terms of version
+.\" 1.0 of the CDDL.
+.\"
+.\" A full copy of the text of the CDDL should have accompanied this
+.\" source. A copy of the CDDL is also available via the Internet at
+.\" http://www.illumos.org/license/CDDL.
+.\"
+.\"
+.\" Copyright 2019 Joyent, Inc.
+.\"
+.Dd December 20, 2019
+.Dt CCID 7D
+.Os
+.Sh NAME
+.Nm ccid
+.Nd chip card interface device USB client class driver
+.Sh SYNOPSIS
+.In sys/usb/clients/ccid/uccid.h
+.Sh INTERFACE LEVEL
+.Sy Volatile
+.Pp
+The interfaces provided by this driver are private at this time and
+subject to change.
+It should not be relied upon.
+.Sh DESCRIPTION
+The
+.Nm
+driver is a USB CCID (chip card interface device) class device driver.
+.Pp
+The driver exposes interfaces that allow consumers to send and receive
+APDUs (application protocol data unit) to a given smart card that is
+plugged into a reader.
+The driver also provides interfaces to obtain status information, the
+ATR (answer to reset), and obtain exclusive access to the device.
+In addition, the system exposes control of CCID devices through
+.Xr cfgadm 1M .
+.Ss Supported Devices
+The CCID specification allows for readers to come in different flavors.
+These different flavors support different communication protocols and
+have different levels of automation for determining the protocol and
+transfers that are required.
+.Pp
+At this time, only the short APDU protocol is supported, which also works with
+readers using the extended APDU protocol.
+TPDU and character level readers are not supported by the driver.
+Readers in this category will still attach; however,
+I/O cannot be performed to them.
+.Pp
+In addition, at this time the driver does not support devices which
+require manually setting the clock and data rates to support an ICC.
+.Ss Device Model
+Each CCID class device provides a number of slots.
+Each slot may have an independent ICC (integrated circuit card or Smart
+Card) inserted into it.
+Each device, or reader, has its own directory under
+.Pa /dev/ccid
+based on its device number.
+Inside of each directory is a character device for each slot.
+A slot exists regardless of whether or not an ICC is inserted into it.
+As long as a CCID device is present in the system, its device nodes will
+be present.
+.Pp
+Slots are enumerated using this pattern:
+.Pa /dev/ccid/ccid%instance/slot%slot .
+.Pp
+For example, all the slots that belong to CCID instance 5 will be
+enumerated under the directory
+.Pa /dev/ccid/ccid5 .
+Slots are numbered starting at zero for each reader and increment from
+there.
+For example, the second physical slot would be numbered as slot one.
+If this were on CCID instance zero, then we would find a character
+device at:
+.Pa /dev/ccid/ccid0/slot1 .
+.Pp
+To enumerate all of the ccid devices present on the system, one could
+read all of the directories under
+.Pa /dev/ccid .
+To enumerate all of the slots on a device, one could read all of the
+device nodes under a particular CCID device, such as:
+.Pa /dev/ccid/ccid0 .
+The number of slots is also obtainable through various ioctls that will
+be discussed later on.
+It's important to note that while slot numbering will always be
+consistent for a given device, the CCID numbering is based on the driver
+instance.
+Therefore, it is possible for a device to change device numbers.
+To deal with this, symlinks based on other properties will be provided
+(for example, the USB serial number).
+.Pp
+All of the CCID devices in the system can also be listed by using the
+.Xr ccidadm 1M
+command.
+.Ss I/O Model
+To send and receive responses to commands, a program must open up the
+corresponding slot's device node.
+In many of the commands that use an ICC, there is a logical notion of
+state associated with the ICC that is mutated by performing commands on
+it.
+For example, a command might be issued that uses a PIN to unlock a slot
+or that selects a particular PIV applet for use.
+Because of this, all I/O to a given device must be performed inside the
+context of a transaction.
+When a program begins a transaction, it is guaranteed that no one else
+may send commands to the ICC.
+When a program is finished, it must explicitly end the transaction,
+which may have the side effect of resetting the ICC.
+If a program with an open transaction crashes or closes the file
+descriptor without taking other actions, then the transaction will be
+automatically closed and the ICC will be reset.
+Without a transaction open, it will still be possible to issue ioctls
+that obtain the status of the slot and the reader.
+.Pp
+While in an active transaction, a program may send commands to a card.
+Sending a command and reading a response are done through the
+traditional
+.Xr read 2
+and
+.Xr write 2
+family of system calls.
+To submit a command, the program would issue a
+.Xr write 2
+family system call that contained the payload to send to the ICC.
+Once submitted, the call would return and the program would be able to
+issue a
+.Xr read 2
+system call to obtain the results.
+Once a command has been submitted, it is illegal to submit another one.
+The next command cannot be submitted until the response has been fully
+consumed.
+Similarly, if a command has not been submitted, one cannot issue a
+.Xr read 2
+system call to obtain results.
+Only a single thread may be blocked waiting to submit a command or
+read a response.
+.Pp
+To facilitate non-blocking operation, the underlying file descriptor may
+be opened with
+.Dv O_NONBLOCK .
+.Pp
+While a transaction is active,
+.Xr poll 2
+may be used to receive status information about the slot.
+The following events are used by
+.Nm :
+.Bl -tag -width POLLRDNORM
+.It Dv POLLOUT
+The device is ready to receive a command using
+.Xr write 2 .
+.It Dv POLLIN, POLLRDNORM
+The device has completed a command the results may be retrieved with
+.Xr read 2 .
+.It Dv POLLHUP
+The card has been removed from the slot.
+.It Dv POLLERR
+An hardware error has occurred, or the CCID reader has been disconnected.
+.El
+.Pp
+One important note is that readers with multiple slots often still only
+allow I/O a single command to be outstanding across all of the slots in
+the system.
+Because transactions are on a per-slot basis, it is still possible for a
+command submission to block even though one has a transaction open.
+.Pp
+While a transaction is open, various events can occur that cause a fatal
+error on the transaction.
+These include:
+.Bl -bullet -offset indent
+.It
+USB CCID reader removed
+.It
+ICC removed
+.It
+A fatal error while communicating to the device
+.It
+An administrator issued an ioctl to power off or reset the ICC
+.El
+.Pp
+Once such a fatal error has occurred, all new I/O will fail though it
+will still be possible to read any successfully completed commands.
+To clear the error state the program will need to end the transaction
+and begin a new one or close the file descriptor if the device has been
+removed.
+.Ss Opening Devices, Exclusive Access, and Performing I/O
+To perform I/O to a particular card, one must first open the slot of
+interest.
+Opening the slot requires that the process be in the global zone and
+that it have the privilege
+.Sy PRIV_SYS_DEVICES .
+The device node can be opened through the
+.Xr open 2
+or
+.Xr openat 2
+system calls.
+For programs that just want to query the slot status using the
+.Dv UCCID_CMD_STATUS
+command, opening the device node read-only is sufficient.
+All other uses require that the device be opened both for reading and
+writing
+.Po Dv O_RDWR Pc .
+.Pp
+Once the device has been opened, the program may issue ioctls to get
+status information.
+.Pp
+To perform general I/O to the card, a program must be in the context of
+a transaction as discussed in the
+.Sx I/O Model
+section.
+To open a transaction, a program must issue the
+.Dv UCCID_CMD_TXN_BEGIN
+command through the
+.Xr ioctl 2
+system call.
+.Pp
+When a program is done, it must issue the
+.Dv UCCID_CMD_TXN_END
+command to release the transaction.
+As part of issuing the command, the program must determine a disposition
+of what it would like done with the card when it has completed.
+These options include leaving the ICC alone and resetting the ICC.
+For many use cases, such as those where a pin is entered or the ICC's
+state is mutated, a reset is the recommended option.
+If the program crashes or closes the file descriptor without issuing a
+transaction end, then the ICC will be reset.
+.Pp
+Please see the ioctl listing in the
+.Sx IOCTLS
+section for more information on the command structure.
+.Pp
+If a multi-threaded application opens a slot once and shares it among multiple
+threads performing I/O to that slot, there can still only be one transaction
+active or waiting on the slot shared by all threads.
+Acquiring another transaction on the same slot minor while another thread is
+already blocked waiting for one will return
+.Dv EINPROGRESS .
+If another transaction is already active,
+.Dv EINVAL
+will be returned.
+Consequently, all threads in a multi-threaded application share the transaction
+state and may issue writes, and read the results.
+The same applies to any other method of sharing an open file descriptor of a slot
+minor, be it by sharing the fd over a socket, a child process inheriting it from
+its parent during
+.Xr fork 2 ,
+even across calls to
+.Xr exec 2 .
+.Ss Device Status and ATR
+Once a slot has been opened, any caller may issue commands to get the
+status of the slot.
+This can also be used to obtain the ATR (answer to reset) of an ICC that
+is present on the slot, if it is known.
+.Pp
+While exclusive access is not required to issue these commands, there is
+no guarantee that they will not have changed between the time that the
+program issued the command and it obtains a transaction.
+.Pp
+To obtain information about the reader, slot, and the ATR, one should
+issue the
+.Dv UCCID_CMD_STATUS
+command.
+Please see the ioctl listing in the
+.Sx IOCTLS
+section for more information.
+.Sh IOCTLS
+This section lists the different commands that may be issued to a CCID
+device through the
+.Xr ioctl 2
+system call.
+.Ss Ic UCCID_CMD_STATUS
+This command is used to obtain the status of the slot.
+It may be used regardless of whether or not the caller has exclusive access.
+.Pp
+The
+.Ic UCCID_CMD_STATUS
+command uses the structure
+.Vt uccid_cmd_status_t ,
+the fields of which have the following meanings:
+.Bl -tag -width Fa
+.It Fa uint32_t ucs_version
+Indicates the current version of the structure.
+This should be set to
+.Dv UCCID_CURRENT_VERSION .
+.It Fa uint32_t ucs_status
+This value is ignored when issuing the command.
+On return, it will be filled in with various flags that describe the
+current status of the slot and the contents returned in the
+.Vt uccid_cmd_status_t .
+The following flags are defined:
+.Bl -tag -width Dv
+.It Dv UCCID_STATUS_F_CARD_PRESENT
+A card has been inserted into the slot of the CCID class device.
+.It Dv UCCID_STATUS_F_CARD_ACTIVE
+The inserted card has been successfully activated.
+This will only be set if the
+.Dv UCCID_STATUS_F_CARD_PRESENT
+flag is also set.
+.It Dv UCCID_STATUS_F_PRODUCT_VALID
+The contents of
+.Fa ucs_product
+are valid.
+.It Dv UCCID_STATUS_F_SERIAL_VALID
+The contents of
+.Fa ucs_serial
+are valid.
+.It Dv UCCID_STATUS_F_PARAMS_VALID
+The parameters returned in
+.Fa ucs_params
+are valid.
+.El
+.It Fa int32_t ucs_instance
+The instance number of the CCID device.
+.It Fa uint32_t ucs_slot
+The slot number currently in use.
+.It Fa uint8_t ucs_atr[UCCID_ATR_MAX]
+The ATR (answer to reset) of the card.
+.It Fa uint8_t ucs_atrlen
+The actual length of the ATR data.
+A length of 0 indicates that there is no ATR data.
+.It Fa int8_t ucs_product[256]
+The product string of the CCID device.
+.It Fa int8_t ucs_serial[256]
+The serial number of the CCID device.
+.It Fa ccid_class_descr_t ucs_class
+The CCID class descriptor of the CCID device.
+.It Fa uccid_prot_t ucs_prot
+The protocol in use by the ICC.
+This can be either
+.Dv UCCID_PROT_T0
+for the TPDU T=0 protocol or
+.Dv UCCID_PROT_T1
+for the TPDU T=1 protocol.
+.It Fa ccid_params_t ucs_params
+The CCID parameters available on the card.
+.El
+.Ss Ic UCCID_CMD_TXN_BEGIN
+This command is used to begin a transaction.
+The command will block until exclusive access is available to the
+caller.
+If the caller does not wish to block, it should set the
+.Dv UCCID_TXN_DONT_BLOCK
+flag.
+.Pp
+The command uses the structure
+.Vt uccid_cmd_txn_begin_t
+with the following members:
+.Bl -tag -width Fa
+.It Fa uint32_t ucs_version
+Indicates the current version of the structure.
+This should be set to
+.Dv UCCID_CURRENT_VERSION .
+.It Fa uint32_t uct_flags
+Flags that impact the behavior of the command.
+The following flags are defined:
+.Bl -tag -width Dv
+.It Dv UCCID_TXN_DONT_BLOCK
+The command should not block for exclusive access.
+If exclusive access is not available, then the command will fail
+immediately.
+.El
+.Pp
+If an unknown flag is specified, an error will be returned.
+.El
+.Ss Ic UCCID_CMD_TXN_END
+The
+.Dv UCCID_CMD_TXN_END
+command is used to end a transaction and relinquish exclusive access
+to the ICC.
+.Pp
+The command uses the structure
+.Vt uccid_cmd_txn_end_t
+with the following members:
+.Bl -tag -width Fa
+.It Fa uint32_t uct_version
+Indicates the current version of the structure.
+This should be set to
+.Dv UCCID_CURRENT_VERSION .
+.It Fa uint32_t uct_flags
+.Bl -tag -width Dv
+.It Dv UCCID_TXN_END_RESET
+The ICC should be reset at the end of the transaction.
+.It Dv UCCID_TXN_END_RELEASE
+The ICC should be released without being reset at the end of the
+transaction.
+.El
+.Pp
+Exactly one of these two flags must be specified.
+It is an error if neither flag or both flags are specified at the same
+time.
+If the device is closed without ending a transaction first, then the ICC
+will be reset.
+.El
+.Ss Ic UCCID_CMD_ICC_MODIFY
+This command can be used to change the state of an ICC, if present.
+.Pp
+The command uses the structure
+.Vt uccid_cmd_icc_modify_t
+with the following members:
+.Bl -tag -width Fa
+.It Fa uint32_t uci_version
+Indicates the current version of the structure.
+This should be set to
+.Dv UCCID_CURRENT_VERSION .
+.It Fa uint32_t uci_action
+The action to be taken on the ICC.
+The following actions are defined:
+.Bl -tag -width Dv
+.It Dv UCCID_ICC_POWER_ON
+Power on the ICC.
+.It Dv UCCID_ICC_POWER_OFF
+Power off the ICC.
+.It Dv UCCID_ICC_WARM_RESET
+Perform a warm reset of the ICC.
+.El
+.El
+.Ss Ic FIONREAD
+This command returns the size in bytes of a command response available
+for reading with
+.Xr read 2 .
+The size is returned in an
+.Vt int
+pointed to by the argument.
+.Sh SYSTEM CALLS
+This section lists the different system calls that may be issued to a
+CCID device.
+.Ss Xr open 2
+.Nm
+slot device nodes can be opened using
+.Xr open 2 .
+Non-blocking operation can be selected by using the
+.Dv O_NONBLOCK
+flag when opening the device node.
+A device node opened for read-only operations will not allow creating
+transactions or doing I/O, but it will allow the ICC/reader status to
+be queried.
+.Ss Xr close 2
+When no longer needed by a program, a device node can be closed with
+.Xr close 2 .
+If a transaction is still active when a device node is closed, the transaction
+will be ended automatically and the ICC will be reset.
+Any unread data is discarded.
+.Ss Xr ioctl 2
+The
+.Xr ioctl 2
+system call can be used to start or end a transaction, query the reply size for
+.Xr read 2 ,
+query the ICC and CCID reader status, or change the state of an ICC in a reader.
+See section
+.Sx IOCTLS
+for details.
+.Ss Xr write 2
+Within an active transaction the
+.Xr write 2
+system call can be used to transfer an APDU (application protocol data unit) to
+an ICC, one single complete APDU at a time.
+Partial writes or writing more than one APDU at a time are not supported.
+The data returned by the ICC must be consumed by a subsequent
+.Xr read 2
+call before
+.Xr write 2
+can be called again within the same transaction.
+.Pp
+The following errors for
+.Xr write 2
+have specific meaning in
+.Nm :
+.Bl -tag -width Dv
+.It Dv E2BIG
+The number of bytes to write is larger than the maximum APDU length supported by
+.Nm ,
+currently defined as 261 bytes.
+.It Dv EACCES
+The device is opened read-only, or no transaction is active.
+.It Dv EBUSY
+There is unread data from a previous call to
+.Xr write 2 .
+.It Dv ENOTSUP
+The reader and/or ICC is unsupported for I/O.
+.It Dv ENXIO
+The ICC is inactive and can't be used for I/O.
+.It Dv ENODEV
+The CCID reader has been disconnected.
+.El
+.Ss Xr read 2
+Within an active transaction the
+.Xr read 2
+system call is used to read the reply from an ICC following sending an APDU with
+.Xr write 2 .
+The whole reply needs to be read with a single call to
+.Xr read 2 .
+The size of the reply can be queried before reading by issuing the
+.Dv FIONREAD
+ioctl.
+See section
+.Sx IOCTLS
+for details.
+.Pp
+The following errors for
+.Xr read 2
+have specific meaning in
+.Nm :
+.Bl -tag -width Dv
+.It Dv EACCES
+No transaction is active.
+.It Dv EBUSY
+Another thread is already blocked in
+.Xr read 2
+waiting for data.
+.It Dv EOVERFLOW
+The buffer size is too small to fit the reply.
+.It Dv ENODATA
+No
+.Xr write 2
+was issued before and consequently there is no reply to be read.
+.It Dv ENODEV
+The CCID reader has been disconnected.
+.El
+.Ss Xr poll 2
+Within an active transaction the
+.Xr poll 2
+system call is used to wait for status changes on a device node.
+See section
+.Sx I/O model
+for details.
+.Pp
+The following errors for
+.Xr poll 2
+have specific meaning in
+.Nm :
+.Bl -tag -width Dv
+.It Dv EACCES
+No transaction is active.
+.It Dv ENODEV
+The CCID reader has been disconnected.
+.El
+.Sh SEE ALSO
+.Xr ccidadm 1M ,
+.Xr cfgadm 1M ,
+.Xr close 2 ,
+.Xr ioctl 2 ,
+.Xr open 2 ,
+.Xr poll 2 ,
+.Xr read 2 ,
+.Xr write 2
+.Rs
+.%T Universal Serial Bus Device Class: Smart Card CCID
+.%O Revision 1.1
+.%D April 22, 2005
+.Re
+.Rs
+.%Q ISO/IEC
+.%B Identification Cards - Integrated Circuits
+.%N Part 3: Cards with contacts — Electrical interface and transmission protocols
+.%O ISO/IEC 7616-3:2006
+.%D 2006
+.Re
diff --git a/usr/src/man/man7i/Makefile b/usr/src/man/man7i/Makefile
index 12e4489be5..f523d12895 100644
--- a/usr/src/man/man7i/Makefile
+++ b/usr/src/man/man7i/Makefile
@@ -16,7 +16,7 @@
include $(SRC)/Makefile.master
-MANSECT= 7i
+MANSECT= 7i
MANFILES= audio.7i \
cdio.7i \
diff --git a/usr/src/pkg/manifests/driver-misc-ccid.mf b/usr/src/pkg/manifests/driver-misc-ccid.mf
new file mode 100644
index 0000000000..e0d1ca0155
--- /dev/null
+++ b/usr/src/pkg/manifests/driver-misc-ccid.mf
@@ -0,0 +1,57 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent Inc.
+#
+
+#
+# The default for payload-bearing actions in this package is to appear in the
+# global zone only. See the include file for greater detail, as well as
+# information about overriding the defaults.
+#
+set name=pkg.fmri value=pkg:/driver/misc/ccid@$(PKGVERS)
+set name=pkg.description \
+ value="Driver for USB Chip Card Interface Devices (CCID)"
+set name=pkg.summary value="CCID driver"
+set name=info.classification \
+ value=org.opensolaris.category.2008:System/Hardware
+set name=variant.arch value=i386
+dir path=kernel group=sys variant.opensolaris.zone=global
+dir path=kernel/drv group=sys variant.opensolaris.zone=global
+dir path=kernel/drv/$(ARCH64) group=sys variant.opensolaris.zone=global
+dir path=usr group=sys
+dir path=usr/include
+dir path=usr/include/sys
+dir path=usr/include/sys/usb
+dir path=usr/include/sys/usb/clients
+dir path=usr/include/sys/usb/clients/ccid
+dir path=usr/lib/cfgadm variant.opensolaris.zone=global
+dir path=usr/lib/cfgadm/$(ARCH64) variant.opensolaris.zone=global
+dir path=usr/sbin variant.opensolaris.zone=global
+dir path=usr/share
+dir path=usr/share/man
+dir path=usr/share/man/man1m variant.opensolaris.zone=global
+dir path=usr/share/man/man7d
+driver name=ccid alias=usbif,classb class=misc perms="* 0600 root sys"
+file path=kernel/drv/$(ARCH64)/ccid group=sys variant.opensolaris.zone=global
+file path=usr/include/sys/usb/clients/ccid/ccid.h
+file path=usr/include/sys/usb/clients/ccid/uccid.h
+file path=usr/lib/cfgadm/$(ARCH64)/ccid.so.1 variant.opensolaris.zone=global
+file path=usr/lib/cfgadm/ccid.so.1 variant.opensolaris.zone=global
+file path=usr/sbin/ccidadm mode=0555 variant.opensolaris.zone=global
+file path=usr/share/man/man1m/ccidadm.1m variant.opensolaris.zone=global
+file path=usr/share/man/man7d/ccid.7d
+license lic_CDDL license=lic_CDDL
+link path=usr/lib/cfgadm/$(ARCH64)/ccid.so target=./ccid.so.1 \
+ variant.opensolaris.zone=global
+link path=usr/lib/cfgadm/ccid.so target=./ccid.so.1 \
+ variant.opensolaris.zone=global
diff --git a/usr/src/pkg/manifests/system-library-libpcsc.mf b/usr/src/pkg/manifests/system-library-libpcsc.mf
new file mode 100644
index 0000000000..fbf60f6595
--- /dev/null
+++ b/usr/src/pkg/manifests/system-library-libpcsc.mf
@@ -0,0 +1,32 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent Inc.
+#
+
+set name=pkg.fmri value=pkg:/system/library/libpcsc@$(PKGVERS)
+set name=pkg.description \
+ value="PC/SC compatible library for Smart Card access"
+set name=info.classification \
+ value=org.opensolaris.category.2008:System/Libraries
+set name=variant.arch value=$(ARCH)
+dir path=usr group=sys
+dir path=usr/include
+dir path=usr/lib
+dir path=usr/lib/$(ARCH64)
+file path=usr/include/winscard.h
+file path=usr/include/wintypes.h
+file path=usr/lib/$(ARCH64)/libpcsc.so.1
+file path=usr/lib/libpcsc.so.1
+link path=usr/lib/$(ARCH64)/libpcsc.so target=./libpcsc.so.1
+link path=usr/lib/libpcsc.so target=./libpcsc.so.1
+depend fmri=driver/misc/ccid type=require
diff --git a/usr/src/pkg/manifests/system-test-ostest.mf b/usr/src/pkg/manifests/system-test-ostest.mf
index feebca78c9..96e6c2128b 100644
--- a/usr/src/pkg/manifests/system-test-ostest.mf
+++ b/usr/src/pkg/manifests/system-test-ostest.mf
@@ -34,6 +34,7 @@ dir path=opt/os-tests/tests/secflags
dir path=opt/os-tests/tests/sigqueue
dir path=opt/os-tests/tests/sockfs
dir path=opt/os-tests/tests/stress
+dir path=opt/os-tests/tests/uccid
file path=opt/os-tests/README mode=0444
file path=opt/os-tests/bin/ostest mode=0555
file path=opt/os-tests/runfiles/default.run mode=0444
@@ -81,6 +82,22 @@ file path=opt/os-tests/tests/sockfs/nosignal mode=0555
file path=opt/os-tests/tests/sockfs/sockpair mode=0555
file path=opt/os-tests/tests/spoof-ras mode=0555
file path=opt/os-tests/tests/stress/dladm-kstat mode=0555
+file path=opt/os-tests/tests/uccid/atrparse mode=0555
+file path=opt/os-tests/tests/uccid/excl-badread mode=0555
+file path=opt/os-tests/tests/uccid/excl-basic mode=0555
+file path=opt/os-tests/tests/uccid/excl-close mode=0555
+file path=opt/os-tests/tests/uccid/excl-loop mode=0555
+file path=opt/os-tests/tests/uccid/excl-nonblock mode=0555
+file path=opt/os-tests/tests/uccid/excl-reset mode=0555
+file path=opt/os-tests/tests/uccid/modify mode=0555
+file path=opt/os-tests/tests/uccid/notxn-poll mode=0555
+file path=opt/os-tests/tests/uccid/pollin mode=0555
+file path=opt/os-tests/tests/uccid/pollout mode=0555
+file path=opt/os-tests/tests/uccid/status mode=0555
+file path=opt/os-tests/tests/uccid/txn-pollerr mode=0555
+file path=opt/os-tests/tests/uccid/yk mode=0555
+file path=opt/os-tests/tests/uccid/yk-poll mode=0555
+file path=opt/os-tests/tests/uccid/yk-readonly mode=0555
file path=opt/os-tests/tests/writev.32 mode=0555
file path=opt/os-tests/tests/writev.64 mode=0555
license cr_Sun license=cr_Sun
diff --git a/usr/src/test/os-tests/runfiles/default.run b/usr/src/test/os-tests/runfiles/default.run
index 616a3f4849..650f023d77 100644
--- a/usr/src/test/os-tests/runfiles/default.run
+++ b/usr/src/test/os-tests/runfiles/default.run
@@ -82,3 +82,11 @@ tests = ['ldt', 'badseg']
[/opt/os-tests/tests/imc_test]
arch = i86pc
+
+#
+# Except atrparse all tests require special hardware (CCID YubiKey) to run,
+# hence they aren't included in the default runfile.
+#
+[/opt/os-tests/tests/uccid]
+arch = i86pc
+tests = ['atrparse']
diff --git a/usr/src/test/os-tests/tests/Makefile b/usr/src/test/os-tests/tests/Makefile
index 46d474d0d8..f923125d5e 100644
--- a/usr/src/test/os-tests/tests/Makefile
+++ b/usr/src/test/os-tests/tests/Makefile
@@ -27,6 +27,7 @@ SUBDIRS = \
sockfs \
spoof-ras \
stress \
+ uccid \
$(SUBDIRS_$(MACH))
PROGS = \
diff --git a/usr/src/test/os-tests/tests/uccid/Makefile b/usr/src/test/os-tests/tests/uccid/Makefile
new file mode 100644
index 0000000000..9f08a6e5cc
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/Makefile
@@ -0,0 +1,82 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+include $(SRC)/Makefile.master
+
+ROOTOPTPKG = $(ROOT)/opt/os-tests
+TESTDIR = $(ROOTOPTPKG)/tests/uccid
+
+PROGS = \
+ atrparse \
+ excl-basic \
+ excl-badread \
+ excl-close \
+ excl-loop \
+ excl-nonblock \
+ excl-reset \
+ modify \
+ notxn-poll \
+ status \
+ pollin \
+ pollout \
+ txn-pollerr \
+ yk \
+ yk-poll \
+ yk-readonly
+
+COMMON_OBJS = \
+ atr.o
+
+atrparse := EXTRA_OBJS = $(COMMON_OBJS)
+
+include $(SRC)/cmd/Makefile.cmd
+include $(SRC)/test/Makefile.com
+
+CMDS = $(PROGS:%=$(TESTDIR)/%)
+$(CMDS) := FILEMODE = 0555
+
+CPPFLAGS += -D_REENTRANT -I$(SRC)/common/ccid/
+
+all: $(PROGS)
+
+install: all $(CMDS)
+
+clobber: clean
+ -$(RM) $(PROGS)
+
+clean:
+ -$(RM) *.o
+
+$(PROGS): $(COMMON_OBJS)
+
+$(CMDS): $(TESTDIR) $(PROGS)
+
+$(TESTDIR):
+ $(INS.dir)
+
+$(TESTDIR)/%: %
+ $(INS.file)
+
+%.o: $(SRC)/common/ccid/%.c
+ $(COMPILE.c) -o $@ -c $<
+ $(POST_PROCESS_O)
+
+%.o: %.c
+ $(COMPILE.c) -o $@ -c $<
+ $(POST_PROCESS_O)
+
+%: %.o
+ $(LINK.c) -o $@ $< $(EXTRA_OBJS) $(LDLIBS)
+ $(POST_PROCESS)
diff --git a/usr/src/test/os-tests/tests/uccid/atrparse.c b/usr/src/test/os-tests/tests/uccid/atrparse.c
new file mode 100644
index 0000000000..f14bbfd34d
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/atrparse.c
@@ -0,0 +1,731 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that we can parse various forms of ATR data and detect invalid data.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+
+#include <atr.h>
+
+typedef struct atr_vals {
+} atr_vals_t;
+
+typedef struct atr_test {
+ const char *ar_test;
+ uint8_t ar_len;
+ uint8_t ar_buf[64];
+ atr_parsecode_t ar_retval;
+ /* Everything after this is data from the ATR */
+ atr_protocol_t ar_sup;
+ atr_protocol_t ar_def;
+ boolean_t ar_neg;
+ uint8_t ar_fi;
+ uint8_t ar_di;
+ atr_convention_t ar_conv;
+ uint8_t ar_guard;
+ atr_clock_stop_t ar_stop;
+ /* These will be checked based on sup prot */
+ uint8_t ar_t0_wi;
+ atr_t1_checksum_t ar_t1_cksum;
+ uint8_t ar_t1_bwi;
+ uint8_t ar_t1_cwi;
+ uint8_t ar_t1_ifsc;
+} atr_test_t;
+
+atr_test_t atr_tests[] = {
+ { "zero-length data", 0, { 0 }, ATR_CODE_TOO_SHORT },
+ { "No T0", 1, { 0x3f }, ATR_CODE_TOO_SHORT },
+ { "Too much data", 34, { 0 }, ATR_CODE_TOO_LONG },
+ { "Overrun T0 (1)", 2, { 0x3b, 0x10 }, ATR_CODE_OVERRUN },
+ { "Overrun T0 (2)", 2, { 0x3b, 0x80 }, ATR_CODE_OVERRUN },
+ { "Overrun T0 (3)", 2, { 0x3b, 0x01 }, ATR_CODE_OVERRUN },
+ { "Overrun T0 (4)", 2, { 0x3b, 0x11 }, ATR_CODE_OVERRUN },
+ { "Overrun T0 (5)", 2, { 0x3b, 0xff }, ATR_CODE_OVERRUN },
+ { "Overrun TD1", 3, { 0x3b, 0x80, 0x10 }, ATR_CODE_OVERRUN },
+ { "Overrun TD2", 4, { 0x3b, 0x80, 0x80, 0x10 }, ATR_CODE_OVERRUN },
+ { "Overrun TD", 33, { 0x3b, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
+ 0x80, 0x80, 0x80 }, ATR_CODE_OVERRUN },
+ { "T0 w/ T=15 and no cksum", 5, { 0x3b, 0x80, 0x80, 0x1f, 0x00 },
+ ATR_CODE_OVERRUN },
+ { "Bad TS (1)", 2, { 0x3a, 0x00 }, ATR_CODE_INVALID_TS },
+ { "Bad TS (2)", 2, { 0xff, 0x00 }, ATR_CODE_INVALID_TS },
+ { "T0 w/ T=15 and bad cksum", 6, { 0x3b, 0x80, 0x80, 0x1f, 0x00, 0x00 },
+ ATR_CODE_CHECKSUM_ERROR },
+ { "T0 w/ T=15 and bad cksum (make sure no TS)", 6,
+ { 0x3b, 0x80, 0x80, 0x1f, 0x00, 0x24 },
+ ATR_CODE_CHECKSUM_ERROR },
+ { "T=15 in TD1", 4, { 0x3b, 0x80, 0x0f, 0x8f }, ATR_CODE_INVALID_TD1 },
+ {
+ .ar_test = "Minimal T0 Direct",
+ .ar_len = 2,
+ .ar_buf = { 0x3b, 0x00 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "Minimal T0 Inverse",
+ .ar_len = 2,
+ .ar_buf = { 0x3f, 0x00 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_INVERSE,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 Fi/Di (1)",
+ .ar_len = 3,
+ .ar_buf = { 0x3b, 0x10, 0x24 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 2,
+ .ar_di = 4,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 Fi/Di (2)",
+ .ar_len = 3,
+ .ar_buf = { 0x3b, 0x10, 0x93 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 9,
+ .ar_di = 3,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 Ignore deprecated TB1",
+ .ar_len = 3,
+ .ar_buf = { 0x3b, 0x20, 0x42 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 Ignore deprecated TB2",
+ .ar_len = 4,
+ .ar_buf = { 0x3b, 0x80, 0x20, 0x42 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 Ignore deprecated TB1/TB2",
+ .ar_len = 5,
+ .ar_buf = { 0x3b, 0xa0, 0x55, 0x20, 0x42 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 Encode TC1",
+ .ar_len = 3,
+ .ar_buf = { 0x3b, 0x40, 0x23 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0x23,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 TA2 says neg",
+ .ar_len = 4,
+ .ar_buf = { 0x3b, 0x80, 0x10, 0x00 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 TA2 says not neg",
+ .ar_len = 4,
+ .ar_buf = { 0x3b, 0x80, 0x10, 0x80 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_FALSE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 TA2 says not neg, honor Fi/Di",
+ .ar_len = 5,
+ .ar_buf = { 0x3b, 0x90, 0x24, 0x10, 0x80 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_FALSE,
+ .ar_fi = 2,
+ .ar_di = 4,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 TA2 says not neg, don't honor Fi/Di",
+ .ar_len = 5,
+ .ar_buf = { 0x3b, 0x90, 0x24, 0x10, 0x90 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_FALSE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 TC2 set",
+ .ar_len = 4,
+ .ar_buf = { 0x3b, 0x80, 0x40, 0x35 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 0x35,
+ }, {
+ .ar_test = "T0 T15 empty (requires checksum)",
+ .ar_len = 5,
+ .ar_buf = { 0x3b, 0x80, 0x80, 0x0f, 0x0f },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 T15 Clock Stop (1)",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0x07, 0x18 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 T15 Clock Stop (2)",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0x47, 0x58 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_LOW,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 T15 Clock Stop (3)",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0x87, 0x98 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_HI,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 T15 Clock Stop (4)",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x80, 0x1f, 0xc7, 0xd8 },
+ .ar_sup = ATR_P_T0,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_BOTH,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "T0 with random prots",
+ .ar_len = 7,
+ .ar_buf = { 0x3b, 0x80, 0x84, 0x85, 0x88, 0x0f, 0x06 },
+ .ar_sup = ATR_P_T0,
+ /*
+ * This comes from the fact that TD1 is T=4 and that isn't
+ * supported in the system.
+ */
+ .ar_def = ATR_P_NONE,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ }, {
+ .ar_test = "Actual ATR (1, Yubikey4)",
+ .ar_len = 18,
+ .ar_buf = { 0x3b, 0xf8, 0x13, 0x00, 0x00, 0x81, 0x31, 0xfe,
+ 0x15, 0x59, 0x75, 0x62, 0x69, 0x6b, 0x65, 0x79, 0x34,
+ 0xd4 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 3,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 1,
+ .ar_t1_cwi = 5,
+ .ar_t1_ifsc = 254
+ }, {
+ .ar_test = "Actual ATR (2)",
+ .ar_len = 19,
+ .ar_buf = { 0x3b, 0xf9, 0x18, 0x00, 0x00, 0x81, 0x31, 0xfe,
+ 0x45, 0x4a, 0x32, 0x44, 0x30, 0x38, 0x31, 0x5f, 0x50, 0x56,
+ 0xb6 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 8,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 5,
+ .ar_t1_ifsc = 254
+ }, {
+ .ar_test = "Actual ATR (3)",
+ .ar_len = 22,
+ .ar_buf = { 0x3b, 0xfc, 0x18, 0x00, 0x00, 0x81, 0x31, 0x80,
+ 0x45, 0x90, 0x67, 0x46, 0x4a, 0x00, 0x64, 0x16, 0x6, 0xf2,
+ 0x72, 0x7e, 0x00, 0xe0 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 8,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 5,
+ .ar_t1_ifsc = 128
+ }, {
+ .ar_test = "Minimal T=1",
+ .ar_len = 4,
+ .ar_buf = { 0x3b, 0x80, 0x01, 0x81 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=1 Fi/Di",
+ .ar_len = 5,
+ .ar_buf = { 0x3b, 0x90, 0x34, 0x01, 0xa5 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 3,
+ .ar_di = 4,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=1 TA2 says neg, T=1 def",
+ .ar_len = 5,
+ .ar_buf = { 0x3b, 0x80, 0x11, 0x11, 0x80 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=0, T=1 TA2 says neg, T=0 def",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x90, 0x10, 0x01, 0x01 },
+ .ar_sup = ATR_P_T0 | ATR_P_T1,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=0, T=1 TA2 says neg, T=1 def",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x90, 0x11, 0x01, 0x00 },
+ .ar_sup = ATR_P_T0 | ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=0, T=1 TA2 says not neg, T=0 def",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x90, 0x90, 0x01, 0x81 },
+ .ar_sup = ATR_P_T0 | ATR_P_T1,
+ .ar_def = ATR_P_T0,
+ .ar_neg = B_FALSE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=0, T=1 TA2 says not neg, T=1 def",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x90, 0x81, 0x01, 0x90 },
+ .ar_sup = ATR_P_T0 | ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_FALSE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t0_wi = 10,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=1, BWI/CWI",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x81, 0x21, 0x59, 0x79 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 5,
+ .ar_t1_cwi = 9,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=1, IFSC",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x81, 0x11, 0x49, 0x59 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 73
+ }, {
+ .ar_test = "T=1, Checksum (LRC)",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x81, 0x41, 0x00, 0x40 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_LRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }, {
+ .ar_test = "T=1, Checksum (CRC)",
+ .ar_len = 6,
+ .ar_buf = { 0x3b, 0x80, 0x81, 0x41, 0x01, 0x41 },
+ .ar_sup = ATR_P_T1,
+ .ar_def = ATR_P_T1,
+ .ar_neg = B_TRUE,
+ .ar_fi = 1,
+ .ar_di = 1,
+ .ar_conv = ATR_CONVENTION_DIRECT,
+ .ar_guard = 0,
+ .ar_stop = ATR_CLOCK_STOP_NONE,
+ .ar_t1_cksum = ATR_T1_CHECKSUM_CRC,
+ .ar_t1_bwi = 4,
+ .ar_t1_cwi = 13,
+ .ar_t1_ifsc = 32
+ }
+};
+
+static void
+atr_parse_failed(atr_test_t *test, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ (void) fprintf(stderr, "Test \"%s\" failed: ", test->ar_test);
+ (void) vfprintf(stderr, fmt, ap);
+ (void) fprintf(stderr, "\n");
+ va_end(ap);
+}
+
+static uint_t
+atr_parse_one(atr_data_t *data, atr_test_t *test)
+{
+ uint_t err = 0;
+ atr_parsecode_t ret;
+ atr_protocol_t sup, def;
+ boolean_t neg;
+ uint8_t fi, di, guard;
+ atr_convention_t conv;
+ atr_clock_stop_t stop;
+
+ ret = atr_parse(test->ar_buf, test->ar_len, data);
+ if (ret != test->ar_retval) {
+ atr_parse_failed(test, "found unexpected return "
+ "value: %u (%s), expected: %u", ret, atr_strerror(ret),
+ test->ar_retval);
+ return (1);
+ }
+
+ /* Don't test anything else if it's not OK */
+ if (ret != ATR_CODE_OK)
+ return (0);
+
+ sup = atr_supported_protocols(data);
+ def = atr_default_protocol(data);
+ neg = atr_params_negotiable(data);
+ fi = atr_fi_index(data);
+ di = atr_di_index(data);
+ conv = atr_convention(data);
+ guard = atr_extra_guardtime(data);
+ stop = atr_clock_stop(data);
+
+ if (sup != test->ar_sup) {
+ atr_parse_failed(test, "Found mismatched supported "
+ "protocols: %u, expected: %u", sup, test->ar_sup);
+ err++;
+ }
+
+ if (def != test->ar_def) {
+ atr_parse_failed(test, "Found mismatched default "
+ "protocols: %u, expected: %u", def, test->ar_def);
+ err++;
+ }
+
+ if (neg != test->ar_neg) {
+ atr_parse_failed(test, "Found mismatched negotiable bit: "
+ "%u, expected %u", neg, test->ar_neg);
+ err++;
+ }
+
+ if (fi != test->ar_fi) {
+ atr_parse_failed(test, "Found mismatched fi index: "
+ "%u, expected: %u", fi, test->ar_fi);
+ err++;
+ }
+
+ if (di != test->ar_di) {
+ atr_parse_failed(test, "Found mismatched di index: "
+ "%u, expected: %u", di, test->ar_di);
+ err++;
+ }
+
+ if (conv != test->ar_conv) {
+ atr_parse_failed(test, "Found mismatched TS convention: "
+ "%u, expected: %u", conv, test->ar_conv);
+ err++;
+ }
+
+ if (guard != test->ar_guard) {
+ atr_parse_failed(test, "Found mismatched extra guardtime: "
+ "%u, expected: %u", guard, test->ar_guard);
+ err++;
+ }
+
+ if (stop != test->ar_stop) {
+ atr_parse_failed(test, "Found mismatched clock stop: "
+ "%u, expected: %u", stop, test->ar_stop);
+ err++;
+ }
+
+ if ((sup & ATR_P_T0) != 0) {
+ uint8_t wi;
+
+ wi = atr_t0_wi(data);
+ if (wi != test->ar_t0_wi) {
+ atr_parse_failed(test, "Found mismatched T0 wi: "
+ "%u, expected: %u", wi, test->ar_t0_wi);
+ err++;
+ }
+ }
+
+ if ((sup & ATR_P_T1) != 0) {
+ atr_t1_checksum_t cksum;
+ uint8_t bwi, cwi, ifsc;
+
+ cksum = atr_t1_checksum(data);
+ bwi = atr_t1_bwi(data);
+ cwi = atr_t1_cwi(data);
+ ifsc = atr_t1_ifsc(data);
+
+ if (cksum != test->ar_t1_cksum) {
+ atr_parse_failed(test, "Found mistmatched T1 checksum: "
+ "%u, expected: %u", cksum, test->ar_t1_cksum);
+ err++;
+ }
+
+ if (bwi != test->ar_t1_bwi) {
+ atr_parse_failed(test, "Found mistmatched T1 bwi: "
+ "%u, expected: %u", bwi, test->ar_t1_bwi);
+ err++;
+ }
+
+ if (cwi != test->ar_t1_cwi) {
+ atr_parse_failed(test, "Found mistmatched T1 cwi: "
+ "%u, expected: %u", cwi, test->ar_t1_cwi);
+ err++;
+ }
+
+ if (ifsc != test->ar_t1_ifsc) {
+ atr_parse_failed(test, "Found mistmatched T1 ifsc: "
+ "%u, expected: %u", ifsc, test->ar_t1_ifsc);
+ err++;
+ }
+ }
+
+ if (err > 0) {
+ atr_data_dump(data, stderr);
+ return (1);
+ }
+
+ return (0);
+}
+
+int
+main(void)
+{
+ uint_t i;
+ uint_t errs = 0;
+ atr_data_t *data;
+
+ data = atr_data_alloc();
+ if (data == NULL) {
+ errx(EXIT_FAILURE, "failed to allocate atr_data_t");
+ }
+
+ for (i = 0; i < sizeof (atr_tests) / sizeof (atr_test_t); i++) {
+ atr_data_reset(data);
+ errs += atr_parse_one(data, &atr_tests[i]);
+ }
+
+ atr_data_free(data);
+
+ if (errs != 0) {
+ warnx("%d test(s) failed", errs);
+ }
+ return (errs != 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/excl-badread.c b/usr/src/test/os-tests/tests/uccid/excl-badread.c
new file mode 100644
index 0000000000..e5f265d1e7
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/excl-badread.c
@@ -0,0 +1,80 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify various bad read conditions fail successfully.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ uccid_cmd_txn_begin_t begin;
+ ssize_t ret;
+ char buf[500];
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+
+ /*
+ * Read without having a transaction
+ */
+ ret = read(fd, buf, sizeof (buf));
+ if (ret != -1) {
+ errx(EXIT_FAILURE, "read succeeded when it should have failed "
+ "(EACCES case), returned %ld", ret);
+ }
+
+ if (errno != EACCES) {
+ errx(EXIT_FAILURE, "found wrong value for errno. Expected "
+ "%d, received %d", EACCES, errno);
+ }
+
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ ret = read(fd, buf, sizeof (buf));
+ if (ret != -1) {
+ errx(EXIT_FAILURE, "read succeeded when it should have failed "
+ "(ENODATA case), returned %ld", ret);
+ }
+
+ if (errno != ENODATA) {
+ errx(EXIT_FAILURE, "found wrong value for errno. Expected "
+ "%d, received %d", ENODATA, errno);
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/excl-basic.c b/usr/src/test/os-tests/tests/uccid/excl-basic.c
new file mode 100644
index 0000000000..c6cf30dd5e
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/excl-basic.c
@@ -0,0 +1,65 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that we can grab a basic exclusive lock through an ioctl on the slot.
+ * Then that we can release it afterwards.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ uint_t i;
+ uccid_cmd_txn_begin_t begin;
+ uccid_cmd_txn_end_t end;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ bzero(&end, sizeof (end));
+
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_flags = UCCID_TXN_END_RELEASE;
+
+ for (i = 0; i < 10; i++) {
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ if (ioctl(fd, UCCID_CMD_TXN_END, &end) != 0) {
+ err(EXIT_FAILURE, "failed to issue end ioctl");
+ }
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/excl-close.c b/usr/src/test/os-tests/tests/uccid/excl-close.c
new file mode 100644
index 0000000000..3936c73ab0
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/excl-close.c
@@ -0,0 +1,81 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that if a child grabs an exclusive lock and calls exit, we can grab it
+ * again.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/wait.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, estat;
+ pid_t pid;
+ uccid_cmd_txn_begin_t begin;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+
+ pid = fork();
+ if (pid == 0) {
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ _exit(0);
+ }
+
+ estat = -1;
+ if (waitpid(pid, &estat, 0) == -1) {
+ err(EXIT_FAILURE, "failed to wait for pid %d", pid);
+ }
+
+ if (estat != 0) {
+ errx(EXIT_FAILURE, "child exited with non-zero value (%d)",
+ estat);
+ }
+
+ fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/excl-loop.c b/usr/src/test/os-tests/tests/uccid/excl-loop.c
new file mode 100644
index 0000000000..f31fc81a34
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/excl-loop.c
@@ -0,0 +1,84 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that we can grab a basic exclusive lock and then if we try to get
+ * another lock it fails. Regardless of whether we do so through open(2) or
+ * ioctl(2).
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/debug.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ uccid_cmd_txn_begin_t begin;
+ uccid_cmd_txn_end_t end;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ bzero(&end, sizeof (end));
+
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_flags = UCCID_TXN_END_RELEASE;
+
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ ret = ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EEXIST);
+
+ if (ioctl(fd, UCCID_CMD_TXN_END, &end) != 0) {
+ err(EXIT_FAILURE, "failed to issue end ioctl");
+ }
+
+ VERIFY0(close(fd));
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ ret = ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin);
+ VERIFY0(ret);
+
+ ret = ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EEXIST);
+
+ VERIFY0(close(fd));
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/excl-nonblock.c b/usr/src/test/os-tests/tests/uccid/excl-nonblock.c
new file mode 100644
index 0000000000..ee4f1edbd1
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/excl-nonblock.c
@@ -0,0 +1,99 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that if we've grabbed an exclusive lock, another thread fails to grab
+ * it as a non-blocking lock.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/debug.h>
+#include <thread.h>
+#include <errno.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+void *
+nonblock_thread(void *arg)
+{
+ uccid_cmd_txn_begin_t begin;
+ int ret;
+ int fd = (uintptr_t)arg;
+
+
+ bzero(&begin, sizeof (begin));
+
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ begin.uct_flags = UCCID_TXN_DONT_BLOCK;
+
+ ret = ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EBUSY);
+
+ return (NULL);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fda, fdb;
+ uccid_cmd_txn_begin_t begin;
+ uccid_cmd_txn_end_t end;
+ thread_t thr;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fda = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ if ((fdb = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ bzero(&end, sizeof (end));
+
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_flags = UCCID_TXN_END_RELEASE;
+
+ if (ioctl(fda, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ if (thr_create(NULL, 0, nonblock_thread, (void *)(uintptr_t)fdb, 0,
+ &thr) != 0) {
+ err(EXIT_FAILURE, "failed to create thread");
+ }
+
+ if (thr_join(thr, NULL, NULL) != 0) {
+ err(EXIT_FAILURE, "failed to join therad");
+ }
+
+ if (ioctl(fda, UCCID_CMD_TXN_END, &end) != 0) {
+ err(EXIT_FAILURE, "failed to issue end ioctl");
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/excl-reset.c b/usr/src/test/os-tests/tests/uccid/excl-reset.c
new file mode 100644
index 0000000000..7ab1718475
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/excl-reset.c
@@ -0,0 +1,65 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that we can grab a basic exclusive lock through an ioctl on the slot.
+ * Then that we can release it afterwards and reset the ICC.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ uint_t i;
+ uccid_cmd_txn_begin_t begin;
+ uccid_cmd_txn_end_t end;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ bzero(&end, sizeof (end));
+
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_flags = UCCID_TXN_END_RESET;
+
+ for (i = 0; i < 10; i++) {
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ if (ioctl(fd, UCCID_CMD_TXN_END, &end) != 0) {
+ err(EXIT_FAILURE, "failed to issue end ioctl");
+ }
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/modify.c b/usr/src/test/os-tests/tests/uccid/modify.c
new file mode 100644
index 0000000000..137c461701
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/modify.c
@@ -0,0 +1,203 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that we can issue ICC_MODIFY ioctls. Also, check some of the failure
+ * modes.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/debug.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+static const uint8_t yk_req[] = {
+ 0x00, 0xa4, 0x04, 0x00, 0x07, 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
+};
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ uccid_cmd_icc_modify_t uci;
+ uccid_cmd_status_t ucs;
+ uccid_cmd_txn_begin_t begin;
+ uint8_t buf[UCCID_APDU_SIZE_MAX];
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ /* power off the card outside of a transaction */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_OFF;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is inactive now */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, ==, 0);
+
+ /* power on the card outside of a transaction */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_ON;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is active again */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, !=, 0);
+
+
+ /* enter transaction */
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ /* make sure the card is active (power on) */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, !=, 0);
+
+ /* power off the card */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_OFF;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is inactive now */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, ==, 0);
+
+ /* power on the card */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_ON;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is active again */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, !=, 0);
+
+ /* do a warm reset of the card */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_WARM_RESET;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is still active */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, !=, 0);
+
+ /* write a command to the card, which is assumed to be a YubiKey */
+ if ((ret = write(fd, yk_req, sizeof (yk_req))) < 0) {
+ err(EXIT_FAILURE, "failed to write data");
+ }
+
+ /* power off the card */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_OFF;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is inactive now */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, ==, 0);
+
+ /* try to read the answer from the YubiKey. */
+ ret = read(fd, buf, sizeof (buf));
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, ENXIO);
+
+ /* power on the card */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_ON;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, 0);
+
+ /* make sure the card is active again */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+ VERIFY3U(ucs.ucs_status & UCCID_STATUS_F_CARD_ACTIVE, !=, 0);
+
+ /* test various failure modes */
+ uci.uci_version = UCCID_VERSION_ONE - 1;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ uci.uci_version = UCCID_VERSION_ONE + 1;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = 0;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = -1;
+ ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/notxn-poll.c b/usr/src/test/os-tests/tests/uccid/notxn-poll.c
new file mode 100644
index 0000000000..3a756b5fbc
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/notxn-poll.c
@@ -0,0 +1,57 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that trying to poll without a transaction / excl access fails
+ * with EACCES.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/debug.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ struct pollfd pfds[1];
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLIN;
+ pfds[0].revents = 0;
+
+ ret = poll(pfds, 1, 0);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EACCES);
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/pollin.c b/usr/src/test/os-tests/tests/uccid/pollin.c
new file mode 100644
index 0000000000..dd81c245cc
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/pollin.c
@@ -0,0 +1,64 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Open up a device and make sure we get pollout by default.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ struct pollfd pfds[1];
+ uccid_cmd_txn_begin_t begin;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLIN;
+ pfds[0].revents = 0;
+
+ ret = poll(pfds, 1, 0);
+ if (ret != 0) {
+ err(EXIT_FAILURE, "poll didn't return 0, returned %d "
+ "(errno %d)", ret, errno);
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/pollout.c b/usr/src/test/os-tests/tests/uccid/pollout.c
new file mode 100644
index 0000000000..a9928a6fe2
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/pollout.c
@@ -0,0 +1,68 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Open up a device and make sure we get pollout by default.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ struct pollfd pfds[1];
+ uccid_cmd_txn_begin_t begin;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLOUT;
+ pfds[0].revents = 0;
+
+ ret = poll(pfds, 1, 0);
+ if (ret != 1) {
+ err(EXIT_FAILURE, "poll didn't return 1, returned %d "
+ "(errno %d)", ret, errno);
+ }
+
+ if (!(pfds[0].revents & POLLOUT)) {
+ err(EXIT_FAILURE, "missing pollout, got %d", pfds[0].revents);
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/status.c b/usr/src/test/os-tests/tests/uccid/status.c
new file mode 100644
index 0000000000..ae2a51226f
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/status.c
@@ -0,0 +1,97 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that we can issue various status ioctls regardless of whether or not
+ * we have exclusive access on our handle. Also, check some of the failure
+ * modes.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <sys/debug.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, efd, ret;
+ uccid_cmd_status_t ucs;
+ uccid_cmd_txn_begin_t begin;
+ void *badaddr;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ if ((efd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(efd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+
+ ret = ioctl(efd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, 0);
+
+ ucs.ucs_version = UCCID_VERSION_ONE - 1;
+ ret = ioctl(efd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ ucs.ucs_version = UCCID_VERSION_ONE + 1;
+ ret = ioctl(efd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ ucs.ucs_version = UCCID_VERSION_ONE - 1;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ ucs.ucs_version = UCCID_VERSION_ONE + 1;
+ ret = ioctl(fd, UCCID_CMD_STATUS, &ucs);
+ VERIFY3S(ret, ==, -1);
+ VERIFY3S(errno, ==, EINVAL);
+
+ badaddr = mmap(NULL, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_ANON, -1,
+ 0);
+ VERIFY3P(badaddr, !=, MAP_FAILED);
+ VERIFY0(munmap(badaddr, PAGESIZE));
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/txn-pollerr.c b/usr/src/test/os-tests/tests/uccid/txn-pollerr.c
new file mode 100644
index 0000000000..b19598f711
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/txn-pollerr.c
@@ -0,0 +1,88 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Verify that closing a transaction while polling generates POLLERR.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/debug.h>
+#include <poll.h>
+#include <port.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd, port;
+ uccid_cmd_txn_end_t end;
+ uccid_cmd_txn_begin_t begin;
+ port_event_t pe;
+ timespec_t to;
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((port = port_create()) == -1) {
+ err(EXIT_FAILURE, "failed to create event port: %d",
+ port);
+ }
+
+ if ((fd = open(argv[1], O_RDWR | O_EXCL)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ /*
+ * Do not poll for pollout here, since by default, after grabbing a
+ * transaction, the device is writeable.
+ */
+ if (port_associate(port, PORT_SOURCE_FD, fd, POLLIN, NULL) != 0) {
+ err(EXIT_FAILURE, "failed to associate");
+ }
+
+ bzero(&end, sizeof (end));
+ end.uct_version = UCCID_CURRENT_VERSION;
+ end.uct_flags = UCCID_TXN_END_RELEASE;
+
+ if (ioctl(fd, UCCID_CMD_TXN_END, &end) != 0) {
+ err(EXIT_FAILURE, "failed to issue end ioctl");
+ }
+
+ bzero(&to, sizeof (timespec_t));
+ if (port_get(port, &pe, &to) != 0) {
+ err(EXIT_FAILURE, "failed to port_get()");
+ }
+
+ VERIFY3S(pe.portev_source, ==, PORT_SOURCE_FD);
+ VERIFY3S(pe.portev_object, ==, fd);
+ VERIFY3S(pe.portev_events & POLLERR, !=, 0);
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/yk-poll.c b/usr/src/test/os-tests/tests/uccid/yk-poll.c
new file mode 100644
index 0000000000..fb52949f38
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/yk-poll.c
@@ -0,0 +1,108 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Open a YubiKey class device and get the basic information applet
+ * through an APDU while using poll(2) to check device readyness.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+#include <poll.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+static const uint8_t yk_req[] = {
+ 0x00, 0xa4, 0x04, 0x00, 0x07, 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
+};
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ struct pollfd pfds[1];
+ uccid_cmd_txn_begin_t begin;
+ uint8_t buf[UCCID_APDU_SIZE_MAX];
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ pfds[0].fd = fd;
+ pfds[0].events = POLLOUT | POLLIN | POLLRDNORM;
+ pfds[0].revents = 0;
+
+ ret = poll(pfds, 1, 0);
+ if (ret != 1) {
+ err(EXIT_FAILURE, "poll didn't return 1, returned %d "
+ "(errno %d)", ret, errno);
+ }
+
+ if ((pfds[0].revents & POLLOUT) != POLLOUT) {
+ err(EXIT_FAILURE, "expecting pollout, got %d", pfds[0].revents);
+ }
+
+ if ((ret = write(fd, yk_req, sizeof (yk_req))) < 0) {
+ err(EXIT_FAILURE, "failed to write data");
+ }
+
+ pfds[0].revents = 0;
+
+ ret = poll(pfds, 1, -1);
+ if (ret != 1) {
+ err(EXIT_FAILURE, "poll didn't return 1, returned %d "
+ "(errno %d)", ret, errno);
+ }
+
+ if ((pfds[0].revents & (POLLIN | POLLRDNORM)) !=
+ (POLLIN | POLLRDNORM)) {
+ err(EXIT_FAILURE, "expecting pollin|pollrdnorm, got %d",
+ pfds[0].revents);
+ }
+
+ if ((ret = read(fd, buf, sizeof (buf))) < 0) {
+ err(EXIT_FAILURE, "failed to read data");
+ }
+
+ pfds[0].revents = 0;
+
+ ret = poll(pfds, 1, 0);
+ if (ret != 1) {
+ err(EXIT_FAILURE, "poll didn't return 1, returned %d "
+ "(errno %d)", ret, errno);
+ }
+
+ if ((pfds[0].revents & POLLOUT) != POLLOUT) {
+ err(EXIT_FAILURE, "expecting pollout, got %d", pfds[0].revents);
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/yk-readonly.c b/usr/src/test/os-tests/tests/uccid/yk-readonly.c
new file mode 100644
index 0000000000..1f5d503891
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/yk-readonly.c
@@ -0,0 +1,100 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Open a YubiKey class device read-only and try to get the basic information
+ * applet through an APDU, which should fail. Try to get the status, which
+ * should succeed, and attempt to power off, which should fail.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+static const uint8_t yk_req[] = {
+ 0x00, 0xa4, 0x04, 0x00, 0x07, 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
+};
+
+int
+main(int argc, char *argv[])
+{
+ int fd, ret;
+ uccid_cmd_icc_modify_t uci;
+ uccid_cmd_txn_begin_t begin;
+ uccid_cmd_status_t ucs;
+ uint8_t buf[UCCID_APDU_SIZE_MAX];
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDONLY)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) == 0) {
+ errx(EXIT_FAILURE, "didn't fail to issue begin ioctl");
+ }
+
+ if ((ret = write(fd, yk_req, sizeof (yk_req))) != -1) {
+ errx(EXIT_FAILURE, "didn't fail to write data");
+ }
+
+ if (errno != EBADF) {
+ err(EXIT_FAILURE, "wrong errno for failed write, "
+ "expected EBADF");
+ }
+
+ if ((ret = read(fd, buf, sizeof (buf))) != -1) {
+ errx(EXIT_FAILURE, "didn't fail to read data");
+ }
+
+ if (errno != EACCES) {
+ err(EXIT_FAILURE, "wrong errno for failed read, "
+ "expected EACCES");
+ }
+
+ /* get card status */
+ bzero(&ucs, sizeof (ucs));
+ ucs.ucs_version = UCCID_CURRENT_VERSION;
+ if ((ret = ioctl(fd, UCCID_CMD_STATUS, &ucs)) != 0) {
+ err(EXIT_FAILURE, "failed to get status");
+ }
+
+
+ /* try to power off the card while opened read-only */
+ bzero(&uci, sizeof (uci));
+ uci.uci_version = UCCID_CURRENT_VERSION;
+ uci.uci_action = UCCID_ICC_POWER_OFF;
+ if ((ret = ioctl(fd, UCCID_CMD_ICC_MODIFY, &uci)) == 0) {
+ errx(EXIT_FAILURE, "didn't fail to power off ICC");
+ }
+
+ if (errno != EBADF) {
+ err(EXIT_FAILURE, "wrong errno for failed write, "
+ "expected EBADF");
+ }
+
+ return (0);
+}
diff --git a/usr/src/test/os-tests/tests/uccid/yk.c b/usr/src/test/os-tests/tests/uccid/yk.c
new file mode 100644
index 0000000000..45bdfd059e
--- /dev/null
+++ b/usr/src/test/os-tests/tests/uccid/yk.c
@@ -0,0 +1,78 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * Attempt to open a YubiKey class device and get the basic information applet
+ * through an APDU.
+ */
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <sys/usb/clients/ccid/uccid.h>
+
+static const uint8_t yk_req[] = {
+ 0x00, 0xa4, 0x04, 0x00, 0x07, 0xa0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01
+};
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ ssize_t ret, i;
+ uccid_cmd_txn_begin_t begin;
+ uint8_t buf[UCCID_APDU_SIZE_MAX];
+
+ if (argc != 2) {
+ errx(EXIT_FAILURE, "missing required ccid path");
+ }
+
+ if ((fd = open(argv[1], O_RDWR)) < 0) {
+ err(EXIT_FAILURE, "failed to open %s", argv[1]);
+ }
+
+ bzero(&begin, sizeof (begin));
+ begin.uct_version = UCCID_CURRENT_VERSION;
+
+ if (ioctl(fd, UCCID_CMD_TXN_BEGIN, &begin) != 0) {
+ err(EXIT_FAILURE, "failed to issue begin ioctl");
+ }
+
+ if ((ret = write(fd, yk_req, sizeof (yk_req))) < 0) {
+ err(EXIT_FAILURE, "failed to write data");
+ }
+
+ if ((ret = read(fd, buf, sizeof (buf))) < 0) {
+ err(EXIT_FAILURE, "failed to read data");
+ }
+
+ (void) printf("read %d bytes\n", ret);
+ for (i = 0; i < ret; i++) {
+ (void) printf("%02x", buf[i]);
+ if (i == (ret - 1) || (i % 16) == 15) {
+ (void) printf("\n");
+ } else {
+ (void) printf(" ");
+ }
+ }
+
+ return (0);
+}
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 754380a622..b146c65178 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -1787,6 +1787,8 @@ USB_IA_OBJS += usb_ia.o
SCSA2USB_OBJS += scsa2usb.o usb_ms_bulkonly.o usb_ms_cbi.o
+CCID_OBJS += ccid.o atr.o
+
IPF_OBJS += ip_fil_solaris.o fil.o solaris.o ip_state.o ip_frag.o ip_nat.o \
ip_proxy.o ip_auth.o ip_pool.o ip_htable.o ip_lookup.o \
ip_log.o misc.o ip_compat.o ip_nat6.o drand48.o
diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules
index c130ae190f..a5df77b507 100644
--- a/usr/src/uts/common/Makefile.rules
+++ b/usr/src/uts/common/Makefile.rules
@@ -1171,6 +1171,14 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/audio/usb_ah/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/ccid/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(COMMONBASE)/ccid/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/usbskel/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
diff --git a/usr/src/uts/common/io/usb/clients/ccid/ccid.c b/usr/src/uts/common/io/usb/clients/ccid/ccid.c
new file mode 100644
index 0000000000..61003875d4
--- /dev/null
+++ b/usr/src/uts/common/io/usb/clients/ccid/ccid.c
@@ -0,0 +1,4420 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+/*
+ * USB CCID class driver
+ *
+ * Slot Detection
+ * --------------
+ *
+ * A CCID reader has one or more slots, each of which may or may not have a ICC
+ * (integrated circuit card) present. Some readers actually have a card that's
+ * permanently plugged in while other readers allow for cards to be inserted and
+ * removed. We model all CCID readers that don't have removable cards as ones
+ * that are removable, but never fire any events. Readers with removable cards
+ * are required to have an Interrupt-IN pipe.
+ *
+ * Each slot starts in an unknown state. After attaching we always kick off a
+ * discovery. When a change event comes in, that causes us to kick off a
+ * discovery again, though we focus it only on those endpoints that have noted a
+ * change. At attach time we logically mark that every endpoint has changed,
+ * allowing us to figure out what its actual state is. We don't rely on any
+ * initial Interrupt-IN polling to allow for the case where either the hardware
+ * doesn't report it or to better handle the devices without an Interrupt-IN
+ * entry. Just because we open up the Interrupt-IN pipe, hardware is not
+ * obligated to tell us, as the detaching and reattaching of a driver will not
+ * cause a power cycle.
+ *
+ * The Interrupt-IN exception callback may need to restart polling. In addition,
+ * we may fail to start or restart polling due to a transient issue. In cases
+ * where the attempt to start polling has failed, we try again in one second
+ * with a timeout.
+ *
+ * Discovery is run through a taskq. The various slots are checked serially. If
+ * a discovery is running when another change event comes in, we flag ourselves
+ * for a follow up run. This means that it's possible that we end up processing
+ * items early and that the follow up run is ignored.
+ *
+ * Two state flags are used to keep track of this dance: CCID_F_WORKER_REQUESTED
+ * and CCID_F_WORKER_RUNNING. The first is used to indicate that discovery is
+ * desired. The second is to indicate that it is actively running. When
+ * discovery is requested, the caller first checks the current flags. If neither
+ * flag is set, then it knows that it can kick off discovery. Regardless if it
+ * can kick off the taskq, it always sets requested. Once the taskq entry
+ * starts, it removes any DISCOVER_REQUESTED flags and sets DISCOVER_RUNNING. If
+ * at the end of discovery, we find that another request has been made, the
+ * discovery function will kick off another entry in the taskq.
+ *
+ * The one possible problem with this model is that it means that we aren't
+ * throttling the set of incoming requests with respect to taskq dispatches.
+ * However, because these are only driven by an Interrupt-IN pipe, it is hoped
+ * that the frequency will be rather reduced. If it turns out that that's not
+ * the case, we may need to use a timeout or another trick to ensure that only
+ * one discovery per tick or so is initialized. The main reason we don't just do
+ * that off the bat and add a delay is because of contactless cards which may
+ * need to be acted upon in a soft real-time fashion.
+ *
+ * Command Handling
+ * ----------------
+ *
+ * Commands are issued to a CCID reader on a Bulk-OUT pipe. Responses are
+ * generated as a series of one or more messages on a Bulk-IN pipe. To correlate
+ * these commands a sequence number is used. This sequence number is one byte
+ * and can be in the range [ CCID_SEQ_MIN, CCID_SEQ_MAX ]. To keep track of the
+ * allocated IDs we leverage an ID space.
+ *
+ * A CCID reader contains a number of slots. Each slot can be addressed
+ * separately as each slot represents a separate place that a card may be
+ * inserted or not. A given slot may only have a single outstanding command. A
+ * given CCID reader may only have a number of commands outstanding to the CCID
+ * device as a whole based on a value in the class descriptor (see the
+ * ccd_bMacCCIDBusySlots member of the ccid_class_descr_t).
+ *
+ * To simplify the driver, we only support issuing a single command to a CCID
+ * reader at any given time. All commands that are outstanding are queued in a
+ * per-device list ccid_command_queue. The head of the queue is the current
+ * command that we believe is outstanding to the reader or will be shortly. The
+ * command is issued by sending a Bulk-OUT request with a CCID header. Once we
+ * have the Bulk-OUT request acknowledged, we begin sending Bulk-IN messages to
+ * the controller. Once the Bulk-IN message is acknowledged, then we complete
+ * the command and proceed to the next command. This is summarized in the
+ * following state machine:
+ *
+ * +-----------------------------------------------------+
+ * | |
+ * | ccid_command_queue |
+ * | +---+---+---------+---+---+ |
+ * v | h | | | | t | |
+ * +-------------------------+ | e | | | | a | |
+ * | ccid_command_dispatch() |<-----| a | | ... | | i | |
+ * +-----------+-------------+ | d | | | | l | |
+ * | +---+---+---------+---+---+ |
+ * v |
+ * +-------------------------+ +-------------------------+ |
+ * | usb_pipe_bulk_xfer() |----->| ccid_dispatch_bulk_cb() | |
+ * | ccid_bulkout_pipe | +------------+------------+ |
+ * +-------------------------+ | |
+ * | |
+ * | v |
+ * | +-------------------------+ |
+ * | | ccid_bulkin_schedule() | |
+ * v +------------+------------+ |
+ * | |
+ * /--------------------\ | |
+ * / \ v |
+ * | ### CCID HW | +-------------------------+ |
+ * | ### | | usb_pipe_bulk_xfer() | |
+ * | | ----> | ccid_bulkin_pipe | |
+ * | | +------------+------------+ |
+ * \ / | |
+ * \--------------------/ | |
+ * v |
+ * +-------------------------+ |
+ * | ccid_reply_bulk_cb() | |
+ * +------------+------------+ |
+ * | |
+ * | |
+ * v |
+ * +-------------------------+ |
+ * | ccid_command_complete() +------+
+ * +-------------------------+
+ *
+ *
+ * APDU and TPDU Processing and Parameter Selection
+ * ------------------------------------------------
+ *
+ * Readers provide four different modes for us to be able to transmit data to
+ * and from the card. These are:
+ *
+ * 1. Character Mode
+ * 2. TPDU Mode
+ * 3. Short APDU Mode
+ * 4. Extended APDU Mode
+ *
+ * Readers either support mode 1, mode 2, mode 3, or mode 3 and 4. All readers
+ * that support extended APDUs support short APDUs. At this time, we do not
+ * support character mode or TPDU mode, and we use only short APDUs even for
+ * readers that support extended APDUs.
+ *
+ * The ICC and the reader need to be in agreement in order for them to be able
+ * to exchange information. The ICC indicates what it supports by replying to a
+ * power on command with an ATR (answer to reset). This data can be parsed to
+ * indicate which of two protocols the ICC supports. These protocols are
+ * referred to as:
+ *
+ * o T=0
+ * o T=1
+ *
+ * These protocols are defined in the ISO/IEC 7816-3:2006 specification. When a
+ * reader supports an APDU mode, then the driver does not have to worry about
+ * the underlying protocol and can just send an application data unit (APDU).
+ * Otherwise, the driver must take the application data (APDU) and transform it
+ * into the form required by the corresponding protocol.
+ *
+ * There are several parameters that need to be negotiated to ensure that the
+ * protocols work correctly. To negotiate these parameters and to select a
+ * protocol, the driver must construct a PPS (protocol and parameters structure)
+ * request and exchange that with the ICC. A reader may optionally take care of
+ * performing this and indicates its support for this in dwFeatures member of
+ * the USB class descriptor.
+ *
+ * In addition, the reader itself must often be told of these configuration
+ * changes through the means of a CCID_REQUEST_SET_PARAMS command. Once both of
+ * these have been performed, the reader and ICC can communicate to their hearts
+ * desire.
+ *
+ * Both the negotiation and the setting of the parameters can be performed
+ * automatically by the CCID reader. When the reader supports APDU exchanges,
+ * then it must support some aspects of this negotiation. Because of that, we
+ * never consider performing this and only support readers that offer this kind
+ * of automation.
+ *
+ * User I/O Basics
+ * ---------------
+ *
+ * A user performs I/O by writing APDUs (Application Protocol Data Units). A
+ * user issues a system call that ends up in write(9E) (write(2), writev(2),
+ * pwrite(2), pwritev(2), etc.). The user data is consumed by the CCID driver
+ * and a series of commands will then be issued to the device, depending on the
+ * protocol mode. The write(9E) call does not block for this to finish. Once
+ * write(9E) has returned, the user may block in a read(2) related system call
+ * or poll for POLLIN.
+ *
+ * A thread may not call read(9E) without having called write(9E). This model is
+ * due to the limited capability of hardware. Only a single command can be going
+ * on a given slot and due to the fact that many commands change the hardware
+ * state, we do not try to multiplex multiple calls to write() or read().
+ *
+ *
+ * User I/O, Transaction Ends, ICC removals, and Reader Removals
+ * -------------------------------------------------------------
+ *
+ * While the I/O model given to user land is somewhat simple, there are a lot of
+ * tricky pieces to get right because we are in a multi-threaded pre-emptible
+ * system. In general, there are four different levels of state that we need to
+ * keep track of:
+ *
+ * 1. User threads in I/O
+ * 2. Kernel protocol level support (T=1, apdu, etc.).
+ * 3. Slot/ICC state
+ * 4. CCID Reader state
+ *
+ * Of course, each level cares about the state above it. The kernel protocol
+ * level state (2) cares about the User threads in I/O (1). The same is true
+ * with the other levels caring about the levels above it. With this in mind
+ * there are three non-data path things that can go wrong:
+ *
+ * A. The user can end a transaction (whether through an ioctl or close(9E)).
+ * B. The ICC can be removed
+ * C. The CCID device can be removed or reset at a USB level.
+ *
+ * Each of these has implications on the outstanding I/O and other states of
+ * the world. When events of type A occur, we need to clean up states 1 and 2.
+ * Then events of type B occur we need to clean up states 1-3. When events of
+ * type C occur we need to clean up states 1-4. The following discusses how we
+ * should clean up these different states:
+ *
+ * Cleaning up State 1:
+ *
+ * To clean up the User threads in I/O there are three different cases to
+ * consider. The first is cleaning up a thread that is in the middle of
+ * write(9E). The second is cleaning up thread that is blocked in read(9E).
+ * The third is dealing with threads that are stuck in chpoll(9E).
+ *
+ * To handle the write case, we have a series of flags that is on the CCID
+ * slot's I/O structure (ccid_io_t, cs_io on the ccid_slot_t). When a thread
+ * begins its I/O it will set the CCID_IO_F_PREPARING flag. This flag is used
+ * to indicate that there is a thread that is performing a write(9E), but it
+ * is not holding the ccid_mutex because of the operations that it is taking.
+ * Once it has finished, the thread will remove that flag and instead
+ * CCID_IO_F_IN_PROGRESS will be set. If we find that the CCID_IO_F_PREPARING
+ * flag is set, then we will need to wait for it to be removed before
+ * continuing. The fact that there is an outstanding physical I/O will be
+ * dealt with when we clean up state 2.
+ *
+ * To handle the read case, we have a flag on the ccid_minor_t which indicates
+ * that a thread is blocked on a condition variable (cm_read_cv), waiting for
+ * the I/O to complete. The way this gets cleaned up varies a bit on each of
+ * the different cases as each one will trigger a different error to the
+ * thread. In all cases, the condition variable will be signaled. Then,
+ * whenever the thread comes out of the condition variable it will always
+ * check the state to see if it has been woken up because the transaction is
+ * being closed, the ICC has been removed, or the reader is being
+ * disconnected. In all such cases, the thread in read will end up receiving
+ * an error (ECANCELED, ENXIO, and ENODEV respectively).
+ *
+ * If we have hit the case that this needs to be cleaned up, then the
+ * CCID_MINOR_F_READ_WAITING flag will be set on the ccid_minor_t's flags
+ * member (cm_flags). In this case, the broader system must change the
+ * corresponding system state flag for the appropriate condition, signal the
+ * read cv, and then wait on an additional cv in the minor, the
+ * ccid_iowait_cv).
+ *
+ * Cleaning up the poll state is somewhat simpler. If any of the conditions
+ * (A-C) occur, then we must flag POLLERR. In addition if B and C occur, then
+ * we will flag POLLHUP at the same time. This will guarantee that any threads
+ * in poll(9E) are woken up.
+ *
+ * Cleaning up State 2.
+ *
+ * While the user I/O thread is a somewhat straightforward, the kernel
+ * protocol level is a bit more complicated. The core problem is that when a
+ * user issues a logical I/O through an APDU, that may result in a series of
+ * one or more protocol level physical commands. The core crux of the issue
+ * with cleaning up this state is twofold:
+ *
+ * 1. We don't want to block a user thread while I/O is outstanding
+ * 2. We need to take one of several steps to clean up the aforementioned
+ * I/O
+ *
+ * To try and deal with that, there are a number of different things that we
+ * do. The first thing we do is that we clean up the user state based on the
+ * notes in cleaning up in State 1. Importantly we need to _block_ on this
+ * activity.
+ *
+ * Once that is done, we need to proceed to step 2. Since we're doing only
+ * APDU processing, this is as simple as waiting for that command to complete
+ * and/or potentially issue an abort or reset.
+ *
+ * While this is ongoing an additional flag (CCID_SLOT_F_NEED_IO_TEARDOWN)
+ * will be set on the slot to make sure that we know that we can't issue new
+ * I/O or that we can't proceed to the next transaction until this phase is
+ * finished.
+ *
+ * Cleaning up State 3
+ *
+ * When the ICC is removed, this is not dissimilar to the previous states. To
+ * handle this we need to first make sure that state 1 and state 2 are
+ * finished being cleaned up. We will have to _block_ on this from the worker
+ * thread. The problem is that we have certain values such as the operations
+ * vector, the ATR data, etc. that we need to make sure are still valid while
+ * we're in the process of cleaning up state. Once all that is done and the
+ * worker thread proceeds we will consider processing a new ICC insertion.
+ * The one good side is that if the ICC was removed, then it should be simpler
+ * to handle all of the outstanding I/O.
+ *
+ * Cleaning up State 4
+ *
+ * When the reader is removed, then we need to clean up all the prior states.
+ * However, this is somewhat simpler than the other cases, as once this
+ * happens our detach endpoint will be called to clean up all of our
+ * resources. Therefore, before we call detach, we need to explicitly clean up
+ * state 1; however, we then at this time leave all the remaining state to be
+ * cleaned up during detach(9E) as part of normal tear down.
+ */
+
+#include <sys/modctl.h>
+#include <sys/errno.h>
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/cmn_err.h>
+#include <sys/sysmacros.h>
+#include <sys/stream.h>
+#include <sys/strsun.h>
+#include <sys/strsubr.h>
+#include <sys/filio.h>
+
+#define USBDRV_MAJOR_VER 2
+#define USBDRV_MINOR_VER 0
+#include <sys/usb/usba.h>
+#include <sys/usb/usba/usbai_private.h>
+#include <sys/usb/clients/ccid/ccid.h>
+#include <sys/usb/clients/ccid/uccid.h>
+
+#include <atr.h>
+
+/*
+ * Set the amount of parallelism we'll want to have from kernel threads which
+ * are processing CCID requests. This is used to size the number of asynchronous
+ * requests in the pipe policy. A single command can only ever be outstanding to
+ * a single slot. However, multiple slots may potentially be able to be
+ * scheduled in parallel. However, we don't actually support this at all and
+ * we'll only ever issue a single command. This basically covers the ability to
+ * have some other asynchronous operation outstanding if needed.
+ */
+#define CCID_NUM_ASYNC_REQS 2
+
+/*
+ * This is the number of Bulk-IN requests that we will have cached per CCID
+ * device. While many commands will generate a single response, the commands
+ * also have the ability to generate time extensions, which means that we'll
+ * want to be able to schedule another Bulk-IN request immediately. If we run
+ * out, we will attempt to refill said cache and will not fail commands
+ * needlessly.
+ */
+#define CCID_BULK_NALLOCED 16
+
+/*
+ * This is a time in seconds for the bulk-out command to run and be submitted.
+ */
+#define CCID_BULK_OUT_TIMEOUT 5
+#define CCID_BULK_IN_TIMEOUT 5
+
+/*
+ * There are two different Interrupt-IN packets that we might receive. The
+ * first, RDR_to_PC_HardwareError, is a fixed four byte packet. However, the
+ * other one, RDR_to_PC_NotifySlotChange, varies in size as it has two bits per
+ * potential slot plus one byte that's always used. The maximum number of slots
+ * in a device is 256. This means there can be up to 64 bytes worth of data plus
+ * the extra byte, so 65 bytes.
+ */
+#define CCID_INTR_RESPONSE_SIZE 65
+
+/*
+ * Minimum and maximum minor ids. We treat the maximum valid 32-bit minor as
+ * what we can use due to issues in some file systems and the minors that they
+ * can use. We reserved zero as an invalid minor number to make it easier to
+ * tell if things have been initialized or not.
+ */
+#define CCID_MINOR_MIN 1
+#define CCID_MINOR_MAX MAXMIN32
+#define CCID_MINOR_INVALID 0
+
+/*
+ * This value represents the minimum size value that we require in the CCID
+ * class descriptor's dwMaxCCIDMessageLength member. We got to 64 bytes based on
+ * the required size of a bulk transfer packet size. Especially as many CCID
+ * devices are these class of speeds. The specification does require that the
+ * minimum size of the dwMaxCCIDMessageLength member is at least the size of its
+ * bulk endpoint packet size.
+ */
+#define CCID_MIN_MESSAGE_LENGTH 64
+
+/*
+ * Required forward declarations.
+ */
+struct ccid;
+struct ccid_slot;
+struct ccid_minor;
+struct ccid_command;
+
+/*
+ * This structure is used to map between the global set of minor numbers and the
+ * things represented by them.
+ *
+ * We have two different kinds of minor nodes. The first are CCID slots. The
+ * second are cloned opens of those slots. Each of these items has a
+ * ccid_minor_idx_t embedded in them that is used to index them in an AVL tree.
+ * Given that the number of entries that should be present here is unlikely to
+ * be terribly large at any given time, it is hoped that an AVL tree will
+ * suffice for now.
+ */
+typedef struct ccid_minor_idx {
+ id_t cmi_minor;
+ avl_node_t cmi_avl;
+ boolean_t cmi_isslot;
+ union {
+ struct ccid_slot *cmi_slot;
+ struct ccid_minor *cmi_user;
+ } cmi_data;
+} ccid_minor_idx_t;
+
+typedef enum ccid_minor_flags {
+ CCID_MINOR_F_WAITING = 1 << 0,
+ CCID_MINOR_F_HAS_EXCL = 1 << 1,
+ CCID_MINOR_F_TXN_RESET = 1 << 2,
+ CCID_MINOR_F_READ_WAITING = 1 << 3,
+ CCID_MINOR_F_WRITABLE = 1 << 4,
+} ccid_minor_flags_t;
+
+typedef struct ccid_minor {
+ ccid_minor_idx_t cm_idx; /* write-once */
+ cred_t *cm_opener; /* write-once */
+ struct ccid_slot *cm_slot; /* write-once */
+ list_node_t cm_minor_list;
+ list_node_t cm_excl_list;
+ kcondvar_t cm_read_cv;
+ kcondvar_t cm_iowait_cv;
+ kcondvar_t cm_excl_cv;
+ ccid_minor_flags_t cm_flags;
+ struct pollhead cm_pollhead;
+} ccid_minor_t;
+
+typedef enum ccid_slot_flags {
+ CCID_SLOT_F_CHANGED = 1 << 0,
+ CCID_SLOT_F_INTR_GONE = 1 << 1,
+ CCID_SLOT_F_INTR_ADD = 1 << 2,
+ CCID_SLOT_F_PRESENT = 1 << 3,
+ CCID_SLOT_F_ACTIVE = 1 << 4,
+ CCID_SLOT_F_NEED_TXN_RESET = 1 << 5,
+ CCID_SLOT_F_NEED_IO_TEARDOWN = 1 << 6,
+ CCID_SLOT_F_INTR_OVERCURRENT = 1 << 7,
+} ccid_slot_flags_t;
+
+#define CCID_SLOT_F_INTR_MASK (CCID_SLOT_F_CHANGED | CCID_SLOT_F_INTR_GONE | \
+ CCID_SLOT_F_INTR_ADD)
+#define CCID_SLOT_F_WORK_MASK (CCID_SLOT_F_INTR_MASK | \
+ CCID_SLOT_F_NEED_TXN_RESET | CCID_SLOT_F_INTR_OVERCURRENT)
+#define CCID_SLOT_F_NOEXCL_MASK (CCID_SLOT_F_NEED_TXN_RESET | \
+ CCID_SLOT_F_NEED_IO_TEARDOWN)
+
+typedef void (*icc_init_func_t)(struct ccid *, struct ccid_slot *);
+typedef int (*icc_transmit_func_t)(struct ccid *, struct ccid_slot *);
+typedef void (*icc_complete_func_t)(struct ccid *, struct ccid_slot *,
+ struct ccid_command *);
+typedef void (*icc_teardown_func_t)(struct ccid *, struct ccid_slot *, int);
+typedef void (*icc_fini_func_t)(struct ccid *, struct ccid_slot *);
+
+typedef struct ccid_icc {
+ atr_data_t *icc_atr_data;
+ atr_protocol_t icc_protocols;
+ atr_protocol_t icc_cur_protocol;
+ ccid_params_t icc_params;
+ icc_init_func_t icc_init;
+ icc_transmit_func_t icc_tx;
+ icc_complete_func_t icc_complete;
+ icc_teardown_func_t icc_teardown;
+ icc_fini_func_t icc_fini;
+} ccid_icc_t;
+
+/*
+ * Structure used to take care of and map I/O requests and things. This may not
+ * make sense as we develop the T=0 and T=1 code.
+ */
+typedef enum ccid_io_flags {
+ /*
+ * This flag is used during the period that a thread has started calling
+ * into ccid_write(9E), but before it has finished queuing up the write.
+ * This blocks pollout or another thread in write.
+ */
+ CCID_IO_F_PREPARING = 1 << 0,
+ /*
+ * This flag is used once a ccid_write() ICC tx function has
+ * successfully completed. While this is set, the device is not
+ * writable; however, it is legal to call ccid_read() and block. This
+ * flag will remain set until the actual write is done. This indicates
+ * that the transmission protocol has finished.
+ */
+ CCID_IO_F_IN_PROGRESS = 1 << 1,
+ /*
+ * This flag is used to indicate that the logical I/O has completed in
+ * one way or the other and that a reader can consume data. When this
+ * flag is set, then POLLIN | POLLRDNORM should be signaled. Until the
+ * I/O is consumed via ccid_read(), calls to ccid_write() will fail with
+ * EBUSY. When this flag is set, the kernel protocol level should be
+ * idle and it should be safe to tear down.
+ */
+ CCID_IO_F_DONE = 1 << 2,
+} ccid_io_flags_t;
+
+/*
+ * If any of the flags in the POLLOUT group are set, then the device is not
+ * writeable. The same distinction isn't true for POLLIN. We are only readable
+ * if CCID_IO_F_DONE is set. However, you are allowed to call read as soon as
+ * CCID_IO_F_IN_PROGRESS is set.
+ */
+#define CCID_IO_F_POLLOUT_FLAGS (CCID_IO_F_PREPARING | CCID_IO_F_IN_PROGRESS | \
+ CCID_IO_F_DONE)
+#define CCID_IO_F_ALL_FLAGS (CCID_IO_F_PREPARING | CCID_IO_F_IN_PROGRESS | \
+ CCID_IO_F_DONE | CCID_IO_F_ABANDONED)
+
+typedef struct ccid_io {
+ ccid_io_flags_t ci_flags;
+ size_t ci_ilen;
+ uint8_t ci_ibuf[CCID_APDU_LEN_MAX];
+ mblk_t *ci_omp;
+ kcondvar_t ci_cv;
+ struct ccid_command *ci_command;
+ int ci_errno;
+ mblk_t *ci_data;
+} ccid_io_t;
+
+typedef struct ccid_slot {
+ ccid_minor_idx_t cs_idx; /* WO */
+ uint_t cs_slotno; /* WO */
+ struct ccid *cs_ccid; /* WO */
+ ccid_slot_flags_t cs_flags;
+ ccid_class_voltage_t cs_voltage;
+ mblk_t *cs_atr;
+ struct ccid_command *cs_command;
+ ccid_minor_t *cs_excl_minor;
+ list_t cs_excl_waiters;
+ list_t cs_minors;
+ ccid_icc_t cs_icc;
+ ccid_io_t cs_io;
+} ccid_slot_t;
+
+typedef enum ccid_attach_state {
+ CCID_ATTACH_USB_CLIENT = 1 << 0,
+ CCID_ATTACH_MUTEX_INIT = 1 << 1,
+ CCID_ATTACH_TASKQ = 1 << 2,
+ CCID_ATTACH_CMD_LIST = 1 << 3,
+ CCID_ATTACH_OPEN_PIPES = 1 << 4,
+ CCID_ATTACH_SEQ_IDS = 1 << 5,
+ CCID_ATTACH_SLOTS = 1 << 6,
+ CCID_ATTACH_HOTPLUG_CB = 1 << 7,
+ CCID_ATTACH_INTR_ACTIVE = 1 << 8,
+ CCID_ATTACH_MINORS = 1 << 9,
+} ccid_attach_state_t;
+
+typedef enum ccid_flags {
+ CCID_F_HAS_INTR = 1 << 0,
+ CCID_F_NEEDS_PPS = 1 << 1,
+ CCID_F_NEEDS_PARAMS = 1 << 2,
+ CCID_F_NEEDS_DATAFREQ = 1 << 3,
+ CCID_F_DETACHING = 1 << 5,
+ CCID_F_WORKER_REQUESTED = 1 << 6,
+ CCID_F_WORKER_RUNNING = 1 << 7,
+ CCID_F_DISCONNECTED = 1 << 8
+} ccid_flags_t;
+
+#define CCID_F_DEV_GONE_MASK (CCID_F_DETACHING | CCID_F_DISCONNECTED)
+#define CCID_F_WORKER_MASK (CCID_F_WORKER_REQUESTED | \
+ CCID_F_WORKER_RUNNING)
+
+typedef struct ccid_stats {
+ uint64_t cst_intr_errs;
+ uint64_t cst_intr_restart;
+ uint64_t cst_intr_unknown;
+ uint64_t cst_intr_slot_change;
+ uint64_t cst_intr_hwerr;
+ uint64_t cst_intr_inval;
+ uint64_t cst_ndiscover;
+ hrtime_t cst_lastdiscover;
+} ccid_stats_t;
+
+typedef struct ccid {
+ dev_info_t *ccid_dip;
+ kmutex_t ccid_mutex;
+ ccid_attach_state_t ccid_attach;
+ ccid_flags_t ccid_flags;
+ id_space_t *ccid_seqs;
+ ddi_taskq_t *ccid_taskq;
+ usb_client_dev_data_t *ccid_dev_data;
+ ccid_class_descr_t ccid_class; /* WO */
+ usb_ep_xdescr_t ccid_bulkin_xdesc; /* WO */
+ usb_pipe_handle_t ccid_bulkin_pipe; /* WO */
+ usb_ep_xdescr_t ccid_bulkout_xdesc; /* WO */
+ usb_pipe_handle_t ccid_bulkout_pipe; /* WO */
+ usb_ep_xdescr_t ccid_intrin_xdesc; /* WO */
+ usb_pipe_handle_t ccid_intrin_pipe; /* WO */
+ usb_pipe_handle_t ccid_control_pipe; /* WO */
+ uint_t ccid_nslots; /* WO */
+ size_t ccid_bufsize; /* WO */
+ ccid_slot_t *ccid_slots;
+ timeout_id_t ccid_poll_timeout;
+ ccid_stats_t ccid_stats;
+ list_t ccid_command_queue;
+ list_t ccid_complete_queue;
+ usb_bulk_req_t *ccid_bulkin_cache[CCID_BULK_NALLOCED];
+ uint_t ccid_bulkin_alloced;
+ usb_bulk_req_t *ccid_bulkin_dispatched;
+} ccid_t;
+
+/*
+ * Command structure for an individual CCID command that we issue to a
+ * controller. Note that the command caches a copy of some of the data that's
+ * normally inside the CCID header in host-endian fashion.
+ */
+typedef enum ccid_command_state {
+ CCID_COMMAND_ALLOCATED = 0x0,
+ CCID_COMMAND_QUEUED,
+ CCID_COMMAND_DISPATCHED,
+ CCID_COMMAND_REPLYING,
+ CCID_COMMAND_COMPLETE,
+ CCID_COMMAND_TRANSPORT_ERROR,
+ CCID_COMMAND_CCID_ABORTED
+} ccid_command_state_t;
+
+typedef enum ccid_command_flags {
+ CCID_COMMAND_F_USER = 1 << 0,
+} ccid_command_flags_t;
+
+typedef struct ccid_command {
+ list_node_t cc_list_node;
+ kcondvar_t cc_cv;
+ uint8_t cc_mtype;
+ ccid_response_code_t cc_rtype;
+ uint8_t cc_slot;
+ ccid_command_state_t cc_state;
+ ccid_command_flags_t cc_flags;
+ int cc_usb;
+ usb_cr_t cc_usbcr;
+ size_t cc_reqlen;
+ id_t cc_seq;
+ usb_bulk_req_t *cc_ubrp;
+ ccid_t *cc_ccid;
+ hrtime_t cc_queue_time;
+ hrtime_t cc_dispatch_time;
+ hrtime_t cc_dispatch_cb_time;
+ hrtime_t cc_response_time;
+ hrtime_t cc_completion_time;
+ mblk_t *cc_response;
+} ccid_command_t;
+
+/*
+ * ddi_soft_state(9F) pointer. This is used for instances of a CCID controller.
+ */
+static void *ccid_softstate;
+
+/*
+ * This is used to keep track of our minor nodes.
+ */
+static kmutex_t ccid_idxlock;
+static avl_tree_t ccid_idx;
+static id_space_t *ccid_minors;
+
+/*
+ * Required Forwards
+ */
+static void ccid_intr_poll_init(ccid_t *);
+static void ccid_worker_request(ccid_t *);
+static void ccid_command_dispatch(ccid_t *);
+static void ccid_command_free(ccid_command_t *);
+static int ccid_bulkin_schedule(ccid_t *);
+static void ccid_command_bcopy(ccid_command_t *, const void *, size_t);
+
+static int ccid_write_apdu(ccid_t *, ccid_slot_t *);
+static void ccid_complete_apdu(ccid_t *, ccid_slot_t *, ccid_command_t *);
+static void ccid_teardown_apdu(ccid_t *, ccid_slot_t *, int);
+
+
+static int
+ccid_idx_comparator(const void *l, const void *r)
+{
+ const ccid_minor_idx_t *lc = l, *rc = r;
+
+ if (lc->cmi_minor > rc->cmi_minor)
+ return (1);
+ if (lc->cmi_minor < rc->cmi_minor)
+ return (-1);
+ return (0);
+}
+
+static void
+ccid_error(ccid_t *ccid, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ if (ccid != NULL) {
+ vdev_err(ccid->ccid_dip, CE_WARN, fmt, ap);
+ } else {
+ vcmn_err(CE_WARN, fmt, ap);
+ }
+ va_end(ap);
+}
+
+static void
+ccid_minor_idx_free(ccid_minor_idx_t *idx)
+{
+ ccid_minor_idx_t *ip;
+
+ VERIFY3S(idx->cmi_minor, !=, CCID_MINOR_INVALID);
+ mutex_enter(&ccid_idxlock);
+ ip = avl_find(&ccid_idx, idx, NULL);
+ VERIFY3P(idx, ==, ip);
+ avl_remove(&ccid_idx, idx);
+ id_free(ccid_minors, idx->cmi_minor);
+ idx->cmi_minor = CCID_MINOR_INVALID;
+ mutex_exit(&ccid_idxlock);
+}
+
+static boolean_t
+ccid_minor_idx_alloc(ccid_minor_idx_t *idx, boolean_t sleep)
+{
+ id_t id;
+
+ mutex_enter(&ccid_idxlock);
+ if (sleep) {
+ id = id_alloc(ccid_minors);
+ } else {
+ id = id_alloc_nosleep(ccid_minors);
+ }
+ if (id == -1) {
+ mutex_exit(&ccid_idxlock);
+ return (B_FALSE);
+ }
+ idx->cmi_minor = id;
+ avl_add(&ccid_idx, idx);
+ mutex_exit(&ccid_idxlock);
+
+ return (B_TRUE);
+}
+
+static ccid_minor_idx_t *
+ccid_minor_find(minor_t m)
+{
+ ccid_minor_idx_t i = { 0 };
+ ccid_minor_idx_t *ret;
+
+ i.cmi_minor = m;
+ mutex_enter(&ccid_idxlock);
+ ret = avl_find(&ccid_idx, &i, NULL);
+ mutex_exit(&ccid_idxlock);
+
+ return (ret);
+}
+
+static ccid_minor_idx_t *
+ccid_minor_find_user(minor_t m)
+{
+ ccid_minor_idx_t *idx;
+
+ idx = ccid_minor_find(m);
+ if (idx == NULL) {
+ return (NULL);
+ }
+ VERIFY0(idx->cmi_isslot);
+ return (idx);
+}
+
+static void
+ccid_clear_io(ccid_io_t *io)
+{
+ freemsg(io->ci_data);
+ io->ci_data = NULL;
+ io->ci_errno = 0;
+ io->ci_flags &= ~CCID_IO_F_DONE;
+ io->ci_ilen = 0;
+ bzero(io->ci_ibuf, sizeof (io->ci_ibuf));
+}
+
+/*
+ * Check if the conditions are met to signal the next exclusive holder. For this
+ * to be true, there should be no one holding it. In addition, there must be
+ * someone in the queue waiting. Finally, we want to make sure that the ICC, if
+ * present, is in a state where it could handle these kinds of issues. That
+ * means that we shouldn't have an outstanding I/O question or warm reset
+ * ongoing. However, we must not block this on the condition of an ICC being
+ * present. But, if the reader has been disconnected, don't signal anyone.
+ */
+static void
+ccid_slot_excl_maybe_signal(ccid_slot_t *slot)
+{
+ ccid_minor_t *cmp;
+
+ VERIFY(MUTEX_HELD(&slot->cs_ccid->ccid_mutex));
+
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0)
+ return;
+ if (slot->cs_excl_minor != NULL)
+ return;
+ if ((slot->cs_flags & CCID_SLOT_F_NOEXCL_MASK) != 0)
+ return;
+ cmp = list_head(&slot->cs_excl_waiters);
+ if (cmp == NULL)
+ return;
+ cv_signal(&cmp->cm_excl_cv);
+}
+
+static void
+ccid_slot_excl_rele(ccid_slot_t *slot)
+{
+ ccid_minor_t *cmp;
+ ccid_t *ccid = slot->cs_ccid;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ VERIFY3P(slot->cs_excl_minor, !=, NULL);
+
+ cmp = slot->cs_excl_minor;
+
+ /*
+ * If we have an outstanding command left by the user when they've
+ * closed the slot, we need to clean up this command. We need to call
+ * the protocol specific handler here to determine what to do. If the
+ * command has completed, but the user has never called read, then it
+ * will simply clean it up. Otherwise it will indicate that there is
+ * some amount of external state still ongoing to take care of and clean
+ * up later.
+ */
+ if (slot->cs_icc.icc_teardown != NULL) {
+ slot->cs_icc.icc_teardown(ccid, slot, ECANCELED);
+ }
+
+ /*
+ * There may either be a thread blocked in read or in the process of
+ * preparing a write. In either case, we need to make sure that they're
+ * woken up or finish, before we finish tear down.
+ */
+ while ((cmp->cm_flags & CCID_MINOR_F_READ_WAITING) != 0 ||
+ (slot->cs_io.ci_flags & CCID_IO_F_PREPARING) != 0) {
+ cv_wait(&cmp->cm_iowait_cv, &ccid->ccid_mutex);
+ }
+
+ /*
+ * At this point, we hold the lock and there should be no other threads
+ * that are past the basic sanity checks. So at this point, note that
+ * this minor no longer has exclusive access (causing other read/write
+ * calls to fail) and start the process of cleaning up the outstanding
+ * I/O on the slot. It is OK that at this point the thread may try to
+ * obtain exclusive access again. It will end up blocking on everything
+ * else.
+ */
+ cmp->cm_flags &= ~CCID_MINOR_F_HAS_EXCL;
+ slot->cs_excl_minor = NULL;
+
+ /*
+ * If at this point, we have an I/O that's noted as being done, but no
+ * one blocked in read, then we need to clean that up. The ICC teardown
+ * function is only designed to take care of in-flight I/Os.
+ */
+ if ((slot->cs_io.ci_flags & CCID_IO_F_DONE) != 0)
+ ccid_clear_io(&slot->cs_io);
+
+ /*
+ * Regardless of when we're polling, we need to go through and error
+ * out.
+ */
+ pollwakeup(&cmp->cm_pollhead, POLLERR);
+
+ /*
+ * If we've been asked to reset the card before handing it off, schedule
+ * that. Otherwise, allow the next entry in the queue to get woken up
+ * and given access to the card.
+ */
+ if ((cmp->cm_flags & CCID_MINOR_F_TXN_RESET) != 0) {
+ slot->cs_flags |= CCID_SLOT_F_NEED_TXN_RESET;
+ ccid_worker_request(ccid);
+ cmp->cm_flags &= ~CCID_MINOR_F_TXN_RESET;
+ } else {
+ ccid_slot_excl_maybe_signal(slot);
+ }
+}
+
+static int
+ccid_slot_excl_req(ccid_slot_t *slot, ccid_minor_t *cmp, boolean_t nosleep)
+{
+ VERIFY(MUTEX_HELD(&slot->cs_ccid->ccid_mutex));
+
+ if (slot->cs_excl_minor == cmp) {
+ VERIFY((cmp->cm_flags & CCID_MINOR_F_HAS_EXCL) != 0);
+ return (EEXIST);
+ }
+
+ if ((cmp->cm_flags & CCID_MINOR_F_WAITING) != 0) {
+ return (EINPROGRESS);
+ }
+
+ /*
+ * If we were asked to try and fail quickly, do that before the main
+ * loop.
+ */
+ if (nosleep && slot->cs_excl_minor != NULL &&
+ (slot->cs_flags & CCID_SLOT_F_NOEXCL_MASK) == 0) {
+ return (EBUSY);
+ }
+
+ /*
+ * Mark that we're waiting in case we race with another thread trying to
+ * claim exclusive access for this. Insert ourselves on the wait list.
+ * If for some reason we get a signal, then we can't know for certain if
+ * we had a signal / cv race. In such a case, we always wake up the
+ * next person in the queue (potentially spuriously).
+ */
+ cmp->cm_flags |= CCID_MINOR_F_WAITING;
+ list_insert_tail(&slot->cs_excl_waiters, cmp);
+ while (slot->cs_excl_minor != NULL ||
+ (slot->cs_flags & CCID_SLOT_F_NOEXCL_MASK) != 0) {
+ if (cv_wait_sig(&cmp->cm_excl_cv, &slot->cs_ccid->ccid_mutex) ==
+ 0) {
+ /*
+ * Remove ourselves from the list and try to signal the
+ * next thread.
+ */
+ list_remove(&slot->cs_excl_waiters, cmp);
+ cmp->cm_flags &= ~CCID_MINOR_F_WAITING;
+ ccid_slot_excl_maybe_signal(slot);
+ return (EINTR);
+ }
+
+ /*
+ * Check if the reader is going away. If so, then we're done
+ * here.
+ */
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ list_remove(&slot->cs_excl_waiters, cmp);
+ cmp->cm_flags &= ~CCID_MINOR_F_WAITING;
+ return (ENODEV);
+ }
+ }
+
+ VERIFY0(slot->cs_flags & CCID_SLOT_F_NOEXCL_MASK);
+ list_remove(&slot->cs_excl_waiters, cmp);
+
+ cmp->cm_flags &= ~CCID_MINOR_F_WAITING;
+ cmp->cm_flags |= CCID_MINOR_F_HAS_EXCL;
+ slot->cs_excl_minor = cmp;
+ return (0);
+}
+
+/*
+ * Check whether or not we're in a state that we can signal a POLLIN. To be able
+ * to signal a POLLIN (meaning that we can read) the following must be true:
+ *
+ * o There is a client that has an exclusive hold open
+ * o There is a data which is readable by the client (an I/O is done).
+ *
+ * Unlike with pollout, we don't care about the state of the ICC.
+ */
+static void
+ccid_slot_pollin_signal(ccid_slot_t *slot)
+{
+ ccid_t *ccid = slot->cs_ccid;
+ ccid_minor_t *cmp = slot->cs_excl_minor;
+
+ if (cmp == NULL)
+ return;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ if ((slot->cs_io.ci_flags & CCID_IO_F_DONE) == 0)
+ return;
+
+ pollwakeup(&cmp->cm_pollhead, POLLIN | POLLRDNORM);
+}
+
+/*
+ * Check whether or not we're in a state that we can signal a POLLOUT. To be
+ * able to signal a POLLOUT (meaning that we can write) the following must be
+ * true:
+ *
+ * o There is a minor which has an exclusive hold on the device
+ * o There is no outstanding I/O activity going on, meaning that there is no
+ * operation in progress and any write data has been consumed.
+ * o There is an ICC present
+ * o There is no outstanding I/O cleanup being done, whether a T=1 abort, a
+ * warm reset, or something else.
+ */
+static void
+ccid_slot_pollout_signal(ccid_slot_t *slot)
+{
+ ccid_t *ccid = slot->cs_ccid;
+ ccid_minor_t *cmp = slot->cs_excl_minor;
+
+ if (cmp == NULL)
+ return;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ if ((slot->cs_io.ci_flags & CCID_IO_F_POLLOUT_FLAGS) != 0 ||
+ (slot->cs_flags & CCID_SLOT_F_ACTIVE) == 0 ||
+ (ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0 ||
+ (slot->cs_flags & CCID_SLOT_F_NEED_IO_TEARDOWN) != 0)
+ return;
+
+ pollwakeup(&cmp->cm_pollhead, POLLOUT);
+}
+
+static void
+ccid_slot_io_teardown_done(ccid_slot_t *slot)
+{
+ ccid_t *ccid = slot->cs_ccid;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ slot->cs_flags &= ~CCID_SLOT_F_NEED_IO_TEARDOWN;
+ cv_broadcast(&slot->cs_io.ci_cv);
+
+ ccid_slot_pollout_signal(slot);
+}
+
+/*
+ * This will probably need to change when we start doing TPDU processing.
+ */
+static size_t
+ccid_command_resp_length(ccid_command_t *cc)
+{
+ uint32_t len;
+ const ccid_header_t *cch;
+
+ VERIFY3P(cc, !=, NULL);
+ VERIFY3P(cc->cc_response, !=, NULL);
+
+ /*
+ * Fetch out an arbitrarily aligned LE uint32_t value from the header.
+ */
+ cch = (ccid_header_t *)cc->cc_response->b_rptr;
+ bcopy(&cch->ch_length, &len, sizeof (len));
+ len = LE_32(len);
+ return (len);
+}
+
+static uint8_t
+ccid_command_resp_param2(ccid_command_t *cc)
+{
+ const ccid_header_t *cch;
+ uint8_t val;
+
+ VERIFY3P(cc, !=, NULL);
+ VERIFY3P(cc->cc_response, !=, NULL);
+
+ cch = (ccid_header_t *)cc->cc_response->b_rptr;
+ bcopy(&cch->ch_param2, &val, sizeof (val));
+ return (val);
+}
+
+/*
+ * Complete a single command. The way that a command completes depends on the
+ * kind of command that occurs. If this command is flagged as a user command,
+ * that implies that it must be handled in a different way from administrative
+ * commands. User commands are placed into the minor to consume via a read(9E).
+ * Non-user commands are placed into a completion queue and must be picked up
+ * via the ccid_command_poll() interface.
+ */
+static void
+ccid_command_complete(ccid_command_t *cc)
+{
+ ccid_t *ccid = cc->cc_ccid;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ cc->cc_completion_time = gethrtime();
+ list_remove(&ccid->ccid_command_queue, cc);
+
+ if (cc->cc_flags & CCID_COMMAND_F_USER) {
+ ccid_slot_t *slot;
+
+ slot = &ccid->ccid_slots[cc->cc_slot];
+ ASSERT3P(slot->cs_icc.icc_complete, !=, NULL);
+ slot->cs_icc.icc_complete(ccid, slot, cc);
+ } else {
+ list_insert_tail(&ccid->ccid_complete_queue, cc);
+ cv_broadcast(&cc->cc_cv);
+ }
+
+ /*
+ * Finally, we also need to kick off the next command.
+ */
+ ccid_command_dispatch(ccid);
+}
+
+static void
+ccid_command_state_transition(ccid_command_t *cc, ccid_command_state_t state)
+{
+ VERIFY(MUTEX_HELD(&cc->cc_ccid->ccid_mutex));
+
+ cc->cc_state = state;
+ cv_broadcast(&cc->cc_cv);
+}
+
+static void
+ccid_command_transport_error(ccid_command_t *cc, int usb_status, usb_cr_t cr)
+{
+ VERIFY(MUTEX_HELD(&cc->cc_ccid->ccid_mutex));
+
+ ccid_command_state_transition(cc, CCID_COMMAND_TRANSPORT_ERROR);
+ cc->cc_usb = usb_status;
+ cc->cc_usbcr = cr;
+ cc->cc_response = NULL;
+
+ ccid_command_complete(cc);
+}
+
+static void
+ccid_command_status_decode(ccid_command_t *cc,
+ ccid_reply_command_status_t *comp, ccid_reply_icc_status_t *iccp,
+ ccid_command_err_t *errp)
+{
+ ccid_header_t cch;
+ size_t mblen;
+
+ VERIFY3S(cc->cc_state, ==, CCID_COMMAND_COMPLETE);
+ VERIFY3P(cc->cc_response, !=, NULL);
+ mblen = msgsize(cc->cc_response);
+ VERIFY3U(mblen, >=, sizeof (cch));
+
+ bcopy(cc->cc_response->b_rptr, &cch, sizeof (cch));
+ if (comp != NULL) {
+ *comp = CCID_REPLY_STATUS(cch.ch_param0);
+ }
+
+ if (iccp != NULL) {
+ *iccp = CCID_REPLY_ICC(cch.ch_param0);
+ }
+
+ if (errp != NULL) {
+ *errp = cch.ch_param1;
+ }
+}
+
+static void
+ccid_reply_bulk_cb(usb_pipe_handle_t ph, usb_bulk_req_t *ubrp)
+{
+ size_t mlen;
+ ccid_t *ccid;
+ ccid_slot_t *slot;
+ ccid_header_t cch;
+ ccid_command_t *cc;
+
+ boolean_t header_valid = B_FALSE;
+
+ VERIFY(ubrp->bulk_data != NULL);
+ mlen = msgsize(ubrp->bulk_data);
+ ccid = (ccid_t *)ubrp->bulk_client_private;
+ mutex_enter(&ccid->ccid_mutex);
+
+ /*
+ * Before we do anything else, we should mark that this Bulk-IN request
+ * is no longer being dispatched.
+ */
+ VERIFY3P(ubrp, ==, ccid->ccid_bulkin_dispatched);
+ ccid->ccid_bulkin_dispatched = NULL;
+
+ if ((cc = list_head(&ccid->ccid_command_queue)) == NULL) {
+ /*
+ * This is certainly an odd case. This means that we got some
+ * response but there are no entries in the queue. Go ahead and
+ * free this. We're done here.
+ */
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+ return;
+ }
+
+ if (mlen >= sizeof (ccid_header_t)) {
+ bcopy(ubrp->bulk_data->b_rptr, &cch, sizeof (cch));
+ header_valid = B_TRUE;
+ }
+
+ /*
+ * If the current command isn't in the replying state, then something is
+ * clearly wrong and this probably isn't intended for the current
+ * command. That said, if we have enough bytes, let's check the sequence
+ * number as that might be indicative of a bug otherwise.
+ */
+ if (cc->cc_state != CCID_COMMAND_REPLYING) {
+ if (header_valid) {
+ VERIFY3S(cch.ch_seq, !=, cc->cc_seq);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+ return;
+ }
+
+ /*
+ * CCID section 6.2.7 says that if we get a short or zero length packet,
+ * then we need to treat that as though the running command was aborted
+ * for some reason. However, section 3.1.3 talks about sending zero
+ * length packets on general principle. To further complicate things,
+ * we don't have the sequence number.
+ *
+ * If we have an outstanding command still, then we opt to treat the
+ * zero length packet as an abort.
+ */
+ if (!header_valid) {
+ ccid_command_state_transition(cc, CCID_COMMAND_CCID_ABORTED);
+ ccid_command_complete(cc);
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+ return;
+ }
+
+ slot = &ccid->ccid_slots[cc->cc_slot];
+
+ /*
+ * If the sequence or slot number don't match the head of the list or
+ * the response type is unexpected for this command then we should be
+ * very suspect of the hardware at this point. At a minimum we should
+ * fail this command and issue a reset.
+ */
+ if (cch.ch_seq != cc->cc_seq ||
+ cch.ch_slot != cc->cc_slot ||
+ cch.ch_mtype != cc->cc_rtype) {
+ ccid_command_state_transition(cc, CCID_COMMAND_CCID_ABORTED);
+ ccid_command_complete(cc);
+ slot->cs_flags |= CCID_SLOT_F_NEED_TXN_RESET;
+ ccid_worker_request(ccid);
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+ return;
+ }
+
+ /*
+ * Check that we have all the bytes that we were told we'd have. If we
+ * don't, simulate this as an aborted command and issue a reset.
+ */
+ if (LE_32(cch.ch_length) + sizeof (ccid_header_t) > mlen) {
+ ccid_command_state_transition(cc, CCID_COMMAND_CCID_ABORTED);
+ ccid_command_complete(cc);
+ slot->cs_flags |= CCID_SLOT_F_NEED_TXN_RESET;
+ ccid_worker_request(ccid);
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+ return;
+ }
+
+ /*
+ * This response is for us. Before we complete the command check to see
+ * what the state of the command is. If the command indicates that more
+ * time has been requested, then we need to schedule a new Bulk-IN
+ * request.
+ */
+ if (CCID_REPLY_STATUS(cch.ch_param0) == CCID_REPLY_STATUS_MORE_TIME) {
+ int ret;
+
+ ret = ccid_bulkin_schedule(ccid);
+ if (ret != USB_SUCCESS) {
+ ccid_command_transport_error(cc, ret, USB_CR_OK);
+ slot->cs_flags |= CCID_SLOT_F_NEED_TXN_RESET;
+ ccid_worker_request(ccid);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+ return;
+ }
+
+ /*
+ * Take the message block from the Bulk-IN request and store it on the
+ * command. We want this regardless if it succeeded, failed, or we have
+ * some unexpected status value.
+ */
+ cc->cc_response = ubrp->bulk_data;
+ ubrp->bulk_data = NULL;
+ ccid_command_state_transition(cc, CCID_COMMAND_COMPLETE);
+ ccid_command_complete(cc);
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+}
+
+static void
+ccid_reply_bulk_exc_cb(usb_pipe_handle_t ph, usb_bulk_req_t *ubrp)
+{
+ ccid_t *ccid;
+ ccid_command_t *cc;
+
+ ccid = (ccid_t *)ubrp->bulk_client_private;
+ mutex_enter(&ccid->ccid_mutex);
+
+ /*
+ * Before we do anything else, we should mark that this Bulk-IN request
+ * is no longer being dispatched.
+ */
+ VERIFY3P(ubrp, ==, ccid->ccid_bulkin_dispatched);
+ ccid->ccid_bulkin_dispatched = NULL;
+
+ /*
+ * While there are many different reasons that the Bulk-IN request could
+ * have failed, each of these are treated as a transport error. If we
+ * have a dispatched command, then we treat this as corresponding to
+ * that command. Otherwise, we drop this.
+ */
+ if ((cc = list_head(&ccid->ccid_command_queue)) != NULL) {
+ if (cc->cc_state == CCID_COMMAND_REPLYING) {
+ ccid_command_transport_error(cc, USB_SUCCESS,
+ ubrp->bulk_completion_reason);
+ }
+ }
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_bulk_req(ubrp);
+}
+
+/*
+ * Fill the Bulk-IN cache. If we do not entirely fill this, that's fine. If
+ * there are no scheduled resources then we'll deal with that when we actually
+ * get there.
+ */
+static void
+ccid_bulkin_cache_refresh(ccid_t *ccid)
+{
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ while (ccid->ccid_bulkin_alloced < CCID_BULK_NALLOCED) {
+ usb_bulk_req_t *ubrp;
+
+ if ((ubrp = usb_alloc_bulk_req(ccid->ccid_dip,
+ ccid->ccid_bufsize, 0)) == NULL)
+ return;
+
+ ubrp->bulk_len = ccid->ccid_bufsize;
+ ubrp->bulk_timeout = CCID_BULK_IN_TIMEOUT;
+ ubrp->bulk_client_private = (usb_opaque_t)ccid;
+ ubrp->bulk_attributes = USB_ATTRS_SHORT_XFER_OK |
+ USB_ATTRS_AUTOCLEARING;
+ ubrp->bulk_cb = ccid_reply_bulk_cb;
+ ubrp->bulk_exc_cb = ccid_reply_bulk_exc_cb;
+
+ ccid->ccid_bulkin_cache[ccid->ccid_bulkin_alloced] = ubrp;
+ ccid->ccid_bulkin_alloced++;
+ }
+
+}
+
+static usb_bulk_req_t *
+ccid_bulkin_cache_get(ccid_t *ccid)
+{
+ usb_bulk_req_t *ubrp;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ if (ccid->ccid_bulkin_alloced == 0) {
+ ccid_bulkin_cache_refresh(ccid);
+ if (ccid->ccid_bulkin_alloced == 0)
+ return (NULL);
+ }
+
+ ccid->ccid_bulkin_alloced--;
+ ubrp = ccid->ccid_bulkin_cache[ccid->ccid_bulkin_alloced];
+ VERIFY3P(ubrp, !=, NULL);
+ ccid->ccid_bulkin_cache[ccid->ccid_bulkin_alloced] = NULL;
+
+ return (ubrp);
+}
+
+/*
+ * Attempt to schedule a Bulk-In request. Note that only one should ever be
+ * scheduled at any time.
+ */
+static int
+ccid_bulkin_schedule(ccid_t *ccid)
+{
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ if (ccid->ccid_bulkin_dispatched == NULL) {
+ usb_bulk_req_t *ubrp;
+ int ret;
+
+ ubrp = ccid_bulkin_cache_get(ccid);
+ if (ubrp == NULL) {
+ return (USB_NO_RESOURCES);
+ }
+
+ if ((ret = usb_pipe_bulk_xfer(ccid->ccid_bulkin_pipe, ubrp,
+ 0)) != USB_SUCCESS) {
+ ccid_error(ccid,
+ "!failed to schedule Bulk-In response: %d", ret);
+ usb_free_bulk_req(ubrp);
+ return (ret);
+ }
+
+ ccid->ccid_bulkin_dispatched = ubrp;
+ }
+
+ return (USB_SUCCESS);
+}
+
+/*
+ * Make sure that the head of the queue has been dispatched. If a dispatch to
+ * the device fails, fail the command and try the next one.
+ */
+static void
+ccid_command_dispatch(ccid_t *ccid)
+{
+ ccid_command_t *cc;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ while ((cc = list_head(&ccid->ccid_command_queue)) != NULL) {
+ int ret;
+
+ if ((ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0)
+ return;
+
+ /*
+ * Head of the queue is already being processed. We're done
+ * here.
+ */
+ if (cc->cc_state > CCID_COMMAND_QUEUED) {
+ return;
+ }
+
+ /*
+ * Mark the command as being dispatched to the device. This
+ * prevents anyone else from getting in and confusing things.
+ */
+ ccid_command_state_transition(cc, CCID_COMMAND_DISPATCHED);
+ cc->cc_dispatch_time = gethrtime();
+
+ /*
+ * Drop the global lock while we schedule the USB I/O.
+ */
+ mutex_exit(&ccid->ccid_mutex);
+
+ ret = usb_pipe_bulk_xfer(ccid->ccid_bulkout_pipe, cc->cc_ubrp,
+ 0);
+ mutex_enter(&ccid->ccid_mutex);
+ if (ret != USB_SUCCESS) {
+ /*
+ * We don't need to free the usb_bulk_req_t here as it
+ * will be taken care of when the command itself is
+ * freed.
+ */
+ ccid_error(ccid, "!Bulk pipe dispatch failed: %d\n",
+ ret);
+ ccid_command_transport_error(cc, ret, USB_CR_OK);
+ }
+ }
+}
+
+static int
+ccid_command_queue(ccid_t *ccid, ccid_command_t *cc)
+{
+ id_t seq;
+ ccid_header_t *cchead;
+
+ seq = id_alloc_nosleep(ccid->ccid_seqs);
+ if (seq == -1)
+ return (ENOMEM);
+ cc->cc_seq = seq;
+ VERIFY3U(seq, <=, UINT8_MAX);
+ cchead = (void *)cc->cc_ubrp->bulk_data->b_rptr;
+ cchead->ch_seq = (uint8_t)seq;
+
+ mutex_enter(&ccid->ccid_mutex);
+ /*
+ * Take a shot at filling up our reply cache while we're submitting this
+ * command.
+ */
+ ccid_bulkin_cache_refresh(ccid);
+ list_insert_tail(&ccid->ccid_command_queue, cc);
+ ccid_command_state_transition(cc, CCID_COMMAND_QUEUED);
+ cc->cc_queue_time = gethrtime();
+ ccid_command_dispatch(ccid);
+ mutex_exit(&ccid->ccid_mutex);
+
+ return (0);
+}
+
+/*
+ * Normal callback for Bulk-Out requests which represents commands issued to the
+ * device.
+ */
+static void
+ccid_dispatch_bulk_cb(usb_pipe_handle_t ph, usb_bulk_req_t *ubrp)
+{
+ int ret;
+ ccid_command_t *cc = (void *)ubrp->bulk_client_private;
+ ccid_t *ccid = cc->cc_ccid;
+
+ mutex_enter(&ccid->ccid_mutex);
+ VERIFY3S(cc->cc_state, ==, CCID_COMMAND_DISPATCHED);
+ ccid_command_state_transition(cc, CCID_COMMAND_REPLYING);
+ cc->cc_dispatch_cb_time = gethrtime();
+
+ /*
+ * Since we have successfully sent the command, give it a Bulk-In
+ * response to reply to us with. If that fails, we'll note a transport
+ * error which will kick off the next command if needed.
+ */
+ ret = ccid_bulkin_schedule(ccid);
+ if (ret != USB_SUCCESS) {
+ ccid_command_transport_error(cc, ret, USB_CR_OK);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+}
+
+/*
+ * Exception callback for the Bulk-Out requests which represent commands issued
+ * to the device.
+ */
+static void
+ccid_dispatch_bulk_exc_cb(usb_pipe_handle_t ph, usb_bulk_req_t *ubrp)
+{
+ ccid_command_t *cc = (void *)ubrp->bulk_client_private;
+ ccid_t *ccid = cc->cc_ccid;
+
+ mutex_enter(&ccid->ccid_mutex);
+ ccid_command_transport_error(cc, USB_SUCCESS,
+ ubrp->bulk_completion_reason);
+ mutex_exit(&ccid->ccid_mutex);
+}
+
+static void
+ccid_command_free(ccid_command_t *cc)
+{
+ VERIFY0(list_link_active(&cc->cc_list_node));
+ VERIFY(cc->cc_state == CCID_COMMAND_ALLOCATED ||
+ cc->cc_state >= CCID_COMMAND_COMPLETE);
+
+ if (cc->cc_response != NULL) {
+ freemsgchain(cc->cc_response);
+ cc->cc_response = NULL;
+ }
+
+ if (cc->cc_ubrp != NULL) {
+ usb_free_bulk_req(cc->cc_ubrp);
+ cc->cc_ubrp = NULL;
+ }
+
+ if (cc->cc_seq != 0) {
+ id_free(cc->cc_ccid->ccid_seqs, cc->cc_seq);
+ cc->cc_seq = 0;
+ }
+
+ cv_destroy(&cc->cc_cv);
+ kmem_free(cc, sizeof (ccid_command_t));
+}
+
+/*
+ * Copy len bytes of data from buf into the allocated message block.
+ */
+static void
+ccid_command_bcopy(ccid_command_t *cc, const void *buf, size_t len)
+{
+ size_t mlen;
+
+ mlen = msgsize(cc->cc_ubrp->bulk_data);
+ VERIFY3U(mlen + len, >=, len);
+ VERIFY3U(mlen + len, >=, mlen);
+ mlen += len;
+ VERIFY3U(mlen, <=, cc->cc_ubrp->bulk_len);
+
+ bcopy(buf, cc->cc_ubrp->bulk_data->b_wptr, len);
+ cc->cc_ubrp->bulk_data->b_wptr += len;
+}
+
+/*
+ * Allocate a command of a specific size and parameters. This will allocate a
+ * USB bulk transfer that the caller will copy data to.
+ */
+static int
+ccid_command_alloc(ccid_t *ccid, ccid_slot_t *slot, boolean_t block,
+ mblk_t *datamp, size_t datasz, uint8_t mtype, uint8_t param0,
+ uint8_t param1, uint8_t param2, ccid_command_t **ccp)
+{
+ size_t allocsz;
+ int kmflag, usbflag;
+ ccid_command_t *cc;
+ ccid_header_t *cchead;
+ ccid_response_code_t rtype;
+
+ switch (mtype) {
+ case CCID_REQUEST_POWER_ON:
+ case CCID_REQUEST_POWER_OFF:
+ case CCID_REQUEST_SLOT_STATUS:
+ case CCID_REQUEST_GET_PARAMS:
+ case CCID_REQUEST_RESET_PARAMS:
+ case CCID_REQUEST_ICC_CLOCK:
+ case CCID_REQUEST_T0APDU:
+ case CCID_REQUEST_MECHANICAL:
+ case CCID_REQEUST_ABORT:
+ if (datasz != 0)
+ return (EINVAL);
+ break;
+ case CCID_REQUEST_TRANSFER_BLOCK:
+ case CCID_REQUEST_ESCAPE:
+ case CCID_REQUEST_SECURE:
+ case CCID_REQUEST_SET_PARAMS:
+ case CCID_REQUEST_DATA_CLOCK:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ switch (mtype) {
+ case CCID_REQUEST_POWER_ON:
+ case CCID_REQUEST_SECURE:
+ case CCID_REQUEST_TRANSFER_BLOCK:
+ rtype = CCID_RESPONSE_DATA_BLOCK;
+ break;
+
+ case CCID_REQUEST_POWER_OFF:
+ case CCID_REQUEST_SLOT_STATUS:
+ case CCID_REQUEST_ICC_CLOCK:
+ case CCID_REQUEST_T0APDU:
+ case CCID_REQUEST_MECHANICAL:
+ case CCID_REQEUST_ABORT:
+ rtype = CCID_RESPONSE_SLOT_STATUS;
+ break;
+
+ case CCID_REQUEST_GET_PARAMS:
+ case CCID_REQUEST_RESET_PARAMS:
+ case CCID_REQUEST_SET_PARAMS:
+ rtype = CCID_RESPONSE_PARAMETERS;
+ break;
+
+ case CCID_REQUEST_ESCAPE:
+ rtype = CCID_RESPONSE_ESCAPE;
+ break;
+
+ case CCID_REQUEST_DATA_CLOCK:
+ rtype = CCID_RESPONSE_DATA_CLOCK;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if (block) {
+ kmflag = KM_SLEEP;
+ usbflag = USB_FLAGS_SLEEP;
+ } else {
+ kmflag = KM_NOSLEEP | KM_NORMALPRI;
+ usbflag = 0;
+ }
+
+ if (datasz + sizeof (ccid_header_t) < datasz)
+ return (EINVAL);
+ if (datasz + sizeof (ccid_header_t) > ccid->ccid_bufsize)
+ return (EINVAL);
+
+ cc = kmem_zalloc(sizeof (ccid_command_t), kmflag);
+ if (cc == NULL)
+ return (ENOMEM);
+
+ allocsz = datasz + sizeof (ccid_header_t);
+ if (datamp == NULL) {
+ cc->cc_ubrp = usb_alloc_bulk_req(ccid->ccid_dip, allocsz,
+ usbflag);
+ } else {
+ cc->cc_ubrp = usb_alloc_bulk_req(ccid->ccid_dip, 0, usbflag);
+ }
+ if (cc->cc_ubrp == NULL) {
+ kmem_free(cc, sizeof (ccid_command_t));
+ return (ENOMEM);
+ }
+
+ list_link_init(&cc->cc_list_node);
+ cv_init(&cc->cc_cv, NULL, CV_DRIVER, NULL);
+ cc->cc_mtype = mtype;
+ cc->cc_rtype = rtype;
+ cc->cc_slot = slot->cs_slotno;
+ cc->cc_reqlen = datasz;
+ cc->cc_ccid = ccid;
+ cc->cc_state = CCID_COMMAND_ALLOCATED;
+
+ /*
+ * Fill in bulk request attributes. Note that short transfers out
+ * are not OK.
+ */
+ if (datamp != NULL) {
+ cc->cc_ubrp->bulk_data = datamp;
+ }
+ cc->cc_ubrp->bulk_len = allocsz;
+ cc->cc_ubrp->bulk_timeout = CCID_BULK_OUT_TIMEOUT;
+ cc->cc_ubrp->bulk_client_private = (usb_opaque_t)cc;
+ cc->cc_ubrp->bulk_attributes = USB_ATTRS_AUTOCLEARING;
+ cc->cc_ubrp->bulk_cb = ccid_dispatch_bulk_cb;
+ cc->cc_ubrp->bulk_exc_cb = ccid_dispatch_bulk_exc_cb;
+
+ /*
+ * Fill in the command header. We fill in everything except the sequence
+ * number, which is done by the actual dispatch code.
+ */
+ cchead = (void *)cc->cc_ubrp->bulk_data->b_rptr;
+ cchead->ch_mtype = mtype;
+ cchead->ch_length = LE_32(datasz);
+ cchead->ch_slot = slot->cs_slotno;
+ cchead->ch_seq = 0;
+ cchead->ch_param0 = param0;
+ cchead->ch_param1 = param1;
+ cchead->ch_param2 = param2;
+ cc->cc_ubrp->bulk_data->b_wptr += sizeof (ccid_header_t);
+ *ccp = cc;
+
+ return (0);
+}
+
+/*
+ * The rest of the stack is in charge of timing out commands and potentially
+ * aborting them. At this point in time, there's no specific timeout aspect
+ * here.
+ */
+static void
+ccid_command_poll(ccid_t *ccid, ccid_command_t *cc)
+{
+ VERIFY0(cc->cc_flags & CCID_COMMAND_F_USER);
+
+ mutex_enter(&ccid->ccid_mutex);
+ while ((cc->cc_state < CCID_COMMAND_COMPLETE) &&
+ (ccid->ccid_flags & CCID_F_DEV_GONE_MASK) == 0) {
+ cv_wait(&cc->cc_cv, &ccid->ccid_mutex);
+ }
+
+ /*
+ * Treat this as a consumption and remove it from the completion list.
+ */
+#ifdef DEBUG
+ ccid_command_t *check;
+ for (check = list_head(&ccid->ccid_complete_queue); check != NULL;
+ check = list_next(&ccid->ccid_complete_queue, check)) {
+ if (cc == check)
+ break;
+ }
+ ASSERT3P(check, !=, NULL);
+#endif
+ VERIFY(list_link_active(&cc->cc_list_node));
+ list_remove(&ccid->ccid_complete_queue, cc);
+ mutex_exit(&ccid->ccid_mutex);
+}
+
+static int
+ccid_command_power_off(ccid_t *ccid, ccid_slot_t *cs)
+{
+ int ret;
+ ccid_command_t *cc;
+ ccid_reply_icc_status_t cis;
+ ccid_reply_command_status_t crs;
+
+ if ((ret = ccid_command_alloc(ccid, cs, B_TRUE, NULL, 0,
+ CCID_REQUEST_POWER_OFF, 0, 0, 0, &cc)) != 0) {
+ return (ret);
+ }
+
+ if ((ret = ccid_command_queue(ccid, cc)) != 0) {
+ ccid_command_free(cc);
+ return (ret);
+ }
+
+ ccid_command_poll(ccid, cc);
+
+ if (cc->cc_state != CCID_COMMAND_COMPLETE) {
+ ret = EIO;
+ goto done;
+ }
+
+ ccid_command_status_decode(cc, &crs, &cis, NULL);
+ if (crs == CCID_REPLY_STATUS_FAILED) {
+ if (cis == CCID_REPLY_ICC_MISSING) {
+ ret = ENXIO;
+ } else {
+ ret = EIO;
+ }
+ } else {
+ ret = 0;
+ }
+done:
+ ccid_command_free(cc);
+ return (ret);
+}
+
+static int
+ccid_command_power_on(ccid_t *ccid, ccid_slot_t *cs, ccid_class_voltage_t volt,
+ mblk_t **atrp)
+{
+ int ret;
+ ccid_command_t *cc;
+ ccid_reply_command_status_t crs;
+ ccid_reply_icc_status_t cis;
+ ccid_command_err_t cce;
+
+ if (atrp == NULL)
+ return (EINVAL);
+
+ *atrp = NULL;
+
+ switch (volt) {
+ case CCID_CLASS_VOLT_AUTO:
+ case CCID_CLASS_VOLT_5_0:
+ case CCID_CLASS_VOLT_3_0:
+ case CCID_CLASS_VOLT_1_8:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ if ((ret = ccid_command_alloc(ccid, cs, B_TRUE, NULL, 0,
+ CCID_REQUEST_POWER_ON, volt, 0, 0, &cc)) != 0) {
+ return (ret);
+ }
+
+ if ((ret = ccid_command_queue(ccid, cc)) != 0) {
+ ccid_command_free(cc);
+ return (ret);
+ }
+
+ ccid_command_poll(ccid, cc);
+
+ if (cc->cc_state != CCID_COMMAND_COMPLETE) {
+ ret = EIO;
+ goto done;
+ }
+
+ /*
+ * Look for a few specific errors here:
+ *
+ * - ICC_MUTE via a few potential ways
+ * - Bad voltage
+ */
+ ccid_command_status_decode(cc, &crs, &cis, &cce);
+ if (crs == CCID_REPLY_STATUS_FAILED) {
+ if (cis == CCID_REPLY_ICC_MISSING) {
+ ret = ENXIO;
+ } else if (cis == CCID_REPLY_ICC_INACTIVE &&
+ cce == 7) {
+ /*
+ * This means that byte 7 was invalid. In other words,
+ * that the voltage wasn't correct. See Table 6.1-2
+ * 'Errors' in the CCID r1.1.0 spec.
+ */
+ ret = ENOTSUP;
+ } else {
+ ret = EIO;
+ }
+ } else {
+ size_t len;
+
+ len = ccid_command_resp_length(cc);
+ if (len == 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+#ifdef DEBUG
+ /*
+ * This should have already been checked by the response
+ * framework, but sanity check this again.
+ */
+ size_t mlen = msgsize(cc->cc_response);
+ VERIFY3U(mlen, >=, len + sizeof (ccid_header_t));
+#endif
+
+ /*
+ * Munge the message block to have the ATR. We want to make sure
+ * that the write pointer is set to the maximum length that we
+ * got back from the driver (the message block could strictly
+ * speaking be larger, because we got a larger transfer for some
+ * reason).
+ */
+ cc->cc_response->b_rptr += sizeof (ccid_header_t);
+ cc->cc_response->b_wptr = cc->cc_response->b_rptr + len;
+ *atrp = cc->cc_response;
+ cc->cc_response = NULL;
+ ret = 0;
+ }
+
+done:
+ ccid_command_free(cc);
+ return (ret);
+}
+
+static int
+ccid_command_get_parameters(ccid_t *ccid, ccid_slot_t *slot,
+ atr_protocol_t *protp, ccid_params_t *paramsp)
+{
+ int ret;
+ uint8_t prot;
+ size_t mlen;
+ ccid_command_t *cc;
+ ccid_reply_command_status_t crs;
+ ccid_reply_icc_status_t cis;
+ const void *cpbuf;
+
+ if ((ret = ccid_command_alloc(ccid, slot, B_TRUE, NULL, 0,
+ CCID_REQUEST_GET_PARAMS, 0, 0, 0, &cc)) != 0) {
+ return (ret);
+ }
+
+ if ((ret = ccid_command_queue(ccid, cc)) != 0)
+ goto done;
+
+ ccid_command_poll(ccid, cc);
+
+ if (cc->cc_state != CCID_COMMAND_COMPLETE) {
+ ret = EIO;
+ goto done;
+ }
+
+ ccid_command_status_decode(cc, &crs, &cis, NULL);
+ if (crs != CCID_REPLY_STATUS_COMPLETE) {
+ if (cis == CCID_REPLY_ICC_MISSING) {
+ ret = ENXIO;
+ } else {
+ ret = EIO;
+ }
+ goto done;
+ }
+
+ /*
+ * The protocol is in ch_param2 of the header.
+ */
+ prot = ccid_command_resp_param2(cc);
+ mlen = ccid_command_resp_length(cc);
+ cpbuf = cc->cc_response->b_rptr + sizeof (ccid_header_t);
+
+ ret = 0;
+ switch (prot) {
+ case 0:
+ if (mlen < sizeof (ccid_params_t0_t)) {
+ ret = EOVERFLOW;
+ goto done;
+ }
+ *protp = ATR_P_T0;
+ bcopy(cpbuf, &paramsp->ccp_t0, sizeof (ccid_params_t0_t));
+ break;
+ case 1:
+ if (mlen < sizeof (ccid_params_t1_t)) {
+ ret = EOVERFLOW;
+ goto done;
+ }
+ *protp = ATR_P_T1;
+ bcopy(cpbuf, &paramsp->ccp_t1, sizeof (ccid_params_t1_t));
+ break;
+ default:
+ ret = ECHRNG;
+ break;
+ }
+
+done:
+ ccid_command_free(cc);
+ return (ret);
+}
+
+static void
+ccid_hw_error(ccid_t *ccid, ccid_intr_hwerr_t *hwerr)
+{
+ ccid_slot_t *slot;
+
+ /* Make sure the slot number is within range. */
+ if (hwerr->cih_slot >= ccid->ccid_nslots) {
+ ccid->ccid_stats.cst_intr_inval++;
+ return;
+ }
+
+ slot = &ccid->ccid_slots[hwerr->cih_slot];
+
+ /* The only error condition defined by the spec is overcurrent. */
+ if (hwerr->cih_code != CCID_INTR_HWERR_OVERCURRENT) {
+ ccid->ccid_stats.cst_intr_inval++;
+ return;
+ }
+
+ /*
+ * The worker thread will take care of this situation.
+ */
+ slot->cs_flags |= CCID_SLOT_F_INTR_OVERCURRENT;
+ ccid_worker_request(ccid);
+}
+
+static void
+ccid_intr_pipe_cb(usb_pipe_handle_t ph, usb_intr_req_t *uirp)
+{
+ mblk_t *mp;
+ size_t msglen, explen;
+ uint_t i;
+ boolean_t change;
+ ccid_intr_hwerr_t ccid_hwerr;
+ ccid_t *ccid = (ccid_t *)uirp->intr_client_private;
+
+ mp = uirp->intr_data;
+ if (mp == NULL)
+ goto done;
+
+ msglen = msgsize(mp);
+ if (msglen == 0)
+ goto done;
+
+ switch (mp->b_rptr[0]) {
+ case CCID_INTR_CODE_SLOT_CHANGE:
+ mutex_enter(&ccid->ccid_mutex);
+ ccid->ccid_stats.cst_intr_slot_change++;
+
+ explen = 1 + ((2 * ccid->ccid_nslots + (NBBY-1)) / NBBY);
+ if (msglen < explen) {
+ ccid->ccid_stats.cst_intr_inval++;
+ mutex_exit(&ccid->ccid_mutex);
+ goto done;
+ }
+
+ change = B_FALSE;
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ uint_t byte = (i * 2 / NBBY) + 1;
+ uint_t shift = i * 2 % NBBY;
+ uint_t present = 1 << shift;
+ uint_t delta = 2 << shift;
+
+ if (mp->b_rptr[byte] & delta) {
+ ccid_slot_t *slot = &ccid->ccid_slots[i];
+
+ slot->cs_flags &= ~CCID_SLOT_F_INTR_MASK;
+ slot->cs_flags |= CCID_SLOT_F_CHANGED;
+ if (mp->b_rptr[byte] & present) {
+ slot->cs_flags |= CCID_SLOT_F_INTR_ADD;
+ } else {
+ slot->cs_flags |= CCID_SLOT_F_INTR_GONE;
+ }
+ change = B_TRUE;
+ }
+ }
+
+ if (change) {
+ ccid_worker_request(ccid);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+ break;
+ case CCID_INTR_CODE_HW_ERROR:
+ mutex_enter(&ccid->ccid_mutex);
+ ccid->ccid_stats.cst_intr_hwerr++;
+
+ if (msglen < sizeof (ccid_intr_hwerr_t)) {
+ ccid->ccid_stats.cst_intr_inval++;
+ mutex_exit(&ccid->ccid_mutex);
+ goto done;
+ }
+
+ bcopy(mp->b_rptr, &ccid_hwerr, sizeof (ccid_intr_hwerr_t));
+ ccid_hw_error(ccid, &ccid_hwerr);
+
+ mutex_exit(&ccid->ccid_mutex);
+ break;
+ default:
+ mutex_enter(&ccid->ccid_mutex);
+ ccid->ccid_stats.cst_intr_unknown++;
+ mutex_exit(&ccid->ccid_mutex);
+ break;
+ }
+
+done:
+ usb_free_intr_req(uirp);
+}
+
+static void
+ccid_intr_pipe_except_cb(usb_pipe_handle_t ph, usb_intr_req_t *uirp)
+{
+ ccid_t *ccid = (ccid_t *)uirp->intr_client_private;
+
+ ccid->ccid_stats.cst_intr_errs++;
+ switch (uirp->intr_completion_reason) {
+ case USB_CR_PIPE_RESET:
+ case USB_CR_NO_RESOURCES:
+ ccid->ccid_stats.cst_intr_restart++;
+ ccid_intr_poll_init(ccid);
+ break;
+ default:
+ break;
+ }
+ usb_free_intr_req(uirp);
+}
+
+/*
+ * Clean up all the state associated with this slot and its ICC.
+ */
+static void
+ccid_slot_teardown(ccid_t *ccid, ccid_slot_t *slot, boolean_t signal)
+{
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ if (slot->cs_icc.icc_fini != NULL) {
+ slot->cs_icc.icc_fini(ccid, slot);
+ }
+
+ atr_data_reset(slot->cs_icc.icc_atr_data);
+ slot->cs_icc.icc_protocols = ATR_P_NONE;
+ slot->cs_icc.icc_cur_protocol = ATR_P_NONE;
+ slot->cs_icc.icc_init = NULL;
+ slot->cs_icc.icc_tx = NULL;
+ slot->cs_icc.icc_complete = NULL;
+ slot->cs_icc.icc_teardown = NULL;
+ slot->cs_icc.icc_fini = NULL;
+
+ slot->cs_voltage = 0;
+ freemsgchain(slot->cs_atr);
+ slot->cs_atr = NULL;
+
+ if (signal && slot->cs_excl_minor != NULL) {
+ pollwakeup(&slot->cs_excl_minor->cm_pollhead, POLLHUP);
+ }
+}
+
+/*
+ * Wait for teardown of outstanding user I/O.
+ */
+static void
+ccid_slot_io_teardown(ccid_t *ccid, ccid_slot_t *slot)
+{
+ /*
+ * If there is outstanding user I/O, then we need to go ahead and take
+ * care of that. Once this function returns, the user I/O will have been
+ * dealt with; however, before we can tear down things, we need to make
+ * sure that the logical I/O has been completed.
+ */
+ if (slot->cs_icc.icc_teardown != NULL) {
+ slot->cs_icc.icc_teardown(ccid, slot, ENXIO);
+ }
+
+ while ((slot->cs_flags & CCID_SLOT_F_NEED_IO_TEARDOWN) != 0) {
+ cv_wait(&slot->cs_io.ci_cv, &ccid->ccid_mutex);
+ }
+}
+
+/*
+ * The given CCID slot has been inactivated. Clean up.
+ */
+static void
+ccid_slot_inactive(ccid_t *ccid, ccid_slot_t *slot)
+{
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ slot->cs_flags &= ~CCID_SLOT_F_ACTIVE;
+
+ ccid_slot_io_teardown(ccid, slot);
+
+ /*
+ * Now that we've finished completely waiting for the logical I/O to be
+ * torn down, it's safe for us to proceed with the rest of the needed
+ * tear down.
+ */
+ ccid_slot_teardown(ccid, slot, B_TRUE);
+}
+
+/*
+ * The given CCID slot has been removed. Clean up.
+ */
+static void
+ccid_slot_removed(ccid_t *ccid, ccid_slot_t *slot, boolean_t notify)
+{
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ if ((slot->cs_flags & CCID_SLOT_F_PRESENT) == 0) {
+ VERIFY0(slot->cs_flags & CCID_SLOT_F_ACTIVE);
+ return;
+ }
+
+ /*
+ * This slot is gone, mark the flags accordingly.
+ */
+ slot->cs_flags &= ~CCID_SLOT_F_PRESENT;
+
+ ccid_slot_inactive(ccid, slot);
+}
+
+static void
+ccid_slot_setup_functions(ccid_t *ccid, ccid_slot_t *slot)
+{
+ uint_t bits = CCID_CLASS_F_SHORT_APDU_XCHG | CCID_CLASS_F_EXT_APDU_XCHG;
+
+ slot->cs_icc.icc_init = NULL;
+ slot->cs_icc.icc_tx = NULL;
+ slot->cs_icc.icc_complete = NULL;
+ slot->cs_icc.icc_teardown = NULL;
+ slot->cs_icc.icc_fini = NULL;
+
+ switch (ccid->ccid_class.ccd_dwFeatures & bits) {
+ case CCID_CLASS_F_SHORT_APDU_XCHG:
+ case CCID_CLASS_F_EXT_APDU_XCHG:
+ /*
+ * Readers with extended APDU support always also support
+ * short APDUs. We only ever use short APDUs.
+ */
+ slot->cs_icc.icc_tx = ccid_write_apdu;
+ slot->cs_icc.icc_complete = ccid_complete_apdu;
+ slot->cs_icc.icc_teardown = ccid_teardown_apdu;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * When we don't have a supported tx function, we don't want to end
+ * up blocking attach. It's important we attach so that users can try
+ * and determine information about the ICC and reader.
+ */
+ if (slot->cs_icc.icc_tx == NULL) {
+ ccid_error(ccid, "!CCID does not support I/O transfers to ICC");
+ }
+}
+
+/*
+ * We have an ICC present in a slot. We require that the reader does all
+ * protocol and parameter related initializations for us. Just parse the ATR
+ * for our own use and use GET_PARAMS to query the parameters the reader set
+ * up for us.
+ */
+static boolean_t
+ccid_slot_params_init(ccid_t *ccid, ccid_slot_t *slot, mblk_t *atr)
+{
+ int ret;
+ atr_parsecode_t p;
+ atr_protocol_t prot;
+ atr_data_t *data;
+
+ /*
+ * Use the slot's atr data structure. This is only used when we're in
+ * the worker context, so it should be safe to access in a lockless
+ * fashion.
+ */
+ data = slot->cs_icc.icc_atr_data;
+ atr_data_reset(data);
+ if ((p = atr_parse(atr->b_rptr, msgsize(atr), data)) != ATR_CODE_OK) {
+ ccid_error(ccid, "!failed to parse ATR data from slot %d: %s",
+ slot->cs_slotno, atr_strerror(p));
+ return (B_FALSE);
+ }
+
+ if ((ret = ccid_command_get_parameters(ccid, slot, &prot,
+ &slot->cs_icc.icc_params)) != 0) {
+ ccid_error(ccid, "!failed to get parameters for slot %u: %d",
+ slot->cs_slotno, ret);
+ return (B_FALSE);
+ }
+
+ slot->cs_icc.icc_protocols = atr_supported_protocols(data);
+ slot->cs_icc.icc_cur_protocol = prot;
+
+ if ((ccid->ccid_flags & (CCID_F_NEEDS_PPS | CCID_F_NEEDS_PARAMS |
+ CCID_F_NEEDS_DATAFREQ)) != 0) {
+ ccid_error(ccid, "!CCID reader does not support required "
+ "protocol/parameter setup automation");
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
+
+/*
+ * Set up the ICC function parameters and initialize the ICC engine.
+ */
+static boolean_t
+ccid_slot_prot_init(ccid_t *ccid, ccid_slot_t *slot)
+{
+ ccid_slot_setup_functions(ccid, slot);
+
+ if (slot->cs_icc.icc_init != NULL) {
+ slot->cs_icc.icc_init(ccid, slot);
+ }
+
+ return (B_TRUE);
+}
+
+static int
+ccid_slot_power_on(ccid_t *ccid, ccid_slot_t *slot, ccid_class_voltage_t volts,
+ mblk_t **atr)
+{
+ int ret;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ mutex_exit(&ccid->ccid_mutex);
+ if ((ret = ccid_command_power_on(ccid, slot, volts, atr)) != 0) {
+ /*
+ * If we got ENXIO, then we know that there is no ICC
+ * present. This could happen for a number of reasons.
+ * For example, we could have just started up and no
+ * card was plugged in (we default to assuming that one
+ * is). Also, some readers won't really tell us that
+ * nothing is there until after the power on fails,
+ * hence why we don't bother with doing a status check
+ * and just try to power on.
+ */
+ if (ret == ENXIO) {
+ mutex_enter(&ccid->ccid_mutex);
+ slot->cs_flags &= ~CCID_SLOT_F_PRESENT;
+ return (ret);
+ }
+
+ /*
+ * If we fail to power off the card, check to make sure
+ * it hasn't been removed.
+ */
+ if (ccid_command_power_off(ccid, slot) == ENXIO) {
+ mutex_enter(&ccid->ccid_mutex);
+ slot->cs_flags &= ~CCID_SLOT_F_PRESENT;
+ return (ENXIO);
+ }
+
+ mutex_enter(&ccid->ccid_mutex);
+ return (ret);
+ }
+
+ if (!ccid_slot_params_init(ccid, slot, *atr)) {
+ ccid_error(ccid, "!failed to set slot paramters for ICC");
+ mutex_enter(&ccid->ccid_mutex);
+ return (ENOTSUP);
+ }
+
+ if (!ccid_slot_prot_init(ccid, slot)) {
+ ccid_error(ccid, "!failed to setup protocol for ICC");
+ mutex_enter(&ccid->ccid_mutex);
+ return (ENOTSUP);
+ }
+
+ mutex_enter(&ccid->ccid_mutex);
+ return (0);
+}
+
+static int
+ccid_slot_power_off(ccid_t *ccid, ccid_slot_t *slot)
+{
+ int ret;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ ccid_slot_io_teardown(ccid, slot);
+
+ /*
+ * Now that we've finished completely waiting for the logical I/O to be
+ * torn down, try and power off the ICC.
+ */
+ mutex_exit(&ccid->ccid_mutex);
+ ret = ccid_command_power_off(ccid, slot);
+ mutex_enter(&ccid->ccid_mutex);
+
+ if (ret != 0)
+ return (ret);
+
+ ccid_slot_inactive(ccid, slot);
+
+ return (ret);
+}
+
+static int
+ccid_slot_inserted(ccid_t *ccid, ccid_slot_t *slot)
+{
+ uint_t nvolts = 4;
+ uint_t cvolt = 0;
+ mblk_t *atr = NULL;
+ ccid_class_voltage_t volts[4] = { CCID_CLASS_VOLT_AUTO,
+ CCID_CLASS_VOLT_5_0, CCID_CLASS_VOLT_3_0, CCID_CLASS_VOLT_1_8 };
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ if ((ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0) {
+ return (0);
+ }
+
+ if ((slot->cs_flags & CCID_SLOT_F_ACTIVE) != 0) {
+ return (0);
+ }
+
+ slot->cs_flags |= CCID_SLOT_F_PRESENT;
+
+ /*
+ * Now, we need to activate this ccid device before we can do anything
+ * with it. First, power on the device. There are two hardware features
+ * which may be at play. There may be automatic voltage detection and
+ * automatic activation on insertion. In theory, when either of those
+ * are present, we should always try to use the auto voltage.
+ *
+ * What's less clear in the specification is if the Auto-Voltage
+ * property is present is if we should try manual voltages or not. For
+ * the moment we do.
+ */
+ if ((ccid->ccid_class.ccd_dwFeatures &
+ (CCID_CLASS_F_AUTO_ICC_ACTIVATE | CCID_CLASS_F_AUTO_ICC_VOLTAGE)) ==
+ 0) {
+ /* Skip auto-voltage */
+ cvolt++;
+ }
+
+ for (; cvolt < nvolts; cvolt++) {
+ int ret;
+
+ if (volts[cvolt] != CCID_CLASS_VOLT_AUTO &&
+ (ccid->ccid_class.ccd_bVoltageSupport & volts[cvolt]) ==
+ 0) {
+ continue;
+ }
+
+ ret = ccid_slot_power_on(ccid, slot, volts[cvolt], &atr);
+ if (ret != 0) {
+ freemsg(atr);
+ atr = NULL;
+ continue;
+ }
+
+ break;
+ }
+
+ if (cvolt >= nvolts) {
+ ccid_error(ccid, "!failed to activate and power on ICC, no "
+ "supported voltages found");
+ goto notsup;
+ }
+
+ slot->cs_voltage = volts[cvolt];
+ slot->cs_atr = atr;
+ slot->cs_flags |= CCID_SLOT_F_ACTIVE;
+
+ ccid_slot_pollout_signal(slot);
+
+ return (0);
+
+notsup:
+ freemsg(atr);
+ ccid_slot_teardown(ccid, slot, B_FALSE);
+ return (ENOTSUP);
+}
+
+static int
+ccid_slot_warm_reset(ccid_t *ccid, ccid_slot_t *slot)
+{
+ int ret;
+ mblk_t *atr;
+ ccid_class_voltage_t voltage;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ ccid_slot_io_teardown(ccid, slot);
+
+ voltage = slot->cs_voltage;
+
+ ccid_slot_teardown(ccid, slot, B_FALSE);
+
+ ret = ccid_slot_power_on(ccid, slot, voltage, &atr);
+ if (ret != 0) {
+ freemsg(atr);
+ return (ret);
+ }
+
+ slot->cs_voltage = voltage;
+ slot->cs_atr = atr;
+
+ return (ret);
+}
+
+static boolean_t
+ccid_slot_reset(ccid_t *ccid, ccid_slot_t *slot)
+{
+ int ret;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ VERIFY(slot->cs_flags & CCID_SLOT_F_NEED_TXN_RESET);
+ VERIFY(ccid->ccid_flags & CCID_F_WORKER_RUNNING);
+
+ if (ccid->ccid_flags & CCID_F_DEV_GONE_MASK)
+ return (B_TRUE);
+
+ /*
+ * Power off the ICC. This will wait for logical I/O if needed.
+ */
+ ret = ccid_slot_power_off(ccid, slot);
+
+ /*
+ * If we failed to power off the ICC because the ICC is removed, then
+ * just return that we failed, so that we can let the next lap clean
+ * things up by noting that the ICC has been removed.
+ */
+ if (ret != 0 && ret == ENXIO) {
+ return (B_FALSE);
+ }
+
+ if (ret != 0) {
+ ccid_error(ccid, "!failed to reset slot %d for next txn: %d; "
+ "taking another lap", slot->cs_slotno, ret);
+ return (B_FALSE);
+ }
+
+ /*
+ * Mimic a slot insertion to power this back on. Don't worry about
+ * success or failure, because as far as we care for resetting it, we've
+ * done our duty once we've powered it off successfully.
+ */
+ (void) ccid_slot_inserted(ccid, slot);
+
+ return (B_TRUE);
+}
+
+/*
+ * We've been asked to perform some amount of work on the various slots that we
+ * have. This may be because the slot needs to be reset due to the completion of
+ * a transaction or it may be because an ICC inside of the slot has been
+ * removed.
+ */
+static void
+ccid_worker(void *arg)
+{
+ uint_t i;
+ ccid_t *ccid = arg;
+
+ mutex_enter(&ccid->ccid_mutex);
+ ccid->ccid_stats.cst_ndiscover++;
+ ccid->ccid_stats.cst_lastdiscover = gethrtime();
+ ccid->ccid_flags |= CCID_F_WORKER_RUNNING;
+ ccid->ccid_flags &= ~CCID_F_WORKER_REQUESTED;
+
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ ccid_slot_t *slot = &ccid->ccid_slots[i];
+ uint_t flags;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ if ((ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0) {
+ ccid->ccid_flags &= ~CCID_F_WORKER_MASK;
+ mutex_exit(&ccid->ccid_mutex);
+ return;
+ }
+
+ /*
+ * Snapshot the flags before we start processing the worker. At
+ * this time we clear out all of the change flags as we'll be
+ * operating on the device. We do not clear the
+ * CCID_SLOT_F_NEED_TXN_RESET flag, as we want to make sure that
+ * this is maintained until we're done here.
+ */
+ flags = slot->cs_flags & CCID_SLOT_F_WORK_MASK;
+ slot->cs_flags &= ~CCID_SLOT_F_INTR_MASK;
+
+ if ((flags & CCID_SLOT_F_INTR_OVERCURRENT) != 0) {
+ ccid_slot_inactive(ccid, slot);
+ }
+
+ if ((flags & CCID_SLOT_F_CHANGED) != 0) {
+ if (flags & CCID_SLOT_F_INTR_GONE) {
+ ccid_slot_removed(ccid, slot, B_TRUE);
+ } else {
+ (void) ccid_slot_inserted(ccid, slot);
+ if ((slot->cs_flags & CCID_SLOT_F_ACTIVE) !=
+ 0) {
+ ccid_slot_excl_maybe_signal(slot);
+ }
+ }
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ }
+
+ if ((flags & CCID_SLOT_F_NEED_TXN_RESET) != 0) {
+ /*
+ * If the CCID_SLOT_F_PRESENT flag is set, then we
+ * should attempt to power off and power on the ICC in
+ * an attempt to reset it. If this fails, trigger
+ * another worker that needs to operate.
+ */
+ if ((slot->cs_flags & CCID_SLOT_F_PRESENT) != 0) {
+ if (!ccid_slot_reset(ccid, slot)) {
+ ccid_worker_request(ccid);
+ continue;
+ }
+ }
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ slot->cs_flags &= ~CCID_SLOT_F_NEED_TXN_RESET;
+ /*
+ * Try to signal the next thread waiting for exclusive
+ * access.
+ */
+ ccid_slot_excl_maybe_signal(slot);
+ }
+ }
+
+ /*
+ * If we have a request to operate again, delay before we consider this,
+ * to make sure we don't do too much work ourselves.
+ */
+ if ((ccid->ccid_flags & CCID_F_WORKER_REQUESTED) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ delay(drv_usectohz(1000) * 10);
+ mutex_enter(&ccid->ccid_mutex);
+ }
+
+ ccid->ccid_flags &= ~CCID_F_WORKER_RUNNING;
+ if ((ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0) {
+ ccid->ccid_flags &= ~CCID_F_WORKER_REQUESTED;
+ mutex_exit(&ccid->ccid_mutex);
+ return;
+ }
+
+ if ((ccid->ccid_flags & CCID_F_WORKER_REQUESTED) != 0) {
+ (void) ddi_taskq_dispatch(ccid->ccid_taskq, ccid_worker, ccid,
+ DDI_SLEEP);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+}
+
+static void
+ccid_worker_request(ccid_t *ccid)
+{
+ boolean_t run;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ if ((ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0) {
+ return;
+ }
+
+ run = (ccid->ccid_flags & CCID_F_WORKER_MASK) == 0;
+ ccid->ccid_flags |= CCID_F_WORKER_REQUESTED;
+ if (run) {
+ mutex_exit(&ccid->ccid_mutex);
+ (void) ddi_taskq_dispatch(ccid->ccid_taskq, ccid_worker, ccid,
+ DDI_SLEEP);
+ mutex_enter(&ccid->ccid_mutex);
+ }
+}
+
+static void
+ccid_intr_restart_timeout(void *arg)
+{
+ ccid_t *ccid = arg;
+
+ mutex_enter(&ccid->ccid_mutex);
+ if ((ccid->ccid_flags & CCID_F_DEV_GONE_MASK) != 0) {
+ ccid->ccid_poll_timeout = NULL;
+ mutex_exit(&ccid->ccid_mutex);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+
+ ccid_intr_poll_init(ccid);
+}
+
+/*
+ * Search for the current class descriptor from the configuration cloud and
+ * parse it for our use. We do this by first finding the current interface
+ * descriptor and expecting it to be one of the next descriptors.
+ */
+static boolean_t
+ccid_parse_class_desc(ccid_t *ccid)
+{
+ uint_t i;
+ size_t len, tlen;
+ usb_client_dev_data_t *dp;
+ usb_alt_if_data_t *alt;
+
+ /*
+ * Establish the target length we're looking for from usb_parse_data().
+ * Note that we cannot use the sizeof (ccid_class_descr_t) for this
+ * because that function does not know how to account for the padding at
+ * the end of the target structure (which is reasonble). So we manually
+ * figure out the number of bytes it should in theory write.
+ */
+ tlen = offsetof(ccid_class_descr_t, ccd_bMaxCCIDBusySlots) +
+ sizeof (ccid->ccid_class.ccd_bMaxCCIDBusySlots);
+ dp = ccid->ccid_dev_data;
+ alt = &dp->dev_curr_cfg->cfg_if[dp->dev_curr_if].if_alt[0];
+ for (i = 0; i < alt->altif_n_cvs; i++) {
+ usb_cvs_data_t *cvs = &alt->altif_cvs[i];
+ if (cvs->cvs_buf == NULL)
+ continue;
+ if (cvs->cvs_buf_len != CCID_DESCR_LENGTH)
+ continue;
+ if (cvs->cvs_buf[1] != CCID_DESCR_TYPE)
+ continue;
+ if ((len = usb_parse_data("ccscc3lcllc5lccscc", cvs->cvs_buf,
+ cvs->cvs_buf_len, &ccid->ccid_class,
+ sizeof (ccid->ccid_class))) >= tlen) {
+ return (B_TRUE);
+ }
+ ccid_error(ccid, "!failed to parse CCID class descriptor from "
+ "cvs %u, expected %lu bytes, received %lu", i, tlen, len);
+ }
+
+ ccid_error(ccid, "!failed to find matching CCID class descriptor");
+ return (B_FALSE);
+}
+
+/*
+ * Verify whether or not we can support this CCID reader.
+ */
+static boolean_t
+ccid_supported(ccid_t *ccid)
+{
+ usb_client_dev_data_t *dp;
+ usb_alt_if_data_t *alt;
+ ccid_class_features_t feat;
+ uint_t bits;
+ uint16_t ver = ccid->ccid_class.ccd_bcdCCID;
+
+ if (CCID_VERSION_MAJOR(ver) != CCID_VERSION_ONE) {
+ ccid_error(ccid, "!refusing to attach to CCID with unsupported "
+ "version %x.%2x", CCID_VERSION_MAJOR(ver),
+ CCID_VERSION_MINOR(ver));
+ return (B_FALSE);
+ }
+
+ /*
+ * Check the number of endpoints. This should have either two or three.
+ * If three, that means we should expect an interrupt-IN endpoint.
+ * Otherwise, we shouldn't. Any other value indicates something weird
+ * that we should ignore.
+ */
+ dp = ccid->ccid_dev_data;
+ alt = &dp->dev_curr_cfg->cfg_if[dp->dev_curr_if].if_alt[0];
+ switch (alt->altif_descr.bNumEndpoints) {
+ case 2:
+ ccid->ccid_flags &= ~CCID_F_HAS_INTR;
+ break;
+ case 3:
+ ccid->ccid_flags |= CCID_F_HAS_INTR;
+ break;
+ default:
+ ccid_error(ccid, "!refusing to attach to CCID with unsupported "
+ "number of endpoints: %d", alt->altif_descr.bNumEndpoints);
+ return (B_FALSE);
+ }
+
+ /*
+ * Try and determine the appropriate buffer size. This can be a little
+ * tricky. The class descriptor tells us the maximum size that the
+ * reader accepts. While it may be tempting to try and use a larger
+ * value such as the maximum size, the readers really don't like
+ * receiving bulk transfers that large. However, there are also reports
+ * of readers that will overwrite to a fixed minimum size. Until we see
+ * such a thing in the wild there's probably no point in trying to deal
+ * with it here.
+ */
+ ccid->ccid_bufsize = ccid->ccid_class.ccd_dwMaxCCIDMessageLength;
+ if (ccid->ccid_bufsize < CCID_MIN_MESSAGE_LENGTH) {
+ ccid_error(ccid, "!CCID reader maximum CCID message length (%u)"
+ " is less than minimum packet length (%u)",
+ ccid->ccid_bufsize, CCID_MIN_MESSAGE_LENGTH);
+ return (B_FALSE);
+ }
+
+ /*
+ * At this time, we do not require that the system have automatic ICC
+ * activation or automatic ICC voltage. These are handled automatically
+ * by the system.
+ */
+ feat = ccid->ccid_class.ccd_dwFeatures;
+
+ /*
+ * Check the number of data rates that are supported by the reader. If
+ * the reader has a non-zero value and we don't support automatic
+ * negotiation then warn about that.
+ */
+ if (ccid->ccid_class.ccd_bNumDataRatesSupported != 0 &&
+ (feat & CCID_CLASS_F_AUTO_BAUD) == 0) {
+ ccid_error(ccid, "!CCID reader only supports fixed clock rates,"
+ " data will be limited to default values");
+ }
+
+ /*
+ * Check which automatic features the reader provides and which features
+ * it does not. Missing features will require additional work before a
+ * card can be activated. Note, this also applies to APDU based readers
+ * which may need to have various aspects of the device negotiated.
+ */
+
+ /*
+ * The footnote for these two bits in CCID r1.1.0 indicates that
+ * when neither are missing we have to do the PPS negotiation
+ * ourselves.
+ */
+ bits = CCID_CLASS_F_AUTO_PARAM_NEG | CCID_CLASS_F_AUTO_PPS;
+ if ((feat & bits) == 0) {
+ ccid->ccid_flags |= CCID_F_NEEDS_PPS;
+ }
+
+ if ((feat & CCID_CLASS_F_AUTO_PARAM_NEG) == 0) {
+ ccid->ccid_flags |= CCID_F_NEEDS_PARAMS;
+ }
+
+ bits = CCID_CLASS_F_AUTO_BAUD | CCID_CLASS_F_AUTO_ICC_CLOCK;
+ if ((feat & bits) != bits) {
+ ccid->ccid_flags |= CCID_F_NEEDS_DATAFREQ;
+ }
+
+ return (B_TRUE);
+}
+
+static boolean_t
+ccid_open_pipes(ccid_t *ccid)
+{
+ int ret;
+ usb_ep_data_t *ep;
+ usb_client_dev_data_t *data;
+ usb_pipe_policy_t policy;
+
+ data = ccid->ccid_dev_data;
+
+ /*
+ * First fill all the descriptors.
+ */
+ ep = usb_lookup_ep_data(ccid->ccid_dip, data, data->dev_curr_if, 0, 0,
+ USB_EP_ATTR_BULK, USB_EP_DIR_IN);
+ if (ep == NULL) {
+ ccid_error(ccid, "!failed to find CCID Bulk-IN endpoint");
+ return (B_FALSE);
+ }
+
+ if ((ret = usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
+ ccid->ccid_dip, ep, &ccid->ccid_bulkin_xdesc)) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to fill Bulk-IN xdescr: %d", ret);
+ return (B_FALSE);
+ }
+
+ ep = usb_lookup_ep_data(ccid->ccid_dip, data, data->dev_curr_if, 0, 0,
+ USB_EP_ATTR_BULK, USB_EP_DIR_OUT);
+ if (ep == NULL) {
+ ccid_error(ccid, "!failed to find CCID Bulk-OUT endpoint");
+ return (B_FALSE);
+ }
+
+ if ((ret = usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
+ ccid->ccid_dip, ep, &ccid->ccid_bulkout_xdesc)) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to fill Bulk-OUT xdescr: %d", ret);
+ return (B_FALSE);
+ }
+
+ if (ccid->ccid_flags & CCID_F_HAS_INTR) {
+ ep = usb_lookup_ep_data(ccid->ccid_dip, data, data->dev_curr_if,
+ 0, 0, USB_EP_ATTR_INTR, USB_EP_DIR_IN);
+ if (ep == NULL) {
+ ccid_error(ccid, "!failed to find CCID Intr-IN "
+ "endpoint");
+ return (B_FALSE);
+ }
+
+ if ((ret = usb_ep_xdescr_fill(USB_EP_XDESCR_CURRENT_VERSION,
+ ccid->ccid_dip, ep, &ccid->ccid_intrin_xdesc)) !=
+ USB_SUCCESS) {
+ ccid_error(ccid, "!failed to fill Intr-OUT xdescr: %d",
+ ret);
+ return (B_FALSE);
+ }
+ }
+
+ /*
+ * Now open up the pipes.
+ */
+ bzero(&policy, sizeof (policy));
+ policy.pp_max_async_reqs = CCID_NUM_ASYNC_REQS;
+
+ if ((ret = usb_pipe_xopen(ccid->ccid_dip, &ccid->ccid_bulkin_xdesc,
+ &policy, USB_FLAGS_SLEEP, &ccid->ccid_bulkin_pipe)) !=
+ USB_SUCCESS) {
+ ccid_error(ccid, "!failed to open Bulk-IN pipe: %d\n", ret);
+ return (B_FALSE);
+ }
+
+ if ((ret = usb_pipe_xopen(ccid->ccid_dip, &ccid->ccid_bulkout_xdesc,
+ &policy, USB_FLAGS_SLEEP, &ccid->ccid_bulkout_pipe)) !=
+ USB_SUCCESS) {
+ ccid_error(ccid, "!failed to open Bulk-OUT pipe: %d\n", ret);
+ usb_pipe_close(ccid->ccid_dip, ccid->ccid_bulkin_pipe,
+ USB_FLAGS_SLEEP, NULL, NULL);
+ ccid->ccid_bulkin_pipe = NULL;
+ return (B_FALSE);
+ }
+
+ if (ccid->ccid_flags & CCID_F_HAS_INTR) {
+ if ((ret = usb_pipe_xopen(ccid->ccid_dip,
+ &ccid->ccid_intrin_xdesc, &policy, USB_FLAGS_SLEEP,
+ &ccid->ccid_intrin_pipe)) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to open Intr-IN pipe: %d\n",
+ ret);
+ usb_pipe_close(ccid->ccid_dip, ccid->ccid_bulkin_pipe,
+ USB_FLAGS_SLEEP, NULL, NULL);
+ ccid->ccid_bulkin_pipe = NULL;
+ usb_pipe_close(ccid->ccid_dip, ccid->ccid_bulkout_pipe,
+ USB_FLAGS_SLEEP, NULL, NULL);
+ ccid->ccid_bulkout_pipe = NULL;
+ return (B_FALSE);
+ }
+ }
+
+ ccid->ccid_control_pipe = data->dev_default_ph;
+ return (B_TRUE);
+}
+
+static void
+ccid_slots_fini(ccid_t *ccid)
+{
+ uint_t i;
+
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ VERIFY3U(ccid->ccid_slots[i].cs_slotno, ==, i);
+
+ if (ccid->ccid_slots[i].cs_command != NULL) {
+ ccid_command_free(ccid->ccid_slots[i].cs_command);
+ ccid->ccid_slots[i].cs_command = NULL;
+ }
+
+ cv_destroy(&ccid->ccid_slots[i].cs_io.ci_cv);
+ freemsgchain(ccid->ccid_slots[i].cs_atr);
+ atr_data_free(ccid->ccid_slots[i].cs_icc.icc_atr_data);
+ list_destroy(&ccid->ccid_slots[i].cs_minors);
+ list_destroy(&ccid->ccid_slots[i].cs_excl_waiters);
+ }
+
+ ddi_remove_minor_node(ccid->ccid_dip, NULL);
+ kmem_free(ccid->ccid_slots, sizeof (ccid_slot_t) * ccid->ccid_nslots);
+ ccid->ccid_nslots = 0;
+ ccid->ccid_slots = NULL;
+}
+
+static boolean_t
+ccid_slots_init(ccid_t *ccid)
+{
+ uint_t i;
+
+ /*
+ * The class descriptor has the maximum index that one can index into.
+ * We therefore have to add one to determine the actual number of slots
+ * that exist.
+ */
+ ccid->ccid_nslots = ccid->ccid_class.ccd_bMaxSlotIndex + 1;
+ ccid->ccid_slots = kmem_zalloc(sizeof (ccid_slot_t) * ccid->ccid_nslots,
+ KM_SLEEP);
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ ccid_slot_t *slot = &ccid->ccid_slots[i];
+
+ /*
+ * We initialize every possible slot as having changed to make
+ * sure that we have a chance to discover it. See the slot
+ * detection section in the big theory statement for more info.
+ */
+ slot->cs_flags |= CCID_SLOT_F_CHANGED;
+ slot->cs_slotno = i;
+ slot->cs_ccid = ccid;
+ slot->cs_icc.icc_atr_data = atr_data_alloc();
+ slot->cs_idx.cmi_minor = CCID_MINOR_INVALID;
+ slot->cs_idx.cmi_isslot = B_TRUE;
+ slot->cs_idx.cmi_data.cmi_slot = slot;
+ cv_init(&slot->cs_io.ci_cv, NULL, CV_DRIVER, NULL);
+ list_create(&slot->cs_minors, sizeof (ccid_minor_t),
+ offsetof(ccid_minor_t, cm_minor_list));
+ list_create(&slot->cs_excl_waiters, sizeof (ccid_minor_t),
+ offsetof(ccid_minor_t, cm_excl_list));
+ }
+
+ return (B_TRUE);
+}
+
+static void
+ccid_minors_fini(ccid_t *ccid)
+{
+ uint_t i;
+
+ ddi_remove_minor_node(ccid->ccid_dip, NULL);
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ if (ccid->ccid_slots[i].cs_idx.cmi_minor == CCID_MINOR_INVALID)
+ continue;
+ ccid_minor_idx_free(&ccid->ccid_slots[i].cs_idx);
+ }
+}
+
+static boolean_t
+ccid_minors_init(ccid_t *ccid)
+{
+ uint_t i;
+
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ char buf[32];
+
+ (void) ccid_minor_idx_alloc(&ccid->ccid_slots[i].cs_idx,
+ B_TRUE);
+
+ (void) snprintf(buf, sizeof (buf), "slot%u", i);
+ if (ddi_create_minor_node(ccid->ccid_dip, buf, S_IFCHR,
+ ccid->ccid_slots[i].cs_idx.cmi_minor,
+ DDI_NT_CCID_ATTACHMENT_POINT, 0) != DDI_SUCCESS) {
+ ccid_minors_fini(ccid);
+ return (B_FALSE);
+ }
+ }
+
+ return (B_TRUE);
+}
+
+static void
+ccid_intr_poll_fini(ccid_t *ccid)
+{
+ if (ccid->ccid_flags & CCID_F_HAS_INTR) {
+ timeout_id_t tid;
+
+ mutex_enter(&ccid->ccid_mutex);
+ tid = ccid->ccid_poll_timeout;
+ ccid->ccid_poll_timeout = NULL;
+ mutex_exit(&ccid->ccid_mutex);
+ (void) untimeout(tid);
+ usb_pipe_stop_intr_polling(ccid->ccid_intrin_pipe,
+ USB_FLAGS_SLEEP);
+ } else {
+ VERIFY3P(ccid->ccid_intrin_pipe, ==, NULL);
+ }
+}
+
+static void
+ccid_intr_poll_init(ccid_t *ccid)
+{
+ int ret;
+ usb_intr_req_t *uirp;
+
+ uirp = usb_alloc_intr_req(ccid->ccid_dip, 0, USB_FLAGS_SLEEP);
+ uirp->intr_client_private = (usb_opaque_t)ccid;
+ uirp->intr_attributes = USB_ATTRS_SHORT_XFER_OK |
+ USB_ATTRS_AUTOCLEARING;
+ uirp->intr_len = CCID_INTR_RESPONSE_SIZE;
+ uirp->intr_cb = ccid_intr_pipe_cb;
+ uirp->intr_exc_cb = ccid_intr_pipe_except_cb;
+
+ mutex_enter(&ccid->ccid_mutex);
+ if (ccid->ccid_flags & CCID_F_DEV_GONE_MASK) {
+ mutex_exit(&ccid->ccid_mutex);
+ usb_free_intr_req(uirp);
+ return;
+ }
+
+ if ((ret = usb_pipe_intr_xfer(ccid->ccid_intrin_pipe, uirp,
+ USB_FLAGS_SLEEP)) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to start polling on CCID Intr-IN "
+ "pipe: %d", ret);
+ ccid->ccid_poll_timeout = timeout(ccid_intr_restart_timeout,
+ ccid, drv_usectohz(1000000));
+ usb_free_intr_req(uirp);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+}
+
+static void
+ccid_cleanup_bulkin(ccid_t *ccid)
+{
+ uint_t i;
+
+ VERIFY3P(ccid->ccid_bulkin_dispatched, ==, NULL);
+ for (i = 0; i < ccid->ccid_bulkin_alloced; i++) {
+ VERIFY3P(ccid->ccid_bulkin_cache[i], !=, NULL);
+ usb_free_bulk_req(ccid->ccid_bulkin_cache[i]);
+ ccid->ccid_bulkin_cache[i] = NULL;
+ }
+
+#ifdef DEBUG
+ for (i = 0; i < CCID_BULK_NALLOCED; i++) {
+ VERIFY3P(ccid->ccid_bulkin_cache[i], ==, NULL);
+ }
+#endif
+ ccid->ccid_bulkin_alloced = 0;
+}
+
+static int
+ccid_disconnect_cb(dev_info_t *dip)
+{
+ int inst;
+ ccid_t *ccid;
+ uint_t i;
+
+ if (dip == NULL)
+ goto done;
+
+ inst = ddi_get_instance(dip);
+ ccid = ddi_get_soft_state(ccid_softstate, inst);
+ if (ccid == NULL)
+ goto done;
+ VERIFY3P(dip, ==, ccid->ccid_dip);
+
+ mutex_enter(&ccid->ccid_mutex);
+ /*
+ * First, set the disconnected flag. This will make sure that anyone
+ * that tries to make additional operations will be kicked out. This
+ * flag is checked by detach and by users.
+ */
+ ccid->ccid_flags |= CCID_F_DISCONNECTED;
+
+ /*
+ * Now, go through any threads that are blocked on a minor for exclusive
+ * access. They should be woken up and they'll fail due to the fact that
+ * we've set the disconnected flag above.
+ */
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ ccid_minor_t *cmp;
+ ccid_slot_t *slot = &ccid->ccid_slots[i];
+
+ for (cmp = list_head(&slot->cs_excl_waiters); cmp != NULL;
+ cmp = list_next(&slot->cs_excl_waiters, cmp)) {
+ cv_signal(&cmp->cm_excl_cv);
+ }
+ }
+
+ /*
+ * Finally, we need to basically wake up anyone blocked in read and make
+ * sure that they don't wait there forever and make sure that anyone
+ * polling gets a POLLHUP. We can't really distinguish between this and
+ * an ICC being removed. It will be discovered when someone tries to do
+ * an operation and they receive an ENODEV. We only need to do this on
+ * minors that have exclusive access. Don't worry about them finishing
+ * up, this'll be done as part of detach.
+ */
+ for (i = 0; i < ccid->ccid_nslots; i++) {
+ ccid_slot_t *slot = &ccid->ccid_slots[i];
+ if (slot->cs_excl_minor == NULL)
+ continue;
+
+ pollwakeup(&slot->cs_excl_minor->cm_pollhead,
+ POLLHUP | POLLERR);
+ cv_signal(&slot->cs_excl_minor->cm_read_cv);
+ }
+
+ /*
+ * If there are outstanding commands, they will ultimately be cleaned
+ * up as the USB commands themselves time out. We will get notified
+ * through the various bulk xfer exception callbacks, which will induce
+ * the cleanup through ccid_command_transport_error(). This will also
+ * take care of commands waiting for I/O teardown.
+ */
+ mutex_exit(&ccid->ccid_mutex);
+
+done:
+ return (USB_SUCCESS);
+}
+
+static usb_event_t ccid_usb_events = {
+ ccid_disconnect_cb,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+ccid_cleanup(dev_info_t *dip)
+{
+ int inst;
+ ccid_t *ccid;
+
+ if (dip == NULL)
+ return;
+
+ inst = ddi_get_instance(dip);
+ ccid = ddi_get_soft_state(ccid_softstate, inst);
+ if (ccid == NULL)
+ return;
+ VERIFY3P(dip, ==, ccid->ccid_dip);
+
+ /*
+ * Make sure we set the detaching flag so anything running in the
+ * background knows to stop.
+ */
+ mutex_enter(&ccid->ccid_mutex);
+ ccid->ccid_flags |= CCID_F_DETACHING;
+ mutex_exit(&ccid->ccid_mutex);
+
+ if ((ccid->ccid_attach & CCID_ATTACH_MINORS) != 0) {
+ ccid_minors_fini(ccid);
+ ccid->ccid_attach &= ~CCID_ATTACH_MINORS;
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_INTR_ACTIVE) != 0) {
+ ccid_intr_poll_fini(ccid);
+ ccid->ccid_attach &= ~CCID_ATTACH_INTR_ACTIVE;
+ }
+
+ /*
+ * At this point, we have shut down the interrupt pipe, the last place
+ * aside from a user that could have kicked off I/O. So finally wait for
+ * any worker threads.
+ */
+ if (ccid->ccid_taskq != NULL) {
+ ddi_taskq_wait(ccid->ccid_taskq);
+ mutex_enter(&ccid->ccid_mutex);
+ VERIFY0(ccid->ccid_flags & CCID_F_WORKER_MASK);
+ mutex_exit(&ccid->ccid_mutex);
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_HOTPLUG_CB) != 0) {
+ usb_unregister_event_cbs(dip, &ccid_usb_events);
+ ccid->ccid_attach &= ~CCID_ATTACH_HOTPLUG_CB;
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_SLOTS) != 0) {
+ ccid_slots_fini(ccid);
+ ccid->ccid_attach &= ~CCID_ATTACH_SLOTS;
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_SEQ_IDS) != 0) {
+ id_space_destroy(ccid->ccid_seqs);
+ ccid->ccid_seqs = NULL;
+ ccid->ccid_attach &= ~CCID_ATTACH_SEQ_IDS;
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_OPEN_PIPES) != 0) {
+ usb_pipe_close(dip, ccid->ccid_bulkin_pipe, USB_FLAGS_SLEEP,
+ NULL, NULL);
+ ccid->ccid_bulkin_pipe = NULL;
+ usb_pipe_close(dip, ccid->ccid_bulkout_pipe, USB_FLAGS_SLEEP,
+ NULL, NULL);
+ ccid->ccid_bulkout_pipe = NULL;
+ if ((ccid->ccid_flags & CCID_F_HAS_INTR) != 0) {
+ usb_pipe_close(dip, ccid->ccid_intrin_pipe,
+ USB_FLAGS_SLEEP, NULL, NULL);
+ ccid->ccid_intrin_pipe = NULL;
+ } else {
+ VERIFY3P(ccid->ccid_intrin_pipe, ==, NULL);
+ }
+ ccid->ccid_control_pipe = NULL;
+ ccid->ccid_attach &= ~CCID_ATTACH_OPEN_PIPES;
+ }
+
+ /*
+ * Now that all of the pipes are closed. If we happened to have any
+ * cached bulk requests, we should free them.
+ */
+ ccid_cleanup_bulkin(ccid);
+
+ if (ccid->ccid_attach & CCID_ATTACH_CMD_LIST) {
+ ccid_command_t *cc;
+
+ while ((cc = list_remove_head(&ccid->ccid_command_queue)) !=
+ NULL) {
+ ccid_command_free(cc);
+ }
+ list_destroy(&ccid->ccid_command_queue);
+
+ while ((cc = list_remove_head(&ccid->ccid_complete_queue)) !=
+ NULL) {
+ ccid_command_free(cc);
+ }
+ list_destroy(&ccid->ccid_complete_queue);
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_TASKQ) != 0) {
+ ddi_taskq_destroy(ccid->ccid_taskq);
+ ccid->ccid_taskq = NULL;
+ ccid->ccid_attach &= ~CCID_ATTACH_TASKQ;
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_MUTEX_INIT) != 0) {
+ mutex_destroy(&ccid->ccid_mutex);
+ ccid->ccid_attach &= ~CCID_ATTACH_MUTEX_INIT;
+ }
+
+ if ((ccid->ccid_attach & CCID_ATTACH_USB_CLIENT) != 0) {
+ usb_client_detach(dip, ccid->ccid_dev_data);
+ ccid->ccid_dev_data = NULL;
+ ccid->ccid_attach &= ~CCID_ATTACH_USB_CLIENT;
+ }
+
+ ASSERT0(ccid->ccid_attach);
+ ddi_soft_state_free(ccid_softstate, inst);
+}
+
+static int
+ccid_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ ccid_t *ccid;
+ int inst, ret;
+ char buf[64];
+
+ if (cmd != DDI_ATTACH)
+ return (DDI_FAILURE);
+
+ inst = ddi_get_instance(dip);
+ if (ddi_soft_state_zalloc(ccid_softstate, inst) != DDI_SUCCESS) {
+ ccid_error(NULL, "!failed to allocate soft state for ccid "
+ "instance %d", inst);
+ return (DDI_FAILURE);
+ }
+
+ ccid = ddi_get_soft_state(ccid_softstate, inst);
+ ccid->ccid_dip = dip;
+
+ if ((ret = usb_client_attach(dip, USBDRV_VERSION, 0)) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to attach to usb client: %d", ret);
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_USB_CLIENT;
+
+ if ((ret = usb_get_dev_data(dip, &ccid->ccid_dev_data, USB_PARSE_LVL_IF,
+ 0)) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to get usb device data: %d", ret);
+ goto cleanup;
+ }
+
+ mutex_init(&ccid->ccid_mutex, NULL, MUTEX_DRIVER,
+ ccid->ccid_dev_data->dev_iblock_cookie);
+ ccid->ccid_attach |= CCID_ATTACH_MUTEX_INIT;
+
+ (void) snprintf(buf, sizeof (buf), "ccid%d_taskq", inst);
+ ccid->ccid_taskq = ddi_taskq_create(dip, buf, 1, TASKQ_DEFAULTPRI, 0);
+ if (ccid->ccid_taskq == NULL) {
+ ccid_error(ccid, "!failed to create CCID taskq");
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_TASKQ;
+
+ list_create(&ccid->ccid_command_queue, sizeof (ccid_command_t),
+ offsetof(ccid_command_t, cc_list_node));
+ list_create(&ccid->ccid_complete_queue, sizeof (ccid_command_t),
+ offsetof(ccid_command_t, cc_list_node));
+
+ if (!ccid_parse_class_desc(ccid)) {
+ ccid_error(ccid, "!failed to parse CCID class descriptor");
+ goto cleanup;
+ }
+
+ if (!ccid_supported(ccid)) {
+ ccid_error(ccid,
+ "!CCID reader is not supported, not attaching");
+ goto cleanup;
+ }
+
+ if (!ccid_open_pipes(ccid)) {
+ ccid_error(ccid, "!failed to open CCID pipes, not attaching");
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_OPEN_PIPES;
+
+ (void) snprintf(buf, sizeof (buf), "ccid%d_seqs", inst);
+ if ((ccid->ccid_seqs = id_space_create(buf, CCID_SEQ_MIN,
+ CCID_SEQ_MAX + 1)) == NULL) {
+ ccid_error(ccid, "!failed to create CCID sequence id space");
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_SEQ_IDS;
+
+ if (!ccid_slots_init(ccid)) {
+ ccid_error(ccid, "!failed to initialize CCID slot structures");
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_SLOTS;
+
+ if (usb_register_event_cbs(dip, &ccid_usb_events, 0) != USB_SUCCESS) {
+ ccid_error(ccid, "!failed to register USB hotplug callbacks");
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_HOTPLUG_CB;
+
+ /*
+ * Before we enable the interrupt pipe, take a shot at priming our
+ * bulkin_cache.
+ */
+ mutex_enter(&ccid->ccid_mutex);
+ ccid_bulkin_cache_refresh(ccid);
+ mutex_exit(&ccid->ccid_mutex);
+
+ if (ccid->ccid_flags & CCID_F_HAS_INTR) {
+ ccid_intr_poll_init(ccid);
+ }
+ ccid->ccid_attach |= CCID_ATTACH_INTR_ACTIVE;
+
+ /*
+ * Create minor nodes for each slot.
+ */
+ if (!ccid_minors_init(ccid)) {
+ ccid_error(ccid, "!failed to create minor nodes");
+ goto cleanup;
+ }
+ ccid->ccid_attach |= CCID_ATTACH_MINORS;
+
+ mutex_enter(&ccid->ccid_mutex);
+ ccid_worker_request(ccid);
+ mutex_exit(&ccid->ccid_mutex);
+
+ return (DDI_SUCCESS);
+
+cleanup:
+ ccid_cleanup(dip);
+ return (DDI_FAILURE);
+}
+
+static int
+ccid_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **outp)
+{
+ return (DDI_FAILURE);
+}
+
+static int
+ccid_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ int inst;
+ ccid_t *ccid;
+
+ if (cmd != DDI_DETACH)
+ return (DDI_FAILURE);
+
+ inst = ddi_get_instance(dip);
+ ccid = ddi_get_soft_state(ccid_softstate, inst);
+ VERIFY3P(ccid, !=, NULL);
+ VERIFY3P(dip, ==, ccid->ccid_dip);
+
+ mutex_enter(&ccid->ccid_mutex);
+
+ /*
+ * If the device hasn't been disconnected from a USB sense, refuse to
+ * detach. Otherwise, there's no way to guarantee that the ccid
+ * driver will be attached when a user hotplugs an ICC.
+ */
+ if ((ccid->ccid_flags & CCID_F_DISCONNECTED) == 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (DDI_FAILURE);
+ }
+
+ if (!list_is_empty(&ccid->ccid_command_queue) ||
+ !list_is_empty(&ccid->ccid_complete_queue)) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (DDI_FAILURE);
+ }
+ mutex_exit(&ccid->ccid_mutex);
+
+ ccid_cleanup(dip);
+ return (DDI_SUCCESS);
+}
+
+static void
+ccid_minor_free(ccid_minor_t *cmp)
+{
+ VERIFY3U(cmp->cm_idx.cmi_minor, ==, CCID_MINOR_INVALID);
+ crfree(cmp->cm_opener);
+ cv_destroy(&cmp->cm_iowait_cv);
+ cv_destroy(&cmp->cm_read_cv);
+ cv_destroy(&cmp->cm_excl_cv);
+ kmem_free(cmp, sizeof (ccid_minor_t));
+
+}
+
+static int
+ccid_open(dev_t *devp, int flag, int otyp, cred_t *credp)
+{
+ ccid_minor_idx_t *idx;
+ ccid_minor_t *cmp;
+ ccid_slot_t *slot;
+
+ /*
+ * Always check the zone first, to make sure we lie about it existing.
+ */
+ if (crgetzoneid(credp) != GLOBAL_ZONEID)
+ return (ENOENT);
+
+ if ((otyp & (FNDELAY | FEXCL)) != 0)
+ return (EINVAL);
+
+ if (drv_priv(credp) != 0)
+ return (EPERM);
+
+ if (otyp != OTYP_CHR)
+ return (ENOTSUP);
+
+ if ((flag & FREAD) != FREAD)
+ return (EINVAL);
+
+ idx = ccid_minor_find(getminor(*devp));
+ if (idx == NULL) {
+ return (ENOENT);
+ }
+
+ /*
+ * We don't expect anyone to be able to get a non-slot related minor. If
+ * that somehow happens, guard against it and error out.
+ */
+ if (!idx->cmi_isslot) {
+ return (ENOENT);
+ }
+
+ slot = idx->cmi_data.cmi_slot;
+
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODEV);
+ }
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+
+ cmp = kmem_zalloc(sizeof (ccid_minor_t), KM_SLEEP);
+
+ cmp->cm_idx.cmi_minor = CCID_MINOR_INVALID;
+ cmp->cm_idx.cmi_isslot = B_FALSE;
+ cmp->cm_idx.cmi_data.cmi_user = cmp;
+ if (!ccid_minor_idx_alloc(&cmp->cm_idx, B_FALSE)) {
+ kmem_free(cmp, sizeof (ccid_minor_t));
+ return (ENOSPC);
+ }
+ cv_init(&cmp->cm_excl_cv, NULL, CV_DRIVER, NULL);
+ cv_init(&cmp->cm_read_cv, NULL, CV_DRIVER, NULL);
+ cv_init(&cmp->cm_iowait_cv, NULL, CV_DRIVER, NULL);
+ cmp->cm_opener = crdup(credp);
+ cmp->cm_slot = slot;
+ *devp = makedevice(getmajor(*devp), cmp->cm_idx.cmi_minor);
+
+ if ((flag & FWRITE) == FWRITE) {
+ cmp->cm_flags |= CCID_MINOR_F_WRITABLE;
+ }
+
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ list_insert_tail(&slot->cs_minors, cmp);
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+
+ return (0);
+}
+
+/*
+ * Copy a command which may have a message block chain out to the user.
+ */
+static int
+ccid_read_copyout(struct uio *uiop, const mblk_t *mp)
+{
+ offset_t off;
+
+ off = uiop->uio_loffset;
+ VERIFY3P(mp->b_next, ==, NULL);
+
+ for (; mp != NULL; mp = mp->b_cont) {
+ int ret;
+
+ if (MBLKL(mp) == 0)
+ continue;
+
+ ret = uiomove(mp->b_rptr, MBLKL(mp), UIO_READ, uiop);
+ if (ret != 0) {
+ return (EFAULT);
+ }
+ }
+
+ uiop->uio_loffset = off;
+ return (0);
+}
+
+/*
+ * Called to indicate that we are ready for a user to consume the I/O.
+ */
+static void
+ccid_user_io_done(ccid_t *ccid, ccid_slot_t *slot)
+{
+ ccid_minor_t *cmp;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ slot->cs_io.ci_flags &= ~CCID_IO_F_IN_PROGRESS;
+ slot->cs_io.ci_flags |= CCID_IO_F_DONE;
+ cmp = slot->cs_excl_minor;
+ if (cmp != NULL) {
+ ccid_slot_pollin_signal(slot);
+ cv_signal(&cmp->cm_read_cv);
+ }
+}
+
+/*
+ * This is called in a few different sitautions. It's called when an exclusive
+ * hold is being released by a user on the slot. It's also called when the ICC
+ * is removed, the reader has been unplugged, or the ICC is being reset. In all
+ * these cases we need to make sure that I/O is taken care of and we won't be
+ * leaving behind vestigial garbage.
+ */
+static void
+ccid_teardown_apdu(ccid_t *ccid, ccid_slot_t *slot, int error)
+{
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ /*
+ * If no I/O is in progress, then there's nothing to do at our end.
+ */
+ if ((slot->cs_io.ci_flags & CCID_IO_F_IN_PROGRESS) == 0) {
+ return;
+ }
+
+ slot->cs_io.ci_errno = error;
+ ccid_user_io_done(ccid, slot);
+
+ /*
+ * There is still I/O going on. We need to mark this on the slot such
+ * that no one can gain ownership of it or issue commands. This will
+ * block hand off of a slot.
+ */
+ slot->cs_flags |= CCID_SLOT_F_NEED_IO_TEARDOWN;
+}
+
+/*
+ * This function is called in response to a CCID command completing.
+ */
+static void
+ccid_complete_apdu(ccid_t *ccid, ccid_slot_t *slot, ccid_command_t *cc)
+{
+ ccid_reply_command_status_t crs;
+ ccid_reply_icc_status_t cis;
+ ccid_command_err_t cce;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+ VERIFY3P(slot->cs_io.ci_command, ==, cc);
+
+ /*
+ * This completion could be called due to the fact that a user is no
+ * longer present, but we still have outstanding work to do in the
+ * stack. As such, we need to go through and check if the flag was set
+ * on the slot during teardown and if so, clean it up now.
+ */
+ if ((slot->cs_flags & CCID_SLOT_F_NEED_IO_TEARDOWN) != 0) {
+ ccid_command_free(cc);
+ slot->cs_io.ci_command = NULL;
+ ccid_slot_io_teardown_done(slot);
+ return;
+ }
+
+ /*
+ * Process this command and figure out what we should logically be
+ * returning to the user.
+ */
+ if (cc->cc_state != CCID_COMMAND_COMPLETE) {
+ slot->cs_io.ci_errno = EIO;
+ slot->cs_flags |= CCID_SLOT_F_NEED_TXN_RESET;
+ ccid_worker_request(ccid);
+ goto consume;
+ }
+
+ ccid_command_status_decode(cc, &crs, &cis, &cce);
+ if (crs == CCID_REPLY_STATUS_COMPLETE) {
+ mblk_t *mp;
+
+ mp = cc->cc_response;
+ cc->cc_response = NULL;
+ mp->b_rptr += sizeof (ccid_header_t);
+ slot->cs_io.ci_errno = 0;
+ slot->cs_io.ci_data = mp;
+ } else if (cis == CCID_REPLY_ICC_MISSING) {
+ slot->cs_io.ci_errno = ENXIO;
+ } else {
+ /*
+ * There are a few more semantic things we can do
+ * with the errors here that we're throwing out and
+ * lumping as EIO. Oh well.
+ */
+ slot->cs_io.ci_errno = EIO;
+ }
+
+ /*
+ * Now, we can go ahead and wake up a reader to process this command.
+ */
+consume:
+ slot->cs_io.ci_command = NULL;
+ ccid_command_free(cc);
+ ccid_user_io_done(ccid, slot);
+}
+
+/*
+ * We have the user buffer in the CCID slot. Given that, transform it into
+ * something that we can send to the device. For APDU's this is simply creating
+ * a transfer command and copying it into that buffer.
+ */
+static int
+ccid_write_apdu(ccid_t *ccid, ccid_slot_t *slot)
+{
+ int ret;
+ ccid_command_t *cc;
+
+ VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
+
+ if ((ret = ccid_command_alloc(ccid, slot, B_FALSE, NULL,
+ slot->cs_io.ci_ilen, CCID_REQUEST_TRANSFER_BLOCK, 0, 0, 0,
+ &cc)) != 0) {
+ return (ret);
+ }
+
+ cc->cc_flags |= CCID_COMMAND_F_USER;
+ ccid_command_bcopy(cc, slot->cs_io.ci_ibuf, slot->cs_io.ci_ilen);
+
+ slot->cs_io.ci_command = cc;
+ mutex_exit(&ccid->ccid_mutex);
+
+ if ((ret = ccid_command_queue(ccid, cc)) != 0) {
+ mutex_enter(&ccid->ccid_mutex);
+ slot->cs_io.ci_command = NULL;
+ ccid_command_free(cc);
+ return (ret);
+ }
+
+ mutex_enter(&ccid->ccid_mutex);
+
+ return (0);
+}
+
+static int
+ccid_read(dev_t dev, struct uio *uiop, cred_t *credp)
+{
+ int ret;
+ ccid_minor_idx_t *idx;
+ ccid_minor_t *cmp;
+ ccid_slot_t *slot;
+ ccid_t *ccid;
+ boolean_t done;
+
+ if (uiop->uio_resid <= 0) {
+ return (EINVAL);
+ }
+
+ if ((idx = ccid_minor_find_user(getminor(dev))) == NULL) {
+ return (ENOENT);
+ }
+
+ cmp = idx->cmi_data.cmi_user;
+ slot = cmp->cm_slot;
+ ccid = slot->cs_ccid;
+
+ mutex_enter(&ccid->ccid_mutex);
+ if ((ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ /*
+ * First, check if we have exclusive access. If not, we're done.
+ */
+ if ((cmp->cm_flags & CCID_MINOR_F_HAS_EXCL) == 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EACCES);
+ }
+
+ /*
+ * While it's tempting to mirror ccid_write() here and check if we have
+ * a tx or rx function, that actually has no relevance on read. The only
+ * thing that matters is whether or not we actually have an I/O.
+ */
+
+ /*
+ * If there's been no write I/O issued, then this read is not allowed.
+ * While this may seem like a silly constraint, it certainly simplifies
+ * a lot of the surrounding logic and fits with the current consumer
+ * model.
+ */
+ if ((slot->cs_io.ci_flags & (CCID_IO_F_IN_PROGRESS | CCID_IO_F_DONE)) ==
+ 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENODATA);
+ }
+
+ /*
+ * If another thread is already blocked in read, then don't allow us
+ * in. We only want to allow one thread to attempt to consume a read,
+ * just as we only allow one thread to initiate a write.
+ */
+ if ((cmp->cm_flags & CCID_MINOR_F_READ_WAITING) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EBUSY);
+ }
+
+ /*
+ * Check if an I/O has completed. Once it has, call the protocol
+ * specific code. Note that the lock may be dropped after polling. In
+ * such a case we will have to logically recheck several conditions.
+ *
+ * Note, we don't really care if the slot is active or not as I/O could
+ * have been in flight while the slot was inactive.
+ */
+ while ((slot->cs_io.ci_flags & CCID_IO_F_DONE) == 0) {
+ if (uiop->uio_fmode & FNONBLOCK) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EWOULDBLOCK);
+ }
+
+ /*
+ * While we perform a cv_wait_sig() we'll end up dropping the
+ * CCID mutex. This means that we need to notify the rest of the
+ * driver that a thread is blocked in read. This is used not
+ * only for excluding multiple threads trying to read from the
+ * device, but more importantly so that we know that if the ICC
+ * or reader are removed, that we need to wake up this thread.
+ */
+ cmp->cm_flags |= CCID_MINOR_F_READ_WAITING;
+ ret = cv_wait_sig(&cmp->cm_read_cv, &ccid->ccid_mutex);
+ cmp->cm_flags &= ~CCID_MINOR_F_READ_WAITING;
+ cv_signal(&cmp->cm_iowait_cv);
+
+ if (ret == 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EINTR);
+ }
+
+ /*
+ * Check if the reader has been removed. We do not need to check
+ * for other conditions, as we'll end up being told that the I/O
+ * is done and that the error has been set.
+ */
+ if ((ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENODEV);
+ }
+ }
+
+ /*
+ * We'll either have an error or data available for the user at this
+ * point that we can copy out. We need to make sure that it's not too
+ * large. The data should have already been adjusted such that we only
+ * have data payloads.
+ */
+ done = B_FALSE;
+ if (slot->cs_io.ci_errno == 0) {
+ size_t mlen;
+
+ mlen = msgsize(slot->cs_io.ci_data);
+ if (mlen > uiop->uio_resid) {
+ ret = EOVERFLOW;
+ } else {
+ ret = ccid_read_copyout(uiop, slot->cs_io.ci_data);
+ if (ret == 0) {
+ done = B_TRUE;
+ }
+ }
+ } else {
+ ret = slot->cs_io.ci_errno;
+ done = B_TRUE;
+ }
+
+ if (done) {
+ ccid_clear_io(&slot->cs_io);
+ ccid_slot_pollout_signal(slot);
+ }
+
+ mutex_exit(&ccid->ccid_mutex);
+
+ return (ret);
+}
+
+static int
+ccid_write(dev_t dev, struct uio *uiop, cred_t *credp)
+{
+ int ret;
+ ccid_minor_idx_t *idx;
+ ccid_minor_t *cmp;
+ ccid_slot_t *slot;
+ ccid_t *ccid;
+ size_t len, cbytes;
+
+ if (uiop->uio_resid > CCID_APDU_LEN_MAX) {
+ return (E2BIG);
+ }
+
+ if (uiop->uio_resid <= 0) {
+ return (EINVAL);
+ }
+
+ len = uiop->uio_resid;
+ idx = ccid_minor_find_user(getminor(dev));
+ if (idx == NULL) {
+ return (ENOENT);
+ }
+
+ cmp = idx->cmi_data.cmi_user;
+ slot = cmp->cm_slot;
+ ccid = slot->cs_ccid;
+
+ /*
+ * Now that we have the slot, verify whether or not we can perform this
+ * I/O.
+ */
+ mutex_enter(&ccid->ccid_mutex);
+ if ((ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ /*
+ * Check that we are open for writing, have exclusive access, and
+ * there's a card present. If not, error out.
+ */
+ if ((cmp->cm_flags & (CCID_MINOR_F_WRITABLE | CCID_MINOR_F_HAS_EXCL)) !=
+ (CCID_MINOR_F_WRITABLE | CCID_MINOR_F_HAS_EXCL)) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EACCES);
+ }
+
+ if ((slot->cs_flags & CCID_SLOT_F_ACTIVE) == 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENXIO);
+ }
+
+ /*
+ * Make sure that we have a supported transmit function.
+ */
+ if (slot->cs_icc.icc_tx == NULL) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENOTSUP);
+ }
+
+ /*
+ * See if another command is in progress. If so, try to claim it.
+ * Otherwise, fail with EBUSY. Note, we only fail for commands that are
+ * user initiated. There may be other commands that are ongoing in the
+ * system.
+ */
+ if ((slot->cs_io.ci_flags & CCID_IO_F_POLLOUT_FLAGS) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EBUSY);
+ }
+
+ /*
+ * Use uiocopy and not uiomove. This way if we fail for whatever reason,
+ * we don't have to worry about restoring the original buffer.
+ */
+ if (uiocopy(slot->cs_io.ci_ibuf, len, UIO_WRITE, uiop, &cbytes) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EFAULT);
+ }
+
+ slot->cs_io.ci_ilen = len;
+ slot->cs_io.ci_flags |= CCID_IO_F_PREPARING;
+ slot->cs_io.ci_omp = NULL;
+
+ /*
+ * Now that we're here, go ahead and call the actual tx function.
+ */
+ if ((ret = slot->cs_icc.icc_tx(ccid, slot)) != 0) {
+ /*
+ * The command wasn't actually transmitted. In this case we need
+ * to reset the copied in data and signal anyone who is polling
+ * that this is writeable again. We don't have to worry about
+ * readers at this point, as they won't get in unless
+ * CCID_IO_F_IN_PROGRESS has been set.
+ */
+ slot->cs_io.ci_ilen = 0;
+ bzero(slot->cs_io.ci_ibuf, sizeof (slot->cs_io.ci_ibuf));
+ slot->cs_io.ci_flags &= ~CCID_IO_F_PREPARING;
+
+ ccid_slot_pollout_signal(slot);
+ } else {
+ slot->cs_io.ci_flags &= ~CCID_IO_F_PREPARING;
+ slot->cs_io.ci_flags |= CCID_IO_F_IN_PROGRESS;
+ uiop->uio_resid -= cbytes;
+ }
+ /*
+ * Notify a waiter that we've moved on.
+ */
+ cv_signal(&slot->cs_excl_minor->cm_iowait_cv);
+ mutex_exit(&ccid->ccid_mutex);
+
+ return (ret);
+}
+
+static int
+ccid_ioctl_status(ccid_slot_t *slot, intptr_t arg, int mode)
+{
+ uccid_cmd_status_t ucs;
+ ccid_t *ccid = slot->cs_ccid;
+
+ if (ddi_copyin((void *)arg, &ucs, sizeof (ucs), mode & FKIOCTL) != 0)
+ return (EFAULT);
+
+ if (ucs.ucs_version != UCCID_VERSION_ONE)
+ return (EINVAL);
+
+ ucs.ucs_status = 0;
+ ucs.ucs_instance = ddi_get_instance(slot->cs_ccid->ccid_dip);
+ ucs.ucs_slot = slot->cs_slotno;
+
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ if ((slot->cs_flags & CCID_SLOT_F_PRESENT) != 0)
+ ucs.ucs_status |= UCCID_STATUS_F_CARD_PRESENT;
+ if ((slot->cs_flags & CCID_SLOT_F_ACTIVE) != 0)
+ ucs.ucs_status |= UCCID_STATUS_F_CARD_ACTIVE;
+
+ if (slot->cs_atr != NULL) {
+ ucs.ucs_atrlen = MIN(UCCID_ATR_MAX, MBLKL(slot->cs_atr));
+ bcopy(slot->cs_atr->b_rptr, ucs.ucs_atr, ucs.ucs_atrlen);
+ } else {
+ bzero(ucs.ucs_atr, sizeof (ucs.ucs_atr));
+ ucs.ucs_atrlen = 0;
+ }
+
+ bcopy(&ccid->ccid_class, &ucs.ucs_class, sizeof (ucs.ucs_class));
+
+ if (ccid->ccid_dev_data->dev_product != NULL) {
+ (void) strlcpy(ucs.ucs_product,
+ ccid->ccid_dev_data->dev_product, sizeof (ucs.ucs_product));
+ ucs.ucs_status |= UCCID_STATUS_F_PRODUCT_VALID;
+ } else {
+ ucs.ucs_product[0] = '\0';
+ }
+
+ if (ccid->ccid_dev_data->dev_serial != NULL) {
+ (void) strlcpy(ucs.ucs_serial, ccid->ccid_dev_data->dev_serial,
+ sizeof (ucs.ucs_serial));
+ ucs.ucs_status |= UCCID_STATUS_F_SERIAL_VALID;
+ } else {
+ ucs.ucs_serial[0] = '\0';
+ }
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+
+ if ((slot->cs_flags & CCID_SLOT_F_ACTIVE) != 0) {
+ ucs.ucs_status |= UCCID_STATUS_F_PARAMS_VALID;
+ ucs.ucs_prot = slot->cs_icc.icc_cur_protocol;
+ ucs.ucs_params = slot->cs_icc.icc_params;
+ }
+
+ if (ddi_copyout(&ucs, (void *)arg, sizeof (ucs), mode & FKIOCTL) != 0)
+ return (EFAULT);
+
+ return (0);
+}
+
+static int
+ccid_ioctl_txn_begin(ccid_slot_t *slot, ccid_minor_t *cmp, intptr_t arg,
+ int mode)
+{
+ int ret;
+ uccid_cmd_txn_begin_t uct;
+ boolean_t nowait;
+
+ if (ddi_copyin((void *)arg, &uct, sizeof (uct), mode & FKIOCTL) != 0)
+ return (EFAULT);
+
+ if (uct.uct_version != UCCID_VERSION_ONE)
+ return (EINVAL);
+
+ if ((uct.uct_flags & ~UCCID_TXN_DONT_BLOCK) != 0)
+ return (EINVAL);
+ nowait = (uct.uct_flags & UCCID_TXN_DONT_BLOCK) != 0;
+
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ if ((cmp->cm_flags & CCID_MINOR_F_WRITABLE) == 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EBADF);
+ }
+
+ ret = ccid_slot_excl_req(slot, cmp, nowait);
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+
+ return (ret);
+}
+
+static int
+ccid_ioctl_txn_end(ccid_slot_t *slot, ccid_minor_t *cmp, intptr_t arg, int mode)
+{
+ uccid_cmd_txn_end_t uct;
+
+ if (ddi_copyin((void *)arg, &uct, sizeof (uct), mode & FKIOCTL) != 0) {
+ return (EFAULT);
+ }
+
+ if (uct.uct_version != UCCID_VERSION_ONE) {
+ return (EINVAL);
+ }
+
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ if (slot->cs_excl_minor != cmp) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EINVAL);
+ }
+ VERIFY3S(cmp->cm_flags & CCID_MINOR_F_HAS_EXCL, !=, 0);
+
+ /*
+ * Require exactly one of the flags to be set.
+ */
+ switch (uct.uct_flags) {
+ case UCCID_TXN_END_RESET:
+ cmp->cm_flags |= CCID_MINOR_F_TXN_RESET;
+
+ case UCCID_TXN_END_RELEASE:
+ break;
+
+ default:
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EINVAL);
+ }
+
+ ccid_slot_excl_rele(slot);
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+
+ return (0);
+}
+
+static int
+ccid_ioctl_fionread(ccid_slot_t *slot, ccid_minor_t *cmp, intptr_t arg,
+ int mode)
+{
+ int data;
+
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ if ((cmp->cm_flags & CCID_MINOR_F_HAS_EXCL) == 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EACCES);
+ }
+
+ if ((cmp->cm_flags & CCID_MINOR_F_WRITABLE) == 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EBADF);
+ }
+
+ if ((slot->cs_io.ci_flags & CCID_IO_F_DONE) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODATA);
+ }
+
+ /*
+ * If there's an error, claim that there's at least one byte to read
+ * even if it means we'll get the error and consume it. FIONREAD only
+ * allows up to an int of data. Realistically because we don't allow
+ * extended APDUs, the amount of data here should be always less than
+ * INT_MAX.
+ */
+ if (slot->cs_io.ci_errno != 0) {
+ data = 1;
+ } else {
+ size_t s = msgsize(slot->cs_io.ci_data);
+ data = MIN(s, INT_MAX);
+ }
+
+ if (ddi_copyout(&data, (void *)arg, sizeof (data), mode & FKIOCTL) !=
+ 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EFAULT);
+ }
+
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (0);
+}
+
+static int
+ccid_ioctl_icc_modify(ccid_slot_t *slot, ccid_minor_t *cmp, intptr_t arg,
+ int mode)
+{
+ int ret = 0;
+ uccid_cmd_icc_modify_t uci;
+ ccid_t *ccid;
+
+ if (ddi_copyin((void *)arg, &uci, sizeof (uci), mode & FKIOCTL) != 0) {
+ return (EFAULT);
+ }
+
+ if (uci.uci_version != UCCID_VERSION_ONE) {
+ return (EINVAL);
+ }
+
+ switch (uci.uci_action) {
+ case UCCID_ICC_POWER_ON:
+ case UCCID_ICC_POWER_OFF:
+ case UCCID_ICC_WARM_RESET:
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ ccid = slot->cs_ccid;
+ mutex_enter(&ccid->ccid_mutex);
+ if ((slot->cs_ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ if ((cmp->cm_flags & CCID_MINOR_F_WRITABLE) == 0) {
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+ return (EBADF);
+ }
+
+ switch (uci.uci_action) {
+ case UCCID_ICC_WARM_RESET:
+ ret = ccid_slot_warm_reset(ccid, slot);
+ break;
+
+ case UCCID_ICC_POWER_OFF:
+ ret = ccid_slot_power_off(ccid, slot);
+ break;
+
+ case UCCID_ICC_POWER_ON:
+ ret = ccid_slot_inserted(ccid, slot);
+ break;
+ }
+
+ mutex_exit(&ccid->ccid_mutex);
+
+ return (ret);
+}
+
+static int
+ccid_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+ int *rvalp)
+{
+ ccid_minor_idx_t *idx;
+ ccid_slot_t *slot;
+ ccid_minor_t *cmp;
+
+ idx = ccid_minor_find_user(getminor(dev));
+ if (idx == NULL) {
+ return (ENOENT);
+ }
+
+ cmp = idx->cmi_data.cmi_user;
+ slot = cmp->cm_slot;
+
+ switch (cmd) {
+ case UCCID_CMD_TXN_BEGIN:
+ return (ccid_ioctl_txn_begin(slot, cmp, arg, mode));
+ case UCCID_CMD_TXN_END:
+ return (ccid_ioctl_txn_end(slot, cmp, arg, mode));
+ case UCCID_CMD_STATUS:
+ return (ccid_ioctl_status(slot, arg, mode));
+ case FIONREAD:
+ return (ccid_ioctl_fionread(slot, cmp, arg, mode));
+ case UCCID_CMD_ICC_MODIFY:
+ return (ccid_ioctl_icc_modify(slot, cmp, arg, mode));
+ default:
+ break;
+ }
+
+ return (ENOTTY);
+}
+
+static int
+ccid_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
+ struct pollhead **phpp)
+{
+ short ready = 0;
+ ccid_minor_idx_t *idx;
+ ccid_minor_t *cmp;
+ ccid_slot_t *slot;
+ ccid_t *ccid;
+
+ idx = ccid_minor_find_user(getminor(dev));
+ if (idx == NULL) {
+ return (ENOENT);
+ }
+
+ cmp = idx->cmi_data.cmi_user;
+ slot = cmp->cm_slot;
+ ccid = slot->cs_ccid;
+
+ mutex_enter(&ccid->ccid_mutex);
+ if ((ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (ENODEV);
+ }
+
+ if (!(cmp->cm_flags & CCID_MINOR_F_HAS_EXCL) != 0) {
+ mutex_exit(&ccid->ccid_mutex);
+ return (EACCES);
+ }
+
+ /*
+ * If the CCID_IO_F_DONE flag is set, then we're always
+ * readable. However, flags are insufficient to be writeable.
+ */
+ if ((slot->cs_io.ci_flags & CCID_IO_F_DONE) != 0) {
+ ready |= POLLIN | POLLRDNORM;
+ } else if ((slot->cs_flags & CCID_SLOT_F_ACTIVE) != 0 &&
+ (slot->cs_io.ci_flags & CCID_IO_F_POLLOUT_FLAGS) == 0 &&
+ slot->cs_icc.icc_tx != NULL) {
+ ready |= POLLOUT;
+ }
+
+ if ((slot->cs_flags & CCID_SLOT_F_PRESENT) == 0) {
+ ready |= POLLHUP;
+ }
+
+ *reventsp = ready & events;
+ if ((*reventsp == 0 && !anyyet) || (events & POLLET)) {
+ *phpp = &cmp->cm_pollhead;
+ }
+
+ mutex_exit(&ccid->ccid_mutex);
+
+ return (0);
+}
+
+static int
+ccid_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+ ccid_minor_idx_t *idx;
+ ccid_minor_t *cmp;
+ ccid_slot_t *slot;
+
+ idx = ccid_minor_find_user(getminor(dev));
+ if (idx == NULL) {
+ return (ENOENT);
+ }
+
+ /*
+ * First tear down the global index entry.
+ */
+ cmp = idx->cmi_data.cmi_user;
+ slot = cmp->cm_slot;
+ ccid_minor_idx_free(idx);
+
+ /*
+ * If the minor node was closed without an explicit transaction end,
+ * then we need to assume that the reader's ICC is in an arbitrary
+ * state. For example, the ICC could have a specific PIV applet
+ * selected. In such a case, the only safe thing to do is to force a
+ * reset.
+ */
+ mutex_enter(&slot->cs_ccid->ccid_mutex);
+ if ((cmp->cm_flags & CCID_MINOR_F_HAS_EXCL) != 0) {
+ cmp->cm_flags |= CCID_MINOR_F_TXN_RESET;
+ ccid_slot_excl_rele(slot);
+ }
+
+ list_remove(&slot->cs_minors, cmp);
+ mutex_exit(&slot->cs_ccid->ccid_mutex);
+
+ pollhead_clean(&cmp->cm_pollhead);
+ ccid_minor_free(cmp);
+
+ return (0);
+}
+
+static struct cb_ops ccid_cb_ops = {
+ ccid_open, /* cb_open */
+ ccid_close, /* cb_close */
+ nodev, /* cb_strategy */
+ nodev, /* cb_print */
+ nodev, /* cb_dump */
+ ccid_read, /* cb_read */
+ ccid_write, /* cb_write */
+ ccid_ioctl, /* cb_ioctl */
+ nodev, /* cb_devmap */
+ nodev, /* cb_mmap */
+ nodev, /* cb_segmap */
+ ccid_chpoll, /* cb_chpoll */
+ ddi_prop_op, /* cb_prop_op */
+ NULL, /* cb_stream */
+ D_MP, /* cb_flag */
+ CB_REV, /* cb_rev */
+ nodev, /* cb_aread */
+ nodev /* cb_awrite */
+};
+
+static struct dev_ops ccid_dev_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* devo_refcnt */
+ ccid_getinfo, /* devo_getinfo */
+ nulldev, /* devo_identify */
+ nulldev, /* devo_probe */
+ ccid_attach, /* devo_attach */
+ ccid_detach, /* devo_detach */
+ nodev, /* devo_reset */
+ &ccid_cb_ops, /* devo_cb_ops */
+ NULL, /* devo_bus_ops */
+ NULL, /* devo_power */
+ ddi_quiesce_not_supported /* devo_quiesce */
+};
+
+static struct modldrv ccid_modldrv = {
+ &mod_driverops,
+ "USB CCID",
+ &ccid_dev_ops
+};
+
+static struct modlinkage ccid_modlinkage = {
+ MODREV_1,
+ { &ccid_modldrv, NULL }
+};
+
+int
+_init(void)
+{
+ int ret;
+
+ if ((ret = ddi_soft_state_init(&ccid_softstate, sizeof (ccid_t),
+ 0)) != 0) {
+ return (ret);
+ }
+
+ if ((ccid_minors = id_space_create("ccid_minors", CCID_MINOR_MIN,
+ INT_MAX)) == NULL) {
+ ddi_soft_state_fini(&ccid_softstate);
+ return (ret);
+ }
+
+ if ((ret = mod_install(&ccid_modlinkage)) != 0) {
+ id_space_destroy(ccid_minors);
+ ccid_minors = NULL;
+ ddi_soft_state_fini(&ccid_softstate);
+ return (ret);
+ }
+
+ mutex_init(&ccid_idxlock, NULL, MUTEX_DRIVER, NULL);
+ avl_create(&ccid_idx, ccid_idx_comparator, sizeof (ccid_minor_idx_t),
+ offsetof(ccid_minor_idx_t, cmi_avl));
+
+ return (ret);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&ccid_modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+ int ret;
+
+ if ((ret = mod_remove(&ccid_modlinkage)) != 0) {
+ return (ret);
+ }
+
+ avl_destroy(&ccid_idx);
+ mutex_destroy(&ccid_idxlock);
+ id_space_destroy(ccid_minors);
+ ccid_minors = NULL;
+ ddi_soft_state_fini(&ccid_softstate);
+
+ return (ret);
+}
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index be0d18449b..9dcb787870 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -1026,6 +1026,10 @@ USBWCMHDRS= \
UGENHDRS= \
usb_ugen.h
+USBCCIDHDRS = \
+ ccid.h \
+ uccid.h
+
HOTPLUGHDRS= \
hpcsvc.h \
hpctrl.h
@@ -1172,6 +1176,7 @@ CHECKHDRS= \
$(SYSEVENTHDRS:%.h=sysevent/%.check) \
$(CONTRACTHDRS:%.h=contract/%.check) \
$(USBAUDHDRS:%.h=usb/clients/audio/%.check) \
+ $(USBCCIDHDRS:%.h=usb/clients/ccid/%.check) \
$(USBHUBDHDRS:%.h=usb/hubd/%.check) \
$(USBHIDHDRS:%.h=usb/clients/hid/%.check) \
$(USBMSHDRS:%.h=usb/clients/mass_storage/%.check) \
@@ -1246,6 +1251,7 @@ CHECKHDRS= \
$(ROOTUSBCDCHDRS) \
$(ROOTUSBVIDHDRS) \
$(ROOTUSBWCMHDRS) \
+ $(ROOTUSBCCIDHDRS) \
$(ROOTUGENHDRS) \
$(ROOT1394HDRS) \
$(ROOTHOTPLUGHDRS) \
@@ -1304,6 +1310,7 @@ install_h: \
$(ROOTUWBHDRS) \
$(ROOTUWBAHDRS) \
$(ROOTUSBHDRS) \
+ $(ROOTUSBCCIDHDRS) \
$(ROOTUSBAUDHDRS) \
$(ROOTUSBHUBDHDRS) \
$(ROOTUSBHIDHDRS) \
diff --git a/usr/src/uts/common/sys/Makefile.syshdrs b/usr/src/uts/common/sys/Makefile.syshdrs
index dee5eef53a..54a89a5e9a 100644
--- a/usr/src/uts/common/sys/Makefile.syshdrs
+++ b/usr/src/uts/common/sys/Makefile.syshdrs
@@ -23,6 +23,7 @@
# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
# Copyright 2014 Garrett D'Amore <garrett@damore.org>
# Copyright 2016 Nexenta Systems, Inc.
+# Copyright 2019 Joyent, Inc.
#
# Common definitions for open and closed headers.
@@ -149,6 +150,9 @@ usb/clients/mass_storage/%.check: usb/clients/mass_storage/%.h
usb/clients/printer/%.check: usb/clients/printer/%.h
$(DOT_H_CHECK)
+usb/clients/ccid/%.check: usb/clients/ccid/%.h
+ $(DOT_H_CHECK)
+
usb/clients/usbcdc/%.check: usb/clients/usbcdc/%.h
$(DOT_H_CHECK)
@@ -231,6 +235,7 @@ ROOTDIRS= \
$(ROOTDIR)/usb/clients/usbcdc \
$(ROOTDIR)/usb/clients/video/usbvc \
$(ROOTDIR)/usb/clients/usbinput/usbwcm \
+ $(ROOTDIR)/usb/clients/ccid \
$(ROOTDIR)/usb/clients/ugen \
$(ROOTDIR)/1394 \
$(ROOTDIR)/rsm \
@@ -311,6 +316,7 @@ ROOTUSBPRNHDRS= $(USBPRNHDRS:%=$(ROOTDIR)/usb/clients/printer/%)
ROOTUSBCDCHDRS= $(USBCDCHDRS:%=$(ROOTDIR)/usb/clients/usbcdc/%)
ROOTUSBVIDHDRS= $(USBVIDHDRS:%=$(ROOTDIR)/usb/clients/video/usbvc/%)
ROOTUSBWCMHDRS= $(USBWCMHDRS:%=$(ROOTDIR)/usb/clients/usbinput/usbwcm/%)
+ROOTUSBCCIDHDRS= $(USBCCIDHDRS:%=$(ROOTDIR)/usb/clients/ccid/%)
ROOTUGENHDRS= $(UGENHDRS:%=$(ROOTDIR)/usb/clients/ugen/%)
ROOT1394HDRS= $(I1394HDRS:%=$(ROOTDIR)/1394/%)
diff --git a/usr/src/uts/common/sys/sunddi.h b/usr/src/uts/common/sys/sunddi.h
index 6d5f4304b4..c15f2fde70 100644
--- a/usr/src/uts/common/sys/sunddi.h
+++ b/usr/src/uts/common/sys/sunddi.h
@@ -247,6 +247,8 @@ extern "C" {
/* Fabric Devices */
#define DDI_NT_IB_ATTACHMENT_POINT "ddi_ctl:attachment_point:ib"
/* IB devices */
+#define DDI_NT_CCID_ATTACHMENT_POINT "ddi_ctl:attachment_point:ccid"
+ /* CCID devices */
#define DDI_NT_AV_ASYNC "ddi_av:async" /* asynchronous AV device */
#define DDI_NT_AV_ISOCH "ddi_av:isoch" /* isochronous AV device */
diff --git a/usr/src/uts/common/sys/usb/clients/ccid/ccid.h b/usr/src/uts/common/sys/usb/clients/ccid/ccid.h
new file mode 100644
index 0000000000..c5ed9ad1f2
--- /dev/null
+++ b/usr/src/uts/common/sys/usb/clients/ccid/ccid.h
@@ -0,0 +1,311 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+#ifndef _SYS_USB_CCID_H
+#define _SYS_USB_CCID_H
+
+/*
+ * CCID class driver definitions.
+ */
+
+#include <sys/stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Values for various Hardware, Mechanical, and Pin features. These come from
+ * the device's class descriptor.
+ */
+typedef enum ccid_class_voltage {
+ CCID_CLASS_VOLT_AUTO = 0x00,
+ CCID_CLASS_VOLT_5_0 = 0x01,
+ CCID_CLASS_VOLT_3_0 = 0x02,
+ CCID_CLASS_VOLT_1_8 = 0x04
+} ccid_class_voltage_t;
+
+typedef enum ccid_class_mechanical {
+ CCID_CLASS_MECH_CARD_ACCEPT = 0x01,
+ CCID_CLASS_MECH_CARD_EJECT = 0x02,
+ CCID_CLASS_MECH_CARD_CAPTURE = 0x04,
+ CCID_CLASS_MECH_CARD_LOCK = 0x08
+} ccid_class_mechanical_t;
+
+typedef enum ccid_class_features {
+ CCID_CLASS_F_AUTO_PARAM_ATR = 0x00000002,
+ CCID_CLASS_F_AUTO_ICC_ACTIVATE = 0x00000004,
+ CCID_CLASS_F_AUTO_ICC_VOLTAGE = 0x00000008,
+ CCID_CLASS_F_AUTO_ICC_CLOCK = 0x00000010,
+ CCID_CLASS_F_AUTO_BAUD = 0x00000020,
+ CCID_CLASS_F_AUTO_PARAM_NEG = 0x00000040,
+ CCID_CLASS_F_AUTO_PPS = 0x00000080,
+ CCID_CLASS_F_ICC_CLOCK_STOP = 0x00000100,
+ CCID_CLASS_F_ALTNAD_SUP = 0x00000200,
+ CCID_CLASS_F_AUTO_IFSD = 0x00000400,
+ CCID_CLASS_F_TPDU_XCHG = 0x00010000,
+ CCID_CLASS_F_SHORT_APDU_XCHG = 0x00020000,
+ CCID_CLASS_F_EXT_APDU_XCHG = 0x00040000,
+ CCID_CLASS_F_WAKE_UP = 0x00100000
+} ccid_class_features_t;
+
+typedef enum ccid_class_pin {
+ CCID_CLASS_PIN_VERIFICATION = 0x01,
+ CCID_CLASS_PIN_MODIFICATION = 0x02
+} ccid_class_pin_t;
+
+/*
+ * CCID Class Descriptor
+ *
+ * This structure represents the CCID class descriptor. Note, it should not be a
+ * packed structure. This is designed to be a native representation. The raw
+ * structure will be parsed into this instead.
+ */
+typedef struct ccid_class_descr {
+ uint8_t ccd_bLength;
+ uint8_t ccd_bDescriptorType;
+ uint16_t ccd_bcdCCID;
+ uint8_t ccd_bMaxSlotIndex;
+ uint8_t ccd_bVoltageSupport;
+ uint32_t ccd_dwProtocols;
+ uint32_t ccd_dwDefaultClock;
+ uint32_t ccd_dwMaximumClock;
+ uint8_t ccd_bNumClockSupported;
+ uint32_t ccd_dwDataRate;
+ uint32_t ccd_dwMaxDataRate;
+ uint8_t ccd_bNumDataRatesSupported;
+ uint32_t ccd_dwMaxIFSD;
+ uint32_t ccd_dwSyncProtocols;
+ uint32_t ccd_dwMechanical;
+ uint32_t ccd_dwFeatures;
+ uint32_t ccd_dwMaxCCIDMessageLength;
+ uint8_t ccd_bClassGetResponse;
+ uint8_t ccd_bClassEnvelope;
+ uint16_t ccd_wLcdLayout;
+ uint8_t ccd_bPinSupport;
+ uint8_t ccd_bMaxCCIDBusySlots;
+} ccid_class_descr_t;
+
+/*
+ * Definitions for the supported versions of the CCID specification. The version
+ * is encoded in binary encoded decimal. The major version is in the upper 8
+ * bits and the minor version is in the lower 8 bits. We currently check for the
+ * major version to match.
+ */
+#define CCID_VERSION_MAJOR(ver) (((ver) & 0xff00) >> 8)
+#define CCID_VERSION_MINOR(ver) ((ver) & 0x00ff)
+#define CCID_VERSION_ONE 0x01
+
+/*
+ * This structure is used as the data for the CCID_REQUEST_SET_PARAMS request
+ * and the CCID_RESPONSE_PARAMETERS response. There are different structures for
+ * T=0 and T=1. These come from CCID r1.1 / Section 6.1.7.
+ */
+typedef struct ccid_params_t0 {
+ uint8_t cp0_bmFindexDindex;
+ uint8_t cp0_bmTCCKST0;
+ uint8_t cp0_bGuardTimeT0;
+ uint8_t cp0_bWaitingIntegerT0;
+ uint8_t cp0_bClockStop;
+} __packed ccid_params_t0_t;
+
+#define CCID_P_TCCKST0_DIRECT 0x00
+#define CCID_P_TCCKST0_INVERSE 0x02
+
+typedef struct ccid_params_t1 {
+ uint8_t cp1_bmFindexDindex;
+ uint8_t cp1_bmTCCKST1;
+ uint8_t cp1_bGuardTimeT1;
+ uint8_t cp1_bmWaitingIntegersT1;
+ uint8_t cp1_bClockStop;
+ uint8_t cp1_bIFSC;
+ uint8_t cp1_bNadValue;
+} __packed ccid_params_t1_t;
+
+typedef union ccid_params {
+ ccid_params_t0_t ccp_t0;
+ ccid_params_t1_t ccp_t1;
+} ccid_params_t;
+
+#define CCID_P_FI_DI(fi, di) ((((fi) & 0x0f) << 4) | ((di) & 0x0f))
+
+/*
+ * Everything below this point is reserved for the kernel.
+ */
+#ifdef _KERNEL
+
+/*
+ * These values come from CCID r1.1.0 Table 5.1-1 'Smart Card Device
+ * Descriptors'
+ */
+#define CCID_DESCR_TYPE 0x21
+#define CCID_DESCR_LENGTH 0x36
+
+
+/*
+ * Minimum and maximum value for a sequence number in the CCID specification.
+ * The sequence is a 1 byte unsigned value. The values are inclusive. We reserve
+ * the value of 0x00 so that we can use it as a sentinel in the ccid_command_t
+ * structure to know when we should or shouldn't free a command structure's
+ * sequence number back to the id space.
+ */
+#define CCID_SEQ_MIN 0x01
+#define CCID_SEQ_MAX UINT8_MAX
+
+
+/*
+ * All structures from the specification must be packed.
+ */
+
+/*
+ * Interrupt-IN messages codes.
+ */
+typedef enum ccid_intr_code {
+ CCID_INTR_CODE_SLOT_CHANGE = 0x50,
+ CCID_INTR_CODE_HW_ERROR = 0x51
+} ccid_intr_code_t;
+
+typedef enum ccid_intr_hwerr_code {
+ CCID_INTR_HWERR_OVERCURRENT = 0x01
+} ccid_intr_hwerr_code_t;
+
+typedef struct ccid_intr_slot {
+ uint8_t cis_type;
+ uint8_t cis_state[];
+} ccid_intr_slot_t;
+
+typedef struct ccid_intr_hwerr {
+ uint8_t cih_type;
+ uint8_t cih_slot;
+ uint8_t cih_seq;
+ uint8_t cih_code;
+} ccid_intr_hwerr_t;
+
+/*
+ * Message request codes. These codes are based on CCID r1.1.0 Table 6.1-1
+ * 'Summary of Bulk-Out Messages'. The name from the standard is to the right of
+ * the enum.
+ */
+typedef enum ccid_request_code {
+ CCID_REQUEST_POWER_ON = 0x62, /* PC_to_RDR_IccPowerOn */
+ CCID_REQUEST_POWER_OFF = 0x63, /* PC_to_RDR_IccPowerOff */
+ CCID_REQUEST_SLOT_STATUS = 0x65, /* PC_to_RDR_GetSlotStatus */
+ CCID_REQUEST_TRANSFER_BLOCK = 0x6f, /* PC_to_RDR_XfrBlock */
+ CCID_REQUEST_GET_PARAMS = 0x6c, /* PC_to_RDR_GetParameters */
+ CCID_REQUEST_RESET_PARAMS = 0x6d, /* PC_to_RDR_ResetParameters */
+ CCID_REQUEST_SET_PARAMS = 0x61, /* PC_to_RDR_SetParameters */
+ CCID_REQUEST_ESCAPE = 0x6b, /* PC_to_RDR_Escape */
+ CCID_REQUEST_ICC_CLOCK = 0x6e, /* PC_to_RDR_IccClock */
+ CCID_REQUEST_T0APDU = 0x6a, /* PC_to_RDR_T0APDU */
+ CCID_REQUEST_SECURE = 0x69, /* PC_to_RDR_Secure */
+ CCID_REQUEST_MECHANICAL = 0x71, /* PC_to_RDR_Mechanica */
+ CCID_REQEUST_ABORT = 0x72, /* PC_to_RDR_Abort */
+ CCID_REQUEST_DATA_CLOCK = 0x73 /* PC_to_RDR_SetDataRateAnd */
+ /* ClockFrequency */
+} ccid_request_code_t;
+
+/*
+ * Message request codes. These codes are based on CCID r1.1.0 Table 6.2-1
+ * 'Summary of Bulk-In Messages'. The name from the standard is to the right of
+ * the enum.
+ */
+typedef enum ccid_response_code {
+ CCID_RESPONSE_DATA_BLOCK = 0x80, /* RDR_to_PC_DataBlock */
+ CCID_RESPONSE_SLOT_STATUS = 0x81, /* RDR_to_PC_SlotStatus */
+ CCID_RESPONSE_PARAMETERS = 0x82, /* RDR_to_PC_Parameters */
+ CCID_RESPONSE_ESCAPE = 0x83, /* RDR_to_PC_Escape */
+ CCID_RESPONSE_DATA_CLOCK = 0x84 /* RDR_to_PC_DataRateAnd */
+ /* ClockFrequency */
+} ccid_response_code_t;
+
+/*
+ * This represents the CCID command header that is used for every request and
+ * response.
+ */
+typedef struct ccid_header {
+ uint8_t ch_mtype;
+ uint32_t ch_length; /* Length of ch_data in bytes */
+ uint8_t ch_slot; /* CCID slot to target */
+ uint8_t ch_seq; /* Request/Response sequence num */
+ uint8_t ch_param0; /* Request/Response specific */
+ uint8_t ch_param1; /* Request/Response specific */
+ uint8_t ch_param2; /* Request/Response specific */
+ uint8_t ch_data[]; /* Optional Request/Response Data */
+} __packed ccid_header_t;
+
+/*
+ * This structure is used as the data for the CCID_REQUEST_DATA_CLOCK and
+ * CCID_RESPONSE_DATA_CLOCK commands.
+ */
+typedef struct ccid_data_clock {
+ uint32_t cdc_clock;
+ uint32_t cdc_data;
+} __packed ccid_data_clock_t;
+
+/*
+ * Macros and constants to take apart the slot status (in ch_param1) when a CCID
+ * reply comes in.
+ */
+#define CCID_REPLY_ICC(x) (x & 0x3)
+#define CCID_REPLY_STATUS(x) ((x & 0xc0) >> 6)
+
+typedef enum {
+ CCID_REPLY_ICC_ACTIVE = 0,
+ CCID_REPLY_ICC_INACTIVE,
+ CCID_REPLY_ICC_MISSING
+} ccid_reply_icc_status_t;
+
+typedef enum {
+ CCID_REPLY_STATUS_COMPLETE = 0,
+ CCID_REPLY_STATUS_FAILED,
+ CCID_REPLY_STATUS_MORE_TIME
+} ccid_reply_command_status_t;
+
+/*
+ * Errors that are defined based when commands fail. These are based on CCID
+ * r.1.1.0 Table 6.2-2 'Slot error register when bmCommandStatus = 1'.
+ */
+typedef enum ccid_command_err {
+ CCID_ERR_CMD_ABORTED = 0xff,
+ CCID_ERR_ICC_MUTE = 0xfe,
+ CCID_ERR_XFR_PARITY_ERROR = 0xfd,
+ CCID_ERR_XFR_OVERRUN = 0xfc,
+ CCID_ERR_HW_ERROR = 0xfb,
+ CCID_ERR_BAD_ATR_TS = 0xf8,
+ CCID_ERR_BAD_ATR_TCK = 0xf7,
+ CCID_ERR_ICC_PROTOCOL_NOT_SUPPORTED = 0xf6,
+ CCID_ERR_ICC_CLASS_NOT_SUPPORTED = 0xf5,
+ CCID_ERR_PROCEDURE_BYTE_CONFLICT = 0xf4,
+ CCID_ERR_DEACTIVATED_PROTOCOL = 0xf3,
+ CCID_ERR_BUSY_WITH_AUTO_SEQUENCE = 0xf2,
+ CCID_ERR_PIN_TIMEOUT = 0xf0,
+ CCID_ERR_PIN_CANCELLED = 0xef,
+ CCID_ERR_CMD_SLOT_BUSY = 0xe0,
+ CCID_ERR_CMD_NOT_SUPPORTED = 0x00
+} ccid_command_err_t;
+
+/*
+ * Maximum size of an APDU (application data unit) payload. There are both short
+ * and extended ADPUs. At this time, we only support the short ADPUs.
+ */
+#define CCID_APDU_LEN_MAX 261
+
+#endif /* _KERNEL */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SYS_USB_CCID_H */
diff --git a/usr/src/uts/common/sys/usb/clients/ccid/uccid.h b/usr/src/uts/common/sys/usb/clients/ccid/uccid.h
new file mode 100644
index 0000000000..f016df3879
--- /dev/null
+++ b/usr/src/uts/common/sys/usb/clients/ccid/uccid.h
@@ -0,0 +1,133 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source. A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2019, Joyent, Inc.
+ */
+
+#ifndef _SYS_USB_UCCID_H
+#define _SYS_USB_UCCID_H
+
+/*
+ * Definitions for the userland CCID interface.
+ */
+
+#include <sys/types.h>
+#include <sys/usb/clients/ccid/ccid.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * The maximum size of a normal APDU. This is the upper bound of what a user can
+ * read or write to a given card.
+ */
+#define UCCID_APDU_SIZE_MAX 261
+
+/*
+ * This is the maximum length of an ATR as per ISO/IEC 7816-3:2006.
+ */
+#define UCCID_ATR_MAX 33
+
+
+#define UCCID_IOCTL (('u' << 24) | ('c' << 16) | ('d') << 8)
+
+#define UCCID_VERSION_ONE 1
+#define UCCID_CURRENT_VERSION UCCID_VERSION_ONE
+
+/*
+ * Attempt to obtain exclusive access. If the UCN_TXN_DONT_BLOCK flag is
+ * specified, the ioctl will return immediately if exclusive access cannot be
+ * gained. Otherwise, it will block in an interruptible fashion. The argument is
+ * a uccid_cmd_txn_begin_t.
+ */
+#define UCCID_CMD_TXN_BEGIN (UCCID_IOCTL | 0x01)
+#define UCCID_TXN_DONT_BLOCK 0x01
+
+typedef struct uccid_cmd_txn_begin {
+ uint32_t uct_version;
+ uint32_t uct_flags;
+} uccid_cmd_txn_begin_t;
+
+/*
+ * Relinquish exclusive access. Takes a uccid_cmd_txn_end_t. The callers should
+ * specify one of UCCID_TXN_END_RESET or UCCID_TXN_END_RELEASE. These indicate
+ * what behavior should be taken when we release the transaction. It is
+ * considered an error if neither is specified. If the caller exits without
+ * calling this function, then the ICC will be reset.
+ */
+#define UCCID_CMD_TXN_END (UCCID_IOCTL | 0x02)
+#define UCCID_TXN_END_RESET 0x01
+#define UCCID_TXN_END_RELEASE 0x02
+
+typedef struct uccid_cmd_txn_end {
+ uint32_t uct_version;
+ uint32_t uct_flags;
+} uccid_cmd_txn_end_t;
+
+/*
+ * Obtain the status of the slot. Returns a filled-in uccid_cmd_status_t.
+ */
+#define UCCID_CMD_STATUS (UCCID_IOCTL | 0x3)
+
+/*
+ * Protocol definitions. This should match common/ccid/atr.h.
+ */
+typedef enum {
+ UCCID_PROT_T0 = 1 << 0,
+ UCCID_PROT_T1 = 1 << 1
+} uccid_prot_t;
+
+/*
+ * Bits for UCS Status
+ */
+#define UCCID_STATUS_F_CARD_PRESENT 0x01
+#define UCCID_STATUS_F_CARD_ACTIVE 0x02
+#define UCCID_STATUS_F_PRODUCT_VALID 0x04
+#define UCCID_STATUS_F_SERIAL_VALID 0x08
+#define UCCID_STATUS_F_PARAMS_VALID 0x10
+
+typedef struct uccid_cmd_status {
+ uint32_t ucs_version;
+ uint32_t ucs_status;
+ int32_t ucs_instance;
+ uint32_t ucs_slot;
+ uint8_t ucs_atr[UCCID_ATR_MAX];
+ uint8_t ucs_atrlen;
+ uint8_t ucs_pad[6];
+ int8_t ucs_product[256];
+ int8_t ucs_serial[256];
+ ccid_class_descr_t ucs_class;
+ uccid_prot_t ucs_prot;
+ ccid_params_t ucs_params;
+} uccid_cmd_status_t;
+
+/*
+ * Modify the state of the ICC, if present.
+ */
+#define UCCID_CMD_ICC_MODIFY (UCCID_IOCTL | 0x04)
+
+#define UCCID_ICC_POWER_ON 0x01
+#define UCCID_ICC_POWER_OFF 0x02
+#define UCCID_ICC_WARM_RESET 0x03
+
+typedef struct uccid_cmd_icc_modify {
+ uint32_t uci_version;
+ uint32_t uci_action;
+} uccid_cmd_icc_modify_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* _SYS_USB_UCCID_H */
diff --git a/usr/src/uts/intel/Makefile.intel b/usr/src/uts/intel/Makefile.intel
index 6bc088bfd2..ec27c74a7d 100644
--- a/usr/src/uts/intel/Makefile.intel
+++ b/usr/src/uts/intel/Makefile.intel
@@ -505,6 +505,7 @@ DRV_KMODS += ses
#
# USB specific modules
#
+DRV_KMODS += ccid
DRV_KMODS += hid
DRV_KMODS += hubd
DRV_KMODS += uhci
diff --git a/usr/src/uts/intel/ccid/Makefile b/usr/src/uts/intel/ccid/Makefile
new file mode 100644
index 0000000000..a7c8b96d70
--- /dev/null
+++ b/usr/src/uts/intel/ccid/Makefile
@@ -0,0 +1,42 @@
+#
+# This file and its contents are supplied under the terms of the
+# Common Development and Distribution License ("CDDL"), version 1.0.
+# You may only use this file in accordance with the terms of version
+# 1.0 of the CDDL.
+#
+# A full copy of the text of the CDDL should have accompanied this
+# source. A copy of the CDDL is also available via the Internet at
+# http://www.illumos.org/license/CDDL.
+#
+
+#
+# Copyright 2019, Joyent, Inc.
+#
+
+UTSBASE = ../..
+
+MODULE = ccid
+OBJECTS = $(CCID_OBJS:%=$(OBJS_DIR)/%)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+
+include $(UTSBASE)/intel/Makefile.intel
+
+ALL_TARGET = $(BINARY)
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE)
+CPPFLAGS += -I$(SRC)/common/ccid
+
+LDFLAGS += -dy -N misc/usba
+
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+install: $(INSTALL_DEPS)
+
+include $(UTSBASE)/intel/Makefile.targ