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

Commit d3b60e7b authored by Jack Pham's avatar Jack Pham Committed by Pratham Pratap
Browse files

usb: gadget: f_fs: Fail waiting IO after eps disabled



Normally in epfile_io() and epfile_ioctl(), if the endpoint is
not enabled the system call will block waiting until the endpoint
is enabled. During a USB disconnect, the function's endpoints get
disabled, so that if an IO call occurs right after that, it would
perform the same blocking wait. From the kernel perspective this
is indistinguishable from an IO call happening after the function
is first initialized but not yet enabled. Hence a userspace
process would be blocked and unaware of the cable disconnection.

For userspace daemons such as ADB, this can be a problem since its
read/write threads cannot be reaped until the I/O call returns,
which won't happen until the cable is reconnected. In that case,
the previous pending read/write would proceed to get successfully
queued. In the case of a read, the previous read consumes data
meant for the new session, and in the case of a write, stale data
could be sent to the host.

To fix this, add a counter to keep track of the epfile's open status.
Upon the endpoint getting disabled mark the endpoint as invalid so as
to fail subsequent IOs until the epfile is fully closed. Returning
-ENODEV upon failure should allow the userspace process to close the
file, so that it can reopen and begin a new session.

Change-Id: I655ffdaa03485753431ff05dc521bf8d1b463ff1
Signed-off-by: default avatarJack Pham <jackp@codeaurora.org>
parent a1fe213f
Loading
Loading
Loading
Loading
+29 −7
Original line number Original line Diff line number Diff line
@@ -139,6 +139,7 @@ struct ffs_epfile {


	struct ffs_data			*ffs;
	struct ffs_data			*ffs;
	struct ffs_ep			*ep;	/* P: ffs->eps_lock */
	struct ffs_ep			*ep;	/* P: ffs->eps_lock */
	atomic_t			opened;


	struct dentry			*dentry;
	struct dentry			*dentry;


@@ -205,7 +206,7 @@ struct ffs_epfile {
	unsigned char			in;	/* P: ffs->eps_lock */
	unsigned char			in;	/* P: ffs->eps_lock */
	unsigned char			isoc;	/* P: ffs->eps_lock */
	unsigned char			isoc;	/* P: ffs->eps_lock */


	unsigned char			_pad;
	bool				invalid;
};
};


struct ffs_buffer {
struct ffs_buffer {
@@ -956,6 +957,16 @@ static ssize_t ffs_epfile_io(struct file *file, struct ffs_io_data *io_data)
		if (file->f_flags & O_NONBLOCK)
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;
			return -EAGAIN;


		/*
		 * epfile->invalid is set when EPs are disabled. Userspace
		 * might have stale threads continuing to do I/O and may be
		 * unaware of that especially if we block here. Instead return
		 * an error immediately here and don't allow any more I/O
		 * until the epfile is reopened.
		 */
		if (epfile->invalid)
			return -ENODEV;

		ret = wait_event_interruptible(
		ret = wait_event_interruptible(
				epfile->ffs->wait, (ep = epfile->ep));
				epfile->ffs->wait, (ep = epfile->ep));
		if (ret)
		if (ret)
@@ -1152,15 +1163,16 @@ ffs_epfile_open(struct inode *inode, struct file *file)


	ENTER();
	ENTER();


	ffs_log("%s: state %d setup_state %d flag %lu", epfile->name,
	ffs_log("%s: state %d setup_state %d flag %lu opened %u",
		epfile->ffs->state, epfile->ffs->setup_state,
		epfile->name, epfile->ffs->state, epfile->ffs->setup_state,
		epfile->ffs->flags);
		epfile->ffs->flags, atomic_read(&epfile->opened));


	if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
	if (WARN_ON(epfile->ffs->state != FFS_ACTIVE))
		return -ENODEV;
		return -ENODEV;


	file->private_data = epfile;
	file->private_data = epfile;
	ffs_data_opened(epfile->ffs);
	ffs_data_opened(epfile->ffs);
	atomic_inc(&epfile->opened);


	return 0;
	return 0;
}
}
@@ -1300,9 +1312,12 @@ ffs_epfile_release(struct inode *inode, struct file *file)
	ENTER();
	ENTER();


	__ffs_epfile_read_buffer_free(epfile);
	__ffs_epfile_read_buffer_free(epfile);
	ffs_log("%s: state %d setup_state %d flag %lu", epfile->name,
	ffs_log("%s: state %d setup_state %d flag %lu opened %u",
			epfile->ffs->state, epfile->ffs->setup_state,
		epfile->name, epfile->ffs->state, epfile->ffs->setup_state,
			epfile->ffs->flags);
		epfile->ffs->flags, atomic_read(&epfile->opened));

	if (atomic_dec_and_test(&epfile->opened))
		epfile->invalid = false;


	ffs_data_closed(epfile->ffs);
	ffs_data_closed(epfile->ffs);


@@ -1332,6 +1347,10 @@ static long ffs_epfile_ioctl(struct file *file, unsigned code,
		if (file->f_flags & O_NONBLOCK)
		if (file->f_flags & O_NONBLOCK)
			return -EAGAIN;
			return -EAGAIN;


		/* don't allow any I/O until file is reopened */
		if (epfile->invalid)
			return -ENODEV;

		ret = wait_event_interruptible(
		ret = wait_event_interruptible(
				epfile->ffs->wait, (ep = epfile->ep));
				epfile->ffs->wait, (ep = epfile->ep));
		if (ret)
		if (ret)
@@ -1997,6 +2016,8 @@ static int ffs_epfiles_create(struct ffs_data *ffs)
			ffs_epfiles_destroy(epfiles, i - 1);
			ffs_epfiles_destroy(epfiles, i - 1);
			return -ENOMEM;
			return -ENOMEM;
		}
		}

		atomic_set(&epfile->opened, 0);
	}
	}


	ffs->epfiles = epfiles;
	ffs->epfiles = epfiles;
@@ -2044,6 +2065,7 @@ static void ffs_func_eps_disable(struct ffs_function *func)
		++ep;
		++ep;


		if (epfile) {
		if (epfile) {
			epfile->invalid = true; /* until file is reopened */
			epfile->ep = NULL;
			epfile->ep = NULL;
			__ffs_epfile_read_buffer_free(epfile);
			__ffs_epfile_read_buffer_free(epfile);
			++epfile;
			++epfile;