On Tue, Sep 24, 2013 at 9:12 PM, Markus Rechberger <mrechberger@xxxxxxxxx> wrote: > 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 * Renamed USBDEVFS_FREE_MEMORY to USBDEVFS_RELEASE_MEMORY of course > * 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