summaryrefslogtreecommitdiff
path: root/iiod/usbd.c
diff options
context:
space:
mode:
Diffstat (limited to 'iiod/usbd.c')
-rw-r--r--iiod/usbd.c408
1 files changed, 408 insertions, 0 deletions
diff --git a/iiod/usbd.c b/iiod/usbd.c
new file mode 100644
index 0000000..ea58c79
--- /dev/null
+++ b/iiod/usbd.c
@@ -0,0 +1,408 @@
+/*
+ * libiio - Library for interfacing industrial I/O (IIO) devices
+ *
+ * Copyright (C) 2016 Analog Devices, Inc.
+ * Author: Paul Cercueil <paul.cercueil@analog.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ */
+
+#include "../debug.h"
+#include "../iio-private.h"
+#include "ops.h"
+#include "thread-pool.h"
+
+#include <fcntl.h>
+#include <linux/usb/functionfs.h>
+#include <stdint.h>
+#include <string.h>
+
+/* u8"IIO" for non-c11 compilers */
+#define NAME "\x0049\x0049\x004F"
+
+#define LE32(x) ((__BYTE_ORDER != __BIG_ENDIAN) ? (x) : __bswap_constant_32(x))
+#define LE16(x) ((__BYTE_ORDER != __BIG_ENDIAN) ? (x) : __bswap_constant_16(x))
+
+#define IIO_USD_CMD_RESET_PIPES 0
+#define IIO_USD_CMD_OPEN_PIPE 1
+#define IIO_USD_CMD_CLOSE_PIPE 2
+
+
+struct usb_ffs_header {
+ struct usb_functionfs_descs_head_v2 header;
+ uint32_t nb_fs, nb_hs, nb_ss;
+} __attribute__((packed));
+
+struct usb_ffs_strings {
+ struct usb_functionfs_strings_head head;
+ uint16_t lang;
+ const char string[sizeof(NAME)];
+} __attribute__((packed));
+
+struct usbd_pdata {
+ struct iio_context *ctx;
+ char *ffs;
+ int ep0_fd;
+ bool debug, use_aio;
+ struct thread_pool **pool;
+ unsigned int nb_pipes;
+};
+
+struct usbd_client_pdata {
+ struct usbd_pdata *pdata;
+ int ep_in, ep_out;
+};
+
+static const struct usb_ffs_strings ffs_strings = {
+ .head = {
+ .magic = LE32(FUNCTIONFS_STRINGS_MAGIC),
+ .length = LE32(sizeof(ffs_strings)),
+ .str_count = LE32(1),
+ .lang_count = LE32(1),
+ },
+
+ .lang = LE16(0x409),
+ .string = NAME,
+};
+
+static void usbd_client_thread(struct thread_pool *pool, void *d)
+{
+ struct usbd_client_pdata *pdata = d;
+
+ interpreter(pdata->pdata->ctx, pdata->ep_in, pdata->ep_out,
+ pdata->pdata->debug, false,
+ pdata->pdata->use_aio, pool);
+
+ close(pdata->ep_in);
+ close(pdata->ep_out);
+ free(pdata);
+}
+
+static int usb_open_pipe(struct usbd_pdata *pdata, unsigned int pipe_id)
+{
+ struct usbd_client_pdata *cpdata;
+ char buf[256];
+ int err;
+
+ if (pipe_id >= pdata->nb_pipes)
+ return -EINVAL;
+
+ cpdata = malloc(sizeof(*cpdata));
+ if (!pdata)
+ return -ENOMEM;
+
+ /* Either we open this pipe for the first time, or it was closed before.
+ * In that case we called thread_pool_stop() without waiting for all the
+ * threads to finish. We do that here. Since the running thread might still
+ * have a open handle to the endpoints make sure that they have exited
+ * before opening the endpoints again. */
+ thread_pool_stop_and_wait(pdata->pool[pipe_id]);
+
+ snprintf(buf, sizeof(buf), "%s/ep%u", pdata->ffs, pipe_id * 2 + 1);
+ cpdata->ep_out = open(buf, O_WRONLY);
+ if (cpdata->ep_out < 0) {
+ err = -errno;
+ goto err_free_cpdata;
+ }
+
+ snprintf(buf, sizeof(buf), "%s/ep%u", pdata->ffs, pipe_id * 2 + 2);
+ cpdata->ep_in = open(buf, O_RDONLY);
+ if (cpdata->ep_in < 0) {
+ err = -errno;
+ goto err_close_ep_out;
+ }
+
+ cpdata->pdata = pdata;
+
+ err = thread_pool_add_thread(pdata->pool[pipe_id],
+ usbd_client_thread, cpdata, "usbd_client_thd");
+ if (!err)
+ return 0;
+
+ close(cpdata->ep_in);
+err_close_ep_out:
+ close(cpdata->ep_out);
+err_free_cpdata:
+ free(cpdata);
+ return err;
+}
+
+static int usb_close_pipe(struct usbd_pdata *pdata, unsigned int pipe_id)
+{
+ if (pipe_id >= pdata->nb_pipes)
+ return -EINVAL;
+
+ thread_pool_stop(pdata->pool[pipe_id]);
+ return 0;
+}
+
+static void usb_close_pipes(struct usbd_pdata *pdata)
+{
+ unsigned int i;
+
+ for (i = 0; i < pdata->nb_pipes; i++)
+ usb_close_pipe(pdata, i);
+}
+
+static int handle_event(struct usbd_pdata *pdata,
+ const struct usb_functionfs_event *event)
+{
+ int ret = 0;
+
+ if (event->type == FUNCTIONFS_SETUP) {
+ const struct usb_ctrlrequest *req = &event->u.setup;
+
+ switch (req->bRequest) {
+ case IIO_USD_CMD_RESET_PIPES:
+ usb_close_pipes(pdata);
+ break;
+ case IIO_USD_CMD_OPEN_PIPE:
+ ret = usb_open_pipe(pdata, le16toh(req->wValue));
+ break;
+ case IIO_USD_CMD_CLOSE_PIPE:
+ ret = usb_close_pipe(pdata, le16toh(req->wValue));
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void usbd_main(struct thread_pool *pool, void *d)
+{
+ int stop_fd = thread_pool_get_poll_fd(pool);
+ struct usbd_pdata *pdata = d;
+ unsigned int i;
+
+ for (;;) {
+ struct usb_functionfs_event event;
+ struct pollfd pfd[2];
+ int ret;
+
+ pfd[0].fd = pdata->ep0_fd;
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+ pfd[1].fd = stop_fd;
+ pfd[1].events = POLLIN;
+ pfd[1].revents = 0;
+
+ poll_nointr(pfd, 2);
+
+ if (pfd[1].revents & POLLIN) /* STOP event */
+ break;
+
+ if (!(pfd[0].revents & POLLIN)) /* Should never happen. */
+ continue;
+
+ ret = read(pdata->ep0_fd, &event, sizeof(event));
+ if (ret != sizeof(event)) {
+ WARNING("Short read!\n");
+ continue;
+ }
+
+ ret = handle_event(pdata, &event);
+ if (ret) {
+ ERROR("Unable to handle event: %i\n", ret);
+ break;
+ }
+
+ /* Clear out the errors on ep0 when we close endpoints */
+ ret = read(pdata->ep0_fd, NULL, 0);
+ }
+
+ for (i = 0; i < pdata->nb_pipes; i++) {
+ thread_pool_stop_and_wait(pdata->pool[i]);
+ thread_pool_destroy(pdata->pool[i]);
+ }
+
+ close(pdata->ep0_fd);
+ free(pdata->ffs);
+ free(pdata->pool);
+ free(pdata);
+}
+
+static struct usb_ffs_header * create_header(
+ unsigned int nb_pipes, uint32_t size)
+{
+ /* Packet sizes for USB high-speed, full-speed, super-speed */
+ const unsigned int packet_sizes[3] = { 64, 512, 1024, };
+ struct usb_ffs_header *hdr;
+ unsigned int i, pipe_id;
+ uintptr_t ptr;
+
+ hdr = zalloc(size);
+ if (!hdr) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ hdr->header.magic = LE32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2);
+ hdr->header.length = htole32(size);
+ hdr->header.flags = LE32(FUNCTIONFS_HAS_FS_DESC |
+ FUNCTIONFS_HAS_HS_DESC |
+ FUNCTIONFS_HAS_SS_DESC);
+
+ hdr->nb_fs = htole32(nb_pipes * 2 + 1);
+ hdr->nb_hs = htole32(nb_pipes * 2 + 1);
+ hdr->nb_ss = htole32(nb_pipes * 4 + 1);
+
+ ptr = ((uintptr_t) hdr) + sizeof(*hdr);
+
+ for (i = 0; i < 3; i++) {
+ struct usb_interface_descriptor *desc =
+ (struct usb_interface_descriptor *) ptr;
+ struct usb_endpoint_descriptor_no_audio *ep;
+ struct usb_ss_ep_comp_descriptor *comp;
+
+ desc->bLength = sizeof(*desc);
+ desc->bDescriptorType = USB_DT_INTERFACE;
+ desc->bNumEndpoints = nb_pipes * 2;
+ desc->bInterfaceClass = USB_CLASS_COMM;
+ desc->iInterface = 1;
+
+ ep = (struct usb_endpoint_descriptor_no_audio *)
+ (ptr + sizeof(*desc));
+
+ for (pipe_id = 0; pipe_id < nb_pipes; pipe_id++) {
+ ep->bLength = sizeof(*ep);
+ ep->bDescriptorType = USB_DT_ENDPOINT;
+ ep->bEndpointAddress = (pipe_id + 1) | USB_DIR_IN;
+ ep->bmAttributes = USB_ENDPOINT_XFER_BULK;
+ ep->wMaxPacketSize = htole16(packet_sizes[i]);
+ ep++;
+
+ if (i == 2) {
+ comp = (struct usb_ss_ep_comp_descriptor *) ep;
+ comp->bLength = USB_DT_SS_EP_COMP_SIZE;
+ comp->bDescriptorType = USB_DT_SS_ENDPOINT_COMP;
+ comp++;
+ ep = (struct usb_endpoint_descriptor_no_audio *) comp;
+ }
+
+ ep->bLength = sizeof(*ep);
+ ep->bDescriptorType = USB_DT_ENDPOINT;
+ ep->bEndpointAddress = (pipe_id + 1) | USB_DIR_OUT;
+ ep->bmAttributes = USB_ENDPOINT_XFER_BULK;
+ ep->wMaxPacketSize = htole16(packet_sizes[i]);
+ ep++;
+
+ if (i == 2) {
+ comp = (struct usb_ss_ep_comp_descriptor *) ep;
+ comp->bLength = USB_DT_SS_EP_COMP_SIZE;
+ comp->bDescriptorType = USB_DT_SS_ENDPOINT_COMP;
+ comp++;
+ ep = (struct usb_endpoint_descriptor_no_audio *) comp;
+ }
+ }
+
+ ptr += sizeof(*desc) + nb_pipes * 2 * sizeof(*ep);
+ }
+
+ return hdr;
+}
+
+static int write_header(int fd, unsigned int nb_pipes)
+{
+ uint32_t size = sizeof(struct usb_ffs_header) +
+ 3 * sizeof(struct usb_interface_descriptor) +
+ 6 * nb_pipes * sizeof(struct usb_endpoint_descriptor_no_audio) +
+ 2 * nb_pipes * sizeof(struct usb_ss_ep_comp_descriptor);
+ struct usb_ffs_header *hdr;
+ int ret;
+
+ hdr = create_header(nb_pipes, size);
+ if (!hdr)
+ return -errno;
+
+ ret = write(fd, hdr, size);
+ free(hdr);
+ if (ret < 0)
+ return -errno;
+
+ ret = write(fd, &ffs_strings, sizeof(ffs_strings));
+ if (ret < 0)
+ return -errno;
+
+ return 0;
+}
+
+int start_usb_daemon(struct iio_context *ctx, const char *ffs,
+ bool debug, bool use_aio, unsigned int nb_pipes,
+ struct thread_pool *pool)
+{
+ struct usbd_pdata *pdata;
+ unsigned int i;
+ char buf[256];
+ int ret;
+
+ pdata = zalloc(sizeof(*pdata));
+ if (!pdata)
+ return -ENOMEM;
+
+ pdata->nb_pipes = nb_pipes;
+
+ pdata->pool = calloc(nb_pipes, sizeof(*pdata->pool));
+ if (!pdata->pool) {
+ ret = -ENOMEM;
+ goto err_free_pdata;
+ }
+
+ pdata->ffs = strdup(ffs);
+ if (!pdata->ffs) {
+ ret = -ENOMEM;
+ goto err_free_pdata_pool;
+ }
+
+ snprintf(buf, sizeof(buf), "%s/ep0", ffs);
+
+ pdata->ep0_fd = open(buf, O_RDWR);
+ if (pdata->ep0_fd < 0) {
+ ret = -errno;
+ goto err_free_ffs;
+ }
+
+ ret = write_header(pdata->ep0_fd, nb_pipes);
+ if (ret < 0)
+ goto err_close_ep0;
+
+ for (i = 0; i < nb_pipes; i++) {
+ pdata->pool[i] = thread_pool_new();
+ if (!pdata->pool[i]) {
+ ret = -errno;
+ goto err_free_pools;
+ }
+ }
+
+ pdata->ctx = ctx;
+ pdata->debug = debug;
+ pdata->use_aio = use_aio;
+
+ ret = thread_pool_add_thread(pool, usbd_main, pdata, "usbd_main_thd");
+ if (!ret)
+ return 0;
+
+err_free_pools:
+ /* If we get here, usbd_main was not started, so the pools can be
+ * destroyed directly */
+ for (i = 0; i < nb_pipes; i++) {
+ if (pdata->pool[i])
+ thread_pool_destroy(pdata->pool[i]);
+ }
+err_close_ep0:
+ close(pdata->ep0_fd);
+err_free_ffs:
+ free(pdata->ffs);
+err_free_pdata_pool:
+ free(pdata->pool);
+err_free_pdata:
+ free(pdata);
+ return ret;
+}