/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt "\n", __func__ #include #include #include #include #include #include #include #include #include #include #include #include #define CCID_CLASS_DECRIPTOR_TYPE 0x21 #define CCID_NOTIFY_SLOT_CHANGE 0x50 #define CCID_NOTIFY_HARDWARE_ERROR 0x51 #define CCID_ABORT_REQ 0x1 #define CCID_GET_CLK_FREQ_REQ 0x2 #define CCID_GET_DATA_RATES 0x3 #define CCID_BRIDGE_MSG_SZ 512 #define CCID_BRIDGE_OPEN_TIMEOUT 500 /* msec */ #define CCID_CONTROL_TIMEOUT 500 /* msec */ #define CCID_BRIDGE_MSG_TIMEOUT 1000 /* msec */ static unsigned ccid_bulk_msg_timeout = CCID_BRIDGE_MSG_TIMEOUT; module_param_named(bulk_msg_timeout, ccid_bulk_msg_timeout, uint, 0644); MODULE_PARM_DESC(bulk_msg_timeout, "Bulk message timeout (msecs)"); struct ccid_bridge { struct usb_device *udev; struct usb_interface *intf; unsigned int in_pipe; unsigned int out_pipe; unsigned int int_pipe; struct urb *inturb; struct urb *readurb; struct urb *writeurb; bool opened; bool events_supported; bool is_suspended; struct mutex open_mutex; struct mutex write_mutex; struct mutex read_mutex; struct mutex event_mutex; int write_result; int read_result; int event_result; wait_queue_head_t open_wq; wait_queue_head_t write_wq; wait_queue_head_t read_wq; wait_queue_head_t event_wq; struct usb_ccid_event cur_event; void *intbuf; dev_t chrdev; struct cdev cdev; struct class *class; struct device *device; struct dentry *dbg_root; unsigned n_write; unsigned n_read; unsigned n_write_timeout; unsigned n_read_timeout; unsigned long write_max_time; unsigned long read_max_time; }; static struct ccid_bridge *__ccid_bridge_dev; static void ccid_bridge_out_cb(struct urb *urb) { struct ccid_bridge *ccid = urb->context; if (urb->dev->state == USB_STATE_NOTATTACHED) ccid->write_result = -ENODEV; else ccid->write_result = urb->status ? : urb->actual_length; pr_debug("write result = %d", ccid->write_result); wake_up(&ccid->write_wq); } static void ccid_bridge_in_cb(struct urb *urb) { struct ccid_bridge *ccid = urb->context; if (urb->dev->state == USB_STATE_NOTATTACHED) ccid->read_result = -ENODEV; else ccid->read_result = urb->status ? : urb->actual_length; pr_debug("read result = %d", ccid->read_result); wake_up(&ccid->read_wq); } static void ccid_bridge_int_cb(struct urb *urb) { struct ccid_bridge *ccid = urb->context; u8 *msg_type; bool wakeup = true; if (urb->dev->state == USB_STATE_NOTATTACHED || (urb->status && urb->status != -ENOENT)) { ccid->event_result = -ENODEV; wakeup = true; goto out; } /* * Don't wakeup the event ioctl process during suspend. * The suspend state is not visible to user space. * we wake up the process after resume to send RESUME * event if the device supports remote wakeup. */ if (urb->status == -ENOENT && !urb->actual_length) { ccid->event_result = -ENOENT; wakeup = false; goto out; } ccid->event_result = 0; msg_type = urb->transfer_buffer; switch (*msg_type) { case CCID_NOTIFY_SLOT_CHANGE: pr_debug("NOTIFY_SLOT_CHANGE event arrived"); ccid->cur_event.event = USB_CCID_NOTIFY_SLOT_CHANGE_EVENT; ccid->cur_event.u.notify.slot_icc_state = *(++msg_type); break; case CCID_NOTIFY_HARDWARE_ERROR: pr_debug("NOTIFY_HARDWARE_ERROR event arrived"); ccid->cur_event.event = USB_CCID_HARDWARE_ERROR_EVENT; ccid->cur_event.u.error.slot = *(++msg_type); ccid->cur_event.u.error.seq = *(++msg_type); ccid->cur_event.u.error.error_code = *(++msg_type); break; default: pr_err("UNKNOWN event arrived\n"); ccid->event_result = -EINVAL; } out: pr_debug("returning %d", ccid->event_result); if (wakeup) wake_up(&ccid->event_wq); } static int ccid_bridge_submit_inturb(struct ccid_bridge *ccid) { int ret = 0; /* * Don't resume the bus to submit an interrupt URB. * We submit the URB in resume path. This is important. * Because the device will be in suspend state during * multiple system suspend/resume cycles. The user space * process comes here during system resume after it is * unfrozen. */ if (!ccid->int_pipe || ccid->is_suspended) goto out; ret = usb_autopm_get_interface(ccid->intf); if (ret < 0) { pr_debug("fail to get autopm with %d\n", ret); goto out; } ret = usb_submit_urb(ccid->inturb, GFP_KERNEL); if (ret < 0) pr_err("fail to submit int urb with %d\n", ret); usb_autopm_put_interface(ccid->intf); out: pr_debug("returning %d", ret); return ret; } static int ccid_bridge_get_event(struct ccid_bridge *ccid) { int ret = 0; /* * The first event returned after the device resume * will be RESUME event. This event is set by * the resume. */ if (ccid->cur_event.event) goto out; ccid->event_result = -EINPROGRESS; ret = ccid_bridge_submit_inturb(ccid); if (ret < 0) goto out; /* * Wait for the notification on interrupt endpoint * or remote wakeup event from the resume. The * int urb completion handler and resume callback * take care of setting the current event. */ mutex_unlock(&ccid->event_mutex); ret = wait_event_interruptible(ccid->event_wq, (ccid->event_result != -EINPROGRESS)); mutex_lock(&ccid->event_mutex); if (ret == -ERESTARTSYS) /* interrupted */ usb_kill_urb(ccid->inturb); else ret = ccid->event_result; out: pr_debug("returning %d", ret); return ret; } static int ccid_bridge_open(struct inode *ip, struct file *fp) { struct ccid_bridge *ccid = container_of(ip->i_cdev, struct ccid_bridge, cdev); int ret; pr_debug("called"); mutex_lock(&ccid->open_mutex); if (ccid->opened) { ret = -EBUSY; goto out; } mutex_unlock(&ccid->open_mutex); ret = wait_event_interruptible_timeout(ccid->open_wq, ccid->intf != NULL, msecs_to_jiffies( CCID_BRIDGE_OPEN_TIMEOUT)); mutex_lock(&ccid->open_mutex); if (ret != -ERESTARTSYS && ccid->intf) { fp->private_data = ccid; ccid->opened = true; ret = 0; } else if (!ret) { /* timed out */ ret = -ENODEV; } out: mutex_unlock(&ccid->open_mutex); pr_debug("returning %d", ret); return ret; } static ssize_t ccid_bridge_write(struct file *fp, const char __user *ubuf, size_t count, loff_t *pos) { struct ccid_bridge *ccid = fp->private_data; int ret; char *kbuf; ktime_t start_t, delta_t; pr_debug("called with %zu", count); if (!ccid->intf) { pr_debug("intf is not active"); return -ENODEV; } mutex_lock(&ccid->write_mutex); ccid->n_write++; start_t = ktime_get(); if (!count || count > CCID_BRIDGE_MSG_SZ) { pr_err("invalid count"); ret = -EINVAL; goto out; } kbuf = kmalloc(count, GFP_KERNEL); if (!kbuf) { pr_err("fail to allocate memory"); ret = -ENOMEM; goto out; } ret = copy_from_user(kbuf, ubuf, count); if (ret) { pr_err("fail to copy user buf"); ret = -EFAULT; goto free_kbuf; } ret = usb_autopm_get_interface(ccid->intf); if (ret) { pr_err("fail to get autopm with %d", ret); goto free_kbuf; } ccid->write_result = 0; usb_fill_bulk_urb(ccid->writeurb, ccid->udev, ccid->out_pipe, kbuf, count, ccid_bridge_out_cb, ccid); ret = usb_submit_urb(ccid->writeurb, GFP_KERNEL); if (ret < 0) { pr_err("urb submit fail with %d", ret); goto put_pm; } ret = wait_event_interruptible_timeout(ccid->write_wq, ccid->write_result != 0, msecs_to_jiffies(ccid_bulk_msg_timeout)); if (!ret || ret == -ERESTARTSYS) { /* timedout or interrupted */ usb_kill_urb(ccid->writeurb); if (!ret) { ccid->n_write_timeout++; ret = -ETIMEDOUT; } } else { ret = ccid->write_result; } if (ret >= 0) { delta_t = ktime_sub(ktime_get(), start_t); if (ktime_to_ms(delta_t) > ccid->write_max_time) ccid->write_max_time = ktime_to_ms(delta_t); } pr_debug("returning %d", ret); put_pm: if (ret != -ENODEV) usb_autopm_put_interface(ccid->intf); free_kbuf: kfree(kbuf); out: mutex_unlock(&ccid->write_mutex); return ret; } static ssize_t ccid_bridge_read(struct file *fp, char __user *ubuf, size_t count, loff_t *pos) { struct ccid_bridge *ccid = fp->private_data; int ret; char *kbuf; ktime_t start_t, delta_t; pr_debug("called with %zu", count); if (!ccid->intf) { pr_debug("intf is not active"); return -ENODEV; } mutex_lock(&ccid->read_mutex); ccid->n_read++; start_t = ktime_get(); if (!count || count > CCID_BRIDGE_MSG_SZ) { pr_err("invalid count"); ret = -EINVAL; goto out; } kbuf = kmalloc(count, GFP_KERNEL); if (!kbuf) { pr_err("fail to allocate memory"); ret = -ENOMEM; goto out; } ret = usb_autopm_get_interface(ccid->intf); if (ret) { pr_err("fail to get autopm with %d", ret); goto free_kbuf; } ccid->read_result = 0; usb_fill_bulk_urb(ccid->readurb, ccid->udev, ccid->in_pipe, kbuf, count, ccid_bridge_in_cb, ccid); ret = usb_submit_urb(ccid->readurb, GFP_KERNEL); if (ret < 0) { pr_err("urb submit fail with %d", ret); if (ret != -ENODEV) usb_autopm_put_interface(ccid->intf); goto free_kbuf; } ret = wait_event_interruptible_timeout(ccid->read_wq, ccid->read_result != 0, msecs_to_jiffies(ccid_bulk_msg_timeout)); if (!ret || ret == -ERESTARTSYS) { /* timedout or interrupted */ usb_kill_urb(ccid->readurb); if (!ret) { ccid->n_read_timeout++; ret = -ETIMEDOUT; } } else { ret = ccid->read_result; } if (ret > 0) { if (copy_to_user(ubuf, kbuf, ret)) ret = -EFAULT; delta_t = ktime_sub(ktime_get(), start_t); if (ktime_to_ms(delta_t) > ccid->read_max_time) ccid->read_max_time = ktime_to_ms(delta_t); } usb_autopm_put_interface(ccid->intf); pr_debug("returning %d", ret); free_kbuf: kfree(kbuf); out: mutex_unlock(&ccid->read_mutex); return ret; } static long ccid_bridge_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) { struct ccid_bridge *ccid = fp->private_data; char *buf; struct usb_ccid_data data; struct usb_ccid_abort abort; struct usb_descriptor_header *header; int ret; struct usb_device *udev = ccid->udev; __u8 intf = ccid->intf->cur_altsetting->desc.bInterfaceNumber; __u8 breq = 0; if (!ccid->intf) { pr_debug("intf is not active"); return -ENODEV; } mutex_lock(&ccid->event_mutex); switch (cmd) { case USB_CCID_GET_CLASS_DESC: pr_debug("GET_CLASS_DESC ioctl called"); ret = copy_from_user(&data, (void __user *)arg, sizeof(data)); if (ret) { ret = -EFAULT; break; } ret = __usb_get_extra_descriptor(udev->rawdescriptors[0], le16_to_cpu(udev->config[0].desc.wTotalLength), CCID_CLASS_DECRIPTOR_TYPE, (void **) &buf); if (ret) { ret = -ENOENT; break; } header = (struct usb_descriptor_header *) buf; if (data.length != header->bLength) { ret = -EINVAL; break; } ret = copy_to_user((void __user *)data.data, buf, data.length); if (ret) ret = -EFAULT; break; case USB_CCID_GET_CLOCK_FREQUENCIES: pr_debug("GET_CLOCK_FREQUENCIES ioctl called"); breq = CCID_GET_CLK_FREQ_REQ; /* fall through */ case USB_CCID_GET_DATA_RATES: if (!breq) { pr_debug("GET_DATA_RATES ioctl called"); breq = CCID_GET_DATA_RATES; } ret = copy_from_user(&data, (void __user *)arg, sizeof(data)); if (ret) { ret = -EFAULT; break; } buf = kmalloc(data.length, GFP_KERNEL); if (!buf) { ret = -ENOMEM; break; } ret = usb_autopm_get_interface(ccid->intf); if (ret < 0) { pr_debug("fail to get autopm with %d", ret); break; } ret = usb_control_msg(ccid->udev, usb_rcvctrlpipe(ccid->udev, 0), breq, (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE), 0, intf, buf, data.length, CCID_CONTROL_TIMEOUT); usb_autopm_put_interface(ccid->intf); if (ret == data.length) { ret = copy_to_user((void __user *)data.data, buf, data.length); if (ret) ret = -EFAULT; } else { if (ret > 0) ret = -EPIPE; } kfree(buf); break; case USB_CCID_ABORT: pr_debug("ABORT ioctl called"); breq = CCID_ABORT_REQ; ret = copy_from_user(&abort, (void __user *)arg, sizeof(abort)); if (ret) { ret = -EFAULT; break; } ret = usb_autopm_get_interface(ccid->intf); if (ret < 0) { pr_debug("fail to get autopm with %d", ret); break; } ret = usb_control_msg(ccid->udev, usb_sndctrlpipe(ccid->udev, 0), breq, (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE), (abort.seq << 8) | abort.slot, intf, NULL, 0, CCID_CONTROL_TIMEOUT); if (ret < 0) pr_err("abort request failed with err %d\n", ret); usb_autopm_put_interface(ccid->intf); break; case USB_CCID_GET_EVENT: pr_debug("GET_EVENT ioctl called"); if (!ccid->events_supported) { ret = -ENOENT; break; } ret = ccid_bridge_get_event(ccid); if (ret == 0) { ret = copy_to_user((void __user *)arg, &ccid->cur_event, sizeof(ccid->cur_event)); if (ret) ret = -EFAULT; } ccid->cur_event.event = 0; break; default: pr_err("UNKNOWN ioctl called"); ret = -EINVAL; break; } mutex_unlock(&ccid->event_mutex); pr_debug("returning %d", ret); return ret; } static void ccid_bridge_reset_stats(struct ccid_bridge *ccid) { ccid->n_write = 0; ccid->n_read = 0; ccid->n_write_timeout = 0; ccid->n_read_timeout = 0; ccid->write_max_time = 0; ccid->read_max_time = 0; } static int ccid_bridge_release(struct inode *ip, struct file *fp) { struct ccid_bridge *ccid = fp->private_data; pr_debug("called"); mutex_lock(&ccid->open_mutex); if (ccid->intf == NULL) { ccid->opened = false; mutex_unlock(&ccid->open_mutex); goto done; } mutex_unlock(&ccid->open_mutex); usb_kill_urb(ccid->writeurb); usb_kill_urb(ccid->readurb); if (ccid->int_pipe) usb_kill_urb(ccid->inturb); ccid->event_result = -EIO; wake_up(&ccid->event_wq); mutex_lock(&ccid->open_mutex); ccid->opened = false; mutex_unlock(&ccid->open_mutex); done: ccid_bridge_reset_stats(ccid); return 0; } static const struct file_operations ccid_bridge_fops = { .owner = THIS_MODULE, .open = ccid_bridge_open, .write = ccid_bridge_write, .read = ccid_bridge_read, .unlocked_ioctl = ccid_bridge_ioctl, .release = ccid_bridge_release, }; static int ccid_bridge_suspend(struct usb_interface *intf, pm_message_t message) { struct ccid_bridge *ccid = usb_get_intfdata(intf); int ret = 0; pr_debug("called"); if (!ccid->opened) goto out; mutex_lock(&ccid->event_mutex); if (ccid->int_pipe) { usb_kill_urb(ccid->inturb); if (ccid->event_result != -ENOENT) { ret = -EBUSY; goto rel_mutex; } } ccid->is_suspended = true; rel_mutex: mutex_unlock(&ccid->event_mutex); out: pr_debug("returning %d", ret); return ret; } static int ccid_bridge_resume(struct usb_interface *intf) { struct ccid_bridge *ccid = usb_get_intfdata(intf); int ret; pr_debug("called"); if (!ccid->opened) goto out; mutex_lock(&ccid->event_mutex); ccid->is_suspended = false; if (device_can_wakeup(&ccid->udev->dev)) { ccid->event_result = 0; ccid->cur_event.event = USB_CCID_RESUME_EVENT; wake_up(&ccid->event_wq); } else if (ccid->int_pipe) { ccid->event_result = -EINPROGRESS; ret = usb_submit_urb(ccid->inturb, GFP_KERNEL); if (ret < 0) pr_debug("fail to submit inturb with %d\n", ret); } mutex_unlock(&ccid->event_mutex); out: return 0; } static int ccid_bridge_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct ccid_bridge *ccid = __ccid_bridge_dev; struct usb_host_interface *intf_desc; struct usb_endpoint_descriptor *ep_desc; struct usb_host_endpoint *ep; __u8 epin_addr = 0, epout_addr = 0, epint_addr = 0; int i, ret; intf_desc = intf->cur_altsetting; if (intf_desc->desc.bNumEndpoints > 3) return -ENODEV; for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) { ep_desc = &intf_desc->endpoint[i].desc; if (usb_endpoint_is_bulk_in(ep_desc)) epin_addr = ep_desc->bEndpointAddress; else if (usb_endpoint_is_bulk_out(ep_desc)) epout_addr = ep_desc->bEndpointAddress; else if (usb_endpoint_is_int_in(ep_desc)) epint_addr = ep_desc->bEndpointAddress; else return -ENODEV; } if (!epin_addr || !epout_addr) return -ENODEV; ccid->udev = usb_get_dev(interface_to_usbdev(intf)); ccid->in_pipe = usb_rcvbulkpipe(ccid->udev, epin_addr); ccid->out_pipe = usb_sndbulkpipe(ccid->udev, epout_addr); if (epint_addr) ccid->int_pipe = usb_rcvbulkpipe(ccid->udev, epint_addr); ccid->writeurb = usb_alloc_urb(0, GFP_KERNEL); if (!ccid->writeurb) { pr_err("fail to allocate write urb"); ret = -ENOMEM; goto put_udev; } ccid->readurb = usb_alloc_urb(0, GFP_KERNEL); if (!ccid->readurb) { pr_err("fail to allocate read urb"); ret = -ENOMEM; goto free_writeurb; } if (ccid->int_pipe) { pr_debug("interrupt endpoint is present"); ep = usb_pipe_endpoint(ccid->udev, ccid->int_pipe); ccid->inturb = usb_alloc_urb(0, GFP_KERNEL); if (!ccid->inturb) { pr_err("fail to allocate int urb"); ret = -ENOMEM; goto free_readurb; } ccid->intbuf = kmalloc(usb_endpoint_maxp(&ep->desc), GFP_KERNEL); if (!ccid->intbuf) { pr_err("fail to allocated int buf"); ret = -ENOMEM; goto free_inturb; } usb_fill_int_urb(ccid->inturb, ccid->udev, usb_rcvintpipe(ccid->udev, epint_addr), ccid->intbuf, usb_endpoint_maxp(&ep->desc), ccid_bridge_int_cb, ccid, ep->desc.bInterval); } if (ccid->int_pipe || device_can_wakeup(&ccid->udev->dev)) { pr_debug("event support is present"); ccid->events_supported = true; } usb_set_intfdata(intf, ccid); usb_enable_autosuspend(ccid->udev); mutex_lock(&ccid->open_mutex); ccid->intf = intf; wake_up(&ccid->open_wq); mutex_unlock(&ccid->open_mutex); pr_info("success"); return 0; free_inturb: if (ccid->int_pipe) usb_free_urb(ccid->inturb); free_readurb: usb_free_urb(ccid->readurb); free_writeurb: usb_free_urb(ccid->writeurb); put_udev: usb_put_dev(ccid->udev); return ret; } static void ccid_bridge_disconnect(struct usb_interface *intf) { struct ccid_bridge *ccid = usb_get_intfdata(intf); pr_debug("called"); usb_kill_urb(ccid->writeurb); usb_kill_urb(ccid->readurb); if (ccid->int_pipe) usb_kill_urb(ccid->inturb); ccid->event_result = -ENODEV; wake_up(&ccid->event_wq); /* * This would synchronize any ongoing read/write/ioctl. * After acquiring the mutex, we can safely set * intf to NULL. */ mutex_lock(&ccid->open_mutex); mutex_lock(&ccid->write_mutex); mutex_lock(&ccid->read_mutex); mutex_lock(&ccid->event_mutex); usb_free_urb(ccid->writeurb); usb_free_urb(ccid->readurb); if (ccid->int_pipe) { usb_free_urb(ccid->inturb); kfree(ccid->intbuf); ccid->int_pipe = 0; } ccid->intf = NULL; usb_put_dev(ccid->udev); mutex_unlock(&ccid->event_mutex); mutex_unlock(&ccid->read_mutex); mutex_unlock(&ccid->write_mutex); mutex_unlock(&ccid->open_mutex); } static const struct usb_device_id ccid_bridge_ids[] = { { USB_INTERFACE_INFO(USB_CLASS_CSCID, 0, 0) }, {} /* terminating entry */ }; MODULE_DEVICE_TABLE(usb, ccid_bridge_ids); static struct usb_driver ccid_bridge_driver = { .name = "ccid_bridge", .probe = ccid_bridge_probe, .disconnect = ccid_bridge_disconnect, .suspend = ccid_bridge_suspend, .resume = ccid_bridge_resume, .id_table = ccid_bridge_ids, .supports_autosuspend = 1, }; static int ccid_bridge_stats_show(struct seq_file *s, void *unused) { struct ccid_bridge *ccid = s->private; seq_printf(s, "ccid_bridge: %s\n", ccid->intf ? "connected" : "disconnected"); seq_printf(s, "ccid_bridge: %s\n", ccid->opened ? "opened" : "closed"); seq_printf(s, "total writes: %u\n", ccid->n_write); seq_printf(s, "total reads: %u\n", ccid->n_write); seq_printf(s, "write/read timeout val: %u\n", ccid_bulk_msg_timeout); seq_printf(s, "write_timeout: %u\n", ccid->n_write_timeout); seq_printf(s, "read_timeout: %u\n", ccid->n_read_timeout); seq_printf(s, "write_max_time (msec): %lu\n", ccid->write_max_time); seq_printf(s, "read_max_time: (msec): %lu\n", ccid->read_max_time); return 0; } static int ccid_bridge_stats_open(struct inode *inode, struct file *file) { return single_open(file, ccid_bridge_stats_show, inode->i_private); } static ssize_t ccid_bridge_stats_write(struct file *file, const char __user *ubuf, size_t count, loff_t *ppos) { struct seq_file *s = file->private_data; struct ccid_bridge *ccid = s->private; ccid_bridge_reset_stats(ccid); return count; } const struct file_operations ccid_bridge_stats_ops = { .open = ccid_bridge_stats_open, .read = seq_read, .write = ccid_bridge_stats_write, .llseek = seq_lseek, .release = single_release, }; static int ccid_bridge_debugfs_init(struct ccid_bridge *ccid) { struct dentry *dir; int ret = 0; dir = debugfs_create_dir("ccid_bridge", NULL); if (!dir || IS_ERR(dir)) { ret = -ENODEV; goto out; } ccid->dbg_root = dir; dir = debugfs_create_file("stats", 0644, ccid->dbg_root, ccid, &ccid_bridge_stats_ops); if (!dir) { debugfs_remove_recursive(ccid->dbg_root); ccid->dbg_root = NULL; ret = -ENODEV; } out: return ret; } static int __init ccid_bridge_init(void) { int ret; struct ccid_bridge *ccid; ccid = kzalloc(sizeof(*ccid), GFP_KERNEL); if (!ccid) { pr_err("Fail to allocate ccid"); ret = -ENOMEM; goto out; } __ccid_bridge_dev = ccid; mutex_init(&ccid->open_mutex); mutex_init(&ccid->write_mutex); mutex_init(&ccid->read_mutex); mutex_init(&ccid->event_mutex); init_waitqueue_head(&ccid->open_wq); init_waitqueue_head(&ccid->write_wq); init_waitqueue_head(&ccid->read_wq); init_waitqueue_head(&ccid->event_wq); ret = usb_register(&ccid_bridge_driver); if (ret < 0) { pr_err("Fail to register ccid usb driver with %d", ret); goto free_ccid; } ret = alloc_chrdev_region(&ccid->chrdev, 0, 1, "ccid_bridge"); if (ret < 0) { pr_err("Fail to allocate ccid char dev region with %d", ret); goto unreg_driver; } ccid->class = class_create(THIS_MODULE, "ccid_bridge"); if (IS_ERR(ccid->class)) { ret = PTR_ERR(ccid->class); pr_err("Fail to create ccid class with %d", ret); goto unreg_chrdev; } cdev_init(&ccid->cdev, &ccid_bridge_fops); ccid->cdev.owner = THIS_MODULE; ret = cdev_add(&ccid->cdev, ccid->chrdev, 1); if (ret < 0) { pr_err("Fail to add ccid cdev with %d", ret); goto destroy_class; } ccid->device = device_create(ccid->class, NULL, ccid->chrdev, NULL, "ccid_bridge"); if (IS_ERR(ccid->device)) { ret = PTR_ERR(ccid->device); pr_err("Fail to create ccid device with %d", ret); goto del_cdev; } ccid_bridge_debugfs_init(ccid); pr_info("success"); return 0; del_cdev: cdev_del(&ccid->cdev); destroy_class: class_destroy(ccid->class); unreg_chrdev: unregister_chrdev_region(ccid->chrdev, 1); unreg_driver: usb_deregister(&ccid_bridge_driver); free_ccid: mutex_destroy(&ccid->open_mutex); mutex_destroy(&ccid->write_mutex); mutex_destroy(&ccid->read_mutex); mutex_destroy(&ccid->event_mutex); kfree(ccid); __ccid_bridge_dev = NULL; out: return ret; } static void __exit ccid_bridge_exit(void) { struct ccid_bridge *ccid = __ccid_bridge_dev; pr_debug("called"); debugfs_remove_recursive(ccid->dbg_root); device_destroy(ccid->class, ccid->chrdev); cdev_del(&ccid->cdev); class_destroy(ccid->class); unregister_chrdev_region(ccid->chrdev, 1); usb_deregister(&ccid_bridge_driver); mutex_destroy(&ccid->open_mutex); mutex_destroy(&ccid->write_mutex); mutex_destroy(&ccid->read_mutex); mutex_destroy(&ccid->event_mutex); kfree(ccid); __ccid_bridge_dev = NULL; } module_init(ccid_bridge_init); module_exit(ccid_bridge_exit); MODULE_DESCRIPTION("USB CCID bridge driver"); MODULE_LICENSE("GPL v2");