diff options
Diffstat (limited to 'drivers/usb')
127 files changed, 55823 insertions, 320 deletions
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 48f1781..2f3e764 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -19,6 +19,7 @@ config USB_ARCH_HAS_HCD boolean default y if USB_ARCH_HAS_OHCI default y if USB_ARCH_HAS_EHCI + default y if USB_ARCH_HAS_XHCI default y if PCMCIA && !M32R # sl811_cs default y if ARM # SL-811 default y if BLACKFIN # SL-811 @@ -42,6 +43,7 @@ config USB_ARCH_HAS_OHCI default y if ARCH_DAVINCI_DA8XX default y if ARCH_CNS3XXX default y if PLAT_SPEAR + default y if PLAT_S5P # PPC: default y if STB03xxx default y if PPC_MPC52xx @@ -71,6 +73,12 @@ config USB_ARCH_HAS_EHCI default y if SPARC_LEON default PCI +# some non-PCI HCDs implement xHCI +config USB_ARCH_HAS_XHCI + boolean + default y if ARCH_EXYNOS + default PCI + # ARM SA1111 chips have a non-PCI based "OHCI-compatible" USB host interface. config USB tristate "Support for Host-side USB" @@ -110,6 +118,8 @@ config USB source "drivers/usb/core/Kconfig" +source "drivers/usb/notify/Kconfig" + source "drivers/usb/mon/Kconfig" source "drivers/usb/wusbcore/Kconfig" diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index 30ddf8d..57634b5 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_USB) += core/ +obj-$(CONFIG_USB_HOST_NOTIFY) += notify/ obj-$(CONFIG_USB_MON) += mon/ obj-$(CONFIG_PCI) += host/ diff --git a/drivers/usb/class/msm-dload.c b/drivers/usb/class/msm-dload.c new file mode 100644 index 0000000..b1738c1 --- /dev/null +++ b/drivers/usb/class/msm-dload.c @@ -0,0 +1,766 @@ +/* + * USB Skeleton driver - 2.2 + * + * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c + * but has been rewritten to be easier to read and use. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/mutex.h> + +/* Define these values to match your devices */ +#define USB_MSM_DLOAD_VENDOR_ID 0x05C6 +#define USB_MSM_DLOAD_PRODUCT_ID 0x9008 + +/* table of devices that work with this driver */ +static const struct usb_device_id skel_table[] = { + { USB_DEVICE(USB_MSM_DLOAD_VENDOR_ID, USB_MSM_DLOAD_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, skel_table); + + +/* Get a minor range for your devices from the usb maintainer */ +#define USB_SKEL_MINOR_BASE 192 + +/* our private defines. if this grows any larger, use your own .h file */ +#define MAX_TRANSFER (PAGE_SIZE - 512) +/* MAX_TRANSFER is chosen so that the VM is not stressed by + allocations > PAGE_SIZE and the number of packets in a page + is an integer 512 is the largest possible packet on EHCI */ +#define WRITES_IN_FLIGHT 8 +/* arbitrarily chosen */ + +/* Structure to hold all of our device specific stuff */ +struct usb_skel { + /* the usb device for this device */ + struct usb_device *udev; + /* the interface for this device */ + struct usb_interface *interface; + /* limiting the number of writes in progress */ + struct semaphore limit_sem; + /* in case we need to retract our submissions */ + struct usb_anchor submitted; + /* the urb to read data with */ + struct urb *bulk_in_urb; + /* the buffer to receive data */ + unsigned char *bulk_in_buffer; + /* the size of the receive buffer */ + size_t bulk_in_size; + /* number of bytes in the buffer */ + size_t bulk_in_filled; + /* already copied to user space */ + size_t bulk_in_copied; + /* the address of the bulk in endpoint */ + __u8 bulk_in_endpointAddr; + /* the address of the bulk out endpoint */ + __u8 bulk_out_endpointAddr; + /* the last request tanked */ + int errors; + /* count the number of openers */ + int open_count; + /* a read is going on */ + bool ongoing_read; + /* indicates we haven't processed the urb */ + bool processed_urb; + /* indicates driver registration is finished */ + bool initialized; + /* lock for errors */ + spinlock_t err_lock; + struct kref kref; + /* synchronize I/O with disconnect */ + struct mutex io_mutex; + /* to wait for an ongoing read */ + struct completion bulk_in_completion; +}; +#define to_skel_dev(d) container_of(d, struct usb_skel, kref) + +static struct usb_driver skel_driver; +static void skel_draw_down(struct usb_skel *dev); + +static void skel_delete(struct kref *kref) +{ + struct usb_skel *dev = to_skel_dev(kref); + + usb_free_urb(dev->bulk_in_urb); + usb_put_dev(dev->udev); + kfree(dev->bulk_in_buffer); + kfree(dev); +} +static int skel_do_read_io(struct usb_skel *dev, size_t count); +static void skel_read_bulk_callback(struct urb *urb); + +static int skel_open(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + pr_info("%s ++ (%d)\n", __func__, current->pid); + + interface = usb_find_interface(&skel_driver, subminor); + if (!interface) { + err("%s - error, can't find device for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + if (!dev->initialized) { + pr_info("%s: not initialized yet\n", __func__); + return -ENODEV; + } + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* lock the device to allow correctly handling errors + * in resumption */ + mutex_lock(&dev->io_mutex); + + if (!dev->open_count++) { + retval = usb_autopm_get_interface(interface); + if (retval) { + dev->open_count--; + mutex_unlock(&dev->io_mutex); + kref_put(&dev->kref, skel_delete); + goto exit; + } + } /* else { //uncomment this block if you want exclusive open + retval = -EBUSY; + dev->open_count--; + mutex_unlock(&dev->io_mutex); + kref_put(&dev->kref, skel_delete); + goto exit; + } */ + /* prevent the device from being autosuspended */ + + /* save our object in the file's private structure */ + file->private_data = dev; + mutex_unlock(&dev->io_mutex); +/* + if (skel_do_read_io(dev, dev->bulk_in_size) < 0) { + err("%s - failed submitting read urb", + __func__); + } +*/ +exit: + pr_info("%s -- ret=%d\n", __func__, retval); + return retval; +} + +static int skel_release(struct inode *inode, struct file *file) +{ + struct usb_skel *dev; + + dev = (struct usb_skel *)file->private_data; + if (dev == NULL) + return -ENODEV; + + pr_info("%s ++ (%d)\n", __func__, current->pid); + + /* allow the device to be autosuspended */ + mutex_lock(&dev->io_mutex); + if (!--dev->open_count && dev->interface) + usb_autopm_put_interface(dev->interface); + mutex_unlock(&dev->io_mutex); + + /* decrement the count on our device */ + kref_put(&dev->kref, skel_delete); + + pr_info("%s --\n", __func__); + return 0; +} + +static int skel_flush(struct file *file, fl_owner_t id) +{ + struct usb_skel *dev; + int res; + + dev = (struct usb_skel *)file->private_data; + if (dev == NULL) + return -ENODEV; + + pr_info("%s ++ (%d)\n", __func__, current->pid); + + /* wait for io to stop */ + mutex_lock(&dev->io_mutex); + skel_draw_down(dev); + + /* read out errors, leave subsequent opens a clean slate */ + spin_lock_irq(&dev->err_lock); + res = dev->errors ? (dev->errors == -EPIPE ? -EPIPE : -EIO) : 0; + dev->errors = 0; + spin_unlock_irq(&dev->err_lock); + + mutex_unlock(&dev->io_mutex); + + pr_info("%s --\n", __func__); + + return res; +} + +static void skel_read_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + + dev = urb->context; + + spin_lock(&dev->err_lock); + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + err("%s - nonzero write bulk status received: %d", + __func__, urb->status); + + dev->errors = urb->status; + } else { + dev->bulk_in_filled = urb->actual_length; + } + dev->ongoing_read = 0; + spin_unlock(&dev->err_lock); + + complete(&dev->bulk_in_completion); +} + +static int skel_do_read_io(struct usb_skel *dev, size_t count) +{ + int rv; + + /* prepare a read */ + usb_fill_bulk_urb(dev->bulk_in_urb, + dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->bulk_in_endpointAddr), + dev->bulk_in_buffer, + min(dev->bulk_in_size, count), + skel_read_bulk_callback, + dev); + /* tell everybody to leave the URB alone */ + spin_lock_irq(&dev->err_lock); + dev->ongoing_read = 1; + spin_unlock_irq(&dev->err_lock); + + /* do it */ + rv = usb_submit_urb(dev->bulk_in_urb, GFP_KERNEL); + if (rv < 0) { + err("%s - failed submitting read urb, error %d", + __func__, rv); + dev->bulk_in_filled = 0; + rv = (rv == -ENOMEM) ? rv : -EIO; + spin_lock_irq(&dev->err_lock); + dev->ongoing_read = 0; + spin_unlock_irq(&dev->err_lock); + } + + return rv; +} + +static ssize_t skel_read(struct file *file, char *buffer, size_t count, + loff_t *ppos) +{ + struct usb_skel *dev; + int rv; + bool ongoing_io; + + dev = (struct usb_skel *)file->private_data; + + pr_debug("%s ++ (%d)\n", __func__, current->pid); + + /* if we cannot read at all, return EOF */ + if (!dev->bulk_in_urb || !count) + return 0; + + /* no concurrent readers */ + rv = mutex_lock_interruptible(&dev->io_mutex); + if (rv < 0) { + err("%s: failed to get mutex", __func__); + return rv; + } + + if (!dev->interface) { /* disconnect() was called */ + rv = -ENODEV; + goto exit; + } + + /* if IO is under way, we must not touch things */ +retry: + spin_lock_irq(&dev->err_lock); + ongoing_io = dev->ongoing_read; + spin_unlock_irq(&dev->err_lock); + + if (ongoing_io) { + /* nonblocking IO shall not wait */ + if (file->f_flags & O_NONBLOCK) { + rv = -EAGAIN; + goto exit; + } + /* + * IO may take forever + * hence wait in an interruptible state + */ + rv = wait_for_completion_interruptible + (&dev->bulk_in_completion); + if (rv < 0) + goto exit; + /* + * by waiting we also semiprocessed the urb + * we must finish now + */ + dev->bulk_in_copied = 0; + } + + rv = skel_do_read_io(dev, dev->bulk_in_size); + if (rv < 0) { + err("%s - failed submitting read urb, error %d", __func__, rv); + rv = 0; + goto exit; + } + init_completion(&dev->bulk_in_completion); + + /* errors must be reported */ + rv = dev->errors; + if (rv < 0) { + /* any error is reported once */ + dev->errors = 0; + /* to preserve notifications about reset */ + rv = (rv == -EPIPE) ? rv : -EIO; + /* no data to deliver */ + dev->bulk_in_filled = 0; + /* report it */ + goto exit; + } + + /* + * if the buffer is filled we may satisfy the read + * else we need to start IO + */ + + if (dev->bulk_in_filled) { + /* we had read data */ + size_t available = dev->bulk_in_filled - dev->bulk_in_copied; + size_t chunk = min(available, count); + + if (!available) { + rv = 0; + goto exit; + } + /* + * data is available + * chunk tells us how much shall be copied + */ + + if (copy_to_user(buffer, + dev->bulk_in_buffer + dev->bulk_in_copied, + chunk)) + rv = -EFAULT; + else + rv = chunk; + + dev->bulk_in_copied += chunk; + + if (dev->bulk_in_filled == dev->bulk_in_copied) { + dev->bulk_in_filled = 0; + dev->bulk_in_copied = 0; + } + + } else { + rv = 0; + goto exit; + + } +exit: + mutex_unlock(&dev->io_mutex); + pr_debug("%s -- ret=%d\n", __func__, rv); + return rv; +} + +static void skel_write_bulk_callback(struct urb *urb) +{ + struct usb_skel *dev; + + dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (urb->status) { + if (!(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + err("%s - nonzero write bulk status received: %d", + __func__, urb->status); + + spin_lock(&dev->err_lock); + dev->errors = urb->status; + spin_unlock(&dev->err_lock); + } + + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + up(&dev->limit_sem); +} + +static ssize_t skel_write(struct file *file, const char *user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_skel *dev; + int retval = 0; + struct urb *urb = NULL; + char *buf = NULL; + size_t max_count = MAX_TRANSFER; + size_t writesize = min(count, max_count); + + dev = (struct usb_skel *)file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* + * limit the number of URBs in flight to stop a user from using up all + * RAM + */ + if (!(file->f_flags & O_NONBLOCK)) { + if (down_interruptible(&dev->limit_sem)) { + retval = -ERESTARTSYS; + goto exit; + } + } else { + if (down_trylock(&dev->limit_sem)) { + retval = -EAGAIN; + goto exit; + } + } + + spin_lock_irq(&dev->err_lock); + retval = dev->errors; + if (retval < 0) { + /* any error is reported once */ + dev->errors = 0; + /* to preserve notifications about reset */ + retval = (retval == -EPIPE) ? retval : -EIO; + } + spin_unlock_irq(&dev->err_lock); + if (retval < 0) + goto error; + + /* create a urb, and a buffer for it, and copy the data to the urb */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto error; + } + + buf = usb_alloc_coherent(dev->udev, writesize, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error; + } + + if (copy_from_user(buf, user_buffer, writesize)) { + retval = -EFAULT; + goto error; + } + + /* this lock makes sure we don't submit URBs to gone devices */ + mutex_lock(&dev->io_mutex); + if (!dev->interface) { /* disconnect() was called */ + mutex_unlock(&dev->io_mutex); + retval = -ENODEV; + goto error; + } + + /* initialize the urb properly */ + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), + buf, writesize, skel_write_bulk_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + usb_anchor_urb(urb, &dev->submitted); + + /* send the data out the bulk port */ + retval = usb_submit_urb(urb, GFP_KERNEL); + mutex_unlock(&dev->io_mutex); + if (retval) { + err("%s - failed submitting write urb, error %d", __func__, + retval); + goto error_unanchor; + } + + /* + * release our reference to this urb, the USB core will eventually free + * it entirely + */ + usb_free_urb(urb); + + + pr_debug("%s success (%d)--\n", __func__, writesize); + return writesize; + +error_unanchor: + usb_unanchor_urb(urb); +error: + if (urb) { + usb_free_coherent(dev->udev, writesize, buf, urb->transfer_dma); + usb_free_urb(urb); + } + up(&dev->limit_sem); + +exit: + pr_debug("%s (%d)--\n", __func__, retval); + return retval; +} + +static const struct file_operations skel_fops = { + .owner = THIS_MODULE, + .read = skel_read, + .write = skel_write, + .open = skel_open, + .release = skel_release, + .flush = skel_flush, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver skel_class = { + .name = "skel%d", + .fops = &skel_fops, + .minor_base = USB_SKEL_MINOR_BASE, +}; + +static int skel_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_skel *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + size_t buffer_size; + int i; + int retval = -ENOMEM; + + pr_info("%s ++\n", __func__); + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + err("Out of memory"); + goto error; + } + kref_init(&dev->kref); + sema_init(&dev->limit_sem, WRITES_IN_FLIGHT); + mutex_init(&dev->io_mutex); + spin_lock_init(&dev->err_lock); + init_usb_anchor(&dev->submitted); + init_completion(&dev->bulk_in_completion); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + iface_desc = interface->cur_altsetting; + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (!dev->bulk_in_endpointAddr && + usb_endpoint_is_bulk_in(endpoint)) { + /* we found a bulk in endpoint */ + buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); + dev->bulk_in_size = buffer_size; + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev_dbg(&interface->dev, "bulk_in_size: %d\n", + dev->bulk_in_size); + dev_dbg(&interface->dev, "in_endpointAddr: %x\n", + dev->bulk_in_endpointAddr); + dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + err("Could not allocate bulk_in_buffer"); + goto error; + } + dev->bulk_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->bulk_in_urb) { + err("Could not allocate bulk_in_urb"); + goto error; + } + } + + if (!dev->bulk_out_endpointAddr && + usb_endpoint_is_bulk_out(endpoint)) { + /* we found a bulk out endpoint */ + dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; + } + } + if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr)) { + err("Could not find both bulk-in and bulk-out endpoints"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &skel_class); + if (retval) { + /* something prevented us from registering this driver */ + err("Not able to get a minor for this device."); + usb_set_intfdata(interface, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, + "USB Skeleton device now attached to USBSkel-%d", + interface->minor); + + mutex_lock(&dev->io_mutex); + dev->initialized = true; + mutex_unlock(&dev->io_mutex); + + pr_info("%s --\n", __func__); + return 0; + +error: + if (dev) + /* this frees allocated memory */ + kref_put(&dev->kref, skel_delete); + return retval; +} + +static void skel_disconnect(struct usb_interface *interface) +{ + struct usb_skel *dev; + int minor = interface->minor; + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* give back our minor */ + usb_deregister_dev(interface, &skel_class); + + /* prevent more I/O from starting */ + mutex_lock(&dev->io_mutex); + dev->interface = NULL; + dev->initialized = false; + mutex_unlock(&dev->io_mutex); + + usb_kill_anchored_urbs(&dev->submitted); + + /* decrement our usage count */ + kref_put(&dev->kref, skel_delete); + + dev_info(&interface->dev, "USB Skeleton #%d now disconnected", minor); +} + +static void skel_draw_down(struct usb_skel *dev) +{ + int time; + + pr_info("%s", __func__); + time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&dev->submitted); + usb_kill_urb(dev->bulk_in_urb); +} + +static int skel_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + pr_info("%s", __func__); + if (!dev) + return 0; + skel_draw_down(dev); + return 0; +} + +static int skel_resume(struct usb_interface *intf) +{ + return 0; +} + +static int skel_pre_reset(struct usb_interface *intf) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + pr_info("%s", __func__); + mutex_lock(&dev->io_mutex); + skel_draw_down(dev); + + return 0; +} + +static int skel_post_reset(struct usb_interface *intf) +{ + struct usb_skel *dev = usb_get_intfdata(intf); + + pr_info("%s", __func__); + /* we are sure no URBs are active - no locking needed */ + dev->errors = -EPIPE; + mutex_unlock(&dev->io_mutex); + + return 0; +} + +static struct usb_driver skel_driver = { + .name = "skeleton", + .probe = skel_probe, + .disconnect = skel_disconnect, + .suspend = skel_suspend, + .resume = skel_resume, + .pre_reset = skel_pre_reset, + .post_reset = skel_post_reset, + .id_table = skel_table, + .supports_autosuspend = 1, +}; + +static int __init usb_skel_init(void) +{ + int result; + + pr_info("%s", __func__); + + /* register this driver with the USB subsystem */ + result = usb_register(&skel_driver); + if (result) + err("usb_register failed. Error number %d", result); + + return result; +} + +static void __exit usb_skel_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&skel_driver); +} + +module_init(usb_skel_init); +module_exit(usb_skel_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/core/Kconfig b/drivers/usb/core/Kconfig index 18d02e3..6bb3bab 100644 --- a/drivers/usb/core/Kconfig +++ b/drivers/usb/core/Kconfig @@ -148,3 +148,22 @@ config USB_OTG_BLACKLIST_HUB and software costs by not supporting external hubs. So are "Embedded Hosts" that don't offer OTG support. +config HOST_COMPLIANT_TEST + bool "Embedded High-speed Host Electrical Test Support" + depends on USB && USB_ARCH_HAS_HCD && PLAT_SAMSUNG + default n + help + This option is only used if you are developing firmware for + an embedded device with a Hi-speed USB Host or OTG port. + + If you say Y here, software support for the Embedded + High-speed Host Electrical Tests will be added to the USB + Host stack. This is one of the tests performed during + High-speed USB Host certification testing. + + Please note that the USB Host Controller Driver must also + support this option. For an example of how to add support + for this to a USB Host Controller Driver see the EHCI driver. + + If you are at all unsure then say N here. + diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 0ca54e2..9823f18 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -709,10 +709,14 @@ static int usbdev_open(struct inode *inode, struct file *file) if (dev->state == USB_STATE_NOTATTACHED) goto out_unlock_device; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + pr_debug("mif: modem usbdev_open, skip usb_autoresume_device\n"); + ret = 0; +#else ret = usb_autoresume_device(dev); if (ret) goto out_unlock_device; - +#endif ps->dev = dev; ps->file = file; spin_lock_init(&ps->lock); @@ -761,7 +765,11 @@ static int usbdev_release(struct inode *inode, struct file *file) releaseintf(ps, ifnum); } destroy_all_async(ps); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + pr_debug("mif: modem usbdev_open, skip usb_autosuspend_device\n"); +#else usb_autosuspend_device(dev); +#endif usb_unlock_device(dev); usb_put_dev(dev); put_pid(ps->disc_pid); diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 210e359..c0c8644 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -863,7 +863,11 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) * If any port-status changes do occur during this delay, khubd * will see them later and handle them normally. */ +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + if (need_debounce_delay && type != HUB_RESET_RESUME) { +#else if (need_debounce_delay) { +#endif delay = HUB_DEBOUNCE_STABLE; /* Don't do a long sleep inside a workqueue routine */ @@ -1640,20 +1644,22 @@ void usb_disconnect(struct usb_device **pdev) { struct usb_device *udev = *pdev; int i; - struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct usb_hcd *hcd; if (!udev) { pr_debug ("%s nodev\n", __func__); return; } + hcd = bus_to_hcd(udev->bus); + /* mark the device as inactive, so any further urb submissions for * this device (and any of its children) will fail immediately. * this quiesces everything except pending urbs. */ usb_set_device_state(udev, USB_STATE_NOTATTACHED); - dev_info(&udev->dev, "USB disconnect, device number %d\n", - udev->devnum); + dev_info(&udev->dev, "USB disconnect, device number %d by %pF\n", + udev->devnum, __builtin_return_address(0)); usb_lock_device(udev); @@ -2424,6 +2430,9 @@ static int finish_port_resume(struct usb_device *udev) retry_reset_resume: status = usb_reset_and_verify_device(udev); + if (udev->quirks & USB_QUIRK_NO_GET_STATUS) + goto done; + /* 10.5.4.5 says be sure devices in the tree are still there. * For now let's assume the device didn't go crazy on resume, * and device drivers will know about any resume quirks. @@ -2462,6 +2471,7 @@ static int finish_port_resume(struct usb_device *udev) } status = 0; } +done: return status; } @@ -2530,7 +2540,12 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) /* drive resume for at least 20 msec */ dev_dbg(&udev->dev, "usb %sresume\n", (msg.event & PM_EVENT_AUTO ? "auto-" : "")); - msleep(25); + + /* Add the 5msec delay for XMM6260 resume fail case*/ + if (udev->quirks & USB_QUIRK_HSIC_TUNE) + msleep(30); + else + msleep(25); /* Virtual root hubs can trigger on GET_PORT_STATUS to * stop resume signaling. Then finish the resume @@ -2540,9 +2555,20 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) /* TRSMRCY = 10 msec */ msleep(10); + + /* If portstatus's still resuming, retry GET_PORT_STATUS */ + if (udev->quirks & USB_QUIRK_HSIC_TUNE) + if (portstatus & USB_PORT_STAT_SUSPEND) { + usleep_range(5000, 10000); + status = hub_port_status(hub, port1, + &portstatus, &portchange); + } } SuspendCleared: +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + pr_debug("mif: %s: %d, %d\n", __func__, portstatus, portchange); +#endif if (status == 0) { if (hub_is_superspeed(hub->hdev)) { if (portchange & USB_PORT_STAT_C_LINK_STATE) @@ -2955,9 +2981,13 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, buf->bMaxPacketSize0; kfree(buf); - retval = hub_port_reset(hub, port1, udev, delay); - if (retval < 0) /* error or disconnect */ - goto fail; + if (!(udev->quirks & USB_QUIRK_HSIC_TUNE)) { + retval = + hub_port_reset(hub, port1, udev, delay); + if (retval < 0) /* error or disconnect */ + goto fail; + } + if (oldspeed != udev->speed) { dev_dbg(&udev->dev, "device reset changed speed!\n"); @@ -4049,3 +4079,19 @@ void usb_queue_reset_device(struct usb_interface *iface) schedule_work(&iface->reset_ws); } EXPORT_SYMBOL_GPL(usb_queue_reset_device); + +void usb_force_disconnect(struct usb_device *udev) +{ + struct usb_hub *parent_hub; + int port1 = udev->portnum; + + if (!udev->parent) + return; + + parent_hub = hdev_to_hub(udev->parent); + if (!parent_hub) + return; + + hub_port_logical_disconnect(parent_hub, port1); +} +EXPORT_SYMBOL_GPL(usb_force_disconnect); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 0b5ec23..0a8da6f 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -135,6 +135,9 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, { struct usb_ctrlrequest *dr; int ret; +#if defined(CONFIG_LINK_DEVICE_HSIC) && defined(CONFIG_UMTS_MODEM_XMM6262) + int limit_timeout; +#endif dr = kmalloc(sizeof(struct usb_ctrlrequest), GFP_NOIO); if (!dr) @@ -148,8 +151,22 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, /* dbg("usb_control_msg"); */ - ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); +#if defined(CONFIG_LINK_DEVICE_HSIC) && defined(CONFIG_UMTS_MODEM_XMM6262) + /* Sometimes AP can't received the HSIC descriptor when AP L3->L0 + * reset-resume, then got the dpm_timeout panic caused 5sec * retry + * timeout. we can get the cp dump after dpm resume. + * portnum 2 is HSIC phy0 for CP. + */ + limit_timeout = (dev->portnum == 2) ? min(timeout, 1500) : timeout; + + /* pr_debug("%s: dev=%s, portnum=%d, timeout=%d\n", __func__, + dev_name(&dev->dev), dev->portnum, limit_timeout); */ + ret = usb_internal_control_msg(dev, pipe, dr, data, size, + limit_timeout); +#else + ret = usb_internal_control_msg(dev, pipe, dr, data, size, timeout); +#endif kfree(dr); return ret; @@ -1857,6 +1874,11 @@ free_interfaces: dev_name(&intf->dev), ret); continue; } +#ifdef CONFIG_HOST_COMPLIANT_TEST + if (usb_get_intfdata(intf) == NULL ) { + dev_info( &intf->dev, "%s : Not match interface - driver detect fail\n",__func__); + } +#endif create_intf_ep_devs(intf); } diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index ecf12e1..72275a6 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -147,6 +147,21 @@ static const struct usb_device_id usb_quirk_list[] = { /* INTEL VALUE SSD */ { USB_DEVICE(0x8086, 0xf1a5), .driver_info = USB_QUIRK_RESET_RESUME }, + /* Samsung CMC221 LTE Modem */ + { USB_DEVICE(0x04e8, 0x6999), .driver_info = USB_QUIRK_NO_GET_STATUS }, + + /* IMC_BOOT - XMM6260, XMM6262 */ + { USB_DEVICE(0x058b, 0x0041), .driver_info = USB_QUIRK_HSIC_TUNE }, + + /* IMC_MAIN - XMM6260, XMM6262 */ + { USB_DEVICE(0x1519, 0x0020), .driver_info = USB_QUIRK_HSIC_TUNE }, + + /* STE_BOOT - M7400 */ + { USB_DEVICE(0x04cc, 0x7400), .driver_info = USB_QUIRK_HSIC_TUNE }, + + /* STE_MAIN - M7400 */ + { USB_DEVICE(0x04cc, 0x2333), .driver_info = USB_QUIRK_HSIC_TUNE }, + { } /* terminating entry must be last */ }; diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 029e288..3d23ed6 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -314,6 +314,20 @@ config USB_S3C_HSOTG default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_EXYNOS_SS_UDC + boolean "EXYNOS SuperSpeed USB 3.0 Device controller" + depends on EXYNOS_DEV_SS_UDC + select USB_GADGET_DUALSPEED + help + The Samsung Exynos SuperSpeed USB 3.0 device controller + integrated into the Exynos5 series SoC. + +config USB_EXYNOS_SS_UDC + tristate + depends on USB_GADGET_EXYNOS_SS_UDC + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_IMX boolean "Freescale IMX USB Peripheral Controller" depends on ARCH_MX1 @@ -373,6 +387,20 @@ config USB_S3C_HSUDC default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_S3C_OTGD + boolean "S3C HS USB OTG Device" + depends on (PLAT_S5P) + help + Samsung's S3C64XX processors include high speed USB OTG2.0 + controller. It has 15 configurable endpoints, as well as + endpoint zero (for control transfers). + + This driver has been tested on the S3C6410, S5P6440, S5PC100, S5PV210, EXYNOS4210 processor. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c-udc-otg" and force all + gadget drivers to also be dynamically linked. + config USB_GADGET_PXA_U2O boolean "PXA9xx Processor USB2.0 controller" select USB_GADGET_DUALSPEED @@ -633,10 +661,56 @@ config USB_DUMMY_HCD endchoice # Selected by UDC drivers that support high-speed operation. +comment "NOTE: S3C OTG device role enables the controller driver below" + depends on USB_GADGET_S3C_OTGD + +config USB_S3C_OTGD + tristate "S3C high speed(2.0, dual-speed) USB OTG device" + depends on USB_GADGET && USB_GADGET_S3C_OTGD + default y + default USB_GADGET + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + help + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c-udc-otg-hs" and force all + gadget drivers to also be dynamically linked. + +choice + prompt "EXYNOS SS UDC speed mode (temporarily)" + depends on USB_GADGET_EXYNOS_SS_UDC + default USB_GADGET_EXYNOS_SS_UDC_SSMODE + help + For the present moment gadget layer doesn't support SuperSpeed. + This option allows to select speed mode for EXYNOS SS UDC driver. + + NOTE: this is temporarily and will be removed once SuperSpeed support + has been added to gadget layer. + +config USB_GADGET_EXYNOS_SS_UDC_HSMODE + boolean "HighSpeed" + help + Use HighSpeed mode to work with USB2.0 host and gadgets _without_ + SuperSpeed support. + +config USB_GADGET_EXYNOS_SS_UDC_SSMODE + boolean "SuperSpeed" + select USB_GADGET_SUPERSPEED + help + Use SuperSpeed mode to debug/test gadget's SuperSpeed support. + +endchoice + config USB_GADGET_DUALSPEED bool depends on USB_GADGET +# Selected by UDC drivers that support super-speed opperation +config USB_GADGET_SUPERSPEED + bool + depends on USB_GADGET + depends on USB_GADGET_DUALSPEED + # # USB Gadget Drivers # @@ -935,6 +1009,102 @@ config USB_G_PRINTER For more information, see Documentation/usb/gadget_printer.txt which includes sample code for accessing the device file. +config USB_G_SLP + boolean "SLP Gadget based on Android" + help + The SLP gadget driver supports multiple USB functions. + The functions can be configured via a board file and may be + enabled and disabled dynamically. + +config USB_G_ANDROID + boolean "Android Gadget" + depends on SWITCH + help + The Android gadget driver supports multiple USB functions. + The functions can be configured via a board file and may be + enabled and disabled dynamically. + +config USB_ANDROID_SAMSUNG_COMPOSITE + boolean "Samsung Composite function" + depends on USB_G_ANDROID + help + Provides SAMSUNG composite driver. + It also provides KIES connection and + Multi Configuration. + If you enable this option, android composite will be changed. + +config USB_ANDROID_SAMSUNG_MTP + boolean "Samsung MTP function" + depends on USB_G_ANDROID + help + Provides Media Transfer Protocol (MTP) support + for samsung gadget driver. + If you enable this option, + google mtp will be changed to samsung mtp. + +config USB_DUN_SUPPORT + boolean "DUN support function" + depends on USB_G_ANDROID + help + Provides USB modem serial driver. + This function makes connection to acm from data router. + It uses misc register. + Support fops : open, close, release, read, poll, llseek, ioctl + +config USB_ANDROID + boolean "Android Gadget for HC&GB" + depends on SWITCH + help + The Android gadget driver supports multiple USB functions. + The functions can be configured via a board file and may be + enabled and disabled dynamically. + +config USB_ANDROID_ACM + boolean "Android gadget ACM serial function" + depends on USB_ANDROID + help + Provides ACM serial function for android gadget driver. + +config USB_ANDROID_ADB + boolean "Android gadget adb function" + depends on USB_ANDROID + help + Provides adb function for android gadget driver. + +config USB_ANDROID_MASS_STORAGE + boolean "Android gadget mass storage function" + depends on USB_ANDROID && SWITCH + help + Provides USB mass storage function for android gadget driver. + +config USB_ANDROID_MTP + boolean "Android MTP function" + depends on USB_ANDROID + help + Provides Media Transfer Protocol (MTP) support for android gadget driver. + +config USB_ANDROID_RNDIS + boolean "Android gadget RNDIS ethernet function" + depends on USB_ANDROID + help + Provides RNDIS ethernet function for android gadget driver. + +config USB_ANDROID_RNDIS_WCEIS + boolean "Use Windows Internet Sharing Class/SubClass/Protocol" + depends on USB_ANDROID_RNDIS + help + Causes the driver to look like a Windows-compatible Internet + Sharing device, so Windows auto-detects it. + + If you enable this option, the device is no longer CDC ethernet + compatible. + +config USB_ANDROID_ACCESSORY + boolean "Android USB accessory function" + depends on USB_ANDROID + help + Provides Android USB Accessory support for android gadget driver. + config USB_CDC_COMPOSITE tristate "CDC Composite Device (Ethernet and ACM)" depends on NET diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 4fe92b1..bd8b874 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -12,6 +12,8 @@ obj-$(CONFIG_USB_IMX) += imx_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o obj-$(CONFIG_USB_OMAP) += omap_udc.o obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o +obj-$(CONFIG_USB_S3C_OTGD) += s3c_udc_otg.o +obj-$(CONFIG_USB_EXYNOS_SS_UDC) += exynos_ss_udc.o obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o @@ -49,6 +51,7 @@ g_dbgp-y := dbgp.o g_nokia-y := nokia.o g_webcam-y := webcam.o g_ncm-y := ncm.o +g_android-y := android.o obj-$(CONFIG_USB_ZERO) += g_zero.o obj-$(CONFIG_USB_AUDIO) += g_audio.o @@ -67,3 +70,11 @@ obj-$(CONFIG_USB_G_MULTI) += g_multi.o obj-$(CONFIG_USB_G_NOKIA) += g_nokia.o obj-$(CONFIG_USB_G_WEBCAM) += g_webcam.o obj-$(CONFIG_USB_G_NCM) += g_ncm.o +obj-$(CONFIG_USB_G_ANDROID) += g_android.o +obj-$(CONFIG_USB_ANDROID) += gadget_gbhc/android.o +obj-$(CONFIG_USB_ANDROID_ACM) += gadget_gbhc/f_acm.o gadget_gbhc/u_serial.o +obj-$(CONFIG_USB_ANDROID_ADB) += gadget_gbhc/f_adb.o +obj-$(CONFIG_USB_ANDROID_MASS_STORAGE) += gadget_gbhc/f_mass_storage.o +obj-$(CONFIG_USB_ANDROID_MTP) += gadget_gbhc/f_mtp.o +obj-$(CONFIG_USB_ANDROID_RNDIS) += gadget_gbhc/f_rndis.o gadget_gbhc/u_ether.o +obj-$(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE) += multi_config.o diff --git a/drivers/usb/gadget/android.c b/drivers/usb/gadget/android.c new file mode 100644 index 0000000..32074ce --- /dev/null +++ b/drivers/usb/gadget/android.c @@ -0,0 +1,1528 @@ +/* + * Gadget Driver for Android + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/platform_device.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> +#include <linux/usb/android_composite.h> + +#include "gadget_chips.h" + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" +#include "composite.c" + +#include "f_mass_storage.c" +#include "u_serial.c" +#ifdef CONFIG_USB_DUN_SUPPORT +#include "serial_acm.c" +#endif +#include "f_acm.c" +#include "f_adb.c" +#ifdef CONFIG_USB_ANDROID_SAMSUNG_MTP +#include "f_mtp_samsung.c" +#else +#include "f_mtp.c" +#endif +#include "f_accessory.c" +#define USB_ETH_RNDIS y +#include "f_rndis.c" +#include "rndis.c" +#include "f_dm.c" + +MODULE_AUTHOR("Mike Lockwood"); +MODULE_DESCRIPTION("Android Composite USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +static const char longname[] = "Gadget Android"; + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +static int composite_string_index; +#endif +/* Default vendor and product IDs, overridden by userspace */ +#define VENDOR_ID 0x18D1 +#define PRODUCT_ID 0x0001 + +/* DM_PORT NUM : /dev/ttyGS* port number */ +#define DM_PORT_NUM 1 + +struct android_usb_function { + char *name; + void *config; + + struct device *dev; + char *dev_name; + struct device_attribute **attributes; + + /* for android_dev.enabled_functions */ + struct list_head enabled_list; + + /* Optional: initialization during gadget bind */ + int (*init)(struct android_usb_function *, struct usb_composite_dev *); + /* Optional: cleanup during gadget unbind */ + void (*cleanup)(struct android_usb_function *); + + int (*bind_config)(struct android_usb_function *, struct usb_configuration *); + + /* Optional: called when the configuration is removed */ + void (*unbind_config)(struct android_usb_function *, struct usb_configuration *); + /* Optional: handle ctrl requests before the device is configured */ + int (*ctrlrequest)(struct android_usb_function *, + struct usb_composite_dev *, + const struct usb_ctrlrequest *); +}; + +struct android_dev { + struct android_usb_function **functions; + struct list_head enabled_functions; + struct usb_composite_dev *cdev; + struct device *dev; + + bool enabled; + struct mutex mutex; + bool connected; + bool sw_connected; + struct work_struct work; +}; + +static struct class *android_class; +static struct android_dev *_android_dev; +static int android_bind_config(struct usb_configuration *c); +static void android_unbind_config(struct usb_configuration *c); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +static struct android_usb_platform_data *android_usb_pdata; +#endif +/* string IDs are assigned dynamically */ +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +static char manufacturer_string[256]; +static char product_string[256]; +static char serial_string[256]; + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +#include "u_ncm.c" +#endif +#include "u_ether.c" + +/* String Table */ +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer_string, + [STRING_PRODUCT_IDX].s = product_string, + [STRING_SERIAL_IDX].s = serial_string, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static struct usb_configuration android_config_driver = { + .label = "android", + .unbind = android_unbind_config, + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0x30, /* 96ma */ +}; + +static void android_work(struct work_struct *data) +{ + struct android_dev *dev = container_of(data, struct android_dev, work); + struct usb_composite_dev *cdev = dev->cdev; + char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL }; + char *connected[2] = { "USB_STATE=CONNECTED", NULL }; + char *configured[2] = { "USB_STATE=CONFIGURED", NULL }; + char **uevent_envp = NULL; + unsigned long flags; + + printk(KERN_DEBUG "usb: %s config=%p,connected=%d,sw_connected=%d\n", + __func__, cdev->config, dev->connected, + dev->sw_connected); + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + uevent_envp = configured; + else if (dev->connected != dev->sw_connected) + uevent_envp = dev->connected ? connected : disconnected; + dev->sw_connected = dev->connected; + spin_unlock_irqrestore(&cdev->lock, flags); + + if (uevent_envp) { + kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_envp); + printk(KERN_DEBUG "usb: %s sent uevent %s\n", + __func__, uevent_envp[0]); + } else { + printk(KERN_DEBUG "usb: %s did not send uevent (%d %d %p)\n", + __func__, dev->connected, dev->sw_connected, cdev->config); + } +} + + +/*-------------------------------------------------------------------------*/ +/* Supported functions initialization */ + +static int adb_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + return adb_setup(); +} + +static void adb_function_cleanup(struct android_usb_function *f) +{ + adb_cleanup(); +} + +static int adb_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + return adb_bind_config(c); +} + +static struct android_usb_function adb_function = { + .name = "adb", + .init = adb_function_init, + .cleanup = adb_function_cleanup, + .bind_config = adb_function_bind_config, +}; + + +#define MAX_ACM_INSTANCES 4 +struct acm_function_config { + int instances; +}; + +static int acm_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + int ret; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + struct acm_function_config *config; +#endif + f->config = kzalloc(sizeof(struct acm_function_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + + ret = gserial_setup(cdev->gadget, MAX_ACM_INSTANCES); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* default setting */ + config = f->config; + config->instances = 1; +#endif + return ret; +} + +static void acm_function_cleanup(struct android_usb_function *f) +{ + gserial_cleanup(); + kfree(f->config); + f->config = NULL; +} + +static int acm_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + int i; + int ret = 0; + struct acm_function_config *config = f->config; + + for (i = 0; i < config->instances; i++) { + ret = acm_bind_config(c, i); + if (ret) { + pr_err("Could not bind acm%u config\n", i); + break; + } + } + + return ret; +} + +static ssize_t acm_instances_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + return sprintf(buf, "%d\n", config->instances); +} + +static ssize_t acm_instances_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct acm_function_config *config = f->config; + int value; + + sscanf(buf, "%d", &value); + if (value > MAX_ACM_INSTANCES) + value = MAX_ACM_INSTANCES; + config->instances = value; + return size; +} + +static DEVICE_ATTR(instances, S_IRUGO | S_IWUSR, acm_instances_show, acm_instances_store); +static struct device_attribute *acm_function_attributes[] = { &dev_attr_instances, NULL }; + +static struct android_usb_function acm_function = { + .name = "acm", + .init = acm_function_init, + .cleanup = acm_function_cleanup, + .bind_config = acm_function_bind_config, + .attributes = acm_function_attributes, +}; + + +static int mtp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + return mtp_setup(); +} + +static void mtp_function_cleanup(struct android_usb_function *f) +{ + mtp_cleanup(); +} + +static int mtp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + return mtp_bind_config(c, false); +} + +static int ptp_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + /* nothing to do - initialization is handled by mtp_function_init */ + return 0; +} + +static void ptp_function_cleanup(struct android_usb_function *f) +{ + /* nothing to do - cleanup is handled by mtp_function_cleanup */ +} + +static int ptp_function_bind_config(struct android_usb_function *f, struct usb_configuration *c) +{ + return mtp_bind_config(c, true); +} + +static int mtp_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return mtp_ctrlrequest(cdev, c); +} + +static struct android_usb_function mtp_function = { + .name = "mtp", + .init = mtp_function_init, + .cleanup = mtp_function_cleanup, + .bind_config = mtp_function_bind_config, + .ctrlrequest = mtp_function_ctrlrequest, +}; + +/* PTP function is same as MTP with slightly different interface descriptor */ +static struct android_usb_function ptp_function = { + .name = "ptp", + .init = ptp_function_init, + .cleanup = ptp_function_cleanup, + .bind_config = ptp_function_bind_config, +}; + + +struct rndis_function_config { + u8 ethaddr[ETH_ALEN]; + u32 vendorID; + char manufacturer[256]; + bool wceis; +}; + +static int rndis_function_init(struct android_usb_function *f, struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct rndis_function_config), GFP_KERNEL); + if (!f->config) + return -ENOMEM; + return 0; +} + +static void rndis_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int rndis_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int ret; + struct rndis_function_config *rndis = f->config; + + if (!rndis) { + pr_err("%s: rndis_pdata\n", __func__); + return -1; + } + + ret = gether_setup_name(c->cdev->gadget, rndis->ethaddr, "rndis"); + if (ret) { + pr_err("%s: gether_setup failed\n", __func__); + return ret; + } + + if (rndis->wceis) { + /* "Wireless" RNDIS; auto-detected by Windows */ + rndis_iad_descriptor.bFunctionClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_iad_descriptor.bFunctionSubClass = 0x01; + rndis_iad_descriptor.bFunctionProtocol = 0x03; + rndis_control_intf.bInterfaceClass = + USB_CLASS_WIRELESS_CONTROLLER; + rndis_control_intf.bInterfaceSubClass = 0x01; + rndis_control_intf.bInterfaceProtocol = 0x03; + } + + return rndis_bind_config(c, rndis->ethaddr, rndis->vendorID, + rndis->manufacturer); +} + +static void rndis_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + gether_cleanup(); +} + +static ssize_t rndis_manufacturer_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%s\n", config->manufacturer); +} + +static ssize_t rndis_manufacturer_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + + if (size >= sizeof(config->manufacturer)) + return -EINVAL; + if (sscanf(buf, "%s", config->manufacturer) == 1) + return size; + return -1; +} + +static DEVICE_ATTR(manufacturer, S_IRUGO | S_IWUSR, rndis_manufacturer_show, + rndis_manufacturer_store); + +static ssize_t rndis_wceis_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%d\n", config->wceis); +} + +static ssize_t rndis_wceis_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%d", &value) == 1) { + config->wceis = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(wceis, S_IRUGO | S_IWUSR, rndis_wceis_show, + rndis_wceis_store); + +static ssize_t rndis_ethaddr_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + return sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + rndis->ethaddr[0], rndis->ethaddr[1], rndis->ethaddr[2], + rndis->ethaddr[3], rndis->ethaddr[4], rndis->ethaddr[5]); +} + +static ssize_t rndis_ethaddr_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *rndis = f->config; + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + int i; + char *src; + for (i = 0; i < ETH_ALEN; i++) + rndis->ethaddr[i] = 0; + /* create a fake MAC address from our serial number. + * first byte is 0x02 to signify locally administered. + */ + rndis->ethaddr[0] = 0x02; + src = serial_string; + for (i = 0; (i < 256) && *src; i++) { + /* XOR the USB serial across the remaining bytes */ + rndis->ethaddr[i % (ETH_ALEN - 1) + 1] ^= *src++; + } + return size; +#else + if (sscanf(buf, "%02x:%02x:%02x:%02x:%02x:%02x\n", + (int *)&rndis->ethaddr[0], (int *)&rndis->ethaddr[1], + (int *)&rndis->ethaddr[2], (int *)&rndis->ethaddr[3], + (int *)&rndis->ethaddr[4], + (int *)&rndis->ethaddr[5]) == 6) + return size; + return -EINVAL; +#endif +} + +static DEVICE_ATTR(ethaddr, S_IRUGO | S_IWUSR, rndis_ethaddr_show, + rndis_ethaddr_store); + +static ssize_t rndis_vendorID_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + return sprintf(buf, "%04x\n", config->vendorID); +} + +static ssize_t rndis_vendorID_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct rndis_function_config *config = f->config; + int value; + + if (sscanf(buf, "%04x", &value) == 1) { + config->vendorID = value; + return size; + } + return -EINVAL; +} + +static DEVICE_ATTR(vendorID, S_IRUGO | S_IWUSR, rndis_vendorID_show, + rndis_vendorID_store); + +static struct device_attribute *rndis_function_attributes[] = { + &dev_attr_manufacturer, + &dev_attr_wceis, + &dev_attr_ethaddr, + &dev_attr_vendorID, + NULL +}; + +static struct android_usb_function rndis_function = { + .name = "rndis", + .init = rndis_function_init, + .cleanup = rndis_function_cleanup, + .bind_config = rndis_function_bind_config, + .unbind_config = rndis_function_unbind_config, + .attributes = rndis_function_attributes, +}; + + +struct mass_storage_function_config { + struct fsg_config fsg; + struct fsg_common *common; +}; + +static int mass_storage_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + struct mass_storage_function_config *config; + struct fsg_common *common; + int err; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + int i; +#endif + config = kzalloc(sizeof(struct mass_storage_function_config), + GFP_KERNEL); + if (!config) + return -ENOMEM; + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (android_usb_pdata && android_usb_pdata->nluns != 0) { + /* Some device use sd card or not. + * If you want to modify nluns, + * please change nluns of standard android USB platform data + * Please do not modify nluns directly in this function. + * Every model uses same android file. + */ + printk(KERN_DEBUG "usb: %s pdata->nluns=%d\n", __func__, + android_usb_pdata->nluns); + config->fsg.nluns = android_usb_pdata->nluns; + for (i = 0; i < android_usb_pdata->nluns; i++) { +#if defined(CONFIG_MACH_M0_CTC) + /*FOR CTC PC-MODEM START*/ + if (2 == i) { + printk(KERN_DEBUG "%s Enable cdfs\n", __func__); + config->fsg.luns[i].ro = 1; + config->fsg.luns[i].cdrom = 1; + } + /*FOR CTC PC-MODEM END*/ +#endif + config->fsg.luns[i].removable = 1; + config->fsg.luns[i].nofua = 1; + } + + common = fsg_common_init(NULL, cdev, &config->fsg); + if (IS_ERR(common)) { + kfree(config); + return PTR_ERR(common); + } + + for (i = 0; i < android_usb_pdata->nluns; i++) { + char luns[5]; + err = snprintf(luns, 5, "lun%d", i); + if (err == 0) { + printk(KERN_ERR "usb: %s snprintf error\n", + __func__); + kfree(config); + return err; + } + err = sysfs_create_link(&f->dev->kobj, + &common->luns[i].dev.kobj, + luns); + if (err) { + kfree(config); + return err; + } + } + } else { +#endif + /* original mainline code */ + printk(KERN_DEBUG "usb: %s pdata is not available. nluns=1\n", + __func__); + config->fsg.nluns = 1; + config->fsg.luns[0].removable = 1; + + common = fsg_common_init(NULL, cdev, &config->fsg); + if (IS_ERR(common)) { + kfree(config); + return PTR_ERR(common); + } + + err = sysfs_create_link(&f->dev->kobj, + &common->luns[0].dev.kobj, + "lun"); + if (err) { + kfree(config); + return err; + } +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + } +#endif + config->common = common; + f->config = config; + return 0; +} + +static void mass_storage_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int mass_storage_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + struct mass_storage_function_config *config = f->config; + return fsg_bind_config(c->cdev, c, config->common); +} + +static ssize_t mass_storage_inquiry_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%s\n", config->common->inquiry_string); +} + +static ssize_t mass_storage_inquiry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + if (size >= sizeof(config->common->inquiry_string)) + return -EINVAL; + if (sscanf(buf, "%s", config->common->inquiry_string) != 1) + return -EINVAL; + return size; +} + +static DEVICE_ATTR(inquiry_string, S_IRUGO | S_IWUSR, + mass_storage_inquiry_show, + mass_storage_inquiry_store); + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +static ssize_t mass_storage_vendor_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%s\n", config->common->vendor_string); +} + +static ssize_t mass_storage_vendor_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + + if (size >= sizeof(config->common->vendor_string)) + return -EINVAL; + if (sscanf(buf, "%s", config->common->vendor_string) != 1) + return -EINVAL; + + printk(KERN_DEBUG "%s: vendor %s", __func__, + config->common->vendor_string); + return size; +} + +static DEVICE_ATTR(vendor_string, S_IRUGO | S_IWUSR, + mass_storage_vendor_show, + mass_storage_vendor_store); + +static ssize_t mass_storage_product_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + return sprintf(buf, "%s\n", config->common->product_string); +} + +static ssize_t mass_storage_product_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct android_usb_function *f = dev_get_drvdata(dev); + struct mass_storage_function_config *config = f->config; + + if (size >= sizeof(config->common->product_string)) + return -EINVAL; + if (sscanf(buf, "%s", config->common->product_string) != 1) + return -EINVAL; + + printk(KERN_DEBUG "%s: product %s", __func__, + config->common->product_string); + return size; +} + +static DEVICE_ATTR(product_string, S_IRUGO | S_IWUSR, + mass_storage_product_show, + mass_storage_product_store); +#endif + +static struct device_attribute *mass_storage_function_attributes[] = { + &dev_attr_inquiry_string, +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + &dev_attr_vendor_string, + &dev_attr_product_string, +#endif + NULL +}; + +static struct android_usb_function mass_storage_function = { + .name = "mass_storage", + .init = mass_storage_function_init, + .cleanup = mass_storage_function_cleanup, + .bind_config = mass_storage_function_bind_config, + .attributes = mass_storage_function_attributes, +}; + + +static int accessory_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + return acc_setup(); +} + +static void accessory_function_cleanup(struct android_usb_function *f) +{ + acc_cleanup(); +} + +static int accessory_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return acc_bind_config(c); +} + +static int accessory_function_ctrlrequest(struct android_usb_function *f, + struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *c) +{ + return acc_ctrlrequest(cdev, c); +} + +static struct android_usb_function accessory_function = { + .name = "accessory", + .init = accessory_function_init, + .cleanup = accessory_function_cleanup, + .bind_config = accessory_function_bind_config, + .ctrlrequest = accessory_function_ctrlrequest, +}; + + +static int dm_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + return dm_bind_config(c, DM_PORT_NUM); +} + +static struct android_usb_function dm_function = { + .name = "dm", + .bind_config = dm_function_bind_config, +}; + +static struct android_usb_function *supported_functions[] = { + &adb_function, + &acm_function, + &mtp_function, + &ptp_function, + &rndis_function, +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + &ncm_function, +#endif + &mass_storage_function, + &accessory_function, + &dm_function, + NULL +}; + + +static int android_init_functions(struct android_usb_function **functions, + struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct android_usb_function *f; + struct device_attribute **attrs; + struct device_attribute *attr; + int err = 0; + int index = 0; + + for (; (f = *functions++); index++) { + f->dev_name = kasprintf(GFP_KERNEL, "f_%s", f->name); + f->dev = device_create(android_class, dev->dev, + MKDEV(0, index), f, f->dev_name); + if (IS_ERR(f->dev)) { + pr_err("%s: Failed to create dev %s", __func__, + f->dev_name); + err = PTR_ERR(f->dev); + goto err_create; + } + + if (f->init) { + err = f->init(f, cdev); + if (err) { + pr_err("%s: Failed to init %s", __func__, + f->name); + goto err_out; + } + } + + attrs = f->attributes; + if (attrs) { + while ((attr = *attrs++) && !err) + err = device_create_file(f->dev, attr); + } + if (err) { + pr_err("%s: Failed to create function %s attributes", + __func__, f->name); + goto err_out; + } + } + return 0; + +err_out: + device_destroy(android_class, f->dev->devt); +err_create: + kfree(f->dev_name); + return err; +} + +static void android_cleanup_functions(struct android_usb_function **functions) +{ + struct android_usb_function *f; + + while (*functions) { + f = *functions++; + + if (f->dev) { + device_destroy(android_class, f->dev->devt); + kfree(f->dev_name); + } + + if (f->cleanup) + f->cleanup(f); + } +} + +static int +android_bind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + int ret; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + printk(KERN_DEBUG "usb: %s f:%s\n", __func__, f->name); + ret = f->bind_config(f, c); + if (ret) { + pr_err("%s: %s failed", __func__, f->name); + return ret; + } + } + return 0; +} + +static void +android_unbind_enabled_functions(struct android_dev *dev, + struct usb_configuration *c) +{ + struct android_usb_function *f; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->unbind_config) + f->unbind_config(f, c); + } +} + +static int android_enable_function(struct android_dev *dev, char *name) +{ + struct android_usb_function **functions = dev->functions; + struct android_usb_function *f; + printk(KERN_DEBUG "usb: %s name=%s\n", __func__, name); + while ((f = *functions++)) { + if (!strcmp(name, f->name)) { + list_add_tail(&f->enabled_list, &dev->enabled_functions); + return 0; + } + } + return -EINVAL; +} + +/*-------------------------------------------------------------------------*/ +/* /sys/class/android_usb/android%d/ interface */ + +static ssize_t +functions_show(struct device *pdev, struct device_attribute *attr, char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct android_usb_function *f; + char *buff = buf; + + mutex_lock(&dev->mutex); + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + printk(KERN_DEBUG "usb: %s enabled_func=%s\n", + __func__, f->name); + buff += sprintf(buff, "%s,", f->name); + } + + mutex_unlock(&dev->mutex); + + if (buff != buf) + *(buff-1) = '\n'; + + return buff - buf; +} + +static ssize_t +functions_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + char *name; + char buf[256], *b; + int err; + + mutex_lock(&dev->mutex); + + if (dev->enabled) { + mutex_unlock(&dev->mutex); + return -EBUSY; + } + + INIT_LIST_HEAD(&dev->enabled_functions); + + printk(KERN_DEBUG "usb: %s buff=%s\n", __func__, buff); + strncpy(buf, buff, sizeof(buf)); + b = strim(buf); + + while (b) { + name = strsep(&b, ","); + if (name) { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* Enable ncm function */ + if (is_ncm_ready(name)) { + printk(KERN_DEBUG "usb: %s ncm on\n", + __func__); + err = android_enable_function(dev, + "ncm"); + continue; + } +#endif + err = android_enable_function(dev, name); + if (err) + pr_err("android_usb: Cannot enable '%s'", name); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* Enable ACM function, if MTP is enabled. */ + if (!strcmp(name, "mtp")) { + err = android_enable_function(dev, "acm"); + if (err) + pr_err( + "android_usb: Cannot enable '%s'", + name); + } +#endif + } + } + + mutex_unlock(&dev->mutex); + + return size; +} + +static ssize_t enable_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + printk(KERN_DEBUG "usb: %s dev->enabled=%d\n", __func__, dev->enabled); + return sprintf(buf, "%d\n", dev->enabled); +} + +static ssize_t enable_store(struct device *pdev, struct device_attribute *attr, + const char *buff, size_t size) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + int enabled = 0; + + mutex_lock(&dev->mutex); + + sscanf(buff, "%d", &enabled); + printk(KERN_DEBUG "usb: %s enabled=%d, !dev->enabled=%d\n", + __func__, enabled, !dev->enabled); + if (enabled && !dev->enabled) { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + struct android_usb_function *f; + cdev->next_string_id = composite_string_index; +#else + cdev->next_string_id = 0; +#endif + /* update values in composite driver's copy of device descriptor */ + cdev->desc.idVendor = device_desc.idVendor; + cdev->desc.idProduct = device_desc.idProduct; + cdev->desc.bcdDevice = device_desc.bcdDevice; + cdev->desc.bDeviceClass = device_desc.bDeviceClass; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + printk(KERN_DEBUG "usb: %s f:%s\n", __func__, f->name); + if (!strcmp(f->name, "acm")) { + printk(KERN_DEBUG "usb: acm is enabled. (bcdDevice=0x400)\n"); + /* Samsung KIES needs fixed bcdDevice number */ + cdev->desc.bcdDevice = cpu_to_le16(0x0400); + } + if (is_ncm_ready(f->name)) + set_ncm_device_descriptor(&cdev->desc); + } + strncpy(manufacturer_string, "SAMSUNG", + sizeof(manufacturer_string) - 1); + strncpy(product_string, "SAMSUNG_Android", + sizeof(product_string) - 1); +#endif + cdev->desc.bDeviceSubClass = device_desc.bDeviceSubClass; + cdev->desc.bDeviceProtocol = device_desc.bDeviceProtocol; + printk(KERN_DEBUG "usb: %s vendor=%x,product=%x,bcdDevice=%x", + __func__, cdev->desc.idVendor, + cdev->desc.idProduct, cdev->desc.bcdDevice); + printk(KERN_DEBUG ",Class=%x,SubClass=%x,Protocol=%x\n", + cdev->desc.bDeviceClass, + cdev->desc.bDeviceSubClass, + cdev->desc.bDeviceProtocol); + printk(KERN_DEBUG "usb: %s next cmd : usb_add_config\n", + __func__); + usb_add_config(cdev, &android_config_driver, + android_bind_config); + usb_gadget_connect(cdev->gadget); + dev->enabled = true; + } else if (!enabled && dev->enabled) { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* avoid sending a disconnect switch event + * until after we disconnect. + */ + cdev->mute_switch = true; +#endif + usb_gadget_disconnect(cdev->gadget); + /* Cancel pending control requests */ + usb_ep_dequeue(cdev->gadget->ep0, cdev->req); + usb_remove_config(cdev, &android_config_driver); + dev->enabled = false; + mdelay(5); + } else if (!enabled) { + usb_gadget_disconnect(cdev->gadget); + dev->enabled = false; + } else { + pr_err("android_usb: already %s\n", + dev->enabled ? "enabled" : "disabled"); + } + + mutex_unlock(&dev->mutex); + return size; +} + +static ssize_t state_show(struct device *pdev, struct device_attribute *attr, + char *buf) +{ + struct android_dev *dev = dev_get_drvdata(pdev); + struct usb_composite_dev *cdev = dev->cdev; + char *state = "DISCONNECTED"; + unsigned long flags; + + if (!cdev) + goto out; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + state = "CONFIGURED"; + else if (dev->connected) + state = "CONNECTED"; + spin_unlock_irqrestore(&cdev->lock, flags); +out: + printk(KERN_DEBUG "usb: %s buf=%s\n", __func__, state); + return sprintf(buf, "%s\n", state); +} + +#define DESCRIPTOR_ATTR(field, format_string) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, format_string, device_desc.field); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + int value; \ + if (sscanf(buf, format_string, &value) == 1) { \ + device_desc.field = value; \ + return size; \ + } \ + return -1; \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + +#define DESCRIPTOR_STRING_ATTR(field, buffer) \ +static ssize_t \ +field ## _show(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + return sprintf(buf, "%s", buffer); \ +} \ +static ssize_t \ +field ## _store(struct device *dev, struct device_attribute *attr, \ + const char *buf, size_t size) \ +{ \ + if (size >= sizeof(buffer)) return -EINVAL; \ + if (sscanf(buf, "%s", buffer) == 1) { \ + return size; \ + } \ + return -1; \ +} \ +static DEVICE_ATTR(field, S_IRUGO | S_IWUSR, field ## _show, field ## _store); + + +DESCRIPTOR_ATTR(idVendor, "%04x\n") +DESCRIPTOR_ATTR(idProduct, "%04x\n") +DESCRIPTOR_ATTR(bcdDevice, "%04x\n") +DESCRIPTOR_ATTR(bDeviceClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceSubClass, "%d\n") +DESCRIPTOR_ATTR(bDeviceProtocol, "%d\n") +DESCRIPTOR_STRING_ATTR(iManufacturer, manufacturer_string) +DESCRIPTOR_STRING_ATTR(iProduct, product_string) +DESCRIPTOR_STRING_ATTR(iSerial, serial_string) + +static DEVICE_ATTR(functions, S_IRUGO | S_IWUSR, functions_show, functions_store); +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); +static DEVICE_ATTR(state, S_IRUGO, state_show, NULL); + +static struct device_attribute *android_usb_attributes[] = { + &dev_attr_idVendor, + &dev_attr_idProduct, + &dev_attr_bcdDevice, + &dev_attr_bDeviceClass, + &dev_attr_bDeviceSubClass, + &dev_attr_bDeviceProtocol, + &dev_attr_iManufacturer, + &dev_attr_iProduct, + &dev_attr_iSerial, + &dev_attr_functions, + &dev_attr_enable, + &dev_attr_state, + NULL +}; + +/*-------------------------------------------------------------------------*/ +/* Composite driver */ + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + int ret = 0; + + ret = android_bind_enabled_functions(dev, c); + if (ret) + return ret; + + return 0; +} + +static void android_unbind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + android_unbind_enabled_functions(dev, c); +} + +static int android_bind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct usb_gadget *gadget = cdev->gadget; + int gcnum, id, ret; + + printk(KERN_DEBUG "usb: %s disconnect\n", __func__); + usb_gadget_disconnect(gadget); + + ret = android_init_functions(dev->functions, cdev); + if (ret) + return ret; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_MANUFACTURER_IDX].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_PRODUCT_IDX].id = id; + device_desc.iProduct = id; + + /* Default strings - should be updated by userspace */ + strncpy(manufacturer_string, "Android", sizeof(manufacturer_string) - 1); + strncpy(product_string, "Android", sizeof(product_string) - 1); + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + sprintf(serial_string, + "%08X%08X", system_serial_high, system_serial_low); +#else + strncpy(serial_string, "0123456789ABCDEF", sizeof(serial_string) - 1); +#endif + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + device_desc.iSerialNumber = id; + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum); + else { + /* gadget zero is so simple (for now, no altsettings) that + * it SHOULD NOT have problems with bulk-capable hardware. + * so just warn about unrcognized controllers -- don't panic. + * + * things like configuration and altsetting numbering + * can need hardware-specific attention though. + */ + pr_warning("%s: controller '%s' not recognized\n", + longname, gadget->name); + device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); + } +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + composite_string_index = 4; +#endif + usb_gadget_set_selfpowered(gadget); + dev->cdev = cdev; + + return 0; +} + +static int android_usb_unbind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + printk(KERN_DEBUG "usb: %s\n", __func__); + cancel_work_sync(&dev->work); + android_cleanup_functions(dev->functions); + return 0; +} + +static struct usb_composite_driver android_usb_driver = { + .name = "android_usb", + .dev = &device_desc, + .strings = dev_strings, + .unbind = android_usb_unbind, +}; + +static int +android_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *c) +{ + struct android_dev *dev = _android_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + struct android_usb_function *f; + int value = -EOPNOTSUPP; + unsigned long flags; + + req->zero = 0; + req->complete = composite_setup_complete; + req->length = 0; + gadget->ep0->driver_data = cdev; + + list_for_each_entry(f, &dev->enabled_functions, enabled_list) { + if (f->ctrlrequest) { + value = f->ctrlrequest(f, cdev, c); + if (value >= 0) + break; + } + } + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (value < 0) + value = terminal_ctrl_request(cdev, c); +#endif + + /* Special case the accessory function. + * It needs to handle control requests before it is enabled. + */ + if (value < 0) + value = acc_ctrlrequest(cdev, c); + + if (value < 0) + value = composite_setup(gadget, c); + + spin_lock_irqsave(&cdev->lock, flags); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (c->bRequest == USB_REQ_SET_CONFIGURATION && + cdev->mute_switch == true) + cdev->mute_switch = false; +#endif + if (!dev->connected) { + dev->connected = 1; + schedule_work(&dev->work); + } + else if (c->bRequest == USB_REQ_SET_CONFIGURATION && cdev->config) { + schedule_work(&dev->work); + } + spin_unlock_irqrestore(&cdev->lock, flags); + + return value; +} + +static void android_disconnect(struct usb_gadget *gadget) +{ + struct android_dev *dev = _android_dev; + struct usb_composite_dev *cdev = get_gadget_data(gadget); + unsigned long flags; + + composite_disconnect(gadget); + + spin_lock_irqsave(&cdev->lock, flags); + dev->connected = 0; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + printk(KERN_DEBUG "usb: %s con(%d), sw(%d)\n", + __func__, dev->connected, dev->sw_connected); + /* avoid sending a disconnect switch event + * until after we disconnect. + */ + if (cdev->mute_switch) { + dev->sw_connected = dev->connected; + printk(KERN_DEBUG "usb: %s mute_switch con(%d) sw(%d)\n", + __func__, dev->connected, dev->sw_connected); + } else { + set_ncm_ready(false); + if (cdev->force_disconnect) { + dev->sw_connected = 1; + printk(KERN_DEBUG "usb: %s force_disconnect\n", + __func__); + cdev->force_disconnect = 0; + } + printk(KERN_DEBUG "usb: %s schedule_work con(%d) sw(%d)\n", + __func__, dev->connected, dev->sw_connected); + schedule_work(&dev->work); + } +#else + schedule_work(&dev->work); +#endif + spin_unlock_irqrestore(&cdev->lock, flags); +} + +static int android_create_device(struct android_dev *dev) +{ + struct device_attribute **attrs = android_usb_attributes; + struct device_attribute *attr; + int err; + + dev->dev = device_create(android_class, NULL, + MKDEV(0, 0), NULL, "android0"); + if (IS_ERR(dev->dev)) + return PTR_ERR(dev->dev); + + dev_set_drvdata(dev->dev, dev); + + while ((attr = *attrs++)) { + err = device_create_file(dev->dev, attr); + if (err) { + device_destroy(android_class, dev->dev->devt); + return err; + } + } + return 0; +} + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +static int android_probe(struct platform_device *pdev) +{ + android_usb_pdata = pdev->dev.platform_data; + + printk(KERN_INFO "usb: %s pdata: %p\n", __func__, android_usb_pdata); + + if (android_usb_pdata) { + printk(KERN_INFO "usb: %s nluns=%d\n", __func__, + android_usb_pdata->nluns); + } else { + printk(KERN_INFO "usb: %s pdata is not available\n", __func__); + } + return 0; +} + +static struct platform_driver android_platform_driver = { + .driver = { .name = "android_usb", }, + .probe = android_probe, +}; +#endif + +static int __init init(void) +{ + struct android_dev *dev; + int err; + + printk(KERN_DEBUG "%s\n", __func__); + android_class = class_create(THIS_MODULE, "android_usb"); + if (IS_ERR(android_class)) + return PTR_ERR(android_class); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->functions = supported_functions; + INIT_LIST_HEAD(&dev->enabled_functions); + INIT_WORK(&dev->work, android_work); + mutex_init(&dev->mutex); + + err = android_create_device(dev); + if (err) { + class_destroy(android_class); + kfree(dev); + return err; + } + + _android_dev = dev; + + /* Override composite driver functions */ + composite_driver.setup = android_setup; + composite_driver.disconnect = android_disconnect; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* Create below sysfs + * /sys/class/android_usb/android0/terminal_version + */ + err = create_terminal_attribute(&dev->dev); + if (err) { + printk(KERN_ERR "usb: %s To create terminal_atttrr is failed\n", + __func__); + return err; + } + err = platform_driver_register(&android_platform_driver); + if (err) { + printk(KERN_ERR "usb: %s platform register is failed\n", + __func__); + return err; + } +#endif + +#ifdef CONFIG_USB_DUN_SUPPORT + err = modem_misc_register(); + if (err) { + printk(KERN_ERR "usb: %s modem misc register is failed\n", + __func__); + return err; + } +#endif + + return usb_composite_probe(&android_usb_driver, android_bind); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&android_usb_driver); + class_destroy(android_class); + kfree(_android_dev); + _android_dev = NULL; +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/composite.c b/drivers/usb/gadget/composite.c index 5cbb1a4..274facf 100644 --- a/drivers/usb/gadget/composite.c +++ b/drivers/usb/gadget/composite.c @@ -27,7 +27,13 @@ #include <linux/utsname.h> #include <linux/usb/composite.h> +#include <asm/unaligned.h> +#include "multi_config.h" +/* +#undef DBG +#define DBG(dev, fmt, args...) printk(KERN_DEBUG "usb: "fmt, ##args) +*/ /* * The code in this file is utility code, used to build a gadget driver @@ -74,7 +80,6 @@ MODULE_PARM_DESC(iSerialNumber, "SerialNumber string"); static char composite_manufacturer[50]; /*-------------------------------------------------------------------------*/ - /** * usb_add_function() - add a function to a configuration * @config: the configuration @@ -123,6 +128,8 @@ int usb_add_function(struct usb_configuration *config, config->fullspeed = true; if (!config->highspeed && function->hs_descriptors) config->highspeed = true; + if (!config->superspeed && function->ss_descriptors) + config->superspeed = true; done: if (value) @@ -247,7 +254,11 @@ static int config_buf(struct usb_configuration *config, c->bDescriptorType = type; /* wTotalLength is written later */ c->bNumInterfaces = config->next_interface_id; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + c->bConfigurationValue = get_config_number() + 1; +#else c->bConfigurationValue = config->bConfigurationValue; +#endif c->iConfiguration = config->iConfiguration; c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes; c->bMaxPower = config->bMaxPower ? : (CONFIG_USB_GADGET_VBUS_DRAW / 2); @@ -266,20 +277,44 @@ static int config_buf(struct usb_configuration *config, list_for_each_entry(f, &config->functions, list) { struct usb_descriptor_header **descriptors; - if (speed == USB_SPEED_HIGH) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (!is_available_function(f->name)) { + USB_DBG("skip f->%s\n", f->name); + continue; + } else { + USB_DBG("f->%s\n", f->name); + } +#endif + switch (speed) { + case USB_SPEED_SUPER: + descriptors = f->ss_descriptors; + break; + case USB_SPEED_HIGH: descriptors = f->hs_descriptors; - else + break; + default: descriptors = f->descriptors; + } + if (!descriptors) continue; status = usb_descriptor_fillbuf(next, len, (const struct usb_descriptor_header **) descriptors); if (status < 0) return status; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (change_conf(f, next, len, config, speed) < 0) { + USB_DBG_ESS("failed to change configuration\n"); + return -EINVAL; + } +#endif len -= status; next += status; } +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + set_interface_count(config, c); +#endif len = next - buf; c->wTotalLength = cpu_to_le16(len); return len; @@ -292,9 +327,10 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value) u8 type = w_value >> 8; enum usb_device_speed speed = USB_SPEED_UNKNOWN; - if (gadget_is_dualspeed(gadget)) { - int hs = 0; - + if (gadget->speed == USB_SPEED_SUPER) + speed = gadget->speed; + else if (gadget_is_dualspeed(gadget)) { + int hs = 0; if (gadget->speed == USB_SPEED_HIGH) hs = 1; if (type == USB_DT_OTHER_SPEED_CONFIG) @@ -306,15 +342,34 @@ static int config_desc(struct usb_composite_dev *cdev, unsigned w_value) /* This is a lookup by config *INDEX* */ w_value &= 0xff; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + w_value = set_config_number(w_value); +#endif list_for_each_entry(c, &cdev->configs, list) { /* ignore configs that won't work at this speed */ - if (speed == USB_SPEED_HIGH) { + switch (speed) { + case USB_SPEED_SUPER: + if (!c->superspeed) + continue; + /* USB 3.0 Max power < 900 mA */ + if (c->bMaxPower > 0x70) /* 896mA = 0x70 * 8mA */ + c->bMaxPower = 0x70; /* 896 mA */ + break; + case USB_SPEED_HIGH: if (!c->highspeed) continue; - } else { + /* USB 2.0 Max power < 500 mA */ + if (c->bMaxPower > 0xFA) /* 500mA = 0xFA * 2mA */ + c->bMaxPower = 0xFA; /* 500 mA */ + break; + default: if (!c->fullspeed) continue; + /* USB 2.0 Max power < 500 mA */ + if (c->bMaxPower > 0xFA) /* 500mA = 0xFA * 2mA */ + c->bMaxPower = 0xFA; /* 500 mA */ } + if (w_value == 0) return config_buf(c, speed, cdev->req->buf, type); w_value--; @@ -328,16 +383,22 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type) struct usb_configuration *c; unsigned count = 0; int hs = 0; + int ss = 0; if (gadget_is_dualspeed(gadget)) { if (gadget->speed == USB_SPEED_HIGH) hs = 1; + if (gadget->speed == USB_SPEED_SUPER) + ss = 1; if (type == USB_DT_DEVICE_QUALIFIER) hs = !hs; } list_for_each_entry(c, &cdev->configs, list) { /* ignore configs that won't work at this speed */ - if (hs) { + if (ss) { + if (!c->superspeed) + continue; + } else if (hs) { if (!c->highspeed) continue; } else { @@ -345,10 +406,78 @@ static int count_configs(struct usb_composite_dev *cdev, unsigned type) continue; } count++; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + count = count_multi_config(c, count); +#endif } return count; } +/** + * bos_desc() - prepares the BOS descriptor. + * @cdev: pointer to usb_composite device to generate the bos + * descriptor for + * + * This function generates the BOS (Binary Device Object) + * descriptor and its device capabilities descriptors. The BOS + * descriptor should be supported by a SuperSpeed device. + */ +static int bos_desc(struct usb_composite_dev *cdev) +{ + struct usb_ext_cap_descriptor *usb_ext; + struct usb_ss_cap_descriptor *ss_cap; + struct usb_dcd_config_params dcd_config_params; + struct usb_bos_descriptor *bos = cdev->req->buf; + + bos->bLength = USB_DT_BOS_SIZE; + bos->bDescriptorType = USB_DT_BOS; + + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE); + bos->bNumDeviceCaps = 0; + + /* + * A SuperSpeed device shall include the USB2.0 extension descriptor + * and shall support LPM when operating in USB2.0 HS mode. + */ + usb_ext = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_EXT_CAP_SIZE); + usb_ext->bLength = USB_DT_USB_EXT_CAP_SIZE; + usb_ext->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + usb_ext->bDevCapabilityType = USB_CAP_TYPE_EXT; + usb_ext->bmAttributes = cpu_to_le32(USB_LPM_SUPPORT); + + /* + * The Superspeed USB Capability descriptor shall be implemented by all + * SuperSpeed devices. + */ + ss_cap = cdev->req->buf + le16_to_cpu(bos->wTotalLength); + bos->bNumDeviceCaps++; + le16_add_cpu(&bos->wTotalLength, USB_DT_USB_SS_CAP_SIZE); + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* LTM is not supported yet */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION | + USB_FULL_SPEED_OPERATION | + USB_HIGH_SPEED_OPERATION | + USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + + /* Get Controller configuration */ + if (cdev->gadget->ops->get_config_params) + cdev->gadget->ops->get_config_params(&dcd_config_params); + else { + dcd_config_params.bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT; + dcd_config_params.bU2DevExitLat = + cpu_to_le16(USB_DEFAULT_U2_DEV_EXIT_LAT); + } + ss_cap->bU1devExitLat = dcd_config_params.bU1devExitLat; + ss_cap->bU2DevExitLat = dcd_config_params.bU2DevExitLat; + + return le16_to_cpu(bos->wTotalLength); +} + static void device_qual(struct usb_composite_dev *cdev) { struct usb_qualifier_descriptor *qual = cdev->req->buf; @@ -361,7 +490,7 @@ static void device_qual(struct usb_composite_dev *cdev) qual->bDeviceSubClass = cdev->desc.bDeviceSubClass; qual->bDeviceProtocol = cdev->desc.bDeviceProtocol; /* ASSUME same EP0 fifo size at both speeds */ - qual->bMaxPacketSize0 = cdev->desc.bMaxPacketSize0; + qual->bMaxPacketSize0 = cdev->gadget->ep0->maxpacket; qual->bNumConfigurations = count_configs(cdev, USB_DT_DEVICE_QUALIFIER); qual->bRESERVED = 0; } @@ -392,20 +521,32 @@ static int set_config(struct usb_composite_dev *cdev, unsigned power = gadget_is_otg(gadget) ? 8 : 100; int tmp; - if (cdev->config) - reset_config(cdev); - if (number) { list_for_each_entry(c, &cdev->configs, list) { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (c->bConfigurationValue == number || + check_config(number)) { +#else if (c->bConfigurationValue == number) { +#endif + /* + * We disable the FDs of the previous + * configuration only if the new configuration + * is a valid one + */ + if (cdev->config) + reset_config(cdev); result = 0; break; } } if (result < 0) goto done; - } else + } else { /* Zero configuration value - need to reset the config */ + if (cdev->config) + reset_config(cdev); result = 0; + } INFO(cdev, "%s speed config #%d: %s\n", ({ char *speed; @@ -413,6 +554,7 @@ static int set_config(struct usb_composite_dev *cdev, case USB_SPEED_LOW: speed = "low"; break; case USB_SPEED_FULL: speed = "full"; break; case USB_SPEED_HIGH: speed = "high"; break; + case USB_SPEED_SUPER: speed = "super"; break; default: speed = "?"; break; } ; speed; }), number, c ? c->label : "unconfigured"); @@ -428,17 +570,25 @@ static int set_config(struct usb_composite_dev *cdev, if (!f) break; - +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + USB_DBG_ESS("e %s[%d]\n", f->name, tmp); +#endif /* * Record which endpoints are used by the function. This is used * to dispatch control requests targeted at that endpoint to the * function's setup callback instead of the current * configuration's setup callback. */ - if (gadget->speed == USB_SPEED_HIGH) + switch (gadget->speed) { + case USB_SPEED_SUPER: + descriptors = f->ss_descriptors; + break; + case USB_SPEED_HIGH: descriptors = f->hs_descriptors; - else + break; + default: descriptors = f->descriptors; + } for (; *descriptors; ++descriptors) { struct usb_endpoint_descriptor *ep; @@ -476,6 +626,7 @@ static int set_config(struct usb_composite_dev *cdev, power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW; done: usb_gadget_vbus_draw(gadget, power); + if (result >= 0 && cdev->delayed_status) result = USB_GADGET_DELAYED_STATUS; return result; @@ -523,6 +674,7 @@ int usb_add_config(struct usb_composite_dev *cdev, INIT_LIST_HEAD(&config->functions); config->next_interface_id = 0; + memset(config->interface, '\0', sizeof(config->interface)); status = bind(config); if (status < 0) { @@ -531,8 +683,9 @@ int usb_add_config(struct usb_composite_dev *cdev, } else { unsigned i; - DBG(cdev, "cfg %d/%p speeds:%s%s\n", + DBG(cdev, "cfg %d/%p speeds:%s%s%s\n", config->bConfigurationValue, config, + config->superspeed ? " super" : "", config->highspeed ? " high" : "", config->fullspeed ? (gadget_is_dualspeed(cdev->gadget) @@ -562,6 +715,47 @@ done: return status; } +static int remove_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + while (!list_empty(&config->functions)) { + struct usb_function *f; + + f = list_first_entry(&config->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", f->name, f); + f->unbind(config, f); + /* may free memory for "f" */ + } + } + list_del(&config->list); + if (config->unbind) { + DBG(cdev, "unbind config '%s'/%p\n", config->label, config); + config->unbind(config); + /* may free memory for "c" */ + } + return 0; +} + +int usb_remove_config(struct usb_composite_dev *cdev, + struct usb_configuration *config) +{ + unsigned long flags; + + printk(KERN_DEBUG "usb: %s cdev->config=%p, config=%p\n", + __func__, cdev->config, config); + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->config == config) + reset_config(cdev); + + spin_unlock_irqrestore(&cdev->lock, flags); + + return remove_config(cdev, config); +} + /*-------------------------------------------------------------------------*/ /* We support strings in multiple languages ... string descriptor zero @@ -641,6 +835,14 @@ static int get_string(struct usb_composite_dev *cdev, collect_langs(sp, s->wData); list_for_each_entry(f, &c->functions, list) { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (!is_available_function(f->name)) { + USB_DBG("skip f->%s\n", f->name); + continue; + } else { + USB_DBG("f->%s\n", f->name); + } +#endif sp = f->strings; if (sp) collect_langs(sp, s->wData); @@ -723,8 +925,12 @@ int usb_string_id(struct usb_composite_dev *cdev) * supported languages */ /* 255 reserved as well? -- mina86 */ cdev->next_string_id++; + printk(KERN_DEBUG "usb: %s cdev(0x%p)->next_string_id=%d\n", + __func__, cdev, cdev->next_string_id); return cdev->next_string_id; } + printk(KERN_DEBUG "usb: %s error cdev(0x%p)->next_string_id=%d\n", + __func__, cdev, cdev->next_string_id); return -ENODEV; } @@ -748,6 +954,8 @@ int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str) { int next = cdev->next_string_id; + printk(KERN_DEBUG "usb: %s --cdev(0x%p)->next_string_id=%d\n", + __func__, cdev, cdev->next_string_id); for (; str->s; ++str) { if (unlikely(next >= 254)) return -ENODEV; @@ -781,6 +989,8 @@ int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str) int usb_string_ids_n(struct usb_composite_dev *c, unsigned n) { unsigned next = c->next_string_id; + printk(KERN_DEBUG "usb: %s --cdev(0x%p)->next_string_id=%d\n", + __func__, c, c->next_string_id); if (unlikely(n > 254 || (unsigned)next + n > 254)) return -ENODEV; c->next_string_id += n; @@ -811,6 +1021,7 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) struct usb_composite_dev *cdev = get_gadget_data(gadget); struct usb_request *req = cdev->req; int value = -EOPNOTSUPP; + int status = 0; u16 w_index = le16_to_cpu(ctrl->wIndex); u8 intf = w_index & 0xFF; u16 w_value = le16_to_cpu(ctrl->wValue); @@ -838,18 +1049,32 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) case USB_DT_DEVICE: cdev->desc.bNumConfigurations = count_configs(cdev, USB_DT_DEVICE); + cdev->desc.bMaxPacketSize0 = + cdev->gadget->ep0->maxpacket; + if (gadget_is_superspeed(gadget)) { + if (gadget->speed >= USB_SPEED_SUPER) { + cdev->desc.bcdUSB = cpu_to_le16(0x0300); + cdev->desc.bMaxPacketSize0 = 9; + } else { + cdev->desc.bcdUSB = cpu_to_le16(0x0210); + } + } + value = min(w_length, (u16) sizeof cdev->desc); memcpy(req->buf, &cdev->desc, value); + printk(KERN_DEBUG "usb: GET_DES\n"); break; case USB_DT_DEVICE_QUALIFIER: - if (!gadget_is_dualspeed(gadget)) + if (!gadget_is_dualspeed(gadget) || + gadget->speed >= USB_SPEED_SUPER) break; device_qual(cdev); value = min_t(int, w_length, sizeof(struct usb_qualifier_descriptor)); break; case USB_DT_OTHER_SPEED_CONFIG: - if (!gadget_is_dualspeed(gadget)) + if (!gadget_is_dualspeed(gadget) || + gadget->speed >= USB_SPEED_SUPER) break; /* FALLTHROUGH */ case USB_DT_CONFIG: @@ -858,11 +1083,20 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) value = min(w_length, (u16) value); break; case USB_DT_STRING: +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + set_string_mode(w_length); +#endif value = get_string(cdev, req->buf, w_index, w_value & 0xff); if (value >= 0) value = min(w_length, (u16) value); break; + case USB_DT_BOS: + if (gadget_is_superspeed(gadget)) { + value = bos_desc(cdev); + value = min(w_length, (u16) value); + } + break; } break; @@ -881,12 +1115,17 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) spin_lock(&cdev->lock); value = set_config(cdev, ctrl, w_value); spin_unlock(&cdev->lock); + printk(KERN_DEBUG "usb: SET_CON\n"); break; case USB_REQ_GET_CONFIGURATION: if (ctrl->bRequestType != USB_DIR_IN) goto unknown; if (cdev->config) +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + *(u8 *)req->buf = get_config_number() + 1; +#else *(u8 *)req->buf = cdev->config->bConfigurationValue; +#endif else *(u8 *)req->buf = 0; value = min(w_length, (u16) 1); @@ -930,6 +1169,61 @@ composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) *((u8 *)req->buf) = value; value = min(w_length, (u16) 1); break; + + /* + * USB 3.0 additions: + * Function driver should handle get_status request. If such cb + * wasn't supplied we respond with default value = 0 + * Note: function driver should supply such cb only for the first + * interface of the function + */ + case USB_REQ_GET_STATUS: + if (!gadget_is_superspeed(gadget)) + goto unknown; + if (ctrl->bRequestType != (USB_DIR_IN | USB_RECIP_INTERFACE)) + goto unknown; + value = 2; /* This is the length of the get_status reply */ + put_unaligned_le16(0, req->buf); + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + status = f->get_status ? f->get_status(f) : 0; + if (status < 0) + break; + put_unaligned_le16(status & 0x0000ffff, req->buf); + break; + /* + * Function drivers should handle SetFeature/ClearFeature + * (FUNCTION_SUSPEND) request. function_suspend cb should be supplied + * only for the first interface of the function + */ + case USB_REQ_CLEAR_FEATURE: + case USB_REQ_SET_FEATURE: + if (!gadget_is_superspeed(gadget)) + goto unknown; + if (ctrl->bRequestType != (USB_DIR_OUT | USB_RECIP_INTERFACE)) + goto unknown; + switch (w_value) { + case USB_INTRF_FUNC_SUSPEND: + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + value = 0; + if (f->func_suspend) + value = f->func_suspend(f, w_index >> 8); + if (value < 0) { + ERROR(cdev, + "func_suspend() returned error %d\n", + value); + value = 0; + } + break; + } + break; default: unknown: VDBG(cdev, @@ -1002,6 +1296,9 @@ static void composite_disconnect(struct usb_gadget *gadget) struct usb_composite_dev *cdev = get_gadget_data(gadget); unsigned long flags; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + set_string_mode(0); +#endif /* REVISIT: should we have config and device level * disconnect callbacks? */ @@ -1041,28 +1338,9 @@ composite_unbind(struct usb_gadget *gadget) while (!list_empty(&cdev->configs)) { struct usb_configuration *c; - c = list_first_entry(&cdev->configs, struct usb_configuration, list); - while (!list_empty(&c->functions)) { - struct usb_function *f; - - f = list_first_entry(&c->functions, - struct usb_function, list); - list_del(&f->list); - if (f->unbind) { - DBG(cdev, "unbind function '%s'/%p\n", - f->name, f); - f->unbind(c, f); - /* may free memory for "f" */ - } - } - list_del(&c->list); - if (c->unbind) { - DBG(cdev, "unbind config '%s'/%p\n", c->label, c); - c->unbind(c); - /* may free memory for "c" */ - } + remove_config(cdev, c); } if (composite->unbind) composite->unbind(cdev); @@ -1140,7 +1418,6 @@ static int composite_bind(struct usb_gadget *gadget) goto fail; cdev->desc = *composite->dev; - cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket; /* standardized runtime overrides for device ID data */ if (idVendor) @@ -1150,6 +1427,10 @@ static int composite_bind(struct usb_gadget *gadget) if (bcdDevice) cdev->desc.bcdDevice = cpu_to_le16(bcdDevice); + printk(KERN_DEBUG "usb: %s idVendor=0x%x, idProduct=0x%x\n", + __func__, idVendor, idProduct); + printk(KERN_DEBUG "usb: %s bcdDevice=0x%x\n", __func__, bcdDevice); + /* string overrides */ if (iManufacturer || !cdev->desc.iManufacturer) { if (!iManufacturer && !composite->iManufacturer && @@ -1165,6 +1446,9 @@ static int composite_bind(struct usb_gadget *gadget) override_id(cdev, &cdev->desc.iManufacturer); } + printk(KERN_DEBUG "usb: %s composite_manufacturer=%s\n", + __func__, composite_manufacturer); + if (iProduct || (!cdev->desc.iProduct && composite->iProduct)) cdev->product_override = override_id(cdev, &cdev->desc.iProduct); @@ -1247,7 +1531,11 @@ composite_resume(struct usb_gadget *gadget) /*-------------------------------------------------------------------------*/ static struct usb_gadget_driver composite_driver = { +#ifdef CONFIG_USB_GADGET_SUPERSPEED + .speed = USB_SPEED_SUPER, +#else .speed = USB_SPEED_HIGH, +#endif .unbind = composite_unbind, diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 9b7360f..212f8fc 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -142,13 +142,13 @@ ep_matches ( max = 0x7ff & le16_to_cpu(desc->wMaxPacketSize); switch (type) { case USB_ENDPOINT_XFER_INT: - /* INT: limit 64 bytes full speed, 1024 high speed */ + /* INT: limit 64 bytes full speed, 1024 high/super speed */ if (!gadget->is_dualspeed && max > 64) return 0; /* FALLTHROUGH */ case USB_ENDPOINT_XFER_ISOC: - /* ISO: limit 1023 bytes full speed, 1024 high speed */ + /* ISO: limit 1023 bytes full speed, 1024 high/super speed */ if (ep->maxpacket < max) return 0; if (!gadget->is_dualspeed && max > 1023) @@ -290,6 +290,62 @@ struct usb_ep *usb_ep_autoconfig ( if (ep && ep_matches (gadget, ep, desc)) return ep; #endif + } else if (gadget_is_s3c(gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep (gadget, "ep3-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep6-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep9-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + ep = find_ep(gadget, "ep12-int"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; +#endif + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep2-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep5-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep8-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + ep = find_ep(gadget, "ep11-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + ep = find_ep(gadget, "ep14-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; +#endif + } else if (USB_ENDPOINT_XFER_BULK == type + && !(USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep1-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep4-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep7-bulk"); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (ep && ep_matches(gadget, ep, desc)) + return ep; + ep = find_ep(gadget, "ep10-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + ep = find_ep(gadget, "ep13-bulk"); +#endif + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } } /* Second, look at endpoints until an unclaimed one looks usable */ @@ -323,4 +379,3 @@ void usb_ep_autoconfig_reset (struct usb_gadget *gadget) #endif epnum = 0; } - diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 1690c9d..dcb17d2 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -246,7 +246,9 @@ static int __init rndis_do_config(struct usb_configuration *c) c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; } - return rndis_bind_config(c, hostaddr); + return rndis_bind_config(c, hostaddr, + cpu_to_le16 (CDC_VENDOR_NUM), + manufacturer); } static struct usb_configuration rndis_config_driver = { diff --git a/drivers/usb/gadget/exynos_ss_udc.c b/drivers/usb/gadget/exynos_ss_udc.c new file mode 100644 index 0000000..c8001e8 --- /dev/null +++ b/drivers/usb/gadget/exynos_ss_udc.c @@ -0,0 +1,3047 @@ +/* linux/drivers/usb/gadget/exynos_ss_udc.c + * + * Copyright 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS SuperSpeed USB 3.0 Device Controlle driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/clk.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> +#include <linux/usb/exynos_usb3_drd.h> + +#include <linux/platform_data/exynos_usb3_drd.h> + +#include <asm/byteorder.h> + +#include "exynos_ss_udc.h" + +#define SAMSUNG_MUIC_OR_BATTERY_DRIVER_NOT_READY +#define USE_WAKE_LOCK + +static void exynos_ss_udc_kill_all_requests(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + int result); +static void exynos_ss_udc_ep0_restart(struct exynos_ss_udc *udc); +static void exynos_ss_udc_complete_setup(struct usb_ep *ep, + struct usb_request *req); +static void exynos_ss_udc_complete_request(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + int result); +static void exynos_ss_udc_start_req(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + bool continuing); +static void exynos_ss_udc_ep_activate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep); +static void exynos_ss_udc_ep_deactivate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep); + + +static struct exynos_ss_udc *our_udc; + +static int poll_bit_set(void __iomem *ptr, u32 val, int timeout) +{ + u32 reg; + + do { + reg = readl(ptr); + if (reg & val) + return 0; + + udelay(1); + } while (timeout-- > 0); + + return -ETIME; +} + +static int poll_bit_clear(void __iomem *ptr, u32 val, int timeout) +{ + u32 reg; + + do { + reg = readl(ptr); + if (!(reg & val)) + return 0; + + udelay(1); + } while (timeout-- > 0); + + return -ETIME; +} + +/** + * ep_from_windex - convert control wIndex value to endpoint + * @udc: The device state. + * @windex: The control request wIndex field (in host order). + * + * Convert the given wIndex into a pointer to an driver endpoint + * structure, or return NULL if it is not a valid endpoint. + */ +static struct exynos_ss_udc_ep *ep_from_windex(struct exynos_ss_udc *udc, + u32 windex) +{ + struct exynos_ss_udc_ep *ep = &udc->eps[windex & 0x7F]; + int dir = (windex & USB_DIR_IN) ? 1 : 0; + int idx = windex & 0x7F; + + if (windex >= 0x100) + return NULL; + + if (idx > EXYNOS_USB3_EPS) + return NULL; + + if (idx && ep->dir_in != dir) + return NULL; + + return ep; +} + +/** + * get_ep_head - return the first request on the endpoint + * @udc_ep: The endpoint to get request from. + * + * Get the first request on the endpoint. + */ +static struct exynos_ss_udc_req *get_ep_head(struct exynos_ss_udc_ep *udc_ep) +{ + if (list_empty(&udc_ep->queue)) + return NULL; + + return list_first_entry(&udc_ep->queue, + struct exynos_ss_udc_req, + queue); +} + +/** + * on_list - check request is on the given endpoint + * @ep: The endpoint to check. + * @test: The request to test if it is on the endpoint. + */ +static bool on_list(struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *test) +{ + struct exynos_ss_udc_req *udc_req, *treq; + + list_for_each_entry_safe(udc_req, treq, &udc_ep->queue, queue) { + if (udc_req == test) + return true; + } + + return false; +} + +/** + * exynos_ss_udc_map_dma - map the DMA memory being used for the request + * @udc: The device state. + * @udc_ep: The endpoint the request is on. + * @req: The request being processed. + * + * We've been asked to queue a request, so ensure that the memory buffer + * is correctly setup for DMA. If we've been passed an extant DMA address + * then ensure the buffer has been synced to memory. If our buffer has no + * DMA memory, then we map the memory and mark our request to allow us to + * cleanup on completion. + */ +static int exynos_ss_udc_map_dma(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct usb_request *req) +{ + enum dma_data_direction dir; + struct exynos_ss_udc_req *udc_req = our_req(req); + + dir = udc_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + /* if the length is zero, ignore the DMA data */ + if (udc_req->req.length == 0) + return 0; + + if (req->dma == DMA_ADDR_INVALID) { + dma_addr_t dma; + + dma = dma_map_single(udc->dev, + req->buf, req->length, dir); + + if (unlikely(dma_mapping_error(udc->dev, dma))) + goto dma_error; + + udc_req->mapped = 1; + req->dma = dma; + } else + dma_sync_single_for_device(udc->dev, + req->dma, req->length, dir); + + return 0; + +dma_error: + dev_err(udc->dev, "%s: failed to map buffer %p, %d bytes\n", + __func__, req->buf, req->length); + + return -EIO; +} + +/** + * exynos_ss_udc_unmap_dma - unmap the DMA memory being used for the request + * @udc: The device state. + * @udc_ep: The endpoint for the request. + * @udc_req: The request being processed. + * + * This is the reverse of exynos_ss_udc_map_dma(), called for the completion + * of a request to ensure the buffer is ready for access by the caller. + */ +static void exynos_ss_udc_unmap_dma(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req) +{ + struct usb_request *req = &udc_req->req; + enum dma_data_direction dir; + + dir = udc_ep->dir_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE; + + /* ignore this if we're not moving any data */ + if (udc_req->req.length == 0) + return; + + if (udc_req->mapped) { + /* we mapped this, so unmap and remove the dma */ + + dma_unmap_single(udc->dev, req->dma, req->length, dir); + + req->dma = DMA_ADDR_INVALID; + udc_req->mapped = 0; + } +} + +/** + * exynos_ss_udc_issue_epcmd - issue physical endpoint-specific command + * @udc: The device state. + * @epcmd: The command to issue. + */ +static int exynos_ss_udc_issue_epcmd(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep_command *epcmd) +{ + int res; + u32 depcmd; + + /* If some of parameters are not in use, we will write it anyway + for simplification */ + writel(epcmd->param0, udc->regs + EXYNOS_USB3_DEPCMDPAR0(epcmd->ep)); + writel(epcmd->param1, udc->regs + EXYNOS_USB3_DEPCMDPAR1(epcmd->ep)); + writel(epcmd->param2, udc->regs + EXYNOS_USB3_DEPCMDPAR2(epcmd->ep)); + + depcmd = epcmd->cmdtyp | epcmd->cmdflags; + writel(depcmd, udc->regs + EXYNOS_USB3_DEPCMD(epcmd->ep)); + + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DEPCMD(epcmd->ep), + EXYNOS_USB3_DEPCMDx_CmdAct, + 1000); + return res; +} + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +void exynos_ss_udc_cable_connect(struct exynos_ss_udc *udc, bool connect) +{ + static int last_connect; + + if (last_connect != connect) { + samsung_cable_check_status(connect); + last_connect = connect; + } +} +#define EXYNOS_SS_UDC_CABLE_CONNECT(udc, connect) \ + exynos_ss_udc_cable_connect(udc, connect) +#else +#define EXYNOS_SS_UDC_CABLE_CONNECT(udc, conect) +#endif + +/** + * exynos_ss_udc_run_stop - start/stop the device controller operation + * @udc: The device state. + * @is_on: The action to take (1 - start, 0 - stop). + */ +static void exynos_ss_udc_run_stop(struct exynos_ss_udc *udc, int is_on) +{ + int res; + + if (is_on) { + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_Run_Stop); + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DSTS, + EXYNOS_USB3_DSTS_DevCtrlHlt, + 1000); + } else { + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_Run_Stop); + res = poll_bit_set(udc->regs + EXYNOS_USB3_DSTS, + EXYNOS_USB3_DSTS_DevCtrlHlt, + 1000); + } + + if (res < 0) + dev_dbg(udc->dev, "Failed to %sconnect by software\n", + is_on ? "" : "dis"); +} + +/** + * exynos_ss_udc_ep_enable - configure endpoint, making it usable + * @ep: The endpoint being configured. + * @desc: The descriptor for desired behavior. + */ +static int exynos_ss_udc_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long flags; + int dir_in; + int epnum; + + dev_dbg(udc->dev, + "%s: ep %s: a 0x%02x, attr 0x%02x, mps 0x%04x, intr %d\n", + __func__, ep->name, desc->bEndpointAddress, desc->bmAttributes, + desc->wMaxPacketSize, desc->bInterval); + + /* not to be called for EP0 */ + WARN_ON(udc_ep->epnum == 0); + + if (udc_ep->enabled) + return -EINVAL; + + epnum = (desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + if (epnum != udc_ep->epnum) { + dev_err(udc->dev, "%s: EP number mismatch!\n", __func__); + return -EINVAL; + } + + dir_in = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? 1 : 0; + if (dir_in != udc_ep->dir_in) { + dev_err(udc->dev, "%s: EP direction mismatch!\n", __func__); + return -EINVAL; + } + + spin_lock_irqsave(&udc_ep->lock, flags); + + /* update the endpoint state */ + udc_ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + udc_ep->type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + switch (udc_ep->type) { + case USB_ENDPOINT_XFER_ISOC: + dev_err(udc->dev, "no current ISOC support\n"); + spin_unlock_irqrestore(&udc_ep->lock, flags); + return -EINVAL; + + case USB_ENDPOINT_XFER_BULK: + dev_dbg(udc->dev, "Bulk endpoint\n"); + break; + + case USB_ENDPOINT_XFER_INT: + dev_dbg(udc->dev, "Interrupt endpoint\n"); + break; + + case USB_ENDPOINT_XFER_CONTROL: + dev_dbg(udc->dev, "Control endpoint\n"); + break; + } + + exynos_ss_udc_ep_activate(udc, udc_ep); + udc_ep->enabled = 1; + udc_ep->halted = udc_ep->wedged = 0; + spin_unlock_irqrestore(&udc_ep->lock, flags); + + return 0; +} + +/** + * exynos_ss_udc_ep_disable - endpoint is no longer usable + * @ep: The endpoint being unconfigured. + */ +static int exynos_ss_udc_ep_disable(struct usb_ep *ep) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long flags; + + dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, + udc_ep->epnum, udc_ep->dir_in ? "in" : "out"); + + spin_lock_irqsave(&udc_ep->lock, flags); + exynos_ss_udc_ep_deactivate(udc, udc_ep); + udc_ep->enabled = 0; + spin_unlock_irqrestore(&udc_ep->lock, flags); + + /* terminate all requests with shutdown */ + exynos_ss_udc_kill_all_requests(udc, udc_ep, -ESHUTDOWN); + + return 0; +} + +/** + * exynos_ss_udc_ep_alloc_request - allocate a request object + * @ep: The endpoint to be used with the request. + * @flags: Allocation flags. + * + * Allocate a new USB request structure appropriate for the specified endpoint. + */ +static struct usb_request *exynos_ss_udc_ep_alloc_request(struct usb_ep *ep, + gfp_t flags) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + struct exynos_ss_udc_req *udc_req; + + dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum); + + udc_req = kzalloc(sizeof(struct exynos_ss_udc_req), flags); + if (!udc_req) + return NULL; + + INIT_LIST_HEAD(&udc_req->queue); + + udc_req->req.dma = DMA_ADDR_INVALID; + return &udc_req->req; +} + +/** + * exynos_ss_udc_ep_free_request - free a request object + * @ep: The endpoint associated with the request. + * @req: The request being freed. + * + * Reverse the effect of exynos_ss_udc_ep_alloc_request(). + */ +static void exynos_ss_udc_ep_free_request(struct usb_ep *ep, + struct usb_request *req) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + struct exynos_ss_udc_req *udc_req = our_req(req); + + dev_dbg(udc->dev, "%s: ep%d, req %p\n", __func__, udc_ep->epnum, req); + + kfree(udc_req); +} + +/** + * exynos_ss_udc_ep_queue - queue (submit) an I/O request to an endpoint + * @ep: The endpoint associated with the request. + * @req: The request being submitted. + * @gfp_flags: Not used. + * + * Queue a request and start it if the first. + */ +static int exynos_ss_udc_ep_queue(struct usb_ep *ep, + struct usb_request *req, + gfp_t gfp_flags) +{ + struct exynos_ss_udc_req *udc_req = our_req(req); + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long irqflags; + bool first; + int ret; + + dev_dbg(udc->dev, "%s: ep%d%s (%p): %d@%p, noi=%d, zero=%d, snok=%d\n", + __func__, udc_ep->epnum, + udc_ep->dir_in ? "in" : "out", req, + req->length, req->buf, req->no_interrupt, + req->zero, req->short_not_ok); + + /* initialise status of the request */ + INIT_LIST_HEAD(&udc_req->queue); + + req->actual = 0; + req->status = -EINPROGRESS; + + /* Sync the buffers as necessary */ + if (req->buf == udc->ctrl_buff) + req->dma = udc->ctrl_buff_dma; + else if (req->buf == udc->ep0_buff) + req->dma = udc->ep0_buff_dma; + else { + ret = exynos_ss_udc_map_dma(udc, udc_ep, req); + if (ret) + return ret; + } + + spin_lock_irqsave(&udc_ep->lock, irqflags); + + first = list_empty(&udc_ep->queue); + list_add_tail(&udc_req->queue, &udc_ep->queue); + + if (first && !udc_ep->not_ready) + exynos_ss_udc_start_req(udc, udc_ep, udc_req, false); + + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + + return 0; +} + +/** + * exynos_ss_udc_ep_dequeue - dequeue an I/O request from an endpoint + * @ep: The endpoint associated with the request. + * @req: The request being canceled. + * + * Dequeue a request and call its completion routine. + */ +static int exynos_ss_udc_ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct exynos_ss_udc_req *udc_req = our_req(req); + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + unsigned long flags; + + dev_dbg(udc->dev, "%s: ep%d%s (%p)\n", __func__, + udc_ep->epnum, udc_ep->dir_in ? "in" : "out", req); + + spin_lock_irqsave(&udc_ep->lock, flags); + + if (!on_list(udc_ep, udc_req)) { + spin_unlock_irqrestore(&udc_ep->lock, flags); + return -EINVAL; + } + + exynos_ss_udc_complete_request(udc, udc_ep, udc_req, -ECONNRESET); + spin_unlock_irqrestore(&udc_ep->lock, flags); + + return 0; +} + +/** + * exynos_ss_udc_ep_sethalt - set/clear the endpoint halt feature + * @ep: The endpoint being stalled/reset. + * @value: The action to take (1 - set stall, 0 - clear stall). + */ +static int exynos_ss_udc_ep_sethalt(struct usb_ep *ep, int value) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + struct exynos_ss_udc_ep_command epcmd = {{0}, }; + int epnum = udc_ep->epnum; + int index = get_phys_epnum(udc_ep); + unsigned long irqflags; + int res; + + dev_dbg(udc->dev, "%s(ep %p %s, %d)\n", __func__, ep, ep->name, value); + + spin_lock_irqsave(&udc_ep->lock, irqflags); + + if (value && epnum != 0 && udc_ep->dir_in && udc_ep->req) { + dev_dbg(udc->dev, "%s: transfer in progress!\n", __func__); + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + return -EAGAIN; + } + + if (epnum == 0) + /* Only OUT direction can be stalled */ + epcmd.ep = 0; + else + epcmd.ep = index; + + if (value) + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL; + else + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCSTALL; + + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) { + dev_err(udc->dev, "Failed to set/clear stall\n"); + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + return res; + } + + if (epnum == 0) { + if (value) + udc->ep0_state = EP0_STALL; + } else { + udc_ep->halted = !!value; + + if (!value) + udc_ep->wedged = 0; + } + + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + + return 0; +} + +/** + * exynos_ss_udc_ep_setwedge - set the halt feature and ignore clear requests + * @ep: The endpoint being wedged. + */ +static int exynos_ss_udc_ep_setwedge(struct usb_ep *ep) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + unsigned long irqflags; + + spin_lock_irqsave(&udc_ep->lock, irqflags); + udc_ep->wedged = 1; + spin_unlock_irqrestore(&udc_ep->lock, irqflags); + + return exynos_ss_udc_ep_sethalt(ep, 1); +} + +static struct usb_ep_ops exynos_ss_udc_ep_ops = { + .enable = exynos_ss_udc_ep_enable, + .disable = exynos_ss_udc_ep_disable, + .alloc_request = exynos_ss_udc_ep_alloc_request, + .free_request = exynos_ss_udc_ep_free_request, + .queue = exynos_ss_udc_ep_queue, + .dequeue = exynos_ss_udc_ep_dequeue, + .set_halt = exynos_ss_udc_ep_sethalt, + .set_wedge = exynos_ss_udc_ep_setwedge, +}; + +/** + * exynos_ss_udc_start_req - start a USB request from an endpoint's queue + * @udc: The device state. + * @udc_ep: The endpoint to process a request for. + * @udc_req: The request being started. + * @continuing: True if we are doing more for the current request. + * + * Start the given request running by setting the TRB appropriately, + * and issuing Start Transfer endpoint command. + */ +static void exynos_ss_udc_start_req(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + bool continuing) +{ + struct exynos_ss_udc_ep_command epcmd = {{0}, }; + struct usb_request *ureq = &udc_req->req; + enum trb_control trb_type = NORMAL; + int epnum = udc_ep->epnum; + int xfer_length; + int res; + + dev_dbg(udc->dev, "%s: ep%d%s, req %p\n", __func__, epnum, + udc_ep->dir_in ? "in" : "out", ureq); + + /* If endpoint is stalled, we will restart request later */ + if (udc_ep->halted) { + dev_dbg(udc->dev, "%s: ep%d is stalled\n", __func__, epnum); + return; + } + + udc_ep->req = udc_req; + + /* Get type of TRB */ + if (epnum == 0 && !continuing) + switch (udc->ep0_state) { + case EP0_SETUP_PHASE: + trb_type = CONTROL_SETUP; + break; + + case EP0_DATA_PHASE: + trb_type = CONTROL_DATA; + break; + + case EP0_STATUS_PHASE: + if (udc->ep0_three_stage) + trb_type = CONTROL_STATUS_3; + else + trb_type = CONTROL_STATUS_2; + break; + default: + dev_warn(udc->dev, "%s: Erroneous EP0 state (%d)", + __func__, udc->ep0_state); + return; + break; + } + else + trb_type = NORMAL; + + /* Get transfer length */ + if (udc_ep->dir_in) + xfer_length = ureq->length; + else + xfer_length = (ureq->length + udc_ep->ep.maxpacket - 1) & + ~(udc_ep->ep.maxpacket - 1); + + /* Fill TRB */ + udc_ep->trb->buff_ptr_low = (u32) ureq->dma; + udc_ep->trb->buff_ptr_high = 0; + udc_ep->trb->param1 = EXYNOS_USB3_TRB_BUFSIZ(xfer_length); + udc_ep->trb->param2 = EXYNOS_USB3_TRB_IOC | + EXYNOS_USB3_TRB_LST | + EXYNOS_USB3_TRB_HWO | + EXYNOS_USB3_TRB_TRBCTL(trb_type); + + /* Start Transfer */ + epcmd.ep = get_phys_epnum(udc_ep); + epcmd.param0 = 0; + epcmd.param1 = udc_ep->trb_dma; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTRTXFER; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to start transfer\n"); + + udc_ep->tri = (readl(udc->regs + EXYNOS_USB3_DEPCMD(epcmd.ep)) >> + EXYNOS_USB3_DEPCMDx_EventParam_SHIFT) & + EXYNOS_USB3_DEPCMDx_XferRscIdx_LIMIT; +} + +/** + * exynos_ss_udc_enqueue_status - start a request for EP0 status stage + * @udc: The device state. + */ +static void exynos_ss_udc_enqueue_status(struct exynos_ss_udc *udc) +{ + struct usb_request *req = udc->ctrl_req; + struct exynos_ss_udc_req *udc_req = our_req(req); + int ret; + + dev_dbg(udc->dev, "%s: queueing status request\n", __func__); + + req->zero = 0; + req->length = 0; + req->buf = udc->ctrl_buff; + req->complete = NULL; + + if (!list_empty(&udc_req->queue)) { + dev_info(udc->dev, "%s already queued???\n", __func__); + return; + } + + ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) + dev_err(udc->dev, "%s: failed queue (%d)\n", __func__, ret); +} + +/** + * exynos_ss_udc_enqueue_data - start a request for EP0 data stage + * @udc: The device state. + * @buff: The buffer used for data. + * @length: The length of data. + * @complete: The function called when request completes. + */ +static int exynos_ss_udc_enqueue_data(struct exynos_ss_udc *udc, + void *buff, int length, + void (*complete) (struct usb_ep *ep, + struct usb_request *req)) +{ + struct usb_request *req = udc->ctrl_req; + struct exynos_ss_udc_req *udc_req = our_req(req); + int ret; + + dev_dbg(udc->dev, "%s: queueing data request\n", __func__); + + req->zero = 0; + req->length = length; + + if (buff == NULL) + req->buf = udc->ep0_buff; + else + req->buf = buff; + + req->complete = complete; + + if (!list_empty(&udc_req->queue)) { + dev_info(udc->dev, "%s: already queued???\n", __func__); + return -EAGAIN; + } + + ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) { + dev_err(udc->dev, "%s: failed to enqueue data request (%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +/** + * exynos_ss_udc_enqueue_setup - start a request for EP0 setup stage + * @udc: The device state. + * + * Enqueue a request on EP0 if necessary to receive any SETUP packets + * from the host. + */ +static void exynos_ss_udc_enqueue_setup(struct exynos_ss_udc *udc) +{ + struct usb_request *req = udc->ctrl_req; + struct exynos_ss_udc_req *udc_req = our_req(req); + int ret; + + dev_dbg(udc->dev, "%s: queueing setup request\n", __func__); + + req->zero = 0; + req->length = EXYNOS_USB3_CTRL_BUFF_SIZE; + req->buf = udc->ctrl_buff; + req->complete = exynos_ss_udc_complete_setup; + + if (!list_empty(&udc_req->queue)) { + dev_dbg(udc->dev, "%s already queued???\n", __func__); + return; + } + + udc->eps[0].dir_in = 0; + + ret = exynos_ss_udc_ep_queue(&udc->eps[0].ep, req, GFP_ATOMIC); + if (ret < 0) + dev_err(udc->dev, "%s: failed queue (%d)\n", __func__, ret); +} + +/** + * exynos_ss_udc_process_set_config - process request SET_CONFIGURATION + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_config(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + int ret; + u16 config = le16_to_cpu(ctrl->wValue); + + dev_dbg(udc->dev, "%s\n", __func__); + + udc->ep0_state = EP0_STATUS_PHASE; + + switch (udc->state) { + case USB_STATE_ADDRESS: + ret = udc->driver->setup(&udc->gadget, ctrl); + if (!ret || ret == USB_GADGET_DELAYED_STATUS) { + ret = 1; + if (config) + udc->state = USB_STATE_CONFIGURED; + } + break; + + case USB_STATE_CONFIGURED: + ret = udc->driver->setup(&udc->gadget, ctrl); + if (!ret || ret == USB_GADGET_DELAYED_STATUS) { + ret = 1; + if (!config) + udc->state = USB_STATE_ADDRESS; + } + break; + + case USB_STATE_DEFAULT: + /* FALLTHROUGH */ + default: + ret = -EINVAL; + break; + } + + return ret; +} + +/** + * exynos_ss_udc_complete_set_sel - completion of SET_SEL request data stage + * @ep: The endpoint the request was on. + * @req: The request completed. + */ +static void exynos_ss_udc_complete_set_sel(struct usb_ep *ep, + struct usb_request *req) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + u8 *sel = req->buf; + u32 param; + u32 dgcmd; + int res; + + /* Our device is U1/U2 enabled, so we will use U2PEL */ + param = sel[5] << 8 | sel[4]; + /* Documentation says "If the value is greater than 125us, then + * software must program a value of zero into this register */ + if (param > 125) + param = 0; + + dev_dbg(udc->dev, "%s: dgcmd_param = 0x%08x\n", __func__, param); + + dgcmd = EXYNOS_USB3_DGCMD_CmdAct | + EXYNOS_USB3_DGCMD_CmdTyp_SetPerParams; + + writel(param, udc->regs + EXYNOS_USB3_DGCMDPAR); + writel(dgcmd, udc->regs + EXYNOS_USB3_DGCMD); + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DGCMD, + EXYNOS_USB3_DGCMD_CmdAct, + 1000); + if (res < 0) + dev_err(udc->dev, "Failed to set periodic parameters\n"); +} + +/** + * exynos_ss_udc_process_set_sel - process request SET_SEL + * @udc: The device state. + */ +static int exynos_ss_udc_process_set_sel(struct exynos_ss_udc *udc) +{ + int ret; + + dev_dbg(udc->dev, "%s\n", __func__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + ret = exynos_ss_udc_enqueue_data(udc, udc->ep0_buff, + EXYNOS_USB3_EP0_BUFF_SIZE, + exynos_ss_udc_complete_set_sel); + if (ret < 0) { + dev_err(udc->dev, "%s: failed to become ready for SEL data\n", + __func__); + return ret; + } + + return 1; +} + +/** + * exynos_ss_udc_process_set_isoch_delay - process request SET_ISOCH_DELAY + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_isoch_delay(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + u16 isoch_delay; + int ret = 1; + + if (ctrl->wIndex || ctrl->wLength) { + ret = -EINVAL; + goto err; + } + + switch (udc->state) { + case USB_STATE_DEFAULT: + /* FALLTHROUGH */ + case USB_STATE_ADDRESS: + /* FALLTHROUGH */ + case USB_STATE_CONFIGURED: + isoch_delay = le16_to_cpu(ctrl->wValue); + /* REVISIT don't know what to do with this value */ + break; + default: + ret = -EINVAL; + break; + } + +err: + return ret; +} + +/** + * exynos_ss_udc_set_test_mode - set TEST_MODE feature + * @udc: The device state. + * @wIndex: The request wIndex field. + */ +static int exynos_ss_udc_set_test_mode(struct exynos_ss_udc *udc, + u16 wIndex) +{ + u8 selector = wIndex >> 8; + char *mode; + u32 reg; + int ret = 0; + + switch (selector) { + case TEST_J: + mode = "TEST J"; + break; + case TEST_K: + mode = "TEST K"; + break; + case TEST_SE0_NAK: + mode = "TEST SE0 NAK"; + break; + case TEST_PACKET: + mode = "TEST PACKET"; + break; + case TEST_FORCE_EN: + mode = "TEST FORCE EN"; + break; + default: + mode = "unknown"; + ret = -EINVAL; + break; + } + + dev_info(udc->dev, "Test mode selector is %s\n", mode); + + if (ret == 0) { + reg = readl(udc->regs + EXYNOS_USB3_DCTL) & + ~EXYNOS_USB3_DCTL_TstCtl_MASK; + + reg |= EXYNOS_USB3_DCTL_TstCtl(selector); + + writel(reg, udc->regs + EXYNOS_USB3_DCTL); + } + + return ret; +} + +/** + * exynos_ss_udc_process_clr_feature - process request CLEAR_FEATURE + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_clr_feature(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *udc_ep; + struct exynos_ss_udc_req *udc_req; + int ret; + bool restart; + u16 wValue; + u16 wIndex; + u8 recip; + + dev_dbg(udc->dev, "%s\n", __func__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_U1_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU1Ena); + break; + + case USB_DEVICE_U2_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + __bic32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU2Ena); + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, wIndex); + if (!udc_ep) { + dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n", + __func__, wIndex); + return -ENOENT; + } + + if (udc->state == USB_STATE_ADDRESS && + udc_ep->epnum != 0) + return -EINVAL; + + switch (wValue) { + case USB_ENDPOINT_HALT: + if (!udc_ep->wedged) { + ret = exynos_ss_udc_ep_sethalt(&udc_ep->ep, 0); + if (ret < 0) + return ret; + + /* If we have pending request, then start it */ + restart = !list_empty(&udc_ep->queue); + if (restart) { + udc_req = get_ep_head(udc_ep); + exynos_ss_udc_start_req(udc, udc_ep, + udc_req, false); + } + } + break; + + default: + return -ENOENT; + } + + break; + + default: + return -ENOENT; + } + + return 1; +} + +/** + * exynos_ss_udc_process_set_feature - process request SET_FEATURE + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_feature(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *udc_ep; + int ret; + u16 wValue; + u16 wIndex; + u8 recip; + + dev_dbg(udc->dev, "%s\n", __func__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + + switch (recip) { + case USB_RECIP_DEVICE: + switch (wValue) { + case USB_DEVICE_TEST_MODE: + if (wIndex & 0xff) + return -EINVAL; + + ret = exynos_ss_udc_set_test_mode(udc, wIndex); + if (ret < 0) + return ret; + break; + case USB_DEVICE_U1_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* + * Enable U1 entry only for DWC3 revisions > 1.85a, + * since earlier revisions have a bug + */ + if (udc->release > 0x185a) + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU1Ena); + break; + + case USB_DEVICE_U2_ENABLE: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* + * Enable U2 entry only for DWC3 revisions > 1.85a, + * since earlier revisions have a bug + */ + if (udc->release > 0x185a) + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_InitU2Ena); + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_INTERFACE: + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + if (udc->gadget.speed != USB_SPEED_SUPER || + udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* + * Currently, there is no functions supporting + * FUNCTION_SUSPEND feature. Moreover, if a function + * doesn't support the feature (true for all), composite + * driver returns error on 'setup' call. This causes + * Command Verifier test to fail. To fix it, will use + * dummy handler instead. + */ + + break; + + default: + return -ENOENT; + } + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, wIndex); + if (!udc_ep) { + dev_dbg(udc->dev, "%s: no endpoint for 0x%04x\n", + __func__, wIndex); + return -ENOENT; + } + + if (udc->state == USB_STATE_ADDRESS && + udc_ep->epnum != 0) + return -EINVAL; + + switch (wValue) { + case USB_ENDPOINT_HALT: + ret = exynos_ss_udc_ep_sethalt(&udc_ep->ep, 1); + if (ret < 0) + return ret; + break; + + default: + return -ENOENT; + } + + break; + + default: + return -ENOENT; + } + + return 1; +} + +/** + * exynos_ss_udc_process_get_status - process request GET_STATUS + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_get_status(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *udc_ep0 = &udc->eps[0]; + struct exynos_ss_udc_ep *udc_ep; + u8 *reply = udc->ep0_buff; + u32 reg; + int ret; + + dev_dbg(udc->dev, "%s: USB_REQ_GET_STATUS\n", __func__); + + if (udc->state != USB_STATE_ADDRESS && + udc->state != USB_STATE_CONFIGURED) + return -EINVAL; + + if (!udc_ep0->dir_in) { + dev_warn(udc->dev, "%s: direction out?\n", __func__); + return -EINVAL; + } + + if (le16_to_cpu(ctrl->wLength) != 2) + return -EINVAL; + + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + *reply = 1; + if (udc->gadget.speed == USB_SPEED_SUPER) { + reg = readl(udc->regs + EXYNOS_USB3_DCTL); + + if (reg & EXYNOS_USB3_DCTL_InitU1Ena) + *reply |= 1 << 2; + + if (reg & EXYNOS_USB3_DCTL_InitU2Ena) + *reply |= 1 << 3; + } + *(reply + 1) = 0; + break; + + case USB_RECIP_INTERFACE: + if (udc->state == USB_STATE_ADDRESS) + return -EINVAL; + + /* currently, the data result should be zero */ + *reply = 0; + *(reply + 1) = 0; + break; + + case USB_RECIP_ENDPOINT: + udc_ep = ep_from_windex(udc, le16_to_cpu(ctrl->wIndex)); + if (!udc_ep) + return -ENOENT; + + if (udc->state == USB_STATE_ADDRESS && + udc_ep->epnum != 0) + return -EINVAL; + + *reply = udc_ep->halted ? 1 : 0; + *(reply + 1) = 0; + break; + + default: + return 0; + } + + ret = exynos_ss_udc_enqueue_data(udc, reply, 2, NULL); + if (ret) { + dev_err(udc->dev, "%s: failed to send reply\n", __func__); + return ret; + } + + return 1; +} + +/** + * exynos_ss_udc_process_set_address - process request SET_CONFIGURATION + * @udc: The device state. + * @ctrl: The USB control request. + */ +static int exynos_ss_udc_process_set_address(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + int ret = 1; + u16 address = le16_to_cpu(ctrl->wValue); + + dev_dbg(udc->dev, "%s\n", __func__); + + switch (udc->state) { + case USB_STATE_DEFAULT: + if (address) + udc->state = USB_STATE_ADDRESS; + break; + + case USB_STATE_ADDRESS: + if (!address) + udc->state = USB_STATE_DEFAULT; + break; + + case USB_STATE_CONFIGURED: + /* FALLTHROUGH */ + default: + ret = -EINVAL; + break; + } + + if (ret == 1) { + __bic32(udc->regs + EXYNOS_USB3_DCFG, + EXYNOS_USB3_DCFG_DevAddr_MASK); + __orr32(udc->regs + EXYNOS_USB3_DCFG, + EXYNOS_USB3_DCFG_DevAddr(address)); + dev_dbg(udc->dev, "new address %d\n", address); + } + + return ret; +} + +/** + * exynos_ss_udc_process_control - process a control request + * @udc: The device state. + * @ctrl: The control request received. + * + * The controller has received the SETUP phase of a control request, and + * needs to work out what to do next (and whether to pass it on to the + * gadget driver). + */ +static void exynos_ss_udc_process_control(struct exynos_ss_udc *udc, + struct usb_ctrlrequest *ctrl) +{ + struct exynos_ss_udc_ep *ep0 = &udc->eps[0]; + int ret = 0; + + dev_dbg(udc->dev, "ctrl Req=%02x, Type=%02x, V=%04x, L=%04x\n", + ctrl->bRequest, ctrl->bRequestType, + ctrl->wValue, ctrl->wLength); + + /* record the direction of the request, for later use when enquing + * packets onto EP0. */ + + ep0->dir_in = (ctrl->bRequestType & USB_DIR_IN) ? 1 : 0; + dev_dbg(udc->dev, "ctrl: dir_in=%d\n", ep0->dir_in); + + /* if we've no data with this request, then the last part of the + * transaction is going to implicitly be IN. */ + if (ctrl->wLength == 0) { + ep0->dir_in = 1; + udc->ep0_three_stage = 0; + udc->ep0_state = EP0_WAIT_NRDY; + } else + udc->ep0_three_stage = 1; + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) { + switch (ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + ret = exynos_ss_udc_process_set_address(udc, ctrl); + break; + + case USB_REQ_GET_STATUS: + ret = exynos_ss_udc_process_get_status(udc, ctrl); + break; + + case USB_REQ_CLEAR_FEATURE: + ret = exynos_ss_udc_process_clr_feature(udc, ctrl); + break; + + case USB_REQ_SET_FEATURE: + ret = exynos_ss_udc_process_set_feature(udc, ctrl); + break; + + case USB_REQ_SET_SEL: + ret = exynos_ss_udc_process_set_sel(udc); + break; + case USB_REQ_SET_ISOCH_DELAY: + ret = exynos_ss_udc_process_set_isoch_delay(udc, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + ret = exynos_ss_udc_process_set_config(udc, ctrl); + break; + } + } + + /* as a fallback, try delivering it to the driver to deal with */ + + if (ret == 0 && udc->driver) { + if (udc->ep0_three_stage == 0) + udc->ep0_state = EP0_STATUS_PHASE; + + ret = udc->driver->setup(&udc->gadget, ctrl); + if (ret < 0) + dev_dbg(udc->dev, "driver->setup() ret %d\n", ret); + } + + /* the request is either unhandlable, or is not formatted correctly + * so respond with a STALL for the status stage to indicate failure. + */ + + if (ret < 0) { + dev_dbg(udc->dev, "ep0 stall (dir=%d)\n", ep0->dir_in); + exynos_ss_udc_ep0_restart(udc); + } +} + +/** + * exynos_ss_udc_complete_setup - completion of a setup transfer + * @ep: The endpoint the request was on. + * @req: The request completed. + * + * Called on completion of any requests the driver itself submitted for + * EP0 setup packets. + */ +static void exynos_ss_udc_complete_setup(struct usb_ep *ep, + struct usb_request *req) +{ + struct exynos_ss_udc_ep *udc_ep = our_ep(ep); + struct exynos_ss_udc *udc = udc_ep->parent; + + if (req->status < 0) { + dev_dbg(udc->dev, "%s: failed %d\n", __func__, req->status); + return; + } + + exynos_ss_udc_process_control(udc, req->buf); +} + +/** + * exynos_ss_udc_kill_all_requests - remove all requests from the endpoint's queue + * @udc: The device state. + * @ep: The endpoint the requests may be on. + * @result: The result code to use. + * + * Go through the requests on the given endpoint and mark them + * completed with the given result code. + */ +static void exynos_ss_udc_kill_all_requests(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + int result) +{ + struct exynos_ss_udc_req *udc_req, *treq; + unsigned long flags; + + dev_dbg(udc->dev, "%s: ep%d\n", __func__, udc_ep->epnum); + + spin_lock_irqsave(&udc_ep->lock, flags); + + list_for_each_entry_safe(udc_req, treq, &udc_ep->queue, queue) { + + exynos_ss_udc_complete_request(udc, udc_ep, udc_req, result); + } + + spin_unlock_irqrestore(&udc_ep->lock, flags); +} + +/** + * exynos_ss_udc_complete_request - complete a request given to us + * @udc: The device state. + * @udc_ep: The endpoint the request was on. + * @udc_req: The request being completed. + * @result: The result code (0 => Ok, otherwise errno) + * + * The given request has finished, so call the necessary completion + * if it has one and then look to see if we can start a new request + * on the endpoint. + * + * Note, expects the ep to already be locked as appropriate. + */ +static void exynos_ss_udc_complete_request(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + int result) +{ + bool restart; + + if (!udc_req) { + dev_dbg(udc->dev, "%s: nothing to complete\n", __func__); + return; + } + + dev_dbg(udc->dev, "complete: ep %p %s, req %p, %d => %p\n", + udc_ep, udc_ep->ep.name, udc_req, + result, udc_req->req.complete); + + /* only replace the status if we've not already set an error + * from a previous transaction */ + + if (udc_req->req.status == -EINPROGRESS) + udc_req->req.status = result; + + udc_ep->req = NULL; + udc_ep->tri = 0; + list_del_init(&udc_req->queue); + + if (udc_req->req.buf != udc->ctrl_buff && + udc_req->req.buf != udc->ep0_buff) + exynos_ss_udc_unmap_dma(udc, udc_ep, udc_req); + + /* call the complete request with the locks off, just in case the + * request tries to queue more work for this endpoint. */ + + if (udc_req->req.complete) { + spin_unlock(&udc_ep->lock); + udc_req->req.complete(&udc_ep->ep, &udc_req->req); + spin_lock(&udc_ep->lock); + } + + /* Look to see if there is anything else to do. Note, the completion + * of the previous request may have caused a new request to be started + * so be careful when doing this. */ + + if (!udc_ep->req && result >= 0) { + restart = !list_empty(&udc_ep->queue); + if (restart) { + udc_req = get_ep_head(udc_ep); + exynos_ss_udc_start_req(udc, udc_ep, udc_req, false); + } + } +} + +/** + * exynos_ss_udc_complete_request_lock - complete a request given to us (locked) + * @udc: The device state. + * @udc_ep: The endpoint the request was on. + * @udc_req: The request being completed. + * @result: The result code (0 => Ok, otherwise errno). + * + * See exynos_ss_udc_complete_request(), but called with the endpoint's + * lock held. + */ +static void exynos_ss_udc_complete_request_lock(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + struct exynos_ss_udc_req *udc_req, + int result) +{ + unsigned long flags; + + spin_lock_irqsave(&udc_ep->lock, flags); + exynos_ss_udc_complete_request(udc, udc_ep, udc_req, result); + spin_unlock_irqrestore(&udc_ep->lock, flags); +} + +/** + * exynos_ss_udc_ep0_restart - stall and restart EP0 + * @udc: The device state. + * + * Stall EP0 and restart control transfer state machine. + */ +static void exynos_ss_udc_ep0_restart(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep *ep0 = &udc->eps[0]; + + exynos_ss_udc_ep_sethalt(&ep0->ep, 1); + exynos_ss_udc_kill_all_requests(udc, ep0, -ECONNRESET); + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); +} + +/** + * exynos_ss_udc_ep_cmd_complete - process event EP Command Complete + * @udc: The device state. + * @udc_ep: The endpoint this event is for. + * @event: The event being handled. + */ +static void exynos_ss_udc_ep_cmd_complete(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + u32 event) +{ + struct exynos_ss_udc_ep_command *epcmd, *tepcmd; + struct exynos_ss_udc_req *udc_req; + int epnum; + int res; + bool restart; + + dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, udc_ep->epnum, + udc_ep->dir_in ? "in" : "out"); + + /* We use IOC _only_ for End Transfer command currently */ + + udc_ep->not_ready = 0; + + /* Issue all pending commands for endpoint */ + list_for_each_entry_safe(epcmd, tepcmd, + &udc_ep->cmd_queue, queue) { + + dev_dbg(udc->dev, "Pending command %02xh for ep%d%s\n", + epcmd->cmdtyp, epnum, + udc_ep->dir_in ? "in" : "out"); + + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to issue command\n"); + + list_del_init(&epcmd->queue); + kfree(epcmd); + } + + /* If we have pending request, then start it */ + restart = !list_empty(&udc_ep->queue); + if (restart) { + udc_req = get_ep_head(udc_ep); + exynos_ss_udc_start_req(udc, udc_ep, + udc_req, false); + } +} + +/** + * exynos_ss_udc_xfer_complete - complete transfer + * @udc: The device state. + * @udc_ep: The endpoint that has just completed. + * @event: The event being handled. + * + * Transfer has been completed, update the transfer's state and then + * call the relevant completion routines. + */ +static void exynos_ss_udc_xfer_complete(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + u32 event) +{ + struct exynos_ss_udc_req *udc_req = udc_ep->req; + struct usb_request *req = &udc_req->req; + int size_left; + int result = 0; + + dev_dbg(udc->dev, "%s: ep%d%s, req %p\n", + __func__, udc_ep->epnum, + udc_ep->dir_in ? "in" : "out", req); + + if (!udc_req) { + dev_dbg(udc->dev, "XferCompl but no req\n"); + return; + } + + if (event & EXYNOS_USB3_DEPEVT_EventStatus_BUSERR) { + dev_err(udc->dev, "%s: Bus Error occured\n", __func__); + result = -ECONNRESET; + } + + if (udc_ep->trb->param2 & EXYNOS_USB3_TRB_HWO) + dev_err(udc->dev, "%s: HWO bit set\n", __func__); + + size_left = udc_ep->trb->param1 & EXYNOS_USB3_TRB_BUFSIZ_MASK; + + if (udc_ep->dir_in) { + /* Incomplete IN transfer */ + if (size_left) { + dev_err(udc->dev, "%s: BUFSIZ is not zero (%d)", + __func__, size_left); + /* REVISIT shall we -ECONNRESET here? */ + } + + udc_req->req.actual = udc_req->req.length - size_left; + } else { + int len; + + len = (req->length + udc_ep->ep.maxpacket - 1) & + ~(udc_ep->ep.maxpacket - 1); + udc_req->req.actual = len - size_left; + } + + if (udc_ep->epnum == 0) { + switch (udc->ep0_state) { + case EP0_SETUP_PHASE: + udc->ep0_state = EP0_DATA_PHASE; + break; + case EP0_DATA_PHASE: + udc->ep0_state = EP0_WAIT_NRDY; + break; + case EP0_STATUS_PHASE: + udc->ep0_state = EP0_SETUP_PHASE; + break; + default: + dev_err(udc->dev, "%s: Erroneous EP0 state (%d)", + __func__, udc->ep0_state); + /* Will try to repair from it */ + udc->ep0_state = EP0_SETUP_PHASE; + return; + break; + } + } + + exynos_ss_udc_complete_request_lock(udc, udc_ep, udc_req, result); +} + +/** + * exynos_ss_udc_xfer_notready - process event Transfer Not Ready + * @udc: The device state. + * @udc_ep: The endpoint this event is for. + * @event: The event being handled. + */ +static void exynos_ss_udc_xfer_notready(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + u32 event) +{ + int index = (event & EXYNOS_USB3_DEPEVT_EPNUM_MASK) >> 1; + int direction = index & 1; + u32 status = event & EXYNOS_USB3_DEPEVT_EventStatus_CTL_MASK; + + dev_dbg(udc->dev, "%s: ep%d%s\n", __func__, udc_ep->epnum, + direction ? "in" : "out"); + + if (udc_ep->epnum == 0) { + switch (udc->ep0_state) { + case EP0_SETUP_PHASE: + /* + * Check if host is attempting to move data or start + * the status stage for a previous control transfer. + */ + if (status != + EXYNOS_USB3_DEPEVT_EventStatus_CTL_SETUP) { + + dev_dbg(udc->dev, "Unexpected XferNotReady " + "during EP0 Setup phase\n"); + + exynos_ss_udc_ep0_restart(udc); + return; + } + + break; + + case EP0_DATA_PHASE: + /* + * Check if host is attempting to move data in the + * wrong direction. + */ + if (udc_ep->dir_in != direction && + status == EXYNOS_USB3_DEPEVT_EventStatus_CTL_DATA) { + + dev_dbg(udc->dev, "Unexpected XferNotReady " + "during EP0 Data phase\n"); + + exynos_ss_udc_ep0_restart(udc); + return; + } + + break; + + case EP0_WAIT_NRDY: + /* + * Check if host is attempting to start the data stage + * when data stage is not present or move more data + * than specified in the wLength field. + */ + if (status == EXYNOS_USB3_DEPEVT_EventStatus_CTL_DATA) { + + dev_dbg(udc->dev, "Unexpected XferNotReady " + "during EP0 Wait NotReady\n"); + + exynos_ss_udc_ep0_restart(udc); + return; + } + + udc_ep->dir_in = direction; + udc->ep0_state = EP0_STATUS_PHASE; + exynos_ss_udc_enqueue_status(udc); + break; + + case EP0_STATUS_PHASE: + /* FALLTHROUGH */ + default: + dev_dbg(udc->dev, "Unexpected XferNotReady\n"); + break; + } + } +} + +/** + * exynos_ss_udc_irq_connectdone - process event Connection Done + * @udc: The device state. + * + * Get the speed of connection and configure physical endpoints 0 & 1. + */ +static void exynos_ss_udc_irq_connectdone(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep_command epcmd = {{0}, }; + u32 reg, speed; + int mps0, mps; + int i; + int res; + + dev_dbg(udc->dev, "%s\n", __func__); + + reg = readl(udc->regs + EXYNOS_USB3_DSTS); + speed = reg & EXYNOS_USB3_DSTS_ConnectSpd_MASK; + + switch (speed) { + /* High-speed */ + case 0: + udc->gadget.speed = USB_SPEED_HIGH; + mps0 = EP0_HS_MPS; + mps = EP_HS_MPS; + break; + /* Full-speed */ + case 1: + case 3: + udc->gadget.speed = USB_SPEED_FULL; + mps0 = EP0_FS_MPS; + mps = EP_FS_MPS; + break; + /* Low-speed */ + case 2: + udc->gadget.speed = USB_SPEED_LOW; + mps0 = EP0_LS_MPS; + mps = EP_LS_MPS; + break; + /* SuperSpeed */ + case 4: + udc->gadget.speed = USB_SPEED_SUPER; + mps0 = EP0_SS_MPS; + mps = EP_SS_MPS; + break; + + default: + dev_err(udc->dev, "Connection speed is unknown (%d)\n", speed); + return; + } + + /* Suspend the inactive Phy */ + if (udc->gadget.speed == USB_SPEED_SUPER) { + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + + /* Accept U1&U2 transition */ + __orr32(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_AcceptU2Ena | + EXYNOS_USB3_DCTL_AcceptU1Ena); + } else { + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + } + + udc->eps[0].ep.maxpacket = mps0; + for (i = 1; i < EXYNOS_USB3_EPS; i++) + udc->eps[i].ep.maxpacket = mps; + + epcmd.ep = 0; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(mps0); + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP0\n"); + + epcmd.ep = 1; + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_EpDir | + EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP1\n"); +} + +/** + * exynos_ss_udc_irq_usbrst - process event USB Reset + * @udc: The device state. + */ +static void exynos_ss_udc_irq_usbrst(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep_command epcmd = {{0}, }; + struct exynos_ss_udc_ep *ep; + int res; + int epnum; + + dev_dbg(udc->dev, "%s\n", __func__); +#ifdef CONFIG_USB_G_ANDROID + /* + * Android MTP should be configuration after disconnect uevet + * A reset USB device has the following characteristics: + * - Responds to the default USB address + * - Is not configured + * - Is not initially suspended + */ + if (udc->state == USB_STATE_CONFIGURED) + call_gadget(udc, disconnect); +#endif + /* Disable test mode */ + __bic32(udc->regs + EXYNOS_USB3_DCTL, EXYNOS_USB3_DCTL_TstCtl_MASK); + + /* Enable PHYs */ + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER; + + /* End transfer, kill all requests and clear STALL on the + non-EP0 endpoints */ + for (epnum = 1; epnum < EXYNOS_USB3_EPS; epnum++) { + + ep = &udc->eps[epnum]; + + epcmd.ep = get_phys_epnum(ep); + + if (ep->tri) { + epcmd.cmdflags = (ep->tri << + EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) | + EXYNOS_USB3_DEPCMDx_HiPri_ForceRM | + EXYNOS_USB3_DEPCMDx_CmdIOC | + EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) { + dev_err(udc->dev, "Failed to end transfer\n"); + ep->not_ready = 1; + } + + ep->tri = 0; + } + + exynos_ss_udc_kill_all_requests(udc, ep, -ECONNRESET); + + if (ep->halted) + exynos_ss_udc_ep_sethalt(&ep->ep, 0); + } + + /* Set device address to 0 */ + __bic32(udc->regs + EXYNOS_USB3_DCFG, EXYNOS_USB3_DCFG_DevAddr_MASK); + + udc->state = USB_STATE_DEFAULT; +} + +/** + * exynos_ss_udc_irq_ulstchng - process event USB Link State Change + * @udc: The device state. + * @event: The event being handled. + */ +static void exynos_ss_udc_irq_ulstchng(struct exynos_ss_udc *udc, u32 event) +{ + u32 link_state; + + link_state = event & EXYNOS_USB3_DEVT_EvtInfo_MASK; + + if (event & EXYNOS_USB3_DEVT_EvtInfo_SS) { + if (link_state == EXYNOS_USB3_DEVT_EvtInfo_U3) + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + else + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + } else { + if (link_state == EXYNOS_USB3_DEVT_EvtInfo_Suspend) + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + else + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY); + } + + /* Disconnect event detection for SMDK5250 EVT0 */ +#if defined(CONFIG_MACH_SMDK5250) + if (udc->release == 0x185a) { + if (link_state == EXYNOS_USB3_DEVT_EvtInfo_Suspend || + link_state == EXYNOS_USB3_DEVT_EvtInfo_SS_DIS) { + call_gadget(udc, disconnect); + EXYNOS_SS_UDC_CABLE_CONNECT(udc, false); + dev_dbg(udc->dev, "Disconnection (0x%x, %s)\n", + link_state >> EXYNOS_USB3_DEVT_EvtInfo_SHIFT, + event & EXYNOS_USB3_DEVT_EvtInfo_SS ? + "SS" : "non-SS"); + } + } +#endif +} + +/** + * exynos_ss_udc_handle_depevt - handle endpoint-specific event + * @udc: The device state. + * @event: The event being handled. + */ +static void exynos_ss_udc_handle_depevt(struct exynos_ss_udc *udc, u32 event) +{ + int index = (event & EXYNOS_USB3_DEPEVT_EPNUM_MASK) >> 1; + int epnum = get_usb_epnum(index); + struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum]; + + switch (event & EXYNOS_USB3_DEPEVT_EVENT_MASK) { + case EXYNOS_USB3_DEPEVT_EVENT_XferNotReady: + dev_dbg(udc->dev, "Xfer Not Ready\n"); + + exynos_ss_udc_xfer_notready(udc, udc_ep, event); + break; + + case EXYNOS_USB3_DEPEVT_EVENT_XferComplete: + dev_dbg(udc->dev, "Xfer Complete\n"); + + exynos_ss_udc_xfer_complete(udc, udc_ep, event); + + if (epnum == 0 && udc->ep0_state == EP0_SETUP_PHASE) + exynos_ss_udc_enqueue_setup(udc); + + break; + + case EXYNOS_USB3_DEPEVT_EVENT_EPCmdCmplt: + dev_dbg(udc->dev, "EP Cmd Complete\n"); + + exynos_ss_udc_ep_cmd_complete(udc, udc_ep, event); + break; + } +} + +/** + * exynos_ss_udc_handle_devt - handle device-specific event + * @udc: The driver state. + * @event: The event being handled. + */ +static void exynos_ss_udc_handle_devt(struct exynos_ss_udc *udc, u32 event) +{ + switch (event & EXYNOS_USB3_DEVT_EVENT_MASK) { + case EXYNOS_USB3_DEVT_EVENT_ULStChng: + dev_dbg(udc->dev, "USB-Link State Change"); + exynos_ss_udc_irq_ulstchng(udc, event); + break; + + case EXYNOS_USB3_DEVT_EVENT_ConnectDone: +#if defined(USE_WAKE_LOCK) + wake_lock(&udc->usbd_wake_lock); +#endif + dev_dbg(udc->dev, "Connection Done"); + EXYNOS_SS_UDC_CABLE_CONNECT(udc, true); + exynos_ss_udc_irq_connectdone(udc); + break; + + case EXYNOS_USB3_DEVT_EVENT_USBRst: + dev_dbg(udc->dev, "USB Reset"); + exynos_ss_udc_irq_usbrst(udc); + break; + + case EXYNOS_USB3_DEVT_EVENT_DisconnEvt: + dev_info(udc->dev, "Disconnection Detected"); + call_gadget(udc, disconnect); + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->state = USB_STATE_NOTATTACHED; + EXYNOS_SS_UDC_CABLE_CONNECT(udc, false); + +#if defined(USE_WAKE_LOCK) + wake_lock_timeout(&udc->usbd_wake_lock, HZ * 5); +#endif + break; + + default: + break; + } +} + +static void exynos_ss_udc_handle_otgevt(struct exynos_ss_udc *udc, u32 event) +{} + +static void exynos_ss_udc_handle_gevt(struct exynos_ss_udc *udc, u32 event) +{} + +/** + * exynos_ss_udc_irq - handle device interrupt + * @irq: The IRQ number triggered. + * @pw: The pw value when registered the handler. + */ +static irqreturn_t exynos_ss_udc_irq(int irq, void *pw) +{ + struct exynos_ss_udc *udc = pw; + int indx = udc->event_indx; + int gevntcount; + u32 event; + u32 ecode1, ecode2; + + gevntcount = readl(udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)) & + EXYNOS_USB3_GEVNTCOUNTx_EVNTCount_MASK; + /* TODO: what if number of events more then buffer size? */ + + dev_dbg(udc->dev, "INTERRUPT (%d)\n", gevntcount >> 2); + + while (gevntcount) { + event = udc->event_buff[indx++]; + + dev_dbg(udc->dev, "event[%x] = 0x%08x\n", + (unsigned int) &udc->event_buff[indx - 1], event); + + ecode1 = event & 0x01; + + if (ecode1 == 0) + /* Device Endpoint-Specific Event */ + exynos_ss_udc_handle_depevt(udc, event); + else { + ecode2 = (event & 0xfe) >> 1; + + switch (ecode2) { + /* Device-Specific Event */ + case 0x00: + exynos_ss_udc_handle_devt(udc, event); + break; + + /* OTG Event */ + case 0x01: + exynos_ss_udc_handle_otgevt(udc, event); + break; + + /* Other Core Event */ + case 0x03: + case 0x04: + exynos_ss_udc_handle_gevt(udc, event); + break; + + /* Unknown Event Type */ + default: + dev_info(udc->dev, "Unknown event type\n"); + break; + } + } + /* We processed 1 event (4 bytes) */ + writel(4, udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)); + + if (indx > (EXYNOS_USB3_EVENT_BUFF_WSIZE - 1)) + indx = 0; + + gevntcount -= 4; + } + + udc->event_indx = indx; + /* Do we need to read GEVENTCOUNT here and retry? */ + + return IRQ_HANDLED; +} + +/** + * exynos_ss_udc_free_all_trb - free all allocated TRBs + * @udc: The device state. + */ +static void exynos_ss_udc_free_all_trb(struct exynos_ss_udc *udc) +{ + int epnum; + + for (epnum = 0; epnum < EXYNOS_USB3_EPS; epnum++) { + struct exynos_ss_udc_ep *udc_ep = &udc->eps[epnum]; + + if (udc_ep->trb_dma) + dma_free_coherent(NULL, + sizeof(struct exynos_ss_udc_trb), + udc_ep->trb, + udc_ep->trb_dma); + } +} + +/** + * exynos_ss_udc_initep - initialize a single endpoint + * @udc: The device state. + * @udc_ep: The endpoint being initialised. + * @epnum: The endpoint number. + * + * Initialise the given endpoint (as part of the probe and device state + * creation) to give to the gadget driver. Setup the endpoint name, any + * direction information and other state that may be required. + */ +static int __devinit exynos_ss_udc_initep(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep, + int epnum) +{ + char *dir; + + if (epnum == 0) + dir = ""; + else if ((epnum % 2) == 0) { + dir = "out"; + } else { + dir = "in"; + udc_ep->dir_in = 1; + } + + udc_ep->epnum = epnum; + + snprintf(udc_ep->name, sizeof(udc_ep->name), "ep%d%s", epnum, dir); + + INIT_LIST_HEAD(&udc_ep->queue); + INIT_LIST_HEAD(&udc_ep->cmd_queue); + INIT_LIST_HEAD(&udc_ep->ep.ep_list); + + spin_lock_init(&udc_ep->lock); + + /* add to the list of endpoints known by the gadget driver */ + if (epnum) + list_add_tail(&udc_ep->ep.ep_list, &udc->gadget.ep_list); + + udc_ep->parent = udc; + udc_ep->ep.name = udc_ep->name; +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + udc_ep->ep.maxpacket = epnum ? EP_SS_MPS : EP0_SS_MPS; +#else + udc_ep->ep.maxpacket = epnum ? EP_HS_MPS : EP0_HS_MPS; +#endif + udc_ep->ep.ops = &exynos_ss_udc_ep_ops; + udc_ep->trb = dma_alloc_coherent(NULL, + sizeof(struct exynos_ss_udc_trb), + &udc_ep->trb_dma, + GFP_KERNEL); + if (!udc_ep->trb) + return -ENOMEM; + + memset(udc_ep->trb, 0, sizeof(struct exynos_ss_udc_trb)); + + if (epnum == 0) { + /* allocate EP0 control request */ + udc->ctrl_req = exynos_ss_udc_ep_alloc_request(&udc->eps[0].ep, + GFP_KERNEL); + if (!udc->ctrl_req) + return -ENOMEM; + + udc->ep0_state = EP0_UNCONNECTED; + } + + return 0; +} + +/** + * exynos_ss_udc_phy_set - intitialize the controller PHY interface + * @pdev: The platform-level device instance. + */ +static void exynos_ss_udc_phy_set(struct platform_device *pdev) +{ + struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data; + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + /* The reset values: + * GUSB2PHYCFG(0) = 0x00002400 + * GUSB3PIPECTL(0) = 0x00260002 + */ + + __orr32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset); + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst); + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst); + + /* PHY initialization */ + if (pdata && pdata->phy_init) + pdata->phy_init(pdev, pdata->phy_type); + + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst); + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst); + __bic32(udc->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset); + + + __bic32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY | + EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM | + EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim_MASK); + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim(9)); + + __bic32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + dev_dbg(udc->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x", + readl(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0)), + readl(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0))); +} + +/** + * exynos_ss_udc_phy_unset - disable the controller PHY interface + * @pdev: The platform-level device instance. + */ +static void exynos_ss_udc_phy_unset(struct platform_device *pdev) +{ + struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data; + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + + __orr32(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY | + EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM); + __orr32(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, pdata->phy_type); + + dev_dbg(udc->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x", + readl(udc->regs + EXYNOS_USB3_GUSB2PHYCFG(0)), + readl(udc->regs + EXYNOS_USB3_GUSB3PIPECTL(0))); +} + +/** + * exynos_ss_udc_corereset - issue softreset to the core + * @udc: The device state. + * + * Issue a soft reset to the core, and await the core finishing it. + */ +static int exynos_ss_udc_corereset(struct exynos_ss_udc *udc) +{ + int res; + + /* issue soft reset */ + __orr32(udc->regs + EXYNOS_USB3_DCTL, EXYNOS_USB3_DCTL_CSftRst); + + res = poll_bit_clear(udc->regs + EXYNOS_USB3_DCTL, + EXYNOS_USB3_DCTL_CSftRst, + 1000); + if (res < 0) + dev_err(udc->dev, "Failed to get CSftRst asserted\n"); + + return res; +} + +/** + * exynos_ss_udc_ep0_activate - activate USB endpoint 0 + * @udc: The device state. + * + * Configure physical endpoints 0 & 1. + */ +static void exynos_ss_udc_ep0_activate(struct exynos_ss_udc *udc) +{ + struct exynos_ss_udc_ep_command epcmd = {{0}, }; + int res; + + /* Start New Configuration */ + epcmd.ep = 0; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to start new configuration\n"); + + /* Configure Physical Endpoint 0 */ + epcmd.ep = 0; +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS); +#else + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_HS_MPS); +#endif + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP0\n"); + + /* Configure Physical Endpoint 1 */ + epcmd.ep = 1; +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_SS_MPS); +#else + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_MPS(EP0_HS_MPS); +#endif + epcmd.param1 = EXYNOS_USB3_DEPCMDPAR1x_EpDir | + EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP1\n"); + + /* Configure Pysical Endpoint 0 Transfer Resource */ + epcmd.ep = 0; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1); + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, + "Failed to configure physical EP0 transfer resource\n"); + + /* Configure Pysical Endpoint 1 Transfer Resource */ + epcmd.ep = 1; + epcmd.param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1); + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG; + epcmd.cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) + dev_err(udc->dev, + "Failed to configure physical EP1 transfer resource\n"); + + /* Enable Physical Endpoints 0 and 1 */ + writel(3, udc->regs + EXYNOS_USB3_DALEPENA); +} + +/** + * exynos_ss_udc_ep_activate - activate USB endpoint + * @udc: The device state. + * @udc_ep: The endpoint being activated. + * + * Configure physical endpoints > 1. + */ +static void exynos_ss_udc_ep_activate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep) +{ + struct exynos_ss_udc_ep_command ep_command; + struct exynos_ss_udc_ep_command *epcmd = &ep_command; + int epnum = udc_ep->epnum; + int maxburst = udc_ep->ep.maxburst; + int res; + + if (!udc->eps_enabled) { + udc->eps_enabled = true; + + /* Start New Configuration */ + epcmd->ep = 0; + epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG; + epcmd->cmdflags = + (2 << EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) | + EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to start new configuration\n"); + } + + if (udc_ep->not_ready) { + epcmd = kzalloc(sizeof(struct exynos_ss_udc_ep_command), + GFP_ATOMIC); + if (!epcmd) { + /* Will try to issue command immediately */ + epcmd = &ep_command; + udc_ep->not_ready = 0; + } + } + + epcmd->ep = get_phys_epnum(udc_ep); + epcmd->param0 = EXYNOS_USB3_DEPCMDPAR0x_EPType(udc_ep->type) | + EXYNOS_USB3_DEPCMDPAR0x_MPS(udc_ep->ep.maxpacket) | + EXYNOS_USB3_DEPCMDPAR0x_BrstSiz(maxburst); + if (udc_ep->dir_in) + epcmd->param0 |= EXYNOS_USB3_DEPCMDPAR0x_FIFONum(epnum); + epcmd->param1 = EXYNOS_USB3_DEPCMDPAR1x_EpNum(epnum) | + (udc_ep->dir_in ? EXYNOS_USB3_DEPCMDPAR1x_EpDir : 0) | + EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn | + EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn; + epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG; + epcmd->cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + if (udc_ep->not_ready) + list_add_tail(&epcmd->queue, &udc_ep->cmd_queue); + else { + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP\n"); + } + + /* Configure Pysical Endpoint Transfer Resource */ + if (udc_ep->not_ready) { + epcmd = kzalloc(sizeof(struct exynos_ss_udc_ep_command), + GFP_ATOMIC); + if (!epcmd) { + epcmd = &ep_command; + udc_ep->not_ready = 0; + } + } + + epcmd->ep = get_phys_epnum(udc_ep); + epcmd->param0 = EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(1); + epcmd->cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG; + epcmd->cmdflags = EXYNOS_USB3_DEPCMDx_CmdAct; + + if (udc_ep->not_ready) + list_add_tail(&epcmd->queue, &udc_ep->cmd_queue); + else { + res = exynos_ss_udc_issue_epcmd(udc, epcmd); + if (res < 0) + dev_err(udc->dev, "Failed to configure physical EP " + "transfer resource\n"); + } + + /* Enable Physical Endpoint */ + __orr32(udc->regs + EXYNOS_USB3_DALEPENA, 1 << epcmd->ep); +} + +/** + * exynos_ss_udc_ep_deactivate - deactivate USB endpoint + * @udc: The device state. + * @udc_ep: The endpoint being deactivated. + * + * End any active transfer and disable endpoint. + */ +static void exynos_ss_udc_ep_deactivate(struct exynos_ss_udc *udc, + struct exynos_ss_udc_ep *udc_ep) +{ + struct exynos_ss_udc_ep_command epcmd = {{0}, }; + int index = get_phys_epnum(udc_ep); + + udc->eps_enabled = false; + + if (udc_ep->tri && !udc_ep->not_ready) { + int res; + + epcmd.ep = get_phys_epnum(udc_ep); + epcmd.cmdtyp = EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER; + epcmd.cmdflags = (udc_ep->tri << + EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT) | + EXYNOS_USB3_DEPCMDx_HiPri_ForceRM | + EXYNOS_USB3_DEPCMDx_CmdIOC | + EXYNOS_USB3_DEPCMDx_CmdAct; + + res = exynos_ss_udc_issue_epcmd(udc, &epcmd); + if (res < 0) { + dev_err(udc->dev, "Failed to end transfer\n"); + udc_ep->not_ready = 1; + } + + udc_ep->tri = 0; + } + + __bic32(udc->regs + EXYNOS_USB3_DALEPENA, 1 << index); +} + +/** + * exynos_ss_udc_init - initialize the controller + * @udc: The device state. + * + * Initialize the event buffer, enable events, activate USB EP0, + * and start the controller operation. + */ +static void exynos_ss_udc_init(struct exynos_ss_udc *udc) +{ + u32 reg; + + reg = readl(udc->regs + EXYNOS_USB3_GSNPSID); + udc->release = reg & 0xffff; + /* + * WORKAROUND: core revision 1.80a has a wrong release number + * in GSNPSID register + */ + if (udc->release == 0x131a) + udc->release = 0x180a; + dev_info(udc->dev, "Core ID Number: 0x%04x\n", reg >> 16); + dev_info(udc->dev, "Release Number: 0x%04x\n", udc->release); + + /* + * WORKAROUND: DWC3 revisions <1.90a have a bug + * when The device fails to connect at SuperSpeed + * and falls back to high-speed mode which causes + * the device to enter in a Connect/Disconnect loop + */ + if (udc->release < 0x190a) + __orr32(udc->regs + EXYNOS_USB3_GCTL, + EXYNOS_USB3_GCTL_U2RSTECN); + + writel(EXYNOS_USB3_GSBUSCFG0_INCR16BrstEna, + udc->regs + EXYNOS_USB3_GSBUSCFG0); + writel(EXYNOS_USB3_GSBUSCFG1_BREQLIMIT(3), + udc->regs + EXYNOS_USB3_GSBUSCFG1); + + /* Event buffer */ + udc->event_indx = 0; + writel(0, udc->regs + EXYNOS_USB3_GEVNTADR_63_32(0)); + writel(udc->event_buff_dma, + udc->regs + EXYNOS_USB3_GEVNTADR_31_0(0)); + /* Set Event Buffer size */ + writel(EXYNOS_USB3_EVENT_BUFF_BSIZE, + udc->regs + EXYNOS_USB3_GEVNTSIZ(0)); + + writel(EXYNOS_USB3_DCFG_NumP(1) | EXYNOS_USB3_DCFG_PerFrInt(2) | +#if defined(CONFIG_USB_GADGET_EXYNOS_SS_UDC_SSMODE) + EXYNOS_USB3_DCFG_DevSpd(4), +#else + EXYNOS_USB3_DCFG_DevSpd(0), +#endif + udc->regs + EXYNOS_USB3_DCFG); + + /* Flush any pending events */ + __orr32(udc->regs + EXYNOS_USB3_GEVNTSIZ(0), + EXYNOS_USB3_GEVNTSIZx_EvntIntMask); + + reg = readl(udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)); + writel(reg, udc->regs + EXYNOS_USB3_GEVNTCOUNT(0)); + + __bic32(udc->regs + EXYNOS_USB3_GEVNTSIZ(0), + EXYNOS_USB3_GEVNTSIZx_EvntIntMask); + + /* Enable events */ + writel(EXYNOS_USB3_DEVTEN_ULStCngEn | EXYNOS_USB3_DEVTEN_ConnectDoneEn | + EXYNOS_USB3_DEVTEN_USBRstEn | EXYNOS_USB3_DEVTEN_DisconnEvtEn, + udc->regs + EXYNOS_USB3_DEVTEN); + + exynos_ss_udc_ep0_activate(udc); +} + +static int exynos_ss_udc_enable(struct exynos_ss_udc *udc) +{ + struct platform_device *pdev = to_platform_device(udc->dev); + + enable_irq(udc->irq); + clk_enable(udc->clk); + + exynos_ss_udc_phy_set(pdev); + exynos_ss_udc_corereset(udc); + exynos_ss_udc_init(udc); + + udc->eps[0].enabled = 1; + udc->ep0_state = EP0_SETUP_PHASE; + exynos_ss_udc_enqueue_setup(udc); + + /* Start the device controller operation */ + exynos_ss_udc_run_stop(udc, 1); + + return 0; +} + +#ifdef CONFIG_USB_EXYNOS_SWITCH +#define EXYNOS_SS_UDC_ENABLE(udc) +#else +#define EXYNOS_SS_UDC_ENABLE(udc) exynos_ss_udc_enable(udc) +#endif + +static int exynos_ss_udc_disable(struct exynos_ss_udc *udc) +{ + struct platform_device *pdev = to_platform_device(udc->dev); + int ep; + + exynos_ss_udc_run_stop(udc, 0); + /* all endpoints should be shutdown */ + for (ep = 0; ep < EXYNOS_USB3_EPS; ep++) + exynos_ss_udc_ep_disable(&udc->eps[ep].ep); + + call_gadget(udc, disconnect); + udc->gadget.speed = USB_SPEED_UNKNOWN; + + exynos_ss_udc_phy_unset(pdev); + + clk_disable(udc->clk); + + disable_irq(udc->irq); + + EXYNOS_SS_UDC_CABLE_CONNECT(udc, false); + + return 0; +} + +#ifdef CONFIG_USB_EXYNOS_SWITCH +#define EXYNOS_SS_UDC_DISABLE(udc) +#else +#define EXYNOS_SS_UDC_DISABLE(udc) exynos_ss_udc_disable(udc) +#endif + +/** + * exynos_ss_udc_vbus_session - software-controlled vbus active/in-active + * @gadget: The peripheral being vbus active/in-active. + * @is_active: The action to take (1 - vbus enable, 0 - vbus disable). + */ +static int exynos_ss_udc_vbus_session(struct usb_gadget *gadget, int is_active) +{ + struct exynos_ss_udc *udc = container_of(gadget, + struct exynos_ss_udc, gadget); + + if (!is_active) + exynos_ss_udc_disable(udc); + else + exynos_ss_udc_enable(udc); + + return 0; +} + +/** + * exynos_ss_udc_pullup - software-controlled connect/disconnect to USB host + * @gadget: The peripheral being connected/disconnected. + * @is_on: The action to take (1 - connect, 0 - disconnect). + */ +static int exynos_ss_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + struct exynos_ss_udc *udc = container_of(gadget, + struct exynos_ss_udc, gadget); + + exynos_ss_udc_run_stop(udc, is_on); + + return 0; +} + +/** + * exynos_ss_udc_get_config_params - get UDC configuration + * @params: The controller parameters being returned to the caller. + */ +void exynos_ss_udc_get_config_params(struct usb_dcd_config_params *params) +{ + params->bU1devExitLat = EXYNOS_USB3_U1_DEV_EXIT_LAT; + params->bU2DevExitLat = cpu_to_le16(EXYNOS_USB3_U2_DEV_EXIT_LAT); +} + +static struct usb_gadget_ops exynos_ss_udc_gadget_ops = { + .vbus_session = exynos_ss_udc_vbus_session, + .pullup = exynos_ss_udc_pullup, + .get_config_params = exynos_ss_udc_get_config_params, +}; + +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct exynos_ss_udc *udc = our_udc; + int ret; + + if (!udc) { + printk(KERN_ERR "%s: called with no device\n", __func__); + return -ENODEV; + } + + if (!driver) { + dev_err(udc->dev, "%s: no driver\n", __func__); + return -EINVAL; + } + + if (driver->speed < USB_SPEED_FULL) { + dev_err(udc->dev, "%s: bad speed\n", __func__); + return -EINVAL; + } + + if (!bind || !driver->setup) { + dev_err(udc->dev, "%s: missing entry points\n", __func__); + return -EINVAL; + } + + WARN_ON(udc->driver); + + driver->driver.bus = NULL; + udc->driver = driver; + udc->gadget.dev.driver = &driver->driver; + udc->gadget.dev.dma_mask = udc->dev->dma_mask; + udc->gadget.speed = USB_SPEED_UNKNOWN; + + ret = device_add(&udc->gadget.dev); + if (ret) { + dev_err(udc->dev, "failed to register gadget device\n"); + goto err; + } + + ret = bind(&udc->gadget); + if (ret) { + dev_err(udc->dev, "failed bind %s\n", driver->driver.name); + + udc->gadget.dev.driver = NULL; + udc->driver = NULL; + goto err; + } + /* we must now enable ep0 ready for host detection and then + * set configuration. */ + EXYNOS_SS_UDC_ENABLE(udc); + /* report to the user, and return */ + dev_info(udc->dev, "bound driver %s\n", driver->driver.name); + return 0; + +err: + udc->driver = NULL; + udc->gadget.dev.driver = NULL; + return ret; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct exynos_ss_udc *udc = our_udc; + + if (!udc) + return -ENODEV; + + if (!driver || driver != udc->driver || !driver->unbind) + return -EINVAL; + + EXYNOS_SS_UDC_DISABLE(udc); + + udc->driver = NULL; + driver->unbind(&udc->gadget); + device_del(&udc->gadget.dev); + + dev_info(udc->dev, "unregistered gadget driver '%s'\n", + driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +static int __devinit exynos_ss_udc_probe(struct platform_device *pdev) +{ + struct exynos_ss_udc_plat *plat = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct exynos_ss_udc *udc; + struct resource *res; + int epnum; + int ret; + + if (!plat) { + dev_err(dev, "cannot get platform data\n"); + return -ENODEV; + } + + udc = kzalloc(sizeof(struct exynos_ss_udc), GFP_KERNEL); + if (!udc) { + dev_err(dev, "cannot get memory\n"); + return -ENOMEM; + } + + udc->dev = dev; + udc->plat = plat; + + udc->event_buff = dma_alloc_coherent(NULL, + EXYNOS_USB3_EVENT_BUFF_BSIZE, + &udc->event_buff_dma, + GFP_KERNEL); + if (!udc->event_buff) { + dev_err(dev, "cannot get memory for event buffer\n"); + ret = -ENOMEM; + goto err_mem; + } + memset(udc->event_buff, 0, EXYNOS_USB3_EVENT_BUFF_BSIZE); + + udc->ctrl_buff = dma_alloc_coherent(NULL, + EXYNOS_USB3_CTRL_BUFF_SIZE, + &udc->ctrl_buff_dma, + GFP_KERNEL); + if (!udc->ctrl_buff) { + dev_err(dev, "cannot get memory for control buffer\n"); + ret = -ENOMEM; + goto err_mem; + } + memset(udc->ctrl_buff, 0, EXYNOS_USB3_CTRL_BUFF_SIZE); + + udc->ep0_buff = dma_alloc_coherent(NULL, + EXYNOS_USB3_EP0_BUFF_SIZE, + &udc->ep0_buff_dma, + GFP_KERNEL); + if (!udc->ep0_buff) { + dev_err(dev, "cannot get memory for EP0 buffer\n"); + ret = -ENOMEM; + goto err_mem; + } + memset(udc->ep0_buff, 0, EXYNOS_USB3_EP0_BUFF_SIZE); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "cannot find register resource 0\n"); + ret = -EINVAL; + goto err_mem; + } + + if (!request_mem_region(res->start, resource_size(res), + dev_name(dev))) { + dev_err(dev, "cannot reserve registers\n"); + ret = -ENOENT; + goto err_mem; + } + + udc->regs = ioremap(res->start, resource_size(res)); + if (!udc->regs) { + dev_err(dev, "cannot map registers\n"); + ret = -ENXIO; + goto err_remap; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(dev, "cannot find irq\n"); + goto err_irq; + } + + udc->irq = ret; + +#if defined(USE_WAKE_LOCK) + wake_lock_init(&udc->usbd_wake_lock, WAKE_LOCK_SUSPEND, + "usb device wake lock"); +#endif + ret = request_irq(udc->irq, + exynos_ss_udc_irq, + IRQF_SHARED, + dev_name(dev), + udc); + if (ret < 0) { + dev_err(dev, "cannot claim IRQ\n"); + goto err_irq; + } + +#ifndef CONFIG_USB_XHCI_EXYNOS + disable_irq(udc->irq); +#endif + dev_info(dev, "regs %p, irq %d\n", udc->regs, udc->irq); + + udc->clk = clk_get(&pdev->dev, "usbdrd30"); + if (IS_ERR(udc->clk)) { + dev_err(dev, "cannot get UDC clock\n"); + ret = -EINVAL; + goto err_clk; + } + + device_initialize(&udc->gadget.dev); + + dev_set_name(&udc->gadget.dev, "gadget"); + + udc->gadget.is_dualspeed = 1; + udc->gadget.ops = &exynos_ss_udc_gadget_ops; + udc->gadget.name = dev_name(dev); + + udc->gadget.dev.parent = dev; + udc->gadget.dev.dma_mask = dev->dma_mask; + + /* setup endpoint information */ + + INIT_LIST_HEAD(&udc->gadget.ep_list); + udc->gadget.ep0 = &udc->eps[0].ep; + + /* initialise the endpoints now the core has been initialised */ + for (epnum = 0; epnum < EXYNOS_USB3_EPS; epnum++) { + ret = exynos_ss_udc_initep(udc, &udc->eps[epnum], epnum); + if (ret < 0) { + dev_err(dev, "cannot get memory for TRB\n"); + goto err_ep; + } + } + + platform_set_drvdata(pdev, udc); + + our_udc = udc; + return 0; + +err_ep: + exynos_ss_udc_ep_free_request(&udc->eps[0].ep, udc->ctrl_req); + exynos_ss_udc_free_all_trb(udc); + clk_put(udc->clk); +err_clk: + free_irq(udc->irq, udc); +err_irq: + iounmap(udc->regs); +err_remap: + release_mem_region(res->start, resource_size(res)); +err_mem: + if (udc->ep0_buff) + dma_free_coherent(NULL, EXYNOS_USB3_EP0_BUFF_SIZE, + udc->ep0_buff, udc->ep0_buff_dma); + if (udc->ctrl_buff) + dma_free_coherent(NULL, EXYNOS_USB3_CTRL_BUFF_SIZE, + udc->ctrl_buff, udc->ctrl_buff_dma); + if (udc->event_buff) + dma_free_coherent(NULL, EXYNOS_USB3_EVENT_BUFF_BSIZE, + udc->event_buff, udc->event_buff_dma); + kfree(udc); + + return ret; +} + +static int __devexit exynos_ss_udc_remove(struct platform_device *pdev) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + struct resource *res; + + usb_gadget_unregister_driver(udc->driver); + + exynos_ss_udc_phy_unset(pdev); + + clk_disable(udc->clk); + clk_put(udc->clk); + + free_irq(udc->irq, udc); + + iounmap(udc->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res) + release_mem_region(res->start, resource_size(res)); + + device_unregister(&udc->gadget.dev); + + platform_set_drvdata(pdev, NULL); + + exynos_ss_udc_ep_free_request(&udc->eps[0].ep, udc->ctrl_req); + exynos_ss_udc_free_all_trb(udc); + + dma_free_coherent(NULL, EXYNOS_USB3_EP0_BUFF_SIZE, + udc->ep0_buff, udc->ep0_buff_dma); + dma_free_coherent(NULL, EXYNOS_USB3_CTRL_BUFF_SIZE, + udc->ctrl_buff, udc->ctrl_buff_dma); + dma_free_coherent(NULL, EXYNOS_USB3_EVENT_BUFF_BSIZE, + udc->event_buff, udc->event_buff_dma); + kfree(udc); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos_ss_udc_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + + if (udc->driver) { + call_gadget(udc, suspend); + EXYNOS_SS_UDC_DISABLE(udc); + } + + return 0; +} + +static int exynos_ss_udc_resume(struct platform_device *pdev) +{ + struct exynos_ss_udc *udc = platform_get_drvdata(pdev); + + if (udc->driver) { + EXYNOS_SS_UDC_ENABLE(udc); + call_gadget(udc, resume); + } + + return 0; +} +#else +#define exynos_ss_udc_suspend NULL +#define exynos_ss_udc_resume NULL +#endif /* CONFIG_PM */ + +static struct platform_driver exynos_ss_udc_driver = { + .driver = { + .name = "exynos-ss-udc", + .owner = THIS_MODULE, + }, + .probe = exynos_ss_udc_probe, + .remove = __devexit_p(exynos_ss_udc_remove), + .suspend = exynos_ss_udc_suspend, + .resume = exynos_ss_udc_resume, +}; + +static int __init exynos_ss_udc_modinit(void) +{ + return platform_driver_register(&exynos_ss_udc_driver); +} + +static void __exit exynos_ss_udc_modexit(void) +{ + platform_driver_unregister(&exynos_ss_udc_driver); +} + +module_init(exynos_ss_udc_modinit); +module_exit(exynos_ss_udc_modexit); + +MODULE_DESCRIPTION("EXYNOS SuperSpeed USB 3.0 Device Controller"); +MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/exynos_ss_udc.h b/drivers/usb/gadget/exynos_ss_udc.h new file mode 100644 index 0000000..a413a44 --- /dev/null +++ b/drivers/usb/gadget/exynos_ss_udc.h @@ -0,0 +1,462 @@ +/* linux/drivers/usb/gadget/exynos_ss_udc.h + * + * Copyright 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * EXYNOS SuperSpeed USB 3.0 Device Controlle driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef __EXYNOS_SS_UDC_H__ +#define __EXYNOS_SS_UDC_H__ + +#define DMA_ADDR_INVALID (~((dma_addr_t)0)) + +/* Maximum packet size for different speeds */ +#define EP0_LS_MPS 8 +#define EP_LS_MPS 8 + +#define EP0_FS_MPS 64 +#define EP_FS_MPS 64 + +#define EP0_HS_MPS 64 +#define EP_HS_MPS 512 + +#define EP0_SS_MPS 512 +#define EP_SS_MPS 1024 + +#define EXYNOS_USB3_EPS 9 + +/* Has to be multiple of four */ +#define EXYNOS_USB3_EVENT_BUFF_WSIZE 256 +#define EXYNOS_USB3_EVENT_BUFF_BSIZE (EXYNOS_USB3_EVENT_BUFF_WSIZE << 2) + +#define EXYNOS_USB3_CTRL_BUFF_SIZE 8 +#define EXYNOS_USB3_EP0_BUFF_SIZE 512 + +#define EXYNOS_USB3_U1_DEV_EXIT_LAT 0 +#define EXYNOS_USB3_U2_DEV_EXIT_LAT 0x20 + +/* Device registers */ +#define EXYNOS_USB3_DCFG 0xC700 +#define EXYNOS_USB3_DCFG_IgnoreStreamPP (1 << 23) +#define EXYNOS_USB3_DCFG_LPMCap (1 << 22) +#define EXYNOS_USB3_DCFG_NumP_MASK (0x1f << 17) +#define EXYNOS_USB3_DCFG_NumP_SHIFT 17 +#define EXYNOS_USB3_DCFG_NumP(_x) ((_x) << 17) +#define EXYNOS_USB3_DCFG_IntrNum_MASK (0x1f << 12) +#define EXYNOS_USB3_DCFG_IntrNum_SHIFT 12 +#define EXYNOS_USB3_DCFG_IntrNum(_x) (0x1f << 12) +#define EXYNOS_USB3_DCFG_PerFrInt_MASK (0x3 << 10) +#define EXYNOS_USB3_DCFG_PerFrInt_SHIFT 10 +#define EXYNOS_USB3_DCFG_PerFrInt(_x) ((_x) << 10) +#define EXYNOS_USB3_DCFG_DevAddr_MASK (0x7f << 3) +#define EXYNOS_USB3_DCFG_DevAddr_SHIFT 3 +#define EXYNOS_USB3_DCFG_DevAddr(_x) ((_x) << 3) +#define EXYNOS_USB3_DCFG_DevSpd_MASK (0x7 << 0) +#define EXYNOS_USB3_DCFG_DevSpd_SHIFT 0 +#define EXYNOS_USB3_DCFG_DevSpd(_x) ((_x) << 0) + +#define EXYNOS_USB3_DCTL 0xC704 +#define EXYNOS_USB3_DCTL_Run_Stop (1 << 31) +#define EXYNOS_USB3_DCTL_CSftRst (1 << 30) +#define EXYNOS_USB3_DCTL_LSftRst (1 << 29) +#define EXYNOS_USB3_DCTL_HIRD_Thres_MASK (0x1f << 24) +#define EXYNOS_USB3_DCTL_HIRD_Thres_SHIFT 24 +#define EXYNOS_USB3_DCTL_HIRD_Thres(_x) ((_x) << 24) +#define EXYNOS_USB3_DCTL_AppL1Res (1 << 23) +#define EXYNOS_USB3_DCTL_TrgtULSt_MASK (0xf << 17) +#define EXYNOS_USB3_DCTL_TrgtULSt_SHIFT 17 +#define EXYNOS_USB3_DCTL_TrgtULSt(_x) ((_x) << 17) +#define EXYNOS_USB3_DCTL_InitU2Ena (1 << 12) +#define EXYNOS_USB3_DCTL_AcceptU2Ena (1 << 11) +#define EXYNOS_USB3_DCTL_InitU1Ena (1 << 10) +#define EXYNOS_USB3_DCTL_AcceptU1Ena (1 << 9) +#define EXYNOS_USB3_DCTL_ULStChngReq_MASK (0xf << 5) +#define EXYNOS_USB3_DCTL_ULStChngReq_SHIFT 5 +#define EXYNOS_USB3_DCTL_ULStChngReq(_x) ((_x) << 5) +#define EXYNOS_USB3_DCTL_TstCtl_MASK (0xf << 1) +#define EXYNOS_USB3_DCTL_TstCtl_SHIFT 1 +#define EXYNOS_USB3_DCTL_TstCtl(_x) ((_x) << 1) + +#define EXYNOS_USB3_DEVTEN 0xC708 +#define EXYNOS_USB3_DEVTEN_VndrDevTstRcvedEn (1 << 12) +#define EXYNOS_USB3_DEVTEN_EvntOverflowEn (1 << 11) +#define EXYNOS_USB3_DEVTEN_CmdCmpltEn (1 << 10) +#define EXYNOS_USB3_DEVTEN_ErrticErrEn (1 << 9) +#define EXYNOS_USB3_DEVTEN_SofEn (1 << 7) +#define EXYNOS_USB3_DEVTEN_EOPFEn (1 << 6) +#define EXYNOS_USB3_DEVTEN_WkUpEvtEn (1 << 4) +#define EXYNOS_USB3_DEVTEN_ULStCngEn (1 << 3) +#define EXYNOS_USB3_DEVTEN_ConnectDoneEn (1 << 2) +#define EXYNOS_USB3_DEVTEN_USBRstEn (1 << 1) +#define EXYNOS_USB3_DEVTEN_DisconnEvtEn (1 << 0) + +#define EXYNOS_USB3_DSTS 0xC70C +#define EXYNOS_USB3_DSTS_PwrUpReq (1 << 24) +#define EXYNOS_USB3_DSTS_CoreIdle (1 << 23) +#define EXYNOS_USB3_DSTS_DevCtrlHlt (1 << 22) +#define EXYNOS_USB3_DSTS_USBLnkSt_MASK (0xf << 18) +#define EXYNOS_USB3_DSTS_USBLnkSt_SHIFT 18 +#define EXYNOS_USB3_DSTS_USBLnkSt(_x) ((_x) << 18) +#define EXYNOS_USB3_LnkSt_LPBK 0xb +#define EXYNOS_USB3_LnkSt_CMPLY 0xa +#define EXYNOS_USB3_LnkSt_HRESET 0x9 +#define EXYNOS_USB3_LnkSt_RECOV 0x8 +#define EXYNOS_USB3_LnkSt_POLL 0x7 +#define EXYNOS_USB3_LnkSt_SS_INACT 0x6 +#define EXYNOS_USB3_LnkSt_RX_DET 0x5 +#define EXYNOS_USB3_LnkSt_SS_DIS 0x4 +#define EXYNOS_USB3_LnkSt_U3 0x3 +#define EXYNOS_USB3_LnkSt_U2 0x2 +#define EXYNOS_USB3_LnkSt_U1 0x1 +#define EXYNOS_USB3_LnkSt_U0 0x0 +#define EXYNOS_USB3_DSTS_RxFIFOEmpty (1 << 17) +#define EXYNOS_USB3_DSTS_SOFFN_MASK (0x3fff << 3) +#define EXYNOS_USB3_DSTS_SOFFN_SHIFT 3 +#define EXYNOS_USB3_DSTS_SOFFN(_x) ((_x) << 3) +#define EXYNOS_USB3_DSTS_ConnectSpd_MASK (0x7 << 0) +#define EXYNOS_USB3_DSTS_ConnectSpd_SHIFT 0 +#define EXYNOS_USB3_DSTS_ConnectSpd(_x) ((_x) << 0) + +#define EXYNOS_USB3_DGCMDPAR 0xC710 + +#define EXYNOS_USB3_DGCMD 0xC714 +#define EXYNOS_USB3_DGCMD_CmdStatus (1 << 15) +#define EXYNOS_USB3_DGCMD_CmdAct (1 << 10) +#define EXYNOS_USB3_DGCMD_CmdIOC (1 << 8) +#define EXYNOS_USB3_DGCMD_CmdTyp_MASK (0xff << 0) +#define EXYNOS_USB3_DGCMD_CmdTyp_SHIFT 0 +#define EXYNOS_USB3_DGCMD_CmdTyp(_x) ((_x) << 0) +/* Device generic commands */ +#define EXYNOS_USB3_DGCMD_CmdTyp_SetPerParams 0x2 + +#define EXYNOS_USB3_DALEPENA 0xC720 + +#define EXYNOS_USB3_DEPCMDPAR2(_a) (0xC800 + ((_a) * 0x10)) + +#define EXYNOS_USB3_DEPCMDPAR1(_a) (0xC804 + ((_a) * 0x10)) +/* DEPCFG command parameter 1 */ +#define EXYNOS_USB3_DEPCMDPAR1x_FIFO_based (1 << 31) +#define EXYNOS_USB3_DEPCMDPAR1x_BULK_based (1 << 30) +#define EXYNOS_USB3_DEPCMDPAR1x_EpNum_MASK (0xf << 26) +#define EXYNOS_USB3_DEPCMDPAR1x_EpNum_SHIFT 26 +#define EXYNOS_USB3_DEPCMDPAR1x_EpNum(_x) ((_x) << 26) +#define EXYNOS_USB3_DEPCMDPAR1x_EpDir (1 << 25) +#define EXYNOS_USB3_DEPCMDPAR1x_StrmCap (1 << 24) +#define EXYNOS_USB3_DEPCMDPAR1x_bInterval_m1_MASK (0xff << 16) +#define EXYNOS_USB3_DEPCMDPAR1x_bInterval_m1_SHIFT 16 +#define EXYNOS_USB3_DEPCMDPAR1x_bInterval_m1(_x) ((_x) << 16) +#define EXYNOS_USB3_DEPCMDPAR1x_StreamEvtEn (1 << 13) +#define EXYNOS_USB3_DEPCMDPAR1x_RxTxfifoEvtEn (1 << 11) +#define EXYNOS_USB3_DEPCMDPAR1x_XferNRdyEn (1 << 10) +#define EXYNOS_USB3_DEPCMDPAR1x_XferInProgEn (1 << 9) +#define EXYNOS_USB3_DEPCMDPAR1x_XferCmplEn (1 << 8) +#define EXYNOS_USB3_DEPCMDPAR1x_IntrNum_MASK (0x1f << 0) +#define EXYNOS_USB3_DEPCMDPAR1x_IntrNum_SHIFT 0 +#define EXYNOS_USB3_DEPCMDPAR1x_IntrNum(_x) ((_x) << 0) + +#define EXYNOS_USB3_DEPCMDPAR0(_a) (0xC808 + ((_a) * 0x10)) +/* DEPCFG command parameter 0 */ +#define EXYNOS_USB3_DEPCMDPAR0x_IgnrSeqNum (1 << 31) +#define EXYNOS_USB3_DEPCMDPAR0x_DataSeqNum_MASK (0x1f << 26) +#define EXYNOS_USB3_DEPCMDPAR0x_DataSeqNum_SHIFT 26 +#define EXYNOS_USB3_DEPCMDPAR0x_DataSeqNum(_x) ((_x) << 26) +#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz_MASK (0xf << 22) +#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz_SHIFT 22 +#define EXYNOS_USB3_DEPCMDPAR0x_BrstSiz(_x) ((_x) << 22) +#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum_MASK (0x1f << 17) +#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum_SHIFT 17 +#define EXYNOS_USB3_DEPCMDPAR0x_FIFONum(_x) ((_x) << 17) +#define EXYNOS_USB3_DEPCMDPAR0x_MPS_MASK (0x7ff << 3) +#define EXYNOS_USB3_DEPCMDPAR0x_MPS_SHIFT 3 +#define EXYNOS_USB3_DEPCMDPAR0x_MPS(_x) ((_x) << 3) +#define EXYNOS_USB3_DEPCMDPAR0x_EPType_MASK (0x3 << 1) +#define EXYNOS_USB3_DEPCMDPAR0x_EPType_SHIFT 1 +#define EXYNOS_USB3_DEPCMDPAR0x_EPType(_x) ((_x) << 1) +/* DEPXFERCFG command parameter 0 */ +#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes_MASK (0xff << 0) +#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes_SHIFT 0 +#define EXYNOS_USB3_DEPCMDPAR0x_NumXferRes(_x) ((_x) << 0) + +#define EXYNOS_USB3_DEPCMD(_a) (0xC80C + ((_a) * 0x10)) +#define EXYNOS_USB3_DEPCMDx_CommandParam_MASK (0xffff << 16) +#define EXYNOS_USB3_DEPCMDx_CommandParam_SHIFT 16 +#define EXYNOS_USB3_DEPCMDx_CommandParam(_x) ((_x) << 16) +#define EXYNOS_USB3_DEPCMDx_EventParam_MASK (0xffff << 16) +#define EXYNOS_USB3_DEPCMDx_EventParam_SHIFT 16 +#define EXYNOS_USB3_DEPCMDx_EventParam(_x) ((_x) << 16) +#define EXYNOS_USB3_DEPCMDx_XferRscIdx_LIMIT 0x7f +#define EXYNOS_USB3_DEPCMDx_CmdStatus_MASK (0xf << 12) +#define EXYNOS_USB3_DEPCMDx_CmdStatus_SHIFT 12 +#define EXYNOS_USB3_DEPCMDx_CmdStatus(_x) ((_x) << 12) +#define EXYNOS_USB3_DEPCMDx_HiPri_ForceRM (1 << 11) +#define EXYNOS_USB3_DEPCMDx_CmdAct (1 << 10) +#define EXYNOS_USB3_DEPCMDx_CmdIOC (1 << 8) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_MASK (0xf << 0) +#define EXYNOS_USB3_DEPCMDx_CmdTyp_SHIFT 0 +#define EXYNOS_USB3_DEPCMDx_CmdTyp(_x) ((_x) << 0) +/* Physical Endpoint commands */ +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTARTCFG 0x9 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPENDXFER 0x8 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPUPDXFER 0x7 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSTRTXFER 0x6 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCSTALL 0x5 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPSSTALL 0x4 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPGETDSEQ 0x3 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPXFERCFG 0x2 +#define EXYNOS_USB3_DEPCMDx_CmdTyp_DEPCFG 0x1 + +/* Transfer Request Block */ +#define EXYNOS_USB3_TRB_TRBSTS_MASK (0xf << 28) +#define EXYNOS_USB3_TRB_TRBSTS_SHIFT 28 +#define EXYNOS_USB3_TRB_TRBSTS(_x) ((_x) << 28) +#define EXYNOS_USB3_TRB_PCM1_MASK (0x3 << 24) +#define EXYNOS_USB3_TRB_PCM1_SHIFT 24 +#define EXYNOS_USB3_TRB_PCM1(_x) ((_x) << 24) +#define EXYNOS_USB3_TRB_BUFSIZ_MASK (0xffffff << 0) +#define EXYNOS_USB3_TRB_BUFSIZ_SHIFT 0 +#define EXYNOS_USB3_TRB_BUFSIZ(_x) ((_x) << 0) +#define EXYNOS_USB3_TRB_StreamID_SOFNumber_MASK (0xffff << 14) +#define EXYNOS_USB3_TRB_StreamID_SOFNumber_SHIFT 14 +#define EXYNOS_USB3_TRB_StreamID_SOFNumber(_x) ((_x) << 14) +#define EXYNOS_USB3_TRB_IOC (1 << 11) +#define EXYNOS_USB3_TRB_ISP_IMI (1 << 10) +#define EXYNOS_USB3_TRB_TRBCTL_MASK (0x3f << 4) +#define EXYNOS_USB3_TRB_TRBCTL_SHIFT 4 +#define EXYNOS_USB3_TRB_TRBCTL(_x) ((_x) << 4) +#define EXYNOS_USB3_TRB_CSP (1 << 3) +#define EXYNOS_USB3_TRB_CHN (1 << 2) +#define EXYNOS_USB3_TRB_LST (1 << 1) +#define EXYNOS_USB3_TRB_HWO (1 << 0) +/*****************************************************************************/ + +#define call_gadget(_udc, _entry) do { \ + if ((_udc)->gadget.speed != USB_SPEED_UNKNOWN && \ + (_udc)->driver && (_udc)->driver->_entry) \ + (_udc)->driver->_entry(&(_udc)->gadget); \ +} while (0) + +#include <linux/wakelock.h> + +/** + * States of EP0 + */ +enum ctrl_ep_state { + EP0_UNCONNECTED, + EP0_SETUP_PHASE, + EP0_DATA_PHASE, + EP0_WAIT_NRDY, + EP0_STATUS_PHASE, + EP0_STALL, +}; + +/** + * Types of TRB + */ +enum trb_control { + NORMAL = 1, + CONTROL_SETUP, + CONTROL_STATUS_2, + CONTROL_STATUS_3, + CONTROL_DATA, + ISOCHRONOUS_FIRST, + ISOCHRONOUS, + LINK_TRB, +}; + +/** + * struct exynos_ss_udc_trb - transfer request block (TRB) + * @buff_ptr_low: Buffer pointer low. + * @buff_ptr_high: Buffer pointer high. + * @param1: TRB parameter 1. + * @param2: TRB parameter 2. + */ +struct exynos_ss_udc_trb { + u32 buff_ptr_low; + u32 buff_ptr_high; + u32 param1; + u32 param2; +}; + +/** + * struct exynos_ss_udc_ep_command - endpoint command. + * @queue: The list of commands for the endpoint. + * @ep: physical endpoint number. + * @param0: Command parameter 0. + * @param1: Command parameter 1. + * @param2: Command parameter 2. + * @cmdtype: Command to issue. + * @cmdflags: Command flags. + */ +struct exynos_ss_udc_ep_command { + struct list_head queue; + + int ep; + u32 param0; + u32 param1; + u32 param2; + u32 cmdtyp; + u32 cmdflags; +}; + +/** + * struct exynos_ss_udc_req - data transfer request + * @req: The USB gadget request. + * @queue: The list of requests for the endpoint this is queued for. + * @mapped: DMA buffer for this request has been mapped via dma_map_single(). + */ +struct exynos_ss_udc_req { + struct usb_request req; + struct list_head queue; + unsigned char mapped; +}; + +/** + * struct exynos_ss_udc_ep - driver endpoint definition. + * @ep: The gadget layer representation of the endpoint. + * @queue: Queue of requests for this endpoint. + * @cmd_queue: Queue of commands for this endpoint. + * @parent: Reference back to the parent device structure. + * @req: The current request that the endpoint is processing. This is + * used to indicate an request has been loaded onto the endpoint + * and has yet to be completed (maybe due to data move, or simply + * awaiting an ack from the core all the data has been completed). + * @lock: State lock to protect contents of endpoint. + * @trb: Transfer Request Block. + * @trb_dma: Transfer Request Block DMA address. + * @tri: Transfer resource index. + * @epnum: The USB endpoint number. + * @type: The endpoint type. + * @dir_in: Set to true if this endpoint is of the IN direction, which + * means that it is sending data to the Host. + * @halted: Set if the endpoint has been halted. + * @enabled: Set to true if endpoint is enabled. + * @wedged: Set if the endpoint has been wedged. + * @not_ready: Set to true if a command for the endpoint hasn't completed + * during timeout interval. + * @name: The driver generated name for the endpoint. + * + * This is the driver's state for each registered enpoint, allowing it + * to keep track of transactions that need doing. Each endpoint has a + * lock to protect the state, to try and avoid using an overall lock + * for the host controller as much as possible. + */ +struct exynos_ss_udc_ep { + struct usb_ep ep; + struct list_head queue; + struct list_head cmd_queue; + struct exynos_ss_udc *parent; + struct exynos_ss_udc_req *req; + + spinlock_t lock; + + struct exynos_ss_udc_trb *trb; + dma_addr_t trb_dma; + u8 tri; + + unsigned char epnum; + unsigned int type; + unsigned int dir_in:1; + unsigned int halted:1; + unsigned int enabled:1; + unsigned int wedged:1; + unsigned int not_ready:1; + + char name[10]; +}; + +/** + * struct exynos_ss_udc - driver state. + * @dev: The parent device supplied to the probe function + * @driver: USB gadget driver + * @plat: The platform specific configuration data. + * @state: The device USB state. + * @regs: The memory area mapped for accessing registers. + * @irq: The IRQ number we are using. + * @clk: The clock we are using. + * @release: The core release number. + * @event_buff: Event buffer. + * @event_buff_dma: Event buffer DMA address. + * @event_indx: Event buffer index. + * @eps_enabled: Set if new configuration for physical endpoints > 1 started. + * @ep0_state: State of EP0. + * @ep0_three_stage: Set if control transfer has three stages. + * @ep0_buff: Buffer for EP0 data. + * @ep0_buff_dma: EP0 data buffer DMA address. + * @ctrl_buff: Buffer for EP0 control requests. + * @ctrl_buff_dma: EP0 control request buffer DMA address. + * @ctrl_req: Request for EP0 control packets. + * @gadget: Represents USB slave device. + * @eps: The endpoints being supplied to the gadget framework + */ +struct exynos_ss_udc { + struct device *dev; + struct usb_gadget_driver *driver; + struct exynos_ss_udc_plat *plat; + + enum usb_device_state state; + + void __iomem *regs; + int irq; + struct clk *clk; + + u16 release; + + u32 *event_buff; + dma_addr_t event_buff_dma; + u32 event_indx; + + bool eps_enabled; + enum ctrl_ep_state ep0_state; + int ep0_three_stage; + + u8 *ep0_buff; + dma_addr_t ep0_buff_dma; + u8 *ctrl_buff; + dma_addr_t ctrl_buff_dma; + struct usb_request *ctrl_req; + + struct usb_gadget gadget; + struct exynos_ss_udc_ep eps[EXYNOS_USB3_EPS]; + struct wake_lock usbd_wake_lock; +}; + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +extern void samsung_cable_check_status(int flag); +#endif + +/* conversion functions */ +static inline struct exynos_ss_udc_req *our_req(struct usb_request *req) +{ + return container_of(req, struct exynos_ss_udc_req, req); +} + +static inline struct exynos_ss_udc_ep *our_ep(struct usb_ep *ep) +{ + return container_of(ep, struct exynos_ss_udc_ep, ep); +} + +static inline void __orr32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) | val, ptr); +} + +static inline void __bic32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) & ~val, ptr); +} + +static inline int get_phys_epnum(struct exynos_ss_udc_ep *udc_ep) +{ + return udc_ep->epnum * 2 + udc_ep->dir_in; +} + +static inline int get_usb_epnum(int index) +{ + return index >> 1; +} + +#endif /* __EXYNOS_SS_UDC_H__ */ diff --git a/drivers/usb/gadget/f_accessory.c b/drivers/usb/gadget/f_accessory.c new file mode 100644 index 0000000..dfe3e51 --- /dev/null +++ b/drivers/usb/gadget/f_accessory.c @@ -0,0 +1,792 @@ +/* + * Gadget Function Driver for Android USB accessories + * + * Copyright (C) 2011 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/freezer.h> + +#include <linux/types.h> +#include <linux/file.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/usb.h> +#include <linux/usb/ch9.h> +#include <linux/usb/f_accessory.h> + +#define BULK_BUFFER_SIZE 16384 +#define ACC_STRING_SIZE 256 + +#define PROTOCOL_VERSION 1 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 + +struct acc_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + /* set to 1 when we connect */ + int online:1; + /* Set to 1 when we disconnect. + * Not cleared until our file is closed. + */ + int disconnected:1; + + /* strings sent by the host */ + char manufacturer[ACC_STRING_SIZE]; + char model[ACC_STRING_SIZE]; + char description[ACC_STRING_SIZE]; + char version[ACC_STRING_SIZE]; + char uri[ACC_STRING_SIZE]; + char serial[ACC_STRING_SIZE]; + + /* for acc_complete_set_string */ + int string_index; + + /* set to 1 if we have a pending start request */ + int start_requested; + + /* synchronize access to our device file */ + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + struct delayed_work work; +}; + +static struct usb_interface_descriptor acc_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_endpoint_descriptor acc_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acc_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acc_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_fullspeed_in_desc, + (struct usb_descriptor_header *) &acc_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_acc_descs[] = { + (struct usb_descriptor_header *) &acc_interface_desc, + (struct usb_descriptor_header *) &acc_highspeed_in_desc, + (struct usb_descriptor_header *) &acc_highspeed_out_desc, + NULL, +}; + +static struct usb_string acc_string_defs[] = { + [INTERFACE_STRING_INDEX].s = "Android Accessory Interface", + { }, /* end of list */ +}; + +static struct usb_gadget_strings acc_string_table = { + .language = 0x0409, /* en-US */ + .strings = acc_string_defs, +}; + +static struct usb_gadget_strings *acc_strings[] = { + &acc_string_table, + NULL, +}; + +/* temporary variable used between acc_open() and acc_gadget_bind() */ +static struct acc_dev *_acc_dev; + +static inline struct acc_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct acc_dev, function); +} + +static struct usb_request *acc_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void acc_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +/* add a request to the tail of a list */ +static void req_put(struct acc_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct acc_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void acc_set_disconnected(struct acc_dev *dev) +{ + dev->online = 0; + dev->disconnected = 1; +} + +static void acc_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + if (req->status != 0) + acc_set_disconnected(dev); + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void acc_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = _acc_dev; + + dev->rx_done = 1; + if (req->status != 0) + acc_set_disconnected(dev); + + wake_up(&dev->read_wq); +} + +static void acc_complete_set_string(struct usb_ep *ep, struct usb_request *req) +{ + struct acc_dev *dev = ep->driver_data; + char *string_dest = NULL; + int length = req->actual; + + if (req->status != 0) { + pr_err("acc_complete_set_string, err %d\n", req->status); + return; + } + + switch (dev->string_index) { + case ACCESSORY_STRING_MANUFACTURER: + string_dest = dev->manufacturer; + break; + case ACCESSORY_STRING_MODEL: + string_dest = dev->model; + break; + case ACCESSORY_STRING_DESCRIPTION: + string_dest = dev->description; + break; + case ACCESSORY_STRING_VERSION: + string_dest = dev->version; + break; + case ACCESSORY_STRING_URI: + string_dest = dev->uri; + break; + case ACCESSORY_STRING_SERIAL: + string_dest = dev->serial; + break; + } + if (string_dest) { + unsigned long flags; + + if (length >= ACC_STRING_SIZE) + length = ACC_STRING_SIZE - 1; + + spin_lock_irqsave(&dev->lock, flags); + memcpy(string_dest, req->buf, length); + /* ensure zero termination */ + string_dest[length] = 0; + spin_unlock_irqrestore(&dev->lock, flags); + } else { + pr_err("unknown accessory string index %d\n", + dev->string_index); + } +} + +static int create_bulk_endpoints(struct acc_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = acc_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = acc_complete_out; + dev->rx_req[i] = req; + } + + return 0; + +fail: + printk(KERN_ERR "acc_bind() could not allocate requests\n"); + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); + return -1; +} + +static ssize_t acc_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int ret = 0; + + pr_debug("acc_read(%d)\n", count); + + if (dev->disconnected) + return -ENODEV; + + if (count > BULK_BUFFER_SIZE) + count = BULK_BUFFER_SIZE; + + /* we will block until we're online */ + pr_debug("acc_read: waiting for online\n"); + ret = wait_event_interruptible(dev->read_wq, dev->online); + if (ret < 0) { + r = ret; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->online) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + pr_debug("acc_read returning %d\n", r); + return r; +} + +static ssize_t acc_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct acc_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + pr_debug("acc_write(%d)\n", count); + + if (!dev->online || dev->disconnected) + return -ENODEV; + + while (count > 0) { + if (!dev->online) { + pr_debug("acc_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) || !dev->online)); + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + pr_debug("acc_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + pr_debug("acc_write returning %d\n", r); + return r; +} + +static long acc_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct acc_dev *dev = fp->private_data; + char *src = NULL; + int ret; + + switch (code) { + case ACCESSORY_GET_STRING_MANUFACTURER: + src = dev->manufacturer; + break; + case ACCESSORY_GET_STRING_MODEL: + src = dev->model; + break; + case ACCESSORY_GET_STRING_DESCRIPTION: + src = dev->description; + break; + case ACCESSORY_GET_STRING_VERSION: + src = dev->version; + break; + case ACCESSORY_GET_STRING_URI: + src = dev->uri; + break; + case ACCESSORY_GET_STRING_SERIAL: + src = dev->serial; + break; + case ACCESSORY_IS_START_REQUESTED: + return dev->start_requested; + } + if (!src) + return -EINVAL; + + ret = strlen(src) + 1; + if (copy_to_user((void __user *)value, src, ret)) + ret = -EFAULT; + return ret; +} + +static int acc_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_open\n"); + if (atomic_xchg(&_acc_dev->open_excl, 1)) + return -EBUSY; + + _acc_dev->disconnected = 0; + fp->private_data = _acc_dev; + return 0; +} + +static int acc_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "acc_release\n"); + + WARN_ON(!atomic_xchg(&_acc_dev->open_excl, 0)); + _acc_dev->disconnected = 0; + return 0; +} + +/* file operations for /dev/acc_usb */ +static const struct file_operations acc_fops = { + .owner = THIS_MODULE, + .read = acc_read, + .write = acc_write, + .unlocked_ioctl = acc_ioctl, + .open = acc_open, + .release = acc_release, +}; + +static struct miscdevice acc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "usb_accessory", + .fops = &acc_fops, +}; + + +static int acc_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct acc_dev *dev = _acc_dev; + int value = -EOPNOTSUPP; + u8 b_requestType = ctrl->bRequestType; + u8 b_request = ctrl->bRequest; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + +/* + printk(KERN_INFO "acc_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + b_requestType, b_request, + w_value, w_index, w_length); +*/ + + if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_START) { + dev->start_requested = 1; + schedule_delayed_work( + &dev->work, msecs_to_jiffies(5)); + value = 0; + } else if (b_request == ACCESSORY_SEND_STRING) { + dev->string_index = w_index; + cdev->gadget->ep0->driver_data = dev; + cdev->req->complete = acc_complete_set_string; + value = w_length; + } + } else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) { + if (b_request == ACCESSORY_GET_PROTOCOL) { + *((u16 *)cdev->req->buf) = PROTOCOL_VERSION; + value = sizeof(u16); + + /* clear any strings left over from a previous session */ + memset(dev->manufacturer, 0, sizeof(dev->manufacturer)); + memset(dev->model, 0, sizeof(dev->model)); + memset(dev->description, 0, sizeof(dev->description)); + memset(dev->version, 0, sizeof(dev->version)); + memset(dev->uri, 0, sizeof(dev->uri)); + memset(dev->serial, 0, sizeof(dev->serial)); + dev->start_requested = 0; + } + } + + if (value >= 0) { + cdev->req->zero = 0; + cdev->req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "%s setup response queue error\n", + __func__); + } + + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + return value; +} + +static int +acc_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct acc_dev *dev = func_to_dev(f); + int id; + int ret; + + DBG(cdev, "acc_function_bind dev: %p\n", dev); + + dev->start_requested = 0; + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + acc_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &acc_fullspeed_in_desc, + &acc_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + acc_highspeed_in_desc.bEndpointAddress = + acc_fullspeed_in_desc.bEndpointAddress; + acc_highspeed_out_desc.bEndpointAddress = + acc_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +acc_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_request *req; + int i; + + while ((req = req_get(dev, &dev->tx_idle))) + acc_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + acc_request_free(dev->rx_req[i], dev->ep_out); +} + +static void acc_work(struct work_struct *data) +{ + char *envp[2] = { "ACCESSORY=START", NULL }; + kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp); +} + +static int acc_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "acc_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &acc_highspeed_in_desc, + &acc_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &acc_highspeed_out_desc, + &acc_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void acc_function_disable(struct usb_function *f) +{ + struct acc_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "acc_function_disable\n"); + acc_set_disconnected(dev); + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int acc_bind_config(struct usb_configuration *c) +{ + struct acc_dev *dev = _acc_dev; + int ret; + + printk(KERN_INFO "acc_bind_config\n"); + + /* allocate a string ID for our interface */ + if (acc_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + acc_string_defs[INTERFACE_STRING_INDEX].id = ret; + acc_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "accessory"; + dev->function.strings = acc_strings, + dev->function.descriptors = fs_acc_descs; + dev->function.hs_descriptors = hs_acc_descs; + dev->function.bind = acc_function_bind; + dev->function.unbind = acc_function_unbind; + dev->function.set_alt = acc_function_set_alt; + dev->function.disable = acc_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int acc_setup(void) +{ + struct acc_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + atomic_set(&dev->open_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_DELAYED_WORK(&dev->work, acc_work); + + /* _acc_dev must be set before calling usb_gadget_register_driver */ + _acc_dev = dev; + + ret = misc_register(&acc_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + printk(KERN_ERR "USB accessory gadget driver failed to initialize\n"); + return ret; +} + +static void acc_cleanup(void) +{ + misc_deregister(&acc_device); + kfree(_acc_dev); + _acc_dev = NULL; +} diff --git a/drivers/usb/gadget/f_acm.c b/drivers/usb/gadget/f_acm.c index bd6226c..0c3c3ea 100644 --- a/drivers/usb/gadget/f_acm.c +++ b/drivers/usb/gadget/f_acm.c @@ -366,6 +366,9 @@ static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) * that bit, we should return to that no-flow state. */ acm->port_handshake_bits = w_value; +#ifdef CONFIG_USB_DUN_SUPPORT + notify_control_line_state((unsigned long)w_value); +#endif break; default: @@ -405,10 +408,10 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(acm->notify); } else { VDBG(cdev, "init acm ctrl interface %d\n", intf); - acm->notify_desc = ep_choose(cdev->gadget, - acm->hs.notify, - acm->fs.notify); } + acm->notify_desc = ep_choose(cdev->gadget, + acm->hs.notify, + acm->fs.notify); usb_ep_enable(acm->notify, acm->notify_desc); acm->notify->driver_data = acm; @@ -418,11 +421,11 @@ static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) gserial_disconnect(&acm->port); } else { DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); - acm->port.in_desc = ep_choose(cdev->gadget, - acm->hs.in, acm->fs.in); - acm->port.out_desc = ep_choose(cdev->gadget, - acm->hs.out, acm->fs.out); } + acm->port.in_desc = ep_choose(cdev->gadget, + acm->hs.in, acm->fs.in); + acm->port.out_desc = ep_choose(cdev->gadget, + acm->hs.out, acm->fs.out); gserial_connect(&acm->port, acm->port_num); } else @@ -536,6 +539,21 @@ static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req) acm_notify_serial_state(acm); } +#ifdef CONFIG_USB_DUN_SUPPORT +int acm_notify(void *dev, u16 state) +{ + struct f_acm *acm; + if (dev) { + acm = (struct f_acm *)dev; + acm->serial_state = state; + acm_notify_serial_state(acm); + } else { + printk(KERN_DEBUG "usb: %s not ready\n", __func__); + return -EAGAIN; + } + return 0; +} +#endif /* connect == the TTY link is open */ static void acm_connect(struct gserial *port) @@ -669,6 +687,9 @@ acm_bind(struct usb_configuration *c, struct usb_function *f) gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", acm->port.in->name, acm->port.out->name, acm->notify->name); +#ifdef CONFIG_USB_DUN_SUPPORT + modem_register(acm); +#endif return 0; fail: @@ -697,7 +718,11 @@ acm_unbind(struct usb_configuration *c, struct usb_function *f) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); gs_free_req(acm->notify, acm->notify_req); + kfree(acm->port.func.name); kfree(acm); +#ifdef CONFIG_USB_DUN_SUPPORT + modem_unregister(); +#endif } /* Some controllers can't support CDC ACM ... */ @@ -768,7 +793,11 @@ int acm_bind_config(struct usb_configuration *c, u8 port_num) acm->port.disconnect = acm_disconnect; acm->port.send_break = acm_send_break; - acm->port.func.name = "acm"; + acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num); + if (!acm->port.func.name) { + kfree(acm); + return -ENOMEM; + } acm->port.func.strings = acm_strings; /* descriptors are per-instance copies */ acm->port.func.bind = acm_bind; diff --git a/drivers/usb/gadget/f_adb.c b/drivers/usb/gadget/f_adb.c new file mode 100644 index 0000000..e4d8b6e --- /dev/null +++ b/drivers/usb/gadget/f_adb.c @@ -0,0 +1,652 @@ +/* + * Gadget Driver for Android ADB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#define ADB_BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char adb_shortname[] = "android_adb"; + +struct adb_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +static struct usb_interface_descriptor adb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor adb_superspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor adb_superspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor adb_superspeed_bulk_comp_desc = { + .bLength = sizeof adb_superspeed_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor adb_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor adb_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_fullspeed_in_desc, + (struct usb_descriptor_header *) &adb_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_highspeed_in_desc, + (struct usb_descriptor_header *) &adb_highspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_superspeed_in_desc, + (struct usb_descriptor_header *) &adb_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &adb_superspeed_out_desc, + (struct usb_descriptor_header *) &adb_superspeed_bulk_comp_desc, + NULL, +}; + + +/* temporary variable used between adb_open() and adb_gadget_bind() */ +static struct adb_dev *_adb_dev; + +static inline struct adb_dev *func_to_adb(struct usb_function *f) +{ + return container_of(f, struct adb_dev, function); +} + + +static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void adb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int adb_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void adb_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void adb_req_put(struct adb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *adb_req_get(struct adb_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void adb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + if (req->status != 0) + dev->error = 1; + + adb_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void adb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int adb_create_bulk_endpoints(struct adb_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = adb_request_new(dev->ep_out, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = adb_request_new(dev->ep_in, ADB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_in; + adb_req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "adb_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t adb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int ret; + + pr_debug("adb_read(%d)\n", count); + if (!_adb_dev) + return -ENODEV; + + if (count > ADB_BULK_BUFFER_SIZE) + return -EINVAL; + + if (adb_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + pr_debug("adb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + adb_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + goto done; + } else { + pr_debug("rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + dev->error = 1; + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + pr_debug("rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + + } else + r = -EIO; + +done: + adb_unlock(&dev->read_excl); + pr_debug("adb_read returning %d\n", r); + return r; +} + +static ssize_t adb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (!_adb_dev) + return -ENODEV; + pr_debug("adb_write(%d)\n", count); + + if (adb_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + pr_debug("adb_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = adb_req_get(dev, &dev->tx_idle)) || dev->error); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > ADB_BULK_BUFFER_SIZE) + xfer = ADB_BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + pr_debug("adb_write: xfer error %d\n", ret); + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + adb_req_put(dev, &dev->tx_idle, req); + + adb_unlock(&dev->write_excl); + pr_debug("adb_write returning %d\n", r); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "adb_open\n"); + if (!_adb_dev) + return -ENODEV; + + if (adb_lock(&_adb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _adb_dev; + + /* clear the error latch */ + _adb_dev->error = 0; + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "adb_release\n"); + adb_unlock(&_adb_dev->open_excl); + return 0; +} + +/* file operations for ADB device /dev/android_adb */ +static struct file_operations adb_fops = { + .owner = THIS_MODULE, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +static struct miscdevice adb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = adb_shortname, + .fops = &adb_fops, +}; + + + + +static int +adb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct adb_dev *dev = func_to_adb(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "adb_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + adb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = adb_create_bulk_endpoints(dev, &adb_fullspeed_in_desc, + &adb_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + adb_highspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_highspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + /* support super speed hardware */ + if (gadget_is_superspeed(c->cdev->gadget)) { + adb_superspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_superspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +adb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_request *req; + + + dev->online = 0; + dev->error = 1; + + wake_up(&dev->read_wq); + + adb_request_free(dev->rx_req, dev->ep_out); + while ((req = adb_req_get(dev, &dev->tx_idle))) + adb_request_free(req, dev->ep_in); +} + +static int adb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &adb_highspeed_in_desc, + &adb_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &adb_highspeed_out_desc, + &adb_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void adb_function_disable(struct usb_function *f) +{ + struct adb_dev *dev = func_to_adb(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "adb_function_disable cdev %p\n", cdev); + dev->online = 0; + dev->error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int adb_bind_config(struct usb_configuration *c) +{ + struct adb_dev *dev = _adb_dev; + + printk(KERN_INFO "adb_bind_config\n"); + + dev->cdev = c->cdev; + dev->function.name = "adb"; + dev->function.descriptors = fs_adb_descs; + dev->function.hs_descriptors = hs_adb_descs; + dev->function.ss_descriptors = ss_adb_descs; + dev->function.bind = adb_function_bind; + dev->function.unbind = adb_function_unbind; + dev->function.set_alt = adb_function_set_alt; + dev->function.disable = adb_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int adb_setup(void) +{ + struct adb_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + INIT_LIST_HEAD(&dev->tx_idle); + + _adb_dev = dev; + + ret = misc_register(&adb_device); + if (ret) + goto err; + + return 0; + +err: + kfree(dev); + printk(KERN_ERR "adb gadget driver failed to initialize\n"); + return ret; +} + +static void adb_cleanup(void) +{ + misc_deregister(&adb_device); + + kfree(_adb_dev); + _adb_dev = NULL; +} diff --git a/drivers/usb/gadget/f_dm.c b/drivers/usb/gadget/f_dm.c new file mode 100644 index 0000000..04eec37 --- /dev/null +++ b/drivers/usb/gadget/f_dm.c @@ -0,0 +1,310 @@ +/* + * f_dm.c - generic USB serial function driver + * modified from f_serial.c and f_diag.c + * ttyGS* + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/device.h> + +#include "u_serial.h" +#include "gadget_chips.h" + +/* + * This function packages a simple "generic serial" port with no real + * control mechanisms, just raw data transfer over two bulk endpoints. + * + * Because it's not standardized, this isn't as interoperable as the + * CDC ACM driver. However, for many purposes it's just as functional + * if you can arrange appropriate host side drivers. + */ + +struct dm_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; +}; + +struct f_dm { + struct gserial port; + u8 data_id; + u8 port_num; + + struct dm_descs fs; + struct dm_descs hs; +}; + +static inline struct f_dm *func_to_dm(struct usb_function *f) +{ + return container_of(f, struct f_dm, port.func); +} + +/*-------------------------------------------------------------------------*/ + +/* interface descriptor: */ +static struct usb_interface_descriptor dm_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = 0x10, + .bInterfaceProtocol = 0x01, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor dm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor dm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *dm_fs_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_fs_in_desc, + (struct usb_descriptor_header *) &dm_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor dm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor dm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *dm_hs_function[] = { + (struct usb_descriptor_header *) &dm_interface_desc, + (struct usb_descriptor_header *) &dm_hs_in_desc, + (struct usb_descriptor_header *) &dm_hs_out_desc, + NULL, +}; + +/* string descriptors: */ +#define F_DM_IDX 0 + +static struct usb_string dm_string_defs[] = { + [F_DM_IDX].s = "Samsung Android DM", + { /* ZEROES END LIST */ }, +}; + +static struct usb_gadget_strings dm_string_table = { + .language = 0x0409, /* en-us */ + .strings = dm_string_defs, +}; + +static struct usb_gadget_strings *dm_strings[] = { + &dm_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +static int dm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_dm *dm = func_to_dm(f); + struct usb_composite_dev *cdev = f->config->cdev; + int status; + + /* we know alt == 0, so this is an activation or a reset */ + + if (dm->port.in->driver_data) { + DBG(cdev, "reset generic ttyGS%d\n", dm->port_num); + gserial_disconnect(&dm->port); + } else { + DBG(cdev, "activate generic ttyGS%d\n", dm->port_num); + } + dm->port.in_desc = ep_choose(cdev->gadget, + dm->hs.in, dm->fs.in); + dm->port.out_desc = ep_choose(cdev->gadget, + dm->hs.out, dm->fs.out); + status = gserial_connect(&dm->port, dm->port_num); + printk(KERN_DEBUG "usb: %s run generic_connect(%d)", __func__, + dm->port_num); + + if (status < 0) { + printk(KERN_ERR "fail to activate generic ttyGS%d\n", + dm->port_num); + + return status; + } + + return 0; +} + +static void dm_disable(struct usb_function *f) +{ + struct f_dm *dm = func_to_dm(f); + + printk(KERN_DEBUG "usb: %s generic ttyGS%d deactivated\n", __func__, + dm->port_num); + gserial_disconnect(&dm->port); +} + +/*-------------------------------------------------------------------------*/ + +/* serial function driver setup/binding */ + +static int +dm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_dm *dm = func_to_dm(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + dm->data_id = status; + dm_interface_desc.bInterfaceNumber = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_in_desc); + if (!ep) + goto fail; + dm->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &dm_fs_out_desc); + if (!ep) + goto fail; + dm->port.out = ep; + ep->driver_data = cdev; /* claim */ + printk(KERN_INFO "[%s] in =0x%p , out =0x%p\n", __func__, + dm->port.in, dm->port.out); + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(dm_fs_function); + + dm->fs.in = usb_find_endpoint(dm_fs_function, + f->descriptors, &dm_fs_in_desc); + dm->fs.out = usb_find_endpoint(dm_fs_function, + f->descriptors, &dm_fs_out_desc); + + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + dm_hs_in_desc.bEndpointAddress = + dm_fs_in_desc.bEndpointAddress; + dm_hs_out_desc.bEndpointAddress = + dm_fs_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(dm_hs_function); + + dm->hs.in = usb_find_endpoint(dm_hs_function, + f->hs_descriptors, &dm_hs_in_desc); + dm->hs.out = usb_find_endpoint(dm_hs_function, + f->hs_descriptors, &dm_hs_out_desc); + } + + printk(KERN_DEBUG "usb: %s generic ttyGS%d: %s speed IN/%s OUT/%s\n", + __func__, + dm->port_num, + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + dm->port.in->name, dm->port.out->name); + return 0; + +fail: + /* we might as well release our claims on endpoints */ + if (dm->port.out) + dm->port.out->driver_data = NULL; + if (dm->port.in) + dm->port.in->driver_data = NULL; + + printk(KERN_ERR "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +dm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + kfree(func_to_dm(f)); + printk(KERN_DEBUG "usb: %s\n", __func__); +} + +/* + * dm_bind_config - add a generic serial function to a configuration + * @c: the configuration to support the serial instance + * @port_num: /dev/ttyGS* port this interface will use + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gserial_setup() with enough ports to + * handle all the ones it binds. Caller is also responsible + * for calling @gserial_cleanup() before module unload. + */ +int dm_bind_config(struct usb_configuration *c, u8 port_num) +{ + struct f_dm *dm; + int status; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string ID */ + if (dm_string_defs[F_DM_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + dm_string_defs[F_DM_IDX].id = status; + } + + /* allocate and initialize one new instance */ + dm = kzalloc(sizeof *dm, GFP_KERNEL); + if (!dm) + return -ENOMEM; + + dm->port_num = port_num; + + dm->port.func.name = "dm"; + dm->port.func.strings = dm_strings; + dm->port.func.bind = dm_bind; + dm->port.func.unbind = dm_unbind; + dm->port.func.set_alt = dm_set_alt; + dm->port.func.disable = dm_disable; + + status = usb_add_function(c, &dm->port.func); + if (status) + kfree(dm); + return status; +} diff --git a/drivers/usb/gadget/f_ecm.c b/drivers/usb/gadget/f_ecm.c index 544257a..84747ae 100644 --- a/drivers/usb/gadget/f_ecm.c +++ b/drivers/usb/gadget/f_ecm.c @@ -66,6 +66,7 @@ struct f_ecm { struct ecm_ep_descs fs; struct ecm_ep_descs hs; + struct ecm_ep_descs ss; struct usb_ep *notify; struct usb_endpoint_descriptor *notify_desc; @@ -86,7 +87,9 @@ static inline struct f_ecm *func_to_ecm(struct usb_function *f) /* peak (theoretical) bulk transfer rate in bits-per-second */ static inline unsigned ecm_bitrate(struct usb_gadget *g) { - if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) + return 13 * 1024 * 8 * 1000 * 8; + else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) return 13 * 512 * 8 * 1000 * 8; else return 19 * 64 * 1 * 1000 * 8; @@ -274,6 +277,76 @@ static struct usb_descriptor_header *ecm_hs_function[] = { NULL, }; +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_ecm_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(ECM_STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_ecm_intr_comp_desc = { + .bLength = sizeof ss_ecm_intr_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(ECM_STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ss_ecm_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_ecm_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_ecm_bulk_comp_desc = { + .bLength = sizeof ss_ecm_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ecm_ss_function[] = { + /* CDC ECM control descriptors */ + (struct usb_descriptor_header *) &ecm_control_intf, + (struct usb_descriptor_header *) &ecm_header_desc, + (struct usb_descriptor_header *) &ecm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + + /* NOTE: status endpoint might need to be removed */ + (struct usb_descriptor_header *) &ss_ecm_notify_desc, + (struct usb_descriptor_header *) &ss_ecm_intr_comp_desc, + + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ecm_data_nop_intf, + (struct usb_descriptor_header *) &ecm_data_intf, + (struct usb_descriptor_header *) &ss_ecm_in_desc, + (struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_ecm_out_desc, + (struct usb_descriptor_header *) &ss_ecm_bulk_comp_desc, + NULL, +}; + /* string descriptors: */ static struct usb_string ecm_string_defs[] = { @@ -466,7 +539,11 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(ecm->notify); } else { VDBG(cdev, "init ecm ctrl %d\n", intf); - ecm->notify_desc = ep_choose(cdev->gadget, + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) + ecm->notify_desc = ecm->ss.notify; + else + ecm->notify_desc = ep_choose(cdev->gadget, ecm->hs.notify, ecm->fs.notify); } @@ -485,10 +562,16 @@ static int ecm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (!ecm->port.in) { DBG(cdev, "init ecm\n"); - ecm->port.in = ep_choose(cdev->gadget, + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) { + ecm->port.in = ecm->ss.in; + ecm->port.out = ecm->ss.out; + } else { + ecm->port.in = ep_choose(cdev->gadget, ecm->hs.in, ecm->fs.in); - ecm->port.out = ep_choose(cdev->gadget, + ecm->port.out = ep_choose(cdev->gadget, ecm->hs.out, ecm->fs.out); + } } /* CDC Ethernet only sends data in non-default altsettings. @@ -697,6 +780,26 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) f->hs_descriptors, &hs_ecm_notify_desc); } + if (gadget_is_superspeed(c->cdev->gadget)) { + ss_ecm_in_desc.bEndpointAddress = + fs_ecm_in_desc.bEndpointAddress; + ss_ecm_out_desc.bEndpointAddress = + fs_ecm_out_desc.bEndpointAddress; + ss_ecm_notify_desc.bEndpointAddress = + fs_ecm_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(ecm_ss_function); + if (!f->ss_descriptors) + goto fail; + + ecm->ss.in = usb_find_endpoint(ecm_ss_function, + f->ss_descriptors, &ss_ecm_in_desc); + ecm->ss.out = usb_find_endpoint(ecm_ss_function, + f->ss_descriptors, &ss_ecm_out_desc); + ecm->ss.notify = usb_find_endpoint(ecm_ss_function, + f->ss_descriptors, &ss_ecm_notify_desc); + } /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code * until we're activated via set_alt(). @@ -706,6 +809,7 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) ecm->port.close = ecm_close; DBG(cdev, "CDC Ethernet: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", ecm->port.in_ep->name, ecm->port.out_ep->name, ecm->notify->name); @@ -714,6 +818,8 @@ ecm_bind(struct usb_configuration *c, struct usb_function *f) fail: if (f->descriptors) usb_free_descriptors(f->descriptors); + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); if (ecm->notify_req) { kfree(ecm->notify_req->buf); @@ -740,6 +846,8 @@ ecm_unbind(struct usb_configuration *c, struct usb_function *f) DBG(c->cdev, "ecm unbind\n"); + if (gadget_is_superspeed(c->cdev->gadget)) + usb_free_descriptors(f->ss_descriptors); if (gadget_is_dualspeed(c->cdev->gadget)) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); diff --git a/drivers/usb/gadget/f_eem.c b/drivers/usb/gadget/f_eem.c index b3c3042..3c25cf5 100644 --- a/drivers/usb/gadget/f_eem.c +++ b/drivers/usb/gadget/f_eem.c @@ -46,6 +46,7 @@ struct f_eem { struct eem_ep_descs fs; struct eem_ep_descs hs; + struct eem_ep_descs ss; }; static inline struct f_eem *func_to_eem(struct usb_function *f) @@ -123,6 +124,45 @@ static struct usb_descriptor_header *eem_hs_function[] __initdata = { NULL, }; +/* super speed support: */ + +static struct usb_endpoint_descriptor eem_ss_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor eem_ss_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor eem_ss_bulk_comp_desc __initdata = { + .bLength = sizeof eem_ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *eem_ss_function[] __initdata = { + /* CDC EEM control descriptors */ + (struct usb_descriptor_header *) &eem_intf, + (struct usb_descriptor_header *) &eem_ss_in_desc, + (struct usb_descriptor_header *) &eem_ss_bulk_comp_desc, + (struct usb_descriptor_header *) &eem_ss_out_desc, + (struct usb_descriptor_header *) &eem_ss_bulk_comp_desc, + NULL, +}; + /* string descriptors: */ static struct usb_string eem_string_defs[] = { @@ -178,10 +218,16 @@ static int eem_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (!eem->port.in) { DBG(cdev, "init eem\n"); - eem->port.in = ep_choose(cdev->gadget, + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) { + eem->port.in = eem->ss.in; + eem->port.out = eem->ss.out; + } else { + eem->port.in = ep_choose(cdev->gadget, eem->hs.in, eem->fs.in); - eem->port.out = ep_choose(cdev->gadget, + eem->port.out = ep_choose(cdev->gadget, eem->hs.out, eem->fs.out); + } } /* zlps should not occur because zero-length EEM packets @@ -279,7 +325,25 @@ eem_bind(struct usb_configuration *c, struct usb_function *f) f->hs_descriptors, &eem_hs_out_desc); } + if (gadget_is_superspeed(c->cdev->gadget)) { + eem_ss_in_desc.bEndpointAddress = + eem_fs_in_desc.bEndpointAddress; + eem_ss_out_desc.bEndpointAddress = + eem_fs_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(eem_ss_function); + if (!f->ss_descriptors) + goto fail; + + eem->ss.in = usb_find_endpoint(eem_ss_function, + f->ss_descriptors, &eem_ss_in_desc); + eem->ss.out = usb_find_endpoint(eem_ss_function, + f->ss_descriptors, &eem_ss_out_desc); + } + DBG(cdev, "CDC Ethernet (EEM): %s speed IN/%s OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", eem->port.in_ep->name, eem->port.out_ep->name); return 0; @@ -287,6 +351,8 @@ eem_bind(struct usb_configuration *c, struct usb_function *f) fail: if (f->descriptors) usb_free_descriptors(f->descriptors); + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); /* we might as well release our claims on endpoints */ if (eem->port.out) @@ -306,6 +372,8 @@ eem_unbind(struct usb_configuration *c, struct usb_function *f) DBG(c->cdev, "eem unbind\n"); + if (gadget_is_superspeed(c->cdev->gadget)) + usb_free_descriptors(f->ss_descriptors); if (gadget_is_dualspeed(c->cdev->gadget)) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); diff --git a/drivers/usb/gadget/f_mass_storage.c b/drivers/usb/gadget/f_mass_storage.c index efb58f9..4616b1f 100644 --- a/drivers/usb/gadget/f_mass_storage.c +++ b/drivers/usb/gadget/f_mass_storage.c @@ -406,6 +406,11 @@ struct fsg_common { */ char inquiry_string[8 + 16 + 4 + 1]; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + char vendor_string[8 + 1]; + char product_string[16 + 1]; +#endif + struct kref ref; }; @@ -775,8 +780,17 @@ static int do_read(struct fsg_common *common) /* Carry out the file reads */ amount_left = common->data_size_from_cmnd; - if (unlikely(amount_left == 0)) + if (unlikely(amount_left == 0)) { + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + bh->inreq->length = 0; return -EIO; /* No default reply */ + } for (;;) { /* @@ -793,9 +807,22 @@ static int do_read(struct fsg_common *common) amount = min((loff_t)amount, curlun->file_length - file_offset); partial_page = file_offset & (PAGE_CACHE_SIZE - 1); - if (partial_page > 0) - amount = min(amount, (unsigned int)PAGE_CACHE_SIZE - - partial_page); + if (common->gadget->speed != USB_SPEED_SUPER) { + if (partial_page > 0) + amount = min(amount, (unsigned int)PAGE_CACHE_SIZE - + partial_page); + } + + if (common->gadget->speed == USB_SPEED_SUPER) { + /* The packet should be multiple of MPS, which is 1024. */ + if (amount < amount_left) { + unsigned mod = amount % common->bulk_out_maxpacket; + if (mod && (amount - mod)) { + // not a multiple of maxpacket + amount -= mod; + } + } + } /* Wait for the next buffer to become available */ bh = common->next_buffhd_to_fill; @@ -876,7 +903,7 @@ static int do_write(struct fsg_common *common) int get_some_more; u32 amount_left_to_req, amount_left_to_write; loff_t usb_offset, file_offset, file_offset_tmp; - unsigned int amount; + unsigned int amount, diff; unsigned int partial_page; ssize_t nwritten; int rc; @@ -946,9 +973,12 @@ static int do_write(struct fsg_common *common) amount = min((loff_t)amount, curlun->file_length - usb_offset); partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); - if (partial_page > 0) - amount = min(amount, - (unsigned int)PAGE_CACHE_SIZE - partial_page); + if (common->gadget->speed != USB_SPEED_SUPER) { + if (partial_page > 0) + amount = min(amount, + (unsigned int) PAGE_CACHE_SIZE - + partial_page); + } if (amount == 0) { get_some_more = 0; @@ -969,6 +999,17 @@ static int do_write(struct fsg_common *common) continue; } + if (common->gadget->speed == USB_SPEED_SUPER) { + /* The packet should be multiple of MPS, which is 1024. */ + if (amount < amount_left_to_write) { + unsigned mod = amount % common->bulk_out_maxpacket; + if (mod && (amount - mod)) { + // not a multiple of maxpacket + amount -= mod; + } + } + } + /* Get the next buffer */ usb_offset += amount; common->usb_amount_left -= amount; @@ -1008,6 +1049,12 @@ static int do_write(struct fsg_common *common) } amount = bh->outreq->actual; + if (amount > bh->outreq->length) { + diff = amount - bh->outreq->length; + amount = bh->outreq->length; + } else { + diff = 0; + } if (curlun->file_length - file_offset < amount) { LERROR(curlun, "write %u @ %llu beyond end %llu\n", @@ -1040,6 +1087,15 @@ static int do_write(struct fsg_common *common) amount_left_to_write -= nwritten; common->residue -= nwritten; + if (diff) { + LDBG(curlun, "host sent too much data: %d\n", diff); + LDBG(curlun, "data_size=%d residue=%d amt_left=%d\n", + common->data_size, common->residue, common->usb_amount_left); + common->data_size = common->residue; + common->usb_amount_left = 0; + break; + } + /* If an error occurred, report it and its position */ if (nwritten < amount) { curlun->sense_data = SS_WRITE_ERROR; @@ -1200,6 +1256,9 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) { struct fsg_lun *curlun = common->curlun; u8 *buf = (u8 *) bh->buf; +#if defined(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE) + static char new_product_name[16 + 1]; +#endif if (!curlun) { /* Unsupported LUNs are okay */ common->bad_lun_okay = 1; @@ -1217,6 +1276,23 @@ static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) buf[5] = 0; /* No special options */ buf[6] = 0; buf[7] = 0; + +#if defined(CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE) + strncpy(new_product_name, common->product_string, 16); + new_product_name[16] = '\0'; + if (common->product_string[0] && + strlen(common->product_string) <= 11 && /* check string length */ + common->lun > 0) { + strncat(new_product_name, " Card", 16); + new_product_name[16] = '\0'; + } + + snprintf(common->inquiry_string, + sizeof common->inquiry_string, + "%-8s%-16s%04x", + common->vendor_string, + new_product_name, 1); +#endif memcpy(buf + 8, common->inquiry_string, sizeof common->inquiry_string); return 36; } @@ -2330,6 +2406,17 @@ static int enable_endpoint(struct fsg_common *common, struct usb_ep *ep, int rc; ep->driver_data = common; + + if (common->gadget->speed == USB_SPEED_SUPER) { + if (ep == common->fsg->bulk_in) + ep->maxburst = fsg_ss_bulk_in_comp_desc.bMaxBurst; + else if (ep == common->fsg->bulk_out) + ep->maxburst = fsg_ss_bulk_out_comp_desc.bMaxBurst; + else + ep->maxburst = 0; + } else + ep->maxburst = 0; + rc = usb_ep_enable(ep, d); if (rc) ERROR(common, "can't enable %s, result %d\n", ep->name, rc); @@ -2396,14 +2483,22 @@ reset: fsg = common->fsg; /* Enable the endpoints */ - d = fsg_ep_desc(common->gadget, + if (gadget_is_superspeed(common->gadget) && + common->gadget->speed == USB_SPEED_SUPER) + d = &fsg_ss_bulk_in_desc; + else + d = fsg_ep_desc(common->gadget, &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); rc = enable_endpoint(common, fsg->bulk_in, d); if (rc) goto reset; fsg->bulk_in_enabled = 1; - d = fsg_ep_desc(common->gadget, + if (gadget_is_superspeed(common->gadget) && + common->gadget->speed == USB_SPEED_SUPER) + d = &fsg_ss_bulk_out_desc; + else + d = fsg_ep_desc(common->gadget, &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); rc = enable_endpoint(common, fsg->bulk_out, d); if (rc) @@ -2779,6 +2874,9 @@ static struct fsg_common *fsg_common_init(struct fsg_common *common, curlun->ro = lcfg->cdrom || lcfg->ro; curlun->initially_ro = curlun->ro; curlun->removable = lcfg->removable; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + curlun->nofua = lcfg->nofua; +#endif curlun->dev.release = fsg_lun_release; curlun->dev.parent = &gadget->dev; /* curlun->dev.driver = &fsg_driver.driver; XXX */ @@ -2856,6 +2954,15 @@ buffhds_first_it: : "File-CD Gadget"), i); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* Default INQUIRY strings */ + strncpy(common->vendor_string, "SAMSUNG", + sizeof(common->vendor_string) - 1); + strncpy(common->product_string, "File-Stor Gadget", + sizeof(common->product_string) - 1); + common->product_string[sizeof(common->product_string) - 1] = '\0'; +#endif + /* * Some peripheral controllers are known not to be able to * halt bulk endpoints correctly. If one of them is present, @@ -2976,6 +3083,7 @@ static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) fsg_common_put(common); usb_free_descriptors(fsg->function.descriptors); usb_free_descriptors(fsg->function.hs_descriptors); + usb_free_descriptors(fsg->function.ss_descriptors); kfree(fsg); } @@ -3026,6 +3134,28 @@ static int fsg_bind(struct usb_configuration *c, struct usb_function *f) } } + if (gadget_is_superspeed(gadget)) { + unsigned max_burst; + + /* Calculate bMaxBurst, we know packet size is 1024 */ + max_burst = min_t(unsigned, FSG_BUFLEN / 1024, 15); + + fsg_ss_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst; + + fsg_ss_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst; + + f->ss_descriptors = usb_copy_descriptors(fsg_ss_function); + if (unlikely(!f->ss_descriptors)) { + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + return -ENOMEM; + } + } + return 0; autoconf_fail: @@ -3052,7 +3182,7 @@ static int fsg_bind_config(struct usb_composite_dev *cdev, if (unlikely(!fsg)) return -ENOMEM; - fsg->function.name = FSG_DRIVER_DESC; + fsg->function.name = "mass_storage"; fsg->function.strings = fsg_strings_array; fsg->function.bind = fsg_bind; fsg->function.unbind = fsg_unbind; diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c new file mode 100644 index 0000000..04c6053 --- /dev/null +++ b/drivers/usb/gadget/f_mtp.c @@ -0,0 +1,1342 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> + +#include <linux/types.h> +#include <linux/file.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/usb.h> +#include <linux/usb_usual.h> +#include <linux/usb/ch9.h> +#include <linux/usb/f_mtp.h> + +#define MTP_BULK_BUFFER_SIZE 16384 +#define INTR_BUFFER_SIZE 28 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* values for mtp_dev.state */ +#define STATE_OFFLINE 0 /* initial state, disconnected */ +#define STATE_READY 1 /* ready for userspace calls */ +#define STATE_BUSY 2 /* processing userspace calls */ +#define STATE_CANCELED 3 /* transaction canceled by host */ +#define STATE_ERROR 4 /* error from completion routine */ + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 +#define INTR_REQ_MAX 5 + +/* ID for Microsoft MTP OS String */ +#define MTP_OS_STRING_ID 0xEE + +/* MTP class reqeusts */ +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +/* constants for device status */ +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 + +static const char mtp_shortname[] = "mtp_usb"; + +struct mtp_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_intr; + + int state; + + /* synchronize access to our device file */ + atomic_t open_excl; + /* to enforce only one ioctl at a time */ + atomic_t ioctl_excl; + + struct list_head tx_idle; + struct list_head intr_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + wait_queue_head_t intr_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + int rx_done; + + /* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and + * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue + */ + struct workqueue_struct *wq; + struct work_struct send_file_work; + struct work_struct receive_file_work; + struct file *xfer_file; + loff_t xfer_file_offset; + int64_t xfer_file_length; + unsigned xfer_send_header; + uint16_t xfer_command; + uint32_t xfer_transaction_id; + int xfer_result; +}; + +static struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor mtp_superspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor mtp_superspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor mtp_superspeed_bulk_comp_desc = { + .bLength = sizeof adb_superspeed_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_endpoint_descriptor mtp_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_intr_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE), + .bInterval = 6, +}; + +static struct usb_ss_ep_comp_descriptor mtp_intr_comp_desc = { + .bLength = sizeof mtp_intr_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = __constant_cpu_to_le16(INTR_BUFFER_SIZE), +}; + +static struct usb_descriptor_header *fs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_superspeed_in_desc, + (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &mtp_superspeed_out_desc, + (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + (struct usb_descriptor_header *) &mtp_intr_comp_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *ss_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_superspeed_in_desc, + (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &mtp_superspeed_out_desc, + (struct usb_descriptor_header *) &mtp_superspeed_bulk_comp_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + (struct usb_descriptor_header *) &mtp_intr_comp_desc, + NULL, +}; + +static struct usb_string mtp_string_defs[] = { + /* Naming interface "MTP" so libmtp will recognize us */ + [INTERFACE_STRING_INDEX].s = "MTP", + { }, /* end of list */ +}; + +static struct usb_gadget_strings mtp_string_table = { + .language = 0x0409, /* en-US */ + .strings = mtp_string_defs, +}; + +static struct usb_gadget_strings *mtp_strings[] = { + &mtp_string_table, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtp_os_string[] = { + 18, /* sizeof(mtp_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtp_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtp_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtp_ext_config_desc_header header; + struct mtp_ext_config_desc_function function; +} mtp_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = __constant_cpu_to_le16(1), + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +struct mtp_device_status { + __le16 wLength; + __le16 wCode; +}; + +/* temporary variable used between mtp_open() and mtp_gadget_bind() */ +static struct mtp_dev *_mtp_dev; + +static inline struct mtp_dev *func_to_mtp(struct usb_function *f) +{ + return container_of(f, struct mtp_dev, function); +} + +static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtp_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int mtp_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void mtp_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void mtp_req_put(struct mtp_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request +*mtp_req_get(struct mtp_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->read_wq); +} + +static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + mtp_req_put(dev, &dev->intr_idle, req); + + wake_up(&dev->intr_wq); +} + +static int mtp_create_bulk_endpoints(struct mtp_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc, + struct usb_endpoint_descriptor *intr_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, intr_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_intr = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + mtp_req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + for (i = 0; i < INTR_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + mtp_req_put(dev, &dev->intr_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "mtp_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t mtp_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int r = count, xfer; + int ret = 0; + + DBG(cdev, "mtp_read(%d)\n", count); + + if (count > MTP_BULK_BUFFER_SIZE) + return -EINVAL; + + /* we will block until we're online */ + DBG(cdev, "mtp_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + dev->state != STATE_OFFLINE); + if (ret < 0) { + r = ret; + goto done; + } + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->state == STATE_BUSY) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_read returning %d\n", r); + return r; +} + +static ssize_t mtp_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int sendZLP = 0; + int ret; + + DBG(cdev, "mtp_write(%d)\n", count); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + return -ENODEV; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + if (dev->state != STATE_BUSY) { + DBG(cdev, "mtp_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + if (xfer && copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "mtp_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_write returning %d\n", r); + return r; +} + +/* read from a local file and write to USB */ +static void send_file_work(struct work_struct *data) { + struct mtp_dev *dev = container_of(data, struct mtp_dev, send_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct mtp_data_header *header; + struct file *filp; + loff_t offset; + int64_t count; + int xfer, ret, hdr_size; + int r = 0; + int sendZLP = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + if (dev->xfer_send_header) { + hdr_size = sizeof(struct mtp_data_header); + count += hdr_size; + } else { + hdr_size = 0; + } + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = mtp_req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + break; + } + if (!req) { + r = ret; + break; + } + + if (count > MTP_BULK_BUFFER_SIZE) + xfer = MTP_BULK_BUFFER_SIZE; + else + xfer = count; + + if (hdr_size) { + /* prepend MTP data header */ + header = (struct mtp_data_header *)req->buf; + header->length = __cpu_to_le32(count); + header->type = __cpu_to_le16(2); /* data packet */ + header->command = __cpu_to_le16(dev->xfer_command); + header->transaction_id = __cpu_to_le32(dev->xfer_transaction_id); + } + + ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret + hdr_size; + hdr_size = 0; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "send_file_work: xfer error %d\n", ret); + dev->state = STATE_ERROR; + r = -EIO; + break; + } + + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + mtp_req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "send_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +/* read from USB and write to a local file */ +static void receive_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, receive_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + struct file *filp; + loff_t offset; + int64_t count; + int ret, cur_buf = 0; + int r = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "receive_file_work(%lld)\n", count); + + while (count > 0 || write_req) { + if (count > 0) { + /* queue a request */ + read_req = dev->rx_req[cur_buf]; + cur_buf = (cur_buf + 1) % RX_REQ_MAX; + + read_req->length = (count > MTP_BULK_BUFFER_SIZE + ? MTP_BULK_BUFFER_SIZE : count); + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + } + + if (write_req) { + DBG(cdev, "rx %p %d\n", write_req, write_req->actual); + ret = vfs_write(filp, write_req->buf, write_req->actual, + &offset); + DBG(cdev, "vfs_write %d\n", ret); + if (ret != write_req->actual) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + write_req = NULL; + } + + if (read_req) { + /* wait for our last read to complete */ + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, read_req); + break; + } + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* short packet is used to signal EOF for sizes > 4 gig */ + DBG(cdev, "got short packet\n"); + count = 0; + } + + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "receive_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req= NULL; + int ret; + int length = event->length; + + DBG(dev->cdev, "mtp_send_event(%d)\n", event->length); + + if (length < 0 || length > INTR_BUFFER_SIZE) + return -EINVAL; + if (dev->state == STATE_OFFLINE) + return -ENODEV; + + ret = wait_event_interruptible_timeout(dev->intr_wq, + (req = mtp_req_get(dev, &dev->intr_idle)), msecs_to_jiffies(1000)); + if (!req) + return -ETIME; + + if (copy_from_user(req->buf, (void __user *)event->data, length)) { + mtp_req_put(dev, &dev->intr_idle, req); + return -EFAULT; + } + req->length = length; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + mtp_req_put(dev, &dev->intr_idle, req); + + return ret; +} + +static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct mtp_dev *dev = fp->private_data; + struct file *filp = NULL; + int ret = -EINVAL; + + if (mtp_lock(&dev->ioctl_excl)) + return -EBUSY; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + case MTP_SEND_FILE_WITH_HEADER: + { + struct mtp_file_range mfr; + struct work_struct *work; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + ret = -ECANCELED; + goto out; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + ret = -ENODEV; + goto out; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + /* hold a reference to the file while we are working with it */ + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + /* write the parameters */ + dev->xfer_file = filp; + dev->xfer_file_offset = mfr.offset; + dev->xfer_file_length = mfr.length; + smp_wmb(); + + if (code == MTP_SEND_FILE_WITH_HEADER) { + work = &dev->send_file_work; + dev->xfer_send_header = 1; + dev->xfer_command = mfr.command; + dev->xfer_transaction_id = mfr.transaction_id; + } else if (code == MTP_SEND_FILE) { + work = &dev->send_file_work; + dev->xfer_send_header = 0; + } else { + work = &dev->receive_file_work; + } + + /* We do the file transfer on a work queue so it will run + * in kernel context, which is necessary for vfs_read and + * vfs_write to use our buffers in the kernel address space. + */ + queue_work(dev->wq, work); + /* wait for operation to complete */ + flush_workqueue(dev->wq); + fput(filp); + + /* read the result */ + smp_rmb(); + ret = dev->xfer_result; + break; + } + case MTP_SEND_EVENT: + { + struct mtp_event event; + /* return here so we don't change dev->state below, + * which would interfere with bulk transfer state. + */ + if (copy_from_user(&event, (void __user *)value, sizeof(event))) + ret = -EFAULT; + else + ret = mtp_send_event(dev, &event); + goto out; + } + } + +fail: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + ret = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); +out: + mtp_unlock(&dev->ioctl_excl); + DBG(dev->cdev, "ioctl returning %d\n", ret); + return ret; +} + +static int mtp_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_open\n"); + if (mtp_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + /* clear any error condition */ + if (_mtp_dev->state != STATE_OFFLINE) + _mtp_dev->state = STATE_READY; + + fp->private_data = _mtp_dev; + return 0; +} + +static int mtp_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_release\n"); + + mtp_unlock(&_mtp_dev->open_excl); + return 0; +} + +/* file operations for /dev/mtp_usb */ +static const struct file_operations mtp_fops = { + .owner = THIS_MODULE, + .read = mtp_read, + .write = mtp_write, + .unlocked_ioctl = mtp_ioctl, + .open = mtp_open, + .release = mtp_release, +}; + +static struct miscdevice mtp_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = mtp_shortname, + .fops = &mtp_fops, +}; + +static int mtp_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = _mtp_dev; + int value = -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); + unsigned long flags; + + VDBG(cdev, "mtp_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTP_OS_STRING_ID) { + value = (w_length < sizeof(mtp_os_string) + ? w_length : sizeof(mtp_os_string)); + memcpy(cdev->req->buf, mtp_os_string, value); + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle MTP OS descriptor */ + DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtp_ext_config_desc) ? + w_length : sizeof(mtp_ext_config_desc)); + memcpy(cdev->req->buf, &mtp_ext_config_desc, value); + } + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + DBG(cdev, "class request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0 + && w_value == 0) { + DBG(cdev, "MTP_REQ_CANCEL\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state == STATE_BUSY) { + dev->state = STATE_CANCELED; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We need to queue a request to read the remaining + * bytes, but we don't actually need to look at + * the contents. + */ + value = w_length; + } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS + && w_index == 0 && w_value == 0) { + struct mtp_device_status *status = cdev->req->buf; + status->wLength = + __constant_cpu_to_le16(sizeof(*status)); + + DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n"); + spin_lock_irqsave(&dev->lock, flags); + /* device status is "busy" until we report + * the cancelation to userspace + */ + if (dev->state == STATE_CANCELED) + status->wCode = + __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY); + else + status->wCode = + __cpu_to_le16(MTP_RESPONSE_OK); + spin_unlock_irqrestore(&dev->lock, flags); + value = sizeof(*status); + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + ERROR(cdev, "%s setup response queue error\n", __func__); + } + return value; +} + +static int +mtp_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtp_dev *dev = func_to_mtp(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "mtp_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + mtp_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, + &mtp_fullspeed_out_desc, &mtp_intr_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + mtp_highspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_highspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + + if (gadget_is_superspeed(c->cdev->gadget)) { + mtp_superspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_superspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_request *req; + int i; + + while ((req = mtp_req_get(dev, &dev->tx_idle))) + mtp_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + mtp_request_free(dev->rx_req[i], dev->ep_out); + while ((req = mtp_req_get(dev, &dev->intr_idle))) + mtp_request_free(req, dev->ep_intr); + dev->state = STATE_OFFLINE; +} + +static int mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) + ret = usb_ep_enable(dev->ep_in, &mtp_superspeed_in_desc); + else + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &mtp_highspeed_in_desc, + &mtp_fullspeed_in_desc)); + if (ret) + return ret; + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) + ret = usb_ep_enable(dev->ep_out, &mtp_superspeed_out_desc); + else + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &mtp_highspeed_out_desc, + &mtp_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + ret = usb_ep_enable(dev->ep_intr, &mtp_intr_desc); + if (ret) { + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_in); + return ret; + } + dev->state = STATE_READY; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void mtp_function_disable(struct usb_function *f) +{ + struct mtp_dev *dev = func_to_mtp(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "mtp_function_disable\n"); + dev->state = STATE_OFFLINE; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_intr); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int mtp_bind_config(struct usb_configuration *c, bool ptp_config) +{ + struct mtp_dev *dev = _mtp_dev; + int ret = 0; + + printk(KERN_INFO "mtp_bind_config\n"); + + /* allocate a string ID for our interface */ + if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; + mtp_interface_desc.iInterface = ret; + } + + dev->cdev = c->cdev; + dev->function.name = "mtp"; + dev->function.strings = mtp_strings; + if (ptp_config) { + dev->function.descriptors = fs_ptp_descs; + dev->function.hs_descriptors = hs_ptp_descs; + dev->function.ss_descriptors = ss_ptp_descs; + } else { + dev->function.descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + dev->function.ss_descriptors = ss_mtp_descs; + } + dev->function.bind = mtp_function_bind; + dev->function.unbind = mtp_function_unbind; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + + return usb_add_function(c, &dev->function); +} + +static int mtp_setup(void) +{ + struct mtp_dev *dev; + int ret; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + init_waitqueue_head(&dev->intr_wq); + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + INIT_LIST_HEAD(&dev->intr_idle); + + dev->wq = create_singlethread_workqueue("f_mtp"); + if (!dev->wq) { + ret = -ENOMEM; + goto err1; + } + INIT_WORK(&dev->send_file_work, send_file_work); + INIT_WORK(&dev->receive_file_work, receive_file_work); + + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err2; + + return 0; + +err2: + destroy_workqueue(dev->wq); +err1: + _mtp_dev = NULL; + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static void mtp_cleanup(void) +{ + struct mtp_dev *dev = _mtp_dev; + + if (!dev) + return; + + misc_deregister(&mtp_device); + destroy_workqueue(dev->wq); + _mtp_dev = NULL; + kfree(dev); +} diff --git a/drivers/usb/gadget/f_mtp.h b/drivers/usb/gadget/f_mtp.h new file mode 100644 index 0000000..5892e3f --- /dev/null +++ b/drivers/usb/gadget/f_mtp.h @@ -0,0 +1,56 @@ +/* + * Gadget Driver for Android MTP + * + * Copyright (C) 2009 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __F_MTP_H +#define __F_MTP_H + + +#define MTP_MAX_PACKET_LEN_FROM_APP 22 + +#define MTP_ACM_ENABLE 0 +#define MTP_ONLY_ENABLE 1 +#define MTP_DISABLE 2 +#define MTP_CLEAR_HALT 3 +#define MTP_WRITE_INT_DATA 4 +#define SET_MTP_USER_PID 5 +#define GET_SETUP_DATA 6 +#define SET_SETUP_DATA 7 +#define SEND_RESET_ACK 8 +#define SET_ZLP_DATA 9 +#define GET_HIGH_FULL_SPEED 10 +#define SIG_SETUP 44 + +/*PIMA15740-2000 spec*/ +#define USB_PTPREQUEST_CANCELIO 0x64 /* Cancel request */ +#define USB_PTPREQUEST_GETEVENT 0x65 /* Get extened event data */ +#define USB_PTPREQUEST_RESET 0x66 /* Reset Device */ +#define USB_PTPREQUEST_GETSTATUS 0x67 /* Get Device Status */ +#define USB_PTPREQUEST_CANCELIO_SIZE 6 +#define USB_PTPREQUEST_GETSTATUS_SIZE 12 + + + +int mtp_function_add(struct usb_configuration *c); +int mtp_function_config_changed(struct usb_composite_dev *cdev, + struct usb_configuration *c); +int mtp_enable(void); +void mtp_function_enable(int enable); + +struct usb_mtp_ctrlrequest { + struct usb_ctrlrequest setup; +}; + +#endif /* __F_MTP_H */ diff --git a/drivers/usb/gadget/f_mtp_samsung.c b/drivers/usb/gadget/f_mtp_samsung.c new file mode 100644 index 0000000..aa58024 --- /dev/null +++ b/drivers/usb/gadget/f_mtp_samsung.c @@ -0,0 +1,1449 @@ +/* + * drivers/usb/gadget/f_mtp_samsung.c + * + * Function Driver for USB MTP, + * f_mtp_samsung.c -- MTP Driver, for MTP development, + * + * Copyright (C) 2009 by Samsung Electronics, + * Author:Deepak M.G. <deepak.guru@samsung.com>, + * Author:Madhukar.J <madhukar.j@samsung.com>, + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + */ + +/* + * f_mtp_samsung.c file is the driver for MTP device. Totally three + * EndPoints will be configured in which 2 Bulk End Points + * and 1 Interrupt End point. This driver will also register as + * misc driver and exposes file operation funtions to user space. + */ + +/* Includes */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/miscdevice.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/usb.h> +#include <linux/usb_usual.h> +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> +#include <linux/hardirq.h> +#include <linux/sched.h> +#include <linux/usb/f_accessory.h> +#include <asm-generic/siginfo.h> +#include <linux/usb/android_composite.h> +#include <linux/kernel.h> +#include "f_mtp.h" +#include "gadget_chips.h" + +/*-------------------------------------------------------------------------*/ +/*Only for Debug*/ +#define DEBUG_MTP 0 +/*#define CSY_TEST */ + +#if DEBUG_MTP +#define DEBUG_MTP_SETUP +#define DEBUG_MTP_READ +#define DEBUG_MTP_WRITE + +#else +#undef DEBUG_MTP_SETUP +#undef DEBUG_MTP_READ +#undef DEBUG_MTP_WRITE +#endif + +/*#define DEBUG_MTP_SETUP*/ +/*#define DEBUG_MTP_READ*/ +/*#define DEBUG_MTP_WRITE*/ + +#ifdef DEBUG_MTP_SETUP +#define DEBUG_MTPB(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_MTPB(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_MTP_READ +#define DEBUG_MTPR(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_MTPR(fmt, args...) do {} while (0) +#endif +#ifdef DEBUG_MTP_WRITE +#define DEBUG_MTPW(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_MTPW(fmt, args...) do {} while (0) +#endif +/*-------------------------------------------------------------------------*/ + +#define MTPG_BULK_BUFFER_SIZE 4096 +#define INT_MAX_PACKET_SIZE 10 + +/* number of rx and tx requests to allocate */ +#define MTPG_RX_REQ_MAX 4 +#define MTPG_MTPG_TX_REQ_MAX 4 + +/* ID for Microsoft MTP OS String */ +#define MTPG_OS_STRING_ID 0xEE + +#define DRIVER_NAME "usb_mtp_gadget" + +static const char mtpg_longname[] = "mtp"; +static const char shortname[] = DRIVER_NAME; +static int mtp_pid; + +/* MTP Device Structure*/ +struct mtpg_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + struct usb_gadget *gadget; + + spinlock_t lock; + + u8 config; + int online; + int error; + int read_ready; + struct list_head tx_idle; + struct list_head rx_idle; + struct list_head rx_done; + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + + struct usb_request *read_req; + unsigned char *read_buf; + unsigned read_count; + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; + struct usb_ep *int_in; + struct usb_request *notify_req; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t ioctl_excl; + atomic_t open_excl; + atomic_t wintfd_excl; + char cancel_io_buf[USB_PTPREQUEST_CANCELIO_SIZE+1]; +}; + +/* Global mtpg_dev Structure +* the_mtpg variable be used between mtpg_open() and mtpg_function_bind() */ +static struct mtpg_dev *the_mtpg; + +/* Three full-speed and high-speed endpoint descriptors: bulk-in, bulk-out, + * and interrupt-in. */ + +struct usb_interface_descriptor mtpg_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor fs_mtpg_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor fs_mtpg_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor int_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = 0x04, +}; + +static struct usb_descriptor_header *fs_mtpg_desc[] = { + (struct usb_descriptor_header *) &mtpg_interface_desc, + (struct usb_descriptor_header *) &fs_mtpg_in_desc, + (struct usb_descriptor_header *) &fs_mtpg_out_desc, + (struct usb_descriptor_header *) &int_fs_notify_desc, + NULL, +}; + +static struct usb_endpoint_descriptor hs_mtpg_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + /*bEndpointAddress copied from fs_mtpg_in_desc + during mtpg_function_bind()*/ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor hs_mtpg_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + /*bEndpointAddress copied from fs_mtpg_out_desc + during mtpg_function_bind()*/ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ +}; + +static struct usb_endpoint_descriptor int_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(64), + .bInterval = INT_MAX_PACKET_SIZE + 4, +}; + +static struct usb_descriptor_header *hs_mtpg_desc[] = { + (struct usb_descriptor_header *) &mtpg_interface_desc, + (struct usb_descriptor_header *) &hs_mtpg_in_desc, + (struct usb_descriptor_header *) &hs_mtpg_out_desc, + (struct usb_descriptor_header *) &int_hs_notify_desc, + NULL +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &fs_mtpg_in_desc, + (struct usb_descriptor_header *) &fs_mtpg_out_desc, + (struct usb_descriptor_header *) &int_fs_notify_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &hs_mtpg_in_desc, + (struct usb_descriptor_header *) &hs_mtpg_out_desc, + (struct usb_descriptor_header *) &int_hs_notify_desc, + NULL, +}; + +/* string IDs are assigned dynamically */ +#define F_MTP_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +/* default serial number takes at least two packets */ +static const char serial[] = "0123456789.0123456789.0123456789"; + +static struct usb_string strings_dev_mtp[] = { + [F_MTP_IDX].s = "MTP", + [STRING_PRODUCT_IDX].s = mtpg_longname, + [STRING_SERIAL_IDX].s = serial, + { }, /* end of list */ +}; + +static struct usb_gadget_strings stringtab_mtp = { + .language = 0x0409, /* en-us */ + .strings = strings_dev_mtp, +}; + +static struct usb_gadget_strings *mtpg_dev_strings[] = { + &stringtab_mtp, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtpg_os_string[] = { + 18, /* sizeof(mtpg_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtpg_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtpg_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtpg_ext_config_desc_header header; + struct mtpg_ext_config_desc_function function; +} mtpg_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32 + (sizeof(mtpg_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = __constant_cpu_to_le16(1), + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +/* Function : Change config for multi configuration + * Parameter : int conf_num (config number) + * 0 - use mtp only without Samsung USB Driver + * 1 - use mtp + acm with Samsung USB Driver + * Description + * Below function is for samsung multi configuration + * feature made by soonyong,cho. + * Please add below handler to set_config_desc of function. + * Date : 2011-08-03 + */ +static int mtp_set_config_desc(int conf_num) +{ + switch (conf_num) { + case 0: + mtpg_interface_desc.bInterfaceClass = + USB_CLASS_VENDOR_SPEC; + mtpg_interface_desc.bInterfaceSubClass = + USB_SUBCLASS_VENDOR_SPEC; + mtpg_interface_desc.bInterfaceProtocol = + 0x0; + break; + case 1: + mtpg_interface_desc.bInterfaceClass = + USB_CLASS_STILL_IMAGE; + mtpg_interface_desc.bInterfaceSubClass = + 0x01; + mtpg_interface_desc.bInterfaceProtocol = + 0x01; + break; + + } + return 1; +} + +/* ------------------------------------------------------------------------- + * Main Functionalities Start! + * ------------------------------------------------------------------------- */ +static inline struct mtpg_dev *mtpg_func_to_dev(struct usb_function *f) +{ + return container_of(f, struct mtpg_dev, function); +} + +static inline int _lock(atomic_t *excl) +{ + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void mtpg_req_put(struct mtpg_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *mtpg_req_get(struct mtpg_dev *dev, + struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + + return req; +} + +static int mtp_send_signal(int value) +{ + int ret; + struct siginfo info; + struct task_struct *t; + memset(&info, 0, sizeof(struct siginfo)); + info.si_signo = SIG_SETUP; + info.si_code = SI_QUEUE; + info.si_int = value; + rcu_read_lock(); + t = pid_task(find_vpid(mtp_pid), PIDTYPE_PID); + if (t == NULL) { + printk(KERN_DEBUG "no such pid\n"); + rcu_read_unlock(); + return -ENODEV; + } + + rcu_read_unlock(); + /*send the signal*/ + ret = send_sig_info(SIG_SETUP, &info, t); + if (ret < 0) { + printk(KERN_ERR "[%s]error sending signal\n", __func__); + return ret; + } + return 0; + +} + +static int mtpg_open(struct inode *ip, struct file *fp) +{ + printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__); + + if (_lock(&the_mtpg->open_excl)) { + printk(KERN_ERR "mtpg_open fn mtpg device busy\n"); + return -EBUSY; + } + + fp->private_data = the_mtpg; + + /* clear the error latch */ + + DEBUG_MTPB("[%s] mtpg_open and clearing the error = 0\n", __func__); + + the_mtpg->error = 0; + + return 0; +} + +static ssize_t mtpg_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtpg_dev *dev = fp->private_data; + struct usb_request *req; + int r = count, xfer; + int ret; + + DEBUG_MTPR("[%s] and count = (%d)\n", __func__, count); + + if (_lock(&dev->read_excl)) + return -EBUSY; + + while (!((dev->online || dev->error) && dev->read_ready)) { + DEBUG_MTPR("[%s] and line is = %d\n", __func__, __LINE__); + ret = wait_event_interruptible(dev->read_wq, + ((dev->online || dev->error) && dev->read_ready)); + if (ret < 0) { + _unlock(&dev->read_excl); + printk(KERN_DEBUG "[%s]line is = %d,mtp_read ret<0\n", + __func__, __LINE__); + return ret; + } + } + + while (count > 0) { + DEBUG_MTPR("[%s] and line is = %d\n", __func__, __LINE__); + + if (dev->error) { + r = -EIO; + printk(KERN_ERR "[%s]\t%d:dev->error so break r=%d\n", + __func__, __LINE__, r); + break; + } + + /* if we have idle read requests, get them queued */ + DEBUG_MTPR("[%s]\t%d: get request\n", __func__, __LINE__); + while ((req = mtpg_req_get(dev, &dev->rx_idle))) { +requeue_req: + req->length = MTPG_BULK_BUFFER_SIZE; + DEBUG_MTPR("[%s]\t%d:usb-ep-queue\n", + __func__, __LINE__); + ret = usb_ep_queue(dev->bulk_out, req, GFP_ATOMIC); + + DEBUG_MTPR("[%s]\t%d:Endpoint: %s\n", + __func__, __LINE__, dev->bulk_out->name); + + if (ret < 0) { + r = -EIO; + dev->error = 1; + mtpg_req_put(dev, &dev->rx_idle, req); + printk(KERN_ERR "[%s]line[%d]FAIL r=%d\n", + __func__, __LINE__, r); + goto fail; + } else { + DEBUG_MTPR("[%s]rx req queue%p\n", + __func__, req); + } + } + + DEBUG_MTPR("[%s]\t%d:read_count = %d\n", + __func__, __LINE__, dev->read_count); + + /* if we have data pending, give it to userspace */ + if (dev->read_count > 0) { + DEBUG_MTPR("[%s]\t%d: read_count = %d\n", + __func__, __LINE__, dev->read_count); + if (dev->read_count < count) + xfer = dev->read_count; + else + xfer = count; + + DEBUG_MTPR("[%s]copy_to_user 0x%x bytes on EP %p\n", + __func__, dev->read_count, dev->bulk_out); + + if (copy_to_user(buf, dev->read_buf, xfer)) { + r = -EFAULT; + printk(KERN_ERR "[%s]%d:cpytouer fail r=%d\n", + __func__, __LINE__, r); + break; + } + + dev->read_buf += xfer; + dev->read_count -= xfer; + buf += xfer; + count -= xfer; + + /* if we've emptied the buffer, release the request */ + if (dev->read_count == 0) { + DEBUG_MTPR("[%s] and line is = %d\n", + __func__, __LINE__); + mtpg_req_put(dev, &dev->rx_idle, dev->read_req); + dev->read_req = 0; + } + + /*Updating the buffer size and returnung + from mtpg_read */ + r = xfer; + DEBUG_MTPR("[%s] \t %d: returning lenght %d\n", + __func__, __LINE__, r); + goto fail; + } + + /* wait for a request to complete */ + req = 0; + DEBUG_MTPR("[%s] and line is = %d\n", __func__, __LINE__); + ret = wait_event_interruptible(dev->read_wq, + ((req = mtpg_req_get(dev, &dev->rx_done)) + || dev->error)); + DEBUG_MTPR("[%s]\t%d: dev->error %d and req = %p\n", + __func__, __LINE__, dev->error, req); + + if (req != 0) { + /* if we got a 0-len one we need to put it back into + ** service. if we made it the current read req we'd + ** be stuck forever + */ + if (req->actual == 0) + goto requeue_req; + + dev->read_req = req; + dev->read_count = req->actual; + dev->read_buf = req->buf; + + DEBUG_MTPR("[%s]\t%d: rx_req=%p req->actual=%d\n", + __func__, __LINE__, req, req->actual); + } + + if (ret < 0) { + r = ret; + printk(KERN_DEBUG "[%s]\t%d after ret=%d brk ret=%d\n", + __func__, __LINE__, ret, r); + break; + } + } + +fail: + _unlock(&dev->read_excl); + DEBUG_MTPR("[%s]\t%d: RETURNING Back to USpace r=%d\n", + __func__, __LINE__, r); + return r; + +} + +static ssize_t mtpg_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtpg_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + + DEBUG_MTPW("[%s] \t%d ep bulk_out name = %s\n", + __func__, __LINE__ , dev->bulk_out->name); + + if (_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + r = -EIO; + printk(KERN_DEBUG "[%s]%d count>0 dev->error so brk\n", + __func__, __LINE__); + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = mtpg_req_get(dev, &dev->tx_idle)) + || dev->error)); + + if (ret < 0) { + r = ret; + printk(KERN_DEBUG "[%s]\t%d ret = %d\n", + __func__, __LINE__, r); + break; + } + + if (req != 0) { + if (count > MTPG_BULK_BUFFER_SIZE) + xfer = MTPG_BULK_BUFFER_SIZE; + else + xfer = count; + + DEBUG_MTPW("[%s]\t%d copy_from_user length %d\n", + __func__, __LINE__, xfer); + + if (copy_from_user(req->buf, buf, xfer)) { + printk(KERN_ERR "mtpwrite cpyfrmusr error\n"); + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->bulk_in, req, GFP_ATOMIC); + if (ret < 0) { + dev->error = 1; + r = -EIO; + printk(KERN_ERR "[%s]\t%d ep_que ret=%d brk ret=%d\n", + __func__, __LINE__, ret, r); + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) { + DEBUG_MTPW("[%s] \t%d mtpg_req_put\n", __func__, __LINE__); + mtpg_req_put(dev, &dev->tx_idle, req); + } + + _unlock(&dev->write_excl); + + DEBUG_MTPW("[%s]\t%d RETURN back to USpace r=%d\n", + __func__, __LINE__, r); + return r; +} + +static void interrupt_complete(struct usb_ep *ep, struct usb_request *req) +{ + printk(KERN_DEBUG "Finished Writing Interrupt Data\n"); +} + +static ssize_t interrupt_write(struct file *fd, + const char __user *buf, size_t count) +{ + struct mtpg_dev *dev = fd->private_data; + struct usb_request *req = 0; + int ret; + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + req = dev->notify_req; + + if (!req) { + printk(KERN_ERR "[%s]Alloc has failed\n", __func__); + return -ENOMEM; + } + + if (_lock(&dev->wintfd_excl)) { + printk(KERN_ERR "write failed on interrupt endpoint\n"); + return -EBUSY; + } + + if (copy_from_user(req->buf, buf, count)) { + printk(KERN_ERR "[%s]copy from user has failed\n", __func__); + return -EIO; + } + + req->length = count; + req->complete = interrupt_complete; + + ret = usb_ep_queue(dev->int_in, req, GFP_ATOMIC); + + if (ret < 0) { + printk(KERN_ERR "[%s]usb_ep_queue failed\n", __func__); + return -EIO; + } + + _unlock(&dev->wintfd_excl); + return ret; +} + +static long mtpg_ioctl(struct file *fd, unsigned int code, unsigned long arg) +{ + struct mtpg_dev *dev = fd->private_data; + struct usb_composite_dev *cdev; + struct usb_request *req; + int status = 0; + int size = 0; + int ret_value = 0; + int max_pkt = 0; + char *buf_ptr = NULL; + char buf[USB_PTPREQUEST_GETSTATUS_SIZE+1] = {0}; + + cdev = dev->cdev; + if (!cdev) { + printk(KERN_ERR "usb: %s cdev not ready\n", __func__); + return -EAGAIN; + } + req = cdev->req; + if (!cdev->req) { + printk(KERN_ERR "usb: %s cdev->req not ready\n", __func__); + return -EAGAIN; + } + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + + switch (code) { + case MTP_ONLY_ENABLE: + printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE ioctl:\n", + __func__, __LINE__); + if (dev->cdev && dev->cdev->gadget) { + usb_gadget_disconnect(cdev->gadget); + printk(KERN_DEBUG "[%s:%d] B4 disconectng gadget\n", + __func__, __LINE__); + msleep(20); + usb_gadget_connect(cdev->gadget); + printk(KERN_DEBUG "[%s:%d] after usb_gadget_connect\n", + __func__, __LINE__); + } + status = 10; + printk(KERN_DEBUG "[%s:%d] MTP_ONLY_ENABLE clearing error 0\n", + __func__, __LINE__); + the_mtpg->error = 0; + break; + case MTP_DISABLE: + /*mtp_function_enable(mtp_disable_desc);*/ + if (dev->cdev && dev->cdev->gadget) { + usb_gadget_disconnect(dev->cdev->gadget); + mdelay(5); + usb_gadget_connect(dev->cdev->gadget); + } + break; + case MTP_CLEAR_HALT: + status = usb_ep_clear_halt(dev->bulk_in); + status = usb_ep_clear_halt(dev->bulk_out); + break; + case MTP_WRITE_INT_DATA: + printk(KERN_INFO "[%s]\t%d MTP intrpt_Write\n", + __func__, __LINE__); + ret_value = interrupt_write(fd, (const char *)arg, + MTP_MAX_PACKET_LEN_FROM_APP); + if (ret_value < 0) { + printk(KERN_ERR "[%s]\t%d interptFD failed\n", + __func__, __LINE__); + status = -EIO; + } else { + printk(KERN_DEBUG "[%s]\t%d intruptFD suces\n", + __func__, __LINE__); + status = MTP_MAX_PACKET_LEN_FROM_APP; + } + break; + + case SET_MTP_USER_PID: + mtp_pid = arg; + printk(KERN_DEBUG "[%s]SET_MTP_USER_PID;pid=%d\tline=[%d]\n", + __func__, mtp_pid, __LINE__); + break; + + case GET_SETUP_DATA: + buf_ptr = (char *)arg; + printk(KERN_DEBUG "[%s] GET_SETUP_DATA\tline = [%d]\n", + __func__, __LINE__); + if (copy_to_user(buf_ptr, dev->cancel_io_buf, + USB_PTPREQUEST_CANCELIO_SIZE)) { + status = -EIO; + printk(KERN_ERR "[%s]\t%d:coptousr failed\n", + __func__, __LINE__); + } + break; + + case SEND_RESET_ACK: + /*req->zero = 1;*/ + req->length = 0; + /*printk(KERN_DEBUG "[%s]SEND_RESET_ACK and usb_ep_queu + ZERO data size = %d\tline=[%d]\n", + __func__, size, __LINE__);*/ + status = usb_ep_queue(cdev->gadget->ep0, + req, GFP_ATOMIC); + if (status < 0) + printk(KERN_ERR "[%s]ep_queue line = [%d]\n", + __func__, __LINE__); + break; + + case SET_SETUP_DATA: + buf_ptr = (char *)arg; + if (copy_from_user(buf, buf_ptr, + USB_PTPREQUEST_GETSTATUS_SIZE)) { + status = -EIO; + printk(KERN_ERR "[%s]\t%d:copyfrmuser fail\n", + __func__, __LINE__); + break; + } + size = buf[0]; + printk(KERN_DEBUG "[%s]SET_SETUP_DATA size=%d line=[%d]\n", + __func__, size, __LINE__); + memcpy(req->buf, buf, size); + req->zero = 0; + req->length = size; + status = usb_ep_queue(cdev->gadget->ep0, req, + GFP_ATOMIC); + if (status < 0) + printk(KERN_ERR "[%s]usbepqueue line=[%d]\n", + __func__, __LINE__); + break; + + case SET_ZLP_DATA: + /*req->zero = 1;*/ + req = mtpg_req_get(dev, &dev->tx_idle); + if (!req) { + printk(KERN_DEBUG "[%s] Failed to get ZLP_DATA\n", + __func__); + return -EAGAIN; + } + req->length = 0; + printk(KERN_DEBUG "[%s]ZLP_DATA data=%d\tline=[%d]\n", + __func__, size, __LINE__); + status = usb_ep_queue(dev->bulk_in, req, GFP_ATOMIC); + if (status < 0) { + printk(KERN_ERR "[%s]usbepqueue line=[%d]\n", + __func__, __LINE__); + } else { + printk(KERN_DEBUG "%sZLPstatus=%d\tline=%d\n", + __func__, __LINE__, status); + status = 20; + } + break; + + case GET_HIGH_FULL_SPEED: + printk(KERN_DEBUG "[%s]GET_HIGH_FULLSPEED line=[%d]\n", + __func__, __LINE__); + max_pkt = dev->bulk_in->maxpacket; + printk(KERN_DEBUG "[%s] line = %d max_pkt = [%d]\n", + __func__, __LINE__, max_pkt); + if (max_pkt == 64) + status = 64; + else + status = 512; + break; + default: + status = -ENOTTY; + } + return status; +} + +static int mtpg_release_device(struct inode *ip, struct file *fp) +{ + printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__); + if (the_mtpg != NULL) + _unlock(&the_mtpg->open_excl); + return 0; +} + +/* file operations for MTP device /dev/usb_mtp_gadget */ +static const struct file_operations mtpg_fops = { + .owner = THIS_MODULE, + .read = mtpg_read, + .write = mtpg_write, + .open = mtpg_open, + .unlocked_ioctl = mtpg_ioctl, + .release = mtpg_release_device, +}; + +static struct miscdevice mtpg_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = shortname, + .fops = &mtpg_fops, +}; + +struct usb_request *alloc_ep_req(struct usb_ep *ep, + unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (req) { + req->length = len; + req->buf = kmalloc(len, GFP_ATOMIC); + if (!req->buf) { + usb_ep_free_request(ep, req); + req = NULL; + } + } + return req; +} + +static void mtpg_request_free(struct usb_request *req, struct usb_ep *ep) +{ + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static struct usb_request *mtpg_request_new(struct usb_ep *ep, int buffer_size) +{ + + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + + DEBUG_MTPB("[%s] \tline = [%d]\n", __func__, __LINE__); + if (!req) { + printk(KERN_ERR "[%s]\tline %d ERROR\n", __func__, __LINE__); + return NULL; + } + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtpg_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtpg_dev *dev = the_mtpg; + DEBUG_MTPB("[%s]\t %d req->status is = %d\n", + __func__, __LINE__, req->status); + + if (req->status != 0) + dev->error = 1; + + mtpg_req_put(dev, &dev->tx_idle, req); + wake_up(&dev->write_wq); +} + +static void mtpg_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtpg_dev *dev = the_mtpg; + + DEBUG_MTPB("[%s]\tline = [%d]req->status is = %d\n", + __func__, __LINE__, req->status); + if (req->status != 0) { + dev->error = 1; + + DEBUG_MTPB("[%s]\t%d dev->error is=%d for rx_idle\n", + __func__, __LINE__, dev->error); + mtpg_req_put(dev, &dev->rx_idle, req); + } else { + DEBUG_MTPB("[%s]\t%d for rx_done\n", __func__, __LINE__); + mtpg_req_put(dev, &dev->rx_done, req); + } + wake_up(&dev->read_wq); +} + +static void +mtpg_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtpg_dev *dev = mtpg_func_to_dev(f); + struct usb_request *req; + + printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__); + + while ((req = mtpg_req_get(dev, &dev->rx_idle))) + mtpg_request_free(req, dev->bulk_out); + + while ((req = mtpg_req_get(dev, &dev->tx_idle))) + mtpg_request_free(req, dev->bulk_in); +} + +static int +mtpg_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtpg_dev *mtpg = mtpg_func_to_dev(f); + struct usb_request *req; + struct usb_ep *ep; + int i, id; + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__); + + id = usb_interface_id(c, f); + if (id < 0) { + printk(KERN_ERR "[%s]Error in usb_interface_id\n", __func__); + return id; + } + + mtpg_interface_desc.bInterfaceNumber = id; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_mtpg_in_desc); + if (!ep) { + printk(KERN_ERR "[%s]Error usb_ep_autoconfig IN\n", __func__); + goto autoconf_fail; + } + ep->driver_data = mtpg; /* claim the endpoint */ + mtpg->bulk_in = ep; + the_mtpg->bulk_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, &fs_mtpg_out_desc); + if (!ep) { + printk(KERN_ERR "[%s]Eror usb_ep_autoconfig OUT\n", __func__); + goto autoconf_fail; + } + ep->driver_data = mtpg; /* claim the endpoint */ + mtpg->bulk_out = ep; + the_mtpg->bulk_out = ep; + + /* Interrupt Support for MTP */ + ep = usb_ep_autoconfig(cdev->gadget, &int_fs_notify_desc); + if (!ep) { + printk(KERN_ERR "[%s]Eror usb_ep_autoconfig INT\n", __func__); + goto autoconf_fail; + } + ep->driver_data = mtpg; + mtpg->int_in = ep; + the_mtpg->int_in = ep; + + mtpg->notify_req = alloc_ep_req(ep, + sizeof(struct usb_mtp_ctrlrequest) + 2, + GFP_ATOMIC); + if (!mtpg->notify_req) + goto out; + + for (i = 0; i < MTPG_RX_REQ_MAX; i++) { + req = mtpg_request_new(mtpg->bulk_out, MTPG_BULK_BUFFER_SIZE); + if (!req) + goto out; + req->complete = mtpg_complete_out; + mtpg_req_put(mtpg, &mtpg->rx_idle, req); + } + + for (i = 0; i < MTPG_MTPG_TX_REQ_MAX; i++) { + req = mtpg_request_new(mtpg->bulk_in, MTPG_BULK_BUFFER_SIZE); + if (!req) + goto out; + req->complete = mtpg_complete_in; + mtpg_req_put(mtpg, &mtpg->tx_idle, req); + } + + if (gadget_is_dualspeed(cdev->gadget)) { + + DEBUG_MTPB("[%s]\tdual speed line = [%d]\n", + __func__, __LINE__); + + /* Assume endpoint addresses are the same for both speeds */ + hs_mtpg_in_desc.bEndpointAddress = + fs_mtpg_in_desc.bEndpointAddress; + hs_mtpg_out_desc.bEndpointAddress = + fs_mtpg_out_desc.bEndpointAddress; + int_hs_notify_desc.bEndpointAddress = + int_fs_notify_desc.bEndpointAddress; + + f->hs_descriptors = hs_mtpg_desc; + } + + mtpg->cdev = cdev; + the_mtpg->cdev = cdev; + + return 0; + +autoconf_fail: + printk(KERN_ERR "mtpg unable to autoconfigure all endpoints\n"); + return -ENOTSUPP; +out: + mtpg_function_unbind(c, f); + return -1; +} + +static int mtpg_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtpg_dev *dev = mtpg_func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + if (dev->int_in->driver_data) + usb_ep_disable(dev->int_in); + + ret = usb_ep_enable(dev->int_in, + ep_choose(cdev->gadget, &int_hs_notify_desc, + &int_fs_notify_desc)); + if (ret) { + usb_ep_disable(dev->int_in); + dev->int_in->driver_data = NULL; + printk(KERN_ERR "[%s]Error in enabling INT EP\n", __func__); + return ret; + } + dev->int_in->driver_data = dev; + + if (dev->bulk_in->driver_data) + usb_ep_disable(dev->bulk_in); + + ret = usb_ep_enable(dev->bulk_in, + ep_choose(cdev->gadget, &hs_mtpg_in_desc, + &fs_mtpg_in_desc)); + if (ret) { + usb_ep_disable(dev->bulk_in); + dev->bulk_in->driver_data = NULL; + printk(KERN_ERR "[%s] Enable Bulk-IN EP error%d\n", + __func__, __LINE__); + return ret; + } + dev->bulk_in->driver_data = dev; + + if (dev->bulk_out->driver_data) + usb_ep_disable(dev->bulk_out); + + ret = usb_ep_enable(dev->bulk_out, + ep_choose(cdev->gadget, &hs_mtpg_out_desc, + &fs_mtpg_out_desc)); + if (ret) { + usb_ep_disable(dev->bulk_out); + dev->bulk_out->driver_data = NULL; + printk(KERN_ERR "[%s] Enable Bulk-Out EP error%d\n", + __func__, __LINE__); + return ret; + } + dev->bulk_out->driver_data = dev; + + dev->online = 1; + dev->error = 0; + dev->read_ready = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + return 0; +} + +static void mtpg_function_disable(struct usb_function *f) +{ + struct mtpg_dev *dev = mtpg_func_to_dev(f); + + printk(KERN_DEBUG "[%s]\tline = [%d]\n", __func__, __LINE__); + dev->online = 0; + dev->error = 1; + + usb_ep_disable(dev->int_in); + dev->int_in->driver_data = NULL; + + usb_ep_disable(dev->bulk_in); + dev->bulk_in->driver_data = NULL; + + usb_ep_disable(dev->bulk_out); + dev->bulk_out->driver_data = NULL; + + wake_up(&dev->read_wq); +} + + +/*PIMA15740-2000 spec: Class specific setup request for MTP*/ +static void +mtp_complete_cancel_io(struct usb_ep *ep, struct usb_request *req) +{ + int i; + struct mtpg_dev *dev = ep->driver_data; + + DEBUG_MTPB("[%s]\tline = [%d]\n", __func__, __LINE__); + if (req->status != 0) { + DEBUG_MTPB("[%s]req->status !=0\tline = [%d]\n", + __func__, __LINE__); + return; + } + + if (req->actual != USB_PTPREQUEST_CANCELIO_SIZE) { + DEBUG_MTPB("[%s]USB_PTPREQUEST_CANCELIO_SIZE line = [%d]\n", + __func__, __LINE__); + usb_ep_set_halt(ep); + + } else { + memset(dev->cancel_io_buf, 0, USB_PTPREQUEST_CANCELIO_SIZE+1); + memcpy(dev->cancel_io_buf, req->buf, + USB_PTPREQUEST_CANCELIO_SIZE); + /*Debugging*/ + for (i = 0; i < USB_PTPREQUEST_CANCELIO_SIZE; i++) + DEBUG_MTPB("[%s]cancel_io_buf[%d]=%x\tline = [%d]\n", + __func__, i, dev->cancel_io_buf[i], __LINE__); + mtp_send_signal(USB_PTPREQUEST_CANCELIO); + } + +} + +static int mtp_ctrlrequest(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + struct mtpg_dev *dev = the_mtpg; + struct usb_request *req = cdev->req; + int signal_request = 0; + int value = -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 (ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTPG_OS_STRING_ID) { + value = (w_length < sizeof(mtpg_os_string) + ? w_length : sizeof(mtpg_os_string)); + memcpy(cdev->req->buf, mtpg_os_string, value); + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + printk(KERN_DEBUG "[%s:%d] setup queue error\n", + __func__, __LINE__); + } + return value; + } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + if (ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtpg_ext_config_desc) ? + w_length : sizeof(mtpg_ext_config_desc)); + memcpy(cdev->req->buf, &mtpg_ext_config_desc, value); + + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + printk(KERN_DEBUG "[%s:%d] setup queue error\n", + __func__, __LINE__); + } + return value; + } + printk(KERN_DEBUG "mtp_ctrlrequest " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_PTPREQUEST_CANCELIO: + DEBUG_MTPB("[%s]\tline=[%d]w_v=%x, w_i=%x, w_l=%x\n", + __func__, __LINE__, w_value, w_index, w_length); + /* if (w_value == 0x00 && w_index == + mtpg_interface_desc.bInterfaceNumber + && w_length == 0x06) */ + if (w_value == 0x00 && w_length == 0x06) { + DEBUG_MTPB("[%s]PTPREQUESTCANCLIO line[%d]\n", + __func__, __LINE__); + value = w_length; + cdev->gadget->ep0->driver_data = dev; + req->complete = mtp_complete_cancel_io; + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, + req, GFP_ATOMIC); + if (value < 0) { + printk(KERN_ERR "[%s:%d]Error usb_ep_queue\n", + __func__, __LINE__); + } else + DEBUG_MTPB("[%s] ep-queue-sucecc line[%d]\n", + __func__, __LINE__); + } + return value; + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_PTPREQUEST_RESET: + DEBUG_MTPB("[%s] USB_PTPREQUEST_RESET\tline = [%d]\n", + __func__, __LINE__); + signal_request = USB_PTPREQUEST_RESET; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_PTPREQUEST_GETSTATUS: + signal_request = USB_PTPREQUEST_GETSTATUS; + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_PTPREQUEST_GETEVENT: + signal_request = USB_PTPREQUEST_GETEVENT; + break; + + default: + DEBUG_MTPB("[%s] INVALID REQUEST \tline = [%d]\n", + __func__, __LINE__); + return value; + } + + value = mtp_send_signal(signal_request); + return value; +} + +static int mtp_bind_config(struct usb_configuration *c, bool ptp_config) +{ + struct mtpg_dev *mtpg = the_mtpg; + int status = 0; + + if (strings_dev_mtp[F_MTP_IDX].id == 0) { + status = usb_string_id(c->cdev); + + if (status < 0) + return status; + + strings_dev_mtp[F_MTP_IDX].id = status; + mtpg_interface_desc.iInterface = status; + } + + mtpg->cdev = c->cdev; + mtpg->function.name = mtpg_longname; + mtpg->function.strings = mtpg_dev_strings; + + /*Test the switch */ + if (ptp_config) { + mtpg->function.descriptors = fs_ptp_descs; + mtpg->function.hs_descriptors = hs_ptp_descs; + } else { + mtpg->function.descriptors = fs_mtpg_desc; + mtpg->function.hs_descriptors = hs_mtpg_desc; + } + + mtpg->function.bind = mtpg_function_bind; + mtpg->function.unbind = mtpg_function_unbind; + mtpg->function.set_alt = mtpg_function_set_alt; + mtpg->function.disable = mtpg_function_disable; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + mtpg->function.set_config_desc = mtp_set_config_desc; +#endif + + return usb_add_function(c, &mtpg->function); +} + +static int mtp_setup(void) +{ + struct mtpg_dev *mtpg; + int rc; + + printk(KERN_DEBUG "[%s] \tline = [%d]\n", __func__, __LINE__); + mtpg = kzalloc(sizeof(*mtpg), GFP_KERNEL); + if (!mtpg) { + printk(KERN_ERR "mtpg_dev_alloc memory failed\n"); + return -ENOMEM; + } + + spin_lock_init(&mtpg->lock); + init_waitqueue_head(&mtpg->read_wq); + init_waitqueue_head(&mtpg->write_wq); + + atomic_set(&mtpg->open_excl, 0); + atomic_set(&mtpg->read_excl, 0); + atomic_set(&mtpg->write_excl, 0); + atomic_set(&mtpg->wintfd_excl, 0); + + INIT_LIST_HEAD(&mtpg->rx_idle); + INIT_LIST_HEAD(&mtpg->rx_done); + INIT_LIST_HEAD(&mtpg->tx_idle); + + /* the_mtpg must be set before calling usb_gadget_register_driver */ + the_mtpg = mtpg; + + rc = misc_register(&mtpg_device); + if (rc != 0) { + printk(KERN_ERR " misc_register of mtpg Failed\n"); + goto err_misc_register; + } + + return 0; + +err_misc_register: + the_mtpg = NULL; + kfree(mtpg); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return rc; +} + +static void mtp_cleanup(void) +{ + struct mtpg_dev *mtpg = the_mtpg; + printk(KERN_DEBUG "[%s:::%d]\n", __func__, __LINE__); + + if (!mtpg) + return; + + misc_deregister(&mtpg_device); + the_mtpg = NULL; + kfree(mtpg); +} + +MODULE_AUTHOR("Deepak And Madhukar"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/f_ncm.c b/drivers/usb/gadget/f_ncm.c index 86902a6..9cc5fb5 100644 --- a/drivers/usb/gadget/f_ncm.c +++ b/drivers/usb/gadget/f_ncm.c @@ -143,7 +143,7 @@ static struct usb_cdc_ncm_ntb_parameters ntb_parameters = { #define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ #define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ -static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = { +static struct usb_interface_assoc_descriptor ncm_iad_desc = { .bLength = sizeof ncm_iad_desc, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, @@ -157,7 +157,7 @@ static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = { /* interface descriptor: */ -static struct usb_interface_descriptor ncm_control_intf __initdata = { +static struct usb_interface_descriptor ncm_control_intf = { .bLength = sizeof ncm_control_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -169,7 +169,7 @@ static struct usb_interface_descriptor ncm_control_intf __initdata = { /* .iInterface = DYNAMIC */ }; -static struct usb_cdc_header_desc ncm_header_desc __initdata = { +static struct usb_cdc_header_desc ncm_header_desc = { .bLength = sizeof ncm_header_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_HEADER_TYPE, @@ -177,7 +177,7 @@ static struct usb_cdc_header_desc ncm_header_desc __initdata = { .bcdCDC = cpu_to_le16(0x0110), }; -static struct usb_cdc_union_desc ncm_union_desc __initdata = { +static struct usb_cdc_union_desc ncm_union_desc = { .bLength = sizeof(ncm_union_desc), .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_UNION_TYPE, @@ -185,7 +185,7 @@ static struct usb_cdc_union_desc ncm_union_desc __initdata = { /* .bSlaveInterface0 = DYNAMIC */ }; -static struct usb_cdc_ether_desc ecm_desc __initdata = { +static struct usb_cdc_ether_desc ecm_desc = { .bLength = sizeof ecm_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, @@ -200,7 +200,7 @@ static struct usb_cdc_ether_desc ecm_desc __initdata = { #define NCAPS (USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE) -static struct usb_cdc_ncm_desc ncm_desc __initdata = { +static struct usb_cdc_ncm_desc ncm_desc = { .bLength = sizeof ncm_desc, .bDescriptorType = USB_DT_CS_INTERFACE, .bDescriptorSubType = USB_CDC_NCM_TYPE, @@ -212,7 +212,7 @@ static struct usb_cdc_ncm_desc ncm_desc __initdata = { /* the default data interface has no endpoints ... */ -static struct usb_interface_descriptor ncm_data_nop_intf __initdata = { +static struct usb_interface_descriptor ncm_data_nop_intf = { .bLength = sizeof ncm_data_nop_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -227,7 +227,7 @@ static struct usb_interface_descriptor ncm_data_nop_intf __initdata = { /* ... but the "real" data interface has two bulk endpoints */ -static struct usb_interface_descriptor ncm_data_intf __initdata = { +static struct usb_interface_descriptor ncm_data_intf = { .bLength = sizeof ncm_data_intf, .bDescriptorType = USB_DT_INTERFACE, @@ -242,7 +242,7 @@ static struct usb_interface_descriptor ncm_data_intf __initdata = { /* full speed support: */ -static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = { +static struct usb_endpoint_descriptor fs_ncm_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -252,7 +252,7 @@ static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = { .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, }; -static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = { +static struct usb_endpoint_descriptor fs_ncm_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -260,7 +260,7 @@ static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = { +static struct usb_endpoint_descriptor fs_ncm_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -268,7 +268,7 @@ static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = { .bmAttributes = USB_ENDPOINT_XFER_BULK, }; -static struct usb_descriptor_header *ncm_fs_function[] __initdata = { +static struct usb_descriptor_header *ncm_fs_function[] = { (struct usb_descriptor_header *) &ncm_iad_desc, /* CDC NCM control descriptors */ (struct usb_descriptor_header *) &ncm_control_intf, @@ -287,7 +287,7 @@ static struct usb_descriptor_header *ncm_fs_function[] __initdata = { /* high speed support: */ -static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = { +static struct usb_endpoint_descriptor hs_ncm_notify_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -296,7 +296,7 @@ static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = { .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT), .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, }; -static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = { +static struct usb_endpoint_descriptor hs_ncm_in_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -305,7 +305,7 @@ static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = { +static struct usb_endpoint_descriptor hs_ncm_out_desc = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, @@ -314,7 +314,7 @@ static struct usb_endpoint_descriptor hs_ncm_out_desc __initdata = { .wMaxPacketSize = cpu_to_le16(512), }; -static struct usb_descriptor_header *ncm_hs_function[] __initdata = { +static struct usb_descriptor_header *ncm_hs_function[] = { (struct usb_descriptor_header *) &ncm_iad_desc, /* CDC NCM control descriptors */ (struct usb_descriptor_header *) &ncm_control_intf, @@ -816,11 +816,19 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (alt > 1) goto fail; - if (ncm->port.in_ep->driver_data) { - DBG(cdev, "reset ncm\n"); - gether_disconnect(&ncm->port); - ncm_reset_values(ncm); +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + if (alt == 0) { +#endif + if (ncm->port.in_ep->driver_data) { + DBG(cdev, "reset ncm\n"); + printk(KERN_DEBUG "usb: %s gether_disconnect\n", + __func__); + gether_disconnect(&ncm->port); + ncm_reset_values(ncm); + } +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE } +#endif /* * CDC Network only sends data in non-default altsettings. @@ -853,9 +861,17 @@ static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) return PTR_ERR(net); } +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + /* + * we don't need below code, because devguru host driver can't + * accpet SpeedChange/Connect Notify while Alterate Setting + * which call ncm_set_alt() + */ +#else spin_lock(&ncm->lock); ncm_notify(ncm); spin_unlock(&ncm->lock); +#endif } else goto fail; @@ -1163,8 +1179,7 @@ static void ncm_close(struct gether *geth) /* ethernet function driver setup/binding */ -static int __init -ncm_bind(struct usb_configuration *c, struct usb_function *f) +static int ncm_bind(struct usb_configuration *c, struct usb_function *f) { struct usb_composite_dev *cdev = c->cdev; struct f_ncm *ncm = func_to_ncm(f); @@ -1328,7 +1343,7 @@ ncm_unbind(struct usb_configuration *c, struct usb_function *f) * Caller must have called @gether_setup(). Caller is also responsible * for calling @gether_cleanup() before module unload. */ -int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) { struct f_ncm *ncm; int status; @@ -1373,19 +1388,30 @@ int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) ncm = kzalloc(sizeof *ncm, GFP_KERNEL); if (!ncm) return -ENOMEM; - + printk(KERN_DEBUG "usb: %s before MAC:%02X:%02X:%02X:%02X:%02X:%02X\n", + __func__, ethaddr[0], ethaddr[1], + ethaddr[2], ethaddr[3], ethaddr[4], + ethaddr[5]); /* export host's Ethernet address in CDC format */ snprintf(ncm->ethaddr, sizeof ncm->ethaddr, "%02X%02X%02X%02X%02X%02X", ethaddr[0], ethaddr[1], ethaddr[2], ethaddr[3], ethaddr[4], ethaddr[5]); + printk(KERN_DEBUG "usb: %s after MAC:%02X:%02X:%02X:%02X:%02X:%02X\n", + __func__, ncm->ethaddr[0], ncm->ethaddr[1], + ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4], + ncm->ethaddr[5]); ncm_string_defs[1].s = ncm->ethaddr; spin_lock_init(&ncm->lock); ncm_reset_values(ncm); ncm->port.is_fixed = true; +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + ncm->port.func.name = "ncm"; +#else ncm->port.func.name = "cdc_network"; +#endif ncm->port.func.strings = ncm_strings; /* descriptors are per-instance copies */ ncm->port.func.bind = ncm_bind; diff --git a/drivers/usb/gadget/f_rndis.c b/drivers/usb/gadget/f_rndis.c index fa12ec8..1f953b6 100644 --- a/drivers/usb/gadget/f_rndis.c +++ b/drivers/usb/gadget/f_rndis.c @@ -26,7 +26,7 @@ #include <linux/slab.h> #include <linux/kernel.h> -#include <linux/device.h> +#include <linux/platform_device.h> #include <linux/etherdevice.h> #include <asm/atomic.h> @@ -86,10 +86,14 @@ struct f_rndis { struct gether port; u8 ctrl_id, data_id; u8 ethaddr[ETH_ALEN]; + u32 vendorID; + const char *manufacturer; int config; + struct rndis_ep_descs fs; struct rndis_ep_descs hs; + struct rndis_ep_descs ss; struct usb_ep *notify; struct usb_endpoint_descriptor *notify_desc; @@ -105,7 +109,9 @@ static inline struct f_rndis *func_to_rndis(struct usb_function *f) /* peak (theoretical) bulk transfer rate in bits-per-second */ static unsigned int bitrate(struct usb_gadget *g) { - if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + if (gadget_is_superspeed(g) && g->speed == USB_SPEED_SUPER) + return 13 * 1024 * 8 * 1000 * 8; + else if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) return 13 * 512 * 8 * 1000 * 8; else return 19 * 64 * 1 * 1000 * 8; @@ -187,12 +193,11 @@ static struct usb_interface_assoc_descriptor rndis_iad_descriptor = { .bLength = sizeof rndis_iad_descriptor, .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, - .bFirstInterface = 0, /* XXX, hardcoded */ .bInterfaceCount = 2, // control + data .bFunctionClass = USB_CLASS_COMM, .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, - .bFunctionProtocol = USB_CDC_PROTO_NONE, + .bFunctionProtocol = USB_CDC_ACM_PROTO_VENDOR, /* .iFunction = DYNAMIC */ }; @@ -285,6 +290,76 @@ static struct usb_descriptor_header *eth_hs_function[] = { NULL, }; +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; + +static struct usb_ss_ep_comp_descriptor ss_intr_comp_desc = { + .bLength = sizeof ss_intr_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 3 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ + .wBytesPerInterval = cpu_to_le16(STATUS_BYTECOUNT), +}; + +static struct usb_endpoint_descriptor ss_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_bulk_comp_desc = { + .bLength = sizeof ss_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *eth_ss_function[] = { + (struct usb_descriptor_header *) &rndis_iad_descriptor, + + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_acm_descriptor, + (struct usb_descriptor_header *) &rndis_union_desc, + (struct usb_descriptor_header *) &ss_notify_desc, + (struct usb_descriptor_header *) &ss_intr_comp_desc, + + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &ss_in_desc, + (struct usb_descriptor_header *) &ss_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_out_desc, + (struct usb_descriptor_header *) &ss_bulk_comp_desc, + NULL, +}; + /* string descriptors: */ static struct usb_string rndis_string_defs[] = { @@ -486,10 +561,14 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) usb_ep_disable(rndis->notify); } else { VDBG(cdev, "init rndis ctrl %d\n", intf); - rndis->notify_desc = ep_choose(cdev->gadget, - rndis->hs.notify, - rndis->fs.notify); } + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) + rndis->notify_desc = rndis->ss.notify; + else + rndis->notify_desc = ep_choose(cdev->gadget, + rndis->hs.notify, + rndis->fs.notify); usb_ep_enable(rndis->notify, rndis->notify_desc); rndis->notify->driver_data = rndis; @@ -503,10 +582,16 @@ static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) if (!rndis->port.in) { DBG(cdev, "init rndis\n"); + } + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) { + rndis->port.in = rndis->ss.in; + rndis->port.out = rndis->ss.out; + } else { rndis->port.in = ep_choose(cdev->gadget, - rndis->hs.in, rndis->fs.in); + rndis->hs.in, rndis->fs.in); rndis->port.out = ep_choose(cdev->gadget, - rndis->hs.out, rndis->fs.out); + rndis->hs.out, rndis->fs.out); } /* Avoid ZLPs; they can be troublesome. */ @@ -695,6 +780,27 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) f->hs_descriptors, &hs_notify_desc); } + if (gadget_is_superspeed(c->cdev->gadget)) { + ss_in_desc.bEndpointAddress = + fs_in_desc.bEndpointAddress; + ss_out_desc.bEndpointAddress = + fs_out_desc.bEndpointAddress; + ss_notify_desc.bEndpointAddress = + fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(eth_ss_function); + if (!f->ss_descriptors) + goto fail; + + rndis->ss.in = usb_find_endpoint(eth_ss_function, + f->ss_descriptors, &ss_in_desc); + rndis->ss.out = usb_find_endpoint(eth_ss_function, + f->ss_descriptors, &ss_out_desc); + rndis->ss.notify = usb_find_endpoint(eth_ss_function, + f->ss_descriptors, &ss_notify_desc); + } + rndis->port.open = rndis_open; rndis->port.close = rndis_close; @@ -706,12 +812,9 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0); rndis_set_host_mac(rndis->config, rndis->ethaddr); -#if 0 -// FIXME - if (rndis_set_param_vendor(rndis->config, vendorID, - manufacturer)) - goto fail0; -#endif + if (rndis_set_param_vendor(rndis->config, rndis->vendorID, + rndis->manufacturer)) + goto fail; /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code @@ -719,12 +822,15 @@ rndis_bind(struct usb_configuration *c, struct usb_function *f) */ DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", rndis->port.in_ep->name, rndis->port.out_ep->name, rndis->notify->name); return 0; fail: + if (gadget_is_superspeed(c->cdev->gadget) && f->ss_descriptors) + usb_free_descriptors(f->ss_descriptors); if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors) usb_free_descriptors(f->hs_descriptors); if (f->descriptors) @@ -756,6 +862,10 @@ rndis_unbind(struct usb_configuration *c, struct usb_function *f) rndis_deregister(rndis->config); rndis_exit(); + rndis_string_defs[0].id = 0; + + if (gadget_is_superspeed(c->cdev->gadget)) + usb_free_descriptors(f->ss_descriptors); if (gadget_is_dualspeed(c->cdev->gadget)) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); @@ -786,7 +896,8 @@ static inline bool can_support_rndis(struct usb_configuration *c) * for calling @gether_cleanup() before module unload. */ int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer) { struct f_rndis *rndis; int status; @@ -831,6 +942,8 @@ rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) goto fail; memcpy(rndis->ethaddr, ethaddr, ETH_ALEN); + rndis->vendorID = vendorID; + rndis->manufacturer = manufacturer; /* RNDIS activates when the host changes this filter */ rndis->port.cdc_filter = 0; diff --git a/drivers/usb/gadget/f_sdb.c b/drivers/usb/gadget/f_sdb.c new file mode 100644 index 0000000..ae717f9 --- /dev/null +++ b/drivers/usb/gadget/f_sdb.c @@ -0,0 +1,770 @@ +/* + * Gadget Driver for Samsung SDB (based on Android ADB) + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> + +#include <linux/types.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#define SDB_BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define SDB_TX_REQ_MAX 4 + +static const char sdb_shortname[] = "samsung_sdb"; + +static DEFINE_MUTEX(sdb_lock); + +struct sdb_ep_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; +}; + +struct f_sdb { + struct usb_function function; + u8 inf_id; + + struct sdb_ep_descs fs; + struct sdb_ep_descs hs; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + struct list_head bulk_in_q; +}; + +struct sdb_dev { + struct f_sdb *sdb_func; + spinlock_t lock; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head *tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + + struct usb_request *rx_req; + int rx_done; +}; + +static struct usb_interface_descriptor sdb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x20, + .bInterfaceProtocol = 0x02, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_endpoint_descriptor sdb_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* .wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor sdb_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* .wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_descriptor_header *fs_sdb_descs[] = { + (struct usb_descriptor_header *) &sdb_interface_desc, + (struct usb_descriptor_header *) &sdb_fullspeed_in_desc, + (struct usb_descriptor_header *) &sdb_fullspeed_out_desc, + NULL, +}; + +static struct usb_endpoint_descriptor sdb_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + /* bEndpointAddress copied from sdb_fullspeed_in_desc + during sdb_function_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor sdb_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + /* bEndpointAddress copied from sdb_fullspeed_in_desc + during sdb_function_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_descriptor_header *hs_sdb_descs[] = { + (struct usb_descriptor_header *) &sdb_interface_desc, + (struct usb_descriptor_header *) &sdb_highspeed_in_desc, + (struct usb_descriptor_header *) &sdb_highspeed_out_desc, + NULL, +}; + +/* string descriptors: */ + +#define F_SDB_IDX 0 + +/* static strings, in UTF-8 */ +static struct usb_string sdb_string_defs[] = { + [F_SDB_IDX].s = "Samsung SDB", + { /* ZEROES END LIST */ }, +}; + +static struct usb_gadget_strings sdb_string_table = { + .language = 0x0409, /* en-us */ + .strings = sdb_string_defs, +}; + +static struct usb_gadget_strings *sdb_strings[] = { + &sdb_string_table, + NULL, +}; + +/* temporary variable used between sdb_open() and sdb_gadget_bind() */ +static struct sdb_dev *_sdb_dev; + + +static inline struct f_sdb *func_to_sdb(struct usb_function *f) +{ + return container_of(f, struct f_sdb, function); +} + + +static struct usb_request *sdb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void sdb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int _sdb_lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _sdb_unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void sdb_req_put(struct sdb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + if (!dev || !req) + return; + + spin_lock_irqsave(&dev->lock, flags); + if (head) + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *sdb_req_get(struct sdb_dev *dev, + struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + if (!dev) + return NULL; + + spin_lock_irqsave(&dev->lock, flags); + if (!head) + req = NULL; + else { + if (list_empty(head)) { + req = NULL; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void sdb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct sdb_dev *dev = _sdb_dev; + struct f_sdb *sdb_func = ep->driver_data; + + if (req->status != 0) + dev->error = 1; + + sdb_req_put(dev, &sdb_func->bulk_in_q, req); + wake_up(&dev->write_wq); +} + +static void sdb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct sdb_dev *dev = _sdb_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int sdb_create_bulk_endpoints(struct f_sdb *sdb_func, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = sdb_func->function.config->cdev; + struct usb_request *req; + struct sdb_dev *dev = _sdb_dev; + struct usb_ep *ep; + int i; + + DBG(cdev, "sdb_create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + ERROR(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = cdev; /* claim the endpoint */ + sdb_func->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + ERROR(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for sdb ep_out got %s\n", ep->name); + ep->driver_data = cdev; /* claim the endpoint */ + sdb_func->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = sdb_request_new(sdb_func->ep_out, SDB_BULK_BUFFER_SIZE); + if (!req) + return -ENOMEM; + req->complete = sdb_complete_out; + dev->rx_req = req; + + for (i = 0; i < SDB_TX_REQ_MAX; i++) { + req = sdb_request_new(sdb_func->ep_in, SDB_BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = sdb_complete_in; + sdb_req_put(dev, &sdb_func->bulk_in_q, req); + } + + return 0; + +fail: + while (!!(req = sdb_req_get(dev, &sdb_func->bulk_in_q))) + sdb_request_free(req, sdb_func->ep_in); + + sdb_request_free(dev->rx_req, sdb_func->ep_out); + dev->rx_req = NULL; + + if (sdb_func->ep_in) + sdb_func->ep_in->driver_data = NULL; + if (sdb_func->ep_out) + sdb_func->ep_out->driver_data = NULL; + + printk(KERN_ERR "sdb_bind() could not allocate requests\n"); + return -ENOMEM; +} + +static ssize_t sdb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct sdb_dev *dev = fp->private_data; + int r = count, xfer; + int ret; + + if (count > SDB_BULK_BUFFER_SIZE) + return -EINVAL; + + if (_sdb_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + _sdb_unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + +requeue_req: + /* queue a request */ + mutex_lock(&sdb_lock); + if (!dev->sdb_func || !dev->rx_req) + ret = -ENODEV; + else { + dev->rx_req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->sdb_func->ep_out, + dev->rx_req, GFP_ATOMIC); + } + mutex_unlock(&sdb_lock); + + if (ret < 0) { + r = -EIO; + dev->error = 1; + goto done; + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + dev->error = 1; + r = ret; + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (dev->rx_req->actual == 0) + goto requeue_req; + + mutex_lock(&sdb_lock); + if (!dev->sdb_func || !dev->rx_req) + r = -ENODEV; + else { + xfer = (dev->rx_req->actual < count) + ? dev->rx_req->actual : count; + if (copy_to_user(buf, dev->rx_req->buf, xfer)) + r = -EFAULT; + } + mutex_unlock(&sdb_lock); + } else + r = -EIO; + +done: + _sdb_unlock(&dev->read_excl); + return r; +} + +static ssize_t sdb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct sdb_dev *dev = fp->private_data; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + if (_sdb_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (!!(req = sdb_req_get(dev, dev->tx_idle)) + || dev->error)); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > SDB_BULK_BUFFER_SIZE) + xfer = SDB_BULK_BUFFER_SIZE; + else + xfer = count; + + mutex_lock(&sdb_lock); + if (!dev->sdb_func) { + mutex_unlock(&sdb_lock); + r = -ENODEV; + break; + } else if (copy_from_user(req->buf, buf, xfer)) { + mutex_unlock(&sdb_lock); + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->sdb_func->ep_in, + req, GFP_ATOMIC); + mutex_unlock(&sdb_lock); + + if (ret < 0) { + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + sdb_req_put(dev, dev->tx_idle, req); + + _sdb_unlock(&dev->write_excl); + return r; +} + +static int sdb_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "sdb_open\n"); + if (_sdb_lock(&_sdb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _sdb_dev; + + /* clear the error latch */ + _sdb_dev->error = 0; + + return 0; +} + +static int sdb_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "sdb_release\n"); + + if (_sdb_dev != NULL) + _sdb_unlock(&_sdb_dev->open_excl); + + return 0; +} + +/* file operations for SDB device /dev/samsung_sdb */ +static const struct file_operations sdb_fops = { + .owner = THIS_MODULE, + .read = sdb_read, + .write = sdb_write, + .open = sdb_open, + .release = sdb_release, +}; + +static struct miscdevice sdb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = sdb_shortname, + .fops = &sdb_fops, +}; + +static int +sdb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_sdb *sdb_func = func_to_sdb(f); + int id; + int ret; + + DBG(cdev, "sdb_function_bind sdb_func: %p\n", sdb_func); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + + sdb_func->inf_id = id; + sdb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = sdb_create_bulk_endpoints(sdb_func, &sdb_fullspeed_in_desc, + &sdb_fullspeed_out_desc); + if (ret) + return ret; + + f->descriptors = usb_copy_descriptors(fs_sdb_descs); + if (!f->descriptors) + goto desc_alloc_fail; + + sdb_func->fs.in = usb_find_endpoint(fs_sdb_descs, f->descriptors, + &sdb_fullspeed_in_desc); + sdb_func->fs.out = usb_find_endpoint(fs_sdb_descs, f->descriptors, + &sdb_fullspeed_out_desc); + + /* support high speed hardware */ + if (gadget_is_dualspeed(cdev->gadget)) { + sdb_highspeed_in_desc.bEndpointAddress = + sdb_fullspeed_in_desc.bEndpointAddress; + sdb_highspeed_out_desc.bEndpointAddress = + sdb_fullspeed_out_desc.bEndpointAddress; + + f->hs_descriptors = usb_copy_descriptors(hs_sdb_descs); + if (!f->hs_descriptors) + goto desc_alloc_fail; + + sdb_func->hs.in = usb_find_endpoint(hs_sdb_descs, + f->hs_descriptors, &sdb_highspeed_in_desc); + sdb_func->hs.out = usb_find_endpoint(hs_sdb_descs, + f->hs_descriptors, &sdb_highspeed_out_desc); + } + + return 0; + +desc_alloc_fail: + if (f->descriptors) + usb_free_descriptors(f->descriptors); + + return -ENOMEM; +} + +static void +sdb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct sdb_dev *dev = _sdb_dev; + struct f_sdb *sdb_func = func_to_sdb(f); + struct usb_request *req; + + dev->online = 0; + dev->error = 1; + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + mutex_lock(&sdb_lock); + + while (!!(req = sdb_req_get(dev, &sdb_func->bulk_in_q))) + sdb_request_free(req, sdb_func->ep_in); + + sdb_request_free(dev->rx_req, sdb_func->ep_out); + + kfree(sdb_func); + dev->sdb_func = NULL; + dev->rx_req = NULL; + + mutex_unlock(&sdb_lock); + + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); +} + +static int sdb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct f_sdb *sdb_func = func_to_sdb(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct sdb_dev *dev = _sdb_dev; + int ret; + + if (sdb_func->inf_id != intf) { + printk(KERN_ERR "sdb_function_set_alt error wrong intf:%d alt:%d\n", + intf, alt); + return -EINVAL; + } + + if (sdb_func->ep_in->driver_data) + usb_ep_disable(sdb_func->ep_in); + ret = usb_ep_enable(sdb_func->ep_in, + ep_choose(cdev->gadget, + sdb_func->hs.in, sdb_func->fs.in)); + if (ret) { + printk(KERN_ERR "error, usb_ep_enable for sdb ep_in\n"); + return ret; + } + sdb_func->ep_in->driver_data = sdb_func; + + if (sdb_func->ep_out->driver_data) + usb_ep_disable(sdb_func->ep_out); + ret = usb_ep_enable(sdb_func->ep_out, + ep_choose(cdev->gadget, + sdb_func->hs.out, sdb_func->fs.out)); + if (ret) { + usb_ep_disable(sdb_func->ep_in); + sdb_func->ep_in->driver_data = NULL; + printk(KERN_ERR "error, usb_ep_enable for sdb ep_out\n"); + return ret; + } + sdb_func->ep_out->driver_data = sdb_func; + + dev->tx_idle = &sdb_func->bulk_in_q; + dev->sdb_func = sdb_func; + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void sdb_function_disable(struct usb_function *f) +{ + struct sdb_dev *dev = _sdb_dev; + struct f_sdb *sdb_func = func_to_sdb(f); + + dev->online = 0; + dev->error = 1; + + spin_lock(&dev->lock); + dev->tx_idle = NULL; + spin_unlock(&dev->lock); + + usb_ep_disable(sdb_func->ep_in); + sdb_func->ep_in->driver_data = NULL; + + usb_ep_disable(sdb_func->ep_out); + sdb_func->ep_out->driver_data = NULL; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); +} + +static int sdb_setup(struct usb_composite_dev *cdev) +{ + struct sdb_dev *dev; + int ret; + + printk(KERN_INFO "sdb_bind_config\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + if (sdb_string_defs[F_SDB_IDX].id == 0) { + ret = usb_string_id(cdev); + if (ret < 0) { + kfree(dev); + return ret; + } + sdb_string_defs[F_SDB_IDX].id = ret; + sdb_interface_desc.iInterface = ret; + } + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + + /* _sdb_dev must be set before calling usb_gadget_register_driver */ + _sdb_dev = dev; + + ret = misc_register(&sdb_device); + if (ret) + goto err1; + + return 0; + +err1: + kfree(dev); + _sdb_dev = NULL; + printk(KERN_ERR "sdb gadget driver failed to initialize\n"); + return ret; +} + +static int sdb_bind_config(struct usb_configuration *c) +{ + int ret; + struct f_sdb *sdb_func; + + if (!_sdb_dev) { + printk(KERN_ERR "Error There is no _sdb_dev!!\n"); + return -ENODEV; + } + + sdb_func = kzalloc(sizeof(*sdb_func), GFP_KERNEL); + if (!sdb_func) { + printk(KERN_ERR "sdb_func memory alloc failed !!!\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&sdb_func->bulk_in_q); + + sdb_func->function.name = "sdb"; + sdb_func->function.strings = sdb_strings; + sdb_func->function.bind = sdb_function_bind; + sdb_func->function.unbind = sdb_function_unbind; + sdb_func->function.set_alt = sdb_function_set_alt; + sdb_func->function.disable = sdb_function_disable; + + ret = usb_add_function(c, &sdb_func->function); + if (ret) + printk(KERN_ERR "Error in usb_add_function failed for sdb\n"); + + return ret; +} + +static void sdb_cleanup(void) +{ + struct sdb_dev *dev = _sdb_dev; + + misc_deregister(&sdb_device); + + if (!dev) + return; + _sdb_dev = NULL; + kfree(dev); +} diff --git a/drivers/usb/gadget/f_subset.c b/drivers/usb/gadget/f_subset.c index 8675ca4..7e2ed9c 100644 --- a/drivers/usb/gadget/f_subset.c +++ b/drivers/usb/gadget/f_subset.c @@ -69,6 +69,7 @@ struct f_gether { struct geth_descs fs; struct geth_descs hs; + struct geth_descs ss; }; static inline struct f_gether *func_to_geth(struct usb_function *f) @@ -209,6 +210,46 @@ static struct usb_descriptor_header *hs_eth_function[] __initdata = { NULL, }; +/* super speed support: */ + +static struct usb_endpoint_descriptor ss_subset_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_endpoint_descriptor ss_subset_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor ss_subset_bulk_comp_desc __initdata = { + .bLength = sizeof ss_subset_bulk_comp_desc, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /* the following 2 values can be tweaked if necessary */ + /* .bMaxBurst = 0, */ + /* .bmAttributes = 0, */ +}; + +static struct usb_descriptor_header *ss_eth_function[] __initdata = { + (struct usb_descriptor_header *) &subset_data_intf, + (struct usb_descriptor_header *) &mdlm_header_desc, + (struct usb_descriptor_header *) &mdlm_desc, + (struct usb_descriptor_header *) &mdlm_detail_desc, + (struct usb_descriptor_header *) ðer_desc, + (struct usb_descriptor_header *) &ss_subset_in_desc, + (struct usb_descriptor_header *) &ss_subset_bulk_comp_desc, + (struct usb_descriptor_header *) &ss_subset_out_desc, + (struct usb_descriptor_header *) &ss_subset_bulk_comp_desc, + NULL, +}; + /* string descriptors: */ static struct usb_string geth_string_defs[] = { @@ -243,10 +284,16 @@ static int geth_set_alt(struct usb_function *f, unsigned intf, unsigned alt) } DBG(cdev, "init + activate cdc subset\n"); - geth->port.in = ep_choose(cdev->gadget, + if (gadget_is_superspeed(cdev->gadget) && + cdev->gadget->speed == USB_SPEED_SUPER) { + geth->port.in = geth->ss.in; + geth->port.out = geth->ss.out; + } else { + geth->port.in = ep_choose(cdev->gadget, geth->hs.in, geth->fs.in); - geth->port.out = ep_choose(cdev->gadget, + geth->port.out = ep_choose(cdev->gadget, geth->hs.out, geth->fs.out); + } net = gether_connect(&geth->port); return IS_ERR(net) ? PTR_ERR(net) : 0; @@ -322,17 +369,40 @@ geth_bind(struct usb_configuration *c, struct usb_function *f) f->hs_descriptors, &hs_subset_out_desc); } + if (gadget_is_superspeed(c->cdev->gadget)) { + ss_subset_in_desc.bEndpointAddress = + fs_subset_in_desc.bEndpointAddress; + ss_subset_out_desc.bEndpointAddress = + fs_subset_out_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->ss_descriptors = usb_copy_descriptors(ss_eth_function); + if (!f->ss_descriptors) + goto fail; + + geth->ss.in = usb_find_endpoint(ss_eth_function, + f->ss_descriptors, &ss_subset_in_desc); + geth->ss.out = usb_find_endpoint(ss_eth_function, + f->ss_descriptors, &ss_subset_out_desc); + } + /* NOTE: all that is done without knowing or caring about * the network link ... which is unavailable to this code * until we're activated via set_alt(). */ DBG(cdev, "CDC Subset: %s speed IN/%s OUT/%s\n", + gadget_is_superspeed(c->cdev->gadget) ? "super" : gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", geth->port.in_ep->name, geth->port.out_ep->name); return 0; fail: + if (f->descriptors) + usb_free_descriptors(f->descriptors); + if (f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + /* we might as well release our claims on endpoints */ if (geth->port.out) geth->port.out_ep->driver_data = NULL; @@ -347,6 +417,8 @@ fail: static void geth_unbind(struct usb_configuration *c, struct usb_function *f) { + if (gadget_is_superspeed(c->cdev->gadget)) + usb_free_descriptors(f->ss_descriptors); if (gadget_is_dualspeed(c->cdev->gadget)) usb_free_descriptors(f->hs_descriptors); usb_free_descriptors(f->descriptors); diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index bcdac7c..2db7cb2 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -142,6 +142,18 @@ #define gadget_is_s3c_hsudc(g) 0 #endif +#ifdef CONFIG_USB_GADGET_S3C_OTGD +#define gadget_is_s3c(g) !strcmp("s3c-udc", (g)->name) +#else +#define gadget_is_s3c(g) 0 +#endif + +#ifdef CONFIG_USB_EXYNOS_SS_UDC +#define gadget_is_exynos_ss_udc(g) (!strcmp("exynos-ss-udc", (g)->name)) +#else +#define gadget_is_exynos_ss_udc(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_EG20T #define gadget_is_pch(g) (!strcmp("pch_udc", (g)->name)) #else @@ -215,6 +227,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x25; else if (gadget_is_s3c_hsotg(gadget)) return 0x26; + else if (gadget_is_s3c(gadget)) + return 0x26; else if (gadget_is_pch(gadget)) return 0x27; else if (gadget_is_ci13xxx_msm(gadget)) @@ -223,6 +237,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x29; else if (gadget_is_s3c_hsudc(gadget)) return 0x30; + else if (gadget_is_exynos_ss_udc(gadget)) + return 0x31; return -ENOENT; } diff --git a/drivers/usb/gadget/gadget_gbhc/android.c b/drivers/usb/gadget/gadget_gbhc/android.c new file mode 100644 index 0000000..ff70f91 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/android.c @@ -0,0 +1,533 @@ +/* + * Gadget Driver for Android + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> + +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/platform_device.h> + +#include <linux/usb/android_composite.h> +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> + +#include "gadget_chips.h" + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" +#include "composite.c" + +MODULE_AUTHOR("Mike Lockwood"); +MODULE_DESCRIPTION("Android Composite USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); + +static const char longname[] = "Gadget Android"; + +/* Default vendor and product IDs, overridden by platform data */ +#define VENDOR_ID 0x18D1 +#define PRODUCT_ID 0x0001 + +struct android_dev { + struct usb_composite_dev *cdev; + struct usb_configuration *config; + int num_products; + struct android_usb_product *products; + int num_functions; + char **functions; + + int vendor_id; + int product_id; + int version; +}; + +static struct android_dev *_android_dev; + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 +#define STRING_SERIAL_IDX 2 + +/* String Table */ +static struct usb_string strings_dev[] = { + /* These dummy values should be overridden by platform data */ + [STRING_MANUFACTURER_IDX].s = "Android", + [STRING_PRODUCT_IDX].s = "Android", + [STRING_SERIAL_IDX].s = "0123456789ABCDEF", + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof(device_desc), + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + .idVendor = __constant_cpu_to_le16(VENDOR_ID), + .idProduct = __constant_cpu_to_le16(PRODUCT_ID), + .bcdDevice = __constant_cpu_to_le16(0xffff), + .bNumConfigurations = 1, +}; + +static struct list_head _functions = LIST_HEAD_INIT(_functions); +static bool _are_functions_bound; + +static struct android_usb_function *get_function(const char *name) +{ + struct android_usb_function *f; + list_for_each_entry(f, &_functions, list) { + if (!strcmp(name, f->name)) + return f; + } + return 0; +} + +static bool are_functions_registered(struct android_dev *dev) +{ + char **functions = dev->functions; + int i; + + /* Look only for functions required by the board config */ + for (i = 0; i < dev->num_functions; i++) { + char *name = *functions++; + bool is_match = false; + /* Could reuse get_function() here, but a reverse search + * should yield less comparisons overall */ + struct android_usb_function *f; + list_for_each_entry_reverse(f, &_functions, list) { + if (!strcmp(name, f->name)) { + is_match = true; + break; + } + } + if (is_match) + continue; + else + return false; + } + + return true; +} + +static bool should_bind_functions(struct android_dev *dev) +{ + /* Don't waste time if the main driver hasn't bound */ + if (!dev->config) + return false; + + /* Don't waste time if we've already bound the functions */ + if (_are_functions_bound) + return false; + + /* This call is the most costly, so call it last */ + if (!are_functions_registered(dev)) + return false; + + return true; +} + +static void bind_functions(struct android_dev *dev) +{ + struct android_usb_function *f; + char **functions = dev->functions; + int i; + + for (i = 0; i < dev->num_functions; i++) { + char *name = *functions++; + f = get_function(name); + if (f) + f->bind_config(dev->config); + else + printk(KERN_ERR "function %s not found in bind_functions\n", name); + } + + _are_functions_bound = true; +} + +static int android_bind_config(struct usb_configuration *c) +{ + struct android_dev *dev = _android_dev; + + printk(KERN_DEBUG "android_bind_config\n"); + dev->config = c; + + if (should_bind_functions(dev)) + bind_functions(dev); + + return 0; +} +#ifdef CONFIG_USB_ANDROID_ACM + #define ANDROID_DEBUG_CONFIG_STRING "UMS + ACM + ADB(Debug mode)" + #define ANDROID_NO_DEBUG_CONFIG_STRING "UMS + ACM" +#else + #define ANDROID_DEBUG_CONFIG_STRING "UMS + ADB (Debugging mode)" + #define ANDROID_NO_DEBUG_CONFIG_STRING "UMS Only (Not debugging mode)" +#endif + +static int android_setup_config(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl); + +static struct usb_configuration android_config_driver = { + .label = ANDROID_NO_DEBUG_CONFIG_STRING, + .setup = android_setup_config, + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0xFA, /* 500ma */ +}; + +static int android_setup_config(struct usb_configuration *c, + const struct usb_ctrlrequest *ctrl) +{ + int i; + int ret = -EOPNOTSUPP; + + for (i = 0; i < android_config_driver.next_interface_id; i++) { + if (android_config_driver.interface[i]->setup) { + ret = android_config_driver.interface[i]->setup( + android_config_driver.interface[i], ctrl); + if (ret >= 0) + return ret; + } + } + return ret; +} + +static int product_has_function(struct android_usb_product *p, + struct usb_function *f) +{ + char **functions = p->functions; + int count = p->num_functions; + const char *name = f->name; + int i; + + for (i = 0; i < count; i++) { + /* For functions with multiple instances, usb_function.name + * will have an index appended to the core name (ex: acm0), + * while android_usb_product.functions[i] will only have the + * core name (ex: acm). So, only compare up to the length of + * android_usb_product.functions[i]. + */ + if (!strncmp(name, functions[i], strlen(functions[i]))) + return 1; + } + return 0; +} + +static int product_matches_functions(struct android_usb_product *p) +{ + struct usb_function *f; + list_for_each_entry(f, &android_config_driver.functions, list) { + if (product_has_function(p, f) == !!f->disabled) + return 0; + } + return 1; +} + +static int get_vendor_id(struct android_dev *dev) +{ + struct android_usb_product *p = dev->products; + int count = dev->num_products; + int i; + + if (p) { + for (i = 0; i < count; i++, p++) { + if (p->vendor_id && product_matches_functions(p)) + return p->vendor_id; + } + } + /* use default vendor ID */ + return dev->vendor_id; +} + +static int get_product_id(struct android_dev *dev) +{ + struct android_usb_product *p = dev->products; + int count = dev->num_products; + int i; + + if (p) { + for (i = 0; i < count; i++, p++) { + if (product_matches_functions(p)) + return p->product_id; + } + } + /* use default product ID */ + return dev->product_id; +} + +static int android_bind(struct usb_composite_dev *cdev) +{ + struct android_dev *dev = _android_dev; + struct usb_gadget *gadget = cdev->gadget; + int gcnum, id, ret; + + printk(KERN_INFO "android_bind\n"); + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_MANUFACTURER_IDX].id = id; + device_desc.iManufacturer = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_PRODUCT_IDX].id = id; + device_desc.iProduct = id; + + id = usb_string_id(cdev); + if (id < 0) + return id; + strings_dev[STRING_SERIAL_IDX].id = id; + device_desc.iSerialNumber = id; + + /* register our configuration */ + ret = usb_add_config(cdev, &android_config_driver, android_bind_config); + if (ret) { + printk(KERN_ERR "usb_add_config failed\n"); + return ret; + } + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum); + else { + /* gadget zero is so simple (for now, no altsettings) that + * it SHOULD NOT have problems with bulk-capable hardware. + * so just warn about unrcognized controllers -- don't panic. + * + * things like configuration and altsetting numbering + * can need hardware-specific attention though. + */ + pr_warning("%s: controller '%s' not recognized\n", + longname, gadget->name); + device_desc.bcdDevice = __constant_cpu_to_le16(0x9999); + } + + usb_gadget_set_selfpowered(gadget); + dev->cdev = cdev; + device_desc.idVendor = __constant_cpu_to_le16(get_vendor_id(dev)); + device_desc.idProduct = __constant_cpu_to_le16(get_product_id(dev)); + cdev->desc.idVendor = device_desc.idVendor; + cdev->desc.idProduct = device_desc.idProduct; + + return 0; +} + +static struct usb_composite_driver android_usb_driver = { + .name = "android_usb", + .dev = &device_desc, + .strings = dev_strings, + .enable_function = android_enable_function, +}; + +void android_register_function(struct android_usb_function *f) +{ + struct android_dev *dev = _android_dev; + + printk(KERN_INFO "android_register_function %s\n", f->name); + list_add_tail(&f->list, &_functions); + + if (dev && should_bind_functions(dev)) + bind_functions(dev); +} + +void update_dev_desc(struct android_dev *dev) +{ + struct usb_function *f; + struct usb_function *last_enabled_f = NULL; + int num_enabled = 0; + int has_iad = 0; + + dev->cdev->desc.bDeviceClass = USB_CLASS_PER_INTERFACE; + dev->cdev->desc.bDeviceSubClass = 0x00; + dev->cdev->desc.bDeviceProtocol = 0x00; + + list_for_each_entry(f, &android_config_driver.functions, list) { + if (!f->disabled) { + num_enabled++; + last_enabled_f = f; + if (f->descriptors[0]->bDescriptorType == + USB_DT_INTERFACE_ASSOCIATION) + has_iad = 1; + } + if (num_enabled > 1 && has_iad) { + dev->cdev->desc.bDeviceClass = USB_CLASS_MISC; + dev->cdev->desc.bDeviceSubClass = 0x02; + dev->cdev->desc.bDeviceProtocol = 0x01; + break; + } + } + + if (num_enabled == 1) { +#ifdef CONFIG_USB_ANDROID_RNDIS + if (!strcmp(last_enabled_f->name, "rndis")) { +#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS + dev->cdev->desc.bDeviceClass = + USB_CLASS_WIRELESS_CONTROLLER; +#else + dev->cdev->desc.bDeviceClass = USB_CLASS_COMM; +#endif + } +#endif + } +} + +void android_enable_function(struct usb_function *f, int enable) +{ + struct android_dev *dev = _android_dev; + int disable = !enable; + + if (!!f->disabled != disable) { + usb_function_set_enabled(f, !disable); + if (!strcmp(f->name, "adb")) { + if (enable) + android_config_driver.label = ANDROID_DEBUG_CONFIG_STRING; + else + android_config_driver.label = ANDROID_NO_DEBUG_CONFIG_STRING; + } +#ifdef CONFIG_USB_ANDROID_RNDIS + if (!strcmp(f->name, "rndis")) { + struct usb_function *func; + /* Windows does not support other interfaces when RNDIS is enabled, + * so we disable UMS and MTP when RNDIS is on. + */ + list_for_each_entry(func, &android_config_driver.functions, list) { + if (!strcmp(func->name, "usb_mass_storage") + || !strcmp(func->name, "mtp")) { + usb_function_set_enabled(func, !enable); + } + } + } +#endif + + update_dev_desc(dev); + + device_desc.idVendor = __constant_cpu_to_le16(get_vendor_id(dev)); + device_desc.idProduct = __constant_cpu_to_le16(get_product_id(dev)); + if (dev->cdev) { + dev->cdev->desc.idVendor = device_desc.idVendor; + dev->cdev->desc.idProduct = device_desc.idProduct; + } + /* usb_composite_force_reset(dev->cdev); */ + + /* Force reenumeration */ + if (dev->cdev && dev->cdev->gadget && + dev->cdev->gadget->speed != USB_SPEED_UNKNOWN) { + usb_gadget_disconnect(dev->cdev->gadget); + msleep(10); + usb_gadget_connect(dev->cdev->gadget); + } + } +} + +static int android_probe(struct platform_device *pdev) +{ + struct android_usb_platform_data *pdata = pdev->dev.platform_data; + struct android_dev *dev = _android_dev; + + printk(KERN_INFO "android_probe pdata: %p\n", pdata); + + if (pdata) { + dev->products = pdata->products; + dev->num_products = pdata->num_products; + dev->functions = pdata->functions; + dev->num_functions = pdata->num_functions; + if (pdata->vendor_id) { + dev->vendor_id = pdata->vendor_id; + device_desc.idVendor = + __constant_cpu_to_le16(pdata->vendor_id); + } + if (pdata->product_id) { + dev->product_id = pdata->product_id; + device_desc.idProduct = + __constant_cpu_to_le16(pdata->product_id); + } + if (pdata->version) + dev->version = pdata->version; + + if (pdata->product_name) + strings_dev[STRING_PRODUCT_IDX].s = pdata->product_name; + if (pdata->manufacturer_name) + strings_dev[STRING_MANUFACTURER_IDX].s = + pdata->manufacturer_name; + if (pdata->serial_number) + strings_dev[STRING_SERIAL_IDX].s = pdata->serial_number; + } + + return usb_composite_probe(&android_usb_driver, android_bind); +} + +static struct platform_driver android_platform_driver = { + .driver = { .name = "android_usb", }, + .probe = android_probe, +}; + +static int __init init(void) +{ + struct android_dev *dev; + + printk(KERN_INFO "android init\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* set default values, which should be overridden by platform data */ + dev->product_id = PRODUCT_ID; + _android_dev = dev; + + return platform_driver_register(&android_platform_driver); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&android_usb_driver); + platform_driver_unregister(&android_platform_driver); + kfree(_android_dev); + _android_dev = NULL; +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/gadget_gbhc/composite.c b/drivers/usb/gadget/gadget_gbhc/composite.c new file mode 100644 index 0000000..c9ae8bb --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/composite.c @@ -0,0 +1,1560 @@ +/* + * composite.c - infrastructure for Composite USB Gadgets + * + * Copyright (C) 2006-2008 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/kallsyms.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/utsname.h> +#include <linux/delay.h> +#include <linux/kdev_t.h> +#include <linux/usb/composite.h> + + +/* + * The code in this file is utility code, used to build a gadget driver + * from one or more "function" drivers, one or more "configuration" + * objects, and a "usb_composite_driver" by gluing them together along + * with the relevant device-wide data. + */ + +/* big enough to hold our biggest descriptor */ +#define USB_BUFSIZ 1024 + +static struct usb_composite_driver *composite; +static int (*composite_gadget_bind)(struct usb_composite_dev *cdev); + +/* Some systems will need runtime overrides for the product identifiers + * published in the device descriptor, either numbers or strings or both. + * String parameters are in UTF-8 (superset of ASCII's 7 bit characters). + */ + +static ushort idVendor; +module_param(idVendor, ushort, 0); +MODULE_PARM_DESC(idVendor, "USB Vendor ID"); + +static ushort idProduct; +module_param(idProduct, ushort, 0); +MODULE_PARM_DESC(idProduct, "USB Product ID"); + +static ushort bcdDevice; +module_param(bcdDevice, ushort, 0); +MODULE_PARM_DESC(bcdDevice, "USB Device version (BCD)"); + +static char *iManufacturer; +module_param(iManufacturer, charp, 0); +MODULE_PARM_DESC(iManufacturer, "USB Manufacturer string"); + +static char *iProduct; +module_param(iProduct, charp, 0); +MODULE_PARM_DESC(iProduct, "USB Product string"); + +static char *iSerialNumber; +module_param(iSerialNumber, charp, 0); +MODULE_PARM_DESC(iSerialNumber, "SerialNumber string"); + +static char composite_manufacturer[50]; + +/*-------------------------------------------------------------------------*/ + +static ssize_t enable_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_function *f = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", !f->disabled); +} + +static ssize_t enable_store( + struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct usb_function *f = dev_get_drvdata(dev); + struct usb_composite_driver *driver = f->config->cdev->driver; + int value; + + sscanf(buf, "%d", &value); + if (driver->enable_function) + driver->enable_function(f, value); + else + usb_function_set_enabled(f, value); + + return size; +} + +static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, enable_show, enable_store); + +void usb_function_set_enabled(struct usb_function *f, int enabled) +{ + f->disabled = !enabled; + kobject_uevent(&f->dev->kobj, KOBJ_CHANGE); +} + + +void usb_composite_force_reset(struct usb_composite_dev *cdev) +{ + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + /* force reenumeration */ + if (cdev && cdev->gadget && cdev->gadget->speed != USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&cdev->lock, flags); + + usb_gadget_disconnect(cdev->gadget); + msleep(10); + usb_gadget_connect(cdev->gadget); + } else { + spin_unlock_irqrestore(&cdev->lock, flags); + } +} + +/** + * usb_add_function() - add a function to a configuration + * @config: the configuration + * @function: the function being added + * Context: single threaded during gadget setup + * + * After initialization, each configuration must have one or more + * functions added to it. Adding a function involves calling its @bind() + * method to allocate resources such as interface and string identifiers + * and endpoints. + * + * This function returns the value of the function's bind(), which is + * zero for success else a negative errno value. + */ +int usb_add_function(struct usb_configuration *config, + struct usb_function *function) +{ + struct usb_composite_dev *cdev = config->cdev; + int value = -EINVAL; + int index; + + DBG(cdev, "adding '%s'/%p to config '%s'/%p\n", + function->name, function, + config->label, config); + + if (!function->set_alt || !function->disable) + goto done; + + index = atomic_inc_return(&cdev->driver->function_count); + function->dev = device_create(cdev->driver->class, NULL, + MKDEV(0, index), NULL, function->name); + if (IS_ERR(function->dev)) + return PTR_ERR(function->dev); + + value = device_create_file(function->dev, &dev_attr_enable); + if (value < 0) { + device_destroy(cdev->driver->class, MKDEV(0, index)); + return value; + } + dev_set_drvdata(function->dev, function); + + function->config = config; + list_add_tail(&function->list, &config->functions); + + /* REVISIT *require* function->bind? */ + if (function->bind) { + value = function->bind(config, function); + if (value < 0) { + list_del(&function->list); + function->config = NULL; + } + } else + value = 0; + + /* We allow configurations that don't work at both speeds. + * If we run into a lowspeed Linux system, treat it the same + * as full speed ... it's the function drivers that will need + * to avoid bulk and ISO transfers. + */ + if (!config->fullspeed && function->descriptors) + config->fullspeed = true; + if (!config->highspeed && function->hs_descriptors) + config->highspeed = true; + +done: + if (value) + DBG(cdev, "adding '%s'/%p --> %d\n", + function->name, function, value); + return value; +} + +/** + * usb_function_deactivate - prevent function and gadget enumeration + * @function: the function that isn't yet ready to respond + * + * Blocks response of the gadget driver to host enumeration by + * preventing the data line pullup from being activated. This is + * normally called during @bind() processing to change from the + * initial "ready to respond" state, or when a required resource + * becomes available. + * + * For example, drivers that serve as a passthrough to a userspace + * daemon can block enumeration unless that daemon (such as an OBEX, + * MTP, or print server) is ready to handle host requests. + * + * Not all systems support software control of their USB peripheral + * data pullups. + * + * Returns zero on success, else negative errno. + */ +int usb_function_deactivate(struct usb_function *function) +{ + struct usb_composite_dev *cdev = function->config->cdev; + unsigned long flags; + int status = 0; + + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->deactivations == 0) + status = usb_gadget_disconnect(cdev->gadget); + if (status == 0) + cdev->deactivations++; + + spin_unlock_irqrestore(&cdev->lock, flags); + return status; +} + +/** + * usb_function_activate - allow function and gadget enumeration + * @function: function on which usb_function_activate() was called + * + * Reverses effect of usb_function_deactivate(). If no more functions + * are delaying their activation, the gadget driver will respond to + * host enumeration procedures. + * + * Returns zero on success, else negative errno. + */ +int usb_function_activate(struct usb_function *function) +{ + struct usb_composite_dev *cdev = function->config->cdev; + int status = 0; + + spin_lock(&cdev->lock); + + if (WARN_ON(cdev->deactivations == 0)) + status = -EINVAL; + else { + cdev->deactivations--; + if (cdev->deactivations == 0) + status = usb_gadget_connect(cdev->gadget); + } + + spin_unlock(&cdev->lock); + return status; +} + +/** + * usb_interface_id() - allocate an unused interface ID + * @config: configuration associated with the interface + * @function: function handling the interface + * Context: single threaded during gadget setup + * + * usb_interface_id() is called from usb_function.bind() callbacks to + * allocate new interface IDs. The function driver will then store that + * ID in interface, association, CDC union, and other descriptors. It + * will also handle any control requests targeted at that interface, + * particularly changing its altsetting via set_alt(). There may + * also be class-specific or vendor-specific requests to handle. + * + * All interface identifier should be allocated using this routine, to + * ensure that for example different functions don't wrongly assign + * different meanings to the same identifier. Note that since interface + * identifiers are configuration-specific, functions used in more than + * one configuration (or more than once in a given configuration) need + * multiple versions of the relevant descriptors. + * + * Returns the interface ID which was allocated; or -ENODEV if no + * more interface IDs can be allocated. + */ +int usb_interface_id(struct usb_configuration *config, + struct usb_function *function) +{ + unsigned id = config->next_interface_id; + + if (id < MAX_CONFIG_INTERFACES) { + config->interface[id] = function; + config->next_interface_id = id + 1; + return id; + } + return -ENODEV; +} + +static int config_buf(struct usb_configuration *config, + enum usb_device_speed speed, void *buf, u8 type) +{ + struct usb_config_descriptor *c = buf; + struct usb_interface_descriptor *intf; + struct usb_interface_assoc_descriptor *iad = NULL; + void *next = buf + USB_DT_CONFIG_SIZE; + int len = USB_BUFSIZ - USB_DT_CONFIG_SIZE; + struct usb_function *f; + int status; + int interfaceCount = 0; + u8 *dest; + + /* write the config descriptor */ + c = buf; + c->bLength = USB_DT_CONFIG_SIZE; + c->bDescriptorType = type; + /* wTotalLength and bNumInterfaces are written later */ + c->bConfigurationValue = config->bConfigurationValue; + c->iConfiguration = config->iConfiguration; + c->bmAttributes = USB_CONFIG_ATT_ONE | config->bmAttributes; + c->bMaxPower = config->bMaxPower ? : (CONFIG_USB_GADGET_VBUS_DRAW / 2); + + /* There may be e.g. OTG descriptors */ + if (config->descriptors) { + status = usb_descriptor_fillbuf(next, len, + config->descriptors); + if (status < 0) + return status; + len -= status; + next += status; + } + + /* add each function's descriptors */ + list_for_each_entry(f, &config->functions, list) { + struct usb_descriptor_header **descriptors; + struct usb_descriptor_header *descriptor; + + if (speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + if (f->disabled || !descriptors || descriptors[0] == NULL) + continue; + status = usb_descriptor_fillbuf(next, len, + (const struct usb_descriptor_header **) descriptors); + if (status < 0) + return status; + + /* set interface numbers dynamically */ + dest = next; + while ((descriptor = *descriptors++) != NULL) { + intf = (struct usb_interface_descriptor *)dest; + if (intf->bDescriptorType == USB_DT_INTERFACE) { + /* don't increment bInterfaceNumber for alternate settings */ + if (intf->bAlternateSetting == 0) + intf->bInterfaceNumber = interfaceCount++; + else + intf->bInterfaceNumber = interfaceCount - 1; + if (iad) { + iad->bFirstInterface = + intf->bInterfaceNumber; + iad = NULL; + } + } else if (intf->bDescriptorType == + USB_DT_INTERFACE_ASSOCIATION) { + /* This will be first if it exists. Save + * a pointer to it so we can properly set + * bFirstInterface when we process the first + * interface. + */ + iad = (struct usb_interface_assoc_descriptor *) + dest; + } + dest += intf->bLength; + } + + len -= status; + next += status; + } + + len = next - buf; + c->wTotalLength = cpu_to_le16(len); + c->bNumInterfaces = interfaceCount; + return len; +} + +static int config_desc(struct usb_composite_dev *cdev, unsigned w_value) +{ + struct usb_gadget *gadget = cdev->gadget; + struct usb_configuration *c; + u8 type = w_value >> 8; + enum usb_device_speed speed = USB_SPEED_UNKNOWN; + + if (gadget_is_dualspeed(gadget)) { + int hs = 0; + + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + if (hs) + speed = USB_SPEED_HIGH; + + } + + /* This is a lookup by config *INDEX* */ + w_value &= 0xff; + list_for_each_entry(c, &cdev->configs, list) { + /* ignore configs that won't work at this speed */ + if (speed == USB_SPEED_HIGH) { + if (!c->highspeed) + continue; + } else { + if (!c->fullspeed) + continue; + } + if (w_value == 0) + return config_buf(c, speed, cdev->req->buf, type); + w_value--; + } + return -EINVAL; +} + +static int count_configs(struct usb_composite_dev *cdev, unsigned type) +{ + struct usb_gadget *gadget = cdev->gadget; + struct usb_configuration *c; + unsigned count = 0; + int hs = 0; + + if (gadget_is_dualspeed(gadget)) { + if (gadget->speed == USB_SPEED_HIGH) + hs = 1; + if (type == USB_DT_DEVICE_QUALIFIER) + hs = !hs; + } + list_for_each_entry(c, &cdev->configs, list) { + /* ignore configs that won't work at this speed */ + if (hs) { + if (!c->highspeed) + continue; + } else { + if (!c->fullspeed) + continue; + } + count++; + } + return count; +} + +static void device_qual(struct usb_composite_dev *cdev) +{ + struct usb_qualifier_descriptor *qual = cdev->req->buf; + + qual->bLength = sizeof(*qual); + qual->bDescriptorType = USB_DT_DEVICE_QUALIFIER; + /* POLICY: same bcdUSB and device type info at both speeds */ + qual->bcdUSB = cdev->desc.bcdUSB; + qual->bDeviceClass = cdev->desc.bDeviceClass; + qual->bDeviceSubClass = cdev->desc.bDeviceSubClass; + qual->bDeviceProtocol = cdev->desc.bDeviceProtocol; + /* ASSUME same EP0 fifo size at both speeds */ + qual->bMaxPacketSize0 = cdev->desc.bMaxPacketSize0; + qual->bNumConfigurations = count_configs(cdev, USB_DT_DEVICE_QUALIFIER); + qual->bRESERVED = 0; +} + +/*-------------------------------------------------------------------------*/ + +static void reset_config(struct usb_composite_dev *cdev) +{ + struct usb_function *f; + + DBG(cdev, "reset config\n"); + + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->disable) + f->disable(f); + + bitmap_zero(f->endpoints, 32); + } + cdev->config = NULL; +} + +static int set_config(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl, unsigned number) +{ + struct usb_gadget *gadget = cdev->gadget; + struct usb_configuration *c = NULL; + int result = -EINVAL; + unsigned power = gadget_is_otg(gadget) ? 8 : 100; + int tmp; + + if (cdev->config) + reset_config(cdev); + + if (number) { + list_for_each_entry(c, &cdev->configs, list) { + if (c->bConfigurationValue == number) { + result = 0; + break; + } + } + if (result < 0) + goto done; + } else + result = 0; + + INFO(cdev, "%s speed config #%d: %s\n", + ({ char *speed; + switch (gadget->speed) { + case USB_SPEED_LOW: speed = "low"; break; + case USB_SPEED_FULL: speed = "full"; break; + case USB_SPEED_HIGH: speed = "high"; break; + default: speed = "?"; break; + } ; speed; }), number, c ? c->label : "unconfigured"); + + if (!c) + goto done; + + cdev->config = c; + + /* Initialize all interfaces by setting them to altsetting zero. */ + for (tmp = 0; tmp < MAX_CONFIG_INTERFACES; tmp++) { + struct usb_function *f = c->interface[tmp]; + struct usb_descriptor_header **descriptors; + + if (!f) + break; + if (f->disabled) + continue; + + /* + * Record which endpoints are used by the function. This is used + * to dispatch control requests targeted at that endpoint to the + * function's setup callback instead of the current + * configuration's setup callback. + */ + if (gadget->speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + + for (; *descriptors; ++descriptors) { + struct usb_endpoint_descriptor *ep; + int addr; + + if ((*descriptors)->bDescriptorType != USB_DT_ENDPOINT) + continue; + + ep = (struct usb_endpoint_descriptor *)*descriptors; + addr = ((ep->bEndpointAddress & 0x80) >> 3) + | (ep->bEndpointAddress & 0x0f); + set_bit(addr, f->endpoints); + } + + result = f->set_alt(f, tmp, 0); + if (result < 0) { + DBG(cdev, "interface %d (%s/%p) alt 0 --> %d\n", + tmp, f->name, f, result); + + reset_config(cdev); + goto done; + } + + if (result == USB_GADGET_DELAYED_STATUS) { + DBG(cdev, + "%s: interface %d (%s) requested delayed status\n", + __func__, tmp, f->name); + cdev->delayed_status++; + DBG(cdev, "delayed_status count %d\n", + cdev->delayed_status); + } + } + + /* when we return, be sure our power usage is valid */ + power = c->bMaxPower ? (2 * c->bMaxPower) : CONFIG_USB_GADGET_VBUS_DRAW; +done: + usb_gadget_vbus_draw(gadget, power); + + schedule_work(&cdev->switch_work); + + if (result >= 0 && cdev->delayed_status) + result = USB_GADGET_DELAYED_STATUS; + return result; +} + +/** + * usb_add_config() - add a configuration to a device. + * @cdev: wraps the USB gadget + * @config: the configuration, with bConfigurationValue assigned + * @bind: the configuration's bind function + * Context: single threaded during gadget setup + * + * One of the main tasks of a composite @bind() routine is to + * add each of the configurations it supports, using this routine. + * + * This function returns the value of the configuration's @bind(), which + * is zero for success else a negative errno value. Binding configurations + * assigns global resources including string IDs, and per-configuration + * resources such as interface IDs and endpoints. + */ +int usb_add_config(struct usb_composite_dev *cdev, + struct usb_configuration *config, + int (*bind)(struct usb_configuration *)) +{ + int status = -EINVAL; + struct usb_configuration *c; + + DBG(cdev, "adding config #%u '%s'/%p\n", + config->bConfigurationValue, + config->label, config); + + if (!config->bConfigurationValue || !bind) + goto done; + + /* Prevent duplicate configuration identifiers */ + list_for_each_entry(c, &cdev->configs, list) { + if (c->bConfigurationValue == config->bConfigurationValue) { + status = -EBUSY; + goto done; + } + } + + config->cdev = cdev; + list_add_tail(&config->list, &cdev->configs); + + INIT_LIST_HEAD(&config->functions); + config->next_interface_id = 0; + + status = bind(config); + if (status < 0) { + list_del(&config->list); + config->cdev = NULL; + } else { + unsigned i; + + DBG(cdev, "cfg %d/%p speeds:%s%s\n", + config->bConfigurationValue, config, + config->highspeed ? " high" : "", + config->fullspeed + ? (gadget_is_dualspeed(cdev->gadget) + ? " full" + : " full/low") + : ""); + + for (i = 0; i < MAX_CONFIG_INTERFACES; i++) { + struct usb_function *f = config->interface[i]; + + if (!f) + continue; + DBG(cdev, " interface %d = %s/%p\n", + i, f->name, f); + } + } + + /* set_alt(), or next bind(), sets up + * ep->driver_data as needed. + */ + usb_ep_autoconfig_reset(cdev->gadget); + +done: + if (status) + DBG(cdev, "added config '%s'/%u --> %d\n", config->label, + config->bConfigurationValue, status); + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* We support strings in multiple languages ... string descriptor zero + * says which languages are supported. The typical case will be that + * only one language (probably English) is used, with I18N handled on + * the host side. + */ + +static void collect_langs(struct usb_gadget_strings **sp, __le16 *buf) +{ + const struct usb_gadget_strings *s; + u16 language; + __le16 *tmp; + + while (*sp) { + s = *sp; + language = cpu_to_le16(s->language); + for (tmp = buf; *tmp && tmp < &buf[126]; tmp++) { + if (*tmp == language) + goto repeat; + } + *tmp++ = language; +repeat: + sp++; + } +} + +static int lookup_string( + struct usb_gadget_strings **sp, + void *buf, + u16 language, + int id +) +{ + struct usb_gadget_strings *s; + int value; + + while (*sp) { + s = *sp++; + if (s->language != language) + continue; + value = usb_gadget_get_string(s, id, buf); + if (value > 0) + return value; + } + return -EINVAL; +} + +static int get_string(struct usb_composite_dev *cdev, + void *buf, u16 language, int id) +{ + struct usb_configuration *c; + struct usb_function *f; + int len; + const char *str; + + /* Yes, not only is USB's I18N support probably more than most + * folk will ever care about ... also, it's all supported here. + * (Except for UTF8 support for Unicode's "Astral Planes".) + */ + + /* 0 == report all available language codes */ + if (id == 0) { + struct usb_string_descriptor *s = buf; + struct usb_gadget_strings **sp; + + memset(s, 0, 256); + s->bDescriptorType = USB_DT_STRING; + + sp = composite->strings; + if (sp) + collect_langs(sp, s->wData); + + list_for_each_entry(c, &cdev->configs, list) { + sp = c->strings; + if (sp) + collect_langs(sp, s->wData); + + list_for_each_entry(f, &c->functions, list) { + sp = f->strings; + if (sp) + collect_langs(sp, s->wData); + } + } + + for (len = 0; len <= 126 && s->wData[len]; len++) + continue; + if (!len) + return -EINVAL; + + s->bLength = 2 * (len + 1); + return s->bLength; + } + + /* Otherwise, look up and return a specified string. First + * check if the string has not been overridden. + */ + if (cdev->manufacturer_override == id) + str = iManufacturer ?: composite->iManufacturer ?: + composite_manufacturer; + else if (cdev->product_override == id) + str = iProduct ?: composite->iProduct; + else if (cdev->serial_override == id) + str = iSerialNumber; + else + str = NULL; + if (str) { + struct usb_gadget_strings strings = { + .language = language, + .strings = &(struct usb_string) { 0xff, str } + }; + return usb_gadget_get_string(&strings, 0xff, buf); + } + + /* String IDs are device-scoped, so we look up each string + * table we're told about. These lookups are infrequent; + * simpler-is-better here. + */ + if (composite->strings) { + len = lookup_string(composite->strings, buf, language, id); + if (len > 0) + return len; + } + list_for_each_entry(c, &cdev->configs, list) { + if (c->strings) { + len = lookup_string(c->strings, buf, language, id); + if (len > 0) + return len; + } + list_for_each_entry(f, &c->functions, list) { + if (!f->strings) + continue; + len = lookup_string(f->strings, buf, language, id); + if (len > 0) + return len; + } + } + return -EINVAL; +} + +/** + * usb_string_id() - allocate an unused string ID + * @cdev: the device whose string descriptor IDs are being allocated + * Context: single threaded during gadget setup + * + * @usb_string_id() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then store that ID in the appropriate descriptors and string table. + * + * All string identifier should be allocated using this, + * @usb_string_ids_tab() or @usb_string_ids_n() routine, to ensure + * that for example different functions don't wrongly assign different + * meanings to the same identifier. + */ +int usb_string_id(struct usb_composite_dev *cdev) +{ + if (cdev->next_string_id < 254) { + /* string id 0 is reserved by USB spec for list of + * supported languages */ + /* 255 reserved as well? -- mina86 */ + cdev->next_string_id++; + return cdev->next_string_id; + } + return -ENODEV; +} + +/** + * usb_string_ids() - allocate unused string IDs in batch + * @cdev: the device whose string descriptor IDs are being allocated + * @str: an array of usb_string objects to assign numbers to + * Context: single threaded during gadget setup + * + * @usb_string_ids() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then copy IDs from the string table to the appropriate descriptors + * and string table for other languages. + * + * All string identifier should be allocated using this, + * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for + * example different functions don't wrongly assign different meanings + * to the same identifier. + */ +int usb_string_ids_tab(struct usb_composite_dev *cdev, struct usb_string *str) +{ + int next = cdev->next_string_id; + + for (; str->s; ++str) { + if (unlikely(next >= 254)) + return -ENODEV; + str->id = ++next; + } + + cdev->next_string_id = next; + + return 0; +} + +/** + * usb_string_ids_n() - allocate unused string IDs in batch + * @c: the device whose string descriptor IDs are being allocated + * @n: number of string IDs to allocate + * Context: single threaded during gadget setup + * + * Returns the first requested ID. This ID and next @n-1 IDs are now + * valid IDs. At least provided that @n is non-zero because if it + * is, returns last requested ID which is now very useful information. + * + * @usb_string_ids_n() is called from bind() callbacks to allocate + * string IDs. Drivers for functions, configurations, or gadgets will + * then store that ID in the appropriate descriptors and string table. + * + * All string identifier should be allocated using this, + * @usb_string_id() or @usb_string_ids_n() routine, to ensure that for + * example different functions don't wrongly assign different meanings + * to the same identifier. + */ +int usb_string_ids_n(struct usb_composite_dev *c, unsigned n) +{ + unsigned next = c->next_string_id; + if (unlikely(n > 254 || (unsigned)next + n > 254)) + return -ENODEV; + c->next_string_id += n; + return next + 1; +} + + +/*-------------------------------------------------------------------------*/ + +static void composite_setup_complete(struct usb_ep *ep, struct usb_request *req) +{ + if (req->status || req->actual != req->length) + DBG((struct usb_composite_dev *) ep->driver_data, + "setup complete --> %d, %d/%d\n", + req->status, req->actual, req->length); +} + +/* + * The setup() callback implements all the ep0 functionality that's + * not handled lower down, in hardware or the hardware driver(like + * device and endpoint feature flags, and their status). It's all + * housekeeping for the gadget function we're implementing. Most of + * the work is in config and function specific setup. + */ +static int +composite_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_request *req = cdev->req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u8 intf = w_index & 0xFF; + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + struct usb_function *f = NULL; + u8 endp; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (!cdev->connected) { + cdev->connected = 1; + schedule_work(&cdev->switch_work); + } + spin_unlock_irqrestore(&cdev->lock, flags); + + /* partial re-init of the response message; the function or the + * gadget might need to intercept e.g. a control-OUT completion + * when we delegate to it. + */ + req->zero = 0; + req->complete = composite_setup_complete; + req->length = 0; + gadget->ep0->driver_data = cdev; + + switch (ctrl->bRequest) { + + /* we handle all standard USB descriptors */ + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + cdev->desc.bNumConfigurations = + count_configs(cdev, USB_DT_DEVICE); + value = min(w_length, (u16) sizeof cdev->desc); + memcpy(req->buf, &cdev->desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + if (!gadget_is_dualspeed(gadget)) + break; + device_qual(cdev); + value = min_t(int, w_length, + sizeof(struct usb_qualifier_descriptor)); + break; + case USB_DT_OTHER_SPEED_CONFIG: + if (!gadget_is_dualspeed(gadget)) + break; + /* FALLTHROUGH */ + case USB_DT_CONFIG: + value = config_desc(cdev, w_value); + if (value >= 0) + value = min(w_length, (u16) value); + break; + case USB_DT_STRING: + value = get_string(cdev, req->buf, + w_index, w_value & 0xff); + + /* Allow functions to handle USB_DT_STRING. + * This is required for MTP. + */ + if (value < 0) { + struct usb_configuration *cfg; + list_for_each_entry(cfg, &cdev->configs, list) { + if (cfg && cfg->setup) { + value = cfg->setup(cfg, ctrl); + if (value >= 0) + break; + } + } + } + + if (value >= 0) + value = min(w_length, (u16) value); + break; + } + break; + + /* any number of configs can work */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unknown; + if (gadget_is_otg(gadget)) { + if (gadget->a_hnp_support) + DBG(cdev, "HNP available\n"); + else if (gadget->a_alt_hnp_support) + DBG(cdev, "HNP on another port\n"); + else + VDBG(cdev, "HNP inactive\n"); + } + spin_lock(&cdev->lock); + value = set_config(cdev, ctrl, w_value); + spin_unlock(&cdev->lock); + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != USB_DIR_IN) + goto unknown; + if (cdev->config) + *(u8 *)req->buf = cdev->config->bConfigurationValue; + else + *(u8 *)req->buf = 0; + value = min(w_length, (u16) 1); + break; + + /* function drivers must handle get/set altsetting; if there's + * no get() method, we know only altsetting zero works. + */ + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != USB_RECIP_INTERFACE) + goto unknown; + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + if (w_value && !f->set_alt) + break; + value = f->set_alt(f, w_index, w_value); + if (value == USB_GADGET_DELAYED_STATUS) { + DBG(cdev, + "%s: interface %d (%s) requested delayed status\n", + __func__, intf, f->name); + cdev->delayed_status++; + DBG(cdev, "delayed_status count %d\n", + cdev->delayed_status); + } + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto unknown; + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + if (!f) + break; + /* lots of interfaces only need altsetting zero... */ + value = f->get_alt ? f->get_alt(f, w_index) : 0; + if (value < 0) + break; + *((u8 *)req->buf) = value; + value = min(w_length, (u16) 1); + break; + default: +unknown: + VDBG(cdev, + "non-core control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* functions always handle their interfaces and endpoints... + * punt other recipients (other, WUSB, ...) to the current + * configuration code. + * + * REVISIT it could make sense to let the composite device + * take such requests too, if that's ever needed: to work + * in config 0, etc. + */ + switch (ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + if (!cdev->config || intf >= MAX_CONFIG_INTERFACES) + break; + f = cdev->config->interface[intf]; + break; + + case USB_RECIP_ENDPOINT: + endp = ((w_index & 0x80) >> 3) | (w_index & 0x0f); + list_for_each_entry(f, &cdev->config->functions, list) { + if (test_bit(endp, f->endpoints)) + break; + } + if (&f->list == &cdev->config->functions) + f = NULL; + break; + } + + if (f && f->setup) + value = f->setup(f, ctrl); + else { + struct usb_configuration *c; + + c = cdev->config; + if (c && c->setup) + value = c->setup(c, ctrl); + } + + /* If the vendor request is not processed (value < 0), + * call all device registered configure setup callbacks + * to process it. + * This is used to handle the following cases: + * - vendor request is for the device and arrives before + * setconfiguration. + * - Some devices are required to handle vendor request before + * setconfiguration such as MTP, USBNET. + */ + + if (value < 0) { + struct usb_configuration *cfg; + + list_for_each_entry(cfg, &cdev->configs, list) { + if (cfg && cfg->setup) + value = cfg->setup(cfg, ctrl); + } + } + + goto done; + } + + /* respond with data transfer before status phase? */ + if (value >= 0 && value != USB_GADGET_DELAYED_STATUS) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue(gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG(cdev, "ep_queue --> %d\n", value); + req->status = 0; + composite_setup_complete(gadget->ep0, req); + } + } else if (value == USB_GADGET_DELAYED_STATUS && w_length != 0) { + WARN(cdev, + "%s: Delayed status not supported for w_length != 0", + __func__); + } + +done: + /* device either stalls (value < 0) or reports success */ + return value; +} + +static void composite_disconnect(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + unsigned long flags; + + /* REVISIT: should we have config and device level + * disconnect callbacks? + */ + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->config) + reset_config(cdev); + + if (composite->disconnect) + composite->disconnect(cdev); + + cdev->connected = 0; + schedule_work(&cdev->switch_work); + spin_unlock_irqrestore(&cdev->lock, flags); +} + +/*-------------------------------------------------------------------------*/ + +static ssize_t composite_show_suspended(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_gadget *gadget = dev_to_usb_gadget(dev); + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + return sprintf(buf, "%d\n", cdev->suspended); +} + +static DEVICE_ATTR(suspended, 0444, composite_show_suspended, NULL); + +static void +composite_unbind(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + + /* composite_disconnect() must already have been called + * by the underlying peripheral controller driver! + * so there's no i/o concurrency that could affect the + * state protected by cdev->lock. + */ + WARN_ON(cdev->config); + + while (!list_empty(&cdev->configs)) { + struct usb_configuration *c; + + c = list_first_entry(&cdev->configs, + struct usb_configuration, list); + while (!list_empty(&c->functions)) { + struct usb_function *f; + + f = list_first_entry(&c->functions, + struct usb_function, list); + list_del(&f->list); + if (f->unbind) { + DBG(cdev, "unbind function '%s'/%p\n", + f->name, f); + f->unbind(c, f); + /* may free memory for "f" */ + } + } + list_del(&c->list); + if (c->unbind) { + DBG(cdev, "unbind config '%s'/%p\n", c->label, c); + c->unbind(c); + /* may free memory for "c" */ + } + } + if (composite->unbind) + composite->unbind(cdev); + + if (cdev->req) { + kfree(cdev->req->buf); + usb_ep_free_request(gadget->ep0, cdev->req); + } + switch_dev_unregister(&cdev->sw_connected); + switch_dev_unregister(&cdev->sw_config); + device_remove_file(&gadget->dev, &dev_attr_suspended); + kfree(cdev); + set_gadget_data(gadget, NULL); + composite = NULL; +} + +static u8 override_id(struct usb_composite_dev *cdev, u8 *desc) +{ + if (!*desc) { + int ret = usb_string_id(cdev); + if (unlikely(ret < 0)) + WARNING(cdev, "failed to override string ID\n"); + else + *desc = ret; + } + + return *desc; +} + +static void +composite_switch_work(struct work_struct *data) +{ + struct usb_composite_dev *cdev = + container_of(data, struct usb_composite_dev, switch_work); + struct usb_configuration *config = cdev->config; + int connected; + unsigned long flags; + + spin_lock_irqsave(&cdev->lock, flags); + if (cdev->connected != cdev->sw_connected.state) { + connected = cdev->connected; + spin_unlock_irqrestore(&cdev->lock, flags); + switch_set_state(&cdev->sw_connected, connected); + } else { + spin_unlock_irqrestore(&cdev->lock, flags); + } + + if (config) + switch_set_state(&cdev->sw_config, config->bConfigurationValue); + else + switch_set_state(&cdev->sw_config, 0); +} + +static int composite_bind(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev; + int status = -ENOMEM; + + cdev = kzalloc(sizeof *cdev, GFP_KERNEL); + if (!cdev) + return status; + + spin_lock_init(&cdev->lock); + cdev->gadget = gadget; + set_gadget_data(gadget, cdev); + INIT_LIST_HEAD(&cdev->configs); + + /* preallocate control response and buffer */ + cdev->req = usb_ep_alloc_request(gadget->ep0, GFP_KERNEL); + if (!cdev->req) + goto fail; + cdev->req->buf = kmalloc(USB_BUFSIZ, GFP_KERNEL); + if (!cdev->req->buf) + goto fail; + cdev->req->complete = composite_setup_complete; + gadget->ep0->driver_data = cdev; + + cdev->bufsiz = USB_BUFSIZ; + cdev->driver = composite; + + /* + * As per USB compliance update, a device that is actively drawing + * more than 100mA from USB must report itself as bus-powered in + * the GetStatus(DEVICE) call. + */ + if (CONFIG_USB_GADGET_VBUS_DRAW <= USB_SELF_POWER_VBUS_MAX_DRAW) + usb_gadget_set_selfpowered(gadget); + + /* interface and string IDs start at zero via kzalloc. + * we force endpoints to start unassigned; few controller + * drivers will zero ep->driver_data. + */ + usb_ep_autoconfig_reset(cdev->gadget); + + /* composite gadget needs to assign strings for whole device (like + * serial number), register function drivers, potentially update + * power state and consumption, etc + */ + status = composite_gadget_bind(cdev); + if (status < 0) + goto fail; + + cdev->sw_connected.name = "usb_connected"; + status = switch_dev_register(&cdev->sw_connected); + if (status < 0) + goto fail; + cdev->sw_config.name = "usb_configuration"; + status = switch_dev_register(&cdev->sw_config); + if (status < 0) + goto fail; + INIT_WORK(&cdev->switch_work, composite_switch_work); + + cdev->desc = *composite->dev; + cdev->desc.bMaxPacketSize0 = gadget->ep0->maxpacket; + + /* standardized runtime overrides for device ID data */ + if (idVendor) + cdev->desc.idVendor = cpu_to_le16(idVendor); + if (idProduct) + cdev->desc.idProduct = cpu_to_le16(idProduct); + if (bcdDevice) + cdev->desc.bcdDevice = cpu_to_le16(bcdDevice); + + /* string overrides */ + if (iManufacturer || !cdev->desc.iManufacturer) { + if (!iManufacturer && !composite->iManufacturer && + !*composite_manufacturer) + snprintf(composite_manufacturer, + sizeof composite_manufacturer, + "%s %s with %s", + init_utsname()->sysname, + init_utsname()->release, + gadget->name); + + cdev->manufacturer_override = + override_id(cdev, &cdev->desc.iManufacturer); + } + + if (iProduct || (!cdev->desc.iProduct && composite->iProduct)) + cdev->product_override = + override_id(cdev, &cdev->desc.iProduct); + + if (iSerialNumber) + cdev->serial_override = + override_id(cdev, &cdev->desc.iSerialNumber); + + /* has userspace failed to provide a serial number? */ + if (composite->needs_serial && !cdev->desc.iSerialNumber) + WARNING(cdev, "userspace failed to provide iSerialNumber\n"); + + /* finish up */ + status = device_create_file(&gadget->dev, &dev_attr_suspended); + if (status) + goto fail; + + INFO(cdev, "%s ready\n", composite->name); + return 0; + +fail: + composite_unbind(gadget); + return status; +} + +/*-------------------------------------------------------------------------*/ + +static void +composite_suspend(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + + /* REVISIT: should we have config level + * suspend/resume callbacks? + */ + DBG(cdev, "suspend\n"); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->suspend) + f->suspend(f); + } + } + if (composite->suspend) + composite->suspend(cdev); + + cdev->suspended = 1; + + usb_gadget_vbus_draw(gadget, 2); +} + +static void +composite_resume(struct usb_gadget *gadget) +{ + struct usb_composite_dev *cdev = get_gadget_data(gadget); + struct usb_function *f; + u8 maxpower; + + /* REVISIT: should we have config level + * suspend/resume callbacks? + */ + DBG(cdev, "resume\n"); + if (composite->resume) + composite->resume(cdev); + if (cdev->config) { + list_for_each_entry(f, &cdev->config->functions, list) { + if (f->resume) + f->resume(f); + } + + maxpower = cdev->config->bMaxPower; + + usb_gadget_vbus_draw(gadget, maxpower ? + (2 * maxpower) : CONFIG_USB_GADGET_VBUS_DRAW); + } + + cdev->suspended = 0; +} + +static int +composite_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct usb_function *f = dev_get_drvdata(dev); + + if (!f) { + /* this happens when the device is first created */ + return 0; + } + + if (add_uevent_var(env, "FUNCTION=%s", f->name)) + return -ENOMEM; + if (add_uevent_var(env, "ENABLED=%d", !f->disabled)) + return -ENOMEM; + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static struct usb_gadget_driver composite_driver = { + .speed = USB_SPEED_HIGH, + + .unbind = composite_unbind, + + .setup = composite_setup, + .disconnect = composite_disconnect, + + .suspend = composite_suspend, + .resume = composite_resume, + + .driver = { + .owner = THIS_MODULE, + }, +}; + +/** + * usb_composite_probe() - register a composite driver + * @driver: the driver to register + * @bind: the callback used to allocate resources that are shared across the + * whole device, such as string IDs, and add its configurations using + * @usb_add_config(). This may fail by returning a negative errno + * value; it should return zero on successful initialization. + * Context: single threaded during gadget setup + * + * This function is used to register drivers using the composite driver + * framework. The return value is zero, or a negative errno value. + * Those values normally come from the driver's @bind method, which does + * all the work of setting up the driver to match the hardware. + * + * On successful return, the gadget is ready to respond to requests from + * the host, unless one of its components invokes usb_gadget_disconnect() + * while it was binding. That would usually be done in order to wait for + * some userspace participation. + */ +int usb_composite_probe(struct usb_composite_driver *driver, + int (*bind)(struct usb_composite_dev *cdev)) +{ + if (!driver || !driver->dev || !bind || composite) + return -EINVAL; + + if (!driver->name) + driver->name = "composite"; + if (!driver->iProduct) + driver->iProduct = driver->name; + composite_driver.function = (char *) driver->name; + composite_driver.driver.name = driver->name; + composite = driver; + composite_gadget_bind = bind; + + driver->class = class_create(THIS_MODULE, "usb_composite"); + if (IS_ERR(driver->class)) + return PTR_ERR(driver->class); + driver->class->dev_uevent = composite_uevent; + + return usb_gadget_probe_driver(&composite_driver, composite_bind); +} + +/** + * usb_composite_unregister() - unregister a composite driver + * @driver: the driver to unregister + * + * This function is used to unregister drivers using the composite + * driver framework. + */ +void usb_composite_unregister(struct usb_composite_driver *driver) +{ + if (composite != driver) + return; + usb_gadget_unregister_driver(&composite_driver); +} + +/** + * usb_composite_setup_continue() - Continue with the control transfer + * @cdev: the composite device who's control transfer was kept waiting + * + * This function must be called by the USB function driver to continue + * with the control transfer's data/status stage in case it had requested to + * delay the data/status stages. A USB function's setup handler (e.g. set_alt()) + * can request the composite framework to delay the setup request's data/status + * stages by returning USB_GADGET_DELAYED_STATUS. + */ +void usb_composite_setup_continue(struct usb_composite_dev *cdev) +{ + int value; + struct usb_request *req = cdev->req; + unsigned long flags; + + DBG(cdev, "%s\n", __func__); + spin_lock_irqsave(&cdev->lock, flags); + + if (cdev->delayed_status == 0) { + WARN(cdev, "%s: Unexpected call\n", __func__); + + } else if (--cdev->delayed_status == 0) { + DBG(cdev, "%s: Completing delayed status\n", __func__); + req->length = 0; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG(cdev, "ep_queue --> %d\n", value); + req->status = 0; + composite_setup_complete(cdev->gadget->ep0, req); + } + } + + spin_unlock_irqrestore(&cdev->lock, flags); +} + diff --git a/drivers/usb/gadget/gadget_gbhc/config.c b/drivers/usb/gadget/gadget_gbhc/config.c new file mode 100644 index 0000000..09084fd --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/config.c @@ -0,0 +1,192 @@ +/* + * usb/gadget/config.c -- simplify building config descriptors + * + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/device.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + + +/** + * usb_descriptor_fillbuf - fill buffer with descriptors + * @buf: Buffer to be filled + * @buflen: Size of buf + * @src: Array of descriptor pointers, terminated by null pointer. + * + * Copies descriptors into the buffer, returning the length or a + * negative error code if they can't all be copied. Useful when + * assembling descriptors for an associated set of interfaces used + * as part of configuring a composite device; or in other cases where + * sets of descriptors need to be marshaled. + */ +int +usb_descriptor_fillbuf(void *buf, unsigned buflen, + const struct usb_descriptor_header **src) +{ + u8 *dest = buf; + + if (!src) + return -EINVAL; + + /* fill buffer from src[] until null descriptor ptr */ + for (; NULL != *src; src++) { + unsigned len = (*src)->bLength; + + if (len > buflen) + return -EINVAL; + memcpy(dest, *src, len); + buflen -= len; + dest += len; + } + return dest - (u8 *)buf; +} + + +/** + * usb_gadget_config_buf - builts a complete configuration descriptor + * @config: Header for the descriptor, including characteristics such + * as power requirements and number of interfaces. + * @desc: Null-terminated vector of pointers to the descriptors (interface, + * endpoint, etc) defining all functions in this device configuration. + * @buf: Buffer for the resulting configuration descriptor. + * @length: Length of buffer. If this is not big enough to hold the + * entire configuration descriptor, an error code will be returned. + * + * This copies descriptors into the response buffer, building a descriptor + * for that configuration. It returns the buffer length or a negative + * status code. The config.wTotalLength field is set to match the length + * of the result, but other descriptor fields (including power usage and + * interface count) must be set by the caller. + * + * Gadget drivers could use this when constructing a config descriptor + * in response to USB_REQ_GET_DESCRIPTOR. They will need to patch the + * resulting bDescriptorType value if USB_DT_OTHER_SPEED_CONFIG is needed. + */ +int usb_gadget_config_buf( + const struct usb_config_descriptor *config, + void *buf, + unsigned length, + const struct usb_descriptor_header **desc +) +{ + struct usb_config_descriptor *cp = buf; + int len; + + /* config descriptor first */ + if (length < USB_DT_CONFIG_SIZE || !desc) + return -EINVAL; + *cp = *config; + + /* then interface/endpoint/class/vendor/... */ + len = usb_descriptor_fillbuf(USB_DT_CONFIG_SIZE + (u8*)buf, + length - USB_DT_CONFIG_SIZE, desc); + if (len < 0) + return len; + len += USB_DT_CONFIG_SIZE; + if (len > 0xffff) + return -EINVAL; + + /* patch up the config descriptor */ + cp->bLength = USB_DT_CONFIG_SIZE; + cp->bDescriptorType = USB_DT_CONFIG; + cp->wTotalLength = cpu_to_le16(len); + cp->bmAttributes |= USB_CONFIG_ATT_ONE; + return len; +} + +/** + * usb_copy_descriptors - copy a vector of USB descriptors + * @src: null-terminated vector to copy + * Context: initialization code, which may sleep + * + * This makes a copy of a vector of USB descriptors. Its primary use + * is to support usb_function objects which can have multiple copies, + * each needing different descriptors. Functions may have static + * tables of descriptors, which are used as templates and customized + * with identifiers (for interfaces, strings, endpoints, and more) + * as needed by a given function instance. + */ +struct usb_descriptor_header ** +usb_copy_descriptors(struct usb_descriptor_header **src) +{ + struct usb_descriptor_header **tmp; + unsigned bytes; + unsigned n_desc; + void *mem; + struct usb_descriptor_header **ret; + + /* count descriptors and their sizes; then add vector size */ + for (bytes = 0, n_desc = 0, tmp = src; *tmp; tmp++, n_desc++) + bytes += (*tmp)->bLength; + bytes += (n_desc + 1) * sizeof(*tmp); + + mem = kmalloc(bytes, GFP_KERNEL); + if (!mem) + return NULL; + + /* fill in pointers starting at "tmp", + * to descriptors copied starting at "mem"; + * and return "ret" + */ + tmp = mem; + ret = mem; + mem += (n_desc + 1) * sizeof(*tmp); + while (*src) { + memcpy(mem, *src, (*src)->bLength); + *tmp = mem; + tmp++; + mem += (*src)->bLength; + src++; + } + *tmp = NULL; + + return ret; +} + +/** + * usb_find_endpoint - find a copy of an endpoint descriptor + * @src: original vector of descriptors + * @copy: copy of @src + * @match: endpoint descriptor found in @src + * + * This returns the copy of the @match descriptor made for @copy. Its + * intended use is to help remembering the endpoint descriptor to use + * when enabling a given endpoint. + */ +struct usb_endpoint_descriptor * +usb_find_endpoint( + struct usb_descriptor_header **src, + struct usb_descriptor_header **copy, + struct usb_endpoint_descriptor *match +) +{ + while (*src) { + if (*src == (void *) match) + return (void *)*copy; + src++; + copy++; + } + return NULL; +} diff --git a/drivers/usb/gadget/gadget_gbhc/epautoconf.c b/drivers/usb/gadget/gadget_gbhc/epautoconf.c new file mode 100644 index 0000000..06b4c64 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/epautoconf.c @@ -0,0 +1,361 @@ +/* + * epautoconf.c -- endpoint autoconfiguration for usb gadget drivers + * + * Copyright (C) 2004 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/device.h> + +#include <linux/ctype.h> +#include <linux/string.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include "gadget_chips.h" + + +/* we must assign addresses for configurable endpoints (like net2280) */ +static unsigned epnum; + +// #define MANY_ENDPOINTS +#ifdef MANY_ENDPOINTS +/* more than 15 configurable endpoints */ +static unsigned in_epnum; +#endif + + +/* + * This should work with endpoints from controller drivers sharing the + * same endpoint naming convention. By example: + * + * - ep1, ep2, ... address is fixed, not direction or type + * - ep1in, ep2out, ... address and direction are fixed, not type + * - ep1-bulk, ep2-bulk, ... address and type are fixed, not direction + * - ep1in-bulk, ep2out-iso, ... all three are fixed + * - ep-* ... no functionality restrictions + * + * Type suffixes are "-bulk", "-iso", or "-int". Numbers are decimal. + * Less common restrictions are implied by gadget_is_*(). + * + * NOTE: each endpoint is unidirectional, as specified by its USB + * descriptor; and isn't specific to a configuration or altsetting. + */ +static int +ep_matches ( + struct usb_gadget *gadget, + struct usb_ep *ep, + struct usb_endpoint_descriptor *desc +) +{ + u8 type; + const char *tmp; + u16 max; + + /* endpoint already claimed? */ + if (NULL != ep->driver_data) + return 0; + + /* only support ep0 for portable CONTROL traffic */ + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + if (USB_ENDPOINT_XFER_CONTROL == type) + return 0; + + /* some other naming convention */ + if ('e' != ep->name[0]) + return 0; + + /* type-restriction: "-iso", "-bulk", or "-int". + * direction-restriction: "in", "out". + */ + if ('-' != ep->name[2]) { + tmp = strrchr (ep->name, '-'); + if (tmp) { + switch (type) { + case USB_ENDPOINT_XFER_INT: + /* bulk endpoints handle interrupt transfers, + * except the toggle-quirky iso-synch kind + */ + if ('s' == tmp[2]) // == "-iso" + return 0; + /* for now, avoid PXA "interrupt-in"; + * it's documented as never using DATA1. + */ + if (gadget_is_pxa (gadget) + && 'i' == tmp [1]) + return 0; + break; + case USB_ENDPOINT_XFER_BULK: + if ('b' != tmp[1]) // != "-bulk" + return 0; + break; + case USB_ENDPOINT_XFER_ISOC: + if ('s' != tmp[2]) // != "-iso" + return 0; + } + } else { + tmp = ep->name + strlen (ep->name); + } + + /* direction-restriction: "..in-..", "out-.." */ + tmp--; + if (!isdigit (*tmp)) { + if (desc->bEndpointAddress & USB_DIR_IN) { + if ('n' != *tmp) + return 0; + } else { + if ('t' != *tmp) + return 0; + } + } + } + + /* + * If the protocol driver hasn't yet decided on wMaxPacketSize + * and wants to know the maximum possible, provide the info. + */ + if (desc->wMaxPacketSize == 0) + desc->wMaxPacketSize = cpu_to_le16(ep->maxpacket); + + /* endpoint maxpacket size is an input parameter, except for bulk + * where it's an output parameter representing the full speed limit. + * the usb spec fixes high speed bulk maxpacket at 512 bytes. + */ + max = 0x7ff & le16_to_cpu(desc->wMaxPacketSize); + switch (type) { + case USB_ENDPOINT_XFER_INT: + /* INT: limit 64 bytes full speed, 1024 high speed */ + if (!gadget->is_dualspeed && max > 64) + return 0; + /* FALLTHROUGH */ + + case USB_ENDPOINT_XFER_ISOC: + /* ISO: limit 1023 bytes full speed, 1024 high speed */ + if (ep->maxpacket < max) + return 0; + if (!gadget->is_dualspeed && max > 1023) + return 0; + + /* BOTH: "high bandwidth" works only at high speed */ + if ((desc->wMaxPacketSize & cpu_to_le16(3<<11))) { + if (!gadget->is_dualspeed) + return 0; + /* configure your hardware with enough buffering!! */ + } + break; + } + + /* MATCH!! */ + + /* report address */ + desc->bEndpointAddress &= USB_DIR_IN; + if (isdigit (ep->name [2])) { + u8 num = simple_strtoul (&ep->name [2], NULL, 10); + desc->bEndpointAddress |= num; +#ifdef MANY_ENDPOINTS + } else if (desc->bEndpointAddress & USB_DIR_IN) { + if (++in_epnum > 15) + return 0; + desc->bEndpointAddress = USB_DIR_IN | in_epnum; +#endif + } else { + if (++epnum > 15) + return 0; + desc->bEndpointAddress |= epnum; + } + + /* report (variable) full speed bulk maxpacket */ + if (USB_ENDPOINT_XFER_BULK == type) { + int size = ep->maxpacket; + + /* min() doesn't work on bitfields with gcc-3.5 */ + if (size > 64) + size = 64; + desc->wMaxPacketSize = cpu_to_le16(size); + } + return 1; +} + +static struct usb_ep * +find_ep (struct usb_gadget *gadget, const char *name) +{ + struct usb_ep *ep; + + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + if (0 == strcmp (ep->name, name)) + return ep; + } + return NULL; +} + +/** + * usb_ep_autoconfig - choose an endpoint matching the descriptor + * @gadget: The device to which the endpoint must belong. + * @desc: Endpoint descriptor, with endpoint direction and transfer mode + * initialized. For periodic transfers, the maximum packet + * size must also be initialized. This is modified on success. + * + * By choosing an endpoint to use with the specified descriptor, this + * routine simplifies writing gadget drivers that work with multiple + * USB device controllers. The endpoint would be passed later to + * usb_ep_enable(), along with some descriptor. + * + * That second descriptor won't always be the same as the first one. + * For example, isochronous endpoints can be autoconfigured for high + * bandwidth, and then used in several lower bandwidth altsettings. + * Also, high and full speed descriptors will be different. + * + * Be sure to examine and test the results of autoconfiguration on your + * hardware. This code may not make the best choices about how to use the + * USB controller, and it can't know all the restrictions that may apply. + * Some combinations of driver and hardware won't be able to autoconfigure. + * + * On success, this returns an un-claimed usb_ep, and modifies the endpoint + * descriptor bEndpointAddress. For bulk endpoints, the wMaxPacket value + * is initialized as if the endpoint were used at full speed. To prevent + * the endpoint from being returned by a later autoconfig call, claim it + * by assigning ep->driver_data to some non-null value. + * + * On failure, this returns a null endpoint descriptor. + */ +struct usb_ep *usb_ep_autoconfig ( + struct usb_gadget *gadget, + struct usb_endpoint_descriptor *desc +) +{ + struct usb_ep *ep; + u8 type; + + type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + /* First, apply chip-specific "best usage" knowledge. + * This might make a good usb_gadget_ops hook ... + */ + if (gadget_is_net2280 (gadget) && type == USB_ENDPOINT_XFER_INT) { + /* ep-e, ep-f are PIO with only 64 byte fifos */ + ep = find_ep (gadget, "ep-e"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep-f"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + + } else if (gadget_is_goku (gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep (gadget, "ep3-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + /* DMA may be available */ + ep = find_ep (gadget, "ep2-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } + +#ifdef CONFIG_BLACKFIN + } else if (gadget_is_musbhdrc(gadget)) { + if ((USB_ENDPOINT_XFER_BULK == type) || + (USB_ENDPOINT_XFER_ISOC == type)) { + if (USB_DIR_IN & desc->bEndpointAddress) + ep = find_ep (gadget, "ep5in"); + else + ep = find_ep (gadget, "ep6out"); + } else if (USB_ENDPOINT_XFER_INT == type) { + if (USB_DIR_IN & desc->bEndpointAddress) + ep = find_ep(gadget, "ep1in"); + else + ep = find_ep(gadget, "ep2out"); + } else + ep = NULL; + if (ep && ep_matches (gadget, ep, desc)) + return ep; +#endif + } else if (gadget_is_s3c(gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep (gadget, "ep3-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep6-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep9-int"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep2-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep5-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep8-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && !(USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep (gadget, "ep1-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep4-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + ep = find_ep (gadget, "ep7-bulk"); + if (ep && ep_matches (gadget, ep, desc)) + return ep; + } + } + + /* Second, look at endpoints until an unclaimed one looks usable */ + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + if (ep_matches (gadget, ep, desc)) + return ep; + } + + /* Fail */ + return NULL; +} + +/** + * usb_ep_autoconfig_reset - reset endpoint autoconfig state + * @gadget: device for which autoconfig state will be reset + * + * Use this for devices where one configuration may need to assign + * endpoint resources very differently from the next one. It clears + * state such as ep->driver_data and the record of assigned endpoints + * used by usb_ep_autoconfig(). + */ +void usb_ep_autoconfig_reset (struct usb_gadget *gadget) +{ + struct usb_ep *ep; + + list_for_each_entry (ep, &gadget->ep_list, ep_list) { + ep->driver_data = NULL; + } +#ifdef MANY_ENDPOINTS + in_epnum = 0; +#endif + epnum = 0; +} + diff --git a/drivers/usb/gadget/gadget_gbhc/ether.c b/drivers/usb/gadget/gadget_gbhc/ether.c new file mode 100644 index 0000000..1690c9d --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/ether.c @@ -0,0 +1,421 @@ +/* + * ether.c -- Ethernet gadget driver, with CDC and non-CDC options + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/kernel.h> +#include <linux/utsname.h> + + +#if defined USB_ETH_RNDIS +# undef USB_ETH_RNDIS +#endif +#ifdef CONFIG_USB_ETH_RNDIS +# define USB_ETH_RNDIS y +#endif + +#include "u_ether.h" + + +/* + * Ethernet gadget driver -- with CDC and non-CDC options + * Builds on hardware support for a full duplex link. + * + * CDC Ethernet is the standard USB solution for sending Ethernet frames + * using USB. Real hardware tends to use the same framing protocol but look + * different for control features. This driver strongly prefers to use + * this USB-IF standard as its open-systems interoperability solution; + * most host side USB stacks (except from Microsoft) support it. + * + * This is sometimes called "CDC ECM" (Ethernet Control Model) to support + * TLA-soup. "CDC ACM" (Abstract Control Model) is for modems, and a new + * "CDC EEM" (Ethernet Emulation Model) is starting to spread. + * + * There's some hardware that can't talk CDC ECM. We make that hardware + * implement a "minimalist" vendor-agnostic CDC core: same framing, but + * link-level setup only requires activating the configuration. Only the + * endpoint descriptors, and product/vendor IDs, are relevant; no control + * operations are available. Linux supports it, but other host operating + * systems may not. (This is a subset of CDC Ethernet.) + * + * It turns out that if you add a few descriptors to that "CDC Subset", + * (Windows) host side drivers from MCCI can treat it as one submode of + * a proprietary scheme called "SAFE" ... without needing to know about + * specific product/vendor IDs. So we do that, making it easier to use + * those MS-Windows drivers. Those added descriptors make it resemble a + * CDC MDLM device, but they don't change device behavior at all. (See + * MCCI Engineering report 950198 "SAFE Networking Functions".) + * + * A third option is also in use. Rather than CDC Ethernet, or something + * simpler, Microsoft pushes their own approach: RNDIS. The published + * RNDIS specs are ambiguous and appear to be incomplete, and are also + * needlessly complex. They borrow more from CDC ACM than CDC ECM. + */ + +#define DRIVER_DESC "Ethernet Gadget" +#define DRIVER_VERSION "Memorial Day 2008" + +#ifdef USB_ETH_RNDIS +#define PREFIX "RNDIS/" +#else +#define PREFIX "" +#endif + +/* + * This driver aims for interoperability by using CDC ECM unless + * + * can_support_ecm() + * + * returns false, in which case it supports the CDC Subset. By default, + * that returns true; most hardware has no problems with CDC ECM, that's + * a good default. Previous versions of this driver had no default; this + * version changes that, removing overhead for new controller support. + * + * IF YOUR HARDWARE CAN'T SUPPORT CDC ECM, UPDATE THAT ROUTINE! + */ + +static inline bool has_rndis(void) +{ +#ifdef USB_ETH_RNDIS + return true; +#else + return false; +#endif +} + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#include "f_ecm.c" +#include "f_subset.c" +#ifdef USB_ETH_RNDIS +#include "f_rndis.c" +#include "rndis.c" +#endif +#include "f_eem.c" +#include "u_ether.c" + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ + +/* For hardware that can't talk CDC, we use the same vendor ID that + * ARM Linux has used for ethernet-over-usb, both with sa1100 and + * with pxa250. We're protocol-compatible, if the host-side drivers + * use the endpoint descriptors. bcdDevice (version) is nonzero, so + * drivers that need to hard-wire endpoint numbers have a hook. + * + * The protocol is a minimal subset of CDC Ether, which works on any bulk + * hardware that's not deeply broken ... even on hardware that can't talk + * RNDIS (like SA-1100, with no interrupt endpoint, or anything that + * doesn't handle control-OUT). + */ +#define SIMPLE_VENDOR_NUM 0x049f +#define SIMPLE_PRODUCT_NUM 0x505a + +/* For hardware that can talk RNDIS and either of the above protocols, + * use this ID ... the windows INF files will know it. Unless it's + * used with CDC Ethernet, Linux 2.4 hosts will need updates to choose + * the non-RNDIS configuration. + */ +#define RNDIS_VENDOR_NUM 0x0525 /* NetChip */ +#define RNDIS_PRODUCT_NUM 0xa4a2 /* Ethernet/RNDIS Gadget */ + +/* For EEM gadgets */ +#define EEM_VENDOR_NUM 0x1d6b /* Linux Foundation */ +#define EEM_PRODUCT_NUM 0x0102 /* EEM Gadget */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16 (0x0200), + + .bDeviceClass = USB_CLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = cpu_to_le16 (CDC_VENDOR_NUM), + .idProduct = cpu_to_le16 (CDC_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 + +static char manufacturer[50]; + +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer, + [STRING_PRODUCT_IDX].s = PREFIX DRIVER_DESC, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static u8 hostaddr[ETH_ALEN]; + +/*-------------------------------------------------------------------------*/ + +/* + * We may not have an RNDIS configuration, but if we do it needs to be + * the first one present. That's to make Microsoft's drivers happy, + * and to follow DOCSIS 1.0 (cable modem standard). + */ +static int __init rndis_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + return rndis_bind_config(c, hostaddr); +} + +static struct usb_configuration rndis_config_driver = { + .label = "RNDIS", + .bConfigurationValue = 2, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +#ifdef CONFIG_USB_ETH_EEM +static int use_eem = 1; +#else +static int use_eem; +#endif +module_param(use_eem, bool, 0); +MODULE_PARM_DESC(use_eem, "use CDC EEM mode"); + +/* + * We _always_ have an ECM, CDC Subset, or EEM configuration. + */ +static int __init eth_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + if (use_eem) + return eem_bind_config(c); + else if (can_support_ecm(c->cdev->gadget)) + return ecm_bind_config(c, hostaddr); + else + return geth_bind_config(c, hostaddr); +} + +static struct usb_configuration eth_config_driver = { + /* .label = f(hardware) */ + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init eth_bind(struct usb_composite_dev *cdev) +{ + int gcnum; + struct usb_gadget *gadget = cdev->gadget; + int status; + + /* set up network link layer */ + status = gether_setup(cdev->gadget, hostaddr); + if (status < 0) + return status; + + /* set up main config label and device descriptor */ + if (use_eem) { + /* EEM */ + eth_config_driver.label = "CDC Ethernet (EEM)"; + device_desc.idVendor = cpu_to_le16(EEM_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(EEM_PRODUCT_NUM); + } else if (can_support_ecm(cdev->gadget)) { + /* ECM */ + eth_config_driver.label = "CDC Ethernet (ECM)"; + } else { + /* CDC Subset */ + eth_config_driver.label = "CDC Subset/SAFE"; + + device_desc.idVendor = cpu_to_le16(SIMPLE_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(SIMPLE_PRODUCT_NUM); + if (!has_rndis()) + device_desc.bDeviceClass = USB_CLASS_VENDOR_SPEC; + } + + if (has_rndis()) { + /* RNDIS plus ECM-or-Subset */ + device_desc.idVendor = cpu_to_le16(RNDIS_VENDOR_NUM); + device_desc.idProduct = cpu_to_le16(RNDIS_PRODUCT_NUM); + device_desc.bNumConfigurations = 2; + } + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum); + else { + /* We assume that can_support_ecm() tells the truth; + * but if the controller isn't recognized at all then + * that assumption is a bit more likely to be wrong. + */ + dev_warn(&gadget->dev, + "controller '%s' not recognized; trying %s\n", + gadget->name, + eth_config_driver.label); + device_desc.bcdDevice = + cpu_to_le16(0x0300 | 0x0099); + } + + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + /* device descriptor strings: manufacturer, product */ + snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_MANUFACTURER_IDX].id = status; + device_desc.iManufacturer = status; + + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_PRODUCT_IDX].id = status; + device_desc.iProduct = status; + + /* register our configuration(s); RNDIS first, if it's used */ + if (has_rndis()) { + status = usb_add_config(cdev, &rndis_config_driver, + rndis_do_config); + if (status < 0) + goto fail; + } + + status = usb_add_config(cdev, ð_config_driver, eth_do_config); + if (status < 0) + goto fail; + + dev_info(&gadget->dev, "%s, version: " DRIVER_VERSION "\n", + DRIVER_DESC); + + return 0; + +fail: + gether_cleanup(); + return status; +} + +static int __exit eth_unbind(struct usb_composite_dev *cdev) +{ + gether_cleanup(); + return 0; +} + +static struct usb_composite_driver eth_driver = { + .name = "g_ether", + .dev = &device_desc, + .strings = dev_strings, + .unbind = __exit_p(eth_unbind), +}; + +MODULE_DESCRIPTION(PREFIX DRIVER_DESC); +MODULE_AUTHOR("David Brownell, Benedikt Spanger"); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return usb_composite_probe(ð_driver, eth_bind); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(ð_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/gadget_gbhc/f_acm.c b/drivers/usb/gadget/gadget_gbhc/f_acm.c new file mode 100644 index 0000000..cf2e7fc --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/f_acm.c @@ -0,0 +1,842 @@ +/* + * f_acm.c -- USB CDC serial (ACM) function driver + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 by David Brownell + * Copyright (C) 2008 by Nokia Corporation + * Copyright (C) 2009 by Samsung Electronics + * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/usb/android_composite.h> + +#include "u_serial.h" +#include "gadget_chips.h" + + +/* + * This CDC ACM function support just wraps control functions and + * notifications around the generic serial-over-usb code. + * + * Because CDC ACM is standardized by the USB-IF, many host operating + * systems have drivers for it. Accordingly, ACM is the preferred + * interop solution for serial-port type connections. The control + * models are often not necessary, and in any case don't do much in + * this bare-bones implementation. + * + * Note that even MS-Windows has some support for ACM. However, that + * support is somewhat broken because when you use ACM in a composite + * device, having multiple interfaces confuses the poor OS. It doesn't + * seem to understand CDC Union descriptors. The new "association" + * descriptors (roughly equivalent to CDC Unions) may sometimes help. + */ + +struct acm_ep_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + struct usb_endpoint_descriptor *notify; +}; + +struct f_acm { + struct gserial port; + u8 ctrl_id, data_id; + u8 port_num; + + u8 pending; + + /* lock is mostly for pending and notify_req ... they get accessed + * by callbacks both from tty (open/close/break) under its spinlock, + * and notify_req.complete() which can't use that lock. + */ + spinlock_t lock; + + struct acm_ep_descs fs; + struct acm_ep_descs hs; + + struct usb_ep *notify; + struct usb_endpoint_descriptor *notify_desc; + struct usb_request *notify_req; + + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ + + /* SetControlLineState request -- CDC 1.1 section 6.2.14 (INPUT) */ + u16 port_handshake_bits; +#define ACM_CTRL_RTS (1 << 1) /* unused with full duplex */ +#define ACM_CTRL_DTR (1 << 0) /* host is ready for data r/w */ + + /* SerialState notification -- CDC 1.1 section 6.3.5 (OUTPUT) */ + u16 serial_state; +#define ACM_CTRL_OVERRUN (1 << 6) +#define ACM_CTRL_PARITY (1 << 5) +#define ACM_CTRL_FRAMING (1 << 4) +#define ACM_CTRL_RI (1 << 3) +#define ACM_CTRL_BRK (1 << 2) +#define ACM_CTRL_DSR (1 << 1) +#define ACM_CTRL_DCD (1 << 0) +}; + +static inline struct f_acm *func_to_acm(struct usb_function *f) +{ + return container_of(f, struct f_acm, port.func); +} + +static inline struct f_acm *port_to_acm(struct gserial *p) +{ + return container_of(p, struct f_acm, port); +} + +/*-------------------------------------------------------------------------*/ + +/* notification endpoint uses smallish and infrequent fixed-size messages */ + +#define GS_LOG2_NOTIFY_INTERVAL 5 /* 1 << 5 == 32 msec */ +#define GS_NOTIFY_MAXPACKET 10 /* notification + 2 bytes */ + +/* interface and class descriptors: */ + +static struct usb_interface_assoc_descriptor +acm_iad_descriptor = { + .bLength = sizeof acm_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, // control + data + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ACM, + .bFunctionProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iFunction = DYNAMIC */ +}; + + +static struct usb_interface_descriptor acm_control_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_AT_V25TER, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_interface_descriptor acm_data_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc acm_header_desc = { + .bLength = sizeof(acm_header_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor +acm_call_mgmt_descriptor = { + .bLength = sizeof(acm_call_mgmt_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + .bmCapabilities = 0, + /* .bDataInterface = DYNAMIC */ +}; + +static struct usb_cdc_acm_descriptor acm_descriptor = { + .bLength = sizeof(acm_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + .bmCapabilities = USB_CDC_CAP_LINE, +}; + +static struct usb_cdc_union_desc acm_union_desc = { + .bLength = sizeof(acm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor acm_fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = 1 << GS_LOG2_NOTIFY_INTERVAL, +}; + +static struct usb_endpoint_descriptor acm_fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor acm_fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *acm_fs_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_fs_notify_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_fs_in_desc, + (struct usb_descriptor_header *) &acm_fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor acm_hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(GS_NOTIFY_MAXPACKET), + .bInterval = GS_LOG2_NOTIFY_INTERVAL+4, +}; + +static struct usb_endpoint_descriptor acm_hs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor acm_hs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_descriptor_header *acm_hs_function[] = { + (struct usb_descriptor_header *) &acm_iad_descriptor, + (struct usb_descriptor_header *) &acm_control_interface_desc, + (struct usb_descriptor_header *) &acm_header_desc, + (struct usb_descriptor_header *) &acm_call_mgmt_descriptor, + (struct usb_descriptor_header *) &acm_descriptor, + (struct usb_descriptor_header *) &acm_union_desc, + (struct usb_descriptor_header *) &acm_hs_notify_desc, + (struct usb_descriptor_header *) &acm_data_interface_desc, + (struct usb_descriptor_header *) &acm_hs_in_desc, + (struct usb_descriptor_header *) &acm_hs_out_desc, + NULL, +}; + +/* string descriptors: */ + +#define ACM_CTRL_IDX 0 +#define ACM_DATA_IDX 1 +#define ACM_IAD_IDX 2 + +/* static strings, in UTF-8 */ +static struct usb_string acm_string_defs[] = { + [ACM_CTRL_IDX].s = "CDC Abstract Control Model (ACM)", + [ACM_DATA_IDX].s = "CDC ACM Data", + [ACM_IAD_IDX ].s = "CDC Serial", + { /* ZEROES END LIST */ }, +}; + +static struct usb_gadget_strings acm_string_table = { + .language = 0x0409, /* en-us */ + .strings = acm_string_defs, +}; + +static struct usb_gadget_strings *acm_strings[] = { + &acm_string_table, + NULL, +}; + +/*-------------------------------------------------------------------------*/ + +/* ACM control ... data handling is delegated to tty library code. + * The main task of this function is to activate and deactivate + * that code based on device state; track parameters like line + * speed, handshake state, and so on; and issue notifications. + */ + +static void acm_complete_set_line_coding(struct usb_ep *ep, + struct usb_request *req) +{ + struct f_acm *acm = ep->driver_data; + struct usb_composite_dev *cdev = acm->port.func.config->cdev; + + if (req->status != 0) { + DBG(cdev, "acm ttyGS%d completion, err %d\n", + acm->port_num, req->status); + return; + } + + /* normal completion */ + if (req->actual != sizeof(acm->port_line_coding)) { + DBG(cdev, "acm ttyGS%d short resp, len %d\n", + acm->port_num, req->actual); + usb_ep_set_halt(ep); + } else { + struct usb_cdc_line_coding *value = req->buf; + + /* REVISIT: we currently just remember this data. + * If we change that, (a) validate it first, then + * (b) update whatever hardware needs updating, + * (c) worry about locking. This is information on + * the order of 9600-8-N-1 ... most of which means + * nothing unless we control a real RS232 line. + */ + acm->port_line_coding = *value; + } +} + +static int acm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -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); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + * + * Note CDC spec table 4 lists the ACM request profile. It requires + * encapsulated command support ... we don't handle any, and respond + * to them by stalling. Options include get/set/clear comm features + * (not that useful) and SEND_BREAK. + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* SET_LINE_CODING ... just read and save what the host sends */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_LINE_CODING: + if (w_length != sizeof(struct usb_cdc_line_coding) + || w_index != acm->ctrl_id) + goto invalid; + + value = w_length; + cdev->gadget->ep0->driver_data = acm; + req->complete = acm_complete_set_line_coding; + break; + + /* GET_LINE_CODING ... return what host sent, or initial value */ + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_GET_LINE_CODING: + if (w_index != acm->ctrl_id) + goto invalid; + + value = min_t(unsigned, w_length, + sizeof(struct usb_cdc_line_coding)); + memcpy(req->buf, &acm->port_line_coding, value); + break; + + /* SET_CONTROL_LINE_STATE ... save what the host sent */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_REQ_SET_CONTROL_LINE_STATE: + if (w_index != acm->ctrl_id) + goto invalid; + + value = 0; + + /* FIXME we should not allow data to flow until the + * host sets the ACM_CTRL_DTR bit; and when it clears + * that bit, we should return to that no-flow state. + */ + acm->port_handshake_bits = w_value; + break; + + default: +invalid: + VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "acm ttyGS%d req%02x.%02x v%04x i%04x l%d\n", + acm->port_num, ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "acm response on ttyGS%d, err %d\n", + acm->port_num, value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + +static int acm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0, so this is an activation or a reset */ + + if (intf == acm->ctrl_id) { + if (acm->notify->driver_data) { + VDBG(cdev, "reset acm control interface %d\n", intf); + usb_ep_disable(acm->notify); + } else { + VDBG(cdev, "init acm ctrl interface %d\n", intf); + } + acm->notify_desc = ep_choose(cdev->gadget, + acm->hs.notify, + acm->fs.notify); + usb_ep_enable(acm->notify, acm->notify_desc); + acm->notify->driver_data = acm; + + } else if (intf == acm->data_id) { + if (acm->port.in->driver_data) { + DBG(cdev, "reset acm ttyGS%d\n", acm->port_num); + gserial_disconnect(&acm->port); + } else { + DBG(cdev, "activate acm ttyGS%d\n", acm->port_num); + } + acm->port.in_desc = ep_choose(cdev->gadget, + acm->hs.in, acm->fs.in); + acm->port.out_desc = ep_choose(cdev->gadget, + acm->hs.out, acm->fs.out); + gserial_connect(&acm->port, acm->port_num); + + } else + return -EINVAL; + + return 0; +} + +static void acm_disable(struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "acm ttyGS%d deactivated\n", acm->port_num); + gserial_disconnect(&acm->port); + usb_ep_disable(acm->notify); + acm->notify->driver_data = NULL; +} + +/*-------------------------------------------------------------------------*/ + +/** + * acm_cdc_notify - issue CDC notification to host + * @acm: wraps host to be notified + * @type: notification type + * @value: Refer to cdc specs, wValue field. + * @data: data to be sent + * @length: size of data + * Context: irqs blocked, acm->lock held, acm_notify_req non-null + * + * Returns zero on success or a negative errno. + * + * See section 6.3.5 of the CDC 1.1 specification for information + * about the only notification we issue: SerialState change. + */ +static int acm_cdc_notify(struct f_acm *acm, u8 type, u16 value, + void *data, unsigned length) +{ + struct usb_ep *ep = acm->notify; + struct usb_request *req; + struct usb_cdc_notification *notify; + const unsigned len = sizeof(*notify) + length; + void *buf; + int status; + + req = acm->notify_req; + acm->notify_req = NULL; + acm->pending = false; + + req->length = len; + notify = req->buf; + buf = notify + 1; + + notify->bmRequestType = USB_DIR_IN | USB_TYPE_CLASS + | USB_RECIP_INTERFACE; + notify->bNotificationType = type; + notify->wValue = cpu_to_le16(value); + notify->wIndex = cpu_to_le16(acm->ctrl_id); + notify->wLength = cpu_to_le16(length); + memcpy(buf, data, length); + + /* ep_queue() can complete immediately if it fills the fifo... */ + spin_unlock(&acm->lock); + status = usb_ep_queue(ep, req, GFP_ATOMIC); + spin_lock(&acm->lock); + + if (status < 0) { + ERROR(acm->port.func.config->cdev, + "acm ttyGS%d can't notify serial state, %d\n", + acm->port_num, status); + acm->notify_req = req; + } + + return status; +} + +static int acm_notify_serial_state(struct f_acm *acm) +{ + struct usb_composite_dev *cdev = acm->port.func.config->cdev; + int status; + + spin_lock(&acm->lock); + if (acm->notify_req) { + DBG(cdev, "acm ttyGS%d serial state %04x\n", + acm->port_num, acm->serial_state); + status = acm_cdc_notify(acm, USB_CDC_NOTIFY_SERIAL_STATE, + 0, &acm->serial_state, sizeof(acm->serial_state)); + } else { + acm->pending = true; + status = 0; + } + spin_unlock(&acm->lock); + return status; +} + +static void acm_cdc_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_acm *acm = req->context; + u8 doit = false; + + /* on this call path we do NOT hold the port spinlock, + * which is why ACM needs its own spinlock + */ + spin_lock(&acm->lock); + if (req->status != -ESHUTDOWN) + doit = acm->pending; + acm->notify_req = req; + spin_unlock(&acm->lock); + + if (doit) + acm_notify_serial_state(acm); +} + +/* connect == the TTY link is open */ + +static void acm_connect(struct gserial *port) +{ + struct f_acm *acm = port_to_acm(port); + + acm->serial_state |= ACM_CTRL_DSR | ACM_CTRL_DCD; + acm_notify_serial_state(acm); +} + +static void acm_disconnect(struct gserial *port) +{ + struct f_acm *acm = port_to_acm(port); + + acm->serial_state &= ~(ACM_CTRL_DSR | ACM_CTRL_DCD); + acm_notify_serial_state(acm); +} + +static int acm_send_break(struct gserial *port, int duration) +{ + struct f_acm *acm = port_to_acm(port); + u16 state; + + state = acm->serial_state; + state &= ~ACM_CTRL_BRK; + if (duration) + state |= ACM_CTRL_BRK; + + acm->serial_state = state; + return acm_notify_serial_state(acm); +} + +/*-------------------------------------------------------------------------*/ + +/* ACM function driver setup/binding */ +static int +acm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_acm *acm = func_to_acm(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs, and patch descriptors */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + acm->ctrl_id = status; + acm_iad_descriptor.bFirstInterface = status; + + acm_control_interface_desc.bInterfaceNumber = status; + acm_union_desc .bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + acm->data_id = status; + + acm_data_interface_desc.bInterfaceNumber = status; + acm_union_desc.bSlaveInterface0 = status; + acm_call_mgmt_descriptor.bDataInterface = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_in_desc); + if (!ep) + goto fail; + acm->port.in = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_out_desc); + if (!ep) + goto fail; + acm->port.out = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &acm_fs_notify_desc); + if (!ep) + goto fail; + acm->notify = ep; + ep->driver_data = cdev; /* claim */ + + /* allocate notification */ + acm->notify_req = gs_alloc_req(ep, + sizeof(struct usb_cdc_notification) + 2, + GFP_KERNEL); + if (!acm->notify_req) + goto fail; + + acm->notify_req->complete = acm_cdc_notify_complete; + acm->notify_req->context = acm; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(acm_fs_function); + if (!f->descriptors) + goto fail; + + acm->fs.in = usb_find_endpoint(acm_fs_function, + f->descriptors, &acm_fs_in_desc); + acm->fs.out = usb_find_endpoint(acm_fs_function, + f->descriptors, &acm_fs_out_desc); + acm->fs.notify = usb_find_endpoint(acm_fs_function, + f->descriptors, &acm_fs_notify_desc); + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + acm_hs_in_desc.bEndpointAddress = + acm_fs_in_desc.bEndpointAddress; + acm_hs_out_desc.bEndpointAddress = + acm_fs_out_desc.bEndpointAddress; + acm_hs_notify_desc.bEndpointAddress = + acm_fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(acm_hs_function); + + acm->hs.in = usb_find_endpoint(acm_hs_function, + f->hs_descriptors, &acm_hs_in_desc); + acm->hs.out = usb_find_endpoint(acm_hs_function, + f->hs_descriptors, &acm_hs_out_desc); + acm->hs.notify = usb_find_endpoint(acm_hs_function, + f->hs_descriptors, &acm_hs_notify_desc); + } + + DBG(cdev, "acm ttyGS%d: %s speed IN/%s OUT/%s NOTIFY/%s\n", + acm->port_num, + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + acm->port.in->name, acm->port.out->name, + acm->notify->name); + return 0; + +fail: + if (acm->notify_req) + gs_free_req(acm->notify, acm->notify_req); + + /* we might as well release our claims on endpoints */ + if (acm->notify) + acm->notify->driver_data = NULL; + if (acm->port.out) + acm->port.out->driver_data = NULL; + if (acm->port.in) + acm->port.in->driver_data = NULL; + + ERROR(cdev, "%s/%p: can't bind, err %d\n", f->name, f, status); + + return status; +} + +static void +acm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_acm *acm = func_to_acm(f); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + gs_free_req(acm->notify, acm->notify_req); + kfree(acm->port.func.name); + kfree(acm); +} + +/* Some controllers can't support CDC ACM ... */ +static inline bool can_support_cdc(struct usb_configuration *c) +{ + /* everything else is *probably* fine ... */ + return true; +} + +/** + * acm_bind_config - add a CDC ACM function to a configuration + * @c: the configuration to support the CDC ACM instance + * @port_num: /dev/ttyGS* port this interface will use + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gserial_setup() with enough ports to + * handle all the ones it binds. Caller is also responsible + * for calling @gserial_cleanup() before module unload. + */ +int acm_bind_config(struct usb_configuration *c, u8 port_num) +{ + struct f_acm *acm; + int status; + + if (!can_support_cdc(c)) + return -EINVAL; + + /* REVISIT might want instance-specific strings to help + * distinguish instances ... + */ + + /* maybe allocate device-global string IDs, and patch descriptors */ + if (acm_string_defs[ACM_CTRL_IDX].id == 0) { + status = usb_string_id(c->cdev); + if (status < 0) + return status; + acm_string_defs[ACM_CTRL_IDX].id = status; + + acm_control_interface_desc.iInterface = status; + + status = usb_string_id(c->cdev); + if (status < 0) + return status; + acm_string_defs[ACM_DATA_IDX].id = status; + + acm_data_interface_desc.iInterface = status; + + status = usb_string_id(c->cdev); + if (status < 0) + return status; + acm_string_defs[ACM_IAD_IDX].id = status; + + acm_iad_descriptor.iFunction = status; + } + + /* allocate and initialize one new instance */ + acm = kzalloc(sizeof *acm, GFP_KERNEL); + if (!acm) + return -ENOMEM; + + spin_lock_init(&acm->lock); + + acm->port_num = port_num; + + acm->port.connect = acm_connect; + acm->port.disconnect = acm_disconnect; + acm->port.send_break = acm_send_break; + + acm->port.func.name = kasprintf(GFP_KERNEL, "acm%u", port_num); + if (!acm->port.func.name) { + kfree(acm); + return -ENOMEM; + } + acm->port.func.strings = acm_strings; + /* descriptors are per-instance copies */ + acm->port.func.bind = acm_bind; + acm->port.func.unbind = acm_unbind; + acm->port.func.set_alt = acm_set_alt; + acm->port.func.setup = acm_setup; + acm->port.func.disable = acm_disable; + + status = usb_add_function(c, &acm->port.func); + if (status) + kfree(acm); + return status; +} + +#ifdef CONFIG_USB_ANDROID_ACM +#include <linux/platform_device.h> + +static struct acm_platform_data *acm_pdata; + +static int acm_probe(struct platform_device *pdev) +{ + acm_pdata = pdev->dev.platform_data; + return 0; +} + +static struct platform_driver acm_platform_driver = { + .driver = { .name = "acm", }, + .probe = acm_probe, +}; + +int acm_function_bind_config(struct usb_configuration *c) +{ + int i; + u8 num_inst = acm_pdata ? acm_pdata->num_inst : 1; + int ret = gserial_setup(c->cdev->gadget, num_inst); + + if (ret) + return ret; + + for (i = 0; i < num_inst; i++) { + ret = acm_bind_config(c, i); + if (ret) { + pr_err("Could not bind acm%u config\n", i); + break; + } + } + + return ret; +} + +static struct android_usb_function acm_function = { + .name = "acm", + .bind_config = acm_function_bind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_acm init\n"); + platform_driver_register(&acm_platform_driver); + android_register_function(&acm_function); + return 0; +} +module_init(init); + +#endif /* CONFIG_USB_ANDROID_ACM */ diff --git a/drivers/usb/gadget/gadget_gbhc/f_adb.c b/drivers/usb/gadget/gadget_gbhc/f_adb.c new file mode 100644 index 0000000..a8d7b7d --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/f_adb.c @@ -0,0 +1,661 @@ +/* + * Gadget Driver for Android ADB + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/usb/android_composite.h> + +#define BULK_BUFFER_SIZE 4096 + +/* number of tx requests to allocate */ +#define TX_REQ_MAX 4 + +static const char shortname[] = "android_adb"; + +struct adb_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + + int online; + int error; + + atomic_t read_excl; + atomic_t write_excl; + atomic_t open_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req; + int rx_done; +}; + +static struct usb_interface_descriptor adb_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 2, + .bInterfaceClass = 0xFF, + .bInterfaceSubClass = 0x42, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor adb_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor adb_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor adb_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *fs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_fullspeed_in_desc, + (struct usb_descriptor_header *) &adb_fullspeed_out_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_adb_descs[] = { + (struct usb_descriptor_header *) &adb_interface_desc, + (struct usb_descriptor_header *) &adb_highspeed_in_desc, + (struct usb_descriptor_header *) &adb_highspeed_out_desc, + NULL, +}; + + +/* temporary variable used between adb_open() and adb_gadget_bind() */ +static struct adb_dev *_adb_dev; + +static atomic_t adb_enable_excl; + +static inline struct adb_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct adb_dev, function); +} + + +static struct usb_request *adb_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void adb_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +void req_put(struct adb_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +struct usb_request *req_get(struct adb_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void adb_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + if (req->status != 0) + dev->error = 1; + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void adb_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct adb_dev *dev = _adb_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->error = 1; + + wake_up(&dev->read_wq); +} + +static int __init create_bulk_endpoints(struct adb_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for adb ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + /* now allocate requests for our endpoints */ + req = adb_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_out; + dev->rx_req = req; + + for (i = 0; i < TX_REQ_MAX; i++) { + req = adb_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = adb_complete_in; + req_put(dev, &dev->tx_idle, req); + } + + return 0; + +fail: + printk(KERN_ERR "adb_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t adb_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int r = count, xfer; + int ret; + + DBG(cdev, "adb_read(%d)\n", count); + + if (count > BULK_BUFFER_SIZE) + return -EINVAL; + + if (_lock(&dev->read_excl)) + return -EBUSY; + + /* we will block until we're online */ + while (!(dev->online || dev->error)) { + DBG(cdev, "adb_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + (dev->online || dev->error)); + if (ret < 0) { + _unlock(&dev->read_excl); + return ret; + } + } + if (dev->error) { + r = -EIO; + goto done; + } + +requeue_req: + /* queue a request */ + req = dev->rx_req; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_ATOMIC); + if (ret < 0) { + DBG(cdev, "adb_read: failed to queue req %p (%d)\n", req, ret); + r = -EIO; + dev->error = 1; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + /* Try to dequeue request since adb daemon + * will queue this request again later. + * If it fails we can do nothing just report. */ + if (usb_ep_dequeue(dev->ep_out, req)) + printk(KERN_WARNING "adb: can not dequeue request\n"); + + dev->error = 1; + r = ret; + goto done; + } + if (!dev->error) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + _unlock(&dev->read_excl); + DBG(cdev, "adb_read returning %d\n", r); + return r; +} + +static ssize_t adb_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct adb_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int ret; + + DBG(cdev, "adb_write(%d)\n", count); + + if (_lock(&dev->write_excl)) + return -EBUSY; + + while (count > 0) { + if (dev->error) { + DBG(cdev, "adb_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) || dev->error)); + + if (ret < 0) { + r = ret; + break; + } + + if (req != 0) { + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + if (copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_ATOMIC); + if (ret < 0) { + DBG(cdev, "adb_write: xfer error %d\n", ret); + dev->error = 1; + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + _unlock(&dev->write_excl); + DBG(cdev, "adb_write returning %d\n", r); + return r; +} + +static int adb_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "adb_open\n"); + if (_lock(&_adb_dev->open_excl)) + return -EBUSY; + + fp->private_data = _adb_dev; + + /* clear the error latch */ + _adb_dev->error = 0; + + return 0; +} + +static int adb_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "adb_release\n"); + _unlock(&_adb_dev->open_excl); + return 0; +} + +/* file operations for ADB device /dev/android_adb */ +static struct file_operations adb_fops = { + .owner = THIS_MODULE, + .read = adb_read, + .write = adb_write, + .open = adb_open, + .release = adb_release, +}; + +static struct miscdevice adb_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = shortname, + .fops = &adb_fops, +}; + +static int adb_enable_open(struct inode *ip, struct file *fp) +{ + if (atomic_inc_return(&adb_enable_excl) != 1) { + atomic_dec(&adb_enable_excl); + return -EBUSY; + } + + printk(KERN_INFO "enabling adb\n"); + android_enable_function(&_adb_dev->function, 1); + + return 0; +} + +static int adb_enable_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "disabling adb\n"); + android_enable_function(&_adb_dev->function, 0); + atomic_dec(&adb_enable_excl); + return 0; +} + +static const struct file_operations adb_enable_fops = { + .owner = THIS_MODULE, + .open = adb_enable_open, + .release = adb_enable_release, +}; + +static struct miscdevice adb_enable_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "android_adb_enable", + .fops = &adb_enable_fops, +}; + +static int +adb_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct adb_dev *dev = func_to_dev(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "adb_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + adb_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &adb_fullspeed_in_desc, + &adb_fullspeed_out_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + adb_highspeed_in_desc.bEndpointAddress = + adb_fullspeed_in_desc.bEndpointAddress; + adb_highspeed_out_desc.bEndpointAddress = + adb_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +adb_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct adb_dev *dev = func_to_dev(f); + struct usb_request *req; + + spin_lock_irq(&dev->lock); + + adb_request_free(dev->rx_req, dev->ep_out); + while ((req = req_get(dev, &dev->tx_idle))) + adb_request_free(req, dev->ep_in); + + dev->online = 0; + dev->error = 1; + spin_unlock_irq(&dev->lock); + + misc_deregister(&adb_device); + misc_deregister(&adb_enable_device); + kfree(_adb_dev); + _adb_dev = NULL; +} + +static int adb_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct adb_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "adb_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &adb_highspeed_in_desc, + &adb_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &adb_highspeed_out_desc, + &adb_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + dev->online = 1; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void adb_function_disable(struct usb_function *f) +{ + struct adb_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "adb_function_disable\n"); + dev->online = 0; + dev->error = 1; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int adb_bind_config(struct usb_configuration *c) +{ + struct adb_dev *dev; + int ret; + + printk(KERN_INFO "adb_bind_config\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->lock); + + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->read_excl, 0); + atomic_set(&dev->write_excl, 0); + + INIT_LIST_HEAD(&dev->tx_idle); + + dev->cdev = c->cdev; + dev->function.name = "adb"; + dev->function.descriptors = fs_adb_descs; + dev->function.hs_descriptors = hs_adb_descs; + dev->function.bind = adb_function_bind; + dev->function.unbind = adb_function_unbind; + dev->function.set_alt = adb_function_set_alt; + dev->function.disable = adb_function_disable; + + /* start disabled */ + dev->function.disabled = 1; + + /* _adb_dev must be set before calling usb_gadget_register_driver */ + _adb_dev = dev; + + ret = misc_register(&adb_device); + if (ret) + goto err1; + ret = misc_register(&adb_enable_device); + if (ret) + goto err2; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto err3; + + return 0; + +err3: + misc_deregister(&adb_enable_device); +err2: + misc_deregister(&adb_device); +err1: + kfree(dev); + printk(KERN_ERR "adb gadget driver failed to initialize\n"); + return ret; +} + +static struct android_usb_function adb_function = { + .name = "adb", + .bind_config = adb_bind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_adb init\n"); + android_register_function(&adb_function); + return 0; +} +module_init(init); diff --git a/drivers/usb/gadget/gadget_gbhc/f_mass_storage.c b/drivers/usb/gadget/gadget_gbhc/f_mass_storage.c new file mode 100644 index 0000000..a296cd8 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/f_mass_storage.c @@ -0,0 +1,3260 @@ +/* + * f_mass_storage.c -- Mass Storage USB Composite Function + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * The Mass Storage Function acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In + * addition to providing an example of a genuinely useful composite + * function for a USB device, it also illustrates a technique of + * double-buffering for increased throughput. + * + * Function supports multiple logical units (LUNs). Backing storage + * for each LUN is provided by a regular file or a block device. + * Access for each LUN can be limited to read-only. Moreover, the + * function can indicate that LUN is removable and/or CD-ROM. (The + * later implies read-only access.) + * + * MSF is configured by specifying a fsg_config structure. It has the + * following fields: + * + * nluns Number of LUNs function have (anywhere from 1 + * to FSG_MAX_LUNS which is 8). + * luns An array of LUN configuration values. This + * should be filled for each LUN that + * function will include (ie. for "nluns" + * LUNs). Each element of the array has + * the following fields: + * ->filename The path to the backing file for the LUN. + * Required if LUN is not marked as + * removable. + * ->ro Flag specifying access to the LUN shall be + * read-only. This is implied if CD-ROM + * emulation is enabled as well as when + * it was impossible to open "filename" + * in R/W mode. + * ->removable Flag specifying that LUN shall be indicated as + * being removable. + * ->cdrom Flag specifying that LUN shall be reported as + * being a CD-ROM. + * ->nofua Flag specifying that FUA flag in SCSI WRITE(10,12) + * commands for this LUN shall be ignored. + * + * lun_name_format A printf-like format for names of the LUN + * devices. This determines how the + * directory in sysfs will be named. + * Unless you are using several MSFs in + * a single gadget (as opposed to single + * MSF in many configurations) you may + * leave it as NULL (in which case + * "lun%d" will be used). In the format + * you can use "%d" to index LUNs for + * MSF's with more than one LUN. (Beware + * that there is only one integer given + * as an argument for the format and + * specifying invalid format may cause + * unspecified behaviour.) + * thread_name Name of the kernel thread process used by the + * MSF. You can safely set it to NULL + * (in which case default "file-storage" + * will be used). + * + * vendor_name + * product_name + * release Information used as a reply to INQUIRY + * request. To use default set to NULL, + * NULL, 0xffff respectively. The first + * field should be 8 and the second 16 + * characters or less. + * + * can_stall Set to permit function to halt bulk endpoints. + * Disabled on some USB devices known not + * to work correctly. You should set it + * to true. + * + * If "removable" is not set for a LUN then a backing file must be + * specified. If it is set, then NULL filename means the LUN's medium + * is not loaded (an empty string as "filename" in the fsg_config + * structure causes error). The CD-ROM emulation includes a single + * data track and no audio tracks; hence there need be only one + * backing file per LUN. Note also that the CD-ROM block length is + * set to 512 rather than the more common value 2048. + * + * + * MSF includes support for module parameters. If gadget using it + * decides to use it, the following module parameters will be + * available: + * + * file=filename[,filename...] + * Names of the files or block devices used for + * backing storage. + * ro=b[,b...] Default false, boolean for read-only access. + * removable=b[,b...] + * Default true, boolean for removable media. + * cdrom=b[,b...] Default false, boolean for whether to emulate + * a CD-ROM drive. + * nofua=b[,b...] Default false, booleans for ignore FUA flag + * in SCSI WRITE(10,12) commands + * luns=N Default N = number of filenames, number of + * LUNs to support. + * stall Default determined according to the type of + * USB device controller (usually true), + * boolean to permit the driver to halt + * bulk endpoints. + * + * The module parameters may be prefixed with some string. You need + * to consult gadget's documentation or source to verify whether it is + * using those module parameters and if it does what are the prefixes + * (look for FSG_MODULE_PARAMETERS() macro usage, what's inside it is + * the prefix). + * + * + * Requirements are modest; only a bulk-in and a bulk-out endpoint are + * needed. The memory requirement amounts to two 16K buffers, size + * configurable by a parameter. Support is included for both + * full-speed and high-speed operation. + * + * Note that the driver is slightly non-portable in that it assumes a + * single memory/DMA buffer will be useable for bulk-in, bulk-out, and + * interrupt-in endpoints. With most device controllers this isn't an + * issue, but there may be some with hardware restrictions that prevent + * a buffer from being used by more than one endpoint. + * + * + * The pathnames of the backing files and the ro settings are + * available in the attribute files "file" and "ro" in the lun<n> (or + * to be more precise in a directory which name comes from + * "lun_name_format" option!) subdirectory of the gadget's sysfs + * directory. If the "removable" option is set, writing to these + * files will simulate ejecting/loading the medium (writing an empty + * line means eject) and adjusting a write-enable tab. Changes to the + * ro setting are not allowed when the medium is loaded or if CD-ROM + * emulation is being used. + * + * When a LUN receive an "eject" SCSI request (Start/Stop Unit), + * if the LUN is removable, the backing file is released to simulate + * ejection. + * + * + * This function is heavily based on "File-backed Storage Gadget" by + * Alan Stern which in turn is heavily based on "Gadget Zero" by David + * Brownell. The driver's SCSI command interface was based on the + * "Information technology - Small Computer System Interface - 2" + * document from X3T9.2 Project 375D, Revision 10L, 7-SEP-93, + * available at <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>. + * The single exception is opcode 0x23 (READ FORMAT CAPACITIES), which + * was based on the "Universal Serial Bus Mass Storage Class UFI + * Command Specification" document, Revision 1.0, December 14, 1998, + * available at + * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>. + */ + +/* + * Driver Design + * + * The MSF is fairly straightforward. There is a main kernel + * thread that handles most of the work. Interrupt routines field + * callbacks from the controller driver: bulk- and interrupt-request + * completion notifications, endpoint-0 events, and disconnect events. + * Completion events are passed to the main thread by wakeup calls. Many + * ep0 requests are handled at interrupt time, but SetInterface, + * SetConfiguration, and device reset requests are forwarded to the + * thread in the form of "exceptions" using SIGUSR1 signals (since they + * should interrupt any ongoing file I/O operations). + * + * The thread's main routine implements the standard command/data/status + * parts of a SCSI interaction. It and its subroutines are full of tests + * for pending signals/exceptions -- all this polling is necessary since + * the kernel has no setjmp/longjmp equivalents. (Maybe this is an + * indication that the driver really wants to be running in userspace.) + * An important point is that so long as the thread is alive it keeps an + * open reference to the backing file. This will prevent unmounting + * the backing file's underlying filesystem and could cause problems + * during system shutdown, for example. To prevent such problems, the + * thread catches INT, TERM, and KILL signals and converts them into + * an EXIT exception. + * + * In normal operation the main thread is started during the gadget's + * fsg_bind() callback and stopped during fsg_unbind(). But it can + * also exit when it receives a signal, and there's no point leaving + * the gadget running when the thread is dead. At of this moment, MSF + * provides no way to deregister the gadget when thread dies -- maybe + * a callback functions is needed. + * + * To provide maximum throughput, the driver uses a circular pipeline of + * buffer heads (struct fsg_buffhd). In principle the pipeline can be + * arbitrarily long; in practice the benefits don't justify having more + * than 2 stages (i.e., double buffering). But it helps to think of the + * pipeline as being a long one. Each buffer head contains a bulk-in and + * a bulk-out request pointer (since the buffer can be used for both + * output and input -- directions always are given from the host's + * point of view) as well as a pointer to the buffer and various state + * variables. + * + * Use of the pipeline follows a simple protocol. There is a variable + * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. + * At any time that buffer head may still be in use from an earlier + * request, so each buffer head has a state variable indicating whether + * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the + * buffer head to be EMPTY, filling the buffer either by file I/O or by + * USB I/O (during which the buffer head is BUSY), and marking the buffer + * head FULL when the I/O is complete. Then the buffer will be emptied + * (again possibly by USB I/O, during which it is marked BUSY) and + * finally marked EMPTY again (possibly by a completion routine). + * + * A module parameter tells the driver to avoid stalling the bulk + * endpoints wherever the transport specification allows. This is + * necessary for some UDCs like the SuperH, which cannot reliably clear a + * halt on a bulk endpoint. However, under certain circumstances the + * Bulk-only specification requires a stall. In such cases the driver + * will halt the endpoint and set a flag indicating that it should clear + * the halt in software during the next device reset. Hopefully this + * will permit everything to work correctly. Furthermore, although the + * specification allows the bulk-out endpoint to halt when the host sends + * too much data, implementing this would cause an unavoidable race. + * The driver will always use the "no-stall" approach for OUT transfers. + * + * One subtle point concerns sending status-stage responses for ep0 + * requests. Some of these requests, such as device reset, can involve + * interrupting an ongoing file I/O operation, which might take an + * arbitrarily long time. During that delay the host might give up on + * the original ep0 request and issue a new one. When that happens the + * driver should not notify the host about completion of the original + * request, as the host will no longer be waiting for it. So the driver + * assigns to each ep0 request a unique tag, and it keeps track of the + * tag value of the request associated with a long-running exception + * (device-reset, interface-change, or configuration-change). When the + * exception handler is finished, the status-stage response is submitted + * only if the current ep0 request tag is equal to the exception request + * tag. Thus only the most recently received ep0 request will get a + * status-stage response. + * + * Warning: This driver source file is too long. It ought to be split up + * into a header file plus about 3 separate .c files, to handle the details + * of the Gadget, USB Mass Storage, and SCSI protocols. + */ + + +/* #define VERBOSE_DEBUG */ +/* #define DUMP_MSGS */ + +#include <linux/blkdev.h> +#include <linux/completion.h> +#include <linux/dcache.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/kref.h> +#include <linux/kthread.h> +#include <linux/limits.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/freezer.h> +#include <linux/utsname.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> + +#include "gadget_chips.h" + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE +#include <linux/usb/android_composite.h> +#include <linux/platform_device.h> + +#define FUNCTION_NAME "usb_mass_storage" +#endif + +/*------------------------------------------------------------------------*/ + +#define FSG_DRIVER_DESC "Mass Storage Function" +#define FSG_DRIVER_VERSION "2009/09/11" + +static const char fsg_string_interface[] = "Mass Storage"; + +#define FSG_NO_INTR_EP 1 +#define FSG_NO_DEVICE_STRINGS 1 +#define FSG_NO_OTG 1 +#define FSG_NO_INTR_EP 1 + +#include "storage_common.c" + + +/*-------------------------------------------------------------------------*/ + +struct fsg_dev; +struct fsg_common; + +/* FSF callback functions */ +struct fsg_operations { + /* + * Callback function to call when thread exits. If no + * callback is set or it returns value lower then zero MSF + * will force eject all LUNs it operates on (including those + * marked as non-removable or with prevent_medium_removal flag + * set). + */ + int (*thread_exits)(struct fsg_common *common); + + /* + * Called prior to ejection. Negative return means error, + * zero means to continue with ejection, positive means not to + * eject. + */ + int (*pre_eject)(struct fsg_common *common, + struct fsg_lun *lun, int num); + /* + * Called after ejection. Negative return means error, zero + * or positive is just a success. + */ + int (*post_eject)(struct fsg_common *common, + struct fsg_lun *lun, int num); +}; + +/* Data shared by all the FSG instances. */ +struct fsg_common { + struct usb_gadget *gadget; + struct usb_composite_dev *cdev; + struct fsg_dev *fsg, *new_fsg; + wait_queue_head_t fsg_wait; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* lock protects: state, all the req_busy's */ + spinlock_t lock; + + struct usb_ep *ep0; /* Copy of gadget->ep0 */ + struct usb_request *ep0req; /* Copy of cdev->req */ + unsigned int ep0_req_tag; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd buffhds[FSG_NUM_BUFFERS]; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + unsigned int lun; + struct fsg_lun *luns; + struct fsg_lun *curlun; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; /* For exception handling */ + unsigned int exception_req_tag; + + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + u32 residue; + u32 usb_amount_left; + + unsigned int can_stall:1; + unsigned int free_storage_on_release:1; + unsigned int phase_error:1; + unsigned int short_packet_received:1; + unsigned int bad_lun_okay:1; + unsigned int running:1; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + /* + * Vendor (8 chars), product (16 chars), release (4 + * hexadecimal digits) and NUL byte + */ + char inquiry_string[8 + 16 + 4 + 1]; + + struct kref ref; +}; + +struct fsg_config { + unsigned nluns; + struct fsg_lun_config { + const char *filename; + char ro; + char removable; + char cdrom; + char nofua; + } luns[FSG_MAX_LUNS]; + + const char *lun_name_format; + const char *thread_name; + + /* Callback functions. */ + const struct fsg_operations *ops; + /* Gadget's private data. */ + void *private_data; + + const char *vendor_name; /* 8 characters or less */ + const char *product_name; /* 16 characters or less */ + u16 release; + + char can_stall; + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + struct platform_device *pdev; +#endif +}; + +struct fsg_dev { + struct usb_function function; + struct usb_gadget *gadget; /* Copy of cdev->gadget */ + struct fsg_common *common; + + u16 interface_number; + + unsigned int bulk_in_enabled:1; + unsigned int bulk_out_enabled:1; + + unsigned long atomic_bitflags; +#define IGNORE_BULK_OUT 0 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; +}; + +static inline int __fsg_is_set(struct fsg_common *common, + const char *func, unsigned line) +{ + if (common->fsg) + return 1; + ERROR(common, "common->fsg is NULL in %s at %u\n", func, line); + WARN_ON(1); + return 0; +} + +#define fsg_is_set(common) likely(__fsg_is_set(common, __func__, __LINE__)) + +static inline struct fsg_dev *fsg_from_func(struct usb_function *f) +{ + return container_of(f, struct fsg_dev, function); +} + +typedef void (*fsg_routine_t)(struct fsg_dev *); + +static int exception_in_progress(struct fsg_common *common) +{ + return common->state > FSG_STATE_IDLE; +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_common *common, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % common->bulk_out_maxpacket; + if (rem > 0) + length += common->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + name = ep->name; + DBG(fsg, "%s set halt\n", name); + return usb_ep_set_halt(ep); +} + + +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_common *common) +{ + /* Tell the main thread that something has happened */ + common->thread_wakeup_needed = 1; + if (common->thread_task) + wake_up_process(common->thread_task); +} + +static void raise_exception(struct fsg_common *common, enum fsg_state new_state) +{ + unsigned long flags; + + /* + * Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. + */ + spin_lock_irqsave(&common->lock, flags); + if (common->state <= new_state) { + common->exception_req_tag = common->ep0_req_tag; + common->state = new_state; + if (common->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_FORCED, + common->thread_task); + } + spin_unlock_irqrestore(&common->lock, flags); +} + + +/*-------------------------------------------------------------------------*/ + +static int ep0_queue(struct fsg_common *common) +{ + int rc; + + rc = usb_ep_queue(common->ep0, common->ep0req, GFP_ATOMIC); + common->ep0->driver_data = common; + if (rc != 0 && rc != -ESHUTDOWN) { + /* We can't do much more than wait for a reset */ + WARNING(common, "error in submission: %s --> %d\n", + common->ep0->name, rc); + } + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* Completion handlers. These always run in_irq. */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&common->lock); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(common); + spin_unlock(&common->lock); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_common *common = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + dump_msg(common, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(common, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, bh->bulk_out_intended_length); + if (req->status == -ECONNRESET) /* Request was cancelled */ + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&common->lock); + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(common); + spin_unlock(&common->lock); +} + +static int fsg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_request *req = fsg->common->ep0req; + 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 (!fsg_is_set(fsg->common)) + return -EOPNOTSUPP; + + ++fsg->common->ep0_req_tag; /* Record arrival of a new request */ + req->context = NULL; + req->length = 0; + dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl)); + + switch (ctrl->bRequest) { + + case USB_BULK_RESET_REQUEST: + if (ctrl->bRequestType != + (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0) + return -EDOM; + + /* + * Raise an exception to stop the current operation + * and reinitialize our state. + */ + DBG(fsg, "bulk reset request\n"); + raise_exception(fsg->common, FSG_STATE_RESET); + return DELAYED_STATUS; + + case USB_BULK_GET_MAX_LUN_REQUEST: + if (ctrl->bRequestType != + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != fsg->interface_number || w_value != 0) + return -EDOM; + VDBG(fsg, "get max LUN\n"); + *(u8 *)req->buf = fsg->common->nluns - 1; + + /* Respond with data/status */ + req->length = min((u16)1, w_length); + return ep0_queue(fsg->common); + } + + VDBG(fsg, + "unknown class-specific control req %02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), w_index, w_length); + return -EOPNOTSUPP; +} + + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + + spin_lock_irq(&fsg->common->lock); + *pbusy = 1; + *state = BUF_STATE_BUSY; + spin_unlock_irq(&fsg->common->lock); + rc = usb_ep_queue(ep, req, GFP_KERNEL); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* + * Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. + */ + if (rc != -ESHUTDOWN && + !(rc == -EOPNOTSUPP && req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } +} + +static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh) +{ + if (!fsg_is_set(common)) + return false; + start_transfer(common->fsg, common->fsg->bulk_in, + bh->inreq, &bh->inreq_busy, &bh->state); + return true; +} + +static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh) +{ + if (!fsg_is_set(common)) + return false; + start_transfer(common->fsg, common->fsg->bulk_out, + bh->outreq, &bh->outreq_busy, &bh->state); + return true; +} + +static int sleep_thread(struct fsg_common *common) +{ + int rc = 0; + + /* Wait until a signal arrives or we are woken up */ + for (;;) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + rc = -EINTR; + break; + } + if (common->thread_wakeup_needed) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + common->thread_wakeup_needed = 0; + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + /* + * Get the starting Logical Block Address and check that it's + * not too big. + */ + if (common->cmnd[0] == READ_6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. + */ + if ((common->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << 9; + + /* Carry out the file reads */ + amount_left = common->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; /* No default reply */ + + for (;;) { + /* + * Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. + */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t)amount, + curlun->file_length - file_offset); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int)PAGE_CACHE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* + * If we were asked to read past the end of file, + * end with an empty buffer. + */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long)file_offset, (int)nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", (int)nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int)nread, amount); + nread -= (nread & 511); /* Round down to a block */ + } + file_offset += nread; + amount_left -= nread; + common->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; /* No more left to read */ + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + if (!start_in_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + } + + return -EIO; /* No default reply */ +} + + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nwritten; + int rc; + + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags &= ~O_SYNC; /* Default is not to wait */ + spin_unlock(&curlun->filp->f_lock); + + /* + * Get the starting Logical Block Address and check that it's + * not too big + */ + if (common->cmnd[0] == WRITE_6) + lba = get_unaligned_be24(&common->cmnd[1]); + else { + lba = get_unaligned_be32(&common->cmnd[2]); + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. + */ + if (common->cmnd[1] & ~0x18) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (!curlun->nofua && (common->cmnd[1] & 0x08)) { /* FUA */ + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags |= O_SYNC; + spin_unlock(&curlun->filp->f_lock); + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << 9; + amount_left_to_req = common->data_size_from_cmnd; + amount_left_to_write = common->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* + * Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. + */ + amount = min(amount_left_to_req, FSG_BUFLEN); + amount = min((loff_t)amount, + curlun->file_length - usb_offset); + partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, + (unsigned int)PAGE_CACHE_SIZE - partial_page); + + if (amount == 0) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = usb_offset >> 9; + curlun->info_valid = 1; + continue; + } + amount -= amount & 511; + if (amount == 0) { + + /* + * Why were we were asked to transfer a + * partial block? + */ + get_some_more = 0; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + common->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* + * amount is always divisible by 512, hence by + * the bulk-out maxpacket size + */ + bh->outreq->length = amount; + bh->bulk_out_intended_length = amount; + bh->outreq->short_not_ok = 1; + if (!start_out_transfer(common, bh)) + /* Dunno what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = common->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; /* We stopped early */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + common->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, + "write %u @ %llu beyond end %llu\n", + amount, (unsigned long long)file_offset, + (unsigned long long)curlun->file_length); + amount = curlun->file_length - file_offset; + } + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, + (char __user *)bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long)file_offset, (int)nwritten); + if (signal_pending(current)) + return -EINTR; /* Interrupted! */ + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int)nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int)nwritten, amount); + nwritten -= (nwritten & 511); + /* Round down to a block */ + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + common->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + common->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + + return -EIO; /* No default reply */ +} + + +/*-------------------------------------------------------------------------*/ + +static int do_synchronize_cache(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + int rc; + + /* We ignore the requested LBA and write out all file's + * dirty data buffers. */ + rc = fsg_lun_fsync_sub(curlun); + if (rc) + curlun->sense_data = SS_WRITE_ERROR; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static void invalidate_sub(struct fsg_lun *curlun) +{ + struct file *filp = curlun->filp; + struct inode *inode = filp->f_path.dentry->d_inode; + unsigned long rc; + + rc = invalidate_mapping_pages(inode->i_mapping, 0, -1); + VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc); +} + +static int do_verify(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + loff_t file_offset, file_offset_tmp; + u32 amount_left; + unsigned int amount; + ssize_t nread; + + /* + * Get the starting Logical Block Address and check that it's + * not too big. + */ + lba = get_unaligned_be32(&common->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* + * We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. + */ + if (common->cmnd[1] & ~0x10) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_unaligned_be16(&common->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; /* No default reply */ + + /* Prepare to carry out the file verify */ + amount_left = verification_length << 9; + file_offset = ((loff_t) lba) << 9; + + /* Write out all the dirty buffers before invalidating them */ + fsg_lun_fsync_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + invalidate_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + /* + * Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + * If this means reading 0 then we were asked to read + * past the end of file. + */ + amount = min(amount_left, FSG_BUFLEN); + amount = min((loff_t)amount, + curlun->file_length - file_offset); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file verify: %d\n", (int)nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int)nread, amount); + nread -= nread & 511; /* Round down to a sector */ + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u8 *buf = (u8 *) bh->buf; + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; /* Unsupported, no device-type */ + buf[4] = 31; /* Additional length */ + return 36; + } + + buf[0] = curlun->cdrom ? TYPE_ROM : TYPE_DISK; + buf[1] = curlun->removable ? 0x80 : 0; + buf[2] = 2; /* ANSI SCSI level 2 */ + buf[3] = 2; /* SCSI-2 INQUIRY data format */ + buf[4] = 31; /* Additional length */ + buf[5] = 0; /* No special options */ + buf[6] = 0; + buf[7] = 0; + memcpy(buf + 8, common->inquiry_string, sizeof common->inquiry_string); + return 36; +} + +static int do_request_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { /* Unsupported LUNs are okay */ + common->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; /* Valid, current error */ + buf[2] = SK(sd); + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ + buf[7] = 18 - 8; /* Additional sense length */ + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + +static int do_read_capacity(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + int pmi = common->cmnd[8]; + u8 *buf = (u8 *)bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + return 8; +} + +static int do_read_header(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int msf = common->cmnd[1] & 0x02; + u32 lba = get_unaligned_be32(&common->cmnd[2]); + u8 *buf = (u8 *)bh->buf; + + if (common->cmnd[1] & ~0x02) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + +static int do_read_toc(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int msf = common->cmnd[1] & 0x02; + int start_track = common->cmnd[6]; + u8 *buf = (u8 *)bh->buf; + + if ((common->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + return 20; +} + +static int do_mode_sense(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + int mscmnd = common->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((common->cmnd[1] & ~0x08) != 0) { /* Mask away DBD */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = common->cmnd[2] >> 6; + page_code = common->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* + * Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. + */ + memset(buf, 0, 8); + if (mscmnd == MODE_SENSE) { + buf[2] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 4; + limit = 255; + } else { /* MODE_SENSE_10 */ + buf[3] = (curlun->ro ? 0x80 : 0x00); /* WP, DPOFUA */ + buf += 8; + limit = 65535; /* Should really be FSG_BUFLEN */ + } + + /* No block descriptors */ + + /* + * The mode pages, in numerical order. The only page we support + * is the Caching page. + */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; /* Page code */ + buf[1] = 10; /* Page length */ + memset(buf+2, 0, 10); /* None of the fields are changeable */ + + if (!changeable_values) { + buf[2] = 0x04; /* Write cache enable, */ + /* Read cache not disabled */ + /* No cache retention priorities */ + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + + /* + * Check that a valid page was requested and the mode data length + * isn't too long. + */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == MODE_SENSE) + buf0[0] = len - 1; + else + put_unaligned_be16(len - 2, buf0); + return len; +} + +static int do_start_stop(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + int loej, start; + + if (!curlun) { + return -EINVAL; + } else if (!curlun->removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } else if ((common->cmnd[1] & ~0x01) != 0 || /* Mask away Immed */ + (common->cmnd[4] & ~0x03) != 0) { /* Mask LoEj, Start */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + loej = common->cmnd[4] & 0x02; + start = common->cmnd[4] & 0x01; + + /* + * Our emulation doesn't support mounting; the medium is + * available for use as soon as it is loaded. + */ + if (start) { + if (!fsg_lun_is_open(curlun)) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + return 0; + } + + /* Are we allowed to unload the media? */ + if (curlun->prevent_medium_removal) { + LDBG(curlun, "unload attempt prevented\n"); + curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED; + return -EINVAL; + } + + if (!loej) + return 0; + + /* Simulate an unload/eject */ + if (common->ops && common->ops->pre_eject) { + int r = common->ops->pre_eject(common, curlun, + curlun - common->luns); + if (unlikely(r < 0)) + return r; + else if (r) + return 0; + } + + up_read(&common->filesem); + down_write(&common->filesem); + fsg_lun_close(curlun); + up_write(&common->filesem); + down_read(&common->filesem); + + return common->ops && common->ops->post_eject + ? min(0, common->ops->post_eject(common, curlun, + curlun - common->luns)) + : 0; +} + +static int do_prevent_allow(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + int prevent; + + if (!common->curlun) { + return -EINVAL; + } else if (!common->curlun->removable) { + common->curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + prevent = common->cmnd[4] & 0x01; + if ((common->cmnd[4] & ~0x01) != 0) { /* Mask away Prevent */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 0; +} + +static int do_read_format_capacities(struct fsg_common *common, + struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; /* Only the Current/Maximum Capacity Descriptor */ + buf += 4; + + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + +static int do_mode_select(struct fsg_common *common, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = common->curlun; + + /* We don't support MODE SELECT */ + if (curlun) + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +/*-------------------------------------------------------------------------*/ + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_halt(fsg->bulk_in); + } + return rc; +} + +static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + DBG(fsg, "bulk-in set wedge\n"); + rc = usb_ep_set_wedge(fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint wedge\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_wedge(fsg->bulk_in); + } + return rc; +} + +static int throw_away_data(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + for (bh = common->next_buffhd_to_drain; + bh->state != BUF_STATE_EMPTY || common->usb_amount_left > 0; + bh = common->next_buffhd_to_drain) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + bh->state = BUF_STATE_EMPTY; + common->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual != bh->outreq->length || + bh->outreq->status != 0) { + raise_exception(common, + FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = common->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY + && common->usb_amount_left > 0) { + amount = min(common->usb_amount_left, FSG_BUFLEN); + + /* + * amount is always divisible by 512, hence by + * the bulk-out maxpacket size. + */ + bh->outreq->length = amount; + bh->bulk_out_intended_length = amount; + bh->outreq->short_not_ok = 1; + if (!start_out_transfer(common, bh)) + /* Dunno what to do if common->fsg is NULL */ + return -EIO; + common->next_buffhd_to_fill = bh->next; + common->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(common); + if (rc) + return rc; + } + return 0; +} + +static int finish_reply(struct fsg_common *common) +{ + struct fsg_buffhd *bh = common->next_buffhd_to_fill; + int rc = 0; + + switch (common->data_dir) { + case DATA_DIR_NONE: + break; /* Nothing to send */ + + /* + * If we don't know whether the host wants to read or write, + * this must be CB or CBI with an unknown command. We mustn't + * try to send or receive any data. So stall both bulk pipes + * if we can and wait for a reset. + */ + case DATA_DIR_UNKNOWN: + if (!common->can_stall) { + /* Nothing */ + } else if (fsg_is_set(common)) { + fsg_set_halt(common->fsg, common->fsg->bulk_out); + rc = halt_bulk_in_endpoint(common->fsg); + } else { + /* Don't know what to do if common->fsg is NULL */ + rc = -EIO; + } + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (common->data_size == 0) { + /* Nothing to send */ + + /* Don't know what to do if common->fsg is NULL */ + } else if (!fsg_is_set(common)) { + rc = -EIO; + + /* If there's no residue, simply send the last buffer */ + } else if (common->residue == 0) { + bh->inreq->zero = 0; + if (!start_in_transfer(common, bh)) + return -EIO; + common->next_buffhd_to_fill = bh->next; + + /* + * For Bulk-only, mark the end of the data with a short + * packet. If we are allowed to stall, halt the bulk-in + * endpoint. (Note: This violates the Bulk-Only Transport + * specification, which requires us to pad the data if we + * don't halt the endpoint. Presumably nobody will mind.) + */ + } else { + bh->inreq->zero = 1; + if (!start_in_transfer(common, bh)) + rc = -EIO; + common->next_buffhd_to_fill = bh->next; + if (common->can_stall) + rc = halt_bulk_in_endpoint(common->fsg); + } + break; + + /* + * We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. + */ + case DATA_DIR_FROM_HOST: + if (common->residue == 0) { + /* Nothing to receive */ + + /* Did the host stop sending unexpectedly early? */ + } else if (common->short_packet_received) { + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + + /* + * We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. + */ +#if 0 + } else if (common->can_stall) { + if (fsg_is_set(common)) + fsg_set_halt(common->fsg, + common->fsg->bulk_out); + raise_exception(common, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; +#endif + + /* + * We can't stall. Read in the excess data and throw it + * all away. + */ + } else { + rc = throw_away_data(common); + } + break; + } + return rc; +} + +static int send_status(struct fsg_common *common) +{ + struct fsg_lun *curlun = common->curlun; + struct fsg_buffhd *bh; + struct bulk_cs_wrap *csw; + int rc; + u8 status = USB_STATUS_PASS; + u32 sd, sdinfo = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + if (curlun) { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + } else if (common->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (common->phase_error) { + DBG(common, "sending phase-error status\n"); + status = USB_STATUS_PHASE_ERROR; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(common, "sending command-failure status\n"); + status = USB_STATUS_FAIL; + VDBG(common, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + /* Store and send the Bulk-only CSW */ + csw = (void *)bh->buf; + + csw->Signature = cpu_to_le32(USB_BULK_CS_SIG); + csw->Tag = common->tag; + csw->Residue = cpu_to_le32(common->residue); + csw->Status = status; + + bh->inreq->length = USB_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + if (!start_in_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + common->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* + * Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. + */ +static int check_command(struct fsg_common *common, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + int lun = common->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct fsg_lun *curlun; + + hdlen[0] = 0; + if (common->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) common->data_dir], + common->data_size); + VDBG(common, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + common->data_size_from_cmnd, common->cmnd_size, hdlen); + + /* + * We can't reply at all until we know the correct data direction + * and size. + */ + if (common->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (common->data_size < common->data_size_from_cmnd) { + /* + * Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much as + * we are allowed. + */ + common->data_size_from_cmnd = common->data_size; + common->phase_error = 1; + } + common->residue = common->data_size; + common->usb_amount_left = common->data_size; + + /* Conflicting data directions is a phase error */ + if (common->data_dir != data_dir && common->data_size_from_cmnd > 0) { + common->phase_error = 1; + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != common->cmnd_size) { + + /* + * Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= common->cmnd_size) { + DBG(common, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, common->cmnd_size); + cmnd_size = common->cmnd_size; + } else { + common->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (common->lun != lun) + DBG(common, "using LUN %d from CBW, not LUN %d from CDB\n", + common->lun, lun); + + /* Check the LUN */ + if (common->lun < common->nluns) { + curlun = &common->luns[common->lun]; + common->curlun = curlun; + if (common->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + } else { + common->curlun = NULL; + curlun = NULL; + common->bad_lun_okay = 0; + + /* + * INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. + */ + if (common->cmnd[0] != INQUIRY && + common->cmnd[0] != REQUEST_SENSE) { + DBG(common, "unsupported LUN %d\n", common->lun); + return -EINVAL; + } + } + + /* + * If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. + */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + common->cmnd[0] != INQUIRY && + common->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } + + /* Check that only command bytes listed in the mask are non-zero */ + common->cmnd[1] &= 0x1f; /* Mask away the LUN */ + for (i = 1; i < cmnd_size; ++i) { + if (common->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + + /* If the medium isn't mounted and the command needs to access + * it, return an error. */ + if (curlun && !fsg_lun_is_open(curlun) && needs_medium) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + + return 0; +} + +static int do_scsi_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + + dump_cdb(common); + + /* Wait for the next buffer to become available for data or status */ + bh = common->next_buffhd_to_fill; + common->next_buffhd_to_drain = bh; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + common->phase_error = 0; + common->short_packet_received = 0; + + down_read(&common->filesem); /* We're using the backing file */ + switch (common->cmnd[0]) { + + case INQUIRY: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY"); + if (reply == 0) + reply = do_inquiry(common, bh); + break; + + case MODE_SELECT: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case MODE_SELECT_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)"); + if (reply == 0) + reply = do_mode_select(common, bh); + break; + + case MODE_SENSE: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case MODE_SENSE_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)"); + if (reply == 0) + reply = do_mode_sense(common, bh); + break; + + case ALLOW_MEDIUM_REMOVAL: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL"); + if (reply == 0) + reply = do_prevent_allow(common); + break; + + case READ_6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)"); + if (reply == 0) + reply = do_read(common); + break; + + case READ_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]) << 9; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)"); + if (reply == 0) + reply = do_read(common); + break; + + case READ_12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]) << 9; + reply = check_command(common, 12, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)"); + if (reply == 0) + reply = do_read(common); + break; + + case READ_CAPACITY: + common->data_size_from_cmnd = 8; + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY"); + if (reply == 0) + reply = do_read_capacity(common, bh); + break; + + case READ_HEADER: + if (!common->curlun || !common->curlun->cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER"); + if (reply == 0) + reply = do_read_header(common, bh); + break; + + case READ_TOC: + if (!common->curlun || !common->curlun->cdrom) + goto unknown_cmnd; + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (7<<6) | (1<<1), 1, + "READ TOC"); + if (reply == 0) + reply = do_read_toc(common, bh); + break; + + case READ_FORMAT_CAPACITIES: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]); + reply = check_command(common, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES"); + if (reply == 0) + reply = do_read_format_capacities(common, bh); + break; + + case REQUEST_SENSE: + common->data_size_from_cmnd = common->cmnd[4]; + reply = check_command(common, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE"); + if (reply == 0) + reply = do_request_sense(common, bh); + break; + + case START_STOP: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT"); + if (reply == 0) + reply = do_start_stop(common); + break; + + case SYNCHRONIZE_CACHE: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE"); + if (reply == 0) + reply = do_synchronize_cache(common); + break; + + case TEST_UNIT_READY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* + * Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. + */ + case VERIFY: + common->data_size_from_cmnd = 0; + reply = check_command(common, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY"); + if (reply == 0) + reply = do_verify(common); + break; + + case WRITE_6: + i = common->cmnd[4]; + common->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + reply = check_command(common, 6, DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)"); + if (reply == 0) + reply = do_write(common); + break; + + case WRITE_10: + common->data_size_from_cmnd = + get_unaligned_be16(&common->cmnd[7]) << 9; + reply = check_command(common, 10, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)"); + if (reply == 0) + reply = do_write(common); + break; + + case WRITE_12: + common->data_size_from_cmnd = + get_unaligned_be32(&common->cmnd[6]) << 9; + reply = check_command(common, 12, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)"); + if (reply == 0) + reply = do_write(common); + break; + + /* + * Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. + */ + case FORMAT_UNIT: + case RELEASE: + case RESERVE: + case SEND_DIAGNOSTIC: + /* Fall through */ + + default: +unknown_cmnd: + common->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", common->cmnd[0]); + reply = check_command(common, common->cmnd_size, + DATA_DIR_UNKNOWN, 0xff, 0, unknown); + if (reply == 0) { + common->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&common->filesem); + + if (reply == -EINTR || signal_pending(current)) + return -EINTR; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; /* Error reply length */ + if (reply >= 0 && common->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32)reply, common->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + common->residue -= reply; + } /* Otherwise it's already set */ + + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct fsg_bulk_cb_wrap *cbw = req->buf; + struct fsg_common *common = fsg->common; + + /* Was this a real packet? Should it be ignored? */ + if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != USB_BULK_CB_WRAP_LEN || + cbw->Signature != cpu_to_le32( + USB_BULK_CB_SIG)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + + /* + * The Bulk-only spec says we MUST stall the IN endpoint + * (6.6.1), so it's unavoidable. It also says we must + * retain this state until the next reset, but there's + * no way to tell the controller driver it should ignore + * Clear-Feature(HALT) requests. + * + * We aren't required to halt the OUT endpoint; instead + * we can simply accept and discard any data received + * until the next reset. + */ + wedge_bulk_in_endpoint(fsg); + set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* + * We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. + */ + if (common->can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + halt_bulk_in_endpoint(fsg); + } + return -EINVAL; + } + + /* Save the command for later */ + common->cmnd_size = cbw->Length; + memcpy(common->cmnd, cbw->CDB, common->cmnd_size); + if (cbw->Flags & USB_BULK_IN_FLAG) + common->data_dir = DATA_DIR_TO_HOST; + else + common->data_dir = DATA_DIR_FROM_HOST; + common->data_size = le32_to_cpu(cbw->DataTransferLength); + if (common->data_size == 0) + common->data_dir = DATA_DIR_NONE; + common->lun = cbw->Lun; + common->tag = cbw->Tag; + return 0; +} + +static int get_next_command(struct fsg_common *common) +{ + struct fsg_buffhd *bh; + int rc = 0; + + /* Wait for the next buffer to become available */ + bh = common->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(common); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(common, bh, USB_BULK_CB_WRAP_LEN); + bh->outreq->short_not_ok = 1; + if (!start_out_transfer(common, bh)) + /* Don't know what to do if common->fsg is NULL */ + return -EIO; + + /* + * We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. + */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(common); + if (rc) + return rc; + } + smp_rmb(); + rc = fsg_is_set(common) ? received_cbw(common->fsg, bh) : -EIO; + bh->state = BUF_STATE_EMPTY; + + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int enable_endpoint(struct fsg_common *common, struct usb_ep *ep, + const struct usb_endpoint_descriptor *d) +{ + int rc; + + ep->driver_data = common; + rc = usb_ep_enable(ep, d); + if (rc) + ERROR(common, "can't enable %s, result %d\n", ep->name, rc); + return rc; +} + +static int alloc_request(struct fsg_common *common, struct usb_ep *ep, + struct usb_request **preq) +{ + *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (*preq) + return 0; + ERROR(common, "can't allocate request for %s\n", ep->name); + return -ENOMEM; +} + +/* Reset interface setting and re-init endpoint state (toggle etc). */ +static int do_set_interface(struct fsg_common *common, struct fsg_dev *new_fsg) +{ + const struct usb_endpoint_descriptor *d; + struct fsg_dev *fsg; + int i, rc = 0; + + if (common->running) + DBG(common, "reset interface\n"); + +reset: + /* Deallocate the requests */ + if (common->fsg) { + fsg = common->fsg; + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + } + + common->fsg = NULL; + wake_up(&common->fsg_wait); + } + + common->running = 0; + if (!new_fsg || rc) + return rc; + + common->fsg = new_fsg; + fsg = common->fsg; + + /* Enable the endpoints */ + d = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); + rc = enable_endpoint(common, fsg->bulk_in, d); + if (rc) + goto reset; + fsg->bulk_in_enabled = 1; + + d = fsg_ep_desc(common->gadget, + &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); + rc = enable_endpoint(common, fsg->bulk_out, d); + if (rc) + goto reset; + fsg->bulk_out_enabled = 1; + common->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize); + clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + + /* Allocate the requests */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &common->buffhds[i]; + + rc = alloc_request(common, fsg->bulk_in, &bh->inreq); + if (rc) + goto reset; + rc = alloc_request(common, fsg->bulk_out, &bh->outreq); + if (rc) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + + common->running = 1; + for (i = 0; i < common->nluns; ++i) + common->luns[i].unit_attention_data = SS_RESET_OCCURRED; + return rc; +} + + +/****************************** ALT CONFIGS ******************************/ + +static int fsg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct fsg_dev *fsg = fsg_from_func(f); + fsg->common->new_fsg = fsg; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + return USB_GADGET_DELAYED_STATUS; +} + +static void fsg_disable(struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_common *common) +{ + siginfo_t info; + int i; + struct fsg_buffhd *bh; + enum fsg_state old_state; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + + /* + * Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. + */ + for (;;) { + int sig = + dequeue_signal_lock(current, ¤t->blocked, &info); + if (!sig) + break; + if (sig != SIGUSR1) { + if (common->state < FSG_STATE_EXIT) + DBG(common, "Main thread exiting on signal\n"); + raise_exception(common, FSG_STATE_EXIT); + } + } + + /* Cancel all the pending transfers */ + if (likely(common->fsg)) { + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(common->fsg->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(common->fsg->bulk_out, + bh->outreq); + } + + /* Wait until everything is idle */ + for (;;) { + int num_active = 0; + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + if (num_active == 0) + break; + if (sleep_thread(common)) + return; + } + + /* Clear out the controller's fifos */ + if (common->fsg->bulk_in_enabled) + usb_ep_fifo_flush(common->fsg->bulk_in); + if (common->fsg->bulk_out_enabled) + usb_ep_fifo_flush(common->fsg->bulk_out); + } + + /* + * Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. + */ + spin_lock_irq(&common->lock); + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &common->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + common->next_buffhd_to_fill = &common->buffhds[0]; + common->next_buffhd_to_drain = &common->buffhds[0]; + exception_req_tag = common->exception_req_tag; + old_state = common->state; + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + common->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < common->nluns; ++i) { + curlun = &common->luns[i]; + curlun->prevent_medium_removal = 0; + curlun->sense_data = SS_NO_SENSE; + curlun->unit_attention_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + common->state = FSG_STATE_IDLE; + } + spin_unlock_irq(&common->lock); + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + case FSG_STATE_ABORT_BULK_OUT: + send_status(common); + spin_lock_irq(&common->lock); + if (common->state == FSG_STATE_STATUS_PHASE) + common->state = FSG_STATE_IDLE; + spin_unlock_irq(&common->lock); + break; + + case FSG_STATE_RESET: + /* + * In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) + */ + if (!fsg_is_set(common)) + break; + if (test_and_clear_bit(IGNORE_BULK_OUT, + &common->fsg->atomic_bitflags)) + usb_ep_clear_halt(common->fsg->bulk_in); + + if (common->ep0_req_tag == exception_req_tag) + ep0_queue(common); /* Complete the status stage */ + + /* + * Technically this should go here, but it would only be + * a waste of time. Ditto for the INTERFACE_CHANGE and + * CONFIG_CHANGE cases. + */ + /* for (i = 0; i < common->nluns; ++i) */ + /* common->luns[i].unit_attention_data = */ + /* SS_RESET_OCCURRED; */ + break; + + case FSG_STATE_CONFIG_CHANGE: + do_set_interface(common, common->new_fsg); + if (common->new_fsg) + usb_composite_setup_continue(common->cdev); + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_interface(common, NULL); /* Free resources */ + spin_lock_irq(&common->lock); + common->state = FSG_STATE_TERMINATED; /* Stop the thread */ + spin_unlock_irq(&common->lock); + break; + + case FSG_STATE_INTERFACE_CHANGE: + case FSG_STATE_DISCONNECT: + case FSG_STATE_COMMAND_PHASE: + case FSG_STATE_DATA_PHASE: + case FSG_STATE_STATUS_PHASE: + case FSG_STATE_IDLE: + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_main_thread(void *common_) +{ + struct fsg_common *common = common_; + + /* + * Allow the thread to be killed by a signal, but set the signal mask + * to block everything but INT, TERM, KILL, and USR1. + */ + allow_signal(SIGINT); + allow_signal(SIGTERM); + allow_signal(SIGKILL); + allow_signal(SIGUSR1); + + /* Allow the thread to be frozen */ + set_freezable(); + + /* + * Arrange for userspace references to be interpreted as kernel + * pointers. That way we can pass a kernel pointer to a routine + * that expects a __user pointer and it will work okay. + */ + set_fs(get_ds()); + + /* The main loop */ + while (common->state != FSG_STATE_TERMINATED) { + if (exception_in_progress(common) || signal_pending(current)) { + handle_exception(common); + continue; + } + + if (!common->running) { + sleep_thread(common); + continue; + } + + if (get_next_command(common)) + continue; + + spin_lock_irq(&common->lock); + if (!exception_in_progress(common)) + common->state = FSG_STATE_DATA_PHASE; + spin_unlock_irq(&common->lock); + + if (do_scsi_command(common) || finish_reply(common)) + continue; + + spin_lock_irq(&common->lock); + if (!exception_in_progress(common)) + common->state = FSG_STATE_STATUS_PHASE; + spin_unlock_irq(&common->lock); + + if (send_status(common)) + continue; + + spin_lock_irq(&common->lock); + if (!exception_in_progress(common)) + common->state = FSG_STATE_IDLE; + spin_unlock_irq(&common->lock); + } + + spin_lock_irq(&common->lock); + common->thread_task = NULL; + spin_unlock_irq(&common->lock); + + if (!common->ops || !common->ops->thread_exits + || common->ops->thread_exits(common) < 0) { + struct fsg_lun *curlun = common->luns; + unsigned i = common->nluns; + + down_write(&common->filesem); + for (; i--; ++curlun) { + if (!fsg_lun_is_open(curlun)) + continue; + + fsg_lun_close(curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + up_write(&common->filesem); + } + + /* Let fsg_unbind() know the thread has exited */ + complete_and_exit(&common->thread_notifier, 0); +} + + +/*************************** DEVICE ATTRIBUTES ***************************/ + +/* Write permission is checked per LUN in store_*() functions. */ +static DEVICE_ATTR(ro, 0644, fsg_show_ro, fsg_store_ro); +static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, fsg_store_nofua); +static DEVICE_ATTR(file, 0644, fsg_show_file, fsg_store_file); + + +/****************************** FSG COMMON ******************************/ + +static void fsg_common_release(struct kref *ref); + +static void fsg_lun_release(struct device *dev) +{ + /* Nothing needs to be done */ +} + +static inline void fsg_common_get(struct fsg_common *common) +{ + kref_get(&common->ref); +} + +static inline void fsg_common_put(struct fsg_common *common) +{ + kref_put(&common->ref, fsg_common_release); +} + +static struct fsg_common *fsg_common_init(struct fsg_common *common, + struct usb_composite_dev *cdev, + struct fsg_config *cfg) +{ + struct usb_gadget *gadget = cdev->gadget; + struct fsg_buffhd *bh; + struct fsg_lun *curlun; + struct fsg_lun_config *lcfg; + int nluns, i, rc; + char *pathbuf; + + /* Find out how many LUNs there should be */ + nluns = cfg->nluns; + if (nluns < 1 || nluns > FSG_MAX_LUNS) { + dev_err(&gadget->dev, "invalid number of LUNs: %u\n", nluns); + return ERR_PTR(-EINVAL); + } + + /* Allocate? */ + if (!common) { + common = kzalloc(sizeof *common, GFP_KERNEL); + if (!common) + return ERR_PTR(-ENOMEM); + common->free_storage_on_release = 1; + } else { + memset(common, 0, sizeof *common); + common->free_storage_on_release = 0; + } + + common->ops = cfg->ops; + common->private_data = cfg->private_data; + + common->gadget = gadget; + common->ep0 = gadget->ep0; + common->ep0req = cdev->req; + common->cdev = cdev; + + /* Maybe allocate device-global string IDs, and patch descriptors */ + if (fsg_strings[FSG_STRING_INTERFACE].id == 0) { + rc = usb_string_id(cdev); + if (unlikely(rc < 0)) + goto error_release; + fsg_strings[FSG_STRING_INTERFACE].id = rc; + fsg_intf_desc.iInterface = rc; + } + + /* + * Create the LUNs, open their backing files, and register the + * LUN devices in sysfs. + */ + curlun = kzalloc(nluns * sizeof *curlun, GFP_KERNEL); + if (unlikely(!curlun)) { + rc = -ENOMEM; + goto error_release; + } + common->luns = curlun; + + init_rwsem(&common->filesem); + + for (i = 0, lcfg = cfg->luns; i < nluns; ++i, ++curlun, ++lcfg) { + curlun->cdrom = !!lcfg->cdrom; + curlun->ro = lcfg->cdrom || lcfg->ro; + curlun->initially_ro = curlun->ro; + curlun->removable = lcfg->removable; + curlun->dev.release = fsg_lun_release; + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + /* use "usb_mass_storage" platform device as parent */ + curlun->dev.parent = &cfg->pdev->dev; +#else + curlun->dev.parent = &gadget->dev; +#endif + /* curlun->dev.driver = &fsg_driver.driver; XXX */ + dev_set_drvdata(&curlun->dev, &common->filesem); + dev_set_name(&curlun->dev, + cfg->lun_name_format + ? cfg->lun_name_format + : "lun%d", + i); + + rc = device_register(&curlun->dev); + if (rc) { + INFO(common, "failed to register LUN%d: %d\n", i, rc); + common->nluns = i; + put_device(&curlun->dev); + goto error_release; + } + + rc = device_create_file(&curlun->dev, &dev_attr_ro); + if (rc) + goto error_luns; + rc = device_create_file(&curlun->dev, &dev_attr_file); + if (rc) + goto error_luns; + rc = device_create_file(&curlun->dev, &dev_attr_nofua); + if (rc) + goto error_luns; + + if (lcfg->filename) { + rc = fsg_lun_open(curlun, lcfg->filename); + if (rc) + goto error_luns; + } else if (!curlun->removable) { + ERROR(common, "no file given for LUN%d\n", i); + rc = -EINVAL; + goto error_luns; + } + } + common->nluns = nluns; + + /* Data buffers cyclic list */ + bh = common->buffhds; + i = FSG_NUM_BUFFERS; + goto buffhds_first_it; + do { + bh->next = bh + 1; + ++bh; +buffhds_first_it: + bh->buf = kmalloc(FSG_BUFLEN, GFP_KERNEL); + if (unlikely(!bh->buf)) { + rc = -ENOMEM; + goto error_release; + } + } while (--i); + bh->next = common->buffhds; + + /* Prepare inquiryString */ + if (cfg->release != 0xffff) { + i = cfg->release; + } else { + i = usb_gadget_controller_number(gadget); + if (i >= 0) { + i = 0x0300 + i; + } else { + WARNING(common, "controller '%s' not recognized\n", + gadget->name); + i = 0x0399; + } + } + snprintf(common->inquiry_string, sizeof common->inquiry_string, + "%-8s%-16s%04x", cfg->vendor_name ?: "Linux", + /* Assume product name dependent on the first LUN */ + cfg->product_name ?: (common->luns->cdrom + ? "File-Stor Gadget" + : "File-CD Gadget"), + i); + + /* + * Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + common->can_stall = cfg->can_stall && + !(gadget_is_at91(common->gadget)); + + spin_lock_init(&common->lock); + kref_init(&common->ref); + + /* Tell the thread to start working */ + common->thread_task = + kthread_create(fsg_main_thread, common, + cfg->thread_name ?: "file-storage"); + if (IS_ERR(common->thread_task)) { + rc = PTR_ERR(common->thread_task); + goto error_release; + } + init_completion(&common->thread_notifier); + init_waitqueue_head(&common->fsg_wait); + + /* Information */ + INFO(common, FSG_DRIVER_DESC ", version: " FSG_DRIVER_VERSION "\n"); + INFO(common, "Number of LUNs=%d\n", common->nluns); + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + for (i = 0, nluns = common->nluns, curlun = common->luns; + i < nluns; + ++curlun, ++i) { + char *p = "(no medium)"; + if (fsg_lun_is_open(curlun)) { + p = "(error)"; + if (pathbuf) { + p = d_path(&curlun->filp->f_path, + pathbuf, PATH_MAX); + if (IS_ERR(p)) + p = "(error)"; + } + } + LINFO(curlun, "LUN: %s%s%sfile: %s\n", + curlun->removable ? "removable " : "", + curlun->ro ? "read only " : "", + curlun->cdrom ? "CD-ROM " : "", + p); + } + kfree(pathbuf); + + DBG(common, "I/O thread pid: %d\n", task_pid_nr(common->thread_task)); + + wake_up_process(common->thread_task); + + return common; + +error_luns: + common->nluns = i + 1; +error_release: + common->state = FSG_STATE_TERMINATED; /* The thread is dead */ + /* Call fsg_common_release() directly, ref might be not initialised. */ + fsg_common_release(&common->ref); + return ERR_PTR(rc); +} + +static void fsg_common_release(struct kref *ref) +{ + struct fsg_common *common = container_of(ref, struct fsg_common, ref); + + /* If the thread isn't already dead, tell it to exit now */ + if (common->state != FSG_STATE_TERMINATED) { + raise_exception(common, FSG_STATE_EXIT); + wait_for_completion(&common->thread_notifier); + } + + if (likely(common->luns)) { + struct fsg_lun *lun = common->luns; + unsigned i = common->nluns; + + /* In error recovery common->nluns may be zero. */ + for (; i; --i, ++lun) { + device_remove_file(&lun->dev, &dev_attr_nofua); + device_remove_file(&lun->dev, &dev_attr_ro); + device_remove_file(&lun->dev, &dev_attr_file); + fsg_lun_close(lun); + device_unregister(&lun->dev); + } + + kfree(common->luns); + } + + { + struct fsg_buffhd *bh = common->buffhds; + unsigned i = FSG_NUM_BUFFERS; + do { + kfree(bh->buf); + } while (++bh, --i); + } + + if (common->free_storage_on_release) + kfree(common); +} + + +/*-------------------------------------------------------------------------*/ + +static void fsg_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct fsg_common *common = fsg->common; + + DBG(fsg, "unbind\n"); + if (fsg->common->fsg == fsg) { + fsg->common->new_fsg = NULL; + raise_exception(fsg->common, FSG_STATE_CONFIG_CHANGE); + /* FIXME: make interruptible or killable somehow? */ + wait_event(common->fsg_wait, common->fsg != fsg); + } + + fsg_common_put(common); + usb_free_descriptors(fsg->function.descriptors); + usb_free_descriptors(fsg->function.hs_descriptors); + kfree(fsg); +} + +static int fsg_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct fsg_dev *fsg = fsg_from_func(f); + struct usb_gadget *gadget = c->cdev->gadget; + int i; + struct usb_ep *ep; + + fsg->gadget = gadget; + + /* New interface */ + i = usb_interface_id(c, f); + if (i < 0) + return i; + fsg_intf_desc.bInterfaceNumber = i; + fsg->interface_number = i; + + /* Find all the endpoints we will use */ + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg->common; /* claim the endpoint */ + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg->common; /* claim the endpoint */ + fsg->bulk_out = ep; + + /* Copy descriptors */ + f->descriptors = usb_copy_descriptors(fsg_fs_function); + if (unlikely(!f->descriptors)) + return -ENOMEM; + + if (gadget_is_dualspeed(gadget)) { + /* Assume endpoint addresses are the same for both speeds */ + fsg_hs_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_hs_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + f->hs_descriptors = usb_copy_descriptors(fsg_hs_function); + if (unlikely(!f->hs_descriptors)) { + usb_free_descriptors(f->descriptors); + return -ENOMEM; + } + } + + return 0; + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); + return -ENOTSUPP; +} + + +/****************************** ADD FUNCTION ******************************/ + +static struct usb_gadget_strings *fsg_strings_array[] = { + &fsg_stringtab, + NULL, +}; + +static int fsg_bind_config(struct usb_composite_dev *cdev, + struct usb_configuration *c, + struct fsg_common *common) +{ + struct fsg_dev *fsg; + int rc; + + fsg = kzalloc(sizeof *fsg, GFP_KERNEL); + if (unlikely(!fsg)) + return -ENOMEM; + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + fsg->function.name = FUNCTION_NAME; +#else + fsg->function.name = FSG_DRIVER_DESC; +#endif + fsg->function.strings = fsg_strings_array; + fsg->function.bind = fsg_bind; + fsg->function.unbind = fsg_unbind; + fsg->function.setup = fsg_setup; + fsg->function.set_alt = fsg_set_alt; + fsg->function.disable = fsg_disable; + + fsg->common = common; + /* + * Our caller holds a reference to common structure so we + * don't have to be worry about it being freed until we return + * from this function. So instead of incrementing counter now + * and decrement in error recovery we increment it only when + * call to usb_add_function() was successful. + */ + + rc = usb_add_function(c, &fsg->function); + if (unlikely(rc)) + kfree(fsg); + else + fsg_common_get(fsg->common); + return rc; +} + +static inline int __deprecated __maybe_unused +fsg_add(struct usb_composite_dev *cdev, struct usb_configuration *c, + struct fsg_common *common) +{ + return fsg_bind_config(cdev, c, common); +} + + +/************************* Module parameters *************************/ + +struct fsg_module_parameters { + char *file[FSG_MAX_LUNS]; + int ro[FSG_MAX_LUNS]; + int removable[FSG_MAX_LUNS]; + int cdrom[FSG_MAX_LUNS]; + int nofua[FSG_MAX_LUNS]; + + unsigned int file_count, ro_count, removable_count, cdrom_count; + unsigned int nofua_count; + unsigned int luns; /* nluns */ + int stall; /* can_stall */ +}; + +#define _FSG_MODULE_PARAM_ARRAY(prefix, params, name, type, desc) \ + module_param_array_named(prefix ## name, params.name, type, \ + &prefix ## params.name ## _count, \ + S_IRUGO); \ + MODULE_PARM_DESC(prefix ## name, desc) + +#define _FSG_MODULE_PARAM(prefix, params, name, type, desc) \ + module_param_named(prefix ## name, params.name, type, \ + S_IRUGO); \ + MODULE_PARM_DESC(prefix ## name, desc) + +#define FSG_MODULE_PARAMETERS(prefix, params) \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, file, charp, \ + "names of backing files or devices"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, ro, bool, \ + "true to force read-only"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, removable, bool, \ + "true to simulate removable media"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, cdrom, bool, \ + "true to simulate CD-ROM instead of disk"); \ + _FSG_MODULE_PARAM_ARRAY(prefix, params, nofua, bool, \ + "true to ignore SCSI WRITE(10,12) FUA bit"); \ + _FSG_MODULE_PARAM(prefix, params, luns, uint, \ + "number of LUNs"); \ + _FSG_MODULE_PARAM(prefix, params, stall, bool, \ + "false to prevent bulk stalls") + +static void +fsg_config_from_params(struct fsg_config *cfg, + const struct fsg_module_parameters *params) +{ + struct fsg_lun_config *lun; + unsigned i; + + /* Configure LUNs */ + cfg->nluns = + min(params->luns ?: (params->file_count ?: 1u), + (unsigned)FSG_MAX_LUNS); + for (i = 0, lun = cfg->luns; i < cfg->nluns; ++i, ++lun) { + lun->ro = !!params->ro[i]; + lun->cdrom = !!params->cdrom[i]; + lun->removable = /* Removable by default */ + params->removable_count <= i || params->removable[i]; + lun->filename = + params->file_count > i && params->file[i][0] + ? params->file[i] + : 0; + } + + /* Let MSF use defaults */ + cfg->lun_name_format = 0; + cfg->thread_name = 0; + cfg->vendor_name = 0; + cfg->product_name = 0; + cfg->release = 0xffff; + + cfg->ops = NULL; + cfg->private_data = NULL; + + /* Finalise */ + cfg->can_stall = params->stall; +} + +static inline struct fsg_common * +fsg_common_from_params(struct fsg_common *common, + struct usb_composite_dev *cdev, + const struct fsg_module_parameters *params) + __attribute__((unused)); +static inline struct fsg_common * +fsg_common_from_params(struct fsg_common *common, + struct usb_composite_dev *cdev, + const struct fsg_module_parameters *params) +{ + struct fsg_config cfg; + fsg_config_from_params(&cfg, params); + return fsg_common_init(common, cdev, &cfg); +} + +#ifdef CONFIG_USB_ANDROID_MASS_STORAGE + +static struct fsg_config fsg_cfg; + +static int fsg_probe(struct platform_device *pdev) +{ + struct usb_mass_storage_platform_data *pdata = pdev->dev.platform_data; + int i, nluns; + + printk(KERN_INFO "fsg_probe pdev: %p, pdata: %p\n", pdev, pdata); + if (!pdata) + return -1; + + nluns = pdata->nluns; + if (nluns > FSG_MAX_LUNS) + nluns = FSG_MAX_LUNS; + fsg_cfg.nluns = nluns; + for (i = 0; i < nluns; i++) + fsg_cfg.luns[i].removable = 1; + + fsg_cfg.vendor_name = pdata->vendor; + fsg_cfg.product_name = pdata->product; + fsg_cfg.release = pdata->release; + fsg_cfg.can_stall = 0; + fsg_cfg.pdev = pdev; + + return 0; +} + +static struct platform_driver fsg_platform_driver = { + .driver = { .name = FUNCTION_NAME, }, + .probe = fsg_probe, +}; + +int mass_storage_bind_config(struct usb_configuration *c) +{ + struct fsg_common *common = fsg_common_init(NULL, c->cdev, &fsg_cfg); + if (IS_ERR(common)) + return -1; + return fsg_add(c->cdev, c, common); +} + +static struct android_usb_function mass_storage_function = { + .name = FUNCTION_NAME, + .bind_config = mass_storage_bind_config, +}; + +static int __init init(void) +{ + int rc; + printk(KERN_INFO "f_mass_storage init\n"); + rc = platform_driver_register(&fsg_platform_driver); + if (rc != 0) + return rc; + android_register_function(&mass_storage_function); + return 0; +}module_init(init); + +#endif /* CONFIG_USB_ANDROID_MASS_STORAGE */ + diff --git a/drivers/usb/gadget/gadget_gbhc/f_mtp.c b/drivers/usb/gadget/gadget_gbhc/f_mtp.c new file mode 100644 index 0000000..d124e29 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/f_mtp.c @@ -0,0 +1,1277 @@ +/* + * Gadget Function Driver for MTP + * + * Copyright (C) 2010 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/delay.h> +#include <linux/wait.h> +#include <linux/err.h> +#include <linux/interrupt.h> + +#include <linux/types.h> +#include <linux/file.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/usb.h> +#include <linux/usb_usual.h> +#include <linux/usb/ch9.h> +#include <linux/usb/android_composite.h> +#include <linux/usb/f_mtp.h> + +#ifdef CONFIG_USB_ANDROID +/* Constants for MTP_SET_INTERFACE_MODE */ +#define MTP_INTERFACE_MODE_MTP 0 +#define MTP_INTERFACE_MODE_PTP 1 + +/* Sets the driver mode to either MTP or PTP */ +#define MTP_SET_INTERFACE_MODE _IOW('M', 2, int) +#endif + +#define BULK_BUFFER_SIZE 16384 +#define INTR_BUFFER_SIZE 28 + +/* String IDs */ +#define INTERFACE_STRING_INDEX 0 + +/* values for mtp_dev.state */ +#define STATE_OFFLINE 0 /* initial state, disconnected */ +#define STATE_READY 1 /* ready for userspace calls */ +#define STATE_BUSY 2 /* processing userspace calls */ +#define STATE_CANCELED 3 /* transaction canceled by host */ +#define STATE_ERROR 4 /* error from completion routine */ + +/* number of tx and rx requests to allocate */ +#define TX_REQ_MAX 4 +#define RX_REQ_MAX 2 + +/* ID for Microsoft MTP OS String */ +#define MTP_OS_STRING_ID 0xEE + +/* MTP class reqeusts */ +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +/* constants for device status */ +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 + +static const char shortname[] = "mtp_usb"; + +struct mtp_dev { + struct usb_function function; + struct usb_composite_dev *cdev; + spinlock_t lock; + + /* appear as MTP or PTP when enumerating */ + int interface_mode; + + struct usb_ep *ep_in; + struct usb_ep *ep_out; + struct usb_ep *ep_intr; + + int state; + + /* synchronize access to our device file */ + atomic_t open_excl; + /* to enforce only one ioctl at a time */ + atomic_t ioctl_excl; + + struct list_head tx_idle; + + wait_queue_head_t read_wq; + wait_queue_head_t write_wq; + struct usb_request *rx_req[RX_REQ_MAX]; + struct usb_request *intr_req; + int rx_done; + /* true if interrupt endpoint is busy */ + int intr_busy; + + /* for processing MTP_SEND_FILE and MTP_RECEIVE_FILE + * ioctls on a work queue + */ + struct workqueue_struct *wq; + struct work_struct send_file_work; + struct work_struct receive_file_work; + struct file *xfer_file; + loff_t xfer_file_offset; + int64_t xfer_file_length; + int xfer_result; +}; + +static struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC, + .bInterfaceProtocol = 0, +}; + +static struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +static struct usb_endpoint_descriptor mtp_highspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_highspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = __constant_cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor mtp_intr_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16(INTR_BUFFER_SIZE), + .bInterval = 6, +}; + +static struct usb_descriptor_header *fs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_mtp_descs[] = { + (struct usb_descriptor_header *) &mtp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *fs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_in_desc, + (struct usb_descriptor_header *) &mtp_fullspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_descriptor_header *hs_ptp_descs[] = { + (struct usb_descriptor_header *) &ptp_interface_desc, + (struct usb_descriptor_header *) &mtp_highspeed_in_desc, + (struct usb_descriptor_header *) &mtp_highspeed_out_desc, + (struct usb_descriptor_header *) &mtp_intr_desc, + NULL, +}; + +static struct usb_string mtp_string_defs[] = { + /* Naming interface "MTP" so libmtp will recognize us */ + [INTERFACE_STRING_INDEX].s = "MTP", + { }, /* end of list */ +}; + +static struct usb_gadget_strings mtp_string_table = { + .language = 0x0409, /* en-US */ + .strings = mtp_string_defs, +}; + +static struct usb_gadget_strings *mtp_strings[] = { + &mtp_string_table, + NULL, +}; + +/* Microsoft MTP OS String */ +static u8 mtp_os_string[] = { + 18, /* sizeof(mtp_os_string) */ + USB_DT_STRING, + /* Signature field: "MSFT100" */ + 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0, + /* vendor code */ + 1, + /* padding */ + 0 +}; + +/* Microsoft Extended Configuration Descriptor Header Section */ +struct mtp_ext_config_desc_header { + __le32 dwLength; + __u16 bcdVersion; + __le16 wIndex; + __u8 bCount; + __u8 reserved[7]; +}; + +/* Microsoft Extended Configuration Descriptor Function Section */ +struct mtp_ext_config_desc_function { + __u8 bFirstInterfaceNumber; + __u8 bInterfaceCount; + __u8 compatibleID[8]; + __u8 subCompatibleID[8]; + __u8 reserved[6]; +}; + +/* MTP Extended Configuration Descriptor */ +struct { + struct mtp_ext_config_desc_header header; + struct mtp_ext_config_desc_function function; +} mtp_ext_config_desc = { + .header = { + .dwLength = __constant_cpu_to_le32(sizeof(mtp_ext_config_desc)), + .bcdVersion = __constant_cpu_to_le16(0x0100), + .wIndex = __constant_cpu_to_le16(4), + .bCount = __constant_cpu_to_le16(1), + }, + .function = { + .bFirstInterfaceNumber = 0, + .bInterfaceCount = 1, + .compatibleID = { 'M', 'T', 'P' }, + }, +}; + +struct mtp_device_status { + __le16 wLength; + __le16 wCode; +}; + +/* temporary variable used between mtp_open() and mtp_gadget_bind() */ +static struct mtp_dev *_mtp_dev; + +static inline struct mtp_dev *func_to_dev(struct usb_function *f) +{ + return container_of(f, struct mtp_dev, function); +} + +static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size) +{ + struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!req) + return NULL; + + /* now allocate buffers for the requests */ + req->buf = kmalloc(buffer_size, GFP_KERNEL); + if (!req->buf) { + usb_ep_free_request(ep, req); + return NULL; + } + + return req; +} + +static void mtp_request_free(struct usb_request *req, struct usb_ep *ep) +{ + if (req) { + kfree(req->buf); + usb_ep_free_request(ep, req); + } +} + +static inline int _lock(atomic_t *excl) +{ + if (atomic_inc_return(excl) == 1) { + return 0; + } else { + atomic_dec(excl); + return -1; + } +} + +static inline void _unlock(atomic_t *excl) +{ + atomic_dec(excl); +} + +/* add a request to the tail of a list */ +static void req_put(struct mtp_dev *dev, struct list_head *head, + struct usb_request *req) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + list_add_tail(&req->list, head); + spin_unlock_irqrestore(&dev->lock, flags); +} + +/* remove a request from the head of a list */ +static struct usb_request *req_get(struct mtp_dev *dev, struct list_head *head) +{ + unsigned long flags; + struct usb_request *req; + + spin_lock_irqsave(&dev->lock, flags); + if (list_empty(head)) { + req = 0; + } else { + req = list_first_entry(head, struct usb_request, list); + list_del(&req->list); + } + spin_unlock_irqrestore(&dev->lock, flags); + return req; +} + +static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + if (req->status != 0) + dev->state = STATE_ERROR; + + req_put(dev, &dev->tx_idle, req); + + wake_up(&dev->write_wq); +} + +static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + dev->rx_done = 1; + if (req->status != 0) + dev->state = STATE_ERROR; + + wake_up(&dev->read_wq); +} + +static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req) +{ + struct mtp_dev *dev = _mtp_dev; + + DBG(dev->cdev, "mtp_complete_intr status: %d actual: %d\n", + req->status, req->actual); + dev->intr_busy = 0; + if (req->status != 0) + dev->state = STATE_ERROR; +} + +static int __init create_bulk_endpoints(struct mtp_dev *dev, + struct usb_endpoint_descriptor *in_desc, + struct usb_endpoint_descriptor *out_desc, + struct usb_endpoint_descriptor *intr_desc) +{ + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + struct usb_ep *ep; + int i; + + DBG(cdev, "create_bulk_endpoints dev: %p\n", dev); + + ep = usb_ep_autoconfig(cdev->gadget, in_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_in failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_in = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, out_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_out failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_out = ep; + + ep = usb_ep_autoconfig(cdev->gadget, intr_desc); + if (!ep) { + DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n"); + return -ENODEV; + } + DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name); + ep->driver_data = dev; /* claim the endpoint */ + dev->ep_intr = ep; + + /* now allocate requests for our endpoints */ + for (i = 0; i < TX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_in, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_in; + req_put(dev, &dev->tx_idle, req); + } + for (i = 0; i < RX_REQ_MAX; i++) { + req = mtp_request_new(dev->ep_out, BULK_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_out; + dev->rx_req[i] = req; + } + req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE); + if (!req) + goto fail; + req->complete = mtp_complete_intr; + dev->intr_req = req; + + return 0; + +fail: + printk(KERN_ERR "mtp_bind() could not allocate requests\n"); + return -1; +} + +static ssize_t mtp_read(struct file *fp, char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req; + int r = count, xfer; + int ret = 0; + + DBG(cdev, "mtp_read(%d)\n", count); + + if (count > BULK_BUFFER_SIZE) + return -EINVAL; + + /* we will block until we're online */ + DBG(cdev, "mtp_read: waiting for online state\n"); + ret = wait_event_interruptible(dev->read_wq, + dev->state != STATE_OFFLINE); + if (ret < 0) { + r = ret; + goto done; + } + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + +requeue_req: + /* queue a request */ + req = dev->rx_req[0]; + req->length = count; + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + goto done; + } else { + DBG(cdev, "rx %p queue\n", req); + } + + /* wait for a request to complete */ + ret = wait_event_interruptible(dev->read_wq, dev->rx_done); + if (ret < 0) { + r = ret; + usb_ep_dequeue(dev->ep_out, req); + goto done; + } + if (dev->state == STATE_BUSY) { + /* If we got a 0-len packet, throw it back and try again. */ + if (req->actual == 0) + goto requeue_req; + + DBG(cdev, "rx %p %d\n", req, req->actual); + xfer = (req->actual < count) ? req->actual : count; + r = xfer; + if (copy_to_user(buf, req->buf, xfer)) + r = -EFAULT; + } else + r = -EIO; + +done: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_read returning %d\n", r); + return r; +} + +static ssize_t mtp_write(struct file *fp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct mtp_dev *dev = fp->private_data; + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + int r = count, xfer; + int sendZLP = 0; + int ret; + + DBG(cdev, "mtp_write(%d)\n", count); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + return -ECANCELED; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + return -ENODEV; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((count & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + if (dev->state != STATE_BUSY) { + DBG(cdev, "mtp_write dev->error\n"); + r = -EIO; + break; + } + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + ((req = req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY)); + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + if (xfer && copy_from_user(req->buf, buf, xfer)) { + r = -EFAULT; + break; + } + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "mtp_write: xfer error %d\n", ret); + r = -EIO; + break; + } + + buf += xfer; + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + r = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + + DBG(cdev, "mtp_write returning %d\n", r); + return r; +} + +/* read from a local file and write to USB */ +static void send_file_work(struct work_struct *data) { + struct mtp_dev *dev = container_of(data, struct mtp_dev, send_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *req = 0; + struct file *filp; + loff_t offset; + int64_t count; + int xfer, ret; + int r = 0; + int sendZLP = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + /* we need to send a zero length packet to signal the end of transfer + * if the transfer size is aligned to a packet boundary. + */ + if ((dev->xfer_file_length & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; + + /* get an idle tx request to use */ + req = 0; + ret = wait_event_interruptible(dev->write_wq, + (req = req_get(dev, &dev->tx_idle)) + || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + break; + } + if (!req) { + r = ret; + break; + } + + if (count > BULK_BUFFER_SIZE) + xfer = BULK_BUFFER_SIZE; + else + xfer = count; + ret = vfs_read(filp, req->buf, xfer, &offset); + if (ret < 0) { + r = ret; + break; + } + xfer = ret; + + req->length = xfer; + ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL); + if (ret < 0) { + DBG(cdev, "send_file_work: xfer error %d\n", ret); + dev->state = STATE_ERROR; + r = -EIO; + break; + } + + count -= xfer; + + /* zero this so we don't try to free it on error exit */ + req = 0; + } + + if (req) + req_put(dev, &dev->tx_idle, req); + + DBG(cdev, "send_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +/* read from USB and write to a local file */ +static void receive_file_work(struct work_struct *data) +{ + struct mtp_dev *dev = container_of(data, struct mtp_dev, receive_file_work); + struct usb_composite_dev *cdev = dev->cdev; + struct usb_request *read_req = NULL, *write_req = NULL; + struct file *filp; + loff_t offset; + int64_t count; + int ret, cur_buf = 0; + int r = 0; + + /* read our parameters */ + smp_rmb(); + filp = dev->xfer_file; + offset = dev->xfer_file_offset; + count = dev->xfer_file_length; + + DBG(cdev, "receive_file_work(%lld)\n", count); + + while (count > 0 || write_req) { + if (count > 0) { + /* queue a request */ + read_req = dev->rx_req[cur_buf]; + cur_buf = (cur_buf + 1) % RX_REQ_MAX; + + read_req->length = (count > BULK_BUFFER_SIZE + ? BULK_BUFFER_SIZE : count); + dev->rx_done = 0; + ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL); + if (ret < 0) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + } + + if (write_req) { + DBG(cdev, "rx %p %d\n", write_req, write_req->actual); + ret = vfs_write(filp, write_req->buf, write_req->actual, + &offset); + DBG(cdev, "vfs_write %d\n", ret); + if (ret != write_req->actual) { + r = -EIO; + dev->state = STATE_ERROR; + break; + } + write_req = NULL; + } + + if (read_req) { + /* wait for our last read to complete */ + ret = wait_event_interruptible(dev->read_wq, + dev->rx_done || dev->state != STATE_BUSY); + if (dev->state == STATE_CANCELED) { + r = -ECANCELED; + if (!dev->rx_done) + usb_ep_dequeue(dev->ep_out, read_req); + break; + } + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* short packet is used to signal EOF for sizes > 4 gig */ + DBG(cdev, "got short packet\n"); + count = 0; + } + + write_req = read_req; + read_req = NULL; + } + } + + DBG(cdev, "receive_file_work returning %d\n", r); + /* write the result */ + dev->xfer_result = r; + smp_wmb(); +} + +static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event) +{ + struct usb_request *req; + int ret; + int length = event->length; + + DBG(dev->cdev, "mtp_send_event(%d)\n", event->length); + + if (length < 0 || length > INTR_BUFFER_SIZE) + return -EINVAL; + if (dev->state == STATE_OFFLINE) + return -ENODEV; + /* unfortunately an interrupt request might hang indefinitely if the host + * is not listening on the interrupt endpoint, so instead of waiting, + * we just fail if the endpoint is busy. + */ + if (dev->intr_busy) + return -EBUSY; + + req = dev->intr_req; + if (copy_from_user(req->buf, (void __user *)event->data, length)) + return -EFAULT; + req->length = length; + dev->intr_busy = 1; + ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL); + if (ret) + dev->intr_busy = 0; + + return ret; +} + +static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value) +{ + struct mtp_dev *dev = fp->private_data; + struct file *filp = NULL; + int ret = -EINVAL; + + if (_lock(&dev->ioctl_excl)) + return -EBUSY; + + switch (code) { + case MTP_SEND_FILE: + case MTP_RECEIVE_FILE: + { + struct mtp_file_range mfr; + struct work_struct *work; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) { + /* report cancelation to userspace */ + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); + ret = -ECANCELED; + goto out; + } + if (dev->state == STATE_OFFLINE) { + spin_unlock_irq(&dev->lock); + ret = -ENODEV; + goto out; + } + dev->state = STATE_BUSY; + spin_unlock_irq(&dev->lock); + + if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) { + ret = -EFAULT; + goto fail; + } + /* hold a reference to the file while we are working with it */ + filp = fget(mfr.fd); + if (!filp) { + ret = -EBADF; + goto fail; + } + + /* write the parameters */ + dev->xfer_file = filp; + dev->xfer_file_offset = mfr.offset; + dev->xfer_file_length = mfr.length; + smp_wmb(); + + if (code == MTP_SEND_FILE) + work = &dev->send_file_work; + else + work = &dev->receive_file_work; + + /* We do the file transfer on a work queue so it will run + * in kernel context, which is necessary for vfs_read and + * vfs_write to use our buffers in the kernel address space. + */ + queue_work(dev->wq, work); + /* wait for operation to complete */ + flush_workqueue(dev->wq); + fput(filp); + + /* read the result */ + smp_rmb(); + ret = dev->xfer_result; + break; + } + case MTP_SET_INTERFACE_MODE: + if (value == MTP_INTERFACE_MODE_MTP || + value == MTP_INTERFACE_MODE_PTP) { + dev->interface_mode = value; + if (value == MTP_INTERFACE_MODE_PTP) { + dev->function.descriptors = fs_ptp_descs; + dev->function.hs_descriptors = hs_ptp_descs; + } else { + dev->function.descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + } + ret = 0; + } + break; + case MTP_SEND_EVENT: + { + struct mtp_event event; + /* return here so we don't change dev->state below, + * which would interfere with bulk transfer state. + */ + if (copy_from_user(&event, (void __user *)value, sizeof(event))) + ret = -EFAULT; + else + ret = mtp_send_event(dev, &event); + goto out; + } + } + +fail: + spin_lock_irq(&dev->lock); + if (dev->state == STATE_CANCELED) + ret = -ECANCELED; + else if (dev->state != STATE_OFFLINE) + dev->state = STATE_READY; + spin_unlock_irq(&dev->lock); +out: + _unlock(&dev->ioctl_excl); + DBG(dev->cdev, "ioctl returning %d\n", ret); + return ret; +} + +static int mtp_open(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_open\n"); + if (_lock(&_mtp_dev->open_excl)) + return -EBUSY; + + /* clear any error condition */ + if (_mtp_dev->state != STATE_OFFLINE) + _mtp_dev->state = STATE_READY; + + fp->private_data = _mtp_dev; + return 0; +} + +static int mtp_release(struct inode *ip, struct file *fp) +{ + printk(KERN_INFO "mtp_release\n"); + + _unlock(&_mtp_dev->open_excl); + return 0; +} + +/* file operations for /dev/mtp_usb */ +static const struct file_operations mtp_fops = { + .owner = THIS_MODULE, + .read = mtp_read, + .write = mtp_write, + .unlocked_ioctl = mtp_ioctl, + .open = mtp_open, + .release = mtp_release, +}; + +static struct miscdevice mtp_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = shortname, + .fops = &mtp_fops, +}; + +static int +mtp_function_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct mtp_dev *dev = func_to_dev(f); + int id; + int ret; + + dev->cdev = cdev; + DBG(cdev, "mtp_function_bind dev: %p\n", dev); + + /* allocate interface ID(s) */ + id = usb_interface_id(c, f); + if (id < 0) + return id; + mtp_interface_desc.bInterfaceNumber = id; + + /* allocate endpoints */ + ret = create_bulk_endpoints(dev, &mtp_fullspeed_in_desc, + &mtp_fullspeed_out_desc, &mtp_intr_desc); + if (ret) + return ret; + + /* support high speed hardware */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + mtp_highspeed_in_desc.bEndpointAddress = + mtp_fullspeed_in_desc.bEndpointAddress; + mtp_highspeed_out_desc.bEndpointAddress = + mtp_fullspeed_out_desc.bEndpointAddress; + } + + DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + f->name, dev->ep_in->name, dev->ep_out->name); + return 0; +} + +static void +mtp_function_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct mtp_dev *dev = func_to_dev(f); + struct usb_request *req; + int i; + + spin_lock_irq(&dev->lock); + while ((req = req_get(dev, &dev->tx_idle))) + mtp_request_free(req, dev->ep_in); + for (i = 0; i < RX_REQ_MAX; i++) + mtp_request_free(dev->rx_req[i], dev->ep_out); + mtp_request_free(dev->intr_req, dev->ep_intr); + dev->state = STATE_OFFLINE; + spin_unlock_irq(&dev->lock); + + misc_deregister(&mtp_device); + kfree(_mtp_dev); + _mtp_dev = NULL; +} + +static int mtp_function_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) +{ + struct mtp_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + int value = -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); + unsigned long flags; + + /* do nothing if we are disabled */ + if (dev->function.disabled) + return value; + + VDBG(cdev, "mtp_function_setup " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + + /* Handle MTP OS string */ + if (dev->interface_mode == MTP_INTERFACE_MODE_MTP + && ctrl->bRequestType == + (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) + && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR + && (w_value >> 8) == USB_DT_STRING + && (w_value & 0xFF) == MTP_OS_STRING_ID) { + value = (w_length < sizeof(mtp_os_string) + ? w_length : sizeof(mtp_os_string)); + memcpy(cdev->req->buf, mtp_os_string, value); + /* return here since composite.c will send for us */ + return value; + } + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle MTP OS descriptor */ + DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (dev->interface_mode == MTP_INTERFACE_MODE_MTP + && ctrl->bRequest == 1 + && (ctrl->bRequestType & USB_DIR_IN) + && (w_index == 4 || w_index == 5)) { + value = (w_length < sizeof(mtp_ext_config_desc) ? + w_length : sizeof(mtp_ext_config_desc)); + memcpy(cdev->req->buf, &mtp_ext_config_desc, value); + } + } + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) { + DBG(cdev, "class request: %d index: %d value: %d length: %d\n", + ctrl->bRequest, w_index, w_value, w_length); + + if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0 + && w_value == 0) { + DBG(cdev, "MTP_REQ_CANCEL\n"); + + spin_lock_irqsave(&dev->lock, flags); + if (dev->state == STATE_BUSY) { + dev->state = STATE_CANCELED; + wake_up(&dev->read_wq); + wake_up(&dev->write_wq); + } + spin_unlock_irqrestore(&dev->lock, flags); + + /* We need to queue a request to read the remaining + * bytes, but we don't actually need to look at + * the contents. + */ + value = w_length; + } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS + && w_index == 0 && w_value == 0) { + struct mtp_device_status *status = cdev->req->buf; + status->wLength = + __constant_cpu_to_le16(sizeof(*status)); + + DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n"); + spin_lock_irqsave(&dev->lock, flags); + /* device status is "busy" until we report + * the cancelation to userspace + */ + if (dev->state == STATE_CANCELED) + status->wCode = + __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY); + else + status->wCode = + __cpu_to_le16(MTP_RESPONSE_OK); + spin_unlock_irqrestore(&dev->lock, flags); + value = sizeof(*status); + } + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + int rc; + cdev->req->zero = value < w_length; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + ERROR(cdev, "%s setup response queue error\n", __func__); + } + + if (value == -EOPNOTSUPP) + VDBG(cdev, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + return value; +} + +static int mtp_function_set_alt(struct usb_function *f, + unsigned intf, unsigned alt) +{ + struct mtp_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = f->config->cdev; + int ret; + + DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt); + ret = usb_ep_enable(dev->ep_in, + ep_choose(cdev->gadget, + &mtp_highspeed_in_desc, + &mtp_fullspeed_in_desc)); + if (ret) + return ret; + ret = usb_ep_enable(dev->ep_out, + ep_choose(cdev->gadget, + &mtp_highspeed_out_desc, + &mtp_fullspeed_out_desc)); + if (ret) { + usb_ep_disable(dev->ep_in); + return ret; + } + ret = usb_ep_enable(dev->ep_intr, &mtp_intr_desc); + if (ret) { + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_in); + return ret; + } + dev->state = STATE_READY; + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + return 0; +} + +static void mtp_function_disable(struct usb_function *f) +{ + struct mtp_dev *dev = func_to_dev(f); + struct usb_composite_dev *cdev = dev->cdev; + + DBG(cdev, "mtp_function_disable\n"); + dev->state = STATE_OFFLINE; + usb_ep_disable(dev->ep_in); + usb_ep_disable(dev->ep_out); + usb_ep_disable(dev->ep_intr); + + /* readers may be blocked waiting for us to go online */ + wake_up(&dev->read_wq); + + VDBG(cdev, "%s disabled\n", dev->function.name); +} + +static int mtp_bind_config(struct usb_configuration *c) +{ + struct mtp_dev *dev; + int ret = 0; + + printk(KERN_INFO "mtp_bind_config\n"); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + /* allocate a string ID for our interface */ + if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) { + ret = usb_string_id(c->cdev); + if (ret < 0) + return ret; + mtp_string_defs[INTERFACE_STRING_INDEX].id = ret; + mtp_interface_desc.iInterface = ret; + } + + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->read_wq); + init_waitqueue_head(&dev->write_wq); + atomic_set(&dev->open_excl, 0); + atomic_set(&dev->ioctl_excl, 0); + INIT_LIST_HEAD(&dev->tx_idle); + + dev->wq = create_singlethread_workqueue("f_mtp"); + if (!dev->wq) + goto err1; + INIT_WORK(&dev->send_file_work, send_file_work); + INIT_WORK(&dev->receive_file_work, receive_file_work); + + dev->cdev = c->cdev; + dev->function.name = "mtp"; + dev->function.strings = mtp_strings, + dev->function.descriptors = fs_mtp_descs; + dev->function.hs_descriptors = hs_mtp_descs; + dev->function.bind = mtp_function_bind; + dev->function.unbind = mtp_function_unbind; + dev->function.setup = mtp_function_setup; + dev->function.set_alt = mtp_function_set_alt; + dev->function.disable = mtp_function_disable; + + /* MTP mode by default */ + dev->interface_mode = MTP_INTERFACE_MODE_MTP; + + /* _mtp_dev must be set before calling usb_gadget_register_driver */ + _mtp_dev = dev; + + ret = misc_register(&mtp_device); + if (ret) + goto err1; + + ret = usb_add_function(c, &dev->function); + if (ret) + goto err2; + + return 0; + +err2: + misc_deregister(&mtp_device); +err1: + if (dev->wq) + destroy_workqueue(dev->wq); + kfree(dev); + printk(KERN_ERR "mtp gadget driver failed to initialize\n"); + return ret; +} + +static struct android_usb_function mtp_function = { + .name = "mtp", + .bind_config = mtp_bind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_mtp init\n"); + android_register_function(&mtp_function); + return 0; +} +module_init(init); diff --git a/drivers/usb/gadget/gadget_gbhc/f_ncm.c b/drivers/usb/gadget/gadget_gbhc/f_ncm.c new file mode 100644 index 0000000..86902a6 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/f_ncm.c @@ -0,0 +1,1407 @@ +/* + * f_ncm.c -- USB CDC Network (NCM) link function driver + * + * Copyright (C) 2010 Nokia Corporation + * Contact: Yauheni Kaliuta <yauheni.kaliuta@nokia.com> + * + * The driver borrows from f_ecm.c which is: + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/etherdevice.h> +#include <linux/crc32.h> + +#include <linux/usb/cdc.h> + +#include "u_ether.h" + +/* + * This function is a "CDC Network Control Model" (CDC NCM) Ethernet link. + * NCM is intended to be used with high-speed network attachments. + * + * Note that NCM requires the use of "alternate settings" for its data + * interface. This means that the set_alt() method has real work to do, + * and also means that a get_alt() method is required. + */ + +/* to trigger crc/non-crc ndp signature */ + +#define NCM_NDP_HDR_CRC_MASK 0x01000000 +#define NCM_NDP_HDR_CRC 0x01000000 +#define NCM_NDP_HDR_NOCRC 0x00000000 + +struct ncm_ep_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + struct usb_endpoint_descriptor *notify; +}; + +enum ncm_notify_state { + NCM_NOTIFY_NONE, /* don't notify */ + NCM_NOTIFY_CONNECT, /* issue CONNECT next */ + NCM_NOTIFY_SPEED, /* issue SPEED_CHANGE next */ +}; + +struct f_ncm { + struct gether port; + u8 ctrl_id, data_id; + + char ethaddr[14]; + + struct ncm_ep_descs fs; + struct ncm_ep_descs hs; + + struct usb_ep *notify; + struct usb_endpoint_descriptor *notify_desc; + struct usb_request *notify_req; + u8 notify_state; + bool is_open; + + struct ndp_parser_opts *parser_opts; + bool is_crc; + + /* + * for notification, it is accessed from both + * callback and ethernet open/close + */ + spinlock_t lock; +}; + +static inline struct f_ncm *func_to_ncm(struct usb_function *f) +{ + return container_of(f, struct f_ncm, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static inline unsigned ncm_bitrate(struct usb_gadget *g) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +/* + * We cannot group frames so use just the minimal size which ok to put + * one max-size ethernet frame. + * If the host can group frames, allow it to do that, 16K is selected, + * because it's used by default by the current linux host driver + */ +#define NTB_DEFAULT_IN_SIZE USB_CDC_NCM_NTB_MIN_IN_SIZE +#define NTB_OUT_SIZE 16384 + +/* + * skbs of size less than that will not be aligned + * to NCM's dwNtbInMaxSize to save bus bandwidth + */ + +#define MAX_TX_NONFIXED (512 * 3) + +#define FORMATS_SUPPORTED (USB_CDC_NCM_NTB16_SUPPORTED | \ + USB_CDC_NCM_NTB32_SUPPORTED) + +static struct usb_cdc_ncm_ntb_parameters ntb_parameters = { + .wLength = sizeof ntb_parameters, + .bmNtbFormatsSupported = cpu_to_le16(FORMATS_SUPPORTED), + .dwNtbInMaxSize = cpu_to_le32(NTB_DEFAULT_IN_SIZE), + .wNdpInDivisor = cpu_to_le16(4), + .wNdpInPayloadRemainder = cpu_to_le16(0), + .wNdpInAlignment = cpu_to_le16(4), + + .dwNtbOutMaxSize = cpu_to_le32(NTB_OUT_SIZE), + .wNdpOutDivisor = cpu_to_le16(4), + .wNdpOutPayloadRemainder = cpu_to_le16(0), + .wNdpOutAlignment = cpu_to_le16(4), +}; + +/* + * Use wMaxPacketSize big enough to fit CDC_NOTIFY_SPEED_CHANGE in one + * packet, to simplify cancellation; and a big transfer interval, to + * waste less bandwidth. + */ + +#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define NCM_STATUS_BYTECOUNT 16 /* 8 byte header + data */ + +static struct usb_interface_assoc_descriptor ncm_iad_desc __initdata = { + .bLength = sizeof ncm_iad_desc, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + /* .bFirstInterface = DYNAMIC, */ + .bInterfaceCount = 2, /* control + data */ + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_NCM, + .bFunctionProtocol = USB_CDC_PROTO_NONE, + /* .iFunction = DYNAMIC */ +}; + +/* interface descriptor: */ + +static struct usb_interface_descriptor ncm_control_intf __initdata = { + .bLength = sizeof ncm_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_NCM, + .bInterfaceProtocol = USB_CDC_PROTO_NONE, + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc ncm_header_desc __initdata = { + .bLength = sizeof ncm_header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_union_desc ncm_union_desc __initdata = { + .bLength = sizeof(ncm_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +static struct usb_cdc_ether_desc ecm_desc __initdata = { + .bLength = sizeof ecm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ETHERNET_TYPE, + + /* this descriptor actually adds value, surprise! */ + /* .iMACAddress = DYNAMIC */ + .bmEthernetStatistics = cpu_to_le32(0), /* no statistics */ + .wMaxSegmentSize = cpu_to_le16(ETH_FRAME_LEN), + .wNumberMCFilters = cpu_to_le16(0), + .bNumberPowerFilters = 0, +}; + +#define NCAPS (USB_CDC_NCM_NCAP_ETH_FILTER | USB_CDC_NCM_NCAP_CRC_MODE) + +static struct usb_cdc_ncm_desc ncm_desc __initdata = { + .bLength = sizeof ncm_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_NCM_TYPE, + + .bcdNcmVersion = cpu_to_le16(0x0100), + /* can process SetEthernetPacketFilter */ + .bmNetworkCapabilities = NCAPS, +}; + +/* the default data interface has no endpoints ... */ + +static struct usb_interface_descriptor ncm_data_nop_intf __initdata = { + .bLength = sizeof ncm_data_nop_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 0, + .bNumEndpoints = 0, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = USB_CDC_NCM_PROTO_NTB, + /* .iInterface = DYNAMIC */ +}; + +/* ... but the "real" data interface has two bulk endpoints */ + +static struct usb_interface_descriptor ncm_data_intf __initdata = { + .bLength = sizeof ncm_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + .bInterfaceNumber = 1, + .bAlternateSetting = 1, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = USB_CDC_NCM_PROTO_NTB, + /* .iInterface = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_ncm_notify_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor fs_ncm_in_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_ncm_out_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *ncm_fs_function[] __initdata = { + (struct usb_descriptor_header *) &ncm_iad_desc, + /* CDC NCM control descriptors */ + (struct usb_descriptor_header *) &ncm_control_intf, + (struct usb_descriptor_header *) &ncm_header_desc, + (struct usb_descriptor_header *) &ncm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + (struct usb_descriptor_header *) &ncm_desc, + (struct usb_descriptor_header *) &fs_ncm_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ncm_data_nop_intf, + (struct usb_descriptor_header *) &ncm_data_intf, + (struct usb_descriptor_header *) &fs_ncm_in_desc, + (struct usb_descriptor_header *) &fs_ncm_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_ncm_notify_desc __initdata = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(NCM_STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor hs_ncm_in_desc __initdata = { + .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 hs_ncm_out_desc __initdata = { + .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 *ncm_hs_function[] __initdata = { + (struct usb_descriptor_header *) &ncm_iad_desc, + /* CDC NCM control descriptors */ + (struct usb_descriptor_header *) &ncm_control_intf, + (struct usb_descriptor_header *) &ncm_header_desc, + (struct usb_descriptor_header *) &ncm_union_desc, + (struct usb_descriptor_header *) &ecm_desc, + (struct usb_descriptor_header *) &ncm_desc, + (struct usb_descriptor_header *) &hs_ncm_notify_desc, + /* data interface, altsettings 0 and 1 */ + (struct usb_descriptor_header *) &ncm_data_nop_intf, + (struct usb_descriptor_header *) &ncm_data_intf, + (struct usb_descriptor_header *) &hs_ncm_in_desc, + (struct usb_descriptor_header *) &hs_ncm_out_desc, + NULL, +}; + +/* string descriptors: */ + +#define STRING_CTRL_IDX 0 +#define STRING_MAC_IDX 1 +#define STRING_DATA_IDX 2 +#define STRING_IAD_IDX 3 + +static struct usb_string ncm_string_defs[] = { + [STRING_CTRL_IDX].s = "CDC Network Control Model (NCM)", + [STRING_MAC_IDX].s = NULL /* DYNAMIC */, + [STRING_DATA_IDX].s = "CDC Network Data", + [STRING_IAD_IDX].s = "CDC NCM", + { } /* end of list */ +}; + +static struct usb_gadget_strings ncm_string_table = { + .language = 0x0409, /* en-us */ + .strings = ncm_string_defs, +}; + +static struct usb_gadget_strings *ncm_strings[] = { + &ncm_string_table, + NULL, +}; + +/* + * Here are options for NCM Datagram Pointer table (NDP) parser. + * There are 2 different formats: NDP16 and NDP32 in the spec (ch. 3), + * in NDP16 offsets and sizes fields are 1 16bit word wide, + * in NDP32 -- 2 16bit words wide. Also signatures are different. + * To make the parser code the same, put the differences in the structure, + * and switch pointers to the structures when the format is changed. + */ + +struct ndp_parser_opts { + u32 nth_sign; + u32 ndp_sign; + unsigned nth_size; + unsigned ndp_size; + unsigned ndplen_align; + /* sizes in u16 units */ + unsigned dgram_item_len; /* index or length */ + unsigned block_length; + unsigned fp_index; + unsigned reserved1; + unsigned reserved2; + unsigned next_fp_index; +}; + +#define INIT_NDP16_OPTS { \ + .nth_sign = USB_CDC_NCM_NTH16_SIGN, \ + .ndp_sign = USB_CDC_NCM_NDP16_NOCRC_SIGN, \ + .nth_size = sizeof(struct usb_cdc_ncm_nth16), \ + .ndp_size = sizeof(struct usb_cdc_ncm_ndp16), \ + .ndplen_align = 4, \ + .dgram_item_len = 1, \ + .block_length = 1, \ + .fp_index = 1, \ + .reserved1 = 0, \ + .reserved2 = 0, \ + .next_fp_index = 1, \ + } + + +#define INIT_NDP32_OPTS { \ + .nth_sign = USB_CDC_NCM_NTH32_SIGN, \ + .ndp_sign = USB_CDC_NCM_NDP32_NOCRC_SIGN, \ + .nth_size = sizeof(struct usb_cdc_ncm_nth32), \ + .ndp_size = sizeof(struct usb_cdc_ncm_ndp32), \ + .ndplen_align = 8, \ + .dgram_item_len = 2, \ + .block_length = 2, \ + .fp_index = 2, \ + .reserved1 = 1, \ + .reserved2 = 2, \ + .next_fp_index = 2, \ + } + +static struct ndp_parser_opts ndp16_opts = INIT_NDP16_OPTS; +static struct ndp_parser_opts ndp32_opts = INIT_NDP32_OPTS; + +static inline void put_ncm(__le16 **p, unsigned size, unsigned val) +{ + switch (size) { + case 1: + put_unaligned_le16((u16)val, *p); + break; + case 2: + put_unaligned_le32((u32)val, *p); + + break; + default: + BUG(); + } + + *p += size; +} + +static inline unsigned get_ncm(__le16 **p, unsigned size) +{ + unsigned tmp; + + switch (size) { + case 1: + tmp = get_unaligned_le16(*p); + break; + case 2: + tmp = get_unaligned_le32(*p); + break; + default: + BUG(); + } + + *p += size; + return tmp; +} + +/*-------------------------------------------------------------------------*/ + +static inline void ncm_reset_values(struct f_ncm *ncm) +{ + ncm->parser_opts = &ndp16_opts; + ncm->is_crc = false; + ncm->port.cdc_filter = DEFAULT_FILTER; + + /* doesn't make sense for ncm, fixed size used */ + ncm->port.header_len = 0; + + ncm->port.fixed_out_len = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize); + ncm->port.fixed_in_len = NTB_DEFAULT_IN_SIZE; +} + +/* + * Context: ncm->lock held + */ +static void ncm_do_notify(struct f_ncm *ncm) +{ + struct usb_request *req = ncm->notify_req; + struct usb_cdc_notification *event; + struct usb_composite_dev *cdev = ncm->port.func.config->cdev; + __le32 *data; + int status; + + /* notification already in flight? */ + if (!req) + return; + + event = req->buf; + switch (ncm->notify_state) { + case NCM_NOTIFY_NONE: + return; + + case NCM_NOTIFY_CONNECT: + event->bNotificationType = USB_CDC_NOTIFY_NETWORK_CONNECTION; + if (ncm->is_open) + event->wValue = cpu_to_le16(1); + else + event->wValue = cpu_to_le16(0); + event->wLength = 0; + req->length = sizeof *event; + + DBG(cdev, "notify connect %s\n", + ncm->is_open ? "true" : "false"); + ncm->notify_state = NCM_NOTIFY_NONE; + break; + + case NCM_NOTIFY_SPEED: + event->bNotificationType = USB_CDC_NOTIFY_SPEED_CHANGE; + event->wValue = cpu_to_le16(0); + event->wLength = cpu_to_le16(8); + req->length = NCM_STATUS_BYTECOUNT; + + /* SPEED_CHANGE data is up/down speeds in bits/sec */ + data = req->buf + sizeof *event; + data[0] = cpu_to_le32(ncm_bitrate(cdev->gadget)); + data[1] = data[0]; + + DBG(cdev, "notify speed %d\n", ncm_bitrate(cdev->gadget)); + ncm->notify_state = NCM_NOTIFY_CONNECT; + break; + } + event->bmRequestType = 0xA1; + event->wIndex = cpu_to_le16(ncm->ctrl_id); + + ncm->notify_req = NULL; + /* + * In double buffering if there is a space in FIFO, + * completion callback can be called right after the call, + * so unlocking + */ + spin_unlock(&ncm->lock); + status = usb_ep_queue(ncm->notify, req, GFP_ATOMIC); + spin_lock(&ncm->lock); + if (status < 0) { + ncm->notify_req = req; + DBG(cdev, "notify --> %d\n", status); + } +} + +/* + * Context: ncm->lock held + */ +static void ncm_notify(struct f_ncm *ncm) +{ + /* + * NOTE on most versions of Linux, host side cdc-ethernet + * won't listen for notifications until its netdevice opens. + * The first notification then sits in the FIFO for a long + * time, and the second one is queued. + * + * If ncm_notify() is called before the second (CONNECT) + * notification is sent, then it will reset to send the SPEED + * notificaion again (and again, and again), but it's not a problem + */ + ncm->notify_state = NCM_NOTIFY_SPEED; + ncm_do_notify(ncm); +} + +static void ncm_notify_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_ncm *ncm = req->context; + struct usb_composite_dev *cdev = ncm->port.func.config->cdev; + struct usb_cdc_notification *event = req->buf; + + spin_lock(&ncm->lock); + switch (req->status) { + case 0: + VDBG(cdev, "Notification %02x sent\n", + event->bNotificationType); + break; + case -ECONNRESET: + case -ESHUTDOWN: + ncm->notify_state = NCM_NOTIFY_NONE; + break; + default: + DBG(cdev, "event %02x --> %d\n", + event->bNotificationType, req->status); + break; + } + ncm->notify_req = req; + ncm_do_notify(ncm); + spin_unlock(&ncm->lock); +} + +static void ncm_ep0out_complete(struct usb_ep *ep, struct usb_request *req) +{ + /* now for SET_NTB_INPUT_SIZE only */ + unsigned in_size; + struct usb_function *f = req->context; + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = ep->driver_data; + + req->context = NULL; + if (req->status || req->actual != req->length) { + DBG(cdev, "Bad control-OUT transfer\n"); + goto invalid; + } + + in_size = get_unaligned_le32(req->buf); + if (in_size < USB_CDC_NCM_NTB_MIN_IN_SIZE || + in_size > le32_to_cpu(ntb_parameters.dwNtbInMaxSize)) { + DBG(cdev, "Got wrong INPUT SIZE (%d) from host\n", in_size); + goto invalid; + } + + ncm->port.fixed_in_len = in_size; + VDBG(cdev, "Set NTB INPUT SIZE %d\n", in_size); + return; + +invalid: + usb_ep_set_halt(ep); + return; +} + +static int ncm_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -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); + + /* + * composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_ETHERNET_PACKET_FILTER: + /* + * see 6.2.30: no data, wIndex = interface, + * wValue = packet filter bitmap + */ + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + DBG(cdev, "packet filter %02x\n", w_value); + /* + * REVISIT locking of cdc_filter. This assumes the UDC + * driver won't have a concurrent packet TX irq running on + * another CPU; or that if it does, this write is atomic... + */ + ncm->port.cdc_filter = w_value; + value = 0; + break; + /* + * and optionally: + * case USB_CDC_SEND_ENCAPSULATED_COMMAND: + * case USB_CDC_GET_ENCAPSULATED_RESPONSE: + * case USB_CDC_SET_ETHERNET_MULTICAST_FILTERS: + * case USB_CDC_SET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_PM_PATTERN_FILTER: + * case USB_CDC_GET_ETHERNET_STATISTIC: + */ + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_PARAMETERS: + + if (w_length == 0 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + value = w_length > sizeof ntb_parameters ? + sizeof ntb_parameters : w_length; + memcpy(req->buf, &ntb_parameters, value); + VDBG(cdev, "Host asked NTB parameters\n"); + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_INPUT_SIZE: + + if (w_length < 4 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + put_unaligned_le32(ncm->port.fixed_in_len, req->buf); + value = 4; + VDBG(cdev, "Host asked INPUT SIZE, sending %d\n", + ncm->port.fixed_in_len); + break; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_INPUT_SIZE: + { + if (w_length != 4 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + req->complete = ncm_ep0out_complete; + req->length = w_length; + req->context = f; + + value = req->length; + break; + } + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_NTB_FORMAT: + { + uint16_t format; + + if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + format = (ncm->parser_opts == &ndp16_opts) ? 0x0000 : 0x0001; + put_unaligned_le16(format, req->buf); + value = 2; + VDBG(cdev, "Host asked NTB FORMAT, sending %d\n", format); + break; + } + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_NTB_FORMAT: + { + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + switch (w_value) { + case 0x0000: + ncm->parser_opts = &ndp16_opts; + DBG(cdev, "NCM16 selected\n"); + break; + case 0x0001: + ncm->parser_opts = &ndp32_opts; + DBG(cdev, "NCM32 selected\n"); + break; + default: + goto invalid; + } + value = 0; + break; + } + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_CRC_MODE: + { + uint16_t is_crc; + + if (w_length < 2 || w_value != 0 || w_index != ncm->ctrl_id) + goto invalid; + is_crc = ncm->is_crc ? 0x0001 : 0x0000; + put_unaligned_le16(is_crc, req->buf); + value = 2; + VDBG(cdev, "Host asked CRC MODE, sending %d\n", is_crc); + break; + } + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SET_CRC_MODE: + { + int ndp_hdr_crc = 0; + + if (w_length != 0 || w_index != ncm->ctrl_id) + goto invalid; + switch (w_value) { + case 0x0000: + ncm->is_crc = false; + ndp_hdr_crc = NCM_NDP_HDR_NOCRC; + DBG(cdev, "non-CRC mode selected\n"); + break; + case 0x0001: + ncm->is_crc = true; + ndp_hdr_crc = NCM_NDP_HDR_CRC; + DBG(cdev, "CRC mode selected\n"); + break; + default: + goto invalid; + } + ncm->parser_opts->ndp_sign &= ~NCM_NDP_HDR_CRC_MASK; + ncm->parser_opts->ndp_sign |= ndp_hdr_crc; + value = 0; + break; + } + + /* and disabled in ncm descriptor: */ + /* case USB_CDC_GET_NET_ADDRESS: */ + /* case USB_CDC_SET_NET_ADDRESS: */ + /* case USB_CDC_GET_MAX_DATAGRAM_SIZE: */ + /* case USB_CDC_SET_MAX_DATAGRAM_SIZE: */ + + default: +invalid: + DBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "ncm req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = 0; + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "ncm req %02x.%02x response err %d\n", + ctrl->bRequestType, ctrl->bRequest, + value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int ncm_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* Control interface has only altsetting 0 */ + if (intf == ncm->ctrl_id) { + if (alt != 0) + goto fail; + + if (ncm->notify->driver_data) { + DBG(cdev, "reset ncm control %d\n", intf); + usb_ep_disable(ncm->notify); + } else { + DBG(cdev, "init ncm ctrl %d\n", intf); + ncm->notify_desc = ep_choose(cdev->gadget, + ncm->hs.notify, + ncm->fs.notify); + } + usb_ep_enable(ncm->notify, ncm->notify_desc); + ncm->notify->driver_data = ncm; + + /* Data interface has two altsettings, 0 and 1 */ + } else if (intf == ncm->data_id) { + if (alt > 1) + goto fail; + + if (ncm->port.in_ep->driver_data) { + DBG(cdev, "reset ncm\n"); + gether_disconnect(&ncm->port); + ncm_reset_values(ncm); + } + + /* + * CDC Network only sends data in non-default altsettings. + * Changing altsettings resets filters, statistics, etc. + */ + if (alt == 1) { + struct net_device *net; + + if (!ncm->port.in) { + DBG(cdev, "init ncm\n"); + ncm->port.in = ep_choose(cdev->gadget, + ncm->hs.in, + ncm->fs.in); + ncm->port.out = ep_choose(cdev->gadget, + ncm->hs.out, + ncm->fs.out); + } + + /* TODO */ + /* Enable zlps by default for NCM conformance; + * override for musb_hdrc (avoids txdma ovhead) + */ + ncm->port.is_zlp_ok = !( + gadget_is_musbhdrc(cdev->gadget) + ); + ncm->port.cdc_filter = DEFAULT_FILTER; + DBG(cdev, "activate ncm\n"); + net = gether_connect(&ncm->port); + if (IS_ERR(net)) + return PTR_ERR(net); + } + + spin_lock(&ncm->lock); + ncm_notify(ncm); + spin_unlock(&ncm->lock); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +/* + * Because the data interface supports multiple altsettings, + * this NCM function *MUST* implement a get_alt() method. + */ +static int ncm_get_alt(struct usb_function *f, unsigned intf) +{ + struct f_ncm *ncm = func_to_ncm(f); + + if (intf == ncm->ctrl_id) + return 0; + return ncm->port.in_ep->driver_data ? 1 : 0; +} + +static struct sk_buff *ncm_wrap_ntb(struct gether *port, + struct sk_buff *skb) +{ + struct f_ncm *ncm = func_to_ncm(&port->func); + struct sk_buff *skb2; + int ncb_len = 0; + __le16 *tmp; + int div = ntb_parameters.wNdpInDivisor; + int rem = ntb_parameters.wNdpInPayloadRemainder; + int pad; + int ndp_align = ntb_parameters.wNdpInAlignment; + int ndp_pad; + unsigned max_size = ncm->port.fixed_in_len; + struct ndp_parser_opts *opts = ncm->parser_opts; + unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; + + ncb_len += opts->nth_size; + ndp_pad = ALIGN(ncb_len, ndp_align) - ncb_len; + ncb_len += ndp_pad; + ncb_len += opts->ndp_size; + ncb_len += 2 * 2 * opts->dgram_item_len; /* Datagram entry */ + ncb_len += 2 * 2 * opts->dgram_item_len; /* Zero datagram entry */ + pad = ALIGN(ncb_len, div) + rem - ncb_len; + ncb_len += pad; + + if (ncb_len + skb->len + crc_len > max_size) { + dev_kfree_skb_any(skb); + return NULL; + } + + skb2 = skb_copy_expand(skb, ncb_len, + max_size - skb->len - ncb_len - crc_len, + GFP_ATOMIC); + dev_kfree_skb_any(skb); + if (!skb2) + return NULL; + + skb = skb2; + + tmp = (void *) skb_push(skb, ncb_len); + memset(tmp, 0, ncb_len); + + put_unaligned_le32(opts->nth_sign, tmp); /* dwSignature */ + tmp += 2; + /* wHeaderLength */ + put_unaligned_le16(opts->nth_size, tmp++); + tmp++; /* skip wSequence */ + put_ncm(&tmp, opts->block_length, skb->len); /* (d)wBlockLength */ + /* (d)wFpIndex */ + /* the first pointer is right after the NTH + align */ + put_ncm(&tmp, opts->fp_index, opts->nth_size + ndp_pad); + + tmp = (void *)tmp + ndp_pad; + + /* NDP */ + put_unaligned_le32(opts->ndp_sign, tmp); /* dwSignature */ + tmp += 2; + /* wLength */ + put_unaligned_le16(ncb_len - opts->nth_size - pad, tmp++); + + tmp += opts->reserved1; + tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */ + tmp += opts->reserved2; + + if (ncm->is_crc) { + uint32_t crc; + + crc = ~crc32_le(~0, + skb->data + ncb_len, + skb->len - ncb_len); + put_unaligned_le32(crc, skb->data + skb->len); + skb_put(skb, crc_len); + } + + /* (d)wDatagramIndex[0] */ + put_ncm(&tmp, opts->dgram_item_len, ncb_len); + /* (d)wDatagramLength[0] */ + put_ncm(&tmp, opts->dgram_item_len, skb->len - ncb_len); + /* (d)wDatagramIndex[1] and (d)wDatagramLength[1] already zeroed */ + + if (skb->len > MAX_TX_NONFIXED) + memset(skb_put(skb, max_size - skb->len), + 0, max_size - skb->len); + + return skb; +} + +static int ncm_unwrap_ntb(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + struct f_ncm *ncm = func_to_ncm(&port->func); + __le16 *tmp = (void *) skb->data; + unsigned index, index2; + unsigned dg_len, dg_len2; + unsigned ndp_len; + struct sk_buff *skb2; + int ret = -EINVAL; + unsigned max_size = le32_to_cpu(ntb_parameters.dwNtbOutMaxSize); + struct ndp_parser_opts *opts = ncm->parser_opts; + unsigned crc_len = ncm->is_crc ? sizeof(uint32_t) : 0; + int dgram_counter; + + /* dwSignature */ + if (get_unaligned_le32(tmp) != opts->nth_sign) { + INFO(port->func.config->cdev, "Wrong NTH SIGN, skblen %d\n", + skb->len); + print_hex_dump(KERN_INFO, "HEAD:", DUMP_PREFIX_ADDRESS, 32, 1, + skb->data, 32, false); + + goto err; + } + tmp += 2; + /* wHeaderLength */ + if (get_unaligned_le16(tmp++) != opts->nth_size) { + INFO(port->func.config->cdev, "Wrong NTB headersize\n"); + goto err; + } + tmp++; /* skip wSequence */ + + /* (d)wBlockLength */ + if (get_ncm(&tmp, opts->block_length) > max_size) { + INFO(port->func.config->cdev, "OUT size exceeded\n"); + goto err; + } + + index = get_ncm(&tmp, opts->fp_index); + /* NCM 3.2 */ + if (((index % 4) != 0) && (index < opts->nth_size)) { + INFO(port->func.config->cdev, "Bad index: %x\n", + index); + goto err; + } + + /* walk through NDP */ + tmp = ((void *)skb->data) + index; + if (get_unaligned_le32(tmp) != opts->ndp_sign) { + INFO(port->func.config->cdev, "Wrong NDP SIGN\n"); + goto err; + } + tmp += 2; + + ndp_len = get_unaligned_le16(tmp++); + /* + * NCM 3.3.1 + * entry is 2 items + * item size is 16/32 bits, opts->dgram_item_len * 2 bytes + * minimal: struct usb_cdc_ncm_ndpX + normal entry + zero entry + */ + if ((ndp_len < opts->ndp_size + 2 * 2 * (opts->dgram_item_len * 2)) + || (ndp_len % opts->ndplen_align != 0)) { + INFO(port->func.config->cdev, "Bad NDP length: %x\n", ndp_len); + goto err; + } + tmp += opts->reserved1; + tmp += opts->next_fp_index; /* skip reserved (d)wNextFpIndex */ + tmp += opts->reserved2; + + ndp_len -= opts->ndp_size; + index2 = get_ncm(&tmp, opts->dgram_item_len); + dg_len2 = get_ncm(&tmp, opts->dgram_item_len); + dgram_counter = 0; + + do { + index = index2; + dg_len = dg_len2; + if (dg_len < 14 + crc_len) { /* ethernet header + crc */ + INFO(port->func.config->cdev, "Bad dgram length: %x\n", + dg_len); + goto err; + } + if (ncm->is_crc) { + uint32_t crc, crc2; + + crc = get_unaligned_le32(skb->data + + index + dg_len - crc_len); + crc2 = ~crc32_le(~0, + skb->data + index, + dg_len - crc_len); + if (crc != crc2) { + INFO(port->func.config->cdev, "Bad CRC\n"); + goto err; + } + } + + index2 = get_ncm(&tmp, opts->dgram_item_len); + dg_len2 = get_ncm(&tmp, opts->dgram_item_len); + + if (index2 == 0 || dg_len2 == 0) { + skb2 = skb; + } else { + skb2 = skb_clone(skb, GFP_ATOMIC); + if (skb2 == NULL) + goto err; + } + + if (!skb_pull(skb2, index)) { + ret = -EOVERFLOW; + goto err; + } + + skb_trim(skb2, dg_len - crc_len); + skb_queue_tail(list, skb2); + + ndp_len -= 2 * (opts->dgram_item_len * 2); + + dgram_counter++; + + if (index2 == 0 || dg_len2 == 0) + break; + } while (ndp_len > 2 * (opts->dgram_item_len * 2)); /* zero entry */ + + VDBG(port->func.config->cdev, + "Parsed NTB with %d frames\n", dgram_counter); + return 0; +err: + skb_queue_purge(list); + dev_kfree_skb_any(skb); + return ret; +} + +static void ncm_disable(struct usb_function *f) +{ + struct f_ncm *ncm = func_to_ncm(f); + struct usb_composite_dev *cdev = f->config->cdev; + + DBG(cdev, "ncm deactivated\n"); + + if (ncm->port.in_ep->driver_data) + gether_disconnect(&ncm->port); + + if (ncm->notify->driver_data) { + usb_ep_disable(ncm->notify); + ncm->notify->driver_data = NULL; + ncm->notify_desc = NULL; + } +} + +/*-------------------------------------------------------------------------*/ + +/* + * Callbacks let us notify the host about connect/disconnect when the + * net device is opened or closed. + * + * For testing, note that link states on this side include both opened + * and closed variants of: + * + * - disconnected/unconfigured + * - configured but inactive (data alt 0) + * - configured and active (data alt 1) + * + * Each needs to be tested with unplug, rmmod, SET_CONFIGURATION, and + * SET_INTERFACE (altsetting). Remember also that "configured" doesn't + * imply the host is actually polling the notification endpoint, and + * likewise that "active" doesn't imply it's actually using the data + * endpoints for traffic. + */ + +static void ncm_open(struct gether *geth) +{ + struct f_ncm *ncm = func_to_ncm(&geth->func); + + DBG(ncm->port.func.config->cdev, "%s\n", __func__); + + spin_lock(&ncm->lock); + ncm->is_open = true; + ncm_notify(ncm); + spin_unlock(&ncm->lock); +} + +static void ncm_close(struct gether *geth) +{ + struct f_ncm *ncm = func_to_ncm(&geth->func); + + DBG(ncm->port.func.config->cdev, "%s\n", __func__); + + spin_lock(&ncm->lock); + ncm->is_open = false; + ncm_notify(ncm); + spin_unlock(&ncm->lock); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int __init +ncm_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_ncm *ncm = func_to_ncm(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ncm->ctrl_id = status; + ncm_iad_desc.bFirstInterface = status; + + ncm_control_intf.bInterfaceNumber = status; + ncm_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + ncm->data_id = status; + + ncm_data_nop_intf.bInterfaceNumber = status; + ncm_data_intf.bInterfaceNumber = status; + ncm_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_in_desc); + if (!ep) + goto fail; + ncm->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_out_desc); + if (!ep) + goto fail; + ncm->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_ncm_notify_desc); + if (!ep) + goto fail; + ncm->notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + ncm->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!ncm->notify_req) + goto fail; + ncm->notify_req->buf = kmalloc(NCM_STATUS_BYTECOUNT, GFP_KERNEL); + if (!ncm->notify_req->buf) + goto fail; + ncm->notify_req->context = ncm; + ncm->notify_req->complete = ncm_notify_complete; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(ncm_fs_function); + if (!f->descriptors) + goto fail; + + ncm->fs.in = usb_find_endpoint(ncm_fs_function, + f->descriptors, &fs_ncm_in_desc); + ncm->fs.out = usb_find_endpoint(ncm_fs_function, + f->descriptors, &fs_ncm_out_desc); + ncm->fs.notify = usb_find_endpoint(ncm_fs_function, + f->descriptors, &fs_ncm_notify_desc); + + /* + * support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + hs_ncm_in_desc.bEndpointAddress = + fs_ncm_in_desc.bEndpointAddress; + hs_ncm_out_desc.bEndpointAddress = + fs_ncm_out_desc.bEndpointAddress; + hs_ncm_notify_desc.bEndpointAddress = + fs_ncm_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(ncm_hs_function); + if (!f->hs_descriptors) + goto fail; + + ncm->hs.in = usb_find_endpoint(ncm_hs_function, + f->hs_descriptors, &hs_ncm_in_desc); + ncm->hs.out = usb_find_endpoint(ncm_hs_function, + f->hs_descriptors, &hs_ncm_out_desc); + ncm->hs.notify = usb_find_endpoint(ncm_hs_function, + f->hs_descriptors, &hs_ncm_notify_desc); + } + + /* + * NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + ncm->port.open = ncm_open; + ncm->port.close = ncm_close; + + DBG(cdev, "CDC Network: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + ncm->port.in_ep->name, ncm->port.out_ep->name, + ncm->notify->name); + return 0; + +fail: + if (f->descriptors) + usb_free_descriptors(f->descriptors); + + if (ncm->notify_req) { + kfree(ncm->notify_req->buf); + usb_ep_free_request(ncm->notify, ncm->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (ncm->notify) + ncm->notify->driver_data = NULL; + if (ncm->port.out) + ncm->port.out_ep->driver_data = NULL; + if (ncm->port.in) + ncm->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +ncm_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_ncm *ncm = func_to_ncm(f); + + DBG(c->cdev, "ncm unbind\n"); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + kfree(ncm->notify_req->buf); + usb_ep_free_request(ncm->notify, ncm->notify_req); + + ncm_string_defs[1].s = NULL; + kfree(ncm); +} + +/** + * ncm_bind_config - add CDC Network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int __init ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +{ + struct f_ncm *ncm; + int status; + + if (!can_support_ecm(c->cdev->gadget) || !ethaddr) + return -EINVAL; + + /* maybe allocate device-global string IDs */ + if (ncm_string_defs[0].id == 0) { + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ncm_string_defs[STRING_CTRL_IDX].id = status; + ncm_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ncm_string_defs[STRING_DATA_IDX].id = status; + ncm_data_nop_intf.iInterface = status; + ncm_data_intf.iInterface = status; + + /* MAC address */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ncm_string_defs[STRING_MAC_IDX].id = status; + ecm_desc.iMACAddress = status; + + /* IAD */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + ncm_string_defs[STRING_IAD_IDX].id = status; + ncm_iad_desc.iFunction = status; + } + + /* allocate and initialize one new instance */ + ncm = kzalloc(sizeof *ncm, GFP_KERNEL); + if (!ncm) + return -ENOMEM; + + /* export host's Ethernet address in CDC format */ + snprintf(ncm->ethaddr, sizeof ncm->ethaddr, + "%02X%02X%02X%02X%02X%02X", + ethaddr[0], ethaddr[1], ethaddr[2], + ethaddr[3], ethaddr[4], ethaddr[5]); + ncm_string_defs[1].s = ncm->ethaddr; + + spin_lock_init(&ncm->lock); + ncm_reset_values(ncm); + ncm->port.is_fixed = true; + + ncm->port.func.name = "cdc_network"; + ncm->port.func.strings = ncm_strings; + /* descriptors are per-instance copies */ + ncm->port.func.bind = ncm_bind; + ncm->port.func.unbind = ncm_unbind; + ncm->port.func.set_alt = ncm_set_alt; + ncm->port.func.get_alt = ncm_get_alt; + ncm->port.func.setup = ncm_setup; + ncm->port.func.disable = ncm_disable; + + ncm->port.wrap = ncm_wrap_ntb; + ncm->port.unwrap = ncm_unwrap_ntb; + + status = usb_add_function(c, &ncm->port.func); + if (status) { + ncm_string_defs[1].s = NULL; + kfree(ncm); + } + return status; +} diff --git a/drivers/usb/gadget/gadget_gbhc/f_rndis.c b/drivers/usb/gadget/gadget_gbhc/f_rndis.c new file mode 100644 index 0000000..64faf65 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/f_rndis.c @@ -0,0 +1,935 @@ +/* + * f_rndis.c -- RNDIS link function driver + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/etherdevice.h> +#include <linux/usb/android_composite.h> + +#include <asm/atomic.h> + +#include "u_ether.h" +#include "rndis.h" + + +/* + * This function is an RNDIS Ethernet port -- a Microsoft protocol that's + * been promoted instead of the standard CDC Ethernet. The published RNDIS + * spec is ambiguous, incomplete, and needlessly complex. Variants such as + * ActiveSync have even worse status in terms of specification. + * + * In short: it's a protocol controlled by (and for) Microsoft, not for an + * Open ecosystem or markets. Linux supports it *only* because Microsoft + * doesn't support the CDC Ethernet standard. + * + * The RNDIS data transfer model is complex, with multiple Ethernet packets + * per USB message, and out of band data. The control model is built around + * what's essentially an "RNDIS RPC" protocol. It's all wrapped in a CDC ACM + * (modem, not Ethernet) veneer, with those ACM descriptors being entirely + * useless (they're ignored). RNDIS expects to be the only function in its + * configuration, so it's no real help if you need composite devices; and + * it expects to be the first configuration too. + * + * There is a single technical advantage of RNDIS over CDC Ethernet, if you + * discount the fluff that its RPC can be made to deliver: it doesn't need + * a NOP altsetting for the data interface. That lets it work on some of the + * "so smart it's stupid" hardware which takes over configuration changes + * from the software, and adds restrictions like "no altsettings". + * + * Unfortunately MSFT's RNDIS drivers are buggy. They hang or oops, and + * have all sorts of contrary-to-specification oddities that can prevent + * them from working sanely. Since bugfixes (or accurate specs, letting + * Linux work around those bugs) are unlikely to ever come from MSFT, you + * may want to avoid using RNDIS on purely operational grounds. + * + * Omissions from the RNDIS 1.0 specification include: + * + * - Power management ... references data that's scattered around lots + * of other documentation, which is incorrect/incomplete there too. + * + * - There are various undocumented protocol requirements, like the need + * to send garbage in some control-OUT messages. + * + * - MS-Windows drivers sometimes emit undocumented requests. + */ + +struct rndis_ep_descs { + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + struct usb_endpoint_descriptor *notify; +}; + +struct f_rndis { + struct gether port; + u8 ctrl_id, data_id; + u8 ethaddr[ETH_ALEN]; + int config; + + struct rndis_ep_descs fs; + struct rndis_ep_descs hs; + + struct usb_ep *notify; + struct usb_endpoint_descriptor *notify_desc; + struct usb_request *notify_req; + atomic_t notify_count; +}; + +static inline struct f_rndis *func_to_rndis(struct usb_function *f) +{ + return container_of(f, struct f_rndis, port.func); +} + +/* peak (theoretical) bulk transfer rate in bits-per-second */ +static unsigned int bitrate(struct usb_gadget *g) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return 13 * 512 * 8 * 1000 * 8; + else + return 19 * 64 * 1 * 1000 * 8; +} + +/*-------------------------------------------------------------------------*/ + +/* + */ + +#define LOG2_STATUS_INTERVAL_MSEC 5 /* 1 << 5 == 32 msec */ +#define STATUS_BYTECOUNT 8 /* 8 bytes data */ + + +/* interface descriptor: */ + +static struct usb_interface_descriptor rndis_control_intf = { + .bLength = sizeof rndis_control_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + /* status endpoint is optional; this could be patched later */ + .bNumEndpoints = 1, +#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS + /* "Wireless" RNDIS; auto-detected by Windows */ + .bInterfaceClass = USB_CLASS_WIRELESS_CONTROLLER, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x03, +#else + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = USB_CDC_SUBCLASS_ACM, + .bInterfaceProtocol = USB_CDC_ACM_PROTO_VENDOR, +#endif + /* .iInterface = DYNAMIC */ +}; + +static struct usb_cdc_header_desc header_desc = { + .bLength = sizeof header_desc, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_HEADER_TYPE, + + .bcdCDC = cpu_to_le16(0x0110), +}; + +static struct usb_cdc_call_mgmt_descriptor call_mgmt_descriptor = { + .bLength = sizeof call_mgmt_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_CALL_MANAGEMENT_TYPE, + + .bmCapabilities = 0x00, + .bDataInterface = 0x01, +}; + +static struct usb_cdc_acm_descriptor rndis_acm_descriptor = { + .bLength = sizeof rndis_acm_descriptor, + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_ACM_TYPE, + + .bmCapabilities = 0x00, +}; + +static struct usb_cdc_union_desc rndis_union_desc = { + .bLength = sizeof(rndis_union_desc), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubType = USB_CDC_UNION_TYPE, + /* .bMasterInterface0 = DYNAMIC */ + /* .bSlaveInterface0 = DYNAMIC */ +}; + +/* the data interface has two bulk endpoints */ + +static struct usb_interface_descriptor rndis_data_intf = { + .bLength = sizeof rndis_data_intf, + .bDescriptorType = USB_DT_INTERFACE, + + /* .bInterfaceNumber = DYNAMIC */ + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_CDC_DATA, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + /* .iInterface = DYNAMIC */ +}; + + +static struct usb_interface_assoc_descriptor +rndis_iad_descriptor = { + .bLength = sizeof rndis_iad_descriptor, + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + + .bFirstInterface = 0, /* XXX, hardcoded */ + .bInterfaceCount = 2, // control + data +#ifdef CONFIG_USB_ANDROID_RNDIS_WCEIS + /* "Wireless" RNDIS; auto-detected by Windows */ + .bFunctionClass = USB_CLASS_WIRELESS_CONTROLLER, + .bFunctionSubClass = 0x01, + .bFunctionProtocol = 0x03, +#else + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = USB_CDC_SUBCLASS_ETHERNET, + .bFunctionProtocol = USB_CDC_ACM_PROTO_VENDOR, +#endif + /* .iFunction = DYNAMIC */ +}; + +/* full speed support: */ + +static struct usb_endpoint_descriptor fs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = 1 << LOG2_STATUS_INTERVAL_MSEC, +}; + +static struct usb_endpoint_descriptor fs_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_endpoint_descriptor fs_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, +}; + +static struct usb_descriptor_header *eth_fs_function[] = { + (struct usb_descriptor_header *) &rndis_iad_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_acm_descriptor, + (struct usb_descriptor_header *) &rndis_union_desc, + (struct usb_descriptor_header *) &fs_notify_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &fs_in_desc, + (struct usb_descriptor_header *) &fs_out_desc, + NULL, +}; + +/* high speed support: */ + +static struct usb_endpoint_descriptor hs_notify_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(STATUS_BYTECOUNT), + .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, +}; +static struct usb_endpoint_descriptor 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 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 *eth_hs_function[] = { + (struct usb_descriptor_header *) &rndis_iad_descriptor, + /* control interface matches ACM, not Ethernet */ + (struct usb_descriptor_header *) &rndis_control_intf, + (struct usb_descriptor_header *) &header_desc, + (struct usb_descriptor_header *) &call_mgmt_descriptor, + (struct usb_descriptor_header *) &rndis_acm_descriptor, + (struct usb_descriptor_header *) &rndis_union_desc, + (struct usb_descriptor_header *) &hs_notify_desc, + /* data interface has no altsetting */ + (struct usb_descriptor_header *) &rndis_data_intf, + (struct usb_descriptor_header *) &hs_in_desc, + (struct usb_descriptor_header *) &hs_out_desc, + NULL, +}; + +/* string descriptors: */ + +static struct usb_string rndis_string_defs[] = { + [0].s = "RNDIS Communications Control", + [1].s = "RNDIS Ethernet Data", + [2].s = "RNDIS", + { } /* end of list */ +}; + +static struct usb_gadget_strings rndis_string_table = { + .language = 0x0409, /* en-us */ + .strings = rndis_string_defs, +}; + +static struct usb_gadget_strings *rndis_strings[] = { + &rndis_string_table, + NULL, +}; + +#ifdef CONFIG_USB_ANDROID_RNDIS +static struct usb_ether_platform_data *rndis_pdata; +#endif + +/*-------------------------------------------------------------------------*/ + +static struct sk_buff *rndis_add_header(struct gether *port, + struct sk_buff *skb) +{ + struct sk_buff *skb2; + + skb2 = skb_realloc_headroom(skb, sizeof(struct rndis_packet_msg_type)); + if (skb2) + rndis_add_hdr(skb2); + + dev_kfree_skb_any(skb); + return skb2; +} + +static void rndis_response_available(void *_rndis) +{ + struct f_rndis *rndis = _rndis; + struct usb_request *req = rndis->notify_req; + struct usb_composite_dev *cdev = rndis->port.func.config->cdev; + __le32 *data = req->buf; + int status; + + if (atomic_inc_return(&rndis->notify_count) != 1) + return; + + /* Send RNDIS RESPONSE_AVAILABLE notification; a + * USB_CDC_NOTIFY_RESPONSE_AVAILABLE "should" work too + * + * This is the only notification defined by RNDIS. + */ + data[0] = cpu_to_le32(1); + data[1] = cpu_to_le32(0); + + status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&rndis->notify_count); + DBG(cdev, "notify/0 --> %d\n", status); + } +} + +static void rndis_response_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rndis *rndis = req->context; + struct usb_composite_dev *cdev = rndis->port.func.config->cdev; + int status = req->status; + + /* after TX: + * - USB_CDC_GET_ENCAPSULATED_RESPONSE (ep0/control) + * - RNDIS_RESPONSE_AVAILABLE (status/irq) + */ + switch (status) { + case -ECONNRESET: + case -ESHUTDOWN: + /* connection gone */ + atomic_set(&rndis->notify_count, 0); + break; + default: + DBG(cdev, "RNDIS %s response error %d, %d/%d\n", + ep->name, status, + req->actual, req->length); + /* FALLTHROUGH */ + case 0: + if (ep != rndis->notify) + break; + + /* handle multiple pending RNDIS_RESPONSE_AVAILABLE + * notifications by resending until we're done + */ + if (atomic_dec_and_test(&rndis->notify_count)) + break; + status = usb_ep_queue(rndis->notify, req, GFP_ATOMIC); + if (status) { + atomic_dec(&rndis->notify_count); + DBG(cdev, "notify/1 --> %d\n", status); + } + break; + } +} + +static void rndis_command_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct f_rndis *rndis = req->context; + struct usb_composite_dev *cdev = rndis->port.func.config->cdev; + int status; + + /* received RNDIS command from USB_CDC_SEND_ENCAPSULATED_COMMAND */ +// spin_lock(&dev->lock); + status = rndis_msg_parser(rndis->config, (u8 *) req->buf); + if (status < 0) + ERROR(cdev, "RNDIS command error %d, %d/%d\n", + status, req->actual, req->length); +// spin_unlock(&dev->lock); +} + +static int +rndis_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int value = -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); + + /* composite driver infrastructure handles everything except + * CDC class messages; interface activation uses set_alt(). + */ + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + + /* RNDIS uses the CDC command encapsulation mechanism to implement + * an RPC scheme, with much getting/setting of attributes by OID. + */ + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_SEND_ENCAPSULATED_COMMAND: + if (w_value || w_index != rndis->ctrl_id) + goto invalid; + /* read the request; process it later */ + value = w_length; + req->complete = rndis_command_complete; + req->context = rndis; + /* later, rndis_response_available() sends a notification */ + break; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8) + | USB_CDC_GET_ENCAPSULATED_RESPONSE: + if (w_value || w_index != rndis->ctrl_id) + goto invalid; + else { + u8 *buf; + u32 n; + + /* return the result */ + buf = rndis_get_next_response(rndis->config, &n); + if (buf) { + memcpy(req->buf, buf, n); + req->complete = rndis_response_complete; + rndis_free_response(rndis->config, buf); + value = n; + } + /* else stalls ... spec says to avoid that */ + } + break; + + default: +invalid: + VDBG(cdev, "invalid control req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + } + + /* respond with data transfer or status phase? */ + if (value >= 0) { + DBG(cdev, "rndis req%02x.%02x v%04x i%04x l%d\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, w_length); + req->zero = (value < w_length); + req->length = value; + value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); + if (value < 0) + ERROR(cdev, "rndis response on err %d\n", value); + } + + /* device either stalls (value < 0) or reports success */ + return value; +} + + +static int rndis_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct usb_composite_dev *cdev = f->config->cdev; + + /* we know alt == 0 */ + + if (intf == rndis->ctrl_id) { + if (rndis->notify->driver_data) { + VDBG(cdev, "reset rndis control %d\n", intf); + usb_ep_disable(rndis->notify); + } else { + VDBG(cdev, "init rndis ctrl %d\n", intf); + } + rndis->notify_desc = ep_choose(cdev->gadget, + rndis->hs.notify, + rndis->fs.notify); + usb_ep_enable(rndis->notify, rndis->notify_desc); + rndis->notify->driver_data = rndis; + + } else if (intf == rndis->data_id) { + struct net_device *net; + + if (rndis->port.in_ep->driver_data) { + DBG(cdev, "reset rndis\n"); + gether_disconnect(&rndis->port); + } + + if (!rndis->port.in) { + DBG(cdev, "init rndis\n"); + } + rndis->port.in = ep_choose(cdev->gadget, + rndis->hs.in, rndis->fs.in); + rndis->port.out = ep_choose(cdev->gadget, + rndis->hs.out, rndis->fs.out); + + /* Avoid ZLPs; they can be troublesome. */ + rndis->port.is_zlp_ok = false; + + /* RNDIS should be in the "RNDIS uninitialized" state, + * either never activated or after rndis_uninit(). + * + * We don't want data to flow here until a nonzero packet + * filter is set, at which point it enters "RNDIS data + * initialized" state ... but we do want the endpoints + * to be activated. It's a strange little state. + * + * REVISIT the RNDIS gadget code has done this wrong for a + * very long time. We need another call to the link layer + * code -- gether_updown(...bool) maybe -- to do it right. + */ + rndis->port.cdc_filter = 0; + + DBG(cdev, "RNDIS RX/TX early activation ... \n"); + net = gether_connect(&rndis->port); + if (IS_ERR(net)) + return PTR_ERR(net); + + rndis_set_param_dev(rndis->config, net, + &rndis->port.cdc_filter); + } else + goto fail; + + return 0; +fail: + return -EINVAL; +} + +static void rndis_disable(struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + struct usb_composite_dev *cdev = f->config->cdev; + + if (!rndis->notify->driver_data) + return; + + DBG(cdev, "rndis deactivated\n"); + + rndis_uninit(rndis->config); + gether_disconnect(&rndis->port); + + usb_ep_disable(rndis->notify); + rndis->notify->driver_data = NULL; +} + +/*-------------------------------------------------------------------------*/ + +/* + * This isn't quite the same mechanism as CDC Ethernet, since the + * notification scheme passes less data, but the same set of link + * states must be tested. A key difference is that altsettings are + * not used to tell whether the link should send packets or not. + */ + +static void rndis_open(struct gether *geth) +{ + struct f_rndis *rndis = func_to_rndis(&geth->func); + struct usb_composite_dev *cdev = geth->func.config->cdev; + + DBG(cdev, "%s\n", __func__); + + rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, + bitrate(cdev->gadget) / 100); + rndis_signal_connect(rndis->config); +} + +static void rndis_close(struct gether *geth) +{ + struct f_rndis *rndis = func_to_rndis(&geth->func); + + DBG(geth->func.config->cdev, "%s\n", __func__); + + rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0); + rndis_signal_disconnect(rndis->config); +} + +/*-------------------------------------------------------------------------*/ + +/* ethernet function driver setup/binding */ + +static int +rndis_bind(struct usb_configuration *c, struct usb_function *f) +{ + struct usb_composite_dev *cdev = c->cdev; + struct f_rndis *rndis = func_to_rndis(f); + int status; + struct usb_ep *ep; + + /* allocate instance-specific interface IDs */ + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + rndis->ctrl_id = status; + rndis_iad_descriptor.bFirstInterface = status; + + rndis_control_intf.bInterfaceNumber = status; + rndis_union_desc.bMasterInterface0 = status; + + status = usb_interface_id(c, f); + if (status < 0) + goto fail; + rndis->data_id = status; + + rndis_data_intf.bInterfaceNumber = status; + rndis_union_desc.bSlaveInterface0 = status; + + status = -ENODEV; + + /* allocate instance-specific endpoints */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_in_desc); + if (!ep) + goto fail; + rndis->port.in_ep = ep; + ep->driver_data = cdev; /* claim */ + + ep = usb_ep_autoconfig(cdev->gadget, &fs_out_desc); + if (!ep) + goto fail; + rndis->port.out_ep = ep; + ep->driver_data = cdev; /* claim */ + + /* NOTE: a status/notification endpoint is, strictly speaking, + * optional. We don't treat it that way though! It's simpler, + * and some newer profiles don't treat it as optional. + */ + ep = usb_ep_autoconfig(cdev->gadget, &fs_notify_desc); + if (!ep) + goto fail; + rndis->notify = ep; + ep->driver_data = cdev; /* claim */ + + status = -ENOMEM; + + /* allocate notification request and buffer */ + rndis->notify_req = usb_ep_alloc_request(ep, GFP_KERNEL); + if (!rndis->notify_req) + goto fail; + rndis->notify_req->buf = kmalloc(STATUS_BYTECOUNT, GFP_KERNEL); + if (!rndis->notify_req->buf) + goto fail; + rndis->notify_req->length = STATUS_BYTECOUNT; + rndis->notify_req->context = rndis; + rndis->notify_req->complete = rndis_response_complete; + + /* copy descriptors, and track endpoint copies */ + f->descriptors = usb_copy_descriptors(eth_fs_function); + if (!f->descriptors) + goto fail; + + rndis->fs.in = usb_find_endpoint(eth_fs_function, + f->descriptors, &fs_in_desc); + rndis->fs.out = usb_find_endpoint(eth_fs_function, + f->descriptors, &fs_out_desc); + rndis->fs.notify = usb_find_endpoint(eth_fs_function, + f->descriptors, &fs_notify_desc); + + /* support all relevant hardware speeds... we expect that when + * hardware is dual speed, all bulk-capable endpoints work at + * both speeds + */ + if (gadget_is_dualspeed(c->cdev->gadget)) { + hs_in_desc.bEndpointAddress = + fs_in_desc.bEndpointAddress; + hs_out_desc.bEndpointAddress = + fs_out_desc.bEndpointAddress; + hs_notify_desc.bEndpointAddress = + fs_notify_desc.bEndpointAddress; + + /* copy descriptors, and track endpoint copies */ + f->hs_descriptors = usb_copy_descriptors(eth_hs_function); + + if (!f->hs_descriptors) + goto fail; + + rndis->hs.in = usb_find_endpoint(eth_hs_function, + f->hs_descriptors, &hs_in_desc); + rndis->hs.out = usb_find_endpoint(eth_hs_function, + f->hs_descriptors, &hs_out_desc); + rndis->hs.notify = usb_find_endpoint(eth_hs_function, + f->hs_descriptors, &hs_notify_desc); + } + + rndis->port.open = rndis_open; + rndis->port.close = rndis_close; + + status = rndis_register(rndis_response_available, rndis); + if (status < 0) + goto fail; + rndis->config = status; + + rndis_set_param_medium(rndis->config, NDIS_MEDIUM_802_3, 0); + rndis_set_host_mac(rndis->config, rndis->ethaddr); + +#ifdef CONFIG_USB_ANDROID_RNDIS + if (rndis_pdata) { + if (rndis_set_param_vendor(rndis->config, rndis_pdata->vendorID, + rndis_pdata->vendorDescr)) + goto fail; + } +#endif + + /* NOTE: all that is done without knowing or caring about + * the network link ... which is unavailable to this code + * until we're activated via set_alt(). + */ + + DBG(cdev, "RNDIS: %s speed IN/%s OUT/%s NOTIFY/%s\n", + gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full", + rndis->port.in_ep->name, rndis->port.out_ep->name, + rndis->notify->name); + return 0; + +fail: + if (gadget_is_dualspeed(c->cdev->gadget) && f->hs_descriptors) + usb_free_descriptors(f->hs_descriptors); + if (f->descriptors) + usb_free_descriptors(f->descriptors); + + if (rndis->notify_req) { + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + } + + /* we might as well release our claims on endpoints */ + if (rndis->notify) + rndis->notify->driver_data = NULL; + if (rndis->port.out) + rndis->port.out_ep->driver_data = NULL; + if (rndis->port.in) + rndis->port.in_ep->driver_data = NULL; + + ERROR(cdev, "%s: can't bind, err %d\n", f->name, status); + + return status; +} + +static void +rndis_unbind(struct usb_configuration *c, struct usb_function *f) +{ + struct f_rndis *rndis = func_to_rndis(f); + + rndis_deregister(rndis->config); + rndis_exit(); + + if (gadget_is_dualspeed(c->cdev->gadget)) + usb_free_descriptors(f->hs_descriptors); + usb_free_descriptors(f->descriptors); + + kfree(rndis->notify_req->buf); + usb_ep_free_request(rndis->notify, rndis->notify_req); + + kfree(rndis); +} + +/* Some controllers can't support RNDIS ... */ +static inline bool can_support_rndis(struct usb_configuration *c) +{ + /* everything else is *presumably* fine */ + return true; +} + +/** + * rndis_bind_config - add RNDIS network link to a configuration + * @c: the configuration to support the network link + * @ethaddr: a buffer in which the ethernet address of the host side + * side of the link was recorded + * Context: single threaded during gadget setup + * + * Returns zero on success, else negative errno. + * + * Caller must have called @gether_setup(). Caller is also responsible + * for calling @gether_cleanup() before module unload. + */ +int +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +{ + struct f_rndis *rndis; + int status; + + if (!can_support_rndis(c) || !ethaddr) + return -EINVAL; + + /* maybe allocate device-global string IDs */ + if (rndis_string_defs[0].id == 0) { + + /* ... and setup RNDIS itself */ + status = rndis_init(); + if (status < 0) + return status; + + /* control interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + rndis_string_defs[0].id = status; + rndis_control_intf.iInterface = status; + + /* data interface label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + rndis_string_defs[1].id = status; + rndis_data_intf.iInterface = status; + + /* IAD iFunction label */ + status = usb_string_id(c->cdev); + if (status < 0) + return status; + rndis_string_defs[2].id = status; + rndis_iad_descriptor.iFunction = status; + } + + /* allocate and initialize one new instance */ + status = -ENOMEM; + rndis = kzalloc(sizeof *rndis, GFP_KERNEL); + if (!rndis) + goto fail; + + memcpy(rndis->ethaddr, ethaddr, ETH_ALEN); + + /* RNDIS activates when the host changes this filter */ + rndis->port.cdc_filter = 0; + + /* RNDIS has special (and complex) framing */ + rndis->port.header_len = sizeof(struct rndis_packet_msg_type); + rndis->port.wrap = rndis_add_header; + rndis->port.unwrap = rndis_rm_hdr; + + rndis->port.func.name = "rndis"; + rndis->port.func.strings = rndis_strings; + /* descriptors are per-instance copies */ + rndis->port.func.bind = rndis_bind; + rndis->port.func.unbind = rndis_unbind; + rndis->port.func.set_alt = rndis_set_alt; + rndis->port.func.setup = rndis_setup; + rndis->port.func.disable = rndis_disable; + +#ifdef CONFIG_USB_ANDROID_RNDIS + /* start disabled */ + rndis->port.func.disabled = 1; +#endif + + status = usb_add_function(c, &rndis->port.func); + if (status) { + kfree(rndis); +fail: + rndis_exit(); + } + return status; +} + +#ifdef CONFIG_USB_ANDROID_RNDIS +#include "rndis.c" + +static int rndis_probe(struct platform_device *pdev) +{ + rndis_pdata = pdev->dev.platform_data; + return 0; +} + +static struct platform_driver rndis_platform_driver = { + .driver = { .name = "rndis", }, + .probe = rndis_probe, +}; + +int rndis_function_bind_config(struct usb_configuration *c) +{ + int ret; + + if (!rndis_pdata) { + printk(KERN_ERR "rndis_pdata null in rndis_function_bind_config\n"); + return -1; + } + + printk(KERN_INFO + "rndis_function_bind_config MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", + rndis_pdata->ethaddr[0], rndis_pdata->ethaddr[1], + rndis_pdata->ethaddr[2], rndis_pdata->ethaddr[3], + rndis_pdata->ethaddr[4], rndis_pdata->ethaddr[5]); + + ret = gether_setup(c->cdev->gadget, rndis_pdata->ethaddr); + if (ret == 0) + ret = rndis_bind_config(c, rndis_pdata->ethaddr); + return ret; +} + +static struct android_usb_function rndis_function = { + .name = "rndis", + .bind_config = rndis_function_bind_config, +}; + +static int __init init(void) +{ + printk(KERN_INFO "f_rndis init\n"); + platform_driver_register(&rndis_platform_driver); + android_register_function(&rndis_function); + return 0; +} +module_init(init); + +#endif /* CONFIG_USB_ANDROID_RNDIS */ diff --git a/drivers/usb/gadget/gadget_gbhc/file_storage.c b/drivers/usb/gadget/gadget_gbhc/file_storage.c new file mode 100644 index 0000000..0360f56 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/file_storage.c @@ -0,0 +1,3626 @@ +/* + * file_storage.c -- File-backed USB Storage Gadget, for USB development + * + * Copyright (C) 2003-2008 Alan Stern + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The names of the above-listed copyright holders may not be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") as published by the Free Software + * Foundation, either version 2 of that License or (at your option) any + * later version. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +/* + * The File-backed Storage Gadget acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In addition + * to providing an example of a genuinely useful gadget driver for a USB + * device, it also illustrates a technique of double-buffering for increased + * throughput. Last but not least, it gives an easy way to probe the + * behavior of the Mass Storage drivers in a USB host. + * + * Backing storage is provided by a regular file or a block device, specified + * by the "file" module parameter. Access can be limited to read-only by + * setting the optional "ro" module parameter. (For CD-ROM emulation, + * access is always read-only.) The gadget will indicate that it has + * removable media if the optional "removable" module parameter is set. + * + * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI), + * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected + * by the optional "transport" module parameter. It also supports the + * following protocols: RBC (0x01), ATAPI or SFF-8020i (0x02), QIC-157 (0c03), + * UFI (0x04), SFF-8070i (0x05), and transparent SCSI (0x06), selected by + * the optional "protocol" module parameter. In addition, the default + * Vendor ID, Product ID, release number and serial number can be overridden. + * + * There is support for multiple logical units (LUNs), each of which has + * its own backing file. The number of LUNs can be set using the optional + * "luns" module parameter (anywhere from 1 to 8), and the corresponding + * files are specified using comma-separated lists for "file" and "ro". + * The default number of LUNs is taken from the number of "file" elements; + * it is 1 if "file" is not given. If "removable" is not set then a backing + * file must be specified for each LUN. If it is set, then an unspecified + * or empty backing filename means the LUN's medium is not loaded. Ideally + * each LUN would be settable independently as a disk drive or a CD-ROM + * drive, but currently all LUNs have to be the same type. The CD-ROM + * emulation includes a single data track and no audio tracks; hence there + * need be only one backing file per LUN. Note also that the CD-ROM block + * length is set to 512 rather than the more common value 2048. + * + * Requirements are modest; only a bulk-in and a bulk-out endpoint are + * needed (an interrupt-out endpoint is also needed for CBI). The memory + * requirement amounts to two 16K buffers, size configurable by a parameter. + * Support is included for both full-speed and high-speed operation. + * + * Note that the driver is slightly non-portable in that it assumes a + * single memory/DMA buffer will be useable for bulk-in, bulk-out, and + * interrupt-in endpoints. With most device controllers this isn't an + * issue, but there may be some with hardware restrictions that prevent + * a buffer from being used by more than one endpoint. + * + * Module options: + * + * file=filename[,filename...] + * Required if "removable" is not set, names of + * the files or block devices used for + * backing storage + * serial=HHHH... Required serial number (string of hex chars) + * ro=b[,b...] Default false, booleans for read-only access + * removable Default false, boolean for removable media + * luns=N Default N = number of filenames, number of + * LUNs to support + * nofua=b[,b...] Default false, booleans for ignore FUA flag + * in SCSI WRITE(10,12) commands + * stall Default determined according to the type of + * USB device controller (usually true), + * boolean to permit the driver to halt + * bulk endpoints + * cdrom Default false, boolean for whether to emulate + * a CD-ROM drive + * transport=XXX Default BBB, transport name (CB, CBI, or BBB) + * protocol=YYY Default SCSI, protocol name (RBC, 8020 or + * ATAPI, QIC, UFI, 8070, or SCSI; + * also 1 - 6) + * vendor=0xVVVV Default 0x0525 (NetChip), USB Vendor ID + * product=0xPPPP Default 0xa4a5 (FSG), USB Product ID + * release=0xRRRR Override the USB release number (bcdDevice) + * buflen=N Default N=16384, buffer size used (will be + * rounded down to a multiple of + * PAGE_CACHE_SIZE) + * + * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file", "serial", "ro", + * "removable", "luns", "nofua", "stall", and "cdrom" options are available; + * default values are used for everything else. + * + * The pathnames of the backing files and the ro settings are available in + * the attribute files "file", "nofua", and "ro" in the lun<n> subdirectory of + * the gadget's sysfs directory. If the "removable" option is set, writing to + * these files will simulate ejecting/loading the medium (writing an empty + * line means eject) and adjusting a write-enable tab. Changes to the ro + * setting are not allowed when the medium is loaded or if CD-ROM emulation + * is being used. + * + * This gadget driver is heavily based on "Gadget Zero" by David Brownell. + * The driver's SCSI command interface was based on the "Information + * technology - Small Computer System Interface - 2" document from + * X3T9.2 Project 375D, Revision 10L, 7-SEP-93, available at + * <http://www.t10.org/ftp/t10/drafts/s2/s2-r10l.pdf>. The single exception + * is opcode 0x23 (READ FORMAT CAPACITIES), which was based on the + * "Universal Serial Bus Mass Storage Class UFI Command Specification" + * document, Revision 1.0, December 14, 1998, available at + * <http://www.usb.org/developers/devclass_docs/usbmass-ufi10.pdf>. + */ + + +/* + * Driver Design + * + * The FSG driver is fairly straightforward. There is a main kernel + * thread that handles most of the work. Interrupt routines field + * callbacks from the controller driver: bulk- and interrupt-request + * completion notifications, endpoint-0 events, and disconnect events. + * Completion events are passed to the main thread by wakeup calls. Many + * ep0 requests are handled at interrupt time, but SetInterface, + * SetConfiguration, and device reset requests are forwarded to the + * thread in the form of "exceptions" using SIGUSR1 signals (since they + * should interrupt any ongoing file I/O operations). + * + * The thread's main routine implements the standard command/data/status + * parts of a SCSI interaction. It and its subroutines are full of tests + * for pending signals/exceptions -- all this polling is necessary since + * the kernel has no setjmp/longjmp equivalents. (Maybe this is an + * indication that the driver really wants to be running in userspace.) + * An important point is that so long as the thread is alive it keeps an + * open reference to the backing file. This will prevent unmounting + * the backing file's underlying filesystem and could cause problems + * during system shutdown, for example. To prevent such problems, the + * thread catches INT, TERM, and KILL signals and converts them into + * an EXIT exception. + * + * In normal operation the main thread is started during the gadget's + * fsg_bind() callback and stopped during fsg_unbind(). But it can also + * exit when it receives a signal, and there's no point leaving the + * gadget running when the thread is dead. So just before the thread + * exits, it deregisters the gadget driver. This makes things a little + * tricky: The driver is deregistered at two places, and the exiting + * thread can indirectly call fsg_unbind() which in turn can tell the + * thread to exit. The first problem is resolved through the use of the + * REGISTERED atomic bitflag; the driver will only be deregistered once. + * The second problem is resolved by having fsg_unbind() check + * fsg->state; it won't try to stop the thread if the state is already + * FSG_STATE_TERMINATED. + * + * To provide maximum throughput, the driver uses a circular pipeline of + * buffer heads (struct fsg_buffhd). In principle the pipeline can be + * arbitrarily long; in practice the benefits don't justify having more + * than 2 stages (i.e., double buffering). But it helps to think of the + * pipeline as being a long one. Each buffer head contains a bulk-in and + * a bulk-out request pointer (since the buffer can be used for both + * output and input -- directions always are given from the host's + * point of view) as well as a pointer to the buffer and various state + * variables. + * + * Use of the pipeline follows a simple protocol. There is a variable + * (fsg->next_buffhd_to_fill) that points to the next buffer head to use. + * At any time that buffer head may still be in use from an earlier + * request, so each buffer head has a state variable indicating whether + * it is EMPTY, FULL, or BUSY. Typical use involves waiting for the + * buffer head to be EMPTY, filling the buffer either by file I/O or by + * USB I/O (during which the buffer head is BUSY), and marking the buffer + * head FULL when the I/O is complete. Then the buffer will be emptied + * (again possibly by USB I/O, during which it is marked BUSY) and + * finally marked EMPTY again (possibly by a completion routine). + * + * A module parameter tells the driver to avoid stalling the bulk + * endpoints wherever the transport specification allows. This is + * necessary for some UDCs like the SuperH, which cannot reliably clear a + * halt on a bulk endpoint. However, under certain circumstances the + * Bulk-only specification requires a stall. In such cases the driver + * will halt the endpoint and set a flag indicating that it should clear + * the halt in software during the next device reset. Hopefully this + * will permit everything to work correctly. Furthermore, although the + * specification allows the bulk-out endpoint to halt when the host sends + * too much data, implementing this would cause an unavoidable race. + * The driver will always use the "no-stall" approach for OUT transfers. + * + * One subtle point concerns sending status-stage responses for ep0 + * requests. Some of these requests, such as device reset, can involve + * interrupting an ongoing file I/O operation, which might take an + * arbitrarily long time. During that delay the host might give up on + * the original ep0 request and issue a new one. When that happens the + * driver should not notify the host about completion of the original + * request, as the host will no longer be waiting for it. So the driver + * assigns to each ep0 request a unique tag, and it keeps track of the + * tag value of the request associated with a long-running exception + * (device-reset, interface-change, or configuration-change). When the + * exception handler is finished, the status-stage response is submitted + * only if the current ep0 request tag is equal to the exception request + * tag. Thus only the most recently received ep0 request will get a + * status-stage response. + * + * Warning: This driver source file is too long. It ought to be split up + * into a header file plus about 3 separate .c files, to handle the details + * of the Gadget, USB Mass Storage, and SCSI protocols. + */ + + +/* #define VERBOSE_DEBUG */ +/* #define DUMP_MSGS */ + + +#include <linux/blkdev.h> +#include <linux/completion.h> +#include <linux/dcache.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/fcntl.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/kref.h> +#include <linux/kthread.h> +#include <linux/limits.h> +#include <linux/rwsem.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/string.h> +#include <linux/freezer.h> +#include <linux/utsname.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include "gadget_chips.h" + + + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_DESC "File-backed Storage Gadget" +#define DRIVER_NAME "g_file_storage" +#define DRIVER_VERSION "1 September 2010" + +static char fsg_string_manufacturer[64]; +static const char fsg_string_product[] = DRIVER_DESC; +static const char fsg_string_config[] = "Self-powered"; +static const char fsg_string_interface[] = "Mass Storage"; + + +#include "storage_common.c" + + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Alan Stern"); +MODULE_LICENSE("Dual BSD/GPL"); + +/* + * This driver assumes self-powered hardware and has no way for users to + * trigger remote wakeup. It uses autoconfiguration to select endpoints + * and endpoint addresses. + */ + + +/*-------------------------------------------------------------------------*/ + + +/* Encapsulate the module parameter settings */ + +static struct { + char *file[FSG_MAX_LUNS]; + char *serial; + int ro[FSG_MAX_LUNS]; + int nofua[FSG_MAX_LUNS]; + unsigned int num_filenames; + unsigned int num_ros; + unsigned int num_nofuas; + unsigned int nluns; + + int removable; + int can_stall; + int cdrom; + + char *transport_parm; + char *protocol_parm; + unsigned short vendor; + unsigned short product; + unsigned short release; + unsigned int buflen; + + int transport_type; + char *transport_name; + int protocol_type; + char *protocol_name; + +} mod_data = { // Default values + .transport_parm = "BBB", + .protocol_parm = "SCSI", + .removable = 0, + .can_stall = 1, + .cdrom = 0, + .vendor = FSG_VENDOR_ID, + .product = FSG_PRODUCT_ID, + .release = 0xffff, // Use controller chip type + .buflen = 16384, + }; + + +module_param_array_named(file, mod_data.file, charp, &mod_data.num_filenames, + S_IRUGO); +MODULE_PARM_DESC(file, "names of backing files or devices"); + +module_param_named(serial, mod_data.serial, charp, S_IRUGO); +MODULE_PARM_DESC(serial, "USB serial number"); + +module_param_array_named(ro, mod_data.ro, bool, &mod_data.num_ros, S_IRUGO); +MODULE_PARM_DESC(ro, "true to force read-only"); + +module_param_array_named(nofua, mod_data.nofua, bool, &mod_data.num_nofuas, + S_IRUGO); +MODULE_PARM_DESC(nofua, "true to ignore SCSI WRITE(10,12) FUA bit"); + +module_param_named(luns, mod_data.nluns, uint, S_IRUGO); +MODULE_PARM_DESC(luns, "number of LUNs"); + +module_param_named(removable, mod_data.removable, bool, S_IRUGO); +MODULE_PARM_DESC(removable, "true to simulate removable media"); + +module_param_named(stall, mod_data.can_stall, bool, S_IRUGO); +MODULE_PARM_DESC(stall, "false to prevent bulk stalls"); + +module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO); +MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk"); + +/* In the non-TEST version, only the module parameters listed above + * are available. */ +#ifdef CONFIG_USB_FILE_STORAGE_TEST + +module_param_named(transport, mod_data.transport_parm, charp, S_IRUGO); +MODULE_PARM_DESC(transport, "type of transport (BBB, CBI, or CB)"); + +module_param_named(protocol, mod_data.protocol_parm, charp, S_IRUGO); +MODULE_PARM_DESC(protocol, "type of protocol (RBC, 8020, QIC, UFI, " + "8070, or SCSI)"); + +module_param_named(vendor, mod_data.vendor, ushort, S_IRUGO); +MODULE_PARM_DESC(vendor, "USB Vendor ID"); + +module_param_named(product, mod_data.product, ushort, S_IRUGO); +MODULE_PARM_DESC(product, "USB Product ID"); + +module_param_named(release, mod_data.release, ushort, S_IRUGO); +MODULE_PARM_DESC(release, "USB release number"); + +module_param_named(buflen, mod_data.buflen, uint, S_IRUGO); +MODULE_PARM_DESC(buflen, "I/O buffer size"); + +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +/* + * These definitions will permit the compiler to avoid generating code for + * parts of the driver that aren't used in the non-TEST version. Even gcc + * can recognize when a test of a constant expression yields a dead code + * path. + */ + +#ifdef CONFIG_USB_FILE_STORAGE_TEST + +#define transport_is_bbb() (mod_data.transport_type == USB_PR_BULK) +#define transport_is_cbi() (mod_data.transport_type == USB_PR_CBI) +#define protocol_is_scsi() (mod_data.protocol_type == USB_SC_SCSI) + +#else + +#define transport_is_bbb() 1 +#define transport_is_cbi() 0 +#define protocol_is_scsi() 1 + +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +/*-------------------------------------------------------------------------*/ + + +struct fsg_dev { + /* lock protects: state, all the req_busy's, and cbbuf_cmnd */ + spinlock_t lock; + struct usb_gadget *gadget; + + /* filesem protects: backing files in use */ + struct rw_semaphore filesem; + + /* reference counting: wait until all LUNs are released */ + struct kref ref; + + struct usb_ep *ep0; // Handy copy of gadget->ep0 + struct usb_request *ep0req; // For control responses + unsigned int ep0_req_tag; + const char *ep0req_name; + + struct usb_request *intreq; // For interrupt responses + int intreq_busy; + struct fsg_buffhd *intr_buffhd; + + unsigned int bulk_out_maxpacket; + enum fsg_state state; // For exception handling + unsigned int exception_req_tag; + + u8 config, new_config; + + unsigned int running : 1; + unsigned int bulk_in_enabled : 1; + unsigned int bulk_out_enabled : 1; + unsigned int intr_in_enabled : 1; + unsigned int phase_error : 1; + unsigned int short_packet_received : 1; + unsigned int bad_lun_okay : 1; + + unsigned long atomic_bitflags; +#define REGISTERED 0 +#define IGNORE_BULK_OUT 1 +#define SUSPENDED 2 + + struct usb_ep *bulk_in; + struct usb_ep *bulk_out; + struct usb_ep *intr_in; + + struct fsg_buffhd *next_buffhd_to_fill; + struct fsg_buffhd *next_buffhd_to_drain; + struct fsg_buffhd buffhds[FSG_NUM_BUFFERS]; + + int thread_wakeup_needed; + struct completion thread_notifier; + struct task_struct *thread_task; + + int cmnd_size; + u8 cmnd[MAX_COMMAND_SIZE]; + enum data_direction data_dir; + u32 data_size; + u32 data_size_from_cmnd; + u32 tag; + unsigned int lun; + u32 residue; + u32 usb_amount_left; + + /* The CB protocol offers no way for a host to know when a command + * has completed. As a result the next command may arrive early, + * and we will still have to handle it. For that reason we need + * a buffer to store new commands when using CB (or CBI, which + * does not oblige a host to wait for command completion either). */ + int cbbuf_cmnd_size; + u8 cbbuf_cmnd[MAX_COMMAND_SIZE]; + + unsigned int nluns; + struct fsg_lun *luns; + struct fsg_lun *curlun; +}; + +typedef void (*fsg_routine_t)(struct fsg_dev *); + +static int exception_in_progress(struct fsg_dev *fsg) +{ + return (fsg->state > FSG_STATE_IDLE); +} + +/* Make bulk-out requests be divisible by the maxpacket size */ +static void set_bulk_out_req_length(struct fsg_dev *fsg, + struct fsg_buffhd *bh, unsigned int length) +{ + unsigned int rem; + + bh->bulk_out_intended_length = length; + rem = length % fsg->bulk_out_maxpacket; + if (rem > 0) + length += fsg->bulk_out_maxpacket - rem; + bh->outreq->length = length; +} + +static struct fsg_dev *the_fsg; +static struct usb_gadget_driver fsg_driver; + + +/*-------------------------------------------------------------------------*/ + +static int fsg_set_halt(struct fsg_dev *fsg, struct usb_ep *ep) +{ + const char *name; + + if (ep == fsg->bulk_in) + name = "bulk-in"; + else if (ep == fsg->bulk_out) + name = "bulk-out"; + else + name = ep->name; + DBG(fsg, "%s set halt\n", name); + return usb_ep_set_halt(ep); +} + + +/*-------------------------------------------------------------------------*/ + +/* + * DESCRIPTORS ... most are static, but strings and (full) configuration + * descriptors are built on demand. Also the (static) config and interface + * descriptors are adjusted during fsg_bind(). + */ + +/* There is only one configuration. */ +#define CONFIG_VALUE 1 + +static struct usb_device_descriptor +device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + /* The next three values can be overridden by module parameters */ + .idVendor = cpu_to_le16(FSG_VENDOR_ID), + .idProduct = cpu_to_le16(FSG_PRODUCT_ID), + .bcdDevice = cpu_to_le16(0xffff), + + .iManufacturer = FSG_STRING_MANUFACTURER, + .iProduct = FSG_STRING_PRODUCT, + .iSerialNumber = FSG_STRING_SERIAL, + .bNumConfigurations = 1, +}; + +static struct usb_config_descriptor +config_desc = { + .bLength = sizeof config_desc, + .bDescriptorType = USB_DT_CONFIG, + + /* wTotalLength computed by usb_gadget_config_buf() */ + .bNumInterfaces = 1, + .bConfigurationValue = CONFIG_VALUE, + .iConfiguration = FSG_STRING_CONFIG, + .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = CONFIG_USB_GADGET_VBUS_DRAW / 2, +}; + + +static struct usb_qualifier_descriptor +dev_qualifier = { + .bLength = sizeof dev_qualifier, + .bDescriptorType = USB_DT_DEVICE_QUALIFIER, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + .bNumConfigurations = 1, +}; + + + +/* + * Config descriptors must agree with the code that sets configurations + * and with code managing interfaces and their altsettings. They must + * also handle different speeds and other-speed requests. + */ +static int populate_config_buf(struct usb_gadget *gadget, + u8 *buf, u8 type, unsigned index) +{ + enum usb_device_speed speed = gadget->speed; + int len; + const struct usb_descriptor_header **function; + + if (index > 0) + return -EINVAL; + + if (gadget_is_dualspeed(gadget) && type == USB_DT_OTHER_SPEED_CONFIG) + speed = (USB_SPEED_FULL + USB_SPEED_HIGH) - speed; + function = gadget_is_dualspeed(gadget) && speed == USB_SPEED_HIGH + ? (const struct usb_descriptor_header **)fsg_hs_function + : (const struct usb_descriptor_header **)fsg_fs_function; + + /* for now, don't advertise srp-only devices */ + if (!gadget_is_otg(gadget)) + function++; + + len = usb_gadget_config_buf(&config_desc, buf, EP0_BUFSIZE, function); + ((struct usb_config_descriptor *) buf)->bDescriptorType = type; + return len; +} + + +/*-------------------------------------------------------------------------*/ + +/* These routines may be called in process context or in_irq */ + +/* Caller must hold fsg->lock */ +static void wakeup_thread(struct fsg_dev *fsg) +{ + /* Tell the main thread that something has happened */ + fsg->thread_wakeup_needed = 1; + if (fsg->thread_task) + wake_up_process(fsg->thread_task); +} + + +static void raise_exception(struct fsg_dev *fsg, enum fsg_state new_state) +{ + unsigned long flags; + + /* Do nothing if a higher-priority exception is already in progress. + * If a lower-or-equal priority exception is in progress, preempt it + * and notify the main thread by sending it a signal. */ + spin_lock_irqsave(&fsg->lock, flags); + if (fsg->state <= new_state) { + fsg->exception_req_tag = fsg->ep0_req_tag; + fsg->state = new_state; + if (fsg->thread_task) + send_sig_info(SIGUSR1, SEND_SIG_FORCED, + fsg->thread_task); + } + spin_unlock_irqrestore(&fsg->lock, flags); +} + + +/*-------------------------------------------------------------------------*/ + +/* The disconnect callback and ep0 routines. These always run in_irq, + * except that ep0_queue() is called in the main thread to acknowledge + * completion of various requests: set config, set interface, and + * Bulk-only device reset. */ + +static void fsg_disconnect(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + + DBG(fsg, "disconnect or port reset\n"); + raise_exception(fsg, FSG_STATE_DISCONNECT); +} + + +static int ep0_queue(struct fsg_dev *fsg) +{ + int rc; + + rc = usb_ep_queue(fsg->ep0, fsg->ep0req, GFP_ATOMIC); + if (rc != 0 && rc != -ESHUTDOWN) { + + /* We can't do much more than wait for a reset */ + WARNING(fsg, "error in submission: %s --> %d\n", + fsg->ep0->name, rc); + } + return rc; +} + +static void ep0_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + + if (req->actual > 0) + dump_msg(fsg, fsg->ep0req_name, req->buf, req->actual); + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + if (req->status == 0 && req->context) + ((fsg_routine_t) (req->context))(fsg); +} + + +/*-------------------------------------------------------------------------*/ + +/* Bulk and interrupt endpoint completion handlers. + * These always run in_irq. */ + +static void bulk_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&fsg->lock); + bh->inreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + +static void bulk_out_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + dump_msg(fsg, "bulk-out", req->buf, req->actual); + if (req->status || req->actual != bh->bulk_out_intended_length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, + bh->bulk_out_intended_length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&fsg->lock); + bh->outreq_busy = 0; + bh->state = BUF_STATE_FULL; + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + + +#ifdef CONFIG_USB_FILE_STORAGE_TEST +static void intr_in_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct fsg_dev *fsg = ep->driver_data; + struct fsg_buffhd *bh = req->context; + + if (req->status || req->actual != req->length) + DBG(fsg, "%s --> %d, %u/%u\n", __func__, + req->status, req->actual, req->length); + if (req->status == -ECONNRESET) // Request was cancelled + usb_ep_fifo_flush(ep); + + /* Hold the lock while we update the request and buffer states */ + smp_wmb(); + spin_lock(&fsg->lock); + fsg->intreq_busy = 0; + bh->state = BUF_STATE_EMPTY; + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + +#else +static void intr_in_complete(struct usb_ep *ep, struct usb_request *req) +{} +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +/*-------------------------------------------------------------------------*/ + +/* Ep0 class-specific handlers. These always run in_irq. */ + +#ifdef CONFIG_USB_FILE_STORAGE_TEST +static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = fsg->ep0req; + static u8 cbi_reset_cmnd[6] = { + SEND_DIAGNOSTIC, 4, 0xff, 0xff, 0xff, 0xff}; + + /* Error in command transfer? */ + if (req->status || req->length != req->actual || + req->actual < 6 || req->actual > MAX_COMMAND_SIZE) { + + /* Not all controllers allow a protocol stall after + * receiving control-out data, but we'll try anyway. */ + fsg_set_halt(fsg, fsg->ep0); + return; // Wait for reset + } + + /* Is it the special reset command? */ + if (req->actual >= sizeof cbi_reset_cmnd && + memcmp(req->buf, cbi_reset_cmnd, + sizeof cbi_reset_cmnd) == 0) { + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "cbi reset request\n"); + raise_exception(fsg, FSG_STATE_RESET); + return; + } + + VDBG(fsg, "CB[I] accept device-specific command\n"); + spin_lock(&fsg->lock); + + /* Save the command for later */ + if (fsg->cbbuf_cmnd_size) + WARNING(fsg, "CB[I] overwriting previous command\n"); + fsg->cbbuf_cmnd_size = req->actual; + memcpy(fsg->cbbuf_cmnd, req->buf, fsg->cbbuf_cmnd_size); + + wakeup_thread(fsg); + spin_unlock(&fsg->lock); +} + +#else +static void received_cbi_adsc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{} +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + +static int class_setup_req(struct fsg_dev *fsg, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = fsg->ep0req; + int value = -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 (!fsg->config) + return value; + + /* Handle Bulk-only class-specific requests */ + if (transport_is_bbb()) { + switch (ctrl->bRequest) { + + case USB_BULK_RESET_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0) { + value = -EDOM; + break; + } + + /* Raise an exception to stop the current operation + * and reinitialize our state. */ + DBG(fsg, "bulk reset request\n"); + raise_exception(fsg, FSG_STATE_RESET); + value = DELAYED_STATUS; + break; + + case USB_BULK_GET_MAX_LUN_REQUEST: + if (ctrl->bRequestType != (USB_DIR_IN | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0) { + value = -EDOM; + break; + } + VDBG(fsg, "get max LUN\n"); + *(u8 *) req->buf = fsg->nluns - 1; + value = 1; + break; + } + } + + /* Handle CBI class-specific requests */ + else { + switch (ctrl->bRequest) { + + case USB_CBI_ADSC_REQUEST: + if (ctrl->bRequestType != (USB_DIR_OUT | + USB_TYPE_CLASS | USB_RECIP_INTERFACE)) + break; + if (w_index != 0 || w_value != 0) { + value = -EDOM; + break; + } + if (w_length > MAX_COMMAND_SIZE) { + value = -EOVERFLOW; + break; + } + value = w_length; + fsg->ep0req->context = received_cbi_adsc; + break; + } + } + + if (value == -EOPNOTSUPP) + VDBG(fsg, + "unknown class-specific control req " + "%02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + le16_to_cpu(ctrl->wValue), w_index, w_length); + return value; +} + + +/*-------------------------------------------------------------------------*/ + +/* Ep0 standard request handlers. These always run in_irq. */ + +static int standard_setup_req(struct fsg_dev *fsg, + const struct usb_ctrlrequest *ctrl) +{ + struct usb_request *req = fsg->ep0req; + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + + /* Usually this just stores reply data in the pre-allocated ep0 buffer, + * but config change events will also reconfigure hardware. */ + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + VDBG(fsg, "get device descriptor\n"); + value = sizeof device_desc; + memcpy(req->buf, &device_desc, value); + break; + case USB_DT_DEVICE_QUALIFIER: + VDBG(fsg, "get device qualifier\n"); + if (!gadget_is_dualspeed(fsg->gadget)) + break; + value = sizeof dev_qualifier; + memcpy(req->buf, &dev_qualifier, value); + break; + + case USB_DT_OTHER_SPEED_CONFIG: + VDBG(fsg, "get other-speed config descriptor\n"); + if (!gadget_is_dualspeed(fsg->gadget)) + break; + goto get_config; + case USB_DT_CONFIG: + VDBG(fsg, "get configuration descriptor\n"); +get_config: + value = populate_config_buf(fsg->gadget, + req->buf, + w_value >> 8, + w_value & 0xff); + break; + + case USB_DT_STRING: + VDBG(fsg, "get string descriptor\n"); + + /* wIndex == language code */ + value = usb_gadget_get_string(&fsg_stringtab, + w_value & 0xff, req->buf); + break; + } + break; + + /* One config, two speeds */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != (USB_DIR_OUT | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + VDBG(fsg, "set configuration\n"); + if (w_value == CONFIG_VALUE || w_value == 0) { + fsg->new_config = w_value; + + /* Raise an exception to wipe out previous transaction + * state (queued bufs, etc) and set the new config. */ + raise_exception(fsg, FSG_STATE_CONFIG_CHANGE); + value = DELAYED_STATUS; + } + break; + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_DEVICE)) + break; + VDBG(fsg, "get configuration\n"); + *(u8 *) req->buf = fsg->config; + value = 1; + break; + + case USB_REQ_SET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_OUT| USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + if (fsg->config && w_index == 0) { + + /* Raise an exception to wipe out previous transaction + * state (queued bufs, etc) and install the new + * interface altsetting. */ + raise_exception(fsg, FSG_STATE_INTERFACE_CHANGE); + value = DELAYED_STATUS; + } + break; + case USB_REQ_GET_INTERFACE: + if (ctrl->bRequestType != (USB_DIR_IN | USB_TYPE_STANDARD | + USB_RECIP_INTERFACE)) + break; + if (!fsg->config) + break; + if (w_index != 0) { + value = -EDOM; + break; + } + VDBG(fsg, "get interface\n"); + *(u8 *) req->buf = 0; + value = 1; + break; + + default: + VDBG(fsg, + "unknown control req %02x.%02x v%04x i%04x l%u\n", + ctrl->bRequestType, ctrl->bRequest, + w_value, w_index, le16_to_cpu(ctrl->wLength)); + } + + return value; +} + + +static int fsg_setup(struct usb_gadget *gadget, + const struct usb_ctrlrequest *ctrl) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + int rc; + int w_length = le16_to_cpu(ctrl->wLength); + + ++fsg->ep0_req_tag; // Record arrival of a new request + fsg->ep0req->context = NULL; + fsg->ep0req->length = 0; + dump_msg(fsg, "ep0-setup", (u8 *) ctrl, sizeof(*ctrl)); + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) + rc = class_setup_req(fsg, ctrl); + else + rc = standard_setup_req(fsg, ctrl); + + /* Respond with data/status or defer until later? */ + if (rc >= 0 && rc != DELAYED_STATUS) { + rc = min(rc, w_length); + fsg->ep0req->length = rc; + fsg->ep0req->zero = rc < w_length; + fsg->ep0req_name = (ctrl->bRequestType & USB_DIR_IN ? + "ep0-in" : "ep0-out"); + rc = ep0_queue(fsg); + } + + /* Device either stalls (rc < 0) or reports success */ + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +/* All the following routines run in process context */ + + +/* Use this for bulk or interrupt transfers, not ep0 */ +static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request *req, int *pbusy, + enum fsg_buffer_state *state) +{ + int rc; + + if (ep == fsg->bulk_in) + dump_msg(fsg, "bulk-in", req->buf, req->length); + else if (ep == fsg->intr_in) + dump_msg(fsg, "intr-in", req->buf, req->length); + + spin_lock_irq(&fsg->lock); + *pbusy = 1; + *state = BUF_STATE_BUSY; + spin_unlock_irq(&fsg->lock); + rc = usb_ep_queue(ep, req, GFP_KERNEL); + if (rc != 0) { + *pbusy = 0; + *state = BUF_STATE_EMPTY; + + /* We can't do much more than wait for a reset */ + + /* Note: currently the net2280 driver fails zero-length + * submissions if DMA is enabled. */ + if (rc != -ESHUTDOWN && !(rc == -EOPNOTSUPP && + req->length == 0)) + WARNING(fsg, "error in submission: %s --> %d\n", + ep->name, rc); + } +} + + +static int sleep_thread(struct fsg_dev *fsg) +{ + int rc = 0; + + /* Wait until a signal arrives or we are woken up */ + for (;;) { + try_to_freeze(); + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + rc = -EINTR; + break; + } + if (fsg->thread_wakeup_needed) + break; + schedule(); + } + __set_current_state(TASK_RUNNING); + fsg->thread_wakeup_needed = 0; + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_read(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba; + struct fsg_buffhd *bh; + int rc; + u32 amount_left; + loff_t file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (fsg->cmnd[0] == READ_6) + lba = get_unaligned_be24(&fsg->cmnd[1]); + else { + lba = get_unaligned_be32(&fsg->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = don't read from the + * cache), but we don't implement them. */ + if ((fsg->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + file_offset = ((loff_t) lba) << 9; + + /* Carry out the file reads */ + amount_left = fsg->data_size_from_cmnd; + if (unlikely(amount_left == 0)) + return -EIO; // No default reply + + for (;;) { + + /* Figure out how much we need to read: + * Try to read the remaining amount. + * But don't read more than the buffer size. + * And don't try to read past the end of the file. + * Finally, if we're not at a page boundary, don't read past + * the next page. + * If this means reading 0 then we were asked to read past + * the end of file. */ + amount = min((unsigned int) amount_left, mod_data.buflen); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + partial_page = file_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, (unsigned int) PAGE_CACHE_SIZE - + partial_page); + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* If we were asked to read past the end of file, + * end with an empty buffer. */ + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + bh->inreq->length = 0; + bh->state = BUF_STATE_FULL; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file read: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file read: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); // Round down to a block + } + file_offset += nread; + amount_left -= nread; + fsg->residue -= nread; + bh->inreq->length = nread; + bh->state = BUF_STATE_FULL; + + /* If an error occurred, report it and its position */ + if (nread < amount) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + if (amount_left == 0) + break; // No more left to read + + /* Send this buffer and go read some more */ + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + + return -EIO; // No default reply +} + + +/*-------------------------------------------------------------------------*/ + +static int do_write(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba; + struct fsg_buffhd *bh; + int get_some_more; + u32 amount_left_to_req, amount_left_to_write; + loff_t usb_offset, file_offset, file_offset_tmp; + unsigned int amount; + unsigned int partial_page; + ssize_t nwritten; + int rc; + + if (curlun->ro) { + curlun->sense_data = SS_WRITE_PROTECTED; + return -EINVAL; + } + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags &= ~O_SYNC; // Default is not to wait + spin_unlock(&curlun->filp->f_lock); + + /* Get the starting Logical Block Address and check that it's + * not too big */ + if (fsg->cmnd[0] == WRITE_6) + lba = get_unaligned_be24(&fsg->cmnd[1]); + else { + lba = get_unaligned_be32(&fsg->cmnd[2]); + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) and FUA (Force Unit Access = write directly to the + * medium). We don't implement DPO; we implement FUA by + * performing synchronous output. */ + if ((fsg->cmnd[1] & ~0x18) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + /* FUA */ + if (!curlun->nofua && (fsg->cmnd[1] & 0x08)) { + spin_lock(&curlun->filp->f_lock); + curlun->filp->f_flags |= O_DSYNC; + spin_unlock(&curlun->filp->f_lock); + } + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* Carry out the file writes */ + get_some_more = 1; + file_offset = usb_offset = ((loff_t) lba) << 9; + amount_left_to_req = amount_left_to_write = fsg->data_size_from_cmnd; + + while (amount_left_to_write > 0) { + + /* Queue a request for more data from the host */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && get_some_more) { + + /* Figure out how much we want to get: + * Try to get the remaining amount. + * But don't get more than the buffer size. + * And don't try to go past the end of the file. + * If we're not at a page boundary, + * don't go past the next page. + * If this means getting 0, then we were asked + * to write past the end of file. + * Finally, round down to a block boundary. */ + amount = min(amount_left_to_req, mod_data.buflen); + amount = min((loff_t) amount, curlun->file_length - + usb_offset); + partial_page = usb_offset & (PAGE_CACHE_SIZE - 1); + if (partial_page > 0) + amount = min(amount, + (unsigned int) PAGE_CACHE_SIZE - partial_page); + + if (amount == 0) { + get_some_more = 0; + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = usb_offset >> 9; + curlun->info_valid = 1; + continue; + } + amount -= (amount & 511); + if (amount == 0) { + + /* Why were we were asked to transfer a + * partial block? */ + get_some_more = 0; + continue; + } + + /* Get the next buffer */ + usb_offset += amount; + fsg->usb_amount_left -= amount; + amount_left_to_req -= amount; + if (amount_left_to_req == 0) + get_some_more = 0; + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = bh->bulk_out_intended_length = + amount; + bh->outreq->short_not_ok = 1; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + continue; + } + + /* Write the received data to the backing file */ + bh = fsg->next_buffhd_to_drain; + if (bh->state == BUF_STATE_EMPTY && !get_some_more) + break; // We stopped early + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + fsg->next_buffhd_to_drain = bh->next; + bh->state = BUF_STATE_EMPTY; + + /* Did something go wrong with the transfer? */ + if (bh->outreq->status != 0) { + curlun->sense_data = SS_COMMUNICATION_FAILURE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + amount = bh->outreq->actual; + if (curlun->file_length - file_offset < amount) { + LERROR(curlun, + "write %u @ %llu beyond end %llu\n", + amount, (unsigned long long) file_offset, + (unsigned long long) curlun->file_length); + amount = curlun->file_length - file_offset; + } + + /* Perform the write */ + file_offset_tmp = file_offset; + nwritten = vfs_write(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file write %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nwritten); + if (signal_pending(current)) + return -EINTR; // Interrupted! + + if (nwritten < 0) { + LDBG(curlun, "error in file write: %d\n", + (int) nwritten); + nwritten = 0; + } else if (nwritten < amount) { + LDBG(curlun, "partial file write: %d/%u\n", + (int) nwritten, amount); + nwritten -= (nwritten & 511); + // Round down to a block + } + file_offset += nwritten; + amount_left_to_write -= nwritten; + fsg->residue -= nwritten; + + /* If an error occurred, report it and its position */ + if (nwritten < amount) { + curlun->sense_data = SS_WRITE_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + /* Did the host decide to stop early? */ + if (bh->outreq->actual != bh->outreq->length) { + fsg->short_packet_received = 1; + break; + } + continue; + } + + /* Wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + return -EIO; // No default reply +} + + +/*-------------------------------------------------------------------------*/ + +static int do_synchronize_cache(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + int rc; + + /* We ignore the requested LBA and write out all file's + * dirty data buffers. */ + rc = fsg_lun_fsync_sub(curlun); + if (rc) + curlun->sense_data = SS_WRITE_ERROR; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static void invalidate_sub(struct fsg_lun *curlun) +{ + struct file *filp = curlun->filp; + struct inode *inode = filp->f_path.dentry->d_inode; + unsigned long rc; + + rc = invalidate_mapping_pages(inode->i_mapping, 0, -1); + VLDBG(curlun, "invalidate_mapping_pages -> %ld\n", rc); +} + +static int do_verify(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba; + u32 verification_length; + struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; + loff_t file_offset, file_offset_tmp; + u32 amount_left; + unsigned int amount; + ssize_t nread; + + /* Get the starting Logical Block Address and check that it's + * not too big */ + lba = get_unaligned_be32(&fsg->cmnd[2]); + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + /* We allow DPO (Disable Page Out = don't save data in the + * cache) but we don't implement it. */ + if ((fsg->cmnd[1] & ~0x10) != 0) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + verification_length = get_unaligned_be16(&fsg->cmnd[7]); + if (unlikely(verification_length == 0)) + return -EIO; // No default reply + + /* Prepare to carry out the file verify */ + amount_left = verification_length << 9; + file_offset = ((loff_t) lba) << 9; + + /* Write out all the dirty buffers before invalidating them */ + fsg_lun_fsync_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + invalidate_sub(curlun); + if (signal_pending(current)) + return -EINTR; + + /* Just try to read the requested blocks */ + while (amount_left > 0) { + + /* Figure out how much we need to read: + * Try to read the remaining amount, but not more than + * the buffer size. + * And don't try to read past the end of the file. + * If this means reading 0 then we were asked to read + * past the end of file. */ + amount = min((unsigned int) amount_left, mod_data.buflen); + amount = min((loff_t) amount, + curlun->file_length - file_offset); + if (amount == 0) { + curlun->sense_data = + SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + + /* Perform the read */ + file_offset_tmp = file_offset; + nread = vfs_read(curlun->filp, + (char __user *) bh->buf, + amount, &file_offset_tmp); + VLDBG(curlun, "file read %u @ %llu -> %d\n", amount, + (unsigned long long) file_offset, + (int) nread); + if (signal_pending(current)) + return -EINTR; + + if (nread < 0) { + LDBG(curlun, "error in file verify: %d\n", + (int) nread); + nread = 0; + } else if (nread < amount) { + LDBG(curlun, "partial file verify: %d/%u\n", + (int) nread, amount); + nread -= (nread & 511); // Round down to a sector + } + if (nread == 0) { + curlun->sense_data = SS_UNRECOVERED_READ_ERROR; + curlun->sense_data_info = file_offset >> 9; + curlun->info_valid = 1; + break; + } + file_offset += nread; + amount_left -= nread; + } + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + u8 *buf = (u8 *) bh->buf; + + static char vendor_id[] = "Linux "; + static char product_disk_id[] = "File-Stor Gadget"; + static char product_cdrom_id[] = "File-CD Gadget "; + + if (!fsg->curlun) { // Unsupported LUNs are okay + fsg->bad_lun_okay = 1; + memset(buf, 0, 36); + buf[0] = 0x7f; // Unsupported, no device-type + buf[4] = 31; // Additional length + return 36; + } + + memset(buf, 0, 8); + buf[0] = (mod_data.cdrom ? TYPE_ROM : TYPE_DISK); + if (mod_data.removable) + buf[1] = 0x80; + buf[2] = 2; // ANSI SCSI level 2 + buf[3] = 2; // SCSI-2 INQUIRY data format + buf[4] = 31; // Additional length + // No special options + sprintf(buf + 8, "%-8s%-16s%04x", vendor_id, + (mod_data.cdrom ? product_cdrom_id : + product_disk_id), + mod_data.release); + return 36; +} + + +static int do_request_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + u8 *buf = (u8 *) bh->buf; + u32 sd, sdinfo; + int valid; + + /* + * From the SCSI-2 spec., section 7.9 (Unit attention condition): + * + * If a REQUEST SENSE command is received from an initiator + * with a pending unit attention condition (before the target + * generates the contingent allegiance condition), then the + * target shall either: + * a) report any pending sense data and preserve the unit + * attention condition on the logical unit, or, + * b) report the unit attention condition, may discard any + * pending sense data, and clear the unit attention + * condition on the logical unit for that initiator. + * + * FSG normally uses option a); enable this code to use option b). + */ +#if 0 + if (curlun && curlun->unit_attention_data != SS_NO_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + } +#endif + + if (!curlun) { // Unsupported LUNs are okay + fsg->bad_lun_okay = 1; + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + sdinfo = 0; + valid = 0; + } else { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + valid = curlun->info_valid << 7; + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + + memset(buf, 0, 18); + buf[0] = valid | 0x70; // Valid, current error + buf[2] = SK(sd); + put_unaligned_be32(sdinfo, &buf[3]); /* Sense information */ + buf[7] = 18 - 8; // Additional sense length + buf[12] = ASC(sd); + buf[13] = ASCQ(sd); + return 18; +} + + +static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + u32 lba = get_unaligned_be32(&fsg->cmnd[2]); + int pmi = fsg->cmnd[8]; + u8 *buf = (u8 *) bh->buf; + + /* Check the PMI and LBA fields */ + if (pmi > 1 || (pmi == 0 && lba != 0)) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + put_unaligned_be32(curlun->num_sectors - 1, &buf[0]); + /* Max logical block */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + return 8; +} + + +static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + int msf = fsg->cmnd[1] & 0x02; + u32 lba = get_unaligned_be32(&fsg->cmnd[2]); + u8 *buf = (u8 *) bh->buf; + + if ((fsg->cmnd[1] & ~0x02) != 0) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + + +static int do_read_toc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + int msf = fsg->cmnd[1] & 0x02; + int start_track = fsg->cmnd[6]; + u8 *buf = (u8 *) bh->buf; + + if ((fsg->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + return 20; +} + + +static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + int mscmnd = fsg->cmnd[0]; + u8 *buf = (u8 *) bh->buf; + u8 *buf0 = buf; + int pc, page_code; + int changeable_values, all_pages; + int valid_page = 0; + int len, limit; + + if ((fsg->cmnd[1] & ~0x08) != 0) { // Mask away DBD + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + pc = fsg->cmnd[2] >> 6; + page_code = fsg->cmnd[2] & 0x3f; + if (pc == 3) { + curlun->sense_data = SS_SAVING_PARAMETERS_NOT_SUPPORTED; + return -EINVAL; + } + changeable_values = (pc == 1); + all_pages = (page_code == 0x3f); + + /* Write the mode parameter header. Fixed values are: default + * medium type, no cache control (DPOFUA), and no block descriptors. + * The only variable value is the WriteProtect bit. We will fill in + * the mode data length later. */ + memset(buf, 0, 8); + if (mscmnd == MODE_SENSE) { + buf[2] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA + buf += 4; + limit = 255; + } else { // MODE_SENSE_10 + buf[3] = (curlun->ro ? 0x80 : 0x00); // WP, DPOFUA + buf += 8; + limit = 65535; // Should really be mod_data.buflen + } + + /* No block descriptors */ + + /* The mode pages, in numerical order. The only page we support + * is the Caching page. */ + if (page_code == 0x08 || all_pages) { + valid_page = 1; + buf[0] = 0x08; // Page code + buf[1] = 10; // Page length + memset(buf+2, 0, 10); // None of the fields are changeable + + if (!changeable_values) { + buf[2] = 0x04; // Write cache enable, + // Read cache not disabled + // No cache retention priorities + put_unaligned_be16(0xffff, &buf[4]); + /* Don't disable prefetch */ + /* Minimum prefetch = 0 */ + put_unaligned_be16(0xffff, &buf[8]); + /* Maximum prefetch */ + put_unaligned_be16(0xffff, &buf[10]); + /* Maximum prefetch ceiling */ + } + buf += 12; + } + + /* Check that a valid page was requested and the mode data length + * isn't too long. */ + len = buf - buf0; + if (!valid_page || len > limit) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + /* Store the mode data length */ + if (mscmnd == MODE_SENSE) + buf0[0] = len - 1; + else + put_unaligned_be16(len - 2, buf0); + return len; +} + + +static int do_start_stop(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + int loej, start; + + if (!mod_data.removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + // int immed = fsg->cmnd[1] & 0x01; + loej = fsg->cmnd[4] & 0x02; + start = fsg->cmnd[4] & 0x01; + +#ifdef CONFIG_USB_FILE_STORAGE_TEST + if ((fsg->cmnd[1] & ~0x01) != 0 || // Mask away Immed + (fsg->cmnd[4] & ~0x03) != 0) { // Mask LoEj, Start + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (!start) { + + /* Are we allowed to unload the media? */ + if (curlun->prevent_medium_removal) { + LDBG(curlun, "unload attempt prevented\n"); + curlun->sense_data = SS_MEDIUM_REMOVAL_PREVENTED; + return -EINVAL; + } + if (loej) { // Simulate an unload/eject + up_read(&fsg->filesem); + down_write(&fsg->filesem); + fsg_lun_close(curlun); + up_write(&fsg->filesem); + down_read(&fsg->filesem); + } + } else { + + /* Our emulation doesn't support mounting; the medium is + * available for use as soon as it is loaded. */ + if (!fsg_lun_is_open(curlun)) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + } +#endif + return 0; +} + + +static int do_prevent_allow(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + int prevent; + + if (!mod_data.removable) { + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; + } + + prevent = fsg->cmnd[4] & 0x01; + if ((fsg->cmnd[4] & ~0x01) != 0) { // Mask away Prevent + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + if (curlun->prevent_medium_removal && !prevent) + fsg_lun_fsync_sub(curlun); + curlun->prevent_medium_removal = prevent; + return 0; +} + + +static int do_read_format_capacities(struct fsg_dev *fsg, + struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + u8 *buf = (u8 *) bh->buf; + + buf[0] = buf[1] = buf[2] = 0; + buf[3] = 8; // Only the Current/Maximum Capacity Descriptor + buf += 4; + + put_unaligned_be32(curlun->num_sectors, &buf[0]); + /* Number of blocks */ + put_unaligned_be32(512, &buf[4]); /* Block length */ + buf[4] = 0x02; /* Current capacity */ + return 12; +} + + +static int do_mode_select(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct fsg_lun *curlun = fsg->curlun; + + /* We don't support MODE SELECT */ + curlun->sense_data = SS_INVALID_COMMAND; + return -EINVAL; +} + + +/*-------------------------------------------------------------------------*/ + +static int halt_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + rc = fsg_set_halt(fsg, fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint halt\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_halt -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_halt(fsg->bulk_in); + } + return rc; +} + +static int wedge_bulk_in_endpoint(struct fsg_dev *fsg) +{ + int rc; + + DBG(fsg, "bulk-in set wedge\n"); + rc = usb_ep_set_wedge(fsg->bulk_in); + if (rc == -EAGAIN) + VDBG(fsg, "delayed bulk-in endpoint wedge\n"); + while (rc != 0) { + if (rc != -EAGAIN) { + WARNING(fsg, "usb_ep_set_wedge -> %d\n", rc); + rc = 0; + break; + } + + /* Wait for a short time and then try again */ + if (msleep_interruptible(100) != 0) + return -EINTR; + rc = usb_ep_set_wedge(fsg->bulk_in); + } + return rc; +} + +static int throw_away_data(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + u32 amount; + int rc; + + while ((bh = fsg->next_buffhd_to_drain)->state != BUF_STATE_EMPTY || + fsg->usb_amount_left > 0) { + + /* Throw away the data in a filled buffer */ + if (bh->state == BUF_STATE_FULL) { + smp_rmb(); + bh->state = BUF_STATE_EMPTY; + fsg->next_buffhd_to_drain = bh->next; + + /* A short packet or an error ends everything */ + if (bh->outreq->actual != bh->outreq->length || + bh->outreq->status != 0) { + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + return -EINTR; + } + continue; + } + + /* Try to submit another request if we need one */ + bh = fsg->next_buffhd_to_fill; + if (bh->state == BUF_STATE_EMPTY && fsg->usb_amount_left > 0) { + amount = min(fsg->usb_amount_left, + (u32) mod_data.buflen); + + /* amount is always divisible by 512, hence by + * the bulk-out maxpacket size */ + bh->outreq->length = bh->bulk_out_intended_length = + amount; + bh->outreq->short_not_ok = 1; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + fsg->usb_amount_left -= amount; + continue; + } + + /* Otherwise wait for something to happen */ + rc = sleep_thread(fsg); + if (rc) + return rc; + } + return 0; +} + + +static int finish_reply(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh = fsg->next_buffhd_to_fill; + int rc = 0; + + switch (fsg->data_dir) { + case DATA_DIR_NONE: + break; // Nothing to send + + /* If we don't know whether the host wants to read or write, + * this must be CB or CBI with an unknown command. We mustn't + * try to send or receive any data. So stall both bulk pipes + * if we can and wait for a reset. */ + case DATA_DIR_UNKNOWN: + if (mod_data.can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + rc = halt_bulk_in_endpoint(fsg); + } + break; + + /* All but the last buffer of data must have already been sent */ + case DATA_DIR_TO_HOST: + if (fsg->data_size == 0) + ; // Nothing to send + + /* If there's no residue, simply send the last buffer */ + else if (fsg->residue == 0) { + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + + /* There is a residue. For CB and CBI, simply mark the end + * of the data with a short packet. However, if we are + * allowed to stall, there was no data at all (residue == + * data_size), and the command failed (invalid LUN or + * sense data is set), then halt the bulk-in endpoint + * instead. */ + else if (!transport_is_bbb()) { + if (mod_data.can_stall && + fsg->residue == fsg->data_size && + (!fsg->curlun || fsg->curlun->sense_data != SS_NO_SENSE)) { + bh->state = BUF_STATE_EMPTY; + rc = halt_bulk_in_endpoint(fsg); + } else { + bh->inreq->zero = 1; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + } + } + + /* + * For Bulk-only, mark the end of the data with a short + * packet. If we are allowed to stall, halt the bulk-in + * endpoint. (Note: This violates the Bulk-Only Transport + * specification, which requires us to pad the data if we + * don't halt the endpoint. Presumably nobody will mind.) + */ + else { + bh->inreq->zero = 1; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + fsg->next_buffhd_to_fill = bh->next; + if (mod_data.can_stall) + rc = halt_bulk_in_endpoint(fsg); + } + break; + + /* We have processed all we want from the data the host has sent. + * There may still be outstanding bulk-out requests. */ + case DATA_DIR_FROM_HOST: + if (fsg->residue == 0) + ; // Nothing to receive + + /* Did the host stop sending unexpectedly early? */ + else if (fsg->short_packet_received) { + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + } + + /* We haven't processed all the incoming data. Even though + * we may be allowed to stall, doing so would cause a race. + * The controller may already have ACK'ed all the remaining + * bulk-out packets, in which case the host wouldn't see a + * STALL. Not realizing the endpoint was halted, it wouldn't + * clear the halt -- leading to problems later on. */ +#if 0 + else if (mod_data.can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + raise_exception(fsg, FSG_STATE_ABORT_BULK_OUT); + rc = -EINTR; + } +#endif + + /* We can't stall. Read in the excess data and throw it + * all away. */ + else + rc = throw_away_data(fsg); + break; + } + return rc; +} + + +static int send_status(struct fsg_dev *fsg) +{ + struct fsg_lun *curlun = fsg->curlun; + struct fsg_buffhd *bh; + int rc; + u8 status = USB_STATUS_PASS; + u32 sd, sdinfo = 0; + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + if (curlun) { + sd = curlun->sense_data; + sdinfo = curlun->sense_data_info; + } else if (fsg->bad_lun_okay) + sd = SS_NO_SENSE; + else + sd = SS_LOGICAL_UNIT_NOT_SUPPORTED; + + if (fsg->phase_error) { + DBG(fsg, "sending phase-error status\n"); + status = USB_STATUS_PHASE_ERROR; + sd = SS_INVALID_COMMAND; + } else if (sd != SS_NO_SENSE) { + DBG(fsg, "sending command-failure status\n"); + status = USB_STATUS_FAIL; + VDBG(fsg, " sense data: SK x%02x, ASC x%02x, ASCQ x%02x;" + " info x%x\n", + SK(sd), ASC(sd), ASCQ(sd), sdinfo); + } + + if (transport_is_bbb()) { + struct bulk_cs_wrap *csw = bh->buf; + + /* Store and send the Bulk-only CSW */ + csw->Signature = cpu_to_le32(USB_BULK_CS_SIG); + csw->Tag = fsg->tag; + csw->Residue = cpu_to_le32(fsg->residue); + csw->Status = status; + + bh->inreq->length = USB_BULK_CS_WRAP_LEN; + bh->inreq->zero = 0; + start_transfer(fsg, fsg->bulk_in, bh->inreq, + &bh->inreq_busy, &bh->state); + + } else if (mod_data.transport_type == USB_PR_CB) { + + /* Control-Bulk transport has no status phase! */ + return 0; + + } else { // USB_PR_CBI + struct interrupt_data *buf = bh->buf; + + /* Store and send the Interrupt data. UFI sends the ASC + * and ASCQ bytes. Everything else sends a Type (which + * is always 0) and the status Value. */ + if (mod_data.protocol_type == USB_SC_UFI) { + buf->bType = ASC(sd); + buf->bValue = ASCQ(sd); + } else { + buf->bType = 0; + buf->bValue = status; + } + fsg->intreq->length = CBI_INTERRUPT_DATA_LEN; + + fsg->intr_buffhd = bh; // Point to the right buffhd + fsg->intreq->buf = bh->inreq->buf; + fsg->intreq->context = bh; + start_transfer(fsg, fsg->intr_in, fsg->intreq, + &fsg->intreq_busy, &bh->state); + } + + fsg->next_buffhd_to_fill = bh->next; + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +/* Check whether the command is properly formed and whether its data size + * and direction agree with the values we already have. */ +static int check_command(struct fsg_dev *fsg, int cmnd_size, + enum data_direction data_dir, unsigned int mask, + int needs_medium, const char *name) +{ + int i; + int lun = fsg->cmnd[1] >> 5; + static const char dirletter[4] = {'u', 'o', 'i', 'n'}; + char hdlen[20]; + struct fsg_lun *curlun; + + /* Adjust the expected cmnd_size for protocol encapsulation padding. + * Transparent SCSI doesn't pad. */ + if (protocol_is_scsi()) + ; + + /* There's some disagreement as to whether RBC pads commands or not. + * We'll play it safe and accept either form. */ + else if (mod_data.protocol_type == USB_SC_RBC) { + if (fsg->cmnd_size == 12) + cmnd_size = 12; + + /* All the other protocols pad to 12 bytes */ + } else + cmnd_size = 12; + + hdlen[0] = 0; + if (fsg->data_dir != DATA_DIR_UNKNOWN) + sprintf(hdlen, ", H%c=%u", dirletter[(int) fsg->data_dir], + fsg->data_size); + VDBG(fsg, "SCSI command: %s; Dc=%d, D%c=%u; Hc=%d%s\n", + name, cmnd_size, dirletter[(int) data_dir], + fsg->data_size_from_cmnd, fsg->cmnd_size, hdlen); + + /* We can't reply at all until we know the correct data direction + * and size. */ + if (fsg->data_size_from_cmnd == 0) + data_dir = DATA_DIR_NONE; + if (fsg->data_dir == DATA_DIR_UNKNOWN) { // CB or CBI + fsg->data_dir = data_dir; + fsg->data_size = fsg->data_size_from_cmnd; + + } else { // Bulk-only + if (fsg->data_size < fsg->data_size_from_cmnd) { + + /* Host data size < Device data size is a phase error. + * Carry out the command, but only transfer as much + * as we are allowed. */ + fsg->data_size_from_cmnd = fsg->data_size; + fsg->phase_error = 1; + } + } + fsg->residue = fsg->usb_amount_left = fsg->data_size; + + /* Conflicting data directions is a phase error */ + if (fsg->data_dir != data_dir && fsg->data_size_from_cmnd > 0) { + fsg->phase_error = 1; + return -EINVAL; + } + + /* Verify the length of the command itself */ + if (cmnd_size != fsg->cmnd_size) { + + /* Special case workaround: There are plenty of buggy SCSI + * implementations. Many have issues with cbw->Length + * field passing a wrong command size. For those cases we + * always try to work around the problem by using the length + * sent by the host side provided it is at least as large + * as the correct command length. + * Examples of such cases would be MS-Windows, which issues + * REQUEST SENSE with cbw->Length == 12 where it should + * be 6, and xbox360 issuing INQUIRY, TEST UNIT READY and + * REQUEST SENSE with cbw->Length == 10 where it should + * be 6 as well. + */ + if (cmnd_size <= fsg->cmnd_size) { + DBG(fsg, "%s is buggy! Expected length %d " + "but we got %d\n", name, + cmnd_size, fsg->cmnd_size); + cmnd_size = fsg->cmnd_size; + } else { + fsg->phase_error = 1; + return -EINVAL; + } + } + + /* Check that the LUN values are consistent */ + if (transport_is_bbb()) { + if (fsg->lun != lun) + DBG(fsg, "using LUN %d from CBW, " + "not LUN %d from CDB\n", + fsg->lun, lun); + } else + fsg->lun = lun; // Use LUN from the command + + /* Check the LUN */ + if (fsg->lun < fsg->nluns) { + fsg->curlun = curlun = &fsg->luns[fsg->lun]; + if (fsg->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + } else { + fsg->curlun = curlun = NULL; + fsg->bad_lun_okay = 0; + + /* INQUIRY and REQUEST SENSE commands are explicitly allowed + * to use unsupported LUNs; all others may not. */ + if (fsg->cmnd[0] != INQUIRY && + fsg->cmnd[0] != REQUEST_SENSE) { + DBG(fsg, "unsupported LUN %d\n", fsg->lun); + return -EINVAL; + } + } + + /* If a unit attention condition exists, only INQUIRY and + * REQUEST SENSE commands are allowed; anything else must fail. */ + if (curlun && curlun->unit_attention_data != SS_NO_SENSE && + fsg->cmnd[0] != INQUIRY && + fsg->cmnd[0] != REQUEST_SENSE) { + curlun->sense_data = curlun->unit_attention_data; + curlun->unit_attention_data = SS_NO_SENSE; + return -EINVAL; + } + + /* Check that only command bytes listed in the mask are non-zero */ + fsg->cmnd[1] &= 0x1f; // Mask away the LUN + for (i = 1; i < cmnd_size; ++i) { + if (fsg->cmnd[i] && !(mask & (1 << i))) { + if (curlun) + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + } + + /* If the medium isn't mounted and the command needs to access + * it, return an error. */ + if (curlun && !fsg_lun_is_open(curlun) && needs_medium) { + curlun->sense_data = SS_MEDIUM_NOT_PRESENT; + return -EINVAL; + } + + return 0; +} + + +static int do_scsi_command(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc; + int reply = -EINVAL; + int i; + static char unknown[16]; + + dump_cdb(fsg); + + /* Wait for the next buffer to become available for data or status */ + bh = fsg->next_buffhd_to_drain = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + fsg->phase_error = 0; + fsg->short_packet_received = 0; + + down_read(&fsg->filesem); // We're using the backing file + switch (fsg->cmnd[0]) { + + case INQUIRY: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "INQUIRY")) == 0) + reply = do_inquiry(fsg, bh); + break; + + case MODE_SELECT: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, + (1<<1) | (1<<4), 0, + "MODE SELECT(6)")) == 0) + reply = do_mode_select(fsg, bh); + break; + + case MODE_SELECT_10: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, + (1<<1) | (3<<7), 0, + "MODE SELECT(10)")) == 0) + reply = do_mode_select(fsg, bh); + break; + + case MODE_SENSE: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (1<<4), 0, + "MODE SENSE(6)")) == 0) + reply = do_mode_sense(fsg, bh); + break; + + case MODE_SENSE_10: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (1<<1) | (1<<2) | (3<<7), 0, + "MODE SENSE(10)")) == 0) + reply = do_mode_sense(fsg, bh); + break; + + case ALLOW_MEDIUM_REMOVAL: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 6, DATA_DIR_NONE, + (1<<4), 0, + "PREVENT-ALLOW MEDIUM REMOVAL")) == 0) + reply = do_prevent_allow(fsg); + break; + + case READ_6: + i = fsg->cmnd[4]; + fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (7<<1) | (1<<4), 1, + "READ(6)")) == 0) + reply = do_read(fsg); + break; + + case READ_10: + fsg->data_size_from_cmnd = + get_unaligned_be16(&fsg->cmnd[7]) << 9; + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "READ(10)")) == 0) + reply = do_read(fsg); + break; + + case READ_12: + fsg->data_size_from_cmnd = + get_unaligned_be32(&fsg->cmnd[6]) << 9; + if ((reply = check_command(fsg, 12, DATA_DIR_TO_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "READ(12)")) == 0) + reply = do_read(fsg); + break; + + case READ_CAPACITY: + fsg->data_size_from_cmnd = 8; + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (0xf<<2) | (1<<8), 1, + "READ CAPACITY")) == 0) + reply = do_read_capacity(fsg, bh); + break; + + case READ_HEADER: + if (!mod_data.cdrom) + goto unknown_cmnd; + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER")) == 0) + reply = do_read_header(fsg, bh); + break; + + case READ_TOC: + if (!mod_data.cdrom) + goto unknown_cmnd; + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (7<<6) | (1<<1), 1, + "READ TOC")) == 0) + reply = do_read_toc(fsg, bh); + break; + + case READ_FORMAT_CAPACITIES: + fsg->data_size_from_cmnd = get_unaligned_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (3<<7), 1, + "READ FORMAT CAPACITIES")) == 0) + reply = do_read_format_capacities(fsg, bh); + break; + + case REQUEST_SENSE: + fsg->data_size_from_cmnd = fsg->cmnd[4]; + if ((reply = check_command(fsg, 6, DATA_DIR_TO_HOST, + (1<<4), 0, + "REQUEST SENSE")) == 0) + reply = do_request_sense(fsg, bh); + break; + + case START_STOP: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 6, DATA_DIR_NONE, + (1<<1) | (1<<4), 0, + "START-STOP UNIT")) == 0) + reply = do_start_stop(fsg); + break; + + case SYNCHRONIZE_CACHE: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 10, DATA_DIR_NONE, + (0xf<<2) | (3<<7), 1, + "SYNCHRONIZE CACHE")) == 0) + reply = do_synchronize_cache(fsg); + break; + + case TEST_UNIT_READY: + fsg->data_size_from_cmnd = 0; + reply = check_command(fsg, 6, DATA_DIR_NONE, + 0, 1, + "TEST UNIT READY"); + break; + + /* Although optional, this command is used by MS-Windows. We + * support a minimal version: BytChk must be 0. */ + case VERIFY: + fsg->data_size_from_cmnd = 0; + if ((reply = check_command(fsg, 10, DATA_DIR_NONE, + (1<<1) | (0xf<<2) | (3<<7), 1, + "VERIFY")) == 0) + reply = do_verify(fsg); + break; + + case WRITE_6: + i = fsg->cmnd[4]; + fsg->data_size_from_cmnd = (i == 0 ? 256 : i) << 9; + if ((reply = check_command(fsg, 6, DATA_DIR_FROM_HOST, + (7<<1) | (1<<4), 1, + "WRITE(6)")) == 0) + reply = do_write(fsg); + break; + + case WRITE_10: + fsg->data_size_from_cmnd = + get_unaligned_be16(&fsg->cmnd[7]) << 9; + if ((reply = check_command(fsg, 10, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (3<<7), 1, + "WRITE(10)")) == 0) + reply = do_write(fsg); + break; + + case WRITE_12: + fsg->data_size_from_cmnd = + get_unaligned_be32(&fsg->cmnd[6]) << 9; + if ((reply = check_command(fsg, 12, DATA_DIR_FROM_HOST, + (1<<1) | (0xf<<2) | (0xf<<6), 1, + "WRITE(12)")) == 0) + reply = do_write(fsg); + break; + + /* Some mandatory commands that we recognize but don't implement. + * They don't mean much in this setting. It's left as an exercise + * for anyone interested to implement RESERVE and RELEASE in terms + * of Posix locks. */ + case FORMAT_UNIT: + case RELEASE: + case RESERVE: + case SEND_DIAGNOSTIC: + // Fall through + + default: + unknown_cmnd: + fsg->data_size_from_cmnd = 0; + sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]); + if ((reply = check_command(fsg, fsg->cmnd_size, + DATA_DIR_UNKNOWN, 0xff, 0, unknown)) == 0) { + fsg->curlun->sense_data = SS_INVALID_COMMAND; + reply = -EINVAL; + } + break; + } + up_read(&fsg->filesem); + + if (reply == -EINTR || signal_pending(current)) + return -EINTR; + + /* Set up the single reply buffer for finish_reply() */ + if (reply == -EINVAL) + reply = 0; // Error reply length + if (reply >= 0 && fsg->data_dir == DATA_DIR_TO_HOST) { + reply = min((u32) reply, fsg->data_size_from_cmnd); + bh->inreq->length = reply; + bh->state = BUF_STATE_FULL; + fsg->residue -= reply; + } // Otherwise it's already set + + return 0; +} + + +/*-------------------------------------------------------------------------*/ + +static int received_cbw(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct usb_request *req = bh->outreq; + struct fsg_bulk_cb_wrap *cbw = req->buf; + + /* Was this a real packet? Should it be ignored? */ + if (req->status || test_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + return -EINVAL; + + /* Is the CBW valid? */ + if (req->actual != USB_BULK_CB_WRAP_LEN || + cbw->Signature != cpu_to_le32( + USB_BULK_CB_SIG)) { + DBG(fsg, "invalid CBW: len %u sig 0x%x\n", + req->actual, + le32_to_cpu(cbw->Signature)); + + /* The Bulk-only spec says we MUST stall the IN endpoint + * (6.6.1), so it's unavoidable. It also says we must + * retain this state until the next reset, but there's + * no way to tell the controller driver it should ignore + * Clear-Feature(HALT) requests. + * + * We aren't required to halt the OUT endpoint; instead + * we can simply accept and discard any data received + * until the next reset. */ + wedge_bulk_in_endpoint(fsg); + set_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + return -EINVAL; + } + + /* Is the CBW meaningful? */ + if (cbw->Lun >= FSG_MAX_LUNS || cbw->Flags & ~USB_BULK_IN_FLAG || + cbw->Length <= 0 || cbw->Length > MAX_COMMAND_SIZE) { + DBG(fsg, "non-meaningful CBW: lun = %u, flags = 0x%x, " + "cmdlen %u\n", + cbw->Lun, cbw->Flags, cbw->Length); + + /* We can do anything we want here, so let's stall the + * bulk pipes if we are allowed to. */ + if (mod_data.can_stall) { + fsg_set_halt(fsg, fsg->bulk_out); + halt_bulk_in_endpoint(fsg); + } + return -EINVAL; + } + + /* Save the command for later */ + fsg->cmnd_size = cbw->Length; + memcpy(fsg->cmnd, cbw->CDB, fsg->cmnd_size); + if (cbw->Flags & USB_BULK_IN_FLAG) + fsg->data_dir = DATA_DIR_TO_HOST; + else + fsg->data_dir = DATA_DIR_FROM_HOST; + fsg->data_size = le32_to_cpu(cbw->DataTransferLength); + if (fsg->data_size == 0) + fsg->data_dir = DATA_DIR_NONE; + fsg->lun = cbw->Lun; + fsg->tag = cbw->Tag; + return 0; +} + + +static int get_next_command(struct fsg_dev *fsg) +{ + struct fsg_buffhd *bh; + int rc = 0; + + if (transport_is_bbb()) { + + /* Wait for the next buffer to become available */ + bh = fsg->next_buffhd_to_fill; + while (bh->state != BUF_STATE_EMPTY) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* Queue a request to read a Bulk-only CBW */ + set_bulk_out_req_length(fsg, bh, USB_BULK_CB_WRAP_LEN); + bh->outreq->short_not_ok = 1; + start_transfer(fsg, fsg->bulk_out, bh->outreq, + &bh->outreq_busy, &bh->state); + + /* We will drain the buffer in software, which means we + * can reuse it for the next filling. No need to advance + * next_buffhd_to_fill. */ + + /* Wait for the CBW to arrive */ + while (bh->state != BUF_STATE_FULL) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + smp_rmb(); + rc = received_cbw(fsg, bh); + bh->state = BUF_STATE_EMPTY; + + } else { // USB_PR_CB or USB_PR_CBI + + /* Wait for the next command to arrive */ + while (fsg->cbbuf_cmnd_size == 0) { + rc = sleep_thread(fsg); + if (rc) + return rc; + } + + /* Is the previous status interrupt request still busy? + * The host is allowed to skip reading the status, + * so we must cancel it. */ + if (fsg->intreq_busy) + usb_ep_dequeue(fsg->intr_in, fsg->intreq); + + /* Copy the command and mark the buffer empty */ + fsg->data_dir = DATA_DIR_UNKNOWN; + spin_lock_irq(&fsg->lock); + fsg->cmnd_size = fsg->cbbuf_cmnd_size; + memcpy(fsg->cmnd, fsg->cbbuf_cmnd, fsg->cmnd_size); + fsg->cbbuf_cmnd_size = 0; + spin_unlock_irq(&fsg->lock); + } + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static int enable_endpoint(struct fsg_dev *fsg, struct usb_ep *ep, + const struct usb_endpoint_descriptor *d) +{ + int rc; + + ep->driver_data = fsg; + rc = usb_ep_enable(ep, d); + if (rc) + ERROR(fsg, "can't enable %s, result %d\n", ep->name, rc); + return rc; +} + +static int alloc_request(struct fsg_dev *fsg, struct usb_ep *ep, + struct usb_request **preq) +{ + *preq = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (*preq) + return 0; + ERROR(fsg, "can't allocate request for %s\n", ep->name); + return -ENOMEM; +} + +/* + * Reset interface setting and re-init endpoint state (toggle etc). + * Call with altsetting < 0 to disable the interface. The only other + * available altsetting is 0, which enables the interface. + */ +static int do_set_interface(struct fsg_dev *fsg, int altsetting) +{ + int rc = 0; + int i; + const struct usb_endpoint_descriptor *d; + + if (fsg->running) + DBG(fsg, "reset interface\n"); + +reset: + /* Deallocate the requests */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + if (bh->inreq) { + usb_ep_free_request(fsg->bulk_in, bh->inreq); + bh->inreq = NULL; + } + if (bh->outreq) { + usb_ep_free_request(fsg->bulk_out, bh->outreq); + bh->outreq = NULL; + } + } + if (fsg->intreq) { + usb_ep_free_request(fsg->intr_in, fsg->intreq); + fsg->intreq = NULL; + } + + /* Disable the endpoints */ + if (fsg->bulk_in_enabled) { + usb_ep_disable(fsg->bulk_in); + fsg->bulk_in_enabled = 0; + } + if (fsg->bulk_out_enabled) { + usb_ep_disable(fsg->bulk_out); + fsg->bulk_out_enabled = 0; + } + if (fsg->intr_in_enabled) { + usb_ep_disable(fsg->intr_in); + fsg->intr_in_enabled = 0; + } + + fsg->running = 0; + if (altsetting < 0 || rc != 0) + return rc; + + DBG(fsg, "set interface %d\n", altsetting); + + /* Enable the endpoints */ + d = fsg_ep_desc(fsg->gadget, + &fsg_fs_bulk_in_desc, &fsg_hs_bulk_in_desc); + if ((rc = enable_endpoint(fsg, fsg->bulk_in, d)) != 0) + goto reset; + fsg->bulk_in_enabled = 1; + + d = fsg_ep_desc(fsg->gadget, + &fsg_fs_bulk_out_desc, &fsg_hs_bulk_out_desc); + if ((rc = enable_endpoint(fsg, fsg->bulk_out, d)) != 0) + goto reset; + fsg->bulk_out_enabled = 1; + fsg->bulk_out_maxpacket = le16_to_cpu(d->wMaxPacketSize); + clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags); + + if (transport_is_cbi()) { + d = fsg_ep_desc(fsg->gadget, + &fsg_fs_intr_in_desc, &fsg_hs_intr_in_desc); + if ((rc = enable_endpoint(fsg, fsg->intr_in, d)) != 0) + goto reset; + fsg->intr_in_enabled = 1; + } + + /* Allocate the requests */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + if ((rc = alloc_request(fsg, fsg->bulk_in, &bh->inreq)) != 0) + goto reset; + if ((rc = alloc_request(fsg, fsg->bulk_out, &bh->outreq)) != 0) + goto reset; + bh->inreq->buf = bh->outreq->buf = bh->buf; + bh->inreq->context = bh->outreq->context = bh; + bh->inreq->complete = bulk_in_complete; + bh->outreq->complete = bulk_out_complete; + } + if (transport_is_cbi()) { + if ((rc = alloc_request(fsg, fsg->intr_in, &fsg->intreq)) != 0) + goto reset; + fsg->intreq->complete = intr_in_complete; + } + + fsg->running = 1; + for (i = 0; i < fsg->nluns; ++i) + fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; + return rc; +} + + +/* + * Change our operational configuration. This code must agree with the code + * that returns config descriptors, and with interface altsetting code. + * + * It's also responsible for power management interactions. Some + * configurations might not work with our current power sources. + * For now we just assume the gadget is always self-powered. + */ +static int do_set_config(struct fsg_dev *fsg, u8 new_config) +{ + int rc = 0; + + /* Disable the single interface */ + if (fsg->config != 0) { + DBG(fsg, "reset config\n"); + fsg->config = 0; + rc = do_set_interface(fsg, -1); + } + + /* Enable the interface */ + if (new_config != 0) { + fsg->config = new_config; + if ((rc = do_set_interface(fsg, 0)) != 0) + fsg->config = 0; // Reset on errors + else { + char *speed; + + switch (fsg->gadget->speed) { + case USB_SPEED_LOW: speed = "low"; break; + case USB_SPEED_FULL: speed = "full"; break; + case USB_SPEED_HIGH: speed = "high"; break; + default: speed = "?"; break; + } + INFO(fsg, "%s speed config #%d\n", speed, fsg->config); + } + } + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void handle_exception(struct fsg_dev *fsg) +{ + siginfo_t info; + int sig; + int i; + int num_active; + struct fsg_buffhd *bh; + enum fsg_state old_state; + u8 new_config; + struct fsg_lun *curlun; + unsigned int exception_req_tag; + int rc; + + /* Clear the existing signals. Anything but SIGUSR1 is converted + * into a high-priority EXIT exception. */ + for (;;) { + sig = dequeue_signal_lock(current, ¤t->blocked, &info); + if (!sig) + break; + if (sig != SIGUSR1) { + if (fsg->state < FSG_STATE_EXIT) + DBG(fsg, "Main thread exiting on signal\n"); + raise_exception(fsg, FSG_STATE_EXIT); + } + } + + /* Cancel all the pending transfers */ + if (fsg->intreq_busy) + usb_ep_dequeue(fsg->intr_in, fsg->intreq); + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &fsg->buffhds[i]; + if (bh->inreq_busy) + usb_ep_dequeue(fsg->bulk_in, bh->inreq); + if (bh->outreq_busy) + usb_ep_dequeue(fsg->bulk_out, bh->outreq); + } + + /* Wait until everything is idle */ + for (;;) { + num_active = fsg->intreq_busy; + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &fsg->buffhds[i]; + num_active += bh->inreq_busy + bh->outreq_busy; + } + if (num_active == 0) + break; + if (sleep_thread(fsg)) + return; + } + + /* Clear out the controller's fifos */ + if (fsg->bulk_in_enabled) + usb_ep_fifo_flush(fsg->bulk_in); + if (fsg->bulk_out_enabled) + usb_ep_fifo_flush(fsg->bulk_out); + if (fsg->intr_in_enabled) + usb_ep_fifo_flush(fsg->intr_in); + + /* Reset the I/O buffer states and pointers, the SCSI + * state, and the exception. Then invoke the handler. */ + spin_lock_irq(&fsg->lock); + + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + bh = &fsg->buffhds[i]; + bh->state = BUF_STATE_EMPTY; + } + fsg->next_buffhd_to_fill = fsg->next_buffhd_to_drain = + &fsg->buffhds[0]; + + exception_req_tag = fsg->exception_req_tag; + new_config = fsg->new_config; + old_state = fsg->state; + + if (old_state == FSG_STATE_ABORT_BULK_OUT) + fsg->state = FSG_STATE_STATUS_PHASE; + else { + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + curlun->prevent_medium_removal = 0; + curlun->sense_data = curlun->unit_attention_data = + SS_NO_SENSE; + curlun->sense_data_info = 0; + curlun->info_valid = 0; + } + fsg->state = FSG_STATE_IDLE; + } + spin_unlock_irq(&fsg->lock); + + /* Carry out any extra actions required for the exception */ + switch (old_state) { + default: + break; + + case FSG_STATE_ABORT_BULK_OUT: + send_status(fsg); + spin_lock_irq(&fsg->lock); + if (fsg->state == FSG_STATE_STATUS_PHASE) + fsg->state = FSG_STATE_IDLE; + spin_unlock_irq(&fsg->lock); + break; + + case FSG_STATE_RESET: + /* In case we were forced against our will to halt a + * bulk endpoint, clear the halt now. (The SuperH UDC + * requires this.) */ + if (test_and_clear_bit(IGNORE_BULK_OUT, &fsg->atomic_bitflags)) + usb_ep_clear_halt(fsg->bulk_in); + + if (transport_is_bbb()) { + if (fsg->ep0_req_tag == exception_req_tag) + ep0_queue(fsg); // Complete the status stage + + } else if (transport_is_cbi()) + send_status(fsg); // Status by interrupt pipe + + /* Technically this should go here, but it would only be + * a waste of time. Ditto for the INTERFACE_CHANGE and + * CONFIG_CHANGE cases. */ + // for (i = 0; i < fsg->nluns; ++i) + // fsg->luns[i].unit_attention_data = SS_RESET_OCCURRED; + break; + + case FSG_STATE_INTERFACE_CHANGE: + rc = do_set_interface(fsg, 0); + if (fsg->ep0_req_tag != exception_req_tag) + break; + if (rc != 0) // STALL on errors + fsg_set_halt(fsg, fsg->ep0); + else // Complete the status stage + ep0_queue(fsg); + break; + + case FSG_STATE_CONFIG_CHANGE: + rc = do_set_config(fsg, new_config); + if (fsg->ep0_req_tag != exception_req_tag) + break; + if (rc != 0) // STALL on errors + fsg_set_halt(fsg, fsg->ep0); + else // Complete the status stage + ep0_queue(fsg); + break; + + case FSG_STATE_DISCONNECT: + for (i = 0; i < fsg->nluns; ++i) + fsg_lun_fsync_sub(fsg->luns + i); + do_set_config(fsg, 0); // Unconfigured state + break; + + case FSG_STATE_EXIT: + case FSG_STATE_TERMINATED: + do_set_config(fsg, 0); // Free resources + spin_lock_irq(&fsg->lock); + fsg->state = FSG_STATE_TERMINATED; // Stop the thread + spin_unlock_irq(&fsg->lock); + break; + } +} + + +/*-------------------------------------------------------------------------*/ + +static int fsg_main_thread(void *fsg_) +{ + struct fsg_dev *fsg = fsg_; + + /* Allow the thread to be killed by a signal, but set the signal mask + * to block everything but INT, TERM, KILL, and USR1. */ + allow_signal(SIGINT); + allow_signal(SIGTERM); + allow_signal(SIGKILL); + allow_signal(SIGUSR1); + + /* Allow the thread to be frozen */ + set_freezable(); + + /* Arrange for userspace references to be interpreted as kernel + * pointers. That way we can pass a kernel pointer to a routine + * that expects a __user pointer and it will work okay. */ + set_fs(get_ds()); + + /* The main loop */ + while (fsg->state != FSG_STATE_TERMINATED) { + if (exception_in_progress(fsg) || signal_pending(current)) { + handle_exception(fsg); + continue; + } + + if (!fsg->running) { + sleep_thread(fsg); + continue; + } + + if (get_next_command(fsg)) + continue; + + spin_lock_irq(&fsg->lock); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_DATA_PHASE; + spin_unlock_irq(&fsg->lock); + + if (do_scsi_command(fsg) || finish_reply(fsg)) + continue; + + spin_lock_irq(&fsg->lock); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_STATUS_PHASE; + spin_unlock_irq(&fsg->lock); + + if (send_status(fsg)) + continue; + + spin_lock_irq(&fsg->lock); + if (!exception_in_progress(fsg)) + fsg->state = FSG_STATE_IDLE; + spin_unlock_irq(&fsg->lock); + } + + spin_lock_irq(&fsg->lock); + fsg->thread_task = NULL; + spin_unlock_irq(&fsg->lock); + + /* If we are exiting because of a signal, unregister the + * gadget driver. */ + if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) + usb_gadget_unregister_driver(&fsg_driver); + + /* Let the unbind and cleanup routines know the thread has exited */ + complete_and_exit(&fsg->thread_notifier, 0); +} + + +/*-------------------------------------------------------------------------*/ + + +/* The write permissions and store_xxx pointers are set in fsg_bind() */ +static DEVICE_ATTR(ro, 0444, fsg_show_ro, NULL); +static DEVICE_ATTR(nofua, 0644, fsg_show_nofua, NULL); +static DEVICE_ATTR(file, 0444, fsg_show_file, NULL); + + +/*-------------------------------------------------------------------------*/ + +static void fsg_release(struct kref *ref) +{ + struct fsg_dev *fsg = container_of(ref, struct fsg_dev, ref); + + kfree(fsg->luns); + kfree(fsg); +} + +static void lun_release(struct device *dev) +{ + struct rw_semaphore *filesem = dev_get_drvdata(dev); + struct fsg_dev *fsg = + container_of(filesem, struct fsg_dev, filesem); + + kref_put(&fsg->ref, fsg_release); +} + +static void /* __init_or_exit */ fsg_unbind(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + int i; + struct fsg_lun *curlun; + struct usb_request *req = fsg->ep0req; + + DBG(fsg, "unbind\n"); + clear_bit(REGISTERED, &fsg->atomic_bitflags); + + /* Unregister the sysfs attribute files and the LUNs */ + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + if (curlun->registered) { + device_remove_file(&curlun->dev, &dev_attr_nofua); + device_remove_file(&curlun->dev, &dev_attr_ro); + device_remove_file(&curlun->dev, &dev_attr_file); + fsg_lun_close(curlun); + device_unregister(&curlun->dev); + curlun->registered = 0; + } + } + + /* If the thread isn't already dead, tell it to exit now */ + if (fsg->state != FSG_STATE_TERMINATED) { + raise_exception(fsg, FSG_STATE_EXIT); + wait_for_completion(&fsg->thread_notifier); + + /* The cleanup routine waits for this completion also */ + complete(&fsg->thread_notifier); + } + + /* Free the data buffers */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) + kfree(fsg->buffhds[i].buf); + + /* Free the request and buffer for endpoint 0 */ + if (req) { + kfree(req->buf); + usb_ep_free_request(fsg->ep0, req); + } + + set_gadget_data(gadget, NULL); +} + + +static int __init check_parameters(struct fsg_dev *fsg) +{ + int prot; + int gcnum; + + /* Store the default values */ + mod_data.transport_type = USB_PR_BULK; + mod_data.transport_name = "Bulk-only"; + mod_data.protocol_type = USB_SC_SCSI; + mod_data.protocol_name = "Transparent SCSI"; + + /* Some peripheral controllers are known not to be able to + * halt bulk endpoints correctly. If one of them is present, + * disable stalls. + */ + if (gadget_is_at91(fsg->gadget)) + mod_data.can_stall = 0; + + if (mod_data.release == 0xffff) { // Parameter wasn't set + gcnum = usb_gadget_controller_number(fsg->gadget); + if (gcnum >= 0) + mod_data.release = 0x0300 + gcnum; + else { + WARNING(fsg, "controller '%s' not recognized\n", + fsg->gadget->name); + mod_data.release = 0x0399; + } + } + + prot = simple_strtol(mod_data.protocol_parm, NULL, 0); + +#ifdef CONFIG_USB_FILE_STORAGE_TEST + if (strnicmp(mod_data.transport_parm, "BBB", 10) == 0) { + ; // Use default setting + } else if (strnicmp(mod_data.transport_parm, "CB", 10) == 0) { + mod_data.transport_type = USB_PR_CB; + mod_data.transport_name = "Control-Bulk"; + } else if (strnicmp(mod_data.transport_parm, "CBI", 10) == 0) { + mod_data.transport_type = USB_PR_CBI; + mod_data.transport_name = "Control-Bulk-Interrupt"; + } else { + ERROR(fsg, "invalid transport: %s\n", mod_data.transport_parm); + return -EINVAL; + } + + if (strnicmp(mod_data.protocol_parm, "SCSI", 10) == 0 || + prot == USB_SC_SCSI) { + ; // Use default setting + } else if (strnicmp(mod_data.protocol_parm, "RBC", 10) == 0 || + prot == USB_SC_RBC) { + mod_data.protocol_type = USB_SC_RBC; + mod_data.protocol_name = "RBC"; + } else if (strnicmp(mod_data.protocol_parm, "8020", 4) == 0 || + strnicmp(mod_data.protocol_parm, "ATAPI", 10) == 0 || + prot == USB_SC_8020) { + mod_data.protocol_type = USB_SC_8020; + mod_data.protocol_name = "8020i (ATAPI)"; + } else if (strnicmp(mod_data.protocol_parm, "QIC", 3) == 0 || + prot == USB_SC_QIC) { + mod_data.protocol_type = USB_SC_QIC; + mod_data.protocol_name = "QIC-157"; + } else if (strnicmp(mod_data.protocol_parm, "UFI", 10) == 0 || + prot == USB_SC_UFI) { + mod_data.protocol_type = USB_SC_UFI; + mod_data.protocol_name = "UFI"; + } else if (strnicmp(mod_data.protocol_parm, "8070", 4) == 0 || + prot == USB_SC_8070) { + mod_data.protocol_type = USB_SC_8070; + mod_data.protocol_name = "8070i"; + } else { + ERROR(fsg, "invalid protocol: %s\n", mod_data.protocol_parm); + return -EINVAL; + } + + mod_data.buflen &= PAGE_CACHE_MASK; + if (mod_data.buflen <= 0) { + ERROR(fsg, "invalid buflen\n"); + return -ETOOSMALL; + } + +#endif /* CONFIG_USB_FILE_STORAGE_TEST */ + + /* Serial string handling. + * On a real device, the serial string would be loaded + * from permanent storage. */ + if (mod_data.serial) { + const char *ch; + unsigned len = 0; + + /* Sanity check : + * The CB[I] specification limits the serial string to + * 12 uppercase hexadecimal characters. + * BBB need at least 12 uppercase hexadecimal characters, + * with a maximum of 126. */ + for (ch = mod_data.serial; *ch; ++ch) { + ++len; + if ((*ch < '0' || *ch > '9') && + (*ch < 'A' || *ch > 'F')) { /* not uppercase hex */ + WARNING(fsg, + "Invalid serial string character: %c\n", + *ch); + goto no_serial; + } + } + if (len > 126 || + (mod_data.transport_type == USB_PR_BULK && len < 12) || + (mod_data.transport_type != USB_PR_BULK && len > 12)) { + WARNING(fsg, "Invalid serial string length!\n"); + goto no_serial; + } + fsg_strings[FSG_STRING_SERIAL - 1].s = mod_data.serial; + } else { + WARNING(fsg, "No serial-number string provided!\n"); + no_serial: + device_desc.iSerialNumber = 0; + } + + return 0; +} + + +static int __init fsg_bind(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = the_fsg; + int rc; + int i; + struct fsg_lun *curlun; + struct usb_ep *ep; + struct usb_request *req; + char *pathbuf, *p; + + fsg->gadget = gadget; + set_gadget_data(gadget, fsg); + fsg->ep0 = gadget->ep0; + fsg->ep0->driver_data = fsg; + + if ((rc = check_parameters(fsg)) != 0) + goto out; + + if (mod_data.removable) { // Enable the store_xxx attributes + dev_attr_file.attr.mode = 0644; + dev_attr_file.store = fsg_store_file; + if (!mod_data.cdrom) { + dev_attr_ro.attr.mode = 0644; + dev_attr_ro.store = fsg_store_ro; + } + } + + /* Only for removable media? */ + dev_attr_nofua.attr.mode = 0644; + dev_attr_nofua.store = fsg_store_nofua; + + /* Find out how many LUNs there should be */ + i = mod_data.nluns; + if (i == 0) + i = max(mod_data.num_filenames, 1u); + if (i > FSG_MAX_LUNS) { + ERROR(fsg, "invalid number of LUNs: %d\n", i); + rc = -EINVAL; + goto out; + } + + /* Create the LUNs, open their backing files, and register the + * LUN devices in sysfs. */ + fsg->luns = kzalloc(i * sizeof(struct fsg_lun), GFP_KERNEL); + if (!fsg->luns) { + rc = -ENOMEM; + goto out; + } + fsg->nluns = i; + + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + curlun->cdrom = !!mod_data.cdrom; + curlun->ro = mod_data.cdrom || mod_data.ro[i]; + curlun->initially_ro = curlun->ro; + curlun->removable = mod_data.removable; + curlun->nofua = mod_data.nofua[i]; + curlun->dev.release = lun_release; + curlun->dev.parent = &gadget->dev; + curlun->dev.driver = &fsg_driver.driver; + dev_set_drvdata(&curlun->dev, &fsg->filesem); + dev_set_name(&curlun->dev,"%s-lun%d", + dev_name(&gadget->dev), i); + + kref_get(&fsg->ref); + rc = device_register(&curlun->dev); + if (rc) { + INFO(fsg, "failed to register LUN%d: %d\n", i, rc); + put_device(&curlun->dev); + goto out; + } + curlun->registered = 1; + + rc = device_create_file(&curlun->dev, &dev_attr_ro); + if (rc) + goto out; + rc = device_create_file(&curlun->dev, &dev_attr_nofua); + if (rc) + goto out; + rc = device_create_file(&curlun->dev, &dev_attr_file); + if (rc) + goto out; + + if (mod_data.file[i] && *mod_data.file[i]) { + rc = fsg_lun_open(curlun, mod_data.file[i]); + if (rc) + goto out; + } else if (!mod_data.removable) { + ERROR(fsg, "no file given for LUN%d\n", i); + rc = -EINVAL; + goto out; + } + } + + /* Find all the endpoints we will use */ + usb_ep_autoconfig_reset(gadget); + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg; // claim the endpoint + fsg->bulk_in = ep; + + ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg; // claim the endpoint + fsg->bulk_out = ep; + + if (transport_is_cbi()) { + ep = usb_ep_autoconfig(gadget, &fsg_fs_intr_in_desc); + if (!ep) + goto autoconf_fail; + ep->driver_data = fsg; // claim the endpoint + fsg->intr_in = ep; + } + + /* Fix up the descriptors */ + device_desc.bMaxPacketSize0 = fsg->ep0->maxpacket; + device_desc.idVendor = cpu_to_le16(mod_data.vendor); + device_desc.idProduct = cpu_to_le16(mod_data.product); + device_desc.bcdDevice = cpu_to_le16(mod_data.release); + + i = (transport_is_cbi() ? 3 : 2); // Number of endpoints + fsg_intf_desc.bNumEndpoints = i; + fsg_intf_desc.bInterfaceSubClass = mod_data.protocol_type; + fsg_intf_desc.bInterfaceProtocol = mod_data.transport_type; + fsg_fs_function[i + FSG_FS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + if (gadget_is_dualspeed(gadget)) { + fsg_hs_function[i + FSG_HS_FUNCTION_PRE_EP_ENTRIES] = NULL; + + /* Assume ep0 uses the same maxpacket value for both speeds */ + dev_qualifier.bMaxPacketSize0 = fsg->ep0->maxpacket; + + /* Assume endpoint addresses are the same for both speeds */ + fsg_hs_bulk_in_desc.bEndpointAddress = + fsg_fs_bulk_in_desc.bEndpointAddress; + fsg_hs_bulk_out_desc.bEndpointAddress = + fsg_fs_bulk_out_desc.bEndpointAddress; + fsg_hs_intr_in_desc.bEndpointAddress = + fsg_fs_intr_in_desc.bEndpointAddress; + } + + if (gadget_is_otg(gadget)) + fsg_otg_desc.bmAttributes |= USB_OTG_HNP; + + rc = -ENOMEM; + + /* Allocate the request and buffer for endpoint 0 */ + fsg->ep0req = req = usb_ep_alloc_request(fsg->ep0, GFP_KERNEL); + if (!req) + goto out; + req->buf = kmalloc(EP0_BUFSIZE, GFP_KERNEL); + if (!req->buf) + goto out; + req->complete = ep0_complete; + + /* Allocate the data buffers */ + for (i = 0; i < FSG_NUM_BUFFERS; ++i) { + struct fsg_buffhd *bh = &fsg->buffhds[i]; + + /* Allocate for the bulk-in endpoint. We assume that + * the buffer will also work with the bulk-out (and + * interrupt-in) endpoint. */ + bh->buf = kmalloc(mod_data.buflen, GFP_KERNEL); + if (!bh->buf) + goto out; + bh->next = bh + 1; + } + fsg->buffhds[FSG_NUM_BUFFERS - 1].next = &fsg->buffhds[0]; + + /* This should reflect the actual gadget power source */ + usb_gadget_set_selfpowered(gadget); + + snprintf(fsg_string_manufacturer, sizeof fsg_string_manufacturer, + "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); + + fsg->thread_task = kthread_create(fsg_main_thread, fsg, + "file-storage-gadget"); + if (IS_ERR(fsg->thread_task)) { + rc = PTR_ERR(fsg->thread_task); + goto out; + } + + INFO(fsg, DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + INFO(fsg, "Number of LUNs=%d\n", fsg->nluns); + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + for (i = 0; i < fsg->nluns; ++i) { + curlun = &fsg->luns[i]; + if (fsg_lun_is_open(curlun)) { + p = NULL; + if (pathbuf) { + p = d_path(&curlun->filp->f_path, + pathbuf, PATH_MAX); + if (IS_ERR(p)) + p = NULL; + } + LINFO(curlun, "ro=%d, nofua=%d, file: %s\n", + curlun->ro, curlun->nofua, (p ? p : "(error)")); + } + } + kfree(pathbuf); + + DBG(fsg, "transport=%s (x%02x)\n", + mod_data.transport_name, mod_data.transport_type); + DBG(fsg, "protocol=%s (x%02x)\n", + mod_data.protocol_name, mod_data.protocol_type); + DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n", + mod_data.vendor, mod_data.product, mod_data.release); + DBG(fsg, "removable=%d, stall=%d, cdrom=%d, buflen=%u\n", + mod_data.removable, mod_data.can_stall, + mod_data.cdrom, mod_data.buflen); + DBG(fsg, "I/O thread pid: %d\n", task_pid_nr(fsg->thread_task)); + + set_bit(REGISTERED, &fsg->atomic_bitflags); + + /* Tell the thread to start working */ + wake_up_process(fsg->thread_task); + return 0; + +autoconf_fail: + ERROR(fsg, "unable to autoconfigure all endpoints\n"); + rc = -ENOTSUPP; + +out: + fsg->state = FSG_STATE_TERMINATED; // The thread is dead + fsg_unbind(gadget); + complete(&fsg->thread_notifier); + return rc; +} + + +/*-------------------------------------------------------------------------*/ + +static void fsg_suspend(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + + DBG(fsg, "suspend\n"); + set_bit(SUSPENDED, &fsg->atomic_bitflags); +} + +static void fsg_resume(struct usb_gadget *gadget) +{ + struct fsg_dev *fsg = get_gadget_data(gadget); + + DBG(fsg, "resume\n"); + clear_bit(SUSPENDED, &fsg->atomic_bitflags); +} + + +/*-------------------------------------------------------------------------*/ + +static struct usb_gadget_driver fsg_driver = { +#ifdef CONFIG_USB_GADGET_DUALSPEED + .speed = USB_SPEED_HIGH, +#else + .speed = USB_SPEED_FULL, +#endif + .function = (char *) fsg_string_product, + .unbind = fsg_unbind, + .disconnect = fsg_disconnect, + .setup = fsg_setup, + .suspend = fsg_suspend, + .resume = fsg_resume, + + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + // .release = ... + // .suspend = ... + // .resume = ... + }, +}; + + +static int __init fsg_alloc(void) +{ + struct fsg_dev *fsg; + + fsg = kzalloc(sizeof *fsg, GFP_KERNEL); + if (!fsg) + return -ENOMEM; + spin_lock_init(&fsg->lock); + init_rwsem(&fsg->filesem); + kref_init(&fsg->ref); + init_completion(&fsg->thread_notifier); + + the_fsg = fsg; + return 0; +} + + +static int __init fsg_init(void) +{ + int rc; + struct fsg_dev *fsg; + + if ((rc = fsg_alloc()) != 0) + return rc; + fsg = the_fsg; + if ((rc = usb_gadget_probe_driver(&fsg_driver, fsg_bind)) != 0) + kref_put(&fsg->ref, fsg_release); + return rc; +} +module_init(fsg_init); + + +static void __exit fsg_cleanup(void) +{ + struct fsg_dev *fsg = the_fsg; + + /* Unregister the driver iff the thread hasn't already done so */ + if (test_and_clear_bit(REGISTERED, &fsg->atomic_bitflags)) + usb_gadget_unregister_driver(&fsg_driver); + + /* Wait for the thread to finish up */ + wait_for_completion(&fsg->thread_notifier); + + kref_put(&fsg->ref, fsg_release); +} +module_exit(fsg_cleanup); diff --git a/drivers/usb/gadget/gadget_gbhc/gadget_chips.h b/drivers/usb/gadget/gadget_gbhc/gadget_chips.h new file mode 100644 index 0000000..2db7cb2 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/gadget_chips.h @@ -0,0 +1,265 @@ +/* + * USB device controllers have lots of quirks. Use these macros in + * gadget drivers or other code that needs to deal with them, and which + * autoconfigures instead of using early binding to the hardware. + * + * This SHOULD eventually work like the ARM mach_is_*() stuff, driven by + * some config file that gets updated as new hardware is supported. + * (And avoiding all runtime comparisons in typical one-choice configs!) + * + * NOTE: some of these controller drivers may not be available yet. + * Some are available on 2.4 kernels; several are available, but not + * yet pushed in the 2.6 mainline tree. + */ + +#ifndef __GADGET_CHIPS_H +#define __GADGET_CHIPS_H + +#ifdef CONFIG_USB_GADGET_NET2280 +#define gadget_is_net2280(g) !strcmp("net2280", (g)->name) +#else +#define gadget_is_net2280(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_AMD5536UDC +#define gadget_is_amd5536udc(g) !strcmp("amd5536udc", (g)->name) +#else +#define gadget_is_amd5536udc(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_DUMMY_HCD +#define gadget_is_dummy(g) !strcmp("dummy_udc", (g)->name) +#else +#define gadget_is_dummy(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_PXA25X +#define gadget_is_pxa(g) !strcmp("pxa25x_udc", (g)->name) +#else +#define gadget_is_pxa(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_GOKU +#define gadget_is_goku(g) !strcmp("goku_udc", (g)->name) +#else +#define gadget_is_goku(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_OMAP +#define gadget_is_omap(g) !strcmp("omap_udc", (g)->name) +#else +#define gadget_is_omap(g) 0 +#endif + +/* various unstable versions available */ +#ifdef CONFIG_USB_GADGET_PXA27X +#define gadget_is_pxa27x(g) !strcmp("pxa27x_udc", (g)->name) +#else +#define gadget_is_pxa27x(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_ATMEL_USBA +#define gadget_is_atmel_usba(g) !strcmp("atmel_usba_udc", (g)->name) +#else +#define gadget_is_atmel_usba(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_S3C2410 +#define gadget_is_s3c2410(g) !strcmp("s3c2410_udc", (g)->name) +#else +#define gadget_is_s3c2410(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_AT91 +#define gadget_is_at91(g) !strcmp("at91_udc", (g)->name) +#else +#define gadget_is_at91(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_IMX +#define gadget_is_imx(g) !strcmp("imx_udc", (g)->name) +#else +#define gadget_is_imx(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_FSL_USB2 +#define gadget_is_fsl_usb2(g) !strcmp("fsl-usb2-udc", (g)->name) +#else +#define gadget_is_fsl_usb2(g) 0 +#endif + +/* Mentor high speed "dual role" controller, in peripheral role */ +#ifdef CONFIG_USB_GADGET_MUSB_HDRC +#define gadget_is_musbhdrc(g) !strcmp("musb-hdrc", (g)->name) +#else +#define gadget_is_musbhdrc(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_LANGWELL +#define gadget_is_langwell(g) (!strcmp("langwell_udc", (g)->name)) +#else +#define gadget_is_langwell(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_M66592 +#define gadget_is_m66592(g) !strcmp("m66592_udc", (g)->name) +#else +#define gadget_is_m66592(g) 0 +#endif + +/* Freescale CPM/QE UDC SUPPORT */ +#ifdef CONFIG_USB_GADGET_FSL_QE +#define gadget_is_fsl_qe(g) !strcmp("fsl_qe_udc", (g)->name) +#else +#define gadget_is_fsl_qe(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_CI13XXX_PCI +#define gadget_is_ci13xxx_pci(g) (!strcmp("ci13xxx_pci", (g)->name)) +#else +#define gadget_is_ci13xxx_pci(g) 0 +#endif + +// CONFIG_USB_GADGET_SX2 +// CONFIG_USB_GADGET_AU1X00 +// ... + +#ifdef CONFIG_USB_GADGET_R8A66597 +#define gadget_is_r8a66597(g) !strcmp("r8a66597_udc", (g)->name) +#else +#define gadget_is_r8a66597(g) 0 +#endif + +#ifdef CONFIG_USB_S3C_HSOTG +#define gadget_is_s3c_hsotg(g) (!strcmp("s3c-hsotg", (g)->name)) +#else +#define gadget_is_s3c_hsotg(g) 0 +#endif + +#ifdef CONFIG_USB_S3C_HSUDC +#define gadget_is_s3c_hsudc(g) (!strcmp("s3c-hsudc", (g)->name)) +#else +#define gadget_is_s3c_hsudc(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_S3C_OTGD +#define gadget_is_s3c(g) !strcmp("s3c-udc", (g)->name) +#else +#define gadget_is_s3c(g) 0 +#endif + +#ifdef CONFIG_USB_EXYNOS_SS_UDC +#define gadget_is_exynos_ss_udc(g) (!strcmp("exynos-ss-udc", (g)->name)) +#else +#define gadget_is_exynos_ss_udc(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_EG20T +#define gadget_is_pch(g) (!strcmp("pch_udc", (g)->name)) +#else +#define gadget_is_pch(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_CI13XXX_MSM +#define gadget_is_ci13xxx_msm(g) (!strcmp("ci13xxx_msm", (g)->name)) +#else +#define gadget_is_ci13xxx_msm(g) 0 +#endif + +#ifdef CONFIG_USB_GADGET_RENESAS_USBHS +#define gadget_is_renesas_usbhs(g) (!strcmp("renesas_usbhs_udc", (g)->name)) +#else +#define gadget_is_renesas_usbhs(g) 0 +#endif + +/** + * usb_gadget_controller_number - support bcdDevice id convention + * @gadget: the controller being driven + * + * Return a 2-digit BCD value associated with the peripheral controller, + * suitable for use as part of a bcdDevice value, or a negative error code. + * + * NOTE: this convention is purely optional, and has no meaning in terms of + * any USB specification. If you want to use a different convention in your + * gadget driver firmware -- maybe a more formal revision ID -- feel free. + * + * Hosts see these bcdDevice numbers, and are allowed (but not encouraged!) + * to change their behavior accordingly. For example it might help avoiding + * some chip bug. + */ +static inline int usb_gadget_controller_number(struct usb_gadget *gadget) +{ + if (gadget_is_net2280(gadget)) + return 0x01; + else if (gadget_is_dummy(gadget)) + return 0x02; + else if (gadget_is_pxa(gadget)) + return 0x03; + else if (gadget_is_goku(gadget)) + return 0x06; + else if (gadget_is_omap(gadget)) + return 0x08; + else if (gadget_is_pxa27x(gadget)) + return 0x11; + else if (gadget_is_s3c2410(gadget)) + return 0x12; + else if (gadget_is_at91(gadget)) + return 0x13; + else if (gadget_is_imx(gadget)) + return 0x14; + else if (gadget_is_musbhdrc(gadget)) + return 0x16; + else if (gadget_is_atmel_usba(gadget)) + return 0x18; + else if (gadget_is_fsl_usb2(gadget)) + return 0x19; + else if (gadget_is_amd5536udc(gadget)) + return 0x20; + else if (gadget_is_m66592(gadget)) + return 0x21; + else if (gadget_is_fsl_qe(gadget)) + return 0x22; + else if (gadget_is_ci13xxx_pci(gadget)) + return 0x23; + else if (gadget_is_langwell(gadget)) + return 0x24; + else if (gadget_is_r8a66597(gadget)) + return 0x25; + else if (gadget_is_s3c_hsotg(gadget)) + return 0x26; + else if (gadget_is_s3c(gadget)) + return 0x26; + else if (gadget_is_pch(gadget)) + return 0x27; + else if (gadget_is_ci13xxx_msm(gadget)) + return 0x28; + else if (gadget_is_renesas_usbhs(gadget)) + return 0x29; + else if (gadget_is_s3c_hsudc(gadget)) + return 0x30; + else if (gadget_is_exynos_ss_udc(gadget)) + return 0x31; + + return -ENOENT; +} + + +/** + * gadget_supports_altsettings - return true if altsettings work + * @gadget: the gadget in question + */ +static inline bool gadget_supports_altsettings(struct usb_gadget *gadget) +{ + /* PXA 21x/25x/26x has no altsettings at all */ + if (gadget_is_pxa(gadget)) + return false; + + /* PXA 27x and 3xx have *broken* altsetting support */ + if (gadget_is_pxa27x(gadget)) + return false; + + /* Everything else is *presumably* fine ... */ + return true; +} + +#endif /* __GADGET_CHIPS_H */ diff --git a/drivers/usb/gadget/gadget_gbhc/inode.c b/drivers/usb/gadget/gadget_gbhc/inode.c new file mode 100644 index 0000000..a56876a --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/inode.c @@ -0,0 +1,2150 @@ +/* + * inode.c -- user mode filesystem api for usb gadget controllers + * + * Copyright (C) 2003-2004 David Brownell + * Copyright (C) 2003 Agilent Technologies + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* #define VERBOSE_DEBUG */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/pagemap.h> +#include <linux/uts.h> +#include <linux/wait.h> +#include <linux/compiler.h> +#include <asm/uaccess.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/poll.h> + +#include <linux/device.h> +#include <linux/moduleparam.h> + +#include <linux/usb/gadgetfs.h> +#include <linux/usb/gadget.h> + + +/* + * The gadgetfs API maps each endpoint to a file descriptor so that you + * can use standard synchronous read/write calls for I/O. There's some + * O_NONBLOCK and O_ASYNC/FASYNC style i/o support. Example usermode + * drivers show how this works in practice. You can also use AIO to + * eliminate I/O gaps between requests, to help when streaming data. + * + * Key parts that must be USB-specific are protocols defining how the + * read/write operations relate to the hardware state machines. There + * are two types of files. One type is for the device, implementing ep0. + * The other type is for each IN or OUT endpoint. In both cases, the + * user mode driver must configure the hardware before using it. + * + * - First, dev_config() is called when /dev/gadget/$CHIP is configured + * (by writing configuration and device descriptors). Afterwards it + * may serve as a source of device events, used to handle all control + * requests other than basic enumeration. + * + * - Then, after a SET_CONFIGURATION control request, ep_config() is + * called when each /dev/gadget/ep* file is configured (by writing + * endpoint descriptors). Afterwards these files are used to write() + * IN data or to read() OUT data. To halt the endpoint, a "wrong + * direction" request is issued (like reading an IN endpoint). + * + * Unlike "usbfs" the only ioctl()s are for things that are rare, and maybe + * not possible on all hardware. For example, precise fault handling with + * respect to data left in endpoint fifos after aborted operations; or + * selective clearing of endpoint halts, to implement SET_INTERFACE. + */ + +#define DRIVER_DESC "USB Gadget filesystem" +#define DRIVER_VERSION "24 Aug 2004" + +static const char driver_desc [] = DRIVER_DESC; +static const char shortname [] = "gadgetfs"; + +MODULE_DESCRIPTION (DRIVER_DESC); +MODULE_AUTHOR ("David Brownell"); +MODULE_LICENSE ("GPL"); + + +/*----------------------------------------------------------------------*/ + +#define GADGETFS_MAGIC 0xaee71ee7 +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +/* /dev/gadget/$CHIP represents ep0 and the whole device */ +enum ep0_state { + /* DISBLED is the initial state. + */ + STATE_DEV_DISABLED = 0, + + /* Only one open() of /dev/gadget/$CHIP; only one file tracks + * ep0/device i/o modes and binding to the controller. Driver + * must always write descriptors to initialize the device, then + * the device becomes UNCONNECTED until enumeration. + */ + STATE_DEV_OPENED, + + /* From then on, ep0 fd is in either of two basic modes: + * - (UN)CONNECTED: read usb_gadgetfs_event(s) from it + * - SETUP: read/write will transfer control data and succeed; + * or if "wrong direction", performs protocol stall + */ + STATE_DEV_UNCONNECTED, + STATE_DEV_CONNECTED, + STATE_DEV_SETUP, + + /* UNBOUND means the driver closed ep0, so the device won't be + * accessible again (DEV_DISABLED) until all fds are closed. + */ + STATE_DEV_UNBOUND, +}; + +/* enough for the whole queue: most events invalidate others */ +#define N_EVENT 5 + +struct dev_data { + spinlock_t lock; + atomic_t count; + enum ep0_state state; /* P: lock */ + struct usb_gadgetfs_event event [N_EVENT]; + unsigned ev_next; + struct fasync_struct *fasync; + u8 current_config; + + /* drivers reading ep0 MUST handle control requests (SETUP) + * reported that way; else the host will time out. + */ + unsigned usermode_setup : 1, + setup_in : 1, + setup_can_stall : 1, + setup_out_ready : 1, + setup_out_error : 1, + setup_abort : 1; + unsigned setup_wLength; + + /* the rest is basically write-once */ + struct usb_config_descriptor *config, *hs_config; + struct usb_device_descriptor *dev; + struct usb_request *req; + struct usb_gadget *gadget; + struct list_head epfiles; + void *buf; + wait_queue_head_t wait; + struct super_block *sb; + struct dentry *dentry; + + /* except this scratch i/o buffer for ep0 */ + u8 rbuf [256]; +}; + +static inline void get_dev (struct dev_data *data) +{ + atomic_inc (&data->count); +} + +static void put_dev (struct dev_data *data) +{ + if (likely (!atomic_dec_and_test (&data->count))) + return; + /* needs no more cleanup */ + BUG_ON (waitqueue_active (&data->wait)); + kfree (data); +} + +static struct dev_data *dev_new (void) +{ + struct dev_data *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return NULL; + dev->state = STATE_DEV_DISABLED; + atomic_set (&dev->count, 1); + spin_lock_init (&dev->lock); + INIT_LIST_HEAD (&dev->epfiles); + init_waitqueue_head (&dev->wait); + return dev; +} + +/*----------------------------------------------------------------------*/ + +/* other /dev/gadget/$ENDPOINT files represent endpoints */ +enum ep_state { + STATE_EP_DISABLED = 0, + STATE_EP_READY, + STATE_EP_ENABLED, + STATE_EP_UNBOUND, +}; + +struct ep_data { + struct mutex lock; + enum ep_state state; + atomic_t count; + struct dev_data *dev; + /* must hold dev->lock before accessing ep or req */ + struct usb_ep *ep; + struct usb_request *req; + ssize_t status; + char name [16]; + struct usb_endpoint_descriptor desc, hs_desc; + struct list_head epfiles; + wait_queue_head_t wait; + struct dentry *dentry; + struct inode *inode; +}; + +static inline void get_ep (struct ep_data *data) +{ + atomic_inc (&data->count); +} + +static void put_ep (struct ep_data *data) +{ + if (likely (!atomic_dec_and_test (&data->count))) + return; + put_dev (data->dev); + /* needs no more cleanup */ + BUG_ON (!list_empty (&data->epfiles)); + BUG_ON (waitqueue_active (&data->wait)); + kfree (data); +} + +/*----------------------------------------------------------------------*/ + +/* most "how to use the hardware" policy choices are in userspace: + * mapping endpoint roles (which the driver needs) to the capabilities + * which the usb controller has. most of those capabilities are exposed + * implicitly, starting with the driver name and then endpoint names. + */ + +static const char *CHIP; + +/*----------------------------------------------------------------------*/ + +/* NOTE: don't use dev_printk calls before binding to the gadget + * at the end of ep0 configuration, or after unbind. + */ + +/* too wordy: dev_printk(level , &(d)->gadget->dev , fmt , ## args) */ +#define xprintk(d,level,fmt,args...) \ + printk(level "%s: " fmt , shortname , ## args) + +#ifdef DEBUG +#define DBG(dev,fmt,args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDEBUG DBG +#else +#define VDEBUG(dev,fmt,args...) \ + do { } while (0) +#endif /* DEBUG */ + +#define ERROR(dev,fmt,args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define INFO(dev,fmt,args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + + +/*----------------------------------------------------------------------*/ + +/* SYNCHRONOUS ENDPOINT OPERATIONS (bulk/intr/iso) + * + * After opening, configure non-control endpoints. Then use normal + * stream read() and write() requests; and maybe ioctl() to get more + * precise FIFO status when recovering from cancellation. + */ + +static void epio_complete (struct usb_ep *ep, struct usb_request *req) +{ + struct ep_data *epdata = ep->driver_data; + + if (!req->context) + return; + if (req->status) + epdata->status = req->status; + else + epdata->status = req->actual; + complete ((struct completion *)req->context); +} + +/* tasklock endpoint, returning when it's connected. + * still need dev->lock to use epdata->ep. + */ +static int +get_ready_ep (unsigned f_flags, struct ep_data *epdata) +{ + int val; + + if (f_flags & O_NONBLOCK) { + if (!mutex_trylock(&epdata->lock)) + goto nonblock; + if (epdata->state != STATE_EP_ENABLED) { + mutex_unlock(&epdata->lock); +nonblock: + val = -EAGAIN; + } else + val = 0; + return val; + } + + val = mutex_lock_interruptible(&epdata->lock); + if (val < 0) + return val; + + switch (epdata->state) { + case STATE_EP_ENABLED: + break; + // case STATE_EP_DISABLED: /* "can't happen" */ + // case STATE_EP_READY: /* "can't happen" */ + default: /* error! */ + pr_debug ("%s: ep %p not available, state %d\n", + shortname, epdata, epdata->state); + // FALLTHROUGH + case STATE_EP_UNBOUND: /* clean disconnect */ + val = -ENODEV; + mutex_unlock(&epdata->lock); + } + return val; +} + +static ssize_t +ep_io (struct ep_data *epdata, void *buf, unsigned len) +{ + DECLARE_COMPLETION_ONSTACK (done); + int value; + + spin_lock_irq (&epdata->dev->lock); + if (likely (epdata->ep != NULL)) { + struct usb_request *req = epdata->req; + + req->context = &done; + req->complete = epio_complete; + req->buf = buf; + req->length = len; + value = usb_ep_queue (epdata->ep, req, GFP_ATOMIC); + } else + value = -ENODEV; + spin_unlock_irq (&epdata->dev->lock); + + if (likely (value == 0)) { + value = wait_event_interruptible (done.wait, done.done); + if (value != 0) { + spin_lock_irq (&epdata->dev->lock); + if (likely (epdata->ep != NULL)) { + DBG (epdata->dev, "%s i/o interrupted\n", + epdata->name); + usb_ep_dequeue (epdata->ep, epdata->req); + spin_unlock_irq (&epdata->dev->lock); + + wait_event (done.wait, done.done); + if (epdata->status == -ECONNRESET) + epdata->status = -EINTR; + } else { + spin_unlock_irq (&epdata->dev->lock); + + DBG (epdata->dev, "endpoint gone\n"); + epdata->status = -ENODEV; + } + } + return epdata->status; + } + return value; +} + + +/* handle a synchronous OUT bulk/intr/iso transfer */ +static ssize_t +ep_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr) +{ + struct ep_data *data = fd->private_data; + void *kbuf; + ssize_t value; + + if ((value = get_ready_ep (fd->f_flags, data)) < 0) + return value; + + /* halt any endpoint by doing a "wrong direction" i/o call */ + if (usb_endpoint_dir_in(&data->desc)) { + if (usb_endpoint_xfer_isoc(&data->desc)) { + mutex_unlock(&data->lock); + return -EINVAL; + } + DBG (data->dev, "%s halt\n", data->name); + spin_lock_irq (&data->dev->lock); + if (likely (data->ep != NULL)) + usb_ep_set_halt (data->ep); + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return -EBADMSG; + } + + /* FIXME readahead for O_NONBLOCK and poll(); careful with ZLPs */ + + value = -ENOMEM; + kbuf = kmalloc (len, GFP_KERNEL); + if (unlikely (!kbuf)) + goto free1; + + value = ep_io (data, kbuf, len); + VDEBUG (data->dev, "%s read %zu OUT, status %d\n", + data->name, len, (int) value); + if (value >= 0 && copy_to_user (buf, kbuf, value)) + value = -EFAULT; + +free1: + mutex_unlock(&data->lock); + kfree (kbuf); + return value; +} + +/* handle a synchronous IN bulk/intr/iso transfer */ +static ssize_t +ep_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct ep_data *data = fd->private_data; + void *kbuf; + ssize_t value; + + if ((value = get_ready_ep (fd->f_flags, data)) < 0) + return value; + + /* halt any endpoint by doing a "wrong direction" i/o call */ + if (!usb_endpoint_dir_in(&data->desc)) { + if (usb_endpoint_xfer_isoc(&data->desc)) { + mutex_unlock(&data->lock); + return -EINVAL; + } + DBG (data->dev, "%s halt\n", data->name); + spin_lock_irq (&data->dev->lock); + if (likely (data->ep != NULL)) + usb_ep_set_halt (data->ep); + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return -EBADMSG; + } + + /* FIXME writebehind for O_NONBLOCK and poll(), qlen = 1 */ + + value = -ENOMEM; + kbuf = kmalloc (len, GFP_KERNEL); + if (!kbuf) + goto free1; + if (copy_from_user (kbuf, buf, len)) { + value = -EFAULT; + goto free1; + } + + value = ep_io (data, kbuf, len); + VDEBUG (data->dev, "%s write %zu IN, status %d\n", + data->name, len, (int) value); +free1: + mutex_unlock(&data->lock); + kfree (kbuf); + return value; +} + +static int +ep_release (struct inode *inode, struct file *fd) +{ + struct ep_data *data = fd->private_data; + int value; + + value = mutex_lock_interruptible(&data->lock); + if (value < 0) + return value; + + /* clean up if this can be reopened */ + if (data->state != STATE_EP_UNBOUND) { + data->state = STATE_EP_DISABLED; + data->desc.bDescriptorType = 0; + data->hs_desc.bDescriptorType = 0; + usb_ep_disable(data->ep); + } + mutex_unlock(&data->lock); + put_ep (data); + return 0; +} + +static long ep_ioctl(struct file *fd, unsigned code, unsigned long value) +{ + struct ep_data *data = fd->private_data; + int status; + + if ((status = get_ready_ep (fd->f_flags, data)) < 0) + return status; + + spin_lock_irq (&data->dev->lock); + if (likely (data->ep != NULL)) { + switch (code) { + case GADGETFS_FIFO_STATUS: + status = usb_ep_fifo_status (data->ep); + break; + case GADGETFS_FIFO_FLUSH: + usb_ep_fifo_flush (data->ep); + break; + case GADGETFS_CLEAR_HALT: + status = usb_ep_clear_halt (data->ep); + break; + default: + status = -ENOTTY; + } + } else + status = -ENODEV; + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return status; +} + +/*----------------------------------------------------------------------*/ + +/* ASYNCHRONOUS ENDPOINT I/O OPERATIONS (bulk/intr/iso) */ + +struct kiocb_priv { + struct usb_request *req; + struct ep_data *epdata; + void *buf; + const struct iovec *iv; + unsigned long nr_segs; + unsigned actual; +}; + +static int ep_aio_cancel(struct kiocb *iocb, struct io_event *e) +{ + struct kiocb_priv *priv = iocb->private; + struct ep_data *epdata; + int value; + + local_irq_disable(); + epdata = priv->epdata; + // spin_lock(&epdata->dev->lock); + kiocbSetCancelled(iocb); + if (likely(epdata && epdata->ep && priv->req)) + value = usb_ep_dequeue (epdata->ep, priv->req); + else + value = -EINVAL; + // spin_unlock(&epdata->dev->lock); + local_irq_enable(); + + aio_put_req(iocb); + return value; +} + +static ssize_t ep_aio_read_retry(struct kiocb *iocb) +{ + struct kiocb_priv *priv = iocb->private; + ssize_t len, total; + void *to_copy; + int i; + + /* we "retry" to get the right mm context for this: */ + + /* copy stuff into user buffers */ + total = priv->actual; + len = 0; + to_copy = priv->buf; + for (i=0; i < priv->nr_segs; i++) { + ssize_t this = min((ssize_t)(priv->iv[i].iov_len), total); + + if (copy_to_user(priv->iv[i].iov_base, to_copy, this)) { + if (len == 0) + len = -EFAULT; + break; + } + + total -= this; + len += this; + to_copy += this; + if (total == 0) + break; + } + kfree(priv->buf); + kfree(priv); + return len; +} + +static void ep_aio_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct kiocb *iocb = req->context; + struct kiocb_priv *priv = iocb->private; + struct ep_data *epdata = priv->epdata; + + /* lock against disconnect (and ideally, cancel) */ + spin_lock(&epdata->dev->lock); + priv->req = NULL; + priv->epdata = NULL; + + /* if this was a write or a read returning no data then we + * don't need to copy anything to userspace, so we can + * complete the aio request immediately. + */ + if (priv->iv == NULL || unlikely(req->actual == 0)) { + kfree(req->buf); + kfree(priv); + iocb->private = NULL; + /* aio_complete() reports bytes-transferred _and_ faults */ + aio_complete(iocb, req->actual ? req->actual : req->status, + req->status); + } else { + /* retry() won't report both; so we hide some faults */ + if (unlikely(0 != req->status)) + DBG(epdata->dev, "%s fault %d len %d\n", + ep->name, req->status, req->actual); + + priv->buf = req->buf; + priv->actual = req->actual; + kick_iocb(iocb); + } + spin_unlock(&epdata->dev->lock); + + usb_ep_free_request(ep, req); + put_ep(epdata); +} + +static ssize_t +ep_aio_rwtail( + struct kiocb *iocb, + char *buf, + size_t len, + struct ep_data *epdata, + const struct iovec *iv, + unsigned long nr_segs +) +{ + struct kiocb_priv *priv; + struct usb_request *req; + ssize_t value; + + priv = kmalloc(sizeof *priv, GFP_KERNEL); + if (!priv) { + value = -ENOMEM; +fail: + kfree(buf); + return value; + } + iocb->private = priv; + priv->iv = iv; + priv->nr_segs = nr_segs; + + value = get_ready_ep(iocb->ki_filp->f_flags, epdata); + if (unlikely(value < 0)) { + kfree(priv); + goto fail; + } + + iocb->ki_cancel = ep_aio_cancel; + get_ep(epdata); + priv->epdata = epdata; + priv->actual = 0; + + /* each kiocb is coupled to one usb_request, but we can't + * allocate or submit those if the host disconnected. + */ + spin_lock_irq(&epdata->dev->lock); + if (likely(epdata->ep)) { + req = usb_ep_alloc_request(epdata->ep, GFP_ATOMIC); + if (likely(req)) { + priv->req = req; + req->buf = buf; + req->length = len; + req->complete = ep_aio_complete; + req->context = iocb; + value = usb_ep_queue(epdata->ep, req, GFP_ATOMIC); + if (unlikely(0 != value)) + usb_ep_free_request(epdata->ep, req); + } else + value = -EAGAIN; + } else + value = -ENODEV; + spin_unlock_irq(&epdata->dev->lock); + + mutex_unlock(&epdata->lock); + + if (unlikely(value)) { + kfree(priv); + put_ep(epdata); + } else + value = (iv ? -EIOCBRETRY : -EIOCBQUEUED); + return value; +} + +static ssize_t +ep_aio_read(struct kiocb *iocb, const struct iovec *iov, + unsigned long nr_segs, loff_t o) +{ + struct ep_data *epdata = iocb->ki_filp->private_data; + char *buf; + + if (unlikely(usb_endpoint_dir_in(&epdata->desc))) + return -EINVAL; + + buf = kmalloc(iocb->ki_left, GFP_KERNEL); + if (unlikely(!buf)) + return -ENOMEM; + + iocb->ki_retry = ep_aio_read_retry; + return ep_aio_rwtail(iocb, buf, iocb->ki_left, epdata, iov, nr_segs); +} + +static ssize_t +ep_aio_write(struct kiocb *iocb, const struct iovec *iov, + unsigned long nr_segs, loff_t o) +{ + struct ep_data *epdata = iocb->ki_filp->private_data; + char *buf; + size_t len = 0; + int i = 0; + + if (unlikely(!usb_endpoint_dir_in(&epdata->desc))) + return -EINVAL; + + buf = kmalloc(iocb->ki_left, GFP_KERNEL); + if (unlikely(!buf)) + return -ENOMEM; + + for (i=0; i < nr_segs; i++) { + if (unlikely(copy_from_user(&buf[len], iov[i].iov_base, + iov[i].iov_len) != 0)) { + kfree(buf); + return -EFAULT; + } + len += iov[i].iov_len; + } + return ep_aio_rwtail(iocb, buf, len, epdata, NULL, 0); +} + +/*----------------------------------------------------------------------*/ + +/* used after endpoint configuration */ +static const struct file_operations ep_io_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .read = ep_read, + .write = ep_write, + .unlocked_ioctl = ep_ioctl, + .release = ep_release, + + .aio_read = ep_aio_read, + .aio_write = ep_aio_write, +}; + +/* ENDPOINT INITIALIZATION + * + * fd = open ("/dev/gadget/$ENDPOINT", O_RDWR) + * status = write (fd, descriptors, sizeof descriptors) + * + * That write establishes the endpoint configuration, configuring + * the controller to process bulk, interrupt, or isochronous transfers + * at the right maxpacket size, and so on. + * + * The descriptors are message type 1, identified by a host order u32 + * at the beginning of what's written. Descriptor order is: full/low + * speed descriptor, then optional high speed descriptor. + */ +static ssize_t +ep_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct ep_data *data = fd->private_data; + struct usb_ep *ep; + u32 tag; + int value, length = len; + + value = mutex_lock_interruptible(&data->lock); + if (value < 0) + return value; + + if (data->state != STATE_EP_READY) { + value = -EL2HLT; + goto fail; + } + + value = len; + if (len < USB_DT_ENDPOINT_SIZE + 4) + goto fail0; + + /* we might need to change message format someday */ + if (copy_from_user (&tag, buf, 4)) { + goto fail1; + } + if (tag != 1) { + DBG(data->dev, "config %s, bad tag %d\n", data->name, tag); + goto fail0; + } + buf += 4; + len -= 4; + + /* NOTE: audio endpoint extensions not accepted here; + * just don't include the extra bytes. + */ + + /* full/low speed descriptor, then high speed */ + if (copy_from_user (&data->desc, buf, USB_DT_ENDPOINT_SIZE)) { + goto fail1; + } + if (data->desc.bLength != USB_DT_ENDPOINT_SIZE + || data->desc.bDescriptorType != USB_DT_ENDPOINT) + goto fail0; + if (len != USB_DT_ENDPOINT_SIZE) { + if (len != 2 * USB_DT_ENDPOINT_SIZE) + goto fail0; + if (copy_from_user (&data->hs_desc, buf + USB_DT_ENDPOINT_SIZE, + USB_DT_ENDPOINT_SIZE)) { + goto fail1; + } + if (data->hs_desc.bLength != USB_DT_ENDPOINT_SIZE + || data->hs_desc.bDescriptorType + != USB_DT_ENDPOINT) { + DBG(data->dev, "config %s, bad hs length or type\n", + data->name); + goto fail0; + } + } + + spin_lock_irq (&data->dev->lock); + if (data->dev->state == STATE_DEV_UNBOUND) { + value = -ENOENT; + goto gone; + } else if ((ep = data->ep) == NULL) { + value = -ENODEV; + goto gone; + } + switch (data->dev->gadget->speed) { + case USB_SPEED_LOW: + case USB_SPEED_FULL: + value = usb_ep_enable (ep, &data->desc); + if (value == 0) + data->state = STATE_EP_ENABLED; + break; +#ifdef CONFIG_USB_GADGET_DUALSPEED + case USB_SPEED_HIGH: + /* fails if caller didn't provide that descriptor... */ + value = usb_ep_enable (ep, &data->hs_desc); + if (value == 0) + data->state = STATE_EP_ENABLED; + break; +#endif + default: + DBG(data->dev, "unconnected, %s init abandoned\n", + data->name); + value = -EINVAL; + } + if (value == 0) { + fd->f_op = &ep_io_operations; + value = length; + } +gone: + spin_unlock_irq (&data->dev->lock); + if (value < 0) { +fail: + data->desc.bDescriptorType = 0; + data->hs_desc.bDescriptorType = 0; + } + mutex_unlock(&data->lock); + return value; +fail0: + value = -EINVAL; + goto fail; +fail1: + value = -EFAULT; + goto fail; +} + +static int +ep_open (struct inode *inode, struct file *fd) +{ + struct ep_data *data = inode->i_private; + int value = -EBUSY; + + if (mutex_lock_interruptible(&data->lock) != 0) + return -EINTR; + spin_lock_irq (&data->dev->lock); + if (data->dev->state == STATE_DEV_UNBOUND) + value = -ENOENT; + else if (data->state == STATE_EP_DISABLED) { + value = 0; + data->state = STATE_EP_READY; + get_ep (data); + fd->private_data = data; + VDEBUG (data->dev, "%s ready\n", data->name); + } else + DBG (data->dev, "%s state %d\n", + data->name, data->state); + spin_unlock_irq (&data->dev->lock); + mutex_unlock(&data->lock); + return value; +} + +/* used before endpoint configuration */ +static const struct file_operations ep_config_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .open = ep_open, + .write = ep_config, + .release = ep_release, +}; + +/*----------------------------------------------------------------------*/ + +/* EP0 IMPLEMENTATION can be partly in userspace. + * + * Drivers that use this facility receive various events, including + * control requests the kernel doesn't handle. Drivers that don't + * use this facility may be too simple-minded for real applications. + */ + +static inline void ep0_readable (struct dev_data *dev) +{ + wake_up (&dev->wait); + kill_fasync (&dev->fasync, SIGIO, POLL_IN); +} + +static void clean_req (struct usb_ep *ep, struct usb_request *req) +{ + struct dev_data *dev = ep->driver_data; + + if (req->buf != dev->rbuf) { + kfree(req->buf); + req->buf = dev->rbuf; + req->dma = DMA_ADDR_INVALID; + } + req->complete = epio_complete; + dev->setup_out_ready = 0; +} + +static void ep0_complete (struct usb_ep *ep, struct usb_request *req) +{ + struct dev_data *dev = ep->driver_data; + unsigned long flags; + int free = 1; + + /* for control OUT, data must still get to userspace */ + spin_lock_irqsave(&dev->lock, flags); + if (!dev->setup_in) { + dev->setup_out_error = (req->status != 0); + if (!dev->setup_out_error) + free = 0; + dev->setup_out_ready = 1; + ep0_readable (dev); + } + + /* clean up as appropriate */ + if (free && req->buf != &dev->rbuf) + clean_req (ep, req); + req->complete = epio_complete; + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int setup_req (struct usb_ep *ep, struct usb_request *req, u16 len) +{ + struct dev_data *dev = ep->driver_data; + + if (dev->setup_out_ready) { + DBG (dev, "ep0 request busy!\n"); + return -EBUSY; + } + if (len > sizeof (dev->rbuf)) + req->buf = kmalloc(len, GFP_ATOMIC); + if (req->buf == NULL) { + req->buf = dev->rbuf; + return -ENOMEM; + } + req->complete = ep0_complete; + req->length = len; + req->zero = 0; + return 0; +} + +static ssize_t +ep0_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr) +{ + struct dev_data *dev = fd->private_data; + ssize_t retval; + enum ep0_state state; + + spin_lock_irq (&dev->lock); + + /* report fd mode change before acting on it */ + if (dev->setup_abort) { + dev->setup_abort = 0; + retval = -EIDRM; + goto done; + } + + /* control DATA stage */ + if ((state = dev->state) == STATE_DEV_SETUP) { + + if (dev->setup_in) { /* stall IN */ + VDEBUG(dev, "ep0in stall\n"); + (void) usb_ep_set_halt (dev->gadget->ep0); + retval = -EL2HLT; + dev->state = STATE_DEV_CONNECTED; + + } else if (len == 0) { /* ack SET_CONFIGURATION etc */ + struct usb_ep *ep = dev->gadget->ep0; + struct usb_request *req = dev->req; + + if ((retval = setup_req (ep, req, 0)) == 0) + retval = usb_ep_queue (ep, req, GFP_ATOMIC); + dev->state = STATE_DEV_CONNECTED; + + /* assume that was SET_CONFIGURATION */ + if (dev->current_config) { + unsigned power; + + if (gadget_is_dualspeed(dev->gadget) + && (dev->gadget->speed + == USB_SPEED_HIGH)) + power = dev->hs_config->bMaxPower; + else + power = dev->config->bMaxPower; + usb_gadget_vbus_draw(dev->gadget, 2 * power); + } + + } else { /* collect OUT data */ + if ((fd->f_flags & O_NONBLOCK) != 0 + && !dev->setup_out_ready) { + retval = -EAGAIN; + goto done; + } + spin_unlock_irq (&dev->lock); + retval = wait_event_interruptible (dev->wait, + dev->setup_out_ready != 0); + + /* FIXME state could change from under us */ + spin_lock_irq (&dev->lock); + if (retval) + goto done; + + if (dev->state != STATE_DEV_SETUP) { + retval = -ECANCELED; + goto done; + } + dev->state = STATE_DEV_CONNECTED; + + if (dev->setup_out_error) + retval = -EIO; + else { + len = min (len, (size_t)dev->req->actual); +// FIXME don't call this with the spinlock held ... + if (copy_to_user (buf, dev->req->buf, len)) + retval = -EFAULT; + clean_req (dev->gadget->ep0, dev->req); + /* NOTE userspace can't yet choose to stall */ + } + } + goto done; + } + + /* else normal: return event data */ + if (len < sizeof dev->event [0]) { + retval = -EINVAL; + goto done; + } + len -= len % sizeof (struct usb_gadgetfs_event); + dev->usermode_setup = 1; + +scan: + /* return queued events right away */ + if (dev->ev_next != 0) { + unsigned i, n; + + n = len / sizeof (struct usb_gadgetfs_event); + if (dev->ev_next < n) + n = dev->ev_next; + + /* ep0 i/o has special semantics during STATE_DEV_SETUP */ + for (i = 0; i < n; i++) { + if (dev->event [i].type == GADGETFS_SETUP) { + dev->state = STATE_DEV_SETUP; + n = i + 1; + break; + } + } + spin_unlock_irq (&dev->lock); + len = n * sizeof (struct usb_gadgetfs_event); + if (copy_to_user (buf, &dev->event, len)) + retval = -EFAULT; + else + retval = len; + if (len > 0) { + /* NOTE this doesn't guard against broken drivers; + * concurrent ep0 readers may lose events. + */ + spin_lock_irq (&dev->lock); + if (dev->ev_next > n) { + memmove(&dev->event[0], &dev->event[n], + sizeof (struct usb_gadgetfs_event) + * (dev->ev_next - n)); + } + dev->ev_next -= n; + spin_unlock_irq (&dev->lock); + } + return retval; + } + if (fd->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto done; + } + + switch (state) { + default: + DBG (dev, "fail %s, state %d\n", __func__, state); + retval = -ESRCH; + break; + case STATE_DEV_UNCONNECTED: + case STATE_DEV_CONNECTED: + spin_unlock_irq (&dev->lock); + DBG (dev, "%s wait\n", __func__); + + /* wait for events */ + retval = wait_event_interruptible (dev->wait, + dev->ev_next != 0); + if (retval < 0) + return retval; + spin_lock_irq (&dev->lock); + goto scan; + } + +done: + spin_unlock_irq (&dev->lock); + return retval; +} + +static struct usb_gadgetfs_event * +next_event (struct dev_data *dev, enum usb_gadgetfs_event_type type) +{ + struct usb_gadgetfs_event *event; + unsigned i; + + switch (type) { + /* these events purge the queue */ + case GADGETFS_DISCONNECT: + if (dev->state == STATE_DEV_SETUP) + dev->setup_abort = 1; + // FALL THROUGH + case GADGETFS_CONNECT: + dev->ev_next = 0; + break; + case GADGETFS_SETUP: /* previous request timed out */ + case GADGETFS_SUSPEND: /* same effect */ + /* these events can't be repeated */ + for (i = 0; i != dev->ev_next; i++) { + if (dev->event [i].type != type) + continue; + DBG(dev, "discard old event[%d] %d\n", i, type); + dev->ev_next--; + if (i == dev->ev_next) + break; + /* indices start at zero, for simplicity */ + memmove (&dev->event [i], &dev->event [i + 1], + sizeof (struct usb_gadgetfs_event) + * (dev->ev_next - i)); + } + break; + default: + BUG (); + } + VDEBUG(dev, "event[%d] = %d\n", dev->ev_next, type); + event = &dev->event [dev->ev_next++]; + BUG_ON (dev->ev_next > N_EVENT); + memset (event, 0, sizeof *event); + event->type = type; + return event; +} + +static ssize_t +ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct dev_data *dev = fd->private_data; + ssize_t retval = -ESRCH; + + spin_lock_irq (&dev->lock); + + /* report fd mode change before acting on it */ + if (dev->setup_abort) { + dev->setup_abort = 0; + retval = -EIDRM; + + /* data and/or status stage for control request */ + } else if (dev->state == STATE_DEV_SETUP) { + + /* IN DATA+STATUS caller makes len <= wLength */ + if (dev->setup_in) { + retval = setup_req (dev->gadget->ep0, dev->req, len); + if (retval == 0) { + dev->state = STATE_DEV_CONNECTED; + spin_unlock_irq (&dev->lock); + if (copy_from_user (dev->req->buf, buf, len)) + retval = -EFAULT; + else { + if (len < dev->setup_wLength) + dev->req->zero = 1; + retval = usb_ep_queue ( + dev->gadget->ep0, dev->req, + GFP_KERNEL); + } + if (retval < 0) { + spin_lock_irq (&dev->lock); + clean_req (dev->gadget->ep0, dev->req); + spin_unlock_irq (&dev->lock); + } else + retval = len; + + return retval; + } + + /* can stall some OUT transfers */ + } else if (dev->setup_can_stall) { + VDEBUG(dev, "ep0out stall\n"); + (void) usb_ep_set_halt (dev->gadget->ep0); + retval = -EL2HLT; + dev->state = STATE_DEV_CONNECTED; + } else { + DBG(dev, "bogus ep0out stall!\n"); + } + } else + DBG (dev, "fail %s, state %d\n", __func__, dev->state); + + spin_unlock_irq (&dev->lock); + return retval; +} + +static int +ep0_fasync (int f, struct file *fd, int on) +{ + struct dev_data *dev = fd->private_data; + // caller must F_SETOWN before signal delivery happens + VDEBUG (dev, "%s %s\n", __func__, on ? "on" : "off"); + return fasync_helper (f, fd, on, &dev->fasync); +} + +static struct usb_gadget_driver gadgetfs_driver; + +static int +dev_release (struct inode *inode, struct file *fd) +{ + struct dev_data *dev = fd->private_data; + + /* closing ep0 === shutdown all */ + + usb_gadget_unregister_driver (&gadgetfs_driver); + + /* at this point "good" hardware has disconnected the + * device from USB; the host won't see it any more. + * alternatively, all host requests will time out. + */ + + kfree (dev->buf); + dev->buf = NULL; + put_dev (dev); + + /* other endpoints were all decoupled from this device */ + spin_lock_irq(&dev->lock); + dev->state = STATE_DEV_DISABLED; + spin_unlock_irq(&dev->lock); + return 0; +} + +static unsigned int +ep0_poll (struct file *fd, poll_table *wait) +{ + struct dev_data *dev = fd->private_data; + int mask = 0; + + poll_wait(fd, &dev->wait, wait); + + spin_lock_irq (&dev->lock); + + /* report fd mode change before acting on it */ + if (dev->setup_abort) { + dev->setup_abort = 0; + mask = POLLHUP; + goto out; + } + + if (dev->state == STATE_DEV_SETUP) { + if (dev->setup_in || dev->setup_can_stall) + mask = POLLOUT; + } else { + if (dev->ev_next != 0) + mask = POLLIN; + } +out: + spin_unlock_irq(&dev->lock); + return mask; +} + +static long dev_ioctl (struct file *fd, unsigned code, unsigned long value) +{ + struct dev_data *dev = fd->private_data; + struct usb_gadget *gadget = dev->gadget; + long ret = -ENOTTY; + + if (gadget->ops->ioctl) + ret = gadget->ops->ioctl (gadget, code, value); + + return ret; +} + +/* used after device configuration */ +static const struct file_operations ep0_io_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .read = ep0_read, + .write = ep0_write, + .fasync = ep0_fasync, + .poll = ep0_poll, + .unlocked_ioctl = dev_ioctl, + .release = dev_release, +}; + +/*----------------------------------------------------------------------*/ + +/* The in-kernel gadget driver handles most ep0 issues, in particular + * enumerating the single configuration (as provided from user space). + * + * Unrecognized ep0 requests may be handled in user space. + */ + +#ifdef CONFIG_USB_GADGET_DUALSPEED +static void make_qualifier (struct dev_data *dev) +{ + struct usb_qualifier_descriptor qual; + struct usb_device_descriptor *desc; + + qual.bLength = sizeof qual; + qual.bDescriptorType = USB_DT_DEVICE_QUALIFIER; + qual.bcdUSB = cpu_to_le16 (0x0200); + + desc = dev->dev; + qual.bDeviceClass = desc->bDeviceClass; + qual.bDeviceSubClass = desc->bDeviceSubClass; + qual.bDeviceProtocol = desc->bDeviceProtocol; + + /* assumes ep0 uses the same value for both speeds ... */ + qual.bMaxPacketSize0 = desc->bMaxPacketSize0; + + qual.bNumConfigurations = 1; + qual.bRESERVED = 0; + + memcpy (dev->rbuf, &qual, sizeof qual); +} +#endif + +static int +config_buf (struct dev_data *dev, u8 type, unsigned index) +{ + int len; + int hs = 0; + + /* only one configuration */ + if (index > 0) + return -EINVAL; + + if (gadget_is_dualspeed(dev->gadget)) { + hs = (dev->gadget->speed == USB_SPEED_HIGH); + if (type == USB_DT_OTHER_SPEED_CONFIG) + hs = !hs; + } + if (hs) { + dev->req->buf = dev->hs_config; + len = le16_to_cpu(dev->hs_config->wTotalLength); + } else { + dev->req->buf = dev->config; + len = le16_to_cpu(dev->config->wTotalLength); + } + ((u8 *)dev->req->buf) [1] = type; + return len; +} + +static int +gadgetfs_setup (struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) +{ + struct dev_data *dev = get_gadget_data (gadget); + struct usb_request *req = dev->req; + int value = -EOPNOTSUPP; + struct usb_gadgetfs_event *event; + u16 w_value = le16_to_cpu(ctrl->wValue); + u16 w_length = le16_to_cpu(ctrl->wLength); + + spin_lock (&dev->lock); + dev->setup_abort = 0; + if (dev->state == STATE_DEV_UNCONNECTED) { + if (gadget_is_dualspeed(gadget) + && gadget->speed == USB_SPEED_HIGH + && dev->hs_config == NULL) { + spin_unlock(&dev->lock); + ERROR (dev, "no high speed config??\n"); + return -EINVAL; + } + + dev->state = STATE_DEV_CONNECTED; + dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket; + + INFO (dev, "connected\n"); + event = next_event (dev, GADGETFS_CONNECT); + event->u.speed = gadget->speed; + ep0_readable (dev); + + /* host may have given up waiting for response. we can miss control + * requests handled lower down (device/endpoint status and features); + * then ep0_{read,write} will report the wrong status. controller + * driver will have aborted pending i/o. + */ + } else if (dev->state == STATE_DEV_SETUP) + dev->setup_abort = 1; + + req->buf = dev->rbuf; + req->dma = DMA_ADDR_INVALID; + req->context = NULL; + value = -EOPNOTSUPP; + switch (ctrl->bRequest) { + + case USB_REQ_GET_DESCRIPTOR: + if (ctrl->bRequestType != USB_DIR_IN) + goto unrecognized; + switch (w_value >> 8) { + + case USB_DT_DEVICE: + value = min (w_length, (u16) sizeof *dev->dev); + req->buf = dev->dev; + break; +#ifdef CONFIG_USB_GADGET_DUALSPEED + case USB_DT_DEVICE_QUALIFIER: + if (!dev->hs_config) + break; + value = min (w_length, (u16) + sizeof (struct usb_qualifier_descriptor)); + make_qualifier (dev); + break; + case USB_DT_OTHER_SPEED_CONFIG: + // FALLTHROUGH +#endif + case USB_DT_CONFIG: + value = config_buf (dev, + w_value >> 8, + w_value & 0xff); + if (value >= 0) + value = min (w_length, (u16) value); + break; + case USB_DT_STRING: + goto unrecognized; + + default: // all others are errors + break; + } + break; + + /* currently one config, two speeds */ + case USB_REQ_SET_CONFIGURATION: + if (ctrl->bRequestType != 0) + goto unrecognized; + if (0 == (u8) w_value) { + value = 0; + dev->current_config = 0; + usb_gadget_vbus_draw(gadget, 8 /* mA */ ); + // user mode expected to disable endpoints + } else { + u8 config, power; + + if (gadget_is_dualspeed(gadget) + && gadget->speed == USB_SPEED_HIGH) { + config = dev->hs_config->bConfigurationValue; + power = dev->hs_config->bMaxPower; + } else { + config = dev->config->bConfigurationValue; + power = dev->config->bMaxPower; + } + + if (config == (u8) w_value) { + value = 0; + dev->current_config = config; + usb_gadget_vbus_draw(gadget, 2 * power); + } + } + + /* report SET_CONFIGURATION like any other control request, + * except that usermode may not stall this. the next + * request mustn't be allowed start until this finishes: + * endpoints and threads set up, etc. + * + * NOTE: older PXA hardware (before PXA 255: without UDCCFR) + * has bad/racey automagic that prevents synchronizing here. + * even kernel mode drivers often miss them. + */ + if (value == 0) { + INFO (dev, "configuration #%d\n", dev->current_config); + if (dev->usermode_setup) { + dev->setup_can_stall = 0; + goto delegate; + } + } + break; + +#ifndef CONFIG_USB_GADGET_PXA25X + /* PXA automagically handles this request too */ + case USB_REQ_GET_CONFIGURATION: + if (ctrl->bRequestType != 0x80) + goto unrecognized; + *(u8 *)req->buf = dev->current_config; + value = min (w_length, (u16) 1); + break; +#endif + + default: +unrecognized: + VDEBUG (dev, "%s req%02x.%02x v%04x i%04x l%d\n", + dev->usermode_setup ? "delegate" : "fail", + ctrl->bRequestType, ctrl->bRequest, + w_value, le16_to_cpu(ctrl->wIndex), w_length); + + /* if there's an ep0 reader, don't stall */ + if (dev->usermode_setup) { + dev->setup_can_stall = 1; +delegate: + dev->setup_in = (ctrl->bRequestType & USB_DIR_IN) + ? 1 : 0; + dev->setup_wLength = w_length; + dev->setup_out_ready = 0; + dev->setup_out_error = 0; + value = 0; + + /* read DATA stage for OUT right away */ + if (unlikely (!dev->setup_in && w_length)) { + value = setup_req (gadget->ep0, dev->req, + w_length); + if (value < 0) + break; + value = usb_ep_queue (gadget->ep0, dev->req, + GFP_ATOMIC); + if (value < 0) { + clean_req (gadget->ep0, dev->req); + break; + } + + /* we can't currently stall these */ + dev->setup_can_stall = 0; + } + + /* state changes when reader collects event */ + event = next_event (dev, GADGETFS_SETUP); + event->u.setup = *ctrl; + ep0_readable (dev); + spin_unlock (&dev->lock); + return 0; + } + } + + /* proceed with data transfer and status phases? */ + if (value >= 0 && dev->state != STATE_DEV_SETUP) { + req->length = value; + req->zero = value < w_length; + value = usb_ep_queue (gadget->ep0, req, GFP_ATOMIC); + if (value < 0) { + DBG (dev, "ep_queue --> %d\n", value); + req->status = 0; + } + } + + /* device stalls when value < 0 */ + spin_unlock (&dev->lock); + return value; +} + +static void destroy_ep_files (struct dev_data *dev) +{ + struct list_head *entry, *tmp; + + DBG (dev, "%s %d\n", __func__, dev->state); + + /* dev->state must prevent interference */ +restart: + spin_lock_irq (&dev->lock); + list_for_each_safe (entry, tmp, &dev->epfiles) { + struct ep_data *ep; + struct inode *parent; + struct dentry *dentry; + + /* break link to FS */ + ep = list_entry (entry, struct ep_data, epfiles); + list_del_init (&ep->epfiles); + dentry = ep->dentry; + ep->dentry = NULL; + parent = dentry->d_parent->d_inode; + + /* break link to controller */ + if (ep->state == STATE_EP_ENABLED) + (void) usb_ep_disable (ep->ep); + ep->state = STATE_EP_UNBOUND; + usb_ep_free_request (ep->ep, ep->req); + ep->ep = NULL; + wake_up (&ep->wait); + put_ep (ep); + + spin_unlock_irq (&dev->lock); + + /* break link to dcache */ + mutex_lock (&parent->i_mutex); + d_delete (dentry); + dput (dentry); + mutex_unlock (&parent->i_mutex); + + /* fds may still be open */ + goto restart; + } + spin_unlock_irq (&dev->lock); +} + + +static struct inode * +gadgetfs_create_file (struct super_block *sb, char const *name, + void *data, const struct file_operations *fops, + struct dentry **dentry_p); + +static int activate_ep_files (struct dev_data *dev) +{ + struct usb_ep *ep; + struct ep_data *data; + + gadget_for_each_ep (ep, dev->gadget) { + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + goto enomem0; + data->state = STATE_EP_DISABLED; + mutex_init(&data->lock); + init_waitqueue_head (&data->wait); + + strncpy (data->name, ep->name, sizeof (data->name) - 1); + atomic_set (&data->count, 1); + data->dev = dev; + get_dev (dev); + + data->ep = ep; + ep->driver_data = data; + + data->req = usb_ep_alloc_request (ep, GFP_KERNEL); + if (!data->req) + goto enomem1; + + data->inode = gadgetfs_create_file (dev->sb, data->name, + data, &ep_config_operations, + &data->dentry); + if (!data->inode) + goto enomem2; + list_add_tail (&data->epfiles, &dev->epfiles); + } + return 0; + +enomem2: + usb_ep_free_request (ep, data->req); +enomem1: + put_dev (dev); + kfree (data); +enomem0: + DBG (dev, "%s enomem\n", __func__); + destroy_ep_files (dev); + return -ENOMEM; +} + +static void +gadgetfs_unbind (struct usb_gadget *gadget) +{ + struct dev_data *dev = get_gadget_data (gadget); + + DBG (dev, "%s\n", __func__); + + spin_lock_irq (&dev->lock); + dev->state = STATE_DEV_UNBOUND; + spin_unlock_irq (&dev->lock); + + destroy_ep_files (dev); + gadget->ep0->driver_data = NULL; + set_gadget_data (gadget, NULL); + + /* we've already been disconnected ... no i/o is active */ + if (dev->req) + usb_ep_free_request (gadget->ep0, dev->req); + DBG (dev, "%s done\n", __func__); + put_dev (dev); +} + +static struct dev_data *the_device; + +static int +gadgetfs_bind (struct usb_gadget *gadget) +{ + struct dev_data *dev = the_device; + + if (!dev) + return -ESRCH; + if (0 != strcmp (CHIP, gadget->name)) { + pr_err("%s expected %s controller not %s\n", + shortname, CHIP, gadget->name); + return -ENODEV; + } + + set_gadget_data (gadget, dev); + dev->gadget = gadget; + gadget->ep0->driver_data = dev; + dev->dev->bMaxPacketSize0 = gadget->ep0->maxpacket; + + /* preallocate control response and buffer */ + dev->req = usb_ep_alloc_request (gadget->ep0, GFP_KERNEL); + if (!dev->req) + goto enomem; + dev->req->context = NULL; + dev->req->complete = epio_complete; + + if (activate_ep_files (dev) < 0) + goto enomem; + + INFO (dev, "bound to %s driver\n", gadget->name); + spin_lock_irq(&dev->lock); + dev->state = STATE_DEV_UNCONNECTED; + spin_unlock_irq(&dev->lock); + get_dev (dev); + return 0; + +enomem: + gadgetfs_unbind (gadget); + return -ENOMEM; +} + +static void +gadgetfs_disconnect (struct usb_gadget *gadget) +{ + struct dev_data *dev = get_gadget_data (gadget); + + spin_lock (&dev->lock); + if (dev->state == STATE_DEV_UNCONNECTED) + goto exit; + dev->state = STATE_DEV_UNCONNECTED; + + INFO (dev, "disconnected\n"); + next_event (dev, GADGETFS_DISCONNECT); + ep0_readable (dev); +exit: + spin_unlock (&dev->lock); +} + +static void +gadgetfs_suspend (struct usb_gadget *gadget) +{ + struct dev_data *dev = get_gadget_data (gadget); + + INFO (dev, "suspended from state %d\n", dev->state); + spin_lock (&dev->lock); + switch (dev->state) { + case STATE_DEV_SETUP: // VERY odd... host died?? + case STATE_DEV_CONNECTED: + case STATE_DEV_UNCONNECTED: + next_event (dev, GADGETFS_SUSPEND); + ep0_readable (dev); + /* FALLTHROUGH */ + default: + break; + } + spin_unlock (&dev->lock); +} + +static struct usb_gadget_driver gadgetfs_driver = { +#ifdef CONFIG_USB_GADGET_DUALSPEED + .speed = USB_SPEED_HIGH, +#else + .speed = USB_SPEED_FULL, +#endif + .function = (char *) driver_desc, + .unbind = gadgetfs_unbind, + .setup = gadgetfs_setup, + .disconnect = gadgetfs_disconnect, + .suspend = gadgetfs_suspend, + + .driver = { + .name = (char *) shortname, + }, +}; + +/*----------------------------------------------------------------------*/ + +static void gadgetfs_nop(struct usb_gadget *arg) { } + +static int gadgetfs_probe (struct usb_gadget *gadget) +{ + CHIP = gadget->name; + return -EISNAM; +} + +static struct usb_gadget_driver probe_driver = { + .speed = USB_SPEED_HIGH, + .unbind = gadgetfs_nop, + .setup = (void *)gadgetfs_nop, + .disconnect = gadgetfs_nop, + .driver = { + .name = "nop", + }, +}; + + +/* DEVICE INITIALIZATION + * + * fd = open ("/dev/gadget/$CHIP", O_RDWR) + * status = write (fd, descriptors, sizeof descriptors) + * + * That write establishes the device configuration, so the kernel can + * bind to the controller ... guaranteeing it can handle enumeration + * at all necessary speeds. Descriptor order is: + * + * . message tag (u32, host order) ... for now, must be zero; it + * would change to support features like multi-config devices + * . full/low speed config ... all wTotalLength bytes (with interface, + * class, altsetting, endpoint, and other descriptors) + * . high speed config ... all descriptors, for high speed operation; + * this one's optional except for high-speed hardware + * . device descriptor + * + * Endpoints are not yet enabled. Drivers must wait until device + * configuration and interface altsetting changes create + * the need to configure (or unconfigure) them. + * + * After initialization, the device stays active for as long as that + * $CHIP file is open. Events must then be read from that descriptor, + * such as configuration notifications. + */ + +static int is_valid_config (struct usb_config_descriptor *config) +{ + return config->bDescriptorType == USB_DT_CONFIG + && config->bLength == USB_DT_CONFIG_SIZE + && config->bConfigurationValue != 0 + && (config->bmAttributes & USB_CONFIG_ATT_ONE) != 0 + && (config->bmAttributes & USB_CONFIG_ATT_WAKEUP) == 0; + /* FIXME if gadget->is_otg, _must_ include an otg descriptor */ + /* FIXME check lengths: walk to end */ +} + +static ssize_t +dev_config (struct file *fd, const char __user *buf, size_t len, loff_t *ptr) +{ + struct dev_data *dev = fd->private_data; + ssize_t value = len, length = len; + unsigned total; + u32 tag; + char *kbuf; + + if (len < (USB_DT_CONFIG_SIZE + USB_DT_DEVICE_SIZE + 4)) + return -EINVAL; + + /* we might need to change message format someday */ + if (copy_from_user (&tag, buf, 4)) + return -EFAULT; + if (tag != 0) + return -EINVAL; + buf += 4; + length -= 4; + + kbuf = memdup_user(buf, length); + if (IS_ERR(kbuf)) + return PTR_ERR(kbuf); + + spin_lock_irq (&dev->lock); + value = -EINVAL; + if (dev->buf) + goto fail; + dev->buf = kbuf; + + /* full or low speed config */ + dev->config = (void *) kbuf; + total = le16_to_cpu(dev->config->wTotalLength); + if (!is_valid_config (dev->config) || total >= length) + goto fail; + kbuf += total; + length -= total; + + /* optional high speed config */ + if (kbuf [1] == USB_DT_CONFIG) { + dev->hs_config = (void *) kbuf; + total = le16_to_cpu(dev->hs_config->wTotalLength); + if (!is_valid_config (dev->hs_config) || total >= length) + goto fail; + kbuf += total; + length -= total; + } + + /* could support multiple configs, using another encoding! */ + + /* device descriptor (tweaked for paranoia) */ + if (length != USB_DT_DEVICE_SIZE) + goto fail; + dev->dev = (void *)kbuf; + if (dev->dev->bLength != USB_DT_DEVICE_SIZE + || dev->dev->bDescriptorType != USB_DT_DEVICE + || dev->dev->bNumConfigurations != 1) + goto fail; + dev->dev->bNumConfigurations = 1; + dev->dev->bcdUSB = cpu_to_le16 (0x0200); + + /* triggers gadgetfs_bind(); then we can enumerate. */ + spin_unlock_irq (&dev->lock); + value = usb_gadget_probe_driver(&gadgetfs_driver, gadgetfs_bind); + if (value != 0) { + kfree (dev->buf); + dev->buf = NULL; + } else { + /* at this point "good" hardware has for the first time + * let the USB the host see us. alternatively, if users + * unplug/replug that will clear all the error state. + * + * note: everything running before here was guaranteed + * to choke driver model style diagnostics. from here + * on, they can work ... except in cleanup paths that + * kick in after the ep0 descriptor is closed. + */ + fd->f_op = &ep0_io_operations; + value = len; + } + return value; + +fail: + spin_unlock_irq (&dev->lock); + pr_debug ("%s: %s fail %Zd, %p\n", shortname, __func__, value, dev); + kfree (dev->buf); + dev->buf = NULL; + return value; +} + +static int +dev_open (struct inode *inode, struct file *fd) +{ + struct dev_data *dev = inode->i_private; + int value = -EBUSY; + + spin_lock_irq(&dev->lock); + if (dev->state == STATE_DEV_DISABLED) { + dev->ev_next = 0; + dev->state = STATE_DEV_OPENED; + fd->private_data = dev; + get_dev (dev); + value = 0; + } + spin_unlock_irq(&dev->lock); + return value; +} + +static const struct file_operations dev_init_operations = { + .owner = THIS_MODULE, + .llseek = no_llseek, + + .open = dev_open, + .write = dev_config, + .fasync = ep0_fasync, + .unlocked_ioctl = dev_ioctl, + .release = dev_release, +}; + +/*----------------------------------------------------------------------*/ + +/* FILESYSTEM AND SUPERBLOCK OPERATIONS + * + * Mounting the filesystem creates a controller file, used first for + * device configuration then later for event monitoring. + */ + + +/* FIXME PAM etc could set this security policy without mount options + * if epfiles inherited ownership and permissons from ep0 ... + */ + +static unsigned default_uid; +static unsigned default_gid; +static unsigned default_perm = S_IRUSR | S_IWUSR; + +module_param (default_uid, uint, 0644); +module_param (default_gid, uint, 0644); +module_param (default_perm, uint, 0644); + + +static struct inode * +gadgetfs_make_inode (struct super_block *sb, + void *data, const struct file_operations *fops, + int mode) +{ + struct inode *inode = new_inode (sb); + + if (inode) { + inode->i_ino = get_next_ino(); + inode->i_mode = mode; + inode->i_uid = default_uid; + inode->i_gid = default_gid; + inode->i_atime = inode->i_mtime = inode->i_ctime + = CURRENT_TIME; + inode->i_private = data; + inode->i_fop = fops; + } + return inode; +} + +/* creates in fs root directory, so non-renamable and non-linkable. + * so inode and dentry are paired, until device reconfig. + */ +static struct inode * +gadgetfs_create_file (struct super_block *sb, char const *name, + void *data, const struct file_operations *fops, + struct dentry **dentry_p) +{ + struct dentry *dentry; + struct inode *inode; + + dentry = d_alloc_name(sb->s_root, name); + if (!dentry) + return NULL; + + inode = gadgetfs_make_inode (sb, data, fops, + S_IFREG | (default_perm & S_IRWXUGO)); + if (!inode) { + dput(dentry); + return NULL; + } + d_add (dentry, inode); + *dentry_p = dentry; + return inode; +} + +static const struct super_operations gadget_fs_operations = { + .statfs = simple_statfs, + .drop_inode = generic_delete_inode, +}; + +static int +gadgetfs_fill_super (struct super_block *sb, void *opts, int silent) +{ + struct inode *inode; + struct dentry *d; + struct dev_data *dev; + + if (the_device) + return -ESRCH; + + /* fake probe to determine $CHIP */ + (void) usb_gadget_probe_driver(&probe_driver, gadgetfs_probe); + if (!CHIP) + return -ENODEV; + + /* superblock */ + sb->s_blocksize = PAGE_CACHE_SIZE; + sb->s_blocksize_bits = PAGE_CACHE_SHIFT; + sb->s_magic = GADGETFS_MAGIC; + sb->s_op = &gadget_fs_operations; + sb->s_time_gran = 1; + + /* root inode */ + inode = gadgetfs_make_inode (sb, + NULL, &simple_dir_operations, + S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) + goto enomem0; + inode->i_op = &simple_dir_inode_operations; + if (!(d = d_alloc_root (inode))) + goto enomem1; + sb->s_root = d; + + /* the ep0 file is named after the controller we expect; + * user mode code can use it for sanity checks, like we do. + */ + dev = dev_new (); + if (!dev) + goto enomem2; + + dev->sb = sb; + if (!gadgetfs_create_file (sb, CHIP, + dev, &dev_init_operations, + &dev->dentry)) + goto enomem3; + + /* other endpoint files are available after hardware setup, + * from binding to a controller. + */ + the_device = dev; + return 0; + +enomem3: + put_dev (dev); +enomem2: + dput (d); +enomem1: + iput (inode); +enomem0: + return -ENOMEM; +} + +/* "mount -t gadgetfs path /dev/gadget" ends up here */ +static struct dentry * +gadgetfs_mount (struct file_system_type *t, int flags, + const char *path, void *opts) +{ + return mount_single (t, flags, opts, gadgetfs_fill_super); +} + +static void +gadgetfs_kill_sb (struct super_block *sb) +{ + kill_litter_super (sb); + if (the_device) { + put_dev (the_device); + the_device = NULL; + } +} + +/*----------------------------------------------------------------------*/ + +static struct file_system_type gadgetfs_type = { + .owner = THIS_MODULE, + .name = shortname, + .mount = gadgetfs_mount, + .kill_sb = gadgetfs_kill_sb, +}; + +/*----------------------------------------------------------------------*/ + +static int __init init (void) +{ + int status; + + status = register_filesystem (&gadgetfs_type); + if (status == 0) + pr_info ("%s: %s, version " DRIVER_VERSION "\n", + shortname, driver_desc); + return status; +} +module_init (init); + +static void __exit cleanup (void) +{ + pr_debug ("unregister %s\n", shortname); + unregister_filesystem (&gadgetfs_type); +} +module_exit (cleanup); + diff --git a/drivers/usb/gadget/gadget_gbhc/mass_storage.c b/drivers/usb/gadget/gadget_gbhc/mass_storage.c new file mode 100644 index 0000000..0182242 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/mass_storage.c @@ -0,0 +1,190 @@ +/* + * mass_storage.c -- Mass Storage USB Gadget + * + * Copyright (C) 2003-2008 Alan Stern + * Copyright (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + * 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* + * The Mass Storage Gadget acts as a USB Mass Storage device, + * appearing to the host as a disk drive or as a CD-ROM drive. In + * addition to providing an example of a genuinely useful gadget + * driver for a USB device, it also illustrates a technique of + * double-buffering for increased throughput. Last but not least, it + * gives an easy way to probe the behavior of the Mass Storage drivers + * in a USB host. + * + * Since this file serves only administrative purposes and all the + * business logic is implemented in f_mass_storage.* file. Read + * comments in this file for more detailed description. + */ + + +#include <linux/kernel.h> +#include <linux/utsname.h> +#include <linux/usb/ch9.h> + + +/*-------------------------------------------------------------------------*/ + +#define DRIVER_DESC "Mass Storage Gadget" +#define DRIVER_VERSION "2009/09/11" + +/*-------------------------------------------------------------------------*/ + +/* + * kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ + +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" +#include "f_mass_storage.c" + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor msg_device_desc = { + .bLength = sizeof msg_device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16(0x0200), + .bDeviceClass = USB_CLASS_PER_INTERFACE, + + /* Vendor and product id can be overridden by module parameters. */ + .idVendor = cpu_to_le16(FSG_VENDOR_ID), + .idProduct = cpu_to_le16(FSG_PRODUCT_ID), + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* + * REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + + +/****************************** Configurations ******************************/ + +static struct fsg_module_parameters mod_data = { + .stall = 1 +}; +FSG_MODULE_PARAMETERS(/* no prefix */, mod_data); + +static unsigned long msg_registered; +static void msg_cleanup(void); + +static int msg_thread_exits(struct fsg_common *common) +{ + msg_cleanup(); + return 0; +} + +static int __init msg_do_config(struct usb_configuration *c) +{ + static const struct fsg_operations ops = { + .thread_exits = msg_thread_exits, + }; + static struct fsg_common common; + + struct fsg_common *retp; + struct fsg_config config; + int ret; + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + fsg_config_from_params(&config, &mod_data); + config.ops = &ops; + + retp = fsg_common_init(&common, c->cdev, &config); + if (IS_ERR(retp)) + return PTR_ERR(retp); + + ret = fsg_bind_config(c->cdev, c, &common); + fsg_common_put(&common); + return ret; +} + +static struct usb_configuration msg_config_driver = { + .label = "Linux File-Backed Storage", + .bConfigurationValue = 1, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + + +/****************************** Gadget Bind ******************************/ + +static int __init msg_bind(struct usb_composite_dev *cdev) +{ + int status; + + status = usb_add_config(cdev, &msg_config_driver, msg_do_config); + if (status < 0) + return status; + + dev_info(&cdev->gadget->dev, + DRIVER_DESC ", version: " DRIVER_VERSION "\n"); + set_bit(0, &msg_registered); + return 0; +} + + +/****************************** Some noise ******************************/ + +static struct usb_composite_driver msg_driver = { + .name = "g_mass_storage", + .dev = &msg_device_desc, + .iProduct = DRIVER_DESC, + .needs_serial = 1, +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Michal Nazarewicz"); +MODULE_LICENSE("GPL"); + +static int __init msg_init(void) +{ + return usb_composite_probe(&msg_driver, msg_bind); +} +module_init(msg_init); + +static void msg_cleanup(void) +{ + if (test_and_clear_bit(0, &msg_registered)) + usb_composite_unregister(&msg_driver); +} +module_exit(msg_cleanup); diff --git a/drivers/usb/gadget/gadget_gbhc/ncm.c b/drivers/usb/gadget/gadget_gbhc/ncm.c new file mode 100644 index 0000000..99c179a --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/ncm.c @@ -0,0 +1,248 @@ +/* + * ncm.c -- NCM gadget driver + * + * Copyright (C) 2010 Nokia Corporation + * Contact: Yauheni Kaliuta <yauheni.kaliuta@nokia.com> + * + * The driver borrows from ether.c which is: + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define DEBUG */ +/* #define VERBOSE_DEBUG */ + +#include <linux/kernel.h> +#include <linux/utsname.h> + + +#include "u_ether.h" + +#define DRIVER_DESC "NCM Gadget" + +/*-------------------------------------------------------------------------*/ + +/* + * Kbuild is not very cooperative with respect to linking separately + * compiled library objects into one module. So for now we won't use + * separate compilation ... ensuring init/exit sections work to shrink + * the runtime footprint, and giving us at least some parts of what + * a "gcc --combine ... part1.c part2.c part3.c ... " build would. + */ +#include "composite.c" +#include "usbstring.c" +#include "config.c" +#include "epautoconf.c" + +#include "f_ncm.c" +#include "u_ether.c" + +/*-------------------------------------------------------------------------*/ + +/* DO NOT REUSE THESE IDs with a protocol-incompatible driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ + +/* Thanks to NetChip Technologies for donating this product ID. + * It's for devices with only CDC Ethernet configurations. + */ +#define CDC_VENDOR_NUM 0x0525 /* NetChip */ +#define CDC_PRODUCT_NUM 0xa4a1 /* Linux-USB Ethernet Gadget */ + +/*-------------------------------------------------------------------------*/ + +static struct usb_device_descriptor device_desc = { + .bLength = sizeof device_desc, + .bDescriptorType = USB_DT_DEVICE, + + .bcdUSB = cpu_to_le16 (0x0200), + + .bDeviceClass = USB_CLASS_COMM, + .bDeviceSubClass = 0, + .bDeviceProtocol = 0, + /* .bMaxPacketSize0 = f(hardware) */ + + /* Vendor and product id defaults change according to what configs + * we support. (As does bNumConfigurations.) These values can + * also be overridden by module parameters. + */ + .idVendor = cpu_to_le16 (CDC_VENDOR_NUM), + .idProduct = cpu_to_le16 (CDC_PRODUCT_NUM), + /* .bcdDevice = f(hardware) */ + /* .iManufacturer = DYNAMIC */ + /* .iProduct = DYNAMIC */ + /* NO SERIAL NUMBER */ + .bNumConfigurations = 1, +}; + +static struct usb_otg_descriptor otg_descriptor = { + .bLength = sizeof otg_descriptor, + .bDescriptorType = USB_DT_OTG, + + /* REVISIT SRP-only hardware is possible, although + * it would not be called "OTG" ... + */ + .bmAttributes = USB_OTG_SRP | USB_OTG_HNP, +}; + +static const struct usb_descriptor_header *otg_desc[] = { + (struct usb_descriptor_header *) &otg_descriptor, + NULL, +}; + + +/* string IDs are assigned dynamically */ + +#define STRING_MANUFACTURER_IDX 0 +#define STRING_PRODUCT_IDX 1 + +static char manufacturer[50]; + +static struct usb_string strings_dev[] = { + [STRING_MANUFACTURER_IDX].s = manufacturer, + [STRING_PRODUCT_IDX].s = DRIVER_DESC, + { } /* end of list */ +}; + +static struct usb_gadget_strings stringtab_dev = { + .language = 0x0409, /* en-us */ + .strings = strings_dev, +}; + +static struct usb_gadget_strings *dev_strings[] = { + &stringtab_dev, + NULL, +}; + +static u8 hostaddr[ETH_ALEN]; + +/*-------------------------------------------------------------------------*/ + +static int __init ncm_do_config(struct usb_configuration *c) +{ + /* FIXME alloc iConfiguration string, set it in c->strings */ + + if (gadget_is_otg(c->cdev->gadget)) { + c->descriptors = otg_desc; + c->bmAttributes |= USB_CONFIG_ATT_WAKEUP; + } + + return ncm_bind_config(c, hostaddr); +} + +static struct usb_configuration ncm_config_driver = { + /* .label = f(hardware) */ + .label = "CDC Ethernet (NCM)", + .bConfigurationValue = 1, + /* .iConfiguration = DYNAMIC */ + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init gncm_bind(struct usb_composite_dev *cdev) +{ + int gcnum; + struct usb_gadget *gadget = cdev->gadget; + int status; + + /* set up network link layer */ + status = gether_setup(cdev->gadget, hostaddr); + if (status < 0) + return status; + + gcnum = usb_gadget_controller_number(gadget); + if (gcnum >= 0) + device_desc.bcdDevice = cpu_to_le16(0x0300 | gcnum); + else { + /* We assume that can_support_ecm() tells the truth; + * but if the controller isn't recognized at all then + * that assumption is a bit more likely to be wrong. + */ + dev_warn(&gadget->dev, + "controller '%s' not recognized; trying %s\n", + gadget->name, + ncm_config_driver.label); + device_desc.bcdDevice = + cpu_to_le16(0x0300 | 0x0099); + } + + + /* Allocate string descriptor numbers ... note that string + * contents can be overridden by the composite_dev glue. + */ + + /* device descriptor strings: manufacturer, product */ + snprintf(manufacturer, sizeof manufacturer, "%s %s with %s", + init_utsname()->sysname, init_utsname()->release, + gadget->name); + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_MANUFACTURER_IDX].id = status; + device_desc.iManufacturer = status; + + status = usb_string_id(cdev); + if (status < 0) + goto fail; + strings_dev[STRING_PRODUCT_IDX].id = status; + device_desc.iProduct = status; + + status = usb_add_config(cdev, &ncm_config_driver, + ncm_do_config); + if (status < 0) + goto fail; + + dev_info(&gadget->dev, "%s\n", DRIVER_DESC); + + return 0; + +fail: + gether_cleanup(); + return status; +} + +static int __exit gncm_unbind(struct usb_composite_dev *cdev) +{ + gether_cleanup(); + return 0; +} + +static struct usb_composite_driver ncm_driver = { + .name = "g_ncm", + .dev = &device_desc, + .strings = dev_strings, + .unbind = __exit_p(gncm_unbind), +}; + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Yauheni Kaliuta"); +MODULE_LICENSE("GPL"); + +static int __init init(void) +{ + return usb_composite_probe(&ncm_driver, gncm_bind); +} +module_init(init); + +static void __exit cleanup(void) +{ + usb_composite_unregister(&ncm_driver); +} +module_exit(cleanup); diff --git a/drivers/usb/gadget/gadget_gbhc/ndis.h b/drivers/usb/gadget/gadget_gbhc/ndis.h new file mode 100644 index 0000000..df886ce --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/ndis.h @@ -0,0 +1,217 @@ +/* + * ndis.h + * + * ntddndis.h modified by Benedikt Spranger <b.spranger@pengutronix.de> + * + * Thanks to the cygwin development team, + * espacially to Casper S. Hornstrup <chorns@users.sourceforge.net> + * + * THIS SOFTWARE IS NOT COPYRIGHTED + * + * This source code is offered for use in the public domain. You may + * use, modify or distribute it freely. + * + * This code is distributed in the hope that it will be useful but + * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY + * DISCLAIMED. This includes but is not limited to warranties of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#ifndef _LINUX_NDIS_H +#define _LINUX_NDIS_H + + +#define NDIS_STATUS_MULTICAST_FULL 0xC0010009 +#define NDIS_STATUS_MULTICAST_EXISTS 0xC001000A +#define NDIS_STATUS_MULTICAST_NOT_FOUND 0xC001000B + +enum NDIS_DEVICE_POWER_STATE { + NdisDeviceStateUnspecified = 0, + NdisDeviceStateD0, + NdisDeviceStateD1, + NdisDeviceStateD2, + NdisDeviceStateD3, + NdisDeviceStateMaximum +}; + +struct NDIS_PM_WAKE_UP_CAPABILITIES { + enum NDIS_DEVICE_POWER_STATE MinMagicPacketWakeUp; + enum NDIS_DEVICE_POWER_STATE MinPatternWakeUp; + enum NDIS_DEVICE_POWER_STATE MinLinkChangeWakeUp; +}; + +/* NDIS_PNP_CAPABILITIES.Flags constants */ +#define NDIS_DEVICE_WAKE_UP_ENABLE 0x00000001 +#define NDIS_DEVICE_WAKE_ON_PATTERN_MATCH_ENABLE 0x00000002 +#define NDIS_DEVICE_WAKE_ON_MAGIC_PACKET_ENABLE 0x00000004 + +struct NDIS_PNP_CAPABILITIES { + __le32 Flags; + struct NDIS_PM_WAKE_UP_CAPABILITIES WakeUpCapabilities; +}; + +struct NDIS_PM_PACKET_PATTERN { + __le32 Priority; + __le32 Reserved; + __le32 MaskSize; + __le32 PatternOffset; + __le32 PatternSize; + __le32 PatternFlags; +}; + + +/* Required Object IDs (OIDs) */ +#define OID_GEN_SUPPORTED_LIST 0x00010101 +#define OID_GEN_HARDWARE_STATUS 0x00010102 +#define OID_GEN_MEDIA_SUPPORTED 0x00010103 +#define OID_GEN_MEDIA_IN_USE 0x00010104 +#define OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105 +#define OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106 +#define OID_GEN_LINK_SPEED 0x00010107 +#define OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108 +#define OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109 +#define OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A +#define OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B +#define OID_GEN_VENDOR_ID 0x0001010C +#define OID_GEN_VENDOR_DESCRIPTION 0x0001010D +#define OID_GEN_CURRENT_PACKET_FILTER 0x0001010E +#define OID_GEN_CURRENT_LOOKAHEAD 0x0001010F +#define OID_GEN_DRIVER_VERSION 0x00010110 +#define OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111 +#define OID_GEN_PROTOCOL_OPTIONS 0x00010112 +#define OID_GEN_MAC_OPTIONS 0x00010113 +#define OID_GEN_MEDIA_CONNECT_STATUS 0x00010114 +#define OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115 +#define OID_GEN_VENDOR_DRIVER_VERSION 0x00010116 +#define OID_GEN_SUPPORTED_GUIDS 0x00010117 +#define OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118 +#define OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119 +#define OID_GEN_MACHINE_NAME 0x0001021A +#define OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B +#define OID_GEN_VLAN_ID 0x0001021C + +/* Optional OIDs */ +#define OID_GEN_MEDIA_CAPABILITIES 0x00010201 +#define OID_GEN_PHYSICAL_MEDIUM 0x00010202 + +/* Required statistics OIDs */ +#define OID_GEN_XMIT_OK 0x00020101 +#define OID_GEN_RCV_OK 0x00020102 +#define OID_GEN_XMIT_ERROR 0x00020103 +#define OID_GEN_RCV_ERROR 0x00020104 +#define OID_GEN_RCV_NO_BUFFER 0x00020105 + +/* Optional statistics OIDs */ +#define OID_GEN_DIRECTED_BYTES_XMIT 0x00020201 +#define OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202 +#define OID_GEN_MULTICAST_BYTES_XMIT 0x00020203 +#define OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204 +#define OID_GEN_BROADCAST_BYTES_XMIT 0x00020205 +#define OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206 +#define OID_GEN_DIRECTED_BYTES_RCV 0x00020207 +#define OID_GEN_DIRECTED_FRAMES_RCV 0x00020208 +#define OID_GEN_MULTICAST_BYTES_RCV 0x00020209 +#define OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A +#define OID_GEN_BROADCAST_BYTES_RCV 0x0002020B +#define OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C +#define OID_GEN_RCV_CRC_ERROR 0x0002020D +#define OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E +#define OID_GEN_GET_TIME_CAPS 0x0002020F +#define OID_GEN_GET_NETCARD_TIME 0x00020210 +#define OID_GEN_NETCARD_LOAD 0x00020211 +#define OID_GEN_DEVICE_PROFILE 0x00020212 +#define OID_GEN_INIT_TIME_MS 0x00020213 +#define OID_GEN_RESET_COUNTS 0x00020214 +#define OID_GEN_MEDIA_SENSE_COUNTS 0x00020215 +#define OID_GEN_FRIENDLY_NAME 0x00020216 +#define OID_GEN_MINIPORT_INFO 0x00020217 +#define OID_GEN_RESET_VERIFY_PARAMETERS 0x00020218 + +/* IEEE 802.3 (Ethernet) OIDs */ +#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001 + +#define OID_802_3_PERMANENT_ADDRESS 0x01010101 +#define OID_802_3_CURRENT_ADDRESS 0x01010102 +#define OID_802_3_MULTICAST_LIST 0x01010103 +#define OID_802_3_MAXIMUM_LIST_SIZE 0x01010104 +#define OID_802_3_MAC_OPTIONS 0x01010105 +#define OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101 +#define OID_802_3_XMIT_ONE_COLLISION 0x01020102 +#define OID_802_3_XMIT_MORE_COLLISIONS 0x01020103 +#define OID_802_3_XMIT_DEFERRED 0x01020201 +#define OID_802_3_XMIT_MAX_COLLISIONS 0x01020202 +#define OID_802_3_RCV_OVERRUN 0x01020203 +#define OID_802_3_XMIT_UNDERRUN 0x01020204 +#define OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205 +#define OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206 +#define OID_802_3_XMIT_LATE_COLLISIONS 0x01020207 + +/* OID_GEN_MINIPORT_INFO constants */ +#define NDIS_MINIPORT_BUS_MASTER 0x00000001 +#define NDIS_MINIPORT_WDM_DRIVER 0x00000002 +#define NDIS_MINIPORT_SG_LIST 0x00000004 +#define NDIS_MINIPORT_SUPPORTS_MEDIA_QUERY 0x00000008 +#define NDIS_MINIPORT_INDICATES_PACKETS 0x00000010 +#define NDIS_MINIPORT_IGNORE_PACKET_QUEUE 0x00000020 +#define NDIS_MINIPORT_IGNORE_REQUEST_QUEUE 0x00000040 +#define NDIS_MINIPORT_IGNORE_TOKEN_RING_ERRORS 0x00000080 +#define NDIS_MINIPORT_INTERMEDIATE_DRIVER 0x00000100 +#define NDIS_MINIPORT_IS_NDIS_5 0x00000200 +#define NDIS_MINIPORT_IS_CO 0x00000400 +#define NDIS_MINIPORT_DESERIALIZE 0x00000800 +#define NDIS_MINIPORT_REQUIRES_MEDIA_POLLING 0x00001000 +#define NDIS_MINIPORT_SUPPORTS_MEDIA_SENSE 0x00002000 +#define NDIS_MINIPORT_NETBOOT_CARD 0x00004000 +#define NDIS_MINIPORT_PM_SUPPORTED 0x00008000 +#define NDIS_MINIPORT_SUPPORTS_MAC_ADDRESS_OVERWRITE 0x00010000 +#define NDIS_MINIPORT_USES_SAFE_BUFFER_APIS 0x00020000 +#define NDIS_MINIPORT_HIDDEN 0x00040000 +#define NDIS_MINIPORT_SWENUM 0x00080000 +#define NDIS_MINIPORT_SURPRISE_REMOVE_OK 0x00100000 +#define NDIS_MINIPORT_NO_HALT_ON_SUSPEND 0x00200000 +#define NDIS_MINIPORT_HARDWARE_DEVICE 0x00400000 +#define NDIS_MINIPORT_SUPPORTS_CANCEL_SEND_PACKETS 0x00800000 +#define NDIS_MINIPORT_64BITS_DMA 0x01000000 + +#define NDIS_MEDIUM_802_3 0x00000000 +#define NDIS_MEDIUM_802_5 0x00000001 +#define NDIS_MEDIUM_FDDI 0x00000002 +#define NDIS_MEDIUM_WAN 0x00000003 +#define NDIS_MEDIUM_LOCAL_TALK 0x00000004 +#define NDIS_MEDIUM_DIX 0x00000005 +#define NDIS_MEDIUM_ARCENT_RAW 0x00000006 +#define NDIS_MEDIUM_ARCENT_878_2 0x00000007 +#define NDIS_MEDIUM_ATM 0x00000008 +#define NDIS_MEDIUM_WIRELESS_LAN 0x00000009 +#define NDIS_MEDIUM_IRDA 0x0000000A +#define NDIS_MEDIUM_BPC 0x0000000B +#define NDIS_MEDIUM_CO_WAN 0x0000000C +#define NDIS_MEDIUM_1394 0x0000000D + +#define NDIS_PACKET_TYPE_DIRECTED 0x00000001 +#define NDIS_PACKET_TYPE_MULTICAST 0x00000002 +#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004 +#define NDIS_PACKET_TYPE_BROADCAST 0x00000008 +#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010 +#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020 +#define NDIS_PACKET_TYPE_SMT 0x00000040 +#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080 +#define NDIS_PACKET_TYPE_GROUP 0x00000100 +#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200 +#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400 +#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800 + +#define NDIS_MEDIA_STATE_CONNECTED 0x00000000 +#define NDIS_MEDIA_STATE_DISCONNECTED 0x00000001 + +#define NDIS_MAC_OPTION_COPY_LOOKAHEAD_DATA 0x00000001 +#define NDIS_MAC_OPTION_RECEIVE_SERIALIZED 0x00000002 +#define NDIS_MAC_OPTION_TRANSFERS_NOT_PEND 0x00000004 +#define NDIS_MAC_OPTION_NO_LOOPBACK 0x00000008 +#define NDIS_MAC_OPTION_FULL_DUPLEX 0x00000010 +#define NDIS_MAC_OPTION_EOTX_INDICATION 0x00000020 +#define NDIS_MAC_OPTION_8021P_PRIORITY 0x00000040 +#define NDIS_MAC_OPTION_RESERVED 0x80000000 + +#endif /* _LINUX_NDIS_H */ diff --git a/drivers/usb/gadget/gadget_gbhc/rndis.c b/drivers/usb/gadget/gadget_gbhc/rndis.c new file mode 100644 index 0000000..6cea2e1 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/rndis.c @@ -0,0 +1,1214 @@ +/* + * RNDIS MSG parser + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + * + * 03/12/2004 Kai-Uwe Bloem <linux-development@auerswald.de> + * Fixed message length bug in init_response + * + * 03/25/2004 Kai-Uwe Bloem <linux-development@auerswald.de> + * Fixed rndis_rm_hdr length bug. + * + * Copyright (C) 2004 by David Brownell + * updates to merge with Linux 2.6, better match RNDIS spec + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/proc_fs.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/netdevice.h> + +#include <asm/io.h> +#include <asm/byteorder.h> +#include <asm/system.h> +#include <asm/unaligned.h> + + +#undef VERBOSE_DEBUG + +#include "rndis.h" + + +/* The driver for your USB chip needs to support ep0 OUT to work with + * RNDIS, plus all three CDC Ethernet endpoints (interrupt not optional). + * + * Windows hosts need an INF file like Documentation/usb/linux.inf + * and will be happier if you provide the host_addr module parameter. + */ + +#if 0 +static int rndis_debug = 0; +module_param (rndis_debug, int, 0); +MODULE_PARM_DESC (rndis_debug, "enable debugging"); +#else +#define rndis_debug 0 +#endif + +#define RNDIS_MAX_CONFIGS 1 + + +static rndis_params rndis_per_dev_params[RNDIS_MAX_CONFIGS]; + +/* Driver Version */ +static const __le32 rndis_driver_version = cpu_to_le32(1); + +/* Function Prototypes */ +static rndis_resp_t *rndis_add_response(int configNr, u32 length); + + +/* supported OIDs */ +static const u32 oid_supported_list[] = +{ + /* the general stuff */ + OID_GEN_SUPPORTED_LIST, + OID_GEN_HARDWARE_STATUS, + OID_GEN_MEDIA_SUPPORTED, + OID_GEN_MEDIA_IN_USE, + OID_GEN_MAXIMUM_FRAME_SIZE, + OID_GEN_LINK_SPEED, + OID_GEN_TRANSMIT_BLOCK_SIZE, + OID_GEN_RECEIVE_BLOCK_SIZE, + OID_GEN_VENDOR_ID, + OID_GEN_VENDOR_DESCRIPTION, + OID_GEN_VENDOR_DRIVER_VERSION, + OID_GEN_CURRENT_PACKET_FILTER, + OID_GEN_MAXIMUM_TOTAL_SIZE, + OID_GEN_MEDIA_CONNECT_STATUS, + OID_GEN_PHYSICAL_MEDIUM, + + /* the statistical stuff */ + OID_GEN_XMIT_OK, + OID_GEN_RCV_OK, + OID_GEN_XMIT_ERROR, + OID_GEN_RCV_ERROR, + OID_GEN_RCV_NO_BUFFER, +#ifdef RNDIS_OPTIONAL_STATS + OID_GEN_DIRECTED_BYTES_XMIT, + OID_GEN_DIRECTED_FRAMES_XMIT, + OID_GEN_MULTICAST_BYTES_XMIT, + OID_GEN_MULTICAST_FRAMES_XMIT, + OID_GEN_BROADCAST_BYTES_XMIT, + OID_GEN_BROADCAST_FRAMES_XMIT, + OID_GEN_DIRECTED_BYTES_RCV, + OID_GEN_DIRECTED_FRAMES_RCV, + OID_GEN_MULTICAST_BYTES_RCV, + OID_GEN_MULTICAST_FRAMES_RCV, + OID_GEN_BROADCAST_BYTES_RCV, + OID_GEN_BROADCAST_FRAMES_RCV, + OID_GEN_RCV_CRC_ERROR, + OID_GEN_TRANSMIT_QUEUE_LENGTH, +#endif /* RNDIS_OPTIONAL_STATS */ + + /* mandatory 802.3 */ + /* the general stuff */ + OID_802_3_PERMANENT_ADDRESS, + OID_802_3_CURRENT_ADDRESS, + OID_802_3_MULTICAST_LIST, + OID_802_3_MAC_OPTIONS, + OID_802_3_MAXIMUM_LIST_SIZE, + + /* the statistical stuff */ + OID_802_3_RCV_ERROR_ALIGNMENT, + OID_802_3_XMIT_ONE_COLLISION, + OID_802_3_XMIT_MORE_COLLISIONS, +#ifdef RNDIS_OPTIONAL_STATS + OID_802_3_XMIT_DEFERRED, + OID_802_3_XMIT_MAX_COLLISIONS, + OID_802_3_RCV_OVERRUN, + OID_802_3_XMIT_UNDERRUN, + OID_802_3_XMIT_HEARTBEAT_FAILURE, + OID_802_3_XMIT_TIMES_CRS_LOST, + OID_802_3_XMIT_LATE_COLLISIONS, +#endif /* RNDIS_OPTIONAL_STATS */ + +#ifdef RNDIS_PM + /* PM and wakeup are "mandatory" for USB, but the RNDIS specs + * don't say what they mean ... and the NDIS specs are often + * confusing and/or ambiguous in this context. (That is, more + * so than their specs for the other OIDs.) + * + * FIXME someone who knows what these should do, please + * implement them! + */ + + /* power management */ + OID_PNP_CAPABILITIES, + OID_PNP_QUERY_POWER, + OID_PNP_SET_POWER, + +#ifdef RNDIS_WAKEUP + /* wake up host */ + OID_PNP_ENABLE_WAKE_UP, + OID_PNP_ADD_WAKE_UP_PATTERN, + OID_PNP_REMOVE_WAKE_UP_PATTERN, +#endif /* RNDIS_WAKEUP */ +#endif /* RNDIS_PM */ +}; + +/* HACK: copied from net/core/dev.c to replace dev_get_stats since + * dev_get_stats cannot be called from atomic context */ +static void netdev_stats_to_stats64(struct rtnl_link_stats64 *stats64, + const struct net_device_stats *netdev_stats) +{ +#if BITS_PER_LONG == 64 + BUILD_BUG_ON(sizeof(*stats64) != sizeof(*netdev_stats)); + memcpy(stats64, netdev_stats, sizeof(*stats64)); +#else + size_t i, n = sizeof(*stats64) / sizeof(u64); + const unsigned long *src = (const unsigned long *)netdev_stats; + u64 *dst = (u64 *)stats64; + + BUILD_BUG_ON(sizeof(*netdev_stats) / sizeof(unsigned long) != + sizeof(*stats64) / sizeof(u64)); + for (i = 0; i < n; i++) + dst[i] = src[i]; +#endif +} + +/* NDIS Functions */ +static int gen_ndis_query_resp(int configNr, u32 OID, u8 *buf, + unsigned buf_len, rndis_resp_t *r) +{ + int retval = -ENOTSUPP; + u32 length = 4; /* usually */ + __le32 *outbuf; + int i, count; + rndis_query_cmplt_type *resp; + struct net_device *net; + struct rtnl_link_stats64 temp; + struct rtnl_link_stats64 *stats = &temp; + + if (!r) return -ENOMEM; + resp = (rndis_query_cmplt_type *)r->buf; + + if (!resp) return -ENOMEM; + + if (buf_len && rndis_debug > 1) { + pr_debug("query OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + pr_debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } + + /* response goes here, right after the header */ + outbuf = (__le32 *)&resp[1]; + resp->InformationBufferOffset = cpu_to_le32(16); + + net = rndis_per_dev_params[configNr].dev; + netdev_stats_to_stats64(stats, &net->stats); + + switch (OID) { + + /* general oids (table 4-1) */ + + /* mandatory */ + case OID_GEN_SUPPORTED_LIST: + pr_debug("%s: OID_GEN_SUPPORTED_LIST\n", __func__); + length = sizeof(oid_supported_list); + count = length / sizeof(u32); + for (i = 0; i < count; i++) + outbuf[i] = cpu_to_le32(oid_supported_list[i]); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_HARDWARE_STATUS: + pr_debug("%s: OID_GEN_HARDWARE_STATUS\n", __func__); + /* Bogus question! + * Hardware must be ready to receive high level protocols. + * BTW: + * reddite ergo quae sunt Caesaris Caesari + * et quae sunt Dei Deo! + */ + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_SUPPORTED: + pr_debug("%s: OID_GEN_MEDIA_SUPPORTED\n", __func__); + *outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_IN_USE: + pr_debug("%s: OID_GEN_MEDIA_IN_USE\n", __func__); + /* one medium, one transport... (maybe you do it better) */ + *outbuf = cpu_to_le32(rndis_per_dev_params[configNr].medium); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MAXIMUM_FRAME_SIZE: + pr_debug("%s: OID_GEN_MAXIMUM_FRAME_SIZE\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].dev->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_LINK_SPEED: + if (rndis_debug > 1) + pr_debug("%s: OID_GEN_LINK_SPEED\n", __func__); + if (rndis_per_dev_params[configNr].media_state + == NDIS_MEDIA_STATE_DISCONNECTED) + *outbuf = cpu_to_le32(0); + else + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].speed); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_TRANSMIT_BLOCK_SIZE: + pr_debug("%s: OID_GEN_TRANSMIT_BLOCK_SIZE\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].dev->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RECEIVE_BLOCK_SIZE: + pr_debug("%s: OID_GEN_RECEIVE_BLOCK_SIZE\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].dev->mtu); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_VENDOR_ID: + pr_debug("%s: OID_GEN_VENDOR_ID\n", __func__); + *outbuf = cpu_to_le32( + rndis_per_dev_params[configNr].vendorID); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_VENDOR_DESCRIPTION: + pr_debug("%s: OID_GEN_VENDOR_DESCRIPTION\n", __func__); + if (rndis_per_dev_params[configNr].vendorDescr) { + length = strlen(rndis_per_dev_params[configNr]. + vendorDescr); + memcpy(outbuf, + rndis_per_dev_params[configNr].vendorDescr, + length); + } else { + outbuf[0] = 0; + } + retval = 0; + break; + + case OID_GEN_VENDOR_DRIVER_VERSION: + pr_debug("%s: OID_GEN_VENDOR_DRIVER_VERSION\n", __func__); + /* Created as LE */ + *outbuf = rndis_driver_version; + retval = 0; + break; + + /* mandatory */ + case OID_GEN_CURRENT_PACKET_FILTER: + pr_debug("%s: OID_GEN_CURRENT_PACKET_FILTER\n", __func__); + *outbuf = cpu_to_le32(*rndis_per_dev_params[configNr].filter); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MAXIMUM_TOTAL_SIZE: + pr_debug("%s: OID_GEN_MAXIMUM_TOTAL_SIZE\n", __func__); + *outbuf = cpu_to_le32(RNDIS_MAX_TOTAL_SIZE); + retval = 0; + break; + + /* mandatory */ + case OID_GEN_MEDIA_CONNECT_STATUS: + if (rndis_debug > 1) + pr_debug("%s: OID_GEN_MEDIA_CONNECT_STATUS\n", __func__); + *outbuf = cpu_to_le32(rndis_per_dev_params[configNr] + .media_state); + retval = 0; + break; + + case OID_GEN_PHYSICAL_MEDIUM: + pr_debug("%s: OID_GEN_PHYSICAL_MEDIUM\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* The RNDIS specification is incomplete/wrong. Some versions + * of MS-Windows expect OIDs that aren't specified there. Other + * versions emit undefined RNDIS messages. DOCUMENT ALL THESE! + */ + case OID_GEN_MAC_OPTIONS: /* from WinME */ + pr_debug("%s: OID_GEN_MAC_OPTIONS\n", __func__); + *outbuf = cpu_to_le32( + NDIS_MAC_OPTION_RECEIVE_SERIALIZED + | NDIS_MAC_OPTION_FULL_DUPLEX); + retval = 0; + break; + + /* statistics OIDs (table 4-2) */ + + /* mandatory */ + case OID_GEN_XMIT_OK: + if (rndis_debug > 1) + pr_debug("%s: OID_GEN_XMIT_OK\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->tx_packets + - stats->tx_errors - stats->tx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_OK: + if (rndis_debug > 1) + pr_debug("%s: OID_GEN_RCV_OK\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_packets + - stats->rx_errors - stats->rx_dropped); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_XMIT_ERROR: + if (rndis_debug > 1) + pr_debug("%s: OID_GEN_XMIT_ERROR\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->tx_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_ERROR: + if (rndis_debug > 1) + pr_debug("%s: OID_GEN_RCV_ERROR\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_GEN_RCV_NO_BUFFER: + pr_debug("%s: OID_GEN_RCV_NO_BUFFER\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_dropped); + retval = 0; + } + break; + + /* ieee802.3 OIDs (table 4-3) */ + + /* mandatory */ + case OID_802_3_PERMANENT_ADDRESS: + pr_debug("%s: OID_802_3_PERMANENT_ADDRESS\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + length = ETH_ALEN; + memcpy(outbuf, + rndis_per_dev_params[configNr].host_mac, + length); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_CURRENT_ADDRESS: + pr_debug("%s: OID_802_3_CURRENT_ADDRESS\n", __func__); + if (rndis_per_dev_params[configNr].dev) { + length = ETH_ALEN; + memcpy(outbuf, + rndis_per_dev_params [configNr].host_mac, + length); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_MULTICAST_LIST: + pr_debug("%s: OID_802_3_MULTICAST_LIST\n", __func__); + /* Multicast base address only */ + *outbuf = cpu_to_le32(0xE0000000); + retval = 0; + break; + + /* mandatory */ + case OID_802_3_MAXIMUM_LIST_SIZE: + pr_debug("%s: OID_802_3_MAXIMUM_LIST_SIZE\n", __func__); + /* Multicast base address only */ + *outbuf = cpu_to_le32(1); + retval = 0; + break; + + case OID_802_3_MAC_OPTIONS: + pr_debug("%s: OID_802_3_MAC_OPTIONS\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* ieee802.3 statistics OIDs (table 4-4) */ + + /* mandatory */ + case OID_802_3_RCV_ERROR_ALIGNMENT: + pr_debug("%s: OID_802_3_RCV_ERROR_ALIGNMENT\n", __func__); + if (stats) { + *outbuf = cpu_to_le32(stats->rx_frame_errors); + retval = 0; + } + break; + + /* mandatory */ + case OID_802_3_XMIT_ONE_COLLISION: + pr_debug("%s: OID_802_3_XMIT_ONE_COLLISION\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + /* mandatory */ + case OID_802_3_XMIT_MORE_COLLISIONS: + pr_debug("%s: OID_802_3_XMIT_MORE_COLLISIONS\n", __func__); + *outbuf = cpu_to_le32(0); + retval = 0; + break; + + default: + pr_warning("%s: query unknown OID 0x%08X\n", + __func__, OID); + } + if (retval < 0) + length = 0; + + resp->InformationBufferLength = cpu_to_le32(length); + r->length = length + sizeof(*resp); + resp->MessageLength = cpu_to_le32(r->length); + return retval; +} + +static int gen_ndis_set_resp(u8 configNr, u32 OID, u8 *buf, u32 buf_len, + rndis_resp_t *r) +{ + rndis_set_cmplt_type *resp; + int i, retval = -ENOTSUPP; + struct rndis_params *params; + + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *)r->buf; + if (!resp) + return -ENOMEM; + + if (buf_len && rndis_debug > 1) { + pr_debug("set OID %08x value, len %d:\n", OID, buf_len); + for (i = 0; i < buf_len; i += 16) { + pr_debug("%03d: %08x %08x %08x %08x\n", i, + get_unaligned_le32(&buf[i]), + get_unaligned_le32(&buf[i + 4]), + get_unaligned_le32(&buf[i + 8]), + get_unaligned_le32(&buf[i + 12])); + } + } + + params = &rndis_per_dev_params[configNr]; + switch (OID) { + case OID_GEN_CURRENT_PACKET_FILTER: + + /* these NDIS_PACKET_TYPE_* bitflags are shared with + * cdc_filter; it's not RNDIS-specific + * NDIS_PACKET_TYPE_x == USB_CDC_PACKET_TYPE_x for x in: + * PROMISCUOUS, DIRECTED, + * MULTICAST, ALL_MULTICAST, BROADCAST + */ + *params->filter = (u16)get_unaligned_le32(buf); + pr_debug("%s: OID_GEN_CURRENT_PACKET_FILTER %08x\n", + __func__, *params->filter); + + /* this call has a significant side effect: it's + * what makes the packet flow start and stop, like + * activating the CDC Ethernet altsetting. + */ + retval = 0; + if (*params->filter) { + params->state = RNDIS_DATA_INITIALIZED; + netif_carrier_on(params->dev); + if (netif_running(params->dev)) + netif_wake_queue(params->dev); + } else { + params->state = RNDIS_INITIALIZED; + netif_carrier_off(params->dev); + netif_stop_queue(params->dev); + } + break; + + case OID_802_3_MULTICAST_LIST: + /* I think we can ignore this */ + pr_debug("%s: OID_802_3_MULTICAST_LIST\n", __func__); + retval = 0; + break; + + default: + pr_warning("%s: set unknown OID 0x%08X, size %d\n", + __func__, OID, buf_len); + } + + return retval; +} + +/* + * Response Functions + */ + +static int rndis_init_response(int configNr, rndis_init_msg_type *buf) +{ + rndis_init_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + if (!params->dev) + return -ENOTSUPP; + + r = rndis_add_response(configNr, sizeof(rndis_init_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_init_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(REMOTE_NDIS_INITIALIZE_CMPLT); + resp->MessageLength = cpu_to_le32(52); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + resp->MajorVersion = cpu_to_le32(RNDIS_MAJOR_VERSION); + resp->MinorVersion = cpu_to_le32(RNDIS_MINOR_VERSION); + resp->DeviceFlags = cpu_to_le32(RNDIS_DF_CONNECTIONLESS); + resp->Medium = cpu_to_le32(RNDIS_MEDIUM_802_3); + resp->MaxPacketsPerTransfer = cpu_to_le32(1); + resp->MaxTransferSize = cpu_to_le32( + params->dev->mtu + + sizeof(struct ethhdr) + + sizeof(struct rndis_packet_msg_type) + + 22); + resp->PacketAlignmentFactor = cpu_to_le32(0); + resp->AFListOffset = cpu_to_le32(0); + resp->AFListSize = cpu_to_le32(0); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_query_response(int configNr, rndis_query_msg_type *buf) +{ + rndis_query_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + /* pr_debug("%s: OID = %08X\n", __func__, cpu_to_le32(buf->OID)); */ + if (!params->dev) + return -ENOTSUPP; + + /* + * we need more memory: + * gen_ndis_query_resp expects enough space for + * rndis_query_cmplt_type followed by data. + * oid_supported_list is the largest data reply + */ + r = rndis_add_response(configNr, + sizeof(oid_supported_list) + sizeof(rndis_query_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_query_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(REMOTE_NDIS_QUERY_CMPLT); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + + if (gen_ndis_query_resp(configNr, le32_to_cpu(buf->OID), + le32_to_cpu(buf->InformationBufferOffset) + + 8 + (u8 *)buf, + le32_to_cpu(buf->InformationBufferLength), + r)) { + /* OID not supported */ + resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); + resp->MessageLength = cpu_to_le32(sizeof *resp); + resp->InformationBufferLength = cpu_to_le32(0); + resp->InformationBufferOffset = cpu_to_le32(0); + } else + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_set_response(int configNr, rndis_set_msg_type *buf) +{ + u32 BufLength, BufOffset; + rndis_set_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + r = rndis_add_response(configNr, sizeof(rndis_set_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_set_cmplt_type *)r->buf; + + BufLength = le32_to_cpu(buf->InformationBufferLength); + BufOffset = le32_to_cpu(buf->InformationBufferOffset); + +#ifdef VERBOSE_DEBUG + pr_debug("%s: Length: %d\n", __func__, BufLength); + pr_debug("%s: Offset: %d\n", __func__, BufOffset); + pr_debug("%s: InfoBuffer: ", __func__); + + for (i = 0; i < BufLength; i++) { + pr_debug("%02x ", *(((u8 *) buf) + i + 8 + BufOffset)); + } + + pr_debug("\n"); +#endif + + resp->MessageType = cpu_to_le32(REMOTE_NDIS_SET_CMPLT); + resp->MessageLength = cpu_to_le32(16); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + if (gen_ndis_set_resp(configNr, le32_to_cpu(buf->OID), + ((u8 *)buf) + 8 + BufOffset, BufLength, r)) + resp->Status = cpu_to_le32(RNDIS_STATUS_NOT_SUPPORTED); + else + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_reset_response(int configNr, rndis_reset_msg_type *buf) +{ + rndis_reset_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + r = rndis_add_response(configNr, sizeof(rndis_reset_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_reset_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32(REMOTE_NDIS_RESET_CMPLT); + resp->MessageLength = cpu_to_le32(16); + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + /* resent information */ + resp->AddressingReset = cpu_to_le32(1); + + params->resp_avail(params->v); + return 0; +} + +static int rndis_keepalive_response(int configNr, + rndis_keepalive_msg_type *buf) +{ + rndis_keepalive_cmplt_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + /* host "should" check only in RNDIS_DATA_INITIALIZED state */ + + r = rndis_add_response(configNr, sizeof(rndis_keepalive_cmplt_type)); + if (!r) + return -ENOMEM; + resp = (rndis_keepalive_cmplt_type *)r->buf; + + resp->MessageType = cpu_to_le32( + REMOTE_NDIS_KEEPALIVE_CMPLT); + resp->MessageLength = cpu_to_le32(16); + resp->RequestID = buf->RequestID; /* Still LE in msg buffer */ + resp->Status = cpu_to_le32(RNDIS_STATUS_SUCCESS); + + params->resp_avail(params->v); + return 0; +} + + +/* + * Device to Host Comunication + */ +static int rndis_indicate_status_msg(int configNr, u32 status) +{ + rndis_indicate_status_msg_type *resp; + rndis_resp_t *r; + struct rndis_params *params = rndis_per_dev_params + configNr; + + if (params->state == RNDIS_UNINITIALIZED) + return -ENOTSUPP; + + r = rndis_add_response(configNr, + sizeof(rndis_indicate_status_msg_type)); + if (!r) + return -ENOMEM; + resp = (rndis_indicate_status_msg_type *)r->buf; + + resp->MessageType = cpu_to_le32(REMOTE_NDIS_INDICATE_STATUS_MSG); + resp->MessageLength = cpu_to_le32(20); + resp->Status = cpu_to_le32(status); + resp->StatusBufferLength = cpu_to_le32(0); + resp->StatusBufferOffset = cpu_to_le32(0); + + params->resp_avail(params->v); + return 0; +} + +int rndis_signal_connect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = NDIS_MEDIA_STATE_CONNECTED; + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_CONNECT); +} + +int rndis_signal_disconnect(int configNr) +{ + rndis_per_dev_params[configNr].media_state + = NDIS_MEDIA_STATE_DISCONNECTED; + return rndis_indicate_status_msg(configNr, + RNDIS_STATUS_MEDIA_DISCONNECT); +} + +void rndis_uninit(int configNr) +{ + u8 *buf; + u32 length; + + if (configNr >= RNDIS_MAX_CONFIGS) + return; + rndis_per_dev_params[configNr].state = RNDIS_UNINITIALIZED; + + /* drain the response queue */ + while ((buf = rndis_get_next_response(configNr, &length))) + rndis_free_response(configNr, buf); +} + +void rndis_set_host_mac(int configNr, const u8 *addr) +{ + rndis_per_dev_params[configNr].host_mac = addr; +} + +/* + * Message Parser + */ +int rndis_msg_parser(u8 configNr, u8 *buf) +{ + u32 MsgType, MsgLength; + __le32 *tmp; + struct rndis_params *params; + + if (!buf) + return -ENOMEM; + + tmp = (__le32 *)buf; + MsgType = get_unaligned_le32(tmp++); + MsgLength = get_unaligned_le32(tmp++); + + if (configNr >= RNDIS_MAX_CONFIGS) + return -ENOTSUPP; + params = &rndis_per_dev_params[configNr]; + + /* NOTE: RNDIS is *EXTREMELY* chatty ... Windows constantly polls for + * rx/tx statistics and link status, in addition to KEEPALIVE traffic + * and normal HC level polling to see if there's any IN traffic. + */ + + /* For USB: responses may take up to 10 seconds */ + switch (MsgType) { + case REMOTE_NDIS_INITIALIZE_MSG: + pr_debug("%s: REMOTE_NDIS_INITIALIZE_MSG\n", + __func__); + params->state = RNDIS_INITIALIZED; + return rndis_init_response(configNr, + (rndis_init_msg_type *)buf); + + case REMOTE_NDIS_HALT_MSG: + pr_debug("%s: REMOTE_NDIS_HALT_MSG\n", + __func__); + params->state = RNDIS_UNINITIALIZED; + if (params->dev) { + netif_carrier_off(params->dev); + netif_stop_queue(params->dev); + } + return 0; + + case REMOTE_NDIS_QUERY_MSG: + return rndis_query_response(configNr, + (rndis_query_msg_type *)buf); + + case REMOTE_NDIS_SET_MSG: + return rndis_set_response(configNr, + (rndis_set_msg_type *)buf); + + case REMOTE_NDIS_RESET_MSG: + pr_debug("%s: REMOTE_NDIS_RESET_MSG\n", + __func__); + return rndis_reset_response(configNr, + (rndis_reset_msg_type *)buf); + + case REMOTE_NDIS_KEEPALIVE_MSG: + /* For USB: host does this every 5 seconds */ + if (rndis_debug > 1) + pr_debug("%s: REMOTE_NDIS_KEEPALIVE_MSG\n", + __func__); + return rndis_keepalive_response(configNr, + (rndis_keepalive_msg_type *) + buf); + + default: + /* At least Windows XP emits some undefined RNDIS messages. + * In one case those messages seemed to relate to the host + * suspending itself. + */ + pr_warning("%s: unknown RNDIS message 0x%08X len %d\n", + __func__, MsgType, MsgLength); + { + unsigned i; + for (i = 0; i < MsgLength; i += 16) { + pr_debug("%03d: " + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + " %02x %02x %02x %02x" + "\n", + i, + buf[i], buf [i+1], + buf[i+2], buf[i+3], + buf[i+4], buf [i+5], + buf[i+6], buf[i+7], + buf[i+8], buf [i+9], + buf[i+10], buf[i+11], + buf[i+12], buf [i+13], + buf[i+14], buf[i+15]); + } + } + break; + } + + return -ENOTSUPP; +} + +int rndis_register(void (*resp_avail)(void *v), void *v) +{ + u8 i; + + if (!resp_avail) + return -EINVAL; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + if (!rndis_per_dev_params[i].used) { + rndis_per_dev_params[i].used = 1; + rndis_per_dev_params[i].resp_avail = resp_avail; + rndis_per_dev_params[i].v = v; + pr_debug("%s: configNr = %d\n", __func__, i); + return i; + } + } + pr_debug("failed\n"); + + return -ENODEV; +} + +void rndis_deregister(int configNr) +{ + pr_debug("%s:\n", __func__); + + if (configNr >= RNDIS_MAX_CONFIGS) return; + rndis_per_dev_params[configNr].used = 0; +} + +int rndis_set_param_dev(u8 configNr, struct net_device *dev, u16 *cdc_filter) +{ + pr_debug("%s:\n", __func__); + if (!dev) + return -EINVAL; + if (configNr >= RNDIS_MAX_CONFIGS) return -1; + + rndis_per_dev_params[configNr].dev = dev; + rndis_per_dev_params[configNr].filter = cdc_filter; + + return 0; +} + +int rndis_set_param_vendor(u8 configNr, u32 vendorID, const char *vendorDescr) +{ + pr_debug("%s:\n", __func__); + if (!vendorDescr) return -1; + if (configNr >= RNDIS_MAX_CONFIGS) return -1; + + rndis_per_dev_params[configNr].vendorID = vendorID; + rndis_per_dev_params[configNr].vendorDescr = vendorDescr; + + return 0; +} + +int rndis_set_param_medium(u8 configNr, u32 medium, u32 speed) +{ + pr_debug("%s: %u %u\n", __func__, medium, speed); + if (configNr >= RNDIS_MAX_CONFIGS) return -1; + + rndis_per_dev_params[configNr].medium = medium; + rndis_per_dev_params[configNr].speed = speed; + + return 0; +} + +void rndis_add_hdr(struct sk_buff *skb) +{ + struct rndis_packet_msg_type *header; + + if (!skb) + return; + header = (void *)skb_push(skb, sizeof(*header)); + memset(header, 0, sizeof *header); + header->MessageType = cpu_to_le32(REMOTE_NDIS_PACKET_MSG); + header->MessageLength = cpu_to_le32(skb->len); + header->DataOffset = cpu_to_le32(36); + header->DataLength = cpu_to_le32(skb->len - sizeof(*header)); +} + +void rndis_free_response(int configNr, u8 *buf) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (r && r->buf == buf) { + list_del(&r->list); + kfree(r); + } + } +} + +u8 *rndis_get_next_response(int configNr, u32 *length) +{ + rndis_resp_t *r; + struct list_head *act, *tmp; + + if (!length) return NULL; + + list_for_each_safe(act, tmp, + &(rndis_per_dev_params[configNr].resp_queue)) + { + r = list_entry(act, rndis_resp_t, list); + if (!r->send) { + r->send = 1; + *length = r->length; + return r->buf; + } + } + + return NULL; +} + +static rndis_resp_t *rndis_add_response(int configNr, u32 length) +{ + rndis_resp_t *r; + + /* NOTE: this gets copied into ether.c USB_BUFSIZ bytes ... */ + r = kmalloc(sizeof(rndis_resp_t) + length, GFP_ATOMIC); + if (!r) return NULL; + + r->buf = (u8 *)(r + 1); + r->length = length; + r->send = 0; + + list_add_tail(&r->list, + &(rndis_per_dev_params[configNr].resp_queue)); + return r; +} + +int rndis_rm_hdr(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list) +{ + /* tmp points to a struct rndis_packet_msg_type */ + __le32 *tmp = (void *)skb->data; + + /* MessageType, MessageLength */ + if (cpu_to_le32(REMOTE_NDIS_PACKET_MSG) + != get_unaligned(tmp++)) { + dev_kfree_skb_any(skb); + return -EINVAL; + } + tmp++; + + /* DataOffset, DataLength */ + if (!skb_pull(skb, get_unaligned_le32(tmp++) + 8)) { + dev_kfree_skb_any(skb); + return -EOVERFLOW; + } + skb_trim(skb, get_unaligned_le32(tmp++)); + + skb_queue_tail(list, skb); + return 0; +} + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static int rndis_proc_show(struct seq_file *m, void *v) +{ + rndis_params *param = m->private; + + seq_printf(m, + "Config Nr. %d\n" + "used : %s\n" + "state : %s\n" + "medium : 0x%08X\n" + "speed : %d\n" + "cable : %s\n" + "vendor ID : 0x%08X\n" + "vendor : %s\n", + param->confignr, (param->used) ? "y" : "n", + ({ char *s = "?"; + switch (param->state) { + case RNDIS_UNINITIALIZED: + s = "RNDIS_UNINITIALIZED"; break; + case RNDIS_INITIALIZED: + s = "RNDIS_INITIALIZED"; break; + case RNDIS_DATA_INITIALIZED: + s = "RNDIS_DATA_INITIALIZED"; break; + }; s; }), + param->medium, + (param->media_state) ? 0 : param->speed*100, + (param->media_state) ? "disconnected" : "connected", + param->vendorID, param->vendorDescr); + return 0; +} + +static ssize_t rndis_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + rndis_params *p = PDE(file->f_path.dentry->d_inode)->data; + u32 speed = 0; + int i, fl_speed = 0; + + for (i = 0; i < count; i++) { + char c; + if (get_user(c, buffer)) + return -EFAULT; + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + fl_speed = 1; + speed = speed * 10 + c - '0'; + break; + case 'C': + case 'c': + rndis_signal_connect(p->confignr); + break; + case 'D': + case 'd': + rndis_signal_disconnect(p->confignr); + break; + default: + if (fl_speed) p->speed = speed; + else pr_debug("%c is not valid\n", c); + break; + } + + buffer++; + } + + return count; +} + +static int rndis_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, rndis_proc_show, PDE(inode)->data); +} + +static const struct file_operations rndis_proc_fops = { + .owner = THIS_MODULE, + .open = rndis_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = rndis_proc_write, +}; + +#define NAME_TEMPLATE "driver/rndis-%03d" + +static struct proc_dir_entry *rndis_connect_state [RNDIS_MAX_CONFIGS]; + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + +int rndis_init(void) +{ + u8 i; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + char name [20]; + + sprintf(name, NAME_TEMPLATE, i); + rndis_connect_state[i] = proc_create_data(name, 0660, NULL, + &rndis_proc_fops, + (void *)(rndis_per_dev_params + i)); + if (!rndis_connect_state[i]) { + pr_debug("%s: remove entries", __func__); + while (i) { + sprintf(name, NAME_TEMPLATE, --i); + remove_proc_entry(name, NULL); + } + pr_debug("\n"); + return -EIO; + } +#endif + rndis_per_dev_params[i].confignr = i; + rndis_per_dev_params[i].used = 0; + rndis_per_dev_params[i].state = RNDIS_UNINITIALIZED; + rndis_per_dev_params[i].media_state + = NDIS_MEDIA_STATE_DISCONNECTED; + INIT_LIST_HEAD(&(rndis_per_dev_params[i].resp_queue)); + } + + return 0; +} + +void rndis_exit(void) +{ +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + u8 i; + char name[20]; + + for (i = 0; i < RNDIS_MAX_CONFIGS; i++) { + sprintf(name, NAME_TEMPLATE, i); + remove_proc_entry(name, NULL); + } +#endif +} diff --git a/drivers/usb/gadget/gadget_gbhc/rndis.h b/drivers/usb/gadget/gadget_gbhc/rndis.h new file mode 100644 index 0000000..907c330 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/rndis.h @@ -0,0 +1,268 @@ +/* + * RNDIS Definitions for Remote NDIS + * + * Authors: Benedikt Spranger, Pengutronix + * Robert Schwebel, Pengutronix + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This software was originally developed in conformance with + * Microsoft's Remote NDIS Specification License Agreement. + */ + +#ifndef _LINUX_RNDIS_H +#define _LINUX_RNDIS_H + +#include "ndis.h" + +#define RNDIS_MAXIMUM_FRAME_SIZE 1518 +#define RNDIS_MAX_TOTAL_SIZE 1558 + +/* Remote NDIS Versions */ +#define RNDIS_MAJOR_VERSION 1 +#define RNDIS_MINOR_VERSION 0 + +/* Status Values */ +#define RNDIS_STATUS_SUCCESS 0x00000000U /* Success */ +#define RNDIS_STATUS_FAILURE 0xC0000001U /* Unspecified error */ +#define RNDIS_STATUS_INVALID_DATA 0xC0010015U /* Invalid data */ +#define RNDIS_STATUS_NOT_SUPPORTED 0xC00000BBU /* Unsupported request */ +#define RNDIS_STATUS_MEDIA_CONNECT 0x4001000BU /* Device connected */ +#define RNDIS_STATUS_MEDIA_DISCONNECT 0x4001000CU /* Device disconnected */ +/* For all not specified status messages: + * RNDIS_STATUS_Xxx -> NDIS_STATUS_Xxx + */ + +/* Message Set for Connectionless (802.3) Devices */ +#define REMOTE_NDIS_PACKET_MSG 0x00000001U +#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002U /* Initialize device */ +#define REMOTE_NDIS_HALT_MSG 0x00000003U +#define REMOTE_NDIS_QUERY_MSG 0x00000004U +#define REMOTE_NDIS_SET_MSG 0x00000005U +#define REMOTE_NDIS_RESET_MSG 0x00000006U +#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007U +#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008U + +/* Message completion */ +#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002U +#define REMOTE_NDIS_QUERY_CMPLT 0x80000004U +#define REMOTE_NDIS_SET_CMPLT 0x80000005U +#define REMOTE_NDIS_RESET_CMPLT 0x80000006U +#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008U + +/* Device Flags */ +#define RNDIS_DF_CONNECTIONLESS 0x00000001U +#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002U + +#define RNDIS_MEDIUM_802_3 0x00000000U + +/* from drivers/net/sk98lin/h/skgepnmi.h */ +#define OID_PNP_CAPABILITIES 0xFD010100 +#define OID_PNP_SET_POWER 0xFD010101 +#define OID_PNP_QUERY_POWER 0xFD010102 +#define OID_PNP_ADD_WAKE_UP_PATTERN 0xFD010103 +#define OID_PNP_REMOVE_WAKE_UP_PATTERN 0xFD010104 +#define OID_PNP_ENABLE_WAKE_UP 0xFD010106 + + +typedef struct rndis_init_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 MaxTransferSize; +} rndis_init_msg_type; + +typedef struct rndis_init_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 MajorVersion; + __le32 MinorVersion; + __le32 DeviceFlags; + __le32 Medium; + __le32 MaxPacketsPerTransfer; + __le32 MaxTransferSize; + __le32 PacketAlignmentFactor; + __le32 AFListOffset; + __le32 AFListSize; +} rndis_init_cmplt_type; + +typedef struct rndis_halt_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_halt_msg_type; + +typedef struct rndis_query_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_query_msg_type; + +typedef struct rndis_query_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; +} rndis_query_cmplt_type; + +typedef struct rndis_set_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 OID; + __le32 InformationBufferLength; + __le32 InformationBufferOffset; + __le32 DeviceVcHandle; +} rndis_set_msg_type; + +typedef struct rndis_set_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_set_cmplt_type; + +typedef struct rndis_reset_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 Reserved; +} rndis_reset_msg_type; + +typedef struct rndis_reset_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 AddressingReset; +} rndis_reset_cmplt_type; + +typedef struct rndis_indicate_status_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 Status; + __le32 StatusBufferLength; + __le32 StatusBufferOffset; +} rndis_indicate_status_msg_type; + +typedef struct rndis_keepalive_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; +} rndis_keepalive_msg_type; + +typedef struct rndis_keepalive_cmplt_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 RequestID; + __le32 Status; +} rndis_keepalive_cmplt_type; + +struct rndis_packet_msg_type +{ + __le32 MessageType; + __le32 MessageLength; + __le32 DataOffset; + __le32 DataLength; + __le32 OOBDataOffset; + __le32 OOBDataLength; + __le32 NumOOBDataElements; + __le32 PerPacketInfoOffset; + __le32 PerPacketInfoLength; + __le32 VcHandle; + __le32 Reserved; +} __attribute__ ((packed)); + +struct rndis_config_parameter +{ + __le32 ParameterNameOffset; + __le32 ParameterNameLength; + __le32 ParameterType; + __le32 ParameterValueOffset; + __le32 ParameterValueLength; +}; + +/* implementation specific */ +enum rndis_state +{ + RNDIS_UNINITIALIZED, + RNDIS_INITIALIZED, + RNDIS_DATA_INITIALIZED, +}; + +typedef struct rndis_resp_t +{ + struct list_head list; + u8 *buf; + u32 length; + int send; +} rndis_resp_t; + +typedef struct rndis_params +{ + u8 confignr; + u8 used; + u16 saved_filter; + enum rndis_state state; + u32 medium; + u32 speed; + u32 media_state; + + const u8 *host_mac; + u16 *filter; + struct net_device *dev; + + u32 vendorID; + const char *vendorDescr; + void (*resp_avail)(void *v); + void *v; + struct list_head resp_queue; +} rndis_params; + +/* RNDIS Message parser and other useless functions */ +int rndis_msg_parser (u8 configNr, u8 *buf); +int rndis_register(void (*resp_avail)(void *v), void *v); +void rndis_deregister (int configNr); +int rndis_set_param_dev (u8 configNr, struct net_device *dev, + u16 *cdc_filter); +int rndis_set_param_vendor (u8 configNr, u32 vendorID, + const char *vendorDescr); +int rndis_set_param_medium (u8 configNr, u32 medium, u32 speed); +void rndis_add_hdr (struct sk_buff *skb); +int rndis_rm_hdr(struct gether *port, struct sk_buff *skb, + struct sk_buff_head *list); +u8 *rndis_get_next_response (int configNr, u32 *length); +void rndis_free_response (int configNr, u8 *buf); + +void rndis_uninit (int configNr); +int rndis_signal_connect (int configNr); +int rndis_signal_disconnect (int configNr); +int rndis_state (int configNr); +extern void rndis_set_host_mac (int configNr, const u8 *addr); + +int rndis_init(void); +void rndis_exit (void); + +#endif /* _LINUX_RNDIS_H */ diff --git a/drivers/usb/gadget/gadget_gbhc/storage_common.c b/drivers/usb/gadget/gadget_gbhc/storage_common.c new file mode 100644 index 0000000..a872248 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/storage_common.c @@ -0,0 +1,797 @@ +/* + * storage_common.c -- Common definitions for mass storage functionality + * + * Copyright (C) 2003-2008 Alan Stern + * Copyeight (C) 2009 Samsung Electronics + * Author: Michal Nazarewicz (m.nazarewicz@samsung.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +/* + * This file requires the following identifiers used in USB strings to + * be defined (each of type pointer to char): + * - fsg_string_manufacturer -- name of the manufacturer + * - fsg_string_product -- name of the product + * - fsg_string_config -- name of the configuration + * - fsg_string_interface -- name of the interface + * The first four are only needed when FSG_DESCRIPTORS_DEVICE_STRINGS + * macro is defined prior to including this file. + */ + +/* + * When FSG_NO_INTR_EP is defined fsg_fs_intr_in_desc and + * fsg_hs_intr_in_desc objects as well as + * FSG_FS_FUNCTION_PRE_EP_ENTRIES and FSG_HS_FUNCTION_PRE_EP_ENTRIES + * macros are not defined. + * + * When FSG_NO_DEVICE_STRINGS is defined FSG_STRING_MANUFACTURER, + * FSG_STRING_PRODUCT, FSG_STRING_SERIAL and FSG_STRING_CONFIG are not + * defined (as well as corresponding entries in string tables are + * missing) and FSG_STRING_INTERFACE has value of zero. + * + * When FSG_NO_OTG is defined fsg_otg_desc won't be defined. + */ + +/* + * When FSG_BUFFHD_STATIC_BUFFER is defined when this file is included + * the fsg_buffhd structure's buf field will be an array of FSG_BUFLEN + * characters rather then a pointer to void. + */ + + +#include <linux/usb/storage.h> +#include <scsi/scsi.h> +#include <asm/unaligned.h> + + +/* + * Thanks to NetChip Technologies for donating this product ID. + * + * DO NOT REUSE THESE IDs with any other driver!! Ever!! + * Instead: allocate your own, using normal USB-IF procedures. + */ +#define FSG_VENDOR_ID 0x0525 /* NetChip */ +#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ + + +/*-------------------------------------------------------------------------*/ + + +#ifndef DEBUG +#undef VERBOSE_DEBUG +#undef DUMP_MSGS +#endif /* !DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VLDBG LDBG +#else +#define VLDBG(lun, fmt, args...) do { } while (0) +#endif /* VERBOSE_DEBUG */ + +#define LDBG(lun, fmt, args...) dev_dbg (&(lun)->dev, fmt, ## args) +#define LERROR(lun, fmt, args...) dev_err (&(lun)->dev, fmt, ## args) +#define LWARN(lun, fmt, args...) dev_warn(&(lun)->dev, fmt, ## args) +#define LINFO(lun, fmt, args...) dev_info(&(lun)->dev, fmt, ## args) + +/* + * Keep those macros in sync with those in + * include/linux/usb/composite.h or else GCC will complain. If they + * are identical (the same names of arguments, white spaces in the + * same places) GCC will allow redefinition otherwise (even if some + * white space is removed or added) warning will be issued. + * + * Those macros are needed here because File Storage Gadget does not + * include the composite.h header. For composite gadgets those macros + * are redundant since composite.h is included any way. + * + * One could check whether those macros are already defined (which + * would indicate composite.h had been included) or not (which would + * indicate we were in FSG) but this is not done because a warning is + * desired if definitions here differ from the ones in composite.h. + * + * We want the definitions to match and be the same in File Storage + * Gadget as well as Mass Storage Function (and so composite gadgets + * using MSF). If someone changes them in composite.h it will produce + * a warning in this file when building MSF. + */ +#define DBG(d, fmt, args...) dev_dbg(&(d)->gadget->dev , fmt , ## args) +#define VDBG(d, fmt, args...) dev_vdbg(&(d)->gadget->dev , fmt , ## args) +#define ERROR(d, fmt, args...) dev_err(&(d)->gadget->dev , fmt , ## args) +#define WARNING(d, fmt, args...) dev_warn(&(d)->gadget->dev , fmt , ## args) +#define INFO(d, fmt, args...) dev_info(&(d)->gadget->dev , fmt , ## args) + + + +#ifdef DUMP_MSGS + +# define dump_msg(fsg, /* const char * */ label, \ + /* const u8 * */ buf, /* unsigned */ length) do { \ + if (length < 512) { \ + DBG(fsg, "%s, length %u:\n", label, length); \ + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, \ + 16, 1, buf, length, 0); \ + } \ +} while (0) + +# define dump_cdb(fsg) do { } while (0) + +#else + +# define dump_msg(fsg, /* const char * */ label, \ + /* const u8 * */ buf, /* unsigned */ length) do { } while (0) + +# ifdef VERBOSE_DEBUG + +# define dump_cdb(fsg) \ + print_hex_dump(KERN_DEBUG, "SCSI CDB: ", DUMP_PREFIX_NONE, \ + 16, 1, (fsg)->cmnd, (fsg)->cmnd_size, 0) \ + +# else + +# define dump_cdb(fsg) do { } while (0) + +# endif /* VERBOSE_DEBUG */ + +#endif /* DUMP_MSGS */ + + + + + +/*-------------------------------------------------------------------------*/ + +/* Bulk-only data structures */ + +/* Command Block Wrapper */ +struct fsg_bulk_cb_wrap { + __le32 Signature; /* Contains 'USBC' */ + u32 Tag; /* Unique per command id */ + __le32 DataTransferLength; /* Size of the data */ + u8 Flags; /* Direction in bit 7 */ + u8 Lun; /* LUN (normally 0) */ + u8 Length; /* Of the CDB, <= MAX_COMMAND_SIZE */ + u8 CDB[16]; /* Command Data Block */ +}; + +#define USB_BULK_CB_WRAP_LEN 31 +#define USB_BULK_CB_SIG 0x43425355 /* Spells out USBC */ +#define USB_BULK_IN_FLAG 0x80 + +/* Command Status Wrapper */ +struct bulk_cs_wrap { + __le32 Signature; /* Should = 'USBS' */ + u32 Tag; /* Same as original command */ + __le32 Residue; /* Amount not transferred */ + u8 Status; /* See below */ +}; + +#define USB_BULK_CS_WRAP_LEN 13 +#define USB_BULK_CS_SIG 0x53425355 /* Spells out 'USBS' */ +#define USB_STATUS_PASS 0 +#define USB_STATUS_FAIL 1 +#define USB_STATUS_PHASE_ERROR 2 + +/* Bulk-only class specific requests */ +#define USB_BULK_RESET_REQUEST 0xff +#define USB_BULK_GET_MAX_LUN_REQUEST 0xfe + + +/* CBI Interrupt data structure */ +struct interrupt_data { + u8 bType; + u8 bValue; +}; + +#define CBI_INTERRUPT_DATA_LEN 2 + +/* CBI Accept Device-Specific Command request */ +#define USB_CBI_ADSC_REQUEST 0x00 + + +/* Length of a SCSI Command Data Block */ +#define MAX_COMMAND_SIZE 16 + +/* SCSI Sense Key/Additional Sense Code/ASC Qualifier values */ +#define SS_NO_SENSE 0 +#define SS_COMMUNICATION_FAILURE 0x040800 +#define SS_INVALID_COMMAND 0x052000 +#define SS_INVALID_FIELD_IN_CDB 0x052400 +#define SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE 0x052100 +#define SS_LOGICAL_UNIT_NOT_SUPPORTED 0x052500 +#define SS_MEDIUM_NOT_PRESENT 0x023a00 +#define SS_MEDIUM_REMOVAL_PREVENTED 0x055302 +#define SS_NOT_READY_TO_READY_TRANSITION 0x062800 +#define SS_RESET_OCCURRED 0x062900 +#define SS_SAVING_PARAMETERS_NOT_SUPPORTED 0x053900 +#define SS_UNRECOVERED_READ_ERROR 0x031100 +#define SS_WRITE_ERROR 0x030c02 +#define SS_WRITE_PROTECTED 0x072700 + +#define SK(x) ((u8) ((x) >> 16)) /* Sense Key byte, etc. */ +#define ASC(x) ((u8) ((x) >> 8)) +#define ASCQ(x) ((u8) (x)) + + +/*-------------------------------------------------------------------------*/ + + +struct fsg_lun { + struct file *filp; + loff_t file_length; + loff_t num_sectors; + + unsigned int initially_ro:1; + unsigned int ro:1; + unsigned int removable:1; + unsigned int cdrom:1; + unsigned int prevent_medium_removal:1; + unsigned int registered:1; + unsigned int info_valid:1; + unsigned int nofua:1; + + u32 sense_data; + u32 sense_data_info; + u32 unit_attention_data; + + struct device dev; +}; + +#define fsg_lun_is_open(curlun) ((curlun)->filp != NULL) + +static struct fsg_lun *fsg_lun_from_dev(struct device *dev) +{ + return container_of(dev, struct fsg_lun, dev); +} + + +/* Big enough to hold our biggest descriptor */ +#define EP0_BUFSIZE 256 +#define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ + +/* Number of buffers we will use. 2 is enough for double-buffering */ +#define FSG_NUM_BUFFERS 2 + +/* Default size of buffer length. */ +#define FSG_BUFLEN ((u32)16384) + +/* Maximal number of LUNs supported in mass storage function */ +#define FSG_MAX_LUNS 8 + +enum fsg_buffer_state { + BUF_STATE_EMPTY = 0, + BUF_STATE_FULL, + BUF_STATE_BUSY +}; + +struct fsg_buffhd { +#ifdef FSG_BUFFHD_STATIC_BUFFER + char buf[FSG_BUFLEN]; +#else + void *buf; +#endif + enum fsg_buffer_state state; + struct fsg_buffhd *next; + + /* + * The NetChip 2280 is faster, and handles some protocol faults + * better, if we don't submit any short bulk-out read requests. + * So we will record the intended request length here. + */ + unsigned int bulk_out_intended_length; + + struct usb_request *inreq; + int inreq_busy; + struct usb_request *outreq; + int outreq_busy; +}; + +enum fsg_state { + /* This one isn't used anywhere */ + FSG_STATE_COMMAND_PHASE = -10, + FSG_STATE_DATA_PHASE, + FSG_STATE_STATUS_PHASE, + + FSG_STATE_IDLE = 0, + FSG_STATE_ABORT_BULK_OUT, + FSG_STATE_RESET, + FSG_STATE_INTERFACE_CHANGE, + FSG_STATE_CONFIG_CHANGE, + FSG_STATE_DISCONNECT, + FSG_STATE_EXIT, + FSG_STATE_TERMINATED +}; + +enum data_direction { + DATA_DIR_UNKNOWN = 0, + DATA_DIR_FROM_HOST, + DATA_DIR_TO_HOST, + DATA_DIR_NONE +}; + + +/*-------------------------------------------------------------------------*/ + + +static inline u32 get_unaligned_be24(u8 *buf) +{ + return 0xffffff & (u32) get_unaligned_be32(buf - 1); +} + + +/*-------------------------------------------------------------------------*/ + + +enum { +#ifndef FSG_NO_DEVICE_STRINGS + FSG_STRING_MANUFACTURER = 1, + FSG_STRING_PRODUCT, + FSG_STRING_SERIAL, + FSG_STRING_CONFIG, +#endif + FSG_STRING_INTERFACE +}; + + +#ifndef FSG_NO_OTG +static struct usb_otg_descriptor +fsg_otg_desc = { + .bLength = sizeof fsg_otg_desc, + .bDescriptorType = USB_DT_OTG, + + .bmAttributes = USB_OTG_SRP, +}; +#endif + +/* There is only one interface. */ + +static struct usb_interface_descriptor +fsg_intf_desc = { + .bLength = sizeof fsg_intf_desc, + .bDescriptorType = USB_DT_INTERFACE, + + .bNumEndpoints = 2, /* Adjusted during fsg_bind() */ + .bInterfaceClass = USB_CLASS_MASS_STORAGE, + .bInterfaceSubClass = USB_SC_SCSI, /* Adjusted during fsg_bind() */ + .bInterfaceProtocol = USB_PR_BULK, /* Adjusted during fsg_bind() */ + .iInterface = FSG_STRING_INTERFACE, +}; + +/* + * Three full-speed endpoint descriptors: bulk-in, bulk-out, and + * interrupt-in. + */ + +static struct usb_endpoint_descriptor +fsg_fs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +static struct usb_endpoint_descriptor +fsg_fs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* wMaxPacketSize set by autoconfiguration */ +}; + +#ifndef FSG_NO_INTR_EP + +static struct usb_endpoint_descriptor +fsg_fs_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bEndpointAddress = USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 32, /* frames -> 32 ms */ +}; + +#ifndef FSG_NO_OTG +# define FSG_FS_FUNCTION_PRE_EP_ENTRIES 2 +#else +# define FSG_FS_FUNCTION_PRE_EP_ENTRIES 1 +#endif + +#endif + +static struct usb_descriptor_header *fsg_fs_function[] = { +#ifndef FSG_NO_OTG + (struct usb_descriptor_header *) &fsg_otg_desc, +#endif + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_fs_bulk_out_desc, +#ifndef FSG_NO_INTR_EP + (struct usb_descriptor_header *) &fsg_fs_intr_in_desc, +#endif + NULL, +}; + + +/* + * USB 2.0 devices need to expose both high speed and full speed + * descriptors, unless they only run at full speed. + * + * That means alternate endpoint descriptors (bigger packets) + * and a "device qualifier" ... plus more construction options + * for the configuration descriptor. + */ +static struct usb_endpoint_descriptor +fsg_hs_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), +}; + +static struct usb_endpoint_descriptor +fsg_hs_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ +}; + +#ifndef FSG_NO_INTR_EP + +static struct usb_endpoint_descriptor +fsg_hs_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_intr_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 9, /* 2**(9-1) = 256 uframes -> 32 ms */ +}; + +#ifndef FSG_NO_OTG +# define FSG_HS_FUNCTION_PRE_EP_ENTRIES 2 +#else +# define FSG_HS_FUNCTION_PRE_EP_ENTRIES 1 +#endif + +#endif + +static struct usb_descriptor_header *fsg_hs_function[] = { +#ifndef FSG_NO_OTG + (struct usb_descriptor_header *) &fsg_otg_desc, +#endif + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_hs_bulk_out_desc, +#ifndef FSG_NO_INTR_EP + (struct usb_descriptor_header *) &fsg_hs_intr_in_desc, +#endif + NULL, +}; + +/* Maxpacket and other transfer characteristics vary by speed. */ +static struct usb_endpoint_descriptor * +fsg_ep_desc(struct usb_gadget *g, struct usb_endpoint_descriptor *fs, + struct usb_endpoint_descriptor *hs) +{ + if (gadget_is_dualspeed(g) && g->speed == USB_SPEED_HIGH) + return hs; + return fs; +} + + +/* Static strings, in UTF-8 (for simplicity we use only ASCII characters) */ +static struct usb_string fsg_strings[] = { +#ifndef FSG_NO_DEVICE_STRINGS + {FSG_STRING_MANUFACTURER, fsg_string_manufacturer}, + {FSG_STRING_PRODUCT, fsg_string_product}, + {FSG_STRING_SERIAL, ""}, + {FSG_STRING_CONFIG, fsg_string_config}, +#endif + {FSG_STRING_INTERFACE, fsg_string_interface}, + {} +}; + +static struct usb_gadget_strings fsg_stringtab = { + .language = 0x0409, /* en-us */ + .strings = fsg_strings, +}; + + + /*-------------------------------------------------------------------------*/ + +/* + * If the next two routines are called while the gadget is registered, + * the caller must own fsg->filesem for writing. + */ + +static int fsg_lun_open(struct fsg_lun *curlun, const char *filename) +{ + int ro; + struct file *filp = NULL; + int rc = -EINVAL; + struct inode *inode = NULL; + loff_t size; + loff_t num_sectors; + loff_t min_sectors; + + /* R/W if we can, R/O if we must */ + ro = curlun->initially_ro; + if (!ro) { + filp = filp_open(filename, O_RDWR | O_LARGEFILE, 0); + if (PTR_ERR(filp) == -EROFS || PTR_ERR(filp) == -EACCES) + ro = 1; + } + if (ro) + filp = filp_open(filename, O_RDONLY | O_LARGEFILE, 0); + if (IS_ERR(filp)) { + LINFO(curlun, "unable to open backing file: %s\n", filename); + return PTR_ERR(filp); + } + + if (!(filp->f_mode & FMODE_WRITE)) + ro = 1; + + if (filp->f_path.dentry) + inode = filp->f_path.dentry->d_inode; + if (!inode || (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))) { + LINFO(curlun, "invalid file type: %s\n", filename); + goto out; + } + + /* + * If we can't read the file, it's no good. + * If we can't write the file, use it read-only. + */ + if (!filp->f_op || !(filp->f_op->read || filp->f_op->aio_read)) { + LINFO(curlun, "file not readable: %s\n", filename); + goto out; + } + if (!(filp->f_op->write || filp->f_op->aio_write)) + ro = 1; + + size = i_size_read(inode->i_mapping->host); + if (size < 0) { + LINFO(curlun, "unable to find file size: %s\n", filename); + rc = (int) size; + goto out; + } + num_sectors = size >> 9; /* File size in 512-byte blocks */ + min_sectors = 1; + if (curlun->cdrom) { + num_sectors &= ~3; /* Reduce to a multiple of 2048 */ + min_sectors = 300*4; /* Smallest track is 300 frames */ + if (num_sectors >= 256*60*75*4) { + num_sectors = (256*60*75 - 1) * 4; + LINFO(curlun, "file too big: %s\n", filename); + LINFO(curlun, "using only first %d blocks\n", + (int) num_sectors); + } + } + if (num_sectors < min_sectors) { + LINFO(curlun, "file too small: %s\n", filename); + rc = -ETOOSMALL; + goto out; + } + + get_file(filp); + curlun->ro = ro; + curlun->filp = filp; + curlun->file_length = size; + curlun->num_sectors = num_sectors; + LDBG(curlun, "open backing file: %s\n", filename); + rc = 0; + +out: + filp_close(filp, current->files); + return rc; +} + + +static void fsg_lun_close(struct fsg_lun *curlun) +{ + if (curlun->filp) { + LDBG(curlun, "close backing file\n"); + fput(curlun->filp); + curlun->filp = NULL; + } +} + + +/*-------------------------------------------------------------------------*/ + +/* + * Sync the file data, don't bother with the metadata. + * This code was copied from fs/buffer.c:sys_fdatasync(). + */ +static int fsg_lun_fsync_sub(struct fsg_lun *curlun) +{ + struct file *filp = curlun->filp; + + if (curlun->ro || !filp) + return 0; + return vfs_fsync(filp, 1); +} + +static void store_cdrom_address(u8 *dest, int msf, u32 addr) +{ + if (msf) { + /* Convert to Minutes-Seconds-Frames */ + addr >>= 2; /* Convert to 2048-byte frames */ + addr += 2*75; /* Lead-in occupies 2 seconds */ + dest[3] = addr % 75; /* Frames */ + addr /= 75; + dest[2] = addr % 60; /* Seconds */ + addr /= 60; + dest[1] = addr; /* Minutes */ + dest[0] = 0; /* Reserved */ + } else { + /* Absolute sector */ + put_unaligned_be32(addr, dest); + } +} + + +/*-------------------------------------------------------------------------*/ + + +static ssize_t fsg_show_ro(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return sprintf(buf, "%d\n", fsg_lun_is_open(curlun) + ? curlun->ro + : curlun->initially_ro); +} + +static ssize_t fsg_show_nofua(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + + return sprintf(buf, "%u\n", curlun->nofua); +} + +static ssize_t fsg_show_file(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + char *p; + ssize_t rc; + + down_read(filesem); + if (fsg_lun_is_open(curlun)) { /* Get the complete pathname */ + p = d_path(&curlun->filp->f_path, buf, PAGE_SIZE - 1); + if (IS_ERR(p)) + rc = PTR_ERR(p); + else { + rc = strlen(p); + memmove(buf, p, rc); + buf[rc] = '\n'; /* Add a newline */ + buf[++rc] = 0; + } + } else { /* No file, return 0 bytes */ + *buf = 0; + rc = 0; + } + up_read(filesem); + return rc; +} + + +static ssize_t fsg_store_ro(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + ssize_t rc; + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + unsigned ro; + + rc = kstrtouint(buf, 2, &ro); + if (rc) + return rc; + + /* + * Allow the write-enable status to change only while the + * backing file is closed. + */ + down_read(filesem); + if (fsg_lun_is_open(curlun)) { + LDBG(curlun, "read-only status change prevented\n"); + rc = -EBUSY; + } else { + curlun->ro = ro; + curlun->initially_ro = ro; + LDBG(curlun, "read-only status set to %d\n", curlun->ro); + rc = count; + } + up_read(filesem); + return rc; +} + +static ssize_t fsg_store_nofua(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + unsigned nofua; + int ret; + + ret = kstrtouint(buf, 2, &nofua); + if (ret) + return ret; + + /* Sync data when switching from async mode to sync */ + if (!nofua && curlun->nofua) + fsg_lun_fsync_sub(curlun); + + curlun->nofua = nofua; + + return count; +} + +static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fsg_lun *curlun = fsg_lun_from_dev(dev); + struct rw_semaphore *filesem = dev_get_drvdata(dev); + int rc = 0; + + +#ifndef CONFIG_USB_ANDROID_MASS_STORAGE + /* disabled in android because we need to allow closing the backing file + * if the media was removed + */ + if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) { + LDBG(curlun, "eject attempt prevented\n"); + return -EBUSY; /* "Door is locked" */ + } +#endif + + /* Remove a trailing newline */ + if (count > 0 && buf[count-1] == '\n') + ((char *) buf)[count-1] = 0; /* Ugh! */ + + /* Eject current medium */ + down_write(filesem); + if (fsg_lun_is_open(curlun)) { + fsg_lun_close(curlun); + curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT; + } + + /* Load new medium */ + if (count > 0 && buf[0]) { + rc = fsg_lun_open(curlun, buf); + if (rc == 0) + curlun->unit_attention_data = + SS_NOT_READY_TO_READY_TRANSITION; + } + up_write(filesem); + return (rc < 0 ? rc : count); +} diff --git a/drivers/usb/gadget/gadget_gbhc/u_ether.c b/drivers/usb/gadget/gadget_gbhc/u_ether.c new file mode 100644 index 0000000..73e9d4b --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/u_ether.c @@ -0,0 +1,1024 @@ +/* + * u_ether.c -- Ethernet-over-USB link layer utilities for Gadget stack + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/kernel.h> +#include <linux/gfp.h> +#include <linux/device.h> +#include <linux/ctype.h> +#include <linux/etherdevice.h> +#include <linux/ethtool.h> + +#include "u_ether.h" + + +/* + * This component encapsulates the Ethernet link glue needed to provide + * one (!) network link through the USB gadget stack, normally "usb0". + * + * The control and data models are handled by the function driver which + * connects to this code; such as CDC Ethernet (ECM or EEM), + * "CDC Subset", or RNDIS. That includes all descriptor and endpoint + * management. + * + * Link level addressing is handled by this component using module + * parameters; if no such parameters are provided, random link level + * addresses are used. Each end of the link uses one address. The + * host end address is exported in various ways, and is often recorded + * in configuration databases. + * + * The driver which assembles each configuration using such a link is + * responsible for ensuring that each configuration includes at most one + * instance of is network link. (The network layer provides ways for + * this single "physical" link to be used by multiple virtual links.) + */ + +#define UETH__VERSION "29-May-2008" + +struct eth_dev { + /* lock is held while accessing port_usb + * or updating its backlink port_usb->ioport + */ + spinlock_t lock; + struct gether *port_usb; + + struct net_device *net; + struct usb_gadget *gadget; + + spinlock_t req_lock; /* guard {rx,tx}_reqs */ + struct list_head tx_reqs, rx_reqs; + atomic_t tx_qlen; + + struct sk_buff_head rx_frames; + + unsigned header_len; + struct sk_buff *(*wrap)(struct gether *, struct sk_buff *skb); + int (*unwrap)(struct gether *, + struct sk_buff *skb, + struct sk_buff_head *list); + + struct work_struct work; + + unsigned long todo; +#define WORK_RX_MEMORY 0 + + bool zlp; + u8 host_mac[ETH_ALEN]; +}; + +/*-------------------------------------------------------------------------*/ + +#define RX_EXTRA 20 /* bytes guarding against rx overflows */ + +#define DEFAULT_QLEN 2 /* double buffering by default */ + + +#ifdef CONFIG_USB_GADGET_DUALSPEED + +static unsigned qmult = 5; +module_param(qmult, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(qmult, "queue length multiplier at high speed"); + +#else /* full speed (low speed doesn't do bulk) */ +#define qmult 1 +#endif + +/* for dual-speed hardware, use deeper queues at highspeed */ +static inline int qlen(struct usb_gadget *gadget) +{ + if (gadget_is_dualspeed(gadget) && gadget->speed == USB_SPEED_HIGH) + return qmult * DEFAULT_QLEN; + else + return DEFAULT_QLEN; +} + +/*-------------------------------------------------------------------------*/ + +/* REVISIT there must be a better way than having two sets + * of debug calls ... + */ + +#undef DBG +#undef VDBG +#undef ERROR +#undef INFO + +#define xprintk(d, level, fmt, args...) \ + printk(level "%s: " fmt , (d)->net->name , ## args) + +#ifdef DEBUG +#undef DEBUG +#define DBG(dev, fmt, args...) \ + xprintk(dev , KERN_DEBUG , fmt , ## args) +#else +#define DBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#ifdef VERBOSE_DEBUG +#define VDBG DBG +#else +#define VDBG(dev, fmt, args...) \ + do { } while (0) +#endif /* DEBUG */ + +#define ERROR(dev, fmt, args...) \ + xprintk(dev , KERN_ERR , fmt , ## args) +#define INFO(dev, fmt, args...) \ + xprintk(dev , KERN_INFO , fmt , ## args) + +/*-------------------------------------------------------------------------*/ + +/* NETWORK DRIVER HOOKUP (to the layer above this driver) */ + +static int ueth_change_mtu(struct net_device *net, int new_mtu) +{ + struct eth_dev *dev = netdev_priv(net); + unsigned long flags; + int status = 0; + + /* don't change MTU on "live" link (peer won't know) */ + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + status = -EBUSY; + else if (new_mtu <= ETH_HLEN || new_mtu > ETH_FRAME_LEN) + status = -ERANGE; + else + net->mtu = new_mtu; + spin_unlock_irqrestore(&dev->lock, flags); + + return status; +} + +static void eth_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *p) +{ + struct eth_dev *dev = netdev_priv(net); + + strlcpy(p->driver, "g_ether", sizeof p->driver); + strlcpy(p->version, UETH__VERSION, sizeof p->version); + strlcpy(p->fw_version, dev->gadget->name, sizeof p->fw_version); + strlcpy(p->bus_info, dev_name(&dev->gadget->dev), sizeof p->bus_info); +} + +/* REVISIT can also support: + * - WOL (by tracking suspends and issuing remote wakeup) + * - msglevel (implies updated messaging) + * - ... probably more ethtool ops + */ + +static const struct ethtool_ops ops = { + .get_drvinfo = eth_get_drvinfo, + .get_link = ethtool_op_get_link, +}; + +static void defer_kevent(struct eth_dev *dev, int flag) +{ + if (test_and_set_bit(flag, &dev->todo)) + return; + if (!schedule_work(&dev->work)) + ERROR(dev, "kevent %d may have been dropped\n", flag); + else + DBG(dev, "kevent %d scheduled\n", flag); +} + +static void rx_complete(struct usb_ep *ep, struct usb_request *req); + +static int +rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) +{ + struct sk_buff *skb; + int retval = -ENOMEM; + size_t size = 0; + struct usb_ep *out; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + out = dev->port_usb->out_ep; + else + out = NULL; + spin_unlock_irqrestore(&dev->lock, flags); + + if (!out) + return -ENOTCONN; + + + /* Padding up to RX_EXTRA handles minor disagreements with host. + * Normally we use the USB "terminate on short read" convention; + * so allow up to (N*maxpacket), since that memory is normally + * already allocated. Some hardware doesn't deal well with short + * reads (e.g. DMA must be N*maxpacket), so for now don't trim a + * byte off the end (to force hardware errors on overflow). + * + * RNDIS uses internal framing, and explicitly allows senders to + * pad to end-of-packet. That's potentially nice for speed, but + * means receivers can't recover lost synch on their own (because + * new packets don't only start after a short RX). + */ + size += sizeof(struct ethhdr) + dev->net->mtu + RX_EXTRA; + size += dev->port_usb->header_len; + size += out->maxpacket - 1; + size -= size % out->maxpacket; + + if (dev->port_usb->is_fixed) + size = max_t(size_t, size, dev->port_usb->fixed_out_len); +#ifdef CONFIG_USB_S3C_OTGD + skb = alloc_skb(size + NET_IP_ALIGN + 6, gfp_flags); +#else + skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags); +#endif + if (skb == NULL) { + DBG(dev, "no rx skb\n"); + goto enomem; + } + + /* Some platforms perform better when IP packets are aligned, + * but on at least one, checksumming fails otherwise. Note: + * RNDIS headers involve variable numbers of LE32 values. + */ +#ifdef CONFIG_USB_S3C_OTGD + skb_reserve(skb, NET_IP_ALIGN + 6); +#else + skb_reserve(skb, NET_IP_ALIGN); +#endif + req->buf = skb->data; + req->length = size; + req->complete = rx_complete; + req->context = skb; + + retval = usb_ep_queue(out, req, gfp_flags); + if (retval == -ENOMEM) +enomem: + defer_kevent(dev, WORK_RX_MEMORY); + if (retval) { + DBG(dev, "rx submit --> %d\n", retval); + if (skb) + dev_kfree_skb_any(skb); + spin_lock_irqsave(&dev->req_lock, flags); + list_add(&req->list, &dev->rx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return retval; +} + +static void rx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context, *skb2; + struct eth_dev *dev = ep->driver_data; + int status = req->status; + + switch (status) { + + /* normal completion */ + case 0: + skb_put(skb, req->actual); + + if (dev->unwrap) { + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + status = dev->unwrap(dev->port_usb, + skb, + &dev->rx_frames); + } else { + dev_kfree_skb_any(skb); + status = -ENOTCONN; + } + spin_unlock_irqrestore(&dev->lock, flags); + } else { + skb_queue_tail(&dev->rx_frames, skb); + } + skb = NULL; + + skb2 = skb_dequeue(&dev->rx_frames); + while (skb2) { + if (status < 0 + || ETH_HLEN > skb2->len + || skb2->len > ETH_FRAME_LEN) { + dev->net->stats.rx_errors++; + dev->net->stats.rx_length_errors++; + DBG(dev, "rx length %d\n", skb2->len); + dev_kfree_skb_any(skb2); + goto next_frame; + } + skb2->protocol = eth_type_trans(skb2, dev->net); + dev->net->stats.rx_packets++; + dev->net->stats.rx_bytes += skb2->len; + + /* no buffer copies needed, unless hardware can't + * use skb buffers. + */ + status = netif_rx(skb2); +next_frame: + skb2 = skb_dequeue(&dev->rx_frames); + } + break; + + /* software-driven interface shutdown */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + VDBG(dev, "rx shutdown, code %d\n", status); + goto quiesce; + + /* for hardware automagic (such as pxa) */ + case -ECONNABORTED: /* endpoint reset */ + DBG(dev, "rx %s reset\n", ep->name); + defer_kevent(dev, WORK_RX_MEMORY); +quiesce: + dev_kfree_skb_any(skb); + goto clean; + + /* data overrun */ + case -EOVERFLOW: + dev->net->stats.rx_over_errors++; + /* FALLTHROUGH */ + + default: + dev->net->stats.rx_errors++; + DBG(dev, "rx status %d\n", status); + break; + } + + if (skb) + dev_kfree_skb_any(skb); + if (!netif_running(dev->net)) { +clean: + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->rx_reqs); + spin_unlock(&dev->req_lock); + req = NULL; + } + if (req) + rx_submit(dev, req, GFP_ATOMIC); +} + +static int prealloc(struct list_head *list, struct usb_ep *ep, unsigned n) +{ + unsigned i; + struct usb_request *req; + + if (!n) + return -ENOMEM; + + /* queue/recycle up to N requests */ + i = n; + list_for_each_entry(req, list, list) { + if (i-- == 0) + goto extra; + } + while (i--) { + req = usb_ep_alloc_request(ep, GFP_ATOMIC); + if (!req) + return list_empty(list) ? -ENOMEM : 0; + list_add(&req->list, list); + } + return 0; + +extra: + /* free extras */ + for (;;) { + struct list_head *next; + + next = req->list.next; + list_del(&req->list); + usb_ep_free_request(ep, req); + + if (next == list) + break; + + req = container_of(next, struct usb_request, list); + } + return 0; +} + +static int alloc_requests(struct eth_dev *dev, struct gether *link, unsigned n) +{ + int status; + + spin_lock(&dev->req_lock); + status = prealloc(&dev->tx_reqs, link->in_ep, n); + if (status < 0) + goto fail; + status = prealloc(&dev->rx_reqs, link->out_ep, n); + if (status < 0) + goto fail; + goto done; +fail: + DBG(dev, "can't alloc requests\n"); +done: + spin_unlock(&dev->req_lock); + return status; +} + +static void rx_fill(struct eth_dev *dev, gfp_t gfp_flags) +{ + struct usb_request *req; + unsigned long flags; + + /* fill unused rxq slots with some skb */ + spin_lock_irqsave(&dev->req_lock, flags); + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del_init(&req->list); + spin_unlock_irqrestore(&dev->req_lock, flags); + + if (rx_submit(dev, req, gfp_flags) < 0) { + defer_kevent(dev, WORK_RX_MEMORY); + return; + } + + spin_lock_irqsave(&dev->req_lock, flags); + } + spin_unlock_irqrestore(&dev->req_lock, flags); +} + +static void eth_work(struct work_struct *work) +{ + struct eth_dev *dev = container_of(work, struct eth_dev, work); + + if (test_and_clear_bit(WORK_RX_MEMORY, &dev->todo)) { + if (netif_running(dev->net)) + rx_fill(dev, GFP_KERNEL); + } + + if (dev->todo) + DBG(dev, "work done, flags = 0x%lx\n", dev->todo); +} + +static void tx_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct sk_buff *skb = req->context; + struct eth_dev *dev = ep->driver_data; + + switch (req->status) { + default: + dev->net->stats.tx_errors++; + VDBG(dev, "tx err %d\n", req->status); + /* FALLTHROUGH */ + case -ECONNRESET: /* unlink */ + case -ESHUTDOWN: /* disconnect etc */ + break; + case 0: + dev->net->stats.tx_bytes += skb->len; + } + dev->net->stats.tx_packets++; + + spin_lock(&dev->req_lock); + list_add(&req->list, &dev->tx_reqs); + spin_unlock(&dev->req_lock); + dev_kfree_skb_any(skb); + +#ifdef CONFIG_USB_S3C_OTGD + if (req->buf != skb->data) + kfree(req->buf); +#endif + atomic_dec(&dev->tx_qlen); + if (netif_carrier_ok(dev->net)) + netif_wake_queue(dev->net); +} + +static inline int is_promisc(u16 cdc_filter) +{ + return cdc_filter & USB_CDC_PACKET_TYPE_PROMISCUOUS; +} + +static netdev_tx_t eth_start_xmit(struct sk_buff *skb, + struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + int length = skb->len; + int retval; + struct usb_request *req = NULL; + unsigned long flags; + struct usb_ep *in; + u16 cdc_filter; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + in = dev->port_usb->in_ep; + cdc_filter = dev->port_usb->cdc_filter; + } else { + in = NULL; + cdc_filter = 0; + } + spin_unlock_irqrestore(&dev->lock, flags); + + if (!in) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* apply outgoing CDC or RNDIS filters */ + if (!is_promisc(cdc_filter)) { + u8 *dest = skb->data; + + if (is_multicast_ether_addr(dest)) { + u16 type; + + /* ignores USB_CDC_PACKET_TYPE_MULTICAST and host + * SET_ETHERNET_MULTICAST_FILTERS requests + */ + if (is_broadcast_ether_addr(dest)) + type = USB_CDC_PACKET_TYPE_BROADCAST; + else + type = USB_CDC_PACKET_TYPE_ALL_MULTICAST; + if (!(cdc_filter & type)) { + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + } + /* ignores USB_CDC_PACKET_TYPE_DIRECTED */ + } + + spin_lock_irqsave(&dev->req_lock, flags); + /* + * this freelist can be empty if an interrupt triggered disconnect() + * and reconfigured the gadget (shutting down this queue) after the + * network stack decided to xmit but before we got the spinlock. + */ + if (list_empty(&dev->tx_reqs)) { + spin_unlock_irqrestore(&dev->req_lock, flags); + return NETDEV_TX_BUSY; + } + + req = container_of(dev->tx_reqs.next, struct usb_request, list); + list_del(&req->list); + + /* temporarily stop TX queue when the freelist empties */ + if (list_empty(&dev->tx_reqs)) + netif_stop_queue(net); + spin_unlock_irqrestore(&dev->req_lock, flags); + + /* no buffer copies needed, unless the network stack did it + * or the hardware can't use skb buffers. + * or there's not enough space for extra headers we need + */ + if (dev->wrap) { + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) + skb = dev->wrap(dev->port_usb, skb); + spin_unlock_irqrestore(&dev->lock, flags); + if (!skb) + goto drop; + + length = skb->len; + } +#ifdef CONFIG_USB_S3C_OTGD + /* for double word align */ + req->buf = kmalloc(skb->len, GFP_ATOMIC | GFP_DMA); + + if (!req->buf) { + req->buf = skb->data; + ERROR(dev, "fail to kmalloc[req->buf = skb->data]\n"); + } else { + memcpy((void *)req->buf, (void *)skb->data, skb->len); + } +#else + req->buf = skb->data; +#endif + req->context = skb; + req->complete = tx_complete; + + /* NCM requires no zlp if transfer is dwNtbInMaxSize */ + if (dev->port_usb->is_fixed && + length == dev->port_usb->fixed_in_len && + (length % in->maxpacket) == 0) + req->zero = 0; + else + req->zero = 1; + + /* use zlp framing on tx for strict CDC-Ether conformance, + * though any robust network rx path ignores extra padding. + * and some hardware doesn't like to write zlps. + */ + if (req->zero && !dev->zlp && (length % in->maxpacket) == 0) + length++; + + req->length = length; + + /* throttle highspeed IRQ rate back slightly */ + if (gadget_is_dualspeed(dev->gadget)) + req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH) + ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) + : 0; + + retval = usb_ep_queue(in, req, GFP_ATOMIC); + switch (retval) { + default: + DBG(dev, "tx queue err %d\n", retval); + break; + case 0: + net->trans_start = jiffies; + atomic_inc(&dev->tx_qlen); + } + + if (retval) { + dev_kfree_skb_any(skb); +#ifdef CONFIG_USB_S3C_OTGD + if (req->buf != skb->data) + kfree(req->buf); +#endif +drop: + dev->net->stats.tx_dropped++; + + spin_lock_irqsave(&dev->req_lock, flags); + if (list_empty(&dev->tx_reqs)) + netif_start_queue(net); + list_add(&req->list, &dev->tx_reqs); + spin_unlock_irqrestore(&dev->req_lock, flags); + } + return NETDEV_TX_OK; +} + +/*-------------------------------------------------------------------------*/ + +static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) +{ + DBG(dev, "%s\n", __func__); + + /* fill the rx queue */ + rx_fill(dev, gfp_flags); + + /* and open the tx floodgates */ + atomic_set(&dev->tx_qlen, 0); + netif_wake_queue(dev->net); +} + +static int eth_open(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + struct gether *link; + + DBG(dev, "%s\n", __func__); + if (netif_carrier_ok(dev->net)) + eth_start(dev, GFP_KERNEL); + + spin_lock_irq(&dev->lock); + link = dev->port_usb; + if (link && link->open) + link->open(link); + spin_unlock_irq(&dev->lock); + + return 0; +} + +static int eth_stop(struct net_device *net) +{ + struct eth_dev *dev = netdev_priv(net); + unsigned long flags; + + VDBG(dev, "%s\n", __func__); + netif_stop_queue(net); + + DBG(dev, "stop stats: rx/tx %ld/%ld, errs %ld/%ld\n", + dev->net->stats.rx_packets, dev->net->stats.tx_packets, + dev->net->stats.rx_errors, dev->net->stats.tx_errors + ); + + /* ensure there are no more active requests */ + spin_lock_irqsave(&dev->lock, flags); + if (dev->port_usb) { + struct gether *link = dev->port_usb; + + if (link->close) + link->close(link); + + /* NOTE: we have no abort-queue primitive we could use + * to cancel all pending I/O. Instead, we disable then + * reenable the endpoints ... this idiom may leave toggle + * wrong, but that's a self-correcting error. + * + * REVISIT: we *COULD* just let the transfers complete at + * their own pace; the network stack can handle old packets. + * For the moment we leave this here, since it works. + */ + usb_ep_disable(link->in_ep); + usb_ep_disable(link->out_ep); + if (netif_carrier_ok(net)) { + DBG(dev, "host still using in/out endpoints\n"); + usb_ep_enable(link->in_ep, link->in); + usb_ep_enable(link->out_ep, link->out); + } + } + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* initial value, changed by "ifconfig usb0 hw ether xx:xx:xx:xx:xx:xx" */ +static char *dev_addr; +module_param(dev_addr, charp, S_IRUGO); +MODULE_PARM_DESC(dev_addr, "Device Ethernet Address"); + +/* this address is invisible to ifconfig */ +static char *host_addr; +module_param(host_addr, charp, S_IRUGO); +MODULE_PARM_DESC(host_addr, "Host Ethernet Address"); + +static int get_ether_addr(const char *str, u8 *dev_addr) +{ + if (str) { + unsigned i; + + for (i = 0; i < 6; i++) { + unsigned char num; + + if ((*str == '.') || (*str == ':')) + str++; + num = hex_to_bin(*str++) << 4; + num |= hex_to_bin(*str++); + dev_addr [i] = num; + } + if (is_valid_ether_addr(dev_addr)) + return 0; + } + random_ether_addr(dev_addr); + return 1; +} + +static struct eth_dev *the_dev; + +static const struct net_device_ops eth_netdev_ops = { + .ndo_open = eth_open, + .ndo_stop = eth_stop, + .ndo_start_xmit = eth_start_xmit, + .ndo_change_mtu = ueth_change_mtu, + .ndo_set_mac_address = eth_mac_addr, + .ndo_validate_addr = eth_validate_addr, +}; + +static struct device_type gadget_type = { + .name = "gadget", +}; + +/** + * gether_setup - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) +{ + struct eth_dev *dev; + struct net_device *net; + int status; + + if (the_dev) + return -EBUSY; + + net = alloc_etherdev(sizeof *dev); + if (!net) + return -ENOMEM; + + dev = netdev_priv(net); + spin_lock_init(&dev->lock); + spin_lock_init(&dev->req_lock); + INIT_WORK(&dev->work, eth_work); + INIT_LIST_HEAD(&dev->tx_reqs); + INIT_LIST_HEAD(&dev->rx_reqs); + + skb_queue_head_init(&dev->rx_frames); + + /* network device setup */ + dev->net = net; + strcpy(net->name, "usb%d"); + + if (get_ether_addr(dev_addr, net->dev_addr)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "self"); + if (get_ether_addr(host_addr, dev->host_mac)) + dev_warn(&g->dev, + "using random %s ethernet address\n", "host"); + + if (ethaddr) + memcpy(ethaddr, dev->host_mac, ETH_ALEN); + + net->netdev_ops = ð_netdev_ops; + + SET_ETHTOOL_OPS(net, &ops); + + /* two kinds of host-initiated state changes: + * - iff DATA transfer is active, carrier is "on" + * - tx queueing enabled if open *and* carrier is "on" + */ + netif_carrier_off(net); + + dev->gadget = g; + SET_NETDEV_DEV(net, &g->dev); + SET_NETDEV_DEVTYPE(net, &gadget_type); + + status = register_netdev(net); + if (status < 0) { + dev_dbg(&g->dev, "register_netdev failed, %d\n", status); + free_netdev(net); + } else { + INFO(dev, "MAC %pM\n", net->dev_addr); + INFO(dev, "HOST MAC %pM\n", dev->host_mac); + + the_dev = dev; + } + + return status; +} + +/** + * gether_cleanup - remove Ethernet-over-USB device + * Context: may sleep + * + * This is called to free all resources allocated by @gether_setup(). + */ +void gether_cleanup(void) +{ + if (!the_dev) + return; + + unregister_netdev(the_dev->net); + flush_work_sync(&the_dev->work); + free_netdev(the_dev->net); + + the_dev = NULL; +} + + +/** + * gether_connect - notify network layer that USB link is active + * @link: the USB link, set up with endpoints, descriptors matching + * current device speed, and any framing wrapper(s) set up. + * Context: irqs blocked + * + * This is called to activate endpoints and let the network layer know + * the connection is active ("carrier detect"). It may cause the I/O + * queues to open and start letting network packets flow, but will in + * any case activate the endpoints so that they respond properly to the + * USB host. + * + * Verify net_device pointer returned using IS_ERR(). If it doesn't + * indicate some error code (negative errno), ep->driver_data values + * have been overwritten. + */ +struct net_device *gether_connect(struct gether *link) +{ + struct eth_dev *dev = the_dev; + int result = 0; + + if (!dev) + return ERR_PTR(-EINVAL); + + link->in_ep->driver_data = dev; + result = usb_ep_enable(link->in_ep, link->in); + if (result != 0) { + DBG(dev, "enable %s --> %d\n", + link->in_ep->name, result); + goto fail0; + } + + link->out_ep->driver_data = dev; + result = usb_ep_enable(link->out_ep, link->out); + if (result != 0) { + DBG(dev, "enable %s --> %d\n", + link->out_ep->name, result); + goto fail1; + } + + if (result == 0) + result = alloc_requests(dev, link, qlen(dev->gadget)); + + if (result == 0) { + dev->zlp = link->is_zlp_ok; + DBG(dev, "qlen %d\n", qlen(dev->gadget)); + + dev->header_len = link->header_len; + dev->unwrap = link->unwrap; + dev->wrap = link->wrap; + + spin_lock(&dev->lock); + dev->port_usb = link; + link->ioport = dev; + if (netif_running(dev->net)) { + if (link->open) + link->open(link); + } else { + if (link->close) + link->close(link); + } + spin_unlock(&dev->lock); + + netif_carrier_on(dev->net); + if (netif_running(dev->net)) + eth_start(dev, GFP_ATOMIC); + + /* on error, disable any endpoints */ + } else { + (void) usb_ep_disable(link->out_ep); +fail1: + (void) usb_ep_disable(link->in_ep); + } +fail0: + /* caller is responsible for cleanup on error */ + if (result < 0) + return ERR_PTR(result); + return dev->net; +} + +/** + * gether_disconnect - notify network layer that USB link is inactive + * @link: the USB link, on which gether_connect() was called + * Context: irqs blocked + * + * This is called to deactivate endpoints and let the network layer know + * the connection went inactive ("no carrier"). + * + * On return, the state is as if gether_connect() had never been called. + * The endpoints are inactive, and accordingly without active USB I/O. + * Pointers to endpoint descriptors and endpoint private data are nulled. + */ +void gether_disconnect(struct gether *link) +{ + struct eth_dev *dev = link->ioport; + struct usb_request *req; + + if (!dev) + return; + + DBG(dev, "%s\n", __func__); + + netif_stop_queue(dev->net); + netif_carrier_off(dev->net); + + /* disable endpoints, forcing (synchronous) completion + * of all pending i/o. then free the request objects + * and forget about the endpoints. + */ + usb_ep_disable(link->in_ep); + spin_lock(&dev->req_lock); + while (!list_empty(&dev->tx_reqs)) { + req = container_of(dev->tx_reqs.next, + struct usb_request, list); + list_del(&req->list); + + spin_unlock(&dev->req_lock); + usb_ep_free_request(link->in_ep, req); + spin_lock(&dev->req_lock); + } + spin_unlock(&dev->req_lock); + link->in_ep->driver_data = NULL; + link->in = NULL; + + usb_ep_disable(link->out_ep); + spin_lock(&dev->req_lock); + while (!list_empty(&dev->rx_reqs)) { + req = container_of(dev->rx_reqs.next, + struct usb_request, list); + list_del(&req->list); + + spin_unlock(&dev->req_lock); + usb_ep_free_request(link->out_ep, req); + spin_lock(&dev->req_lock); + } + spin_unlock(&dev->req_lock); + link->out_ep->driver_data = NULL; + link->out = NULL; + + /* finish forgetting about this USB link episode */ + dev->header_len = 0; + dev->unwrap = NULL; + dev->wrap = NULL; + + spin_lock(&dev->lock); + dev->port_usb = NULL; + link->ioport = NULL; + spin_unlock(&dev->lock); +} diff --git a/drivers/usb/gadget/gadget_gbhc/u_ether.h b/drivers/usb/gadget/gadget_gbhc/u_ether.h new file mode 100644 index 0000000..2779865 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/u_ether.h @@ -0,0 +1,127 @@ +/* + * u_ether.h -- interface to USB gadget "ethernet link" utilities + * + * Copyright (C) 2003-2005,2008 David Brownell + * Copyright (C) 2003-2004 Robert Schwebel, Benedikt Spranger + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __U_ETHER_H +#define __U_ETHER_H + +#include <linux/err.h> +#include <linux/if_ether.h> +#include <linux/usb/composite.h> +#include <linux/usb/cdc.h> + +#include "gadget_chips.h" + + +/* + * This represents the USB side of an "ethernet" link, managed by a USB + * function which provides control and (maybe) framing. Two functions + * in different configurations could share the same ethernet link/netdev, + * using different host interaction models. + * + * There is a current limitation that only one instance of this link may + * be present in any given configuration. When that's a problem, network + * layer facilities can be used to package multiple logical links on this + * single "physical" one. + */ +struct gether { + struct usb_function func; + + /* updated by gether_{connect,disconnect} */ + struct eth_dev *ioport; + + /* endpoints handle full and/or high speeds */ + struct usb_ep *in_ep; + struct usb_ep *out_ep; + + /* descriptors match device speed at gether_connect() time */ + struct usb_endpoint_descriptor *in; + struct usb_endpoint_descriptor *out; + + bool is_zlp_ok; + + u16 cdc_filter; + + /* hooks for added framing, as needed for RNDIS and EEM. */ + u32 header_len; + /* NCM requires fixed size bundles */ + bool is_fixed; + u32 fixed_out_len; + u32 fixed_in_len; + struct sk_buff *(*wrap)(struct gether *port, + struct sk_buff *skb); + int (*unwrap)(struct gether *port, + struct sk_buff *skb, + struct sk_buff_head *list); + + /* called on network open/close */ + void (*open)(struct gether *); + void (*close)(struct gether *); +}; + +#define DEFAULT_FILTER (USB_CDC_PACKET_TYPE_BROADCAST \ + |USB_CDC_PACKET_TYPE_ALL_MULTICAST \ + |USB_CDC_PACKET_TYPE_PROMISCUOUS \ + |USB_CDC_PACKET_TYPE_DIRECTED) + + +/* netdev setup/teardown as directed by the gadget driver */ +int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]); +void gether_cleanup(void); + +/* connect/disconnect is handled by individual functions */ +struct net_device *gether_connect(struct gether *); +void gether_disconnect(struct gether *); + +/* Some controllers can't support CDC Ethernet (ECM) ... */ +static inline bool can_support_ecm(struct usb_gadget *gadget) +{ + if (!gadget_supports_altsettings(gadget)) + return false; + + /* Everything else is *presumably* fine ... but this is a bit + * chancy, so be **CERTAIN** there are no hardware issues with + * your controller. Add it above if it can't handle CDC. + */ + return true; +} + +/* each configuration may bind one instance of an ethernet link */ +int geth_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); +int ecm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); +int ncm_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); +int eem_bind_config(struct usb_configuration *c); + +#if defined(USB_ETH_RNDIS) || defined(CONFIG_USB_ANDROID_RNDIS) + +int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); + +#else + +static inline int +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +{ + return 0; +} + +#endif + +#endif /* __U_ETHER_H */ diff --git a/drivers/usb/gadget/gadget_gbhc/u_serial.c b/drivers/usb/gadget/gadget_gbhc/u_serial.c new file mode 100644 index 0000000..3dcbeca --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/u_serial.c @@ -0,0 +1,1348 @@ +/* + * u_serial.c - utilities for USB gadget "serial port"/TTY support + * + * Copyright (C) 2003 Al Borchers (alborchers@steinerpoint.com) + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This code also borrows from usbserial.c, which is + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + * Copyright (C) 2000 Peter Berger (pberger@brimson.com) + * Copyright (C) 2000 Al Borchers (alborchers@steinerpoint.com) + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +/* #define VERBOSE_DEBUG */ + +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/tty.h> +#include <linux/tty_flip.h> +#include <linux/slab.h> + +#include "u_serial.h" + + +/* + * This component encapsulates the TTY layer glue needed to provide basic + * "serial port" functionality through the USB gadget stack. Each such + * port is exposed through a /dev/ttyGS* node. + * + * After initialization (gserial_setup), these TTY port devices stay + * available until they are removed (gserial_cleanup). Each one may be + * connected to a USB function (gserial_connect), or disconnected (with + * gserial_disconnect) when the USB host issues a config change event. + * Data can only flow when the port is connected to the host. + * + * A given TTY port can be made available in multiple configurations. + * For example, each one might expose a ttyGS0 node which provides a + * login application. In one case that might use CDC ACM interface 0, + * while another configuration might use interface 3 for that. The + * work to handle that (including descriptor management) is not part + * of this component. + * + * Configurations may expose more than one TTY port. For example, if + * ttyGS0 provides login service, then ttyGS1 might provide dialer access + * for a telephone or fax link. And ttyGS2 might be something that just + * needs a simple byte stream interface for some messaging protocol that + * is managed in userspace ... OBEX, PTP, and MTP have been mentioned. + */ + +#define PREFIX "ttyGS" + +/* + * gserial is the lifecycle interface, used by USB functions + * gs_port is the I/O nexus, used by the tty driver + * tty_struct links to the tty/filesystem framework + * + * gserial <---> gs_port ... links will be null when the USB link is + * inactive; managed by gserial_{connect,disconnect}(). each gserial + * instance can wrap its own USB control protocol. + * gserial->ioport == usb_ep->driver_data ... gs_port + * gs_port->port_usb ... gserial + * + * gs_port <---> tty_struct ... links will be null when the TTY file + * isn't opened; managed by gs_open()/gs_close() + * gserial->port_tty ... tty_struct + * tty_struct->driver_data ... gserial + */ + +/* RX and TX queues can buffer QUEUE_SIZE packets before they hit the + * next layer of buffering. For TX that's a circular buffer; for RX + * consider it a NOP. A third layer is provided by the TTY code. + */ +#define QUEUE_SIZE 16 +#define WRITE_BUF_SIZE 8192 /* TX only */ + +/* circular buffer */ +struct gs_buf { + unsigned buf_size; + char *buf_buf; + char *buf_get; + char *buf_put; +}; + +/* + * The port structure holds info for each port, one for each minor number + * (and thus for each /dev/ node). + */ +struct gs_port { + spinlock_t port_lock; /* guard port_* access */ + + struct gserial *port_usb; + struct tty_struct *port_tty; + + unsigned open_count; + bool openclose; /* open/close in progress */ + u8 port_num; + + wait_queue_head_t close_wait; /* wait for last close */ + + struct list_head read_pool; + int read_started; + int read_allocated; + struct list_head read_queue; + unsigned n_read; + struct tasklet_struct push; + + struct list_head write_pool; + int write_started; + int write_allocated; + struct gs_buf port_write_buf; + wait_queue_head_t drain_wait; /* wait while writes drain */ + + /* REVISIT this state ... */ + struct usb_cdc_line_coding port_line_coding; /* 8-N-1 etc */ +}; + +/* increase N_PORTS if you need more */ +#define N_PORTS 8 +static struct portmaster { + struct mutex lock; /* protect open/close */ + struct gs_port *port; +} ports[N_PORTS]; +static unsigned n_ports; + +#define GS_CLOSE_TIMEOUT 15 /* seconds */ + + + +#ifdef VERBOSE_DEBUG +#define pr_vdebug(fmt, arg...) \ + pr_debug(fmt, ##arg) +#else +#define pr_vdebug(fmt, arg...) \ + ({ if (0) pr_debug(fmt, ##arg); }) +#endif + +/*-------------------------------------------------------------------------*/ + +/* Circular Buffer */ + +/* + * gs_buf_alloc + * + * Allocate a circular buffer and all associated memory. + */ +static int gs_buf_alloc(struct gs_buf *gb, unsigned size) +{ + gb->buf_buf = kmalloc(size, GFP_KERNEL); + if (gb->buf_buf == NULL) + return -ENOMEM; + + gb->buf_size = size; + gb->buf_put = gb->buf_buf; + gb->buf_get = gb->buf_buf; + + return 0; +} + +/* + * gs_buf_free + * + * Free the buffer and all associated memory. + */ +static void gs_buf_free(struct gs_buf *gb) +{ + kfree(gb->buf_buf); + gb->buf_buf = NULL; +} + +/* + * gs_buf_clear + * + * Clear out all data in the circular buffer. + */ +static void gs_buf_clear(struct gs_buf *gb) +{ + gb->buf_get = gb->buf_put; + /* equivalent to a get of all data available */ +} + +/* + * gs_buf_data_avail + * + * Return the number of bytes of data written into the circular + * buffer. + */ +static unsigned gs_buf_data_avail(struct gs_buf *gb) +{ + return (gb->buf_size + gb->buf_put - gb->buf_get) % gb->buf_size; +} + +/* + * gs_buf_space_avail + * + * Return the number of bytes of space available in the circular + * buffer. + */ +static unsigned gs_buf_space_avail(struct gs_buf *gb) +{ + return (gb->buf_size + gb->buf_get - gb->buf_put - 1) % gb->buf_size; +} + +/* + * gs_buf_put + * + * Copy data data from a user buffer and put it into the circular buffer. + * Restrict to the amount of space available. + * + * Return the number of bytes copied. + */ +static unsigned +gs_buf_put(struct gs_buf *gb, const char *buf, unsigned count) +{ + unsigned len; + + len = gs_buf_space_avail(gb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = gb->buf_buf + gb->buf_size - gb->buf_put; + if (count > len) { + memcpy(gb->buf_put, buf, len); + memcpy(gb->buf_buf, buf+len, count - len); + gb->buf_put = gb->buf_buf + count - len; + } else { + memcpy(gb->buf_put, buf, count); + if (count < len) + gb->buf_put += count; + else /* count == len */ + gb->buf_put = gb->buf_buf; + } + + return count; +} + +/* + * gs_buf_get + * + * Get data from the circular buffer and copy to the given buffer. + * Restrict to the amount of data available. + * + * Return the number of bytes copied. + */ +static unsigned +gs_buf_get(struct gs_buf *gb, char *buf, unsigned count) +{ + unsigned len; + + len = gs_buf_data_avail(gb); + if (count > len) + count = len; + + if (count == 0) + return 0; + + len = gb->buf_buf + gb->buf_size - gb->buf_get; + if (count > len) { + memcpy(buf, gb->buf_get, len); + memcpy(buf+len, gb->buf_buf, count - len); + gb->buf_get = gb->buf_buf + count - len; + } else { + memcpy(buf, gb->buf_get, count); + if (count < len) + gb->buf_get += count; + else /* count == len */ + gb->buf_get = gb->buf_buf; + } + + return count; +} + +/*-------------------------------------------------------------------------*/ + +/* I/O glue between TTY (upper) and USB function (lower) driver layers */ + +/* + * gs_alloc_req + * + * Allocate a usb_request and its buffer. Returns a pointer to the + * usb_request or NULL if there is an error. + */ +struct usb_request * +gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t kmalloc_flags) +{ + struct usb_request *req; + + req = usb_ep_alloc_request(ep, kmalloc_flags); + + if (req != NULL) { + req->length = len; + req->buf = kmalloc(len, kmalloc_flags); + if (req->buf == NULL) { + usb_ep_free_request(ep, req); + return NULL; + } + } + + return req; +} + +/* + * gs_free_req + * + * Free a usb_request and its buffer. + */ +void gs_free_req(struct usb_ep *ep, struct usb_request *req) +{ + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +/* + * gs_send_packet + * + * If there is data to send, a packet is built in the given + * buffer and the size is returned. If there is no data to + * send, 0 is returned. + * + * Called with port_lock held. + */ +static unsigned +gs_send_packet(struct gs_port *port, char *packet, unsigned size) +{ + unsigned len; + + len = gs_buf_data_avail(&port->port_write_buf); + if (len < size) + size = len; + if (size != 0) + size = gs_buf_get(&port->port_write_buf, packet, size); + return size; +} + +/* + * gs_start_tx + * + * This function finds available write requests, calls + * gs_send_packet to fill these packets with data, and + * continues until either there are no more write requests + * available or no more data to send. This function is + * run whenever data arrives or write requests are available. + * + * Context: caller owns port_lock; port_usb is non-null. + */ +static int gs_start_tx(struct gs_port *port) +/* +__releases(&port->port_lock) +__acquires(&port->port_lock) +*/ +{ + struct list_head *pool = &port->write_pool; + struct usb_ep *in = port->port_usb->in; + int status = 0; + bool do_tty_wake = false; + + while (!list_empty(pool)) { + struct usb_request *req; + int len; + + if (port->write_started >= QUEUE_SIZE) + break; + + req = list_entry(pool->next, struct usb_request, list); + len = gs_send_packet(port, req->buf, in->maxpacket); + if (len == 0) { + wake_up_interruptible(&port->drain_wait); + break; + } + do_tty_wake = true; + + req->length = len; + list_del(&req->list); + req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0); + + pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", + port->port_num, len, *((u8 *)req->buf), + *((u8 *)req->buf+1), *((u8 *)req->buf+2)); + + /* Drop lock while we call out of driver; completions + * could be issued while we do so. Disconnection may + * happen too; maybe immediately before we queue this! + * + * NOTE that we may keep sending data for a while after + * the TTY closed (dev->ioport->port_tty is NULL). + */ + spin_unlock(&port->port_lock); + status = usb_ep_queue(in, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + + if (status) { + pr_debug("%s: %s %s err %d\n", + __func__, "queue", in->name, status); + list_add(&req->list, pool); + break; + } + + port->write_started++; + + /* abort immediately after disconnect */ + if (!port->port_usb) + break; + } + + if (do_tty_wake && port->port_tty) + tty_wakeup(port->port_tty); + return status; +} + +/* + * Context: caller owns port_lock, and port_usb is set + */ +static unsigned gs_start_rx(struct gs_port *port) +/* +__releases(&port->port_lock) +__acquires(&port->port_lock) +*/ +{ + struct list_head *pool = &port->read_pool; + struct usb_ep *out = port->port_usb->out; + + while (!list_empty(pool)) { + struct usb_request *req; + int status; + struct tty_struct *tty; + + /* no more rx if closed */ + tty = port->port_tty; + if (!tty) + break; + + if (port->read_started >= QUEUE_SIZE) + break; + + req = list_entry(pool->next, struct usb_request, list); + list_del(&req->list); + req->length = out->maxpacket; + + /* drop lock while we call out; the controller driver + * may need to call us back (e.g. for disconnect) + */ + spin_unlock(&port->port_lock); + status = usb_ep_queue(out, req, GFP_ATOMIC); + spin_lock(&port->port_lock); + + if (status) { + pr_debug("%s: %s %s err %d\n", + __func__, "queue", out->name, status); + list_add(&req->list, pool); + break; + } + port->read_started++; + + /* abort immediately after disconnect */ + if (!port->port_usb) + break; + } + return port->read_started; +} + +/* + * RX tasklet takes data out of the RX queue and hands it up to the TTY + * layer until it refuses to take any more data (or is throttled back). + * Then it issues reads for any further data. + * + * If the RX queue becomes full enough that no usb_request is queued, + * the OUT endpoint may begin NAKing as soon as its FIFO fills up. + * So QUEUE_SIZE packets plus however many the FIFO holds (usually two) + * can be buffered before the TTY layer's buffers (currently 64 KB). + */ +static void gs_rx_push(unsigned long _port) +{ + struct gs_port *port = (void *)_port; + struct tty_struct *tty; + struct list_head *queue = &port->read_queue; + bool disconnect = false; + bool do_push = false; + + /* hand any queued data to the tty */ + spin_lock_irq(&port->port_lock); + tty = port->port_tty; + while (!list_empty(queue)) { + struct usb_request *req; + + req = list_first_entry(queue, struct usb_request, list); + + /* discard data if tty was closed */ + if (!tty) + goto recycle; + + /* leave data queued if tty was rx throttled */ + if (test_bit(TTY_THROTTLED, &tty->flags)) + break; + + switch (req->status) { + case -ESHUTDOWN: + disconnect = true; + pr_vdebug(PREFIX "%d: shutdown\n", port->port_num); + break; + + default: + /* presumably a transient fault */ + pr_warning(PREFIX "%d: unexpected RX status %d\n", + port->port_num, req->status); + /* FALLTHROUGH */ + case 0: + /* normal completion */ + break; + } + + /* push data to (open) tty */ + if (req->actual) { + char *packet = req->buf; + unsigned size = req->actual; + unsigned n; + int count; + + /* we may have pushed part of this packet already... */ + n = port->n_read; + if (n) { + packet += n; + size -= n; + } + + count = tty_insert_flip_string(tty, packet, size); + if (count) + do_push = true; + if (count != size) { + /* stop pushing; TTY layer can't handle more */ + port->n_read += count; + pr_vdebug(PREFIX "%d: rx block %d/%d\n", + port->port_num, + count, req->actual); + break; + } + port->n_read = 0; + } +recycle: + list_move(&req->list, &port->read_pool); + port->read_started--; + } + + /* Push from tty to ldisc; without low_latency set this is handled by + * a workqueue, so we won't get callbacks and can hold port_lock + */ + if (tty && do_push) { + tty_flip_buffer_push(tty); + } + + + /* We want our data queue to become empty ASAP, keeping data + * in the tty and ldisc (not here). If we couldn't push any + * this time around, there may be trouble unless there's an + * implicit tty_unthrottle() call on its way... + * + * REVISIT we should probably add a timer to keep the tasklet + * from starving ... but it's not clear that case ever happens. + */ + if (!list_empty(queue) && tty) { + if (!test_bit(TTY_THROTTLED, &tty->flags)) { + if (do_push) + tasklet_schedule(&port->push); + else + pr_warning(PREFIX "%d: RX not scheduled?\n", + port->port_num); + } + } + + /* If we're still connected, refill the USB RX queue. */ + if (!disconnect && port->port_usb) + gs_start_rx(port); + + spin_unlock_irq(&port->port_lock); +} + +static void gs_read_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_port *port = ep->driver_data; + + /* Queue all received data until the tty layer is ready for it. */ + spin_lock(&port->port_lock); + list_add_tail(&req->list, &port->read_queue); + tasklet_schedule(&port->push); + spin_unlock(&port->port_lock); +} + +static void gs_write_complete(struct usb_ep *ep, struct usb_request *req) +{ + struct gs_port *port = ep->driver_data; + + spin_lock(&port->port_lock); + list_add(&req->list, &port->write_pool); + port->write_started--; + + switch (req->status) { + default: + /* presumably a transient fault */ + pr_warning("%s: unexpected %s status %d\n", + __func__, ep->name, req->status); + /* FALL THROUGH */ + case 0: + /* normal completion */ + gs_start_tx(port); + break; + + case -ESHUTDOWN: + /* disconnect */ + pr_vdebug("%s: %s shutdown\n", __func__, ep->name); + break; + } + + spin_unlock(&port->port_lock); +} + +static void gs_free_requests(struct usb_ep *ep, struct list_head *head, + int *allocated) +{ + struct usb_request *req; + + while (!list_empty(head)) { + req = list_entry(head->next, struct usb_request, list); + list_del(&req->list); + gs_free_req(ep, req); + if (allocated) + (*allocated)--; + } +} + +static int gs_alloc_requests(struct usb_ep *ep, struct list_head *head, + void (*fn)(struct usb_ep *, struct usb_request *), + int *allocated) +{ + int i; + struct usb_request *req; + int n = allocated ? QUEUE_SIZE - *allocated : QUEUE_SIZE; + + /* Pre-allocate up to QUEUE_SIZE transfers, but if we can't + * do quite that many this time, don't fail ... we just won't + * be as speedy as we might otherwise be. + */ + for (i = 0; i < n; i++) { + req = gs_alloc_req(ep, ep->maxpacket, GFP_ATOMIC); + if (!req) + return list_empty(head) ? -ENOMEM : 0; + req->complete = fn; + list_add_tail(&req->list, head); + if (allocated) + (*allocated)++; + } + return 0; +} + +/** + * gs_start_io - start USB I/O streams + * @dev: encapsulates endpoints to use + * Context: holding port_lock; port_tty and port_usb are non-null + * + * We only start I/O when something is connected to both sides of + * this port. If nothing is listening on the host side, we may + * be pointlessly filling up our TX buffers and FIFO. + */ +static int gs_start_io(struct gs_port *port) +{ + struct list_head *head = &port->read_pool; + struct usb_ep *ep = port->port_usb->out; + int status; + unsigned started; + + /* Allocate RX and TX I/O buffers. We can't easily do this much + * earlier (with GFP_KERNEL) because the requests are coupled to + * endpoints, as are the packet sizes we'll be using. Different + * configurations may use different endpoints with a given port; + * and high speed vs full speed changes packet sizes too. + */ + status = gs_alloc_requests(ep, head, gs_read_complete, + &port->read_allocated); + if (status) + return status; + + status = gs_alloc_requests(port->port_usb->in, &port->write_pool, + gs_write_complete, &port->write_allocated); + if (status) { + gs_free_requests(ep, head, &port->read_allocated); + return status; + } + + /* queue read requests */ + port->n_read = 0; + started = gs_start_rx(port); + + /* unblock any pending writes into our circular buffer */ + if (started) { + tty_wakeup(port->port_tty); + } else { + gs_free_requests(ep, head, &port->read_allocated); + gs_free_requests(port->port_usb->in, &port->write_pool, + &port->write_allocated); + status = -EIO; + } + + return status; +} + +/*-------------------------------------------------------------------------*/ + +/* TTY Driver */ + +/* + * gs_open sets up the link between a gs_port and its associated TTY. + * That link is broken *only* by TTY close(), and all driver methods + * know that. + */ +static int gs_open(struct tty_struct *tty, struct file *file) +{ + int port_num = tty->index; + struct gs_port *port; + int status; + + if (port_num < 0 || port_num >= n_ports) + return -ENXIO; + + do { + mutex_lock(&ports[port_num].lock); + port = ports[port_num].port; + if (!port) + status = -ENODEV; + else { + spin_lock_irq(&port->port_lock); + + /* already open? Great. */ + if (port->open_count) { + status = 0; + port->open_count++; + + /* currently opening/closing? wait ... */ + } else if (port->openclose) { + status = -EBUSY; + + /* ... else we do the work */ + } else { + status = -EAGAIN; + port->openclose = true; + } + spin_unlock_irq(&port->port_lock); + } + mutex_unlock(&ports[port_num].lock); + + switch (status) { + default: + /* fully handled */ + return status; + case -EAGAIN: + /* must do the work */ + break; + case -EBUSY: + /* wait for EAGAIN task to finish */ + msleep(1); + /* REVISIT could have a waitchannel here, if + * concurrent open performance is important + */ + break; + } + } while (status != -EAGAIN); + + /* Do the "real open" */ + spin_lock_irq(&port->port_lock); + + /* allocate circular buffer on first open */ + if (port->port_write_buf.buf_buf == NULL) { + + spin_unlock_irq(&port->port_lock); + status = gs_buf_alloc(&port->port_write_buf, WRITE_BUF_SIZE); + spin_lock_irq(&port->port_lock); + + if (status) { + pr_debug("gs_open: ttyGS%d (%p,%p) no buffer\n", + port->port_num, tty, file); + port->openclose = false; + goto exit_unlock_port; + } + } + + /* REVISIT if REMOVED (ports[].port NULL), abort the open + * to let rmmod work faster (but this way isn't wrong). + */ + + /* REVISIT maybe wait for "carrier detect" */ + + tty->driver_data = port; + port->port_tty = tty; + + port->open_count = 1; + port->openclose = false; + + /* if connected, start the I/O stream */ + if (port->port_usb) { + struct gserial *gser = port->port_usb; + + pr_debug("gs_open: start ttyGS%d\n", port->port_num); + gs_start_io(port); + + if (gser->connect) + gser->connect(gser); + } + + pr_debug("gs_open: ttyGS%d (%p,%p)\n", port->port_num, tty, file); + + status = 0; + +exit_unlock_port: + spin_unlock_irq(&port->port_lock); + return status; +} + +static int gs_writes_finished(struct gs_port *p) +{ + int cond; + + /* return true on disconnect or empty buffer */ + spin_lock_irq(&p->port_lock); + cond = (p->port_usb == NULL) || !gs_buf_data_avail(&p->port_write_buf); + spin_unlock_irq(&p->port_lock); + + return cond; +} + +static void gs_close(struct tty_struct *tty, struct file *file) +{ + struct gs_port *port = tty->driver_data; + struct gserial *gser; + + spin_lock_irq(&port->port_lock); + + if (port->open_count != 1) { + if (port->open_count == 0) + WARN_ON(1); + else + --port->open_count; + goto exit; + } + + pr_debug("gs_close: ttyGS%d (%p,%p) ...\n", port->port_num, tty, file); + + /* mark port as closing but in use; we can drop port lock + * and sleep if necessary + */ + port->openclose = true; + port->open_count = 0; + + gser = port->port_usb; + if (gser && gser->disconnect) + gser->disconnect(gser); + + /* wait for circular write buffer to drain, disconnect, or at + * most GS_CLOSE_TIMEOUT seconds; then discard the rest + */ + if (gs_buf_data_avail(&port->port_write_buf) > 0 && gser) { + spin_unlock_irq(&port->port_lock); + wait_event_interruptible_timeout(port->drain_wait, + gs_writes_finished(port), + GS_CLOSE_TIMEOUT * HZ); + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + } + + /* Iff we're disconnected, there can be no I/O in flight so it's + * ok to free the circular buffer; else just scrub it. And don't + * let the push tasklet fire again until we're re-opened. + */ + if (gser == NULL) + gs_buf_free(&port->port_write_buf); + else + gs_buf_clear(&port->port_write_buf); + + tty->driver_data = NULL; + port->port_tty = NULL; + + port->openclose = false; + + pr_debug("gs_close: ttyGS%d (%p,%p) done!\n", + port->port_num, tty, file); + + wake_up_interruptible(&port->close_wait); +exit: + spin_unlock_irq(&port->port_lock); +} + +static int gs_write(struct tty_struct *tty, const unsigned char *buf, int count) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int status; + + pr_vdebug("gs_write: ttyGS%d (%p) writing %d bytes\n", + port->port_num, tty, count); + + spin_lock_irqsave(&port->port_lock, flags); + if (count) + count = gs_buf_put(&port->port_write_buf, buf, count); + /* treat count == 0 as flush_chars() */ + if (port->port_usb) + status = gs_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); + + return count; +} + +static int gs_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int status; + + pr_vdebug("gs_put_char: (%d,%p) char=0x%x, called from %p\n", + port->port_num, tty, ch, __builtin_return_address(0)); + + spin_lock_irqsave(&port->port_lock, flags); + status = gs_buf_put(&port->port_write_buf, &ch, 1); + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; +} + +static void gs_flush_chars(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + + pr_vdebug("gs_flush_chars: (%d,%p)\n", port->port_num, tty); + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + gs_start_tx(port); + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int gs_write_room(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int room = 0; + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) + room = gs_buf_space_avail(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_vdebug("gs_write_room: (%d,%p) room=%d\n", + port->port_num, tty, room); + + return room; +} + +static int gs_chars_in_buffer(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + int chars = 0; + + spin_lock_irqsave(&port->port_lock, flags); + chars = gs_buf_data_avail(&port->port_write_buf); + spin_unlock_irqrestore(&port->port_lock, flags); + + pr_vdebug("gs_chars_in_buffer: (%d,%p) chars=%d\n", + port->port_num, tty, chars); + + return chars; +} + +/* undo side effects of setting TTY_THROTTLED */ +static void gs_unthrottle(struct tty_struct *tty) +{ + struct gs_port *port = tty->driver_data; + unsigned long flags; + + spin_lock_irqsave(&port->port_lock, flags); + if (port->port_usb) { + /* Kickstart read queue processing. We don't do xon/xoff, + * rts/cts, or other handshaking with the host, but if the + * read queue backs up enough we'll be NAKing OUT packets. + */ + tasklet_schedule(&port->push); + pr_vdebug(PREFIX "%d: unthrottle\n", port->port_num); + } + spin_unlock_irqrestore(&port->port_lock, flags); +} + +static int gs_break_ctl(struct tty_struct *tty, int duration) +{ + struct gs_port *port = tty->driver_data; + int status = 0; + struct gserial *gser; + + pr_vdebug("gs_break_ctl: ttyGS%d, send break (%d) \n", + port->port_num, duration); + + spin_lock_irq(&port->port_lock); + gser = port->port_usb; + if (gser && gser->send_break) + status = gser->send_break(gser, duration); + spin_unlock_irq(&port->port_lock); + + return status; +} + +static const struct tty_operations gs_tty_ops = { + .open = gs_open, + .close = gs_close, + .write = gs_write, + .put_char = gs_put_char, + .flush_chars = gs_flush_chars, + .write_room = gs_write_room, + .chars_in_buffer = gs_chars_in_buffer, + .unthrottle = gs_unthrottle, + .break_ctl = gs_break_ctl, +}; + +/*-------------------------------------------------------------------------*/ + +static struct tty_driver *gs_tty_driver; + +static int __init +gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) +{ + struct gs_port *port; + + port = kzalloc(sizeof(struct gs_port), GFP_KERNEL); + if (port == NULL) + return -ENOMEM; + + spin_lock_init(&port->port_lock); + init_waitqueue_head(&port->close_wait); + init_waitqueue_head(&port->drain_wait); + + tasklet_init(&port->push, gs_rx_push, (unsigned long) port); + + INIT_LIST_HEAD(&port->read_pool); + INIT_LIST_HEAD(&port->read_queue); + INIT_LIST_HEAD(&port->write_pool); + + port->port_num = port_num; + port->port_line_coding = *coding; + + ports[port_num].port = port; + + return 0; +} + +/** + * gserial_setup - initialize TTY driver for one or more ports + * @g: gadget to associate with these ports + * @count: how many ports to support + * Context: may sleep + * + * The TTY stack needs to know in advance how many devices it should + * plan to manage. Use this call to set up the ports you will be + * exporting through USB. Later, connect them to functions based + * on what configuration is activated by the USB host; and disconnect + * them as appropriate. + * + * An example would be a two-configuration device in which both + * configurations expose port 0, but through different functions. + * One configuration could even expose port 1 while the other + * one doesn't. + * + * Returns negative errno or zero. + */ +int __init gserial_setup(struct usb_gadget *g, unsigned count) +{ + unsigned i; + struct usb_cdc_line_coding coding; + int status; + + if (count == 0 || count > N_PORTS) + return -EINVAL; + + gs_tty_driver = alloc_tty_driver(count); + if (!gs_tty_driver) + return -ENOMEM; + + gs_tty_driver->owner = THIS_MODULE; + gs_tty_driver->driver_name = "g_serial"; + gs_tty_driver->name = PREFIX; + /* uses dynamically assigned dev_t values */ + + gs_tty_driver->type = TTY_DRIVER_TYPE_SERIAL; + gs_tty_driver->subtype = SERIAL_TYPE_NORMAL; + gs_tty_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + gs_tty_driver->init_termios = tty_std_termios; + + /* 9600-8-N-1 ... matches defaults expected by "usbser.sys" on + * MS-Windows. Otherwise, most of these flags shouldn't affect + * anything unless we were to actually hook up to a serial line. + */ + gs_tty_driver->init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + gs_tty_driver->init_termios.c_ispeed = 9600; + gs_tty_driver->init_termios.c_ospeed = 9600; + + coding.dwDTERate = cpu_to_le32(9600); + coding.bCharFormat = 8; + coding.bParityType = USB_CDC_NO_PARITY; + coding.bDataBits = USB_CDC_1_STOP_BITS; + + tty_set_operations(gs_tty_driver, &gs_tty_ops); + + /* make devices be openable */ + for (i = 0; i < count; i++) { + mutex_init(&ports[i].lock); + status = gs_port_alloc(i, &coding); + if (status) { + count = i; + goto fail; + } + } + n_ports = count; + + /* export the driver ... */ + status = tty_register_driver(gs_tty_driver); + if (status) { + pr_err("%s: cannot register, err %d\n", + __func__, status); + goto fail; + } + + /* ... and sysfs class devices, so mdev/udev make /dev/ttyGS* */ + for (i = 0; i < count; i++) { + struct device *tty_dev; + + tty_dev = tty_register_device(gs_tty_driver, i, &g->dev); + if (IS_ERR(tty_dev)) + pr_warning("%s: no classdev for port %d, err %ld\n", + __func__, i, PTR_ERR(tty_dev)); + } + + pr_debug("%s: registered %d ttyGS* device%s\n", __func__, + count, (count == 1) ? "" : "s"); + + return status; +fail: + while (count--) + kfree(ports[count].port); + put_tty_driver(gs_tty_driver); + gs_tty_driver = NULL; + return status; +} + +static int gs_closed(struct gs_port *port) +{ + int cond; + + spin_lock_irq(&port->port_lock); + cond = (port->open_count == 0) && !port->openclose; + spin_unlock_irq(&port->port_lock); + return cond; +} + +/** + * gserial_cleanup - remove TTY-over-USB driver and devices + * Context: may sleep + * + * This is called to free all resources allocated by @gserial_setup(). + * Accordingly, it may need to wait until some open /dev/ files have + * closed. + * + * The caller must have issued @gserial_disconnect() for any ports + * that had previously been connected, so that there is never any + * I/O pending when it's called. + */ +void gserial_cleanup(void) +{ + unsigned i; + struct gs_port *port; + + if (!gs_tty_driver) + return; + + /* start sysfs and /dev/ttyGS* node removal */ + for (i = 0; i < n_ports; i++) + tty_unregister_device(gs_tty_driver, i); + + for (i = 0; i < n_ports; i++) { + /* prevent new opens */ + mutex_lock(&ports[i].lock); + port = ports[i].port; + ports[i].port = NULL; + mutex_unlock(&ports[i].lock); + + tasklet_kill(&port->push); + + /* wait for old opens to finish */ + wait_event(port->close_wait, gs_closed(port)); + + WARN_ON(port->port_usb != NULL); + + kfree(port); + } + n_ports = 0; + + tty_unregister_driver(gs_tty_driver); + put_tty_driver(gs_tty_driver); + gs_tty_driver = NULL; + + pr_debug("%s: cleaned up ttyGS* support\n", __func__); +} + +/** + * gserial_connect - notify TTY I/O glue that USB link is active + * @gser: the function, set up with endpoints and descriptors + * @port_num: which port is active + * Context: any (usually from irq) + * + * This is called activate endpoints and let the TTY layer know that + * the connection is active ... not unlike "carrier detect". It won't + * necessarily start I/O queues; unless the TTY is held open by any + * task, there would be no point. However, the endpoints will be + * activated so the USB host can perform I/O, subject to basic USB + * hardware flow control. + * + * Caller needs to have set up the endpoints and USB function in @dev + * before calling this, as well as the appropriate (speed-specific) + * endpoint descriptors, and also have set up the TTY driver by calling + * @gserial_setup(). + * + * Returns negative errno or zero. + * On success, ep->driver_data will be overwritten. + */ +int gserial_connect(struct gserial *gser, u8 port_num) +{ + struct gs_port *port; + unsigned long flags; + int status; + + if (!gs_tty_driver || port_num >= n_ports) + return -ENXIO; + + /* we "know" gserial_cleanup() hasn't been called */ + port = ports[port_num].port; + + /* activate the endpoints */ + status = usb_ep_enable(gser->in, gser->in_desc); + if (status < 0) + return status; + gser->in->driver_data = port; + + status = usb_ep_enable(gser->out, gser->out_desc); + if (status < 0) + goto fail_out; + gser->out->driver_data = port; + + /* then tell the tty glue that I/O can work */ + spin_lock_irqsave(&port->port_lock, flags); + gser->ioport = port; + port->port_usb = gser; + + /* REVISIT unclear how best to handle this state... + * we don't really couple it with the Linux TTY. + */ + gser->port_line_coding = port->port_line_coding; + + /* REVISIT if waiting on "carrier detect", signal. */ + + /* if it's already open, start I/O ... and notify the serial + * protocol about open/close status (connect/disconnect). + */ + if (port->open_count) { + pr_debug("gserial_connect: start ttyGS%d\n", port->port_num); + gs_start_io(port); + if (gser->connect) + gser->connect(gser); + } else { + if (gser->disconnect) + gser->disconnect(gser); + } + + spin_unlock_irqrestore(&port->port_lock, flags); + + return status; + +fail_out: + usb_ep_disable(gser->in); + gser->in->driver_data = NULL; + return status; +} + +/** + * gserial_disconnect - notify TTY I/O glue that USB link is inactive + * @gser: the function, on which gserial_connect() was called + * Context: any (usually from irq) + * + * This is called to deactivate endpoints and let the TTY layer know + * that the connection went inactive ... not unlike "hangup". + * + * On return, the state is as if gserial_connect() had never been called; + * there is no active USB I/O on these endpoints. + */ +void gserial_disconnect(struct gserial *gser) +{ + struct gs_port *port = gser->ioport; + unsigned long flags; + + if (!port) + return; + + /* tell the TTY glue not to do I/O here any more */ + spin_lock_irqsave(&port->port_lock, flags); + + /* REVISIT as above: how best to track this? */ + port->port_line_coding = gser->port_line_coding; + + port->port_usb = NULL; + gser->ioport = NULL; + if (port->open_count > 0 || port->openclose) { + wake_up_interruptible(&port->drain_wait); + if (port->port_tty) + tty_hangup(port->port_tty); + } + spin_unlock_irqrestore(&port->port_lock, flags); + + /* disable endpoints, aborting down any active I/O */ + usb_ep_disable(gser->out); + gser->out->driver_data = NULL; + + usb_ep_disable(gser->in); + gser->in->driver_data = NULL; + + /* finally, free any unused/unusable I/O buffers */ + spin_lock_irqsave(&port->port_lock, flags); + if (port->open_count == 0 && !port->openclose) + gs_buf_free(&port->port_write_buf); + gs_free_requests(gser->out, &port->read_pool, NULL); + gs_free_requests(gser->out, &port->read_queue, NULL); + gs_free_requests(gser->in, &port->write_pool, NULL); + + port->read_allocated = port->read_started = + port->write_allocated = port->write_started = 0; + + spin_unlock_irqrestore(&port->port_lock, flags); +} diff --git a/drivers/usb/gadget/gadget_gbhc/u_serial.h b/drivers/usb/gadget/gadget_gbhc/u_serial.h new file mode 100644 index 0000000..300f0ed --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/u_serial.h @@ -0,0 +1,67 @@ +/* + * u_serial.h - interface to USB gadget "serial port"/TTY utilities + * + * Copyright (C) 2008 David Brownell + * Copyright (C) 2008 by Nokia Corporation + * + * This software is distributed under the terms of the GNU General + * Public License ("GPL") as published by the Free Software Foundation, + * either version 2 of that License or (at your option) any later version. + */ + +#ifndef __U_SERIAL_H +#define __U_SERIAL_H + +#include <linux/usb/composite.h> +#include <linux/usb/cdc.h> + +/* + * One non-multiplexed "serial" I/O port ... there can be several of these + * on any given USB peripheral device, if it provides enough endpoints. + * + * The "u_serial" utility component exists to do one thing: manage TTY + * style I/O using the USB peripheral endpoints listed here, including + * hookups to sysfs and /dev for each logical "tty" device. + * + * REVISIT at least ACM could support tiocmget() if needed. + * + * REVISIT someday, allow multiplexing several TTYs over these endpoints. + */ +struct gserial { + struct usb_function func; + + /* port is managed by gserial_{connect,disconnect} */ + struct gs_port *ioport; + + struct usb_ep *in; + struct usb_ep *out; + struct usb_endpoint_descriptor *in_desc; + struct usb_endpoint_descriptor *out_desc; + + /* REVISIT avoid this CDC-ACM support harder ... */ + struct usb_cdc_line_coding port_line_coding; /* 9600-8-N-1 etc */ + + /* notification callbacks */ + void (*connect)(struct gserial *p); + void (*disconnect)(struct gserial *p); + int (*send_break)(struct gserial *p, int duration); +}; + +/* utilities to allocate/free request and buffer */ +struct usb_request *gs_alloc_req(struct usb_ep *ep, unsigned len, gfp_t flags); +void gs_free_req(struct usb_ep *, struct usb_request *req); + +/* port setup/teardown is handled by gadget driver */ +int gserial_setup(struct usb_gadget *g, unsigned n_ports); +void gserial_cleanup(void); + +/* connect/disconnect is handled by individual functions */ +int gserial_connect(struct gserial *, u8 port_num); +void gserial_disconnect(struct gserial *); + +/* functions are bound to configurations by a config or gadget driver */ +int acm_bind_config(struct usb_configuration *c, u8 port_num); +int gser_bind_config(struct usb_configuration *c, u8 port_num); +int obex_bind_config(struct usb_configuration *c, u8 port_num); + +#endif /* __U_SERIAL_H */ diff --git a/drivers/usb/gadget/gadget_gbhc/usbstring.c b/drivers/usb/gadget/gadget_gbhc/usbstring.c new file mode 100644 index 0000000..58c4d37 --- /dev/null +++ b/drivers/usb/gadget/gadget_gbhc/usbstring.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2003 David Brownell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/device.h> +#include <linux/init.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <asm/unaligned.h> + + +static int utf8_to_utf16le(const char *s, __le16 *cp, unsigned len) +{ + int count = 0; + u8 c; + u16 uchar; + + /* this insists on correct encodings, though not minimal ones. + * BUT it currently rejects legit 4-byte UTF-8 code points, + * which need surrogate pairs. (Unicode 3.1 can use them.) + */ + while (len != 0 && (c = (u8) *s++) != 0) { + if (unlikely(c & 0x80)) { + // 2-byte sequence: + // 00000yyyyyxxxxxx = 110yyyyy 10xxxxxx + if ((c & 0xe0) == 0xc0) { + uchar = (c & 0x1f) << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0x80) + goto fail; + c &= 0x3f; + uchar |= c; + + // 3-byte sequence (most CJKV characters): + // zzzzyyyyyyxxxxxx = 1110zzzz 10yyyyyy 10xxxxxx + } else if ((c & 0xf0) == 0xe0) { + uchar = (c & 0x0f) << 12; + + c = (u8) *s++; + if ((c & 0xc0) != 0x80) + goto fail; + c &= 0x3f; + uchar |= c << 6; + + c = (u8) *s++; + if ((c & 0xc0) != 0x80) + goto fail; + c &= 0x3f; + uchar |= c; + + /* no bogus surrogates */ + if (0xd800 <= uchar && uchar <= 0xdfff) + goto fail; + + // 4-byte sequence (surrogate pairs, currently rare): + // 11101110wwwwzzzzyy + 110111yyyyxxxxxx + // = 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx + // (uuuuu = wwww + 1) + // FIXME accept the surrogate code points (only) + + } else + goto fail; + } else + uchar = c; + put_unaligned_le16(uchar, cp++); + count++; + len--; + } + return count; +fail: + return -1; +} + + +/** + * usb_gadget_get_string - fill out a string descriptor + * @table: of c strings encoded using UTF-8 + * @id: string id, from low byte of wValue in get string descriptor + * @buf: at least 256 bytes + * + * Finds the UTF-8 string matching the ID, and converts it into a + * string descriptor in utf16-le. + * Returns length of descriptor (always even) or negative errno + * + * If your driver needs stings in multiple languages, you'll probably + * "switch (wIndex) { ... }" in your ep0 string descriptor logic, + * using this routine after choosing which set of UTF-8 strings to use. + * Note that US-ASCII is a strict subset of UTF-8; any string bytes with + * the eighth bit set will be multibyte UTF-8 characters, not ISO-8859/1 + * characters (which are also widely used in C strings). + */ +int +usb_gadget_get_string (struct usb_gadget_strings *table, int id, u8 *buf) +{ + struct usb_string *s; + int len; + + /* descriptor 0 has the language id */ + if (id == 0) { + buf [0] = 4; + buf [1] = USB_DT_STRING; + buf [2] = (u8) table->language; + buf [3] = (u8) (table->language >> 8); + return 4; + } + for (s = table->strings; s && s->s; s++) + if (s->id == id) + break; + + /* unrecognized: stall. */ + if (!s || !s->s) + return -EINVAL; + + /* string descriptors have length, tag, then UTF16-LE text */ + len = min ((size_t) 126, strlen (s->s)); + memset (buf + 2, 0, 2 * len); /* zero all the bytes */ + len = utf8_to_utf16le(s->s, (__le16 *)&buf[2], len); + if (len < 0) + return -EINVAL; + buf [0] = (len + 1) * 2; + buf [1] = USB_DT_STRING; + return buf [0]; +} + diff --git a/drivers/usb/gadget/multi_config.c b/drivers/usb/gadget/multi_config.c new file mode 100644 index 0000000..2e4f52e --- /dev/null +++ b/drivers/usb/gadget/multi_config.c @@ -0,0 +1,260 @@ +/* + * File Name : multi_config.c + * + * Virtual multi configuration utilities for composite USB gadgets. + * This utilitie can support variable interface for variable Host PC. + * + * Copyright (C) 2011 Samsung Electronics + * Author: SoonYong, Cho <soonyong.cho@samsung.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include "multi_config.h" + +static int multi; /* current configuration */ +static int is_multi; /* Is multi configuration available ? */ +static int stringMode = OTHER_REQUEST; +static int interfaceCount; + +/* Description : Set configuration number + * Parameter : unsigned num (host request) + * Return value : always return 0 (It's virtual multiconfiguration) + */ +unsigned set_config_number(unsigned num) +{ + if (num != 0) + USB_DBG_ESS("multi config_num=%d(zero base)\n", num); + else + USB_DBG_ESS("single config\n"); + + multi = num; /* save config number from Host request */ + return 0; /* always return 0 config */ +} + +/* Description : Get configuration number + * Return value : virtual multiconfiguration number (zero base) + */ +int get_config_number(void) +{ + USB_DBG("multi=%d\n", multi); + + return multi; +} + +/* Description : Check configuration number + * Parameter : unsigned num (host request) + * Return value : 1 (true : virtual multi configuraiton) + 0 (false : normal configuration) + */ +int check_config(unsigned num) +{ + USB_DBG("num=%d, multi=%d\n", num, multi); + /* multi is zero base, but num is 1 ase */ + if (num && num == multi + 1) { + USB_DBG("Use virtual multi configuration\n"); + return 1; + } else { + USB_DBG("normal configuration\n"); + return 0; + } +} + +/* Description : Search number of configuration including virtual configuration + * Parameter : usb_configuration *c (referenced configuration) + unsigned count (real number of configuration) + * Return value : virtual or real number of configuration + */ +unsigned count_multi_config(struct usb_configuration *c, unsigned count) +{ + int f_first = 0; + int f_second = 0; + int f_exception = 0; + struct usb_function *f; + is_multi = 0; + + if (!c) { + USB_DBG("usb_configuration is not valid\n"); + return 0; + } + + list_for_each_entry(f, &c->functions, list) { + if (!strcmp(f->name, MULTI_FUNCTION_1)) { + USB_DBG("%s +\n", MULTI_FUNCTION_1); + f_first = 1; + } else if (!strcmp(f->name, MULTI_FUNCTION_2)) { + USB_DBG("%s +\n", MULTI_FUNCTION_2); + f_second = 1; + } else if (!strcmp(f->name, MULTI_EXCEPTION_FUNCTION)) { + USB_DBG("exception %s +\n", MULTI_EXCEPTION_FUNCTION); + f_exception = 1; + } + } + + if (f_first && f_second && !f_exception) { + USB_DBG_ESS("ready multi\n"); + is_multi = 1; + return 2; + } + return count; +} + +/* Description : Is multi configuration available ? + * Return value : 1 (true), 0 (false) + */ +int is_multi_configuration(void) +{ + USB_DBG("= %d\n", is_multi); + return is_multi; +} + +/* Description : Check function to skip for multi configuration + * Parameter : char* name (function name) + * Return value : 0 (not available), 1 (available) + */ +int is_available_function(const char *name) +{ + if (is_multi_configuration()) { + USB_DBG("multi case\n"); + if (!multi) { + if (!strcmp(name, MAIN_FUNCTION)) { + USB_DBG("%s is available.\n", + MAIN_FUNCTION); + return 1; + } + return 0; /* anothor function is not available */ + } else { + USB_DBG("multi=%d all available\n", multi); + } + } + return 1; /* if single configuration, every function is available */ +} + +/* Description : Change configuration using virtual multi configuration. + * Parameter : struct usb_funciton f (to be changed function interface) + void *next (next means usb req->buf) + int len (length for to fill buffer) + struct usb_configuration *config + (To reference interface array of current config) + enum usb_device_speed speed (usb speed) + * Return value : "ret < 0" means fillbuffer function is failed. + */ +int change_conf(struct usb_function *f, + void *next, int len, + struct usb_configuration *config, + enum usb_device_speed speed) +{ + u8 *dest; + int status = 0; + struct usb_descriptor_header *descriptor; + struct usb_interface_descriptor *intf; + int index_intf = 0; + int change_intf = 0; + struct usb_descriptor_header **descriptors; + + USB_DBG("f->%s process multi\n", f->name); + + if (!f || !config || !next) { + USB_DBG_ESS("one of f, config, next is not valid\n"); + return -EFAULT; + } + if (speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + if (!descriptors) { + USB_DBG_ESS("descriptor is not available\n"); + return -EFAULT; + } + + if (f->set_config_desc) + f->set_config_desc(stringMode); + + /* set interface numbers dynamically */ + dest = next; + + while ((descriptor = *descriptors++) != NULL) { + intf = (struct usb_interface_descriptor *)dest; + if (intf->bDescriptorType == USB_DT_INTERFACE) { + if (intf->bAlternateSetting == 0) { + intf->bInterfaceNumber = interfaceCount++; + USB_DBG("a=0 intf->bInterfaceNumber=%d\n", + intf->bInterfaceNumber); + } else { + intf->bInterfaceNumber = interfaceCount - 1; + USB_DBG("a!=0 intf->bInterfaceNumber=%d\n", + intf->bInterfaceNumber); + } + config->interface + [intf->bInterfaceNumber] + = f; + if (f->set_intf_num) { + change_intf = 1; + f->set_intf_num(f, + intf->bInterfaceNumber, + index_intf++); + } + } + dest += intf->bLength; + } + + if (change_intf) { + if (speed == USB_SPEED_HIGH) + descriptors = f->hs_descriptors; + else + descriptors = f->descriptors; + status = usb_descriptor_fillbuf( + next, len, + (const struct usb_descriptor_header **) + descriptors); + if (status < 0) { + USB_DBG_ESS("usb_descriptor_fillbuf failed\n"); + return status; + } + } + + return status; +} + +/* Description : Set interface count + * Parameter : struct usb_configuration *config + * (To reference interface array of current config) + * struct usb_config_descriptor *c + * (number of interfaces) + * Return value : void + */ +void set_interface_count(struct usb_configuration *config, + struct usb_config_descriptor *c) +{ + USB_DBG_ESS("next_interface_id=%d\n", interfaceCount); + config->next_interface_id = interfaceCount; + config->interface[interfaceCount] = 0; + c->bNumInterfaces = interfaceCount; + interfaceCount = 0; + return ; +} + +/* Description : Set string mode + * This mode will be used for deciding other interface. + * Parameter : u16 w_length + * - 2 means MAC request. + * - Windows and Linux PC always request 255 size. + */ +void set_string_mode(u16 w_length) +{ + if (w_length == 2) { + USB_DBG("mac request\n"); + stringMode = MAC_REQUEST; + } else if (w_length == 0) { + USB_DBG("initialize string mode\n"); + stringMode = OTHER_REQUEST; + } +} diff --git a/drivers/usb/gadget/multi_config.h b/drivers/usb/gadget/multi_config.h new file mode 100644 index 0000000..792bbae --- /dev/null +++ b/drivers/usb/gadget/multi_config.h @@ -0,0 +1,135 @@ +/* + * File Name : multi_config.h + * + * Virtual multi configuration utilities for composite USB gadgets. + * This utilitie can support variable interface for variable Host PC. + * + * Copyright (C) 2011 Samsung Electronics + * Author: SoonYong, Cho <soonyong.cho@samsung.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __MULTI_CONFIG_H +#define __MULTI_CONFIG_H + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/usb/composite.h> + +/* + * Debugging macro and defines + */ +/*#define USB_DEBUG + *#define USB_MORE_DEBUG + */ +#define USB_DEBUG_ESS + +#ifdef USB_DEBUG +# ifdef USB_MORE_DEBUG +# define USB_DBG(fmt, args...) \ + printk(KERN_INFO "usb: %s "fmt, __func__, ##args) +# else +# define USB_DBG(fmt, args...) \ + printk(KERN_DEBUG "usb: %s "fmt, __func__, ##args) +# endif +#else /* DO NOT PRINT LOG */ +# define USB_DBG(fmt, args...) do { } while (0) +#endif /* USB_DEBUG */ + +#ifdef USB_DEBUG_ESS +# ifdef USB_MORE_DEBUG +# define USB_DBG_ESS(fmt, args...) \ + printk(KERN_INFO "usb: %s "fmt, __func__, ##args) +# else +# define USB_DBG_ESS(fmt, args...) \ + printk(KERN_DEBUG "usb: %s "fmt, __func__, ##args) +# endif +#else /* DO NOT PRINT LOG */ +# define USB_DBG_ESS(fmt, args...) do { } while (0) +#endif /* USB_DEBUG_ESS */ + +#define MAIN_FUNCTION "mtp" +#define MULTI_FUNCTION_1 "mtp" +#define MULTI_FUNCTION_2 "acm0" +#define MULTI_EXCEPTION_FUNCTION "adb" + +#define MAC_REQUEST 0 +#define OTHER_REQUEST 1 + +/* Description : Set configuration number + * Parameter : unsigned num (host request) + * Return value : always return 0 (It's virtual multiconfiguration) + */ +unsigned set_config_number(unsigned num); + +/* Description : Get configuration number + * Return value : virtual multiconfiguration number (zero base) + */ +int get_config_number(void); + +/* Description : Check configuration number + * Parameter : unsigned num (host request) + * Return value : 1 (true : virtual multi configuraiton) + 0 (false : normal configuration) + */ +int check_config(unsigned num); + +/* Description : Search number of configuration including virtual configuration + * Parameter : usb_configuration *c (referenced configuration) + unsigned count (real number of configuration) + * Return value : virtual or real number of configuration + */ +unsigned count_multi_config(struct usb_configuration *c, unsigned count); + +/* Description : Is multi configuration available ? + * Return value : 1 (true), 0 (false) + */ +int is_multi_configuration(void); + +/* Description : Check function to skip for multi configuration + * Parameter : char* name (function name) + * Return value : 0 (not available), 1 (available) + */ +int is_available_function(const char *name); + +/* Description : Change configuration using virtual multi configuration. + * Parameter : struct usb_funciton f (to be changed function interface) + void *next (next means usb req->buf) + int len (length for to fill buffer) + struct usb_configuration *config + (To reference interface array of current config) + enum usb_device_speed speed (usb speed) + * Return value : "ret < 0" means fillbuffer function is failed. + */ +int change_conf(struct usb_function *f, + void *next, int len, + struct usb_configuration *config, + enum usb_device_speed speed); + +/* Description : Set interface count + * Parameter : struct usb_configuration *config + (To reference interface array of current config) + struct usb_config_descriptor *c + (number of interfaces) + * Return value : void + */ +void set_interface_count(struct usb_configuration *config, + struct usb_config_descriptor *c); + +/* Description : Set string mode + * This mode will be used for deciding other interface. + * Parameter : u16 w_length + * - 2 means MAC request. + * - Windows and Linux PC always request 255 size. + */ +void set_string_mode(u16 w_length); +#endif /* __MULTI_CONFIG_H */ diff --git a/drivers/usb/gadget/s3c_udc.h b/drivers/usb/gadget/s3c_udc.h new file mode 100644 index 0000000..a75f8f2 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc.h @@ -0,0 +1,156 @@ +/* + * drivers/usb/gadget/s3c_udc.h + * Samsung S3C on-chip full/high speed USB device controllers + * Copyright (C) 2005 for Samsung Electronics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __S3C_USB_GADGET +#define __S3C_USB_GADGET + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <asm/byteorder.h> +#include <asm/dma.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <linux/wakelock.h> + +/* Max packet size */ +#if defined(CONFIG_USB_GADGET_S3C_FS) +#define EP0_FIFO_SIZE 8 +#define EP_FIFO_SIZE 64 +#define S3C_MAX_ENDPOINTS 5 +#elif defined(CONFIG_USB_GADGET_S3C_HS) || defined(CONFIG_PLAT_S5P64XX)\ + || defined(CONFIG_PLAT_S5PC11X) || defined(CONFIG_CPU_S5P6442)\ + || defined(CONFIG_CPU_S5P6450) || defined(CONFIG_CPU_S5PV310)\ + || defined(CONFIG_ARCH_EXYNOS) + +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#define DED_TX_FIFO 1 /* Dedicated NPTx fifo for s5p6440 */ +#else +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#endif + +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 +#define RegReadErr 5 +#define FAIL_TO_SETUP 6 + +#define TEST_J_SEL 0x1 +#define TEST_K_SEL 0x2 +#define TEST_SE0_NAK_SEL 0x3 +#define TEST_PACKET_SEL 0x4 +#define TEST_FORCE_ENABLE_SEL 0x5 + +/* ************************************************************************* */ +/* IO + */ + +typedef enum ep_type { + ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt +} ep_type_t; + +struct s3c_ep { + struct usb_ep ep; + struct s3c_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + u8 stopped; + u8 bEndpointAddress; + u8 bmAttributes; + + ep_type_t ep_type; + u32 fifo; +#ifdef CONFIG_USB_GADGET_S3C_FS + u32 csr1; + u32 csr2; +#endif +}; + +struct s3c_request { + struct usb_request req; + struct list_head queue; + unsigned char mapped; + unsigned written_bytes; +}; + +struct s3c_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *dev; + struct clk *clk; + spinlock_t lock; + + int ep0state; + struct s3c_ep ep[S3C_MAX_ENDPOINTS]; + + unsigned char usb_address; + struct usb_ctrlrequest *usb_ctrl; + dma_addr_t usb_ctrl_dma; + + void __iomem *regs; + struct resource *regs_res; + unsigned int irq; + unsigned req_pending:1, req_std:1, req_config:1; + struct wake_lock usbd_wake_lock; + struct wake_lock usb_cb_wake_lock; + int udc_enabled; +}; + +extern struct s3c_udc *the_controller; +extern void samsung_cable_check_status(int flag); + +#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN) == USB_DIR_IN) + +#define ep_index(EP) ((EP)->bEndpointAddress&0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#endif diff --git a/drivers/usb/gadget/s3c_udc_otg.c b/drivers/usb/gadget/s3c_udc_otg.c new file mode 100644 index 0000000..6703973 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc_otg.c @@ -0,0 +1,1402 @@ +/* + * drivers/usb/gadget/s3c_udc_otg.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008 for Samsung Electronics + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "s3c_udc.h" +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <mach/map.h> +#include <plat/regs-otg.h> +#include <plat/usb-phy.h> +#include <plat/usbgadget.h> +#include <plat/cpu.h> + +#if 1 +#undef DEBUG_S3C_UDC_SETUP +#undef DEBUG_S3C_UDC_EP0 +#undef DEBUG_S3C_UDC_ISR +#undef DEBUG_S3C_UDC_OUT_EP +#undef DEBUG_S3C_UDC_IN_EP +#undef DEBUG_S3C_UDC +#else +#define DEBUG_S3C_UDC_SETUP +#define DEBUG_S3C_UDC_EP0 +#define DEBUG_S3C_UDC_ISR +#define DEBUG_S3C_UDC_OUT_EP +#define DEBUG_S3C_UDC_IN_EP +#define DEBUG_S3C_UDC +#endif + +#define EP0_CON 0 +#define EP1_OUT 1 +#define EP2_IN 2 +#define EP3_IN 3 +#define EP_MASK 0xF + +#define BACK2BACK_SIZE 4 + +#if defined(DEBUG_S3C_UDC_SETUP) || defined(DEBUG_S3C_UDC_ISR)\ + || defined(DEBUG_S3C_UDC_OUT_EP) + +static char *state_names[] = { + "WAIT_FOR_SETUP", + "DATA_STATE_XMIT", + "DATA_STATE_NEED_ZLP", + "WAIT_FOR_OUT_STATUS", + "DATA_STATE_RECV", + }; +#endif + +#ifdef DEBUG_S3C_UDC_SETUP +#define DEBUG_SETUP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_SETUP(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_EP0 +#define DEBUG_EP0(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_EP0(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC +#define DEBUG(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_ISR +#define DEBUG_ISR(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_ISR(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_OUT_EP +#define DEBUG_OUT_EP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_OUT_EP(fmt, args...) do {} while (0) +#endif + +#ifdef DEBUG_S3C_UDC_IN_EP +#define DEBUG_IN_EP(fmt, args...) printk(fmt, ##args) +#else +#define DEBUG_IN_EP(fmt, args...) do {} while (0) +#endif + +#define DRIVER_DESC "S3C HS USB OTG Device Driver,"\ + "(c) 2008-2009 Samsung Electronics" +#define DRIVER_VERSION "15 March 2009" + +struct s3c_udc *the_controller; + +static const char driver_name[] = "s3c-udc"; +static const char driver_desc[] = DRIVER_DESC; +static const char ep0name[] = "ep0-control"; + +/* Max packet size*/ +static unsigned int ep0_fifo_size = 64; +static unsigned int ep_fifo_size = 512; +static unsigned int ep_fifo_size2 = 1024; +static int reset_available = 1; + +/* + Local declarations. +*/ +static int s3c_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *); +static int s3c_ep_disable(struct usb_ep *ep); +static struct usb_request *s3c_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags); +static void s3c_free_request(struct usb_ep *ep, struct usb_request *); + +static int s3c_queue(struct usb_ep *ep, struct usb_request *, gfp_t gfp_flags); +static int s3c_dequeue(struct usb_ep *ep, struct usb_request *); +static int s3c_fifo_status(struct usb_ep *ep); +static void s3c_fifo_flush(struct usb_ep *ep); +static void s3c_ep0_read(struct s3c_udc *dev); +static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep); +static void s3c_handle_ep0(struct s3c_udc *dev); +static int s3c_ep0_write(struct s3c_udc *dev); +static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req); +static void done(struct s3c_ep *ep, + struct s3c_request *req, int status); +static void stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver); +static int udc_enable(struct s3c_udc *dev); +static void udc_disable(struct s3c_udc *dev); +static void udc_set_address(struct s3c_udc *dev, unsigned char address); +static void reset_usbd(void); +static void reconfig_usbd(void); +static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed); +static void nuke(struct s3c_ep *ep, int status); +static int s3c_udc_set_halt(struct usb_ep *_ep, int value); +static void s3c_udc_soft_connect(void); +static void s3c_udc_soft_disconnect(void); + +static struct usb_ep_ops s3c_ep_ops = { + .enable = s3c_ep_enable, + .disable = s3c_ep_disable, + + .alloc_request = s3c_alloc_request, + .free_request = s3c_free_request, + + .queue = s3c_queue, + .dequeue = s3c_dequeue, + + .set_halt = s3c_udc_set_halt, + .fifo_status = s3c_fifo_status, + .fifo_flush = s3c_fifo_flush, +}; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static const char proc_node_name[] = "driver/udc"; + +static int +udc_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + struct s3c_udc *dev = _dev; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + + if (off != 0) + return 0; + + local_irq_save(flags); + + /* basic device status */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n" + "\n", + driver_name, DRIVER_VERSION, + dev->driver ? dev->driver->driver.name : "(none)"); + size -= t; + next += t; + + local_irq_restore(flags); + *eof = 1; + return count - size; +} + +#define create_proc_files() \ + create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) +#define remove_proc_files() \ + remove_proc_entry(proc_node_name, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_files() do {} while (0) +#define remove_proc_files() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + +#include "s3c_udc_otg_xfer_dma.c" + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct s3c_udc *dev) +{ + struct platform_device *pdev = dev->dev; + struct s5p_usbgadget_platdata *pdata = pdev->dev.platform_data; + u32 utemp; + DEBUG_SETUP("%s: %p\n", __func__, dev); + + disable_irq(dev->irq); + udc_set_address(dev, 0); + + dev->ep0state = WAIT_FOR_SETUP; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->usb_address = 0; + + /* Mask the core interrupt */ + __raw_writel(0, dev->regs + S3C_UDC_OTG_GINTMSK); + + /* Put the OTG device core in the disconnected state.*/ + utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + utemp |= SOFT_DISCONNECT; + __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL); + udelay(20); + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_DEVICE); + clk_disable(dev->clk); +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct s3c_udc *dev) +{ + unsigned int i; + + DEBUG_SETUP("%s: %p\n", __func__, dev); + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = WAIT_FOR_SETUP; + + /* basic endpoint records init */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = 0; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +#define BYTES2MAXP(x) (x / 8) +#define MAXP2BYTES(x) (x * 8) + +/* until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static int udc_enable(struct s3c_udc *dev) +{ + struct platform_device *pdev = dev->dev; + struct s5p_usbgadget_platdata *pdata = pdev->dev.platform_data; + DEBUG_SETUP("%s: %p\n", __func__, dev); + + enable_irq(dev->irq); + clk_enable(dev->clk); + if (pdata->phy_init) + pdata->phy_init(pdev, S5P_USB_PHY_DEVICE); + reconfig_usbd(); + + DEBUG_SETUP("S3C USB 2.0 OTG Controller Core Initialized : 0x%x\n", + __raw_readl(dev->regs + S3C_UDC_OTG_GINTMSK)); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + return 0; +} + +int s3c_vbus_enable(struct usb_gadget *gadget, int is_active) +{ + unsigned long flags; + struct s3c_udc *dev = container_of(gadget, struct s3c_udc, gadget); + + if (dev->udc_enabled != is_active) { + dev->udc_enabled = is_active; + + if (!is_active) { + printk(KERN_DEBUG "usb: %s is_active=%d(udc_disable)\n", + __func__, is_active); + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, dev->driver); + spin_unlock_irqrestore(&dev->lock, flags); + udc_disable(dev); +#if defined(CONFIG_BATTERY_SAMSUNG) + s3c_udc_cable_disconnect(dev); +#endif + wake_lock_timeout(&dev->usbd_wake_lock, HZ * 5); + wake_lock_timeout(&dev->usb_cb_wake_lock, HZ * 5); + } else { + printk(KERN_DEBUG "usb: %s is_active=%d(udc_enable)\n", + __func__, is_active); + wake_lock(&dev->usb_cb_wake_lock); + udc_reinit(dev); + udc_enable(dev); + } + } else { + printk(KERN_DEBUG "usb: %s, udc_enabled : %d, is_active : %d\n", + __func__, dev->udc_enabled, is_active); + + } + + + return 0; +} + +/* + Register entry point for the peripheral controller driver. +*/ +int usb_gadget_probe_driver(struct usb_gadget_driver *driver, + int (*bind)(struct usb_gadget *)) +{ + struct s3c_udc *dev = the_controller; + int retval; + + DEBUG_SETUP("%s: %s\n", __func__, driver->driver.name); + + if (!driver + || (driver->speed != USB_SPEED_FULL + && driver->speed != USB_SPEED_HIGH) + || !bind || !driver->disconnect || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + /* first hook up the driver ... */ + dev->driver = driver; + dev->gadget.dev.driver = &driver->driver; + retval = device_add(&dev->gadget.dev); + + if (retval) { /* TODO */ + printk(KERN_ERR "target device_add failed, error %d\n", retval); + return retval; + } + + retval = bind(&dev->gadget); + if (retval) { + printk(KERN_ERR "%s: bind to driver %s --> error %d\n", + dev->gadget.name, driver->driver.name, retval); + device_del(&dev->gadget.dev); + + dev->driver = 0; + dev->gadget.dev.driver = 0; + return retval; + } +#if defined(CONFIG_USB_EXYNOS_SWITCH) || defined(CONFIG_MFD_MAX77693)\ + || defined(CONFIG_MFD_MAX8997) || defined(CONFIG_MFD_MAX77686) + printk(KERN_INFO "usb: Skip udc_enable\n"); +#else + printk(KERN_INFO "usb: udc_enable\n"); + udc_enable(dev); + dev->udc_enabled = 1; +#endif + return 0; +} +EXPORT_SYMBOL(usb_gadget_probe_driver); + +/* + Unregister entry point for the peripheral controller driver. +*/ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct s3c_udc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + dev->driver = 0; + stop_activity(dev, driver); + spin_unlock_irqrestore(&dev->lock, flags); + + driver->unbind(&dev->gadget); + device_del(&dev->gadget.dev); + + printk(KERN_INFO "Unregistered gadget driver '%s'\n", + driver->driver.name); + + udc_disable(dev); + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct s3c_ep *ep, struct s3c_request *req, int status) +{ + unsigned int stopped = ep->stopped; + struct device *dev = &the_controller->dev->dev; + + DEBUG("%s: %s %p, req = %p, stopped = %d\n", + __func__, ep->ep.name, ep, &req->req, stopped); + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (req->mapped) { + dma_unmap_single(dev, req->req.dma, req->req.length, + (ep->bEndpointAddress & USB_DIR_IN) ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + req->req.dma = DMA_ADDR_INVALID; + req->mapped = 0; + } + + if (status && status != -ESHUTDOWN) { + DEBUG("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + } + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + + spin_unlock(&ep->dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->dev->lock); + + ep->stopped = stopped; +} + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct s3c_ep *ep, int status) +{ + struct s3c_request *req; + + DEBUG("%s: %s %p\n", __func__, ep->ep.name, ep); + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + done(ep, req, status); + } +} + +static void stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +static void reset_usbd(void) +{ + struct s3c_udc *dev = the_controller; + unsigned int utemp; +#ifdef DED_TX_FIFO + int i; + + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(i)); + + if (utemp & DEPCTL_EPENA) + __raw_writel(utemp|DEPCTL_EPDIS|DEPCTL_SNAK, + dev->regs + S3C_UDC_OTG_DIEPCTL(i)); + + /* OUT EP0 cannot be disabled */ + if (i != 0) { + utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(i)); + + if (utemp & DEPCTL_EPENA) + __raw_writel(utemp|DEPCTL_EPDIS|DEPCTL_SNAK, + dev->regs + S3C_UDC_OTG_DOEPCTL(i)); + } + } +#endif + + /* 5. Configure OTG Core to initial settings of device mode.*/ + __raw_writel(1<<18|0x0<<0, + dev->regs + S3C_UDC_OTG_DCFG); + + mdelay(1); + + /* 6. Unmask the core interrupts*/ + __raw_writel(GINTMSK_INIT, dev->regs + S3C_UDC_OTG_GINTMSK); + + /* 7. Set NAK bit of OUT EP0*/ + __raw_writel(DEPCTL_SNAK, + dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + + /* 8. Unmask EPO interrupts*/ + __raw_writel(((1<<EP0_CON)<<DAINT_OUT_BIT)|(1<<EP0_CON), + dev->regs + S3C_UDC_OTG_DAINTMSK); + + /* 9. Unmask device OUT EP common interrupts*/ + __raw_writel(DOEPMSK_INIT, dev->regs + S3C_UDC_OTG_DOEPMSK); + + /* 10. Unmask device IN EP common interrupts*/ + __raw_writel(DIEPMSK_INIT, dev->regs + S3C_UDC_OTG_DIEPMSK); + + /* 11. Set Rx FIFO Size (in 32-bit words) */ + __raw_writel(RX_FIFO_SIZE, dev->regs + S3C_UDC_OTG_GRXFSIZ); + + /* 12. Set Non Periodic Tx FIFO Size*/ + __raw_writel((NPTX_FIFO_SIZE) << 16 | (NPTX_FIFO_START_ADDR) << 0, + dev->regs + S3C_UDC_OTG_GNPTXFSIZ); + +#ifdef DED_TX_FIFO + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + __raw_writel((PTX_FIFO_SIZE) << 16 | + (NPTX_FIFO_START_ADDR + NPTX_FIFO_SIZE + + PTX_FIFO_SIZE*(i-1)) << 0, + dev->regs + S3C_UDC_OTG_DIEPTXF(i)); +#endif + + /* Flush the RX FIFO */ + __raw_writel(0x10, dev->regs + S3C_UDC_OTG_GRSTCTL); + while (__raw_readl(dev->regs + S3C_UDC_OTG_GRSTCTL) & 0x10) + ; + + /* Flush all the Tx FIFO's */ + __raw_writel(0x10<<6, dev->regs + S3C_UDC_OTG_GRSTCTL); + __raw_writel((0x10<<6)|0x20, dev->regs + S3C_UDC_OTG_GRSTCTL); + while (__raw_readl(dev->regs + S3C_UDC_OTG_GRSTCTL) & 0x20) + ; + + /* 13. Clear NAK bit of OUT EP0*/ + /* For Slave mode*/ + __raw_writel(DEPCTL_CNAK, + dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + + /* 14. Initialize OTG Link Core.*/ + __raw_writel(GAHBCFG_INIT, dev->regs + S3C_UDC_OTG_GAHBCFG); +} + +static void reconfig_usbd(void) +{ + struct s3c_udc *dev = the_controller; + unsigned int utemp; + + /* 2. Soft-reset OTG Core and then unreset again. */ + __raw_writel(CORE_SOFT_RESET, dev->regs + S3C_UDC_OTG_GRSTCTL); + + utemp = (0<<15 /* PHY Low Power Clock sel */ + |1<<14 /* Non-Periodic TxFIFO Rewind Enable */ + |0<<9|0<<8 /* [0:HNP disable, 1:HNP enable][ 0:SRP disable, 1:SRP enable] H1= 1,1 */ + |0<<7 /* Ulpi DDR sel */ + |0<<6 /* 0: high speed utmi+, 1: full speed serial */ + |0<<4 /* 0: utmi+, 1:ulpi */ + |0x7<<0); /* HS/FS Timeout */ + /* [13:10] Turnaround time 0x5:16-bit 0x9:8-bit UTMI+ */ + /* [3] phy i/f 0:8bit, 1:16bit */ + if (soc_is_exynos4210()) + utemp |= (0x5<<10 | 1<<3); + else + utemp |= (0x9<<10 | 0<<3); + __raw_writel(utemp, dev->regs + S3C_UDC_OTG_GUSBCFG); + + /* 3. Put the OTG device core in the disconnected state.*/ + utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + utemp |= SOFT_DISCONNECT; + __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL); + + udelay(20); + + /* 4. Make the OTG device core exit from the disconnected state.*/ + utemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + utemp = utemp & ~SOFT_DISCONNECT; + __raw_writel(utemp, dev->regs + S3C_UDC_OTG_DCTL); + + reset_usbd(); +} + +static void set_max_pktsize(struct s3c_udc *dev, enum usb_device_speed speed) +{ + unsigned int ep_ctrl; + int i; + + if (speed == USB_SPEED_HIGH) { + ep0_fifo_size = 64; + ep_fifo_size = 512; + ep_fifo_size2 = 1024; + dev->gadget.speed = USB_SPEED_HIGH; + } else { + ep0_fifo_size = 64; + ep_fifo_size = 64; + ep_fifo_size2 = 64; + dev->gadget.speed = USB_SPEED_FULL; + } + + dev->ep[0].ep.maxpacket = ep0_fifo_size; + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + dev->ep[i].ep.maxpacket = ep_fifo_size; + + /* EP0 - Control IN (64 bytes)*/ + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + __raw_writel(ep_ctrl|(0<<0), dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* EP0 - Control OUT (64 bytes)*/ + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + __raw_writel(ep_ctrl|(0<<0), dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); +} + +static int s3c_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) { + + DEBUG("%s: bad ep or descriptor\n", __func__); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + + DEBUG("%s: %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(desc->wMaxPacketSize) != ep_maxpacket(ep)) + || !desc->wMaxPacketSize) { + + DEBUG("%s: bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + + DEBUG("%s: bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->stopped = 0; + ep->desc = desc; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* Reset halt state */ + s3c_udc_set_halt(_ep, 0); + + spin_lock_irqsave(&ep->dev->lock, flags); + s3c_udc_ep_activate(ep); + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: enabled %s, stopped = %d, maxpacket = %d\n", + __func__, _ep->name, ep->stopped, ep->ep.maxpacket); + return 0; +} + +/** Disable EP + */ +static int s3c_ep_disable(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !ep->desc) { + DEBUG("%s: %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* Nuke all pending requests */ + nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DEBUG("%s: disabled %s\n", __func__, _ep->name); + return 0; +} + +static struct usb_request *s3c_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct s3c_request *req; + + DEBUG("%s: %s %p\n", __func__, ep->name, ep); + + req = kmalloc(sizeof *req, gfp_flags); + if (!req) + return 0; + + memset(req, 0, sizeof *req); + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + +static void s3c_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct s3c_request *req; + + DEBUG("%s: %p\n", __func__, ep); + + req = container_of(_req, struct s3c_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/* dequeue JUST ONE request */ +static int s3c_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c_ep *ep; + struct s3c_request *req; + unsigned long flags; + + DEBUG("%s: %p\n", __func__, _ep); + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + return 0; +} + +/** Return bytes in EP FIFO + */ +static int s3c_fifo_status(struct usb_ep *_ep) +{ + int count = 0; + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep) { + DEBUG("%s: bad ep\n", __func__); + return -ENODEV; + } + + DEBUG("%s: %d\n", __func__, ep_index(ep)); + + /* LPD can't report unclaimed bytes from IN fifos */ + if (ep_is_in(ep)) + return -EOPNOTSUPP; + + return count; +} + +/** Flush EP FIFO + */ +static void s3c_fifo_flush(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DEBUG("%s: bad ep\n", __func__); + return; + } + + DEBUG("%s: %d\n", __func__, ep_index(ep)); +} + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static int s3c_udc_get_frame(struct usb_gadget *_gadget) +{ + struct s3c_udc *dev = container_of(_gadget, struct s3c_udc, gadget); + /*fram count number [21:8]*/ + unsigned int frame = __raw_readl(dev->regs + S3C_UDC_OTG_DSTS); + + DEBUG("%s: %p\n", __func__, _gadget); + return frame & 0x3ff00; +} + +static int s3c_udc_wakeup(struct usb_gadget *_gadget) +{ + DEBUG("%s: %p\n", __func__, _gadget); + return -ENOTSUPP; +} + +static void s3c_udc_soft_connect(void) +{ + struct s3c_udc *dev = the_controller; + u32 uTemp; + + DEBUG("[%s]\n", __func__); + uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + uTemp = uTemp & ~SOFT_DISCONNECT; + __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL); +} + +static void s3c_udc_soft_disconnect(void) +{ + struct s3c_udc *dev = the_controller; + u32 uTemp; + unsigned long flags; + + DEBUG("[%s]\n", __func__); + uTemp = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + uTemp |= SOFT_DISCONNECT; + __raw_writel(uTemp, dev->regs + S3C_UDC_OTG_DCTL); + + spin_lock_irqsave(&dev->lock, flags); + stop_activity(dev, dev->driver); + spin_unlock_irqrestore(&dev->lock, flags); +} + +static int s3c_udc_pullup(struct usb_gadget *gadget, int is_on) +{ + if (is_on) + s3c_udc_soft_connect(); + else + s3c_udc_soft_disconnect(); + return 0; +} + +static const struct usb_gadget_ops s3c_udc_ops = { + .get_frame = s3c_udc_get_frame, + .wakeup = s3c_udc_wakeup, + /* current versions must always be self-powered */ + .pullup = s3c_udc_pullup, + .vbus_session = s3c_vbus_enable, +}; + +static void nop_release(struct device *dev) +{ + DEBUG("%s %s\n", __func__, dev->bus_id); +} + +static struct s3c_udc memory = { + .usb_address = 0, + + .gadget = { + .ops = &s3c_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + .dev = { + .release = nop_release, + }, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &s3c_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = 0, + .bmAttributes = 0, + + .ep_type = ep_control, + .fifo = (unsigned int) S3C_UDC_OTG_EP0_FIFO, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP1_FIFO, + }, + + .ep[2] = { + .ep = { + .name = "ep2-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP2_FIFO, + }, + + .ep[3] = { + .ep = { + .name = "ep3-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP3_FIFO, + }, + .ep[4] = { + .ep = { + .name = "ep4-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 4, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP4_FIFO, + }, + .ep[5] = { + .ep = { + .name = "ep5-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP5_FIFO, + }, + .ep[6] = { + .ep = { + .name = "ep6-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP6_FIFO, + }, + .ep[7] = { + .ep = { + .name = "ep7-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP7_FIFO, + }, + .ep[8] = { + .ep = { + .name = "ep8-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP8_FIFO, + }, + .ep[9] = { + .ep = { + .name = "ep9-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 9, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP9_FIFO, + }, + .ep[10] = { + .ep = { + .name = "ep10-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 10, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP10_FIFO, + }, + .ep[11] = { + .ep = { + .name = "ep11-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 11, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP11_FIFO, + }, + .ep[12] = { + .ep = { + .name = "ep12-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 12, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP12_FIFO, + }, + .ep[13] = { + .ep = { + .name = "ep13-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_OUT | 13, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (unsigned int) S3C_UDC_OTG_EP13_FIFO, + }, + .ep[14] = { + .ep = { + .name = "ep14-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 14, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (unsigned int) S3C_UDC_OTG_EP14_FIFO, + }, + .ep[15] = { + .ep = { + .name = "ep15-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 15, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (unsigned int) S3C_UDC_OTG_EP15_FIFO, + }, +}; + +/* + * probe - binds to the platform device + */ +static int s3c_udc_probe(struct platform_device *pdev) +{ + struct s5p_usbgadget_platdata *pdata; + struct s3c_udc *dev = &memory; + struct resource *res; + unsigned int irq; + int retval; + + if (!pdev) { + pr_err("%s: pdev is null\n", __func__); + return -EINVAL; + } + DEBUG("%s: %p\n", __func__, pdev); + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform data defined\n"); + return -EINVAL; + } +#ifdef CONFIG_USB_S3C_OTG_HOST + pdata->udc_irq = s3c_udc_irq; + pr_info("otg pdata %p, irq %p\n", pdata, pdata->udc_irq); +#endif + + spin_lock_init(&dev->lock); + dev->dev = pdev; + + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &pdev->dev; + dev_set_name(&dev->gadget.dev, "%s", "gadget"); + + dev->gadget.is_dualspeed = 1; /* Hack only*/ + dev->gadget.is_otg = 0; + dev->gadget.is_a_peripheral = 0; + dev->gadget.b_hnp_enable = 0; + dev->gadget.a_hnp_support = 0; + dev->gadget.a_alt_hnp_support = 0; + + the_controller = dev; + platform_set_drvdata(pdev, dev); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DEBUG(KERN_ERR "cannot find register resource 0\n"); + return -EINVAL; + } + + dev->regs_res = request_mem_region(res->start, resource_size(res), + dev_name(&pdev->dev)); + if (!dev->regs_res) { + DEBUG(KERN_ERR "cannot reserve registers\n"); + return -ENOENT; + } + + dev->regs = ioremap(res->start, resource_size(res)); + if (!dev->regs) { + DEBUG(KERN_ERR "cannot map registers\n"); + retval = -ENXIO; + goto err_regs_res; + } + + udc_reinit(dev); + wake_lock_init(&dev->usbd_wake_lock, WAKE_LOCK_SUSPEND, + "usb device wake lock"); + wake_lock_init(&dev->usb_cb_wake_lock, WAKE_LOCK_SUSPEND, + "usb cb wake lock"); + + /* irq setup after old hardware state is cleaned up */ + irq = platform_get_irq(pdev, 0); + + retval = + request_irq(irq, s3c_udc_irq, 0, driver_name, dev); + + if (retval != 0) { + DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name, + dev->irq, retval); + retval = -EBUSY; + goto err_regs; + } + dev->irq = irq; + disable_irq(dev->irq); + + dev->clk = clk_get(&pdev->dev, "usbotg"); + + if (IS_ERR(dev->clk)) { + dev_err(&pdev->dev, "Failed to get clock\n"); + goto err_irq; + } + + dev->usb_ctrl = dma_alloc_coherent(&pdev->dev, + sizeof(struct usb_ctrlrequest)*BACK2BACK_SIZE, + &dev->usb_ctrl_dma, GFP_KERNEL); + + if (!dev->usb_ctrl) { + DEBUG(KERN_ERR "%s: can't get usb_ctrl dma memory\n", + driver_name); + retval = -ENOMEM; + goto err_clk; + } + + create_proc_files(); + + return retval; +err_clk: + clk_put(dev->clk); +err_irq: + free_irq(dev->irq, dev); +err_regs: + iounmap(dev->regs); +err_regs_res: + release_resource(dev->regs_res); + kfree(dev->regs_res); + return retval; +} + +static int s3c_udc_remove(struct platform_device *pdev) +{ + struct s3c_udc *dev = platform_get_drvdata(pdev); + + DEBUG("%s: %p\n", __func__, pdev); + + remove_proc_files(); + usb_gadget_unregister_driver(dev->driver); + clk_put(dev->clk); + if (dev->usb_ctrl) + dma_free_coherent(&pdev->dev, + sizeof(struct usb_ctrlrequest)*BACK2BACK_SIZE, + dev->usb_ctrl, dev->usb_ctrl_dma); + free_irq(dev->irq, dev); + iounmap(dev->regs); + release_resource(dev->regs_res); + kfree(dev->regs_res); + + platform_set_drvdata(pdev, 0); + + the_controller = 0; + wake_lock_destroy(&dev->usbd_wake_lock); + wake_lock_destroy(&dev->usb_cb_wake_lock); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_udc_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_udc *dev = the_controller; + int i; + + if (dev->driver) { + if (dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + + /* Terminate any outstanding requests */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + if (ep->dev != NULL) + spin_lock(&ep->dev->lock); + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + if (ep->dev != NULL) + spin_unlock(&ep->dev->lock); + } + + if (dev->driver->disconnect) + dev->driver->disconnect(&dev->gadget); + +#if defined(CONFIG_USB_EXYNOS_SWITCH) || defined(CONFIG_MFD_MAX77693)\ + || defined(CONFIG_MFD_MAX8997) || defined(CONFIG_MFD_MAX77686) + /* Nothing to do */ +#else + udc_disable(dev); +#endif + } + + return 0; +} + +static int s3c_udc_resume(struct platform_device *pdev) +{ + struct s3c_udc *dev = the_controller; + + if (dev->driver) { + udc_reinit(dev); +#if defined(CONFIG_USB_EXYNOS_SWITCH) || defined(CONFIG_MFD_MAX77693)\ + || defined(CONFIG_MFD_MAX8997) || defined(CONFIG_MFD_MAX77686) + /* Nothing to do */ +#else + udc_enable(dev); +#endif + + if (dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + + return 0; +} +#else +#define s3c_udc_suspend NULL +#define s3c_udc_resume NULL +#endif /* CONFIG_PM */ + +/*-------------------------------------------------------------------------*/ +static struct platform_driver s3c_udc_driver = { + .probe = s3c_udc_probe, + .remove = s3c_udc_remove, + .suspend = s3c_udc_suspend, + .resume = s3c_udc_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c-usbgadget", + }, +}; + +static int __init udc_init(void) +{ + int ret; + + ret = platform_driver_register(&s3c_udc_driver); + if (!ret) + printk(KERN_INFO "%s : %s\n" + "%s : version %s\n", + driver_name, DRIVER_DESC, + driver_name, DRIVER_VERSION); + + return ret; +} + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&s3c_udc_driver); + printk(KERN_INFO "Unloaded %s version %s\n", + driver_name, DRIVER_VERSION); +} + +module_init(udc_init); +module_exit(udc_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_AUTHOR("Samsung"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c new file mode 100644 index 0000000..7554772 --- /dev/null +++ b/drivers/usb/gadget/s3c_udc_otg_xfer_dma.c @@ -0,0 +1,1455 @@ +/* drivers/usb/gadget/s3c_udc_otg_xfer_dma.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define GINTMSK_INIT (INT_OUT_EP | INT_IN_EP | INT_RESUME |\ + INT_ENUMDONE|INT_RESET|INT_SUSPEND) +#define DOEPMSK_INIT (CTRL_OUT_EP_SETUP_PHASE_DONE | AHB_ERROR |\ + TRANSFER_DONE) +#define DIEPMSK_INIT (NON_ISO_IN_EP_TIMEOUT|AHB_ERROR|TRANSFER_DONE) +#define GAHBCFG_INIT (PTXFE_HALF | NPTXFE_HALF | MODE_DMA | BURST_INCR4 |\ + GBL_INT_UNMASK) + +#define DMA_ADDR_INVALID (~(dma_addr_t)0) + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE +#include <linux/usb/composite.h> +#endif +static u8 clear_feature_num; +static int clear_feature_flag; +static int set_conf_done; + +/* Bulk-Only Mass Storage Reset (class-specific request) */ +#define GET_MAX_LUN_REQUEST 0xFE +#define BOT_RESET_REQUEST 0xFF + +/* TEST MODE in set_feature request */ +#define TEST_SELECTOR_MASK 0xFF +#define TEST_PKT_SIZE 53 + +static u8 test_pkt[TEST_PKT_SIZE] __attribute__((aligned(8))) = { + /* JKJKJKJK x 9 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* JJKKJJKK x 8 */ + 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + /* JJJJKKKK x 8 */ + 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, + /* JJJJJJJKKKKKKK x8 - '1' */ + 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* '1' + JJJJJJJK x 8 */ + 0x7F, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, + /* {JKKKKKKK x 10},JK */ + 0xFC, 0x7E, 0xBF, 0xDF, 0xEF, 0xF7, 0xFB, 0xFD, 0x7E +}; + +static void s3c_udc_ep_set_stall(struct s3c_ep *ep); + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +u32 cable_connected; + +void s3c_udc_cable_connect(struct s3c_udc *dev) +{ + samsung_cable_check_status(1); + cable_connected = 1; +} + +void s3c_udc_cable_disconnect(struct s3c_udc *dev) +{ + if (cable_connected) { + samsung_cable_check_status(0); + cable_connected = 0; + } +} +#endif + +static inline void s3c_udc_ep0_zlp(struct s3c_udc *dev) +{ + u32 ep_ctrl; + + __raw_writel(dev->usb_ctrl_dma, dev->regs + S3C_UDC_OTG_DIEPDMA(EP0_CON)); + __raw_writel((1<<19 | 0<<0), dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + __raw_writel(ep_ctrl | DEPCTL_EPENA | DEPCTL_CNAK, + dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + DEBUG_EP0("%s:EP0 ZLP DIEPCTL0 = 0x%x\n", + __func__, __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON))); +} + +static inline void s3c_udc_pre_setup(struct s3c_udc *dev) +{ + u32 ep_ctrl; + + DEBUG_IN_EP("%s : Prepare Setup packets.\n", __func__); + + __raw_writel((3<<29) | (1<<19) | sizeof(struct usb_ctrlrequest), + dev->regs + S3C_UDC_OTG_DOEPTSIZ(EP0_CON)); + __raw_writel(dev->usb_ctrl_dma, dev->regs + S3C_UDC_OTG_DOEPDMA(EP0_CON)); + + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + __raw_writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, + dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); +} + +static int setdma_rx(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 *buf, ctrl; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + struct s3c_udc *udc = ep->dev; + struct device *dev = &udc->dev->dev; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + length = req->req.length - req->req.actual; + + req->req.dma = dma_map_single(dev, buf, + length, DMA_FROM_DEVICE); + req->mapped = 1; + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + + ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + + __raw_writel(virt_to_phys(buf), udc->regs + S3C_UDC_OTG_DOEPDMA(ep_num)); + __raw_writel((pktcnt<<19) | (length<<0), + udc->regs + S3C_UDC_OTG_DOEPTSIZ(ep_num)); + __raw_writel(DEPCTL_EPENA | DEPCTL_CNAK | ctrl, + udc->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + + DEBUG_OUT_EP("%s: EP%d RX DMA start : DOEPDMA = 0x%x," + "DOEPTSIZ = 0x%x, DOEPCTL = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + __raw_readl(udc->regs + S3C_UDC_OTG_DOEPDMA(ep_num)), + __raw_readl(udc->regs + S3C_UDC_OTG_DOEPTSIZ(ep_num)), + __raw_readl(udc->regs + S3C_UDC_OTG_DOEPCTL(ep_num)), + buf, pktcnt, length); + return 0; +} + +static int setdma_tx(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 *buf, ctrl = 0; + u32 length, pktcnt; + u32 ep_num = ep_index(ep); + struct s3c_udc *udc = ep->dev; + struct device *dev = &udc->dev->dev; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + length = req->req.length - req->req.actual; + + if (ep_num == EP0_CON) + length = min(length, (u32)ep_maxpacket(ep)); + + req->req.actual += length; + + req->req.dma = dma_map_single(dev, buf, + length, DMA_TO_DEVICE); + req->mapped = 1; + + if (length == 0) + pktcnt = 1; + else + pktcnt = (length - 1)/(ep->ep.maxpacket) + 1; + +#ifdef DED_TX_FIFO + /* Write the FIFO number to be used for this endpoint */ + ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + ctrl &= ~DEPCTL_TXFNUM_MASK;; + ctrl |= (ep_num << DEPCTL_TXFNUM_BIT); + __raw_writel(ctrl , udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); +#endif + + __raw_writel(virt_to_phys(buf), udc->regs + S3C_UDC_OTG_DIEPDMA(ep_num)); + __raw_writel((pktcnt<<19)|(length<<0), udc->regs + S3C_UDC_OTG_DIEPTSIZ(ep_num)); + ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + __raw_writel(DEPCTL_EPENA|DEPCTL_CNAK|ctrl, + udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + +#ifndef DED_TX_FIFO + ctrl = __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + ctrl = (ctrl & ~(EP_MASK<<DEPCTL_NEXT_EP_BIT)) | + (ep_num<<DEPCTL_NEXT_EP_BIT); + __raw_writel(ctrl, udc->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); +#endif + + DEBUG_IN_EP("%s:EP%d TX DMA start : DIEPDMA0 = 0x%x," + "DIEPTSIZ0 = 0x%x, DIEPCTL0 = 0x%x\n" + "\tbuf = 0x%p, pktcnt = %d, xfersize = %d\n", + __func__, ep_num, + __raw_readl(udc->regs + S3C_UDC_OTG_DIEPDMA(ep_num)), + __raw_readl(udc->regs + S3C_UDC_OTG_DIEPTSIZ(ep_num)), + __raw_readl(udc->regs + S3C_UDC_OTG_DIEPCTL(ep_num)), + buf, pktcnt, length); + + req->written_bytes = length; + return length; +} + +static void complete_rx(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req = NULL; + u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0; + + if (list_empty(&ep->queue)) { + DEBUG_OUT_EP("%s: RX DMA done : NULL REQ on OUT EP-%d\n", + __func__, ep_num); + return; + } + + req = list_entry(ep->queue.next, struct s3c_request, queue); + + ep_tsr = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPTSIZ(ep_num)); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & 0x7f); + + else + xfer_size = (ep_tsr & 0x7fff); + + __dma_single_cpu_to_dev(req->req.buf, req->req.length, DMA_FROM_DEVICE); + xfer_length = req->req.length - xfer_size; + req->req.actual += min(xfer_length, req->req.length - req->req.actual); + is_short = (xfer_length < ep->ep.maxpacket); + + DEBUG_OUT_EP("%s: RX DMA done : ep = %d, rx bytes = %d/%d, " + "is_short = %d, DOEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, xfer_size); + + if (is_short || req->req.actual == xfer_length) { + if (ep_num == EP0_CON && dev->ep0state == DATA_STATE_RECV) { + DEBUG_OUT_EP(" => Send ZLP\n"); + dev->ep0state = WAIT_FOR_SETUP; + s3c_udc_ep0_zlp(dev); + } else { + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct s3c_request, queue); + DEBUG_OUT_EP("%s: Next Rx request start...\n", + __func__); + setdma_rx(ep, req); + } + } + } +} + +static void complete_tx(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + u32 ep_tsr = 0, xfer_size = 0, xfer_length, is_short = 0; + u32 last; + + if (list_empty(&ep->queue)) { + DEBUG_IN_EP("%s: TX DMA done : NULL REQ on IN EP-%d\n", + __func__, ep_num); + return; + } + + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (dev->ep0state == DATA_STATE_XMIT) { + DEBUG_IN_EP("%s: ep_num = %d, ep0stat == DATA_STATE_XMIT\n", + __func__, ep_num); + + last = write_fifo_ep0(ep, req); + + if (last) + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + ep_tsr = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPTSIZ(ep_num)); + + if (ep_num == EP0_CON) + xfer_size = (ep_tsr & 0x7f); + else + xfer_size = (ep_tsr & 0x7fff); + + req->req.actual = req->req.length - xfer_size; + xfer_length = req->req.length - xfer_size; + req->req.actual += min(xfer_length, req->req.length - req->req.actual); + is_short = (xfer_length < ep->ep.maxpacket); + + DEBUG_IN_EP("%s: TX DMA done : ep = %d, tx bytes = %d/%d, " + "is_short = %d, DIEPTSIZ = 0x%x, remained bytes = %d\n", + __func__, ep_num, req->req.actual, req->req.length, + is_short, ep_tsr, xfer_size); + + if (req->req.actual == req->req.length) { + /* send ZLP when req.zero is set and the last packet is maxpacket */ + if (req->req.zero) { + req->req.zero = 0; + if (req->written_bytes == ep_maxpacket(ep)) { + setdma_tx(ep, req); + return; + } + } + done(ep, req, 0); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, + queue); + DEBUG_IN_EP("%s: Next Tx request start...\n", __func__); + setdma_tx(ep, req); + } + } +} + +static inline void s3c_udc_check_tx_queue(struct s3c_udc *dev, u8 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + + DEBUG_IN_EP("%s: Check queue, ep_num = %d\n", __func__, ep_num); + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + DEBUG_IN_EP("%s: Next Tx request(0x%p) start...\n", + __func__, req); + + if (ep_is_in(ep)) + setdma_tx(ep, req); + else + setdma_rx(ep, req); + } else { + DEBUG_IN_EP("%s: NULL REQ on IN EP-%d\n", __func__, ep_num); + + return; + } +} + +static void process_ep_in_intr(struct s3c_udc *dev) +{ + u32 ep_intr, ep_intr_status; + u8 ep_num = 0; + + ep_intr = __raw_readl(dev->regs + S3C_UDC_OTG_DAINT); + DEBUG_IN_EP("*** %s: EP In interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr &= DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPINT(ep_num)); + DEBUG_IN_EP("\tEP%d-IN : DIEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + __raw_writel(ep_intr_status, dev->regs + S3C_UDC_OTG_DIEPINT(ep_num)); + + if (ep_intr_status & TRANSFER_DONE) { + complete_tx(dev, ep_num); + + if (ep_num == 0) { + if (dev->ep0state == WAIT_FOR_SETUP) + s3c_udc_pre_setup(dev); + + /* continue transfer after + set_clear_halt for DMA mode */ + if (clear_feature_flag == 1) { + s3c_udc_check_tx_queue(dev, + clear_feature_num); + clear_feature_flag = 0; + } + } + } + } + ep_num++; + ep_intr >>= 1; + } +} + +static void process_ep_out_intr(struct s3c_udc *dev) +{ + u32 ep_intr, ep_intr_status; + u8 ep_num = 0; + u32 ep_ctrl = 0; + ep_intr = __raw_readl(dev->regs + S3C_UDC_OTG_DAINT); + DEBUG_OUT_EP("*** %s: EP OUT interrupt : DAINT = 0x%x\n", + __func__, ep_intr); + + ep_intr = (ep_intr >> DAINT_OUT_BIT) & DAINT_MASK; + + while (ep_intr) { + if (ep_intr & 0x1) { + ep_intr_status = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPINT(ep_num)); + DEBUG_OUT_EP("\tEP%d-OUT : DOEPINT = 0x%x\n", + ep_num, ep_intr_status); + + /* Interrupt Clear */ + __raw_writel(ep_intr_status, dev->regs + S3C_UDC_OTG_DOEPINT(ep_num)); + + if (ep_num == 0) { + if (ep_intr_status & + CTRL_OUT_EP_SETUP_PHASE_DONE) { + DEBUG_OUT_EP("\tSETUP" + "packet(transaction)" + "arrived\n"); + s3c_handle_ep0(dev); + } + + if (ep_intr_status & TRANSFER_DONE) { + complete_rx(dev, ep_num); + __raw_writel((3<<29)|(1 << 19)|sizeof(struct usb_ctrlrequest), + dev->regs + S3C_UDC_OTG_DOEPTSIZ(EP0_CON)); + __raw_writel(dev->usb_ctrl_dma, + dev->regs + S3C_UDC_OTG_DOEPDMA(EP0_CON)); + + ep_ctrl = readl(dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + __raw_writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_SNAK, + dev->regs + S3C_UDC_OTG_DOEPCTL(EP0_CON)); + } + + } else { + if (ep_intr_status & TRANSFER_DONE) + complete_rx(dev, ep_num); + } + } + ep_num++; + ep_intr >>= 1; + } +} + +/* + * usb client interrupt handler. + */ +static irqreturn_t s3c_udc_irq(int irq, void *_dev) +{ + struct s3c_udc *dev = _dev; + u32 intr_status; + u32 usb_status, gintmsk; + unsigned long flags; + + spin_lock_irqsave(&dev->lock, flags); + + intr_status = __raw_readl(dev->regs + S3C_UDC_OTG_GINTSTS); + gintmsk = __raw_readl(dev->regs + S3C_UDC_OTG_GINTMSK); + + DEBUG_ISR("\n*** %s : GINTSTS=0x%x(on state %s), GINTMSK :" + "0x%x, DAINT : 0x%x, DAINTMSK : 0x%x\n", + __func__, intr_status, + state_names[dev->ep0state], gintmsk, + __raw_readl(dev->regs + S3C_UDC_OTG_DAINT), __raw_readl(dev->regs + S3C_UDC_OTG_DAINTMSK)); + + if (!intr_status) { + spin_unlock_irqrestore(&dev->lock, flags); + return IRQ_HANDLED; + } + + if (intr_status & INT_ENUMDONE) { + DEBUG_ISR("\tSpeed Detection interrupt\n"); + + __raw_writel(INT_ENUMDONE, dev->regs + S3C_UDC_OTG_GINTSTS); + usb_status = (__raw_readl(dev->regs + S3C_UDC_OTG_DSTS) & 0x6); + + if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) { + DEBUG_ISR("\t\tFull Speed Detection\n"); + set_max_pktsize(dev, USB_SPEED_FULL); + + } else { + DEBUG_ISR("\t\tHigh Speed Detection : 0x%x\n", + usb_status); + set_max_pktsize(dev, USB_SPEED_HIGH); + } + wake_lock(&dev->usbd_wake_lock); + } + + if (intr_status & INT_EARLY_SUSPEND) { + DEBUG_ISR("\tEarly suspend interrupt\n"); + __raw_writel(INT_EARLY_SUSPEND, dev->regs + S3C_UDC_OTG_GINTSTS); + } + + if (intr_status & INT_SUSPEND) { + usb_status = __raw_readl(dev->regs + S3C_UDC_OTG_DSTS); + DEBUG_ISR("\tSuspend interrupt :(DSTS):0x%x\n", usb_status); + __raw_writel(INT_SUSPEND, dev->regs + S3C_UDC_OTG_GINTSTS); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) { + + dev->driver->suspend(&dev->gadget); + } + } + + if (intr_status & INT_RESUME) { + DEBUG_ISR("\tResume interrupt\n"); + __raw_writel(INT_RESUME, dev->regs + S3C_UDC_OTG_GINTSTS); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) { + dev->driver->resume(&dev->gadget); + } + } + + if (intr_status & INT_RESET) { + usb_status = __raw_readl(dev->regs + S3C_UDC_OTG_GOTGCTL); + DEBUG_ISR("\tReset interrupt - (GOTGCTL):0x%x\n", usb_status); + __raw_writel(INT_RESET, dev->regs + S3C_UDC_OTG_GINTSTS); + +#if defined(CONFIG_MACH_M0_CMCC) + pr_info("[YSJ][%s] intr_status=0x%x, " + "gintmsk=0x%x, " + "usb_status=0x%x\n", + __func__, + intr_status, + gintmsk, + usb_status); +#endif + + set_conf_done = 0; + + if ((usb_status & 0xc0000) == (0x3 << 18)) { + if (reset_available) { + DEBUG_ISR("\t\tOTG core got reset (%d)!!\n", + reset_available); + reset_usbd(); + dev->ep0state = WAIT_FOR_SETUP; + reset_available = 0; + s3c_udc_pre_setup(dev); + } else { + reset_available = 1; + } + } else { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + struct usb_composite_dev *cdev; +#endif + reset_available = 1; + DEBUG_ISR("\t\tRESET handling skipped\n"); + wake_lock_timeout(&dev->usbd_wake_lock, HZ * 5); + printk(KERN_DEBUG "usb: reset\n"); + /* report disconnect; the driver is already quiesced */ + if (dev->driver) { +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + cdev = get_gadget_data(&dev->gadget); + if (cdev != NULL) { + cdev->mute_switch = 0; + cdev->force_disconnect = 1; + } +#endif + spin_unlock(&dev->lock); +#if defined(CONFIG_MACH_M0_CMCC) + pr_info("[YSJ][%s] disconnect gadget", + __func__); +#endif + dev->driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) + s3c_udc_cable_disconnect(dev); +#endif + } + } + + if (intr_status & INT_IN_EP) + { + process_ep_in_intr(dev); + } + if (intr_status & INT_OUT_EP) + { + process_ep_out_intr(dev); + } + spin_unlock_irqrestore(&dev->lock, flags); + + return IRQ_HANDLED; +} + +/** Queue one request + * Kickstart transfer if needed + */ +static int s3c_queue(struct usb_ep *_ep, struct usb_request *_req, + gfp_t gfp_flags) +{ + struct s3c_request *req; + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u32 ep_num, gintsts; + + req = container_of(_req, struct s3c_request, req); + if (unlikely(!_req || !_req->complete || + !_req->buf || !list_empty(&req->queue))) { + + DEBUG("%s: bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct s3c_ep, ep); + + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + + DEBUG("%s: bad ep\n", __func__); + return -EINVAL; + } + + ep_num = ep_index(ep); + dev = ep->dev; + if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + + DEBUG("%s: bogus device state %p\n", __func__, dev->driver); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + /* kickstart this i/o queue? */ + DEBUG("\n*** %s: %s-%s req = %p, len = %d, buf = %p" + "Q empty = %d, stopped = %d\n", + __func__, _ep->name, ep_is_in(ep) ? "in" : "out", + _req, _req->length, _req->buf, + list_empty(&ep->queue), ep->stopped); + + if (list_empty(&ep->queue) && !ep->stopped) { + + if (ep_num == 0) { + /* EP0 */ + list_add_tail(&req->queue, &ep->queue); + s3c_ep0_kick(dev, ep); + req = 0; + + } else if (ep_is_in(ep)) { + gintsts = __raw_readl(dev->regs + S3C_UDC_OTG_GINTSTS); + DEBUG_IN_EP("%s: ep_is_in, S3C_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + if (set_conf_done == 1) { + setdma_tx(ep, req); + } else { + done(ep, req, 0); + DEBUG("%s: Not yet Set_configureation," + "ep_num = %d, req = %p\n", + __func__, ep_num, req); + req = 0; + } + + } else { + gintsts = __raw_readl(dev->regs + S3C_UDC_OTG_GINTSTS); + DEBUG_OUT_EP("%s: ep_is_out," + "S3C_UDC_OTG_GINTSTS=0x%x\n", + __func__, gintsts); + + setdma_rx(ep, req); + } + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != 0)) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/****************************************************************/ +/* End Point 0 related functions */ +/****************************************************************/ + +/* return: 0 = still running, 1 = completed, negative = errno */ +static int write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 max; + unsigned count; + int is_last; + + max = ep_maxpacket(ep); + + DEBUG_EP0("%s: max = %d\n", __func__, max); + + count = setdma_tx(ep, req); + + /* last packet is usually short (or a zlp) */ + if (likely(count != max)) + is_last = 1; + else { + if (likely(req->req.length != req->req.actual)) + is_last = 0; + else + is_last = 1; + } + + DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __func__, + ep->ep.name, count, + is_last ? "/L" : "", req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + ep->dev->ep0state = WAIT_FOR_SETUP; + return 1; + } + + return 0; +} + +/** + * udc_set_address - set the USB address for this device + * @address: + * + * Called from control endpoint function + * after it decodes a set address setup packet. + */ +static void udc_set_address(struct s3c_udc *dev, unsigned char address) +{ + u32 ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DCFG); + __raw_writel(address << 4 | ctrl, dev->regs + S3C_UDC_OTG_DCFG); + + s3c_udc_ep0_zlp(dev); + + DEBUG_EP0("%s: USB OTG 2.0 Device address=%d, DCFG=0x%x\n", + __func__, address, __raw_readl(dev->regs + S3C_UDC_OTG_DCFG)); + + dev->usb_address = address; +} + +static inline void s3c_udc_ep0_set_stall(struct s3c_ep *ep) +{ + struct s3c_udc *dev; + u32 ep_ctrl = 0; + + dev = ep->dev; + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + DEBUG_EP0("%s: set ep%d stall, DIEPCTL0 = 0x%x\n", + __func__, ep_index(ep), __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON))); + /* + * The application can only set this bit, and the core clears it, + * when a SETUP token is received for this endpoint + */ + dev->ep0state = WAIT_FOR_SETUP; + + s3c_udc_pre_setup(dev); +} + +static void s3c_ep0_read(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret; + + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + + } else { + DEBUG("%s: ---> BUG\n", __func__); + BUG(); + return; + } + + DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + if (req->req.length == 0) { + /* zlp for Set_configuration, Set_interface, + * or Bulk-Only mass storge reset */ + + dev->ep0state = WAIT_FOR_SETUP; + set_conf_done = 1; + s3c_udc_ep0_zlp(dev); + + DEBUG_EP0("%s: req.length = 0, bRequest = %d\n", + __func__, dev->usb_ctrl->bRequest); + return; + } + + ret = setdma_rx(ep, req); +} + +/* + * DATA_STATE_XMIT + */ +static int s3c_ep0_write(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (!req) { + DEBUG_EP0("%s: NULL REQ\n", __func__); + return 0; + } + + DEBUG_EP0("%s: req = %p, req.length = 0x%x, req.actual = 0x%x\n", + __func__, req, req->req.length, req->req.actual); + + ret = write_fifo_ep0(ep, req); + + if (ret == 1) { + /* Last packet */ + dev->ep0state = WAIT_FOR_SETUP; + DEBUG_EP0("%s: finished, waiting for status\n", __func__); + + } else { + dev->ep0state = DATA_STATE_XMIT; + DEBUG_EP0("%s: not finished\n", __func__); + } + + return 1; +} + +u16 g_status __attribute__((aligned(8))); + +static int s3c_udc_get_status(struct s3c_udc *dev, + struct usb_ctrlrequest *crq) +{ + u8 ep_num = crq->wIndex & 0x7F; + u32 ep_ctrl; + + DEBUG_SETUP("%s: *** USB_REQ_GET_STATUS\n", __func__); + + switch (crq->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_INTERFACE: + g_status = 0; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_INTERFACE," + "g_stauts = %d\n", g_status); + break; + + case USB_RECIP_DEVICE: + g_status = 0x0; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_DEVICE," + "g_stauts = %d\n", g_status); + break; + + case USB_RECIP_ENDPOINT: + if (crq->wLength > 2) { + DEBUG_SETUP("\tGET_STATUS:" + "Not support EP or wLength\n"); + return 1; + } + + g_status = dev->ep[ep_num].stopped; + DEBUG_SETUP("\tGET_STATUS: USB_RECIP_ENDPOINT," + "g_stauts = %d\n", g_status); + + break; + default: + return 1; + } + + __dma_single_cpu_to_dev(&g_status, 2, DMA_TO_DEVICE); + + __raw_writel(virt_to_phys(&g_status), dev->regs + S3C_UDC_OTG_DIEPDMA(EP0_CON)); + __raw_writel((1<<19)|(2<<0), dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + __raw_writel(ep_ctrl|DEPCTL_EPENA|DEPCTL_CNAK, dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + dev->ep0state = WAIT_FOR_SETUP; + + return 0; +} + +static void s3c_udc_ep_set_stall(struct s3c_ep *ep) +{ + struct s3c_udc *dev = ep->dev; + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + + /* set the disable and stall bits */ + if (ep_ctrl & DEPCTL_EPENA) + ep_ctrl |= DEPCTL_EPDIS; + + ep_ctrl |= DEPCTL_STALL; + + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: set stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num))); + + } else { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + + /* set the stall bit */ + ep_ctrl |= DEPCTL_STALL; + + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: set stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num))); + } + + return; +} + +void s3c_udc_ep_clear_stall(struct s3c_ep *ep) +{ + struct s3c_udc *dev = ep->dev; + u8 ep_num; + u32 ep_ctrl = 0; + + ep_num = ep_index(ep); + DEBUG("%s: ep_num = %d, ep_type = %d\n", __func__, ep_num, ep->ep_type); + + if (ep_is_in(ep)) { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + /* + * USB Spec 9.4.5: For endpoints using data toggle, regardless + * of whether an endpoint has the Halt feature set, a + * ClearFeature(ENDPOINT_HALT) request always results in the + * data toggle being reinitialized to DATA0. + */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: cleared stall, DIEPCTL%d = 0x%x\n", + __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num))); + + } else { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + + /* clear stall bit */ + ep_ctrl &= ~DEPCTL_STALL; + + if (ep->bmAttributes == USB_ENDPOINT_XFER_INT + || ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep_ctrl |= DEPCTL_SETD0PID; /* DATA0 */ + } + + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: cleared stall, DOEPCTL%d = 0x%x\n", + __func__, ep_num, __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num))); + } + + return; +} + +static int s3c_udc_set_halt(struct usb_ep *_ep, int value) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u8 ep_num; + + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + + if (unlikely(!_ep || !ep->desc || ep_num == EP0_CON || + ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC)) { + DEBUG("%s: %s bad ep or descriptor\n", __func__, ep->ep.name); + return -EINVAL; + } + + /* Attempt to halt IN ep will fail if any transfer requests + * are still queue */ + if (value && ep_is_in(ep) && !list_empty(&ep->queue)) { + DEBUG("%s: %s queue not empty, req = %p\n", + __func__, ep->ep.name, + list_entry(ep->queue.next, struct s3c_request, queue)); + + return -EAGAIN; + } + + dev = ep->dev; + DEBUG("%s: ep_num = %d, value = %d\n", __func__, ep_num, value); + + spin_lock_irqsave(&dev->lock, flags); + + if (value == 0) { + ep->stopped = 0; + s3c_udc_ep_clear_stall(ep); + } else { + ep->stopped = 1; + s3c_udc_ep_set_stall(ep); + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +void s3c_udc_ep_activate(struct s3c_ep *ep) +{ + struct s3c_udc *dev = ep->dev; + u8 ep_num; + u32 ep_ctrl = 0, daintmsk = 0; + + ep_num = ep_index(ep); + + /* Read DEPCTLn register */ + if (ep_is_in(ep)) { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + daintmsk = 1 << ep_num; + } else { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + daintmsk = (1 << ep_num) << DAINT_OUT_BIT; + } + + DEBUG("%s: EPCTRL%d = 0x%x, ep_is_in = %d\n", + __func__, ep_num, ep_ctrl, ep_is_in(ep)); + + /* If the EP is already active don't change the EP Control + * register. */ + if (!(ep_ctrl & DEPCTL_USBACTEP)) { + ep_ctrl = (ep_ctrl & ~DEPCTL_TYPE_MASK) | + (ep->bmAttributes << DEPCTL_TYPE_BIT); + ep_ctrl = (ep_ctrl & ~DEPCTL_MPS_MASK) | + (ep->ep.maxpacket << DEPCTL_MPS_BIT); + ep_ctrl |= (DEPCTL_SETD0PID | DEPCTL_USBACTEP | DEPCTL_SNAK); + + if (ep_is_in(ep)) { + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num)); + DEBUG("%s: USB Ative EP%d, DIEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, + __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(ep_num))); + } else { + __raw_writel(ep_ctrl, dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num)); + DEBUG("%s: USB Ative EP%d, DOEPCTRL%d = 0x%x\n", + __func__, ep_num, ep_num, + __raw_readl(dev->regs + S3C_UDC_OTG_DOEPCTL(ep_num))); + } + } + + /* Unmask EP Interrtupt */ + __raw_writel(__raw_readl(dev->regs + S3C_UDC_OTG_DAINTMSK)|daintmsk, dev->regs + S3C_UDC_OTG_DAINTMSK); + DEBUG("%s: DAINTMSK = 0x%x\n", __func__, __raw_readl(dev->regs + S3C_UDC_OTG_DAINTMSK)); +} + +static int s3c_udc_clear_feature(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + u8 ep_num; + struct usb_ctrlrequest *usb_ctrl; + + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + usb_ctrl = ep->dev->usb_ctrl; + + DEBUG_SETUP("%s: ep_num = %d, is_in = %d, clear_feature_flag = %d\n", + __func__, ep_num, ep_is_in(ep), clear_feature_flag); + + if (usb_ctrl->wLength != 0) { + DEBUG_SETUP("\tCLEAR_FEATURE:" + "wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + DEBUG_SETUP("\tCLEAR_FEATURE:" + "USB_DEVICE_REMOTE_WAKEUP\n"); + break; + + case USB_DEVICE_TEST_MODE: + DEBUG_SETUP("\tCLEAR_FEATURE:" + "USB_DEVICE_TEST_MODE\n"); + /** @todo Add CLEAR_FEATURE for TEST modes. */ + break; + default: + s3c_udc_ep0_set_stall(ep); + return 1; + } + + s3c_udc_ep0_zlp(ep->dev); + break; + + case USB_RECIP_ENDPOINT: + DEBUG_SETUP("\tCLEAR_FEATURE: USB_RECIP_ENDPOINT," + "wValue = %d\n", usb_ctrl->wValue); + + if (usb_ctrl->wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + s3c_udc_ep0_set_stall(ep); + return 0; + } + + s3c_udc_ep0_zlp(ep->dev); + + s3c_udc_ep_clear_stall(ep); + s3c_udc_ep_activate(ep); + ep->stopped = 0; + + clear_feature_num = ep_num; + clear_feature_flag = 1; + } + break; + } + + return 0; +} + +/* Set into the test mode for Test Mode set_feature request */ +static inline void set_test_mode(struct s3c_udc *dev) +{ + u32 ep_ctrl, dctl; + u8 test_selector = (dev->usb_ctrl->wIndex>>8) & TEST_SELECTOR_MASK; + + if (test_selector > 0 && test_selector < 6) { + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + __raw_writel(1<<19 | 0<<0, dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + __raw_writel(ep_ctrl | DEPCTL_EPENA | DEPCTL_CNAK + | EP0_CON<<DEPCTL_NEXT_EP_BIT, + dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + } + + switch (test_selector) { + case TEST_J_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is" + "TEST J\n"); + + dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_J_MODE, + dev->regs + S3C_UDC_OTG_DCTL); + break; + case TEST_K_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is" + "TEST K\n"); + + dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + __raw_writel((dctl&~(TEST_CONTROL_MASK))|TEST_K_MODE, + dev->regs + S3C_UDC_OTG_DCTL); + break; + case TEST_SE0_NAK_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is" + "TEST SE0 NAK\n"); + + dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_SE0_NAK_MODE, + dev->regs + S3C_UDC_OTG_DCTL); + break; + case TEST_PACKET_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is" + "TEST PACKET\n"); + + __dma_single_cpu_to_dev(test_pkt, TEST_PKT_SIZE, DMA_TO_DEVICE); + __raw_writel(virt_to_phys(test_pkt), dev->regs + S3C_UDC_OTG_DIEPDMA(EP0_CON)); + + ep_ctrl = __raw_readl(dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + __raw_writel(1<<19 | TEST_PKT_SIZE<<0, dev->regs + S3C_UDC_OTG_DIEPTSIZ(EP0_CON)); + __raw_writel(ep_ctrl | DEPCTL_EPENA | DEPCTL_CNAK + | EP0_CON<<DEPCTL_NEXT_EP_BIT, + dev->regs + S3C_UDC_OTG_DIEPCTL(EP0_CON)); + + dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_PACKET_MODE, + dev->regs + S3C_UDC_OTG_DCTL); + break; + case TEST_FORCE_ENABLE_SEL: + /* some delay is necessary like printk() or udelay() */ + printk(KERN_INFO "Test mode selector in set_feature request is" + "TEST FORCE ENABLE\n"); + + dctl = __raw_readl(dev->regs + S3C_UDC_OTG_DCTL); + __raw_writel((dctl & ~(TEST_CONTROL_MASK)) | TEST_FORCE_ENABLE_MODE, + dev->regs + S3C_UDC_OTG_DCTL); + break; + } +} + +static int s3c_udc_set_feature(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + u8 ep_num; + struct usb_ctrlrequest *usb_ctrl; + + ep = container_of(_ep, struct s3c_ep, ep); + ep_num = ep_index(ep); + usb_ctrl = ep->dev->usb_ctrl; + + DEBUG_SETUP("%s: *** USB_REQ_SET_FEATURE," + "ep_num = %d\n", __func__, ep_num); + + if (usb_ctrl->wLength != 0) { + DEBUG_SETUP("\tSET_FEATURE: wLength is not zero.....\n"); + return 1; + } + + switch (usb_ctrl->bRequestType & USB_RECIP_MASK) { + case USB_RECIP_DEVICE: + switch (usb_ctrl->wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + DEBUG_SETUP("\tSET_FEATURE:" + "USB_DEVICE_REMOTE_WAKEUP\n"); + break; + + case USB_DEVICE_TEST_MODE: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_TEST_MODE\n"); + set_test_mode(ep->dev); + break; + + case USB_DEVICE_B_HNP_ENABLE: + DEBUG_SETUP("\tSET_FEATURE: USB_DEVICE_B_HNP_ENABLE\n"); + break; + + case USB_DEVICE_A_HNP_SUPPORT: + /* RH port supports HNP */ + DEBUG_SETUP("\tSET_FEATURE:" + "USB_DEVICE_A_HNP_SUPPORT\n"); + break; + + case USB_DEVICE_A_ALT_HNP_SUPPORT: + /* other RH port does */ + DEBUG_SETUP("\tSET_FEATURE:" + "USB_DEVICE_A_ALT_HNP_SUPPORT\n"); + break; + default: + s3c_udc_ep0_set_stall(ep); + return 1; + } + + s3c_udc_ep0_zlp(ep->dev); + return 0; + + case USB_RECIP_INTERFACE: + DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_INTERFACE\n"); + break; + + case USB_RECIP_ENDPOINT: + DEBUG_SETUP("\tSET_FEATURE: USB_RECIP_ENDPOINT\n"); + if (usb_ctrl->wValue == USB_ENDPOINT_HALT) { + if (ep_num == 0) { + s3c_udc_ep0_set_stall(ep); + return 0; + } + ep->stopped = 1; + s3c_udc_ep_set_stall(ep); + } + + s3c_udc_ep0_zlp(ep->dev); + return 0; + } + + return 1; +} + +/* + * WAIT_FOR_SETUP (OUT_PKT_RDY) + */ +static void s3c_ep0_setup(struct s3c_udc *dev) +{ + struct s3c_ep *ep = &dev->ep[0]; + int i, is_in; + u8 ep_num; + struct usb_ctrlrequest *usb_ctrl = dev->usb_ctrl; + + /* Nuke all previous transfers */ + nuke(ep, -EPROTO); + + DEBUG_SETUP("%s: bRequestType = 0x%x(%s), bRequest = 0x%x" + "\twLength = 0x%x, wValue = 0x%x, wIndex= 0x%x\n", + __func__, usb_ctrl->bRequestType, + (usb_ctrl->bRequestType & USB_DIR_IN) ? "IN" : "OUT", + usb_ctrl->bRequest, usb_ctrl->wLength, usb_ctrl->wValue, + usb_ctrl->wIndex); + + if (usb_ctrl->bRequest == GET_MAX_LUN_REQUEST && usb_ctrl->wLength != 1) { + DEBUG_SETUP("\t%s:GET_MAX_LUN_REQUEST:invalid wLength = %d," + "setup returned\n", __func__, usb_ctrl->wLength); + + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } else if (usb_ctrl->bRequest == + BOT_RESET_REQUEST && usb_ctrl->wLength != 0) { + /* Bulk-Only *mass storge reset of class-specific request */ + DEBUG_SETUP("\t%s:BOT Rest:invalid wLength = %d," + "setup returned\n", + __func__, usb_ctrl->wLength); + + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + return; + } + + /* Set direction of EP0 */ + if (likely(usb_ctrl->bRequestType & USB_DIR_IN)) { + ep->bEndpointAddress |= USB_DIR_IN; + is_in = 1; + + } else { + ep->bEndpointAddress &= ~USB_DIR_IN; + is_in = 0; + } + /* cope with automagic for some standard requests. */ + dev->req_std = (usb_ctrl->bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + + /* Handle some SETUP packets ourselves */ + switch (usb_ctrl->bRequest) { + case USB_REQ_SET_ADDRESS: + DEBUG_SETUP("%s: *** USB_REQ_SET_ADDRESS (%d)\n", + __func__, usb_ctrl->wValue); + + if (usb_ctrl->bRequestType + != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) + break; + + udc_set_address(dev, usb_ctrl->wValue); + return; + + case USB_REQ_SET_CONFIGURATION: + DEBUG_SETUP("============================================\n"); + DEBUG_SETUP("%s: USB_REQ_SET_CONFIGURATION (%d)\n", + __func__, usb_ctrl->wValue); + + if (usb_ctrl->bRequestType == USB_RECIP_DEVICE) { + reset_available = 1; + dev->req_config = 1; + } +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) + s3c_udc_cable_connect(dev); +#endif + break; + + case USB_REQ_GET_DESCRIPTOR: + DEBUG_SETUP("%s: *** USB_REQ_GET_DESCRIPTOR\n", __func__); + break; + + case USB_REQ_SET_INTERFACE: + DEBUG_SETUP("%s: *** USB_REQ_SET_INTERFACE (%d)\n", + __func__, usb_ctrl->wValue); + + if (usb_ctrl->bRequestType == USB_RECIP_INTERFACE) { + reset_available = 1; + dev->req_config = 1; + } + break; + + case USB_REQ_GET_CONFIGURATION: + DEBUG_SETUP("%s: *** USB_REQ_GET_CONFIGURATION\n", __func__); + break; + + case USB_REQ_GET_STATUS: + if (dev->req_std) { + if (!s3c_udc_get_status(dev, usb_ctrl)) + return; + } + break; + + case USB_REQ_CLEAR_FEATURE: + ep_num = usb_ctrl->wIndex & 0x7f; + + if (!s3c_udc_clear_feature(&dev->ep[ep_num].ep)) + return; + break; + case USB_REQ_SET_FEATURE: + ep_num = usb_ctrl->wIndex & 0x7f; + + if (!s3c_udc_set_feature(&dev->ep[ep_num].ep)) + return; + break; + default: + DEBUG_SETUP("%s: *** Default of usb_ctrl->bRequest=0x%x" + "happened.\n", __func__, usb_ctrl->bRequest); + break; + } + + if (likely(dev->driver)) { + /* device-2-host (IN) or no data setup command, + * process immediately */ + DEBUG_SETUP("%s: usb_ctrlrequest will be passed to" + "fsg_setup()\n", __func__); + + spin_unlock(&dev->lock); + i = dev->driver->setup(&dev->gadget, usb_ctrl); + spin_lock(&dev->lock); + + if (i < 0) { + if (dev->req_config) { + DEBUG_SETUP("\tconfig change 0x%02x fail %d?\n", + (u32)usb_ctrl->bRequest, i); + } + + /* setup processing failed, force stall */ + s3c_udc_ep0_set_stall(ep); + dev->ep0state = WAIT_FOR_SETUP; + + DEBUG_SETUP("\tdev->driver->setup failed (%d)," + "bRequest = %d\n", + i, usb_ctrl->bRequest); + } else if (dev->req_pending) { + dev->req_pending = 0; + DEBUG_SETUP("\tdev->req_pending...\n"); + } + + DEBUG_SETUP("\tep0state = %s\n", state_names[dev->ep0state]); + } +} + +/* + * handle ep0 interrupt + */ +static void s3c_handle_ep0(struct s3c_udc *dev) +{ + if (dev->ep0state == WAIT_FOR_SETUP) { + DEBUG_OUT_EP("%s: WAIT_FOR_SETUP\n", __func__); + s3c_ep0_setup(dev); + + } else { + DEBUG_OUT_EP("%s: strange state!!(state = %s)\n", + __func__, state_names[dev->ep0state]); + } +} + +static void s3c_ep0_kick(struct s3c_udc *dev, struct s3c_ep *ep) +{ + DEBUG_EP0("%s: ep_is_in = %d\n", __func__, ep_is_in(ep)); + if (ep_is_in(ep)) { + dev->ep0state = DATA_STATE_XMIT; + s3c_ep0_write(dev); + + } else { + dev->ep0state = DATA_STATE_RECV; + s3c_ep0_read(dev); + } +} diff --git a/drivers/usb/gadget/serial_acm.c b/drivers/usb/gadget/serial_acm.c new file mode 100644 index 0000000..aa4a38d --- /dev/null +++ b/drivers/usb/gadget/serial_acm.c @@ -0,0 +1,171 @@ +/* + * serial_acm.c -- USB modem serial driver + * + * Copyright 2008 (C) Samsung Electronics + * + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/fcntl.h> +#include <linux/device.h> +#include <linux/miscdevice.h> + +#include <linux/uaccess.h> +#include <linux/io.h> + +#include <linux/interrupt.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/poll.h> +#include <linux/usb/cdc.h> + +static int acm_notify(void *dev, u16 state); + + +static wait_queue_head_t modem_wait_q; + +static unsigned int read_state; +static unsigned int control_line_state; + +static void *acm_data; + +static int modem_open(struct inode *inode, struct file *file) +{ + read_state = 0; + + return 0; +} + +static int modem_close(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t modem_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(modem_wait_q, read_state); + if (ret) + return ret; + + if (copy_to_user(buf, &control_line_state, sizeof(u32))) + return -EFAULT; + + read_state = 0; + + return sizeof(u32); +} + +static unsigned int modem_poll(struct file *file, poll_table *wait) +{ + int ret; + poll_wait(file, &modem_wait_q, wait); + + ret = (read_state ? (POLLIN | POLLRDNORM) : 0); + + return ret; +} + +void notify_control_line_state(u32 value) +{ + control_line_state = value; + + read_state = 1; + + wake_up_interruptible(&modem_wait_q); +} +EXPORT_SYMBOL(notify_control_line_state); + + +#define GS_CDC_NOTIFY_SERIAL_STATE _IOW('S', 1, int) +#define GS_IOC_NOTIFY_DTR_TEST _IOW('S', 3, int) + +static long +modem_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int ret = 0; + printk(KERN_INFO "modem_ioctl: cmd=0x%x, arg=%lu\n", cmd, arg); + + /* handle ioctls */ + switch (cmd) { + case GS_CDC_NOTIFY_SERIAL_STATE: + ret = acm_notify(acm_data, __constant_cpu_to_le16(arg)); + return ret; + + case GS_IOC_NOTIFY_DTR_TEST: + { + printk(KERN_ALERT"DUN : DTR %d\n", (int)arg); + notify_control_line_state((int)arg); + break; + } + + default: + printk(KERN_INFO "modem_ioctl: Unknown ioctl cmd(0x%x).\n", + cmd); + return -ENOIOCTLCMD; + } + return 0; +} + + +static const struct file_operations modem_fops = { + .owner = THIS_MODULE, + .open = modem_open, + .release = modem_close, + .read = modem_read, + .poll = modem_poll, + .llseek = no_llseek, + .unlocked_ioctl = modem_ioctl, +}; + +static struct miscdevice modem_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = "dun", + .fops = &modem_fops, +}; + +int modem_register(void *data) +{ + if (data == NULL) { + printk(KERN_INFO "DUN register failed. data is null.\n"); + return -1; + } + + acm_data = data; + + init_waitqueue_head(&modem_wait_q); + + printk(KERN_INFO "DUN is registerd\n"); + + return 0; +} +EXPORT_SYMBOL(modem_register); + +static int modem_misc_register(void) +{ + int ret; + ret = misc_register(&modem_device); + if (ret) { + printk(KERN_ERR "DUN register is failed, ret = %d\n", ret); + return ret; + } + return ret; +} + +void modem_unregister(void) +{ + acm_data = NULL; + + printk(KERN_INFO "DUN is unregisterd\n"); +} +EXPORT_SYMBOL(modem_unregister); diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 1fa4f70..4dd598c 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -260,6 +260,7 @@ static struct fsg_lun *fsg_lun_from_dev(struct device *dev) /* Big enough to hold our biggest descriptor */ #define EP0_BUFSIZE 256 +#define EP0_BUFSIZE_SS 512 #define DELAYED_STATUS (EP0_BUFSIZE + 999) /* An impossibly large value */ /* Number of buffers we will use. 2 is enough for double-buffering */ @@ -429,6 +430,118 @@ static struct usb_descriptor_header *fsg_fs_function[] = { NULL, }; +static struct usb_endpoint_descriptor +fsg_ss_bulk_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor fsg_ss_bulk_in_comp_desc = { + .bLength = sizeof(fsg_ss_bulk_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /*.bMaxBurst = DYNAMIC, */ +}; + +static struct usb_endpoint_descriptor +fsg_ss_bulk_out_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_bulk_out_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), +}; + +static struct usb_ss_ep_comp_descriptor fsg_ss_bulk_out_comp_desc = { + .bLength = sizeof(fsg_ss_bulk_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + /*.bMaxBurst = DYNAMIC, */ +}; + +#ifndef FSG_NO_INTR_EP + +static struct usb_endpoint_descriptor +fsg_ss_intr_in_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + /* bEndpointAddress copied from fs_intr_in_desc during fsg_bind() */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16(2), + .bInterval = 9, /* 2**(9-1) = 256 uframes -> 32 ms */ +}; + +static struct usb_ss_ep_comp_descriptor fsg_ss_intr_in_comp_desc = { + .bLength = sizeof(fsg_ss_bulk_in_comp_desc), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + + .wBytesPerInterval = cpu_to_le16(1024), +}; + +#ifndef FSG_NO_OTG +# define FSG_SS_FUNCTION_PRE_EP_ENTRIES 4 +#else +# define FSG_SS_FUNCTION_PRE_EP_ENTRIES 3 +#endif + +#endif + +static __maybe_unused struct usb_ext_cap_descriptor fsg_ext_cap_desc = { + .bLength = USB_DT_USB_EXT_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_CAP_TYPE_EXT, + + .bmAttributes = cpu_to_le32(USB_LPM_SUPPORT), +}; + +static __maybe_unused struct usb_ss_cap_descriptor fsg_ss_cap_desc = { + .bLength = USB_DT_USB_SS_CAP_SIZE, + .bDescriptorType = USB_DT_DEVICE_CAPABILITY, + .bDevCapabilityType = USB_SS_CAP_TYPE, + + /* .bmAttributes = LTM is not supported yet */ + + .wSpeedSupported = cpu_to_le16(USB_LOW_SPEED_OPERATION + | USB_FULL_SPEED_OPERATION + | USB_HIGH_SPEED_OPERATION + | USB_5GBPS_OPERATION), + .bFunctionalitySupport = USB_LOW_SPEED_OPERATION, + .bU1devExitLat = USB_DEFAULT_U1_DEV_EXIT_LAT, + .bU2DevExitLat = USB_DEFAULT_U2_DEV_EXIT_LAT, +}; + +static __maybe_unused struct usb_bos_descriptor fsg_bos_desc = { + .bLength = USB_DT_BOS_SIZE, + .bDescriptorType = USB_DT_BOS, + + .wTotalLength = USB_DT_BOS_SIZE + + USB_DT_USB_EXT_CAP_SIZE + + USB_DT_USB_SS_CAP_SIZE, + + .bNumDeviceCaps = 2, +}; + +static struct usb_descriptor_header *fsg_ss_function[] = { +#ifndef FSG_NO_OTG + (struct usb_descriptor_header *) &fsg_otg_desc, +#endif + (struct usb_descriptor_header *) &fsg_intf_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_in_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_in_comp_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_out_desc, + (struct usb_descriptor_header *) &fsg_ss_bulk_out_comp_desc, +#ifndef FSG_NO_INTR_EP + (struct usb_descriptor_header *) &fsg_ss_intr_in_desc, + (struct usb_descriptor_header *) &fsg_ss_intr_in_comp_desc, +#endif + NULL, +}; /* * USB 2.0 devices need to expose both high speed and full speed @@ -763,10 +876,16 @@ static ssize_t fsg_store_file(struct device *dev, struct device_attribute *attr, struct rw_semaphore *filesem = dev_get_drvdata(dev); int rc = 0; + +#if !defined(CONFIG_USB_ANDROID_MASS_STORAGE) && !defined(CONFIG_USB_G_ANDROID) + /* disabled in android because we need to allow closing the backing file + * if the media was removed + */ if (curlun->prevent_medium_removal && fsg_lun_is_open(curlun)) { LDBG(curlun, "eject attempt prevented\n"); return -EBUSY; /* "Door is locked" */ } +#endif /* Remove a trailing newline */ if (count > 0 && buf[count-1] == '\n') diff --git a/drivers/usb/gadget/u_ether.c b/drivers/usb/gadget/u_ether.c index 2ac1d21..b0a56ee 100644 --- a/drivers/usb/gadget/u_ether.c +++ b/drivers/usb/gadget/u_ether.c @@ -97,16 +97,17 @@ struct eth_dev { static unsigned qmult = 5; module_param(qmult, uint, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(qmult, "queue length multiplier at high speed"); +MODULE_PARM_DESC(qmult, "queue length multiplier at high/super speed"); #else /* full speed (low speed doesn't do bulk) */ #define qmult 1 #endif -/* for dual-speed hardware, use deeper queues at highspeed */ +/* for dual-speed hardware, use deeper queues at high/super speed */ static inline int qlen(struct usb_gadget *gadget) { - if (gadget_is_dualspeed(gadget) && gadget->speed == USB_SPEED_HIGH) + if (gadget_is_dualspeed(gadget) && (gadget->speed == USB_SPEED_HIGH || + gadget->speed == USB_SPEED_SUPER)) return qmult * DEFAULT_QLEN; else return DEFAULT_QLEN; @@ -242,8 +243,11 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) if (dev->port_usb->is_fixed) size = max_t(size_t, size, dev->port_usb->fixed_out_len); - +#ifdef CONFIG_USB_S3C_OTGD + skb = alloc_skb(size + NET_IP_ALIGN + 6, gfp_flags); +#else skb = alloc_skb(size + NET_IP_ALIGN, gfp_flags); +#endif if (skb == NULL) { DBG(dev, "no rx skb\n"); goto enomem; @@ -253,8 +257,11 @@ rx_submit(struct eth_dev *dev, struct usb_request *req, gfp_t gfp_flags) * but on at least one, checksumming fails otherwise. Note: * RNDIS headers involve variable numbers of LE32 values. */ +#ifdef CONFIG_USB_S3C_OTGD + skb_reserve(skb, NET_IP_ALIGN + 6); +#else skb_reserve(skb, NET_IP_ALIGN); - +#endif req->buf = skb->data; req->length = size; req->complete = rx_complete; @@ -482,6 +489,11 @@ static void tx_complete(struct usb_ep *ep, struct usb_request *req) spin_lock(&dev->req_lock); list_add(&req->list, &dev->tx_reqs); spin_unlock(&dev->req_lock); + +#ifdef CONFIG_USB_S3C_OTGD + if (req->buf != skb->data) + kfree(req->buf); +#endif dev_kfree_skb_any(skb); atomic_dec(&dev->tx_qlen); @@ -577,7 +589,19 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, length = skb->len; } +#ifdef CONFIG_USB_S3C_OTGD + /* for double word align */ + req->buf = kmalloc(skb->len, GFP_ATOMIC | GFP_DMA); + + if (!req->buf) { + req->buf = skb->data; + ERROR(dev, "fail to kmalloc[req->buf = skb->data]\n"); + } else { + memcpy((void *)req->buf, (void *)skb->data, skb->len); + } +#else req->buf = skb->data; +#endif req->context = skb; req->complete = tx_complete; @@ -598,9 +622,10 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, req->length = length; - /* throttle highspeed IRQ rate back slightly */ + /* throttle high/super speed IRQ rate back slightly */ if (gadget_is_dualspeed(dev->gadget)) - req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH) + req->no_interrupt = (dev->gadget->speed == USB_SPEED_HIGH || + dev->gadget->speed == USB_SPEED_SUPER) ? ((atomic_read(&dev->tx_qlen) % qmult) != 0) : 0; @@ -615,9 +640,14 @@ static netdev_tx_t eth_start_xmit(struct sk_buff *skb, } if (retval) { +#ifdef CONFIG_USB_S3C_OTGD + if (req->buf != skb->data) + kfree(req->buf); +#endif dev_kfree_skb_any(skb); drop: dev->net->stats.tx_dropped++; + spin_lock_irqsave(&dev->req_lock, flags); if (list_empty(&dev->tx_reqs)) netif_start_queue(net); @@ -631,8 +661,7 @@ drop: static void eth_start(struct eth_dev *dev, gfp_t gfp_flags) { - DBG(dev, "%s\n", __func__); - + printk(KERN_DEBUG "usb: %s ++\n", __func__); /* fill the rx queue */ rx_fill(dev, gfp_flags); @@ -646,7 +675,7 @@ static int eth_open(struct net_device *net) struct eth_dev *dev = netdev_priv(net); struct gether *link; - DBG(dev, "%s\n", __func__); + printk(KERN_DEBUG "usb: %s ++\n", __func__); if (netif_carrier_ok(dev->net)) eth_start(dev, GFP_KERNEL); @@ -765,6 +794,26 @@ static struct device_type gadget_type = { */ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) { + return gether_setup_name(g, ethaddr, "usb"); +} + +/** + * gether_setup_name - initialize one ethernet-over-usb link + * @g: gadget to associated with these links + * @ethaddr: NULL, or a buffer in which the ethernet address of the + * host side of the link is recorded + * @netname: name for network device (for example, "usb") + * Context: may sleep + * + * This sets up the single network link that may be exported by a + * gadget driver using this framework. The link layer addresses are + * set up using module parameters. + * + * Returns negative errno, or zero on success + */ +int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname) +{ struct eth_dev *dev; struct net_device *net; int status; @@ -787,17 +836,23 @@ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]) /* network device setup */ dev->net = net; - strcpy(net->name, "usb%d"); + snprintf(net->name, sizeof(net->name), "%s%%d", netname); if (get_ether_addr(dev_addr, net->dev_addr)) dev_warn(&g->dev, "using random %s ethernet address\n", "self"); + +#ifdef CONFIG_USB_ANDROID_SAMSUNG_COMPOSITE + memcpy(dev->host_mac, ethaddr, ETH_ALEN); + printk(KERN_DEBUG "usb: set unique host mac\n"); +#else if (get_ether_addr(host_addr, dev->host_mac)) dev_warn(&g->dev, "using random %s ethernet address\n", "host"); if (ethaddr) memcpy(ethaddr, dev->host_mac, ETH_ALEN); +#endif net->netdev_ops = ð_netdev_ops; @@ -867,6 +922,7 @@ struct net_device *gether_connect(struct gether *link) struct eth_dev *dev = the_dev; int result = 0; + printk(KERN_DEBUG "usb: %s ++\n", __func__); if (!dev) return ERR_PTR(-EINVAL); @@ -886,12 +942,15 @@ struct net_device *gether_connect(struct gether *link) goto fail1; } + printk(KERN_DEBUG "usb: %s enable ep in/out\n", __func__); if (result == 0) result = alloc_requests(dev, link, qlen(dev->gadget)); if (result == 0) { dev->zlp = link->is_zlp_ok; DBG(dev, "qlen %d\n", qlen(dev->gadget)); + printk(KERN_DEBUG "usb: %s qlen=%d\n", + __func__, qlen(dev->gadget)); dev->header_len = link->header_len; dev->unwrap = link->unwrap; @@ -909,9 +968,12 @@ struct net_device *gether_connect(struct gether *link) } spin_unlock(&dev->lock); + printk(KERN_DEBUG "usb: %s netif_carrier_on\n", __func__); netif_carrier_on(dev->net); - if (netif_running(dev->net)) + if (netif_running(dev->net)) { + printk(KERN_DEBUG "usb: %s eth_start\n", __func__); eth_start(dev, GFP_ATOMIC); + } /* on error, disable any endpoints */ } else { @@ -943,7 +1005,6 @@ void gether_disconnect(struct gether *link) struct eth_dev *dev = link->ioport; struct usb_request *req; - WARN_ON(!dev); if (!dev) return; diff --git a/drivers/usb/gadget/u_ether.h b/drivers/usb/gadget/u_ether.h index b56e1e7..64b65f9 100644 --- a/drivers/usb/gadget/u_ether.h +++ b/drivers/usb/gadget/u_ether.h @@ -86,6 +86,9 @@ struct gether { /* netdev setup/teardown as directed by the gadget driver */ int gether_setup(struct usb_gadget *g, u8 ethaddr[ETH_ALEN]); void gether_cleanup(void); +/* variant of gether_setup that allows customizing network device name */ +int gether_setup_name(struct usb_gadget *g, u8 ethaddr[ETH_ALEN], + const char *netname); /* connect/disconnect is handled by individual functions */ struct net_device *gether_connect(struct gether *); @@ -112,12 +115,14 @@ int eem_bind_config(struct usb_configuration *c); #ifdef USB_ETH_RNDIS -int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]); +int rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer); #else static inline int -rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN]) +rndis_bind_config(struct usb_configuration *c, u8 ethaddr[ETH_ALEN], + u32 vendorID, const char *manufacturer) { return 0; } diff --git a/drivers/usb/gadget/u_ncm.c b/drivers/usb/gadget/u_ncm.c new file mode 100644 index 0000000..33e9f50 --- /dev/null +++ b/drivers/usb/gadget/u_ncm.c @@ -0,0 +1,229 @@ +/* + * File Name : u_ncm.c + * + * ncm utilities for composite USB gadgets. + * This utilitie can support to connect head unit for mirror link + * + * Copyright (C) 2011 Samsung Electronics + * Author: SoonYong, Cho <soonyong.cho@samsung.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include "f_ncm.c" + +/* Support dynamic tethering mode. + * if ncm_connect is true, device is received vendor specific request + * from head unit. + */ +static bool ncm_connect; + +/* terminal version using vendor specific request */ +u16 terminal_mode_version; +u16 terminal_mode_vendor_id; + +struct ncm_function_config { + u8 ethaddr[ETH_ALEN]; +}; + +static int ncm_function_init(struct android_usb_function *f, + struct usb_composite_dev *cdev) +{ + f->config = kzalloc(sizeof(struct ncm_function_config), GFP_KERNEL); + return 0; +} + +static void ncm_function_cleanup(struct android_usb_function *f) +{ + kfree(f->config); + f->config = NULL; +} + +static int ncm_function_bind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + int ret; + int i; + char *src; + struct ncm_function_config *ncm = f->config; + + if (!ncm) { + pr_err("%s: ncm_pdata\n", __func__); + return -1; + } + + ncm = f->config; + if (!f->config) + return -ENOMEM; + + for (i = 0; i < ETH_ALEN; i++) + ncm->ethaddr[i] = 0; + /* create a fake MAC address from our serial number. + * first byte is 0x02 to signify locally administered. + */ + ncm->ethaddr[0] = 0x02; + src = serial_string; + for (i = 0; (i < 256) && *src; i++) { + /* XOR the USB serial across the remaining bytes */ + ncm->ethaddr[i % (ETH_ALEN - 1) + 1] ^= *src++; + } + + printk(KERN_DEBUG "usb: %s MAC:%02X:%02X:%02X:%02X:%02X:%02X\n", + __func__, ncm->ethaddr[0], ncm->ethaddr[1], + ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4], + ncm->ethaddr[5]); + + + printk(KERN_DEBUG "usb: %s before MAC:%02X:%02X:%02X:%02X:%02X:%02X\n", + __func__, ncm->ethaddr[0], ncm->ethaddr[1], + ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4], + ncm->ethaddr[5]); + /* we have to use trick. + * rndis name will be used for ethernet interface name. + */ + ret = gether_setup_name(c->cdev->gadget, ncm->ethaddr, "rndis"); + printk(KERN_DEBUG "usb: %s after MAC:%02X:%02X:%02X:%02X:%02X:%02X\n", + __func__, ncm->ethaddr[0], ncm->ethaddr[1], + ncm->ethaddr[2], ncm->ethaddr[3], ncm->ethaddr[4], + ncm->ethaddr[5]); + if (ret) { + pr_err("%s: gether_setup failed\n", __func__); + return ret; + } + + return ncm_bind_config(c, ncm->ethaddr); +} + +static void ncm_function_unbind_config(struct android_usb_function *f, + struct usb_configuration *c) +{ + gether_cleanup(); +} + +static struct android_usb_function ncm_function = { + .name = "ncm", + .init = ncm_function_init, + .cleanup = ncm_function_cleanup, + .bind_config = ncm_function_bind_config, + .unbind_config = ncm_function_unbind_config, +}; + +bool is_ncm_ready(char *name) +{ + /* Enable ncm function */ + if (!strcmp(name, "rndis") || !strcmp(name, "ncm")) { + if (ncm_connect) { + printk(KERN_DEBUG "usb: %s ncm ready (%s)\n", + __func__, name); + return true; + } + } + return false; +} + +void set_ncm_device_descriptor(struct usb_device_descriptor *desc) +{ + desc->idProduct = 0x685d; + desc->bDeviceClass = USB_CLASS_COMM; + printk(KERN_DEBUG "usb: %s idProduct=0x%x, DeviceClass=0x%x\n", + __func__, desc->idProduct, desc->bDeviceClass); + +} + +void set_ncm_ready(bool ready) +{ + if (ready != ncm_connect) + printk(KERN_DEBUG "usb: %s old status=%d, new status=%d\n", + __func__, ncm_connect, ready); + ncm_connect = ready; + if (ready == false) { + terminal_mode_version = 0; + terminal_mode_vendor_id = 0; + } +} + +static ssize_t terminal_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + ret = sprintf(buf, "major %x minor %x vendor %x\n", + terminal_mode_version & 0xff, + (terminal_mode_version >> 8 & 0xff), + terminal_mode_vendor_id); + printk(KERN_DEBUG "usb: %s terminal_mode %s\n", __func__, buf); + return ret; +} + +static ssize_t terminal_version_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + int value; + sscanf(buf, "%x", &value); + terminal_mode_version = (u16)value; + printk(KERN_DEBUG "usb: %s buf=%s\n", __func__, buf); + /* always set ncm ready */ + set_ncm_ready(true); + return size; +} + +static DEVICE_ATTR(terminal_version, S_IRUGO | S_IWUSR, + terminal_version_show, terminal_version_store); + +static int create_terminal_attribute(struct device **pdev) +{ + int err; + if (IS_ERR(*pdev)) { + printk(KERN_DEBUG "usb: %s error pdev(%p)\n", + __func__, *pdev); + return PTR_ERR(*pdev); + } + + err = device_create_file(*pdev, &dev_attr_terminal_version); + if (err) { + printk(KERN_DEBUG "usb: %s failed to create attr\n", + __func__); + return err; + } + return 0; +} + +static int terminal_ctrl_request(struct usb_composite_dev *cdev, + const struct usb_ctrlrequest *ctrl) +{ + int value = -EOPNOTSUPP; + u16 w_index = le16_to_cpu(ctrl->wIndex); + u16 w_value = le16_to_cpu(ctrl->wValue); + + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) { + /* Handle Terminal mode request */ + if (ctrl->bRequest == 0xf0) { + terminal_mode_version = w_value; + terminal_mode_vendor_id = w_index; + set_ncm_ready(true); + printk(KERN_DEBUG "usb: %s ver=0x%x vendor_id=0x%x\n", + __func__, terminal_mode_version, + terminal_mode_vendor_id); + value = 0; + } + } + + /* respond ZLP */ + if (value >= 0) { + int rc; + cdev->req->zero = 0; + cdev->req->length = value; + rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC); + if (rc < 0) + printk(KERN_DEBUG "usb: %s failed usb_ep_queue\n", + __func__); + } + return value; +} diff --git a/drivers/usb/gadget/u_serial.c b/drivers/usb/gadget/u_serial.c index 40f7716..6698ebb14 100644 --- a/drivers/usb/gadget/u_serial.c +++ b/drivers/usb/gadget/u_serial.c @@ -122,7 +122,7 @@ struct gs_port { }; /* increase N_PORTS if you need more */ -#define N_PORTS 4 +#define N_PORTS 8 static struct portmaster { struct mutex lock; /* protect open/close */ struct gs_port *port; @@ -380,7 +380,8 @@ __acquires(&port->port_lock) req->length = len; list_del(&req->list); - req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0); + req->zero = (gs_buf_data_avail(&port->port_write_buf) == 0) + && (req->length % in->maxpacket == 0); pr_vdebug(PREFIX "%d: tx len=%d, 0x%02x 0x%02x 0x%02x ...\n", port->port_num, len, *((u8 *)req->buf), @@ -1028,7 +1029,7 @@ static const struct tty_operations gs_tty_ops = { static struct tty_driver *gs_tty_driver; -static int __init +static int gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) { struct gs_port *port; @@ -1074,7 +1075,7 @@ gs_port_alloc(unsigned port_num, struct usb_cdc_line_coding *coding) * * Returns negative errno or zero. */ -int __init gserial_setup(struct usb_gadget *g, unsigned count) +int gserial_setup(struct usb_gadget *g, unsigned count) { unsigned i; struct usb_cdc_line_coding coding; diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index ab085f1..912cac7 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -19,7 +19,7 @@ config USB_C67X00_HCD config USB_XHCI_HCD tristate "xHCI HCD (USB 3.0) support (EXPERIMENTAL)" - depends on USB && PCI && EXPERIMENTAL + depends on USB && USB_ARCH_HAS_XHCI && EXPERIMENTAL ---help--- The eXtensible Host Controller Interface (xHCI) is standard for USB 3.0 "SuperSpeed" host controller hardware. @@ -37,6 +37,12 @@ config USB_XHCI_HCD_DEBUGGING If unsure, say N. +config USB_XHCI_EXYNOS + boolean "EXYNOS XHCI support" + depends on USB_XHCI_HCD && ARCH_EXYNOS + help + Enable support for the EXYNOS SOC's on-chip XHCI controller. + config USB_EHCI_HCD tristate "EHCI HCD (USB 2.0) support" depends on USB && USB_ARCH_HAS_EHCI @@ -189,11 +195,25 @@ config USB_EHCI_SH If you use the PCI EHCI controller, this option is not necessary. config USB_EHCI_S5P - boolean "S5P EHCI support" - depends on USB_EHCI_HCD && PLAT_S5P - help + boolean "S5P EHCI support" + depends on USB_EHCI_HCD && PLAT_S5P + help Enable support for the S5P SOC's on-chip EHCI controller. +config USB_S5P_HSIC0 + boolean "S5P HSIC0 support" + depends on USB_EHCI_HCD && PLAT_S5P && USB_EHCI_S5P + default y + help + Enable support for the S5P SOC's on-chip HSIC PHY. + +config USB_S5P_HSIC1 + boolean "S5P HSIC1 support" + depends on USB_EHCI_HCD && PLAT_S5P && USB_EHCI_S5P + default y + help + Enable support for the S5P SOC's on-chip HSIC PHY. + config USB_W90X900_EHCI bool "W90X900(W90P910) EHCI support" depends on USB_EHCI_HCD && ARCH_W90X900 @@ -371,6 +391,12 @@ config USB_OHCI_SH Enables support for the on-chip OHCI controller on the SuperH. If you use the PCI OHCI controller, this option is not necessary. +config USB_OHCI_S5P + boolean "S5P OHCI support" + depends on USB_OHCI_HCD && PLAT_S5P + help + Enable support for the S5P SOC's on-chip OHCI controller. + config USB_CNS3XXX_OHCI bool "Cavium CNS3XXX OHCI Module" depends on USB_OHCI_HCD && ARCH_CNS3XXX @@ -578,3 +604,18 @@ config USB_OCTEON_OHCI config USB_OCTEON2_COMMON bool default y if USB_OCTEON_EHCI || USB_OCTEON_OHCI + +config USB_S3C_OTG_HOST + tristate "S3C USB OTG Host support" + depends on USB && (PLAT_S3C64XX || PLAT_S5P) + help + Samsung's S3C64XX processors include high speed USB OTG2.0 + controller. It has 15 configurable endpoints, as well as + endpoint zero (for control transfers). + + This driver support only OTG Host role. If you want to use + OTG Device role, select USB Gadget support and S3C OTG Device. + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c_otg_hcd" and force all + drivers to also be dynamically linked. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 624a362..5720733 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -11,10 +11,13 @@ fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o -xhci-hcd-y := xhci.o xhci-mem.o xhci-pci.o +xhci-hcd-y := xhci.o xhci-mem.o xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o +xhci-hcd-$(CONFIG_PCI) += xhci-pci.o +xhci-hcd-$(CONFIG_USB_XHCI_EXYNOS) += xhci-exynos.o obj-$(CONFIG_USB_WHCI_HCD) += whci/ +obj-$(CONFIG_USB_S3C_OTG_HOST) += shost/ obj-$(CONFIG_PCI) += pci-quirks.o diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index b27ceab..ac0377f 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -885,12 +885,32 @@ static irqreturn_t ehci_irq (struct usb_hcd *hcd) ehci->reset_done[i] == 0)) continue; +#if defined(CONFIG_LINK_DEVICE_HSIC) + /* FIXME: Debugging + * HSIC with XMM6262 don't use the remote wakeup, + * but someitme EHCI got the PCD and we have a doubt + * whether remote wakeup or not. So, we find the irq + * from XMM6262, print out the port status. + */ + printk(KERN_DEBUG "mif: %s port %d," + "pcd status 0x%x, ehci port status 0x%x, " + "suspend_ports %lu, reset_done %lu\n", + __func__, i + 1, status, pstatus, + ehci->suspended_ports, ehci->reset_done[i]); +#endif + /* start 20 msec resume signaling from this port, * and make khubd collect PORT_STAT_C_SUSPEND to * stop that signaling. Use 5 ms extra for safety, * like usb_port_resume() does. */ +#ifdef CONFIG_LINK_DEVICE_HSIC + /* ensure suspend bit clear by adding 5 msec delay. */ + ehci->reset_done[i] = jiffies + msecs_to_jiffies(30); +#else ehci->reset_done[i] = jiffies + msecs_to_jiffies(25); +#endif + ehci_dbg (ehci, "port %d remote wakeup\n", i + 1); mod_timer(&hcd->rh_timer, ehci->reset_done[i]); } diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index f5d7fed..fb0394b 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -734,6 +734,340 @@ ehci_hub_descriptor ( } /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_HOST_COMPLIANT_TEST +struct api_context { + struct completion done; + int status; +}; + +static int single_step_get_descriptor( struct usb_hcd *hcd, u8 port) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct list_head qtd_list; + struct list_head test_list; + struct usb_device *udev; + struct ehci_qtd *qtd; + struct urb *urb; + struct usb_ctrlrequest setup_packet; + char data_buffer[USB_DT_DEVICE_SIZE]; + int retval = 0; + + ehci_info (ehci, "Testing SINGLE_STEP_GET_DEV_DESC\n"); + + udev = hcd->self.root_hub; + if (udev == NULL) { + ehci_err (ehci, "EHSET: root_hub pointer is NULL\n"); + retval = -EPIPE; + goto error; + } + + if (udev->children[port] != NULL) { + udev = udev->children[port]; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + + if (!urb) { + retval = -ENOMEM; + goto error; + } + + setup_packet.bRequestType = USB_DIR_IN; + setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR; + setup_packet.wValue = (USB_DT_DEVICE << 8); + setup_packet.wIndex = 0; + setup_packet.wLength = USB_DT_DEVICE_SIZE; + + INIT_LIST_HEAD (&qtd_list); + INIT_LIST_HEAD (&test_list); + + urb->dev = udev; + urb->pipe = usb_rcvctrlpipe(udev, 0); + urb->hcpriv= udev->ep0.hcpriv; + urb->setup_packet = (char *)&setup_packet; + urb->transfer_buffer = data_buffer; + urb->transfer_flags = URB_HCD_DRIVER_TEST; + urb->ep = udev->ep_in[usb_pipeendpoint(urb->pipe)]; + if (!urb->ep) { + retval = -ENOENT; + goto error; + } + + urb->setup_dma = dma_map_single( + hcd->self.controller, + urb->setup_packet, + sizeof(struct usb_ctrlrequest), + DMA_TO_DEVICE); + + urb->transfer_dma = dma_map_single ( + hcd->self.controller, + urb->transfer_buffer, + urb->transfer_buffer_length, + DMA_TO_DEVICE); + + if (!urb->setup_dma || !urb->transfer_dma) { + ehci_err (ehci, "dma_map_single Failed" + "\n"); + retval = -EBUSY; + goto error; + } + + if (!qh_urb_transaction (ehci, urb, &qtd_list, + GFP_ATOMIC)) { + ehci_err (ehci, "qh_urb_transaction " + "Failed\n"); + retval = -EBUSY; + goto error; + } + + qtd = container_of (qtd_list.next, + struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + list_add (&qtd->qtd_list, &test_list); + qtd = container_of (qtd_list.next, + struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + ehci_qtd_free (ehci, qtd); + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(15000)); + + ehci_info (ehci, "Sending SETUP PHASE\n"); + if (submit_async (ehci, urb, + &test_list, GFP_ATOMIC)) { + ehci_err (ehci, "Failed to queue up " + "qtds\n"); + retval = -EBUSY; + goto error; + } +error: + return retval; +} + +static int single_step_set_feature( struct usb_hcd *hcd, u8 port) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + struct usb_device *udev; + struct list_head qtd_list; + struct list_head setup_list; + struct list_head data_list; + struct ehci_qtd *qtd; + struct urb *urb; + struct usb_ctrlrequest setup_packet; + char data_buffer[USB_DT_DEVICE_SIZE]; + int retval = 0; + + ehci_info (ehci, "Testing SINGLE_STEP_SET_FEATURE\n"); + + udev = hcd->self.root_hub; + if (udev == NULL) { + ehci_err (ehci, "EHSET: root_hub pointer is NULL\n"); + retval = -EPIPE; + goto error; + } + + if (udev->children[port] != NULL) { + udev = udev->children[port]; + } + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + retval = -ENOMEM; + goto error; + } + setup_packet.bRequestType = USB_DIR_IN; + setup_packet.bRequest = USB_REQ_GET_DESCRIPTOR; + setup_packet.wValue = (USB_DT_DEVICE << 8); + setup_packet.wIndex = 0; + setup_packet.wLength = USB_DT_DEVICE_SIZE; + + INIT_LIST_HEAD (&qtd_list); + INIT_LIST_HEAD (&setup_list); + INIT_LIST_HEAD (&data_list); + + urb->transfer_buffer_length = USB_DT_DEVICE_SIZE; + urb->dev = udev; + urb->pipe = usb_rcvctrlpipe(udev, 0); + urb->hcpriv = udev->ep0.hcpriv; + urb->setup_packet = (char *)&setup_packet; + urb->transfer_buffer = data_buffer; + urb->transfer_flags = URB_HCD_DRIVER_TEST; + urb->ep = udev->ep_in[usb_pipeendpoint(urb->pipe)]; + if (!urb->ep) { + retval = -ENOENT; + goto error; + } + + urb->setup_dma = dma_map_single( hcd->self.controller, + urb->setup_packet, + sizeof (struct usb_ctrlrequest), + DMA_TO_DEVICE); + + urb->transfer_dma = dma_map_single (hcd->self.controller, + urb->transfer_buffer, + sizeof (struct usb_ctrlrequest), + DMA_TO_DEVICE); + + if (!urb->setup_dma || !urb->transfer_dma) { + ehci_err (ehci, "dma_map_single Failed\n"); + retval = -EBUSY; + goto error; + } + + if (!qh_urb_transaction (ehci, urb, &qtd_list, GFP_ATOMIC)) { + ehci_err (ehci, "qh_urb_transaction Failed\n"); + retval = -EBUSY; + goto error; + } + + qtd = container_of (qtd_list.next, struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + list_add (&qtd->qtd_list, &setup_list); + qtd = container_of (qtd_list.next, struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + list_add (&qtd->qtd_list, &data_list); + qtd = container_of (qtd_list.next, struct ehci_qtd, qtd_list); + list_del_init (&qtd->qtd_list); + ehci_qtd_free (ehci, qtd); + + ehci_info (ehci, "Sending SETUP PHASE\n"); + if (submit_async (ehci, urb, &setup_list, GFP_ATOMIC)) { + ehci_err (ehci, "Failed to queue up qtds\n"); + retval = -EBUSY; + goto error; + } + + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(15000)); + urb->status = 0; + urb->actual_length = 0; + + ehci_info (ehci, "Sending DATA PHASE\n"); + if (submit_async (ehci, urb, &data_list, GFP_ATOMIC)) + { + ehci_err (ehci, "Failed to queue up qtds\n"); + retval = -EBUSY; + goto error; + } +error: + return retval; +} + +static int ehci_port_test(struct usb_hcd *hcd, u8 selector, u8 port, + unsigned long flags) +{ + struct ehci_hcd *ehci = hcd_to_ehci (hcd); + u32 temp; + u32 __iomem *status_reg = &ehci->regs->port_status[port]; + int retval = 0; + + temp = ehci_readl(ehci, status_reg); + + ehci_info (ehci, "status_reg BEFORE write regs = 0x%x\n",temp); + switch (selector) { + case USB_PORT_TEST_J: + spin_unlock_irqrestore (&ehci->lock, flags); + ehci_info (ehci, "Testing J State\n"); + ehci_quiesce(ehci); + if(hcd->driver->bus_suspend) + hcd->driver->bus_suspend(hcd); + ehci_halt(ehci); + spin_lock_irqsave (&ehci->lock, flags); + ehci_writel(ehci, temp|PORT_TEST_J, status_reg); + break; + + case USB_PORT_TEST_K: + spin_unlock_irqrestore (&ehci->lock, flags); + ehci_info (ehci, "Testing K State\n"); + ehci_quiesce(ehci); + if(hcd->driver->bus_suspend) + hcd->driver->bus_suspend(hcd); + ehci_halt(ehci); + spin_lock_irqsave (&ehci->lock, flags); + ehci_writel(ehci, temp|PORT_TEST_K, status_reg); + break; + + case USB_PORT_TEST_SE0_NAK: + spin_unlock_irqrestore (&ehci->lock, flags); + ehci_info (ehci, "Testing SE0_NAK\n"); + ehci_quiesce(ehci); + if(hcd->driver->bus_suspend) + hcd->driver->bus_suspend(hcd); + ehci_halt(ehci); + spin_lock_irqsave (&ehci->lock, flags); + ehci_writel(ehci, temp|PORT_TEST_SE0_NAK, status_reg); + break; + + case USB_PORT_TEST_PACKET: + ehci_info (ehci, "Sending Test Packets\n"); + ehci_writel(ehci, temp|PORT_TEST_PKT, status_reg); + break; + + case USB_PORT_TEST_FORCE_ENABLE: + ehci_info (ehci, "Testing FORCE_ENABLE\n"); + ehci_writel(ehci, temp|PORT_TEST_FORCE, status_reg); + break; + + case (EHSET_HS_HOST_PORT_SUSPEND_RESUME & 0xFF): + spin_unlock_irqrestore (&ehci->lock, flags); + ehci_info (ehci, "Testing SUSPEND RESUME\n"); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(15000)); + ehci_info (ehci, "Suspend Root Hub\n"); + temp = ehci_readl(ehci, status_reg); + ehci_info(ehci, "[Before -> Suspend Status Reg : 0x%x\n",temp); + if(hcd->driver->bus_suspend) + hcd->driver->bus_suspend(hcd); + temp = ehci_readl(ehci, status_reg); + ehci_info(ehci, "[After -> Suspend Status Reg : 0x%x\n",temp); + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(msecs_to_jiffies(15000)); + ehci_info (ehci, "Resume Root Hub\n"); + if(hcd->driver->bus_resume) + hcd->driver->bus_resume(hcd); + + spin_lock_irqsave (&ehci->lock, flags); + break; + + case (EHSET_SINGLE_STEP_GET_DEV_DESC&0xFF): + spin_unlock_irqrestore (&ehci->lock, flags); + retval = single_step_get_descriptor(hcd, port); + if (retval < 0) { + ehci_err (ehci, "EHSET: get descriptor test fail\n"); + spin_lock_irqsave (&ehci->lock, flags); + goto error; + } + spin_lock_irqsave (&ehci->lock, flags); + break; + + case (EHSET_SINGLE_STEP_SET_FEATURE & 0xFF): + spin_unlock_irqrestore (&ehci->lock, flags); + retval = single_step_set_feature(hcd, port); + if (retval < 0) { + ehci_err (ehci, "EHSET: set feature test fail\n"); + spin_lock_irqsave (&ehci->lock, flags); + goto error; + } + spin_lock_irqsave (&ehci->lock, flags); + break; + default: + ehci_err (ehci, "EHSET: Unknown test %x\n", + (selector)); + goto error; + } + + temp = ehci_readl(ehci, status_reg); + ehci_info (ehci, "status_reg AFTER write regs = 0x%x\n",temp); + ehci_err(ehci, "EHSET test done. retval = 0x%x\n", retval); + return retval; + +error: + ehci_err (ehci, "EHSET test error. retval = 0x%x\n",retval); + return retval; + +} + +#endif /* CONFIG_HOST_COMPLIANT_TEST */ static int ehci_hub_control ( struct usb_hcd *hcd, @@ -1116,9 +1450,20 @@ static int ehci_hub_control ( * or else system reboot). See EHCI 2.3.9 and 4.14 for info * about the EHCI-specific stuff. */ +#ifdef CONFIG_HOST_COMPLIANT_TEST case USB_PORT_FEAT_TEST: - if (!selector || selector > 5) + ehci_info (ehci, "TEST MODE !!!!!!!! selector == 0x%x \n",selector); + + ehci_info (ehci, "running EHCI test %x on port %x\n", + selector, wIndex); + + retval = ehci_port_test(hcd, selector & 0xFF, wIndex, flags); + if (retval < 0) { + ehci_info (ehci, "EHCI test Fail!!\n"); goto error; + } + break; +#endif ehci_quiesce(ehci); /* Put all enabled ports into suspend */ diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 0917e3a..e4cc485 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -289,6 +289,12 @@ __acquires(ehci->lock) urb->actual_length, urb->transfer_buffer_length); #endif +#ifdef CONFIG_HOST_COMPLIANT_TEST + if (likely (urb->transfer_flags == URB_HCD_DRIVER_TEST)) { + ehci_dbg(ehci, "USB_TEST : transfer_flags = URB_HCD_DRIVER_TEST so... return!\n"); + return; + } +#endif /* complete() can reenter this HCD */ usb_hcd_unlink_urb_from_ep(ehci_to_hcd(ehci), urb); spin_unlock (&ehci->lock); @@ -387,10 +393,6 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) QTD_CERR(token) == 0 && ++qh->xacterrs < QH_XACTERR_MAX && !urb->unlinked) { - ehci_dbg(ehci, - "detected XactErr len %zu/%zu retry %d\n", - qtd->length - QTD_LENGTH(token), qtd->length, qh->xacterrs); - /* reset the token in the qtd and the * qh overlay (which still contains * the qtd) so that we pick up from @@ -406,6 +408,11 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) token); goto retry_xacterr; } + if (qh->xacterrs >= QH_XACTERR_MAX) + ehci_dbg(ehci, + "detected XactErr len %zu/%zu retry %d\n", + qtd->length - QTD_LENGTH(token), + qtd->length, qh->xacterrs); stopped = 1; /* magic dummy for some short reads; qh won't advance. @@ -995,6 +1002,12 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) head->qh_next.qh = qh; head->hw->hw_next = dma; + /* + * flush qh descriptor into memory immediately, + * see comments in qh_append_tds. + * */ + ehci_sync_mem(); + qh_get(qh); qh->xacterrs = 0; qh->qh_state = QH_STATE_LINKED; @@ -1082,6 +1095,18 @@ static struct ehci_qh *qh_append_tds ( wmb (); dummy->hw_token = token; + /* + * Writing to dma coherent buffer on ARM may + * be delayed to reach memory, so HC may not see + * hw_token of dummy qtd in time, which can cause + * the qtd transaction to be executed very late, + * and degrade performance a lot. ehci_sync_mem + * is added to flush 'token' immediatelly into + * memory, so that ehci can execute the transaction + * ASAP. + * */ + ehci_sync_mem(); + urb->hcpriv = qh_get (qh); } } diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c index 491a209..8cb7ae2 100644 --- a/drivers/usb/host/ehci-s5p.c +++ b/drivers/usb/host/ehci-s5p.c @@ -14,17 +14,324 @@ #include <linux/clk.h> #include <linux/platform_device.h> -#include <mach/regs-pmu.h> +#include <linux/pm_runtime.h> + #include <plat/cpu.h> #include <plat/ehci.h> #include <plat/usb-phy.h> +#include <mach/regs-pmu.h> +#include <mach/regs-usb-host.h> +#include <mach/board_rev.h> + struct s5p_ehci_hcd { struct device *dev; struct usb_hcd *hcd; struct clk *clk; + int power_on; }; +#ifdef CONFIG_USB_EXYNOS_SWITCH +int s5p_ehci_port_power_off(struct platform_device *pdev) +{ + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + (void) ehci_hub_control(hcd, + ClearPortFeature, + USB_PORT_FEAT_POWER, + 1, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); + return 0; +} +EXPORT_SYMBOL_GPL(s5p_ehci_port_power_off); + +int s5p_ehci_port_power_on(struct platform_device *pdev) +{ + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + (void) ehci_hub_control(hcd, + SetPortFeature, + USB_PORT_FEAT_POWER, + 1, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); + return 0; +} +EXPORT_SYMBOL_GPL(s5p_ehci_port_power_on); +#endif + +static int s5p_ehci_configurate(struct usb_hcd *hcd) +{ + int delay_count = 0; + + /* This is for waiting phy before ehci configuration */ + do { + if (readl(hcd->regs)) + break; + udelay(1); + ++delay_count; + } while (delay_count < 200); + if (delay_count) + dev_info(hcd->self.controller, "phy delay count = %d\n", + delay_count); + + /* DMA burst Enable, set utmi suspend_on_n */ + writel(readl(INSNREG00(hcd->regs)) | ENA_DMA_INCR | OHCI_SUSP_LGCY, + INSNREG00(hcd->regs)); + return 0; +} + +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) ||\ + defined(CONFIG_CDMA_MODEM_MDM6600) +#define CP_PORT 2 /* HSIC0 in S5PC210 */ +#define RETRY_CNT_LIMIT 30 /* Max 300ms wait for cp resume*/ + +int s5p_ehci_port_control(struct platform_device *pdev, int port, int enable) +{ + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + (void) ehci_hub_control(hcd, + enable ? SetPortFeature : ClearPortFeature, + USB_PORT_FEAT_POWER, + port, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); + return 0; +} + +static void s5p_wait_for_cp_resume(struct platform_device *pdev, + struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + u32 __iomem *portsc ; + u32 val32, retry_cnt = 0; + + portsc = &ehci->regs->port_status[CP_PORT-1]; + + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_ON); + + do { + msleep(10); + val32 = ehci_readl(ehci, portsc); + } while (++retry_cnt < RETRY_CNT_LIMIT && !(val32 & PORT_CONNECT)); + + if (retry_cnt >= RETRY_CNT_LIMIT) + dev_info(&pdev->dev, "%s: retry_cnt = %d, portsc = 0x%x\n", + __func__, retry_cnt, val32); + +#if defined(CONFIG_UMTS_MODEM_XMM6262) + if (pdata->get_cp_active_state && !pdata->get_cp_active_state()) { + s5p_ehci_port_control(pdev, CP_PORT, 0); + pr_err("mif: force port%d off by cp reset\n", CP_PORT); + } +#endif +} +#endif + +static void s5p_ehci_phy_init(struct platform_device *pdev) +{ + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + u32 delay_count = 0; + + if (pdata->phy_init) { + pdata->phy_init(pdev, S5P_USB_PHY_HOST); + + while (!readl(hcd->regs) && delay_count < 200) { + delay_count++; + udelay(1); + } + if (delay_count) + dev_info(&pdev->dev, "waiting time = %d\n", + delay_count); + s5p_ehci_configurate(hcd); + dev_dbg(&pdev->dev, "%s : 0x%x\n", __func__, + readl(INSNREG00(hcd->regs))); + } + +} + +#ifdef CONFIG_PM +static int s5p_ehci_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + unsigned long flags; + int rc = 0; + + if (time_before(jiffies, ehci->next_statechange)) + msleep(10); + + /* Root hub was already suspended. Disable irq emission and + * mark HW unaccessible, bail out if RH has been resumed. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + * + * This is still racy as hcd->state is manipulated outside of + * any locks =P But that will be a different fix. + */ + + spin_lock_irqsave(&ehci->lock, flags); + if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) { + spin_unlock_irqrestore(&ehci->lock, flags); + return -EINVAL; + } + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + (void)ehci_readl(ehci, &ehci->regs->intr_enable); + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&ehci->lock, flags); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif + + clk_disable(s5p_ehci->clk); + + return rc; +} + +static int s5p_ehci_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + clk_enable(s5p_ehci->clk); + pm_runtime_resume(&pdev->dev); + + s5p_ehci_phy_init(pdev); + + /* if EHCI was off, hcd was removed */ + if (!s5p_ehci->power_on) { + dev_info(dev, "Nothing to do for the device (power off)\n"); + return 0; + } + + if (time_before(jiffies, ehci->next_statechange)) + msleep(10); + + /* Mark hardware accessible again as we are out of D3 state by now */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) { + int mask = INTR_MASK; + + if (!hcd->self.root_hub->do_remote_wakeup) + mask &= ~STS_PCD; + ehci_writel(ehci, mask, &ehci->regs->intr_enable); + ehci_readl(ehci, &ehci->regs->intr_enable); + return 0; + } + + ehci_dbg(ehci, "lost power, restarting\n"); + usb_root_hub_lost_power(hcd->self.root_hub); + + (void) ehci_halt(ehci); + (void) ehci_reset(ehci); + + /* emptying the schedule aborts any urbs */ + spin_lock_irq(&ehci->lock); + if (ehci->reclaim) + end_unlink_async(ehci); + ehci_work(ehci); + spin_unlock_irq(&ehci->lock); + + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + + /* here we "know" root ports should always stay powered */ + ehci_port_power(ehci, 1); + + hcd->state = HC_STATE_SUSPENDED; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + s5p_wait_for_cp_resume(pdev, hcd); +#endif + return 0; +} + +#else +#define s5p_ehci_suspend NULL +#define s5p_ehci_resume NULL +#endif + +#ifdef CONFIG_USB_SUSPEND +static int s5p_ehci_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + + if (pdata && pdata->phy_suspend) + pdata->phy_suspend(pdev, S5P_USB_PHY_HOST); + + return 0; +} + +static int s5p_ehci_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int rc = 0; + + if (dev->power.is_suspended) + return 0; + + /* platform device isn't suspended */ + if (pdata && pdata->phy_resume) + rc = pdata->phy_resume(pdev, S5P_USB_PHY_HOST); + + if (rc) { + s5p_ehci_configurate(hcd); + + /* emptying the schedule aborts any urbs */ + spin_lock_irq(&ehci->lock); + if (ehci->reclaim) + end_unlink_async(ehci); + ehci_work(ehci); + spin_unlock_irq(&ehci->lock); + + usb_root_hub_lost_power(hcd->self.root_hub); + + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); + (void)ehci_readl(ehci, &ehci->regs->intr_enable); + + /* here we "know" root ports should always stay powered */ + ehci_port_power(ehci, 1); + + hcd->state = HC_STATE_SUSPENDED; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + s5p_wait_for_cp_resume(pdev, hcd); +#endif + } + + return 0; +} +#else +#define s5p_ehci_runtime_suspend NULL +#define s5p_ehci_runtime_resume NULL +#endif + static const struct hc_driver s5p_ehci_hc_driver = { .description = hcd_name, .product_desc = "S5P EHCI Host Controller", @@ -56,6 +363,140 @@ static const struct hc_driver s5p_ehci_hc_driver = { .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, }; +static ssize_t show_ehci_power(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + + return sprintf(buf, "EHCI Power %s\n", (s5p_ehci->power_on) ? "on" : "off"); +} + +static ssize_t store_ehci_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + int power_on; + int irq; + int retval; + + if (sscanf(buf, "%d", &power_on) != 1) + return -EINVAL; + + device_lock(dev); + + if (!power_on && s5p_ehci->power_on) { + printk(KERN_DEBUG "%s: EHCI turns off\n", __func__); + pm_runtime_forbid(dev); + s5p_ehci->power_on = 0; + usb_remove_hcd(hcd); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); + +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif + } else if (power_on) { + printk(KERN_DEBUG "%s: EHCI turns on\n", __func__); + if (s5p_ehci->power_on) { + pm_runtime_forbid(dev); + usb_remove_hcd(hcd); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif + } else + s5p_ehci_phy_init(pdev); + + irq = platform_get_irq(pdev, 0); + retval = usb_add_hcd(hcd, irq, + IRQF_DISABLED | IRQF_SHARED); + if (retval < 0) { + dev_err(dev, "Power On Fail\n"); + goto exit; + } + + s5p_ehci->power_on = 1; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /* Sometimes XMM6262 send remote wakeup when hub enter suspend + * So, set the hub waiting 500ms autosuspend delay*/ + if (hcd->self.root_hub) + pm_runtime_set_autosuspend_delay( + &hcd->self.root_hub->dev, + msecs_to_jiffies(500)); + + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_ON); +#endif + pm_runtime_allow(dev); + } +exit: + device_unlock(dev); + return count; +} +static DEVICE_ATTR(ehci_power, 0664, show_ehci_power, store_ehci_power); + +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) +static ssize_t store_port_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + + int power_on, port; + int err; + + err = sscanf(buf, "%d %d", &power_on, &port); + if (err < 2 || port < 0 || port > 3 || power_on < 0 || power_on > 1) { + pr_err("port power fail: port_power 1 2(port 2 enable 1)\n"); + return count; + } + + pr_debug("%s: Port:%d power: %d\n", __func__, port, power_on); + device_lock(dev); + s5p_ehci_port_control(pdev, port, power_on); + + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states && port == CP_PORT) + pdata->noti_host_states(pdev, power_on ? S5P_HOST_ON : + S5P_HOST_OFF); + device_unlock(dev); + return count; +} +static DEVICE_ATTR(port_power, 0664, NULL, store_port_power); +#endif + +static inline int create_ehci_sys_file(struct ehci_hcd *ehci) +{ +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + BUG_ON(device_create_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_port_power)); +#endif + return device_create_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_ehci_power); +} + +static inline void remove_ehci_sys_file(struct ehci_hcd *ehci) +{ + device_remove_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_ehci_power); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + device_remove_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_port_power); +#endif +} + static int __devinit s5p_ehci_probe(struct platform_device *pdev) { struct s5p_ehci_platdata *pdata; @@ -75,7 +516,6 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) s5p_ehci = kzalloc(sizeof(struct s5p_ehci_hcd), GFP_KERNEL); if (!s5p_ehci) return -ENOMEM; - s5p_ehci->dev = &pdev->dev; hcd = usb_create_hcd(&s5p_ehci_hc_driver, &pdev->dev, @@ -122,8 +562,9 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) goto fail; } - if (pdata->phy_init) - pdata->phy_init(pdev, S5P_USB_PHY_HOST); + platform_set_drvdata(pdev, s5p_ehci); + + s5p_ehci_phy_init(pdev); ehci = hcd_to_ehci(hcd); ehci->caps = hcd->regs; @@ -142,7 +583,20 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) goto fail; } - platform_set_drvdata(pdev, s5p_ehci); + create_ehci_sys_file(ehci); + s5p_ehci->power_on = 1; + +#ifdef CONFIG_USB_SUSPEND + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); +#endif +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /* for cp enumeration */ + pm_runtime_forbid(&pdev->dev); + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_ON); +#endif return 0; @@ -165,8 +619,18 @@ static int __devexit s5p_ehci_remove(struct platform_device *pdev) struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); struct usb_hcd *hcd = s5p_ehci->hcd; +#ifdef CONFIG_USB_SUSPEND + pm_runtime_disable(&pdev->dev); +#endif + s5p_ehci->power_on = 0; + remove_ehci_sys_file(hcd_to_ehci(hcd)); usb_remove_hcd(hcd); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif if (pdata && pdata->phy_exit) pdata->phy_exit(pdev, S5P_USB_PHY_HOST); @@ -190,6 +654,18 @@ static void s5p_ehci_shutdown(struct platform_device *pdev) hcd->driver->shutdown(hcd); } +static const struct dev_pm_ops s5p_ehci_pm_ops = { + .suspend = s5p_ehci_suspend, + .resume = s5p_ehci_resume, +#ifdef CONFIG_HIBERNATION + .freeze = s5p_ehci_suspend, + .thaw = s5p_ehci_resume, + .restore = s5p_ehci_resume, +#endif + .runtime_suspend = s5p_ehci_runtime_suspend, + .runtime_resume = s5p_ehci_runtime_resume, +}; + static struct platform_driver s5p_ehci_driver = { .probe = s5p_ehci_probe, .remove = __devexit_p(s5p_ehci_remove), @@ -197,6 +673,7 @@ static struct platform_driver s5p_ehci_driver = { .driver = { .name = "s5p-ehci", .owner = THIS_MODULE, + .pm = &s5p_ehci_pm_ops, } }; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 3ffb27f..f3db2b3 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -591,6 +591,24 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc) #define ehci_port_speed(ehci, portsc) USB_PORT_STAT_HIGH_SPEED #endif +#ifdef CONFIG_HOST_COMPLIANT_TEST +static struct list_head * qh_urb_transaction ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *head, + gfp_t flags); + +static int submit_async ( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *qtd_list, + gfp_t mem_flags); + +static inline void ehci_qtd_free ( + struct ehci_hcd *ehci, + struct ehci_qtd *qtd); +#endif + /*-------------------------------------------------------------------------*/ #ifdef CONFIG_PPC_83xx @@ -737,6 +755,23 @@ static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) #endif +/* + * Writing to dma coherent memory on ARM may be delayed via L2 + * writing buffer, so introduce the helper which can flush L2 writing + * buffer into memory immediately, especially used to flush ehci + * descriptor to memory. + * */ +#ifdef CONFIG_ARM_DMA_MEM_BUFFERABLE +static inline void ehci_sync_mem(void) +{ + mb(); +} +#else +static inline void ehci_sync_mem(void) +{ +} +#endif + /*-------------------------------------------------------------------------*/ #ifdef CONFIG_PCI diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index f9cf3f0..f428987 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -899,8 +899,11 @@ static void ohci_stop (struct usb_hcd *hcd) if (quirk_nec(ohci)) flush_work_sync(&ohci->nec_work); - ohci_usb_reset (ohci); ohci_writel (ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + ohci_usb_reset (ohci); + + // flush those writes + (void) ohci_readl (ohci, &ohci->regs->intrdisable); free_irq(hcd->irq, hcd); hcd->irq = -1; @@ -1008,6 +1011,11 @@ MODULE_LICENSE ("GPL"); #define PLATFORM_DRIVER ohci_hcd_s3c2410_driver #endif +#ifdef CONFIG_USB_OHCI_S5P +#include "ohci-s5p.c" +#define PLATFORM_DRIVER ohci_hcd_s5p_driver +#endif + #ifdef CONFIG_USB_OHCI_HCD_OMAP1 #include "ohci-omap.c" #define OMAP1_PLATFORM_DRIVER ohci_hcd_omap_driver diff --git a/drivers/usb/host/ohci-s5p.c b/drivers/usb/host/ohci-s5p.c new file mode 100644 index 0000000..fce68ef --- /dev/null +++ b/drivers/usb/host/ohci-s5p.c @@ -0,0 +1,485 @@ +/* ohci-s5p.c - Driver for USB HOST on Samsung S5P platform device + * + * Bus Glue for SAMSUNG S5P USB HOST OHCI Controller + * + * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> + * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> + * (C) Copyright 2002 Hewlett-Packard Company + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * Author: Jingoo Han <jg1.han@samsung.com> + * + * Based on "ohci-au1xxx.c" by Matt Porter <mporter@kernel.crashing.org> + * Modified for SAMSUNG s5p OHCI by Jingoo Han <jg1.han@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/platform_device.h> +#include <linux/clk.h> + +#include <plat/ehci.h> +#include <plat/usb-phy.h> + +#include <mach/board_rev.h> + +struct s5p_ohci_hcd { + struct device *dev; + struct usb_hcd *hcd; + struct clk *clk; + int power_on; +}; + +#ifdef CONFIG_USB_EXYNOS_SWITCH +int s5p_ohci_port_power_off(struct platform_device *pdev) +{ + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + (void)ohci_readl(ohci, &ohci->regs->intrdisable); + + ohci_writel (ohci, RH_HS_LPS, &ohci->regs->roothub.status); + + return 0; +} +EXPORT_SYMBOL_GPL(s5p_ohci_port_power_off); + +int s5p_ohci_port_power_on(struct platform_device *pdev) +{ + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + + ohci_writel (ohci, RH_HS_LPSC, &ohci->regs->roothub.status); + + return 0; +} +EXPORT_SYMBOL_GPL(s5p_ohci_port_power_on); +#endif + +#ifdef CONFIG_PM +static int ohci_hcd_s5p_drv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + unsigned long flags; + int rc = 0; + + /* Root hub was already suspended. Disable irq emission and + * mark HW unaccessible, bail out if RH has been resumed. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + * + * This is still racy as hcd->state is manipulated outside of + * any locks =P But that will be a different fix. + */ + spin_lock_irqsave(&ohci->lock, flags); + if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) { + spin_unlock_irqrestore(&ohci->lock, flags); + return -EINVAL; + } + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&ohci->lock, flags); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); + + clk_disable(s5p_ohci->clk); + + return rc; +} + +static int ohci_hcd_s5p_drv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + int rc = 0; + + clk_enable(s5p_ohci->clk); + pm_runtime_resume(&pdev->dev); + + if (pdata && pdata->phy_init) + pdata->phy_init(pdev, S5P_USB_PHY_HOST); + + /* if OHCI was off, hcd was removed */ + if (!s5p_ohci->power_on) { + dev_info(dev, "Nothing to do for the device (power off)\n"); + return 0; + } + + /* Mark hardware accessible again as we are out of D3 state by now */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + ohci_finish_controller_resume(hcd); + + return rc; +} + +#else +#define ohci_hcd_s5p_drv_suspend NULL +#define ohci_hcd_s5p_drv_resume NULL +#endif + +#ifdef CONFIG_USB_SUSPEND +static int ohci_hcd_s5p_drv_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + unsigned long flags; + int rc = 0; + + /* Root hub was already suspended. Disable irq emission and + * mark HW unaccessible, bail out if RH has been resumed. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + * + * This is still racy as hcd->state is manipulated outside of + * any locks =P But that will be a different fix. + */ + spin_lock_irqsave(&ohci->lock, flags); + if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) { + spin_unlock_irqrestore(&ohci->lock, flags); + err("Not ready %s", hcd->self.bus_name); + return rc; + } + + ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrdisable); + (void)ohci_readl(ohci, &ohci->regs->intrdisable); + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&ohci->lock, flags); + + if (pdata->phy_suspend) + pdata->phy_suspend(pdev, S5P_USB_PHY_HOST); + + return rc; +} + +static int ohci_hcd_s5p_drv_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + + if (dev->power.is_suspended) + return 0; + + if (pdata->phy_resume) + pdata->phy_resume(pdev, S5P_USB_PHY_HOST); + /* Mark hardware accessible again as we are out of D3 state by now */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + + ohci_finish_controller_resume(hcd); + + return 0; +} +#else +#define ohci_hcd_s5p_drv_runtime_suspend NULL +#define ohci_hcd_s5p_drv_runtime_resume NULL +#endif + +static int ohci_s5p_start(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int ret; + + ohci_dbg(ohci, "ohci_s5p_start, ohci:%p", ohci); + + ret = ohci_init(ohci); + if (ret < 0) + return ret; + + ret = ohci_run(ohci); + if (ret < 0) { + err("can't start %s", hcd->self.bus_name); + ohci_stop(hcd); + return ret; + } + + return 0; +} + +static const struct hc_driver ohci_s5p_hc_driver = { + .description = hcd_name, + .product_desc = "s5p OHCI", + .hcd_priv_size = sizeof(struct ohci_hcd), + + .irq = ohci_irq, + .flags = HCD_MEMORY|HCD_USB11, + + .start = ohci_s5p_start, + .stop = ohci_stop, + .shutdown = ohci_shutdown, + + .get_frame_number = ohci_get_frame, + + .urb_enqueue = ohci_urb_enqueue, + .urb_dequeue = ohci_urb_dequeue, + .endpoint_disable = ohci_endpoint_disable, + + .hub_status_data = ohci_hub_status_data, + .hub_control = ohci_hub_control, +#ifdef CONFIG_PM + .bus_suspend = ohci_bus_suspend, + .bus_resume = ohci_bus_resume, +#endif + .start_port_reset = ohci_start_port_reset, +}; + +static ssize_t show_ohci_power(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + + return sprintf(buf, "OHCI Power %s\n", (s5p_ohci->power_on) ? "on" : "off"); +} + +static ssize_t store_ohci_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + int power_on; + int irq; + int retval; + + if (sscanf(buf, "%d", &power_on) != 1) + return -EINVAL; + + device_lock(dev); + if (!power_on && s5p_ohci->power_on) { + printk(KERN_DEBUG "%s: OHCI turns off\n", __func__); + pm_runtime_forbid(dev); + s5p_ohci->power_on = 0; + usb_remove_hcd(hcd); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); + } else if (power_on) { + printk(KERN_DEBUG "%s: OHCI turns on\n", __func__); + if (s5p_ohci->power_on) { + pm_runtime_forbid(dev); + usb_remove_hcd(hcd); + } else { + if (pdata && pdata->phy_init) + pdata->phy_init(pdev, S5P_USB_PHY_HOST); + } + + irq = platform_get_irq(pdev, 0); + retval = usb_add_hcd(hcd, irq, + IRQF_DISABLED | IRQF_SHARED); + if (retval < 0) { + dev_err(dev, "Power On Fail\n"); + goto exit; + } + + s5p_ohci->power_on = 1; + pm_runtime_allow(dev); + } +exit: + device_unlock(dev); + return count; +} +static DEVICE_ATTR(ohci_power, 0664, show_ohci_power, store_ohci_power); + +static inline int create_ohci_sys_file(struct ohci_hcd *ohci) +{ + return device_create_file(ohci_to_hcd(ohci)->self.controller, + &dev_attr_ohci_power); +} + +static inline void remove_ohci_sys_file(struct ohci_hcd *ohci) +{ + device_remove_file(ohci_to_hcd(ohci)->self.controller, + &dev_attr_ohci_power); +} +static int __devinit ohci_hcd_s5p_drv_probe(struct platform_device *pdev) +{ + struct s5p_ohci_platdata *pdata; + struct s5p_ohci_hcd *s5p_ohci; + struct usb_hcd *hcd = NULL; + struct ohci_hcd *ohci; + struct resource *res; + int irq; + int err; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform data defined\n"); + return -EINVAL; + } + + s5p_ohci = kzalloc(sizeof(struct s5p_ohci_hcd), GFP_KERNEL); + if (!s5p_ohci) + return -ENOMEM; + + s5p_ohci->dev = &pdev->dev; + + hcd = usb_create_hcd(&ohci_s5p_hc_driver, &pdev->dev, + dev_name(&pdev->dev)); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + err = -ENOMEM; + goto fail_hcd; + } + + s5p_ohci->hcd = hcd; + s5p_ohci->clk = clk_get(&pdev->dev, "usbhost"); + + if (IS_ERR(s5p_ohci->clk)) { + dev_err(&pdev->dev, "Failed to get usbhost clock\n"); + err = PTR_ERR(s5p_ohci->clk); + goto fail_clk; + } + + err = clk_enable(s5p_ohci->clk); + if (err) + goto fail_clken; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + err = -ENXIO; + goto fail_io; + } + + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = ioremap(res->start, resource_size(res)); + if (!hcd->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + err = -ENOMEM; + goto fail_io; + } + + irq = platform_get_irq(pdev, 0); + if (!irq) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + err = -ENODEV; + goto fail; + } + + if (pdata->phy_init) + pdata->phy_init(pdev, S5P_USB_PHY_HOST); + + ohci = hcd_to_ohci(hcd); + ohci_hcd_init(ohci); + + err = usb_add_hcd(hcd, irq, + IRQF_DISABLED | IRQF_SHARED); + + if (err) { + dev_err(&pdev->dev, "Failed to add USB HCD\n"); + goto fail; + } + + platform_set_drvdata(pdev, s5p_ohci); + + create_ohci_sys_file(ohci); + s5p_ohci->power_on = 1; + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return 0; + +fail: + iounmap(hcd->regs); +fail_io: + clk_disable(s5p_ohci->clk); +fail_clken: + clk_put(s5p_ohci->clk); +fail_clk: + usb_put_hcd(hcd); +fail_hcd: + kfree(s5p_ohci); + return err; +} + +static int __devexit ohci_hcd_s5p_drv_remove(struct platform_device *pdev) +{ + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + + if (pdata && pdata->phy_resume) + pdata->phy_resume(pdev, S5P_USB_PHY_HOST); + + usb_remove_hcd(hcd); + + s5p_ohci->power_on = 0; + remove_ohci_sys_file(hcd_to_ohci(hcd)); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); + + iounmap(hcd->regs); + + clk_disable(s5p_ohci->clk); + clk_put(s5p_ohci->clk); + + usb_put_hcd(hcd); + kfree(s5p_ohci); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void ohci_hcd_s5p_drv_shutdown(struct platform_device *pdev) +{ + struct s5p_ohci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ohci_hcd *s5p_ohci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ohci->hcd; + + if (!s5p_ohci->power_on) + return; + + if (pdata && pdata->phy_resume) + pdata->phy_resume(pdev, S5P_USB_PHY_HOST); + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static const struct dev_pm_ops ohci_s5p_pm_ops = { + .suspend = ohci_hcd_s5p_drv_suspend, + .resume = ohci_hcd_s5p_drv_resume, +#ifdef CONFIG_HIBERNATION + .freeze = ohci_hcd_s5p_drv_suspend, + .thaw = ohci_hcd_s5p_drv_resume, + .restore = ohci_hcd_s5p_drv_resume, +#endif + .runtime_suspend = ohci_hcd_s5p_drv_runtime_suspend, + .runtime_resume = ohci_hcd_s5p_drv_runtime_resume, +}; + +static struct platform_driver ohci_hcd_s5p_driver = { + .probe = ohci_hcd_s5p_drv_probe, + .remove = __devexit_p(ohci_hcd_s5p_drv_remove), + .shutdown = ohci_hcd_s5p_drv_shutdown, + .driver = { + .name = "s5p-ohci", + .owner = THIS_MODULE, + .pm = &ohci_s5p_pm_ops, + } +}; + +MODULE_ALIAS("platform:s5p-ohci"); diff --git a/drivers/usb/host/shost/Makefile b/drivers/usb/host/shost/Makefile new file mode 100644 index 0000000..c891a18 --- /dev/null +++ b/drivers/usb/host/shost/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for USB OTG Host Controller Drivers +# + +obj-$(CONFIG_USB_S3C_OTG_HOST) += shost_hcd.o + +shost_hcd-objs += shost_driver.o +shost_hcd-objs += shost_scheduler.o +shost_hcd-objs += shost_transferchecker.o diff --git a/drivers/usb/host/shost/shost.h b/drivers/usb/host/shost/shost.h new file mode 100644 index 0000000..6774667 --- /dev/null +++ b/drivers/usb/host/shost/shost.h @@ -0,0 +1,56 @@ +#ifndef _SHOST_H +#define _SHOST_H + + +#include <linux/usb.h> +#include <linux/errno.h> +#include <linux/wakelock.h> +#include <plat/s5p-otghost.h> + + +#include "shost_debug.h" +#include "shost_list.h" + +#include "shost_const.h" +#include "shost_errorcode.h" +#include "shost_regs.h" +#include "shost_struct.h" + +#include "shost_kal.h" +#include "shost_mem.h" +#include "shost_oci.h" + +#include "shost_scheduler.h" +#include "shost_transfer.h" + +/* #ifdef CONFIG_MACH_C1 GB */ +#if 0 + #include <plat/regs-otg.h> + #define OTG_CLOCK S5P_CLKGATE_IP_FSYS + #define OTG_PHY_CONTROL S5P_USBOTG_PHY_CONTROL + #define OTG_PHYPWR S3C_USBOTG_PHYPWR + #define OTG_PHYCLK S3C_USBOTG_PHYCLK + #define OTG_RSTCON S3C_USBOTG_RSTCON + #define S3C_VA_HSOTG S3C_VA_OTG +#else /* ICS */ + #include <mach/regs-usb-phy.h> + #define OTG_CLOCK EXYNOS4_CLKGATE_IP_FSYS + #define OTG_PHY_CONTROL S5P_USBOTG_PHY_CONTROL + #define OTG_PHYPWR EXYNOS4_PHYPWR + #define OTG_PHYCLK EXYNOS4_PHYCLK + #define OTG_RSTCON EXYNOS4_RSTCON + #define IRQ_OTG IRQ_USB_HSOTG + #define S3C_VA_OTG S3C_VA_HSOTG +#endif + +/* transferchecker-common.c + * called by isr + */ +void do_transfer_checker(struct sec_otghost *otghost); +void otg_print_registers(void); + +#ifdef CONFIG_USB_HOST_NOTIFY +#undef CONFIG_USB_HOST_NOTIFY +#endif + +#endif diff --git a/drivers/usb/host/shost/shost_const.h b/drivers/usb/host/shost/shost_const.h new file mode 100644 index 0000000..8ba1494 --- /dev/null +++ b/drivers/usb/host/shost/shost_const.h @@ -0,0 +1,140 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-const.h + * [Description] : The Header file defines constants + * to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created s3c-otg-common-const.h file and defines some constants. + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _CONST_TYPE_DEF_H_ +#define _CONST_TYPE_DEF_H_ + +/** + * @def OTG_PORT_NUMBER + * + * @brief write~ description + * + * describe in detail + */ +#define OTG_PORT_NUMBER 0 + +/* Defines Stages of Control Transfer */ +#define SETUP_STAGE 1 +#define DATA_STAGE 2 +#define STATUS_STAGE 3 +#define COMPLETE_STAGE 4 + +/* Defines Direction of Endpoint */ +#define EP_IN 1 +#define EP_OUT 0 + +/* Define speed of USB Device */ +#define LOW_SPEED_OTG 2 +#define FULL_SPEED_OTG 1 +#define HIGH_SPEED_OTG 0 +#define SUPER_SPEED_OTG 3 + +/* Define multiple count of packet in periodic transfer. */ +#define MULTI_COUNT_ZERO 0 +#define MULTI_COUNT_ONE 1 +#define MULTI_COUNT_TWO 2 + +/* Define USB Transfer Types. */ +#define CONTROL_TRANSFER 0 +#define ISOCH_TRANSFER 1 +#define BULK_TRANSFER 2 +#define INT_TRANSFER 3 + +#define BULK_TIMEOUT 300 + +/* Defines PID */ +#define DATA0 0 +#define DATA1 2 +#define DATA2 1 +#define MDATA 3 +#define SETUP 3 + +/* Defines USB Transfer Request Size on USB2.0 */ +#define USB_20_STAND_DEV_REQUEST_SIZE 8 +/* Define Max Channel Number */ +#define MAX_CH_NUMBER 16 +/* Define Channel Number */ +#define CH_0 0 +#define CH_1 1 +#define CH_2 2 +#define CH_3 3 +#define CH_4 4 +#define CH_5 5 +#define CH_6 6 +#define CH_7 7 +#define CH_8 8 +#define CH_9 9 +#define CH_10 10 +#define CH_11 11 +#define CH_12 12 +#define CH_13 13 +#define CH_14 14 +#define CH_15 15 +#define CH_NONE 20 + +/* define the Constant for result of processing the USB Transfer. */ +#define RE_TRANSMIT 1 +#define RE_SCHEDULE 2 +#define DE_ALLOCATE 3 +#define NO_ACTION 4 + +/* define the threshold value to retransmit USB Transfer */ +#define RETRANSMIT_THRESHOLD 2 + +/* define the maximum size of data to be tranferred through channel. */ +#define MAX_CH_TRANSFER_SIZE 65536 /* 65535 */ + +/* define Max Frame Number which Synopsys OTG suppports. */ +#define MAX_FRAME_NUMBER 0x3FFF +/* Channel Interrupt Status */ +#define CH_STATUS_DataTglErr (0x1<<10) +#define CH_STATUS_FrmOvrun (0x1<<9) +#define CH_STATUS_BblErr (0x1<<8) +#define CH_STATUS_XactErr (0x1<<7) +#define CH_STATUS_NYET (0x1<<6) +#define CH_STATUS_ACK (0x1<<5) +#define CH_STATUS_NAK (0x1<<4) +#define CH_STATUS_STALL (0x1<<3) +#define CH_STATUS_AHBErr (0x1<<2) +#define CH_STATUS_ChHltd (0x1<<1) +#define CH_STATUS_XferCompl (0x1<<0) +#define CH_STATUS_ALL 0x7FF + +/* Define USB Transfer Flag.. */ +#define USB_TRANS_FLAG_NOT_SHORT URB_SHORT_NOT_OK +#define USB_TRANS_FLAG_ISO_ASYNCH URB_ISO_ASAP + +#define HFNUM_MAX_FRNUM 0x3FFF +#define SCHEDULE_SLOT 10 + + +#endif + + diff --git a/drivers/usb/host/shost/shost_debug.h b/drivers/usb/host/shost/shost_debug.h new file mode 100644 index 0000000..dd67b98 --- /dev/null +++ b/drivers/usb/host/shost/shost_debug.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-debug.c + * @brief It provides debug functions for display message \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _SHOST_DEBUG_H +#define _SHOST_DEBUG_H + +#define OTG_DEBUG + +#ifdef OTG_DEBUG + +#define OTG_DBG_OTGHCDI_DRIVER 1 +#define OTG_DBG_OTGHCDI_HCD 0 +#define OTG_DBG_OTGHCDI_KAL 0 +#define OTG_DBG_OTGHCDI_LIST 0 +#define OTG_DBG_OTGHCDI_MEM 0 +#define OTG_DBG_OTGHCDI_IRQ 0 + +#define OTG_DBG_TRANSFER 0 +#define OTG_DBG_SCHEDULE 0 +#define OTG_DBG_SCHEDULE_ED 0 +#define OTG_DBG_OCI 0 +#define OTG_DBG_DONETRASF 0 +#define OTG_DBG_ISR 0 +#define OTG_DBG_ROOTHUB 0 + + +#include <linux/kernel.h> /* for printk */ + +#define otg_err(is_active, msg, args...) \ + do { \ + if ((is_active) == true) {\ + pr_err("OTG_ERR %s(%d): " msg, \ + __func__ , __LINE__, ##args); \ + } \ + } while (0) + +#define otg_dbg(is_active, msg, args...) \ + do { \ + if ((is_active) == true) { \ + pr_info("OTG %s(%d): " msg, \ + __func__, __LINE__, ##args); \ + } \ + } while (0) + +#else /* OTG_DEBUG */ + +#define otg_err(is_active, msg...) do {} while (0) +#define otg_dbg(is_active, msg...) do {} while (0) + +#endif + + +#endif /* SHOST_DEBUG_H */ diff --git a/drivers/usb/host/shost/shost_driver.c b/drivers/usb/host/shost/shost_driver.c new file mode 100644 index 0000000..f10136c --- /dev/null +++ b/drivers/usb/host/shost/shost_driver.c @@ -0,0 +1,450 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-driver.c + * @brief It provides functions related with module for OTGHCD driver. + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) + * : Creating the initial version of this code + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) + * : Modifying for successful rmmod & disconnecting + * @see None + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include <linux/module.h> +#include <linux/init.h> + +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/interrupt.h> /* for SA_SHIRQ */ +#include <mach/map.h> /* address for smdk */ +#include <linux/dma-mapping.h> /* dma_alloc_coherent */ +#include <linux/ioport.h> /* request_mem_request ... */ +#include <asm/irq.h> /* for IRQ_OTG */ +#include <linux/clk.h> + + +#include "shost.h" + + +static inline struct sec_otghost *hcd_to_sec_otghost(struct usb_hcd *hcd) +{ + return (struct sec_otghost *)(hcd->hcd_priv); +} +static inline struct usb_hcd *sec_otghost_to_hcd(struct sec_otghost *otghost) +{ + return container_of((void *) otghost, struct usb_hcd, hcd_priv); +} + + +#include "shost_oci.c" +#include "shost_transfer.c" +#include "shost_roothub.c" +#include "shost_hcd.c" + +volatile u8 *g_pUDCBase; +struct usb_hcd *g_pUsbHcd; + +static const char gHcdName[] = "EMSP_OTG_HCD"; +static struct platform_device *g_pdev; + + +static void otg_power_work(struct work_struct *work) +{ + struct sec_otghost *otghost = container_of(work, + struct sec_otghost, work); + struct sec_otghost_data *hdata = otghost->otg_data; + + if (hdata && hdata->set_pwr_cb) { + pr_info("otg power off - don't turn off the power\n"); + hdata->set_pwr_cb(0); +#ifdef CONFIG_USB_HOST_NOTIFY + if (hdata->host_notify_cb) + hdata->host_notify_cb(NOTIFY_HOST_OVERCURRENT); +#endif + } else { + otg_err(true, "invalid otghost data\n"); + } +} + +static int s5pc110_mapping_resources(struct platform_device *pdev) +{ + g_pUsbHcd->rsrc_start = pdev->resource[0].start; + g_pUsbHcd->rsrc_len = pdev->resource[0].end - + pdev->resource[0].start + 1; + + if (!request_mem_region(g_pUsbHcd->rsrc_start, + g_pUsbHcd->rsrc_len, gHcdName)) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to request_mem_region\n"); + return -EBUSY; + } + + pr_info("otg rsrc_start %llu, ren %llu\n", + g_pUsbHcd->rsrc_start, + g_pUsbHcd->rsrc_len); + + pr_info("otg regs : %p\n", S3C_VA_HSOTG); + + /* Physical address => Virtual address */ + g_pUsbHcd->regs = S3C_VA_HSOTG; + g_pUDCBase = (u8 *)g_pUsbHcd->regs; + + return 0; +} + +static void s5pc110_prevent_suspend(struct usb_device *rhdev) +{ + dev_info(&rhdev->dev, "otg host do not enter suspend.\n"); + pm_runtime_disable(&rhdev->dev); +} + +static int s5pc110_start_otg(u32 regs) +{ + int ret_val = 0; + u32 reg_val = 0; + struct platform_device *pdev = g_pdev; + struct sec_otghost *otghost = NULL; + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + + pr_info("%s + regs=0x%x\n", __func__, regs); + + /* 1. hcd */ + g_pUsbHcd = usb_create_hcd(&s5pc110_otg_hc_driver, &pdev->dev, + "s3cotg");/*pdev->dev.bus_id*/ + if (g_pUsbHcd == NULL) { + otg_err(1, "failed to usb_create_hcd\n"); + return -ENOMEM; + } + g_pUsbHcd->self.otg_port = 1; + + /* sec_otghost */ + otghost = hcd_to_sec_otghost(g_pUsbHcd); + if (otghost == NULL) { + otg_err(true, "failed to get otghost hcd\n"); + ret_val = USB_ERR_FAIL; + goto err_out_create_hcd; + } + otghost->otg_data = otg_data; + + /* 2. wake lock */ + wake_lock_init(&otghost->wake_lock, WAKE_LOCK_SUSPEND, "usb_otg"); + wake_lock(&otghost->wake_lock); + + /* base address */ + if (!regs) { + pr_info("otg mapping hcd resource\n"); + ret_val = s5pc110_mapping_resources(pdev); + if (ret_val) + goto err_out_create_hcd; + } else + g_pUDCBase = (u8 *)regs; + + pr_info("otg g_pUDCBase 0x%p\n", g_pUDCBase); + + /* 3. workqueue */ + INIT_WORK(&otghost->work, otg_power_work); + otghost->wq = create_singlethread_workqueue("sec_otghostd"); + + /* 4. phy */ + ret_val = otg_hcd_init_modules(otghost); + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "failed to otg_hcd_init_modules\n"); + ret_val = USB_ERR_FAIL; + goto err_out_create_hcd; + } + + /** + * Attempt to ensure this device is really a s5pc110 USB-OTG Controller. + * Read and verify the SNPSID register contents. The value should be + * 0x45F42XXX, which corresponds to "OT2", as in "OTG version 2.XX". + */ + reg_val = read_reg_32(0x40); + pr_info("otg reg 0x40 = %x\n", reg_val); + if ((reg_val & 0xFFFFF000) != 0x4F542000) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Bad value for SNPSID: 0x%x\n", reg_val); + ret_val = -EINVAL; + goto err_out_create_hcd_init; + } + +#ifdef CONFIG_USB_SEC_WHITELIST + if (otg_data->sec_whlist_table_num) + g_pUsbHcd->sec_whlist_table_num = + otg_data->sec_whlist_table_num; +#endif + + /* 5. hcd + * Finish generic HCD initialization and start the HCD. This function + * allocates the DMA buffer pool, registers the USB bus, requests the + * IRQ line, and calls s5pc110_otghcd_start method. + */ + ret_val = usb_add_hcd(g_pUsbHcd, + pdev->resource[1].start, IRQF_DISABLED); + if (ret_val < 0) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Failed to add hcd driver\n"); + goto err_out_create_hcd_init; + } + + s5pc110_prevent_suspend(g_pUsbHcd->self.root_hub); + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "OTG HCD Initialized HCD, bus=%s, usbbus=%d\n", + "C110 OTG Controller", g_pUsbHcd->self.busnum); + + /* otg_print_registers(); */ + pr_info("%s -\n", __func__); + + return USB_ERR_SUCCESS; + +err_out_create_hcd_init: + otg_hcd_deinit_modules(otghost); + if (!regs) + release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len); + +err_out_create_hcd: + usb_put_hcd(g_pUsbHcd); + + return ret_val; +} + +static int s5pc110_stop_otg(void) +{ + struct sec_otghost *otghost = NULL; + struct sec_otghost_data *otgdata = NULL; + + pr_info("%s +\n", __func__); + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, "s5pc110_stop_otg\n"); + + otghost = hcd_to_sec_otghost(g_pUsbHcd); + + otg_hcd_deinit_modules(otghost); + + /* 5. hcd */ + usb_remove_hcd(g_pUsbHcd); +#if 1 + if (g_pUDCBase == S3C_VA_HSOTG) { + pr_info("otg release_mem_region\n"); + release_mem_region(g_pUsbHcd->rsrc_start, g_pUsbHcd->rsrc_len); + } +#endif + /* 4. phy */ + otgdata = otghost->otg_data; + if (otgdata && otgdata->phy_exit && otgdata->pdev) { + pr_info("otg phy_off\n"); + otgdata->phy_exit(0); + clk_disable(otgdata->clk); + } + + /* 3. workqueue */ + destroy_workqueue(otghost->wq); + + /* 2. wake lock */ + wake_unlock(&otghost->wake_lock); + wake_lock_destroy(&otghost->wake_lock); + + /* 1. hcd */ + usb_put_hcd(g_pUsbHcd); + + pr_info("%s -\n", __func__); + + return 0; +} + +/** + * static int s5pc110_otg_drv_probe (struct platform_device *pdev) + * + * @brief probe function of OTG hcd platform_driver + * + * @param [in] pdev : pointer of platform_device of otg hcd platform_driver + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If fail + * @remark + * it allocates resources of it and call other modules' init function. + * then call usb_create_hcd, usb_add_hcd, s5pc110_otghcd_start functions + */ + +static int s5pc110_otg_drv_probe(struct platform_device *pdev) +{ + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + g_pdev = pdev; + + otg_data->clk = clk_get(&pdev->dev, "usbotg"); + + if (IS_ERR(otg_data->clk)) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "Failed to get clock\n"); + return PTR_RET(otg_data->clk); + } + + pr_info("otg host_probe start %p\n", s5pc110_start_otg); + otg_data->start = s5pc110_start_otg; + otg_data->stop = s5pc110_stop_otg; + otg_data->pdev = pdev; + + return 0; +} + + +/** + * static int s5pc110_otg_drv_remove (struct platform_device *dev) + * + * @brief remove function of OTG hcd platform_driver + * + * @param [in] pdev : pointer of platform_device of otg hcd platform_driver + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If fail + * @remark + * This function is called when the otg device unregistered with the + * s5pc110_otg_driver. This happens, for example, when the rmmod command is + * executed. The device may or may not be electrically present. If it is + * present, the driver stops device processing. Any resources used on behalf + * of this device are freed. + */ + +static int s5pc110_otg_drv_remove(struct platform_device *pdev) +{ + struct sec_otghost_data *otg_data = dev_get_platdata(&pdev->dev); + + clk_put(otg_data->clk); + return USB_ERR_SUCCESS; +} + +/** + * @struct s5pc110_otg_driver + * + * @brief + * This structure defines the methods to be called by a bus driver + * during the lifecycle of a device on that bus. Both drivers and + * devices are registered with a bus driver. The bus driver matches + * devices to drivers based on information in the device and driver + * structures. + * + * The probe function is called when the bus driver matches a device + * to this driver. The remove function is called when a device is + * unregistered with the bus driver. + */ +struct platform_driver s5pc110_otg_driver = { + .probe = s5pc110_otg_drv_probe, + .remove = s5pc110_otg_drv_remove, +/* .shutdown = usb_hcd_platform_shutdown, */ + .driver = { + .name = "s3c_otghcd", + .owner = THIS_MODULE, + }, +}; + +/** + * static int __init s5pc110_otg_module_init(void) + * + * @brief module_init function + * + * @return it returns result of platform_driver_register + * @remark + * This function is called when the s5pc110_otg_driver is installed with the + * insmod command. It registers the s5pc110_otg_driver structure with the + * appropriate bus driver. This will cause the s5pc110_otg_driver_probe function + * to be called. In addition, the bus driver will automatically expose + * attributes defined for the device and driver in the special sysfs file + * system. + */ +static int __init s5pc110_otg_module_init(void) +{ + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "s3c_otg_module_init\n"); + + ret_val = platform_driver_register(&s5pc110_otg_driver); + if (ret_val < 0) { + otg_err(OTG_DBG_OTGHCDI_DRIVER, + "platform_driver_register\n"); + } + return ret_val; +} + +/** + * static void __exit s5pc110_otg_module_exit(void) + * + * @brief module_exit function + * + * @remark + * This function is called when the driver is removed from the kernel + * with the rmmod command. The driver unregisters itself with its bus + * driver. + */ +static void __exit s5pc110_otg_module_exit(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_DRIVER, + "s3c_otg_module_exit\n"); + platform_driver_unregister(&s5pc110_otg_driver); +} + +/* for debug */ +void otg_print_registers(void) +{ + /* USB PHY Control Registers */ + + pr_info("otg clock = %s\n", + (readl(OTG_CLOCK) & (1<<13)) ? "ON" : "OFF"); + pr_info("otg USB_CONTROL = 0x%x.\n", readl(OTG_PHY_CONTROL)); + pr_info("otg UPHYPWR = 0x%x.\n", readl(OTG_PHYPWR)); + pr_info("otg UPHYCLK = 0x%x.\n", readl(OTG_PHYCLK)); + pr_info("otg URSTCON = 0x%x.\n", readl(OTG_RSTCON)); + + /* OTG LINK Core registers (Core Global Registers) */ + pr_info("otg GOTGCTL = 0x%x.\n", read_reg_32(GOTGCTL)); + pr_info("otg GOTGINT = 0x%x.\n", read_reg_32(GOTGINT)); + pr_info("otg GAHBCFG = 0x%x.\n", read_reg_32(GAHBCFG)); + pr_info("otg GUSBCFG = 0x%x.\n", read_reg_32(GUSBCFG)); + pr_info("otg GINTSTS = 0x%x.\n", read_reg_32(GINTSTS)); + pr_info("otg GINTMSK = 0x%x.\n", read_reg_32(GINTMSK)); + + /* Host Mode Registers */ + pr_info("otg HCFG = 0x%x.\n", read_reg_32(HCFG)); + pr_info("otg HPRT = 0x%x.\n", read_reg_32(HPRT)); + pr_info("otg HFIR = 0x%x.\n", read_reg_32(HFIR)); + + /* Synopsys ID */ + pr_info("otg GSNPSID = 0x%x.\n", read_reg_32(GSNPSID)); + + /* HWCFG */ + pr_info("otg GHWCFG1 = 0x%x.\n", read_reg_32(GHWCFG1)); + pr_info("otg GHWCFG2 = 0x%x.\n", read_reg_32(GHWCFG2)); + pr_info("otg GHWCFG3 = 0x%x.\n", read_reg_32(GHWCFG3)); + pr_info("otg GHWCFG4 = 0x%x.\n", read_reg_32(GHWCFG4)); + + /* PCGCCTL */ + pr_info("otg PCGCCTL = 0x%x.\n", read_reg_32(PCGCCTL)); +} + +late_initcall(s5pc110_otg_module_init); +module_exit(s5pc110_otg_module_exit); + +MODULE_DESCRIPTION("OTG USB HOST controller driver"); +MODULE_AUTHOR("SAMSUNG / System LSI / EMSP"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/shost/shost_errorcode.h b/drivers/usb/host/shost/shost_errorcode.h new file mode 100644 index 0000000..f52b606 --- /dev/null +++ b/drivers/usb/host/shost/shost_errorcode.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-errorcode.h + * [Description] : The Header file defines Error Codes + * to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file. + * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - add HCD error code + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _ERRORCODE_H +#define _ERRORCODE_H + + +/* General USB Error Code.*/ +#define USB_ERR_SUCCESS 0 +#define USB_ERR_FAIL (-1) + +#define USB_ERR_NO 1 + +#define USB_ERR_NO_ENTITY (-2) + +/* S3CTransfer Error Code */ +#define USB_ERR_NODEV (-ENODEV) +#define USB_ERR_NOMEM (-ENOMEM) +#define USB_ERR_NOSPACE (-ENOSPC) +#define USB_ERR_NOIO (-EIO) + +/* OTG-HCD error code */ +#define USB_ERR_NOELEMENT (-ENOENT) +#define USB_ERR_ESHUTDOWN (-ESHUTDOWN) /* unplug */ +#define USB_ERR_DEQUEUED (-ECONNRESET) /* unlink */ + + +/* S3CScheduler Error Code */ +#define USB_ERR_ALREADY_EXIST (-1) +#define USB_ERR_NO_RESOURCE (-2) +#define USB_ERR_NO_CHANNEL (-3) +#define USB_ERR_NO_BANDWIDTH (-4) +#define USB_ERR_ALL_RESROUCE (-5) + +/************************************************ + *Defines the USB Error Status Code of USB Transfer. + ************************************************/ + +#define USB_ERR_STATUS_COMPLETE 0 +#define USB_ERR_STATUS_INPROGRESS (-EINPROGRESS) +#define USB_ERR_STATUS_CRC (-EILSEQ) +#define USB_ERR_STATUS_XACTERR (-EPROTO) +#define USB_ERR_STATUS_STALL (-EPIPE) +#define USB_ERR_STATUS_BBLERR (-EOVERFLOW) +#define USB_ERR_STATUS_AHBERR (-EIO) +#define USB_ERR_STATUS_FRMOVRUN_OUT (-ENOSR) +#define USB_ERR_STATUS_FRMOVRUN_IN (-ECOMM) +#define USB_ERR_STATUS_SHORTREAD (-EREMOTEIO) + + +#endif + diff --git a/drivers/usb/host/shost/shost_hcd.c b/drivers/usb/host/shost/shost_hcd.c new file mode 100644 index 0000000..f06b237 --- /dev/null +++ b/drivers/usb/host/shost/shost_hcd.c @@ -0,0 +1,912 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-hcd.c + * @brief implementation of structure hc_drive + * @version + * -# Jun 11,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) + * : Creating the initial version of this code + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) + * : Modifying for successful rmmod & disconnecting + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +/*Internal function of isr */ +static void process_port_intr(struct usb_hcd *hcd) +{ + hprt_t hprt; /* by ss1, clear_hprt; */ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ISR, "Port Interrupt() : HPRT = 0x%x\n", hprt.d32); + + if (hprt.b.prtconndet) { + otg_dbg(true, "detect connection"); + + otghost->port_flag.b.port_connect_status_change = 1; + + if (hprt.b.prtconnsts) + otghost->port_flag.b.port_connect_status = 1; + + /* wake_lock(&otghost->wake_lock); */ + } + + + if (hprt.b.prtenchng) { + otg_dbg(true, "port enable/disable changed\n"); + otghost->port_flag.b.port_enable_change = 1; + } + + if (hprt.b.prtovrcurrchng) { + otg_dbg(true, "over current condition is changed\n"); + + if (hprt.b.prtovrcurract) { + otg_dbg(true, "port_over_current_change = 1\n"); + otghost->port_flag.b.port_over_current_change = 1; + + } else { + otghost->port_flag.b.port_over_current_change = 0; + } + /* defer otg power control into a kernel thread */ + queue_work(otghost->wq, &otghost->work); + } + + hprt.b.prtena = 0; /* prtena를 writeclear시키면 안됨. */ + /* hprt.b.prtpwr = 0; */ + hprt.b.prtrst = 0; + hprt.b.prtconnsts = 0; + + write_reg_32(HPRT, hprt.d32); +} + +/** + * void otg_handle_interrupt(void) + * + * @brief Main interrupt processing routine + * + * @param None + * + * @return None + * + * @remark + * + */ + +static inline void otg_handle_interrupt(struct usb_hcd *hcd) +{ + gintsts_t clearIntr = {.d32 = 0}; + gintsts_t gintsts = {.d32 = 0}; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + gintsts.d32 = read_reg_32(GINTSTS) & read_reg_32(GINTMSK); + + otg_dbg(OTG_DBG_ISR, "otg_handle_interrupt - GINTSTS=0x%8x\n", + gintsts.d32); + + if (gintsts.b.wkupintr) { + otg_dbg(true, "Wakeup Interrupt\n"); + clearIntr.b.wkupintr = 1; + } + + if (gintsts.b.disconnect) { + otg_dbg(true, "Disconnect Interrupt\n"); + otghost->port_flag.b.port_connect_status_change = 1; + otghost->port_flag.b.port_connect_status = 0; + clearIntr.b.disconnect = 1; + /* + wake_unlock(&otghost->wake_lock); + */ + } + + if (gintsts.b.conidstschng) { + otg_dbg(OTG_DBG_ISR, "Connect ID Status Change Interrupt\n"); + clearIntr.b.conidstschng = 1; + oci_init_mode(); + } + + if (gintsts.b.hcintr) { + /* Mask Channel Interrupt to prevent generating interrupt */ + otg_dbg(OTG_DBG_ISR, "Channel Interrupt\n"); + + if (!otghost->ch_halt) + do_transfer_checker(otghost); + } + + if (gintsts.b.portintr) { + /* Read Only */ + otg_dbg(true, "Port Interrupt\n"); + process_port_intr(hcd); + } + + + if (gintsts.b.otgintr) { + /* Read Only */ + otg_dbg(OTG_DBG_ISR, "OTG Interrupt\n"); + } + + if (gintsts.b.sofintr) { + /* otg_dbg(OTG_DBG_ISR, "SOF Interrupt\n"); */ + do_schedule(otghost); + clearIntr.b.sofintr = 1; + } + + if (gintsts.b.modemismatch) { + otg_dbg(OTG_DBG_ISR, "Mode Mismatch Interrupt\n"); + clearIntr.b.modemismatch = 1; + } + update_reg_32(GINTSTS, clearIntr.d32); +} + + + +/** + * otg_hcd_init_modules(struct sec_otghost *otghost) + * + * @brief call other modules' init functions + * + * @return PASS : If success + * FAIL : If fail + */ +static int otg_hcd_init_modules(struct sec_otghost *otghost) +{ + otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_init_modules\n"); + + spin_lock_init(&otghost->lock); + + init_transfer(); + init_scheduler(); + oci_init(otghost); + + return USB_ERR_SUCCESS; +}; + +/** + * void otg_hcd_deinit_modules(struct sec_otghost *otghost) + * + * @brief call other modules' de-init functions + * + * @return PASS : If success + * FAIL : If fail + */ +static void otg_hcd_deinit_modules(struct sec_otghost *otghost) +{ + unsigned long spin_lock_flag = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "otg_hcd_deinit_modules\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + deinit_transfer(otghost); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); +} + +/** + * irqreturn_t (*s5pc110_otghcd_irq) (struct usb_hcd *hcd) + * + * @brief interrupt handler of otg irq + * + * @param [in] hcd : pointer of usb_hcd + * + * @return IRQ_HANDLED + */ +static irqreturn_t s5pc110_otghcd_irq(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_IRQ, "s5pc110_otghcd_irq\n"); + + spin_lock(&otghost->lock); + otg_handle_interrupt(hcd); + spin_unlock(&otghost->lock); + + return IRQ_HANDLED; +} + +/** + * int s5pc110_otghcd_start(struct usb_hcd *hcd) + * + * @brief initialize and start otg hcd + * + * @param [in] usb_hcd_p : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If call fail + */ +static int s5pc110_otghcd_start(struct usb_hcd *usb_hcd_p) +{ + struct usb_bus *usb_bus_p; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start\n"); + + usb_bus_p = hcd_to_bus(usb_hcd_p); + + /* Initialize and connect root hub if one is not already attached */ + if (usb_bus_p->root_hub) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "OTG HCD Has Root Hub\n"); + + /* Inform the HUB driver to resume. */ + otg_usbcore_resume_roothub(); + } else { + otg_err(OTG_DBG_OTGHCDI_HCD, + "OTG HCD Does Not Have Root Hub\n"); + return USB_ERR_FAIL; + } + +/* #ifdef CONFIG_MACH_C1 */ +#if 0 + usb_hcd_p->poll_rh = 1; +#else + set_bit(HCD_FLAG_POLL_RH, &usb_hcd_p->flags); +#endif + usb_hcd_p->uses_new_polling = 1; + usb_hcd_p->has_tt = 1; + + /* init bus state before enable irq */ + usb_hcd_p->state = HC_STATE_RUNNING; + + oci_start(); /* enable irq */ + + return USB_ERR_SUCCESS; +} + +/** + * void s5pc110_otghcd_stop(struct usb_hcd *hcd) + * + * @brief deinitialize and stop otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + */ +static void s5pc110_otghcd_stop(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_stop\n"); + + otg_hcd_deinit_modules(otghost); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + oci_stop(); + + root_hub_feature(hcd, 0, ClearPortFeature, USB_PORT_FEAT_POWER, NULL); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); +} + +/** + * void s5pc110_otghcd_shutdown(struct usb_hcd *hcd) + * + * @brief shutdown otg hcd + * + * @param [in] usb_hcd_p : pointer of usb_hcd + * + */ +static void s5pc110_otghcd_shutdown(struct usb_hcd *usb_hcd_p) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(usb_hcd_p); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_shutdown\n"); + otg_hcd_deinit_modules(otghost); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + oci_stop(); + root_hub_feature(usb_hcd_p, 0, ClearPortFeature, + USB_PORT_FEAT_POWER, NULL); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + free_irq(IRQ_OTG, usb_hcd_p); + usb_hcd_p->state = HC_STATE_HALT; + otg_usbcore_hc_died(); +} + + +/** + * int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd) + * + * @brief get currnet frame number + * + * @param [in] hcd : pointer of usb_hcd + * + * @return ret : frame number + */ +static int s5pc110_otghcd_get_frame_number(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + int ret = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_get_frame_number\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret = oci_get_frame_num(); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + return ret; +} + +int compare_ed(struct sec_otghost *otghost, void *hcpriv, struct urb *urb) +{ + struct ed *ped = NULL; + int ret = 0; + u32 update = 0; + + if (!hcpriv) + return ret; + + ped = (struct ed *)hcpriv; + + if (ped->ed_desc.device_addr != usb_pipedevice(urb->pipe)) { + ped->ed_desc.device_addr = usb_pipedevice(urb->pipe); + update |= 1 << 1; + } + + if (ped->ed_desc.endpoint_num != usb_pipeendpoint(urb->pipe)) { + ped->ed_desc.endpoint_num = usb_pipeendpoint(urb->pipe); + update |= 1 << 2; + } + + if (ped->ed_desc.is_ep_in != (usb_pipein(urb->pipe) ? 1 : 0)) { + ped->ed_desc.is_ep_in = usb_pipein(urb->pipe) ? 1 : 0; + update |= 1 << 3; + } + + if (ped->ed_desc.max_packet_size != + usb_maxpacket(urb->dev, urb->pipe, + !(usb_pipein(urb->pipe)))) { + update |= 1 << 4; + } + + if (update) + otg_dbg(1, "update ed %d (0x%x)\n", update, update); + + return ret; +} + +/** + * int s5pc110_otghcd_urb_enqueue() + * + * @brief enqueue a urb to otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * [in] ep : pointer of usb_host_endpoint + * [in] urb : pointer of urb + * [in] mem_flags : type of gfp_t + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If call fail + */ +static int s5pc110_otghcd_urb_enqueue(struct usb_hcd *hcd, + struct urb *urb, + gfp_t mem_flags) +{ + int ret_val = 0; + u32 trans_flag = 0; + u32 return_td_addr = 0; + u8 dev_speed, ed_type = 0, additional_multi_count; + u16 max_packet_size; + + u8 dev_addr = 0; + u8 ep_num = 0; + bool f_is_ep_in = true; + u8 interval = 0; + u32 sched_frame = 0; + u8 hub_addr = 0; + u8 hub_port = 0; + bool f_is_do_split = false; + + struct ed *target_ed = NULL; + struct isoch_packet_desc *new_isoch_packet_desc = NULL; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + unsigned long spin_lock_flag = 0; + + if (!otghost && !otghost->port_flag.b.port_connect_status) { + otg_err(1, "Error : %s is zero\n", otghost ? + "Port status" : "otghost"); + return USB_ERR_NOIO; + } + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "enqueue\n"); + if (compare_ed(otghost, urb->ep->hcpriv, urb)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "compare ed error\n"); + pr_info("otg compare ed error\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + /* check ep has ed_t or not */ + if (!(urb->ep->hcpriv)) { + /* for getting dev_speed */ + switch (urb->dev->speed) { + case USB_SPEED_HIGH: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "HS_OTG\n"); + dev_speed = HIGH_SPEED_OTG; + break; + + case USB_SPEED_FULL: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "FS_OTG\n"); + dev_speed = FULL_SPEED_OTG; + break; + + case USB_SPEED_LOW: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "LS_OTG\n"); + dev_speed = LOW_SPEED_OTG; + break; + + default: + otg_err(OTG_DBG_OTGHCDI_HCD, + "unKnown Device Speed\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + /* for getting ed_type */ + switch (usb_pipetype(urb->pipe)) { + case PIPE_BULK: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "bulk transfer\n"); + ed_type = BULK_TRANSFER; + break; + + case PIPE_INTERRUPT: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "interrupt transfer\n"); + ed_type = INT_TRANSFER; + break; + + case PIPE_CONTROL: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "control transfer\n"); + ed_type = CONTROL_TRANSFER; + break; + + case PIPE_ISOCHRONOUS: + otg_dbg(OTG_DBG_OTGHCDI_HCD, "isochronous transfer\n"); + ed_type = ISOCH_TRANSFER; + break; + default: + otg_err(OTG_DBG_OTGHCDI_HCD, "unKnown ep type\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + max_packet_size = usb_maxpacket(urb->dev, urb->pipe, + !(usb_pipein(urb->pipe))); + + additional_multi_count = ((max_packet_size) >> 11) & 0x03; + dev_addr = usb_pipedevice(urb->pipe); + ep_num = usb_pipeendpoint(urb->pipe); + + f_is_ep_in = usb_pipein(urb->pipe) ? true : false; + interval = (u8)(urb->interval); + sched_frame = (u8)(urb->start_frame); + + /* check */ + if (urb->dev->tt == NULL) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "urb->dev->tt == NULL\n"); + hub_port = 0; /* u8 hub_port */ + hub_addr = 0; /* u8 hub_addr */ + + } else { + hub_port = (u8)(urb->dev->ttport); + if (urb->dev->tt->hub) { + if (((dev_speed == FULL_SPEED_OTG) || + (dev_speed == LOW_SPEED_OTG)) && + (urb->dev->tt) && + (urb->dev->tt->hub->devnum != 1)) { + f_is_do_split = true; + } + hub_addr = (u8)(urb->dev->tt->hub->devnum); + } + + if (urb->dev->tt->multi) + hub_addr = 0x80; + } + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "dev_spped =%d, hub_port=%d, hub_addr=%d\n", + dev_speed, hub_port, hub_addr); + + ret_val = create_ed(&target_ed); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to create_ed()\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return ret_val; + } + + ret_val = init_ed(target_ed, + dev_addr, + ep_num, + f_is_ep_in, + dev_speed, + ed_type, + max_packet_size, + additional_multi_count, + interval, + sched_frame, + hub_addr, + hub_port, + f_is_do_split, + (void *)urb->ep); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to init_ed() :err = %d\n", (int)ret_val); + otg_mem_free(target_ed); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + urb->ep->hcpriv = (void *)(target_ed); + + } else { /* if (!(ep->hcpriv)) */ + + dev_addr = usb_pipedevice(urb->pipe); + if (((struct ed *)(urb->ep->hcpriv))->ed_desc.device_addr + != dev_addr) + ((struct ed *)urb->ep->hcpriv)->ed_desc.device_addr + = dev_addr; + } + + target_ed = (struct ed *)urb->ep->hcpriv; + + if (urb->transfer_flags & URB_SHORT_NOT_OK) + trans_flag += USB_TRANS_FLAG_NOT_SHORT; + + if (urb->transfer_flags & URB_ISO_ASAP) + trans_flag += USB_TRANS_FLAG_ISO_ASYNCH; + + if (ed_type == ISOCH_TRANSFER) { + otg_err(OTG_DBG_OTGHCDI_HCD, "ISO not yet supported\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + if (!HC_IS_RUNNING(hcd->state)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state)\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return -USB_ERR_NODEV; + } + + /* in case of unlink-during-submit */ + if (urb->status != -EINPROGRESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "urb->status is -EINPROGRESS\n"); + urb->hcpriv = NULL; + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + usb_hcd_giveback_urb(hcd, urb, urb->status); + return USB_ERR_SUCCESS; + } + + ret_val = issue_transfer(otghost, target_ed, (void *)NULL, (void *)NULL, + trans_flag, + (usb_pipetype(urb->pipe) == PIPE_CONTROL) ? true : false, + (u32)urb->setup_packet, (u32)urb->setup_dma, + (u32)urb->transfer_buffer, (u32)urb->transfer_dma, + (u32)urb->transfer_buffer_length, + (u32)urb->start_frame, (u32)urb->number_of_packets, + new_isoch_packet_desc, (void *)urb, &return_td_addr); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to issue_transfer()\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + urb->hcpriv = (void *)return_td_addr; + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_urb_dequeue(struct usb_hcd *_hcd, struct urb *_urb ) + * + * @brief dequeue a urb to otg + * + * @param [in] _hcd : pointer of usb_hcd + * [in] _urb : pointer of urb + * + * @return USB_ERR_SUCCESS : If success + * USB_ERR_FAIL : If call fail + */ +static int s5pc110_otghcd_urb_dequeue( + struct usb_hcd *_hcd, struct urb *_urb, int status) +{ + int ret_val = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd); + + unsigned long spin_lock_flag = 0; + struct td *cancel_td = (struct td *)_urb->hcpriv; + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "dequeue\n"); */ + + if (cancel_td == NULL) { + otg_err(OTG_DBG_OTGHCDI_HCD, "cancel_td is NULL\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "dequeue status = %d\n", status); + + ret_val = usb_hcd_check_unlink_urb(_hcd, _urb, status); + if ((ret_val) && (ret_val != -EIDRM)) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, "ret_val = %d\n", ret_val); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return ret_val; + } + + if (!HC_IS_RUNNING(_hcd->state)) { + otg_err(OTG_DBG_OTGHCDI_HCD, "!HC_IS_RUNNING(hcd->state)\n"); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + otg_usbcore_giveback(cancel_td); + return USB_ERR_SUCCESS; + } + + ret_val = cancel_transfer(otghost, cancel_td->parent_ed_p, cancel_td); + if (ret_val != USB_ERR_DEQUEUED && ret_val != USB_ERR_NOELEMENT) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to cancel_transfer()\n"); +/* otg_usbcore_giveback(cancel_td); */ + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_FAIL; + } + +/* otg_usbcore_giveback(cancel_td); */ + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * void s5pc110_otghcd_endpoint_disable( + * struct usb_hcd *hcd, + * struct usb_host_endpoint *ep) + * + * @brief disable a endpoint + * + * @param [in] hcd : pointer of usb_hcd + * [in] ep : pointer of usb_host_endpoint + */ +static void s5pc110_otghcd_endpoint_disable( + struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_endpoint_disable\n"); + + if (!((struct ed *)ep->hcpriv)) + return; + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret_val = delete_ed(otghost, (struct ed *)ep->hcpriv); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to delete_ed()\n"); + return; + } + + /* ep->hcpriv = NULL; delete_ed coveres it */ +} + +/** + * int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf) + * + * @brief get status of root hub + * + * @param [in] _hcd : pointer of usb_hcd + * [inout] _buf : pointer of buffer for write a status data + * + * @return ret_val : return port status + */ +static int s5pc110_otghcd_hub_status_data(struct usb_hcd *_hcd, char *_buf) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(_hcd); + + /* otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_status_data\n"); */ + + /* if !USB_SUSPEND, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(_hcd->state)) { + otg_dbg(OTG_DBG_OTGHCDI_HCD, + "_hcd->state is NOT HC_IS_RUNNING\n"); + return 0; + } + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret_val = get_otg_port_status(_hcd, OTG_PORT_NUMBER, _buf); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + return (int)ret_val; +} + +/** + * int s5pc110_otghcd_hub_control() + * + * @brief control root hub + * + * @param [in] hcd : pointer of usb_hcd + * [in] typeReq : type of control request + * [in] value : value + * [in] index : index + * [in] buf_p : pointer of urb + * [in] length : type of gfp_t + * + * @return ret_val : return root_hub_feature + */ +static int +s5pc110_otghcd_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 value, + u16 index, + char *buf_p, + u16 length) +{ + int ret_val = 0; + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_hub_control\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + ret_val = root_hub_feature(hcd, OTG_PORT_NUMBER, + typeReq, value, (void *)buf_p); + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, "fail to root_hub_feature()\n"); + return ret_val; + } + return (int)ret_val; +} + +/** + * int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd) + * + * @brief suspend otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS + */ +static int s5pc110_otghcd_bus_suspend(struct usb_hcd *hcd) +{ + unsigned long spin_lock_flag = 0; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_suspend\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + bus_suspend(); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd) + * + * @brief resume otg hcd + * + * @param [in] hcd : pointer of usb_hcd + * + * @return USB_ERR_SUCCESS + */ +static int s5pc110_otghcd_bus_resume(struct usb_hcd *hcd) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_bus_resume\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + + if (bus_resume(otghost) != USB_ERR_SUCCESS) + return USB_ERR_FAIL; + + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + return USB_ERR_SUCCESS; +} + +/** + * int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port) + * + * @brief reset otg port + * + * @param [in] hcd : pointer of usb_hcd + * [in] port : number of port + * + * @return USB_ERR_SUCCESS : If success + * ret_val : If call fail + */ +static int s5pc110_otghcd_start_port_reset(struct usb_hcd *hcd, unsigned port) +{ + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + unsigned long spin_lock_flag = 0; + int ret_val = 0; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "s5pc110_otghcd_start_port_reset\n"); + + spin_lock_irqsave(&otghost->lock, spin_lock_flag); + ret_val = reset_and_enable_port(hcd, OTG_PORT_NUMBER); + spin_unlock_irqrestore(&otghost->lock, spin_lock_flag); + + if (ret_val != USB_ERR_SUCCESS) { + otg_err(OTG_DBG_OTGHCDI_HCD, + "fail to reset_and_enable_port()\n"); + return ret_val; + } + return USB_ERR_SUCCESS; +} + + +/** + * @struct hc_driver s5pc110_otg_hc_driver + * + * @brief implementation of hc_driver for OTG HCD + * + * describe in detail + */ +static const struct hc_driver s5pc110_otg_hc_driver = { + .description = "EMSP_OTGHCD", + .product_desc = "S3C OTGHCD", + .hcd_priv_size = sizeof(struct sec_otghost), + + .irq = s5pc110_otghcd_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /** basic lifecycle operations */ + /* .reset = */ + .start = s5pc110_otghcd_start, + /* .suspend = */ + /* .resume = */ + .stop = s5pc110_otghcd_stop, + .shutdown = s5pc110_otghcd_shutdown, + + /** managing i/o requests and associated device resources */ + .urb_enqueue = s5pc110_otghcd_urb_enqueue, + .urb_dequeue = s5pc110_otghcd_urb_dequeue, + .endpoint_disable = s5pc110_otghcd_endpoint_disable, + + /** scheduling support */ + .get_frame_number = s5pc110_otghcd_get_frame_number, + + /** root hub support */ + .hub_status_data = s5pc110_otghcd_hub_status_data, + .hub_control = s5pc110_otghcd_hub_control, + /* .hub_irq_enable = */ + .bus_suspend = s5pc110_otghcd_bus_suspend, + .bus_resume = s5pc110_otghcd_bus_resume, + .start_port_reset = s5pc110_otghcd_start_port_reset, +}; + + diff --git a/drivers/usb/host/shost/shost_kal.h b/drivers/usb/host/shost/shost_kal.h new file mode 100644 index 0000000..1a213a1 --- /dev/null +++ b/drivers/usb/host/shost/shost_kal.h @@ -0,0 +1,361 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-kal.h + * @brief header of s3c-otg-hcdi-kal \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * -# Aug 18,2008 v1.3 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Modifying for successful rmmod & disconnecting \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _SHOST_KAL_H_ +#define _SHOST_KAL_H_ + +#include <linux/io.h> /* for readl, writel */ +#include <linux/usb/ch9.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <mach/map.h> +#include <plat/regs-otg.h> + +extern volatile u8 *g_pUDCBase; +extern struct usb_hcd *g_pUsbHcd; + +#define ctrlr_base_reg_addr(offset) \ + ((volatile unsigned int *)((g_pUDCBase) + (offset))) +/** + * otg_kal_make_ep_null + * + * @brief make ep->hcpriv NULL + * + * @param [in] pdelete_ed : pointer of ed + * + * @return void \n + */ +static inline void +otg_kal_make_ep_null(struct ed *pdelete_ed) +{ + ((struct usb_host_endpoint *)(pdelete_ed->ed_private))->hcpriv = NULL; +} + +/** + * otg_kal_is_ep_null + * + * @brief check ep->hcpriv is NULL or not + * + * @param [in] pdelete_ed : pointer of ed + * + * @return bool \n + */ +static inline bool +otg_kal_is_ep_null(struct ed *pdelete_ed) +{ + if (((struct usb_host_endpoint *) + (pdelete_ed->ed_private))->hcpriv == NULL) + return true; + else + return false; +} + + +/** + * int otg_usbcore_get_calc_bustime() + * + * @brief get bus time of usbcore + * + * @param [in] speed : usb speed + * [in] is_input : input or not + * [in] is_isoch : isochronous or not + * [in] byte_count : bytes + * + * @return bus time of usbcore \n + */ +static inline int +otg_usbcore_get_calc_bustime(u8 speed, bool is_input, + bool is_isoch, unsigned int byte_count) +{ + unsigned int convert_speed = 0; + + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_get_calc_bustime\n"); +/* enum usb_device_speed { + USB_SPEED_UNKNOWN = 0, + USB_SPEED_LOW, USB_SPEED_FULL, + USB_SPEED_HIGH, + USB_SPEED_VARIABLE, };*/ + switch (speed) { + case HIGH_SPEED_OTG: + convert_speed = USB_SPEED_HIGH; break; + case FULL_SPEED_OTG: + convert_speed = USB_SPEED_FULL; break; + case LOW_SPEED_OTG: + convert_speed = USB_SPEED_LOW; break; + default: + convert_speed = USB_SPEED_UNKNOWN; break; + } + return usb_calc_bus_time(convert_speed, is_input, + (unsigned int)is_isoch, byte_count); +} + + +/** + * void otg_usbcore_giveback(struct td td_p) + * + * @brief give-back a td as urb + * + * @param [in] td_p : pointer of struct td to give back + * + * @return void \n + */ +static inline void +otg_usbcore_giveback(struct td *td_p) +{ + struct urb *urb_p = NULL; + + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_giveback\n"); + + if (td_p->td_private == NULL) { + otg_err(OTG_DBG_OTGHCDI_KAL, + "td_p->td_private == NULL\n"); + return; + } + + urb_p = (struct urb *)td_p->td_private; + + urb_p->actual_length = (int)(td_p->transferred_szie); + urb_p->status = (int)(td_p->error_code); + urb_p->error_count = (int)(td_p->err_cnt); + urb_p->hcpriv = NULL; + + usb_hcd_giveback_urb(g_pUsbHcd, urb_p, urb_p->status); +} + +/** + * void otg_usbcore_hc_died(void) + * + * @brief inform usbcore of hc die + * + * @return void \n + */ +static inline void +otg_usbcore_hc_died(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, "otg_usbcore_hc_died\n"); + usb_hc_died(g_pUsbHcd); +} + +/** + * void otg_usbcore_poll_rh_status(void) + * + * @brief invoke usbcore's usb_hcd_poll_rh_status + * + * @param void + * + * @return void \n + */ +static inline void +otg_usbcore_poll_rh_status(void) +{ + usb_hcd_poll_rh_status(g_pUsbHcd); +} + +/** + * void otg_usbcore_resume_roothub(void) + * + * @brief invoke usbcore's usb_hcd_resume_root_hub + * + * @param void + * + * @return void \n + */ +static inline void +otg_usbcore_resume_roothub(void) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_resume_roothub\n"); + usb_hcd_resume_root_hub(g_pUsbHcd); +}; + +/** + * int otg_usbcore_inc_usb_bandwidth(u32 band_width) + * + * @brief increase bandwidth of usb bus + * + * @param [in] band_width : bandwidth to be increased + * + * @return USB_ERR_SUCCESS \n + */ +static inline int +otg_usbcore_inc_usb_bandwidth(u32 band_width) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_inc_usb_bandwidth\n"); + hcd_to_bus(g_pUsbHcd)->bandwidth_allocated += band_width; + return USB_ERR_SUCCESS; +} + +/** + * int otg_usbcore_des_usb_bandwidth(u32 uiBandwidth) + * + * @brief decrease bandwidth of usb bus + * + * @param [in] band_width : bandwidth to be decreased + * + * @return USB_ERR_SUCCESS \n + */ +static inline int +otg_usbcore_des_usb_bandwidth(u32 band_width) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_des_usb_bandwidth\n"); + hcd_to_bus(g_pUsbHcd)->bandwidth_allocated -= band_width; + return USB_ERR_SUCCESS; +} + +/** + * int otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type) + * + * @brief increase count of periodic transfer + * + * @param [in] transfer_type : type of transfer + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_usbcore_inc_periodic_transfer_cnt(u8 transfer_type) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_inc_periodic_transfer_cnt\n"); + + switch (transfer_type) { + case INT_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs++; + break; + case ISOCH_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs++; + break; + default: + otg_err(OTG_DBG_OTGHCDI_KAL, + "not proper TransferType\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + +/** + * int otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type) + * + * @brief decrease count of periodic transfer + * + * @param [in] transfer_type : type of transfer + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_usbcore_des_periodic_transfer_cnt(u8 transfer_type) +{ + otg_dbg(OTG_DBG_OTGHCDI_KAL, + "otg_usbcore_des_periodic_transfer_cnt\n"); + + switch (transfer_type) { + case INT_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_int_reqs--; + break; + case ISOCH_TRANSFER: + hcd_to_bus(g_pUsbHcd)->bandwidth_isoc_reqs--; + break; + default: + otg_err(OTG_DBG_OTGHCDI_KAL, + "not proper TransferType\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + +/** + * u32 read_reg_32(u32 offset) + * + * @brief Reads the content of a register. + * + * @param [in] offset : offset of address of register to read. + * + * @return contents of the register. \n + * @remark call readl() + */ +static inline u32 read_reg_32(u32 offset) +{ + volatile unsigned int *reg_addr_p = ctrlr_base_reg_addr(offset); + + return *reg_addr_p; + /* return readl(reg_addr_p); */ +}; + +/** + * void write_reg_32( u32 offset, const u32 value) + * + * @brief Writes a register with a 32 bit value. + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to write + * + * @remark call writel() + */ +static inline void write_reg_32(u32 offset, const u32 value) +{ + volatile unsigned int *reg_addr_p = ctrlr_base_reg_addr(offset); + + *reg_addr_p = value; + /* writel( value, reg_addr_p ); */ +}; + +/** + * void update_reg_32(u32 offset, u32 value) + * + * @brief logic or operation + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to or + * + */ +static inline void update_reg_32(u32 offset, u32 value) +{ + write_reg_32(offset, (read_reg_32(offset) | value)); +} + +/** + * void clear_reg_32(u32 offset, u32 value) + * + * @brief logic not operation + * + * @param [in] offset : offset of address of register to write. + * @param [in] value : value to not + * + */ +static inline void clear_reg_32(u32 offset, u32 value) +{ + write_reg_32(offset, (read_reg_32(offset) & ~value)); +} + +#endif /* _S3C_OTG_HCDI_KAL_H_ */ + diff --git a/drivers/usb/host/shost/shost_list.h b/drivers/usb/host/shost/shost_list.h new file mode 100644 index 0000000..7283fb7 --- /dev/null +++ b/drivers/usb/host/shost/shost_list.h @@ -0,0 +1,188 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-list.h + * @brief list functions for otg + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) + * : Creating the initial version of this code + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _S3C_OTG_HCDI_LIST_H_ +#define _S3C_OTG_HCDI_LIST_H_ + +#include <linux/list.h> + +typedef struct list_head otg_list_head; + +#define otg_list_get_node(ptr, type, member) container_of(ptr, type, member) + + +#define otg_list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + + +/** + * void otg_list_push_next(otg_list_head *n, otg_list_head *list) + * + * @brief push a list node into the next of head + * + * @param [in] n : node to be pushed + * @param [in] otg_list_head : target list head + * + * @return void \n + */ + +static inline +void otg_list_push_next(otg_list_head *n, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_next\n"); + list_add(n, list); +} + +/** + * void otg_list_push_prev(otg_list_head *n, otg_list_head *list) + * + * @brief push a list node into the previous of head + * + * @param [in] n : node to be pushed + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_push_prev(otg_list_head *n, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_push_prev\n"); + list_add_tail(n, list); +} + +/** + * void otg_list_pop(otg_list_head *list_entity_p) + * + * @brief pop a list node + * + * @param [in] n : node to be poped + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_pop(otg_list_head *list_entity_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_pop\n"); + + if (list_entity_p->prev && list_entity_p->next) + list_del(list_entity_p); + else + pr_info("usb: otg_list_pop error\n"); +} + +/** + * void otg_list_move_next(otg_list_head *node_p, otg_list_head *list) + * + * @brief move a list to next of head + * + * @param [in] n : node to be moved + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_move_next(otg_list_head *node_p, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_next\n"); + list_move(node_p, list); +} + +/** + * void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list) + * + * @brief move a list to previous of head + * + * @param [in] n : node to be moved + * @param [in] otg_list_head : target list head + * + * @return void \n + */ +static inline +void otg_list_move_prev(otg_list_head *node_p, otg_list_head *list) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_move_prev\n"); + list_move_tail(node_p, list); +} + +/** + * bool otg_list_empty(otg_list_head *list) + * + * @brief check a list empty or not + * + * @param [in] list : node to check + * + * @return true : empty list \n + * false : not empty list + */ +static inline +bool otg_list_empty(otg_list_head *list) +{ + /* otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_empty\n"); */ + if (list_empty(list)) + return true; + return false; +} + +/** + * void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p) + * + * @brief merge two list + * + * @param [in] list_p : a head + * @param [in] head_p : target list head + * + * @return void \n + */ +static inline +void otg_list_merge(otg_list_head *list_p, otg_list_head *head_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_merge\n"); + list_splice(list_p, head_p); +} + +/** + * void otg_list_init(otg_list_head *list_p) + * + * @brief initialize a list + * + * @param [in] list_p : node to be initialized + * + * @return void \n + */ +static inline +void otg_list_init(otg_list_head *list_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_LIST, "otg_list_init\n"); + list_p->next = list_p; + list_p->prev = list_p; +} + +#endif /* _S3C_OTG_HCDI_LIST_H_ */ + diff --git a/drivers/usb/host/shost/shost_mem.h b/drivers/usb/host/shost/shost_mem.h new file mode 100644 index 0000000..ecbbed2 --- /dev/null +++ b/drivers/usb/host/shost/shost_mem.h @@ -0,0 +1,155 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * @file s3c-otg-hcdi-memory.h + * @brief header of s3c-otg-hcdi-memory \n + * @version + * -# Jun 9,2008 v1.0 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Creating the initial version of this code \n + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * @see None + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _S3C_OTG_HCDI_MEMORY_H_ +#define _S3C_OTG_HCDI_MEMORY_H_ + + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> + +/** + * @enum otg_mem_alloc_flag + * + * @brief enumeration for flag of memory allocation + */ +enum otg_mem_type { + USB_MEM_SYNC, + USB_MEM_ASYNC, + USB_MEM_DMA +}; + +/** + * int otg_mem_alloc(void ** addr_pp, u16 byte_size, u8 ubType); + * + * @brief allocating momory specified + * + * @param [inout] addr_pp : address to be assigned + * [in] byte_size : size of memory + * [in] type : enum otg_mem_type + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_alloc(void **addr_pp, u16 byte_size, + enum otg_mem_type type) +{ + gfp_t flags; + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_alloc\n"); + + switch (type) { + case USB_MEM_SYNC: + flags = GFP_KERNEL; + break; + case USB_MEM_ASYNC: + flags = GFP_ATOMIC; + break; + case USB_MEM_DMA: + flags = GFP_DMA; + break; + default: + otg_err(OTG_DBG_OTGHCDI_MEM, + "not proper enum otg_mem_type\n"); + return USB_ERR_FAIL; + } + + *addr_pp = kmalloc((size_t)byte_size, flags); + + if (*addr_pp == 0) { + otg_err(OTG_DBG_OTGHCDI_MEM, "kmalloc failed\n"); + return USB_ERR_FAIL; + } + return USB_ERR_SUCCESS; +} + +/** + * int otg_mem_copy(void *to_addr_p, void *from_addr_p, u16 byte_size); + * + * @brief memory copy + * + * @param [in] to_addr_p : target address + * [in] from_addr_p : source address + * [in] byte_size : size + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_copy(void *to_addr_p, + void *from_addr_p, u16 byte_size) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_copy\n"); + + memcpy(to_addr_p, from_addr_p, (size_t)byte_size); + + return USB_ERR_SUCCESS; +} + +/** + * int otg_mem_free(void * addr_p); + * + * @brief de-allocating memory + * + * @param [in] addr_p : target address to be de-allocated + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_free(void *addr_p) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_free\n"); + kfree(addr_p); + return USB_ERR_SUCCESS; +} + + +/** + * int otg_mem_set(void *addr_p, char value, u16 byte_size) + * + * @brief writing a value to memory + * + * @param [in] addr_p : target address + * [in] value : value to be written + * [in] byte_size : size + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + */ +static inline int +otg_mem_set(void *addr_p, char value, u16 byte_size) +{ + otg_dbg(OTG_DBG_OTGHCDI_MEM, "otg_mem_set\n"); + memset(addr_p, value, (size_t)byte_size); + return USB_ERR_SUCCESS; +} + + +#endif /* _S3C_OTG_HCDI_MEMORY_H_ */ diff --git a/drivers/usb/host/shost/shost_oci.c b/drivers/usb/host/shost/shost_oci.c new file mode 100644 index 0000000..4f47055 --- /dev/null +++ b/drivers/usb/host/shost/shost_oci.c @@ -0,0 +1,809 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : OCI.c + * [Description] : The file implement the external + * and internal functions of OCI + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/12 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and Implement functions of OCI + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + +static bool ch_enable[16]; + +/* OCI Internal Functions */ + +static void oci_set_global_interrupt(bool set) +{ + gahbcfg_t ahbcfg; + + otg_dbg(OTG_DBG_OCI, "oci_set_global_interrupt\n"); + + ahbcfg.d32 = 0; + ahbcfg.b.glblintrmsk = 1; + + if (set) + update_reg_32(GAHBCFG, ahbcfg.d32); + else + clear_reg_32(GAHBCFG, ahbcfg.d32); +} + +static int oci_channel_alloc(u8 *ch_num) +{ + hcchar_t hcchar = {.d32 = 0}; + u8 ch; + + for (ch = 0 ; ch < 16 ; ch++) { + if (ch_enable[ch] == true) { + + hcchar.d32 = read_reg_32(HCCHAR(ch)); + + if (hcchar.b.chdis == 0) { + *ch_num = ch; + ch_enable[ch] = false; + return USB_ERR_SUCCESS; + } + } + } + + return USB_ERR_FAIL; +} + + + +static void oci_flush_tx_fifo(u32 num) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_flush_tx_fifo\n"); + + greset.b.txfflsh = 1; + greset.b.txfnum = num; + write_reg_32(GRSTCTL, greset.d32); + + /* wait for flush to end */ + while (greset.b.txfflsh == 1) { + greset.d32 = read_reg_32(GRSTCTL); + if (++count > MAX_COUNT) + break; + }; + + /* Wait for 3 PHY Clocks*/ + udelay(30); +} + +static void oci_flush_rx_fifo(void) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_flush_rx_fifo\n"); + + greset.b.rxfflsh = 1; + write_reg_32(GRSTCTL, greset.d32); + + do { + greset.d32 = read_reg_32(GRSTCTL); + if (++count > MAX_COUNT) + break; + } while (greset.b.rxfflsh == 1); + + /* Wait for 3 PHY Clocks*/ + udelay(30); +} + +static void oci_config_flush_fifo(u32 mode) +{ + ghwcfg2_t hwcfg2 = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_config_flush_fifo\n"); + + hwcfg2.d32 = read_reg_32(GHWCFG2); + + /* Configure data FIFO sizes */ + if (hwcfg2.b.dynamic_fifo) { + /* Rx FIFO */ + write_reg_32(GRXFSIZ, 0x0000010D); + + /* Non-periodic Tx FIFO */ + write_reg_32(GNPTXFSIZ, 0x0080010D); + + if (mode == OTG_HOST_MODE) { + /* For Periodic transactions, */ + /* program HPTXFSIZ */ + } + } + + /* Flush the FIFOs */ + oci_flush_tx_fifo(0); + + oci_flush_rx_fifo(); +} + +static int oci_core_reset(void) +{ + grstctl_t greset = {.d32 = 0}; + u32 count = 0; + + otg_dbg(OTG_DBG_OCI, "oci_core_reset\n"); + + /* Wait for AHB master IDLE state. */ + do { + greset.d32 = read_reg_32(GRSTCTL); + mdelay(50); + + if (++count > 100) { + otg_dbg(OTG_DBG_OCI, "AHB status is not IDLE\n"); + return USB_ERR_FAIL; + } + + } while (greset.b.ahbidle != 1); + + /* Core Soft Reset */ + greset.b.csftrst = 1; + write_reg_32(GRSTCTL, greset.d32); + + /* Wait for 3 PHY Clocks*/ + mdelay(50); + return USB_ERR_SUCCESS; +} + +/** + * int oci_core_init(void) + * + * @brief process core initialize as s5pc110 otg spec + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_core_init(void) +{ + gahbcfg_t ahbcfg = {.d32 = 0}; + gusbcfg_t usbcfg = {.d32 = 0}; + ghwcfg2_t hwcfg2 = {.d32 = 0}; + gintmsk_t gintmsk = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_core_init\n"); + + usbcfg.d32 = read_reg_32(GUSBCFG); + otg_dbg(OTG_DBG_OCI, "before - GUSBCFG=0x%x, GOTGCTL=0x%x\n", + usbcfg.d32, read_reg_32(GOTGCTL)); + + /* PHY parameters */ + usbcfg.b.physel = 0; + usbcfg.b.phyif = 1; /* 16 bit */ + usbcfg.b.ulpi_utmi_sel = 0; /* UTMI */ + /* usbcfg.b.ddrsel = 1; */ /* DDR */ + usbcfg.b.usbtrdtim = 5; /* 16 bit UTMI */ + usbcfg.b.toutcal = 7; + usbcfg.b.forcehstmode = 1; + write_reg_32(GUSBCFG, usbcfg.d32); + + otg_dbg(OTG_DBG_OCI, + "after - GUSBCFG=0x%x, GOTGCTL=0x%x\n", + read_reg_32(GUSBCFG), + read_reg_32(GOTGCTL)); + + /* Reset after setting the PHY parameters */ + if (oci_core_reset() == USB_ERR_SUCCESS) { + /* Program the GAHBCFG Register.*/ + hwcfg2.d32 = read_reg_32(GHWCFG2); + + switch (hwcfg2.b.architecture) { + case HWCFG2_ARCH_SLAVE_ONLY: + otg_dbg(OTG_DBG_OCI, "Slave Only Mode\n"); + ahbcfg.b.nptxfemplvl = 0; + ahbcfg.b.ptxfemplvl = 0; + break; + + case HWCFG2_ARCH_EXT_DMA: + otg_dbg(OTG_DBG_OCI, "External DMA Mode - TBD!\n"); + break; + + case HWCFG2_ARCH_INT_DMA: + otg_dbg(OTG_DBG_OCI, "Internal DMA Setting\n"); + ahbcfg.b.dmaenable = true; + ahbcfg.b.hburstlen = INT_DMA_MODE_INCR4; + break; + + default: + otg_dbg(OTG_DBG_OCI, "ERR> hwcfg2\n "); + break; + } + write_reg_32(GAHBCFG, ahbcfg.d32); + + /* Program the GUSBCFG register.*/ + switch (hwcfg2.b.op_mode) { + case MODE_HNP_SRP_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_HNP_SRP_CAPABLE\n"); + usbcfg.b.hnpcap = 1; + usbcfg.b.srpcap = 1; + + otg_dbg(OTG_DBG_OCI, + "OTG_DBG_OCI : use HNP and SRP\n"); + break; + + case MODE_SRP_ONLY_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_ONLY_CAPABLE\n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_HNP_SRP_CAPABLE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_HNP_SRP_CAPABLE\n"); + usbcfg.b.hnpcap = 0; + break; + + case MODE_SRP_CAPABLE_DEVICE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_DEVICE\n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_SRP_CAPABLE_DEVICE: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_DEVICE\n"); + usbcfg.b.srpcap = 0; + break; + + case MODE_SRP_CAPABLE_HOST: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_SRP_CAPABLE_HOST\n"); + usbcfg.b.srpcap = 1; + break; + + case MODE_NO_SRP_CAPABLE_HOST: + otg_dbg(OTG_DBG_OCI, + "GHWCFG2 OP Mode : MODE_NO_SRP_CAPABLE_HOST\n"); + usbcfg.b.srpcap = 0; + break; + default: + otg_err(OTG_DBG_OCI, "ERR> hwcfg2\n"); + break; + } + write_reg_32(GUSBCFG, usbcfg.d32); + + /* Program the GINTMSK register.*/ + gintmsk.b.modemismatch = 1; + gintmsk.b.sofintr = 1; + /*gintmsk.b.otgintr = 1; */ + gintmsk.b.conidstschng = 1; + /*gintmsk.b.wkupintr = 1;*/ + gintmsk.b.disconnect = 1; + /*gintmsk.b.usbsuspend = 1;*/ + /*gintmsk.b.sessreqintr = 1;*/ + /*gintmsk.b.portintr = 1;*/ + /*gintmsk.b.hcintr = 1; */ + write_reg_32(GINTMSK, gintmsk.d32); + + return USB_ERR_SUCCESS; + + } else { + otg_err(OTG_DBG_OCI, "Core Reset FAIL\n"); + return USB_ERR_FAIL; + } +} + +/** + * int oci_host_init(void) + * + * @brief Process host initialize as s5pc110 spec + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ + +static int oci_host_init(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + hcfg_t hcfg = {.d32 = 0}; +#if 0 +/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/ + hprt_t hprt; + hprt.d32 = read_reg_32(HPRT); +#endif + + otg_dbg(OTG_DBG_OCI, "oci_host_init\n"); + + gintmsk.b.portintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + + hcfg.b.fslspclksel = HCFG_30_60_MHZ; + update_reg_32(HCFG, hcfg.d32); + +#if 0 +/*#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS*/ + /* turn on vbus */ + if (!hprt.b.prtpwr) { + hprt.b.prtpwr = 1; + write_reg_32(HPRT, hprt.d32); + + otg_dbg(true, "turn on Vbus\n"); + } +#endif + + oci_config_flush_fifo(OTG_HOST_MODE); + + return USB_ERR_SUCCESS; +} + +/** + * int oci_channel_init( u8 ch_num, struct stransfer *transfer) + * + * @brief Process channel initialize to prepare starting transfer + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_channel_init(u8 ch_num, struct stransfer *transfer) +{ + u32 intr_enable = 0; + gintmsk_t gintmsk = {.d32 = 0}; + hcchar_t hcchar = {.d32 = 0}; + hctsiz_t hctsiz = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_channel_init\n"); + + /* Clear channel information */ + write_reg_32(HCTSIZ(ch_num), 0); + write_reg_32(HCCHAR(ch_num), 0); + write_reg_32(HCINTMSK(ch_num), 0); + write_reg_32(HCINT(ch_num), INT_ALL);/*write clear*/ + write_reg_32(HCDMA(ch_num), 0); + + /* enable host channel interrupt in GINTSTS */ + gintmsk.b.hcintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + /* Enable the top level host channel interrupt in HAINT */ + intr_enable = (1 << ch_num); + update_reg_32(HAINTMSK, intr_enable); + /* unmask the down level host channel interrupt in HCINT */ + write_reg_32(HCINTMSK(ch_num), transfer->hc_reg.hc_int_msk.d32); + + /* Program the HCSIZn register with the endpoint characteristics for */ + hctsiz.b.xfersize = transfer->buf_size; + hctsiz.b.pktcnt = transfer->packet_cnt; + + /* Program the HCCHARn register with the endpoint characteristics for */ + hcchar.b.mps = transfer->ed_desc_p->max_packet_size; + hcchar.b.epnum = transfer->ed_desc_p->endpoint_num; + hcchar.b.epdir = transfer->ed_desc_p->is_ep_in; + hcchar.b.lspddev = (transfer->ed_desc_p->dev_speed == LOW_SPEED_OTG); + hcchar.b.eptype = transfer->ed_desc_p->endpoint_type; + hcchar.b.multicnt = transfer->ed_desc_p->mc; + hcchar.b.devaddr = transfer->ed_desc_p->device_addr; + + if (transfer->ed_desc_p->endpoint_type == INT_TRANSFER || + transfer->ed_desc_p->endpoint_type == ISOCH_TRANSFER) { + u32 uiFrameNum = 0; + uiFrameNum = oci_get_frame_num(); + + hcchar.b.oddfrm = (uiFrameNum % 2) ? 1 : 0; + + /* + * if transfer type is periodic transfer, + * must support sof interrupt + */ + + /* + gintmsk.b.sofintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); + */ + } + + if (transfer->ed_desc_p->endpoint_type == CONTROL_TRANSFER) { + + struct td *td_p; + td_p = (struct td *)transfer->parent_td; + + switch (td_p->standard_dev_req_info.conrol_transfer_stage) { + + case SETUP_STAGE: + hctsiz.b.pid = transfer->ed_status_p-> + control_data_toggle.setup; + hcchar.b.epdir = EP_OUT; + break; + + case DATA_STAGE: + hctsiz.b.pid = + transfer->ed_status_p->control_data_toggle.data; + hcchar.b.epdir = transfer->ed_desc_p->is_ep_in; + break; + + case STATUS_STAGE: + hctsiz.b.pid = transfer->ed_status_p-> + control_data_toggle.status; + + if (td_p->standard_dev_req_info.is_data_stage) { + hcchar.b.epdir = + ~(transfer->ed_desc_p->is_ep_in); + } else + hcchar.b.epdir = EP_IN; + break; + default: + break; + } + + } else + hctsiz.b.pid = transfer->ed_status_p->data_toggle; + + otg_dbg(OTG_DBG_OCI, "eptype %d, stage %d, dir %d\n", + transfer->ed_desc_p->endpoint_type, + ((struct td *)transfer->parent_td)-> + standard_dev_req_info.conrol_transfer_stage, + hcchar.b.epdir); + + hctsiz.b.dopng = transfer->ed_status_p->is_ping_enable; + write_reg_32(HCTSIZ(ch_num), hctsiz.d32); + transfer->ed_status_p->is_ping_enable = false; + + /* Write DMA Address */ + write_reg_32(HCDMA(ch_num), transfer->phy_addr); + + /* Wrote HCCHAR Register */ + write_reg_32(HCCHAR(ch_num), hcchar.d32); + + return USB_ERR_SUCCESS; +} + +static int oci_dev_init(void) +{ + otg_dbg(OTG_DBG_OCI, "oci_dev_init - do nothing.\n"); + /* return USB_ERR_FAIL; */ + return USB_ERR_SUCCESS; +} + +static int oci_init_mode(void) +{ + gintsts_t gintsts; + gintsts.d32 = read_reg_32(GINTSTS); + + otg_dbg(OTG_DBG_OCI, + "GINSTS = 0x%x,GINMSK = 0x%x.\n", + (unsigned int)gintsts.d32, + (unsigned int)read_reg_32(GINTMSK)); + + if (gintsts.b.curmode == OTG_HOST_MODE) { + otg_dbg(OTG_DBG_OCI, "HOST Mode\n"); + + if (oci_host_init() == USB_ERR_SUCCESS) { + return USB_ERR_SUCCESS; + + } else { + otg_dbg(OTG_DBG_OCI, "oci_host_init() Fail\n"); + return USB_ERR_FAIL; + } + + } else { /* Device Mode */ + otg_dbg(OTG_DBG_OCI, "DEVICE Mode\n"); + + if (oci_dev_init() == USB_ERR_SUCCESS) { + return USB_ERR_SUCCESS; + + } else { + otg_err(OTG_DBG_OCI, "oci_dev_init() Fail\n"); + return USB_ERR_FAIL; + } + } + + return USB_ERR_SUCCESS; +} + + + +/** + * int oci_start(void) + * + * @brief start to operate oci module by calling oci_core_init function + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_start(void) +{ + otg_dbg(OTG_DBG_OCI, "oci_start\n"); + + if (oci_core_init() == USB_ERR_SUCCESS) { + mdelay(50); + + if (oci_init_mode() == USB_ERR_SUCCESS) { + oci_set_global_interrupt(true); + return USB_ERR_SUCCESS; + + } else { + otg_dbg(OTG_DBG_OCI, "oci_init_mode() Fail\n"); + return USB_ERR_FAIL; + } + + } else { + otg_dbg(OTG_DBG_OCI, "oci_core_init() Fail\n"); + return USB_ERR_FAIL; + } +} + +/** + * int oci_stop(void) + * + * @brief stop to opearte otg core + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_stop(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_stop\n"); + + /* sometimes, port interrupt occured after removed + * otg host driver. so, we have to mask port interrupt. */ + write_reg_32(GINTMSK, gintmsk.d32); + + oci_set_global_interrupt(false); + + return USB_ERR_SUCCESS; +} + + +static int oci_sys_init(struct sec_otghost *otghost) +{ + struct sec_otghost_data *pdata = otghost->otg_data; + + if (pdata && pdata->phy_init && pdata->pdev) { + pr_info("otg phy_init\n"); + clk_enable(pdata->clk); + pdata->phy_init(0); + + writel(0, OTG_PHYPWR); + writel(0x87, OTG_PHYCLK); + } else + pr_info("otg phy_init failed\n"); + + return USB_ERR_SUCCESS; +} + +/** + * int oci_init(struct sec_otghost *otghost) + * + * @brief Initialize oci module. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +static int oci_init(struct sec_otghost *otghost) +{ + otg_mem_set((void *)ch_enable, true, sizeof(bool)*16); + otghost->ch_halt = false; + + if (oci_sys_init(otghost) == USB_ERR_SUCCESS) { + if (oci_core_reset() == USB_ERR_SUCCESS) { + + oci_set_global_interrupt(false); + return USB_ERR_SUCCESS; + + } else { + otg_dbg(OTG_DBG_OCI, "oci_core_reset() Fail\n"); + return USB_ERR_FAIL; + } + } + + return USB_ERR_FAIL; +} + +/*********************************************************************** + * API + */ + +/* also called by ischeduler.c */ +int oci_channel_dealloc(u8 ch_num) +{ + if (ch_num < 16 && ch_enable[ch_num] == false) { + ch_enable[ch_num] = true; + + write_reg_32(HCTSIZ(ch_num), 0); + write_reg_32(HCCHAR(ch_num), 0); + write_reg_32(HCINTMSK(ch_num), 0); + write_reg_32(HCINT(ch_num), INT_ALL); + write_reg_32(HCDMA(ch_num), 0); + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + +/** + * oci_start_transfer(struct sec_otghost *otghost, struct stransfer *transfer) + * + * @brief start transfer by using transfer information to receive from scheduler + * + * @param [IN] *transfer - information about transfer to write register by calling oci_channel_init function + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ + +u8 oci_start_transfer(struct sec_otghost *otghost, struct stransfer *transfer) +{ + hcchar_t hcchar = {.d32 = 0}; + + otg_dbg(OTG_DBG_OCI, "oci_start_transfer\n"); + + if (transfer->alloc_chnum == CH_NONE) { + + if (oci_channel_alloc(&(transfer->alloc_chnum)) + != USB_ERR_SUCCESS) { + + otg_dbg(OTG_DBG_OCI, + "Fail - Channel Allocation Error\n"); + return CH_NONE; + } + } + + oci_channel_init(transfer->alloc_chnum, transfer); + + hcchar.b.chen = 1; + update_reg_32(HCCHAR(transfer->alloc_chnum), hcchar.d32); + + return transfer->alloc_chnum; +} + +/** + * int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num) + * + * @brief stop to transfer even if transfering + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num) +{ + hcchar_t hcchar = {.d32 = 0}; + hcintmsk_t hcintmsk = {.d32 = 0}; + int count = 0, max_error_count = 10000; + + otg_dbg(OTG_DBG_OCI, + "step1: oci_stop_transfer ch=%d, hcchar=0x%x\n", + ch_num, read_reg_32(HCCHAR(ch_num))); + + if (ch_num > 16) + return USB_ERR_FAIL; + + otghost->ch_halt = true; + + hcintmsk.b.chhltd = 1; + update_reg_32(HCINTMSK(ch_num), hcintmsk.d32); + + hcchar.b.chdis = 1; + hcchar.b.chen = 1; + update_reg_32(HCCHAR(ch_num), hcchar.d32); + + /* wait for Channel Disabled Interrupt */ + do { + hcchar.d32 = read_reg_32(HCCHAR(ch_num)); + + if (count > max_error_count) { + otg_dbg(OTG_DBG_OCI, + "Warning!! oci_stop_transfer()" + "ChDis is not cleared! ch=%d, hcchar=0x%x\n", + ch_num, hcchar.d32); + break; + } + count++; + + } while (hcchar.b.chdis); + + oci_channel_dealloc(ch_num); + + clear_reg_32(HAINTMSK, ch_num); + write_reg_32(HCINT(ch_num), INT_ALL); + clear_reg_32(HCINTMSK(ch_num), INT_ALL); + + otghost->ch_halt = false; + otg_dbg(OTG_DBG_OCI, + "step2 : oci_stop_transfer ch=%d, hcchar=0x%x\n", + ch_num, read_reg_32(HCCHAR(ch_num))); + + return USB_ERR_SUCCESS; +} + +/** + * u32 oci_get_frame_num(void) + * + * @brief Get current frame number by reading register. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +u32 oci_get_frame_num(void) +{ + hfnum_t hfnum; + hfnum.d32 = read_reg_32(HFNUM); + + otg_dbg(OTG_DBG_OCI, "oci_get_frame_num=%d\n", hfnum.b.frnum); + + return hfnum.b.frnum; +} + +/** + * u16 oci_get_frame_interval(void) + * + * @brief Get current frame interval by reading register. + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * @remark + * + */ +u16 oci_get_frame_interval(void) +{ + hfir_t hfir; + hfir.d32 = read_reg_32(HFIR); + return hfir.b.frint; +} + +void oci_set_frame_interval(u16 interval) +{ + hfir_t hfir = {.d32 = 0}; + hfir.b.frint = interval; + write_reg_32(HFIR, hfir.d32); +} + diff --git a/drivers/usb/host/shost/shost_oci.h b/drivers/usb/host/shost/shost_oci.h new file mode 100644 index 0000000..8fa6bbb --- /dev/null +++ b/drivers/usb/host/shost/shost_oci.h @@ -0,0 +1,50 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] :s3c-otg-oci.h + * [Description] : The Header file defines the external + * and internal functions of OCI. + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2008/06/18 + * [Revision History] + * (1) 2008/06/25 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Added some functions and data structure of OCI + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _OCI_H_ +#define _OCI_H_ + + +#include <mach/map.h> /* virtual address*/ + +extern void otg_host_phy_init(void); +#include <mach/regs-clock.h> + +int oci_channel_dealloc(u8 ch_num); + +u8 oci_start_transfer(struct sec_otghost *otghost, struct stransfer *st_t); +int oci_stop_transfer(struct sec_otghost *otghost, u8 ch_num); + +u32 oci_get_frame_num(void); +u16 oci_get_frame_interval(void); +void oci_set_frame_interval(u16 intervl); + + +#endif /* _OCI_H_ */ diff --git a/drivers/usb/host/shost/shost_readyq.c b/drivers/usb/host/shost/shost_readyq.c new file mode 100644 index 0000000..9e41da5 --- /dev/null +++ b/drivers/usb/host/shost/shost_readyq.c @@ -0,0 +1,240 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : TransferReadyQ.c + * [Description] : The source file implements the internal + * functions of TransferReadyQ. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/04 + * [Revision History] + * (1) 2008/06/04 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of TransferReadyQ. + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ + +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +static struct trans_ready_q periodic_trans_ready_q; +static struct trans_ready_q nonperiodic_trans_ready_q; + +/******************************************************************************/ +/*! + * @name void init_transfer_ready_q(void) + * + * @brief this function initiates PeriodicTransferReadyQ + * and NonPeriodicTransferReadyQ. + * + * + * @param void + * + * @return void. + */ +/******************************************************************************/ +static void init_transfer_ready_q(void) +{ + otg_dbg(OTG_DBG_SCHEDULE, "start init_transfer_ready_q\n"); + + otg_list_init(&periodic_trans_ready_q.entity_list); + periodic_trans_ready_q.is_periodic = true; + periodic_trans_ready_q.entity_num = 0; + periodic_trans_ready_q.total_alloc_chnum = 0; + periodic_trans_ready_q.total_perio_bus_bandwidth = 0; + + otg_list_init(&nonperiodic_trans_ready_q.entity_list); + nonperiodic_trans_ready_q.is_periodic = false; + nonperiodic_trans_ready_q.entity_num = 0; + nonperiodic_trans_ready_q.total_alloc_chnum = 0; + nonperiodic_trans_ready_q.total_perio_bus_bandwidth = 0; +} + + +/******************************************************************************/ +/*! + * @name int insert_ed_to_ready_q(struct ed *insert_ed, + * bool is_first) + * + * @brief this function inserts ed_t * to TransferReadyQ. + * + * + * @param [IN] insert_ed + * = indicates the ed_t to be inserted to TransferReadyQ. + * [IN] is_first + * = indicates whether the insert_ed is inserted + * as first entry of TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if successes to insert the insert_ed to TransferReadyQ. + * USB_ERR_FAILl -if fails to insert the insert_ed to TransferReadyQ. + */ +/******************************************************************************/ +static int insert_ed_to_ready_q(struct ed *insert_ed, bool is_first) +{ + otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td_id %d, %d\n", + insert_ed->ed_id, insert_ed->num_td, is_first); + + if (insert_ed->ed_desc.endpoint_type == BULK_TRANSFER || + insert_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) { + + if (is_first) { + otg_list_push_next(&insert_ed->readyq_list, + &nonperiodic_trans_ready_q.entity_list); + } else { + otg_list_push_prev(&insert_ed->readyq_list, + &nonperiodic_trans_ready_q.entity_list); + } + nonperiodic_trans_ready_q.entity_num++; + + } else { + if (is_first) { + otg_list_push_next(&insert_ed->readyq_list, + &periodic_trans_ready_q.entity_list); + } else { + otg_list_push_prev(&insert_ed->readyq_list, + &periodic_trans_ready_q.entity_list); + } + periodic_trans_ready_q.entity_num++; + } + return USB_ERR_SUCCESS; +} + + +static u32 get_periodic_ready_q_entity_num(void) +{ + return periodic_trans_ready_q.entity_num; +} + +/******************************************************************************/ +/*! + * @name int remove_ed_from_ready_q(ed_t *remove_ed) + * + * @brief this function removes ed_t * from TransferReadyQ. + * + * + * @param [IN] remove_ed + * = indicate the ed_t to be removed from TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if successes to remove the remove_ed from TransferReadyQ. + * USB_ERR_FAILl -if fails to remove the remove_ed from TransferReadyQ. + */ +/******************************************************************************/ +static int remove_ed_from_ready_q(struct ed *remove_ed) +{ + otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td_id %d\n", + remove_ed->ed_id, remove_ed->num_td); + + otg_list_pop(&remove_ed->readyq_list); + + if (remove_ed->ed_desc.endpoint_type == BULK_TRANSFER || + remove_ed->ed_desc.endpoint_type == CONTROL_TRANSFER) { + + nonperiodic_trans_ready_q.entity_num--; + + } else + periodic_trans_ready_q.entity_num--; + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int get_ed_from_ready_q(bool is_periodic, + * struct td **get_ed) + * + * @brief this function returns the first entity of TransferReadyQ. + * if there are some ed_t on TransferReadyQ, + * this function pops first ed_t from TransferReadyQ. + * So, the TransferReadyQ don's has the poped ed_t. + * + * @param [IN] is_periodic + * = indicate whether Periodic or not + * [OUT] get_ed + * = indicate the double pointer to store the address of first entity + * on TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if successes to get frist ed_t from TransferReadyQ. + * USB_ERR_NO_ENTITY -if fails to get frist ed_t from TransferReadyQ + * because there is no entity on TransferReadyQ. + */ +/******************************************************************************/ + +static int get_ed_from_ready_q(struct ed **get_ed, bool is_periodic) +{ + otg_list_head *qlist = NULL; + + /* periodic transfer : control and bulk */ + if (is_periodic) { + + if (periodic_trans_ready_q.entity_num == 0) + return USB_ERR_NO_ENTITY; + + qlist = periodic_trans_ready_q.entity_list.next; + + if (!otg_list_empty(&periodic_trans_ready_q.entity_list)) { + + *get_ed = otg_list_get_node(qlist, + struct ed, readyq_list); + + if (qlist->prev == LIST_POISON2 || + qlist->next == LIST_POISON1) { + + printk(KERN_ERR "shost scheduler: get_ed_from_ready_q error\n"); + periodic_trans_ready_q.entity_num = 0; + + } else { + otg_list_pop(qlist); + periodic_trans_ready_q.entity_num--; + } + return USB_ERR_SUCCESS; + + } else + return USB_ERR_NO_ENTITY; + + /* non-periodic transfer : interrupt and ischo */ + } else { + + if (nonperiodic_trans_ready_q.entity_num == 0) + return USB_ERR_NO_ENTITY; + + qlist = nonperiodic_trans_ready_q.entity_list.next; + + if (!otg_list_empty(&nonperiodic_trans_ready_q.entity_list)) { + + *get_ed = otg_list_get_node(qlist, + struct ed, readyq_list); + + if (qlist->prev == LIST_POISON2 || + qlist->next == LIST_POISON1) { + + printk(KERN_ERR "s3c-otg-scheduler get_ed_from_ready_q error\n"); + nonperiodic_trans_ready_q.entity_num = 0; + + } else { + otg_list_pop(qlist); + nonperiodic_trans_ready_q.entity_num--; + } + return USB_ERR_SUCCESS; + } else + return USB_ERR_NO_ENTITY; + } +} + + diff --git a/drivers/usb/host/shost/shost_regs.h b/drivers/usb/host/shost/shost_regs.h new file mode 100644 index 0000000..d560567 --- /dev/null +++ b/drivers/usb/host/shost/shost_regs.h @@ -0,0 +1,747 @@ +/**************************************************************************** +* (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved +* +* [File Name] : s3c-otg-common-regdef.h +* [Description] : +* +* [Author] : Kyu Hyeok Jang { kyuhyeok.jang@samsung.com } +* [Department] : System LSI Division/Embedded Software Center +* [Created Date]: 2007/12/15 +* [Revision History] +* (1) 2007/12/15 by Kyu Hyeok Jang { kyuhyeok.jang@samsung.com } +* - Created +* +****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _OTG_REG_DEF_H +#define _OTG_REG_DEF_H + + +#define GOTGCTL 0x000 /* OTG Control & Status */ +#define GOTGINT 0x004 /* OTG Interrupt */ +#define GAHBCFG 0x008 /* Core AHB Configuration */ +#define GUSBCFG 0x00C /* Core USB Configuration */ +#define GRSTCTL 0x010 /* Core Reset */ +#define GINTSTS 0x014 /* Core Interrupt */ +#define GINTMSK 0x018 /* Core Interrupt Mask */ +#define GRXSTSR 0x01C /* Receive Status Debug Read/Status Read */ +#define GRXSTSP 0x020 /* Receive Status Debug Pop/Status Pop */ +#define GRXFSIZ 0x024 /* Receive FIFO Size */ +#define GNPTXFSIZ 0x028 /* Non-Periodic Transmit FIFO Size */ +#define GNPTXSTS 0x02C /* Non-Periodic Transmit FIFO/Queue Status */ +#define GPVNDCTL 0x034 /* PHY Vendor Control */ +#define GGPIO 0x038 /* General Purpose I/O */ +#define GUID 0x03C /* User ID */ +#define GSNPSID 0x040 /* Synopsys ID */ +#define GHWCFG1 0x044 /* User HW Config1 */ +#define GHWCFG2 0x048 /* User HW Config2 */ +#define GHWCFG3 0x04C /* User HW Config3 */ +#define GHWCFG4 0x050 /* User HW Config4 */ + +#define HPTXFSIZ 0x100 /* Host Periodic Transmit FIFO Size */ +#define DPTXFSIZ1 0x104 /* Device Periodic Transmit FIFO-1 Size */ +#define DPTXFSIZ2 0x108 /* Device Periodic Transmit FIFO-2 Size */ +#define DPTXFSIZ3 0x10C /* Device Periodic Transmit FIFO-3 Size */ +#define DPTXFSIZ4 0x110 /* Device Periodic Transmit FIFO-4 Size */ +#define DPTXFSIZ5 0x114 /* Device Periodic Transmit FIFO-5 Size */ +#define DPTXFSIZ6 0x118 /* Device Periodic Transmit FIFO-6 Size */ +#define DPTXFSIZ7 0x11C /* Device Periodic Transmit FIFO-7 Size */ +#define DPTXFSIZ8 0x120 /* Device Periodic Transmit FIFO-8 Size */ +#define DPTXFSIZ9 0x124 /* Device Periodic Transmit FIFO-9 Size */ +#define DPTXFSIZ10 0x128 /* Device Periodic Transmit FIFO-10 Size */ +#define DPTXFSIZ11 0x12C /* Device Periodic Transmit FIFO-11 Size */ +#define DPTXFSIZ12 0x130 /* Device Periodic Transmit FIFO-12 Size */ +#define DPTXFSIZ13 0x134 /* Device Periodic Transmit FIFO-13 Size */ +#define DPTXFSIZ14 0x138 /* Device Periodic Transmit FIFO-14 Size */ +#define DPTXFSIZ15 0x13C /* Device Periodic Transmit FIFO-15 Size */ + +/********************************************************************* + * Host Mode Registers + *********************************************************************/ +/* Host Global Registers */ + +/* Channel specific registers */ +#define HCCHAR_ADDR (0x500) +#define HCCHAR(n) (0x500 + ((n)*0x20)) +#define HCSPLT(n) (0x504 + ((n)*0x20)) +#define HCINT(n) (0x508 + ((n)*0x20)) +#define HCINTMSK(n) (0x50C + ((n)*0x20)) +#define HCTSIZ(n) (0x510 + ((n)*0x20)) +#define HCDMA(n) (0x514 + ((n)*0x20)) + +#define HCFG 0x400 /* Host Configuration */ +#define HFIR 0x404 /* Host Frame Interval */ +#define HFNUM 0x408 /* Host Frame Number/Frame Time Remaining */ +#define HPTXSTS 0x410 /* Host Periodic Transmit FIFO/Queue Status */ +#define HAINT 0x414 /* Host All Channels Interrupt */ +#define HAINTMSK 0x418 /* Host All Channels Interrupt Mask */ + +/* Host Port Control & Status Registers */ +#define HPRT 0x440 /* Host Port Control & Status */ + +#define EP_FIFO(n) (0x1000 + ((n)*0x1000)) + +#define PCGCCTL 0x0E00 + +#define BASE_REGISTER_OFFSET 0x0 +#define REGISTER_SET_SIZE 0x200 + +/* Power Reg Bits */ +#define USB_RESET 0x8 +#define MCU_RESUME 0x4 +#define SUSPEND_MODE 0x2 +#define SUSPEND_MODE_ENABLE_CTRL 0x1 + +/* EP0 CSR */ +#define EP0_OUT_PACKET_RDY 0x1 +#define EP0_IN_PACKET_RDY 0x2 +#define EP0_SENT_STALL 0x4 +#define DATA_END 0x8 +#define SETUP_END 0x10 +#define EP0_SEND_STALL 0x20 +#define SERVICED_OUT_PKY_RDY 0x40 +#define SERVICED_SETUP_END 0x80 + +/* IN_CSR1_REG Bit definitions */ +#define IN_PACKET_READY 0x1 +#define UNDER_RUN 0x4 /* Iso Mode Only */ +#define FLUSH_IN_FIFO 0x8 +#define IN_SEND_STALL 0x10 +#define IN_SENT_STALL 0x20 +#define IN_CLR_DATA_TOGGLE 0x40 + +/* OUT_CSR1_REG Bit definitions */ +#define OUT_PACKET_READY 0x1 +#define FLUSH_OUT_FIFO 0x10 +#define OUT_SEND_STALL 0x20 +#define OUT_SENT_STALL 0x40 +#define OUT_CLR_DATA_TOGGLE 0x80 + +/* IN_CSR2_REG Bit definitions */ +#define IN_DMA_INT_DISABLE 0x10 +#define SET_MODE_IN 0x20 + +#define EPTYPE (0x3<<18) +#define SET_TYPE_CONTROL (0x0<<18) +#define SET_TYPE_ISO (0x1<<18) +#define SET_TYPE_BULK (0x2<<18) +#define SET_TYPE_INTERRUPT (0x3<<18) + +#define AUTO_MODE 0x80 + +/* OUT_CSR2_REG Bit definitions */ +#define AUTO_CLR 0x40 +#define OUT_DMA_INT_DISABLE 0x20 + +/* Can be used for Interrupt and Interrupt Enable Reg - common bit def */ +#define EP0_IN_INT (0x1<<0) +#define EP1_IN_INT (0x1<<1) +#define EP2_IN_INT (0x1<<2) +#define EP3_IN_INT (0x1<<3) +#define EP4_IN_INT (0x1<<4) +#define EP5_IN_INT (0x1<<5) +#define EP6_IN_INT (0x1<<6) +#define EP7_IN_INT (0x1<<7) +#define EP8_IN_INT (0x1<<8) +#define EP9_IN_INT (0x1<<9) +#define EP10_IN_INT (0x1<<10) +#define EP11_IN_INT (0x1<<11) +#define EP12_IN_INT (0x1<<12) +#define EP13_IN_INT (0x1<<13) +#define EP14_IN_INT (0x1<<14) +#define EP15_IN_INT (0x1<<15) +#define EP0_OUT_INT (0x1<<16) +#define EP1_OUT_INT (0x1<<17) +#define EP2_OUT_INT (0x1<<18) +#define EP3_OUT_INT (0x1<<19) +#define EP4_OUT_INT (0x1<<20) +#define EP5_OUT_INT (0x1<<21) +#define EP6_OUT_INT (0x1<<22) +#define EP7_OUT_INT (0x1<<23) +#define EP8_OUT_INT (0x1<<24) +#define EP9_OUT_INT (0x1<<25) +#define EP10_OUT_INT (0x1<<26) +#define EP11_OUT_INT (0x1<<27) +#define EP12_OUT_INT (0x1<<28) +#define EP13_OUT_INT (0x1<<29) +#define EP14_OUT_INT (0x1<<30) +#define EP15_OUT_INT (0x1<<31) + +/* GOTGINT */ +#define SesEndDet (0x1<<2) + +/* GRSTCTL */ +#define TxFFlsh (0x1<<5) +#define RxFFlsh (0x1<<4) +#define INTknQFlsh (0x1<<3) +#define FrmCntrRst (0x1<<2) +#define HSftRst (0x1<<1) +#define CSftRst (0x1<<0) + +#define CLEAR_ALL_EP_INTRS 0xffffffff + +/* Bits to write to EP_INT_EN_REG - Use CLEAR */ +#define EP_INTERRUPT_DISABLE_ALL 0x0 + +/* DMA control register bit definitions */ +#define RUN_OB 0x80 +#define STATE 0x70 +#define DEMAND_MODE 0x8 +#define OUT_DMA_RUN 0x4 +#define IN_DMA_RUN 0x2 +#define DMA_MODE_EN 0x1 + + +#define REAL_PHYSICAL_ADDR_EP0_FIFO (0x520001c0) /*Endpoint 0 FIFO */ +#define REAL_PHYSICAL_ADDR_EP1_FIFO (0x520001c4) /*Endpoint 1 FIFO */ +#define REAL_PHYSICAL_ADDR_EP2_FIFO (0x520001c8) /*Endpoint 2 FIFO */ +#define REAL_PHYSICAL_ADDR_EP3_FIFO (0x520001cc) /*Endpoint 3 FIFO */ +#define REAL_PHYSICAL_ADDR_EP4_FIFO (0x520001d0) /*Endpoint 4 FIFO */ + +/* GAHBCFG */ +#define MODE_DMA (1<<5) +#define MODE_SLAVE (0<<5) +#define BURST_SINGLE (0<<1) +#define BURST_INCR (1<<1) +#define BURST_INCR4 (3<<1) +#define BURST_INCR8 (5<<1) +#define BURST_INCR16 (7<<1) +#define GBL_INT_MASK (0<<0) +#define GBL_INT_UNMASK (1<<0) + + +/********************************************************************* + * Global CSR + *********************************************************************/ + +/* GAHBCFG + * OTG AHB Configuration Register + * p1359 + * d201 + */ +typedef union _gahbcfg_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned glblintrmsk:1; +#define GAHBCFG_GLBINT_ENABLE 1 + unsigned hburstlen:4; +#define INT_DMA_MODE_SINGLE 0 +#define INT_DMA_MODE_INCR 1 +#define INT_DMA_MODE_INCR4 3 +#define INT_DMA_MODE_INCR8 5 +#define INT_DMA_MODE_INCR16 7 + unsigned dmaenable:1; +#define GAHBCFG_DMAENABLE 1 + unsigned reserved:1; + unsigned nptxfemplvl:1; + unsigned ptxfemplvl:1; +#define GAHBCFG_TXFEMPTYLVL_EMPTY 1 +#define GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0 + unsigned reserved9_31:22; + } b; +} gahbcfg_t; + + +/* GUSBCFG + * OTG USB Configuration Register + * p1360 + * d202 + */ +typedef union _gusbcfg_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned toutcal:3; + unsigned phyif:1; + unsigned ulpi_utmi_sel:1; + unsigned fsintf:1; + unsigned physel:1; + unsigned ddrsel:1; + unsigned srpcap:1; + unsigned hnpcap:1; + unsigned usbtrdtim:4; + unsigned nptxfrwnden:1; + unsigned phylpwrclksel:1; + unsigned reserved:13; + unsigned forcehstmode:1; + unsigned reserved2:2; + } b; +} gusbcfg_t; + + +/* GRSTCTL + * Core Reset Register + * p1362 + * d208 + */ +typedef union grstctl_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned csftrst:1; + unsigned hsftrst:1; + unsigned hstfrm:1; + unsigned intknqflsh:1; + unsigned rxfflsh:1; + unsigned txfflsh:1; + unsigned txfnum:5; + unsigned reserved11_29:19; + unsigned dmareq:1; + unsigned ahbidle:1; + } b; +} grstctl_t; + + +/* GINTSTS + * Core Interrupt Register + * p1364 + * d212 + */ +typedef union _gintsts_t { + /** raw register data */ + u32 d32; +#define SOF_INTR_MASK 0x0008 + /** register bits */ + struct { +#define HOST_MODE 1 +#define DEVICE_MODE 0 + unsigned curmode:1; +#define OTG_HOST_MODE 1 +#define OTG_DEVICE_MODE 0 + + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned reserved8:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned intokenrx:1; + unsigned epmismatch:1; + unsigned inepint:1; + unsigned outepintr:1; + unsigned incompisoin:1; + unsigned incompisoout:1; + unsigned reserved22_23:2; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned reserved27:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintsts_t; + + +/* GINTMSK + * Core Interrupt Mask Register + * p1369 + * d217 + */ +typedef union _gintmsk_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned reserved0:1; + unsigned modemismatch:1; + unsigned otgintr:1; + unsigned sofintr:1; + unsigned rxstsqlvl:1; + unsigned nptxfempty:1; + unsigned ginnakeff:1; + unsigned goutnakeff:1; + unsigned reserved8:1; + unsigned i2cintr:1; + unsigned erlysuspend:1; + unsigned usbsuspend:1; + unsigned usbreset:1; + unsigned enumdone:1; + unsigned isooutdrop:1; + unsigned eopframe:1; + unsigned reserved16:1; + unsigned epmismatch:1; + unsigned inepintr:1; + unsigned outepintr:1; + unsigned incompisoin:1; + unsigned incompisoout:1; + unsigned reserved22_23:2; + unsigned portintr:1; + unsigned hcintr:1; + unsigned ptxfempty:1; + unsigned reserved27:1; + unsigned conidstschng:1; + unsigned disconnect:1; + unsigned sessreqintr:1; + unsigned wkupintr:1; + } b; +} gintmsk_t; + + +/* GRXSTSR/GRXSTSP + * Host Mode Receive Status Debug Read/Status Read and Pop Registers + * p1370 + * d219 + */ +typedef union _grxstsr_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned chnum:4; + unsigned bcnt:11; + unsigned dpid:2; + unsigned pktsts:4; + unsigned Reserved:11; + } b; +} grxstsr_t; + + +/* User HW Config2 Register + * d230 + */ +typedef union _ghwcfg2_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + /* GHWCFG2 */ + unsigned op_mode:3; +#define MODE_HNP_SRP_CAPABLE 0 +#define MODE_SRP_ONLY_CAPABLE 1 +#define MODE_NO_HNP_SRP_CAPABLE 2 +#define MODE_SRP_CAPABLE_DEVICE 3 +#define MODE_NO_SRP_CAPABLE_DEVICE 4 +#define MODE_SRP_CAPABLE_HOST 5 +#define MODE_NO_SRP_CAPABLE_HOST 6 + + unsigned architecture:2; +#define HWCFG2_ARCH_SLAVE_ONLY 0x00 +#define HWCFG2_ARCH_EXT_DMA 0x01 +#define HWCFG2_ARCH_INT_DMA 0x02 + + unsigned point2point:1; + unsigned hs_phy_type:2; + unsigned fs_phy_type:2; + unsigned num_dev_ep:4; + unsigned num_host_chan:4; + unsigned perio_ep_supported:1; + unsigned dynamic_fifo:1; + unsigned rx_status_q_depth:2; + unsigned nonperio_tx_q_depth:2; + unsigned host_perio_tx_q_depth:2; + unsigned dev_token_q_depth:5; + unsigned reserved31:1; + } b; +} ghwcfg2_t; + + +/********************************************************************* + * Host Mode Registers + *********************************************************************/ + +/* Host Configuration Register + * d247 + */ +typedef union _hcfg_t { + /** raw register data */ + u32 d32; + + /** register bits */ + struct { + /** FS/LS Phy Clock Select */ + unsigned fslspclksel:2; +#define HCFG_30_60_MHZ 0 +#define HCFG_48_MHZ 1 +#define HCFG_6_MHZ 2 + + /** FS/LS Only Support */ + unsigned fslssupp:1; + unsigned reserved3_31:29; + } b; +} hcfg_t; + + +/* Host Frame Interval Register + * d247 + */ +typedef union _hfir_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned frint:16; + unsigned Reserved:16; + } b; +} hfir_t; + +/* Host Frame Number/Frame Time Remaining Register + * d248 + */ +typedef union _hfnum_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned frnum:16; +#define HFNUM_MAX_FRNUM 0x3FFF + unsigned frrem:16; + } b; +} hfnum_t; + + +/* Host Channel Interrupt Mask Register + d257 + */ +typedef union _hcintmsk_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + unsigned xfercompl:1; + unsigned chhltd:1; + unsigned ahberr:1; + unsigned stall:1; + unsigned nak:1; + unsigned ack:1; + unsigned nyet:1; + unsigned xacterr:1; + unsigned bblerr:1; + unsigned frmovrun:1; + unsigned datatglerr:1; + unsigned reserved:21; + } b; +} hcintmsk_t; + + +/* Host Channel Interrupt Register + d254 + */ +typedef union _hcintn_t { + u32 d32; + struct { + u32 xfercompl:1; + u32 chhltd:1; + u32 abherr:1; + u32 stall:1; + u32 nak:1; + u32 ack:1; + u32 nyet:1; + u32 xacterr:1; + u32 bblerr:1; + u32 frmovrun:1; + u32 datatglerr:1; + u32 reserved:21; + } b; +} hcintn_t; + + +/* Host All Channels Interrupt Register + d250 + */ + +#define MAX_COUNT 10000 +#define INT_ALL 0xffffffff + +typedef union _haint_t { + u32 d32; + struct { + u32 channel_intr_0:1; + u32 channel_intr_1:1; + u32 channel_intr_2:1; + u32 channel_intr_3:1; + u32 channel_intr_4:1; + u32 channel_intr_5:1; + u32 channel_intr_6:1; + u32 channel_intr_7:1; + u32 channel_intr_8:1; + u32 channel_intr_9:1; + u32 channel_intr_10:1; + u32 channel_intr_11:1; + u32 channel_intr_12:1; + u32 channel_intr_13:1; + u32 channel_intr_14:1; + u32 channel_intr_15:1; + u32 reserved1:16; + } b; +} haint_t; + + +/* Host Port Control and Status Register + * d250 + */ +typedef union _hprt_t { + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned prtconnsts:1; + unsigned prtconndet:1; + unsigned prtena:1; + unsigned prtenchng:1; + unsigned prtovrcurract:1; + unsigned prtovrcurrchng:1; + unsigned prtres:1; + unsigned prtsusp:1; + unsigned prtrst:1; + unsigned reserved9:1; + unsigned prtlnsts:2; + unsigned prtpwr:1; + unsigned prttstctl:4; + unsigned prtspd:2; +#define HPRT0_PRTSPD_HIGH_SPEED 0 +#define HPRT0_PRTSPD_FULL_SPEED 1 +#define HPRT0_PRTSPD_LOW_SPEED 2 + unsigned reserved19_31:13; + } b; +} hprt_t; + + +/* Port status for the HC */ +#define HCD_DRIVE_RESET 0x0001 +#define HCD_SEND_SETUP 0x0002 + +#define HC_MAX_PKT_COUNT 511 +#define HC_MAX_TRANSFER_SIZE 65535 +#define MAXP_SIZE_64BYTE 64 +#define MAXP_SIZE_512BYTE 512 +#define MAXP_SIZE_1024BYTE 1024 + +/* Host Channel-n Charracteristics Register + * d253 + */ +typedef union _hcchar_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + /* Maximum packet size in bytes */ + unsigned mps:11; + + /* Endpoint number */ + unsigned epnum:4; + + /* 0: OUT, 1: IN */ + unsigned epdir:1; +#define HCDIR_OUT 0 +#define HCDIR_IN 1 + + unsigned reserved:1; + + /* 0: Full/high speed device, 1: Low speed device */ + unsigned lspddev:1; + + /* 0: Control, 1: Isoc, 2: Bulk, 3: Intr */ + unsigned eptype:2; +#define OTG_EP_TYPE_CONTROL 0 +#define OTG_EP_TYPE_ISOC 1 +#define OTG_EP_TYPE_BULK 2 +#define OTG_EP_TYPE_INTR 3 + + /* Packets per frame for periodic transfers. 0 is reserved. */ + unsigned multicnt:2; + + /* Device address */ + unsigned devaddr:7; + + /* Frame to transmit periodic transaction. */ + /* 0: even, 1: odd */ + unsigned oddfrm:1; + + /* Channel disable */ + unsigned chdis:1; + + /* Channel enable */ + unsigned chen:1; + } b; +} hcchar_t; + +/* Host Channel-n Transfer Size Register + * d257 + */ +typedef union _hctsiz_t { + /* raw register data */ + u32 d32; + + /* register bits */ + struct { + /* Total transfer size in bytes */ + unsigned xfersize:19; + + /* Data packets to transfer */ + unsigned pktcnt:10; + + /* Packet ID for next data packet */ + /* 0: DATA0 */ + /* 1: DATA2 */ + /* 2: DATA1 */ + /* 3: MDATA (non-Control), SETUP (Control) */ + unsigned pid:2; +#define HCTSIZ_DATA0 0 +#define HCTSIZ_DATA1 2 +#define HCTSIZ_DATA2 1 +#define HCTSIZ_MDATA 3 +#define HCTSIZ_SETUP 3 + + /* Do PING protocol when 1 */ + unsigned dopng:1; + } b; +} hctsiz_t; + + +/* The Power and Clock Gating Control Register + d292 + */ +typedef union _pcgcctl_t { + + /** raw register data */ + u32 d32; + /** register bits */ + struct { + unsigned stoppclk:1; + unsigned gatehclk:1; + unsigned pwrclmp:1; + unsigned rstpdwnmodule:1; + unsigned physuspended:1; + unsigned Reserved5_31:27; + } b; +} pcgcctl_t; + + +#endif + + diff --git a/drivers/usb/host/shost/shost_roothub.c b/drivers/usb/host/shost/shost_roothub.c new file mode 100644 index 0000000..3e2541d --- /dev/null +++ b/drivers/usb/host/shost/shost_roothub.c @@ -0,0 +1,579 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : RootHub.c + * [Description] : The file implement the external + * and internal functions of RootHub + * [Author] : Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * [Department] : System LSI Division/Embedded S/W Platform + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Jang Kyu Hyeok { kyuhyeok.jang@samsung.com } + * - Created this file and implements functions of RootHub + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + +/** + * void bus_suspend(void) + * + * @brief Make suspend status when this platform support PM Mode + * + * @param None + * + * @return None + * + * @remark + * + */ +static void bus_suspend(void) +{ + hprt_t hprt; + pcgcctl_t pcgcctl; + + otg_dbg(OTG_DBG_ROOTHUB, " bus_suspend\n"); + + hprt.d32 = 0; + pcgcctl.d32 = 0; + + hprt.b.prtsusp = 1; + update_reg_32(HPRT, hprt.d32); + + pcgcctl.b.pwrclmp = 1; + update_reg_32(PCGCCTL, pcgcctl.d32); + udelay(1); + + pcgcctl.b.rstpdwnmodule = 1; + update_reg_32(PCGCCTL, pcgcctl.d32); + udelay(1); + + pcgcctl.b.stoppclk = 1; + update_reg_32(PCGCCTL, pcgcctl.d32); + udelay(1); +} + +/** + * int bus_resume(struct sec_otghost *otghost) + * + * @brief Make resume status when this platform support PM Mode + * + * @param None + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static int bus_resume(struct sec_otghost *otghost) +{ + /* + hprt_t hprt; + pcgcctl_t pcgcctl; + hprt.d32 = 0; + pcgcctl.d32 = 0; + + pcgcctl.b.stoppclk = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.pwrclmp = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + pcgcctl.b.rstpdwnmodule = 1; + clear_reg_32(PCGCCTL,pcgcctl.d32); + udelay(1); + + hprt.b.prtres = 1; + update_reg_32(HPRT, hprt.d32); + mdelay(20); + + clear_reg_32(HPRT, hprt.d32); + */ + otg_dbg(OTG_DBG_ROOTHUB, "bus_resume()......\n"); + otg_dbg(OTG_DBG_ROOTHUB, "wait for 50 ms...\n"); + + mdelay(50); + + if (oci_init(otghost) == USB_ERR_SUCCESS) { + if (oci_start() == USB_ERR_SUCCESS) { + otg_dbg(OTG_DBG_ROOTHUB, "OTG Init Success\n"); + return USB_ERR_SUCCESS; + } + } + + return USB_ERR_FAIL; +} + +static void setPortPower(bool on) +{ +#ifdef CONFIG_USB_S3C_OTG_HOST_DTGDRVVBUS + hprt_t hprt = {.d32 = 0}; + + if (on) { + hprt.d32 = read_reg_32(HPRT); + + if (!hprt.b.prtpwr) { + /*hprt.d32 = 0;*/ + hprt.b.prtpwr = 1; + write_reg_32(HPRT, hprt.d32); + } + } else { + hprt.b.prtpwr = 1; + clear_reg_32(HPRT, hprt.d32); + } +#else + otg_dbg(true, "turn %s Vbus\n", on ? "on" : "off"); +#endif +} + +/** + * int get_otg_port_status(const u8 port, char* status) + * + * @brief Get port change bitmap information + * + * @param [IN] port : port number + * [OUT] status : buffer to store bitmap information + * + * @returnUSB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static inline int get_otg_port_status( + struct usb_hcd *hcd, const u8 port, char *status) +{ + + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + /* return root_hub_feature(port, GetPortStatus, NULL, status); */ +#if 0 + /* for debug */ + hprt_t hprt; + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ROOTHUB, + "HPRT:spd=%d,pwr=%d,lnsts=%d,rst=%d,susp=%d,res=%d," + "ovrcurract=%d,ena=%d,connsts=%d\n", + hprt.b.prtspd, + hprt.b.prtpwr, + hprt.b.prtlnsts, + hprt.b.prtrst, + hprt.b.prtsusp, + hprt.b.prtres, + hprt.b.prtovrcurract, + hprt.b.prtena, + hprt.b.prtconnsts); + +#endif + status[port] = 0; + status[port] |= (otghost->port_flag.b.port_connect_status_change || + otghost->port_flag.b.port_reset_change || + otghost->port_flag.b.port_enable_change || + otghost->port_flag.b.port_suspend_change || + otghost->port_flag.b.port_over_current_change) << 1; + +#if 0 + /* for debug */ + otg_dbg(OTG_DBG_ROOTHUB, + "connect:%d,reset:%d,enable:%d,suspend:%d,over_current:%d\n", + otghost->port_flag.b.port_connect_status_change, + otghost->port_flag.b.port_reset_change, + otghost->port_flag.b.port_enable_change, + otghost->port_flag.b.port_suspend_change, + otghost->port_flag.b.port_over_current_change); +#endif + + if (status[port]) { + otg_dbg(OTG_DBG_ROOTHUB, + " Root port status changed\n"); + otg_dbg(OTG_DBG_ROOTHUB, + " port_connect_status_change: %d\n", + otghost->port_flag.b.port_connect_status_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_reset_change: %d\n", + otghost->port_flag.b.port_reset_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_enable_change: %d\n", + otghost->port_flag.b.port_enable_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_suspend_change: %d\n", + otghost->port_flag.b.port_suspend_change); + otg_dbg(OTG_DBG_ROOTHUB, + " port_over_current_change: %d\n", + otghost->port_flag.b.port_over_current_change); + } + + return (status[port] != 0); +} + +/** + * int reset_and_enable_port(struct usb_hcd *hcd, const u8 port) + * + * @brief Reset port and make enable status the specific port + * + * @param [IN] port : port number + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static int reset_and_enable_port(struct usb_hcd *hcd, const u8 port) +{ + hprt_t hprt; + u32 count = 0; + u32 max_error_count = 10000; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + hprt.d32 = read_reg_32(HPRT); + + otg_dbg(OTG_DBG_ROOTHUB, + " reset_and_enable_port\n"); + + if (hprt.b.prtconnsts == 0) { + otg_dbg(OTG_DBG_ROOTHUB, + "No Attached Device, HPRT = 0x%x\n", hprt.d32); + + otghost->port_flag.b.port_connect_status_change = 1; + otghost->port_flag.b.port_connect_status = 0; + + return USB_ERR_FAIL; + } + + if (!hprt.b.prtena) { + hprt.b.prtrst = 1; /* drive reset */ + write_reg_32(HPRT, hprt.d32); + + mdelay(60); + hprt.b.prtrst = 0; + write_reg_32(HPRT, hprt.d32); + + do { + hprt.d32 = read_reg_32(HPRT); + + if (count > max_error_count) { + otg_dbg(OTG_DBG_ROOTHUB, + "Port Reset Fail : HPRT : 0x%x\n", hprt.d32); + return USB_ERR_FAIL; + } + count++; + + } while (!hprt.b.prtena); + + } + return USB_ERR_SUCCESS; +} + +/** + * int root_hub_feature( + * struct usb_hcd *hcd, + * const u8 port, + * const u16 type_req, + * const u16 feature, + * void* buf) + * + * @brief Get port change bitmap information + * + * @param [IN] port : port number + * [IN] type_req : request type of hub feature as usb 2.0 spec + * [IN] feature : hub feature as usb 2.0 spec + * [OUT] status : buffer to store results + * + * @return USB_ERR_SUCCESS : If success \n + * USB_ERR_FAIL : If call fail \n + * + * @remark + * + */ +static inline int root_hub_feature( + struct usb_hcd *hcd, + const u8 port, + const u16 type_req, + const u16 feature, + void *buf) +{ + int retval = USB_ERR_SUCCESS; + struct hub_descriptor *desc = NULL; + u32 port_status = 0; + hprt_t hprt = {.d32 = 0}; + struct sec_otghost *otghost = hcd_to_sec_otghost(hcd); + + otg_dbg(OTG_DBG_ROOTHUB, " root_hub_feature\n"); + + switch (type_req) { + case ClearHubFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearHubFeature\n"); + switch (feature) { + case C_HUB_LOCAL_POWER: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearHubFeature -C_HUB_LOCAL_POWER\n"); + break; + case C_HUB_OVER_CURRENT: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearHubFeature -C_HUB_OVER_CURRENT\n"); + /* Nothing required here */ + break; + default: + retval = USB_ERR_FAIL; + } + break; + + case ClearPortFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case ClearPortFeature\n"); + switch (feature) { + case USB_PORT_FEAT_ENABLE: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_ENABLE\n"); + hprt.b.prtena = 1; + update_reg_32(HPRT, hprt.d32); + break; + + case USB_PORT_FEAT_SUSPEND: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_SUSPEND\n"); + bus_resume(otghost); + break; + + case USB_PORT_FEAT_POWER: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_POWER\n"); + setPortPower(false); + break; + + case USB_PORT_FEAT_INDICATOR: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_INDICATOR\n"); + /* Port inidicator not supported */ + break; + + case USB_PORT_FEAT_C_CONNECTION: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_CONNECTION\n"); + /* Clears drivers internal connect status change flag */ + otghost->port_flag.b.port_connect_status_change = 0; + break; + + case USB_PORT_FEAT_C_RESET: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_RESET\n"); + /* Clears the driver's internal Port Reset Change flag*/ + otghost->port_flag.b.port_reset_change = 0; + break; + + case USB_PORT_FEAT_C_ENABLE: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_ENABLE\n"); + /* Clears Port Enable/Disable Change flag */ + otghost->port_flag.b.port_enable_change = 0; + break; + + case USB_PORT_FEAT_C_SUSPEND: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature -USB_PORT_FEAT_C_SUSPEND\n"); + /* Clears the driver's internal Port Suspend + * Change flag, which is set when resume signaling on + * the host port is complete */ + otghost->port_flag.b.port_suspend_change = 0; + break; + + case USB_PORT_FEAT_C_OVER_CURRENT: + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature - USB_PORT_FEAT_C_OVER_CURRENT\n"); + otghost->port_flag.b.port_over_current_change = 0; + break; + + default: + retval = USB_ERR_FAIL; + otg_dbg(OTG_DBG_ROOTHUB, + "case ClearPortFeature - FAIL\n"); + } + break; + + case GetHubDescriptor: + otg_dbg(OTG_DBG_ROOTHUB, "case GetHubDescriptor\n"); + desc = (struct hub_descriptor *)buf; + desc->desc_length = 9; + desc->desc_type = 0x29; + desc->port_number = 1; + desc->hub_characteristics = 0x08; + desc->power_on_to_power_good = 1; + desc->hub_control_current = 0; + desc->DeviceRemovable[0] = 0; + desc->DeviceRemovable[1] = 0xff; + break; + + case GetHubStatus: + otg_dbg(OTG_DBG_ROOTHUB, "case GetHubStatus\n"); + otg_mem_set(buf, 0, 4); + break; + + case GetPortStatus: + otg_dbg(OTG_DBG_ROOTHUB, "case GetPortStatus\n"); + + if (otghost->port_flag.b.port_connect_status_change) { + port_status |= (1 << USB_PORT_FEAT_C_CONNECTION); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_CONNECTION\n"); + } + + if (otghost->port_flag.b.port_enable_change) { + port_status |= (1 << USB_PORT_FEAT_C_ENABLE); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_ENABLE\n"); + } + + if (otghost->port_flag.b.port_suspend_change) { + port_status |= (1 << USB_PORT_FEAT_C_SUSPEND); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_SUSPEND\n"); + } + + if (otghost->port_flag.b.port_reset_change) { + port_status |= (1 << USB_PORT_FEAT_C_RESET); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_RESET\n"); + } + + if (otghost->port_flag.b.port_over_current_change) { + port_status |= (1 << USB_PORT_FEAT_C_OVER_CURRENT); + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - USB_PORT_FEAT_C_OVER_CURRENT\n"); + } + + if (!otghost->port_flag.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return 0's for the remainder of the port status + * since the port register can't be read if the core + * is in device mode. + */ + otg_dbg(OTG_DBG_ROOTHUB, + "case GetPortStatus - disconnected\n"); + + *((__le32 *)buf) = cpu_to_le32(port_status); + /*break;*/ + } + + hprt.d32 = read_reg_32(HPRT); + + if (hprt.b.prtconnsts) + port_status |= (1 << USB_PORT_FEAT_CONNECTION); + + if (hprt.b.prtena) + port_status |= (1 << USB_PORT_FEAT_ENABLE); + + if (hprt.b.prtsusp) + port_status |= (1 << USB_PORT_FEAT_SUSPEND); + + if (hprt.b.prtovrcurract) + port_status |= (1 << USB_PORT_FEAT_OVER_CURRENT); + + if (hprt.b.prtrst) + port_status |= (1 << USB_PORT_FEAT_RESET); + + if (hprt.b.prtpwr) + port_status |= (1 << USB_PORT_FEAT_POWER); + + if (hprt.b.prtspd == 0) { + port_status |= USB_PORT_STAT_HIGH_SPEED; + } else { + if (hprt.b.prtspd == 2) + port_status |= USB_PORT_STAT_LOW_SPEED; + } + + if (hprt.b.prttstctl) + port_status |= (1 << USB_PORT_FEAT_TEST); + + *((__le32 *)buf) = cpu_to_le32(port_status); + + otg_dbg(OTG_DBG_ROOTHUB, "port_status=0x%x.\n", port_status); + break; + + case SetHubFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case SetHubFeature\n"); + /* No HUB features supported */ + break; + + case SetPortFeature: + otg_dbg(OTG_DBG_ROOTHUB, "case SetPortFeature\n"); + if (!otghost->port_flag.b.port_connect_status) { + /* + * The port is disconnected, which means the core is + * either in device mode or it soon will be. Just + * return without doing anything since the port + * register can't be written if the core is in device + * mode. + */ + otg_dbg(OTG_DBG_ROOTHUB, + "case SetPortFeature - disconnected\n"); + + /*break;*/ + } + + switch (feature) { + case USB_PORT_FEAT_SUSPEND: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_SUSPEND\n"); + bus_suspend(); + break; + + case USB_PORT_FEAT_POWER: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_POWER\n"); + setPortPower(true); + break; + + case USB_PORT_FEAT_RESET: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_RESET\n"); + retval = reset_and_enable_port(hcd, port); + /* + if(retval == USB_ERR_SUCCESS) + wake_lock(&otghost->wake_lock); + */ + break; + + case USB_PORT_FEAT_INDICATOR: + otg_dbg(true, + "case SetPortFeature -USB_PORT_FEAT_INDICATOR\n"); + break; + + default: + otg_dbg(true, "case SetPortFeature -USB_ERR_FAIL\n"); + retval = USB_ERR_FAIL; + break; + } + break; + + default: + retval = USB_ERR_FAIL; + otg_err(true, "root_hub_feature() Function Error\n"); + break; + } + + if (retval != USB_ERR_SUCCESS) + retval = USB_ERR_FAIL; + + return retval; +} + diff --git a/drivers/usb/host/shost/shost_scheduler.c b/drivers/usb/host/shost/shost_scheduler.c new file mode 100644 index 0000000..1739298 --- /dev/null +++ b/drivers/usb/host/shost/shost_scheduler.c @@ -0,0 +1,420 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.c + * [Description] : The source file implements the internal + * functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/2/10 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) \n + * : Optimizing for performance \n + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +****************************************************************************/ + +#include "shost.h" + +#include "shost_readyq.c" +#include "shost_schedulerlib.c" + +void init_scheduler(void) +{ + /*init_scheduling();*/ + init_transfer_ready_q(); +} + +/******************************************************************************/ +/*! + * @name int reserve_used_resource_for_periodic(u32 usb_time) + * + * @brief this function reserves the necessary resource of USB Transfer + * for Periodic Transfer. + * So, this function firstly checks there ares some available USB Time + * and Channel resource for USB Transfer. + * if there exists necessary resources for Periodic Transfer, + * then reserves the resource. + * + * @param [IN] usb_time + * - indicates the USB Time for the USB Transfer. + * + * @return USB_ERR_SUCCESS - if success to insert pInsertED to S3CScheduler. + * USB_ERR_NO_BANDWIDTH - if fail to reserve the USB Bandwidth. + * USB_ERR_NO_CHANNEL - if fail to reserve the Channel. + */ +/******************************************************************************/ + +int reserve_used_resource_for_periodic(u32 usb_time, + u8 dev_speed, u8 trans_type) +{ + if (inc_perio_bus_time(usb_time, dev_speed) == USB_ERR_SUCCESS) { + + if (inc_perio_chnum() == USB_ERR_SUCCESS) { + + otg_usbcore_inc_usb_bandwidth(usb_time); + otg_usbcore_inc_periodic_transfer_cnt(trans_type); + + return USB_ERR_SUCCESS; + + } else { + dec_perio_bus_time(usb_time); + return USB_ERR_NO_CHANNEL; + } + + } else + return USB_ERR_NO_BANDWIDTH; +} + +/******************************************************************************/ +/*! + * @name int free_usb_resource_for_periodic(ed_t *pFreeED) + * + * @brief this function frees the resources to be allocated + * to pFreeED at S3CScheduler. + * that is, this functions only releases the resources + * to be allocated by S3C6400Scheduler. + * + * @param [IN] pFreeED + * - indicates ed_t to have the information of the resource to be released. + * + * @return USB_ERR_SUCCESS - if success to free the USB Resource. + * USB_ERR_FAIL - if fail to free the USB Resrouce. + */ +/******************************************************************************/ +int free_usb_resource_for_periodic( + u32 free_usb_time, u8 free_chnum, u8 trans_type) +{ + if (dec_perio_bus_time(free_usb_time) == USB_ERR_SUCCESS) { + + if (dec_perio_chnum() == USB_ERR_SUCCESS) { + + if (free_chnum != CH_NONE) { + oci_channel_dealloc(free_chnum); + set_transferring_td_array(free_chnum, 0); + } + + otg_usbcore_des_usb_bandwidth(free_usb_time); + otg_usbcore_des_periodic_transfer_cnt(trans_type); + + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int remove_ed_from_scheduler(ed_t *remove_ed) + * + * @brief this function just remove the remove_ed from TransferReadyQ. + * So if you want to stop the USB Tranfer of remove_ed + * or release the releated resources. + * you should call another functions of S3CScheduler. + * + * @param [IN] remove_ed + * - indicates ed_t to be removed from TransferReadyQ. + * + * @return + * USB_ERR_SUCCESS -if success to remove the remove_ed from TransferReadyQ. + * USB_ERR_FAIL -if fail to remove the remove_ed from TransferReadyQ. + */ +/******************************************************************************/ +int remove_ed_from_scheduler(struct ed *remove_ed) +{ + if (remove_ed->ed_status.is_in_transfer_ready_q) { + remove_ed_from_ready_q(remove_ed); + remove_ed->ed_status.is_in_transfer_ready_q = false; + + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int cancel_to_transfer_td(struct sec_otghost *otghost, + * struct td *cancel_td) + * + * @brief this function stop to execute the USB Transfer of cancel_td and + * release the Channel Resources to be allocated the cancel_td , + * if the Transfer Type of cancel_td is NonPeriodic Transfer. + * this function don't release any usb resources(Channel, USB Bandwidth) + * for Periodic Transfer. + * if you want to release some usb resources for a periodic Transfer, + * you should call the free_usb_resource_for_periodic() + * + * @param [IN] cancel_td = indicates the struct td to be canceled. + * + * @return + * USB_ERR_SUCCESS-if success to cancel the USB Transfer of cancel_td. + * USB_ERR_FAIL -if fail to cancel the USB Transfer of cancel_td. + */ +/******************************************************************************/ +int cancel_to_transfer_td(struct sec_otghost *otghost, struct td *cancel_td) +{ + if (cancel_td->is_transfer_done) + return USB_ERR_FAIL; + + if (cancel_td->is_transferring) { + int err; + err = oci_stop_transfer(otghost, cancel_td->cur_stransfer. + alloc_chnum); + + if (err == USB_ERR_SUCCESS) { + + set_transferring_td_array(cancel_td->cur_stransfer. + alloc_chnum, 0); + + cancel_td->cur_stransfer.alloc_chnum = CH_NONE; + cancel_td->is_transferring = false; + cancel_td->parent_ed_p->ed_status.is_in_transferring = + false; + cancel_td->parent_ed_p->ed_status.in_transferring_td = + 0; + cancel_td->parent_ed_p->is_need_to_insert_scheduler = + true; + + if (cancel_td->cur_stransfer.ed_desc_p-> + endpoint_type == BULK_TRANSFER || + cancel_td->cur_stransfer.ed_desc_p-> + endpoint_type == CONTROL_TRANSFER) { + + dec_nonperio_chnum(); + } + return err; + } else + return err; + } else + return USB_ERR_FAIL; + + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name int retransmit(struct sec_otghost *otghost, struct td *retrasmit_td) + * + * @brief this function retransmits the retrasmit_td immediately. + * So, the Channel of pRetransmitted is reused for retransmittion. + * + * @param [IN] retrasmit_td + * - indicates the pointer ot the struct td to be retransmitted. + * + * @return USB_ERR_SUCCESS - if success to retransmit the retrasmit_td. + * USB_ERR_FAIL - if fail to retransmit the retrasmit_td. + */ +/******************************************************************************/ +int retransmit(struct sec_otghost *otghost, struct td *retrasmit_td) +{ + u32 td_addr = 0; + + if (get_transferring_td_array(retrasmit_td->cur_stransfer. + alloc_chnum, &td_addr) == USB_ERR_SUCCESS) { + + if (td_addr == (u32)retrasmit_td) { + + if (oci_start_transfer(otghost, + &retrasmit_td->cur_stransfer) == + retrasmit_td->cur_stransfer.alloc_chnum) { + + retrasmit_td->is_transferring = true; + retrasmit_td->parent_ed_p->ed_status. + in_transferring_td = (u32)retrasmit_td; + retrasmit_td->parent_ed_p->ed_status. + is_in_transfer_ready_q = false; + retrasmit_td->parent_ed_p->ed_status. + is_in_transferring = true; + } + + } else + return USB_ERR_FAIL; + } else + return USB_ERR_FAIL; + + return USB_ERR_SUCCESS; + +} + +/******************************************************************************/ +/*! + * @name int reschedule(struct td *reschedule_td) + * + * @brief this function re-schedules the reschedule_td. + * So, the Channel of pRescheuleTD is released + * and reschedule_td is inserted to TransferReadyQ. + * + * @param [IN] reschedule_td + * - indicates the pointer ot the struct td to be rescheduled. + * + * @return USB_ERR_SUCCESS -if success to re-schedule the reschedule_td. + * USB_ERR_FAIL -if fail to re-schedule the reschedule_td. + */ +/******************************************************************************/ +int reschedule(struct td *reschedule_td) +{ + u32 td_addr; + + if (get_transferring_td_array(reschedule_td->cur_stransfer. + alloc_chnum, &td_addr) == USB_ERR_SUCCESS) { + if ((u32)reschedule_td == td_addr) { + set_transferring_td_array(reschedule_td->cur_stransfer. + alloc_chnum, 0); + oci_channel_dealloc(reschedule_td->cur_stransfer. + alloc_chnum); + + reschedule_td->cur_stransfer.alloc_chnum = CH_NONE; + reschedule_td->parent_ed_p-> + is_need_to_insert_scheduler = true; + reschedule_td->parent_ed_p->ed_status. + in_transferring_td = 0; + + if (reschedule_td->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER || + reschedule_td->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER) { + /* Increase the available Channel */ + dec_nonperio_chnum(); + + } + + insert_ed_to_ready_q(reschedule_td->parent_ed_p, false); + reschedule_td->parent_ed_p->ed_status. + is_in_transfer_ready_q = true; + + } + /* this case is not support.... + else { + } + */ + } + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int deallocate(struct td *deallocate_td) + * + * @brief this function frees resources to be allocated deallocate_td + * by S3CScheduler. + * this function just free the resource by S3CScheduler. + * that is, Channel Resource. + * if there are another struct td at ed_t, + * deallocate() insert the ed_t to TransferReadyQ. + * + * @param [IN] deallocate_td + * - indicates the pointer ot the struct td to be deallocated. + * + * @return + * USB_ERR_SUCCESS -if success to dealloate the resources for the deallocate_td. + * USB_ERR_FAIL -if fail to dealloate the resources for the deallocate_td. + */ +/******************************************************************************/ +int deallocate(struct td *deallocate_td) +{ + u32 td_addr; + + if (get_transferring_td_array(deallocate_td->cur_stransfer.alloc_chnum, + &td_addr) == USB_ERR_SUCCESS) { + if ((u32)deallocate_td == td_addr) { + + set_transferring_td_array(deallocate_td-> + cur_stransfer.alloc_chnum, 0); + + oci_channel_dealloc(deallocate_td->cur_stransfer. + alloc_chnum); + + deallocate_td->cur_stransfer.alloc_chnum = CH_NONE; + + if (deallocate_td->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER || + deallocate_td->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER) { + /* Increase the available Channel */ + dec_nonperio_chnum(); + } + + deallocate_td->parent_ed_p->is_need_to_insert_scheduler + = true; + + if (deallocate_td->parent_ed_p->num_td) { + /* insert ed_t to TransferReadyQ. */ + insert_ed_to_ready_q(deallocate_td->parent_ed_p, + false); + deallocate_td->parent_ed_p->ed_status. + is_in_transfer_ready_q = true; + deallocate_td->parent_ed_p-> + is_need_to_insert_scheduler = false; + } + return USB_ERR_SUCCESS; + + } else + return USB_ERR_FAIL; + } else + return USB_ERR_FAIL; + +} + +/* TBD.... */ +/* transferchecker-common.c + hcd.c */ +void do_schedule(struct sec_otghost *otghost) +{ + if (get_avail_chnum()) { + do_periodic_schedule(otghost); + do_nonperiodic_schedule(otghost); + } +} + + +/******************************************************************************/ +/*! + * @name int get_td_info(u8 chnum, + * unsigned int *td_addr_p) + * + * @brief this function returns the pointer of + * struct td at TransferringTDArray[chnum] + * + * @param [IN] chnum + * - indicates the index of TransferringTDArray + * to include the address of struct td which we gets + * [OUT] td_addr_p + * - indicate pointer to store the address of struct td. + * + * @return + * USB_ERR_SUCCESS -if success to get the address of struct td. + * USB_ERR_FAIL-if fail to get the address of struct td. + */ +/******************************************************************************/ +/* transferchecker-common.c */ +int get_td_info(u8 chnum, unsigned int *td_addr_p) +{ + u32 td_addr; + + if (get_transferring_td_array(chnum, &td_addr) == USB_ERR_SUCCESS) { + *td_addr_p = td_addr; + return USB_ERR_SUCCESS; + } + + return USB_ERR_FAIL; +} + + diff --git a/drivers/usb/host/shost/shost_scheduler.h b/drivers/usb/host/shost/shost_scheduler.h new file mode 100644 index 0000000..fed5035 --- /dev/null +++ b/drivers/usb/host/shost/shost_scheduler.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.h + * [Description] : The Header file defines the external + * and internal functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _SCHEDULER_H +#define _SCHEDULER_H + +/* hcd.c */ +extern void init_scheduler(void); + +extern int reserve_used_resource_for_periodic(u32 usb_time, + u8 dev_speed, u8 trans_type); +extern int free_usb_resource_for_periodic(u32 free_usb_time, + u8 free_chnum, u8 trans_type); +extern int remove_ed_from_scheduler(struct ed *remove_ed); +extern int cancel_to_transfer_td(struct sec_otghost *otghost, + struct td *cancel_td); +extern int retransmit(struct sec_otghost *otghost, struct td *retransmit_td); +extern int reschedule(struct td *resched_td); +extern int deallocate(struct td *dealloc_td); +extern void do_schedule(struct sec_otghost *otghost); +extern int get_td_info(u8 chnum, unsigned int *td_addr); + +/* transfer-common.c */ +int insert_ed_to_scheduler(struct sec_otghost *otghost, struct ed *insert_ed); + +#endif + diff --git a/drivers/usb/host/shost/shost_schedulerlib.c b/drivers/usb/host/shost/shost_schedulerlib.c new file mode 100644 index 0000000..de17401 --- /dev/null +++ b/drivers/usb/host/shost/shost_schedulerlib.c @@ -0,0 +1,426 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Scheduler.c + * [Description] : The source file implements the internal + * functions of Scheduler. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/04 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of Scheduler + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +/* the max periodic bus time is 80%*125us on High Speed Mode */ +static const u32 perio_highbustime_threshold = 100; + +/* the max periodic bus time is 90%*1000us(1ms) on Full/Low Speed Mode. */ +static const u32 perio_fullbustime_threshold = 900; + +static const u8 perio_chnum_threshold = 14; +/* static const u8 total_chnum_threshold = 16; */ +static u8 total_chnum_threshold = 16; + +static u32 perio_used_bustime; +static u8 perio_used_chnum; +static u8 nonperio_used_chnum; +static u8 total_used_chnum; +static u32 transferring_td_array[16]; + + +static int inc_perio_bus_time(u32 bus_time, u8 dev_speed) +{ + switch (dev_speed) { + + case HIGH_SPEED_OTG: + if ((bus_time + perio_used_bustime) <= + perio_highbustime_threshold) { + + perio_used_bustime += bus_time; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; + + case LOW_SPEED_OTG: + case FULL_SPEED_OTG: + if ((bus_time + perio_used_bustime) <= + perio_fullbustime_threshold) { + + perio_used_bustime += bus_time; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; + + case SUPER_SPEED_OTG: + break; + default: + break; + } + return USB_ERR_FAIL; +} + +static int dec_perio_bus_time(u32 bus_time) +{ + if (perio_used_bustime >= bus_time) { + perio_used_bustime -= bus_time; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; +} + +static int inc_perio_chnum(void) +{ + if (perio_used_chnum < perio_chnum_threshold) { + if (total_used_chnum < total_chnum_threshold) { + perio_used_chnum++; + total_used_chnum++; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static u8 get_avail_chnum(void) +{ + return total_chnum_threshold - total_used_chnum; +} + +static int dec_perio_chnum(void) +{ + if (perio_used_chnum > 0) { + if (total_used_chnum > 0) { + perio_used_chnum--; + total_used_chnum--; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static int inc_non_perio_chnum(void) +{ + if (nonperio_used_chnum < total_chnum_threshold) { + if (total_used_chnum < total_chnum_threshold) { + nonperio_used_chnum++; + total_used_chnum++; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static int dec_nonperio_chnum(void) +{ + if (nonperio_used_chnum > 0) { + if (total_used_chnum > 0) { + nonperio_used_chnum--; + total_used_chnum--; + return USB_ERR_SUCCESS; + } + } + return USB_ERR_FAIL; +} + +static int get_transferring_td_array(u8 chnum, unsigned int *td_addr) +{ + if (transferring_td_array[chnum] != 0) { + *td_addr = transferring_td_array[chnum]; + return USB_ERR_SUCCESS; + } + return USB_ERR_FAIL; +} + +static int set_transferring_td_array(u8 chnum, u32 td_addr) +{ + if (td_addr == 0) { + transferring_td_array[chnum] = td_addr; + return USB_ERR_SUCCESS; + } + + if (transferring_td_array[chnum] == 0) { + transferring_td_array[chnum] = td_addr; + return USB_ERR_SUCCESS; + } else + return USB_ERR_FAIL; +} + +/******************************************************************************/ +/*! + * @name int do_periodic_schedule(struct sec_otghost *otghost) + * + * @brief this function schedules PeriodicTransferReadyQ. + * this function checks whether PeriodicTransferReadyQ has some ed_t. + * if there are some ed_t on PeriodicTransferReadyQ + * , this function request to start USB Trasnfer to S3C6400OCI. + * + * + * @param void + * + * @return void + */ +/******************************************************************************/ +static void do_periodic_schedule(struct sec_otghost *otghost) +{ + struct ed *scheduling_ed = NULL; + int err_sched = USB_ERR_SUCCESS; + u32 sched_cnt = 0; + + otg_list_head *td_list_entry; + struct td *td; + u32 cur_frame_num = 0; + u8 alloc_ch; + + otg_dbg(OTG_DBG_SCHEDULE, "***** Start periodicSchedul *****\n"); + + sched_cnt = get_periodic_ready_q_entity_num(); + + while (sched_cnt) { + /* in periodic transfser, + the channel resource was already reserved. + So, we don't need this routine... */ + +start_sched_perio_transfer: + if (!sched_cnt) + goto end_sched_perio_transfer; + + otg_dbg(OTG_DBG_SCHEDULE, "sched_cnt : %d\n", sched_cnt); + + err_sched = get_ed_from_ready_q(&scheduling_ed, true); + + if (err_sched != USB_ERR_SUCCESS) { + /* there is no ED on PeriodicTransferQ. + So we finish scheduling.*/ + otg_dbg(OTG_DBG_SCHEDULE, "no ed\n"); + goto end_sched_perio_transfer; + } + + cur_frame_num = 0; + + otg_dbg(OTG_DBG_SCHEDULE, + "the %d ed to be scheduled\n", (int)scheduling_ed); + + sched_cnt--; + td_list_entry = scheduling_ed->td_list_entry.next; + + if (td_list_entry == &scheduling_ed->td_list_entry) { + /* scheduling_ed has no struct td. + so we schedules another ed on + PeriodicTransferReadyQ. */ + goto start_sched_perio_transfer; + } + + if (scheduling_ed->ed_status.is_in_transferring) { + /* scheduling_ed is already Scheduled. + so we schedules another ed on + PeriodicTransferReadyQ. */ + goto start_sched_perio_transfer; + } + + cur_frame_num = oci_get_frame_num(); + + if (((cur_frame_num - scheduling_ed->ed_desc.sched_frame) & + HFNUM_MAX_FRNUM) > (HFNUM_MAX_FRNUM >> 1)) { + + insert_ed_to_ready_q(scheduling_ed, false); + goto start_sched_perio_transfer; + } + + td = otg_list_get_node(td_list_entry, + struct td, td_list_entry); + + if ((td->is_transferring) || (td->is_transfer_done)) { + /* the selected struct td was already transferring + or completed to transfer. + we should decide how to control this case.*/ + goto end_sched_perio_transfer; + } + + otg_dbg(OTG_DBG_SCHEDULE, + "the struct td to be scheduled :%d", (int)td); + + alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer); + + if (alloc_ch < total_chnum_threshold) { + + td->cur_stransfer.alloc_chnum = alloc_ch; + transferring_td_array[alloc_ch] = (u32)td; + + scheduling_ed->ed_status.is_in_transferring = true; + scheduling_ed->ed_status.is_in_transfer_ready_q = false; + scheduling_ed->ed_status.in_transferring_td = (u32)td; + + td->is_transferring = true; + + } else { + /* we should insert the ed_t to TransferReadyQ, + because the USB Transfer of current ed is failed.*/ + scheduling_ed->ed_status.is_in_transferring = false; + scheduling_ed->ed_status.is_in_transfer_ready_q = true; + scheduling_ed->ed_status.in_transferring_td = 0; + + insert_ed_to_ready_q(scheduling_ed, true); + + scheduling_ed->is_need_to_insert_scheduler = false; + goto end_sched_perio_transfer; + } + + } + +end_sched_perio_transfer: + + return; +} + + +/******************************************************************************/ +/*! + * @name int do_nonperiodic_schedule(struct sec_otghost *otghost) + * + * @brief this function start to schedule thie NonPeriodicTransferReadyQ. + * + * this function checks whether NonPeriodicTransferReadyQ has some ed. + * if there are some ed on NonPeriodicTransferReadyQ, + * this function request to start USB Trasnfer to S3C6400OCI. + * + * @param void + * @return void + */ +/******************************************************************************/ +static void do_nonperiodic_schedule(struct sec_otghost *otghost) +{ + struct ed *scheduling_ed; + int err_sched; + + otg_list_head *td_list_entry; + struct td *td; + u8 alloc_ch; + + if (total_used_chnum >= total_chnum_threshold) { + pr_info("shost nonperiodic: used_chnum >= total_chnum\n"); + return; + } + + while (1) { + +start_nonperiodic: + /* check there is available channel resource + for Non-Periodic Transfer. */ + if (total_used_chnum == total_chnum_threshold) { + pr_info("shost nonperiodic: used_chnum == total_chnum\n"); + goto end_nonperiodic; + } + + err_sched = get_ed_from_ready_q(&scheduling_ed, false); + + if (err_sched != USB_ERR_SUCCESS) { + /* pr_info("shost: non: nothing to schedule\n"); */ + goto end_nonperiodic; + } + + td_list_entry = scheduling_ed->td_list_entry.next; + + if (otg_list_empty(&scheduling_ed->td_list_entry)) { + /* scheduling_ed has no td. + so we schedules another ed + on PeriodicTransferReadyQ.*/ + goto start_nonperiodic; + } + + if (scheduling_ed->ed_status.is_in_transferring) { + /* scheduling_ed is already Scheduled. + so we schedules another ed + on PeriodicTransferReadyQ.*/ + goto start_nonperiodic; + } + + td = otg_list_get_node(td_list_entry, struct td, td_list_entry); + + if ((td->is_transferring) || (td->is_transfer_done)) { + pr_info("shost nonperiodic: td is busy\n"); + goto end_nonperiodic; + } + + alloc_ch = oci_start_transfer(otghost, &td->cur_stransfer); + + if (alloc_ch < total_chnum_threshold) { + + td->cur_stransfer.alloc_chnum = alloc_ch; + transferring_td_array[alloc_ch] = (u32)td; + + inc_non_perio_chnum(); + + scheduling_ed->ed_status.is_in_transferring = true; + scheduling_ed->ed_status.is_in_transfer_ready_q = false; + scheduling_ed->ed_status.in_transferring_td = (u32)td; + td->is_transferring = true; + + } else { + /* we must insert the ed to queue, + because the USB Transfer of the ed is failed.*/ + scheduling_ed->ed_status.is_in_transferring = false; + scheduling_ed->ed_status.in_transferring_td = 0; + insert_ed_to_ready_q(scheduling_ed, true); + scheduling_ed->ed_status.is_in_transfer_ready_q = true; + + goto end_nonperiodic; + } + } /* while */ + +end_nonperiodic: + return; +} + +/******************************************************************************/ +/*! + * @name int insert_ed_to_scheduler(struct sec_otghost *otghost, + * ed_t *insert_ed) + * + * @brief this function transfers the insert_ed to S3C6400Scheduler, + * and after that, the insert_ed is inserted to TransferReadyQ + * and scheduled by Scheduler. + * + * @param [IN] insert_ed + * = indicates pointer of ed_t to be inserted to TransferReadyQ. + * + * @return + * USB_ERR_ALREADY_EXIST - if the insert_ed is already existed. + * USB_ERR_SUCCESS - if success to insert insert_ed to S3CScheduler. + */ +/******************************************************************************/ +int insert_ed_to_scheduler(struct sec_otghost *otghost, struct ed *insert_ed) +{ + if (!insert_ed->is_need_to_insert_scheduler) + return USB_ERR_ALREADY_EXIST; + + otg_dbg(OTG_DBG_SCHEDULE_ED, "ed_id %d, td %d\n", + insert_ed->ed_id, insert_ed->num_td); + + insert_ed_to_ready_q(insert_ed, false); + insert_ed->is_need_to_insert_scheduler = false; + insert_ed->ed_status.is_in_transfer_ready_q = true; + + do_periodic_schedule(otghost); + do_nonperiodic_schedule(otghost); + + return USB_ERR_SUCCESS; +} + diff --git a/drivers/usb/host/shost/shost_struct.h b/drivers/usb/host/shost/shost_struct.h new file mode 100644 index 0000000..638c6f2 --- /dev/null +++ b/drivers/usb/host/shost/shost_struct.h @@ -0,0 +1,189 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-common-datastruct.h + * [Description] : The Header file defines Data Structures + * to be used at sub-modules of S3C6400HCD. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines Data Structure to be managed by Transfer. + * (2) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - modifying ED structure + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _DATA_STRUCT_DEF_H +#define _DATA_STRUCT_DEF_H + + +struct isoch_packet_desc { + /* start address of buffer is buffer address + uiOffsert.*/ + u32 isoch_packiet_start_addr; + u32 buf_size; + u32 transferred_szie; + u32 isoch_status; +}; + +struct standard_dev_req_info { + bool is_data_stage; + u8 conrol_transfer_stage; + u32 vir_standard_dev_req_addr; + u32 phy_standard_dev_req_addr; +}; + +struct control_data_toggle { + u8 setup; + u8 data; + u8 status; +}; + +struct ed_status { + struct control_data_toggle control_data_toggle; + u8 data_toggle; + bool is_ping_enable; + bool is_in_transfer_ready_q; + bool is_in_transferring; + u32 in_transferring_td; + bool is_alloc_resource_for_ed; +}; + +struct ed_desc { + u8 device_addr; + u8 endpoint_num; + bool is_ep_in; + u8 dev_speed; + u8 endpoint_type; + u16 max_packet_size; + u8 mc; + u8 interval; + u32 sched_frame; + u32 used_bus_time; + u8 hub_addr; + u8 hub_port; + bool is_do_split; +}; + +struct hc_reg { + hcintmsk_t hc_int_msk; + hcintn_t hc_int; + u32 dma_addr; + +}; + +struct stransfer { + u32 stransfer_id; + u32 parent_td; + struct ed_desc *ed_desc_p; + struct ed_status *ed_status_p; + u32 vir_addr; + u32 phy_addr; + u32 buf_size; + u32 packet_cnt; + u8 alloc_chnum; + struct hc_reg hc_reg; +}; + +struct ed { + u32 ed_id; + bool is_halted; + bool is_need_to_insert_scheduler; + struct ed_desc ed_desc; + struct ed_status ed_status; + otg_list_head ed_list_entry; + otg_list_head td_list_entry; + otg_list_head readyq_list; + u32 num_td; + void *ed_private; +}; + +struct td { + u32 td_id; + struct ed *parent_ed_p; + void *call_back_func_p; + void *call_back_func_param_p; + bool is_transferring; + bool is_transfer_done; + u32 transferred_szie; + bool is_standard_dev_req; + + struct standard_dev_req_info standard_dev_req_info; + u32 vir_buf_addr; + u32 phy_buf_addr; + u32 buf_size; + u32 transfer_flag; + + struct stransfer cur_stransfer; + u32 error_code; + u32 err_cnt; + otg_list_head td_list_entry; + + /* Isochronous Transfer Specific */ + u32 isoch_packet_num; + struct isoch_packet_desc *isoch_packet_desc_p; + u32 isoch_packet_index; + u32 isoch_packet_position; + u32 sched_frame; + u32 interval; + u32 used_total_bus_time; + + /* the private data can be used by S3C6400Interface. */ + void *td_private; +}; + +struct trans_ready_q { + bool is_periodic; + otg_list_head entity_list; + u32 entity_num; + + /* In case of Periodic Transfer */ + u32 total_perio_bus_bandwidth; + u8 total_alloc_chnum; +}; + +struct hc_info { + hcintmsk_t hc_int_msk; + hcintn_t hc_int; + u32 dma_addr; + hcchar_t hc_char; + hctsiz_t hc_size; +}; + +#ifndef USB_MAXCHILDREN + #define USB_MAXCHILDREN (31) +#endif + +/* TODO: use usb_hub_descriptor + * <linux/usb/ch11.h> + */ +struct hub_descriptor { + u8 desc_length; + u8 desc_type; + u8 port_number; + u16 hub_characteristics; + u8 power_on_to_power_good; + u8 hub_control_current; + + /* add 1 bit for hub status change; round to bytes */ + u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8]; + u8 port_pwr_ctrl_mask[(USB_MAXCHILDREN + 1 + 7) / 8]; +} __packed; + +#endif diff --git a/drivers/usb/host/shost/shost_transfer.c b/drivers/usb/host/shost/shost_transfer.c new file mode 100644 index 0000000..122ce65 --- /dev/null +++ b/drivers/usb/host/shost/shost_transfer.c @@ -0,0 +1,1025 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : Commons3c-otg-transfer-transfer.h + * [Description] : This source file implements the functions + * to be defined at CommonTransfer Module. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements some functions of CommonTransfer. + * (2) 2008/07/15 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - Optimizing for performance + * (3) 2008/08/18 by SeungSoo Yang ( ss1.yang@samsung.com ) + * - Modifying for successful rmmod & disconnecting + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include "shost_transfer.h" + +/* the header pointer to indicate the ED_list + * to manage the struct ed to be created and initiated. + */ +static otg_list_head ed_list_head; +static u32 ref_periodic_transfer; + +/** + * void enable_sof(void) + * + * @brief Generate SOF Interrupt. + * + * @param None + * + * @return None + * + * @remark + * + */ +static void enable_sof(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + gintmsk.b.sofintr = 1; + update_reg_32(GINTMSK, gintmsk.d32); +} + +/** + * void disable_sof(void) + * + * @brief Stop to generage SOF interrupt + * + * @param None + * + * @return None + * + * @remark + * + */ +static void disable_sof(void) +{ + gintmsk_t gintmsk = {.d32 = 0}; + gintmsk.b.sofintr = 1; + clear_reg_32(GINTMSK, gintmsk.d32); +} + + + +/******************************************************************************/ +/*! + * @name int cancel_transfer(struct sec_otghost *otghost, + * struct ed *parent_ed, + * struct td *cancel_td) + * + * @brief this function cancels to transfer USB Transfer of cancel_td. + * this function firstly check whether this cancel_td + * is transferring or not. if the cancel_td is transferring, the this + * function requests to cancel the USB Transfer + * to S3CScheduler. if the parent_ed is for Periodic Transfer, and + * there is not any struct td at parent_ed, then this function requests + * to release some usb resources for the struct ed to S3CScheduler. + * finally this function deletes the cancel_td. + * + * @param [IN] pUpdateTD = indicates the pointer ot the struct td + * to have STransfer to be updated. + * + * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD. + */ +/******************************************************************************/ +static int cancel_transfer(struct sec_otghost *otghost, + struct ed *parent_ed, struct td *cancel_td) +{ + otg_list_head *tmp_list_p, *tmp_list2_p; + int err = USB_ERR_DEQUEUED; + bool cond_found = false; + + if (parent_ed == NULL || cancel_td == NULL) { + otg_err(1, "%s is null.\n", parent_ed ? + "cancel_td" : "parent_ed"); + return USB_ERR_NOELEMENT; + } + + otg_list_for_each_safe(tmp_list_p, tmp_list2_p, + &parent_ed->td_list_entry) { + + if (&cancel_td->td_list_entry == tmp_list_p) { + cond_found = true; + break; + } + } + + if (cond_found != true) { + otg_dbg(OTG_DBG_TRANSFER, "cond_found != true\n"); + cancel_td->error_code = USB_ERR_NOELEMENT; + otg_usbcore_giveback(cancel_td); + return cancel_td->error_code; + } + + + if (cancel_td->is_transferring) { + if (!parent_ed->ed_status.is_in_transfer_ready_q) { + err = cancel_to_transfer_td(otghost, cancel_td); + + parent_ed->ed_status.in_transferring_td = 0; + + if (err != USB_ERR_SUCCESS) { + otg_dbg(OTG_DBG_TRANSFER, + "cancel_to_transfer_td\n"); + cancel_td->error_code = err; + otg_usbcore_giveback(cancel_td); + goto ErrorStatus; + } + + otg_list_pop(&cancel_td->td_list_entry); + parent_ed->num_td--; + } + + } else { + + otg_list_pop(&cancel_td->td_list_entry); + parent_ed->num_td--; + + if (parent_ed->num_td == 0) { + remove_ed_from_scheduler(parent_ed); + parent_ed->is_need_to_insert_scheduler = true; + } + } + + if (parent_ed->num_td) { + parent_ed->is_need_to_insert_scheduler = true; + insert_ed_to_scheduler(otghost, parent_ed); + + } else { + + if (parent_ed->ed_desc.endpoint_type == INT_TRANSFER || + parent_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + + /* Release channel and usb bus resource for + * this struct ed. but, not release memory for this ed. + */ + free_usb_resource_for_periodic( + parent_ed->ed_desc.used_bus_time, + cancel_td->cur_stransfer.alloc_chnum, + cancel_td->parent_ed_p->ed_desc.endpoint_type); + + parent_ed->ed_status.is_alloc_resource_for_ed = false; + } + } + /* the caller of this functions should call + otg_usbcore_giveback(cancel_td); */ + cancel_td->error_code = USB_ERR_DEQUEUED; + otg_usbcore_giveback(cancel_td); + + /* TODO: recursive call occured. FIX */ + delete_td(otghost, cancel_td); + +ErrorStatus: + + return err; +} + + + + +/******************************************************************************/ +/*! + * @name int cancel_all_td(struct sec_otghost *otghost, struct ed *parent_ed) + * + * @brief this function cancels all Transfer which parent_ed manages. + * + * @param [IN] parent_ed = indicates the pointer ot the struct ed + * to manage TD_ts to be canceled. + * + * @return + * USB_ERR_SUCCESS - if success to cancel all TD_ts of pParentsED. + * USB_ERR_FAIL - if fail to cancel all TD_ts of pParentsED. + */ +/******************************************************************************/ +static int cancel_all_td(struct sec_otghost *otghost, struct ed *parent_ed) +{ + otg_list_head *cancel_td_list_entry; + struct td *cancel_td; + + otg_dbg(OTG_DBG_OTGHCDI_HCD, "cancel_all_td\n"); + do { + cancel_td_list_entry = parent_ed->td_list_entry.next; + + cancel_td = otg_list_get_node(cancel_td_list_entry, + struct td, td_list_entry); + + cancel_transfer(otghost, parent_ed, cancel_td); + + } while (parent_ed->num_td); + + return USB_ERR_SUCCESS; +} + + + + +/******************************************************************************/ +/*! + * @name int delete_ed(struct ed *delete_ed) + * + * @brief this function delete the delete_ed. + * if there is some available TD_ts on delete_ed, + * then this function also deletes these struct td + * + * @param [IN] delete_ed = indicates the address of struct ed to be deleted. + * + * @return USB_ERR_SUCCESS -if successes to delete the struct ed. + * USB_ERR_FAILl -if fails to delete the ed. + */ +/******************************************************************************/ +static int delete_ed(struct sec_otghost *otghost, struct ed *delete_ed) +{ + otg_kal_make_ep_null(delete_ed); + + if (delete_ed->num_td) { + cancel_all_td(otghost, delete_ed); + /** + * need to giveback of td's urb with considering life-cycle of + * TD, ED, urb->hcpriv, td->private, ep->hcpriv, td->parentED + * (commented by ss1.yang) + */ + } + + otg_list_pop(&delete_ed->ed_list_entry); + + if (delete_ed->ed_desc.endpoint_type == INT_TRANSFER || + delete_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + ref_periodic_transfer--; + } + + if (ref_periodic_transfer == 0) + disable_sof(); + + otg_mem_free(delete_ed); + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name void init_transfer(void) + * + * @brief this function initiates the S3CTranfer module. + * that is, this functions initiates + * the ED_list_head OTG List which manages the all ed to be existed. + * + * @param void + * @return void + */ +/******************************************************************************/ + +static void init_transfer(void) +{ + otg_dbg(OTG_DBG_TRANSFER, "start to init_transfer\n"); + otg_list_init(&ed_list_head); + ref_periodic_transfer = 0; +} + + +/******************************************************************************/ +/*! + * @name void DeInitTransfer(void) + * + * @brief this function Deinitiates the S3CTranfer module. + * this functions check which there are + * some ed on ED_list_head. if some ed exists, + * deinit_transfer() deletes the ed. + * + * @param void + * @return void + */ +/******************************************************************************/ +static void deinit_transfer(struct sec_otghost *otghost) +{ + otg_list_head *ed_list_member; + struct ed *delete_ed_p; + + while (otg_list_empty(&ed_list_head) != true) { + + ed_list_member = ed_list_head.next; + + /* otg_list_pop(ed_list_member); */ + + delete_ed_p = otg_list_get_node(ed_list_member, + struct ed, ed_list_entry); + + delete_ed(otghost, delete_ed_p); + } +} + +/******************************************************************************/ +/*! + * @name int delete_td(struct sec_otghost *otghost, struct td *delete_td) + * + * @brief this function frees memory resource for the delete_td. + * and if delete_td is transferring USB Transfer, then + * this function request to cancel the USB Transfer to scheduler. + * + * + * @param [OUT] new_td_p = returns the address of the new struct td. + * + * @return USB_ERR_SUCCESS -if successes to create the new struct td. + * USB_ERR_FAILl -if fails to create to new struct td. + */ +/******************************************************************************/ +int delete_td(struct sec_otghost *otghost, struct td *delete_td) +{ + if (delete_td->is_transferring) { + /* at this case, we should cancel the USB Transfer. */ + cancel_to_transfer_td(otghost, delete_td); + } + + otg_mem_free(delete_td); + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name int create_ed(struct ed **new_ed) + * + * @brief this function creates a new ed and returns the ed to Caller + * + * @param [OUT] new_ed = returns the address of the new ed . + * + * @return USB_ERR_SUCCESS -if successes to create the new ed. + * USB_ERR_FAILl -if fails to create to new ed. + */ +/******************************************************************************/ +static int create_ed(struct ed **new_ed) +{ + int err_code = USB_ERR_SUCCESS; + + err_code = otg_mem_alloc((void **)new_ed, + (u16)sizeof(struct ed), USB_MEM_ASYNC); + otg_mem_set(*new_ed, 0, sizeof(struct ed)); + return err_code; +} + + +/******************************************************************************/ +/*! + * @name int init_ed(struct ed *init_ed, + * u8 dev_addr, + * u8 ep_num, + * bool f_is_ep_in, + * u8 dev_speed, + * u8 ep_type, + * u32 max_packet_size, + * u8 multi_count, + * u8 interval, + * u32 sched_frame, + * u8 hub_addr, + * u8 hub_port, + * bool f_is_do_split) + * + * @brief this function initiates the init_ed by using the another parameters. + * + * @param [OUT] init_ed = returns the struct ed to be initiated. + * [IN] dev_addr = inidcates the address of USB Device. + * [IN] ep_num = inidcates the number of the specific endpoint on USB Device. + * [IN] f_is_ep_in = inidcates whether the endpoint is IN or not + * [IN] dev_speed = inidcates the speed of USB Device. + * [IN] max_packet_size = inidcates the maximum packet size of a specific + * endpoint on USB Device. + * [IN] multi_count = if the endpoint supports periodic transfer + * , this indicates the multiple packet to be transferred on a uframe + * [IN] interval= if the endpoint support periodic transfer, this indicates + * the polling rate. + * [IN] sched_frame= if the endpoint supports periodic transfer, this indicates + * the start frame number. + * [IN] hub_addr= indicate the address of hub which the USB device attachs to. + * [IN] hub_port= inidcates the port number of the hub which the USB + * device attachs to. + * [IN] f_is_do_split= inidcates whether this tranfer is + * split transaction or not. + * + * @return USB_ERR_SUCCESS -if successes to initiate the ed. + * USB_ERR_FAILl -if fails to initiate the ed. + * USB_ERR_NOSPACE -if fails to initiate the ed + * because there is no USB Resource for this init_ed. + */ +/******************************************************************************/ +static int init_ed(struct ed *init_ed, + u8 dev_addr, + u8 ep_num, + bool f_is_ep_in, + u8 dev_speed, + u8 ep_type, + u16 max_packet_size, + u8 multi_count, + u8 interval, + u32 sched_frame, + u8 hub_addr, + u8 hub_port, + bool f_is_do_split, + void *ep) +{ + init_ed->is_halted = false; + init_ed->is_need_to_insert_scheduler = true; + init_ed->ed_id = (u32)init_ed; + init_ed->num_td = 0; + init_ed->ed_private = ep; + + otg_list_init(&init_ed->td_list_entry); + + /* start to initiate struct ed_desc.... */ + init_ed->ed_desc.is_do_split = f_is_do_split; + init_ed->ed_desc.is_ep_in = f_is_ep_in; + init_ed->ed_desc.dev_speed = dev_speed; + init_ed->ed_desc.hub_addr = hub_addr; + init_ed->ed_desc.hub_port = hub_port; + init_ed->ed_desc.mc = multi_count; + init_ed->ed_desc.device_addr = dev_addr; + init_ed->ed_desc.endpoint_num = ep_num; + init_ed->ed_desc.endpoint_type = ep_type; + init_ed->ed_desc.max_packet_size = max_packet_size; + init_ed->ed_desc.sched_frame = sched_frame; + + if (init_ed->ed_desc.endpoint_type == INT_TRANSFER) { + + if (init_ed->ed_desc.dev_speed == LOW_SPEED_OTG || + init_ed->ed_desc.dev_speed == FULL_SPEED_OTG) { + + init_ed->ed_desc.interval = interval; + + } else if (init_ed->ed_desc.dev_speed == HIGH_SPEED_OTG) { + + u8 count = 0; + u8 cal_interval = 1; + + for (count = 0; + count < (init_ed->ed_desc.interval-1); count++) + cal_interval *= 2; + + init_ed->ed_desc.interval = cal_interval; + + } else { + otg_dbg(OTG_DBG_TRANSFER, + "Super-Speed is not supported\n"); + } + + init_ed->ed_desc.sched_frame = + (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM; + ref_periodic_transfer++; + } + + if (init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + u8 count = 0; + u8 cal_interval = 1; + + for (count = 0; count < (init_ed->ed_desc.interval-1); count++) + cal_interval *= 2; + + init_ed->ed_desc.interval = cal_interval; + init_ed->ed_desc.sched_frame = + (SCHEDULE_SLOT+oci_get_frame_num())&HFNUM_MAX_FRNUM; + ref_periodic_transfer++; + } + + /* start to initiate struct ed_status.... */ + + /* initiates PID */ + switch (ep_type) { + case BULK_TRANSFER: + case INT_TRANSFER: + init_ed->ed_status.data_toggle = DATA0; + break; + + case CONTROL_TRANSFER: + init_ed->ed_status.control_data_toggle.setup = SETUP; + init_ed->ed_status.control_data_toggle.data = DATA1; + init_ed->ed_status.control_data_toggle.status = DATA1; + break; + + case ISOCH_TRANSFER: + if (f_is_ep_in) { + switch (multi_count) { + case MULTI_COUNT_ZERO: + init_ed->ed_status.data_toggle = DATA0; + break; + case MULTI_COUNT_ONE: + init_ed->ed_status.data_toggle = DATA1; + break; + case MULTI_COUNT_TWO: + init_ed->ed_status.data_toggle = DATA2; + break; + default: + break; + } + + } else { + switch (multi_count) { + case MULTI_COUNT_ZERO: + init_ed->ed_status.data_toggle = DATA0; + break; + case MULTI_COUNT_ONE: + init_ed->ed_status.data_toggle = MDATA; + break; + case MULTI_COUNT_TWO: + init_ed->ed_status.data_toggle = MDATA; + break; + default: + break; + } + } + break; + default: + break; + } + + if (init_ed->ed_desc.endpoint_type == INT_TRANSFER || + init_ed->ed_desc.endpoint_type == ISOCH_TRANSFER) { + + u32 usb_time = 0, byte_count = 0; + + /* calculates the bytes to be transferred + at one (uframe)frame.*/ + byte_count = (init_ed->ed_desc.mc+1) * + init_ed->ed_desc.max_packet_size; + + usb_time = (u32)otg_usbcore_get_calc_bustime( + init_ed->ed_desc.dev_speed, + init_ed->ed_desc.is_ep_in, + (init_ed->ed_desc.endpoint_type == + ISOCH_TRANSFER ? true : false), byte_count); + + usb_time /= 1000; /* convert nanosec unit to usec unit */ + + if (reserve_used_resource_for_periodic(usb_time, + init_ed->ed_desc.dev_speed, + init_ed->ed_desc.endpoint_type) + != USB_ERR_SUCCESS) { + return USB_ERR_NOSPACE; + } + + init_ed->ed_status.is_alloc_resource_for_ed = true; + init_ed->ed_desc.used_bus_time = usb_time; + init_ed->ed_desc.mc = multi_count+1; + } + + init_ed->ed_status.is_in_transfer_ready_q = false; + init_ed->ed_status.is_in_transferring = false; + init_ed->ed_status.is_ping_enable = false; + init_ed->ed_status.in_transferring_td = 0; + + /* push the ed to ED_list. */ + otg_list_push_prev(&init_ed->ed_list_entry, &ed_list_head); + + if (ref_periodic_transfer) + enable_sof(); + + return USB_ERR_SUCCESS; +} + + +/*****************************************************************************/ +/*! + * @name int create_td(struct td **new_td) + * + * @brief this function creates a new struct td and returns the struct td to Caller + * + * + * @param [OUT] new_td = returns the address of the new struct td . + * + * @return USB_ERR_SUCCESS -if successes to create the new struct td. + * USB_ERR_FAILl -if fails to create to new struct td. + */ +/*****************************************************************************/ +static int create_td(struct td **new_td) +{ + int err_code = USB_ERR_SUCCESS; + + err_code = otg_mem_alloc((void **)new_td, + (u16)sizeof(struct td), USB_MEM_ASYNC); + otg_mem_set(*new_td, 0, sizeof(struct td)); + return err_code; +} + + +/*****************************************************************************/ +/*! + * @name int init_perio_stransfer( bool f_is_isoch_transfer, + * struct td *parent_td) + * + * @brief this function initiates the parent_td->cur_stransfer for Periodic + * Transfer and inserts this init_td_p to init_td_p->parent_ed_p. + * + * @param [IN] f_is_isoch_transfer = indicates whether this transfer + * is Isochronous or not. + * [IN] parent_td = indicates the address of struct td to be initiated. + * + * @return USB_ERR_SUCCESS -if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL -if fail to update the STranfer of pUpdateTD. + */ +/*****************************************************************************/ +static int init_perio_stransfer(bool f_is_isoch_transfer, + struct td *parent_td) +{ + parent_td->cur_stransfer.ed_desc_p = + &parent_td->parent_ed_p->ed_desc; + parent_td->cur_stransfer.ed_status_p = + &parent_td->parent_ed_p->ed_status; + parent_td->cur_stransfer.alloc_chnum = CH_NONE; + parent_td->cur_stransfer.parent_td = + (u32)parent_td; + parent_td->cur_stransfer.stransfer_id = + (u32)&parent_td->cur_stransfer; + + otg_mem_set(&parent_td->cur_stransfer.hc_reg, 0, sizeof(struct hc_reg)); + + parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1; + + if (f_is_isoch_transfer) { + /* initiates the STransfer usinb the IsochPacketDesc[0]. */ + parent_td->cur_stransfer.buf_size = + parent_td->isoch_packet_desc_p[0].buf_size; + + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->isoch_packet_desc_p[0]. + isoch_packiet_start_addr; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->isoch_packet_desc_p[0]. + isoch_packiet_start_addr; + + } else { + parent_td->cur_stransfer.buf_size = + (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) ? + MAX_CH_TRANSFER_SIZE : parent_td->buf_size; + + parent_td->cur_stransfer.phy_addr = parent_td->phy_buf_addr; + parent_td->cur_stransfer.vir_addr = parent_td->vir_buf_addr; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + + return USB_ERR_SUCCESS; +} + + +/*****************************************************************************/ +/*! + * @name int init_nonperio_stransfer(bool f_is_standard_dev_req, + * struct td *parent_td) + * + * @brief this function initiates the parent_td->cur_stransfer + * for NonPeriodic Transfer and inserts this init_td_p + * to init_td_p->parent_ed_p. + * + * @param [IN] f_is_standard_dev_req- indicates whether this is Control or not. + * [IN] parent_td- indicates the address of struct td to be initiated. + * + * @return USB_ERR_SUCCESS - if success to update the STranfer of pUpdateTD. + * USB_ERR_FAIL - if fail to update the STranfer of pUpdateTD. + */ +/******************************************************************************/ +static int init_nonperio_stransfer(bool f_is_standard_dev_req, + struct td *parent_td) +{ + parent_td->cur_stransfer.ed_desc_p = + &parent_td->parent_ed_p->ed_desc; + parent_td->cur_stransfer.ed_status_p = + &parent_td->parent_ed_p->ed_status; + parent_td->cur_stransfer.alloc_chnum = CH_NONE; + parent_td->cur_stransfer.parent_td = (u32)parent_td; + parent_td->cur_stransfer.stransfer_id = + (u32)&parent_td->cur_stransfer; + + otg_mem_set(&(parent_td->cur_stransfer.hc_reg), + 0, sizeof(struct hc_reg)); + + parent_td->cur_stransfer.hc_reg.hc_int_msk.b.chhltd = 1; + + if (f_is_standard_dev_req) { + parent_td->cur_stransfer.buf_size = + USB_20_STAND_DEV_REQUEST_SIZE; + parent_td->cur_stransfer.phy_addr = + parent_td->standard_dev_req_info. + phy_standard_dev_req_addr; + parent_td->cur_stransfer.vir_addr = + parent_td->standard_dev_req_info. + vir_standard_dev_req_addr; + } else { + parent_td->cur_stransfer.buf_size + = (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size; + + parent_td->cur_stransfer.phy_addr + = parent_td->phy_buf_addr; + parent_td->cur_stransfer.vir_addr + = parent_td->vir_buf_addr; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int init_td(struct td *init_td, + * struct ed *parent_ed, + * void *call_back_fun, + * void *call_back_param, + * u32 transfer_flag, + * bool f_is_standard_dev_req, + * u32 phy_setup, + * u32 vir_setup, + * u32 vir_buf_addr, + * u32 phy_buf_addr, + * u32 buf_size, + * u32 isoch_start_frame, + * isoch_packet_desc_t *isoch_packet_desc, + * u32 isoch_packet_num, + * void *td_priv) + * + * @brief this function initiates the init_td by using another parameter. + * + * + * @param [IN] init_td- indicate the struct td to be initiated. + * [IN] parent_ed- indicate the ed to manage this init_td + * [IN] call_back_func- indicate the call-back function of application. + * [IN] call_back_param- indicate the parameter of the call-back function. + * [IN] transfer_flag- indicate the transfer flag. + * [IN] f_is_standard_dev_req- indicates the issue transfer request is Request + * [IN] phy_setup- the physical address of buffer to store the Request. + * [IN] vir_setup- the virtual address of buffer to store the Request. + * [IN] vir_buf_addr- the virtual address of buffer to store the data + * to be transferred or received. + * [IN] phy_buf_addr- the physical address of buffer to store the data + * to be transferred or received. + * [IN] buf_size- indicates the buffer size. + * [IN] isoch_start_frame- if this usb transfer is isochronous transfer + * , this indicates the start frame to start the usb transfer. + * [IN] isoch_packet_desc- if the usb transfer is isochronous transfer + * , this indicates the structure to describe the isochronous transfer. + * [IN] isoch_packet_num- if the usb transfer is isochronous transfer + * , this indicates the number of packet to consist of the usb transfer. + * [IN] td_priv- indicate the private data to be delivered from usb core. + * td_priv stores the urb of linux. + * + * @return USB_ERR_SUCCESS -if successes to initiate the new struct td. + * USB_ERR_FAILl -if fails to create to new struct td. + */ +/******************************************************************************/ +static int init_td(struct td *init_td, + struct ed *parent_ed, + void *call_back_fun, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 phy_setup, + u32 vir_setup, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 isoch_start_frame, + struct isoch_packet_desc *isoch_packet_desc, + u32 isoch_packet_num, + void *td_priv) +{ + if (f_is_standard_dev_req) { + + if ((phy_buf_addr > 0) && (buf_size > 0)) + init_td->standard_dev_req_info.is_data_stage = true; + else + init_td->standard_dev_req_info.is_data_stage = false; + + init_td->standard_dev_req_info.conrol_transfer_stage + = SETUP_STAGE; + init_td->standard_dev_req_info.phy_standard_dev_req_addr + = phy_setup; + init_td->standard_dev_req_info.vir_standard_dev_req_addr + = vir_setup; + } + + init_td->call_back_func_p = call_back_fun; + init_td->call_back_func_param_p = call_back_param; + init_td->error_code = USB_ERR_SUCCESS; + init_td->is_standard_dev_req = f_is_standard_dev_req; + init_td->is_transfer_done = false; + init_td->is_transferring = false; + init_td->td_private = td_priv; + init_td->err_cnt = 0; + init_td->parent_ed_p = parent_ed; + init_td->phy_buf_addr = phy_buf_addr; + init_td->vir_buf_addr = vir_buf_addr; + init_td->buf_size = buf_size; + init_td->isoch_packet_desc_p = isoch_packet_desc; + init_td->isoch_packet_num = isoch_packet_num; + init_td->isoch_packet_index = 0; + init_td->isoch_packet_position = 0; + init_td->sched_frame = isoch_start_frame; + init_td->used_total_bus_time = parent_ed->ed_desc.used_bus_time; + init_td->td_id = (u32)init_td; + init_td->transfer_flag = transfer_flag; + init_td->transferred_szie = 0; + + switch (parent_ed->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + init_nonperio_stransfer(true, init_td); + break; + + case BULK_TRANSFER: + init_nonperio_stransfer(false, init_td); + break; + + case INT_TRANSFER: + init_perio_stransfer(false, init_td); + break; + + case ISOCH_TRANSFER: + init_perio_stransfer(true, init_td); + break; + + default: + return USB_ERR_FAIL; + } + + /* insert the struct td to parent_ed->td_list_entry. */ + otg_list_push_prev(&init_td->td_list_entry, &parent_ed->td_list_entry); + parent_ed->num_td++; + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name int issue_transfer(struct sec_otghost *otghost, + * struct ed *parent_ed, + * void *call_back_func, + * void *call_back_param, + * u32 transfer_flag, + * bool f_is_standard_dev_req, + * u32 setup_vir_addr, + * u32 setup_phy_addr, + * u32 vir_buf_addr, + * u32 phy_buf_addr, + * u32 buf_size, + * u32 start_frame, + * u32 isoch_packet_num, + * isoch_packet_desc_t *isoch_packet_desc, + * void *td_priv, + * unsigned int *return_td_addr) + * + * @brief this function start USB Transfer + * + * + * @param [IN] parent_ed - indicate the ed to manage this issue transfer. + * [IN] call_back_func - indicate the call-back function of application. + * [IN] call_back_param - indicate the parameter of the call-back function. + * [IN] transfer_flag - indicate the transfer flag. + * [IN] f_is_standard_dev_req - indicates the issue transfer request + * is USB Standard Request + * [IN] setup_vir_addr - the virtual address of buffer to store the Request. + * [IN] setup_phy_addr - the physical address of buffer to store the Request. + * [IN] vir_buf_addr - the virtual address of buffer to store the data + * to be transferred or received. + * [IN] phy_buf_addr - the physical address of buffer to store the data + * to be transferred or received. + * [IN] buf_siz- indicates the buffer size. + * [IN] start_frame - if this usb transfer is isochronous transfer, + * this indicates the start frame to start the usb transfer. + * [IN] isoch_packet_num - if the usb transfer is isochronous transfer, + * this indicates the number of packet to consist of the usb transfer. + * [IN] isoch_packet_desc - if the usb transfer is isochronous transfer + * this indicates the structure to describe the isochronous transfer. + * [IN] td_priv - indicate the private data to be delivered from usb core. + * td_priv stores the urb of linux. + * [OUT] return_td_addr - indicates the variable address + * to store the new struct td for this transfer + * + * @return USB_ERR_SUCCESS - if successes to initiate the new struct td. + * USB_ERR_FAILl - if fails to create to new struct td. + */ +/******************************************************************************/ +static int issue_transfer(struct sec_otghost *otghost, + struct ed *parent_ed, + void *call_back_func, + void *call_back_param, + u32 transfer_flag, + bool f_is_standard_dev_req, + u32 setup_vir_addr, + u32 setup_phy_addr, + u32 vir_buf_addr, + u32 phy_buf_addr, + u32 buf_size, + u32 start_frame, + u32 isoch_packet_num, + struct isoch_packet_desc *isoch_packet_desc, + void *td_priv, + unsigned int *return_td_addr) +{ + struct td *new_td_p = NULL; + int err = USB_ERR_SUCCESS; + + if (create_td(&new_td_p) == USB_ERR_SUCCESS) { + err = init_td(new_td_p, + parent_ed, + call_back_func, + call_back_param, + transfer_flag, + f_is_standard_dev_req, + setup_phy_addr, + setup_vir_addr, + vir_buf_addr, + phy_buf_addr, + buf_size, + start_frame, + isoch_packet_desc, + isoch_packet_num, + td_priv); + + if (err != USB_ERR_SUCCESS) + return USB_ERR_NOMEM; + + if (parent_ed->is_need_to_insert_scheduler) + insert_ed_to_scheduler(otghost, parent_ed); + + *return_td_addr = (u32)new_td_p; + + return USB_ERR_SUCCESS; + } else + return USB_ERR_NOMEM; +} + + +/* TODO: not used. removed */ +#if 0 +static int create_isoch_packet_desc(isoch_packet_desc_t **new_isoch_packet_desc, + u32 isoch_packet_num) +{ + return otg_mem_alloc((void **)new_isoch_packet_desc, + (u16)sizeof(isoch_packet_desc_t)*isoch_packet_num, + USB_MEM_SYNC); +} + +static int delete_isoch_packet_desc(isoch_packet_desc_t *del_isoch_packet_desc, + u32 isoch_packet_num) +{ + return otg_mem_free(del_isoch_packet_desc); +} + +/******************************************************************************/ +/*! + * @name + * void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc, + * u32 isoch_packet_start_addr, + * u32 isoch_packet_size, + * u32 index) + * + * @brief this function initiates the isoch_packet_desc_t[index]. + * + * @param + * [OUT] init_isoch_packet_desc = indicates the pointer of + * IsochPackDesc_t to be initiated. + * [IN] isoch_packet_start_addr = indicates the start address of the buffer + * to be used at USB Isochronous Transfer. + * [IN] isoch_packet_size = indicates the size of Isochronous packet. + * [IN] index = indicates the index to be mapped with this. + * + * @return void + */ +/******************************************************************************/ +static void init_isoch_packet_desc(isoch_packet_desc_t *init_isoch_packet_desc, + u32 isoch_packet_start_addr, + u32 isoch_packet_size, + u32 index) +{ + init_isoch_packet_desc[index].buf_size = isoch_packet_size; + init_isoch_packet_desc[index].isoch_packiet_start_addr + = isoch_packet_start_addr; + init_isoch_packet_desc[index].isoch_status = 0; + init_isoch_packet_desc[index].transferred_szie = 0; +} +#endif + + diff --git a/drivers/usb/host/shost/shost_transfer.h b/drivers/usb/host/shost/shost_transfer.h new file mode 100644 index 0000000..397fdae --- /dev/null +++ b/drivers/usb/host/shost/shost_transfer.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : s3c-otg-transfer-transfer.h + * [Description] : The Header file defines the external + * and internal functions of Transfer. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/03 + * [Revision History] + * (1) 2008/06/03 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and defines functions of Transfer + * -# Jul 15,2008 v1.2 by SeungSoo Yang (ss1.yang@samsung.com) + * : Optimizing for performance + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#ifndef _TRANSFER_H +#define _TRANSFER_H + +/* + transfer-common.c + transferchecker_common.c + */ +int delete_td(struct sec_otghost *otghost, struct td * delete_td); + +static inline u32 calc_packet_cnt(u32 data_size, u16 max_packet_size) +{ + if (data_size != 0) { + return (data_size % max_packet_size == 0) ? + data_size/max_packet_size : + data_size/max_packet_size + 1; + } + return 1; +} +#endif + diff --git a/drivers/usb/host/shost/shost_transferchecker.c b/drivers/usb/host/shost/shost_transferchecker.c new file mode 100644 index 0000000..855164c --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker.c @@ -0,0 +1,503 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : CommonTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of CommonTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/01/12 + * [Revision History] + * (1) 2008/06/12 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of CommonTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + +#include "shost.h" + +/** + * void mask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Mask specific channel interrupt + * + * @param [IN] chnum : channel number for masking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +static void mask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + clear_reg_32(HCINTMSK(ch_num), mask_info); +} + +/** + * void unmask_channel_interrupt(u32 ch_num, u32 mask_info) + * + * @brief Unmask specific channel interrupt + * + * @param [IN] chnum : channel number for unmasking + * [IN] mask_info : mask information to write register + * + * @return None + * + * @remark + * + */ +static void unmask_channel_interrupt(u32 ch_num, u32 mask_info) +{ + update_reg_32(HCINTMSK(ch_num), mask_info); +} + +/** + * int get_ch_info(struct hc_info * hc_reg, u8 ch_num) + * + * @brief Get current channel information about specific channel + * + * @param [OUT] hc_reg : structure to write channel inforamtion value + * [IN] ch_num : channel number for unmasking + * + * @return None + * + * @remark + * + */ +static int get_ch_info(struct hc_info *hc_reg, u8 ch_num) +{ + if (hc_reg != NULL) { + hc_reg->hc_int_msk.d32 = read_reg_32(HCINTMSK(ch_num)); + hc_reg->hc_int.d32 = read_reg_32(HCINT(ch_num)); + hc_reg->dma_addr = read_reg_32(HCDMA(ch_num)); + hc_reg->hc_char.d32 = read_reg_32(HCCHAR(ch_num)); + hc_reg->hc_size.d32 = read_reg_32(HCTSIZ(ch_num)); + + return USB_ERR_SUCCESS; + } + return USB_ERR_FAIL; +} + +/** + * void get_intr_ch(u32* haint, u32* haintmsk) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [OUT] haint : HAINT register value + * [OUT] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +static void get_intr_ch(u32 *haint, u32 *haintmsk) +{ + *haint = read_reg_32(HAINT); + *haintmsk = read_reg_32(HAINTMSK); +} + +/** + * void clear_ch_intr(u8 ch_num, u32 clear_bit) + * + * @brief Get Channel Interrupt Information in HAINT, HAINTMSK register + * + * @param [IN] haint : HAINT register value + * [IN] haintmsk : HAINTMSK register value + * + * @return None + * + * @remark + * + */ +static void clear_ch_intr(u8 ch_num, u32 clear_bit) +{ + update_reg_32(HCINT(ch_num), clear_bit); +} + + +static int +release_trans_resource(struct sec_otghost *otghost, + struct td *done_td) +{ + /* remove the pDeallocateTD from parent_ed_p. */ + otg_list_pop(&done_td->td_list_entry); + done_td->parent_ed_p->num_td--; + + /* Call deallocate to release the channel + and bandwidth resource of S3CScheduler. */ + deallocate(done_td); + delete_td(otghost, done_td); + return USB_ERR_SUCCESS; +} + +static u32 calc_transferred_size(bool f_is_complete, + struct td *td, struct hc_info *hc_info) +{ + if (f_is_complete) { + if (td->parent_ed_p->ed_desc.is_ep_in) { + return td->cur_stransfer.buf_size - + hc_info->hc_size.b.xfersize; + + } else + return td->cur_stransfer.buf_size; + + } else { + return (td->cur_stransfer.packet_cnt - + hc_info->hc_size.b.pktcnt)*td-> + parent_ed_p->ed_desc.max_packet_size; + } +} + +static void update_frame_number(struct td *pResultTD) +{ + u32 cur_frame_num = 0; + + if (pResultTD->parent_ed_p->ed_desc. + endpoint_type == CONTROL_TRANSFER || + pResultTD->parent_ed_p->ed_desc. + endpoint_type == BULK_TRANSFER) { + return; + } + + pResultTD->parent_ed_p->ed_desc.sched_frame += + pResultTD->parent_ed_p->ed_desc.interval; + pResultTD->parent_ed_p->ed_desc.sched_frame &= + HFNUM_MAX_FRNUM; + + cur_frame_num = oci_get_frame_num(); + + if (((cur_frame_num - pResultTD->parent_ed_p->ed_desc.sched_frame) + &HFNUM_MAX_FRNUM) <= (HFNUM_MAX_FRNUM >> 1)) { + pResultTD->parent_ed_p->ed_desc.sched_frame = cur_frame_num; + } +} + +static void update_data_toggle(struct td *td, u8 toggle) +{ + switch (td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + if (td->standard_dev_req_info.conrol_transfer_stage + == DATA_STAGE) { + td->parent_ed_p->ed_status. + control_data_toggle.data = toggle; + } + break; + case BULK_TRANSFER: + case INT_TRANSFER: + td->parent_ed_p->ed_status.data_toggle = toggle; + break; + + case ISOCH_TRANSFER: + break; + default: + break; + } +} + +/******************************************************************************/ +/*! + * @name void update_perio_stransfer(struct td *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer + * to be used by oci. the STransfer of parent_td is for Periodic Transfer. + * + * @param [IN/OUT]parent_td = indicates the pointer of struct td + * to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ +/******************************************************************************/ +static void update_perio_stransfer(struct td *parent_td) +{ + switch (parent_td->parent_ed_p->ed_desc.endpoint_type) { + case INT_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + (parent_td->buf_size > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE : parent_td->buf_size; + break; + + case ISOCH_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->isoch_packet_desc_p[parent_td-> + isoch_packet_index]. + isoch_packiet_start_addr + + parent_td->isoch_packet_position; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->isoch_packet_desc_p[parent_td-> + isoch_packet_index]. + isoch_packiet_start_addr + + parent_td->isoch_packet_position; + + parent_td->cur_stransfer.buf_size = + (parent_td->isoch_packet_desc_p + [parent_td->isoch_packet_index].buf_size - + parent_td->isoch_packet_position) > + MAX_CH_TRANSFER_SIZE + ? MAX_CH_TRANSFER_SIZE + : parent_td->isoch_packet_desc_p + [parent_td->isoch_packet_index].buf_size - + parent_td->isoch_packet_position; + + break; + + default: + break; + } +} + +/****************************************************************************/ +/*! + * @name void update_nonperio_stransfer(struct td *parent_td) + * + * @brief this function updates the parent_td->cur_stransfer + * to be used by S3COCI. + * + * @param [IN/OUT]parent_td = indicates the pointer of struct td + * to store the STranser to be updated. + * + * @return USB_ERR_SUCCESS -if success to update the parent_td->cur_stransfer. + * USB_ERR_FAIL -if fail to update the parent_td->cur_stransfer. + */ +/****************************************************************************/ +static void update_nonperio_stransfer(struct td *parent_td) +{ + switch (parent_td->parent_ed_p->ed_desc.endpoint_type) { + + case BULK_TRANSFER: + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + ((parent_td->buf_size - parent_td->transferred_szie) + > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size - + parent_td->transferred_szie; + break; + + case CONTROL_TRANSFER: + if (parent_td->standard_dev_req_info. + conrol_transfer_stage == SETUP_STAGE) { + /* but, this case will not be occured...... */ + parent_td->cur_stransfer.phy_addr = + parent_td->standard_dev_req_info. + phy_standard_dev_req_addr; + parent_td->cur_stransfer.vir_addr = + parent_td->standard_dev_req_info. + vir_standard_dev_req_addr; + parent_td->cur_stransfer.buf_size = 8; + + } else if (parent_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + + parent_td->cur_stransfer.phy_addr = + parent_td->phy_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.vir_addr = + parent_td->vir_buf_addr + + parent_td->transferred_szie; + + parent_td->cur_stransfer.buf_size = + ((parent_td->buf_size - parent_td-> + transferred_szie) > MAX_CH_TRANSFER_SIZE) + ? MAX_CH_TRANSFER_SIZE + : parent_td->buf_size - + parent_td->transferred_szie; + + } else { + parent_td->cur_stransfer.phy_addr = 0; + parent_td->cur_stransfer.vir_addr = 0; + parent_td->cur_stransfer.buf_size = 0; + } + break; + default: + break; + } + + parent_td->cur_stransfer.packet_cnt = + calc_packet_cnt(parent_td->cur_stransfer.buf_size, + parent_td->parent_ed_p->ed_desc.max_packet_size); + +} + +#include "shost_transferchecker_control.c" +#include "shost_transferchecker_interrupt.c" +#include "shost_transferchecker_bulk.c" + +/******************************************************************************/ +/*! + * @name void do_transfer_checker(struct sec_otghost *otghost) + * + * @brief this function processes the result of USB Transfer. + * So, do_transfer_checker fistly + * check which channel occurs OTG Interrupt and gets the status + * information of the channel. + * do_transfer_checker requests the information of td to scheduler. + * To process the interrupt of the channel, do_transfer_checker + * calls the sub-modules of + * S3CDoneTransferChecker, for example, + * ControlTransferChecker, BulkTransferChecker. + * according to the process result of the channel interrupt, + * do_transfer_checker decides + * the USB Transfer will be done or retransmitted. + * + * + * @param void + * + * @return void + */ +/*****************************************************************************/ +void do_transfer_checker(struct sec_otghost *otghost) +__releases(&otghost->lock) +__acquires(&otghost->lock) +{ + u32 hc_intr = 0; + u32 hc_intr_msk = 0; + u8 do_try_cnt = 0; + + struct hc_info ch_info; + struct td *done_td = {0}; + + u32 td_addr = 0; + u8 proc_result = 0; + + otg_mem_set((void *)&ch_info, 0, sizeof(struct hc_info)); + + /* Get value of HAINT... */ + get_intr_ch(&hc_intr, &hc_intr_msk); + +start_do_transfer_checker: + + while (do_try_cnt < MAX_CH_NUMBER) { + /* checks the channel number to be masked or not. */ + if (!(hc_intr & hc_intr_msk & (1 << do_try_cnt))) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + /* Gets the address of the struct td + to have the channel to be interrupted. */ + if (!(get_td_info(do_try_cnt, &td_addr))) { + + done_td = (struct td *)td_addr; + + if (do_try_cnt != done_td->cur_stransfer.alloc_chnum) { + do_try_cnt++; + goto start_do_transfer_checker; + } + + } else { + do_try_cnt++; + goto start_do_transfer_checker; + } + + /* Gets the informationof channel to be interrupted. */ + get_ch_info(&ch_info, do_try_cnt); + + switch (done_td->parent_ed_p->ed_desc.endpoint_type) { + case CONTROL_TRANSFER: + proc_result = + process_control_transfer(done_td, &ch_info); + break; + case BULK_TRANSFER: + proc_result = process_bulk_transfer(done_td, &ch_info); + break; + case INT_TRANSFER: + proc_result = process_intr_transfer(done_td, &ch_info); + break; + case ISOCH_TRANSFER: + /* proc_result = + ProcessIsochTransfer(done_td, &ch_info); */ + break; + default: + break; + } + + if ((proc_result == RE_TRANSMIT) || + (proc_result == RE_SCHEDULE)) { + + done_td->parent_ed_p->ed_status. + is_in_transferring = false; + done_td->is_transfer_done = false; + done_td->is_transferring = false; + + if (done_td->parent_ed_p->ed_desc.endpoint_type + == CONTROL_TRANSFER || + done_td->parent_ed_p->ed_desc.endpoint_type + == BULK_TRANSFER) { + + update_nonperio_stransfer(done_td); + + } else + update_perio_stransfer(done_td); + + if (proc_result == RE_TRANSMIT) + retransmit(otghost, done_td); + else + reschedule(done_td); + } + + else if (proc_result == DE_ALLOCATE) { + + done_td->parent_ed_p->ed_status. + is_in_transferring = false; + done_td->parent_ed_p->ed_status. + in_transferring_td = 0; + done_td->is_transfer_done = true; + done_td->is_transferring = false; + + spin_unlock(&otghost->lock); + otg_usbcore_giveback(done_td); + spin_lock(&otghost->lock); + release_trans_resource(otghost, done_td); + + } else { /* NO_ACTION.... */ + done_td->parent_ed_p->ed_status. + is_in_transferring = true; + done_td->parent_ed_p->ed_status. + in_transferring_td = (u32)done_td; + done_td->is_transfer_done = false; + done_td->is_transferring = true; + } + do_try_cnt++; + } + /* Complete to process the Channel Interrupt. + So. we now start to scheduler of S3CScheduler. */ + do_schedule(otghost); +} + + diff --git a/drivers/usb/host/shost/shost_transferchecker_bulk.c b/drivers/usb/host/shost/shost_transferchecker_bulk.c new file mode 100644 index 0000000..13c6b0c --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker_bulk.c @@ -0,0 +1,671 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : BulkTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of BulkTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/13 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of BulkTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the xfercompl event. + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT + * 2. masks some bit of HCINTMSK + * 3. updates the result_td fields + * err_cnt/u8/standard_dev_req_info. + * 4. updates the result_td->parent_ed_p->ed_status.BulkDataTgl. + * 5. calculates the tranferred size on DATA_STAGE. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +static u8 process_xfercompl_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + result_td->transferred_szie += calc_transferred_size(true, + result_td, hc_reg_data); + + if (result_td->transferred_szie == result_td->buf_size) { + /* at IN Transfer, short transfer is accepted. */ + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + + } else { + + if (result_td->parent_ed_p->ed_desc.is_ep_in && + hc_reg_data->hc_size.b.xfersize) { + + if (result_td->transfer_flag & USB_TRANS_FLAG_NOT_SHORT) + result_td->error_code = + USB_ERR_STATUS_SHORTREAD; + else + result_td->error_code = USB_ERR_STATUS_COMPLETE; + + ret_val = DE_ALLOCATE; + + } else + ret_val = RE_SCHEDULE; + } + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + if (hc_reg_data->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + if (result_td->parent_ed_p->ed_desc. + dev_speed == HIGH_SPEED_OTG) + + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + } + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with theAHB Errorl event. + * this function stop the channel to be executed + * TBD.... + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +/* TODO: not used. removed */ +#if 0 +static u8 process_ahb_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_AHBErr); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + /* we just calculate the size of the transferred data on Data Stage + of Bulk Transfer. */ + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return DE_ALLOCATE; + +} +#endif + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with theStall event. + * when Stall is occured at Bulk Transfer, we should reset the PID as DATA0 + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_stall_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + /* this channel is stalled, So we don't process another interrupts.*/ + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + result_td->transferred_szie += calc_transferred_size(false, + result_td, hc_reg_data); + + update_data_toggle(result_td, DATA0); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the nak event. + * nak is occured at OUT/IN Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * If nak is occured at IN Transaction, + * this function processes this interrupt as following. + * + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. be careful, nak of IN Transaction don't require re-transmit. + * If nak is occured at OUT Transaction, + * this function processes this interrupt as following. + + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle at OUT Transaction, + * this function check whether the speed of USB Device + * is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the direction of the Transfer is OUT + * NO_ACTION -if the direction of the Transfer is IN + */ +/******************************************************************************/ +static u8 process_nak_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NAK); + + /* at OUT Transfer, we must re-transmit.*/ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + if (result_td->parent_ed_p->ed_desc.dev_speed == + HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status.is_ping_enable = true; + else + result_td->parent_ed_p->ed_status.is_ping_enable = + false; + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + return RE_TRANSMIT; + } + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the ack event + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT + * and ed_status.is_ping_enable. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +static u8 process_ack_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ACK); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return NO_ACTION; +} + +/******************************************************************************/ +/*! + * @name u8 process_nyet_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the nyet event. + * nyet is only occured at OUT Transaction. + * If nyet is occured at OUT Transaction, + * this function processes this interrupt as following. + * + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the nyet bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * 7. return RE_SCHEDULE to retransmit. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_nyet_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + /* Error State.... */ + return NO_ACTION; + } + + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status.is_ping_enable = true; + else + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + return RE_TRANSMIT; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the xacterr event. + * xacterr is occured at OUT/IN Transaction + * and we should retransmit the USB Transfer + * if the Error Counter is less than the RETRANSMIT_THRESHOLD. + * the reasons of xacterr is Timeout/CRC error/false EOP. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 3. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 4. clears the xacterr bit of HCINT + * 5. calculates the size of the transferred data. + * 6. if the speed of USB Device is High-Speed, sets the ping protocol. + * 7. update the Data Toggle. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if the error count is less than 3 + * DE_ALLOCATE -if the error count is equal to 3 + */ +/******************************************************************************/ +static u8 process_xacterr_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + u8 ret_val = 0; + + if (result_td->err_cnt < RETRANSMIT_THRESHOLD) { + + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= + (CH_STATUS_ACK + CH_STATUS_NAK+CH_STATUS_DataTglErr); + + ret_val = RE_TRANSMIT; + result_td->err_cnt++ ; + + } else { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + result_td->error_code = USB_ERR_STATUS_XACTERR; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status.is_ping_enable = true; + else + result_td->parent_ed_p->ed_status.is_ping_enable = + false; + } + + update_data_toggle(result_td, hc_reg_data->hc_size.b.pid); + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the Babble event + * babble error is occured when the buffer to receive data + * to be transmit is overflow. + * So, babble error can be just occured at IN Transaction. + * when Babble Error is occured, we should stop the USB Transfer, + * and return the fact to Application. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_bblerr_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + + if (!result_td->parent_ed_p->ed_desc.is_ep_in) + return NO_ACTION; + + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt..*/ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + result_td->transferred_szie += + calc_transferred_size(false, result_td, hc_reg_data); + + return DE_ALLOCATE; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_bulk( struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function deals with the datatglerr + * the datatglerr event is occured at IN Transfer, + * and the channel is not halted. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_datatgl_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + result_td->err_cnt = 0; + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + return NO_ACTION; + +} + + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_bulk(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * @brief this function processes Channel Halt event. + * firstly, this function checks the reason of the Channel Halt, + * and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_chhltd_on_bulk(struct td *result_td, + struct hc_info *hc_reg_data) +{ + if (hc_reg_data->hc_int.b.xfercompl) + return process_xfercompl_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.stall) + return process_stall_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.bblerr) + return process_bblerr_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.xacterr) + return process_xacterr_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.nak) + return process_nak_on_bulk(result_td, hc_reg_data); + + else if (hc_reg_data->hc_int.b.nyet) + return process_nyet_on_bulk(result_td, hc_reg_data); + + else { + /* occur error state...*/ + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + /* Mask ack Interrupt..*/ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->err_cnt++; + if (result_td->err_cnt == 3) { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + + return USB_ERR_SUCCESS; +} + +/******************************************************************************/ +/*! + * @name u8 process_bulk_transfer(struct td *result_td, + * struct hc_info *hc_reg_data) + * + * + * @brief this function processes the result of the Bulk Transfer. + * firstly, this function checks the result of the Bulk Transfer. + * and according to the result, calls the sub-functions + * to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td whose channel is interruped. + * [IN] hc_reg_data + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + * NO_ACTION -if we don't need any action, + */ +/******************************************************************************/ +static u8 process_bulk_transfer(struct td *result_td, + struct hc_info *hc_reg_data) +{ + hcintn_t hc_intr_info; + u8 ret = 0; + + /* we just deal with the interrupts to be unmasked.*/ + hc_intr_info.d32 = hc_reg_data->hc_int.d32 & + result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + if (hc_intr_info.b.chhltd) + ret = process_chhltd_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.ack) + ret = process_ack_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.nak) + ret = process_nak_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.datatglerr) + ret = process_datatgl_on_bulk(result_td, hc_reg_data); + + } else { + + if (hc_intr_info.b.chhltd) + ret = process_chhltd_on_bulk(result_td, hc_reg_data); + + else if (hc_intr_info.b.ack) + ret = process_ack_on_bulk(result_td, hc_reg_data); + } + + return ret; +} + diff --git a/drivers/usb/host/shost/shost_transferchecker_control.c b/drivers/usb/host/shost/shost_transferchecker_control.c new file mode 100644 index 0000000..8d0cfbc --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker_control.c @@ -0,0 +1,777 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : ControlTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of ControlTransferChecker. + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2009/02/10 + * [Revision History] + * (1) 2008/06/13 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of ControlTransferChecker + * (2) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Completed to implement ControlTransferChecker.c v1.0 + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the xfercompl event + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT + * by using clear_ch_intr() of S3CIsr. + * 2. masks some bit of HCINTMSK + * 3. updates the result_td fields + * err_cnt/u8/standard_dev_req_info. + * 4. updates the result_td->parent_ed_p->ed_status. + * control_data_tgl. + * 5. calculates the tranferred size + * by calling calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return USB_ERR_SUCCESS + */ +/******************************************************************************/ +static u8 process_xfercompl_on_control(struct td *result_td, struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + switch (result_td->standard_dev_req_info.conrol_transfer_stage) { + + case SETUP_STAGE: + if (result_td->standard_dev_req_info.is_data_stage) + result_td->standard_dev_req_info. + conrol_transfer_stage = DATA_STAGE; + else + result_td->standard_dev_req_info. + conrol_transfer_stage = STATUS_STAGE; + + ret_val = RE_TRANSMIT; + + break; + + case DATA_STAGE: + + result_td->transferred_szie += + calc_transferred_size(true, result_td, hcinfo); + + /* at IN Transfer, short transfer is accepted. */ + if (result_td->transferred_szie == result_td->buf_size) { + result_td->standard_dev_req_info. + conrol_transfer_stage = STATUS_STAGE; + result_td->error_code = USB_ERR_STATUS_COMPLETE; + + } else { + if (result_td->parent_ed_p->ed_desc.is_ep_in && + hcinfo->hc_size.b.xfersize) { + + if (result_td->transfer_flag & + USB_TRANS_FLAG_NOT_SHORT) { + + result_td->error_code = + USB_ERR_STATUS_SHORTREAD; + result_td->standard_dev_req_info. + conrol_transfer_stage = + STATUS_STAGE; + + } else { + result_td->error_code = + USB_ERR_STATUS_COMPLETE; + result_td->standard_dev_req_info. + conrol_transfer_stage = + STATUS_STAGE; + } + + } else { + /* the Data Stage is not completed. + So we need to continue Data Stage. */ + result_td->standard_dev_req_info. + conrol_transfer_stage = DATA_STAGE; + update_data_toggle(result_td, + hcinfo->hc_size.b.pid); + } + } + + if (hcinfo->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + if (result_td->parent_ed_p->ed_desc. + dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + } + ret_val = RE_TRANSMIT; + break; + + case STATUS_STAGE: + result_td->standard_dev_req_info. + conrol_transfer_stage = COMPLETE_STAGE; + + if (hcinfo->hc_int.b.nyet) { + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + + if (result_td->parent_ed_p->ed_desc. + dev_speed == HIGH_SPEED_OTG) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + } + + ret_val = DE_ALLOCATE; + break; + + default: + break; + } + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with theAHB Errorl event + * this function stop the channel to be executed + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ + +/* TODO: not used. remove */ +#if 0 +static u8 process_ahb_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_AHBErr); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + /* we just calculate the size of the transferred data + on Data Stage of Control Transfer. */ + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + return DE_ALLOCATE; + +} +#endif + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with theStall event + * but USB2.0 Spec don't permit the Stall + * on Setup Stage of Control Transfer. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_stall_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + /* we just calculate the size of the transferred data + on Data Stage of Control Transfer. */ + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the nak event + * nak is occured at OUT/IN Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * If nak is occured at IN Transaction, + * this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. be careful, nak of IN Transaction don't require re-transmit. + * + * If nak is occured at OUT Transaction, + * this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of + * USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_nak_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + /* at OUT Transfer, we must re-transmit. */ + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) { + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + } + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + } + + else if (result_td->standard_dev_req_info. + conrol_transfer_stage == STATUS_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == true) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + + } else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + + return RE_SCHEDULE; +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the ack event + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_ack_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return NO_ACTION; + +} + +/******************************************************************************/ +/*! + * @name u8 process_nyet_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the nyet event + * nyet is occured at OUT Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * If nyet is occured at OUT Transaction, + * this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/datatglerr bit of HCINTMSK. + * 3. clears the nyet bit of HCINT + * 4. calculates the size of the transferred data. + * 5. if the speed of USB Device is High-Speed, sets the ping protocol. + * 6. update the Data Toggle. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_nyet_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_NYET); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + } + + else if (result_td->standard_dev_req_info. + conrol_transfer_stage == STATUS_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == true) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + + } else + result_td->parent_ed_p->ed_status. + is_ping_enable = false; + } + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + return RE_SCHEDULE; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_control(ruct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the xacterr event + * xacterr is occured at OUT/IN Transaction of Data/Status Stage, + * and is not occured at Setup Stage. + * if Timeout/CRC error/false EOP is occured, then xacterr is occured. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 3. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 4. clears the xacterr bit of HCINT + * 5. calculates the size of the transferred data. + * 6. if the speed of USB Device is High-Speed, sets the ping protocol. + * 7. update the Data Toggle. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if the Error Counter is less than RETRANSMIT_THRESHOLD + * DE_ALLOCATE -if the Error Counter is equal to RETRANSMIT_THRESHOLD + */ +/******************************************************************************/ +static u8 process_xacterr_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + if (result_td->err_cnt < RETRANSMIT_THRESHOLD) { + + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= + (CH_STATUS_ACK + CH_STATUS_NAK + CH_STATUS_DataTglErr); + + ret_val = RE_TRANSMIT; + + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + unmask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + result_td->err_cnt++ ; + + } else { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + result_td->error_code = USB_ERR_STATUS_XACTERR; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + if (result_td->parent_ed_p->ed_desc.dev_speed == HIGH_SPEED_OTG) { + + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) { + if (result_td->parent_ed_p->ed_desc.is_ep_in == false) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + } + + else if (result_td->standard_dev_req_info. + conrol_transfer_stage == STATUS_STAGE) { + + if (result_td->parent_ed_p->ed_desc.is_ep_in == true) + result_td->parent_ed_p->ed_status. + is_ping_enable = true; + + } else + result_td->parent_ed_p->ed_status.is_ping_enable = + false; + } + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_control(truct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the Babble event + * babble error can be just occured at IN Transaction. + * So if the direction of transfer is + * OUT, this function return Error Code. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + * NO_ACTION -if the direction is OUT + */ +/******************************************************************************/ +static u8 process_bblerr_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + + if (!result_td->parent_ed_p->ed_desc.is_ep_in) + return NO_ACTION; + + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + /* we just calculate the size of the transferred data + on Data Stage of Control Transfer. */ + if (result_td->standard_dev_req_info. + conrol_transfer_stage == DATA_STAGE) + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_control(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the datatglerr event + * the datatglerr event is occured at IN Transfer. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_datatgl_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + return NO_ACTION; + +} + + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_control(truct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function processes Channel Halt event + * firstly, this function checks the reason of the Channel Halt, + * and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_chhltd_on_control(struct td *result_td, + struct hc_info *hcinfo) +{ + if (hcinfo->hc_int.b.xfercompl) + return process_xfercompl_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.stall) + return process_stall_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.bblerr) + return process_bblerr_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.xacterr) + return process_xacterr_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nak) + return process_nak_on_control(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nyet) + return process_nyet_on_control(result_td, hcinfo); + + else { + + /* Occure Error State.....*/ + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + /* Mask ack Interrupt.. */ + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + result_td->err_cnt++; + + if (result_td->err_cnt == 3) { + + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + return RE_TRANSMIT; + } + return USB_ERR_SUCCESS; +} + + +/******************************************************************************/ +/*! + * @name u8 process_control_transfer(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function processes the result of the Control Transfer. + * firstly, this function checks the result the Control Transfer. + * and according to the result, calls the sub-functions + * to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] ubChNum + * -indicates the number of the channel to be interrupted. + * [IN] hcinfo + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_control_transfer(struct td *result_td, + struct hc_info *hcinfo) +{ + hcintn_t hcintr_info; + u8 ret_val = 0; + + /* we just deal with the interrupts to be unmasked. */ + hcintr_info.d32 = hcinfo->hc_int.d32 & + result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + if (hcintr_info.b.chhltd) + ret_val = process_chhltd_on_control(result_td, hcinfo); + + else if (hcintr_info.b.ack) + ret_val = process_ack_on_control(result_td, hcinfo); + + else if (hcintr_info.b.nak) + ret_val = process_nak_on_control(result_td, hcinfo); + + else if (hcintr_info.b.datatglerr) + ret_val = process_datatgl_on_control(result_td, hcinfo); + + } else { + + if (hcintr_info.b.chhltd) + ret_val = process_chhltd_on_control(result_td, hcinfo); + + else if (hcintr_info.b.ack) + ret_val = process_ack_on_control(result_td, hcinfo); + } + + return ret_val; +} + diff --git a/drivers/usb/host/shost/shost_transferchecker_interrupt.c b/drivers/usb/host/shost/shost_transferchecker_interrupt.c new file mode 100644 index 0000000..fa85721 --- /dev/null +++ b/drivers/usb/host/shost/shost_transferchecker_interrupt.c @@ -0,0 +1,617 @@ +/**************************************************************************** + * (C) Copyright 2008 Samsung Electronics Co., Ltd., All rights reserved + * + * [File Name] : IntTransferChecker.c + * [Description] : The Source file implements the external + * and internal functions of IntTransferChecker + * [Author] : Yang Soon Yeal { syatom.yang@samsung.com } + * [Department] : System LSI Division/System SW Lab + * [Created Date]: 2008/06/19 + * [Revision History] + * (1) 2008/06/18 by Yang Soon Yeal { syatom.yang@samsung.com } + * - Created this file and implements functions of IntTransferChecker + * + ****************************************************************************/ +/**************************************************************************** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + ****************************************************************************/ + + + +/******************************************************************************/ +/*! + * @name u8 process_xfercompl_on_intr( struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the xfercompl event + * the procedure of this function is as following + * 1. clears all bits of the channel' HCINT + * by using clear_ch_intr() of S3CIsr. + * 2. masks ack/nak(?)/datatglerr(?) bit of HCINTMSK + * 3. Resets the err_cnt of result_td. + * 4. updates the result_td->parent_ed_p->ed_status. + * IntDataTgl. + * 5. calculates the tranferred size by calling + * calc_transferred_size() on DATA_STAGE. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE -if USB Transfer is completed. + * RE_TRANSMIT -if need to retransmit the result_td. + */ +/******************************************************************************/ +static u8 process_xfercompl_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + result_td->err_cnt = 0; + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(true, result_td, hcinfo); + + if (result_td->transferred_szie == result_td->buf_size) { + /* at IN Transfer, short transfer is accepted. */ + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + + } else { + + if (result_td->parent_ed_p->ed_desc.is_ep_in && + hcinfo->hc_size.b.xfersize) { + + if (result_td->transfer_flag & USB_TRANS_FLAG_NOT_SHORT) + result_td->error_code = + USB_ERR_STATUS_SHORTREAD; + else + result_td->error_code = USB_ERR_STATUS_COMPLETE; + ret_val = DE_ALLOCATE; + + } else { + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + ret_val = RE_TRANSMIT; + } + } + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + return ret_val; + +} + +/******************************************************************************/ +/*! + * @name u8 process_ahb_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with theAHB Errorl event + * this function stop the channel to be executed + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +/* TODO: not used. removed */ +#if 0 +static u8 process_ahb_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_AHBERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_AHBErr); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + result_td->parent_ed_p->ed_status.is_ping_enable = false; + + return DE_ALLOCATE; + +} +#endif + +/******************************************************************************/ +/*! + * @name u8 process_stall_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the Stall event + * when Stall is occured at Int Transfer, we should reset the PID as DATA0 + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_stall_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_STALL; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, DATA0); + + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_nak_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the nak event + * nak is occured at OUT/IN Transaction of Interrupt Transfer. + * we can't use ping protocol on Interrupt Transfer. + * and Syonopsys OTG IP occures + * chhltd interrupt on nak of IN/OUT Transaction. + * So we should retransmit the transfer on IN Transfer. + * If nak is occured at IN Transaction, + * this function processes this interrupt as following. + * 1. resets the result_td->err_cnt. + * 2. masks ack/nak/DaaTglErr bit of HCINTMSK. + * 3. clears the nak bit of HCINT + * 4. calculates frame number to retransmit this Interrupt Transfer. + * + * If nak is occured at OUT Transaction, + * this function processes this interrupt as following. + * 1. all procedures of IN Transaction are executed. + * 2. calculates the size of the transferred data. + * 3. if the speed of USB Device is High-Speed, sets the ping protocol. + * 4. update the Toggle + * at OUT Transaction, this function check whether the speed of + * USB Device is High-Speed or not. + * if USB Device is High-Speed, then + * this function sets the ping protocol. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the direction of the Transfer is OUT + * NO_ACTION -if the direction of the Transfer is IN + */ +/******************************************************************************/ +static u8 process_nak_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + update_frame_number(result_td); + + return RE_SCHEDULE; + + +} + +/******************************************************************************/ +/*! + * @name u8 process_ack_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * + * @brief this function deals with the ack event + * ack of IN/OUT Transaction don't need any retransmit. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears ack bit of HCINT + * and ed_status.is_ping_enable. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return NO_ACTION + */ +/******************************************************************************/ +static u8 process_ack_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + + return NO_ACTION; +} + +/******************************************************************************/ +/*! + * @name u8 process_xacterr_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the xacterr event + * xacterr is occured at OUT/IN Transaction and we should + * retransmit the USB Transfer + * if the Error Counter is less than the RETRANSMIT_THRESHOLD. + * the reasons of xacterr is Timeout/CRC error/false EOP. + * the procedure to process xacterr is as following. + * 1. increses the result_td->err_cnt + * 2. check whether the result_td->err_cnt is equal to 3. + * 3. unmasks ack/nak/datatglerr bit of HCINTMSK. + * 4. clears the xacterr bit of HCINT + * 5. calculates the size of the transferred data. + * 6. update the Data Toggle. + * 7. update the frame number to start retransmitting + * the Interrupt Transfer. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE -if the error count is less than 3 + * DE_ALLOCATE -if the error count is equal to 3 + */ +/******************************************************************************/ +static u8 process_xacterr_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + u8 ret_val = 0; + + if (result_td->err_cnt < RETRANSMIT_THRESHOLD) { + result_td->cur_stransfer.hc_reg.hc_int_msk.d32 |= + (CH_STATUS_ACK + CH_STATUS_NAK + CH_STATUS_DataTglErr); + ret_val = RE_SCHEDULE; + result_td->err_cnt++ ; + + } else { + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + ret_val = DE_ALLOCATE; + result_td->err_cnt = 0 ; + } + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + if (ret_val == RE_SCHEDULE) + update_frame_number(result_td); + + return ret_val; +} + +/******************************************************************************/ +/*! + * @name void process_bblerr_on_intr(struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the Babble event + * babble error is occured when the USB device continues to send packets + * althrough EOP is occured. So Babble error is only occured + * at IN Transfer. + * when Babble Error is occured, we should stop the USB Transfer, + * and return the fact to Application. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return DE_ALLOCATE + */ +/******************************************************************************/ +static u8 process_bblerr_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + if (!result_td->parent_ed_p->ed_desc.is_ep_in) + return NO_ACTION; + + result_td->err_cnt = 0; + result_td->error_code = USB_ERR_STATUS_BBLERR; + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + return DE_ALLOCATE; +} + +/******************************************************************************/ +/*! + * @name u8 process_datatgl_on_intr( struct td *result_td, + * struct hc_info *hcinfo) + * + * @brief this function deals with the datatglerr event + * the datatglerr event is occured at IN Transfer, + * and the channel is not halted. + * this function just resets result_td->err_cnt + * and masks ack/nak/DataTgl of HCINTMSK. + * finally, this function clears datatglerr bit of HCINT. + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_SCHEDULE + */ +/******************************************************************************/ +static u8 process_datatgl_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + result_td->err_cnt = 0; + + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_DataTglErr); + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + result_td->transferred_szie += + calc_transferred_size(false, result_td, hcinfo); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + update_frame_number(result_td); + + return RE_SCHEDULE; +} + +static u8 process_frmovrrun_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_NAK); + + update_data_toggle(result_td, hcinfo->hc_size.b.pid); + + update_frame_number(result_td); + + return RE_TRANSMIT; +} + + +/******************************************************************************/ +/*! + * @name u8 process_chhltd_on_intr(struct td *result_td, + * struct hc_info *HCRegData) + * + * + * @brief this function processes Channel Halt event + * firstly, this function checks the reason of the Channel Halt, + * and according to the reason, + * calls the sub-functions to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td to be mapped with the uChNum. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + */ +/******************************************************************************/ +static u8 process_chhltd_on_intr(struct td *result_td, + struct hc_info *hcinfo) +{ + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + if (hcinfo->hc_int.b.xfercompl) + return process_xfercompl_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.stall) + return process_stall_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.bblerr) + return process_bblerr_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nak) + return process_nak_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.datatglerr) + return process_datatgl_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.frmovrun) + return process_frmovrrun_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.xacterr) + return process_xacterr_on_intr(result_td, hcinfo); + + else { + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_DataTglErr); + return RE_TRANSMIT; + } + + } else { + if (hcinfo->hc_int.b.xfercompl) + return process_xfercompl_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.stall) + return process_stall_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.nak) + return process_nak_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.frmovrun) + return process_frmovrrun_on_intr(result_td, hcinfo); + + else if (hcinfo->hc_int.b.xacterr) + return process_xacterr_on_intr(result_td, hcinfo); + + else { + clear_ch_intr(result_td->cur_stransfer.alloc_chnum, + CH_STATUS_ALL); + + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_ACK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_NAK); + mask_channel_interrupt(result_td->cur_stransfer. + alloc_chnum, CH_STATUS_DataTglErr); + + result_td->err_cnt++; + + if (result_td->err_cnt == 3) { + result_td->error_code = USB_ERR_STATUS_XACTERR; + result_td->err_cnt = 0; + return DE_ALLOCATE; + } + + return RE_TRANSMIT; + } + } +} + + + +/******************************************************************************/ +/*! + * @name u8 process_intr_transfer(struct td *result_td, + * struct hc_info *HCRegData) + * + * @brief this function processes the result of the Interrupt Transfer. + * firstly, this function checks the result of the Interrupt Transfer. + * and according to the result, calls the sub-functions + * to process the result. + * + * + * @param [IN] result_td + * -indicates the pointer of the struct td whose channel is interruped. + * [IN] HCRegData + * -indicates the interrupt information of the Channel to be interrupted + * + * @return RE_TRANSMIT -if need to retransmit the result_td. + * RE_SCHEDULE -if need to reschedule the result_td. + * DE_ALLOCATE -if USB Transfer is completed. + * NO_ACTION -if we don't need any action, + */ +/******************************************************************************/ +static u8 process_intr_transfer(struct td *result_td, + struct hc_info *hcinfo) +{ + hcintn_t hc_intr_info; + u8 ret_val = 0; + + hc_intr_info.d32 = hcinfo->hc_int.d32 & + result_td->cur_stransfer.hc_reg.hc_int_msk.d32; + + if (result_td->parent_ed_p->ed_desc.is_ep_in) { + + if (hc_intr_info.b.chhltd) + ret_val = process_chhltd_on_intr(result_td, hcinfo); + + else if (hc_intr_info.b.ack) + ret_val = process_ack_on_intr(result_td, hcinfo); + + } else { + if (hc_intr_info.b.chhltd) + ret_val = process_chhltd_on_intr(result_td, hcinfo); + + else if (hc_intr_info.b.ack) + ret_val = process_ack_on_intr(result_td, hcinfo); + } + + return ret_val; +} + + diff --git a/drivers/usb/host/xhci-exynos.c b/drivers/usb/host/xhci-exynos.c new file mode 100644 index 0000000..7bd7ff2 --- /dev/null +++ b/drivers/usb/host/xhci-exynos.c @@ -0,0 +1,625 @@ +/* xhci-exynos.c - Driver for USB HOST on Samsung EXYNOS platform device + * + * Bus Glue for SAMSUNG EXYNOS USB HOST xHCI Controller + * xHCI host controller driver + * + * Copyright (C) 2008 Intel Corp. + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * + * Author: Yulgon Kim <yulgon.kim@samsung.com> + * + * Based on "xhci-pci.c" by Sarah Sharp + * Modified for SAMSUNG EXYNOS XHCI by Yulgon Kim <yulgon.kim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/clk.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/usb/exynos_usb3_drd.h> +#include <linux/platform_data/exynos_usb3_drd.h> + +#include "xhci.h" + +struct exynos_xhci_hcd { + struct device *dev; + struct usb_hcd *hcd; + struct clk *clk; + int irq; +}; + +struct xhci_hcd *exynos_xhci_dbg; + +static const char hcd_name[] = "xhci_hcd"; + +static inline void __orr32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) | val, ptr); +} + +static inline void __bic32(void __iomem *ptr, u32 val) +{ + writel(readl(ptr) & ~val, ptr); +} + +static u32 exynos_xhci_change_mode(struct usb_hcd *hcd, bool host) +{ + u32 gctl; + + gctl = readl(hcd->regs + EXYNOS_USB3_GCTL); + gctl &= ~(EXYNOS_USB3_GCTL_PrtCapDir_MASK | + EXYNOS_USB3_GCTL_FRMSCLDWN_MASK | + EXYNOS_USB3_GCTL_RAMClkSel_MASK); + gctl |= (EXYNOS_USB3_GCTL_FRMSCLDWN(0x1e85) | /* Power Down Scale */ + EXYNOS_USB3_GCTL_RAMClkSel(0x2) | /* Ram Clock Select */ + EXYNOS_USB3_GCTL_DisScramble); + + if (host) + gctl |= EXYNOS_USB3_GCTL_PrtCapDir(0x1);/* 0x1 : Host */ + else + gctl |= EXYNOS_USB3_GCTL_PrtCapDir(0x2);/* 0x2 : Device */ + + writel(gctl, hcd->regs + EXYNOS_USB3_GCTL); + + printk(KERN_INFO "Change xHCI %s mode %x\n", + host ? "host" : "device", gctl); + + return gctl; +} + +static void exynos_xhci_phy_set(struct platform_device *pdev) +{ + struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data; + struct exynos_xhci_hcd *exynos_xhci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = exynos_xhci->hcd; + /* The reset values: + * GUSB2PHYCFG(0) = 0x00002400 + * GUSB3PIPECTL(0) = 0x00260002 + */ + + __orr32(hcd->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset); + __orr32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst); + __orr32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst); + + /* PHY initialization */ + if (pdata && pdata->phy_init) + pdata->phy_init(pdev, pdata->phy_type); + + __bic32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_PHYSoftRst); + __bic32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_PHYSoftRst); + __bic32(hcd->regs + EXYNOS_USB3_GCTL, EXYNOS_USB3_GCTL_CoreSoftReset); + + + __bic32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY | + EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM | + EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim_MASK); + __orr32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_USBTrdTim(9)); + + __bic32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + dev_dbg(exynos_xhci->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x", + readl(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0)), + readl(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0))); + + /* Global core init */ + writel(EXYNOS_USB3_GSBUSCFG0_INCR16BrstEna | + EXYNOS_USB3_GSBUSCFG0_INCR8BrstEna | + EXYNOS_USB3_GSBUSCFG0_INCR4BrstEna, + hcd->regs + EXYNOS_USB3_GSBUSCFG0); + + writel(EXYNOS_USB3_GSBUSCFG1_BREQLIMIT(0x3), + hcd->regs + EXYNOS_USB3_GSBUSCFG1); + + writel(0x0, hcd->regs + EXYNOS_USB3_GTXTHRCFG); + writel(0x0, hcd->regs + EXYNOS_USB3_GRXTHRCFG); +} + +static void exynos_xhci_phy_unset(struct platform_device *pdev) +{ + struct exynos_usb3_drd_pdata *pdata = pdev->dev.platform_data; + struct exynos_xhci_hcd *exynos_xhci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = exynos_xhci->hcd; + + __orr32(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0), + EXYNOS_USB3_GUSB2PHYCFGx_SusPHY | + EXYNOS_USB3_GUSB2PHYCFGx_EnblSlpM); + + __orr32(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0), + EXYNOS_USB3_GUSB3PIPECTLx_SuspSSPhy); + + dev_dbg(exynos_xhci->dev, "GUSB2PHYCFG(0)=0x%08x, GUSB3PIPECTL(0)=0x%08x", + readl(hcd->regs + EXYNOS_USB3_GUSB2PHYCFG(0)), + readl(hcd->regs + EXYNOS_USB3_GUSB3PIPECTL(0))); + + /* PHY shutdown */ + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, pdata->phy_type); +} + +#ifdef CONFIG_PM +static int exynos_xhci_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_xhci_hcd *exynos_xhci; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + int retval = 0; + + exynos_xhci = dev_get_drvdata(dev); + if (!exynos_xhci) + return -EINVAL; + + hcd = exynos_xhci->hcd; + if (!hcd) + return -EINVAL; + + xhci = hcd_to_xhci(hcd); + + if (hcd->state != HC_STATE_SUSPENDED || + xhci->shared_hcd->state != HC_STATE_SUSPENDED) + return -EINVAL; + + if (pm_runtime_suspended(dev)) + return 0; + + retval = xhci_suspend(xhci); + + exynos_xhci_change_mode(hcd, false); + exynos_xhci_phy_unset(pdev); + clk_disable(exynos_xhci->clk); + + return retval; +} + +static int exynos_xhci_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_xhci_hcd *exynos_xhci; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + int retval = 0; + + exynos_xhci = dev_get_drvdata(dev); + if (!exynos_xhci) + return -EINVAL; + + hcd = exynos_xhci->hcd; + if (!hcd) + return -EINVAL; + + pm_runtime_resume(dev); + + clk_enable(exynos_xhci->clk); + + exynos_xhci_change_mode(hcd, true); + exynos_xhci_phy_set(pdev); + + xhci = hcd_to_xhci(hcd); + retval = xhci_resume(xhci, 0); + + return retval; +} +#else +#define exynos_xhci_suspend NULL +#define exynos_xhci_resume NULL +#endif + +#ifdef CONFIG_USB_SUSPEND +static int exynos_xhci_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_xhci_hcd *exynos_xhci; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + int retval = 0; + + exynos_xhci = dev_get_drvdata(dev); + if (!exynos_xhci) + return -EINVAL; + + hcd = exynos_xhci->hcd; + if (!hcd) + return -EINVAL; + + xhci = hcd_to_xhci(hcd); + + if (hcd->state != HC_STATE_SUSPENDED || + xhci->shared_hcd->state != HC_STATE_SUSPENDED) + return -EINVAL; + + retval = xhci_suspend(xhci); + + exynos_xhci_change_mode(hcd, false); + exynos_xhci_phy_unset(pdev); + clk_disable(exynos_xhci->clk); +#ifdef CONFIG_USB_EXYNOS_SWITCH + disable_irq(exynos_xhci->irq); +#endif + return retval; +} + +static int exynos_xhci_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_xhci_hcd *exynos_xhci; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + int retval = 0; + + exynos_xhci = dev_get_drvdata(dev); + if (!exynos_xhci) + return -EINVAL; + + hcd = exynos_xhci->hcd; + if (!hcd) + return -EINVAL; + +#ifdef CONFIG_USB_EXYNOS_SWITCH + enable_irq(exynos_xhci->irq); +#endif + if (dev->power.is_suspended) + return 0; + + clk_enable(exynos_xhci->clk); + + exynos_xhci_change_mode(hcd, true); + exynos_xhci_phy_set(pdev); + + xhci = hcd_to_xhci(hcd); + retval = xhci_resume(xhci, 0); + + return retval; +} +#else +#define exynos_xhci_runtime_suspend NULL +#define exynos_xhci_runtime_resume NULL +#endif + +int exynos_xhci_bus_resume(struct usb_hcd *hcd) +{ + /* When suspend is failed, re-enable clocks & PHY */ + pm_runtime_resume(hcd->self.controller); + + return xhci_bus_resume(hcd); +} + +static void exynos_xhci_quirks(struct device *dev, struct xhci_hcd *xhci) +{ + /* Don't use MSI interrupt */ + xhci->quirks |= XHCI_BROKEN_MSI; +} + +/* called during probe() after chip reset completes */ +static int exynos_xhci_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci; + int retval; + + retval = xhci_gen_setup(hcd, exynos_xhci_quirks); + if (retval) + return retval; + + xhci = hcd_to_xhci(hcd); + if (!usb_hcd_is_primary_hcd(hcd)) + return 0; + + xhci->sbrn = HCD_USB3; + xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int) xhci->sbrn); + + return retval; +} + + +static const struct hc_driver exynos_xhci_hc_driver = { + .description = hcd_name, + .product_desc = "EXYNOS xHCI Host Controller", + .hcd_priv_size = sizeof(struct xhci_hcd *), + + /* + * generic hardware linkage + */ + .irq = xhci_irq, + .flags = HCD_MEMORY | HCD_USB3 | HCD_SHARED, + + /* + * basic lifecycle operations + */ + .reset = exynos_xhci_setup, + .start = xhci_run, + .stop = xhci_stop, + .shutdown = xhci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = xhci_urb_enqueue, + .urb_dequeue = xhci_urb_dequeue, + .alloc_dev = xhci_alloc_dev, + .free_dev = xhci_free_dev, + .alloc_streams = xhci_alloc_streams, + .free_streams = xhci_free_streams, + .add_endpoint = xhci_add_endpoint, + .drop_endpoint = xhci_drop_endpoint, + .endpoint_reset = xhci_endpoint_reset, + .check_bandwidth = xhci_check_bandwidth, + .reset_bandwidth = xhci_reset_bandwidth, + .address_device = xhci_address_device, + .update_hub_device = xhci_update_hub_device, + .reset_device = xhci_discover_or_reset_device, + + /* + * scheduling support + */ + .get_frame_number = xhci_get_frame, + + /* Root hub support */ + .hub_control = xhci_hub_control, + .hub_status_data = xhci_hub_status_data, + .bus_suspend = xhci_bus_suspend, + .bus_resume = exynos_xhci_bus_resume, +}; + +static int usb_hcd_exynos_probe(struct platform_device *pdev, const struct hc_driver *driver) +{ + struct exynos_xhci_hcd *exynos_xhci; + struct exynos_xhci_plat *pdata; + struct usb_hcd *hcd; + struct resource *res; + int err; + + if (usb_disabled()) + return -ENODEV; + + if (!driver) + return -EINVAL; + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "No platform data defined\n"); + return -EINVAL; + } + + exynos_xhci = kzalloc(sizeof(struct exynos_xhci_hcd), GFP_KERNEL); + if (!exynos_xhci) + return -ENOMEM; + + hcd = usb_create_hcd(driver, &pdev->dev, dev_name(&pdev->dev)); + if (!hcd) { + err = -ENOMEM; + goto fail_hcd; + } + + + exynos_xhci->clk = clk_get(&pdev->dev, "usbdrd30"); + if (IS_ERR(exynos_xhci->clk)) { + dev_err(&pdev->dev, "Failed to get usbhost clock\n"); + err = PTR_ERR(exynos_xhci->clk); + goto fail_clk; + } + + err = clk_enable(exynos_xhci->clk); + if (err) + goto fail_clken; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Failed to get I/O memory\n"); + err = -ENXIO; + goto fail_io; + } + + /* EHCI, OHCI */ + hcd->rsrc_start = res->start; + hcd->rsrc_len = resource_size(res); + hcd->regs = ioremap(res->start, resource_size(res)); + if (!hcd->regs) { + dev_err(&pdev->dev, "Failed to remap I/O memory\n"); + err = -ENOMEM; + goto fail_io; + } + + exynos_xhci->irq = platform_get_irq(pdev, 0); + if (!exynos_xhci->irq) { + dev_err(&pdev->dev, "Failed to get IRQ\n"); + err = -ENODEV; + goto fail; + } + + exynos_xhci->dev = &pdev->dev; + exynos_xhci->hcd = hcd; + platform_set_drvdata(pdev, exynos_xhci); + + exynos_xhci_change_mode(hcd, true); + exynos_xhci_phy_set(pdev); + + err = usb_add_hcd(hcd, exynos_xhci->irq, IRQF_DISABLED | IRQF_SHARED); + if (err) { + dev_err(&pdev->dev, "Failed to add USB HCD\n"); + goto fail; + } + + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + return err; + +fail: + iounmap(hcd->regs); +fail_io: + clk_disable(exynos_xhci->clk); +fail_clken: + clk_put(exynos_xhci->clk); +fail_clk: + usb_put_hcd(hcd); +fail_hcd: + kfree(exynos_xhci); + return err; +} + +void usb_hcd_exynos_remove(struct platform_device *pdev) +{ + struct exynos_xhci_hcd *exynos_xhci; + struct usb_hcd *hcd; + + exynos_xhci = dev_get_drvdata(&pdev->dev); + hcd = exynos_xhci->hcd; + if (!hcd) + return; + + pm_runtime_disable(&pdev->dev); + /* Fake an interrupt request in order to give the driver a chance + * to test whether the controller hardware has been removed (e.g., + * cardbus physical eject). + */ + local_irq_disable(); + usb_hcd_irq(0, hcd); + local_irq_enable(); + + usb_remove_hcd(hcd); + + exynos_xhci_change_mode(hcd, false); + exynos_xhci_phy_unset(pdev); + + if (hcd->driver->flags & HCD_MEMORY) { + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + } else { + release_region(hcd->rsrc_start, hcd->rsrc_len); + } + usb_put_hcd(hcd); + + kfree(exynos_xhci); + clk_disable(exynos_xhci->clk); + clk_put(exynos_xhci->clk); +} + +static int __devinit exynos_xhci_probe(struct platform_device *pdev) +{ + struct exynos_xhci_hcd *exynos_xhci; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + int err; + + /* Register the USB 2.0 roothub. + * FIXME: USB core must know to register the USB 2.0 roothub first. + * This is sort of silly, because we could just set the HCD driver flags + * to say USB 2.0, but I'm not sure what the implications would be in + * the other parts of the HCD code. + */ + err = usb_hcd_exynos_probe(pdev, &exynos_xhci_hc_driver); + + if (err) + return err; + + exynos_xhci = dev_get_drvdata(&pdev->dev); + hcd = exynos_xhci->hcd; + xhci = hcd_to_xhci(hcd); + xhci->shared_hcd = usb_create_shared_hcd(&exynos_xhci_hc_driver, + &pdev->dev, dev_name(&pdev->dev), hcd); + if (!hcd) { + dev_err(&pdev->dev, "Unable to create HCD\n"); + err = -ENOMEM; + goto dealloc_usb2_hcd; + } + + exynos_xhci_dbg = xhci; + /* Set the xHCI pointer before exynos_xhci_setup() (aka hcd_driver.reset) + * is called by usb_add_hcd(). + */ + *((struct xhci_hcd **) xhci->shared_hcd->hcd_priv) = xhci; + + err = usb_add_hcd(xhci->shared_hcd, exynos_xhci->irq, + IRQF_DISABLED | IRQF_SHARED); + if (err) + goto put_usb3_hcd; + + /* Roothub already marked as USB 3.0 speed */ + return err; + +put_usb3_hcd: + usb_put_hcd(xhci->shared_hcd); +dealloc_usb2_hcd: + usb_remove_hcd(hcd); + usb_hcd_exynos_remove(pdev); + + return err; +} + +static int __devexit exynos_xhci_remove(struct platform_device *pdev) +{ + struct exynos_xhci_hcd *exynos_xhci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = exynos_xhci->hcd; + struct xhci_hcd *xhci; + + xhci = hcd_to_xhci(hcd); + if (xhci->shared_hcd) { + usb_remove_hcd(xhci->shared_hcd); + usb_put_hcd(xhci->shared_hcd); + } + usb_remove_hcd(hcd); + + usb_hcd_exynos_remove(pdev); + kfree(xhci); + + return 0; +} + +static void exynos_xhci_shutdown(struct platform_device *pdev) +{ + struct exynos_xhci_hcd *s5p_xhci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_xhci->hcd; + + if (hcd->driver->shutdown) + hcd->driver->shutdown(hcd); +} + +static const struct dev_pm_ops exynos_xhci_pm_ops = { + .suspend = exynos_xhci_suspend, + .resume = exynos_xhci_resume, + .runtime_suspend = exynos_xhci_runtime_suspend, + .runtime_resume = exynos_xhci_runtime_resume, +}; + +static struct platform_driver exynos_xhci_driver = { + .probe = exynos_xhci_probe, + .remove = __devexit_p(exynos_xhci_remove), + .shutdown = exynos_xhci_shutdown, + .driver = { + .name = "exynos-xhci", + .owner = THIS_MODULE, + .pm = &exynos_xhci_pm_ops, + } +}; + +int xhci_register_exynos(void) +{ + return platform_driver_register(&exynos_xhci_driver); +} + +void xhci_unregister_exynos(void) +{ + platform_driver_unregister(&exynos_xhci_driver); +} + +MODULE_ALIAS("platform:exynos-xhci"); diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index ffeee57..61a0cf3 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -319,7 +319,7 @@ static void xhci_free_stream_ctx(struct xhci_hcd *xhci, struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE) - pci_free_consistent(pdev, + dma_free_coherent(&pdev->dev, sizeof(struct xhci_stream_ctx)*num_stream_ctxs, stream_ctx, dma); else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE) @@ -347,9 +347,9 @@ static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); if (num_stream_ctxs > MEDIUM_STREAM_ARRAY_SIZE) - return pci_alloc_consistent(pdev, + return dma_alloc_coherent(&pdev->dev, sizeof(struct xhci_stream_ctx)*num_stream_ctxs, - dma); + dma, mem_flags); else if (num_stream_ctxs <= SMALL_STREAM_ARRAY_SIZE) return dma_pool_alloc(xhci->small_streams_pool, mem_flags, dma); @@ -1344,10 +1344,9 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) if (!xhci->scratchpad) goto fail_sp; - xhci->scratchpad->sp_array = - pci_alloc_consistent(to_pci_dev(dev), + xhci->scratchpad->sp_array = dma_alloc_coherent(dev, num_sp * sizeof(u64), - &xhci->scratchpad->sp_dma); + &xhci->scratchpad->sp_dma, flags); if (!xhci->scratchpad->sp_array) goto fail_sp2; @@ -1364,8 +1363,8 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) xhci->dcbaa->dev_context_ptrs[0] = cpu_to_le64(xhci->scratchpad->sp_dma); for (i = 0; i < num_sp; i++) { dma_addr_t dma; - void *buf = pci_alloc_consistent(to_pci_dev(dev), - xhci->page_size, &dma); + void *buf = dma_alloc_coherent(dev, xhci->page_size, &dma, + flags); if (!buf) goto fail_sp5; @@ -1378,7 +1377,7 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) fail_sp5: for (i = i - 1; i >= 0; i--) { - pci_free_consistent(to_pci_dev(dev), xhci->page_size, + dma_free_coherent(dev, xhci->page_size, xhci->scratchpad->sp_buffers[i], xhci->scratchpad->sp_dma_buffers[i]); } @@ -1388,7 +1387,7 @@ static int scratchpad_alloc(struct xhci_hcd *xhci, gfp_t flags) kfree(xhci->scratchpad->sp_buffers); fail_sp3: - pci_free_consistent(to_pci_dev(dev), num_sp * sizeof(u64), + dma_free_coherent(dev, num_sp * sizeof(u64), xhci->scratchpad->sp_array, xhci->scratchpad->sp_dma); @@ -1412,13 +1411,13 @@ static void scratchpad_free(struct xhci_hcd *xhci) num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); for (i = 0; i < num_sp; i++) { - pci_free_consistent(pdev, xhci->page_size, + dma_free_coherent(&pdev->dev, xhci->page_size, xhci->scratchpad->sp_buffers[i], xhci->scratchpad->sp_dma_buffers[i]); } kfree(xhci->scratchpad->sp_dma_buffers); kfree(xhci->scratchpad->sp_buffers); - pci_free_consistent(pdev, num_sp * sizeof(u64), + dma_free_coherent(&pdev->dev, num_sp * sizeof(u64), xhci->scratchpad->sp_array, xhci->scratchpad->sp_dma); kfree(xhci->scratchpad); @@ -1500,7 +1499,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) } size = sizeof(struct xhci_erst_entry)*(xhci->erst.num_entries); if (xhci->erst.entries) - pci_free_consistent(pdev, size, + dma_free_coherent(&pdev->dev, size, xhci->erst.entries, xhci->erst.erst_dma_addr); xhci->erst.entries = NULL; xhci_dbg(xhci, "Freed ERST\n"); @@ -1540,7 +1539,7 @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) xhci_write_64(xhci, 0, &xhci->op_regs->dcbaa_ptr); if (xhci->dcbaa) - pci_free_consistent(pdev, sizeof(*xhci->dcbaa), + dma_free_coherent(&pdev->dev, sizeof(*xhci->dcbaa), xhci->dcbaa, xhci->dcbaa->dma); xhci->dcbaa = NULL; @@ -1959,8 +1958,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) * Section 5.4.8 - doorbell array must be * "physically contiguous and 64-byte (cache line) aligned". */ - xhci->dcbaa = pci_alloc_consistent(to_pci_dev(dev), - sizeof(*xhci->dcbaa), &dma); + xhci->dcbaa = dma_alloc_coherent(dev, sizeof(*xhci->dcbaa), &dma, + GFP_KERNEL); if (!xhci->dcbaa) goto fail; memset(xhci->dcbaa, 0, sizeof *(xhci->dcbaa)); @@ -1994,7 +1993,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) dma_pool_create("xHCI 1KB stream ctx arrays", dev, MEDIUM_STREAM_ARRAY_SIZE, 16, 0); /* Any stream context array bigger than MEDIUM_STREAM_ARRAY_SIZE - * will be allocated with pci_alloc_consistent() + * will be allocated with dma_alloc_coherent() */ if (!xhci->small_streams_pool || !xhci->medium_streams_pool) @@ -2039,8 +2038,9 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) if (xhci_check_trb_in_td_math(xhci, flags) < 0) goto fail; - xhci->erst.entries = pci_alloc_consistent(to_pci_dev(dev), - sizeof(struct xhci_erst_entry)*ERST_NUM_SEGS, &dma); + xhci->erst.entries = dma_alloc_coherent(dev, + sizeof(struct xhci_erst_entry) * ERST_NUM_SEGS, &dma, + GFP_KERNEL); if (!xhci->erst.entries) goto fail; xhci_dbg(xhci, "// Allocated event ring segment table at 0x%llx\n", diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c index 50e7156..dc78c63 100644 --- a/drivers/usb/host/xhci-pci.c +++ b/drivers/usb/host/xhci-pci.c @@ -51,61 +51,9 @@ static int xhci_pci_reinit(struct xhci_hcd *xhci, struct pci_dev *pdev) return 0; } -/* called during probe() after chip reset completes */ -static int xhci_pci_setup(struct usb_hcd *hcd) +static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci) { - struct xhci_hcd *xhci; - struct pci_dev *pdev = to_pci_dev(hcd->self.controller); - int retval; - u32 temp; - - hcd->self.sg_tablesize = TRBS_PER_SEGMENT - 2; - - if (usb_hcd_is_primary_hcd(hcd)) { - xhci = kzalloc(sizeof(struct xhci_hcd), GFP_KERNEL); - if (!xhci) - return -ENOMEM; - *((struct xhci_hcd **) hcd->hcd_priv) = xhci; - xhci->main_hcd = hcd; - /* Mark the first roothub as being USB 2.0. - * The xHCI driver will register the USB 3.0 roothub. - */ - hcd->speed = HCD_USB2; - hcd->self.root_hub->speed = USB_SPEED_HIGH; - /* - * USB 2.0 roothub under xHCI has an integrated TT, - * (rate matching hub) as opposed to having an OHCI/UHCI - * companion controller. - */ - hcd->has_tt = 1; - } else { - /* xHCI private pointer was set in xhci_pci_probe for the second - * registered roothub. - */ - xhci = hcd_to_xhci(hcd); - temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); - if (HCC_64BIT_ADDR(temp)) { - xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); - dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)); - } else { - dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); - } - return 0; - } - - xhci->cap_regs = hcd->regs; - xhci->op_regs = hcd->regs + - HC_LENGTH(xhci_readl(xhci, &xhci->cap_regs->hc_capbase)); - xhci->run_regs = hcd->regs + - (xhci_readl(xhci, &xhci->cap_regs->run_regs_off) & RTSOFF_MASK); - /* Cache read-only capability registers */ - xhci->hcs_params1 = xhci_readl(xhci, &xhci->cap_regs->hcs_params1); - xhci->hcs_params2 = xhci_readl(xhci, &xhci->cap_regs->hcs_params2); - xhci->hcs_params3 = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); - xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); - xhci->hci_version = HC_VERSION(xhci->hcc_params); - xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hcc_params); - xhci_print_registers(xhci); + struct pci_dev *pdev = to_pci_dev(dev); /* Look for vendor-specific quirks */ if (pdev->vendor == PCI_VENDOR_ID_FRESCO_LOGIC && @@ -145,33 +93,22 @@ static int xhci_pci_setup(struct usb_hcd *hcd) xhci->quirks |= XHCI_RESET_ON_RESUME; xhci_dbg(xhci, "QUIRK: Resetting on resume\n"); } +} - /* Make sure the HC is halted. */ - retval = xhci_halt(xhci); - if (retval) - goto error; +/* called during probe() after chip reset completes */ +static int xhci_pci_setup(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci; + struct pci_dev *pdev = to_pci_dev(hcd->self.controller); + int retval; - xhci_dbg(xhci, "Resetting HCD\n"); - /* Reset the internal HC memory state and registers. */ - retval = xhci_reset(xhci); + retval = xhci_gen_setup(hcd, xhci_pci_quirks); if (retval) - goto error; - xhci_dbg(xhci, "Reset complete\n"); - - temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); - if (HCC_64BIT_ADDR(temp)) { - xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); - dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)); - } else { - dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); - } + return retval; - xhci_dbg(xhci, "Calling HCD init\n"); - /* Initialize HCD and host controller data structures. */ - retval = xhci_init(hcd); - if (retval) - goto error; - xhci_dbg(xhci, "Called HCD init\n"); + xhci = hcd_to_xhci(hcd); + if (!usb_hcd_is_primary_hcd(hcd)) + return 0; pci_read_config_byte(pdev, XHCI_SBRN_OFFSET, &xhci->sbrn); xhci_dbg(xhci, "Got SBRN %u\n", (unsigned int) xhci->sbrn); @@ -181,7 +118,6 @@ static int xhci_pci_setup(struct usb_hcd *hcd) if (!retval) return retval; -error: kfree(xhci); return retval; } @@ -378,12 +314,12 @@ static struct pci_driver xhci_pci_driver = { #endif }; -int xhci_register_pci(void) +int __init xhci_register_pci(void) { return pci_register_driver(&xhci_pci_driver); } -void xhci_unregister_pci(void) +void __exit xhci_unregister_pci(void) { pci_unregister_driver(&xhci_pci_driver); } diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index b4b0691..9615b80 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1647,7 +1647,6 @@ static int process_ctrl_td(struct xhci_hcd *xhci, struct xhci_td *td, } break; case COMP_SHORT_TX: - xhci_warn(xhci, "WARN: short transfer on control ep\n"); if (td->urb->transfer_flags & URB_SHORT_NOT_OK) *status = -EREMOTEIO; else @@ -1993,7 +1992,7 @@ static int handle_tx_event(struct xhci_hcd *xhci, xhci_dbg(xhci, "Stopped on No-op or Link TRB\n"); break; case COMP_STALL: - xhci_warn(xhci, "WARN: Stalled endpoint\n"); + xhci_dbg(xhci, "Stalled endpoint\n"); ep->ep_state |= EP_HALTED; status = -EPIPE; break; @@ -2003,11 +2002,11 @@ static int handle_tx_event(struct xhci_hcd *xhci, break; case COMP_SPLIT_ERR: case COMP_TX_ERR: - xhci_warn(xhci, "WARN: transfer error on endpoint\n"); + xhci_dbg(xhci, "Transfer error on endpoint\n"); status = -EPROTO; break; case COMP_BABBLE: - xhci_warn(xhci, "WARN: babble error on endpoint\n"); + xhci_dbg(xhci, "Babble error on endpoint\n"); status = -EOVERFLOW; break; case COMP_DB_ERR: diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 221f14e..7605e46 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -175,28 +175,19 @@ int xhci_reset(struct xhci_hcd *xhci) return handshake(xhci, &xhci->op_regs->status, STS_CNR, 0, 250 * 1000); } -/* - * Free IRQs - * free all IRQs request - */ -static void xhci_free_irq(struct xhci_hcd *xhci) +#ifdef CONFIG_PCI +static int xhci_free_msi(struct xhci_hcd *xhci) { int i; - struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); - - /* return if using legacy interrupt */ - if (xhci_to_hcd(xhci)->irq >= 0) - return; - if (xhci->msix_entries) { - for (i = 0; i < xhci->msix_count; i++) - if (xhci->msix_entries[i].vector) - free_irq(xhci->msix_entries[i].vector, - xhci_to_hcd(xhci)); - } else if (pdev->irq >= 0) - free_irq(pdev->irq, xhci_to_hcd(xhci)); + if (!xhci->msix_entries) + return -EINVAL; - return; + for (i = 0; i < xhci->msix_count; i++) + if (xhci->msix_entries[i].vector) + free_irq(xhci->msix_entries[i].vector, + xhci_to_hcd(xhci)); + return 0; } /* @@ -224,6 +215,28 @@ static int xhci_setup_msi(struct xhci_hcd *xhci) } /* + * Free IRQs + * free all IRQs request + */ +static void xhci_free_irq(struct xhci_hcd *xhci) +{ + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + int ret; + + /* return if using legacy interrupt */ + if (xhci_to_hcd(xhci)->irq >= 0) + return; + + ret = xhci_free_msi(xhci); + if (!ret) + return; + if (pdev->irq >= 0) + free_irq(pdev->irq, xhci_to_hcd(xhci)); + + return; +} + +/* * Set up MSI-X */ static int xhci_setup_msix(struct xhci_hcd *xhci) @@ -302,6 +315,72 @@ static void xhci_cleanup_msix(struct xhci_hcd *xhci) return; } +static void xhci_msix_sync_irqs(struct xhci_hcd *xhci) +{ + int i; + + if (xhci->msix_entries) { + for (i = 0; i < xhci->msix_count; i++) + synchronize_irq(xhci->msix_entries[i].vector); + } +} + +static int xhci_try_enable_msi(struct usb_hcd *hcd) +{ + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); + int ret; + + /* + * Some Fresco Logic host controllers advertise MSI, but fail to + * generate interrupts. Don't even try to enable MSI. + */ + if (xhci->quirks & XHCI_BROKEN_MSI) + return 0; + + /* unregister the legacy interrupt */ + if (hcd->irq) + free_irq(hcd->irq, hcd); + hcd->irq = -1; + + ret = xhci_setup_msix(xhci); + if (ret) + /* fall back to msi*/ + ret = xhci_setup_msi(xhci); + + if (!ret) + /* hcd->irq is -1, we have MSI */ + return 0; + + /* fall back to legacy interrupt*/ + ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, + hcd->irq_descr, hcd); + if (ret) { + xhci_err(xhci, "request interrupt %d failed\n", + pdev->irq); + return ret; + } + hcd->irq = pdev->irq; + return 0; +} + +#else + +static int xhci_try_enable_msi(struct usb_hcd *hcd) +{ + return 0; +} + +static void xhci_cleanup_msix(struct xhci_hcd *xhci) +{ +} + +static void xhci_msix_sync_irqs(struct xhci_hcd *xhci) +{ +} + +#endif + /* * Initialize memory for HCD and xHC (one-time init). * @@ -413,9 +492,8 @@ int xhci_run(struct usb_hcd *hcd) { u32 temp; u64 temp_64; - u32 ret; + int ret; struct xhci_hcd *xhci = hcd_to_xhci(hcd); - struct pci_dev *pdev = to_pci_dev(xhci_to_hcd(xhci)->self.controller); /* Start the xHCI host controller running only after the USB 2.0 roothub * is setup. @@ -426,34 +504,10 @@ int xhci_run(struct usb_hcd *hcd) return xhci_run_finished(xhci); xhci_dbg(xhci, "xhci_run\n"); - /* unregister the legacy interrupt */ - if (hcd->irq) - free_irq(hcd->irq, hcd); - hcd->irq = -1; - - /* Some Fresco Logic host controllers advertise MSI, but fail to - * generate interrupts. Don't even try to enable MSI. - */ - if (xhci->quirks & XHCI_BROKEN_MSI) - goto legacy_irq; - ret = xhci_setup_msix(xhci); + ret = xhci_try_enable_msi(hcd); if (ret) - /* fall back to msi*/ - ret = xhci_setup_msi(xhci); - - if (ret) { -legacy_irq: - /* fall back to legacy interrupt*/ - ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, - hcd->irq_descr, hcd); - if (ret) { - xhci_err(xhci, "request interrupt %d failed\n", - pdev->irq); - return ret; - } - hcd->irq = pdev->irq; - } + return ret; #ifdef CONFIG_USB_XHCI_HCD_DEBUGGING init_timer(&xhci->event_ring_timer); @@ -697,7 +751,6 @@ int xhci_suspend(struct xhci_hcd *xhci) int rc = 0; struct usb_hcd *hcd = xhci_to_hcd(xhci); u32 command; - int i; spin_lock_irq(&xhci->lock); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); @@ -733,10 +786,7 @@ int xhci_suspend(struct xhci_hcd *xhci) /* step 5: remove core well power */ /* synchronize irq when using MSI-X */ - if (xhci->msix_entries) { - for (i = 0; i < xhci->msix_count; i++) - synchronize_irq(xhci->msix_entries[i].vector); - } + xhci_msix_sync_irqs(xhci); return rc; } @@ -3064,17 +3114,110 @@ int xhci_get_frame(struct usb_hcd *hcd) return xhci_readl(xhci, &xhci->run_regs->microframe_index) >> 3; } +int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks) +{ + struct xhci_hcd *xhci; + struct device *dev = hcd->self.controller; + int retval; + u32 temp; + + hcd->self.sg_tablesize = TRBS_PER_SEGMENT - 2; + + if (usb_hcd_is_primary_hcd(hcd)) { + xhci = kzalloc(sizeof(struct xhci_hcd), GFP_KERNEL); + if (!xhci) + return -ENOMEM; + *((struct xhci_hcd **) hcd->hcd_priv) = xhci; + xhci->main_hcd = hcd; + /* Mark the first roothub as being USB 2.0. + * The xHCI driver will register the USB 3.0 roothub. + */ + hcd->speed = HCD_USB2; + hcd->self.root_hub->speed = USB_SPEED_HIGH; + /* + * USB 2.0 roothub under xHCI has an integrated TT, + * (rate matching hub) as opposed to having an OHCI/UHCI + * companion controller. + */ + hcd->has_tt = 1; + } else { + /* xHCI private pointer was set in xhci_pci_probe for the second + * registered roothub. + */ + xhci = hcd_to_xhci(hcd); + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + if (HCC_64BIT_ADDR(temp)) { + xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)); + } else { + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); + } + return 0; + } + + xhci->cap_regs = hcd->regs; + xhci->op_regs = hcd->regs + + HC_LENGTH(xhci_readl(xhci, &xhci->cap_regs->hc_capbase)); + xhci->run_regs = hcd->regs + + (xhci_readl(xhci, &xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + xhci->hcs_params1 = xhci_readl(xhci, &xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = xhci_readl(xhci, &xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = xhci_readl(xhci, &xhci->cap_regs->hcs_params3); + xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hc_capbase); + xhci->hci_version = HC_VERSION(xhci->hcc_params); + xhci->hcc_params = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + xhci_print_registers(xhci); + + get_quirks(dev, xhci); + + /* Make sure the HC is halted. */ + retval = xhci_halt(xhci); + if (retval) + goto error; + + xhci_dbg(xhci, "Resetting HCD\n"); + /* Reset the internal HC memory state and registers. */ + retval = xhci_reset(xhci); + if (retval) + goto error; + xhci_dbg(xhci, "Reset complete\n"); + + temp = xhci_readl(xhci, &xhci->cap_regs->hcc_params); + if (HCC_64BIT_ADDR(temp)) { + xhci_dbg(xhci, "Enabling 64-bit DMA addresses.\n"); + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(64)); + } else { + dma_set_mask(hcd->self.controller, DMA_BIT_MASK(32)); + } + + xhci_dbg(xhci, "Calling HCD init\n"); + /* Initialize HCD and host controller data structures. */ + retval = xhci_init(hcd); + if (retval) + goto error; + xhci_dbg(xhci, "Called HCD init\n"); + return 0; +error: + kfree(xhci); + return retval; +} + MODULE_DESCRIPTION(DRIVER_DESC); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_LICENSE("GPL"); static int __init xhci_hcd_init(void) { -#ifdef CONFIG_PCI - int retval = 0; - + int retval; +#ifdef CONFIG_USB_XHCI_EXYNOS + retval = xhci_register_exynos(); + if (retval < 0) { + printk(KERN_DEBUG "Problem registering Exynos driver."); + return retval; + } +#else retval = xhci_register_pci(); - if (retval < 0) { printk(KERN_DEBUG "Problem registering PCI driver."); return retval; @@ -3104,7 +3247,9 @@ module_init(xhci_hcd_init); static void __exit xhci_hcd_cleanup(void) { -#ifdef CONFIG_PCI +#ifdef CONFIG_USB_XHCI_EXYNOS + xhci_unregister_exynos(); +#else xhci_unregister_pci(); #endif } diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 49ce76c..3bd0d41 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1477,9 +1477,16 @@ void xhci_free_command(struct xhci_hcd *xhci, /* xHCI PCI glue */ int xhci_register_pci(void); void xhci_unregister_pci(void); +#elif CONFIG_USB_XHCI_EXYNOS +int xhci_register_exynos(void); +void xhci_unregister_exynos(void); +#else +static inline int xhci_register_pci(void) { return 0; } +static inline void xhci_unregister_pci(void) {} #endif /* xHCI host controller glue */ +typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); void xhci_quiesce(struct xhci_hcd *xhci); int xhci_halt(struct xhci_hcd *xhci); int xhci_reset(struct xhci_hcd *xhci); @@ -1487,6 +1494,7 @@ int xhci_init(struct usb_hcd *hcd); int xhci_run(struct usb_hcd *hcd); void xhci_stop(struct usb_hcd *hcd); void xhci_shutdown(struct usb_hcd *hcd); +int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks); #ifdef CONFIG_PM int xhci_suspend(struct xhci_hcd *xhci); diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1bfcd02..b2a96a5 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -244,3 +244,45 @@ config USB_YUREX To compile this driver as a module, choose M here: the module will be called yurex. +config USB_EXYNOS_SWITCH + bool "EXYNOS USB SWITCH driver" + depends on USB_EHCI_S5P || USB_OHCI_S5P || USB_XHCI_EXYNOS + depends on USB_SUSPEND + depends on S5P_DEV_USB_SWITCH + help + This driver is for switching host & device controller software. + +config USB_QCOM_DIAG_BRIDGE + tristate "USB Qualcomm diagnostic bridge driver" + depends on USB + help + Say Y here if you have a Qualcomm modem device connected via USB that + will be bridged in kernel space. This driver communicates with the + diagnostic interface and allows for bridging with the diag forwarding + driver. + + To compile this driver as a module, choose M here: the + module will be called diag_bridge. If unsure, choose N. + +config USB_QCOM_DIAG_BRIDGE_TEST + tristate "USB Qualcomm diagnostic bridge driver test" + depends on USB && USB_QCOM_DIAG_BRIDGE + help + Say Y here if you want to enable the test hook for the + Qualcomm diag bridge driver. When enabled, this will create + a debugfs file entry named "diag_bridge_test" which can be used + to send a ping command to the diag port of the modem over USB. + + To compile this driver as a module, choose M here: the + module will be called diag_bridge_test. If unsure, choose N. + +config USB_QCOM_MDM_BRIDGE + tristate "USB Qualcomm modem bridge driver for DUN and RMNET" + depends on USB + help + Say Y here if you have a Qualcomm modem device connected via USB that + will be bridged in kernel space. This driver works as a bridge to pass + control and data packets between the modem and peripheral usb gadget + driver for dial up network and RMNET. + To compile this driver as a module, choose M here: the module + will be called mdm_bridge. If unsure, choose N. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 796ce7e..1a00ac5 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -25,5 +25,11 @@ obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o obj-$(CONFIG_USB_USS720) += uss720.o obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o obj-$(CONFIG_USB_YUREX) += yurex.o +obj-$(CONFIG_USB_EXYNOS_SWITCH) += exynos-usb-switch.o obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ + +obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE) += diag_bridge.o +obj-$(CONFIG_USB_QCOM_DIAG_BRIDGE_TEST) += diag_bridge_test.o +mdm_bridge-y := mdm_ctrl_bridge.o mdm_data_bridge.o +obj-$(CONFIG_USB_QCOM_MDM_BRIDGE) += mdm_bridge.o diff --git a/drivers/usb/misc/diag_bridge.c b/drivers/usb/misc/diag_bridge.c new file mode 100644 index 0000000..9794918 --- /dev/null +++ b/drivers/usb/misc/diag_bridge.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2011-2012, Code Aurora Forum. 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. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/debugfs.h> +#include <mach/diag_bridge.h> + +#define DRIVER_DESC "USB host diag bridge driver" +#define DRIVER_VERSION "1.0" + +struct diag_bridge { + struct usb_device *udev; + struct usb_interface *ifc; + struct usb_anchor submitted; + __u8 in_epAddr; + __u8 out_epAddr; + int err; + struct kref kref; + struct diag_bridge_ops *ops; + struct platform_device *pdev; + + /* debugging counters */ + unsigned long bytes_to_host; + unsigned long bytes_to_mdm; + unsigned pending_reads; + unsigned pending_writes; +}; +struct diag_bridge *__dev; + +int diag_bridge_open(struct diag_bridge_ops *ops) +{ + struct diag_bridge *dev = __dev; + + if (!dev) { + err("dev is null"); + return -ENODEV; + } + + dev->ops = ops; + dev->err = 0; + + return 0; +} +EXPORT_SYMBOL(diag_bridge_open); + +void diag_bridge_close(void) +{ + struct diag_bridge *dev = __dev; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + usb_kill_anchored_urbs(&dev->submitted); + + dev->ops = 0; +} +EXPORT_SYMBOL(diag_bridge_close); + +static void diag_bridge_read_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + dev_dbg(&dev->udev->dev, "%s: status:%d actual:%d\n", __func__, + urb->status, urb->actual_length); + + if (urb->status == -EPROTO) { + dev_err(&dev->udev->dev, "%s: proto error\n", __func__); + /* save error so that subsequent read/write returns ESHUTDOWN */ + dev->err = urb->status; + return; + } + + cbs->read_complete_cb(cbs->ctxt, + urb->transfer_buffer, + urb->transfer_buffer_length, + urb->status < 0 ? urb->status : urb->actual_length); + + dev->bytes_to_host += urb->actual_length; + dev->pending_reads--; +} + +int diag_bridge_read(char *data, int size) +{ + struct urb *urb = NULL; + unsigned int pipe; + struct diag_bridge *dev = __dev; + int ret; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + if (!size) { + dev_err(&dev->udev->dev, "invalid size:%d\n", size); + return -EINVAL; + } + + if (!dev->ifc) { + dev_err(&dev->udev->dev, "device is disconnected\n"); + return -ENODEV; + } + + /* if there was a previous unrecoverable error, just quit */ + if (dev->err) + return -ESHUTDOWN; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + dev_err(&dev->udev->dev, "unable to allocate urb\n"); + return -ENOMEM; + } + + ret = usb_autopm_get_interface(dev->ifc); + if (ret < 0) { + dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret); + usb_free_urb(urb); + return ret; + } + + pipe = usb_rcvbulkpipe(dev->udev, dev->in_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, + diag_bridge_read_cb, dev); + usb_anchor_urb(urb, &dev->submitted); + dev->pending_reads++; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret); + dev->pending_reads--; + usb_unanchor_urb(urb); + usb_free_urb(urb); + usb_autopm_put_interface(dev->ifc); + return ret; + } + + usb_autopm_put_interface(dev->ifc); + usb_free_urb(urb); + + return 0; +} +EXPORT_SYMBOL(diag_bridge_read); + +static void diag_bridge_write_cb(struct urb *urb) +{ + struct diag_bridge *dev = urb->context; + struct diag_bridge_ops *cbs = dev->ops; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + usb_autopm_put_interface_async(dev->ifc); + + if (urb->status == -EPROTO) { + dev_err(&dev->udev->dev, "%s: proto error\n", __func__); + /* save error so that subsequent read/write returns ESHUTDOWN */ + dev->err = urb->status; + return; + } + + cbs->write_complete_cb(cbs->ctxt, + urb->transfer_buffer, + urb->transfer_buffer_length, + urb->status < 0 ? urb->status : urb->actual_length); + + dev->bytes_to_mdm += urb->actual_length; + dev->pending_writes--; +} + +int diag_bridge_write(char *data, int size) +{ + struct urb *urb = NULL; + unsigned int pipe; + struct diag_bridge *dev = __dev; + int ret; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + if (!size) { + dev_err(&dev->udev->dev, "invalid size:%d\n", size); + return -EINVAL; + } + + if (!dev->ifc) { + dev_err(&dev->udev->dev, "device is disconnected\n"); + return -ENODEV; + } + + /* if there was a previous unrecoverable error, just quit */ + if (dev->err) + return -ESHUTDOWN; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + err("unable to allocate urb"); + return -ENOMEM; + } + + ret = usb_autopm_get_interface(dev->ifc); + if (ret < 0) { + dev_err(&dev->udev->dev, "autopm_get failed:%d\n", ret); + usb_free_urb(urb); + return ret; + } + + pipe = usb_sndbulkpipe(dev->udev, dev->out_epAddr); + usb_fill_bulk_urb(urb, dev->udev, pipe, data, size, + diag_bridge_write_cb, dev); + usb_anchor_urb(urb, &dev->submitted); + dev->pending_writes++; + + ret = usb_submit_urb(urb, GFP_KERNEL); + if (ret) { + dev_err(&dev->udev->dev, "submitting urb failed err:%d\n", ret); + dev->pending_writes--; + usb_unanchor_urb(urb); + usb_free_urb(urb); + usb_autopm_put_interface(dev->ifc); + return ret; + } + + usb_free_urb(urb); + + return 0; +} +EXPORT_SYMBOL(diag_bridge_write); + +static void diag_bridge_delete(struct kref *kref) +{ + struct diag_bridge *dev = + container_of(kref, struct diag_bridge, kref); + + usb_put_dev(dev->udev); + __dev = 0; + kfree(dev); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 512 +static ssize_t diag_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct diag_bridge *dev = __dev; + char *buf; + int ret; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, DEBUG_BUF_SIZE, + "epin:%d, epout:%d\n" + "bytes to host: %lu\n" + "bytes to mdm: %lu\n" + "pending reads: %u\n" + "pending writes: %u\n" + "last error: %d\n", + dev->in_epAddr, dev->out_epAddr, + dev->bytes_to_host, dev->bytes_to_mdm, + dev->pending_reads, dev->pending_writes, + dev->err); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, ret); + kfree(buf); + return ret; +} + +static ssize_t diag_reset_stats(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct diag_bridge *dev = __dev; + + dev->bytes_to_host = dev->bytes_to_mdm = 0; + dev->pending_reads = dev->pending_writes = 0; + + return count; +} + +const struct file_operations diag_stats_ops = { + .read = diag_read_stats, + .write = diag_reset_stats, +}; + +static struct dentry *dent; + +static void diag_bridge_debugfs_init(void) +{ + struct dentry *dfile; + + dent = debugfs_create_dir("diag_bridge", 0); + if (IS_ERR(dent)) + return; + + dfile = debugfs_create_file("status", 0444, dent, 0, &diag_stats_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} + +static void diag_bridge_debugfs_cleanup(void) +{ + if (dent) { + debugfs_remove_recursive(dent); + dent = NULL; + } +} +#else +static inline void diag_bridge_debugfs_init(void) { } +static inline void diag_bridge_debugfs_cleanup(void) { } +#endif + +static int +diag_bridge_probe(struct usb_interface *ifc, const struct usb_device_id *id) +{ + struct diag_bridge *dev; + struct usb_host_interface *ifc_desc; + struct usb_endpoint_descriptor *ep_desc; + int i; + int ret = -ENOMEM; + __u8 ifc_num; + + dbg("%s: id:%lu", __func__, id->driver_info); + + ifc_num = ifc->cur_altsetting->desc.bInterfaceNumber; + + /* is this interface supported ? */ + if (ifc_num != id->driver_info) + return -ENODEV; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + pr_err("%s: unable to allocate dev\n", __func__); + return -ENOMEM; + } + dev->pdev = platform_device_alloc("diag_bridge", -1); + if (!dev->pdev) { + pr_err("%s: unable to allocate platform device\n", __func__); + kfree(dev); + return -ENOMEM; + } + __dev = dev; + + dev->udev = usb_get_dev(interface_to_usbdev(ifc)); + dev->ifc = ifc; + kref_init(&dev->kref); + init_usb_anchor(&dev->submitted); + + ifc_desc = ifc->cur_altsetting; + for (i = 0; i < ifc_desc->desc.bNumEndpoints; i++) { + ep_desc = &ifc_desc->endpoint[i].desc; + + if (!dev->in_epAddr && usb_endpoint_is_bulk_in(ep_desc)) + dev->in_epAddr = ep_desc->bEndpointAddress; + + if (!dev->out_epAddr && usb_endpoint_is_bulk_out(ep_desc)) + dev->out_epAddr = ep_desc->bEndpointAddress; + } + + if (!(dev->in_epAddr && dev->out_epAddr)) { + err("could not find bulk in and bulk out endpoints"); + ret = -ENODEV; + goto error; + } + + usb_set_intfdata(ifc, dev); + diag_bridge_debugfs_init(); + platform_device_add(dev->pdev); + + dev_dbg(&dev->udev->dev, "%s: complete\n", __func__); + + return 0; + +error: + if (dev) + kref_put(&dev->kref, diag_bridge_delete); + + return ret; +} + +static void diag_bridge_disconnect(struct usb_interface *ifc) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + platform_device_del(dev->pdev); + diag_bridge_debugfs_cleanup(); + kref_put(&dev->kref, diag_bridge_delete); + usb_set_intfdata(ifc, NULL); +} + +static int diag_bridge_suspend(struct usb_interface *ifc, pm_message_t message) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + struct diag_bridge_ops *cbs = dev->ops; + int ret = 0; + + if (cbs && cbs->suspend) { + ret = cbs->suspend(cbs->ctxt); + if (ret) { + dev_dbg(&dev->udev->dev, + "%s: diag veto'd suspend\n", __func__); + return ret; + } + + usb_kill_anchored_urbs(&dev->submitted); + } + + return ret; +} + +static int diag_bridge_resume(struct usb_interface *ifc) +{ + struct diag_bridge *dev = usb_get_intfdata(ifc); + struct diag_bridge_ops *cbs = dev->ops; + + + if (cbs && cbs->resume) + cbs->resume(cbs->ctxt); + + return 0; +} + +#define VALID_INTERFACE_NUM 0 +static const struct usb_device_id diag_bridge_ids[] = { + { USB_DEVICE(0x5c6, 0x9001), + .driver_info = VALID_INTERFACE_NUM, }, + { USB_DEVICE(0x5c6, 0x9034), + .driver_info = VALID_INTERFACE_NUM, }, + { USB_DEVICE(0x5c6, 0x9048), + .driver_info = VALID_INTERFACE_NUM, }, + { USB_DEVICE(0x5c6, 0x904C), + .driver_info = VALID_INTERFACE_NUM, }, + + {} /* terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, diag_bridge_ids); + +static struct usb_driver diag_bridge_driver = { + .name = "diag_bridge", + .probe = diag_bridge_probe, + .disconnect = diag_bridge_disconnect, + .suspend = diag_bridge_suspend, + .resume = diag_bridge_resume, + .id_table = diag_bridge_ids, + .supports_autosuspend = 1, +}; + +static int __init diag_bridge_init(void) +{ + int ret; + + ret = usb_register(&diag_bridge_driver); + if (ret) { + err("%s: unable to register diag driver", __func__); + return ret; + } + + return 0; +} + +static void __exit diag_bridge_exit(void) +{ + usb_deregister(&diag_bridge_driver); +} + +module_init(diag_bridge_init); +module_exit(diag_bridge_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/diag_bridge_test.c b/drivers/usb/misc/diag_bridge_test.c new file mode 100644 index 0000000..5bc0828 --- /dev/null +++ b/drivers/usb/misc/diag_bridge_test.c @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2011, Code Aurora Forum. 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. + */ + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/debugfs.h> +#include <linux/crc-ccitt.h> +#include <mach/diag_bridge.h> + +#define DRIVER_DESC "USB host diag bridge driver test" +#define DRIVER_VERSION "1.0" + +#define RD_BUF_SIZE 2048 +#define DIAG_TEST_CONNECTED 0 + +struct diag_test_dev { + char *read_buf; + struct work_struct read_w; + unsigned long flags; + + struct diag_bridge_ops ops; +}; +static struct diag_test_dev *__dev; +static struct dentry *dent; + +static void +diag_test_read_complete_cb(void *d, char *buf, size_t size, size_t actual) +{ + if (actual < 0) { + pr_err("%s: read complete err\n", __func__); + return; + } + + print_hex_dump(KERN_INFO, "to_host:", 0, 1, 1, buf, actual, false); +} +static void diag_test_read_work(struct work_struct *w) +{ + struct diag_test_dev *dev = + container_of(w, struct diag_test_dev, read_w); + + memset(dev->read_buf, 0, RD_BUF_SIZE); + diag_bridge_read(dev->read_buf, RD_BUF_SIZE); +} + +static void +diag_test_write_complete_cb(void *d, char *buf, size_t size, size_t actual) +{ + struct diag_test_dev *dev = d; + + if (actual > 0) + schedule_work(&dev->read_w); +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t send_ping_cmd(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct diag_test_dev *dev = __dev; + unsigned char *buf; + int temp = sizeof(unsigned char) * 4; + + if (!dev) + return -ENODEV; + + buf = kmalloc(temp, GFP_KERNEL); + if (!buf) { + pr_err("%s: unable to allocate mem for ping cmd\n", + __func__); + return -ENOMEM; + } + + /* hdlc encoded ping command */ + buf[0] = 0x0C; + buf[1] = 0x14; + buf[2] = 0x3A; + buf[3] = 0x7E; + + diag_bridge_write(buf, temp); + + return count; +} + +const struct file_operations diag_test_ping_ops = { + .write = send_ping_cmd, +}; + +static void diag_test_debug_init(void) +{ + struct dentry *dfile; + + dent = debugfs_create_dir("diag_test", 0); + if (IS_ERR(dent)) + return; + + dfile = debugfs_create_file("send_ping", 0444, dent, + 0, &diag_test_ping_ops); + if (!dfile || IS_ERR(dfile)) + debugfs_remove(dent); +} +#else +static void diag_test_debug_init(void) { } +#endif + +static int diag_test_remove(struct platform_device *pdev) +{ + diag_bridge_close(); + + if (dent) { + debugfs_remove_recursive(dent); + dent = NULL; + } + + return 0; +} + +static int diag_test_probe(struct platform_device *pdev) +{ + struct diag_test_dev *dev = __dev; + int ret = 0; + + pr_info("%s:\n", __func__); + + ret = diag_bridge_open(&dev->ops); + if (ret) + pr_err("diag open failed: %d", ret); + + + diag_test_debug_init(); + + return ret; +} + +static struct platform_driver diag_test = { + .remove = diag_test_remove, + .probe = diag_test_probe, + .driver = { + .name = "diag_bridge", + .owner = THIS_MODULE, + }, +}; + +static int __init diag_test_init(void) +{ + struct diag_test_dev *dev; + int ret = 0; + + pr_info("%s\n", __func__); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + __dev = dev; + + dev->ops.read_complete_cb = diag_test_read_complete_cb; + dev->ops.write_complete_cb = diag_test_write_complete_cb; + dev->read_buf = kmalloc(RD_BUF_SIZE, GFP_KERNEL); + if (!dev->read_buf) { + pr_err("%s: unable to allocate read buffer\n", __func__); + kfree(dev); + return -ENOMEM; + } + + dev->ops.ctxt = dev; + INIT_WORK(&dev->read_w, diag_test_read_work); + + ret = platform_driver_register(&diag_test); + if (ret) + pr_err("%s: platform driver %s register failed %d\n", + __func__, diag_test.driver.name, ret); + + return ret; +} + +static void __exit diag_test_exit(void) +{ + struct diag_test_dev *dev = __dev; + + pr_info("%s:\n", __func__); + + if (test_bit(DIAG_TEST_CONNECTED, &dev->flags)) + diag_bridge_close(); + + kfree(dev->read_buf); + kfree(dev); + +} + +module_init(diag_test_init); +module_exit(diag_test_exit); + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/exynos-usb-switch.c b/drivers/usb/misc/exynos-usb-switch.c new file mode 100644 index 0000000..519c845 --- /dev/null +++ b/drivers/usb/misc/exynos-usb-switch.c @@ -0,0 +1,642 @@ +/* + * exynos-usb-switch.c - USB switch driver for Exynos + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * Yulgon Kim <yulgon.kim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <plat/devs.h> +#include <plat/ehci.h> +#include <plat/usbgadget.h> +#include <plat/usb-switch.h> + +#include <mach/regs-clock.h> + +#include "../gadget/s3c_udc.h" +#include "../gadget/exynos_ss_udc.h" +#include "exynos-usb-switch.h" + +#define DRIVER_DESC "Exynos USB Switch Driver" +#define SWITCH_WAIT_TIME 500 +#define WAIT_TIMES 10 + +static const char switch_name[] = "exynos_usb_switch"; +static struct exynos_usb_switch *our_switch; + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) +void exynos_usb_cable_connect(void) +{ + samsung_cable_check_status(1); +} + +void exynos_usb_cable_disconnect(void) +{ + samsung_cable_check_status(0); +} +#endif + +static int is_host_detect(struct exynos_usb_switch *usb_switch) +{ + if (!usb_switch->gpio_host_detect) + return 0; + return !gpio_get_value(usb_switch->gpio_host_detect); +} + +static int is_device_detect(struct exynos_usb_switch *usb_switch) +{ + if (!usb_switch->gpio_device_detect) + return 0; + return gpio_get_value(usb_switch->gpio_device_detect); +} + +static int is_drd_host_detect(struct exynos_usb_switch *usb_switch) +{ + if (!usb_switch->gpio_drd_host_detect) + return 0; + return !gpio_get_value(usb_switch->gpio_drd_host_detect); +} + +static int is_drd_device_detect(struct exynos_usb_switch *usb_switch) +{ + if (!usb_switch->gpio_drd_device_detect) + return 0; + return gpio_get_value(usb_switch->gpio_drd_device_detect); +} + +static void set_host_vbus(struct exynos_usb_switch *usb_switch, int value) +{ + gpio_set_value(usb_switch->gpio_host_vbus, value); +} + +static int exynos_change_usb_mode(struct exynos_usb_switch *usb_switch, + enum usb_cable_status mode) +{ + struct s3c_udc *udc; + struct exynos_ss_udc *ss_udc; + unsigned long cur_mode = usb_switch->connect; + int ret = 0; + + if (test_bit(USB_DEVICE_ATTACHED, &cur_mode) || + test_bit(USB_HOST_ATTACHED, &cur_mode)) { + if (mode == USB_DEVICE_ATTACHED || + mode == USB_HOST_ATTACHED) { + printk(KERN_DEBUG "Skip request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + } else if (test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode) || + test_bit(USB_DRD_HOST_ATTACHED, &cur_mode)) { + if (mode == USB_DRD_DEVICE_ATTACHED || + mode == USB_DRD_HOST_ATTACHED) { + printk(KERN_DEBUG "Skip request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + } + + if (!test_bit(USB_DEVICE_ATTACHED, &cur_mode) && + mode == USB_DEVICE_DETACHED) { + printk(KERN_DEBUG "Skip request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } else if (!test_bit(USB_HOST_ATTACHED, &cur_mode) && + mode == USB_HOST_DETACHED) { + printk(KERN_DEBUG "Skip request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } else if (!test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode) && + mode == USB_DRD_DEVICE_DETACHED) { + printk(KERN_DEBUG "Skip request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } else if (!test_bit(USB_DRD_HOST_ATTACHED, &cur_mode) && + mode == USB_DRD_HOST_DETACHED) { + printk(KERN_DEBUG "Skip request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + + switch (mode) { + case USB_DEVICE_DETACHED: + if (test_bit(USB_HOST_ATTACHED, &cur_mode)) { + printk(KERN_ERR "Abnormal request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + udc = dev_get_drvdata(usb_switch->s3c_udc_dev); + if (udc && udc->gadget.ops && udc->gadget.ops->vbus_session) + udc->gadget.ops->vbus_session(&udc->gadget, 0); + clear_bit(USB_DEVICE_ATTACHED, &usb_switch->connect); + break; + case USB_DEVICE_ATTACHED: + udc = dev_get_drvdata(usb_switch->s3c_udc_dev); + if (udc && udc->gadget.ops && udc->gadget.ops->vbus_session) + udc->gadget.ops->vbus_session(&udc->gadget, 1); + set_bit(USB_DEVICE_ATTACHED, &usb_switch->connect); + break; + case USB_HOST_DETACHED: + if (test_bit(USB_DEVICE_ATTACHED, &cur_mode)) { + printk(KERN_ERR "Abnormal request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + if (usb_switch->ohci_dev) + pm_runtime_put(usb_switch->ohci_dev); + if (usb_switch->ehci_dev) + pm_runtime_put(usb_switch->ehci_dev); + if (usb_switch->gpio_host_vbus) + set_host_vbus(usb_switch, 0); + +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) + exynos_usb_cable_disconnect(); +#endif + clear_bit(USB_HOST_ATTACHED, &usb_switch->connect); + break; + case USB_HOST_ATTACHED: +#if defined(CONFIG_BATTERY_SAMSUNG) || defined(CONFIG_BATTERY_SAMSUNG_S2PLUS) + exynos_usb_cable_connect(); +#endif + if (usb_switch->gpio_host_vbus) + set_host_vbus(usb_switch, 1); + + if (usb_switch->ehci_dev) + pm_runtime_get_sync(usb_switch->ehci_dev); + if (usb_switch->ohci_dev) + pm_runtime_get_sync(usb_switch->ohci_dev); + set_bit(USB_HOST_ATTACHED, &usb_switch->connect); + break; + case USB_DRD_DEVICE_DETACHED: + if (test_bit(USB_DRD_HOST_ATTACHED, &cur_mode)) { + printk(KERN_ERR "Abnormal request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + + ss_udc = dev_get_drvdata(usb_switch->exynos_udc_dev); + if (ss_udc && ss_udc->gadget.ops && + ss_udc->gadget.ops->vbus_session) + ss_udc->gadget.ops->vbus_session(&ss_udc->gadget, 0); + clear_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect); + break; + case USB_DRD_DEVICE_ATTACHED: + ss_udc = dev_get_drvdata(usb_switch->exynos_udc_dev); + if (ss_udc && ss_udc->gadget.ops && + ss_udc->gadget.ops->vbus_session) + ss_udc->gadget.ops->vbus_session(&ss_udc->gadget, 1); + set_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect); + break; + case USB_DRD_HOST_DETACHED: + if (test_bit(USB_DRD_DEVICE_ATTACHED, &cur_mode)) { + printk(KERN_ERR "Abnormal request %d, current %lu\n", + mode, cur_mode); + return -EPERM; + } + + if (usb_switch->xhci_dev) + pm_runtime_put_sync(usb_switch->xhci_dev); +#if defined(CONFIG_BATTERY_SAMSUNG) + exynos_usb_cable_disconnect(); +#endif + clear_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect); + break; + case USB_DRD_HOST_ATTACHED: +#if defined(CONFIG_BATTERY_SAMSUNG) + exynos_usb_cable_connect(); +#endif + if (usb_switch->xhci_dev) + pm_runtime_get_sync(usb_switch->xhci_dev); + set_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect); + break; + default: + printk(KERN_ERR "Does not changed\n"); + } + printk(KERN_INFO "usb cable = %d\n", mode); + + return ret; +} + +static void exnos_usb_switch_worker(struct work_struct *work) +{ + struct exynos_usb_switch *usb_switch = + container_of(work, struct exynos_usb_switch, switch_work); + int cnt = 0; + + mutex_lock(&usb_switch->mutex); + /* If already device detached or host_detected, */ + if (!is_device_detect(usb_switch) || is_host_detect(usb_switch)) + goto done; + if (!usb_switch->ehci_dev || !usb_switch->ohci_dev) + goto detect; + + while (!pm_runtime_suspended(usb_switch->ehci_dev) || + !pm_runtime_suspended(usb_switch->ohci_dev)) { + + mutex_unlock(&usb_switch->mutex); + msleep(SWITCH_WAIT_TIME); + mutex_lock(&usb_switch->mutex); + + /* If already device detached or host_detected, */ + if (!is_device_detect(usb_switch) || is_host_detect(usb_switch)) + goto done; + + if (cnt++ > WAIT_TIMES) { + printk(KERN_ERR "%s:device not attached by host\n", + __func__); + goto done; + } + + } + + if (cnt > 1) + printk(KERN_INFO "Device wait host power during %d\n", (cnt-1)); +detect: + /* Check Device, VBUS PIN high active */ + exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED); +done: + mutex_unlock(&usb_switch->mutex); +} + +static void exnos_usb_drd_switch_worker(struct work_struct *work) +{ + struct exynos_usb_switch *usb_switch = + container_of(work, struct exynos_usb_switch, switch_drd_work); + int cnt = 0; + + mutex_lock(&usb_switch->mutex); + /* If already device detached or host_detected, */ + if (!is_drd_device_detect(usb_switch) || is_drd_host_detect(usb_switch)) + goto done; + if (!usb_switch->xhci_dev) + goto detect; + + while (!pm_runtime_suspended(usb_switch->xhci_dev) || + usb_switch->xhci_dev->power.is_suspended) { + mutex_unlock(&usb_switch->mutex); + msleep(SWITCH_WAIT_TIME); + mutex_lock(&usb_switch->mutex); + + /* If already device detached or host_detected, */ + if (!is_drd_device_detect(usb_switch) || + is_drd_host_detect(usb_switch)) + goto done; + + if (cnt++ > WAIT_TIMES) { + printk(KERN_ERR "%s:device not attached by host\n", + __func__); + goto done; + } + + } + + if (cnt > 1) + printk(KERN_INFO "Device wait host power during %d\n", (cnt-1)); + +detect: + /* Check Device, VBUS PIN high active */ + exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_ATTACHED); +done: + mutex_unlock(&usb_switch->mutex); +} + +static irqreturn_t exynos_host_detect_thread(int irq, void *data) +{ + struct exynos_usb_switch *usb_switch = data; + + mutex_lock(&usb_switch->mutex); + + if (is_host_detect(usb_switch)) + exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED); + else + exynos_change_usb_mode(usb_switch, USB_HOST_DETACHED); + + mutex_unlock(&usb_switch->mutex); + + return IRQ_HANDLED; +} + +static irqreturn_t exynos_device_detect_thread(int irq, void *data) +{ + struct exynos_usb_switch *usb_switch = data; + + mutex_lock(&usb_switch->mutex); + + /* Debounce connect delay */ + msleep(20); + + if (is_host_detect(usb_switch)) + printk(KERN_DEBUG "Not expected situation\n"); + else if (is_device_detect(usb_switch)) { + if (usb_switch->gpio_host_vbus) + exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED); + else + queue_work(usb_switch->workqueue, &usb_switch->switch_work); + } else { + /* VBUS PIN low */ + exynos_change_usb_mode(usb_switch, USB_DEVICE_DETACHED); + } + + mutex_unlock(&usb_switch->mutex); + + return IRQ_HANDLED; +} + +static irqreturn_t exynos_drd_host_detect_thread(int irq, void *data) +{ + struct exynos_usb_switch *usb_switch = data; + + mutex_lock(&usb_switch->mutex); + + if (is_drd_host_detect(usb_switch)) + exynos_change_usb_mode(usb_switch, USB_DRD_HOST_ATTACHED); + else + exynos_change_usb_mode(usb_switch, USB_DRD_HOST_DETACHED); + + mutex_unlock(&usb_switch->mutex); + + return IRQ_HANDLED; +} + +static irqreturn_t exynos_drd_device_detect_thread(int irq, void *data) +{ + struct exynos_usb_switch *usb_switch = data; + + mutex_lock(&usb_switch->mutex); + + /* Debounce connect delay */ + msleep(20); + + if (is_drd_host_detect(usb_switch)) + printk(KERN_DEBUG "Not expected situation\n"); + else if (is_drd_device_detect(usb_switch)) + queue_work(usb_switch->workqueue, &usb_switch->switch_drd_work); + else { + /* VBUS PIN low */ + exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_DETACHED); + } + + mutex_unlock(&usb_switch->mutex); + + return IRQ_HANDLED; +} + +static int exynos_usb_status_init(struct exynos_usb_switch *usb_switch) +{ + mutex_lock(&usb_switch->mutex); + + /* 2.0 USB */ + if (!test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect) || + !test_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect)) { + if (is_host_detect(usb_switch)) + exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED); + else if (is_device_detect(usb_switch)) { + if (usb_switch->gpio_host_vbus) + exynos_change_usb_mode(usb_switch, + USB_DEVICE_ATTACHED); + else + queue_work(usb_switch->workqueue, + &usb_switch->switch_work); + } + } + + /* 3.0 USB */ + if (!test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect) || + !test_bit(USB_DRD_HOST_ATTACHED, &usb_switch->connect)) { + if (is_drd_host_detect(usb_switch)) + exynos_change_usb_mode(usb_switch, + USB_DRD_HOST_ATTACHED); + else if (is_drd_device_detect(usb_switch)) + queue_work(usb_switch->workqueue, + &usb_switch->switch_drd_work); + } + + mutex_unlock(&usb_switch->mutex); + + return 0; +} + +#ifdef CONFIG_PM +static int exynos_usbswitch_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev); + + dev_dbg(dev, "%s\n", __func__); + mutex_lock(&usb_switch->mutex); + if (test_bit(USB_DEVICE_ATTACHED, &usb_switch->connect)) + exynos_change_usb_mode(usb_switch, USB_DEVICE_DETACHED); + + if (test_bit(USB_DRD_DEVICE_ATTACHED, &usb_switch->connect)) + exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_DETACHED); + + usb_switch->connect = 0; + mutex_unlock(&usb_switch->mutex); + + return 0; +} + +static int exynos_usbswitch_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev); + + dev_dbg(dev, "%s\n", __func__); + exynos_usb_status_init(usb_switch); + + return 0; +} +#else +#define exynos_usbswitch_suspend NULL +#define exynos_usbswitch_resume NULL +#endif + +static int __devinit exynos_usbswitch_probe(struct platform_device *pdev) +{ + struct s5p_usbswitch_platdata *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct exynos_usb_switch *usb_switch; + int irq; + int ret = 0; + + usb_switch = kzalloc(sizeof(struct exynos_usb_switch), GFP_KERNEL); + if (!usb_switch) { + ret = -ENOMEM; + return ret; + } + + our_switch = usb_switch; + mutex_init(&usb_switch->mutex); + usb_switch->workqueue = create_singlethread_workqueue("usb_switch"); + INIT_WORK(&usb_switch->switch_work, exnos_usb_switch_worker); + INIT_WORK(&usb_switch->switch_drd_work, exnos_usb_drd_switch_worker); + + usb_switch->gpio_host_detect = pdata->gpio_host_detect; + usb_switch->gpio_device_detect = pdata->gpio_device_detect; + usb_switch->gpio_host_vbus = pdata->gpio_host_vbus; + usb_switch->gpio_drd_host_detect = pdata->gpio_drd_host_detect; + usb_switch->gpio_drd_device_detect = pdata->gpio_drd_device_detect; + + usb_switch->ehci_dev = pdata->ehci_dev; + usb_switch->ohci_dev = pdata->ohci_dev; + usb_switch->xhci_dev = pdata->xhci_dev; + usb_switch->s3c_udc_dev = pdata->s3c_udc_dev; + usb_switch->exynos_udc_dev = pdata->exynos_udc_dev; + + /* USB Device detect IRQ */ + irq = platform_get_irq(pdev, 1); + if (irq > 0 && usb_switch->s3c_udc_dev) { + ret = request_threaded_irq(irq, NULL, + exynos_device_detect_thread, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "DEVICE_DETECT", usb_switch); + if (ret) { + dev_err(dev, "Failed to request device irq %d\n", irq); + goto fail; + } + usb_switch->device_detect_irq = irq; + } else if (usb_switch->s3c_udc_dev) + exynos_change_usb_mode(usb_switch, USB_DEVICE_ATTACHED); + else + dev_info(dev, "Disable device detect IRQ\n"); + + /* USB Host detect IRQ */ + irq = platform_get_irq(pdev, 0); + if (irq > 0 && (usb_switch->ehci_dev || usb_switch->ohci_dev)) { + ret = request_threaded_irq(irq, NULL, + exynos_host_detect_thread, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "HOST_DETECT", usb_switch); + if (ret) { + dev_err(dev, "Failed to request host irq %d\n", irq); + goto fail_gpio_device_detect; + } + usb_switch->host_detect_irq = irq; + } else if (usb_switch->ehci_dev || usb_switch->ohci_dev) + exynos_change_usb_mode(usb_switch, USB_HOST_ATTACHED); + else + dev_info(dev, "Disable host detect IRQ\n"); + + + /* USB DRD Device detect IRQ */ + irq = platform_get_irq(pdev, 3); + if (irq > 0 && usb_switch->exynos_udc_dev) { + ret = request_threaded_irq(irq, NULL, + exynos_drd_device_detect_thread, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "DRD_DEVICE_DETECT", usb_switch); + if (ret) { + dev_err(dev, "Failed to request drd device irq %d\n", + irq); + goto fail_gpio_host_detect; + } + usb_switch->device_drd_detect_irq = irq; + } else if (usb_switch->exynos_udc_dev) + exynos_change_usb_mode(usb_switch, USB_DRD_DEVICE_ATTACHED); + else + dev_info(dev, "Disable drd device detect IRQ\n"); + + /* USB DRD Host detect IRQ */ + irq = platform_get_irq(pdev, 2); + if (irq > 0 && usb_switch->xhci_dev) { + ret = request_threaded_irq(irq, NULL, + exynos_drd_host_detect_thread, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "DRD_HOST_DETECT", usb_switch); + if (ret) { + dev_err(dev, "Failed to request drd host irq %d\n", + irq); + goto fail_gpio_drd_device_detect; + } + usb_switch->host_drd_detect_irq = irq; + } else if (usb_switch->xhci_dev) + exynos_change_usb_mode(usb_switch, USB_DRD_HOST_ATTACHED); + else + dev_info(dev, "Disable drd host detect IRQ\n"); + + exynos_usb_status_init(usb_switch); + + platform_set_drvdata(pdev, usb_switch); + + return ret; +fail_gpio_drd_device_detect: + free_irq(usb_switch->device_drd_detect_irq, usb_switch); +fail_gpio_host_detect: + free_irq(usb_switch->host_detect_irq, usb_switch); +fail_gpio_device_detect: + free_irq(usb_switch->device_detect_irq, usb_switch); +fail: + cancel_work_sync(&usb_switch->switch_work); + destroy_workqueue(usb_switch->workqueue); + mutex_destroy(&usb_switch->mutex); + kfree(usb_switch); + return ret; +} + +static int __devexit exynos_usbswitch_remove(struct platform_device *pdev) +{ + struct exynos_usb_switch *usb_switch = platform_get_drvdata(pdev); + + free_irq(usb_switch->host_drd_detect_irq, usb_switch); + free_irq(usb_switch->device_drd_detect_irq, usb_switch); + free_irq(usb_switch->host_detect_irq, usb_switch); + free_irq(usb_switch->device_detect_irq, usb_switch); + platform_set_drvdata(pdev, 0); + + cancel_work_sync(&usb_switch->switch_work); + destroy_workqueue(usb_switch->workqueue); + mutex_destroy(&usb_switch->mutex); + kfree(usb_switch); + + return 0; +} + +static const struct dev_pm_ops exynos_usbswitch_pm_ops = { + .suspend = exynos_usbswitch_suspend, + .resume = exynos_usbswitch_resume, +}; + +static struct platform_driver exynos_usbswitch_driver = { + .probe = exynos_usbswitch_probe, + .remove = __devexit_p(exynos_usbswitch_remove), + .driver = { + .name = "exynos-usb-switch", + .owner = THIS_MODULE, + .pm = &exynos_usbswitch_pm_ops, + }, +}; + +static int __init exynos_usbswitch_init(void) +{ + int ret; + + ret = platform_device_register(&s5p_device_usbswitch); + if (ret < 0) + return ret; + + ret = platform_driver_register(&exynos_usbswitch_driver); + if (!ret) + printk(KERN_INFO "%s: " DRIVER_DESC "\n", switch_name); + + return ret; +} +late_initcall(exynos_usbswitch_init); + +static void __exit exynos_usbswitch_exit(void) +{ + platform_driver_unregister(&exynos_usbswitch_driver); +} +module_exit(exynos_usbswitch_exit); + +MODULE_DESCRIPTION("Exynos USB switch driver"); +MODULE_AUTHOR("<yulgon.kim@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/exynos-usb-switch.h b/drivers/usb/misc/exynos-usb-switch.h new file mode 100644 index 0000000..3749987 --- /dev/null +++ b/drivers/usb/misc/exynos-usb-switch.h @@ -0,0 +1,63 @@ +/* + * exynos-usb-switch.h - USB switch driver for Exynos + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * Yulgon Kim <yulgon.kim@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ +#ifndef __EXYNOS_USB_SWITCH +#define __EXYNOS_USB_SWITCH + +#define SWITCH_WAIT_TIME 500 +#define WAIT_TIMES 10 + +enum usb_cable_status { + USB_DEVICE_ATTACHED, + USB_HOST_ATTACHED, + USB_DRD_DEVICE_ATTACHED, + USB_DRD_HOST_ATTACHED, + USB_DEVICE_DETACHED, + USB_HOST_DETACHED, + USB_DRD_DEVICE_DETACHED, + USB_DRD_HOST_DETACHED, +}; + +struct exynos_usb_switch { + unsigned long connect; + + unsigned int host_detect_irq; + unsigned int device_detect_irq; + unsigned int host_drd_detect_irq; + unsigned int device_drd_detect_irq; + unsigned int gpio_host_detect; + unsigned int gpio_device_detect; + unsigned int gpio_host_vbus; + unsigned int gpio_drd_host_detect; + unsigned int gpio_drd_device_detect; + + struct device *ehci_dev; + struct device *ohci_dev; + struct device *xhci_dev; + + struct device *s3c_udc_dev; + struct device *exynos_udc_dev; + + struct workqueue_struct *workqueue; + struct work_struct switch_work; + struct work_struct switch_drd_work; + struct mutex mutex; + atomic_t usb_status; + int (*get_usb_mode)(void); + int (*change_usb_mode)(int mode); +}; + +extern int s5p_ehci_port_power_off(struct platform_device *pdev); +extern int s5p_ohci_port_power_off(struct platform_device *pdev); + +extern int s5p_ehci_port_power_on(struct platform_device *pdev); +extern int s5p_ohci_port_power_on(struct platform_device *pdev); + +#endif diff --git a/drivers/usb/misc/mdm_ctrl_bridge.c b/drivers/usb/misc/mdm_ctrl_bridge.c new file mode 100644 index 0000000..4755790 --- /dev/null +++ b/drivers/usb/misc/mdm_ctrl_bridge.c @@ -0,0 +1,768 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. 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. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/ratelimit.h> +#include <linux/usb/ch9.h> +#include <linux/usb/cdc.h> +#include <linux/termios.h> +#include <asm/unaligned.h> +#include <mach/usb_bridge.h> + +static const char const *ctrl_bridge_names[] = { + "dun_ctrl_hsic0", + "rmnet_ctrl_hsic0" +}; + +/* polling interval for Interrupt ep */ +#define HS_INTERVAL 7 +#define FS_LS_INTERVAL 3 + +#define ACM_CTRL_DTR (1 << 0) +#define DEFAULT_READ_URB_LENGTH 4096 + +#define SUSPENDED BIT(0) + +struct ctrl_bridge { + struct usb_device *udev; + struct usb_interface *intf; + + unsigned int int_pipe; + struct urb *inturb; + void *intbuf; + + struct urb *readurb; + void *readbuf; + + struct usb_anchor tx_submitted; + struct usb_anchor tx_deferred; + struct usb_ctrlrequest *in_ctlreq; + + struct bridge *brdg; + struct platform_device *pdev; + + unsigned long flags; + + /* input control lines (DSR, CTS, CD, RI) */ + unsigned int cbits_tohost; + + /* output control lines (DTR, RTS) */ + unsigned int cbits_tomdm; + + /* counters */ + unsigned int snd_encap_cmd; + unsigned int get_encap_res; + unsigned int resp_avail; + unsigned int set_ctrl_line_sts; + unsigned int notify_ser_state; +}; + +static struct ctrl_bridge *__dev[MAX_BRIDGE_DEVICES]; + +/* counter used for indexing ctrl bridge devices */ +static int ch_id; + +unsigned int ctrl_bridge_get_cbits_tohost(unsigned int id) +{ + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + return dev->cbits_tohost; +} +EXPORT_SYMBOL(ctrl_bridge_get_cbits_tohost); + +int ctrl_bridge_set_cbits(unsigned int id, unsigned int cbits) +{ + struct ctrl_bridge *dev; + struct bridge *brdg; + int retval; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + pr_debug("%s: dev[id] =%u cbits : %u\n", __func__, id, cbits); + + brdg = dev->brdg; + if (!brdg) + return -ENODEV; + + dev->cbits_tomdm = cbits; + + retval = ctrl_bridge_write(id, NULL, 0); + + /* if DTR is high, update latest modem info to host */ + if (brdg && (cbits & ACM_CTRL_DTR) && brdg->ops.send_cbits) + brdg->ops.send_cbits(brdg->ctx, dev->cbits_tohost); + + return retval; +} +EXPORT_SYMBOL(ctrl_bridge_set_cbits); + +static void resp_avail_cb(struct urb *urb) +{ + struct ctrl_bridge *dev = urb->context; + struct usb_device *udev; + int status = 0; + int resubmit_urb = 1; + struct bridge *brdg = dev->brdg; + + udev = interface_to_usbdev(dev->intf); + switch (urb->status) { + case 0: + /*success*/ + dev->get_encap_res++; + if (brdg && brdg->ops.send_pkt) + brdg->ops.send_pkt(brdg->ctx, urb->transfer_buffer, + urb->actual_length); + break; + + /*do not resubmit*/ + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + /* unplug */ + case -EPROTO: + /*babble error*/ + resubmit_urb = 0; + /*resubmit*/ + case -EOVERFLOW: + default: + dev_dbg(&udev->dev, "%s: non zero urb status = %d\n", + __func__, urb->status); + } + + if (resubmit_urb) { + /*re- submit int urb to check response available*/ + usb_anchor_urb(dev->inturb, &dev->tx_submitted); + status = usb_submit_urb(dev->inturb, GFP_ATOMIC); + if (status) { + dev_err(&udev->dev, + "%s: Error re-submitting Int URB %d\n", + __func__, status); + usb_unanchor_urb(dev->inturb); + } + } +} + +static void notification_available_cb(struct urb *urb) +{ + int status; + struct usb_cdc_notification *ctrl; + struct usb_device *udev; + struct ctrl_bridge *dev = urb->context; + struct bridge *brdg = dev->brdg; + unsigned int ctrl_bits; + unsigned char *data; + + udev = interface_to_usbdev(dev->intf); + + switch (urb->status) { + case 0: + /*success*/ + break; + case -ESHUTDOWN: + case -ENOENT: + case -ECONNRESET: + case -EPROTO: + /* unplug */ + return; + case -EPIPE: + dev_err(&udev->dev, "%s: stall on int endpoint\n", __func__); + /* TBD : halt to be cleared in work */ + case -EOVERFLOW: + default: + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + goto resubmit_int_urb; + } + + ctrl = (struct usb_cdc_notification *)urb->transfer_buffer; + data = (unsigned char *)(ctrl + 1); + + switch (ctrl->bNotificationType) { + case USB_CDC_NOTIFY_RESPONSE_AVAILABLE: + dev->resp_avail++; + usb_fill_control_urb(dev->readurb, udev, + usb_rcvctrlpipe(udev, 0), + (unsigned char *)dev->in_ctlreq, + dev->readbuf, + DEFAULT_READ_URB_LENGTH, + resp_avail_cb, dev); + + usb_anchor_urb(dev->readurb, &dev->tx_submitted); + status = usb_submit_urb(dev->readurb, GFP_ATOMIC); + if (status) { + dev_err(&udev->dev, + "%s: Error submitting Read URB %d\n", + __func__, status); + usb_unanchor_urb(dev->readurb); + goto resubmit_int_urb; + } + return; + case USB_CDC_NOTIFY_NETWORK_CONNECTION: + dev_dbg(&udev->dev, "%s network\n", ctrl->wValue ? + "connected to" : "disconnected from"); + break; + case USB_CDC_NOTIFY_SERIAL_STATE: + dev->notify_ser_state++; + ctrl_bits = get_unaligned_le16(data); + dev_dbg(&udev->dev, "serial state: %d\n", ctrl_bits); + dev->cbits_tohost = ctrl_bits; + if (brdg && brdg->ops.send_cbits) + brdg->ops.send_cbits(brdg->ctx, ctrl_bits); + break; + default: + dev_err(&udev->dev, "%s: unknown notification %d received:" + "index %d len %d data0 %d data1 %d", + __func__, ctrl->bNotificationType, ctrl->wIndex, + ctrl->wLength, data[0], data[1]); + } + +resubmit_int_urb: + usb_anchor_urb(urb, &dev->tx_submitted); + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + dev_err(&udev->dev, "%s: Error re-submitting Int URB %d\n", + __func__, status); + usb_unanchor_urb(urb); + } +} + +int ctrl_bridge_start_read(struct ctrl_bridge *dev) +{ + int retval = 0; + + if (!dev->inturb) { + dev_err(&dev->udev->dev, "%s: inturb is NULL\n", __func__); + return -ENODEV; + } + + if (!dev->inturb->anchor) { + usb_anchor_urb(dev->inturb, &dev->tx_submitted); + retval = usb_submit_urb(dev->inturb, GFP_KERNEL); + if (retval < 0) { + dev_err(&dev->udev->dev, + "%s error submitting int urb %d\n", + __func__, retval); + usb_unanchor_urb(dev->inturb); + } + } + + return retval; +} + +int ctrl_bridge_open(struct bridge *brdg) +{ + struct ctrl_bridge *dev; + int ret; + + if (!brdg) { + err("bridge is null\n"); + return -EINVAL; + } + + if (brdg->ch_id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[brdg->ch_id]; + if (!dev) { + err("dev is null\n"); + return -ENODEV; + } + + dev->brdg = brdg; + dev->snd_encap_cmd = 0; + dev->get_encap_res = 0; + dev->resp_avail = 0; + dev->set_ctrl_line_sts = 0; + dev->notify_ser_state = 0; + + ret = usb_autopm_get_interface(dev->intf); + if (ret < 0) { + dev_err(&dev->udev->dev, "%s autopm_get fail: %d\n", + __func__, ret); + return ret; + } + + ret = ctrl_bridge_start_read(dev); + usb_autopm_put_interface(dev->intf); + return ret; +} +EXPORT_SYMBOL(ctrl_bridge_open); + +void ctrl_bridge_close(unsigned int id) +{ + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return; + + dev = __dev[id]; + if (!dev || !dev->brdg) + return; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + ctrl_bridge_set_cbits(dev->brdg->ch_id, 0); + usb_unlink_anchored_urbs(&dev->tx_submitted); + + dev->brdg = NULL; +} +EXPORT_SYMBOL(ctrl_bridge_close); + +static void ctrl_write_callback(struct urb *urb) +{ + struct ctrl_bridge *dev = urb->context; + + if (urb->status) { + pr_debug("Write status/size %d/%d\n", + urb->status, urb->actual_length); + } + + kfree(urb->transfer_buffer); + kfree(urb->setup_packet); + usb_free_urb(urb); + usb_autopm_put_interface_async(dev->intf); +} + +int ctrl_bridge_write(unsigned int id, char *data, size_t size) +{ + int result; + struct urb *writeurb; + struct usb_ctrlrequest *out_ctlreq; + struct usb_device *udev; + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) { + result = -EINVAL; + goto free_data; + } + + dev = __dev[id]; + + if (!dev) { + result = -ENODEV; + goto free_data; + } + + udev = interface_to_usbdev(dev->intf); + + dev_dbg(&udev->dev, "%s:[id]:%u: write (%d bytes)\n", + __func__, id, size); + + writeurb = usb_alloc_urb(0, GFP_ATOMIC); + if (!writeurb) { + dev_err(&udev->dev, "%s: error allocating read urb\n", + __func__); + result = -ENOMEM; + goto free_data; + } + + out_ctlreq = kmalloc(sizeof(*out_ctlreq), GFP_ATOMIC); + if (!out_ctlreq) { + dev_err(&udev->dev, + "%s: error allocating setup packet buffer\n", + __func__); + result = -ENOMEM; + goto free_urb; + } + + /* CDC Send Encapsulated Request packet */ + out_ctlreq->bRequestType = (USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE); + if (!data && !size) { + out_ctlreq->bRequest = USB_CDC_REQ_SET_CONTROL_LINE_STATE; + out_ctlreq->wValue = dev->cbits_tomdm; + dev->set_ctrl_line_sts++; + } else { + out_ctlreq->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; + out_ctlreq->wValue = 0; + dev->snd_encap_cmd++; + } + out_ctlreq->wIndex = + dev->intf->cur_altsetting->desc.bInterfaceNumber; + out_ctlreq->wLength = cpu_to_le16(size); + + usb_fill_control_urb(writeurb, udev, + usb_sndctrlpipe(udev, 0), + (unsigned char *)out_ctlreq, + (void *)data, size, + ctrl_write_callback, dev); + + result = usb_autopm_get_interface_async(dev->intf); + if (result < 0) { + dev_err(&udev->dev, "%s: unable to resume interface: %d\n", + __func__, result); + + /* + * Revisit: if (result == -EPERM) + * bridge_suspend(dev->intf, PMSG_SUSPEND); + */ + + goto free_ctrlreq; + } + + if (test_bit(SUSPENDED, &dev->flags)) { + usb_anchor_urb(writeurb, &dev->tx_deferred); + goto deferred; + } + + usb_anchor_urb(writeurb, &dev->tx_submitted); + result = usb_submit_urb(writeurb, GFP_ATOMIC); + if (result < 0) { + dev_err(&udev->dev, "%s: submit URB error %d\n", + __func__, result); + usb_autopm_put_interface_async(dev->intf); + goto unanchor_urb; + } +deferred: + return size; + +unanchor_urb: + usb_unanchor_urb(writeurb); +free_ctrlreq: + kfree(out_ctlreq); +free_urb: + usb_free_urb(writeurb); +free_data: + kfree(data); + + return result; +} +EXPORT_SYMBOL(ctrl_bridge_write); + +int ctrl_bridge_suspend(unsigned int id) +{ + struct ctrl_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + set_bit(SUSPENDED, &dev->flags); + usb_kill_anchored_urbs(&dev->tx_submitted); + + return 0; +} + +int ctrl_bridge_resume(unsigned int id) +{ + struct ctrl_bridge *dev; + struct urb *urb; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev) + return -ENODEV; + + if (!test_and_clear_bit(SUSPENDED, &dev->flags)) + return 0; + + /* submit pending write requests */ + while ((urb = usb_get_from_anchor(&dev->tx_deferred))) { + int ret; + usb_anchor_urb(urb, &dev->tx_submitted); + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + usb_unanchor_urb(urb); + kfree(urb->setup_packet); + kfree(urb->transfer_buffer); + usb_free_urb(urb); + usb_autopm_put_interface_async(dev->intf); + } + } + + /* if the bridge is open, resume reading */ + if (dev->brdg) + return ctrl_bridge_start_read(dev); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 +static ssize_t ctrl_bridge_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct ctrl_bridge *dev; + char *buf; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName#%s dev %p\n" + "snd encap cmd cnt: %u\n" + "get encap res cnt: %u\n" + "res available cnt: %u\n" + "set ctrlline sts cnt: %u\n" + "notify ser state cnt: %u\n" + "cbits_tomdm: %d\n" + "cbits_tohost: %d\n" + "suspended: %d\n", + dev->pdev->name, dev, + dev->snd_encap_cmd, + dev->get_encap_res, + dev->resp_avail, + dev->set_ctrl_line_sts, + dev->notify_ser_state, + dev->cbits_tomdm, + dev->cbits_tohost, + test_bit(SUSPENDED, &dev->flags)); + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t ctrl_bridge_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct ctrl_bridge *dev; + int i; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + dev->snd_encap_cmd = 0; + dev->get_encap_res = 0; + dev->resp_avail = 0; + dev->set_ctrl_line_sts = 0; + dev->notify_ser_state = 0; + } + return count; +} + +const struct file_operations ctrl_stats_ops = { + .read = ctrl_bridge_read_stats, + .write = ctrl_bridge_reset_stats, +}; + +struct dentry *ctrl_dent; +struct dentry *ctrl_dfile; +static void ctrl_bridge_debugfs_init(void) +{ + ctrl_dent = debugfs_create_dir("ctrl_hsic_bridge", 0); + if (IS_ERR(ctrl_dent)) + return; + + ctrl_dfile = + debugfs_create_file("status", 0644, ctrl_dent, 0, + &ctrl_stats_ops); + if (!ctrl_dfile || IS_ERR(ctrl_dfile)) + debugfs_remove(ctrl_dent); +} + +static void ctrl_bridge_debugfs_exit(void) +{ + debugfs_remove(ctrl_dfile); + debugfs_remove(ctrl_dent); +} + +#else +static void ctrl_bridge_debugfs_init(void) { } +static void ctrl_bridge_debugfs_exit(void) { } +#endif + +int +ctrl_bridge_probe(struct usb_interface *ifc, struct usb_host_endpoint *int_in, + int id) +{ + struct ctrl_bridge *dev; + struct usb_device *udev; + struct usb_endpoint_descriptor *ep; + u16 wMaxPacketSize; + int retval = 0; + int interval; + + udev = interface_to_usbdev(ifc); + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + dev_err(&udev->dev, "%s: unable to allocate dev\n", + __func__); + return -ENOMEM; + } + dev->pdev = platform_device_alloc(ctrl_bridge_names[id], id); + if (!dev->pdev) { + dev_err(&dev->udev->dev, + "%s: unable to allocate platform device\n", __func__); + retval = -ENOMEM; + goto nomem; + } + + dev->udev = udev; + dev->int_pipe = usb_rcvintpipe(udev, + int_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + dev->intf = ifc; + + init_usb_anchor(&dev->tx_submitted); + init_usb_anchor(&dev->tx_deferred); + + /*use max pkt size from ep desc*/ + ep = &dev->intf->cur_altsetting->endpoint[0].desc; + + dev->inturb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->inturb) { + dev_err(&udev->dev, "%s: error allocating int urb\n", __func__); + retval = -ENOMEM; + goto pdev_del; + } + + wMaxPacketSize = le16_to_cpu(ep->wMaxPacketSize); + + dev->intbuf = kmalloc(wMaxPacketSize, GFP_KERNEL); + if (!dev->intbuf) { + dev_err(&udev->dev, "%s: error allocating int buffer\n", + __func__); + retval = -ENOMEM; + goto free_inturb; + } + + interval = + (udev->speed == USB_SPEED_HIGH) ? HS_INTERVAL : FS_LS_INTERVAL; + + usb_fill_int_urb(dev->inturb, udev, dev->int_pipe, + dev->intbuf, wMaxPacketSize, + notification_available_cb, dev, interval); + + dev->readurb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->readurb) { + dev_err(&udev->dev, "%s: error allocating read urb\n", + __func__); + retval = -ENOMEM; + goto free_intbuf; + } + + dev->readbuf = kmalloc(DEFAULT_READ_URB_LENGTH, GFP_KERNEL); + if (!dev->readbuf) { + dev_err(&udev->dev, "%s: error allocating read buffer\n", + __func__); + retval = -ENOMEM; + goto free_rurb; + } + + dev->in_ctlreq = kmalloc(sizeof(*dev->in_ctlreq), GFP_KERNEL); + if (!dev->in_ctlreq) { + dev_err(&udev->dev, + "%s:error allocating setup packet buffer\n", + __func__); + retval = -ENOMEM; + goto free_rbuf; + } + + dev->in_ctlreq->bRequestType = + (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE); + dev->in_ctlreq->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + dev->in_ctlreq->wValue = 0; + dev->in_ctlreq->wIndex = + dev->intf->cur_altsetting->desc.bInterfaceNumber; + dev->in_ctlreq->wLength = cpu_to_le16(DEFAULT_READ_URB_LENGTH); + + __dev[id] = dev; + + platform_device_add(dev->pdev); + + ch_id++; + + return retval; + +free_rbuf: + kfree(dev->readbuf); +free_rurb: + usb_free_urb(dev->readurb); +free_intbuf: + kfree(dev->intbuf); +free_inturb: + usb_free_urb(dev->inturb); +pdev_del: + platform_device_del(dev->pdev); +nomem: + kfree(dev); + + return retval; +} + +void ctrl_bridge_disconnect(unsigned int id) +{ + struct ctrl_bridge *dev = __dev[id]; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + platform_device_del(dev->pdev); + + kfree(dev->in_ctlreq); + kfree(dev->readbuf); + kfree(dev->intbuf); + + usb_free_urb(dev->readurb); + usb_free_urb(dev->inturb); + + __dev[id] = NULL; + ch_id--; + + kfree(dev); +} + +static int __init ctrl_bridge_init(void) +{ + ctrl_bridge_debugfs_init(); + + return 0; +} +module_init(ctrl_bridge_init); + +static void __exit ctrl_bridge_exit(void) +{ + ctrl_bridge_debugfs_exit(); +} +module_exit(ctrl_bridge_exit); + +MODULE_DESCRIPTION("Qualcomm modem control bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/mdm_data_bridge.c b/drivers/usb/misc/mdm_data_bridge.c new file mode 100644 index 0000000..6af9664 --- /dev/null +++ b/drivers/usb/misc/mdm_data_bridge.c @@ -0,0 +1,1079 @@ +/* Copyright (c) 2011-2012, Code Aurora Forum. 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. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/uaccess.h> +#include <linux/ratelimit.h> +#include <mach/usb_bridge.h> + +#define MAX_RX_URBS 50 +#define RMNET_RX_BUFSIZE 2048 + +#define STOP_SUBMIT_URB_LIMIT 500 +#define FLOW_CTRL_EN_THRESHOLD 500 +#define FLOW_CTRL_DISABLE 300 +#define FLOW_CTRL_SUPPORT 1 + +static const char const *data_bridge_names[] = { + "dun_data_hsic0", + "rmnet_data_hsic0" +}; + +static struct workqueue_struct *bridge_wq; + +static unsigned int fctrl_support = FLOW_CTRL_SUPPORT; +module_param(fctrl_support, uint, S_IRUGO | S_IWUSR); + +static unsigned int fctrl_en_thld = FLOW_CTRL_EN_THRESHOLD; +module_param(fctrl_en_thld, uint, S_IRUGO | S_IWUSR); + +static unsigned int fctrl_dis_thld = FLOW_CTRL_DISABLE; +module_param(fctrl_dis_thld, uint, S_IRUGO | S_IWUSR); + +unsigned int max_rx_urbs = MAX_RX_URBS; +module_param(max_rx_urbs, uint, S_IRUGO | S_IWUSR); + +unsigned int stop_submit_urb_limit = STOP_SUBMIT_URB_LIMIT; +module_param(stop_submit_urb_limit, uint, S_IRUGO | S_IWUSR); + +static unsigned tx_urb_mult = 20; +module_param(tx_urb_mult, uint, S_IRUGO|S_IWUSR); + +#define TX_HALT BIT(0) +#define RX_HALT BIT(1) +#define SUSPENDED BIT(2) + +struct data_bridge { + struct usb_interface *intf; + struct usb_device *udev; + int id; + + unsigned int bulk_in; + unsigned int bulk_out; + int err; + + /* keep track of in-flight URBs */ + struct usb_anchor tx_active; + struct usb_anchor rx_active; + + /* keep track of outgoing URBs during suspend */ + struct usb_anchor delayed; + + struct list_head rx_idle; + struct sk_buff_head rx_done; + + struct workqueue_struct *wq; + struct work_struct process_rx_w; + + struct bridge *brdg; + + /* work queue function for handling halt conditions */ + struct work_struct kevent; + + unsigned long flags; + + struct platform_device *pdev; + + /* counters */ + atomic_t pending_txurbs; + unsigned int txurb_drp_cnt; + unsigned long to_host; + unsigned long to_modem; + unsigned int tx_throttled_cnt; + unsigned int tx_unthrottled_cnt; + unsigned int rx_throttled_cnt; + unsigned int rx_unthrottled_cnt; +}; + +static struct data_bridge *__dev[MAX_BRIDGE_DEVICES]; + +/* counter used for indexing data bridge devices */ +static int ch_id; + +static unsigned int get_timestamp(void); +static void dbg_timestamp(char *, struct sk_buff *); +static int submit_rx_urb(struct data_bridge *dev, struct urb *urb, + gfp_t flags); + +static inline bool rx_halted(struct data_bridge *dev) +{ + return test_bit(RX_HALT, &dev->flags); +} + +static inline bool rx_throttled(struct bridge *brdg) +{ + return test_bit(RX_THROTTLED, &brdg->flags); +} + +int data_bridge_unthrottle_rx(unsigned int id) +{ + struct data_bridge *dev; + + if (id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[id]; + if (!dev || !dev->brdg) + return -ENODEV; + + dev->rx_unthrottled_cnt++; + queue_work(dev->wq, &dev->process_rx_w); + + return 0; +} +EXPORT_SYMBOL(data_bridge_unthrottle_rx); + +static void data_bridge_process_rx(struct work_struct *work) +{ + int retval; + unsigned long flags; + struct urb *rx_idle; + struct sk_buff *skb; + struct timestamp_info *info; + struct data_bridge *dev = + container_of(work, struct data_bridge, process_rx_w); + + struct bridge *brdg = dev->brdg; + + if (!brdg || !brdg->ops.send_pkt || rx_halted(dev)) + return; + + while (!rx_throttled(brdg) && (skb = skb_dequeue(&dev->rx_done))) { + dev->to_host++; + info = (struct timestamp_info *)skb->cb; + info->rx_done_sent = get_timestamp(); + /* hand off sk_buff to client,they'll need to free it */ + retval = brdg->ops.send_pkt(brdg->ctx, skb, skb->len); + if (retval == -ENOTCONN || retval == -EINVAL) { + return; + } else if (retval == -EBUSY) { + dev->rx_throttled_cnt++; + break; + } + } + + spin_lock_irqsave(&dev->rx_done.lock, flags); + while (!list_empty(&dev->rx_idle)) { + if (dev->rx_done.qlen > stop_submit_urb_limit) + break; + + rx_idle = list_first_entry(&dev->rx_idle, struct urb, urb_list); + list_del(&rx_idle->urb_list); + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + retval = submit_rx_urb(dev, rx_idle, GFP_KERNEL); + spin_lock_irqsave(&dev->rx_done.lock, flags); + if (retval) { + list_add_tail(&rx_idle->urb_list, &dev->rx_idle); + break; + } + } + spin_unlock_irqrestore(&dev->rx_done.lock, flags); +} + +static void data_bridge_read_cb(struct urb *urb) +{ + struct bridge *brdg; + struct sk_buff *skb = urb->context; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + struct data_bridge *dev = info->dev; + bool queue = 0; + + brdg = dev->brdg; + skb_put(skb, urb->actual_length); + + switch (urb->status) { + case 0: /* success */ + queue = 1; + info->rx_done = get_timestamp(); + spin_lock(&dev->rx_done.lock); + __skb_queue_tail(&dev->rx_done, skb); + spin_unlock(&dev->rx_done.lock); + break; + + /*do not resubmit*/ + case -EPIPE: + set_bit(RX_HALT, &dev->flags); + dev_err(&dev->udev->dev, "%s: epout halted\n", __func__); + schedule_work(&dev->kevent); + /* FALLTHROUGH */ + case -ESHUTDOWN: + case -ENOENT: /* suspended */ + case -ECONNRESET: /* unplug */ + case -EPROTO: + dev_kfree_skb_any(skb); + break; + + /*resubmit */ + case -EOVERFLOW: /*babble error*/ + default: + queue = 1; + dev_kfree_skb_any(skb); + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + break; + } + + spin_lock(&dev->rx_done.lock); + list_add_tail(&urb->urb_list, &dev->rx_idle); + spin_unlock(&dev->rx_done.lock); + + if (queue) + queue_work(dev->wq, &dev->process_rx_w); +} + +static int submit_rx_urb(struct data_bridge *dev, struct urb *rx_urb, + gfp_t flags) +{ + struct sk_buff *skb; + struct timestamp_info *info; + int retval = -EINVAL; + unsigned int created; + + created = get_timestamp(); + skb = alloc_skb(RMNET_RX_BUFSIZE, flags); + if (!skb) + return -ENOMEM; + + info = (struct timestamp_info *)skb->cb; + info->dev = dev; + info->created = created; + + usb_fill_bulk_urb(rx_urb, dev->udev, dev->bulk_in, + skb->data, RMNET_RX_BUFSIZE, + data_bridge_read_cb, skb); + + if (test_bit(SUSPENDED, &dev->flags)) + goto suspended; + + usb_anchor_urb(rx_urb, &dev->rx_active); + info->rx_queued = get_timestamp(); + retval = usb_submit_urb(rx_urb, flags); + if (retval) + goto fail; + + return 0; +fail: + usb_unanchor_urb(rx_urb); +suspended: + dev_kfree_skb_any(skb); + + return retval; +} + +static int data_bridge_prepare_rx(struct data_bridge *dev) +{ + int i; + struct urb *rx_urb; + + for (i = 0; i < max_rx_urbs; i++) { + rx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!rx_urb) + return -ENOMEM; + + list_add_tail(&rx_urb->urb_list, &dev->rx_idle); + } + return 0; +} + +int data_bridge_open(struct bridge *brdg) +{ + struct data_bridge *dev; + + if (!brdg) { + err("bridge is null\n"); + return -EINVAL; + } + + if (brdg->ch_id >= MAX_BRIDGE_DEVICES) + return -EINVAL; + + dev = __dev[brdg->ch_id]; + if (!dev) { + err("dev is null\n"); + return -ENODEV; + } + + dev_dbg(&dev->udev->dev, "%s: dev:%p\n", __func__, dev); + + dev->brdg = brdg; + dev->err = 0; + atomic_set(&dev->pending_txurbs, 0); + dev->to_host = 0; + dev->to_modem = 0; + dev->txurb_drp_cnt = 0; + dev->tx_throttled_cnt = 0; + dev->tx_unthrottled_cnt = 0; + dev->rx_throttled_cnt = 0; + dev->rx_unthrottled_cnt = 0; + + queue_work(dev->wq, &dev->process_rx_w); + + return 0; +} +EXPORT_SYMBOL(data_bridge_open); + +void data_bridge_close(unsigned int id) +{ + struct data_bridge *dev; + struct sk_buff *skb; + unsigned long flags; + + if (id >= MAX_BRIDGE_DEVICES) + return; + + dev = __dev[id]; + if (!dev || !dev->brdg) + return; + + dev_dbg(&dev->udev->dev, "%s:\n", __func__); + + usb_unlink_anchored_urbs(&dev->tx_active); + usb_unlink_anchored_urbs(&dev->rx_active); + usb_unlink_anchored_urbs(&dev->delayed); + + spin_lock_irqsave(&dev->rx_done.lock, flags); + while ((skb = __skb_dequeue(&dev->rx_done))) + dev_kfree_skb_any(skb); + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + + dev->brdg = NULL; +} +EXPORT_SYMBOL(data_bridge_close); + +static void defer_kevent(struct work_struct *work) +{ + int status; + struct data_bridge *dev = + container_of(work, struct data_bridge, kevent); + + if (!dev) + return; + + if (test_bit(TX_HALT, &dev->flags)) { + usb_unlink_anchored_urbs(&dev->tx_active); + + status = usb_autopm_get_interface(dev->intf); + if (status < 0) { + dev_err(&dev->udev->dev, + "can't acquire interface, status %d\n", status); + return; + } + + status = usb_clear_halt(dev->udev, dev->bulk_out); + usb_autopm_put_interface(dev->intf); + if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) + dev_err(&dev->udev->dev, + "can't clear tx halt, status %d\n", status); + else + clear_bit(TX_HALT, &dev->flags); + } + + if (test_bit(RX_HALT, &dev->flags)) { + usb_unlink_anchored_urbs(&dev->rx_active); + + status = usb_autopm_get_interface(dev->intf); + if (status < 0) { + dev_err(&dev->udev->dev, + "can't acquire interface, status %d\n", status); + return; + } + + status = usb_clear_halt(dev->udev, dev->bulk_in); + usb_autopm_put_interface(dev->intf); + if (status < 0 && status != -EPIPE && status != -ESHUTDOWN) + dev_err(&dev->udev->dev, + "can't clear rx halt, status %d\n", status); + else { + clear_bit(RX_HALT, &dev->flags); + if (dev->brdg) + queue_work(dev->wq, &dev->process_rx_w); + } + } +} + +static void data_bridge_write_cb(struct urb *urb) +{ + struct sk_buff *skb = urb->context; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + struct data_bridge *dev = info->dev; + struct bridge *brdg = dev->brdg; + int pending; + + pr_debug("%s: dev:%p\n", __func__, dev); + + switch (urb->status) { + case 0: /*success*/ + dbg_timestamp("UL", skb); + break; + case -EPROTO: + dev->err = -EPROTO; + break; + case -EPIPE: + set_bit(TX_HALT, &dev->flags); + dev_err(&dev->udev->dev, "%s: epout halted\n", __func__); + schedule_work(&dev->kevent); + /* FALLTHROUGH */ + case -ESHUTDOWN: + case -ENOENT: /* suspended */ + case -ECONNRESET: /* unplug */ + case -EOVERFLOW: /*babble error*/ + /* FALLTHROUGH */ + default: + pr_debug_ratelimited("%s: non zero urb status = %d\n", + __func__, urb->status); + } + + usb_free_urb(urb); + dev_kfree_skb_any(skb); + + pending = atomic_dec_return(&dev->pending_txurbs); + + /*flow ctrl*/ + if (brdg && fctrl_support && pending <= fctrl_dis_thld && + test_and_clear_bit(TX_THROTTLED, &brdg->flags)) { + pr_debug_ratelimited("%s: disable flow ctrl: pend urbs:%u\n", + __func__, pending); + dev->tx_unthrottled_cnt++; + if (brdg->ops.unthrottle_tx) + brdg->ops.unthrottle_tx(brdg->ctx); + } + + usb_autopm_put_interface_async(dev->intf); +} + +int data_bridge_write(unsigned int id, struct sk_buff *skb) +{ + int result; + int size = skb->len; + int pending; + struct urb *txurb; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + struct data_bridge *dev = __dev[id]; + struct bridge *brdg; + + if (!dev || !dev->brdg || dev->err || !usb_get_intfdata(dev->intf)) + return -ENODEV; + + brdg = dev->brdg; + if (!brdg) + return -ENODEV; + + dev_dbg(&dev->udev->dev, "%s: write (%d bytes)\n", __func__, skb->len); + + result = usb_autopm_get_interface(dev->intf); + if (result < 0) { + dev_err(&dev->udev->dev, "%s: resume failure\n", __func__); + goto error; + } + + txurb = usb_alloc_urb(0, GFP_KERNEL); + if (!txurb) { + dev_err(&dev->udev->dev, "%s: error allocating read urb\n", + __func__); + result = -ENOMEM; + goto error; + } + + /* store dev pointer in skb */ + info->dev = dev; + info->tx_queued = get_timestamp(); + + usb_fill_bulk_urb(txurb, dev->udev, dev->bulk_out, + skb->data, skb->len, data_bridge_write_cb, skb); + + if (test_bit(SUSPENDED, &dev->flags)) { + usb_anchor_urb(txurb, &dev->delayed); + goto free_urb; + } + + pending = atomic_inc_return(&dev->pending_txurbs); + usb_anchor_urb(txurb, &dev->tx_active); + + if (atomic_read(&dev->pending_txurbs) % tx_urb_mult) + txurb->transfer_flags |= URB_NO_INTERRUPT; + + result = usb_submit_urb(txurb, GFP_KERNEL); + if (result < 0) { + usb_unanchor_urb(txurb); + atomic_dec(&dev->pending_txurbs); + dev_err(&dev->udev->dev, "%s: submit URB error %d\n", + __func__, result); + goto free_urb; + } + + dev->to_modem++; + dev_dbg(&dev->udev->dev, "%s: pending_txurbs: %u\n", __func__, pending); + + /* flow control: last urb submitted but return -EBUSY */ + if (fctrl_support && pending > fctrl_en_thld) { + set_bit(TX_THROTTLED, &brdg->flags); + dev->tx_throttled_cnt++; + pr_debug_ratelimited("%s: enable flow ctrl pend txurbs:%u\n", + __func__, pending); + return -EBUSY; + } + + return size; + +free_urb: + usb_free_urb(txurb); +error: + dev->txurb_drp_cnt++; + usb_autopm_put_interface(dev->intf); + + return result; +} +EXPORT_SYMBOL(data_bridge_write); + +static int data_bridge_resume(struct data_bridge *dev) +{ + struct urb *urb; + int retval; + + if (!test_and_clear_bit(SUSPENDED, &dev->flags)) + return 0; + + while ((urb = usb_get_from_anchor(&dev->delayed))) { + usb_anchor_urb(urb, &dev->tx_active); + atomic_inc(&dev->pending_txurbs); + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval < 0) { + atomic_dec(&dev->pending_txurbs); + usb_unanchor_urb(urb); + + /* TODO: need to free urb data */ + usb_scuttle_anchored_urbs(&dev->delayed); + break; + } + dev->to_modem++; + dev->txurb_drp_cnt--; + } + + if (dev->brdg) + queue_work(dev->wq, &dev->process_rx_w); + + return 0; +} + +static int bridge_resume(struct usb_interface *iface) +{ + int retval = 0; + int oldstate; + struct data_bridge *dev = usb_get_intfdata(iface); + + oldstate = iface->dev.power.power_state.event; + iface->dev.power.power_state.event = PM_EVENT_ON; + + if (oldstate & PM_EVENT_SUSPEND) { + retval = data_bridge_resume(dev); + if (!retval) + retval = ctrl_bridge_resume(dev->id); + } + + return retval; +} + +static int data_bridge_suspend(struct data_bridge *dev, pm_message_t message) +{ + if (atomic_read(&dev->pending_txurbs) && + (message.event & PM_EVENT_AUTO)) + return -EBUSY; + + set_bit(SUSPENDED, &dev->flags); + + usb_kill_anchored_urbs(&dev->tx_active); + usb_kill_anchored_urbs(&dev->rx_active); + + return 0; +} + +static int bridge_suspend(struct usb_interface *intf, pm_message_t message) +{ + int retval; + struct data_bridge *dev = usb_get_intfdata(intf); + + retval = data_bridge_suspend(dev, message); + if (!retval) { + retval = ctrl_bridge_suspend(dev->id); + intf->dev.power.power_state.event = message.event; + } + + return retval; +} + +static int data_bridge_probe(struct usb_interface *iface, + struct usb_host_endpoint *bulk_in, + struct usb_host_endpoint *bulk_out, int id) +{ + struct data_bridge *dev; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) { + err("%s: unable to allocate dev\n", __func__); + return -ENOMEM; + } + + dev->pdev = platform_device_alloc(data_bridge_names[id], id); + if (!dev->pdev) { + err("%s: unable to allocate platform device\n", __func__); + kfree(dev); + return -ENOMEM; + } + + init_usb_anchor(&dev->tx_active); + init_usb_anchor(&dev->rx_active); + init_usb_anchor(&dev->delayed); + + INIT_LIST_HEAD(&dev->rx_idle); + skb_queue_head_init(&dev->rx_done); + + dev->wq = bridge_wq; + dev->id = id; + dev->udev = interface_to_usbdev(iface); + dev->intf = iface; + + dev->bulk_in = usb_rcvbulkpipe(dev->udev, + bulk_in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + dev->bulk_out = usb_sndbulkpipe(dev->udev, + bulk_out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + usb_set_intfdata(iface, dev); + + INIT_WORK(&dev->kevent, defer_kevent); + INIT_WORK(&dev->process_rx_w, data_bridge_process_rx); + + __dev[id] = dev; + + /*allocate list of rx urbs*/ + data_bridge_prepare_rx(dev); + + platform_device_add(dev->pdev); + + return 0; +} + +#if defined(CONFIG_DEBUG_FS) +#define DEBUG_BUF_SIZE 1024 + +static unsigned int record_timestamp; +module_param(record_timestamp, uint, S_IRUGO | S_IWUSR); + +static struct timestamp_buf dbg_data = { + .idx = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/*get_timestamp - returns time of day in us */ +static unsigned int get_timestamp(void) +{ + struct timeval tval; + unsigned int stamp; + + if (!record_timestamp) + return 0; + + do_gettimeofday(&tval); + /* 2^32 = 4294967296. Limit to 4096s. */ + stamp = tval.tv_sec & 0xFFF; + stamp = stamp * 1000000 + tval.tv_usec; + return stamp; +} + +static void dbg_inc(unsigned *idx) +{ + *idx = (*idx + 1) & (DBG_DATA_MAX-1); +} + +/** +* dbg_timestamp - Stores timestamp values of a SKB life cycle +* to debug buffer +* @event: "UL": Uplink Data +* @skb: SKB used to store timestamp values to debug buffer +*/ +static void dbg_timestamp(char *event, struct sk_buff * skb) +{ + unsigned long flags; + struct timestamp_info *info = (struct timestamp_info *)skb->cb; + + if (!record_timestamp) + return; + + write_lock_irqsave(&dbg_data.lck, flags); + + scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, + "%p %u[%s] %u %u %u %u %u %u\n", + skb, skb->len, event, info->created, info->rx_queued, + info->rx_done, info->rx_done_sent, info->tx_queued, + get_timestamp()); + + dbg_inc(&dbg_data.idx); + + write_unlock_irqrestore(&dbg_data.lck, flags); +} + +/* show_timestamp: displays the timestamp buffer */ +static ssize_t show_timestamp(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + unsigned long flags; + unsigned i; + unsigned j = 0; + char *buf; + int ret = 0; + + if (!record_timestamp) + return 0; + + buf = kzalloc(sizeof(char) * 4 * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + read_lock_irqsave(&dbg_data.lck, flags); + + i = dbg_data.idx; + for (dbg_inc(&i); i != dbg_data.idx; dbg_inc(&i)) { + if (!strnlen(dbg_data.buf[i], DBG_DATA_MSG)) + continue; + j += scnprintf(buf + j, (4 * DEBUG_BUF_SIZE) - j, + "%s\n", dbg_data.buf[i]); + } + + read_unlock_irqrestore(&dbg_data.lck, flags); + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, j); + + kfree(buf); + + return ret; +} + +const struct file_operations data_timestamp_ops = { + .read = show_timestamp, +}; + +static ssize_t data_bridge_read_stats(struct file *file, char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct data_bridge *dev; + char *buf; + int ret; + int i; + int temp = 0; + + buf = kzalloc(sizeof(char) * DEBUG_BUF_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + temp += scnprintf(buf + temp, DEBUG_BUF_SIZE - temp, + "\nName#%s dev %p\n" + "pending tx urbs: %u\n" + "tx urb drp cnt: %u\n" + "to host: %lu\n" + "to mdm: %lu\n" + "tx throttled cnt: %u\n" + "tx unthrottled cnt: %u\n" + "rx throttled cnt: %u\n" + "rx unthrottled cnt: %u\n" + "rx done skb qlen: %u\n" + "dev err: %d\n" + "suspended: %d\n" + "TX_HALT: %d\n" + "RX_HALT: %d\n", + dev->pdev->name, dev, + atomic_read(&dev->pending_txurbs), + dev->txurb_drp_cnt, + dev->to_host, + dev->to_modem, + dev->tx_throttled_cnt, + dev->tx_unthrottled_cnt, + dev->rx_throttled_cnt, + dev->rx_unthrottled_cnt, + dev->rx_done.qlen, + dev->err, + test_bit(SUSPENDED, &dev->flags), + test_bit(TX_HALT, &dev->flags), + test_bit(RX_HALT, &dev->flags)); + + } + + ret = simple_read_from_buffer(ubuf, count, ppos, buf, temp); + + kfree(buf); + + return ret; +} + +static ssize_t data_bridge_reset_stats(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct data_bridge *dev; + int i; + + for (i = 0; i < ch_id; i++) { + dev = __dev[i]; + if (!dev) + continue; + + dev->to_host = 0; + dev->to_modem = 0; + dev->txurb_drp_cnt = 0; + dev->tx_throttled_cnt = 0; + dev->tx_unthrottled_cnt = 0; + dev->rx_throttled_cnt = 0; + dev->rx_unthrottled_cnt = 0; + } + return count; +} + +const struct file_operations data_stats_ops = { + .read = data_bridge_read_stats, + .write = data_bridge_reset_stats, +}; + +static struct dentry *data_dent; +static struct dentry *data_dfile_stats; +static struct dentry *data_dfile_tstamp; + +static void data_bridge_debugfs_init(void) +{ + data_dent = debugfs_create_dir("data_hsic_bridge", 0); + if (IS_ERR(data_dent)) + return; + + data_dfile_stats = debugfs_create_file("status", 0644, data_dent, 0, + &data_stats_ops); + if (!data_dfile_stats || IS_ERR(data_dfile_stats)) { + debugfs_remove(data_dent); + return; + } + + data_dfile_tstamp = debugfs_create_file("timestamp", 0644, data_dent, + 0, &data_timestamp_ops); + if (!data_dfile_tstamp || IS_ERR(data_dfile_tstamp)) + debugfs_remove(data_dent); +} + +static void data_bridge_debugfs_exit(void) +{ + debugfs_remove(data_dfile_stats); + debugfs_remove(data_dfile_tstamp); + debugfs_remove(data_dent); +} + +#else +static void data_bridge_debugfs_init(void) { } +static void data_bridge_debugfs_exit(void) { } +static void dbg_timestamp(char *event, struct sk_buff * skb) +{ + return; +} + +static unsigned int get_timestamp(void) +{ + return 0; +} + +#endif + +static int __devinit +bridge_probe(struct usb_interface *iface, const struct usb_device_id *id) +{ + struct usb_host_endpoint *endpoint = NULL; + struct usb_host_endpoint *bulk_in = NULL; + struct usb_host_endpoint *bulk_out = NULL; + struct usb_host_endpoint *int_in = NULL; + struct usb_device *udev; + int i; + int status = 0; + int numends; + unsigned int iface_num; + + iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + + if (iface->num_altsetting != 1) { + err("%s invalid num_altsetting %u\n", + __func__, iface->num_altsetting); + return -EINVAL; + } + + udev = interface_to_usbdev(iface); + usb_get_dev(udev); + + if (!test_bit(iface_num, &id->driver_info)) + return -ENODEV; + + numends = iface->cur_altsetting->desc.bNumEndpoints; + for (i = 0; i < numends; i++) { + endpoint = iface->cur_altsetting->endpoint + i; + if (!endpoint) { + dev_err(&udev->dev, "%s: invalid endpoint %u\n", + __func__, i); + status = -EINVAL; + goto out; + } + + if (usb_endpoint_is_bulk_in(&endpoint->desc)) + bulk_in = endpoint; + else if (usb_endpoint_is_bulk_out(&endpoint->desc)) + bulk_out = endpoint; + else if (usb_endpoint_is_int_in(&endpoint->desc)) + int_in = endpoint; + } + + if (!bulk_in || !bulk_out || !int_in) { + dev_err(&udev->dev, "%s: invalid endpoints\n", __func__); + status = -EINVAL; + goto out; + } + + status = data_bridge_probe(iface, bulk_in, bulk_out, ch_id); + if (status < 0) { + dev_err(&udev->dev, "data_bridge_probe failed %d\n", status); + goto out; + } + + status = ctrl_bridge_probe(iface, int_in, ch_id); + if (status < 0) { + dev_err(&udev->dev, "ctrl_bridge_probe failed %d\n", status); + goto free_data_bridge; + } + + ch_id++; + + return 0; + +free_data_bridge: + platform_device_del(__dev[ch_id]->pdev); + usb_set_intfdata(iface, NULL); + kfree(__dev[ch_id]); + __dev[ch_id] = NULL; +out: + usb_put_dev(udev); + + return status; +} + +static void bridge_disconnect(struct usb_interface *intf) +{ + struct data_bridge *dev = usb_get_intfdata(intf); + struct list_head *head; + struct urb *rx_urb; + unsigned long flags; + + if (!dev) { + err("%s: data device not found\n", __func__); + return; + } + + ch_id--; + ctrl_bridge_disconnect(ch_id); + platform_device_del(dev->pdev); + usb_set_intfdata(intf, NULL); + __dev[ch_id] = NULL; + + cancel_work_sync(&dev->process_rx_w); + cancel_work_sync(&dev->kevent); + + /*free rx urbs*/ + head = &dev->rx_idle; + spin_lock_irqsave(&dev->rx_done.lock, flags); + while (!list_empty(head)) { + rx_urb = list_entry(head->next, struct urb, urb_list); + list_del(&rx_urb->urb_list); + usb_free_urb(rx_urb); + } + spin_unlock_irqrestore(&dev->rx_done.lock, flags); + + usb_put_dev(dev->udev); + kfree(dev); +} + +/*bit position represents interface number*/ +#define PID9001_IFACE_MASK 0xC +#define PID9034_IFACE_MASK 0xC +#define PID9048_IFACE_MASK 0x18 +#define PID904C_IFACE_MASK 0x28 + +static const struct usb_device_id bridge_ids[] = { + { USB_DEVICE(0x5c6, 0x9001), + .driver_info = PID9001_IFACE_MASK, + }, + { USB_DEVICE(0x5c6, 0x9034), + .driver_info = PID9034_IFACE_MASK, + }, + { USB_DEVICE(0x5c6, 0x9048), + .driver_info = PID9048_IFACE_MASK, + }, + { USB_DEVICE(0x5c6, 0x904c), + .driver_info = PID904C_IFACE_MASK, + }, + + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, bridge_ids); + +static struct usb_driver bridge_driver = { + .name = "mdm_bridge", + .probe = bridge_probe, + .disconnect = bridge_disconnect, + .id_table = bridge_ids, + .suspend = bridge_suspend, + .resume = bridge_resume, + .supports_autosuspend = 1, +}; + +static int __init bridge_init(void) +{ + int ret; + + ret = usb_register(&bridge_driver); + if (ret) { + err("%s: unable to register mdm_bridge driver", __func__); + return ret; + } + + bridge_wq = create_singlethread_workqueue("mdm_bridge"); + if (!bridge_wq) { + usb_deregister(&bridge_driver); + pr_err("%s: Unable to create workqueue:bridge\n", __func__); + return -ENOMEM; + } + + data_bridge_debugfs_init(); + + return 0; +} + +static void __exit bridge_exit(void) +{ + data_bridge_debugfs_exit(); + destroy_workqueue(bridge_wq); + usb_deregister(&bridge_driver); +} + +module_init(bridge_init); +module_exit(bridge_exit); + +MODULE_DESCRIPTION("Qualcomm modem data bridge driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/notify/Kconfig b/drivers/usb/notify/Kconfig new file mode 100644 index 0000000..fef8c88 --- /dev/null +++ b/drivers/usb/notify/Kconfig @@ -0,0 +1,11 @@ +# +# USB Host notify configuration +# + +config USB_HOST_NOTIFY + boolean "USB Host notify Driver" + depends on USB + help + Android framework needs uevents for usb host operation. + Host notify Driver serves uevent format + that is used by usb host or otg.
\ No newline at end of file diff --git a/drivers/usb/notify/Makefile b/drivers/usb/notify/Makefile new file mode 100644 index 0000000..5fd4512 --- /dev/null +++ b/drivers/usb/notify/Makefile @@ -0,0 +1,4 @@ + +# host notify driver +obj-y += host_notify_class.o +obj-y += host_notifier.o diff --git a/drivers/usb/notify/host_notifier.c b/drivers/usb/notify/host_notifier.c new file mode 100644 index 0000000..613e84b --- /dev/null +++ b/drivers/usb/notify/host_notifier.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co. Ltd. + * Hyuk Kang <hyuk78.kang@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/module.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/kthread.h> +#include <linux/wakelock.h> +#include <linux/host_notify.h> + +struct host_notifier_info { + struct host_notifier_platform_data *pdata; + struct task_struct *th; + struct wake_lock wlock; + struct delayed_work current_dwork; + wait_queue_head_t delay_wait; + int thread_remove; + int currentlimit_irq; +}; + +static struct host_notifier_info ninfo; + +static int currentlimit_thread(void *data) +{ + struct host_notifier_info *ninfo = data; + struct host_notify_dev *ndev = &ninfo->pdata->ndev; + int gpio = ninfo->pdata->gpio; + int prev = ndev->booster; + int ret = 0; + + pr_info("host_notifier usbhostd: start %d\n", prev); + + while (!kthread_should_stop()) { + wait_event_interruptible_timeout(ninfo->delay_wait, + ninfo->thread_remove, 1 * HZ); + + ret = gpio_get_value(gpio); + if (prev != ret) { + pr_info("host_notifier usbhostd: gpio %d = %s\n", + gpio, ret ? "HIGH" : "LOW"); + ndev->booster = ret ? + NOTIFY_POWER_ON : NOTIFY_POWER_OFF; + prev = ret; + + if (!ret && ndev->mode == NOTIFY_HOST_MODE) { + host_state_notify(ndev, + NOTIFY_HOST_OVERCURRENT); + pr_err("host_notifier usbhostd: overcurrent\n"); + break; + } + } + } + + ninfo->thread_remove = 1; + + pr_info("host_notifier usbhostd: exit %d\n", ret); + return 0; +} + +static int start_usbhostd_thread(void) +{ + if (!ninfo.th) { + pr_info("host_notifier: start usbhostd thread\n"); + + init_waitqueue_head(&ninfo.delay_wait); + ninfo.thread_remove = 0; + ninfo.th = kthread_run(currentlimit_thread, + &ninfo, "usbhostd"); + + if (IS_ERR(ninfo.th)) { + pr_err("host_notifier: Unable to start usbhostd\n"); + ninfo.th = NULL; + ninfo.thread_remove = 1; + return -1; + } + host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_ADD); + wake_lock(&ninfo.wlock); + + } else + pr_info("host_notifier: usbhostd already started!\n"); + + return 0; +} + +static int stop_usbhostd_thread(void) +{ + if (ninfo.th) { + pr_info("host_notifier: stop thread\n"); + + if (!ninfo.thread_remove) + kthread_stop(ninfo.th); + + ninfo.th = NULL; + host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_REMOVE); + wake_unlock(&ninfo.wlock); + } else + pr_info("host_notifier: no thread\n"); + + return 0; +} + +static int start_usbhostd_notify(void) +{ + pr_info("host_notifier: start usbhostd notify\n"); + + host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_ADD); + wake_lock(&ninfo.wlock); + + return 0; +} + +static int stop_usbhostd_notify(void) +{ + pr_info("host_notifier: stop usbhostd notify\n"); + + host_state_notify(&ninfo.pdata->ndev, NOTIFY_HOST_REMOVE); + wake_unlock(&ninfo.wlock); + + return 0; +} + +static void host_notifier_booster(int enable) +{ + pr_info("host_notifier: booster %s\n", enable ? "ON" : "OFF"); + + ninfo.pdata->booster(enable); + + if (ninfo.pdata->thread_enable) { + if (enable) + start_usbhostd_thread(); + else + stop_usbhostd_thread(); + } +} + +static irqreturn_t currentlimit_irq_thread(int irq, void *data) +{ + struct host_notifier_info *hostinfo = data; + struct host_notify_dev *ndev = &hostinfo->pdata->ndev; + int gpio = hostinfo->pdata->gpio; + int prev = ndev->booster; + int ret = 0; + + ret = gpio_get_value(gpio); + pr_info("currentlimit_irq_thread gpio : %d, value : %d\n", gpio, ret); + + if (prev != ret) { + pr_info("host_notifier currentlimit_irq_thread: gpio %d = %s\n", + gpio, ret ? "HIGH" : "LOW"); + ndev->booster = ret ? + NOTIFY_POWER_ON : NOTIFY_POWER_OFF; + prev = ret; + + if (!ret && ndev->mode == NOTIFY_HOST_MODE) { + host_state_notify(ndev, + NOTIFY_HOST_OVERCURRENT); + pr_err("host_notifier currentlimit_irq_thread: overcurrent\n"); + } + } + return IRQ_HANDLED; +} + +static int currentlimit_irq_init(struct host_notifier_info *hostinfo) +{ + int ret = 0; + + hostinfo->currentlimit_irq = gpio_to_irq(hostinfo->pdata->gpio); + + ret = request_threaded_irq(hostinfo->currentlimit_irq, NULL, + currentlimit_irq_thread, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "overcurrent_detect", hostinfo); + if (ret) + pr_info("host_notifier: %s return : %d\n", __func__, ret); + + return ret; +} + +static void currentlimit_irq_work(struct work_struct *work) +{ + int retval; + struct host_notifier_info *hostinfo = container_of(work, + struct host_notifier_info, current_dwork.work); + + retval = currentlimit_irq_init(hostinfo); + + if (retval) + pr_err("host_notifier: %s retval : %d\n", __func__, retval); + return; +} + +static int host_notifier_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (pdev && pdev->dev.platform_data) + ninfo.pdata = pdev->dev.platform_data; + else { + pr_err("host_notifier: platform_data is null.\n"); + return -ENODEV; + } + + dev_info(&pdev->dev, "notifier_prove\n"); + + if (ninfo.pdata->thread_enable) { + ret = gpio_request(ninfo.pdata->gpio, "host_notifier"); + if (ret) { + dev_err(&pdev->dev, "failed to request %d\n", + ninfo.pdata->gpio); + return -EPERM; + } + gpio_direction_input(ninfo.pdata->gpio); + dev_info(&pdev->dev, "gpio = %d\n", ninfo.pdata->gpio); + + ninfo.pdata->ndev.set_booster = host_notifier_booster; + ninfo.pdata->usbhostd_start = start_usbhostd_thread; + ninfo.pdata->usbhostd_stop = stop_usbhostd_thread; + } else if (ninfo.pdata->irq_enable) { + INIT_DELAYED_WORK(&ninfo.current_dwork, currentlimit_irq_work); + schedule_delayed_work(&ninfo.current_dwork, + msecs_to_jiffies(10000)); + ninfo.pdata->ndev.set_booster = host_notifier_booster; + ninfo.pdata->usbhostd_start = start_usbhostd_notify; + ninfo.pdata->usbhostd_stop = stop_usbhostd_notify; + } else { + ninfo.pdata->ndev.set_booster = host_notifier_booster; + ninfo.pdata->usbhostd_start = start_usbhostd_notify; + ninfo.pdata->usbhostd_stop = stop_usbhostd_notify; + } + + ret = host_notify_dev_register(&ninfo.pdata->ndev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to host_notify_dev_register\n"); + return ret; + } + wake_lock_init(&ninfo.wlock, WAKE_LOCK_SUSPEND, "hostd"); + + return 0; +} + +static int host_notifier_remove(struct platform_device *pdev) +{ + /* gpio_free(ninfo.pdata->gpio); */ + host_notify_dev_unregister(&ninfo.pdata->ndev); + wake_lock_destroy(&ninfo.wlock); + return 0; +} + +static struct platform_driver host_notifier_driver = { + .probe = host_notifier_probe, + .remove = host_notifier_remove, + .driver = { + .name = "host_notifier", + .owner = THIS_MODULE, + }, +}; + + +static int __init host_notifier_init(void) +{ + return platform_driver_register(&host_notifier_driver); +} + +static void __init host_notifier_exit(void) +{ + platform_driver_unregister(&host_notifier_driver); +} + +module_init(host_notifier_init); +module_exit(host_notifier_exit); + +MODULE_AUTHOR("Hyuk Kang <hyuk78.kang@samsung.com>"); +MODULE_DESCRIPTION("USB Host notifier"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/notify/host_notify_class.c b/drivers/usb/notify/host_notify_class.c new file mode 100644 index 0000000..9250568 --- /dev/null +++ b/drivers/usb/notify/host_notify_class.c @@ -0,0 +1,262 @@ +/* + * drivers/usb/notify/host_notify_class.c + * + * Copyright (C) 2011 Samsung, Inc. + * Author: Dongrak Shin <dongrak.shin@samsung.com> + * +*/ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/err.h> +#include <linux/host_notify.h> + +struct notify_data { + struct class *host_notify_class; + atomic_t device_count; +}; + +static struct notify_data host_notify; + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *mode; + + switch (ndev->mode) { + case NOTIFY_HOST_MODE: + mode = "HOST"; + break; + case NOTIFY_PERIPHERAL_MODE: + mode = "PERIPHERAL"; + break; + case NOTIFY_TEST_MODE: + mode = "TEST"; + break; + case NOTIFY_NONE_MODE: + default: + mode = "NONE"; + break; + } + + printk(KERN_INFO "host_notify: read mode %s\n", mode); + return sprintf(buf, "%s\n", mode); +} + +static ssize_t mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + + char *mode; + size_t ret = -ENOMEM; + + mode = kzalloc(size + 1, GFP_KERNEL); + if (!mode) + goto error; + + sscanf(buf, "%s", mode); + + if (ndev->set_mode) { + printk(KERN_INFO "host_notify: set mode %s\n", mode); + if (!strcmp(mode, "HOST")) + ndev->set_mode(NOTIFY_SET_ON); + else if (!strcmp(mode, "NONE")) + ndev->set_mode(NOTIFY_SET_OFF); + } + ret = size; + kfree(mode); + error: + return ret; +} + +static ssize_t booster_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *booster; + + switch (ndev->booster) { + case NOTIFY_POWER_ON: + booster = "ON"; + break; + case NOTIFY_POWER_OFF: + default: + booster = "OFF"; + break; + } + + printk(KERN_INFO "host_notify: read booster %s\n", booster); + return sprintf(buf, "%s\n", booster); +} + +static ssize_t booster_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + + char *booster; + size_t ret = -ENOMEM; + + booster = kzalloc(size + 1, GFP_KERNEL); + if (!booster) + goto error; + + sscanf(buf, "%s", booster); + + if (ndev->set_booster) { + printk(KERN_INFO "host_notify: set booster %s\n", booster); + if (!strcmp(booster, "ON")) { + ndev->mode = NOTIFY_TEST_MODE; + ndev->set_booster(NOTIFY_SET_ON); + } else if (!strcmp(booster, "OFF")) { + ndev->mode = NOTIFY_NONE_MODE; + ndev->set_booster(NOTIFY_SET_OFF); + } + } + ret = size; + kfree(booster); + error: + return ret; +} + +static DEVICE_ATTR(mode, S_IRUGO | S_IWUSR | S_IWGRP, mode_show, mode_store); +static DEVICE_ATTR(booster, S_IRUGO | S_IWUSR | S_IWGRP, + booster_show, booster_store); + +static struct attribute *host_notify_attrs[] = { + &dev_attr_mode.attr, + &dev_attr_booster.attr, + NULL, +}; + +static struct attribute_group host_notify_attr_grp = { + .attrs = host_notify_attrs, +}; + +void host_state_notify(struct host_notify_dev *ndev, int state) +{ + printk(KERN_INFO + "host_notify: ndev name=%s: from state=%d -> to state=%d\n", + ndev->name, ndev->state, state); + if (ndev->state != state) { + ndev->state = state; + kobject_uevent(&ndev->dev->kobj, KOBJ_CHANGE); + } +} +EXPORT_SYMBOL_GPL(host_state_notify); + +static int host_notify_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct host_notify_dev *ndev = (struct host_notify_dev *) + dev_get_drvdata(dev); + char *state; + + if (!ndev) { + /* this happens when the device is first created */ + return 0; + } + switch (ndev->state) { + case NOTIFY_HOST_ADD: + state = "ADD"; + break; + case NOTIFY_HOST_REMOVE: + state = "REMOVE"; + break; + case NOTIFY_HOST_OVERCURRENT: + state = "OVERCURRENT"; + break; + case NOTIFY_HOST_LOWBATT: + state = "LOWBATT"; + break; + case NOTIFY_HOST_UNKNOWN: + state = "UNKNOWN"; + break; + case NOTIFY_HOST_NONE: + default: + return 0; + } + if (add_uevent_var(env, "DEVNAME=%s", ndev->dev->kobj.name)) + return -ENOMEM; + if (add_uevent_var(env, "STATE=%s", state)) + return -ENOMEM; + return 0; +} + +static int create_notify_class(void) +{ + if (!host_notify.host_notify_class) { + host_notify.host_notify_class + = class_create(THIS_MODULE, "host_notify"); + if (IS_ERR(host_notify.host_notify_class)) + return PTR_ERR(host_notify.host_notify_class); + atomic_set(&host_notify.device_count, 0); + host_notify.host_notify_class->dev_uevent = host_notify_uevent; + } + + return 0; +} + +int host_notify_dev_register(struct host_notify_dev *ndev) +{ + int ret; + + if (!host_notify.host_notify_class) { + ret = create_notify_class(); + if (ret < 0) + return ret; + } + + ndev->index = atomic_inc_return(&host_notify.device_count); + ndev->dev = device_create(host_notify.host_notify_class, NULL, + MKDEV(0, ndev->index), NULL, ndev->name); + if (IS_ERR(ndev->dev)) + return PTR_ERR(ndev->dev); + + ret = sysfs_create_group(&ndev->dev->kobj, &host_notify_attr_grp); + if (ret < 0) { + device_destroy(host_notify.host_notify_class, + MKDEV(0, ndev->index)); + return ret; + } + + dev_set_drvdata(ndev->dev, ndev); + ndev->state = 0; + return 0; +} +EXPORT_SYMBOL_GPL(host_notify_dev_register); + +void host_notify_dev_unregister(struct host_notify_dev *ndev) +{ + ndev->state = NOTIFY_HOST_NONE; + sysfs_remove_group(&ndev->dev->kobj, &host_notify_attr_grp); + device_destroy(host_notify.host_notify_class, MKDEV(0, ndev->index)); + dev_set_drvdata(ndev->dev, NULL); +} +EXPORT_SYMBOL_GPL(host_notify_dev_unregister); + +static int __init notify_class_init(void) +{ + return create_notify_class(); +} + +static void __exit notify_class_exit(void) +{ + class_destroy(host_notify.host_notify_class); +} + +module_init(notify_class_init); +module_exit(notify_class_exit); + +MODULE_AUTHOR("Dongrak Shin <dongrak.shin@samsung.com>"); +MODULE_DESCRIPTION("Usb host notify driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index c66481a..cd77719 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -12,6 +12,14 @@ config USB_OTG_UTILS Select this to make sure the build includes objects from the OTG infrastructure directory. +config USB_OTG_WAKELOCK + bool "Hold a wakelock when USB connected" + depends on WAKELOCK + select USB_OTG_UTILS + help + Select this to automatically hold a wakelock when USB is + connected, preventing suspend. + if USB || USB_GADGET # diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 566655c..d2c0a7b 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -7,6 +7,8 @@ ccflags-$(CONFIG_USB_GADGET_DEBUG) += -DDEBUG # infrastructure obj-$(CONFIG_USB_OTG_UTILS) += otg.o +obj-$(CONFIG_USB_OTG_WAKELOCK) += otg-wakelock.o +obj-$(CONFIG_USB_OTG_UTILS) += otg_id.o # transceiver drivers obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o diff --git a/drivers/usb/otg/otg-wakelock.c b/drivers/usb/otg/otg-wakelock.c new file mode 100644 index 0000000..2f11472 --- /dev/null +++ b/drivers/usb/otg/otg-wakelock.c @@ -0,0 +1,169 @@ +/* + * otg-wakelock.c + * + * Copyright (C) 2011 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/device.h> +#include <linux/notifier.h> +#include <linux/wakelock.h> +#include <linux/spinlock.h> +#include <linux/usb/otg.h> + +#define TEMPORARY_HOLD_TIME 2000 + +static bool enabled = true; +static struct otg_transceiver *otgwl_xceiv; +static struct notifier_block otgwl_nb; + +/* + * otgwl_spinlock is held while the VBUS lock is grabbed or dropped and the + * held field is updated to match. + */ + +static DEFINE_SPINLOCK(otgwl_spinlock); + +/* + * Only one lock, but since these 3 fields are associated with each other... + */ + +struct otgwl_lock { + char name[40]; + struct wake_lock wakelock; + bool held; +}; + +/* + * VBUS present lock. Also used as a timed lock on charger + * connect/disconnect and USB host disconnect, to allow the system + * to react to the change in power. + */ + +static struct otgwl_lock vbus_lock; + +static void otgwl_hold(struct otgwl_lock *lock) +{ + if (!lock->held) { + wake_lock(&lock->wakelock); + lock->held = true; + } +} + +static void otgwl_temporary_hold(struct otgwl_lock *lock) +{ + wake_lock_timeout(&lock->wakelock, + msecs_to_jiffies(TEMPORARY_HOLD_TIME)); + lock->held = false; +} + +static void otgwl_drop(struct otgwl_lock *lock) +{ + if (lock->held) { + wake_unlock(&lock->wakelock); + lock->held = false; + } +} + +static void otgwl_handle_event(unsigned long event) +{ + unsigned long irqflags; + + spin_lock_irqsave(&otgwl_spinlock, irqflags); + + if (!enabled) { + otgwl_drop(&vbus_lock); + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); + return; + } + + switch (event) { + case USB_EVENT_VBUS: + case USB_EVENT_ENUMERATED: + otgwl_hold(&vbus_lock); + break; + + case USB_EVENT_NONE: + case USB_EVENT_ID: + case USB_EVENT_CHARGER: + otgwl_temporary_hold(&vbus_lock); + break; + + default: + break; + } + + spin_unlock_irqrestore(&otgwl_spinlock, irqflags); +} + +static int otgwl_otg_notifications(struct notifier_block *nb, + unsigned long event, void *unused) +{ + otgwl_handle_event(event); + return NOTIFY_OK; +} + +static int set_enabled(const char *val, const struct kernel_param *kp) +{ + int rv = param_set_bool(val, kp); + + if (rv) + return rv; + + if (otgwl_xceiv) + otgwl_handle_event(otgwl_xceiv->last_event); + + return 0; +} + +static struct kernel_param_ops enabled_param_ops = { + .set = set_enabled, + .get = param_get_bool, +}; + +module_param_cb(enabled, &enabled_param_ops, &enabled, 0644); +MODULE_PARM_DESC(enabled, "enable wakelock when VBUS present"); + +static int __init otg_wakelock_init(void) +{ + int ret; + + otgwl_xceiv = otg_get_transceiver(); + + if (!otgwl_xceiv) { + pr_err("%s: No OTG transceiver found\n", __func__); + return -ENODEV; + } + + snprintf(vbus_lock.name, sizeof(vbus_lock.name), "vbus-%s", + dev_name(otgwl_xceiv->dev)); + wake_lock_init(&vbus_lock.wakelock, WAKE_LOCK_SUSPEND, + vbus_lock.name); + + otgwl_nb.notifier_call = otgwl_otg_notifications; + ret = otg_register_notifier(otgwl_xceiv, &otgwl_nb); + + if (ret) { + pr_err("%s: otg_register_notifier on transceiver %s" + " failed\n", __func__, + dev_name(otgwl_xceiv->dev)); + otgwl_xceiv = NULL; + wake_lock_destroy(&vbus_lock.wakelock); + return ret; + } + + otgwl_handle_event(otgwl_xceiv->last_event); + return ret; +} + +late_initcall(otg_wakelock_init); diff --git a/drivers/usb/otg/otg_id.c b/drivers/usb/otg/otg_id.c new file mode 100644 index 0000000..8037edb --- /dev/null +++ b/drivers/usb/otg/otg_id.c @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2011 Google, Inc. + * + * Author: + * Colin Cross <ccross@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/notifier.h> +#include <linux/usb/otg_id.h> + +static DEFINE_MUTEX(otg_id_lock); +static struct plist_head otg_id_plist = + PLIST_HEAD_INIT(otg_id_plist); +static struct otg_id_notifier_block *otg_id_active; +static bool otg_id_cancelling; +static bool otg_id_inited; +static int otg_id_suspended; +static bool otg_id_pending; + +static void otg_id_cancel(void) +{ + if (otg_id_active) { + otg_id_cancelling = true; + mutex_unlock(&otg_id_lock); + + otg_id_active->cancel(otg_id_active); + + mutex_lock(&otg_id_lock); + otg_id_cancelling = false; + } +} + +static void __otg_id_notify(void) +{ + int ret; + struct otg_id_notifier_block *otg_id_nb; + bool proxy_wait = false; + if (plist_head_empty(&otg_id_plist)) + return; + + plist_for_each_entry(otg_id_nb, &otg_id_plist, p) { + if (proxy_wait) { + if (otg_id_nb->proxy_wait) + ret = otg_id_nb->proxy_wait(otg_id_nb); + } else { + ret = otg_id_nb->detect(otg_id_nb); + } + if (ret == OTG_ID_HANDLED) { + otg_id_active = otg_id_nb; + return; + } + if (ret == OTG_ID_PROXY_WAIT) + proxy_wait = true; + + } + + WARN(1, "otg id event not handled"); + otg_id_active = NULL; +} + +int otg_id_init(void) +{ + mutex_lock(&otg_id_lock); + + otg_id_inited = true; + __otg_id_notify(); + + mutex_unlock(&otg_id_lock); + return 0; +} +late_initcall(otg_id_init); + +/** + * otg_id_register_notifier + * @otg_id_nb: notifier block containing priority and callback function + * + * Register a notifier that will be called on any USB cable state change. + * The priority determines the order the callback will be called in, a higher + * number will be called first. A callback function needs to determine the + * type of USB cable that is connected. If it can determine the type, it + * should notify the appropriate drivers (for example, call an otg notifier + * with USB_EVENT_VBUS), and return OTG_ID_HANDLED. Once a callback has + * returned OTG_ID_HANDLED, it is responsible for calling otg_id_notify() when + * the detected USB cable is disconnected. + */ +int otg_id_register_notifier(struct otg_id_notifier_block *otg_id_nb) +{ + plist_node_init(&otg_id_nb->p, otg_id_nb->priority); + + mutex_lock(&otg_id_lock); + plist_add(&otg_id_nb->p, &otg_id_plist); + + if (otg_id_inited) { + otg_id_cancel(); + __otg_id_notify(); + } + + mutex_unlock(&otg_id_lock); + + return 0; +} + +void otg_id_unregister_notifier(struct otg_id_notifier_block *otg_id_nb) +{ + mutex_lock(&otg_id_lock); + + plist_del(&otg_id_nb->p, &otg_id_plist); + + if (otg_id_inited && (otg_id_active == otg_id_nb)) { + otg_id_cancel(); + __otg_id_notify(); + } + + mutex_unlock(&otg_id_lock); +} + +/** + * otg_id_notify + * + * Notify listeners on any USB cable state change. + * + * A driver may only call otg_id_notify if it returned OTG_ID_HANDLED the last + * time it's notifier was called, and it's cancel function has not been called. + */ +void otg_id_notify(void) +{ + mutex_lock(&otg_id_lock); + + if (otg_id_cancelling) + goto out; + + if (otg_id_suspended != 0) { + otg_id_pending = true; + goto out; + } + + __otg_id_notify(); +out: + mutex_unlock(&otg_id_lock); +} + +/** + * otg_id_suspend + * + * Mark the otg_id subsystem as going into suspend. From here on out, + * any notifications will be deferred until the last otg_id client resumes. + * If there is a pending notification when calling this function, it will + * return a negative errno and expects that the caller will abort suspend. + * Returs 0 on success. + */ +int otg_id_suspend(void) +{ + int ret = 0; + + mutex_lock(&otg_id_lock); + + /* + * if there's a pending notification, tell the caller to abort suspend + */ + if (otg_id_suspended != 0 && otg_id_pending) { + pr_info("otg_id: pending notification, should abort suspend\n"); + ret = -EBUSY; + goto out; + } + + otg_id_suspended++; +out: + mutex_unlock(&otg_id_lock); + return ret; +} + +/** + * otg_id_resume + * + * Inform the otg_id subsystem that a client is resuming. If this is the + * last client to be resumed and there's a pending notification, + * otg_id_notify() is called. + */ +void otg_id_resume(void) +{ + mutex_lock(&otg_id_lock); + if (WARN(!otg_id_suspended, "unbalanced otg_id_resume\n")) + goto out; + if (--otg_id_suspended == 0) { + if (otg_id_pending) { + pr_info("otg_id: had pending notification\n"); + otg_id_pending = false; + __otg_id_notify(); + } + } +out: + mutex_unlock(&otg_id_lock); +} diff --git a/drivers/usb/serial/qcserial.c b/drivers/usb/serial/qcserial.c index b9bb247..3508152 100644 --- a/drivers/usb/serial/qcserial.c +++ b/drivers/usb/serial/qcserial.c @@ -86,10 +86,14 @@ static const struct usb_device_id id_table[] = { {USB_DEVICE(0x05c6, 0x9204)}, /* Gobi 2000 QDL device */ {USB_DEVICE(0x05c6, 0x9205)}, /* Gobi 2000 Modem device */ {USB_DEVICE(0x1199, 0x9013)}, /* Sierra Wireless Gobi 3000 Modem device (MC8355) */ + {USB_DEVICE(0x05c6, 0x9048)}, /* MDM9x15 device */ + {USB_DEVICE(0x05c6, 0x904C)}, /* MDM9x15 device */ { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, id_table); +#define EFS_SYNC_IFC_NUM 2 + static struct usb_driver qcdriver = { .name = "qcserial", .probe = usb_serial_probe, @@ -201,6 +205,14 @@ static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id) } break; + case 9: + if (ifnum != EFS_SYNC_IFC_NUM) { + kfree(data); + break; + } + + retval = 0; + break; default: dev_err(&serial->dev->dev, "unknown number of interfaces: %d\n", nintf); diff --git a/drivers/usb/serial/usb-wwan.h b/drivers/usb/serial/usb-wwan.h index c47b6ec..de8d490 100644 --- a/drivers/usb/serial/usb-wwan.h +++ b/drivers/usb/serial/usb-wwan.h @@ -31,10 +31,10 @@ extern int usb_wwan_resume(struct usb_serial *serial); /* per port private data */ -#define N_IN_URB 4 -#define N_OUT_URB 4 -#define IN_BUFLEN 4096 -#define OUT_BUFLEN 4096 +#define N_IN_URB 5 +#define N_OUT_URB 5 +#define IN_BUFLEN 65536 +#define OUT_BUFLEN 65536 struct usb_wwan_intf_private { spinlock_t susp_lock; diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index e4fad5e..2a9d21d 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -406,6 +406,11 @@ int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port) portdata = usb_get_serial_port_data(port); intfdata = serial->private; + /* explicitly set the driver mode to raw */ + tty->raw = 1; + tty->real_raw = 1; + + set_bit(TTY_NO_WRITE_SPLIT, &tty->flags); dbg("%s", __func__); /* Start reading from the IN endpoint */ @@ -548,7 +553,7 @@ int usb_wwan_startup(struct usb_serial *serial) init_usb_anchor(&portdata->delayed); for (j = 0; j < N_IN_URB; j++) { - buffer = (u8 *) __get_free_page(GFP_KERNEL); + buffer = kmalloc(IN_BUFLEN, GFP_KERNEL); if (!buffer) goto bail_out_error; portdata->in_buffer[j] = buffer; diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index c325e69..9d6cf49 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -907,6 +907,9 @@ int usb_stor_probe1(struct us_data **pus, /* * Allow 16-byte CDBs and thus > 2TB */ +#ifdef CONFIG_USB_HOST_NOTIFY + host->by_usb = 1; +#endif host->max_cmd_len = 16; host->sg_tablesize = usb_stor_sg_tablesize(intf); *pus = us = host_to_us(host); |