summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrzej Pietrasiewicz <andrzej.p@samsung.com>2018-12-10 18:27:05 +0100
committerAndrzej Pietrasiewicz <andrzej.p@samsung.com>2018-12-10 18:27:05 +0100
commit4a9cc466bb7e036746ce9e4dedb0b4b1099d47fe (patch)
treefc9d806ac8bf89db2918f05956eb6bc85be3c502
parent1f61d56c340ba8c12b863b8878fd12c0e80f978e (diff)
downloadlinux-artik7-sandbox/andrzej.p/dwc2-sg-port.tar.gz
linux-artik7-sandbox/andrzej.p/dwc2-sg-port.tar.bz2
linux-artik7-sandbox/andrzej.p/dwc2-sg-port.zip
usb: gadget: f_fs: Allow scatter-gather bufferssandbox/andrzej.p/dwc2-sg-port
Some protocols implemented in userspace with FunctionFS might require large buffers, e.g. 64kB or more. Currently the said memory is allocated with kmalloc, which might fail should system memory be highly fragmented. On the other hand, some UDC hardware allows scatter-gather operation and this patch takes advantage of this capability: if the requested buffer is larger than PAGE_SIZE and the UDC allows scatter-gather operation, then the buffer is allocated with vmalloc and a scatterlist describing it is created and passed to usb request. Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@samsung.com> # Conflicts: # drivers/usb/gadget/function/f_fs.c # Conflicts: # drivers/usb/gadget/function/f_fs.c
-rw-r--r--drivers/usb/gadget/function/f_fs.c94
1 files changed, 87 insertions, 7 deletions
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index aff62be8a056..23873cfd3162 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -22,8 +22,11 @@
#include <linux/pagemap.h>
#include <linux/export.h>
#include <linux/hid.h>
+#include <linux/mm.h>
#include <linux/module.h>
+#include <linux/scatterlist.h>
#include <linux/uio.h>
+#include <linux/vmalloc.h>
#include <asm/unaligned.h>
#include <linux/usb/composite.h>
@@ -154,6 +157,8 @@ struct ffs_io_data {
struct usb_ep *ep;
struct usb_request *req;
+ struct sg_table sgt;
+ bool use_sg;
struct ffs_data *ffs;
};
@@ -640,6 +645,65 @@ static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req)
}
}
+/*
+ * allocate a virtually contiguous buffer and create a scatterlist describing it
+ * @sg_table - pointer to a place to be filled with sg_table contents
+ * @size - required buffer size
+ */
+static void *ffs_build_sg_list(struct sg_table *sgt, size_t sz)
+{
+ struct page **pages;
+ void *vaddr, *ptr;
+ unsigned int n_pages;
+ int i;
+
+ vaddr = vmalloc(sz);
+ if (!vaddr)
+ return NULL;
+
+ n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT;
+ pages = kcalloc(n_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!pages) {
+ vfree(vaddr);
+
+ return NULL;
+ }
+ for (i = 0, ptr = vaddr; i < n_pages; ++i, ptr += PAGE_SIZE)
+ pages[i] = vmalloc_to_page(ptr);
+
+ if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) {
+ kfree(pages);
+ vfree(vaddr);
+
+ return NULL;
+ }
+ kfree(pages);
+
+ return vaddr;
+}
+
+static inline void *ffs_alloc_buffer(struct ffs_io_data *io_data,
+ size_t data_len)
+{
+ if (io_data->use_sg)
+ return ffs_build_sg_list(&io_data->sgt, data_len);
+
+ return kmalloc(data_len, GFP_KERNEL);
+}
+
+static inline void ffs_free_buffer(struct ffs_io_data *io_data)
+{
+ if (!io_data->buf)
+ return;
+
+ if (io_data->use_sg) {
+ sg_free_table(&io_data->sgt);
+ vfree(io_data->buf);
+ } else {
+ kfree(io_data->buf);
+ }
+}
+
static void ffs_user_copy_worker(struct work_struct *work)
{
struct ffs_io_data *io_data = container_of(work, struct ffs_io_data,
@@ -665,7 +729,7 @@ static void ffs_user_copy_worker(struct work_struct *work)
if (io_data->read)
kfree(io_data->to_free);
- kfree(io_data->buf);
+ ffs_free_buffer(io_data);
kfree(io_data);
}
@@ -725,6 +789,8 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
struct usb_gadget *gadget = epfile->ffs->gadget;
size_t copied;
+ io_data->use_sg = gadget->sg_supported && data_len > PAGE_SIZE;
+
spin_lock_irq(&epfile->ffs->eps_lock);
/* In the meantime, endpoint got disabled or changed. */
if (epfile->ep != ep) {
@@ -748,7 +814,7 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
data_len = usb_ep_align_maybe(gadget, ep->ep, data_len);
spin_unlock_irq(&epfile->ffs->eps_lock);
- data = kmalloc(data_len, GFP_KERNEL);
+ data = ffs_alloc_buffer(io_data, data_len);
if (unlikely(!data))
return -ENOMEM;
if (!io_data->read) {
@@ -803,8 +869,14 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
if (unlikely(!req))
goto error_lock;
- req->buf = data;
- req->length = data_len;
+ if (io_data->use_sg) {
+ req->buf = NULL;
+ req->sg = io_data->sgt.sgl;
+ req->num_sgs = io_data->sgt.nents;
+ } else {
+ req->buf = data;
+ }
+ req->length = data_len;
io_data->buf = data;
io_data->ep = ep->ep;
@@ -826,8 +898,16 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
DECLARE_COMPLETION_ONSTACK(done);
req = ep->req;
- req->buf = data;
- req->length = data_len;
+ if (io_data->use_sg) {
+ req->buf = NULL;
+ req->sg = io_data->sgt.sgl;
+ req->num_sgs = io_data->sgt.nents;
+ } else {
+ req->buf = data;
+ }
+ req->length = data_len;
+
+ io_data->buf = data;
req->context = &done;
req->complete = ffs_epfile_io_complete;
@@ -868,7 +948,7 @@ error_lock:
spin_unlock_irq(&epfile->ffs->eps_lock);
mutex_unlock(&epfile->mutex);
error:
- kfree(data);
+ ffs_free_buffer(io_data);
return ret;
}