summaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@quicinc.com>2017-11-13 07:26:30 -0800
committerGerrit - the friendly Code Review server <code-review@localhost>2017-11-13 07:26:30 -0800
commit525336aad16e686d90c87242db716ecec42e9255 (patch)
tree227f5d39b6e2458d2b25097ca63a4fcde2b00f2d /drivers/usb
parent3d47351ac1c07f58bccb16102529a5da49d53be3 (diff)
parenta17c88a8083977fb552de7ec78b8bc1ddf554465 (diff)
Merge "usb: gadget: ffs: Defer freeing memory on free_inst if in use"
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/gadget/function/f_fs.c150
1 files changed, 145 insertions, 5 deletions
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index bb02b3be17f7..2cbd90a7db43 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -49,7 +49,7 @@ static void *ffs_ipc_log;
if (ffs_ipc_log) \
ipc_log_string(ffs_ipc_log, "%s: " fmt, __func__, \
##__VA_ARGS__); \
- pr_debug(fmt, ##__VA_ARGS__); \
+ pr_debug("%s: " fmt, __func__, ##__VA_ARGS__); \
} while (0)
/* Reference counter handling */
@@ -68,6 +68,18 @@ __ffs_data_got_descs(struct ffs_data *ffs, char *data, size_t len);
static int __must_check
__ffs_data_got_strings(struct ffs_data *ffs, char *data, size_t len);
+/* ffs instance status */
+static DEFINE_MUTEX(ffs_ep_lock);
+static bool ffs_inst_exist;
+static struct f_fs_opts *g_opts;
+
+/* Free instance structures */
+static void ffs_inst_clean(struct f_fs_opts *opts);
+static void ffs_inst_clean_delay(void);
+static int ffs_inst_exist_check(void);
+
+/* Global ffs_data pointer */
+static struct ffs_data *g_ffs_data;
/* The function structure ***************************************************/
@@ -288,6 +300,10 @@ static ssize_t ffs_ep0_write(struct file *file, const char __user *buf,
ffs_log("enter:len %zu state %d setup_state %d flags %lu", len,
ffs->state, ffs->setup_state, ffs->flags);
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
/* Fast check if setup was canceled */
if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
return -EIDRM;
@@ -474,6 +490,10 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
ffs_log("enter:len %zu state %d setup_state %d flags %lu", len,
ffs->state, ffs->setup_state, ffs->flags);
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
/* Fast check if setup was canceled */
if (ffs_setup_state_clear_cancelled(ffs) == FFS_SETUP_CANCELLED)
return -EIDRM;
@@ -574,12 +594,17 @@ done_mutex:
static int ffs_ep0_open(struct inode *inode, struct file *file)
{
struct ffs_data *ffs = inode->i_private;
+ int ret;
ENTER();
ffs_log("state %d setup_state %d flags %lu opened %d", ffs->state,
ffs->setup_state, ffs->flags, atomic_read(&ffs->opened));
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
if (unlikely(ffs->state == FFS_CLOSING))
return -EBUSY;
@@ -618,6 +643,10 @@ static long ffs_ep0_ioctl(struct file *file, unsigned code, unsigned long value)
ffs_log("state %d setup_state %d flags %lu opened %d", ffs->state,
ffs->setup_state, ffs->flags, atomic_read(&ffs->opened));
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
if (code == FUNCTIONFS_INTERFACE_REVMAP) {
struct ffs_function *func = ffs->func;
ret = func ? ffs_func_revmap_intf(func, value) : -ENODEV;
@@ -639,6 +668,10 @@ static unsigned int ffs_ep0_poll(struct file *file, poll_table *wait)
ffs_log("enter:state %d setup_state %d flags %lu opened %d", ffs->state,
ffs->setup_state, ffs->flags, atomic_read(&ffs->opened));
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
poll_wait(file, &ffs->ev.waitq, wait);
ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
@@ -1045,12 +1078,17 @@ static int
ffs_epfile_open(struct inode *inode, struct file *file)
{
struct ffs_epfile *epfile = inode->i_private;
+ int ret;
ENTER();
ffs_log("enter:state %d setup_state %d flag %lu", epfile->ffs->state,
epfile->ffs->setup_state, epfile->ffs->flags);
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
@@ -1105,11 +1143,16 @@ static ssize_t ffs_epfile_write_iter(struct kiocb *kiocb, struct iov_iter *from)
{
struct ffs_io_data io_data, *p = &io_data;
ssize_t res;
+ int ret;
ENTER();
ffs_log("enter");
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
if (!is_sync_kiocb(kiocb)) {
p = kmalloc(sizeof(io_data), GFP_KERNEL);
if (unlikely(!p))
@@ -1146,11 +1189,16 @@ static ssize_t ffs_epfile_read_iter(struct kiocb *kiocb, struct iov_iter *to)
{
struct ffs_io_data io_data, *p = &io_data;
ssize_t res;
+ int ret;
ENTER();
ffs_log("enter");
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
if (!is_sync_kiocb(kiocb)) {
p = kmalloc(sizeof(io_data), GFP_KERNEL);
if (unlikely(!p))
@@ -1227,6 +1275,10 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
ffs_log("enter:state %d setup_state %d flag %lu", epfile->ffs->state,
epfile->ffs->setup_state, epfile->ffs->flags);
+ ret = ffs_inst_exist_check();
+ if (ret < 0)
+ return ret;
+
if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
return -ENODEV;
@@ -1582,7 +1634,6 @@ ffs_fs_kill_sb(struct super_block *sb)
if (sb->s_fs_info) {
ffs_release_dev(sb->s_fs_info);
ffs_data_closed(sb->s_fs_info);
- ffs_data_put(sb->s_fs_info);
}
ffs_log("exit");
@@ -1667,11 +1718,16 @@ static void ffs_data_put(struct ffs_data *ffs)
smp_mb__before_atomic();
if (unlikely(atomic_dec_and_test(&ffs->ref))) {
pr_info("%s(): freeing\n", __func__);
+ /* Clear g_ffs_data */
+ ffs_dev_lock();
+ g_ffs_data = NULL;
+ ffs_dev_unlock();
ffs_data_clear(ffs);
BUG_ON(waitqueue_active(&ffs->ev.waitq) ||
waitqueue_active(&ffs->ep0req_completion.wait));
kfree(ffs->dev_name);
kfree(ffs);
+ ffs_inst_clean_delay();
}
ffs_log("exit");
@@ -1736,6 +1792,11 @@ static struct ffs_data *ffs_data_new(void)
/* XXX REVISIT need to update it in some places, or do we? */
ffs->ev.can_stall = 1;
+ /* Store ffs to g_ffs_data */
+ ffs_dev_lock();
+ g_ffs_data = ffs;
+ ffs_dev_unlock();
+
ffs_log("exit");
return ffs;
@@ -3621,17 +3682,71 @@ static struct config_item_type ffs_func_type = {
/* Function registration interface ******************************************/
-static void ffs_free_inst(struct usb_function_instance *f)
+static int ffs_inst_exist_check(void)
{
- struct f_fs_opts *opts;
+ mutex_lock(&ffs_ep_lock);
- opts = to_f_fs_opts(f);
+ if (unlikely(ffs_inst_exist == false)) {
+ mutex_unlock(&ffs_ep_lock);
+ pr_err_ratelimited(
+ "%s: f_fs instance freed already.\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ mutex_unlock(&ffs_ep_lock);
+
+ return 0;
+}
+
+static void ffs_inst_clean(struct f_fs_opts *opts)
+{
+ g_opts = NULL;
ffs_dev_lock();
_ffs_free_dev(opts->dev);
ffs_dev_unlock();
kfree(opts);
}
+static void ffs_inst_clean_delay(void)
+{
+ mutex_lock(&ffs_ep_lock);
+
+ if (unlikely(ffs_inst_exist == false)) {
+ if (g_opts) {
+ ffs_inst_clean(g_opts);
+ pr_err_ratelimited("%s: Delayed free memory\n",
+ __func__);
+ }
+ mutex_unlock(&ffs_ep_lock);
+ return;
+ }
+
+ mutex_unlock(&ffs_ep_lock);
+}
+
+static void ffs_free_inst(struct usb_function_instance *f)
+{
+ struct f_fs_opts *opts;
+
+ opts = to_f_fs_opts(f);
+
+ mutex_lock(&ffs_ep_lock);
+ if (opts->dev->ffs_data
+ && atomic_read(&opts->dev->ffs_data->opened)) {
+ ffs_inst_exist = false;
+ mutex_unlock(&ffs_ep_lock);
+ ffs_log("%s: Dev is open, free mem when dev close\n",
+ __func__);
+ return;
+ }
+
+ ffs_inst_clean(opts);
+ ffs_inst_exist = false;
+ g_opts = NULL;
+ mutex_unlock(&ffs_ep_lock);
+}
+
#define MAX_INST_NAME_LEN 40
static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name)
@@ -3649,6 +3764,14 @@ static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name)
if (!ptr)
return -ENOMEM;
+ mutex_lock(&ffs_ep_lock);
+ if (g_opts) {
+ mutex_unlock(&ffs_ep_lock);
+ ffs_log("%s: prev inst do not freed yet\n", __func__);
+ return -EBUSY;
+ }
+ mutex_unlock(&ffs_ep_lock);
+
opts = to_f_fs_opts(fi);
tmp = NULL;
@@ -3663,10 +3786,27 @@ static int ffs_set_inst_name(struct usb_function_instance *fi, const char *name)
}
opts->dev->name_allocated = true;
+ /*
+ * If ffs instance is freed and created once, new allocated
+ * opts->dev need to initialize opts->dev->ffs_data, and
+ * ffs_private_data also need to update new allocated opts->dev
+ * address.
+ */
+ if (g_ffs_data)
+ opts->dev->ffs_data = g_ffs_data;
+
+ if (opts->dev->ffs_data)
+ opts->dev->ffs_data->private_data = opts->dev;
+
ffs_dev_unlock();
kfree(tmp);
+ mutex_lock(&ffs_ep_lock);
+ ffs_inst_exist = true;
+ g_opts = opts;
+ mutex_unlock(&ffs_ep_lock);
+
return 0;
}