387 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			387 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			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;
 | |
| }
 |