Initial support for FunctionFS 13/14113/5
authorAleksander Zdyb <a.zdyb@partner.samsung.com>
Wed, 29 Jan 2014 14:22:43 +0000 (15:22 +0100)
committerAleksander Zdyb <a.zdyb@partner.samsung.com>
Fri, 31 Jan 2014 13:19:23 +0000 (14:19 +0100)
Add support for FunctionFS. It's not yet built by default, but can be
enabled by substituting usb_linux_client.c with usb_funcfs_client.c.

Change-Id: Id3209741e21c331ed239cdd6204b70e275224449
Signed-off-by: Aleksander Zdyb <a.zdyb@partner.samsung.com>
src/sdb.h
src/usb_funcfs_client.c [new file with mode: 0644]

index e8bb48c..8075d60 100644 (file)
--- a/src/sdb.h
+++ b/src/sdb.h
@@ -512,4 +512,5 @@ int read_line(const int fd, char* ptr, const size_t maxlen);
 #endif
 #endif
 
+#define USB_FUNCFS_SDB_PATH "/dev/usbgadget/sdb"
 #define USB_NODE_FILE "/dev/samsung_sdb"
diff --git a/src/usb_funcfs_client.c b/src/usb_funcfs_client.c
new file mode 100644 (file)
index 0000000..0a2ff7e
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <dirent.h>
+#include <errno.h>
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/functionfs.h>
+
+#include "sysdeps.h"
+
+#define   TRACE_TAG  TRACE_USB
+#include "sdb.h"
+
+#define MAX_PACKET_SIZE_FS     64
+#define MAX_PACKET_SIZE_HS     512
+
+#define cpu_to_le16(x)  htole16(x)
+#define cpu_to_le32(x)  htole32(x)
+
+static const char ep0_path[] = USB_FUNCFS_SDB_PATH"/ep0";
+static const char ep1_path[] = USB_FUNCFS_SDB_PATH"/ep1";
+static const char ep2_path[] = USB_FUNCFS_SDB_PATH"/ep2";
+
+static const struct {
+    struct usb_functionfs_descs_head header;
+    struct {
+        struct usb_interface_descriptor intf;
+        struct usb_endpoint_descriptor_no_audio source;
+        struct usb_endpoint_descriptor_no_audio sink;
+    } __attribute__((packed)) fs_descs, hs_descs;
+} __attribute__((packed)) descriptors = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+        .length = cpu_to_le32(sizeof(descriptors)),
+        .fs_count = 3,
+        .hs_count = 3,
+    },
+    .fs_descs = {
+        .intf = {
+            .bLength = sizeof(descriptors.fs_descs.intf),
+            .bDescriptorType = USB_DT_INTERFACE,
+            .bInterfaceNumber = 0,
+            .bNumEndpoints = 2,
+            .bInterfaceClass = SDB_CLASS,
+            .bInterfaceSubClass = SDB_SUBCLASS,
+            .bInterfaceProtocol = SDB_PROTOCOL,
+            .iInterface = 1, /* first string from the provided table */
+        },
+        .source = {
+            .bLength = sizeof(descriptors.fs_descs.source),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 1 | USB_DIR_OUT,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+        },
+        .sink = {
+          .bLength = sizeof(descriptors.fs_descs.sink),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 2 | USB_DIR_IN,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_FS,
+        },
+    },
+    .hs_descs = {
+        .intf = {
+            .bLength = sizeof(descriptors.hs_descs.intf),
+            .bDescriptorType = USB_DT_INTERFACE,
+            .bInterfaceNumber = 0,
+            .bNumEndpoints = 2,
+            .bInterfaceClass = SDB_CLASS,
+            .bInterfaceSubClass = SDB_SUBCLASS,
+            .bInterfaceProtocol = SDB_PROTOCOL,
+            .iInterface = 1, /* first string from the provided table */
+        },
+        .source = {
+            .bLength = sizeof(descriptors.hs_descs.source),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 1 | USB_DIR_OUT,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+        },
+        .sink = {
+            .bLength = sizeof(descriptors.hs_descs.sink),
+            .bDescriptorType = USB_DT_ENDPOINT,
+            .bEndpointAddress = 2 | USB_DIR_IN,
+            .bmAttributes = USB_ENDPOINT_XFER_BULK,
+            .wMaxPacketSize = MAX_PACKET_SIZE_HS,
+        },
+    },
+};
+
+#define STR_INTERFACE "SDB Interface"
+
+static const struct {
+    struct usb_functionfs_strings_head header;
+    struct {
+        __le16 code;
+        const char str1[sizeof(STR_INTERFACE)];
+    } __attribute__((packed)) lang0;
+} __attribute__((packed)) strings = {
+    .header = {
+        .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+        .length = cpu_to_le32(sizeof(strings)),
+        .str_count = cpu_to_le32(1),
+        .lang_count = cpu_to_le32(1),
+    },
+    .lang0 = {
+        cpu_to_le16(0x0409), /* en-us */
+        STR_INTERFACE,
+    },
+};
+
+
+/* A local struct to store state of application */
+struct usb_handle
+{
+    const char *EP0_NAME;
+    const char *EP_IN_NAME;
+    const char *EP_OUT_NAME;
+    int control;
+    int bulk_out; /* "out" from the host's perspective => source for sdbd */
+    int bulk_in;  /* "in" from the host's perspective => sink for sdbd */
+    sdb_cond_t notify;
+    sdb_mutex_t lock;
+    sdb_cond_t control_notify;
+    sdb_mutex_t control_lock;
+    sdb_cond_t kick_notify;
+    sdb_mutex_t kick_lock;
+    int ffs_enabled;
+    int needs_kick;
+};
+
+
+/*
+ * Initializes FunctionFS by writing descriptors and strings
+ * to control endpoint (EP0)
+ */
+static void init_functionfs(struct usb_handle *h)
+{
+    ssize_t ret;
+
+    sdb_mutex_lock(&h->control_lock);
+
+    /* open control endpoint */
+    D("OPENING %s\n", h->EP0_NAME);
+    h->control = sdb_open(h->EP0_NAME, O_RDWR);
+    if (h->control < 0) {
+        D("[ %s: cannot open control endpoint ]\n", h->EP0_NAME);
+        h->control = -errno;
+        goto error;
+    }
+
+    /* write descriptors to control endpoint */
+    D("[ %s: writing descriptors ]\n", h->EP0_NAME);
+    ret = sdb_write(h->control, &descriptors, sizeof(descriptors));
+    if (ret < 0) {
+        D("[ %s: cannot write descriptors ]\n", h->EP0_NAME);
+        h->control = -errno;
+        goto error;
+    }
+
+    /* write strings to control endpoint */
+    D("[ %s: writing strings ]\n", h->EP0_NAME);
+    ret = sdb_write(h->control, &strings, sizeof(strings));
+    if(ret < 0) {
+        D("[ %s: cannot write strings ]\n", h->EP0_NAME);
+        h->control = -errno;
+        goto error;
+    }
+
+    sdb_cond_signal(&h->control_notify);
+    sdb_mutex_unlock(&h->control_lock);
+
+
+    /* once configuration is passed to FunctionFS, io endpoints can be opened */
+
+    /* open output endpoint */
+    D("[ %s: opening ]\n", h->EP_OUT_NAME);
+    if ((h->bulk_out = sdb_open(h->EP_OUT_NAME, O_RDWR)) < 0) {
+        D("[ %s: cannot open bulk-out endpoint ]\n", h->EP_OUT_NAME);
+        h->bulk_out = -errno;
+        return;
+    }
+
+    /* open input endpoint */
+    D("[ %s: opening ]\n", h->EP_IN_NAME);
+    if ((h->bulk_in = sdb_open(h->EP_IN_NAME, O_RDWR)) < 0) {
+        D("[ %s: cannot open bulk-in endpoint ]\n", h->EP_IN_NAME);
+        h->bulk_in = -errno;
+        return;
+    }
+
+    return;
+
+error:
+    sdb_mutex_unlock(&h->control_lock);
+}
+
+
+void usb_cleanup()
+{
+    /* nothing to do here */
+}
+
+
+static void *usb_open_thread(void *x)
+{
+    struct usb_handle *usb = (struct usb_handle *)x;
+
+    init_functionfs(usb);
+    if (usb->control < 0 || usb->bulk_in < 0 || usb->bulk_out < 0) {
+        D("[ opening device failed ]\n");
+        return (void *)-1;
+    }
+
+    D("[ opening device succeeded ]\n");
+
+    /* wait until the USB device becomes operational */
+    sdb_mutex_lock(&usb->lock);
+    while (usb->ffs_enabled == 0)
+        sdb_cond_wait(&usb->notify, &usb->lock);
+    sdb_mutex_unlock(&usb->lock);
+
+    D("[ usb_thread - registering device ]\n");
+    register_usb_transport(usb, NULL, 1);      /* writable transport */
+
+    while (1) {
+        /* wait until the USB device needs reset */
+        D("%s: WAIT UNTIL NEEDS RESET\n", __func__);
+        sdb_mutex_lock(&usb->kick_lock);
+        while (usb->needs_kick != 1)
+            sdb_cond_wait(&usb->kick_notify, &usb->kick_lock);
+        usb->needs_kick = 0;
+        sdb_mutex_unlock(&usb->kick_lock);
+
+        /* wait until the USB device becomes operational */
+        D("%s: WAIT UNTIL OPERATIONAL\n", __func__);
+        sdb_mutex_lock(&usb->lock);
+        while (usb->ffs_enabled == 0)
+            sdb_cond_wait(&usb->notify, &usb->lock);
+        sdb_mutex_unlock(&usb->lock);
+
+        D("[ usb_thread - registering device ]\n");
+        register_usb_transport(usb, NULL, 1);  /* writable transport */
+    }
+
+    /* never gets here */
+    return 0;
+}
+
+
+/*
+ * Reads and dispatches control messages
+ *
+ * @returns cumulative size of read messages or negative error number
+ */
+static int read_control(struct usb_handle *usb)
+{
+    static const char *const names[] = {
+        [FUNCTIONFS_BIND] = "BIND",
+        [FUNCTIONFS_UNBIND] = "UNBIND",
+        [FUNCTIONFS_ENABLE] = "ENABLE",
+        [FUNCTIONFS_DISABLE] = "DISABLE",
+        [FUNCTIONFS_SETUP] = "SETUP",
+        [FUNCTIONFS_SUSPEND] = "SUSPEND",
+        [FUNCTIONFS_RESUME] = "RESUME",
+    };
+    const struct usb_functionfs_event read_event;
+    int ret;
+
+    /* Read events from control endpoint
+       Fortunately, FunctionFS guarantees reading of full event (or nothing),
+       so we're not bothered with ret < sizeof(read_event) */
+    ret = sdb_read(usb->control, &read_event, sizeof(read_event));
+    if (ret < 0) {
+        /* EAGAIN support will be useful, when non-blocking ep0 reads
+           are supported in FunctionFS */
+        if (errno == EAGAIN) {
+            sleep(1);
+            return ret;
+        }
+        perror("ep0 read after poll");
+        return ret;
+    }
+
+    /* dispatch read event */
+       switch (read_event.type) {
+               case FUNCTIONFS_RESUME:
+               case FUNCTIONFS_ENABLE:
+                       D("FFSEvent %s\n", names[read_event.type]);
+                       sdb_mutex_lock(&usb->lock);
+                       usb->ffs_enabled = 1;
+                       sdb_cond_signal(&usb->notify);
+                       sdb_mutex_unlock(&usb->lock);
+                       break;
+
+               case FUNCTIONFS_SUSPEND:
+               case FUNCTIONFS_DISABLE:
+                       D("FFSEvent %s\n", names[read_event.type]);
+                       sdb_mutex_lock(&usb->lock);
+                       usb->ffs_enabled = 0;
+                       sdb_mutex_unlock(&usb->lock);
+                       break;
+
+               case FUNCTIONFS_BIND:
+               case FUNCTIONFS_UNBIND:
+               case FUNCTIONFS_SETUP:
+                       D("FFSEvent %s\n", names[read_event.type]);
+                       break;
+
+               default:
+                       D("FFSEvent event (type=%d) is unknown -- ignored\n", read_event.type);
+                       break;
+       }
+
+    return ret;
+}
+
+
+/*
+ * Polls for control messages
+ *
+ * Calls read_control if control messages arrive
+ */
+static void *usb_read_control(void *x)
+{
+    struct usb_handle *usb = (struct usb_handle *)x;
+    struct pollfd ep0_poll[1];
+    int ret;
+
+    sdb_mutex_lock(&usb->control_lock);
+    while (usb->control == -1)
+        sdb_cond_wait(&usb->control_notify, &usb->control_lock);
+    sdb_mutex_unlock(&usb->control_lock);
+
+    while (1) {
+        ep0_poll[0].fd = usb->control;
+        ep0_poll[0].events = POLLIN;
+
+        /* In fact, polling ep0 is not yet supported in FunctionFS,
+           but we want the code to be ready for it.
+           The current approach makes no harm, because poll returns
+           immediately and we end up waiting on read (in read_control()). */
+        ret = poll(ep0_poll, 1, -1);
+        if (ret < 0) {
+            perror("poll on control endpoint");
+            continue;
+        }
+
+        if (ep0_poll[0].revents & POLLIN) {
+            ret = read_control(usb);
+        }
+    }
+
+    return 0;
+}
+
+
+/*
+ * Writes data to bulkin_fd
+ *
+ * Blocks until length data is written or error occurs.
+ *
+ * @returns amount of bytes written or -1 on failure (errno is set)
+ */
+static int bulk_write(int bulkin_fd, const void *buf, size_t length)
+{
+    size_t count = 0;
+    int ret;
+
+    do {
+        ret = sdb_write(bulkin_fd, buf + count, length - count);
+        if (ret < 0) {
+            if (errno != EINTR)
+                return ret;
+            } else
+                count += ret;
+    } while (count < length);
+
+    D("[ bulk_write done fd=%d ]\n", bulkin_fd);
+    return count;
+}
+
+
+/*
+ * Writes data to bulk_in descriptor
+ *
+ * In fact, data is forwarded to bulk_write.
+ *
+ * @returns 0 on success and -1 on failure (errno is set)
+ */
+int usb_write(usb_handle *h, const void *data, int len)
+{
+    int n;
+
+    D("about to write (fd=%d, len=%d)\n", h->bulk_in, len);
+    n = bulk_write(h->bulk_in, data, len);
+    if(n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->bulk_in, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->bulk_in);
+    return 0;
+}
+
+
+/*
+ * Reads data from bulkout_fd
+ *
+ * Blocks until length data is read or error occurs.
+ *
+ * @returns amount of bytes read or -1 on failure (errno is set)
+ */
+static int bulk_read(int bulkout_fd, void *buf, size_t length)
+{
+    size_t count = 0;
+    int ret;
+
+    do {
+        D("%d: before sdb_read...\n", getpid());
+        ret = sdb_read(bulkout_fd, buf + count, length - count);
+        D("%d: after sdb_read...\n", getpid());
+
+        if (ret < 0) {
+            if (errno != EINTR) {
+                return ret;
+            }
+               } else {
+            count += ret;
+               }
+
+    } while (count < length);
+
+    D("[ bulk_read done fd=%d ]\n", bulkout_fd);
+    return count;
+}
+
+
+/*
+ * Reads data from bulk_out descriptor
+ *
+ * In fact, reading task is forwarded to bulk_read.
+ *
+ * @returns 0 on success and -1 on failure (errno is set)
+ */
+int usb_read(usb_handle *h, void *data, int len)
+{
+    int n;
+
+    D("%d: about to read (fd=%d, len=%d)\n", getpid(), h->bulk_out, len);
+    n = bulk_read(h->bulk_out, data, len);
+    if(n != len) {
+        D("ERROR: fd = %d, n = %d, errno = %d (%s)\n",
+            h->bulk_out, n, errno, strerror(errno));
+        return -1;
+    }
+    D("[ done fd=%d ]\n", h->bulk_out);
+    return 0;
+}
+
+
+/*
+ * Checks if EP0 exists on filesystem
+ */
+int ep0_exists()
+{
+       struct stat statb;
+       return stat(ep0_path, &statb) == 0;
+}
+
+
+/*
+ * Initializes struct usb_handle with paths to endpoints
+ *
+ * Fails, if EP0 does not exist on filesystem.
+ *
+ * @returns 0 on success or -ENODEV on failure
+ */
+static int autoconfig(struct usb_handle *h)
+{
+    if (!ep0_exists()) {
+        return -ENODEV;
+    }
+
+       h->EP0_NAME = ep0_path;
+       h->EP_OUT_NAME = ep1_path;
+       h->EP_IN_NAME = ep2_path;
+    return 0;
+}
+
+
+/*
+ * Creates and starts USB threads
+ */
+void usb_init()
+{
+    usb_handle *h;
+    sdb_thread_t tid;
+
+    D("[ usb_init - using FunctionFS ]\n");
+
+    h = calloc(1, sizeof(usb_handle));
+    if (autoconfig(h) < 0) {
+        fatal_errno("[ can't recognize usb FunctionFS bulk device ]\n");
+        free(h);
+        return;
+    }
+
+    h->control = h->bulk_out = h->bulk_in = -1;
+    h->ffs_enabled = h->needs_kick = 0;
+
+    sdb_cond_init(&h->notify, 0);
+    sdb_mutex_init(&h->lock, 0);
+
+    sdb_cond_init(&h->control_notify, 0);
+    sdb_mutex_init(&h->control_lock, 0);
+
+    sdb_cond_init(&h->kick_notify, 0);
+    sdb_mutex_init(&h->kick_lock, 0);
+
+    D("[ usb_init - starting thread ]\n");
+    if(sdb_thread_create(&tid, usb_open_thread, h))
+        fatal_errno("[ cannot create usb thread ]\n");
+
+    if(sdb_thread_create(&tid, usb_read_control, h))
+        fatal_errno("[ cannot create usb read control thread ]\n");
+}
+
+
+void usb_kick(usb_handle *h)
+{
+    int err;
+
+    err = ioctl(h->bulk_in, FUNCTIONFS_CLEAR_HALT);
+    if (err < 0)
+        perror("[ reset source fd ]\n");
+
+    err = ioctl(h->bulk_out, FUNCTIONFS_CLEAR_HALT);
+    if (err < 0)
+        perror("reset sink fd");
+
+    sdb_mutex_lock(&h->kick_lock);
+    h->needs_kick = 1;
+
+    /* notify usb_open_thread that we are disconnected */
+    sdb_cond_signal(&h->kick_notify);
+    sdb_mutex_unlock(&h->kick_lock);
+}
+
+
+int usb_close(usb_handle *h)
+{
+    return 0;
+}