USB: gadget: f_mtp: Add support for sending MTP header during file transfer

MTP_SEND_FILE_WITH_HEADER ioctl allows sending a file with the 12 byte header
prepended at the beginning.
This is to allow MTP to use a single packet for the data phase instead of two.

Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/drivers/usb/gadget/f_mtp.c b/drivers/usb/gadget/f_mtp.c
index 7723946..227cc56 100644
--- a/drivers/usb/gadget/f_mtp.c
+++ b/drivers/usb/gadget/f_mtp.c
@@ -94,8 +94,8 @@
 	struct usb_request *rx_req[RX_REQ_MAX];
 	int rx_done;
 
-	/* for processing MTP_SEND_FILE and MTP_RECEIVE_FILE
-	 * ioctls on a work queue
+	/* for processing MTP_SEND_FILE, MTP_RECEIVE_FILE and
+	 * MTP_SEND_FILE_WITH_HEADER ioctls on a work queue
 	 */
 	struct workqueue_struct *wq;
 	struct work_struct send_file_work;
@@ -103,6 +103,9 @@
 	struct file *xfer_file;
 	loff_t xfer_file_offset;
 	int64_t xfer_file_length;
+	unsigned xfer_send_header;
+	uint16_t xfer_command;
+	uint32_t xfer_transaction_id;
 	int xfer_result;
 };
 
@@ -629,10 +632,11 @@
 	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;
+	struct mtp_data_header *header;
 	struct file *filp;
 	loff_t offset;
 	int64_t count;
-	int xfer, ret;
+	int xfer, ret, hdr_size;
 	int r = 0;
 	int sendZLP = 0;
 
@@ -644,10 +648,17 @@
 
 	DBG(cdev, "send_file_work(%lld %lld)\n", offset, count);
 
+	if (dev->xfer_send_header) {
+		hdr_size = sizeof(struct mtp_data_header);
+		count += hdr_size;
+	} else {
+		hdr_size = 0;
+	}
+
 	/* we need to send a zero length packet to signal the end of transfer
 	 * if the transfer size is aligned to a packet boundary.
 	 */
-	if ((dev->xfer_file_length & (dev->ep_in->maxpacket - 1)) == 0) {
+	if ((count & (dev->ep_in->maxpacket - 1)) == 0) {
 		sendZLP = 1;
 	}
 
@@ -674,12 +685,23 @@
 			xfer = MTP_BULK_BUFFER_SIZE;
 		else
 			xfer = count;
-		ret = vfs_read(filp, req->buf, xfer, &offset);
+
+		if (hdr_size) {
+			/* prepend MTP data header */
+			header = (struct mtp_data_header *)req->buf;
+			header->length = __cpu_to_le32(count);
+			header->type = __cpu_to_le16(2); /* data packet */
+			header->command = __cpu_to_le16(dev->xfer_command);
+			header->transaction_id = __cpu_to_le32(dev->xfer_transaction_id);
+		}
+
+		ret = vfs_read(filp, req->buf + hdr_size, xfer - hdr_size, &offset);
 		if (ret < 0) {
 			r = ret;
 			break;
 		}
-		xfer = ret;
+		xfer = ret + hdr_size;
+		hdr_size = 0;
 
 		req->length = xfer;
 		ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
@@ -829,6 +851,7 @@
 	switch (code) {
 	case MTP_SEND_FILE:
 	case MTP_RECEIVE_FILE:
+	case MTP_SEND_FILE_WITH_HEADER:
 	{
 		struct mtp_file_range	mfr;
 		struct work_struct *work;
@@ -866,10 +889,17 @@
 		dev->xfer_file_length = mfr.length;
 		smp_wmb();
 
-		if (code == MTP_SEND_FILE)
+		if (code == MTP_SEND_FILE_WITH_HEADER) {
 			work = &dev->send_file_work;
-		else
+			dev->xfer_send_header = 1;
+			dev->xfer_command = mfr.command;
+			dev->xfer_transaction_id = mfr.transaction_id;
+		} else if (code == MTP_SEND_FILE) {
+			work = &dev->send_file_work;
+			dev->xfer_send_header = 0;
+		} else {
 			work = &dev->receive_file_work;
+		}
 
 		/* We do the file transfer on a work queue so it will run
 		 * in kernel context, which is necessary for vfs_read and
diff --git a/include/linux/usb/f_mtp.h b/include/linux/usb/f_mtp.h
index fdf828c..7422b17 100644
--- a/include/linux/usb/f_mtp.h
+++ b/include/linux/usb/f_mtp.h
@@ -18,6 +18,22 @@
 #ifndef __LINUX_USB_F_MTP_H
 #define __LINUX_USB_F_MTP_H
 
+#include <linux/ioctl.h>
+
+#ifdef __KERNEL__
+
+struct mtp_data_header {
+	/* length of packet, including this header */
+	uint32_t	length;
+	/* container type (2 for data packet) */
+	uint16_t	type;
+	/* MTP command code */
+	uint16_t    command;
+	/* MTP transaction ID */
+	uint32_t	transaction_id;
+};
+
+#endif /* __KERNEL__ */
 
 struct mtp_file_range {
 	/* file descriptor for file to transfer */
@@ -26,6 +42,14 @@
 	loff_t  	offset;
 	/* number of bytes to transfer */
 	int64_t		length;
+	/* MTP command ID for data header,
+	 * used only for MTP_SEND_FILE_WITH_HEADER
+	 */
+	uint16_t	command;
+	/* MTP transaction ID for data header,
+	 * used only for MTP_SEND_FILE_WITH_HEADER
+	 */
+	uint32_t	transaction_id;
 };
 
 struct mtp_event {
@@ -43,5 +67,9 @@
 #define MTP_RECEIVE_FILE           _IOW('M', 1, struct mtp_file_range)
 /* Sends an event to the host via the interrupt endpoint */
 #define MTP_SEND_EVENT             _IOW('M', 3, struct mtp_event)
+/* Sends the specified file range to the host,
+ * with a 12 byte MTP data packet header at the beginning.
+ */
+#define MTP_SEND_FILE_WITH_HEADER  _IOW('M', 4, struct mtp_file_range)
 
 #endif /* __LINUX_USB_F_MTP_H */