@@ -149,6 +149,9 @@ config USB_LIBCOMPOSITE
config USB_F_ACM
tristate
+config USB_F_CCID
+ tristate
+
config USB_F_SS_LB
tristate
@@ -248,6 +251,20 @@ config USB_CONFIGFS_ACM
ACM serial link. This function can be used to interoperate with
MS-Windows hosts or with the Linux-USB "cdc-acm" driver.
+config USB_CONFIGFS_CCID
+ bool "Chip Card Interface Device (CCID)"
+ depends on USB_CONFIGFS
+ select USB_F_CCID
+ help
+ The CCID function driver provides generic emulation of a
+ Chip Card Interface Device (CCID).
+
+ You will need a user space server talking to /dev/ccidg*,
+ since the kernel itself does not implement CCID/TPDU/APDU
+ protocol.
+
+ For more information, see Documentation/usb/gadget_ccid.rst.
+
config USB_CONFIGFS_OBEX
bool "Object Exchange Model (CDC OBEX)"
depends on USB_CONFIGFS
@@ -9,6 +9,7 @@ ccflags-y += -I$(srctree)/drivers/usb/gadget/udc/
# USB Functions
usb_f_acm-y := f_acm.o
obj-$(CONFIG_USB_F_ACM) += usb_f_acm.o
+obj-$(CONFIG_USB_F_CCID) += f_ccid.o
usb_f_ss_lb-y := f_loopback.o f_sourcesink.o
obj-$(CONFIG_USB_F_SS_LB) += usb_f_ss_lb.o
obj-$(CONFIG_USB_U_SERIAL) += u_serial.o
new file mode 100644
@@ -0,0 +1,993 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * f_ccid.c -- Chip Card Interface Device (CCID) function Driver
+ *
+ * Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com>
+ *
+ */
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/usb/composite.h>
+#include <uapi/linux/usb/ccid.h>
+
+#include "f_ccid.h"
+#include "u_f.h"
+
+/* Number of tx requests to allocate */
+#define N_TX_REQS 4
+
+/* Maximum number of devices */
+#define CCID_MINORS 4
+
+struct ccidg_bulk_dev {
+ atomic_t is_open;
+ atomic_t rx_req_busy;
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ struct usb_request *rx_req;
+ atomic_t rx_done;
+ struct list_head tx_idle;
+};
+
+struct f_ccidg {
+ struct usb_function_instance func_inst;
+ struct usb_function function;
+ spinlock_t lock;
+ atomic_t online;
+
+ /* Character device */
+ struct cdev cdev;
+ int minor;
+
+ /* Dynamic attributes */
+ u32 features;
+ u32 protocols;
+ u8 pinsupport;
+ u8 nslots;
+ u8 lcdlayout;
+
+ /* Endpoints */
+ struct usb_ep *in;
+ struct usb_ep *out;
+ struct ccidg_bulk_dev bulk_dev;
+};
+
+/* Interface Descriptor: */
+static struct usb_interface_descriptor ccid_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_CSCID,
+ .bInterfaceSubClass = 0,
+ .bInterfaceProtocol = 0,
+};
+
+/* CCID Class Descriptor */
+static struct ccid_class_descriptor ccid_class_desc = {
+ .bLength = sizeof(ccid_class_desc),
+ .bDescriptorType = CCID_DECRIPTOR_TYPE,
+ .bcdCCID = cpu_to_le16(CCID1_10),
+ /* .bMaxSlotIndex = DYNAMIC */
+ .bVoltageSupport = CCID_VOLTS_3_0,
+ /* .dwProtocols = DYNAMIC */
+ .dwDefaultClock = cpu_to_le32(3580),
+ .dwMaximumClock = cpu_to_le32(3580),
+ .bNumClockSupported = 0,
+ .dwDataRate = cpu_to_le32(9600),
+ .dwMaxDataRate = cpu_to_le32(9600),
+ .bNumDataRatesSupported = 0,
+ .dwMaxIFSD = 0,
+ .dwSynchProtocols = 0,
+ .dwMechanical = 0,
+ /* .dwFeatures = DYNAMIC */
+
+ /* extended APDU level Message Length */
+ .dwMaxCCIDMessageLength = cpu_to_le32(0x200),
+ .bClassGetResponse = 0x0,
+ .bClassEnvelope = 0x0,
+ /* .wLcdLayout = DYNAMIC */
+ /* .bPINSupport = DYNAMIC */
+ .bMaxCCIDBusySlots = 1
+};
+
+/* Full speed support: */
+static struct usb_endpoint_descriptor ccid_fs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(64),
+};
+
+static struct usb_endpoint_descriptor ccid_fs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(64),
+};
+
+static struct usb_descriptor_header *ccid_fs_descs[] = {
+ (struct usb_descriptor_header *) &ccid_interface_desc,
+ (struct usb_descriptor_header *) &ccid_class_desc,
+ (struct usb_descriptor_header *) &ccid_fs_in_desc,
+ (struct usb_descriptor_header *) &ccid_fs_out_desc,
+ NULL,
+};
+
+/* High speed support: */
+static struct usb_endpoint_descriptor ccid_hs_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor ccid_hs_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_descriptor_header *ccid_hs_descs[] = {
+ (struct usb_descriptor_header *) &ccid_interface_desc,
+ (struct usb_descriptor_header *) &ccid_class_desc,
+ (struct usb_descriptor_header *) &ccid_hs_in_desc,
+ (struct usb_descriptor_header *) &ccid_hs_out_desc,
+ NULL,
+};
+
+static DEFINE_IDA(ccidg_ida);
+static int major;
+static DEFINE_MUTEX(ccidg_ida_lock); /* protects access to ccidg_ida */
+static struct class *ccidg_class;
+
+static inline struct f_ccidg_opts *to_f_ccidg_opts(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct f_ccidg_opts,
+ func_inst.group);
+}
+
+static inline struct f_ccidg *func_to_ccidg(struct usb_function *f)
+{
+ return container_of(f, struct f_ccidg, function);
+}
+
+static inline int ccidg_get_minor(void)
+{
+ int ret;
+
+ ret = ida_simple_get(&ccidg_ida, 0, 0, GFP_KERNEL);
+ if (ret >= CCID_MINORS) {
+ ida_simple_remove(&ccidg_ida, ret);
+ ret = -ENODEV;
+ }
+
+ return ret;
+}
+
+static inline void ccidg_put_minor(int minor)
+{
+ ida_simple_remove(&ccidg_ida, minor);
+}
+
+static int ccidg_setup(void)
+{
+ int ret;
+ dev_t dev;
+
+ ccidg_class = class_create(THIS_MODULE, "ccidg");
+ if (IS_ERR(ccidg_class)) {
+ ccidg_class = NULL;
+ return PTR_ERR(ccidg_class);
+ }
+
+ ret = alloc_chrdev_region(&dev, 0, CCID_MINORS, "ccidg");
+ if (ret) {
+ class_destroy(ccidg_class);
+ ccidg_class = NULL;
+ return ret;
+ }
+
+ major = MAJOR(dev);
+
+ return 0;
+}
+
+static void ccidg_cleanup(void)
+{
+ if (major) {
+ unregister_chrdev_region(MKDEV(major, 0), CCID_MINORS);
+ major = 0;
+ }
+
+ class_destroy(ccidg_class);
+ ccidg_class = NULL;
+}
+
+static void ccidg_attr_release(struct config_item *item)
+{
+ struct f_ccidg_opts *opts = to_f_ccidg_opts(item);
+
+ usb_put_function_instance(&opts->func_inst);
+}
+
+static struct configfs_item_operations ccidg_item_ops = {
+ .release = ccidg_attr_release,
+};
+
+#define F_CCIDG_OPT(name, prec, limit) \
+static ssize_t f_ccidg_opts_##name##_show(struct config_item *item, char *page)\
+{ \
+ struct f_ccidg_opts *opts = to_f_ccidg_opts(item); \
+ int result; \
+ \
+ mutex_lock(&opts->lock); \
+ result = sprintf(page, "%x\n", opts->name); \
+ mutex_unlock(&opts->lock); \
+ \
+ return result; \
+} \
+ \
+static ssize_t f_ccidg_opts_##name##_store(struct config_item *item, \
+ const char *page, size_t len) \
+{ \
+ struct f_ccidg_opts *opts = to_f_ccidg_opts(item); \
+ int ret; \
+ u##prec num; \
+ \
+ mutex_lock(&opts->lock); \
+ if (opts->refcnt) { \
+ ret = -EBUSY; \
+ goto end; \
+ } \
+ \
+ ret = kstrtou##prec(page, 0, &num); \
+ if (ret) \
+ goto end; \
+ \
+ if (num > limit) { \
+ ret = -EINVAL; \
+ goto end; \
+ } \
+ opts->name = num; \
+ ret = len; \
+ \
+end: \
+ mutex_unlock(&opts->lock); \
+ return ret; \
+} \
+ \
+CONFIGFS_ATTR(f_ccidg_opts_, name)
+
+F_CCIDG_OPT(features, 32, 0xffffffff);
+F_CCIDG_OPT(protocols, 32, 0x03);
+F_CCIDG_OPT(pinsupport, 8, 0x03);
+F_CCIDG_OPT(lcdlayout, 16, 0xffff);
+F_CCIDG_OPT(nslots, 8, 0xff);
+
+static struct configfs_attribute *ccidg_attrs[] = {
+ &f_ccidg_opts_attr_features,
+ &f_ccidg_opts_attr_protocols,
+ &f_ccidg_opts_attr_pinsupport,
+ &f_ccidg_opts_attr_lcdlayout,
+ &f_ccidg_opts_attr_nslots,
+ NULL,
+};
+
+static struct config_item_type ccidg_func_type = {
+ .ct_item_ops = &ccidg_item_ops,
+ .ct_attrs = ccidg_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static void ccidg_req_put(struct f_ccidg *ccidg, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ccidg->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&ccidg->lock, flags);
+}
+
+static struct usb_request *ccidg_req_get(struct f_ccidg *ccidg,
+ struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req = NULL;
+
+ spin_lock_irqsave(&ccidg->lock, flags);
+ if (!list_empty(head)) {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&ccidg->lock, flags);
+
+ return req;
+}
+
+static void ccidg_bulk_complete_tx(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_ccidg *ccidg = (struct f_ccidg *)ep->driver_data;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ struct usb_composite_dev *cdev = ccidg->function.config->cdev;
+
+ switch (req->status) {
+ default:
+ VDBG(cdev, "ccid: tx err %d\n", req->status);
+ /* FALLTHROUGH */
+ case -ECONNRESET: /* unlink */
+ case -ESHUTDOWN: /* disconnect etc */
+ break;
+ case 0:
+ break;
+ }
+
+ ccidg_req_put(ccidg, &bulk_dev->tx_idle, req);
+ wake_up(&bulk_dev->write_wq);
+}
+
+static void ccidg_bulk_complete_rx(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_ccidg *ccidg = (struct f_ccidg *)ep->driver_data;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ struct usb_composite_dev *cdev = ccidg->function.config->cdev;
+
+ switch (req->status) {
+
+ /* normal completion */
+ case 0:
+ /* We only cares about packets with nonzero length */
+ if (req->actual > 0)
+ atomic_set(&bulk_dev->rx_done, 1);
+ break;
+
+ /* software-driven interface shutdown */
+ case -ECONNRESET: /* unlink */
+ case -ESHUTDOWN: /* disconnect etc */
+ VDBG(cdev, "ccid: rx shutdown, code %d\n", req->status);
+ break;
+
+ /* for hardware automagic (such as pxa) */
+ case -ECONNABORTED: /* endpoint reset */
+ DBG(cdev, "ccid: rx %s reset\n", ep->name);
+ break;
+
+ /* data overrun */
+ case -EOVERFLOW:
+ /* FALLTHROUGH */
+ default:
+ DBG(cdev, "ccid: rx status %d\n", req->status);
+ break;
+ }
+
+ wake_up(&bulk_dev->read_wq);
+}
+
+static struct usb_request *
+ccidg_request_alloc(struct usb_ep *ep, unsigned int len)
+{
+ struct usb_request *req;
+
+ req = usb_ep_alloc_request(ep, GFP_ATOMIC);
+ if (!req)
+ return ERR_PTR(-ENOMEM);
+
+ req->length = len;
+ req->buf = kmalloc(len, GFP_ATOMIC);
+ if (req->buf == NULL) {
+ usb_ep_free_request(ep, req);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return req;
+}
+
+static void ccidg_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static int ccidg_function_setup(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct f_ccidg *ccidg = container_of(f, struct f_ccidg, function);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct usb_request *req = cdev->req;
+ int ret = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+
+ if (!atomic_read(&ccidg->online))
+ return -ENOTCONN;
+
+ switch (ctrl->bRequestType & USB_TYPE_MASK) {
+ case USB_TYPE_CLASS:
+ {
+ switch (ctrl->bRequest) {
+ case CCIDGENERICREQ_GET_CLOCK_FREQUENCIES:
+ if (w_length > sizeof(ccid_class_desc.dwDefaultClock))
+ break;
+
+ *(__le32 *) req->buf = ccid_class_desc.dwDefaultClock;
+ ret = sizeof(ccid_class_desc.dwDefaultClock);
+ break;
+
+ case CCIDGENERICREQ_GET_DATA_RATES:
+ if (w_length > sizeof(ccid_class_desc.dwDataRate))
+ break;
+
+ *(__le32 *) req->buf = ccid_class_desc.dwDataRate;
+ ret = sizeof(ccid_class_desc.dwDataRate);
+ break;
+
+ default:
+ VDBG(f->config->cdev,
+ "ccid: invalid control req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+ }
+ }
+ }
+
+ /* responded with data transfer or status phase? */
+ if (ret >= 0) {
+ VDBG(f->config->cdev, "ccid: req%02x.%02x v%04x i%04x l%d\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ req->length = ret;
+ ret = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
+ if (ret < 0)
+ ERROR(f->config->cdev,
+ "ccid: ep0 enqueue err %d\n", ret);
+ }
+
+ return ret;
+}
+
+static void ccidg_function_disable(struct usb_function *f)
+{
+ struct f_ccidg *ccidg = func_to_ccidg(f);
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ struct usb_request *req;
+
+ /* Disable endpoints */
+ usb_ep_disable(ccidg->in);
+ usb_ep_disable(ccidg->out);
+
+ /* Free endpoint related requests */
+ if (!atomic_read(&bulk_dev->rx_req_busy))
+ ccidg_request_free(bulk_dev->rx_req, ccidg->out);
+ while ((req = ccidg_req_get(ccidg, &bulk_dev->tx_idle)))
+ ccidg_request_free(req, ccidg->in);
+
+ atomic_set(&ccidg->online, 0);
+
+ /* Wake up threads */
+ wake_up(&bulk_dev->write_wq);
+ wake_up(&bulk_dev->read_wq);
+}
+
+static int ccidg_start_ep(struct f_ccidg *ccidg, struct usb_function *f,
+ struct usb_ep *ep)
+{
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ usb_ep_disable(ep);
+
+ ret = config_ep_by_speed(cdev->gadget, f, ep);
+ if (ret) {
+ ERROR(cdev, "ccid: can't configure %s: %d\n", ep->name, ret);
+ return ret;
+ }
+
+ ret = usb_ep_enable(ep);
+ if (ret) {
+ ERROR(cdev, "ccid: can't start %s: %d\n", ep->name, ret);
+ return ret;
+ }
+
+ ep->driver_data = ccidg;
+
+ return ret;
+}
+
+static int ccidg_function_set_alt(struct usb_function *f,
+ unsigned int intf, unsigned int alt)
+{
+ struct f_ccidg *ccidg = func_to_ccidg(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ struct usb_request *req;
+ int ret;
+ int i;
+
+ /* Allocate requests for our endpoints */
+ req = ccidg_request_alloc(ccidg->out,
+ sizeof(struct ccidg_bulk_out_header));
+ if (IS_ERR(req)) {
+ ERROR(cdev, "ccid: uname to allocate memory for out req\n");
+ return PTR_ERR(req);
+ }
+ req->complete = ccidg_bulk_complete_rx;
+ req->context = ccidg;
+ bulk_dev->rx_req = req;
+
+ /* Allocate bunch of in requests */
+ for (i = 0; i < N_TX_REQS; i++) {
+ req = ccidg_request_alloc(ccidg->in,
+ sizeof(struct ccidg_bulk_in_header));
+
+ if (IS_ERR(req)) {
+ ret = PTR_ERR(req);
+ ERROR(cdev,
+ "ccid: uname to allocate memory for in req\n");
+ goto free_bulk_out;
+ }
+ req->complete = ccidg_bulk_complete_tx;
+ req->context = ccidg;
+ ccidg_req_put(ccidg, &bulk_dev->tx_idle, req);
+ }
+
+ /* choose the descriptors and enable endpoints */
+ ret = ccidg_start_ep(ccidg, f, ccidg->in);
+ if (ret)
+ goto free_bulk_in;
+
+ ret = ccidg_start_ep(ccidg, f, ccidg->out);
+ if (ret)
+ goto disable_ep_in;
+
+ atomic_set(&ccidg->online, 1);
+ return ret;
+
+disable_ep_in:
+ usb_ep_disable(ccidg->in);
+free_bulk_in:
+ while ((req = ccidg_req_get(ccidg, &bulk_dev->tx_idle)))
+ ccidg_request_free(req, ccidg->in);
+free_bulk_out:
+ ccidg_request_free(bulk_dev->rx_req, ccidg->out);
+ return ret;
+}
+
+static int ccidg_bulk_open(struct inode *inode, struct file *file)
+{
+ struct f_ccidg *ccidg;
+ struct ccidg_bulk_dev *bulk_dev;
+
+ ccidg = container_of(inode->i_cdev, struct f_ccidg, cdev);
+ bulk_dev = &ccidg->bulk_dev;
+
+ if (!atomic_read(&ccidg->online)) {
+ DBG(ccidg->function.config->cdev, "ccid: device not online\n");
+ return -ENODEV;
+ }
+
+ if (atomic_read(&bulk_dev->is_open)) {
+ DBG(ccidg->function.config->cdev,
+ "ccid: device already opened\n");
+ return -EBUSY;
+ }
+
+ atomic_set(&bulk_dev->is_open, 1);
+
+ file->private_data = ccidg;
+
+ return 0;
+}
+
+static int ccidg_bulk_release(struct inode *inode, struct file *file)
+{
+ struct f_ccidg *ccidg = file->private_data;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+
+ atomic_set(&bulk_dev->is_open, 0);
+ return 0;
+}
+
+static ssize_t ccidg_bulk_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct f_ccidg *ccidg = file->private_data;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ struct usb_request *req;
+ int r = count, xfer;
+ int ret;
+
+ /* Make sure we have enough space for a whole package */
+ if (count < sizeof(struct ccidg_bulk_out_header)) {
+ DBG(ccidg->function.config->cdev,
+ "ccid: too small buffer size. %zu provided, need at least %zu\n",
+ count, sizeof(struct ccidg_bulk_out_header));
+ return -ENOMEM;
+ }
+
+ if (!atomic_read(&ccidg->online))
+ return -ENODEV;
+
+ /* queue a request */
+ req = bulk_dev->rx_req;
+ req->length = count;
+ atomic_set(&bulk_dev->rx_done, 0);
+
+ ret = usb_ep_queue(ccidg->out, req, GFP_KERNEL);
+ if (ret < 0) {
+ ERROR(ccidg->function.config->cdev,
+ "ccid: usb ep queue failed\n");
+ return -EIO;
+ }
+
+ if (!atomic_read(&bulk_dev->rx_done) &&
+ file->f_flags & (O_NONBLOCK | O_NDELAY))
+ return -EAGAIN;
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(bulk_dev->read_wq,
+ atomic_read(&bulk_dev->rx_done) ||
+ !atomic_read(&ccidg->online));
+ if (ret < 0) {
+ usb_ep_dequeue(ccidg->out, req);
+ return -ERESTARTSYS;
+ }
+
+ /* Still online? */
+ if (!atomic_read(&ccidg->online))
+ return -ENODEV;
+
+ atomic_set(&bulk_dev->rx_req_busy, 1);
+ xfer = (req->actual < count) ? req->actual : count;
+
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+
+ atomic_set(&bulk_dev->rx_req_busy, 0);
+ if (!atomic_read(&ccidg->online)) {
+ ccidg_request_free(bulk_dev->rx_req, ccidg->out);
+ return -ENODEV;
+ }
+
+ return xfer;
+}
+
+static ssize_t ccidg_bulk_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct f_ccidg *ccidg = file->private_data;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ struct usb_request *req = NULL;
+ int ret;
+
+ /* Are we online? */
+ if (!atomic_read(&ccidg->online))
+ return -ENODEV;
+
+ /* Avoid Zero Length Packets (ZLP) */
+ if (!count)
+ return 0;
+
+ /* Make sure we have enough space for a whole package */
+ if (count > sizeof(struct ccidg_bulk_out_header)) {
+ DBG(ccidg->function.config->cdev,
+ "ccid: too much data. %zu provided, but we can only handle %zu\n",
+ count, sizeof(struct ccidg_bulk_out_header));
+ return -ENOMEM;
+ }
+
+ if (list_empty(&bulk_dev->tx_idle) &&
+ file->f_flags & (O_NONBLOCK | O_NDELAY))
+ return -EAGAIN;
+
+ /* get an idle tx request to use */
+ ret = wait_event_interruptible(bulk_dev->write_wq,
+ ((req = ccidg_req_get(ccidg, &bulk_dev->tx_idle))));
+
+ if (ret < 0)
+ return -ERESTARTSYS;
+
+ if (copy_from_user(req->buf, buf, count)) {
+ if (!atomic_read(&ccidg->online)) {
+ ccidg_request_free(req, ccidg->in);
+ return -ENODEV;
+ } else {
+ ccidg_req_put(ccidg, &bulk_dev->tx_idle, req);
+ return -EFAULT;
+ }
+ }
+
+ req->length = count;
+ ret = usb_ep_queue(ccidg->in, req, GFP_KERNEL);
+ if (ret < 0) {
+ ccidg_req_put(ccidg, &bulk_dev->tx_idle, req);
+
+ if (!atomic_read(&ccidg->online)) {
+ /* Free up all requests if we are not online */
+ while ((req = ccidg_req_get(ccidg, &bulk_dev->tx_idle)))
+ ccidg_request_free(req, ccidg->in);
+
+ return -ENODEV;
+ }
+ return -EIO;
+ }
+
+ return count;
+}
+
+static __poll_t ccidg_bulk_poll(struct file *file, poll_table * wait)
+{
+ struct f_ccidg *ccidg = file->private_data;
+ struct ccidg_bulk_dev *bulk_dev = &ccidg->bulk_dev;
+ __poll_t ret = 0;
+
+ poll_wait(file, &bulk_dev->read_wq, wait);
+ poll_wait(file, &bulk_dev->write_wq, wait);
+
+ if (list_empty(&bulk_dev->tx_idle))
+ ret |= EPOLLOUT | EPOLLWRNORM;
+
+ if (atomic_read(&bulk_dev->rx_done))
+ ret |= EPOLLIN | EPOLLRDNORM;
+
+ return ret;
+}
+
+static const struct file_operations f_ccidg_fops = {
+ .owner = THIS_MODULE,
+ .read = ccidg_bulk_read,
+ .write = ccidg_bulk_write,
+ .open = ccidg_bulk_open,
+ .poll = ccidg_bulk_poll,
+ .release = ccidg_bulk_release,
+};
+
+static int ccidg_bulk_device_init(struct f_ccidg *dev)
+{
+ struct ccidg_bulk_dev *bulk_dev = &dev->bulk_dev;
+
+ init_waitqueue_head(&bulk_dev->read_wq);
+ init_waitqueue_head(&bulk_dev->write_wq);
+ INIT_LIST_HEAD(&bulk_dev->tx_idle);
+
+ return 0;
+}
+
+static void ccidg_function_free(struct usb_function *f)
+{
+ struct f_ccidg *ccidg;
+ struct f_ccidg_opts *opts;
+
+ ccidg = func_to_ccidg(f);
+ opts = container_of(f->fi, struct f_ccidg_opts, func_inst);
+
+ kfree(ccidg);
+ mutex_lock(&opts->lock);
+ --opts->refcnt;
+ mutex_unlock(&opts->lock);
+}
+
+static void ccidg_function_unbind(struct usb_configuration *c,
+ struct usb_function *f)
+{
+ struct f_ccidg *ccidg = func_to_ccidg(f);
+
+ device_destroy(ccidg_class, MKDEV(major, ccidg->minor));
+ cdev_del(&ccidg->cdev);
+
+ /* disable/free request and end point */
+ usb_free_all_descriptors(f);
+}
+
+static int ccidg_function_bind(struct usb_configuration *c,
+ struct usb_function *f)
+{
+ struct f_ccidg *ccidg = func_to_ccidg(f);
+ struct usb_ep *ep;
+ struct usb_composite_dev *cdev = c->cdev;
+ struct device *device;
+ dev_t dev;
+ int ifc_id;
+ int ret;
+
+ /* allocate instance-specific interface IDs, and patch descriptors */
+ ifc_id = usb_interface_id(c, f);
+ if (ifc_id < 0) {
+ ERROR(cdev, "ccid: unable to allocate ifc id, err:%d\n",
+ ifc_id);
+ return ifc_id;
+ }
+ ccid_interface_desc.bInterfaceNumber = ifc_id;
+
+ /* allocate instance-specific endpoints */
+ ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_in_desc);
+ if (!ep) {
+ ERROR(cdev, "ccid: usb epin autoconfig failed\n");
+ ret = -ENODEV;
+ goto ep_auto_in_fail;
+ }
+ ccidg->in = ep;
+ ep->driver_data = ccidg;
+
+ ep = usb_ep_autoconfig(cdev->gadget, &ccid_fs_out_desc);
+ if (!ep) {
+ ERROR(cdev, "ccid: usb epout autoconfig failed\n");
+ ret = -ENODEV;
+ goto ep_auto_out_fail;
+ }
+ ccidg->out = ep;
+ ep->driver_data = ccidg;
+
+ /* set descriptor dynamic values */
+ ccid_class_desc.dwFeatures = cpu_to_le32(ccidg->features);
+ ccid_class_desc.bPINSupport = ccidg->pinsupport;
+ ccid_class_desc.wLcdLayout = cpu_to_le16(ccidg->lcdlayout);
+ ccid_class_desc.bMaxSlotIndex = ccidg->nslots;
+ ccid_class_desc.dwProtocols = cpu_to_le32(ccidg->protocols);
+
+ if (ccidg->protocols == CCID_PROTOCOL_NOT_SEL) {
+ ccidg->protocols = CCID_PROTOCOL_T0 | CCID_PROTOCOL_T1;
+ INFO(ccidg->function.config->cdev,
+ "ccid: No protocol selected. Support both T0 and T1.\n");
+ }
+
+
+ ccid_hs_in_desc.bEndpointAddress =
+ ccid_fs_in_desc.bEndpointAddress;
+ ccid_hs_out_desc.bEndpointAddress =
+ ccid_fs_out_desc.bEndpointAddress;
+
+ ret = usb_assign_descriptors(f, ccid_fs_descs,
+ ccid_hs_descs, NULL, NULL);
+ if (ret)
+ goto ep_auto_out_fail;
+
+ /* create char device */
+ cdev_init(&ccidg->cdev, &f_ccidg_fops);
+ dev = MKDEV(major, ccidg->minor);
+ ret = cdev_add(&ccidg->cdev, dev, 1);
+ if (ret)
+ goto fail_free_descs;
+
+ device = device_create(ccidg_class, NULL, dev, NULL,
+ "%s%d", "ccidg", ccidg->minor);
+ if (IS_ERR(device)) {
+ ret = PTR_ERR(device);
+ goto del;
+ }
+
+ return 0;
+
+del:
+ cdev_del(&ccidg->cdev);
+fail_free_descs:
+ usb_free_all_descriptors(f);
+ep_auto_out_fail:
+ ccidg->out->driver_data = NULL;
+ ccidg->out = NULL;
+ep_auto_in_fail:
+ ccidg->in->driver_data = NULL;
+ ccidg->in = NULL;
+ ERROR(f->config->cdev, "ccidg_bind FAILED\n");
+
+ return ret;
+}
+
+static struct usb_function *ccidg_alloc(struct usb_function_instance *fi)
+{
+ struct f_ccidg *ccidg;
+ struct f_ccidg_opts *opts;
+ int ret;
+
+ ccidg = kzalloc(sizeof(*ccidg), GFP_KERNEL);
+ if (!ccidg)
+ return ERR_PTR(-ENOMEM);
+
+ spin_lock_init(&ccidg->lock);
+
+ ret = ccidg_bulk_device_init(ccidg);
+ if (ret) {
+ kfree(ccidg);
+ return ERR_PTR(ret);
+ }
+
+ opts = container_of(fi, struct f_ccidg_opts, func_inst);
+
+ mutex_lock(&opts->lock);
+ ++opts->refcnt;
+
+ ccidg->minor = opts->minor;
+ ccidg->features = opts->features;
+ ccidg->protocols = opts->protocols;
+ ccidg->pinsupport = opts->pinsupport;
+ ccidg->nslots = opts->nslots;
+ mutex_unlock(&opts->lock);
+
+ ccidg->function.name = "ccid";
+ ccidg->function.bind = ccidg_function_bind;
+ ccidg->function.unbind = ccidg_function_unbind;
+ ccidg->function.set_alt = ccidg_function_set_alt;
+ ccidg->function.disable = ccidg_function_disable;
+ ccidg->function.setup = ccidg_function_setup;
+ ccidg->function.free_func = ccidg_function_free;
+
+ return &ccidg->function;
+}
+
+static void ccidg_free_inst(struct usb_function_instance *f)
+{
+ struct f_ccidg_opts *opts;
+
+ opts = container_of(f, struct f_ccidg_opts, func_inst);
+ mutex_lock(&ccidg_ida_lock);
+
+ ccidg_put_minor(opts->minor);
+ if (ida_is_empty(&ccidg_ida))
+ ccidg_cleanup();
+
+ mutex_unlock(&ccidg_ida_lock);
+
+ kfree(opts);
+}
+
+static struct usb_function_instance *ccidg_alloc_inst(void)
+{
+ struct f_ccidg_opts *opts;
+ struct usb_function_instance *ret;
+ int status = 0;
+
+ opts = kzalloc(sizeof(*opts), GFP_KERNEL);
+ if (!opts)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&opts->lock);
+ opts->func_inst.free_func_inst = ccidg_free_inst;
+ ret = &opts->func_inst;
+
+ mutex_lock(&ccidg_ida_lock);
+
+ if (ida_is_empty(&ccidg_ida)) {
+ status = ccidg_setup();
+ if (status) {
+ ret = ERR_PTR(status);
+ kfree(opts);
+ goto unlock;
+ }
+ }
+
+ opts->minor = ccidg_get_minor();
+ if (opts->minor < 0) {
+ ret = ERR_PTR(opts->minor);
+ kfree(opts);
+ if (ida_is_empty(&ccidg_ida))
+ ccidg_cleanup();
+ goto unlock;
+ }
+
+ config_group_init_type_name(&opts->func_inst.group,
+ "", &ccidg_func_type);
+
+unlock:
+ mutex_unlock(&ccidg_ida_lock);
+ return ret;
+}
+
+DECLARE_USB_FUNCTION_INIT(ccid, ccidg_alloc_inst, ccidg_alloc);
+
+MODULE_DESCRIPTION("USB CCID Gadget driver");
+MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>");
+MODULE_LICENSE("GPL v2");
new file mode 100644
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com>
+ */
+
+#ifndef F_CCID_H
+#define F_CCID_H
+
+#define CCID1_10 0x0110
+#define CCID_DECRIPTOR_TYPE 0x21
+#define ABDATA_SIZE 512
+#define SMART_CARD_DEVICE_CLASS 0x0B
+
+/* CCID Class Specific Request */
+#define CCIDGENERICREQ_ABORT 0x01
+#define CCIDGENERICREQ_GET_CLOCK_FREQUENCIES 0x02
+#define CCIDGENERICREQ_GET_DATA_RATES 0x03
+
+/* Supported voltages */
+#define CCID_VOLTS_AUTO 0x00
+#define CCID_VOLTS_5_0 0x01
+#define CCID_VOLTS_3_0 0x02
+#define CCID_VOLTS_1_8 0x03
+
+struct f_ccidg_opts {
+ struct usb_function_instance func_inst;
+ int minor;
+ __u32 features;
+ __u32 protocols;
+ __u8 pinsupport;
+ __u8 nslots;
+ __u8 lcdlayout;
+
+ /*
+ * Protect the data form concurrent access by read/write
+ * and create symlink/remove symlink.
+ */
+ struct mutex lock;
+ int refcnt;
+};
+
+struct ccidg_bulk_in_header {
+ __u8 bMessageType;
+ __le32 wLength;
+ __u8 bSlot;
+ __u8 bSeq;
+ __u8 bStatus;
+ __u8 bError;
+ __u8 bSpecific;
+ __u8 abData[ABDATA_SIZE];
+ __u8 bSizeToSend;
+} __packed;
+
+struct ccidg_bulk_out_header {
+ __u8 bMessageType;
+ __le32 wLength;
+ __u8 bSlot;
+ __u8 bSeq;
+ __u8 bSpecific_0;
+ __u8 bSpecific_1;
+ __u8 bSpecific_2;
+ __u8 APDU[ABDATA_SIZE];
+} __packed;
+
+struct ccid_class_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __le16 bcdCCID;
+ __u8 bMaxSlotIndex;
+ __u8 bVoltageSupport;
+ __le32 dwProtocols;
+ __le32 dwDefaultClock;
+ __le32 dwMaximumClock;
+ __u8 bNumClockSupported;
+ __le32 dwDataRate;
+ __le32 dwMaxDataRate;
+ __u8 bNumDataRatesSupported;
+ __le32 dwMaxIFSD;
+ __le32 dwSynchProtocols;
+ __le32 dwMechanical;
+ __le32 dwFeatures;
+ __le32 dwMaxCCIDMessageLength;
+ __u8 bClassGetResponse;
+ __u8 bClassEnvelope;
+ __le16 wLcdLayout;
+ __u8 bPINSupport;
+ __u8 bMaxCCIDBusySlots;
+} __packed;
+
+
+#endif
new file mode 100644
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2018 Marcus Folkesson <marcus.folkesson@gmail.com>
+ *
+ * This file holds USB constants defined by the CCID Specification.
+ */
+
+#ifndef CCID_H
+#define CCID_H
+
+/* Slot error register when bmCommandStatus = 1 */
+#define CCID_CMD_ABORTED 0xFF
+#define CCID_ICC_MUTE 0xFE
+#define CCID_XFR_PARITY_ERROR 0xFD
+#define CCID_XFR_OVERRUN 0xFC
+#define CCID_HW_ERROR 0xFB
+#define CCID_BAD_ATR_TS 0xF8
+#define CCID_BAD_ATR_TCK 0xF7
+#define CCID_ICC_PROTOCOL_NOT_SUPPORTED 0xF6
+#define CCID_ICC_CLASS_NOT_SUPPORTED 0xF5
+#define CCID_PROCEDURE_BYTE_CONFLICT 0xF4
+#define CCID_DEACTIVATED_PROTOCOL 0xF3
+#define CCID_BUSY_WITH_AUTO_SEQUENCE 0xF2
+#define CCID_PIN_TIMEOUT 0xF0
+#define CCID_PIN_CANCELLED 0xEF
+#define CCID_CMD_SLOT_BUSY 0xE0
+
+/* PC to RDR messages (bulk out) */
+#define CCID_PC_TO_RDR_ICCPOWERON 0x62
+#define CCID_PC_TO_RDR_ICCPOWEROFF 0x63
+#define CCID_PC_TO_RDR_GETSLOTSTATUS 0x65
+#define CCID_PC_TO_RDR_XFRBLOCK 0x6F
+#define CCID_PC_TO_RDR_GETPARAMETERS 0x6C
+#define CCID_PC_TO_RDR_RESETPARAMETERS 0x6D
+#define CCID_PC_TO_RDR_SETPARAMETERS 0x61
+#define CCID_PC_TO_RDR_ESCAPE 0x6B
+#define CCID_PC_TO_RDR_ICCCLOCK 0x6E
+#define CCID_PC_TO_RDR_T0APDU 0x6A
+#define CCID_PC_TO_RDR_SECURE 0x69
+#define CCID_PC_TO_RDR_MECHANICAL 0x71
+#define CCID_PC_TO_RDR_ABORT 0x72
+#define CCID_PC_TO_RDR_SETDATARATEANDCLOCKFREQUENCY 0x73
+
+/* RDR to PC messages (bulk in) */
+#define CCID_RDR_TO_PC_DATABLOCK 0x80
+#define CCID_RDR_TO_PC_SLOTSTATUS 0x81
+#define CCID_RDR_TO_PC_PARAMETERS 0x82
+#define CCID_RDR_TO_PC_ESCAPE 0x83
+#define CCID_RDR_TO_PC_DATARATEANDCLOCKFREQUENCY 0x84
+
+/* Class Features */
+
+/* No special characteristics */
+#define CCID_FEATURES_NADA 0x00000000
+/* Automatic parameter configuration based on ATR data */
+#define CCID_FEATURES_AUTO_PCONF 0x00000002
+/* Automatic activation of ICC on inserting */
+#define CCID_FEATURES_AUTO_ACTIV 0x00000004
+/* Automatic ICC voltage selection */
+#define CCID_FEATURES_AUTO_VOLT 0x00000008
+/* Automatic ICC clock frequency change */
+#define CCID_FEATURES_AUTO_CLOCK 0x00000010
+/* Automatic baud rate change */
+#define CCID_FEATURES_AUTO_BAUD 0x00000020
+/*Automatic parameters negotiation made by the CCID */
+#define CCID_FEATURES_AUTO_PNEGO 0x00000040
+/* Automatic PPS made by the CCID according to the active parameters */
+#define CCID_FEATURES_AUTO_PPS 0x00000080
+/* CCID can set ICC in clock stop mode */
+#define CCID_FEATURES_ICCSTOP 0x00000100
+/* NAD value other than 00 accepted (T=1 protocol in use) */
+#define CCID_FEATURES_NAD 0x00000200
+/* Automatic IFSD exchange as first exchange (T=1 protocol in use) */
+#define CCID_FEATURES_AUTO_IFSD 0x00000400
+/* TPDU level exchanges with CCID */
+#define CCID_FEATURES_EXC_TPDU 0x00010000
+/* Short APDU level exchange with CCID */
+#define CCID_FEATURES_EXC_SAPDU 0x00020000
+/* Short and Extended APDU level exchange with CCID */
+#define CCID_FEATURES_EXC_APDU 0x00040000
+/* USB Wake up signaling supported on card insertion and removal */
+#define CCID_FEATURES_WAKEUP 0x00100000
+
+/* Supported protocols */
+#define CCID_PROTOCOL_NOT_SEL 0x00
+#define CCID_PROTOCOL_T0 0x01
+#define CCID_PROTOCOL_T1 0x02
+
+#define CCID_PINSUPOORT_NONE 0x00
+#define CCID_PINSUPOORT_VERIFICATION (1 << 1)
+#define CCID_PINSUPOORT_MODIFICATION (1 << 2)
+
+#endif
Chip Card Interface Device (CCID) protocol is a USB protocol that allows a smartcard device to be connected to a computer via a card reader using a standard USB interface, without the need for each manufacturer of smartcards to provide its own reader or protocol. This gadget driver makes Linux show up as a CCID device to the host and let a userspace daemon act as the smartcard. This is useful when the Linux gadget itself should act as a cryptographic device or forward APDUs to an embedded smartcard device. Signed-off-by: Marcus Folkesson <marcus.folkesson@gmail.com> --- Notes: v3: - fix sparse warnings reported by kbuild test robot v2: - add the missing changelog text drivers/usb/gadget/Kconfig | 17 + drivers/usb/gadget/function/Makefile | 1 + drivers/usb/gadget/function/f_ccid.c | 993 +++++++++++++++++++++++++++++++++++ drivers/usb/gadget/function/f_ccid.h | 91 ++++ include/uapi/linux/usb/ccid.h | 93 ++++ 5 files changed, 1195 insertions(+) create mode 100644 drivers/usb/gadget/function/f_ccid.c create mode 100644 drivers/usb/gadget/function/f_ccid.h create mode 100644 include/uapi/linux/usb/ccid.h