mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-10 12:57:06 +09:00
USB: gadget: f_mtp: MTP driver cleanup:
Use a work queue instead of a separate thread for file transfer ioctls (note: the file transfer must be done on a kernel thread rather than in process context so vfs_read and vfs_write will use the correct address space for the buffers) Enforce requirement that only one ioctl call may be active at a time, and remove mutex in mtp_send_event that is now no longer necessary. Synchronize around use of shared variables to avoid SMP issues Fix mismatched calls to fget and fput Signed-off-by: Mike Lockwood <lockwood@android.com>
This commit is contained in:
committed by
Colin Cross
parent
ffab952c86
commit
491d4186e0
@@ -25,8 +25,6 @@
|
||||
#include <linux/wait.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/freezer.h>
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <linux/file.h>
|
||||
@@ -56,11 +54,6 @@
|
||||
#define TX_REQ_MAX 4
|
||||
#define RX_REQ_MAX 2
|
||||
|
||||
/* IO Thread commands */
|
||||
#define ANDROID_THREAD_QUIT 1
|
||||
#define ANDROID_THREAD_SEND_FILE 2
|
||||
#define ANDROID_THREAD_RECEIVE_FILE 3
|
||||
|
||||
/* ID for Microsoft MTP OS String */
|
||||
#define MTP_OS_STRING_ID 0xEE
|
||||
|
||||
@@ -92,6 +85,8 @@ struct mtp_dev {
|
||||
|
||||
/* synchronize access to our device file */
|
||||
atomic_t open_excl;
|
||||
/* to enforce only one ioctl at a time */
|
||||
atomic_t ioctl_excl;
|
||||
|
||||
struct list_head tx_idle;
|
||||
|
||||
@@ -101,23 +96,19 @@ struct mtp_dev {
|
||||
struct usb_request *rx_req[RX_REQ_MAX];
|
||||
struct usb_request *intr_req;
|
||||
int rx_done;
|
||||
|
||||
/* synchronize access to interrupt endpoint */
|
||||
struct mutex intr_mutex;
|
||||
/* true if interrupt endpoint is busy */
|
||||
int intr_busy;
|
||||
|
||||
/* for our file IO thread */
|
||||
struct task_struct *thread;
|
||||
/* current command for IO thread (or zero for none) */
|
||||
int thread_command;
|
||||
struct file *thread_file;
|
||||
loff_t thread_file_offset;
|
||||
size_t thread_file_length;
|
||||
/* used to wait for thread to complete current command */
|
||||
struct completion thread_wait;
|
||||
/* result from current command */
|
||||
int thread_result;
|
||||
/* for processing MTP_SEND_FILE and MTP_RECEIVE_FILE
|
||||
* ioctls on a work queue
|
||||
*/
|
||||
struct workqueue_struct *wq;
|
||||
struct work_struct send_file_work;
|
||||
struct work_struct receive_file_work;
|
||||
struct file *xfer_file;
|
||||
loff_t xfer_file_offset;
|
||||
size_t xfer_file_length;
|
||||
int xfer_result;
|
||||
};
|
||||
|
||||
static struct usb_interface_descriptor mtp_interface_desc = {
|
||||
@@ -622,14 +613,23 @@ static ssize_t mtp_write(struct file *fp, const char __user *buf,
|
||||
return r;
|
||||
}
|
||||
|
||||
static int mtp_send_file(struct mtp_dev *dev, struct file *filp,
|
||||
loff_t offset, size_t count)
|
||||
{
|
||||
/* read from a local file and write to USB */
|
||||
static void send_file_work(struct work_struct *data) {
|
||||
struct mtp_dev *dev = container_of(data, struct mtp_dev, send_file_work);
|
||||
struct usb_composite_dev *cdev = dev->cdev;
|
||||
struct usb_request *req = 0;
|
||||
int r = count, xfer, ret;
|
||||
struct file *filp;
|
||||
loff_t offset;
|
||||
size_t count;
|
||||
int r, xfer, ret;
|
||||
|
||||
DBG(cdev, "mtp_send_file(%lld %d)\n", offset, count);
|
||||
/* read our parameters */
|
||||
smp_rmb();
|
||||
filp = dev->xfer_file;
|
||||
offset = dev->xfer_file_offset;
|
||||
r = count = dev->xfer_file_length;
|
||||
|
||||
DBG(cdev, "send_file_work(%lld %d)\n", offset, count);
|
||||
|
||||
while (count > 0) {
|
||||
/* get an idle tx request to use */
|
||||
@@ -656,7 +656,7 @@ static int mtp_send_file(struct mtp_dev *dev, struct file *filp,
|
||||
req->length = xfer;
|
||||
ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
|
||||
if (ret < 0) {
|
||||
DBG(cdev, "mtp_write: xfer error %d\n", ret);
|
||||
DBG(cdev, "send_file_work: xfer error %d\n", ret);
|
||||
dev->state = STATE_ERROR;
|
||||
r = -EIO;
|
||||
break;
|
||||
@@ -671,20 +671,30 @@ static int mtp_send_file(struct mtp_dev *dev, struct file *filp,
|
||||
if (req)
|
||||
req_put(dev, &dev->tx_idle, req);
|
||||
|
||||
DBG(cdev, "mtp_write returning %d\n", r);
|
||||
return r;
|
||||
DBG(cdev, "send_file_work returning %d\n", r);
|
||||
/* write the result */
|
||||
dev->xfer_result = r;
|
||||
smp_wmb();
|
||||
}
|
||||
|
||||
static int mtp_receive_file(struct mtp_dev *dev, struct file *filp,
|
||||
loff_t offset, size_t count)
|
||||
/* read from USB and write to a local file */
|
||||
static void receive_file_work(struct work_struct *data)
|
||||
{
|
||||
struct mtp_dev *dev = container_of(data, struct mtp_dev, receive_file_work);
|
||||
struct usb_composite_dev *cdev = dev->cdev;
|
||||
struct usb_request *read_req = NULL, *write_req = NULL;
|
||||
int r = count;
|
||||
int ret;
|
||||
int cur_buf = 0;
|
||||
struct file *filp;
|
||||
loff_t offset;
|
||||
size_t count;
|
||||
int r, ret, cur_buf = 0;
|
||||
|
||||
DBG(cdev, "mtp_receive_file(%d)\n", count);
|
||||
/* read our parameters */
|
||||
smp_rmb();
|
||||
filp = dev->xfer_file;
|
||||
offset = dev->xfer_file_offset;
|
||||
r = count = dev->xfer_file_length;
|
||||
|
||||
DBG(cdev, "receive_file_work(%d)\n", count);
|
||||
|
||||
while (count > 0 || write_req) {
|
||||
if (count > 0) {
|
||||
@@ -731,64 +741,10 @@ static int mtp_receive_file(struct mtp_dev *dev, struct file *filp,
|
||||
}
|
||||
}
|
||||
|
||||
DBG(cdev, "mtp_read returning %d\n", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Kernel thread for handling file IO operations */
|
||||
static int mtp_thread(void *data)
|
||||
{
|
||||
struct mtp_dev *dev = (struct mtp_dev *)data;
|
||||
struct usb_composite_dev *cdev = dev->cdev;
|
||||
int flags;
|
||||
|
||||
DBG(cdev, "mtp_thread started\n");
|
||||
|
||||
while (1) {
|
||||
/* wait for a command */
|
||||
while (1) {
|
||||
try_to_freeze();
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (dev->thread_command != 0)
|
||||
break;
|
||||
schedule();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
|
||||
if (dev->thread_command == ANDROID_THREAD_QUIT) {
|
||||
DBG(cdev, "ANDROID_THREAD_QUIT\n");
|
||||
dev->thread_result = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (dev->thread_command == ANDROID_THREAD_SEND_FILE)
|
||||
flags = O_RDONLY | O_LARGEFILE;
|
||||
else
|
||||
flags = O_WRONLY | O_LARGEFILE | O_CREAT;
|
||||
|
||||
if (dev->thread_command == ANDROID_THREAD_SEND_FILE) {
|
||||
dev->thread_result = mtp_send_file(dev,
|
||||
dev->thread_file,
|
||||
dev->thread_file_offset,
|
||||
dev->thread_file_length);
|
||||
} else {
|
||||
dev->thread_result = mtp_receive_file(dev,
|
||||
dev->thread_file,
|
||||
dev->thread_file_offset,
|
||||
dev->thread_file_length);
|
||||
}
|
||||
|
||||
if (dev->thread_file) {
|
||||
fput(dev->thread_file);
|
||||
dev->thread_file = NULL;
|
||||
}
|
||||
dev->thread_command = 0;
|
||||
complete(&dev->thread_wait);
|
||||
}
|
||||
|
||||
done:
|
||||
DBG(cdev, "android_thread done\n");
|
||||
complete_and_exit(&dev->thread_wait, 0);
|
||||
DBG(cdev, "receive_file_work returning %d\n", r);
|
||||
/* write the result */
|
||||
dev->xfer_result = r;
|
||||
smp_wmb();
|
||||
}
|
||||
|
||||
static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event)
|
||||
@@ -802,29 +758,21 @@ static int mtp_send_event(struct mtp_dev *dev, struct mtp_event *event)
|
||||
if (length < 0 || length > INTR_BUFFER_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&dev->intr_mutex);
|
||||
|
||||
/* wait for a request to complete */
|
||||
ret = wait_event_interruptible(dev->intr_wq, !dev->intr_busy || dev->state == STATE_OFFLINE);
|
||||
if (ret < 0)
|
||||
goto done;
|
||||
if (dev->state == STATE_OFFLINE) {
|
||||
ret = -ENODEV;
|
||||
goto done;
|
||||
}
|
||||
return ret;
|
||||
if (dev->state == STATE_OFFLINE)
|
||||
return -ENODEV;
|
||||
req = dev->intr_req;
|
||||
if (copy_from_user(req->buf, (void __user *)event->data, length)) {
|
||||
ret = -EFAULT;
|
||||
goto done;
|
||||
}
|
||||
if (copy_from_user(req->buf, (void __user *)event->data, length))
|
||||
return -EFAULT;
|
||||
req->length = length;
|
||||
dev->intr_busy = 1;
|
||||
ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL);
|
||||
if (ret)
|
||||
dev->intr_busy = 0;
|
||||
|
||||
done:
|
||||
mutex_unlock(&dev->intr_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -834,22 +782,28 @@ static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
|
||||
struct file *filp = NULL;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (_lock(&dev->ioctl_excl))
|
||||
return -EBUSY;
|
||||
|
||||
switch (code) {
|
||||
case MTP_SEND_FILE:
|
||||
case MTP_RECEIVE_FILE:
|
||||
{
|
||||
struct mtp_file_range mfr;
|
||||
struct work_struct *work;
|
||||
|
||||
spin_lock_irq(&dev->lock);
|
||||
if (dev->state == STATE_CANCELED) {
|
||||
/* report cancelation to userspace */
|
||||
dev->state = STATE_READY;
|
||||
spin_unlock_irq(&dev->lock);
|
||||
return -ECANCELED;
|
||||
ret = -ECANCELED;
|
||||
goto out;
|
||||
}
|
||||
if (dev->state == STATE_OFFLINE) {
|
||||
spin_unlock_irq(&dev->lock);
|
||||
return -ENODEV;
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
dev->state = STATE_BUSY;
|
||||
spin_unlock_irq(&dev->lock);
|
||||
@@ -858,29 +812,36 @@ static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
|
||||
ret = -EFAULT;
|
||||
goto fail;
|
||||
}
|
||||
/* hold a reference to the file while we are working with it */
|
||||
filp = fget(mfr.fd);
|
||||
if (!filp) {
|
||||
ret = -EBADF;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dev->thread_file = filp;
|
||||
dev->thread_file_offset = mfr.offset;
|
||||
dev->thread_file_length = mfr.length;
|
||||
/* write the parameters */
|
||||
dev->xfer_file = filp;
|
||||
dev->xfer_file_offset = mfr.offset;
|
||||
dev->xfer_file_length = mfr.length;
|
||||
smp_wmb();
|
||||
|
||||
if (code == MTP_SEND_FILE)
|
||||
dev->thread_command = ANDROID_THREAD_SEND_FILE;
|
||||
work = &dev->send_file_work;
|
||||
else
|
||||
dev->thread_command = ANDROID_THREAD_RECEIVE_FILE;
|
||||
work = &dev->receive_file_work;
|
||||
|
||||
/* wake up the thread */
|
||||
init_completion(&dev->thread_wait);
|
||||
wake_up_process(dev->thread);
|
||||
/* We do the file transfer on a work queue so it will run
|
||||
* in kernel context, which is necessary for vfs_read and
|
||||
* vfs_write to use our buffers in the kernel address space.
|
||||
*/
|
||||
queue_work(dev->wq, work);
|
||||
/* wait for operation to complete */
|
||||
flush_workqueue(dev->wq);
|
||||
fput(filp);
|
||||
|
||||
/* wait for the thread to complete the command */
|
||||
wait_for_completion(&dev->thread_wait);
|
||||
ret = dev->thread_result;
|
||||
DBG(dev->cdev, "thread returned %d\n", ret);
|
||||
/* read the result */
|
||||
smp_rmb();
|
||||
ret = dev->xfer_result;
|
||||
break;
|
||||
}
|
||||
case MTP_SET_INTERFACE_MODE:
|
||||
@@ -904,21 +865,22 @@ static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
|
||||
* which would interfere with bulk transfer state.
|
||||
*/
|
||||
if (copy_from_user(&event, (void __user *)value, sizeof(event)))
|
||||
return -EFAULT;
|
||||
ret = -EFAULT;
|
||||
else
|
||||
return mtp_send_event(dev, &event);
|
||||
ret = mtp_send_event(dev, &event);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
fail:
|
||||
if (filp)
|
||||
fput(filp);
|
||||
spin_lock_irq(&dev->lock);
|
||||
if (dev->state == STATE_CANCELED)
|
||||
ret = -ECANCELED;
|
||||
else if (dev->state != STATE_OFFLINE)
|
||||
dev->state = STATE_READY;
|
||||
spin_unlock_irq(&dev->lock);
|
||||
out:
|
||||
_unlock(&dev->ioctl_excl);
|
||||
DBG(dev->cdev, "ioctl returning %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
@@ -929,10 +891,6 @@ static int mtp_open(struct inode *ip, struct file *fp)
|
||||
if (_lock(&_mtp_dev->open_excl))
|
||||
return -EBUSY;
|
||||
|
||||
_mtp_dev->thread = kthread_create(mtp_thread, _mtp_dev, "f_mtp");
|
||||
if (IS_ERR(_mtp_dev->thread))
|
||||
return -ENOMEM;
|
||||
|
||||
/* clear any error condition */
|
||||
if (_mtp_dev->state != STATE_OFFLINE)
|
||||
_mtp_dev->state = STATE_READY;
|
||||
@@ -945,14 +903,6 @@ static int mtp_release(struct inode *ip, struct file *fp)
|
||||
{
|
||||
printk(KERN_INFO "mtp_release\n");
|
||||
|
||||
/* tell the thread to quit */
|
||||
if (_mtp_dev->thread) {
|
||||
_mtp_dev->thread_command = ANDROID_THREAD_QUIT;
|
||||
init_completion(&_mtp_dev->thread_wait);
|
||||
wake_up_process(_mtp_dev->thread);
|
||||
wait_for_completion(&_mtp_dev->thread_wait);
|
||||
}
|
||||
|
||||
_unlock(&_mtp_dev->open_excl);
|
||||
return 0;
|
||||
}
|
||||
@@ -1216,13 +1166,18 @@ static int mtp_bind_config(struct usb_configuration *c)
|
||||
}
|
||||
|
||||
spin_lock_init(&dev->lock);
|
||||
init_completion(&dev->thread_wait);
|
||||
init_waitqueue_head(&dev->read_wq);
|
||||
init_waitqueue_head(&dev->write_wq);
|
||||
init_waitqueue_head(&dev->intr_wq);
|
||||
atomic_set(&dev->open_excl, 0);
|
||||
atomic_set(&dev->ioctl_excl, 0);
|
||||
INIT_LIST_HEAD(&dev->tx_idle);
|
||||
mutex_init(&dev->intr_mutex);
|
||||
|
||||
dev->wq = create_singlethread_workqueue("f_mtp");
|
||||
if (!dev->wq)
|
||||
goto err1;
|
||||
INIT_WORK(&dev->send_file_work, send_file_work);
|
||||
INIT_WORK(&dev->receive_file_work, receive_file_work);
|
||||
|
||||
dev->cdev = c->cdev;
|
||||
dev->function.name = "mtp";
|
||||
@@ -1254,6 +1209,8 @@ static int mtp_bind_config(struct usb_configuration *c)
|
||||
err2:
|
||||
misc_deregister(&mtp_device);
|
||||
err1:
|
||||
if (dev->wq)
|
||||
destroy_workqueue(dev->wq);
|
||||
kfree(dev);
|
||||
printk(KERN_ERR "mtp gadget driver failed to initialize\n");
|
||||
return ret;
|
||||
|
||||
Reference in New Issue
Block a user