#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>
#define CCID_NUM_ASYNC_REQS 2
#define CCID_BULK_NALLOCED 16
#define CCID_BULK_OUT_TIMEOUT 5
#define CCID_BULK_IN_TIMEOUT 5
#define CCID_INTR_RESPONSE_SIZE 65
#define CCID_MINOR_MIN 1
#define CCID_MINOR_MAX MAXMIN32
#define CCID_MINOR_INVALID 0
#define CCID_MIN_MESSAGE_LENGTH 64
struct ccid;
struct ccid_slot;
struct ccid_minor;
struct ccid_command;
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;
cred_t *cm_opener;
struct ccid_slot *cm_slot;
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;
typedef enum ccid_io_flags {
CCID_IO_F_PREPARING = 1 << 0,
CCID_IO_F_IN_PROGRESS = 1 << 1,
CCID_IO_F_DONE = 1 << 2,
} ccid_io_flags_t;
#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;
uint_t cs_slotno;
struct ccid *cs_ccid;
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;
usb_ep_xdescr_t ccid_bulkin_xdesc;
usb_pipe_handle_t ccid_bulkin_pipe;
usb_ep_xdescr_t ccid_bulkout_xdesc;
usb_pipe_handle_t ccid_bulkout_pipe;
usb_ep_xdescr_t ccid_intrin_xdesc;
usb_pipe_handle_t ccid_intrin_pipe;
usb_pipe_handle_t ccid_control_pipe;
uint_t ccid_nslots;
size_t ccid_bufsize;
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;
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;
static void *ccid_softstate;
static kmutex_t ccid_idxlock;
static avl_tree_t ccid_idx;
static id_space_t *ccid_minors;
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));
}
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 (slot->cs_icc.icc_teardown != NULL) {
slot->cs_icc.icc_teardown(ccid, slot, ECANCELED);
}
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);
}
cmp->cm_flags &= ~CCID_MINOR_F_HAS_EXCL;
slot->cs_excl_minor = NULL;
if ((slot->cs_io.ci_flags & CCID_IO_F_DONE) != 0)
ccid_clear_io(&slot->cs_io);
pollwakeup(&cmp->cm_pollhead, POLLERR);
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 (nosleep && slot->cs_excl_minor != NULL &&
(slot->cs_flags & CCID_SLOT_F_NOEXCL_MASK) == 0) {
return (EBUSY);
}
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) {
list_remove(&slot->cs_excl_waiters, cmp);
cmp->cm_flags &= ~CCID_MINOR_F_WAITING;
ccid_slot_excl_maybe_signal(slot);
return (EINTR);
}
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);
}
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);
}
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);
}
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);
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);
}
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);
}
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);
VERIFY3P(ubrp, ==, ccid->ccid_bulkin_dispatched);
ccid->ccid_bulkin_dispatched = NULL;
if ((cc = list_head(&ccid->ccid_command_queue)) == NULL) {
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 (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;
}
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 (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;
}
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;
}
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;
}
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);
VERIFY3P(ubrp, ==, ccid->ccid_bulkin_dispatched);
ccid->ccid_bulkin_dispatched = NULL;
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);
}
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);
}
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);
}
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;
if (cc->cc_state > CCID_COMMAND_QUEUED) {
return;
}
ccid_command_state_transition(cc, CCID_COMMAND_DISPATCHED);
cc->cc_dispatch_time = gethrtime();
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) {
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);
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);
}
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();
ret = ccid_bulkin_schedule(ccid);
if (ret != USB_SUCCESS) {
ccid_command_transport_error(cc, ret, USB_CR_OK);
}
mutex_exit(&ccid->ccid_mutex);
}
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));
}
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;
}
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_LAZY;
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;
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;
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);
}
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);
}
#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;
}
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) {
ret = ENOTSUP;
} else {
ret = EIO;
}
} else {
size_t len;
len = ccid_command_resp_length(cc);
if (len == 0) {
ret = EINVAL;
goto done;
}
#ifdef DEBUG
size_t mlen = msgsize(cc->cc_response);
VERIFY3U(mlen, >=, len + sizeof (ccid_header_t));
#endif
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;
}
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;
if (hwerr->cih_slot >= ccid->ccid_nslots) {
ccid->ccid_stats.cst_intr_inval++;
return;
}
slot = &ccid->ccid_slots[hwerr->cih_slot];
if (hwerr->cih_code != CCID_INTR_HWERR_OVERCURRENT) {
ccid->ccid_stats.cst_intr_inval++;
return;
}
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);
}
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);
}
}
static void
ccid_slot_io_teardown(ccid_t *ccid, ccid_slot_t *slot)
{
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);
}
}
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);
ccid_slot_teardown(ccid, slot, B_TRUE);
}
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;
}
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:
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;
}
if (slot->cs_icc.icc_tx == NULL) {
ccid_error(ccid, "!CCID does not support I/O transfers to ICC");
}
}
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;
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);
}
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 (ret == ENXIO) {
mutex_enter(&ccid->ccid_mutex);
slot->cs_flags &= ~CCID_SLOT_F_PRESENT;
return (ret);
}
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);
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;
if ((ccid->ccid_class.ccd_dwFeatures &
(CCID_CLASS_F_AUTO_ICC_ACTIVATE | CCID_CLASS_F_AUTO_ICC_VOLTAGE)) ==
0) {
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);
ret = ccid_slot_power_off(ccid, slot);
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);
}
(void) ccid_slot_inserted(ccid, slot);
return (B_TRUE);
}
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;
}
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 ((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;
ccid_slot_excl_maybe_signal(slot);
}
}
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);
}
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;
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);
}
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);
}
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);
}
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);
}
feat = ccid->ccid_class.ccd_dwFeatures;
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");
}
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;
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);
}
}
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;
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];
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);
ccid->ccid_flags |= CCID_F_DISCONNECTED;
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);
}
}
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);
}
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);
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;
}
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;
}
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;
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;
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 ((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;
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);
}
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);
}
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);
}
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);
}
}
static void
ccid_teardown_apdu(ccid_t *ccid, ccid_slot_t *slot, int error)
{
VERIFY(MUTEX_HELD(&ccid->ccid_mutex));
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);
slot->cs_flags |= CCID_SLOT_F_NEED_IO_TEARDOWN;
}
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);
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;
}
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 {
slot->cs_io.ci_errno = EIO;
}
consume:
slot->cs_io.ci_command = NULL;
ccid_command_free(cc);
ccid_user_io_done(ccid, slot);
}
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);
}
if ((cmp->cm_flags & CCID_MINOR_F_HAS_EXCL) == 0) {
mutex_exit(&ccid->ccid_mutex);
return (EACCES);
}
if ((slot->cs_io.ci_flags & (CCID_IO_F_IN_PROGRESS | CCID_IO_F_DONE)) ==
0) {
mutex_exit(&ccid->ccid_mutex);
return (ENODATA);
}
if ((cmp->cm_flags & CCID_MINOR_F_READ_WAITING) != 0) {
mutex_exit(&ccid->ccid_mutex);
return (EBUSY);
}
while ((slot->cs_io.ci_flags & CCID_IO_F_DONE) == 0) {
if (uiop->uio_fmode & FNONBLOCK) {
mutex_exit(&ccid->ccid_mutex);
return (EWOULDBLOCK);
}
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);
}
if ((ccid->ccid_flags & CCID_F_DISCONNECTED) != 0) {
mutex_exit(&ccid->ccid_mutex);
return (ENODEV);
}
}
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;
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_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);
}
if (slot->cs_icc.icc_tx == NULL) {
mutex_exit(&ccid->ccid_mutex);
return (ENOTSUP);
}
if ((slot->cs_io.ci_flags & CCID_IO_F_POLLOUT_FLAGS) != 0) {
mutex_exit(&ccid->ccid_mutex);
return (EBUSY);
}
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;
if ((ret = slot->cs_icc.icc_tx(ccid, slot)) != 0) {
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;
}
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 = (uccid_prot_t)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);
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 (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 ((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);
}
cmp = idx->cmi_data.cmi_user;
slot = cmp->cm_slot;
ccid_minor_idx_free(idx);
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,
ccid_close,
nodev,
nodev,
nodev,
ccid_read,
ccid_write,
ccid_ioctl,
nodev,
nodev,
nodev,
ccid_chpoll,
ddi_prop_op,
NULL,
D_MP,
CB_REV,
nodev,
nodev
};
static struct dev_ops ccid_dev_ops = {
DEVO_REV,
0,
ccid_getinfo,
nulldev,
nulldev,
ccid_attach,
ccid_detach,
nodev,
&ccid_cb_ops,
NULL,
NULL,
ddi_quiesce_not_supported
};
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);
}