This patch adds memory mapping support to USBFS for isochronous and bulk data transfers, it allows to pre-allocate usb transfer buffers. The CPU usage decreases 1-2% on my 1.3ghz U7300 notebook when transferring 20mbyte/sec, it should be more interesting to see those statistics on embedded systems where copying data is more expensive. Usage from userspace: allocation: rv = ioctl(priv->usbfd, USBDEVFS_ALLOC_MEMORY, &mem); if (rv == 0) buffer = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, priv->usbfd, mem.offset); use the mapped buffer with urb->buffer. release: rv = munmap(buffer, size); memset(&mem, 0x0, sizeof(struct usbdevfs_memory)); mem.buffer = buffer; rv = ioctl(priv->usbfd, USBDEVFS_RELEASE_MEMORY, &mem); non freed memory buffers are collected and will be released when closing the node. I tested this patch with Bulk and Isochronous, with and without memory mapping (applications which don't use mmap will just fall back to the legacy mechanism). Version 0.3: * Removed comment * Renamed USBDEVFS_FREE_MEMORY to USBDEVFS_ALLOC_MEMORY * Clearing allocated memory Signed-off-by: Markus Rechberger <patches@xxxxxxxxxxx> diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 737e3c1..584253b 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -69,6 +69,7 @@ struct 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; @@ -96,6 +97,16 @@ struct async { u8 bulk_status; }; +struct usb_memory { + struct list_head memlist; + int vma_use_count; + int usb_use_count; + u32 offset; + u32 size; + void *mem; + unsigned long vm_start; +}; + static bool usbfs_snoop; module_param(usbfs_snoop, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(usbfs_snoop, "true to log all usbfs traffic"); @@ -288,6 +299,9 @@ static struct async *alloc_async(unsigned int numisoframes) static void free_async(struct async *as) { + struct usb_memory *usbm = NULL, *usbm_iter; + unsigned long flags; + struct dev_state *ps = as->ps; int i; put_pid(as->pid); @@ -297,8 +311,22 @@ static void free_async(struct async *as) if (sg_page(&as->urb->sg[i])) kfree(sg_virt(&as->urb->sg[i])); } + + spin_lock_irqsave(&ps->lock, flags); + list_for_each_entry(usbm_iter, &ps->memory_list, memlist) { + if (usbm_iter->mem == as->urb->transfer_buffer) { + usbm = usbm_iter; + break; + } + } + spin_unlock_irqrestore(&ps->lock, flags); + kfree(as->urb->sg); - kfree(as->urb->transfer_buffer); + if (usbm == NULL) + kfree(as->urb->transfer_buffer); + else + usbm->usb_use_count--; + kfree(as->urb->setup_packet); usb_free_urb(as->urb); usbfs_decrease_memory_usage(as->mem_usage); @@ -811,6 +839,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)); @@ -839,6 +868,8 @@ static int usbdev_release(struct inode *inode, struct file *file) struct dev_state *ps = file->private_data; struct usb_device *dev = ps->dev; unsigned int ifnum; + struct list_head *p, *q; + struct usb_memory *tmp; struct async *as; usb_lock_device(dev); @@ -863,6 +894,14 @@ static int usbdev_release(struct inode *inode, struct file *file) free_async(as); as = async_getcompleted(ps); } + + list_for_each_safe(p, q, &ps->memory_list) { + tmp = list_entry(p, struct usb_memory, memlist); + list_del(p); + if (tmp->mem) + free_pages_exact(tmp->mem, tmp->size); + kfree(tmp); + } kfree(ps); return 0; } @@ -1173,6 +1212,7 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, struct usb_host_endpoint *ep; struct async *as = NULL; struct usb_ctrlrequest *dr = NULL; + struct usb_memory *usbm = NULL, *iter = NULL; unsigned int u, totlen, isofrmlen; int i, ret, is_in, num_sgs = 0, ifnum = -1; void *buf; @@ -1323,6 +1363,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, goto error; } + as->ps = ps; + u += sizeof(struct async) + sizeof(struct urb) + uurb->buffer_length + num_sgs * sizeof(struct scatterlist); ret = usbfs_increase_memory_usage(u); @@ -1360,21 +1402,47 @@ static int proc_do_submiturb(struct 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 (!list_empty(&ps->memory_list)) { + unsigned long flags; + usbm=NULL; + as->urb->transfer_buffer = NULL; + spin_lock_irqsave(&ps->lock, flags); + list_for_each_entry(iter, &ps->memory_list, memlist) { + if (iter->vm_start == (unsigned long)uurb->buffer && iter->usb_use_count == 0 && + (PAGE_SIZE<<get_order(iter->size)) >= uurb->buffer_length) { + usbm = iter; + usbm->usb_use_count++; + break; + } + } + spin_unlock_irqrestore(&ps->lock, flags); + if (usbm) { + as->urb->transfer_buffer = usbm->mem; + } else { + ret = -ENOMEM; + goto error; + } + if (as->urb->transfer_buffer == NULL) { + ret = -ENOMEM; + goto error; + } + } else { + as->urb->transfer_buffer = kmalloc(uurb->buffer_length, + GFP_KERNEL); + if (!as->urb->transfer_buffer) { + ret = -ENOMEM; + goto error; + } } - if (!is_in) { + if (!is_in && usbm == NULL) { 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) { + } else if (uurb->type == USBDEVFS_URB_TYPE_ISO && usbm == NULL) { /* * Isochronous input data may end up being * discontiguous if some of the packets are short. @@ -1487,6 +1555,8 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, return 0; error: + if (usbm) + usbm->usb_use_count--; kfree(isopkt); kfree(dr); if (as) @@ -1969,6 +2039,64 @@ static int proc_disconnect_claim(struct dev_state *ps, void __user *arg) return claimintf(ps, dc.interface); } +static int proc_release_memory(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_memory m; + struct usb_memory *usbm; + unsigned long flags; + + if (copy_from_user(&m, arg, sizeof(m))) + return -EFAULT; + + spin_lock_irqsave(&ps->lock, flags); + list_for_each_entry(usbm, &ps->memory_list, memlist) { + if ((usbm->vm_start == (unsigned long)m.buffer || usbm->offset == m.offset) && + usbm->usb_use_count == 0 && usbm->vma_use_count == 0) { + + list_del_init(&usbm->memlist); + spin_unlock_irqrestore(&ps->lock, flags); + free_pages_exact(usbm->mem, usbm->size); + kfree(usbm); + return 0; + } + } + spin_unlock_irqrestore(&ps->lock, flags); + return -EBUSY; +} + +static int proc_alloc_memory(struct dev_state *ps, void __user *arg) +{ + struct usbdevfs_memory m; + struct usb_memory *usbmem; + void *mem; + unsigned long flags; + + if (copy_from_user(&m, arg, sizeof(m))) + return -EFAULT; + + mem = alloc_pages_exact(m.size, GFP_KERNEL | GFP_DMA32); + if (!mem) + return -ENOMEM; + + usbmem = kzalloc(sizeof(struct usb_memory), GFP_KERNEL); + if (!usbmem) { + free_pages_exact(mem, m.size); + return -ENOMEM; + } + memset(mem, 0x0, (PAGE_SIZE<<get_order(m.size))); + usbmem->mem = mem; + m.offset = usbmem->offset = virt_to_phys(mem); + usbmem->size = m.size; + spin_lock_irqsave(&ps->lock, flags); + list_add_tail(&usbmem->memlist, &ps->memory_list); + spin_unlock_irqrestore(&ps->lock, flags); + + if (copy_to_user(arg, &m, sizeof(m))) + return -EFAULT; + + return 0; +} + /* * NOTE: All requests here that have interface numbers as parameters * are assuming that somehow the configuration has been prevented from @@ -2145,7 +2273,16 @@ static long usbdev_do_ioctl(struct file *file, unsigned int cmd, case USBDEVFS_DISCONNECT_CLAIM: ret = proc_disconnect_claim(ps, p); break; + case USBDEVFS_ALLOC_MEMORY: + snoop(&dev->dev, "%s: ALLOC_MEMORY\n", __func__); + ret = proc_alloc_memory(ps, p); + break; + case USBDEVFS_RELEASE_MEMORY: + snoop(&dev->dev, "%s: RELEASE_MEMORY\n", __func__); + ret = proc_release_memory(ps, p); + break; } + usb_unlock_device(dev); if (ret >= 0) inode->i_atime = CURRENT_TIME; @@ -2162,6 +2299,58 @@ static long usbdev_ioctl(struct file *file, unsigned int cmd, return ret; } +static void usbdev_vm_open(struct vm_area_struct *vma) +{ + struct usb_memory *usbm = vma->vm_private_data; + usbm->vma_use_count++; +} + +static void usbdev_vm_close(struct vm_area_struct *vma) +{ + struct usb_memory *usbm = vma->vm_private_data; + 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, *usbm_iter = NULL; + struct dev_state *ps = file->private_data; + int size = vma->vm_end - vma->vm_start; + unsigned long flags; + spin_lock_irqsave(&ps->lock, flags); + list_for_each_entry(usbm_iter, &ps->memory_list, memlist) { + + if (usbm_iter->offset == (vma->vm_pgoff<<PAGE_SHIFT) && + size <= (PAGE_SIZE<<get_order(usbm_iter->size))) { + usbm = usbm_iter; + usbm->vm_start = vma->vm_start; + break; + } + } + spin_unlock_irqrestore(&ps->lock, flags); + + if (usbm == NULL) + return -EINVAL; + + if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(usbm->mem) >> PAGE_SHIFT, + size, + vma->vm_page_prot) < 0) + 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; + usbdev_vm_open(vma); + return 0; +} + #ifdef CONFIG_COMPAT static long usbdev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) @@ -2198,6 +2387,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 0c65e4b..1e63eec 100644 --- a/include/uapi/linux/usbdevice_fs.h +++ b/include/uapi/linux/usbdevice_fs.h @@ -144,6 +144,11 @@ struct usbdevfs_disconnect_claim { char driver[USBDEVFS_MAXDRIVERNAME + 1]; }; +struct usbdevfs_memory { + u32 size; + u32 offset; + void __user *buffer; +}; #define USBDEVFS_CONTROL _IOWR('U', 0, struct usbdevfs_ctrltransfer) #define USBDEVFS_CONTROL32 _IOWR('U', 0, struct usbdevfs_ctrltransfer32) @@ -176,5 +181,7 @@ struct usbdevfs_disconnect_claim { #define USBDEVFS_RELEASE_PORT _IOR('U', 25, unsigned int) #define USBDEVFS_GET_CAPABILITIES _IOR('U', 26, __u32) #define USBDEVFS_DISCONNECT_CLAIM _IOR('U', 27, struct usbdevfs_disconnect_claim) +#define USBDEVFS_ALLOC_MEMORY _IOWR('U', 28, struct usbdevfs_memory) +#define USBDEVFS_RELEASE_MEMORY _IOW('U', 29, struct usbdevfs_memory) #endif /* _UAPI_LINUX_USBDEVICE_FS_H */ -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html