From: Marcus Folkesson <marcus.folkesson@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jonathan Corbet <corbet@lwn.net>, Felipe Balbi <balbi@kernel.org>,
davem@davemloft.net,
Mauro Carvalho Chehab <mchehab+samsung@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Ruslan Bilovol <ruslan.bilovol@gmail.com>,
Thomas Gleixner <tglx@linutronix.de>,
Kate Stewart <kstewart@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org,
Marcus Folkesson <marcus.folkesson@gmail.com>
Subject: [PATCH v4 1/3] usb: gadget: ccid: add support for USB CCID Gadget Device
Date: Tue, 26 Jun 2018 10:14:20 +0200 [thread overview]
Message-ID: <20180626081422.19237-1-marcus.folkesson@gmail.com> (raw)
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:
v4:
- fix comments from Andy, Thanks!
- add "WITH Linux-syscall-note" in the SPDX identifier for uapi-header
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 | 989 +++++++++++++++++++++++++++
drivers/usb/gadget/function/f_ccid.h | 91 +++
include/uapi/linux/usb/ccid.h | 93 +++
5 files changed, 1191 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
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 31cce7805eb2..bdebdf1ffa2b 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -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
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02218..629851009e1a 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -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
diff --git a/drivers/usb/gadget/function/f_ccid.c b/drivers/usb/gadget/function/f_ccid.c
new file mode 100644
index 000000000000..2a890dca31e1
--- /dev/null
+++ b/drivers/usb/gadget/function/f_ccid.c
@@ -0,0 +1,989 @@
+// 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 <asm/unaligned.h>
+#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)) {
+ ret = PTR_ERR(ccidg_class);
+ ccidg_class = NULL;
+ return ret;
+ }
+
+ 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;
+ ida_destroy(&ccidg_ida);
+}
+
+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);
+ req = list_first_entry_or_null(head, struct usb_request, list);
+ if (req)
+ 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->buf = kmalloc(len, GFP_ATOMIC);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return ERR_PTR(-ENOMEM);
+ }
+ req->length = len;
+
+ return req;
+}
+
+static void ccidg_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (!req)
+ return;
+
+ 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;
+
+ put_unaligned(ccid_class_desc.dwDefaultClock, req->buf);
+ ret = sizeof(ccid_class_desc.dwDefaultClock);
+ break;
+
+ case CCIDGENERICREQ_GET_DATA_RATES:
+ if (w_length > sizeof(ccid_class_desc.dwDataRate))
+ break;
+
+ put_unaligned(ccid_class_desc.dwDataRate, req->buf);
+ 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 0;
+
+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 ccidg_bulk_dev *bulk_dev;
+ struct f_ccidg *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;
+ size_t 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 = min_t(size_t, req->actual, count);
+
+ if (copy_to_user(buf, req->buf, xfer))
+ return -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 = func_to_ccidg(f);
+ struct f_ccidg_opts *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 = container_of(fi, struct f_ccidg_opts,
+ func_inst);
+ 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);
+ }
+
+ 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 = 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");
diff --git a/drivers/usb/gadget/function/f_ccid.h b/drivers/usb/gadget/function/f_ccid.h
new file mode 100644
index 000000000000..f1053ec5c4d9
--- /dev/null
+++ b/drivers/usb/gadget/function/f_ccid.h
@@ -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
diff --git a/include/uapi/linux/usb/ccid.h b/include/uapi/linux/usb/ccid.h
new file mode 100644
index 000000000000..a9f8906f1948
--- /dev/null
+++ b/include/uapi/linux/usb/ccid.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * 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 (0 << 0)
+#define CCID_PINSUPOORT_VERIFICATION (1 << 1)
+#define CCID_PINSUPOORT_MODIFICATION (1 << 2)
+
+#endif
--
2.18.0
WARNING: multiple messages have this Message-ID (diff)
From: Marcus Folkesson <marcus.folkesson@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jonathan Corbet <corbet@lwn.net>, Felipe Balbi <balbi@kernel.org>,
davem@davemloft.net,
Mauro Carvalho Chehab <mchehab+samsung@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Ruslan Bilovol <ruslan.bilovol@gmail.com>,
Thomas Gleixner <tglx@linutronix.de>,
Kate Stewart <kstewart@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org,
Marcus Folkesson <marcus.folkesson@gmail.com>
Subject: [PATCH v4 1/3] usb: gadget: ccid: add support for USB CCID Gadget Device
Date: Tue, 26 Jun 2018 10:14:20 +0200 [thread overview]
Message-ID: <20180626081422.19237-1-marcus.folkesson@gmail.com> (raw)
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:
v4:
- fix comments from Andy, Thanks!
- add "WITH Linux-syscall-note" in the SPDX identifier for uapi-header
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 | 989 +++++++++++++++++++++++++++
drivers/usb/gadget/function/f_ccid.h | 91 +++
include/uapi/linux/usb/ccid.h | 93 +++
5 files changed, 1191 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
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 31cce7805eb2..bdebdf1ffa2b 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -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
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02218..629851009e1a 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -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
diff --git a/drivers/usb/gadget/function/f_ccid.c b/drivers/usb/gadget/function/f_ccid.c
new file mode 100644
index 000000000000..2a890dca31e1
--- /dev/null
+++ b/drivers/usb/gadget/function/f_ccid.c
@@ -0,0 +1,989 @@
+// 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 <asm/unaligned.h>
+#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)) {
+ ret = PTR_ERR(ccidg_class);
+ ccidg_class = NULL;
+ return ret;
+ }
+
+ 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;
+ ida_destroy(&ccidg_ida);
+}
+
+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);
+ req = list_first_entry_or_null(head, struct usb_request, list);
+ if (req)
+ 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->buf = kmalloc(len, GFP_ATOMIC);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return ERR_PTR(-ENOMEM);
+ }
+ req->length = len;
+
+ return req;
+}
+
+static void ccidg_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (!req)
+ return;
+
+ 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;
+
+ put_unaligned(ccid_class_desc.dwDefaultClock, req->buf);
+ ret = sizeof(ccid_class_desc.dwDefaultClock);
+ break;
+
+ case CCIDGENERICREQ_GET_DATA_RATES:
+ if (w_length > sizeof(ccid_class_desc.dwDataRate))
+ break;
+
+ put_unaligned(ccid_class_desc.dwDataRate, req->buf);
+ 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 0;
+
+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 ccidg_bulk_dev *bulk_dev;
+ struct f_ccidg *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;
+ size_t 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 = min_t(size_t, req->actual, count);
+
+ if (copy_to_user(buf, req->buf, xfer))
+ return -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 = func_to_ccidg(f);
+ struct f_ccidg_opts *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 = container_of(fi, struct f_ccidg_opts,
+ func_inst);
+ 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);
+ }
+
+ 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 = 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");
diff --git a/drivers/usb/gadget/function/f_ccid.h b/drivers/usb/gadget/function/f_ccid.h
new file mode 100644
index 000000000000..f1053ec5c4d9
--- /dev/null
+++ b/drivers/usb/gadget/function/f_ccid.h
@@ -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
diff --git a/include/uapi/linux/usb/ccid.h b/include/uapi/linux/usb/ccid.h
new file mode 100644
index 000000000000..a9f8906f1948
--- /dev/null
+++ b/include/uapi/linux/usb/ccid.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * 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 (0 << 0)
+#define CCID_PINSUPOORT_VERIFICATION (1 << 1)
+#define CCID_PINSUPOORT_MODIFICATION (1 << 2)
+
+#endif
--
2.18.0
--
To unsubscribe from this list: send the line "unsubscribe linux-doc" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
WARNING: multiple messages have this Message-ID (diff)
From: Marcus Folkesson <marcus.folkesson@gmail.com>
To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Jonathan Corbet <corbet@lwn.net>, Felipe Balbi <balbi@kernel.org>,
davem@davemloft.net,
Mauro Carvalho Chehab <mchehab+samsung@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Ruslan Bilovol <ruslan.bilovol@gmail.com>,
Thomas Gleixner <tglx@linutronix.de>,
Kate Stewart <kstewart@linuxfoundation.org>
Cc: linux-usb@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kernel@vger.kernel.org,
Marcus Folkesson <marcus.folkesson@gmail.com>
Subject: [v4,1/3] usb: gadget: ccid: add support for USB CCID Gadget Device
Date: Tue, 26 Jun 2018 10:14:20 +0200 [thread overview]
Message-ID: <20180626081422.19237-1-marcus.folkesson@gmail.com> (raw)
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:
v4:
- fix comments from Andy, Thanks!
- add "WITH Linux-syscall-note" in the SPDX identifier for uapi-header
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 | 989 +++++++++++++++++++++++++++
drivers/usb/gadget/function/f_ccid.h | 91 +++
include/uapi/linux/usb/ccid.h | 93 +++
5 files changed, 1191 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
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 31cce7805eb2..bdebdf1ffa2b 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -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
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index 5d3a6cf02218..629851009e1a 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -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
diff --git a/drivers/usb/gadget/function/f_ccid.c b/drivers/usb/gadget/function/f_ccid.c
new file mode 100644
index 000000000000..2a890dca31e1
--- /dev/null
+++ b/drivers/usb/gadget/function/f_ccid.c
@@ -0,0 +1,989 @@
+// 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 <asm/unaligned.h>
+#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)) {
+ ret = PTR_ERR(ccidg_class);
+ ccidg_class = NULL;
+ return ret;
+ }
+
+ 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;
+ ida_destroy(&ccidg_ida);
+}
+
+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);
+ req = list_first_entry_or_null(head, struct usb_request, list);
+ if (req)
+ 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->buf = kmalloc(len, GFP_ATOMIC);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return ERR_PTR(-ENOMEM);
+ }
+ req->length = len;
+
+ return req;
+}
+
+static void ccidg_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (!req)
+ return;
+
+ 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;
+
+ put_unaligned(ccid_class_desc.dwDefaultClock, req->buf);
+ ret = sizeof(ccid_class_desc.dwDefaultClock);
+ break;
+
+ case CCIDGENERICREQ_GET_DATA_RATES:
+ if (w_length > sizeof(ccid_class_desc.dwDataRate))
+ break;
+
+ put_unaligned(ccid_class_desc.dwDataRate, req->buf);
+ 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 0;
+
+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 ccidg_bulk_dev *bulk_dev;
+ struct f_ccidg *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;
+ size_t 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 = min_t(size_t, req->actual, count);
+
+ if (copy_to_user(buf, req->buf, xfer))
+ return -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 = func_to_ccidg(f);
+ struct f_ccidg_opts *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 = container_of(fi, struct f_ccidg_opts,
+ func_inst);
+ 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);
+ }
+
+ 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 = 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");
diff --git a/drivers/usb/gadget/function/f_ccid.h b/drivers/usb/gadget/function/f_ccid.h
new file mode 100644
index 000000000000..f1053ec5c4d9
--- /dev/null
+++ b/drivers/usb/gadget/function/f_ccid.h
@@ -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
diff --git a/include/uapi/linux/usb/ccid.h b/include/uapi/linux/usb/ccid.h
new file mode 100644
index 000000000000..a9f8906f1948
--- /dev/null
+++ b/include/uapi/linux/usb/ccid.h
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * 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 (0 << 0)
+#define CCID_PINSUPOORT_VERIFICATION (1 << 1)
+#define CCID_PINSUPOORT_MODIFICATION (1 << 2)
+
+#endif
next reply other threads:[~2018-06-26 8:16 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-06-26 8:14 Marcus Folkesson [this message]
2018-06-26 8:14 ` [v4,1/3] usb: gadget: ccid: add support for USB CCID Gadget Device Marcus Folkesson
2018-06-26 8:14 ` [PATCH v4 1/3] " Marcus Folkesson
2018-06-26 8:14 ` [PATCH v4 2/3] Documentation: usb: add documentation " Marcus Folkesson
2018-06-26 8:14 ` [v4,2/3] " Marcus Folkesson
2018-06-26 8:14 ` [PATCH v4 2/3] " Marcus Folkesson
2018-06-26 8:14 ` [PATCH v4 3/3] MAINTAINERS: add " Marcus Folkesson
2018-06-26 8:14 ` [v4,3/3] " Marcus Folkesson
2018-06-26 8:14 ` [PATCH v4 3/3] " Marcus Folkesson
2018-07-26 10:53 ` [PATCH v4 1/3] usb: gadget: ccid: add support for " Felipe Balbi
2018-07-26 10:53 ` [v4,1/3] " Felipe Balbi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20180626081422.19237-1-marcus.folkesson@gmail.com \
--to=marcus.folkesson@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=balbi@kernel.org \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=gregkh@linuxfoundation.org \
--cc=kstewart@linuxfoundation.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=mchehab+samsung@kernel.org \
--cc=rdunlap@infradead.org \
--cc=ruslan.bilovol@gmail.com \
--cc=tglx@linutronix.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.