Re: [PATCH] Memory mapping for USBFS

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

 



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




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

  Powered by Linux