|
@@ -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;
|
|
@@ -162,6 +176,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 ssize_t usbdev_read(struct file *file, char __user *buf, size_t nbytes,
|
|
|
loff_t *ppos)
|
|
|
{
|
|
@@ -278,8 +397,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);
|
|
@@ -893,6 +1017,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));
|
|
@@ -945,6 +1070,7 @@ static int usbdev_release(struct inode *inode, struct file *file)
|
|
|
free_async(as);
|
|
|
as = async_getcompleted(ps);
|
|
|
}
|
|
|
+
|
|
|
kfree(ps);
|
|
|
return 0;
|
|
|
}
|
|
@@ -1266,6 +1392,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)
|
|
@@ -1421,6 +1572,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);
|
|
@@ -1458,29 +1622,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;
|
|
@@ -1527,10 +1697,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));
|
|
@@ -1586,6 +1760,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)
|
|
@@ -2039,7 +2215,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)
|
|
@@ -2365,6 +2541,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,
|
|
|
};
|