| /* | |
| * 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; | |
| } |