From 3482be62e48ddd2182873b25989270eb9088a65f Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Tue, 16 Nov 2010 17:14:32 -0500 Subject: [PATCH] USB: gadget: f_mtp: Support for file transfer length greater than 4 gigabytes For backward compatibility with PTP, MTP is limited to a 32-bit file size. When transferring files greater than 4 gig, MTP uses 0xFFFFFFFF as the file size and the receiver reads until it receives a short packet. Expanded size of mtp_file_range.length to 64 bits and added support for writing zero length packets. Signed-off-by: Mike Lockwood --- drivers/usb/gadget/f_mtp.c | 46 +++++++++++++++++++++++++++++--------- include/linux/usb/f_mtp.h | 2 +- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c index 81cc01868397..03ea4a7066ca 100644 --- a/drivers/usb/gadget/f_mtp.c +++ b/drivers/usb/gadget/f_mtp.c @@ -107,7 +107,7 @@ struct mtp_dev { struct work_struct receive_file_work; struct file *xfer_file; loff_t xfer_file_offset; - size_t xfer_file_length; + int64_t xfer_file_length; int xfer_result; }; @@ -558,7 +558,8 @@ static ssize_t mtp_write(struct file *fp, const char __user *buf, dev->state = STATE_BUSY; spin_unlock_irq(&dev->lock); - while (count > 0) { + /* condition check at bottom to allow zero length packet write */ + do { if (dev->state != STATE_BUSY) { DBG(cdev, "mtp_write dev->error\n"); r = -EIO; @@ -579,7 +580,7 @@ static ssize_t mtp_write(struct file *fp, const char __user *buf, xfer = BULK_BUFFER_SIZE; else xfer = count; - if (copy_from_user(req->buf, buf, xfer)) { + if (xfer && copy_from_user(req->buf, buf, xfer)) { r = -EFAULT; break; } @@ -597,7 +598,7 @@ static ssize_t mtp_write(struct file *fp, const char __user *buf, /* zero this so we don't try to free it on error exit */ req = 0; - } + } while (count > 0); if (req) req_put(dev, &dev->tx_idle, req); @@ -620,9 +621,10 @@ static void send_file_work(struct work_struct *data) { struct usb_request *req = 0; struct file *filp; loff_t offset; - size_t count; + int64_t count; int xfer, ret; int r = 0; + int sendZLP = 0; /* read our parameters */ smp_rmb(); @@ -630,9 +632,22 @@ static void send_file_work(struct work_struct *data) { offset = dev->xfer_file_offset; count = dev->xfer_file_length; - DBG(cdev, "send_file_work(%lld %u)\n", offset, count); + DBG(cdev, "send_file_work(%lld %lld)\n", offset, count); + + /* we need to send a zero length packet to signal the end of transfer + * if the length is > 4 gig and the last packet is aligned to a + * packet boundary. + */ + if (dev->xfer_file_length >= 0xFFFFFFFF + && (dev->xfer_file_length & (dev->ep_in->maxpacket - 1)) == 0) { + sendZLP = 1; + } + + while (count > 0 || sendZLP) { + /* so we exit after sending ZLP */ + if (count == 0) + sendZLP = 0; - while (count > 0) { /* get an idle tx request to use */ req = 0; ret = wait_event_interruptible(dev->write_wq, @@ -686,7 +701,7 @@ static void receive_file_work(struct work_struct *data) struct usb_request *read_req = NULL, *write_req = NULL; struct file *filp; loff_t offset; - size_t count; + int64_t count; int ret, cur_buf = 0; int r = 0; @@ -696,7 +711,7 @@ static void receive_file_work(struct work_struct *data) offset = dev->xfer_file_offset; count = dev->xfer_file_length; - DBG(cdev, "receive_file_work(%u)\n", count); + DBG(cdev, "receive_file_work(%lld)\n", count); while (count > 0 || write_req) { if (count > 0) { @@ -713,7 +728,6 @@ static void receive_file_work(struct work_struct *data) dev->state = STATE_ERROR; break; } - count -= ret; } if (write_req) { @@ -737,7 +751,17 @@ static void receive_file_work(struct work_struct *data) r = ret; break; } - count -= read_req->actual; + /* if xfer_file_length is 0xFFFFFFFF, then we read until + * we get a zero length packet + */ + if (count != 0xFFFFFFFF) + count -= read_req->actual; + if (read_req->actual < read_req->length) { + /* short packet is used to signal EOF for sizes > 4 gig */ + DBG(cdev, "got short packet\n"); + count = 0; + } + write_req = read_req; read_req = NULL; } diff --git a/include/linux/usb/f_mtp.h b/include/linux/usb/f_mtp.h index 426c6b5cc06a..e4fd88066389 100644 --- a/include/linux/usb/f_mtp.h +++ b/include/linux/usb/f_mtp.h @@ -29,7 +29,7 @@ struct mtp_file_range { /* offset in file for start of transfer */ loff_t offset; /* number of bytes to transfer */ - size_t length; + int64_t length; }; struct mtp_event { -- 2.34.1