Re: [RFC] Patch Memory Mapping for USBFS

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Sep 20, 2013 at 1:57 AM, 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.
> I guess some things can be made a little bit nicer here so any feedback is welcome.
> 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_FREE_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).
>
> http://sundtek.de/support/devio_mmap_v0.1.diff
>
> diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c
> index 737e3c1..d588e2e 100644
> --- a/drivers/usb/core/devio.c
> +++ b/drivers/usb/core/devio.c
> @@ -30,6 +30,7 @@
>   *    04.01.2000   0.2   Turned into its own filesystem
>   *    30.09.2005   0.3   Fix user-triggerable oops in async URB delivery
>   *     (CAN-2005-3055)
> + *    19.09.2013         Added memory mapping support for data packets
>   */
>
>  /*****************************************************************************/
> @@ -69,6 +70,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 +98,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 +300,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 +312,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 +840,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 +869,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 +895,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 +1213,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 +1364,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 +1403,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 == 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 +1556,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 +2040,59 @@ static int proc_disconnect_claim(struct dev_state *ps, void __user *arg)
>   return claimintf(ps, dc.interface);
>  }
>
> +static int proc_free_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 == 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 == NULL)
> + return -EFAULT;
> +
> + usbmem = kzalloc(sizeof(struct usb_memory), GFP_KERNEL);
> + usbmem->mem = mem;


to comment it by myself, memory should be cleared for not exposing
uncleared memory to userspace
and usbmem should be checked if(!mem), if(!usbmem). It will be in the
next version.

> + 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 +2269,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_FREE_MEMORY:
> + snoop(&dev->dev, "%s: FREE_MEMORY\n", __func__);
> + ret = proc_free_memory(ps, p);
> + break;
>   }
> +
>   usb_unlock_device(dev);
>   if (ret >= 0)
>   inode->i_atime = CURRENT_TIME;
> @@ -2162,6 +2295,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, __pa(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 +2383,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..a20ff92 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_FREE_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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux