RFC on Android's ConfigFS based MTP function implementation and usage
of Uevents to notify the userspace about the USB state changes.
The MTP function is based on years of work originally done in the
Android kernel tree by:
Mike Lockwood <[email protected]>
Benoit Goby <[email protected]>
Colin Cross <[email protected]>
Arve Hjønnevåg <[email protected]>
Peter Oh <[email protected]>
Greg Hackmann <[email protected]>
Badhri Jagan Sridharan <[email protected]>
The Uevent notification patch is more of an RFC than an actual
submission. It is based on Android patchset originaly authored by
Badhri to send uevent notifications to Android userpace for USB
state changes.
I've folded the series up to make it easier to review, provided a
coherent patch description and modified it enough that I don't want
them to be blamed for any mistakes I've made condensing their patches
down.
Thoughts and feedback would be appreciated.
Thanks,
Amit Pundir
Cc: Mike Lockwood <[email protected]>
Cc: Benoit Goby <[email protected]>
Cc: Colin Cross <[email protected]>
Cc: Arve Hjønnevåg <[email protected]>
Cc: Peter Oh <[email protected]>
Cc: Greg Hackmann <[email protected]>
Cc: Badhri Jagan Sridharan <[email protected]>
Cc: Android Kernel Team <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: Felipe Balbi <[email protected]>
Cc: Andrzej Pietrasiewicz <[email protected]>
Cc: Laurent Pinchart <[email protected]>
Cc: Yegor Yefremov <[email protected]>
Cc: Philippe Reynes <[email protected]>
Cc: John Stultz <[email protected]>
Cc: Sumit Semwal <[email protected]>
Amit Pundir (2):
usb: gadget: configfs: add MTP function
usb: gadget: configfs: notify userspace of usb state changes
Documentation/ABI/testing/configfs-usb-gadget-mtp | 7 +
Documentation/usb/gadget-testing.txt | 24 +
drivers/usb/gadget/Kconfig | 21 +
drivers/usb/gadget/configfs.c | 201 ++-
drivers/usb/gadget/function/Makefile | 2 +
drivers/usb/gadget/function/f_mtp.c | 1365 +++++++++++++++++++++
include/linux/usb/f_mtp.h | 23 +
include/uapi/linux/usb/f_mtp.h | 61 +
8 files changed, 1702 insertions(+), 2 deletions(-)
create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-mtp
create mode 100644 drivers/usb/gadget/function/f_mtp.c
create mode 100644 include/linux/usb/f_mtp.h
create mode 100644 include/uapi/linux/usb/f_mtp.h
--
1.9.1
This MTP function is based on years of work originally done in the
Android kernel tree by:
Mike Lockwood <[email protected]>
Benoit Goby <[email protected]>
Colin Cross <[email protected]>
Arve Hjønnevåg <[email protected]>
Peter Oh <[email protected]>
Greg Hackmann <[email protected]>
Badhri Jagan Sridharan <[email protected]>
I've folded the series up to make it easier to review, and to provide
a coherent patch description.
Post Gingerbread (Android v2.3), Android dropped USB Mass Storage
in favor of Media Transfer Protocal (MTP), which is widely used for
transferring media files to digital music players and similar
applications. This USB gadget function implements MTP functionalty.
Historically this function has been a part of Android composite
gadget driver. Android composite driver was Android's solution
for dynamic gadget function switching prior to the ConfigFS gadget
being merged. There were failed few attempts in past
http://marc.info/?l=linux-usb&m=132451695808552 to upstream Android
composite driver as well. Now this Android MTP gadget function has been
re-implemented so as to be used as a generic ConfigFS function instead.
Again, many thanks to Mike, Benoit, Colin, Arve, Peter, Greg and Badhri,
as they are the real authors of this work. However, I've folded their
patches together and modified it enough that I don't want them to be
blamed for any mistakes I've made condensing their patches down.
Cc: Mike Lockwood <[email protected]>
Cc: Benoit Goby <[email protected]>
Cc: Colin Cross <[email protected]>
Cc: Arve Hjønnevåg <[email protected]>
Cc: Peter Oh <[email protected]>
Cc: Greg Hackmann <[email protected]>
Cc: Badhri Jagan Sridharan <[email protected]>
Cc: Android Kernel Team <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: Felipe Balbi <[email protected]>
Cc: Andrzej Pietrasiewicz <[email protected]>
Cc: Laurent Pinchart <[email protected]>
Cc: Yegor Yefremov <[email protected]>
Cc: Philippe Reynes <[email protected]>
Cc: John Stultz <[email protected]>
Cc: Sumit Semwal <[email protected]>
Signed-off-by: Amit Pundir <[email protected]>
---
Documentation/ABI/testing/configfs-usb-gadget-mtp | 7 +
Documentation/usb/gadget-testing.txt | 24 +
drivers/usb/gadget/Kconfig | 13 +
drivers/usb/gadget/function/Makefile | 2 +
drivers/usb/gadget/function/f_mtp.c | 1365 +++++++++++++++++++++
include/linux/usb/f_mtp.h | 23 +
include/uapi/linux/usb/f_mtp.h | 61 +
7 files changed, 1495 insertions(+)
create mode 100644 Documentation/ABI/testing/configfs-usb-gadget-mtp
create mode 100644 drivers/usb/gadget/function/f_mtp.c
create mode 100644 include/linux/usb/f_mtp.h
create mode 100644 include/uapi/linux/usb/f_mtp.h
diff --git a/Documentation/ABI/testing/configfs-usb-gadget-mtp b/Documentation/ABI/testing/configfs-usb-gadget-mtp
new file mode 100644
index 0000000..6738bee
--- /dev/null
+++ b/Documentation/ABI/testing/configfs-usb-gadget-mtp
@@ -0,0 +1,7 @@
+What: /config/usb-gadget/gadget/functions/mtp.name
+Date: Aug 2015
+KernelVersion: 4.2
+Description: The purpose of this directory is to create and remove it.
+
+ A corresponding USB function instance is created/removed.
+ There are no attributes here.
diff --git a/Documentation/usb/gadget-testing.txt b/Documentation/usb/gadget-testing.txt
index 5926780..7fb8494d4 100644
--- a/Documentation/usb/gadget-testing.txt
+++ b/Documentation/usb/gadget-testing.txt
@@ -20,6 +20,7 @@ provided by gadgets.
17. UAC2 function
18. UVC function
19. PRINTER function
+20. MTP function
1. ACM function
@@ -771,3 +772,26 @@ host:
More advanced testing can be done with the prn_example
described in Documentation/usb/gadget-printer.txt.
+
+20. MTP function
+===============
+
+The function is provided by usb_f_mtp.ko module.
+
+Function-specific configfs interface
+------------------------------------
+
+The function name to use when creating the function directory is "mtp".
+The function directory is intentionally empty and has no attributes as such.
+
+After creating the mtp function directory, link mtp function with the gadget
+configuration by creating symbolic link, enable the gadget by writing a
+suitable string to usb_gadget/<gadget>/UDC and start the mtp userspace daemon.
+
+Testing the mtp function
+------------------------
+
+On the device: enable the gadget, and start the mtp userspace daemon.
+On the host: if configured correctly the media storage device should auto-mount
+itself, or use Linux mtp-tools package to browse/transfer the media content
+instead.
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index bcf83c0..65d110d 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -199,6 +199,9 @@ config USB_F_HID
config USB_F_PRINTER
tristate
+config USB_F_MTP
+ tristate
+
choice
tristate "USB Gadget Drivers"
default USB_ETH
@@ -451,6 +454,16 @@ config USB_CONFIGFS_F_PRINTER
For more information, see Documentation/usb/gadget_printer.txt
which includes sample code for accessing the device file.
+config USB_CONFIGFS_F_MTP
+ bool "MTP gadget"
+ depends on USB_CONFIGFS
+ select USB_F_MTP
+ help
+ The Media Transfer Protocol (MTP) function mounts USB gadget as
+ a media device but unlike Mass Storage Gadget, MTP operates at
+ the file level. Thus exposing the relevant content but hiding
+ the system/restricted files.
+
source "drivers/usb/gadget/legacy/Kconfig"
endchoice
diff --git a/drivers/usb/gadget/function/Makefile b/drivers/usb/gadget/function/Makefile
index bd7def5..11ef10f 100644
--- a/drivers/usb/gadget/function/Makefile
+++ b/drivers/usb/gadget/function/Makefile
@@ -44,3 +44,5 @@ usb_f_hid-y := f_hid.o
obj-$(CONFIG_USB_F_HID) += usb_f_hid.o
usb_f_printer-y := f_printer.o
obj-$(CONFIG_USB_F_PRINTER) += usb_f_printer.o
+usb_f_mtp-y := f_mtp.o
+obj-$(CONFIG_USB_F_MTP) += usb_f_mtp.o
diff --git a/drivers/usb/gadget/function/f_mtp.c b/drivers/usb/gadget/function/f_mtp.c
new file mode 100644
index 0000000..3eedece
--- /dev/null
+++ b/drivers/usb/gadget/function/f_mtp.c
@@ -0,0 +1,1365 @@
+/*
+ * Gadget Function Driver for MTP
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Mike Lockwood <[email protected]>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/* #define DEBUG */
+/* #define VERBOSE_DEBUG */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/delay.h>
+#include <linux/wait.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+
+#include <linux/types.h>
+#include <linux/file.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+
+#include <linux/usb.h>
+#include <linux/usb_usual.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/f_mtp.h>
+#include <linux/configfs.h>
+#include <linux/usb/composite.h>
+
+#include "configfs.h"
+
+#define MTP_BULK_BUFFER_SIZE 16384
+#define INTR_BUFFER_SIZE 28
+#define MAX_INST_NAME_LEN 40
+
+/* String IDs */
+#define INTERFACE_STRING_INDEX 0
+
+/* values for mtp_dev.state */
+#define STATE_OFFLINE 0 /* initial state, disconnected */
+#define STATE_READY 1 /* ready for userspace calls */
+#define STATE_BUSY 2 /* processing userspace calls */
+#define STATE_CANCELED 3 /* transaction canceled by host */
+#define STATE_ERROR 4 /* error from completion routine */
+
+/* number of tx and rx requests to allocate */
+#define TX_REQ_MAX 4
+#define RX_REQ_MAX 2
+#define INTR_REQ_MAX 5
+
+/* ID for Microsoft MTP OS String */
+#define MTP_OS_STRING_ID 0xEE
+
+/* MTP class reqeusts */
+#define MTP_REQ_CANCEL 0x64
+#define MTP_REQ_GET_EXT_EVENT_DATA 0x65
+#define MTP_REQ_RESET 0x66
+#define MTP_REQ_GET_DEVICE_STATUS 0x67
+
+/* constants for device status */
+#define MTP_RESPONSE_OK 0x2001
+#define MTP_RESPONSE_DEVICE_BUSY 0x2019
+#define DRIVER_NAME "mtp"
+
+static const char mtp_shortname[] = DRIVER_NAME "_usb";
+
+struct mtp_dev {
+ struct usb_function function;
+ struct usb_composite_dev *cdev;
+ spinlock_t lock;
+
+ struct usb_ep *ep_in;
+ struct usb_ep *ep_out;
+ struct usb_ep *ep_intr;
+
+ int state;
+
+ /* 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;
+ struct list_head intr_idle;
+
+ wait_queue_head_t read_wq;
+ wait_queue_head_t write_wq;
+ wait_queue_head_t intr_wq;
+ struct usb_request *rx_req[RX_REQ_MAX];
+ int rx_done;
+
+ /* 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;
+ struct work_struct receive_file_work;
+ 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;
+};
+
+static struct usb_interface_descriptor mtp_interface_desc = {
+ .bLength = USB_DT_INTERFACE_SIZE,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bInterfaceNumber = 0,
+ .bNumEndpoints = 3,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .bInterfaceSubClass = USB_SUBCLASS_VENDOR_SPEC,
+ .bInterfaceProtocol = 0,
+};
+
+static struct usb_endpoint_descriptor mtp_highspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mtp_highspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+};
+
+static struct usb_endpoint_descriptor mtp_fullspeed_in_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_fullspeed_out_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+};
+
+static struct usb_endpoint_descriptor mtp_intr_desc = {
+ .bLength = USB_DT_ENDPOINT_SIZE,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_INT,
+ .wMaxPacketSize = cpu_to_le16(INTR_BUFFER_SIZE),
+ .bInterval = 6,
+};
+
+static struct usb_descriptor_header *fs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_fullspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_descriptor_header *hs_mtp_descs[] = {
+ (struct usb_descriptor_header *) &mtp_interface_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_in_desc,
+ (struct usb_descriptor_header *) &mtp_highspeed_out_desc,
+ (struct usb_descriptor_header *) &mtp_intr_desc,
+ NULL,
+};
+
+static struct usb_string mtp_string_defs[] = {
+ /* Naming interface "MTP" so libmtp will recognize us */
+ [INTERFACE_STRING_INDEX].s = "MTP",
+ { }, /* end of list */
+};
+
+static struct usb_gadget_strings mtp_string_table = {
+ .language = 0x0409, /* en-US */
+ .strings = mtp_string_defs,
+};
+
+static struct usb_gadget_strings *mtp_strings[] = {
+ &mtp_string_table,
+ NULL,
+};
+
+/* Microsoft MTP OS String */
+static u8 mtp_os_string[] = {
+ 18, /* sizeof(mtp_os_string) */
+ USB_DT_STRING,
+ /* Signature field: "MSFT100" */
+ 'M', 0, 'S', 0, 'F', 0, 'T', 0, '1', 0, '0', 0, '0', 0,
+ /* vendor code */
+ 1,
+ /* padding */
+ 0
+};
+
+/* Microsoft Extended Configuration Descriptor Header Section */
+struct mtp_ext_config_desc_header {
+ __le32 dwLength;
+ __u16 bcdVersion;
+ __le16 wIndex;
+ __u8 bCount;
+ __u8 reserved[7];
+};
+
+/* Microsoft Extended Configuration Descriptor Function Section */
+struct mtp_ext_config_desc_function {
+ __u8 bFirstInterfaceNumber;
+ __u8 bInterfaceCount;
+ __u8 compatibleID[8];
+ __u8 subCompatibleID[8];
+ __u8 reserved[6];
+};
+
+/* MTP Extended Configuration Descriptor */
+struct {
+ struct mtp_ext_config_desc_header header;
+ struct mtp_ext_config_desc_function function;
+} mtp_ext_config_desc = {
+ .header = {
+ .dwLength = cpu_to_le32(sizeof(mtp_ext_config_desc)),
+ .bcdVersion = cpu_to_le16(0x0100),
+ .wIndex = cpu_to_le16(4),
+ .bCount = cpu_to_le16(1),
+ },
+ .function = {
+ .bFirstInterfaceNumber = 0,
+ .bInterfaceCount = 1,
+ .compatibleID = { 'M', 'T', 'P' },
+ },
+};
+
+struct mtp_device_status {
+ __le16 wLength;
+ __le16 wCode;
+};
+
+struct mtp_data_header {
+ /* length of packet, including this header */
+ __le32 length;
+ /* container type (2 for data packet) */
+ __le16 type;
+ /* MTP command code */
+ __le16 command;
+ /* MTP transaction ID */
+ __le32 transaction_id;
+};
+
+struct mtp_instance {
+ struct usb_function_instance func_inst;
+ const char *name;
+ struct mtp_dev *dev;
+};
+
+/* temporary variable used between mtp_open() and mtp_gadget_bind() */
+static struct mtp_dev *_mtp_dev;
+
+static inline struct mtp_dev *func_to_mtp(struct usb_function *f)
+{
+ return container_of(f, struct mtp_dev, function);
+}
+
+static struct usb_request *mtp_request_new(struct usb_ep *ep, int buffer_size)
+{
+ struct usb_request *req = usb_ep_alloc_request(ep, GFP_KERNEL);
+
+ if (!req)
+ return NULL;
+ /* now allocate buffers for the requests */
+ req->buf = kmalloc(buffer_size, GFP_KERNEL);
+ if (!req->buf) {
+ usb_ep_free_request(ep, req);
+ return NULL;
+ }
+
+ return req;
+}
+
+static void mtp_request_free(struct usb_request *req, struct usb_ep *ep)
+{
+ if (req) {
+ kfree(req->buf);
+ usb_ep_free_request(ep, req);
+ }
+}
+
+static inline int mtp_lock(atomic_t *excl)
+{
+ if (atomic_inc_return(excl) == 1) {
+ return 0;
+ } else {
+ atomic_dec(excl);
+ return -1;
+ }
+}
+
+static inline void mtp_unlock(atomic_t *excl)
+{
+ atomic_dec(excl);
+}
+
+/* add a request to the tail of a list */
+static void mtp_req_put(struct mtp_dev *dev, struct list_head *head,
+ struct usb_request *req)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ list_add_tail(&req->list, head);
+ spin_unlock_irqrestore(&dev->lock, flags);
+}
+
+/* remove a request from the head of a list */
+static struct usb_request
+*mtp_req_get(struct mtp_dev *dev, struct list_head *head)
+{
+ unsigned long flags;
+ struct usb_request *req;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (list_empty(head)) {
+ req = 0;
+ } else {
+ req = list_first_entry(head, struct usb_request, list);
+ list_del(&req->list);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return req;
+}
+
+static void mtp_complete_in(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ mtp_req_put(dev, &dev->tx_idle, req);
+
+ wake_up(&dev->write_wq);
+}
+
+static void mtp_complete_out(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ dev->rx_done = 1;
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ wake_up(&dev->read_wq);
+}
+
+static void mtp_complete_intr(struct usb_ep *ep, struct usb_request *req)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (req->status != 0)
+ dev->state = STATE_ERROR;
+
+ mtp_req_put(dev, &dev->intr_idle, req);
+
+ wake_up(&dev->intr_wq);
+}
+
+static int mtp_create_bulk_endpoints(struct mtp_dev *dev,
+ struct usb_endpoint_descriptor *in_desc,
+ struct usb_endpoint_descriptor *out_desc,
+ struct usb_endpoint_descriptor *intr_desc)
+{
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ struct usb_ep *ep;
+ int i;
+
+ DBG(cdev, "create_bulk_endpoints dev: %p\n", dev);
+
+ ep = usb_ep_autoconfig(cdev->gadget, in_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_in failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for ep_in got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_in = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, out_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_out failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_out got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_out = ep;
+
+ ep = usb_ep_autoconfig(cdev->gadget, intr_desc);
+ if (!ep) {
+ DBG(cdev, "usb_ep_autoconfig for ep_intr failed\n");
+ return -ENODEV;
+ }
+ DBG(cdev, "usb_ep_autoconfig for mtp ep_intr got %s\n", ep->name);
+ ep->driver_data = dev; /* claim the endpoint */
+ dev->ep_intr = ep;
+
+ /* now allocate requests for our endpoints */
+ for (i = 0; i < TX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_in, MTP_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_in;
+ mtp_req_put(dev, &dev->tx_idle, req);
+ }
+ for (i = 0; i < RX_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_out, MTP_BULK_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_out;
+ dev->rx_req[i] = req;
+ }
+ for (i = 0; i < INTR_REQ_MAX; i++) {
+ req = mtp_request_new(dev->ep_intr, INTR_BUFFER_SIZE);
+ if (!req)
+ goto fail;
+ req->complete = mtp_complete_intr;
+ mtp_req_put(dev, &dev->intr_idle, req);
+ }
+
+ return 0;
+
+fail:
+ pr_err("mtp_bind() could not allocate requests\n");
+ return -1;
+}
+
+static ssize_t mtp_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req;
+ ssize_t r = count;
+ unsigned xfer;
+ int ret = 0;
+
+ DBG(cdev, "mtp_read(%zu)\n", count);
+
+ if (count > MTP_BULK_BUFFER_SIZE)
+ return -EINVAL;
+
+ /* we will block until we're online */
+ DBG(cdev, "mtp_read: waiting for online state\n");
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->state != STATE_OFFLINE);
+ if (ret < 0) {
+ r = ret;
+ goto done;
+ }
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancellation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+requeue_req:
+ /* queue a request */
+ req = dev->rx_req[0];
+ req->length = count;
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ goto done;
+ } else {
+ DBG(cdev, "rx %p queue\n", req);
+ }
+
+ /* wait for a request to complete */
+ ret = wait_event_interruptible(dev->read_wq, dev->rx_done);
+ if (ret < 0) {
+ r = ret;
+ usb_ep_dequeue(dev->ep_out, req);
+ goto done;
+ }
+ if (dev->state == STATE_BUSY) {
+ /* If we got a 0-len packet, throw it back and try again. */
+ if (req->actual == 0)
+ goto requeue_req;
+
+ DBG(cdev, "rx %p %d\n", req, req->actual);
+ xfer = (req->actual < count) ? req->actual : count;
+ r = xfer;
+ if (copy_to_user(buf, req->buf, xfer))
+ r = -EFAULT;
+ } else
+ r = -EIO;
+
+done:
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_read returning %zd\n", r);
+ return r;
+}
+
+static ssize_t mtp_write(struct file *fp, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct usb_composite_dev *cdev = dev->cdev;
+ struct usb_request *req = 0;
+ ssize_t r = count;
+ unsigned xfer;
+ int sendZLP = 0;
+ int ret;
+
+ DBG(cdev, "mtp_write(%zu)\n", count);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancellation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ return -ECANCELED;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ return -ENODEV;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ /* 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 ((count & (dev->ep_in->maxpacket - 1)) == 0)
+ sendZLP = 1;
+
+ while (count > 0 || sendZLP) {
+ /* so we exit after sending ZLP */
+ if (count == 0)
+ sendZLP = 0;
+
+ if (dev->state != STATE_BUSY) {
+ DBG(cdev, "mtp_write dev->error\n");
+ r = -EIO;
+ break;
+ }
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ ((req = mtp_req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY));
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > MTP_BULK_BUFFER_SIZE)
+ xfer = MTP_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+ if (xfer && copy_from_user(req->buf, buf, xfer)) {
+ r = -EFAULT;
+ break;
+ }
+
+ 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);
+ r = -EIO;
+ break;
+ }
+
+ buf += xfer;
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ mtp_req_put(dev, &dev->tx_idle, req);
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED)
+ r = -ECANCELED;
+ else if (dev->state != STATE_OFFLINE)
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+
+ DBG(cdev, "mtp_write returning %zd\n", r);
+ return r;
+}
+
+/* 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;
+ struct mtp_data_header *header;
+ struct file *filp;
+ loff_t offset;
+ int64_t count;
+ int xfer, ret, hdr_size;
+ int r = 0;
+ int sendZLP = 0;
+
+ /* read our parameters */
+ smp_rmb();
+ filp = dev->xfer_file;
+ offset = dev->xfer_file_offset;
+ count = dev->xfer_file_length;
+
+ 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 ((count & (dev->ep_in->maxpacket - 1)) == 0)
+ sendZLP = 1;
+
+ while (count > 0 || sendZLP) {
+ /* so we exit after sending ZLP */
+ if (count == 0)
+ sendZLP = 0;
+
+ /* get an idle tx request to use */
+ req = 0;
+ ret = wait_event_interruptible(dev->write_wq,
+ (req = mtp_req_get(dev, &dev->tx_idle))
+ || dev->state != STATE_BUSY);
+ if (dev->state == STATE_CANCELED) {
+ r = -ECANCELED;
+ break;
+ }
+ if (!req) {
+ r = ret;
+ break;
+ }
+
+ if (count > MTP_BULK_BUFFER_SIZE)
+ xfer = MTP_BULK_BUFFER_SIZE;
+ else
+ xfer = count;
+
+ 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 + hdr_size;
+ hdr_size = 0;
+
+ req->length = xfer;
+ ret = usb_ep_queue(dev->ep_in, req, GFP_KERNEL);
+ if (ret < 0) {
+ DBG(cdev, "send_file_work: xfer error %d\n", ret);
+ dev->state = STATE_ERROR;
+ r = -EIO;
+ break;
+ }
+
+ count -= xfer;
+
+ /* zero this so we don't try to free it on error exit */
+ req = 0;
+ }
+
+ if (req)
+ mtp_req_put(dev, &dev->tx_idle, req);
+
+ DBG(cdev, "send_file_work returning %d\n", r);
+ /* write the result */
+ dev->xfer_result = r;
+ smp_wmb();
+}
+
+/* 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;
+ struct file *filp;
+ loff_t offset;
+ int64_t count;
+ int ret, cur_buf = 0;
+ int r = 0;
+
+ /* read our parameters */
+ smp_rmb();
+ filp = dev->xfer_file;
+ offset = dev->xfer_file_offset;
+ count = dev->xfer_file_length;
+
+ DBG(cdev, "receive_file_work(%lld)\n", count);
+
+ while (count > 0 || write_req) {
+ if (count > 0) {
+ /* queue a request */
+ read_req = dev->rx_req[cur_buf];
+ cur_buf = (cur_buf + 1) % RX_REQ_MAX;
+
+ read_req->length = (count > MTP_BULK_BUFFER_SIZE
+ ? MTP_BULK_BUFFER_SIZE : count);
+ dev->rx_done = 0;
+ ret = usb_ep_queue(dev->ep_out, read_req, GFP_KERNEL);
+ if (ret < 0) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ }
+
+ if (write_req) {
+ DBG(cdev, "rx %p %d\n", write_req, write_req->actual);
+ ret = vfs_write(filp, write_req->buf, write_req->actual,
+ &offset);
+ DBG(cdev, "vfs_write %d\n", ret);
+ if (ret != write_req->actual) {
+ r = -EIO;
+ dev->state = STATE_ERROR;
+ break;
+ }
+ write_req = NULL;
+ }
+
+ if (read_req) {
+ /* wait for our last read to complete */
+ ret = wait_event_interruptible(dev->read_wq,
+ dev->rx_done || dev->state != STATE_BUSY);
+ if (dev->state == STATE_CANCELED) {
+ r = -ECANCELED;
+ if (!dev->rx_done)
+ usb_ep_dequeue(dev->ep_out, read_req);
+ break;
+ }
+ /* 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;
+ }
+ }
+
+ 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)
+{
+ struct usb_request *req = NULL;
+ int ret;
+ int length = event->length;
+
+ DBG(dev->cdev, "mtp_send_event(%zu)\n", event->length);
+
+ if (length < 0 || length > INTR_BUFFER_SIZE)
+ return -EINVAL;
+ if (dev->state == STATE_OFFLINE)
+ return -ENODEV;
+
+ ret = wait_event_interruptible_timeout(dev->intr_wq,
+ (req = mtp_req_get(dev, &dev->intr_idle)),
+ msecs_to_jiffies(1000));
+ if (!req)
+ return -ETIME;
+
+ if (copy_from_user(req->buf, (void __user *)event->data, length)) {
+ mtp_req_put(dev, &dev->intr_idle, req);
+ return -EFAULT;
+ }
+ req->length = length;
+ ret = usb_ep_queue(dev->ep_intr, req, GFP_KERNEL);
+ if (ret)
+ mtp_req_put(dev, &dev->intr_idle, req);
+
+ return ret;
+}
+
+static long mtp_ioctl(struct file *fp, unsigned code, unsigned long value)
+{
+ struct mtp_dev *dev = fp->private_data;
+ struct file *filp = NULL;
+ int ret = -EINVAL;
+
+ if (mtp_lock(&dev->ioctl_excl))
+ return -EBUSY;
+
+ 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;
+
+ spin_lock_irq(&dev->lock);
+ if (dev->state == STATE_CANCELED) {
+ /* report cancellation to userspace */
+ dev->state = STATE_READY;
+ spin_unlock_irq(&dev->lock);
+ ret = -ECANCELED;
+ goto out;
+ }
+ if (dev->state == STATE_OFFLINE) {
+ spin_unlock_irq(&dev->lock);
+ ret = -ENODEV;
+ goto out;
+ }
+ dev->state = STATE_BUSY;
+ spin_unlock_irq(&dev->lock);
+
+ if (copy_from_user(&mfr, (void __user *)value, sizeof(mfr))) {
+ 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;
+ }
+
+ /* 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_WITH_HEADER) {
+ work = &dev->send_file_work;
+ 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
+ * 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);
+
+ /* read the result */
+ smp_rmb();
+ ret = dev->xfer_result;
+ break;
+ }
+ case MTP_SEND_EVENT:
+ {
+ struct mtp_event event;
+ /* return here so we don't change dev->state below,
+ * which would interfere with bulk transfer state.
+ */
+ if (copy_from_user(&event, (void __user *)value, sizeof(event)))
+ ret = -EFAULT;
+ else
+ ret = mtp_send_event(dev, &event);
+ goto out;
+ }
+ }
+
+fail:
+ 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:
+ mtp_unlock(&dev->ioctl_excl);
+ DBG(dev->cdev, "ioctl returning %d\n", ret);
+ return ret;
+}
+
+static int mtp_open(struct inode *ip, struct file *fp)
+{
+ pr_info("mtp_open\n");
+ if (mtp_lock(&_mtp_dev->open_excl))
+ return -EBUSY;
+
+ /* clear any error condition */
+ if (_mtp_dev->state != STATE_OFFLINE)
+ _mtp_dev->state = STATE_READY;
+
+ fp->private_data = _mtp_dev;
+ return 0;
+}
+
+static int mtp_release(struct inode *ip, struct file *fp)
+{
+ pr_info("mtp_release\n");
+
+ mtp_unlock(&_mtp_dev->open_excl);
+ return 0;
+}
+
+/* file operations for /dev/mtp_usb */
+static const struct file_operations mtp_fops = {
+ .owner = THIS_MODULE,
+ .read = mtp_read,
+ .write = mtp_write,
+ .unlocked_ioctl = mtp_ioctl,
+ .open = mtp_open,
+ .release = mtp_release,
+};
+
+static struct miscdevice mtp_device = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = mtp_shortname,
+ .fops = &mtp_fops,
+};
+
+static int mtp_ctrlrequest(struct usb_composite_dev *cdev,
+ const struct usb_ctrlrequest *ctrl)
+{
+ struct mtp_dev *dev = _mtp_dev;
+ int value = -EOPNOTSUPP;
+ u16 w_index = le16_to_cpu(ctrl->wIndex);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 w_length = le16_to_cpu(ctrl->wLength);
+ unsigned long flags;
+
+ VDBG(cdev, "mtp_ctrlrequest %02x.%02x v%04x i%04x l%u\n",
+ ctrl->bRequestType, ctrl->bRequest,
+ w_value, w_index, w_length);
+
+ /* Handle MTP OS string */
+ if (ctrl->bRequestType ==
+ (USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE)
+ && ctrl->bRequest == USB_REQ_GET_DESCRIPTOR
+ && (w_value >> 8) == USB_DT_STRING
+ && (w_value & 0xFF) == MTP_OS_STRING_ID) {
+ value = (w_length < sizeof(mtp_os_string)
+ ? w_length : sizeof(mtp_os_string));
+ memcpy(cdev->req->buf, mtp_os_string, value);
+ } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_VENDOR) {
+ /* Handle MTP OS descriptor */
+ DBG(cdev, "vendor request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (ctrl->bRequest == 1
+ && (ctrl->bRequestType & USB_DIR_IN)
+ && (w_index == 4 || w_index == 5)) {
+ value = (w_length < sizeof(mtp_ext_config_desc) ?
+ w_length : sizeof(mtp_ext_config_desc));
+ memcpy(cdev->req->buf, &mtp_ext_config_desc, value);
+ }
+ } else if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_CLASS) {
+ DBG(cdev, "class request: %d index: %d value: %d length: %d\n",
+ ctrl->bRequest, w_index, w_value, w_length);
+
+ if (ctrl->bRequest == MTP_REQ_CANCEL && w_index == 0
+ && w_value == 0) {
+ DBG(cdev, "MTP_REQ_CANCEL\n");
+
+ spin_lock_irqsave(&dev->lock, flags);
+ if (dev->state == STATE_BUSY) {
+ dev->state = STATE_CANCELED;
+ wake_up(&dev->read_wq);
+ wake_up(&dev->write_wq);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ /* We need to queue a request to read the remaining
+ * bytes, but we don't actually need to look at
+ * the contents.
+ */
+ value = w_length;
+ } else if (ctrl->bRequest == MTP_REQ_GET_DEVICE_STATUS
+ && w_index == 0 && w_value == 0) {
+ struct mtp_device_status *status = cdev->req->buf;
+
+ status->wLength =
+ cpu_to_le16(sizeof(*status));
+ DBG(cdev, "MTP_REQ_GET_DEVICE_STATUS\n");
+ spin_lock_irqsave(&dev->lock, flags);
+ /* device status is "busy" until we report
+ * the cancellation to userspace
+ */
+ if (dev->state == STATE_CANCELED)
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_DEVICE_BUSY);
+ else
+ status->wCode =
+ __cpu_to_le16(MTP_RESPONSE_OK);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ value = sizeof(*status);
+ }
+ }
+
+ /* respond with data transfer or status phase? */
+ if (value >= 0) {
+ int rc;
+
+ cdev->req->zero = value < w_length;
+ cdev->req->length = value;
+ rc = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
+ if (rc < 0)
+ ERROR(cdev, "%s: response queue error\n", __func__);
+ }
+ return value;
+}
+
+static int
+mtp_function_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct mtp_dev *dev = func_to_mtp(f);
+ int id;
+ int ret;
+
+ dev->cdev = cdev;
+ DBG(cdev, "mtp_function_bind dev: %p\n", dev);
+
+ /* allocate interface ID(s) */
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ mtp_interface_desc.bInterfaceNumber = id;
+
+ if (mtp_string_defs[INTERFACE_STRING_INDEX].id == 0) {
+ ret = usb_string_id(c->cdev);
+ if (ret < 0)
+ return ret;
+ mtp_string_defs[INTERFACE_STRING_INDEX].id = ret;
+ mtp_interface_desc.iInterface = ret;
+ }
+ /* allocate endpoints */
+ ret = mtp_create_bulk_endpoints(dev, &mtp_fullspeed_in_desc,
+ &mtp_fullspeed_out_desc, &mtp_intr_desc);
+ if (ret)
+ return ret;
+
+ /* support high speed hardware */
+ if (gadget_is_dualspeed(c->cdev->gadget)) {
+ mtp_highspeed_in_desc.bEndpointAddress =
+ mtp_fullspeed_in_desc.bEndpointAddress;
+ mtp_highspeed_out_desc.bEndpointAddress =
+ mtp_fullspeed_out_desc.bEndpointAddress;
+ }
+
+ DBG(cdev, "%s speed %s: IN/%s, OUT/%s\n",
+ gadget_is_dualspeed(c->cdev->gadget) ? "dual" : "full",
+ f->name, dev->ep_in->name, dev->ep_out->name);
+ return 0;
+}
+
+static void
+mtp_function_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_mtp(f);
+ struct usb_request *req;
+ int i;
+
+ mtp_string_defs[INTERFACE_STRING_INDEX].id = 0;
+ while ((req = mtp_req_get(dev, &dev->tx_idle)))
+ mtp_request_free(req, dev->ep_in);
+ for (i = 0; i < RX_REQ_MAX; i++)
+ mtp_request_free(dev->rx_req[i], dev->ep_out);
+ while ((req = mtp_req_get(dev, &dev->intr_idle)))
+ mtp_request_free(req, dev->ep_intr);
+ dev->state = STATE_OFFLINE;
+}
+
+static int mtp_function_set_alt(struct usb_function *f,
+ unsigned intf, unsigned alt)
+{
+ struct mtp_dev *dev = func_to_mtp(f);
+ struct usb_composite_dev *cdev = f->config->cdev;
+ int ret;
+
+ DBG(cdev, "mtp_function_set_alt intf: %d alt: %d\n", intf, alt);
+
+ ret = config_ep_by_speed(cdev->gadget, f, dev->ep_in);
+ if (ret)
+ return ret;
+
+ ret = usb_ep_enable(dev->ep_in);
+ if (ret)
+ return ret;
+
+ ret = config_ep_by_speed(cdev->gadget, f, dev->ep_out);
+ if (ret)
+ return ret;
+
+ ret = usb_ep_enable(dev->ep_out);
+ if (ret) {
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+
+ ret = config_ep_by_speed(cdev->gadget, f, dev->ep_intr);
+ if (ret)
+ return ret;
+
+ ret = usb_ep_enable(dev->ep_intr);
+ if (ret) {
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_in);
+ return ret;
+ }
+ dev->state = STATE_READY;
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+ return 0;
+}
+
+static void mtp_function_disable(struct usb_function *f)
+{
+ struct mtp_dev *dev = func_to_mtp(f);
+ struct usb_composite_dev *cdev = dev->cdev;
+
+ DBG(cdev, "mtp_function_disable\n");
+ dev->state = STATE_OFFLINE;
+ usb_ep_disable(dev->ep_in);
+ usb_ep_disable(dev->ep_out);
+ usb_ep_disable(dev->ep_intr);
+
+ /* readers may be blocked waiting for us to go online */
+ wake_up(&dev->read_wq);
+
+ VDBG(cdev, "%s disabled\n", dev->function.name);
+}
+
+static int mtp_setup(struct mtp_instance *fi_mtp)
+{
+ struct mtp_dev *dev;
+ int ret;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+
+ if (fi_mtp != NULL)
+ fi_mtp->dev = dev;
+
+ if (!dev)
+ return -ENOMEM;
+
+ spin_lock_init(&dev->lock);
+ 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);
+ INIT_LIST_HEAD(&dev->intr_idle);
+
+ dev->wq = create_singlethread_workqueue("f_mtp");
+ if (!dev->wq) {
+ ret = -ENOMEM;
+ goto err1;
+ }
+ INIT_WORK(&dev->send_file_work, send_file_work);
+ INIT_WORK(&dev->receive_file_work, receive_file_work);
+
+ _mtp_dev = dev;
+
+ ret = misc_register(&mtp_device);
+ if (ret)
+ goto err2;
+
+ return 0;
+
+err2:
+ destroy_workqueue(dev->wq);
+err1:
+ _mtp_dev = NULL;
+ kfree(dev);
+ pr_err("mtp gadget driver failed to initialize\n");
+ return ret;
+}
+
+static void mtp_cleanup(void)
+{
+ struct mtp_dev *dev = _mtp_dev;
+
+ if (!dev)
+ return;
+
+ misc_deregister(&mtp_device);
+ destroy_workqueue(dev->wq);
+ _mtp_dev = NULL;
+ kfree(dev);
+}
+
+static struct mtp_instance *to_mtp_instance(struct config_item *item)
+{
+ return container_of(to_config_group(item), struct mtp_instance,
+ func_inst.group);
+}
+
+static void mtp_attr_release(struct config_item *item)
+{
+ struct mtp_instance *fi_mtp = to_mtp_instance(item);
+
+ usb_put_function_instance(&fi_mtp->func_inst);
+}
+
+static struct configfs_item_operations mtp_item_ops = {
+ .release = mtp_attr_release,
+};
+
+static struct config_item_type mtp_func_type = {
+ .ct_item_ops = &mtp_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct mtp_instance *to_fi_mtp(struct usb_function_instance *fi)
+{
+ return container_of(fi, struct mtp_instance, func_inst);
+}
+
+static int mtp_set_inst_name(struct usb_function_instance *fi, const char *name)
+{
+ struct mtp_instance *fi_mtp;
+ char *ptr;
+ int name_len;
+
+ name_len = strlen(name) + 1;
+ if (name_len > MAX_INST_NAME_LEN)
+ return -ENAMETOOLONG;
+
+ ptr = kstrndup(name, name_len, GFP_KERNEL);
+ if (!ptr)
+ return -ENOMEM;
+
+ fi_mtp = to_fi_mtp(fi);
+ fi_mtp->name = ptr;
+
+ return 0;
+}
+
+static void mtp_free_inst(struct usb_function_instance *fi)
+{
+ struct mtp_instance *fi_mtp;
+
+ fi_mtp = to_fi_mtp(fi);
+ kfree(fi_mtp->name);
+ mtp_cleanup();
+ kfree(fi_mtp);
+}
+
+static struct usb_function_instance *mtp_alloc_inst(void)
+{
+ struct mtp_instance *fi_mtp;
+ int ret = 0;
+
+ fi_mtp = kzalloc(sizeof(*fi_mtp), GFP_KERNEL);
+ if (!fi_mtp)
+ return ERR_PTR(-ENOMEM);
+ fi_mtp->func_inst.set_inst_name = mtp_set_inst_name;
+ fi_mtp->func_inst.free_func_inst = mtp_free_inst;
+
+ ret = mtp_setup(fi_mtp);
+ if (ret) {
+ kfree(fi_mtp);
+ pr_err("Error setting MTP\n");
+ return ERR_PTR(ret);
+ }
+
+ config_group_init_type_name(&fi_mtp->func_inst.group,
+ "", &mtp_func_type);
+
+ return &fi_mtp->func_inst;
+}
+
+static int mtp_ctrlreq_configfs(struct usb_function *f,
+ const struct usb_ctrlrequest *ctrl)
+{
+ return mtp_ctrlrequest(f->config->cdev, ctrl);
+}
+
+static void mtp_free(struct usb_function *f)
+{
+ /*NO-OP: no function specific resource allocation in mtp_alloc*/
+}
+
+static struct usb_function *mtp_alloc(struct usb_function_instance *fi)
+{
+ struct mtp_instance *fi_mtp = to_fi_mtp(fi);
+ struct mtp_dev *dev = fi_mtp->dev;
+
+ dev->function.name = DRIVER_NAME;
+ dev->function.strings = mtp_strings;
+ dev->function.fs_descriptors = fs_mtp_descs;
+ dev->function.hs_descriptors = hs_mtp_descs;
+ dev->function.bind = mtp_function_bind;
+ dev->function.unbind = mtp_function_unbind;
+ dev->function.set_alt = mtp_function_set_alt;
+ dev->function.disable = mtp_function_disable;
+ dev->function.setup = mtp_ctrlreq_configfs;
+ dev->function.free_func = mtp_free;
+
+ return &dev->function;
+}
+
+DECLARE_USB_FUNCTION_INIT(mtp, mtp_alloc_inst, mtp_alloc);
+MODULE_LICENSE("GPL");
diff --git a/include/linux/usb/f_mtp.h b/include/linux/usb/f_mtp.h
new file mode 100644
index 0000000..4e84177
--- /dev/null
+++ b/include/linux/usb/f_mtp.h
@@ -0,0 +1,23 @@
+/*
+ * Gadget Function Driver for MTP
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Mike Lockwood <[email protected]>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __LINUX_USB_F_MTP_H
+#define __LINUX_USB_F_MTP_H
+
+#include <uapi/linux/usb/f_mtp.h>
+
+#endif /* __LINUX_USB_F_MTP_H */
diff --git a/include/uapi/linux/usb/f_mtp.h b/include/uapi/linux/usb/f_mtp.h
new file mode 100644
index 0000000..6baa90d
--- /dev/null
+++ b/include/uapi/linux/usb/f_mtp.h
@@ -0,0 +1,61 @@
+/*
+ * MTP Userspace Interface
+ *
+ * Copyright (C) 2010 Google, Inc.
+ * Author: Mike Lockwood <[email protected]>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _UAPI_LINUX_USB_F_MTP_H
+#define _UAPI_LINUX_USB_F_MTP_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+struct mtp_file_range {
+ /* file descriptor for file to transfer */
+ int fd;
+ /* offset in file for start of transfer */
+ 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 {
+ /* size of the event */
+ size_t length;
+ /* event data to send */
+ void *data;
+};
+
+/* Sends the specified file range to the host */
+#define MTP_SEND_FILE _IOW('M', 0, struct mtp_file_range)
+/* Receives data from the host and writes it to a file.
+ * The file is created if it does not exist.
+ */
+#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 /* _UAPI_LINUX_USB_F_MTP_H */
--
1.9.1
This is more of an RFC than an actual submission. There are few
scattered #ifdefs..#endifs here and there which still need to be
taken care of before going for actual submission.
Currently there is no way with the upstream ConfigFS gadget to
communicate state changes (connected, disconnected, configured), at
the gadget level. Instead such state changes are handled function by
function independently I presume. This is problematic, because some
coordination between the functions, across the state changes, may be
desired at the userspace level. Thus to address this issue, this
patch send uevents to allow userspace to be notified of these usb
state changes, allowing userspace to respond and configure the
configfs gadget appropriately.
This patch is based on an Android patchset originaly authored by
Badhri Jagan Sridharan <[email protected]> to send uevent notifications
to Android userpace for USB state changes. I've folded his patches
together and modified it enough that I don't want him to be blamed for
any mistakes I've made condensing his patches down.
This patch introduces USB_CONFIGFS_UEVENT Kconfig to handle userspace
notifications of usb state changes, and add setup and disconnect
functions to intercept the setup requests from the usb_core. It also
creates a sysfs device class entry and a device attribute (state) to
read and respond to gadget's current state from userspace. As of now
this sysfs device class (/sys/class/android_usb) and gadget device
(/sys/class/android_usb/android0) with state attribute
(/sys/class/android_usb/android0/state) are strictly tied up to
facilitate Android userspace requests. But going forward we may want
to bring all function devices (hid, printer etc) under a unified usb
gadget device class e.g. /sys/class/usb_gadget/g_{func0,func1} etc..
Also I think it make sense to add this state attribute to the configfs
usb gadget itself i.e. have something like /config/usb_gadget/g1/state
to read USB gadget's current state. Since it is going to be consistent
throughout all the functions tied up to that gadget.
Again this is just an initial RFC, thoughts and feedback would be
greatly appreciated.
Cc: Mike Lockwood <[email protected]>
Cc: Benoit Goby <[email protected]>
Cc: Colin Cross <[email protected]>
Cc: Arve Hjønnevåg <[email protected]>
Cc: Peter Oh <[email protected]>
Cc: Greg Hackmann <[email protected]>
Cc: Badhri Jagan Sridharan <[email protected]>
Cc: Android Kernel Team <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Jonathan Corbet <[email protected]>
Cc: Felipe Balbi <[email protected]>
Cc: Andrzej Pietrasiewicz <[email protected]>
Cc: Laurent Pinchart <[email protected]>
Cc: Yegor Yefremov <[email protected]>
Cc: Philippe Reynes <[email protected]>
Cc: John Stultz <[email protected]>
Cc: Sumit Semwal <[email protected]>
Signed-off-by: Amit Pundir <[email protected]>
---
drivers/usb/gadget/Kconfig | 8 ++
drivers/usb/gadget/configfs.c | 201 +++++++++++++++++++++++++++++++++++++++++-
2 files changed, 207 insertions(+), 2 deletions(-)
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 65d110d..e1d1fc1 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -464,6 +464,14 @@ config USB_CONFIGFS_F_MTP
the file level. Thus exposing the relevant content but hiding
the system/restricted files.
+config USB_CONFIGFS_UEVENT
+ bool "Uevent notification of Gadget state"
+ depends on USB_CONFIGFS
+ help
+ Enable uevent notifications to userspace when the gadget
+ state changes. The gadget can be in any of the following
+ three states: "CONNECTED/DISCONNECTED/CONFIGURED"
+
source "drivers/usb/gadget/legacy/Kconfig"
endchoice
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index 289e201..1575343 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -9,6 +9,15 @@
#include "u_f.h"
#include "u_os_desc.h"
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+#include <linux/platform_device.h>
+#include <linux/kdev_t.h>
+#include <linux/usb/ch9.h>
+
+static struct class *usb_gadget_class;
+static struct device *usb_gadget_device;
+#endif
+
int check_user_usb_string(const char *name,
struct usb_gadget_strings *stringtab_dev)
{
@@ -63,6 +72,12 @@ struct gadget_info {
bool use_os_desc;
char b_vendor_code;
char qw_sign[OS_STRING_QW_SIGN_LEN];
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ bool connected;
+ bool sw_connected;
+ struct work_struct work;
+ struct device *dev;
+#endif
};
struct config_usb_cfg {
@@ -1444,13 +1459,143 @@ static void configfs_composite_unbind(struct usb_gadget *gadget)
set_gadget_data(gadget, NULL);
}
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+static ssize_t state_show(struct device *pdev, struct device_attribute *attr,
+ char *buf)
+{
+ struct gadget_info *dev = dev_get_drvdata(pdev);
+ struct usb_composite_dev *cdev;
+ char *state = "DISCONNECTED";
+ unsigned long flags;
+
+ if (!dev)
+ goto out;
+
+ cdev = &dev->cdev;
+
+ if (!cdev)
+ goto out;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ state = "CONFIGURED";
+ else if (dev->connected)
+ state = "CONNECTED";
+ spin_unlock_irqrestore(&cdev->lock, flags);
+out:
+ return sprintf(buf, "%s\n", state);
+}
+
+static DEVICE_ATTR(state, S_IRUGO, state_show, NULL);
+
+static struct device_attribute *cfs_usb_attributes[] = {
+ &dev_attr_state,
+ NULL
+};
+
+static void cfs_uevent_work(struct work_struct *data)
+{
+ struct gadget_info *gi = container_of(data, struct gadget_info, work);
+ struct usb_composite_dev *cdev = &gi->cdev;
+ char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL };
+ char *connected[2] = { "USB_STATE=CONNECTED", NULL };
+ char *configured[2] = { "USB_STATE=CONFIGURED", NULL };
+ /* 0-connected 1-configured 2-disconnected*/
+ bool status[3] = { false, false, false };
+ unsigned long flags;
+ bool uevent_sent = false;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (cdev->config)
+ status[1] = true;
+
+ if (gi->connected != gi->sw_connected) {
+ if (gi->connected)
+ status[0] = true;
+ else
+ status[2] = true;
+ gi->sw_connected = gi->connected;
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ if (status[0]) {
+ kobject_uevent_env(&usb_gadget_device->kobj,
+ KOBJ_CHANGE, connected);
+ pr_info("%s: sent uevent %s\n", __func__, connected[0]);
+ uevent_sent = true;
+ }
+
+ if (status[1]) {
+ kobject_uevent_env(&usb_gadget_device->kobj,
+ KOBJ_CHANGE, configured);
+ pr_info("%s: sent uevent %s\n", __func__, configured[0]);
+ uevent_sent = true;
+ }
+
+ if (status[2]) {
+ kobject_uevent_env(&usb_gadget_device->kobj,
+ KOBJ_CHANGE, disconnected);
+ pr_info("%s: sent uevent %s\n", __func__, disconnected[0]);
+ uevent_sent = true;
+ }
+
+ if (!uevent_sent) {
+ pr_info("%s: did not send uevent (%d %d %p)\n", __func__,
+ gi->connected, gi->sw_connected, cdev->config);
+ }
+}
+
+static int cfs_uevent_setup(struct usb_gadget *gadget,
+ const struct usb_ctrlrequest *c)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ unsigned long flags;
+ struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
+ int value = -EOPNOTSUPP;
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (!gi->connected) {
+ gi->connected = 1;
+ schedule_work(&gi->work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ value = composite_setup(gadget, c);
+
+ spin_lock_irqsave(&cdev->lock, flags);
+ if (c->bRequest == USB_REQ_SET_CONFIGURATION &&
+ cdev->config) {
+ schedule_work(&gi->work);
+ }
+ spin_unlock_irqrestore(&cdev->lock, flags);
+
+ return value;
+}
+
+static void cfs_uevent_disconnect(struct usb_gadget *gadget)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct gadget_info *gi = container_of(cdev, struct gadget_info, cdev);
+
+ gi->connected = 0;
+ schedule_work(&gi->work);
+ composite_disconnect(gadget);
+}
+#endif
+
static const struct usb_gadget_driver configfs_driver_template = {
.bind = configfs_composite_bind,
.unbind = configfs_composite_unbind,
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ .setup = cfs_uevent_setup,
+ .reset = cfs_uevent_disconnect,
+ .disconnect = cfs_uevent_disconnect,
+#else
.setup = composite_setup,
.reset = composite_disconnect,
.disconnect = composite_disconnect,
+#endif
.suspend = composite_suspend,
.resume = composite_resume,
@@ -1462,16 +1607,21 @@ static const struct usb_gadget_driver configfs_driver_template = {
},
};
+
static struct config_group *gadgets_make(
struct config_group *group,
const char *name)
{
struct gadget_info *gi;
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ struct device_attribute **attrs;
+ struct device_attribute *attr;
+ int err;
+#endif
gi = kzalloc(sizeof(*gi), GFP_KERNEL);
if (!gi)
return ERR_PTR(-ENOMEM);
-
gi->group.default_groups = gi->default_groups;
gi->group.default_groups[0] = &gi->functions_group;
gi->group.default_groups[1] = &gi->configs_group;
@@ -1507,9 +1657,26 @@ static struct config_group *gadgets_make(
gi->composite.gadget_driver.function = kstrdup(name, GFP_KERNEL);
gi->composite.name = gi->composite.gadget_driver.function;
- if (!gi->composite.gadget_driver.function)
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ INIT_WORK(&gi->work, cfs_uevent_work);
+ usb_gadget_device = device_create(usb_gadget_class, NULL,
+ MKDEV(0, 0), NULL, "android0");
+ if (IS_ERR(usb_gadget_device))
goto err;
+ dev_set_drvdata(usb_gadget_device, gi);
+
+ attrs = cfs_usb_attributes;
+ while ((attr = *attrs++)) {
+ err = device_create_file(usb_gadget_device, attr);
+ if (err)
+ goto err1;
+ }
+#endif
+
+ if (!gi->composite.gadget_driver.function)
+ goto err1;
+
#ifdef CONFIG_USB_OTG
gi->otg.bLength = sizeof(struct usb_otg_descriptor);
gi->otg.bDescriptorType = USB_DT_OTG;
@@ -1519,13 +1686,31 @@ static struct config_group *gadgets_make(
config_group_init_type_name(&gi->group, name,
&gadget_root_type);
return &gi->group;
+
+err1:
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ attrs = cfs_usb_attributes;
+ while ((attr = *attrs++))
+ device_remove_file(usb_gadget_device, attr);
+ device_destroy(usb_gadget_device->class, usb_gadget_device->devt);
err:
+#endif
kfree(gi);
return ERR_PTR(-ENOMEM);
}
static void gadgets_drop(struct config_group *group, struct config_item *item)
{
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ struct device_attribute **attrs;
+ struct device_attribute *attr;
+
+ attrs = cfs_usb_attributes;
+ while ((attr = *attrs++))
+ device_remove_file(usb_gadget_device, attr);
+ device_destroy(usb_gadget_device->class, usb_gadget_device->devt);
+#endif
+
config_item_put(item);
}
@@ -1564,6 +1749,13 @@ static int __init gadget_cfs_init(void)
config_group_init(&gadget_subsys.su_group);
ret = configfs_register_subsystem(&gadget_subsys);
+
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ usb_gadget_class = class_create(THIS_MODULE, "android_usb");
+ if (IS_ERR(usb_gadget_class))
+ return PTR_ERR(usb_gadget_class);
+#endif
+
return ret;
}
module_init(gadget_cfs_init);
@@ -1571,5 +1763,10 @@ module_init(gadget_cfs_init);
static void __exit gadget_cfs_exit(void)
{
configfs_unregister_subsystem(&gadget_subsys);
+#ifdef CONFIG_USB_CONFIGFS_UEVENT
+ if (!IS_ERR(usb_gadget_class))
+ class_destroy(usb_gadget_class);
+#endif
+
}
module_exit(gadget_cfs_exit);
--
1.9.1
Hello,
On 08/13/2015 09:12 PM, Amit Pundir wrote:
> his MTP function is based on years of work originally done in the
> Android kernel tree by:
> Mike Lockwood<[email protected]>
> Benoit Goby<[email protected]>
> Colin Cross<[email protected]>
> Arve Hjønnevåg<[email protected]>
> Peter Oh<[email protected]>
> Greg Hackmann<[email protected]>
> Badhri Jagan Sridharan<[email protected]>
> I've folded the series up to make it easier to review, and to provide
> a coherent patch description.
>
> Post Gingerbread (Android v2.3), Android dropped USB Mass Storage
> in favor of Media Transfer Protocal (MTP), which is widely used for
> transferring media files to digital music players and similar
> applications. This USB gadget function implements MTP functionalty.
>
> Historically this function has been a part of Android composite
> gadget driver. Android composite driver was Android's solution
> for dynamic gadget function switching prior to the ConfigFS gadget
> being merged. There were failed few attempts in past
> http://marc.info/?l=linux-usb&m=132451695808552 to upstream Android
> composite driver as well. Now this Android MTP gadget function has been
> re-implemented so as to be used as a generic ConfigFS function instead.
>
> Again, many thanks to Mike, Benoit, Colin, Arve, Peter, Greg and Badhri,
> as they are the real authors of this work. However, I've folded their
> patches together and modified it enough that I don't want them to be
> blamed for any mistakes I've made condensing their patches down.
>
> Cc: Mike Lockwood<[email protected]>
> Cc: Benoit Goby<[email protected]>
> Cc: Colin Cross<[email protected]>
> Cc: Arve Hjønnevåg<[email protected]>
> Cc: Peter Oh<[email protected]>
> Cc: Greg Hackmann<[email protected]>
> Cc: Badhri Jagan Sridharan<[email protected]>
> Cc: Android Kernel Team<[email protected]>
> Cc: Greg Kroah-Hartman<[email protected]>
> Cc: Jonathan Corbet<[email protected]>
> Cc: Felipe Balbi<[email protected]>
> Cc: Andrzej Pietrasiewicz<[email protected]>
> Cc: Laurent Pinchart<[email protected]>
> Cc: Yegor Yefremov<[email protected]>
> Cc: Philippe Reynes<[email protected]>
> Cc: John Stultz<[email protected]>
> Cc: Sumit Semwal<[email protected]>
> Signed-off-by: Amit Pundir<[email protected]>
In my humble opinion adding such function to Linux kernel doesn't make
any sense. By design, MTP is a protocol which requires access to
userspace features esp. file system. It is very important to run MTP
daemon with suitable user and LSM label and many many other issues which
should be handled by userspace access policy.
Moreover this is not a fully functional USB function but only some
interface which can be used by mtp-responder (mtp-daemon - call it as
you like) to communicate with host. As we have FunctionFS which allows
to implement any USB function in as a userspace service. As MTP nature
is more related to userspace I think that porting MTP daemon to use this
is a right way to go. This should be much more reasonable than adding
new function which also requires daemon for proper working. So why add
another interface while we can use a generic one?
Best regards,
--
Krzysztof Opasiak
Samsung R&D Institute Poland
Samsung Electronics
Hello,
On 08/13/2015 09:12 PM, Amit Pundir wrote:
> This is more of an RFC than an actual submission. There are few
> scattered #ifdefs..#endifs here and there which still need to be
> taken care of before going for actual submission.
>
> Currently there is no way with the upstream ConfigFS gadget to
> communicate state changes (connected, disconnected, configured), at
> the gadget level. Instead such state changes are handled function by
> function independently I presume. This is problematic, because some
> coordination between the functions, across the state changes, may be
> desired at the userspace level. Thus to address this issue, this
> patch send uevents to allow userspace to be notified of these usb
> state changes, allowing userspace to respond and configure the
> configfs gadget appropriately.
>
> This patch is based on an Android patchset originaly authored by
> Badhri Jagan Sridharan<[email protected]> to send uevent notifications
> to Android userpace for USB state changes. I've folded his patches
> together and modified it enough that I don't want him to be blamed for
> any mistakes I've made condensing his patches down.
>
> This patch introduces USB_CONFIGFS_UEVENT Kconfig to handle userspace
> notifications of usb state changes, and add setup and disconnect
> functions to intercept the setup requests from the usb_core. It also
> creates a sysfs device class entry and a device attribute (state) to
> read and respond to gadget's current state from userspace. As of now
> this sysfs device class (/sys/class/android_usb) and gadget device
> (/sys/class/android_usb/android0) with state attribute
> (/sys/class/android_usb/android0/state) are strictly tied up to
> facilitate Android userspace requests. But going forward we may want
> to bring all function devices (hid, printer etc) under a unified usb
> gadget device class e.g. /sys/class/usb_gadget/g_{func0,func1} etc..
>
> Also I think it make sense to add this state attribute to the configfs
> usb gadget itself i.e. have something like /config/usb_gadget/g1/state
> to read USB gadget's current state. Since it is going to be consistent
> throughout all the functions tied up to that gadget.
>
> Again this is just an initial RFC, thoughts and feedback would be
> greatly appreciated.
>
> Cc: Mike Lockwood<[email protected]>
> Cc: Benoit Goby<[email protected]>
> Cc: Colin Cross<[email protected]>
> Cc: Arve Hjønnevåg<[email protected]>
> Cc: Peter Oh<[email protected]>
> Cc: Greg Hackmann<[email protected]>
> Cc: Badhri Jagan Sridharan<[email protected]>
> Cc: Android Kernel Team<[email protected]>
> Cc: Greg Kroah-Hartman<[email protected]>
> Cc: Jonathan Corbet<[email protected]>
> Cc: Felipe Balbi<[email protected]>
> Cc: Andrzej Pietrasiewicz<[email protected]>
> Cc: Laurent Pinchart<[email protected]>
> Cc: Yegor Yefremov<[email protected]>
> Cc: Philippe Reynes<[email protected]>
> Cc: John Stultz<[email protected]>
> Cc: Sumit Semwal<[email protected]>
> Signed-off-by: Amit Pundir<[email protected]>
Generally I agree that there should be some way of notifying userspace
about gadget state but I'm not sure if this is proper way to go. In my
opinion gadget-bus which has been discussed some time ago on linux-usb
makes much more sense than this.
Maybe I will be wrong but I guess that you are adding this feature to
make android mtp-responder working properly in Linux (not android).
Again if we use FFS instead of adding mtp function we don't need this
notification as mtp-responder can get all the required informations
about gadget (function) via ep0 using functionfs events.
Best regards,
--
Krzysztof Opasiak
Samsung R&D Institute Poland
Samsung Electronics
On Thu, Aug 13, 2015 at 09:34:46PM +0200, Krzysztof Opasiak wrote:
> Hello,
>
> On 08/13/2015 09:12 PM, Amit Pundir wrote:
> >his MTP function is based on years of work originally done in the
> >Android kernel tree by:
> > Mike Lockwood<[email protected]>
> > Benoit Goby<[email protected]>
> > Colin Cross<[email protected]>
> > Arve Hj?nnev?g<[email protected]>
> > Peter Oh<[email protected]>
> > Greg Hackmann<[email protected]>
> > Badhri Jagan Sridharan<[email protected]>
> >I've folded the series up to make it easier to review, and to provide
> >a coherent patch description.
> >
> >Post Gingerbread (Android v2.3), Android dropped USB Mass Storage
> >in favor of Media Transfer Protocal (MTP), which is widely used for
> >transferring media files to digital music players and similar
> >applications. This USB gadget function implements MTP functionalty.
> >
> >Historically this function has been a part of Android composite
> >gadget driver. Android composite driver was Android's solution
> >for dynamic gadget function switching prior to the ConfigFS gadget
> >being merged. There were failed few attempts in past
> >http://marc.info/?l=linux-usb&m=132451695808552 to upstream Android
> >composite driver as well. Now this Android MTP gadget function has been
> >re-implemented so as to be used as a generic ConfigFS function instead.
> >
> >Again, many thanks to Mike, Benoit, Colin, Arve, Peter, Greg and Badhri,
> >as they are the real authors of this work. However, I've folded their
> >patches together and modified it enough that I don't want them to be
> >blamed for any mistakes I've made condensing their patches down.
> >
> >Cc: Mike Lockwood<[email protected]>
> >Cc: Benoit Goby<[email protected]>
> >Cc: Colin Cross<[email protected]>
> >Cc: Arve Hj?nnev?g<[email protected]>
> >Cc: Peter Oh<[email protected]>
> >Cc: Greg Hackmann<[email protected]>
> >Cc: Badhri Jagan Sridharan<[email protected]>
> >Cc: Android Kernel Team<[email protected]>
> >Cc: Greg Kroah-Hartman<[email protected]>
> >Cc: Jonathan Corbet<[email protected]>
> >Cc: Felipe Balbi<[email protected]>
> >Cc: Andrzej Pietrasiewicz<[email protected]>
> >Cc: Laurent Pinchart<[email protected]>
> >Cc: Yegor Yefremov<[email protected]>
> >Cc: Philippe Reynes<[email protected]>
> >Cc: John Stultz<[email protected]>
> >Cc: Sumit Semwal<[email protected]>
> >Signed-off-by: Amit Pundir<[email protected]>
>
> In my humble opinion adding such function to Linux kernel doesn't make any
> sense. By design, MTP is a protocol which requires access to userspace
> features esp. file system. It is very important to run MTP daemon with
> suitable user and LSM label and many many other issues which should be
> handled by userspace access policy.
>
> Moreover this is not a fully functional USB function but only some interface
> which can be used by mtp-responder (mtp-daemon - call it as you like) to
> communicate with host. As we have FunctionFS which allows to implement any
> USB function in as a userspace service. As MTP nature is more related to
> userspace I think that porting MTP daemon to use this is a right way to go.
> This should be much more reasonable than adding new function which also
> requires daemon for proper working. So why add another interface while we
> can use a generic one?
Isn't there already a userspace MTP daemon that uses the existing
functionfs for usb gadgets? I thought I remember seeing that
somewhere...
thanks,
greg k-h
On 08/13/2015 09:57 PM, Greg Kroah-Hartman wrote:
> On Thu, Aug 13, 2015 at 09:34:46PM +0200, Krzysztof Opasiak wrote:
>> Hello,
>>
>> On 08/13/2015 09:12 PM, Amit Pundir wrote:
>>> his MTP function is based on years of work originally done in the
>>> Android kernel tree by:
>>> Mike Lockwood<[email protected]>
>>> Benoit Goby<[email protected]>
>>> Colin Cross<[email protected]>
>>> Arve Hj?nnev?g<[email protected]>
>>> Peter Oh<[email protected]>
>>> Greg Hackmann<[email protected]>
>>> Badhri Jagan Sridharan<[email protected]>
>>> I've folded the series up to make it easier to review, and to provide
>>> a coherent patch description.
>>>
>>> Post Gingerbread (Android v2.3), Android dropped USB Mass Storage
>>> in favor of Media Transfer Protocal (MTP), which is widely used for
>>> transferring media files to digital music players and similar
>>> applications. This USB gadget function implements MTP functionalty.
>>>
>>> Historically this function has been a part of Android composite
>>> gadget driver. Android composite driver was Android's solution
>>> for dynamic gadget function switching prior to the ConfigFS gadget
>>> being merged. There were failed few attempts in past
>>> http://marc.info/?l=linux-usb&m=132451695808552 to upstream Android
>>> composite driver as well. Now this Android MTP gadget function has been
>>> re-implemented so as to be used as a generic ConfigFS function instead.
>>>
>>> Again, many thanks to Mike, Benoit, Colin, Arve, Peter, Greg and Badhri,
>>> as they are the real authors of this work. However, I've folded their
>>> patches together and modified it enough that I don't want them to be
>>> blamed for any mistakes I've made condensing their patches down.
>>>
>>> Cc: Mike Lockwood<[email protected]>
>>> Cc: Benoit Goby<[email protected]>
>>> Cc: Colin Cross<[email protected]>
>>> Cc: Arve Hj?nnev?g<[email protected]>
>>> Cc: Peter Oh<[email protected]>
>>> Cc: Greg Hackmann<[email protected]>
>>> Cc: Badhri Jagan Sridharan<[email protected]>
>>> Cc: Android Kernel Team<[email protected]>
>>> Cc: Greg Kroah-Hartman<[email protected]>
>>> Cc: Jonathan Corbet<[email protected]>
>>> Cc: Felipe Balbi<[email protected]>
>>> Cc: Andrzej Pietrasiewicz<[email protected]>
>>> Cc: Laurent Pinchart<[email protected]>
>>> Cc: Yegor Yefremov<[email protected]>
>>> Cc: Philippe Reynes<[email protected]>
>>> Cc: John Stultz<[email protected]>
>>> Cc: Sumit Semwal<[email protected]>
>>> Signed-off-by: Amit Pundir<[email protected]>
>>
>> In my humble opinion adding such function to Linux kernel doesn't make any
>> sense. By design, MTP is a protocol which requires access to userspace
>> features esp. file system. It is very important to run MTP daemon with
>> suitable user and LSM label and many many other issues which should be
>> handled by userspace access policy.
>>
>> Moreover this is not a fully functional USB function but only some interface
>> which can be used by mtp-responder (mtp-daemon - call it as you like) to
>> communicate with host. As we have FunctionFS which allows to implement any
>> USB function in as a userspace service. As MTP nature is more related to
>> userspace I think that porting MTP daemon to use this is a right way to go.
>> This should be much more reasonable than adding new function which also
>> requires daemon for proper working. So why add another interface while we
>> can use a generic one?
>
> Isn't there already a userspace MTP daemon that uses the existing
> functionfs for usb gadgets? I thought I remember seeing that
> somewhere...
>
I know for sure that ADB and SDB has been ported to use functionfs. I
can even see ADB with ffs backend working on my nexus 9 with 3.10 kernel.
I've seen such mtp-responder implementation but that time it has not
been published to open source. I don't know what is the current state...
Best regards,
--
Krzysztof Opasiak
Samsung R&D Institute Poland
Samsung Electronics
On 08/13/2015 09:57 PM, Greg Kroah-Hartman wrote:
> On Thu, Aug 13, 2015 at 09:34:46PM +0200, Krzysztof Opasiak wrote:
>> Hello,
>>
>> On 08/13/2015 09:12 PM, Amit Pundir wrote:
>>> his MTP function is based on years of work originally done in the
>>> Android kernel tree by:
>>> Mike Lockwood<[email protected]>
>>> Benoit Goby<[email protected]>
>>> Colin Cross<[email protected]>
>>> Arve Hj?nnev?g<[email protected]>
>>> Peter Oh<[email protected]>
>>> Greg Hackmann<[email protected]>
>>> Badhri Jagan Sridharan<[email protected]>
>>> I've folded the series up to make it easier to review, and to provide
>>> a coherent patch description.
>>>
>>> Post Gingerbread (Android v2.3), Android dropped USB Mass Storage
>>> in favor of Media Transfer Protocal (MTP), which is widely used for
>>> transferring media files to digital music players and similar
>>> applications. This USB gadget function implements MTP functionalty.
>>>
>>> Historically this function has been a part of Android composite
>>> gadget driver. Android composite driver was Android's solution
>>> for dynamic gadget function switching prior to the ConfigFS gadget
>>> being merged. There were failed few attempts in past
>>> http://marc.info/?l=linux-usb&m=132451695808552 to upstream Android
>>> composite driver as well. Now this Android MTP gadget function has been
>>> re-implemented so as to be used as a generic ConfigFS function instead.
>>>
>>> Again, many thanks to Mike, Benoit, Colin, Arve, Peter, Greg and Badhri,
>>> as they are the real authors of this work. However, I've folded their
>>> patches together and modified it enough that I don't want them to be
>>> blamed for any mistakes I've made condensing their patches down.
>>>
>>> Cc: Mike Lockwood<[email protected]>
>>> Cc: Benoit Goby<[email protected]>
>>> Cc: Colin Cross<[email protected]>
>>> Cc: Arve Hj?nnev?g<[email protected]>
>>> Cc: Peter Oh<[email protected]>
>>> Cc: Greg Hackmann<[email protected]>
>>> Cc: Badhri Jagan Sridharan<[email protected]>
>>> Cc: Android Kernel Team<[email protected]>
>>> Cc: Greg Kroah-Hartman<[email protected]>
>>> Cc: Jonathan Corbet<[email protected]>
>>> Cc: Felipe Balbi<[email protected]>
>>> Cc: Andrzej Pietrasiewicz<[email protected]>
>>> Cc: Laurent Pinchart<[email protected]>
>>> Cc: Yegor Yefremov<[email protected]>
>>> Cc: Philippe Reynes<[email protected]>
>>> Cc: John Stultz<[email protected]>
>>> Cc: Sumit Semwal<[email protected]>
>>> Signed-off-by: Amit Pundir<[email protected]>
>>
>> In my humble opinion adding such function to Linux kernel doesn't make any
>> sense. By design, MTP is a protocol which requires access to userspace
>> features esp. file system. It is very important to run MTP daemon with
>> suitable user and LSM label and many many other issues which should be
>> handled by userspace access policy.
>>
>> Moreover this is not a fully functional USB function but only some interface
>> which can be used by mtp-responder (mtp-daemon - call it as you like) to
>> communicate with host. As we have FunctionFS which allows to implement any
>> USB function in as a userspace service. As MTP nature is more related to
>> userspace I think that porting MTP daemon to use this is a right way to go.
>> This should be much more reasonable than adding new function which also
>> requires daemon for proper working. So why add another interface while we
>> can use a generic one?
>
> Isn't there already a userspace MTP daemon that uses the existing
> functionfs for usb gadgets? I thought I remember seeing that
> somewhere...
>
I've found some interesting link[2] which may mean that Sailfish OS guys
has some mtp implementation with functionfs backend:
<<<<< cite
- /dev/mtp
mtp functionfs rw,relatime
>>>>> cite
Started digging and got it!
This looks like mtp with ffs backend:
https://github.com/nemomobile/buteo-mtp
Didn't tested, even didn't try to compile, no guarantee;)
Footnotes:
1 -
http://reviewjolla.blogspot.com/2014/06/techspecs-android-on-jolla-phone.html
Best regards,
--
Krzysztof Opasiak
Samsung R&D Institute Poland
Samsung Electronics
Hi,
On Thu, Aug 13, 2015 at 09:42:17PM +0200, Krzysztof Opasiak wrote:
> Hello,
>
> On 08/13/2015 09:12 PM, Amit Pundir wrote:
> >This is more of an RFC than an actual submission. There are few
> >scattered #ifdefs..#endifs here and there which still need to be
> >taken care of before going for actual submission.
> >
> >Currently there is no way with the upstream ConfigFS gadget to
> >communicate state changes (connected, disconnected, configured), at
> >the gadget level. Instead such state changes are handled function by
> >function independently I presume. This is problematic, because some
> >coordination between the functions, across the state changes, may be
> >desired at the userspace level. Thus to address this issue, this
> >patch send uevents to allow userspace to be notified of these usb
> >state changes, allowing userspace to respond and configure the
> >configfs gadget appropriately.
> >
> >This patch is based on an Android patchset originaly authored by
> >Badhri Jagan Sridharan<[email protected]> to send uevent notifications
> >to Android userpace for USB state changes. I've folded his patches
> >together and modified it enough that I don't want him to be blamed for
> >any mistakes I've made condensing his patches down.
> >
> >This patch introduces USB_CONFIGFS_UEVENT Kconfig to handle userspace
> >notifications of usb state changes, and add setup and disconnect
> >functions to intercept the setup requests from the usb_core. It also
> >creates a sysfs device class entry and a device attribute (state) to
> >read and respond to gadget's current state from userspace. As of now
> >this sysfs device class (/sys/class/android_usb) and gadget device
> >(/sys/class/android_usb/android0) with state attribute
> >(/sys/class/android_usb/android0/state) are strictly tied up to
> >facilitate Android userspace requests. But going forward we may want
> >to bring all function devices (hid, printer etc) under a unified usb
> >gadget device class e.g. /sys/class/usb_gadget/g_{func0,func1} etc..
> >
> >Also I think it make sense to add this state attribute to the configfs
> >usb gadget itself i.e. have something like /config/usb_gadget/g1/state
> >to read USB gadget's current state. Since it is going to be consistent
> >throughout all the functions tied up to that gadget.
> >
> >Again this is just an initial RFC, thoughts and feedback would be
> >greatly appreciated.
> >
> >Cc: Mike Lockwood<[email protected]>
> >Cc: Benoit Goby<[email protected]>
> >Cc: Colin Cross<[email protected]>
> >Cc: Arve Hj?nnev?g<[email protected]>
> >Cc: Peter Oh<[email protected]>
> >Cc: Greg Hackmann<[email protected]>
> >Cc: Badhri Jagan Sridharan<[email protected]>
> >Cc: Android Kernel Team<[email protected]>
> >Cc: Greg Kroah-Hartman<[email protected]>
> >Cc: Jonathan Corbet<[email protected]>
> >Cc: Felipe Balbi<[email protected]>
> >Cc: Andrzej Pietrasiewicz<[email protected]>
> >Cc: Laurent Pinchart<[email protected]>
> >Cc: Yegor Yefremov<[email protected]>
> >Cc: Philippe Reynes<[email protected]>
> >Cc: John Stultz<[email protected]>
> >Cc: Sumit Semwal<[email protected]>
> >Signed-off-by: Amit Pundir<[email protected]>
>
> Generally I agree that there should be some way of notifying userspace about
yes, and we already have a sysfs file for that. See udc-core.c:
static void usb_gadget_state_work(struct work_struct *work)
{
struct usb_gadget *gadget = work_to_gadget(work);
struct usb_udc *udc = gadget->udc;
if (udc)
sysfs_notify(&udc->dev.kobj, NULL, "state");
}
void usb_gadget_set_state(struct usb_gadget *gadget,
enum usb_device_state state)
{
gadget->state = state;
schedule_work(&gadget->work);
}
EXPORT_SYMBOL_GPL(usb_gadget_set_state);
If it's not working for any UDC, it just means the UDC needs to be
patched and if we're missing any state, it means that either the UDC
can't provide that IRQ, or we need to add more states to that
enumeration (which I find unlikely).
--
balbi
On 14 August 2015 at 02:11, Krzysztof Opasiak <[email protected]> wrote:
>
>
> On 08/13/2015 09:57 PM, Greg Kroah-Hartman wrote:
>>
>> On Thu, Aug 13, 2015 at 09:34:46PM +0200, Krzysztof Opasiak wrote:
>>>
>>> Hello,
>>>
>>> On 08/13/2015 09:12 PM, Amit Pundir wrote:
>>>>
>>>> his MTP function is based on years of work originally done in the
>>>> Android kernel tree by:
>>>> Mike Lockwood<[email protected]>
>>>> Benoit Goby<[email protected]>
>>>> Colin Cross<[email protected]>
>>>> Arve Hjønnevåg<[email protected]>
>>>> Peter Oh<[email protected]>
>>>> Greg Hackmann<[email protected]>
>>>> Badhri Jagan Sridharan<[email protected]>
>>>> I've folded the series up to make it easier to review, and to provide
>>>> a coherent patch description.
>>>>
>>>> Post Gingerbread (Android v2.3), Android dropped USB Mass Storage
>>>> in favor of Media Transfer Protocal (MTP), which is widely used for
>>>> transferring media files to digital music players and similar
>>>> applications. This USB gadget function implements MTP functionalty.
>>>>
>>>> Historically this function has been a part of Android composite
>>>> gadget driver. Android composite driver was Android's solution
>>>> for dynamic gadget function switching prior to the ConfigFS gadget
>>>> being merged. There were failed few attempts in past
>>>> http://marc.info/?l=linux-usb&m=132451695808552 to upstream Android
>>>> composite driver as well. Now this Android MTP gadget function has been
>>>> re-implemented so as to be used as a generic ConfigFS function instead.
>>>>
>>>> Again, many thanks to Mike, Benoit, Colin, Arve, Peter, Greg and Badhri,
>>>> as they are the real authors of this work. However, I've folded their
>>>> patches together and modified it enough that I don't want them to be
>>>> blamed for any mistakes I've made condensing their patches down.
>>>>
>>>> Cc: Mike Lockwood<[email protected]>
>>>> Cc: Benoit Goby<[email protected]>
>>>> Cc: Colin Cross<[email protected]>
>>>> Cc: Arve Hjønnevåg<[email protected]>
>>>> Cc: Peter Oh<[email protected]>
>>>> Cc: Greg Hackmann<[email protected]>
>>>> Cc: Badhri Jagan Sridharan<[email protected]>
>>>> Cc: Android Kernel Team<[email protected]>
>>>> Cc: Greg Kroah-Hartman<[email protected]>
>>>> Cc: Jonathan Corbet<[email protected]>
>>>> Cc: Felipe Balbi<[email protected]>
>>>> Cc: Andrzej Pietrasiewicz<[email protected]>
>>>> Cc: Laurent Pinchart<[email protected]>
>>>> Cc: Yegor Yefremov<[email protected]>
>>>> Cc: Philippe Reynes<[email protected]>
>>>> Cc: John Stultz<[email protected]>
>>>> Cc: Sumit Semwal<[email protected]>
>>>> Signed-off-by: Amit Pundir<[email protected]>
>>>
>>>
>>> In my humble opinion adding such function to Linux kernel doesn't make
>>> any
>>> sense. By design, MTP is a protocol which requires access to userspace
>>> features esp. file system. It is very important to run MTP daemon with
>>> suitable user and LSM label and many many other issues which should be
>>> handled by userspace access policy.
>>>
>>> Moreover this is not a fully functional USB function but only some
>>> interface
>>> which can be used by mtp-responder (mtp-daemon - call it as you like) to
>>> communicate with host. As we have FunctionFS which allows to implement
>>> any
>>> USB function in as a userspace service. As MTP nature is more related to
>>> userspace I think that porting MTP daemon to use this is a right way to
>>> go.
>>> This should be much more reasonable than adding new function which also
>>> requires daemon for proper working. So why add another interface while we
>>> can use a generic one?
Fairly valid point. I did see MTP mentioned in FunctionFS context in
Documentation/usb/functionfs.txt but I could not find an open
userpsace MTP daemon implementation based on F_FS to try.
>>
>>
>> Isn't there already a userspace MTP daemon that uses the existing
>> functionfs for usb gadgets? I thought I remember seeing that
>> somewhere...
>>
>
> I've found some interesting link[2] which may mean that Sailfish OS guys has
> some mtp implementation with functionfs backend:
>
> <<<<< cite
>
> - /dev/mtp
> mtp functionfs rw,relatime
>
>>>>>> cite
>
> Started digging and got it!
>
> This looks like mtp with ffs backend:
>
> https://github.com/nemomobile/buteo-mtp
Thanks for the pointer. This "buteo-mtp" looks interesting.
Regards,
Amit Pundir
>
> Didn't tested, even didn't try to compile, no guarantee;)
>
> Footnotes:
> 1 -
> http://reviewjolla.blogspot.com/2014/06/techspecs-android-on-jolla-phone.html
>
>
> Best regards,
>
> --
> Krzysztof Opasiak
> Samsung R&D Institute Poland
> Samsung Electronics