diff options
Diffstat (limited to 'usr')
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, ¶msp->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, ¶msp->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 |