[RFC] usb: gadget: f_fs: stop sleeping in ffs_func_eps_disable

From: Michal Nazarewicz
Date: Wed Sep 28 2016 - 17:36:56 EST


ffs_func_eps_disable is called from atomic context so it cannot sleep
thus cannot grab a mutex. Change the handling of epfile->read_buffer
to use non-sleeping synchronisation method.

Reported-by: Chen Yu <chenyu56@xxxxxxxxxx>
Signed-off-by: Micha=C5=82 Nazarewicz <mina86@xxxxxxxxxx>
Fixes: 9353afbbfa7b ("buffer data from =E2=80=98oversized=E2=80=99 OUT requ=
ests")
---
drivers/usb/gadget/function/f_fs.c | 89 +++++++++++++++++++++++++++++++---=
----
1 file changed, 73 insertions(+), 16 deletions(-)

diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/functi=
on/f_fs.c
index 759f5d4..8db53da 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -136,8 +136,50 @@ struct ffs_epfile {
/*
* Buffer for holding data from partial reads which may happen since
* we=E2=80=99re rounding user read requests to a multiple of a max packe=
t size.
+ *
+ * The pointer starts with NULL value and may be initialised to other
+ * value by __ffs_epfile_read_data function which may need to allocate
+ * the temporary buffer.
+ *
+ * In normal operation, subsequent calls to __ffs_epfile_read_buffered
+ * will consume data from the 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 (as they are
+ * protected by epfile->mutex) so the latter will not assign a new value
+ * to the buffer.
+ *
+ * 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.
+ *
+ * This how concurrent calls to the two functions would look like (=E2=80=
=98<->=E2=80=99
+ * denotes xchg operation):
+ *
+ * read_buffer =3D some buffer
+ *
+ * THREAD A THREAD B
+ * __ffs_epfile_read_data:
+ * buf =3D NULL
+ * buf <-> read_buffer
+ * =E2=80=A6 do stuff on buf =E2=80=A6
+ * __ffs_func_eps_disable:
+ * buf =3D READ_BUFFER_DROP
+ * buf <-> read_buffer
+ * kfree(buf);
+ *
+ * old =3D cmpxchg(read_buffer, NULL, buf)
+ * if (old)
+ * kfree(buf)
*/
- struct ffs_buffer *read_buffer; /* P: epfile->mutex */
+ struct ffs_buffer *read_buffer;
+#define READ_BUFFER_DROP ((struct ffs_buffer *)ERR_PTR(-ESHUTDOWN))
=20
char name[5];
=20
@@ -740,21 +782,31 @@ static void ffs_epfile_async_io_complete(struct usb_e=
p *_ep,
static ssize_t __ffs_epfile_read_buffered(struct ffs_epfile *epfile,
struct iov_iter *iter)
{
- struct ffs_buffer *buf =3D epfile->read_buffer;
+ /*
+ * Null out epfile->read_buffer so ffs_func_eps_disable does not free
+ * the buffer while we are using it.
+ */
+ struct ffs_buffer *buf =3D xchg(&epfile->read_buffer, NULL);
ssize_t ret;
- if (!buf)
+ if (!buf || buf =3D=3D READ_BUFFER_DROP)
return 0;
=20
ret =3D copy_to_iter(buf->data, buf->length, iter);
if (buf->length =3D=3D ret) {
kfree(buf);
- epfile->read_buffer =3D NULL;
- } else if (unlikely(iov_iter_count(iter))) {
+ return ret;
+ }
+
+ if (unlikely(iov_iter_count(iter))) {
ret =3D -EFAULT;
} else {
buf->length -=3D ret;
buf->data +=3D ret;
}
+
+ if (cmpxchg(&epfile->read_buffer, NULL, buf))
+ kfree(buf);
+
return ret;
}
=20
@@ -783,7 +835,10 @@ static ssize_t __ffs_epfile_read_data(struct ffs_epfil=
e *epfile,
buf->length =3D data_len;
buf->data =3D buf->storage;
memcpy(buf->storage, data + ret, data_len);
- epfile->read_buffer =3D buf;
+
+ buf =3D xchg(&epfile->read_buffer, buf);
+ if (buf && buf !=3D READ_BUFFER_DROP)
+ kfree(buf);
=20
return ret;
}
@@ -1094,11 +1149,14 @@ static int
ffs_epfile_release(struct inode *inode, struct file *file)
{
struct ffs_epfile *epfile =3D inode->i_private;
+ struct ffs_buffer *buf;
=20
ENTER();
=20
- kfree(epfile->read_buffer);
- epfile->read_buffer =3D NULL;
+ buf =3D xchg(&epfile->read_buffer, NULL);
+ if (buf && buf !=3D READ_BUFFER_DROP)
+ kfree(buf);
+
ffs_data_closed(epfile->ffs);
=20
return 0;
@@ -1721,27 +1779,26 @@ static void ffs_func_eps_disable(struct ffs_functio=
n *func)
{
struct ffs_ep *ep =3D func->eps;
struct ffs_epfile *epfile =3D func->ffs->epfiles;
+ struct ffs_buffer *buf;
unsigned count =3D func->ffs->eps_count;
unsigned long flags;
=20
+ spin_lock_irqsave(&func->ffs->eps_lock, flags);
do {
- spin_lock_irqsave(&func->ffs->eps_lock, flags);
/* pending requests get nuked */
if (likely(ep->ep))
usb_ep_disable(ep->ep);
++ep;
- if (epfile)
- epfile->ep =3D NULL;
- spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
=20
if (epfile) {
- mutex_lock(&epfile->mutex);
- kfree(epfile->read_buffer);
- epfile->read_buffer =3D NULL;
- mutex_unlock(&epfile->mutex);
+ epfile->ep =3D NULL;
+ buf =3D xchg(&epfile->read_buffer, READ_BUFFER_DROP);
+ if (buf && buf !=3D READ_BUFFER_DROP)
+ kfree(buf);
++epfile;
}
} while (--count);
+ spin_unlock_irqrestore(&func->ffs->eps_lock, flags);
}
=20
static int ffs_func_eps_enable(struct ffs_function *func)
---- >8 -------------------------------------------------- -------------

Note: This has not been tested in *any* way. It=E2=80=99s more to demonstr=
ate
the concept even though it is likely that it does actually work.

--=20
Best regards
=E3=83=9F=E3=83=8F=E3=82=A6 =E2=80=9C=F0=9D=93=B6=F0=9D=93=B2=F0=9D=93=B7=
=F0=9D=93=AA86=E2=80=9D =E3=83=8A=E3=82=B6=E3=83=AC=E3=83=B4=E3=82=A4=E3=83=
=84
=C2=ABIf at first you don=E2=80=99t succeed, give up skydiving=C2=BB