| /* |
| * libiio - Library for interfacing industrial I/O (IIO) devices |
| * |
| * Copyright (C) 2016 Analog Devices, Inc. |
| * Author: Paul Cercueil <[email protected]> |
| * |
| * 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; |
| } |