Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 93e270aa authored by Pavankumar Kondeti's avatar Pavankumar Kondeti Committed by Gerrit - the friendly Code Review server
Browse files

usb: gadget: f_hid: Fix unbind issues



Implement kref tracking for f_hid structure so that it
does not get freed while user space has reference to the
/dev/hidgX nodes.

When the hidg is unbound, unblock poll/read/write. The expectation
is that user space close and open the device nodes created upon the
next bind.

Change-Id: I19701189ed4d564f88052ef0f079880c3e744d47
Signed-off-by: default avatarPavankumar Kondeti <quic_pkondeti@quicinc.com>
parent 4803db7a
Loading
Loading
Loading
Loading
+37 −4
Original line number Diff line number Diff line
@@ -63,6 +63,8 @@ struct f_hidg {

	struct usb_ep			*in_ep;
	struct usb_ep			*out_ep;
	struct kref			kref;
	bool				bound;
};

static inline struct f_hidg *func_to_hidg(struct usb_function *f)
@@ -257,7 +259,7 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer,

	spin_lock_irqsave(&hidg->read_spinlock, flags);

#define READ_COND (!list_empty(&hidg->completed_out_req))
#define READ_COND (!list_empty(&hidg->completed_out_req) || !hidg->bound)

	/* wait for at least one buffer to complete */
	while (!READ_COND) {
@@ -271,6 +273,11 @@ static ssize_t f_hidg_read(struct file *file, char __user *buffer,
		spin_lock_irqsave(&hidg->read_spinlock, flags);
	}

	if (!hidg->bound) {
		spin_unlock_irqrestore(&hidg->read_spinlock, flags);
		return -ENODEV;
	}

	/* pick the first one */
	list = list_first_entry(&hidg->completed_out_req,
				struct f_hidg_req_list, list);
@@ -349,7 +356,7 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,

	spin_lock_irqsave(&hidg->write_spinlock, flags);

#define WRITE_COND (!hidg->write_pending)
#define WRITE_COND (!hidg->write_pending || !hidg->bound)
try_again:
	/* write queue */
	while (!WRITE_COND) {
@@ -364,6 +371,11 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
		spin_lock_irqsave(&hidg->write_spinlock, flags);
	}

	if (!hidg->bound) {
		spin_unlock_irqrestore(&hidg->write_spinlock, flags);
		return -ENODEV;
	}

	hidg->write_pending = 1;
	req = hidg->req;
	count  = min_t(unsigned, count, hidg->report_length);
@@ -426,6 +438,9 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
	poll_wait(file, &hidg->read_queue, wait);
	poll_wait(file, &hidg->write_queue, wait);

	if (!hidg->bound)
		return POLLHUP;

	if (WRITE_COND)
		ret |= EPOLLOUT | EPOLLWRNORM;

@@ -438,9 +453,21 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
#undef WRITE_COND
#undef READ_COND

static void hidg_destroy(struct kref *kref)
{
	struct f_hidg *hidg = container_of(kref, struct f_hidg, kref);

	kfree(hidg->report_desc);
	kfree(hidg);
}

static int f_hidg_release(struct inode *inode, struct file *fd)
{
	struct f_hidg *hidg =
		container_of(inode->i_cdev, struct f_hidg, cdev);

	fd->private_data = NULL;
	kref_put(&hidg->kref, hidg_destroy);
	return 0;
}

@@ -450,6 +477,7 @@ static int f_hidg_open(struct inode *inode, struct file *fd)
		container_of(inode->i_cdev, struct f_hidg, cdev);

	fd->private_data = hidg;
	kref_get(&hidg->kref);

	return 0;
}
@@ -841,6 +869,7 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
	init_waitqueue_head(&hidg->write_queue);
	init_waitqueue_head(&hidg->read_queue);
	INIT_LIST_HEAD(&hidg->completed_out_req);
	hidg->bound = true;

	/* create char device */
	cdev_init(&hidg->cdev, &f_hidg_fops);
@@ -1087,8 +1116,7 @@ static void hidg_free(struct usb_function *f)

	hidg = func_to_hidg(f);
	opts = container_of(f->fi, struct f_hid_opts, func_inst);
	kfree(hidg->report_desc);
	kfree(hidg);
	kref_put(&hidg->kref, hidg_destroy);
	mutex_lock(&opts->lock);
	--opts->refcnt;
	mutex_unlock(&opts->lock);
@@ -1098,6 +1126,10 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f)
{
	struct f_hidg *hidg = func_to_hidg(f);

	hidg->bound = false;
	wake_up(&hidg->read_queue);
	wake_up(&hidg->write_queue);

	device_destroy(hidg_class, MKDEV(major, hidg->minor));
	cdev_del(&hidg->cdev);

@@ -1148,6 +1180,7 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi)
	/* this could me made configurable at some point */
	hidg->qlen	   = 4;

	kref_init(&hidg->kref);
	return &hidg->func;
}