text: /* 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 <linux/kernel.h>
  #include <linux/errno.h>
  #include <linux/init.h>
  #include <linux/slab.h>
  #include <linux/module.h>
  #include <linux/uaccess.h>
  #include <linux/usb.h>
  #include <linux/wait.h>
  #include <linux/cdev.h>
  #include <linux/ktime.h>
  #include <linux/debugfs.h>
  
  #include <linux/usb/ccid_bridge.h>
  
  #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;
  	__u8 breq = 0;
  
  	if (!ccid->intf) {
  		pr_debug("intf is not active");
  		return -ENODEV;
  	}
  	intf = ccid->intf->cur_altsetting->desc.bInterfaceNumber;
  
  	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");
  