blob: 0efbfbf03c738edeb247c96aa160b13c6ea72bd6 [file] [log] [blame]
/*
* 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 (!cpdata)
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;
}