root/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c
/*
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * For more information, please refer to <http://unlicense.org/>
 */

/* $(CROSS_COMPILE)cc -g -o aio_simple aio_simple.c -laio */

#define _DEFAULT_SOURCE /* for endian.h */

#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/poll.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/eventfd.h>

#include "libaio.h"
#define IOCB_FLAG_RESFD         (1 << 0)

#include <linux/usb/functionfs.h>

#define BUF_LEN         8192

/*
 * cpu_to_le16/32 are used when initializing structures, a context where a
 * function call is not allowed. To solve this, we code cpu_to_le16/32 in a way
 * that allows them to be used when initializing structures.
 */

#if BYTE_ORDER == __LITTLE_ENDIAN
#define cpu_to_le16(x)  (x)
#define cpu_to_le32(x)  (x)
#else
#define cpu_to_le16(x)  ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8))
#define cpu_to_le32(x)  \
        ((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >>  8) | \
        (((x) & 0x0000ff00u) <<  8) | (((x) & 0x000000ffu) << 24))
#endif

/******************** Descriptors and Strings *******************************/

static const struct {
        struct usb_functionfs_descs_head_v2 header;
        __le32 fs_count;
        __le32 hs_count;
        struct {
                struct usb_interface_descriptor intf;
                struct usb_endpoint_descriptor_no_audio bulk_sink;
                struct usb_endpoint_descriptor_no_audio bulk_source;
        } __attribute__ ((__packed__)) fs_descs, hs_descs;
} __attribute__ ((__packed__)) descriptors = {
        .header = {
                .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2),
                .flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC |
                                     FUNCTIONFS_HAS_HS_DESC),
                .length = cpu_to_le32(sizeof(descriptors)),
        },
        .fs_count = cpu_to_le32(3),
        .fs_descs = {
                .intf = {
                        .bLength = sizeof(descriptors.fs_descs.intf),
                        .bDescriptorType = USB_DT_INTERFACE,
                        .bNumEndpoints = 2,
                        .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
                        .iInterface = 1,
                },
                .bulk_sink = {
                        .bLength = sizeof(descriptors.fs_descs.bulk_sink),
                        .bDescriptorType = USB_DT_ENDPOINT,
                        .bEndpointAddress = 1 | USB_DIR_IN,
                        .bmAttributes = USB_ENDPOINT_XFER_BULK,
                },
                .bulk_source = {
                        .bLength = sizeof(descriptors.fs_descs.bulk_source),
                        .bDescriptorType = USB_DT_ENDPOINT,
                        .bEndpointAddress = 2 | USB_DIR_OUT,
                        .bmAttributes = USB_ENDPOINT_XFER_BULK,
                },
        },
        .hs_count = cpu_to_le32(3),
        .hs_descs = {
                .intf = {
                        .bLength = sizeof(descriptors.hs_descs.intf),
                        .bDescriptorType = USB_DT_INTERFACE,
                        .bNumEndpoints = 2,
                        .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
                        .iInterface = 1,
                },
                .bulk_sink = {
                        .bLength = sizeof(descriptors.hs_descs.bulk_sink),
                        .bDescriptorType = USB_DT_ENDPOINT,
                        .bEndpointAddress = 1 | USB_DIR_IN,
                        .bmAttributes = USB_ENDPOINT_XFER_BULK,
                        .wMaxPacketSize = cpu_to_le16(512),
                },
                .bulk_source = {
                        .bLength = sizeof(descriptors.hs_descs.bulk_source),
                        .bDescriptorType = USB_DT_ENDPOINT,
                        .bEndpointAddress = 2 | USB_DIR_OUT,
                        .bmAttributes = USB_ENDPOINT_XFER_BULK,
                        .wMaxPacketSize = cpu_to_le16(512),
                },
        },
};

#define STR_INTERFACE "AIO Test"

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,
        },
};

/******************** Endpoints handling *******************************/

static void display_event(struct usb_functionfs_event *event)
{
        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",
        };
        switch (event->type) {
        case FUNCTIONFS_BIND:
        case FUNCTIONFS_UNBIND:
        case FUNCTIONFS_ENABLE:
        case FUNCTIONFS_DISABLE:
        case FUNCTIONFS_SETUP:
        case FUNCTIONFS_SUSPEND:
        case FUNCTIONFS_RESUME:
                printf("Event %s\n", names[event->type]);
        }
}

static void handle_ep0(int ep0, bool *ready)
{
        struct usb_functionfs_event event;
        int ret;

        struct pollfd pfds[1];
        pfds[0].fd = ep0;
        pfds[0].events = POLLIN;

        ret = poll(pfds, 1, 0);

        if (ret && (pfds[0].revents & POLLIN)) {
                ret = read(ep0, &event, sizeof(event));
                if (!ret) {
                        perror("unable to read event from ep0");
                        return;
                }
                display_event(&event);
                switch (event.type) {
                case FUNCTIONFS_SETUP:
                        if (event.u.setup.bRequestType & USB_DIR_IN)
                                write(ep0, NULL, 0);
                        else
                                read(ep0, NULL, 0);
                        break;

                case FUNCTIONFS_ENABLE:
                        *ready = true;
                        break;

                case FUNCTIONFS_DISABLE:
                        *ready = false;
                        break;

                default:
                        break;
                }
        }
}

int main(int argc, char *argv[])
{
        int i, ret;
        char *ep_path;

        int ep0;
        int ep[2];

        io_context_t ctx;

        int evfd;
        fd_set rfds;

        char *buf_in, *buf_out;
        struct iocb *iocb_in, *iocb_out;
        int req_in = 0, req_out = 0;
        bool ready;

        if (argc != 2) {
                printf("ffs directory not specified!\n");
                return 1;
        }

        ep_path = malloc(strlen(argv[1]) + 4 /* "/ep#" */ + 1 /* '\0' */);
        if (!ep_path) {
                perror("malloc");
                return 1;
        }

        /* open endpoint files */
        sprintf(ep_path, "%s/ep0", argv[1]);
        ep0 = open(ep_path, O_RDWR);
        if (ep0 < 0) {
                perror("unable to open ep0");
                return 1;
        }
        if (write(ep0, &descriptors, sizeof(descriptors)) < 0) {
                perror("unable do write descriptors");
                return 1;
        }
        if (write(ep0, &strings, sizeof(strings)) < 0) {
                perror("unable to write strings");
                return 1;
        }
        for (i = 0; i < 2; ++i) {
                sprintf(ep_path, "%s/ep%d", argv[1], i+1);
                ep[i] = open(ep_path, O_RDWR);
                if (ep[i] < 0) {
                        printf("unable to open ep%d: %s\n", i+1,
                               strerror(errno));
                        return 1;
                }
        }

        free(ep_path);

        memset(&ctx, 0, sizeof(ctx));
        /* setup aio context to handle up to 2 requests */
        if (io_setup(2, &ctx) < 0) {
                perror("unable to setup aio");
                return 1;
        }

        evfd = eventfd(0, 0);
        if (evfd < 0) {
                perror("unable to open eventfd");
                return 1;
        }

        /* alloc buffers and requests */
        buf_in = malloc(BUF_LEN);
        buf_out = malloc(BUF_LEN);
        iocb_in = malloc(sizeof(*iocb_in));
        iocb_out = malloc(sizeof(*iocb_out));

        while (1) {
                FD_ZERO(&rfds);
                FD_SET(ep0, &rfds);
                FD_SET(evfd, &rfds);

                ret = select(((ep0 > evfd) ? ep0 : evfd)+1,
                             &rfds, NULL, NULL, NULL);
                if (ret < 0) {
                        if (errno == EINTR)
                                continue;
                        perror("select");
                        break;
                }

                if (FD_ISSET(ep0, &rfds))
                        handle_ep0(ep0, &ready);

                /* we are waiting for function ENABLE */
                if (!ready)
                        continue;

                /* if something was submitted we wait for event */
                if (FD_ISSET(evfd, &rfds)) {
                        uint64_t ev_cnt;
                        ret = read(evfd, &ev_cnt, sizeof(ev_cnt));
                        if (ret < 0) {
                                perror("unable to read eventfd");
                                break;
                        }

                        struct io_event e[2];
                        /* we wait for one event */
                        ret = io_getevents(ctx, 1, 2, e, NULL);
                        /* if we got event */
                        for (i = 0; i < ret; ++i) {
                                if (e[i].obj->aio_fildes == ep[0]) {
                                        printf("ev=in; ret=%lu\n", e[i].res);
                                        req_in = 0;
                                } else if (e[i].obj->aio_fildes == ep[1]) {
                                        printf("ev=out; ret=%lu\n", e[i].res);
                                        req_out = 0;
                                }
                        }
                }

                if (!req_in) { /* if IN transfer not requested*/
                        /* prepare write request */
                        io_prep_pwrite(iocb_in, ep[0], buf_in, BUF_LEN, 0);
                        /* enable eventfd notification */
                        iocb_in->u.c.flags |= IOCB_FLAG_RESFD;
                        iocb_in->u.c.resfd = evfd;
                        /* submit table of requests */
                        ret = io_submit(ctx, 1, &iocb_in);
                        if (ret >= 0) { /* if ret > 0 request is queued */
                                req_in = 1;
                                printf("submit: in\n");
                        } else
                                perror("unable to submit request");
                }
                if (!req_out) { /* if OUT transfer not requested */
                        /* prepare read request */
                        io_prep_pread(iocb_out, ep[1], buf_out, BUF_LEN, 0);
                        /* enable eventfs notification */
                        iocb_out->u.c.flags |= IOCB_FLAG_RESFD;
                        iocb_out->u.c.resfd = evfd;
                        /* submit table of requests */
                        ret = io_submit(ctx, 1, &iocb_out);
                        if (ret >= 0) { /* if ret > 0 request is queued */
                                req_out = 1;
                                printf("submit: out\n");
                        } else
                                perror("unable to submit request");
                }
        }

        /* free resources */

        io_destroy(ctx);

        free(buf_in);
        free(buf_out);
        free(iocb_in);
        free(iocb_out);

        for (i = 0; i < 2; ++i)
                close(ep[i]);
        close(ep0);

        return 0;
}