+++ /dev/null
-From 206b4c81b6b31d87c758748cdbc6d25e9c721ea1 Mon Sep 17 00:00:00 2001
-In-Reply-To: <20160106001143.GA1171@kroah.com>
-References: <20160106001143.GA1171@kroah.com>
-From: "Steinar H. Gunderson" <sesse@google.com>
-Date: Thu, 26 Nov 2015 01:19:13 +0100
-Subject: [PATCH v2] Add support for usbfs zerocopy.
-To: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-Cc: linux-usb@vger.kernel.org,linux-kernel@vger.kernel.org,stern@rowland.harvard.edu
-
-Add a new interface for userspace to preallocate memory that can be
-used with usbfs. This gives two primary benefits:
-
- - Zerocopy; data no longer needs to be copied between the userspace
- and the kernel, but can instead be read directly by the driver from
- userspace's buffers. This works for all kinds of transfers (even if
- nonsensical for control and interrupt transfers); isochronous also
- no longer need to memset() the buffer to zero to avoid leaking kernel data.
-
- - Once the buffers are allocated, USB transfers can no longer fail due to
- memory fragmentation; previously, long-running programs could run into
- problems finding a large enough contiguous memory chunk, especially on
- embedded systems or at high rates.
-
-Memory is allocated by using mmap() against the usbfs file descriptor,
-and similarly deallocated by munmap(). Once memory has been allocated,
-using it as pointers to a bulk or isochronous operation means you will
-automatically get zerocopy behavior. Note that this also means you cannot
-modify outgoing data until the transfer is complete. The same holds for
-data on the same cache lines as incoming data; DMA modifying them at the
-same time could lead to your changes being overwritten.
-
-There's a new capability USBDEVFS_CAP_MMAP that userspace can query to see
-if the running kernel supports this functionality, if just trying mmap() is
-not acceptable.
-
-Largely based on a patch by Markus Rechberger with some updates. The original
-patch can be found at:
-
- http://sundtek.de/support/devio_mmap_v0.4.diff
-
-Signed-off-by: Steinar H. Gunderson <sesse@google.com>
-Signed-off-by: Markus Rechberger <mrechberger@gmail.com>
-Acked-by: Alan Stern <stern@rowland.harvard.edu>
----
- drivers/usb/core/devio.c | 227 +++++++++++++++++++++++++++++++++-----
- include/uapi/linux/usbdevice_fs.h | 1 +
- 2 files changed, 203 insertions(+), 25 deletions(-)
-
-diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
-index 38ae877c..0238c78 100644
---- a/drivers/usb/core/devio.c
-+++ b/drivers/usb/core/devio.c
-@@ -50,6 +50,7 @@
- #include <linux/user_namespace.h>
- #include <linux/scatterlist.h>
- #include <linux/uaccess.h>
-+#include <linux/dma-mapping.h>
- #include <asm/byteorder.h>
- #include <linux/moduleparam.h>
-
-@@ -69,6 +70,7 @@ struct usb_dev_state {
- spinlock_t lock; /* protects the async urb lists */
- struct list_head async_pending;
- struct list_head async_completed;
-+ struct list_head memory_list;
- wait_queue_head_t wait; /* wake up if a request completed */
- unsigned int discsignr;
- struct pid *disc_pid;
-@@ -79,6 +81,17 @@ struct usb_dev_state {
- u32 disabled_bulk_eps;
- };
-
-+struct usb_memory {
-+ struct list_head memlist;
-+ int vma_use_count;
-+ int urb_use_count;
-+ u32 size;
-+ void *mem;
-+ dma_addr_t dma_handle;
-+ unsigned long vm_start;
-+ struct usb_dev_state *ps;
-+};
-+
- struct async {
- struct list_head asynclist;
- struct usb_dev_state *ps;
-@@ -89,6 +102,7 @@ struct async {
- void __user *userbuffer;
- void __user *userurb;
- struct urb *urb;
-+ struct usb_memory *usbm;
- unsigned int mem_usage;
- int status;
- u32 secid;
-@@ -157,6 +171,111 @@ static int connected(struct usb_dev_state *ps)
- ps->dev->state != USB_STATE_NOTATTACHED);
- }
-
-+static void dec_usb_memory_use_count(struct usb_memory *usbm, int *count)
-+{
-+ struct usb_dev_state *ps = usbm->ps;
-+ unsigned long flags;
-+
-+ spin_lock_irqsave(&ps->lock, flags);
-+ --*count;
-+ if (usbm->urb_use_count == 0 && usbm->vma_use_count == 0) {
-+ list_del(&usbm->memlist);
-+ spin_unlock_irqrestore(&ps->lock, flags);
-+
-+ usb_free_coherent(ps->dev, usbm->size, usbm->mem,
-+ usbm->dma_handle);
-+ usbfs_decrease_memory_usage(
-+ usbm->size + sizeof(struct usb_memory));
-+ kfree(usbm);
-+ } else {
-+ spin_unlock_irqrestore(&ps->lock, flags);
-+ }
-+}
-+
-+static void usbdev_vm_open(struct vm_area_struct *vma)
-+{
-+ struct usb_memory *usbm = vma->vm_private_data;
-+ unsigned long flags;
-+
-+ spin_lock_irqsave(&usbm->ps->lock, flags);
-+ ++usbm->vma_use_count;
-+ spin_unlock_irqrestore(&usbm->ps->lock, flags);
-+}
-+
-+static void usbdev_vm_close(struct vm_area_struct *vma)
-+{
-+ struct usb_memory *usbm = vma->vm_private_data;
-+
-+ dec_usb_memory_use_count(usbm, &usbm->vma_use_count);
-+}
-+
-+struct vm_operations_struct usbdev_vm_ops = {
-+ .open = usbdev_vm_open,
-+ .close = usbdev_vm_close
-+};
-+
-+static int usbdev_mmap(struct file *file, struct vm_area_struct *vma)
-+{
-+ struct usb_memory *usbm = NULL;
-+ struct usb_dev_state *ps = file->private_data;
-+ size_t size = vma->vm_end - vma->vm_start;
-+ void *mem;
-+ unsigned long flags;
-+ dma_addr_t dma_handle;
-+ int ret;
-+
-+ ret = usbfs_increase_memory_usage(size + sizeof(struct usb_memory));
-+ if (ret)
-+ goto error;
-+
-+ usbm = kzalloc(sizeof(struct usb_memory), GFP_KERNEL);
-+ if (!usbm) {
-+ ret = -ENOMEM;
-+ goto error_decrease_mem;
-+ }
-+
-+ mem = usb_alloc_coherent(ps->dev, size, GFP_USER, &dma_handle);
-+ if (!mem) {
-+ ret = -ENOMEM;
-+ goto error_free_usbm;
-+ }
-+
-+ memset(mem, 0, size);
-+
-+ usbm->mem = mem;
-+ usbm->dma_handle = dma_handle;
-+ usbm->size = size;
-+ usbm->ps = ps;
-+ usbm->vm_start = vma->vm_start;
-+ usbm->vma_use_count = 1;
-+ INIT_LIST_HEAD(&usbm->memlist);
-+
-+ if (remap_pfn_range(vma, vma->vm_start,
-+ virt_to_phys(usbm->mem) >> PAGE_SHIFT,
-+ size, vma->vm_page_prot) < 0) {
-+ dec_usb_memory_use_count(usbm, &usbm->vma_use_count);
-+ return -EAGAIN;
-+ }
-+
-+ vma->vm_flags |= VM_IO;
-+ vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP);
-+ vma->vm_ops = &usbdev_vm_ops;
-+ vma->vm_private_data = usbm;
-+
-+ spin_lock_irqsave(&ps->lock, flags);
-+ list_add_tail(&usbm->memlist, &ps->memory_list);
-+ spin_unlock_irqrestore(&ps->lock, flags);
-+
-+ return 0;
-+
-+error_free_usbm:
-+ kfree(usbm);
-+error_decrease_mem:
-+ usbfs_decrease_memory_usage(size + sizeof(struct usb_memory));
-+error:
-+ return ret;
-+}
-+
- static loff_t usbdev_lseek(struct file *file, loff_t offset, int orig)
- {
- loff_t ret;
-@@ -297,8 +416,13 @@ static void free_async(struct async *as)
- if (sg_page(&as->urb->sg[i]))
- kfree(sg_virt(&as->urb->sg[i]));
- }
-+
- kfree(as->urb->sg);
-- kfree(as->urb->transfer_buffer);
-+ if (as->usbm == NULL)
-+ kfree(as->urb->transfer_buffer);
-+ else
-+ dec_usb_memory_use_count(as->usbm, &as->usbm->urb_use_count);
-+
- kfree(as->urb->setup_packet);
- usb_free_urb(as->urb);
- usbfs_decrease_memory_usage(as->mem_usage);
-@@ -910,6 +1034,7 @@ static int usbdev_open(struct inode *inode, struct file *file)
- INIT_LIST_HEAD(&ps->list);
- INIT_LIST_HEAD(&ps->async_pending);
- INIT_LIST_HEAD(&ps->async_completed);
-+ INIT_LIST_HEAD(&ps->memory_list);
- init_waitqueue_head(&ps->wait);
- ps->discsignr = 0;
- ps->disc_pid = get_pid(task_pid(current));
-@@ -962,6 +1087,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
- free_async(as);
- as = async_getcompleted(ps);
- }
-+
- kfree(ps);
- return 0;
- }
-@@ -1283,6 +1409,31 @@ static int proc_setconfig(struct usb_dev_state *ps, void __user *arg)
- return status;
- }
-
-+static struct usb_memory *
-+find_memory_area(struct usb_dev_state *ps, const struct usbdevfs_urb *uurb)
-+{
-+ struct usb_memory *usbm = NULL, *iter;
-+ unsigned long flags;
-+ unsigned long uurb_start = (unsigned long)uurb->buffer;
-+
-+ spin_lock_irqsave(&ps->lock, flags);
-+ list_for_each_entry(iter, &ps->memory_list, memlist) {
-+ if (uurb_start >= iter->vm_start &&
-+ uurb_start < iter->vm_start + iter->size) {
-+ if (uurb->buffer_length > iter->vm_start + iter->size -
-+ uurb_start) {
-+ usbm = ERR_PTR(-EINVAL);
-+ } else {
-+ usbm = iter;
-+ usbm->urb_use_count++;
-+ }
-+ break;
-+ }
-+ }
-+ spin_unlock_irqrestore(&ps->lock, flags);
-+ return usbm;
-+}
-+
- static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb,
- struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
- void __user *arg)
-@@ -1439,6 +1590,19 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
- goto error;
- }
-
-+ as->usbm = find_memory_area(ps, uurb);
-+ if (IS_ERR(as->usbm)) {
-+ ret = PTR_ERR(as->usbm);
-+ as->usbm = NULL;
-+ goto error;
-+ }
-+
-+ /* do not use SG buffers when memory mapped segments
-+ * are in use
-+ */
-+ if (as->usbm)
-+ num_sgs = 0;
-+
- u += sizeof(struct async) + sizeof(struct urb) + uurb->buffer_length +
- num_sgs * sizeof(struct scatterlist);
- ret = usbfs_increase_memory_usage(u);
-@@ -1476,29 +1640,35 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
- totlen -= u;
- }
- } else if (uurb->buffer_length > 0) {
-- as->urb->transfer_buffer = kmalloc(uurb->buffer_length,
-- GFP_KERNEL);
-- if (!as->urb->transfer_buffer) {
-- ret = -ENOMEM;
-- goto error;
-- }
-+ if (as->usbm) {
-+ unsigned long uurb_start = (unsigned long)uurb->buffer;
-
-- if (!is_in) {
-- if (copy_from_user(as->urb->transfer_buffer,
-- uurb->buffer,
-- uurb->buffer_length)) {
-- ret = -EFAULT;
-+ as->urb->transfer_buffer = as->usbm->mem +
-+ (uurb_start - as->usbm->vm_start);
-+ } else {
-+ as->urb->transfer_buffer = kmalloc(uurb->buffer_length,
-+ GFP_KERNEL);
-+ if (!as->urb->transfer_buffer) {
-+ ret = -ENOMEM;
- goto error;
- }
-- } else if (uurb->type == USBDEVFS_URB_TYPE_ISO) {
-- /*
-- * Isochronous input data may end up being
-- * discontiguous if some of the packets are short.
-- * Clear the buffer so that the gaps don't leak
-- * kernel data to userspace.
-- */
-- memset(as->urb->transfer_buffer, 0,
-- uurb->buffer_length);
-+ if (!is_in) {
-+ if (copy_from_user(as->urb->transfer_buffer,
-+ uurb->buffer,
-+ uurb->buffer_length)) {
-+ ret = -EFAULT;
-+ goto error;
-+ }
-+ } else if (uurb->type == USBDEVFS_URB_TYPE_ISO) {
-+ /*
-+ * Isochronous input data may end up being
-+ * discontiguous if some of the packets are
-+ * short. Clear the buffer so that the gaps
-+ * don't leak kernel data to userspace.
-+ */
-+ memset(as->urb->transfer_buffer, 0,
-+ uurb->buffer_length);
-+ }
- }
- }
- as->urb->dev = ps->dev;
-@@ -1545,10 +1715,14 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
- isopkt = NULL;
- as->ps = ps;
- as->userurb = arg;
-- if (is_in && uurb->buffer_length > 0)
-+ if (as->usbm) {
-+ unsigned long uurb_start = (unsigned long)uurb->buffer;
-+
-+ as->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
-+ as->urb->transfer_dma = as->usbm->dma_handle +
-+ (uurb_start - as->usbm->vm_start);
-+ } else if (is_in && uurb->buffer_length > 0)
- as->userbuffer = uurb->buffer;
-- else
-- as->userbuffer = NULL;
- as->signr = uurb->signr;
- as->ifnum = ifnum;
- as->pid = get_pid(task_pid(current));
-@@ -1604,6 +1778,8 @@ static int proc_do_submiturb(struct usb_dev_state *ps, struct usbdevfs_urb *uurb
- return 0;
-
- error:
-+ if (as && as->usbm)
-+ dec_usb_memory_use_count(as->usbm, &as->usbm->urb_use_count);
- kfree(isopkt);
- kfree(dr);
- if (as)
-@@ -2047,7 +2223,7 @@ static int proc_get_capabilities(struct usb_dev_state *ps, void __user *arg)
- __u32 caps;
-
- caps = USBDEVFS_CAP_ZERO_PACKET | USBDEVFS_CAP_NO_PACKET_SIZE_LIM |
-- USBDEVFS_CAP_REAP_AFTER_DISCONNECT;
-+ USBDEVFS_CAP_REAP_AFTER_DISCONNECT | USBDEVFS_CAP_MMAP;
- if (!ps->dev->bus->no_stop_on_short)
- caps |= USBDEVFS_CAP_BULK_CONTINUATION;
- if (ps->dev->bus->sg_tablesize)
-@@ -2373,6 +2549,7 @@ const struct file_operations usbdev_file_operations = {
- #ifdef CONFIG_COMPAT
- .compat_ioctl = usbdev_compat_ioctl,
- #endif
-+ .mmap = usbdev_mmap,
- .open = usbdev_open,
- .release = usbdev_release,
- };
-diff --git a/include/uapi/linux/usbdevice_fs.h b/include/uapi/linux/usbdevice_fs.h
-index 019ba1e..ecbd176 100644
---- a/include/uapi/linux/usbdevice_fs.h
-+++ b/include/uapi/linux/usbdevice_fs.h
-@@ -134,6 +134,7 @@ struct usbdevfs_hub_portinfo {
- #define USBDEVFS_CAP_NO_PACKET_SIZE_LIM 0x04
- #define USBDEVFS_CAP_BULK_SCATTER_GATHER 0x08
- #define USBDEVFS_CAP_REAP_AFTER_DISCONNECT 0x10
-+#define USBDEVFS_CAP_MMAP 0x20
-
- /* USBDEVFS_DISCONNECT_CLAIM flags & struct */
-
---
-2.1.4
-