diff options
Diffstat (limited to 'drivers/usb/gadget/function/f_fs.c')
| -rw-r--r-- | drivers/usb/gadget/function/f_fs.c | 265 |
1 files changed, 231 insertions, 34 deletions
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c index cc33d2667..3a5530d05 100644 --- a/drivers/usb/gadget/function/f_fs.c +++ b/drivers/usb/gadget/function/f_fs.c @@ -130,6 +130,64 @@ struct ffs_epfile { struct dentry *dentry; + /* + * Buffer for holding data from partial reads which may happen since + * we’re rounding user read requests to a multiple of a max packet size. + * + * The pointer is initialised with NULL value and may be set by + * __ffs_epfile_read_data function to point to a temporary buffer. + * + * In normal operation, calls to __ffs_epfile_read_buffered will consume + * data from said buffer and eventually free it. Importantly, while the + * function is using the buffer, it sets the pointer to NULL. This is + * all right since __ffs_epfile_read_data and __ffs_epfile_read_buffered + * can never run concurrently (they are synchronised by epfile->mutex) + * so the latter will not assign a new value to the pointer. + * + * Meanwhile ffs_func_eps_disable frees the buffer (if the pointer is + * valid) and sets the pointer to READ_BUFFER_DROP value. This special + * value is crux of the synchronisation between ffs_func_eps_disable and + * __ffs_epfile_read_data. + * + * Once __ffs_epfile_read_data is about to finish it will try to set the + * pointer back to its old value (as described above), but seeing as the + * pointer is not-NULL (namely READ_BUFFER_DROP) it will instead free + * the buffer. + * + * == State transitions == + * + * • ptr == NULL: (initial state) + * ◦ __ffs_epfile_read_buffer_free: go to ptr == DROP + * ◦ __ffs_epfile_read_buffered: nop + * ◦ __ffs_epfile_read_data allocates temp buffer: go to ptr == buf + * ◦ reading finishes: n/a, not in ‘and reading’ state + * • ptr == DROP: + * ◦ __ffs_epfile_read_buffer_free: nop + * ◦ __ffs_epfile_read_buffered: go to ptr == NULL + * ◦ __ffs_epfile_read_data allocates temp buffer: free buf, nop + * ◦ reading finishes: n/a, not in ‘and reading’ state + * • ptr == buf: + * ◦ __ffs_epfile_read_buffer_free: free buf, go to ptr == DROP + * ◦ __ffs_epfile_read_buffered: go to ptr == NULL and reading + * ◦ __ffs_epfile_read_data: n/a, __ffs_epfile_read_buffered + * is always called first + * ◦ reading finishes: n/a, not in ‘and reading’ state + * • ptr == NULL and reading: + * ◦ __ffs_epfile_read_buffer_free: go to ptr == DROP and reading + * ◦ __ffs_epfile_read_buffered: n/a, mutex is held + * ◦ __ffs_epfile_read_data: n/a, mutex is held + * ◦ reading finishes and … + * … all data read: free buf, go to ptr == NULL + * … otherwise: go to ptr == buf and reading + * • ptr == DROP and reading: + * ◦ __ffs_epfile_read_buffer_free: nop + * ◦ __ffs_epfile_read_buffered: n/a, mutex is held + * ◦ __ffs_epfile_read_data: n/a, mutex is held + * ◦ reading finishes: free buf, go to ptr == DROP + */ + struct ffs_buffer *read_buffer; +#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN)) + char name[5]; unsigned char in; /* P: ffs->eps_lock */ @@ -138,6 +196,12 @@ struct ffs_epfile { unsigned char _pad; }; +struct ffs_buffer { + size_t length; + char *data; + char storage[]; +}; + /* ffs_io_data structure ***************************************************/ struct ffs_io_data { @@ -640,6 +704,49 @@ static void ffs_epfile_io_complete(struct usb_ep *_ep, struct usb_request *req) } } +static ssize_t ffs_copy_to_iter(void *data, int data_len, struct iov_iter *iter) +{ + ssize_t ret = copy_to_iter(data, data_len, iter); + if (likely(ret == data_len)) + return ret; + + if (unlikely(iov_iter_count(iter))) + return -EFAULT; + + /* + * Dear user space developer! + * + * TL;DR: To stop getting below error message in your kernel log, change + * user space code using functionfs to align read buffers to a max + * packet size. + * + * Some UDCs (e.g. dwc3) require request sizes to be a multiple of a max + * packet size. When unaligned buffer is passed to functionfs, it + * internally uses a larger, aligned buffer so that such UDCs are happy. + * + * Unfortunately, this means that host may send more data than was + * requested in read(2) system call. f_fs doesn’t know what to do with + * that excess data so it simply drops it. + * + * Was the buffer aligned in the first place, no such problem would + * happen. + * + * Data may be dropped only in AIO reads. Synchronous reads are handled + * by splitting a request into multiple parts. This splitting may still + * be a problem though so it’s likely best to align the buffer + * regardless of it being AIO or not.. + * + * This only affects OUT endpoints, i.e. reading data with a read(2), + * aio_read(2) etc. system calls. Writing data to an IN endpoint is not + * affected. + */ + pr_err("functionfs read size %d > requested size %zd, dropping excess data. " + "Align read buffer size to max packet size to avoid the problem.\n", + data_len, ret); + + return ret; +} + static void ffs_user_copy_worker(struct work_struct *work) { struct ffs_io_data *io_data = container_of(work, struct ffs_io_data, @@ -650,9 +757,7 @@ static void ffs_user_copy_worker(struct work_struct *work) if (io_data->read && ret > 0) { use_mm(io_data->mm); - ret = copy_to_iter(io_data->buf, ret, &io_data->data); - if (ret != io_data->req->actual && iov_iter_count(&io_data->data)) - ret = -EFAULT; + ret = ffs_copy_to_iter(io_data->buf, ret, &io_data->data); unuse_mm(io_data->mm); } @@ -680,6 +785,88 @@ static void ffs_epfile_async_io_complete(struct usb_ep *_ep, schedule_work(&io_data->work); } +static void __ffs_epfile_read_buffer_free(struct ffs_epfile *epfile) +{ + /* + * See comment in struct ffs_epfile for full read_buffer pointer + * synchronisation story. + */ + struct ffs_buffer *buf = xchg(&epfile->read_buffer, READ_BUFFER_DROP); + if (buf && buf != READ_BUFFER_DROP) + kfree(buf); +} + +/* Assumes epfile->mutex is held. */ +static ssize_t __ffs_epfile_read_buffered(struct ffs_epfile *epfile, + struct iov_iter *iter) +{ + /* + * Null out epfile->read_buffer so ffs_func_eps_disable does not free + * the buffer while we are using it. See comment in struct ffs_epfile + * for full read_buffer pointer synchronisation story. + */ + struct ffs_buffer *buf = xchg(&epfile->read_buffer, NULL); + ssize_t ret; + if (!buf || buf == READ_BUFFER_DROP) + return 0; + + ret = copy_to_iter(buf->data, buf->length, iter); + if (buf->length == ret) { + kfree(buf); + return ret; + } + + if (unlikely(iov_iter_count(iter))) { + ret = -EFAULT; + } else { + buf->length -= ret; + buf->data += ret; + } + + if (cmpxchg(&epfile->read_buffer, NULL, buf)) + kfree(buf); + + return ret; +} + +/* Assumes epfile->mutex is held. */ +static ssize_t __ffs_epfile_read_data(struct ffs_epfile *epfile, + void *data, int data_len, + struct iov_iter *iter) +{ + struct ffs_buffer *buf; + + ssize_t ret = copy_to_iter(data, data_len, iter); + if (likely(data_len == ret)) + return ret; + + if (unlikely(iov_iter_count(iter))) + return -EFAULT; + + /* See ffs_copy_to_iter for more context. */ + pr_warn("functionfs read size %d > requested size %zd, splitting request into multiple reads.", + data_len, ret); + + data_len -= ret; + buf = kmalloc(sizeof(*buf) + data_len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + buf->length = data_len; + buf->data = buf->storage; + memcpy(buf->storage, data + ret, data_len); + + /* + * At this point read_buffer is NULL or READ_BUFFER_DROP (if + * ffs_func_eps_disable has been called in the meanwhile). See comment + * in struct ffs_epfile for full read_buffer pointer synchronisation + * story. + */ + if (unlikely(cmpxchg(&epfile->read_buffer, NULL, buf))) + kfree(buf); + + return ret; +} + static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) { struct ffs_epfile *epfile = file->private_data; @@ -709,21 +896,40 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) if (halt && epfile->isoc) return -EINVAL; + /* We will be using request and read_buffer */ + ret = ffs_mutex_lock(&epfile->mutex, file->f_flags & O_NONBLOCK); + if (unlikely(ret)) + goto error; + /* Allocate & copy */ if (!halt) { + struct usb_gadget *gadget; + + /* + * Do we have buffered data from previous partial read? Check + * that for synchronous case only because we do not have + * facility to ‘wake up’ a pending asynchronous read and push + * buffered data to it which we would need to make things behave + * consistently. + */ + if (!io_data->aio && io_data->read) { + ret = __ffs_epfile_read_buffered(epfile, &io_data->data); + if (ret) + goto error_mutex; + } + /* * if we _do_ wait above, the epfile->ffs->gadget might be NULL * before the waiting completes, so do not assign to 'gadget' * earlier */ - struct usb_gadget *gadget = epfile->ffs->gadget; - size_t copied; + gadget = epfile->ffs->gadget; spin_lock_irq(&epfile->ffs->eps_lock); /* In the meantime, endpoint got disabled or changed. */ if (epfile->ep != ep) { - spin_unlock_irq(&epfile->ffs->eps_lock); - return -ESHUTDOWN; + ret = -ESHUTDOWN; + goto error_lock; } data_len = iov_iter_count(&io_data->data); /* @@ -735,22 +941,17 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) spin_unlock_irq(&epfile->ffs->eps_lock); data = kmalloc(data_len, GFP_KERNEL); - if (unlikely(!data)) - return -ENOMEM; - if (!io_data->read) { - copied = copy_from_iter(data, data_len, &io_data->data); - if (copied != data_len) { - ret = -EFAULT; - goto error; - } + if (unlikely(!data)) { + ret = -ENOMEM; + goto error_mutex; + } + if (!io_data->read && + copy_from_iter(data, data_len, &io_data->data) != data_len) { + ret = -EFAULT; + goto error_mutex; } } - /* We will be using request */ - ret = ffs_mutex_lock(&epfile->mutex, file->f_flags & O_NONBLOCK); - if (unlikely(ret)) - goto error; - spin_lock_irq(&epfile->ffs->eps_lock); if (epfile->ep != ep) { @@ -803,18 +1004,13 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data) interrupted = ep->status < 0; } - /* - * XXX We may end up silently droping data here. Since data_len - * (i.e. req->length) may be bigger than len (after being - * rounded up to maxpacketsize), we may end up with more data - * then user space has space for. - */ - ret = interrupted ? -EINTR : ep->status; - if (io_data->read && ret > 0) { - ret = copy_to_iter(data, ret, &io_data->data); - if (!ret) - ret = -EFAULT; - } + if (interrupted) + ret = -EINTR; + else if (io_data->read && ep->status > 0) + ret = __ffs_epfile_read_data(epfile, data, ep->status, + &io_data->data); + else + ret = ep->status; goto error_mutex; } else if (!(req = usb_ep_alloc_request(ep->ep, GFP_KERNEL))) { ret = -ENOMEM; @@ -980,6 +1176,7 @@ ffs_epfile_release(struct inode *inode, struct file *file) ENTER(); + __ffs_epfile_read_buffer_free(epfile); ffs_data_closed(epfile->ffs); return 0; @@ -1614,6 +1811,7 @@ static void ffs_func_eps_disable(struct ffs_function *func) if (epfile) { epfile->ep = NULL; + __ffs_epfile_read_buffer_free(epfile); ++epfile; } } while (--count); @@ -2227,8 +2425,8 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, { u32 str_count, needed_count, lang_count; struct usb_gadget_strings **stringtabs, *t; - struct usb_string *strings, *s; const char *data = _data; + struct usb_string *s; ENTER(); @@ -2286,7 +2484,6 @@ static int __ffs_data_got_strings(struct ffs_data *ffs, stringtabs = vla_ptr(vlabuf, d, stringtabs); t = vla_ptr(vlabuf, d, stringtab); s = vla_ptr(vlabuf, d, strings); - strings = s; } /* For each language */ |
