1 | /* |
2 | * This is free and unencumbered software released into the public domain. |
3 | * |
4 | * Anyone is free to copy, modify, publish, use, compile, sell, or |
5 | * distribute this software, either in source code form or as a compiled |
6 | * binary, for any purpose, commercial or non-commercial, and by any |
7 | * means. |
8 | * |
9 | * In jurisdictions that recognize copyright laws, the author or authors |
10 | * of this software dedicate any and all copyright interest in the |
11 | * software to the public domain. We make this dedication for the benefit |
12 | * of the public at large and to the detriment of our heirs and |
13 | * successors. We intend this dedication to be an overt act of |
14 | * relinquishment in perpetuity of all present and future rights to this |
15 | * software under copyright law. |
16 | * |
17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
20 | * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
21 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
22 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
23 | * OTHER DEALINGS IN THE SOFTWARE. |
24 | * |
25 | * For more information, please refer to <http://unlicense.org/> |
26 | */ |
27 | |
28 | /* $(CROSS_COMPILE)cc -g -o aio_simple aio_simple.c -laio */ |
29 | |
30 | #define _DEFAULT_SOURCE /* for endian.h */ |
31 | |
32 | #include <endian.h> |
33 | #include <errno.h> |
34 | #include <fcntl.h> |
35 | #include <stdarg.h> |
36 | #include <stdio.h> |
37 | #include <stdlib.h> |
38 | #include <string.h> |
39 | #include <sys/ioctl.h> |
40 | #include <sys/stat.h> |
41 | #include <sys/types.h> |
42 | #include <sys/poll.h> |
43 | #include <unistd.h> |
44 | #include <stdbool.h> |
45 | #include <sys/eventfd.h> |
46 | |
47 | #include "libaio.h" |
48 | #define IOCB_FLAG_RESFD (1 << 0) |
49 | |
50 | #include <linux/usb/functionfs.h> |
51 | |
52 | #define BUF_LEN 8192 |
53 | |
54 | /* |
55 | * cpu_to_le16/32 are used when initializing structures, a context where a |
56 | * function call is not allowed. To solve this, we code cpu_to_le16/32 in a way |
57 | * that allows them to be used when initializing structures. |
58 | */ |
59 | |
60 | #if BYTE_ORDER == __LITTLE_ENDIAN |
61 | #define cpu_to_le16(x) (x) |
62 | #define cpu_to_le32(x) (x) |
63 | #else |
64 | #define cpu_to_le16(x) ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8)) |
65 | #define cpu_to_le32(x) \ |
66 | ((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \ |
67 | (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24)) |
68 | #endif |
69 | |
70 | /******************** Descriptors and Strings *******************************/ |
71 | |
72 | static const struct { |
73 | struct usb_functionfs_descs_head_v2 ; |
74 | __le32 fs_count; |
75 | __le32 hs_count; |
76 | struct { |
77 | struct usb_interface_descriptor intf; |
78 | struct usb_endpoint_descriptor_no_audio bulk_sink; |
79 | struct usb_endpoint_descriptor_no_audio bulk_source; |
80 | } __attribute__ ((__packed__)) fs_descs, hs_descs; |
81 | } __attribute__ ((__packed__)) descriptors = { |
82 | .header = { |
83 | .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), |
84 | .flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC | |
85 | FUNCTIONFS_HAS_HS_DESC), |
86 | .length = cpu_to_le32(sizeof(descriptors)), |
87 | }, |
88 | .fs_count = cpu_to_le32(3), |
89 | .fs_descs = { |
90 | .intf = { |
91 | .bLength = sizeof(descriptors.fs_descs.intf), |
92 | .bDescriptorType = USB_DT_INTERFACE, |
93 | .bNumEndpoints = 2, |
94 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, |
95 | .iInterface = 1, |
96 | }, |
97 | .bulk_sink = { |
98 | .bLength = sizeof(descriptors.fs_descs.bulk_sink), |
99 | .bDescriptorType = USB_DT_ENDPOINT, |
100 | .bEndpointAddress = 1 | USB_DIR_IN, |
101 | .bmAttributes = USB_ENDPOINT_XFER_BULK, |
102 | }, |
103 | .bulk_source = { |
104 | .bLength = sizeof(descriptors.fs_descs.bulk_source), |
105 | .bDescriptorType = USB_DT_ENDPOINT, |
106 | .bEndpointAddress = 2 | USB_DIR_OUT, |
107 | .bmAttributes = USB_ENDPOINT_XFER_BULK, |
108 | }, |
109 | }, |
110 | .hs_count = cpu_to_le32(3), |
111 | .hs_descs = { |
112 | .intf = { |
113 | .bLength = sizeof(descriptors.hs_descs.intf), |
114 | .bDescriptorType = USB_DT_INTERFACE, |
115 | .bNumEndpoints = 2, |
116 | .bInterfaceClass = USB_CLASS_VENDOR_SPEC, |
117 | .iInterface = 1, |
118 | }, |
119 | .bulk_sink = { |
120 | .bLength = sizeof(descriptors.hs_descs.bulk_sink), |
121 | .bDescriptorType = USB_DT_ENDPOINT, |
122 | .bEndpointAddress = 1 | USB_DIR_IN, |
123 | .bmAttributes = USB_ENDPOINT_XFER_BULK, |
124 | .wMaxPacketSize = cpu_to_le16(512), |
125 | }, |
126 | .bulk_source = { |
127 | .bLength = sizeof(descriptors.hs_descs.bulk_source), |
128 | .bDescriptorType = USB_DT_ENDPOINT, |
129 | .bEndpointAddress = 2 | USB_DIR_OUT, |
130 | .bmAttributes = USB_ENDPOINT_XFER_BULK, |
131 | .wMaxPacketSize = cpu_to_le16(512), |
132 | }, |
133 | }, |
134 | }; |
135 | |
136 | #define STR_INTERFACE "AIO Test" |
137 | |
138 | static const struct { |
139 | struct usb_functionfs_strings_head ; |
140 | struct { |
141 | __le16 code; |
142 | const char str1[sizeof(STR_INTERFACE)]; |
143 | } __attribute__ ((__packed__)) lang0; |
144 | } __attribute__ ((__packed__)) strings = { |
145 | .header = { |
146 | .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC), |
147 | .length = cpu_to_le32(sizeof(strings)), |
148 | .str_count = cpu_to_le32(1), |
149 | .lang_count = cpu_to_le32(1), |
150 | }, |
151 | .lang0 = { |
152 | cpu_to_le16(0x0409), /* en-us */ |
153 | STR_INTERFACE, |
154 | }, |
155 | }; |
156 | |
157 | /******************** Endpoints handling *******************************/ |
158 | |
159 | static void display_event(struct usb_functionfs_event *event) |
160 | { |
161 | static const char *const names[] = { |
162 | [FUNCTIONFS_BIND] = "BIND" , |
163 | [FUNCTIONFS_UNBIND] = "UNBIND" , |
164 | [FUNCTIONFS_ENABLE] = "ENABLE" , |
165 | [FUNCTIONFS_DISABLE] = "DISABLE" , |
166 | [FUNCTIONFS_SETUP] = "SETUP" , |
167 | [FUNCTIONFS_SUSPEND] = "SUSPEND" , |
168 | [FUNCTIONFS_RESUME] = "RESUME" , |
169 | }; |
170 | switch (event->type) { |
171 | case FUNCTIONFS_BIND: |
172 | case FUNCTIONFS_UNBIND: |
173 | case FUNCTIONFS_ENABLE: |
174 | case FUNCTIONFS_DISABLE: |
175 | case FUNCTIONFS_SETUP: |
176 | case FUNCTIONFS_SUSPEND: |
177 | case FUNCTIONFS_RESUME: |
178 | printf("Event %s\n" , names[event->type]); |
179 | } |
180 | } |
181 | |
182 | static void handle_ep0(int ep0, bool *ready) |
183 | { |
184 | struct usb_functionfs_event event; |
185 | int ret; |
186 | |
187 | struct pollfd pfds[1]; |
188 | pfds[0].fd = ep0; |
189 | pfds[0].events = POLLIN; |
190 | |
191 | ret = poll(pfds, 1, 0); |
192 | |
193 | if (ret && (pfds[0].revents & POLLIN)) { |
194 | ret = read(ep0, &event, sizeof(event)); |
195 | if (!ret) { |
196 | perror("unable to read event from ep0" ); |
197 | return; |
198 | } |
199 | display_event(event: &event); |
200 | switch (event.type) { |
201 | case FUNCTIONFS_SETUP: |
202 | if (event.u.setup.bRequestType & USB_DIR_IN) |
203 | write(ep0, NULL, 0); |
204 | else |
205 | read(ep0, NULL, 0); |
206 | break; |
207 | |
208 | case FUNCTIONFS_ENABLE: |
209 | *ready = true; |
210 | break; |
211 | |
212 | case FUNCTIONFS_DISABLE: |
213 | *ready = false; |
214 | break; |
215 | |
216 | default: |
217 | break; |
218 | } |
219 | } |
220 | } |
221 | |
222 | int main(int argc, char *argv[]) |
223 | { |
224 | int i, ret; |
225 | char *ep_path; |
226 | |
227 | int ep0; |
228 | int ep[2]; |
229 | |
230 | io_context_t ctx; |
231 | |
232 | int evfd; |
233 | fd_set rfds; |
234 | |
235 | char *buf_in, *buf_out; |
236 | struct iocb *iocb_in, *iocb_out; |
237 | int req_in = 0, req_out = 0; |
238 | bool ready; |
239 | |
240 | if (argc != 2) { |
241 | printf("ffs directory not specified!\n" ); |
242 | return 1; |
243 | } |
244 | |
245 | ep_path = malloc(strlen(argv[1]) + 4 /* "/ep#" */ + 1 /* '\0' */); |
246 | if (!ep_path) { |
247 | perror("malloc" ); |
248 | return 1; |
249 | } |
250 | |
251 | /* open endpoint files */ |
252 | sprintf(ep_path, "%s/ep0" , argv[1]); |
253 | ep0 = open(ep_path, O_RDWR); |
254 | if (ep0 < 0) { |
255 | perror("unable to open ep0" ); |
256 | return 1; |
257 | } |
258 | if (write(ep0, &descriptors, sizeof(descriptors)) < 0) { |
259 | perror("unable do write descriptors" ); |
260 | return 1; |
261 | } |
262 | if (write(ep0, &strings, sizeof(strings)) < 0) { |
263 | perror("unable to write strings" ); |
264 | return 1; |
265 | } |
266 | for (i = 0; i < 2; ++i) { |
267 | sprintf(ep_path, "%s/ep%d" , argv[1], i+1); |
268 | ep[i] = open(ep_path, O_RDWR); |
269 | if (ep[i] < 0) { |
270 | printf("unable to open ep%d: %s\n" , i+1, |
271 | strerror(errno)); |
272 | return 1; |
273 | } |
274 | } |
275 | |
276 | free(ep_path); |
277 | |
278 | memset(&ctx, 0, sizeof(ctx)); |
279 | /* setup aio context to handle up to 2 requests */ |
280 | if (io_setup(2, &ctx) < 0) { |
281 | perror("unable to setup aio" ); |
282 | return 1; |
283 | } |
284 | |
285 | evfd = eventfd(0, 0); |
286 | if (evfd < 0) { |
287 | perror("unable to open eventfd" ); |
288 | return 1; |
289 | } |
290 | |
291 | /* alloc buffers and requests */ |
292 | buf_in = malloc(BUF_LEN); |
293 | buf_out = malloc(BUF_LEN); |
294 | iocb_in = malloc(sizeof(*iocb_in)); |
295 | iocb_out = malloc(sizeof(*iocb_out)); |
296 | |
297 | while (1) { |
298 | FD_ZERO(&rfds); |
299 | FD_SET(ep0, &rfds); |
300 | FD_SET(evfd, &rfds); |
301 | |
302 | ret = select(((ep0 > evfd) ? ep0 : evfd)+1, |
303 | &rfds, NULL, NULL, NULL); |
304 | if (ret < 0) { |
305 | if (errno == EINTR) |
306 | continue; |
307 | perror("select" ); |
308 | break; |
309 | } |
310 | |
311 | if (FD_ISSET(ep0, &rfds)) |
312 | handle_ep0(ep0, ready: &ready); |
313 | |
314 | /* we are waiting for function ENABLE */ |
315 | if (!ready) |
316 | continue; |
317 | |
318 | /* if something was submitted we wait for event */ |
319 | if (FD_ISSET(evfd, &rfds)) { |
320 | uint64_t ev_cnt; |
321 | ret = read(evfd, &ev_cnt, sizeof(ev_cnt)); |
322 | if (ret < 0) { |
323 | perror("unable to read eventfd" ); |
324 | break; |
325 | } |
326 | |
327 | struct io_event e[2]; |
328 | /* we wait for one event */ |
329 | ret = io_getevents(ctx, 1, 2, e, NULL); |
330 | /* if we got event */ |
331 | for (i = 0; i < ret; ++i) { |
332 | if (e[i].obj->aio_fildes == ep[0]) { |
333 | printf("ev=in; ret=%lu\n" , e[i].res); |
334 | req_in = 0; |
335 | } else if (e[i].obj->aio_fildes == ep[1]) { |
336 | printf("ev=out; ret=%lu\n" , e[i].res); |
337 | req_out = 0; |
338 | } |
339 | } |
340 | } |
341 | |
342 | if (!req_in) { /* if IN transfer not requested*/ |
343 | /* prepare write request */ |
344 | io_prep_pwrite(iocb_in, ep[0], buf_in, BUF_LEN, 0); |
345 | /* enable eventfd notification */ |
346 | iocb_in->u.c.flags |= IOCB_FLAG_RESFD; |
347 | iocb_in->u.c.resfd = evfd; |
348 | /* submit table of requests */ |
349 | ret = io_submit(ctx, 1, &iocb_in); |
350 | if (ret >= 0) { /* if ret > 0 request is queued */ |
351 | req_in = 1; |
352 | printf("submit: in\n" ); |
353 | } else |
354 | perror("unable to submit request" ); |
355 | } |
356 | if (!req_out) { /* if OUT transfer not requested */ |
357 | /* prepare read request */ |
358 | io_prep_pread(iocb_out, ep[1], buf_out, BUF_LEN, 0); |
359 | /* enable eventfs notification */ |
360 | iocb_out->u.c.flags |= IOCB_FLAG_RESFD; |
361 | iocb_out->u.c.resfd = evfd; |
362 | /* submit table of requests */ |
363 | ret = io_submit(ctx, 1, &iocb_out); |
364 | if (ret >= 0) { /* if ret > 0 request is queued */ |
365 | req_out = 1; |
366 | printf("submit: out\n" ); |
367 | } else |
368 | perror("unable to submit request" ); |
369 | } |
370 | } |
371 | |
372 | /* free resources */ |
373 | |
374 | io_destroy(ctx); |
375 | |
376 | free(buf_in); |
377 | free(buf_out); |
378 | free(iocb_in); |
379 | free(iocb_out); |
380 | |
381 | for (i = 0; i < 2; ++i) |
382 | close(ep[i]); |
383 | close(ep0); |
384 | |
385 | return 0; |
386 | } |
387 | |