1*1a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 293f2aa4dSMichal Nazarewicz /* 351c208c7SMichal Nazarewicz * ffs-test.c -- user mode filesystem api for usb composite function 493f2aa4dSMichal Nazarewicz * 593f2aa4dSMichal Nazarewicz * Copyright (C) 2010 Samsung Electronics 654b8360fSMichal Nazarewicz * Author: Michal Nazarewicz <mina86@mina86.com> 793f2aa4dSMichal Nazarewicz */ 893f2aa4dSMichal Nazarewicz 993f2aa4dSMichal Nazarewicz /* $(CROSS_COMPILE)cc -Wall -Wextra -g -o ffs-test ffs-test.c -lpthread */ 1093f2aa4dSMichal Nazarewicz 1193f2aa4dSMichal Nazarewicz 125abb9b91SFelipe Balbi #define _DEFAULT_SOURCE /* for endian.h */ 1393f2aa4dSMichal Nazarewicz 1493f2aa4dSMichal Nazarewicz #include <endian.h> 1593f2aa4dSMichal Nazarewicz #include <errno.h> 1693f2aa4dSMichal Nazarewicz #include <fcntl.h> 1793f2aa4dSMichal Nazarewicz #include <pthread.h> 1893f2aa4dSMichal Nazarewicz #include <stdarg.h> 19b9a42746SMichal Nazarewicz #include <stdbool.h> 2093f2aa4dSMichal Nazarewicz #include <stdio.h> 2193f2aa4dSMichal Nazarewicz #include <stdlib.h> 2293f2aa4dSMichal Nazarewicz #include <string.h> 2393f2aa4dSMichal Nazarewicz #include <sys/ioctl.h> 2493f2aa4dSMichal Nazarewicz #include <sys/stat.h> 2593f2aa4dSMichal Nazarewicz #include <sys/types.h> 2693f2aa4dSMichal Nazarewicz #include <unistd.h> 2724fa9a9dSMatt Fleming #include <tools/le_byteshift.h> 2893f2aa4dSMichal Nazarewicz 29a0f11aceSMaxin B. John #include "../../include/uapi/linux/usb/functionfs.h" 3093f2aa4dSMichal Nazarewicz 3193f2aa4dSMichal Nazarewicz 3293f2aa4dSMichal Nazarewicz /******************** Little Endian Handling ********************************/ 3393f2aa4dSMichal Nazarewicz 34a2b22dddSPeter Senna Tschudin /* 35a2b22dddSPeter Senna Tschudin * cpu_to_le16/32 are used when initializing structures, a context where a 36a2b22dddSPeter Senna Tschudin * function call is not allowed. To solve this, we code cpu_to_le16/32 in a way 37a2b22dddSPeter Senna Tschudin * that allows them to be used when initializing structures. 38a2b22dddSPeter Senna Tschudin */ 39a2b22dddSPeter Senna Tschudin 40a2b22dddSPeter Senna Tschudin #if __BYTE_ORDER == __LITTLE_ENDIAN 41a2b22dddSPeter Senna Tschudin #define cpu_to_le16(x) (x) 42a2b22dddSPeter Senna Tschudin #define cpu_to_le32(x) (x) 43a2b22dddSPeter Senna Tschudin #else 44a2b22dddSPeter Senna Tschudin #define cpu_to_le16(x) ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8)) 45a2b22dddSPeter Senna Tschudin #define cpu_to_le32(x) \ 46a2b22dddSPeter Senna Tschudin ((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \ 47a2b22dddSPeter Senna Tschudin (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24)) 48a2b22dddSPeter Senna Tschudin #endif 49a2b22dddSPeter Senna Tschudin 5093f2aa4dSMichal Nazarewicz #define le32_to_cpu(x) le32toh(x) 5193f2aa4dSMichal Nazarewicz #define le16_to_cpu(x) le16toh(x) 5293f2aa4dSMichal Nazarewicz 5393f2aa4dSMichal Nazarewicz /******************** Messages and Errors ***********************************/ 5493f2aa4dSMichal Nazarewicz 5593f2aa4dSMichal Nazarewicz static const char argv0[] = "ffs-test"; 5693f2aa4dSMichal Nazarewicz 5793f2aa4dSMichal Nazarewicz static unsigned verbosity = 7; 5893f2aa4dSMichal Nazarewicz 5993f2aa4dSMichal Nazarewicz static void _msg(unsigned level, const char *fmt, ...) 6093f2aa4dSMichal Nazarewicz { 6193f2aa4dSMichal Nazarewicz if (level < 2) 6293f2aa4dSMichal Nazarewicz level = 2; 6393f2aa4dSMichal Nazarewicz else if (level > 7) 6493f2aa4dSMichal Nazarewicz level = 7; 6593f2aa4dSMichal Nazarewicz 6693f2aa4dSMichal Nazarewicz if (level <= verbosity) { 6793f2aa4dSMichal Nazarewicz static const char levels[8][6] = { 6893f2aa4dSMichal Nazarewicz [2] = "crit:", 6993f2aa4dSMichal Nazarewicz [3] = "err: ", 7093f2aa4dSMichal Nazarewicz [4] = "warn:", 7193f2aa4dSMichal Nazarewicz [5] = "note:", 7293f2aa4dSMichal Nazarewicz [6] = "info:", 7393f2aa4dSMichal Nazarewicz [7] = "dbg: " 7493f2aa4dSMichal Nazarewicz }; 7593f2aa4dSMichal Nazarewicz 7693f2aa4dSMichal Nazarewicz int _errno = errno; 7793f2aa4dSMichal Nazarewicz va_list ap; 7893f2aa4dSMichal Nazarewicz 7993f2aa4dSMichal Nazarewicz fprintf(stderr, "%s: %s ", argv0, levels[level]); 8093f2aa4dSMichal Nazarewicz va_start(ap, fmt); 8193f2aa4dSMichal Nazarewicz vfprintf(stderr, fmt, ap); 8293f2aa4dSMichal Nazarewicz va_end(ap); 8393f2aa4dSMichal Nazarewicz 8493f2aa4dSMichal Nazarewicz if (fmt[strlen(fmt) - 1] != '\n') { 8593f2aa4dSMichal Nazarewicz char buffer[128]; 8693f2aa4dSMichal Nazarewicz strerror_r(_errno, buffer, sizeof buffer); 8793f2aa4dSMichal Nazarewicz fprintf(stderr, ": (-%d) %s\n", _errno, buffer); 8893f2aa4dSMichal Nazarewicz } 8993f2aa4dSMichal Nazarewicz 9093f2aa4dSMichal Nazarewicz fflush(stderr); 9193f2aa4dSMichal Nazarewicz } 9293f2aa4dSMichal Nazarewicz } 9393f2aa4dSMichal Nazarewicz 9493f2aa4dSMichal Nazarewicz #define die(...) (_msg(2, __VA_ARGS__), exit(1)) 9593f2aa4dSMichal Nazarewicz #define err(...) _msg(3, __VA_ARGS__) 9693f2aa4dSMichal Nazarewicz #define warn(...) _msg(4, __VA_ARGS__) 9793f2aa4dSMichal Nazarewicz #define note(...) _msg(5, __VA_ARGS__) 9893f2aa4dSMichal Nazarewicz #define info(...) _msg(6, __VA_ARGS__) 9993f2aa4dSMichal Nazarewicz #define debug(...) _msg(7, __VA_ARGS__) 10093f2aa4dSMichal Nazarewicz 10193f2aa4dSMichal Nazarewicz #define die_on(cond, ...) do { \ 10293f2aa4dSMichal Nazarewicz if (cond) \ 10393f2aa4dSMichal Nazarewicz die(__VA_ARGS__); \ 10493f2aa4dSMichal Nazarewicz } while (0) 10593f2aa4dSMichal Nazarewicz 10693f2aa4dSMichal Nazarewicz 10793f2aa4dSMichal Nazarewicz /******************** Descriptors and Strings *******************************/ 10893f2aa4dSMichal Nazarewicz 10993f2aa4dSMichal Nazarewicz static const struct { 11051c208c7SMichal Nazarewicz struct usb_functionfs_descs_head_v2 header; 11151c208c7SMichal Nazarewicz __le32 fs_count; 11251c208c7SMichal Nazarewicz __le32 hs_count; 113271d2d6dSFelipe Balbi __le32 ss_count; 11493f2aa4dSMichal Nazarewicz struct { 11593f2aa4dSMichal Nazarewicz struct usb_interface_descriptor intf; 11693f2aa4dSMichal Nazarewicz struct usb_endpoint_descriptor_no_audio sink; 11793f2aa4dSMichal Nazarewicz struct usb_endpoint_descriptor_no_audio source; 11893f2aa4dSMichal Nazarewicz } __attribute__((packed)) fs_descs, hs_descs; 119271d2d6dSFelipe Balbi struct { 120271d2d6dSFelipe Balbi struct usb_interface_descriptor intf; 121271d2d6dSFelipe Balbi struct usb_endpoint_descriptor_no_audio sink; 122271d2d6dSFelipe Balbi struct usb_ss_ep_comp_descriptor sink_comp; 123271d2d6dSFelipe Balbi struct usb_endpoint_descriptor_no_audio source; 124271d2d6dSFelipe Balbi struct usb_ss_ep_comp_descriptor source_comp; 125271d2d6dSFelipe Balbi } ss_descs; 12693f2aa4dSMichal Nazarewicz } __attribute__((packed)) descriptors = { 12793f2aa4dSMichal Nazarewicz .header = { 12851c208c7SMichal Nazarewicz .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), 12951c208c7SMichal Nazarewicz .flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC | 130271d2d6dSFelipe Balbi FUNCTIONFS_HAS_HS_DESC | 131271d2d6dSFelipe Balbi FUNCTIONFS_HAS_SS_DESC), 13293f2aa4dSMichal Nazarewicz .length = cpu_to_le32(sizeof descriptors), 13393f2aa4dSMichal Nazarewicz }, 13451c208c7SMichal Nazarewicz .fs_count = cpu_to_le32(3), 13593f2aa4dSMichal Nazarewicz .fs_descs = { 13693f2aa4dSMichal Nazarewicz .intf = { 13793f2aa4dSMichal Nazarewicz .bLength = sizeof descriptors.fs_descs.intf, 13893f2aa4dSMichal Nazarewicz .bDescriptorType = USB_DT_INTERFACE, 13993f2aa4dSMichal Nazarewicz .bNumEndpoints = 2, 14093f2aa4dSMichal Nazarewicz .bInterfaceClass = USB_CLASS_VENDOR_SPEC, 14193f2aa4dSMichal Nazarewicz .iInterface = 1, 14293f2aa4dSMichal Nazarewicz }, 14393f2aa4dSMichal Nazarewicz .sink = { 14493f2aa4dSMichal Nazarewicz .bLength = sizeof descriptors.fs_descs.sink, 14593f2aa4dSMichal Nazarewicz .bDescriptorType = USB_DT_ENDPOINT, 14693f2aa4dSMichal Nazarewicz .bEndpointAddress = 1 | USB_DIR_IN, 14793f2aa4dSMichal Nazarewicz .bmAttributes = USB_ENDPOINT_XFER_BULK, 14893f2aa4dSMichal Nazarewicz /* .wMaxPacketSize = autoconfiguration (kernel) */ 14993f2aa4dSMichal Nazarewicz }, 15093f2aa4dSMichal Nazarewicz .source = { 15193f2aa4dSMichal Nazarewicz .bLength = sizeof descriptors.fs_descs.source, 15293f2aa4dSMichal Nazarewicz .bDescriptorType = USB_DT_ENDPOINT, 15393f2aa4dSMichal Nazarewicz .bEndpointAddress = 2 | USB_DIR_OUT, 15493f2aa4dSMichal Nazarewicz .bmAttributes = USB_ENDPOINT_XFER_BULK, 15593f2aa4dSMichal Nazarewicz /* .wMaxPacketSize = autoconfiguration (kernel) */ 15693f2aa4dSMichal Nazarewicz }, 15793f2aa4dSMichal Nazarewicz }, 15851c208c7SMichal Nazarewicz .hs_count = cpu_to_le32(3), 15993f2aa4dSMichal Nazarewicz .hs_descs = { 16093f2aa4dSMichal Nazarewicz .intf = { 16193f2aa4dSMichal Nazarewicz .bLength = sizeof descriptors.fs_descs.intf, 16293f2aa4dSMichal Nazarewicz .bDescriptorType = USB_DT_INTERFACE, 16393f2aa4dSMichal Nazarewicz .bNumEndpoints = 2, 16493f2aa4dSMichal Nazarewicz .bInterfaceClass = USB_CLASS_VENDOR_SPEC, 16593f2aa4dSMichal Nazarewicz .iInterface = 1, 16693f2aa4dSMichal Nazarewicz }, 16793f2aa4dSMichal Nazarewicz .sink = { 16893f2aa4dSMichal Nazarewicz .bLength = sizeof descriptors.hs_descs.sink, 16993f2aa4dSMichal Nazarewicz .bDescriptorType = USB_DT_ENDPOINT, 17093f2aa4dSMichal Nazarewicz .bEndpointAddress = 1 | USB_DIR_IN, 17193f2aa4dSMichal Nazarewicz .bmAttributes = USB_ENDPOINT_XFER_BULK, 17293f2aa4dSMichal Nazarewicz .wMaxPacketSize = cpu_to_le16(512), 17393f2aa4dSMichal Nazarewicz }, 17493f2aa4dSMichal Nazarewicz .source = { 17593f2aa4dSMichal Nazarewicz .bLength = sizeof descriptors.hs_descs.source, 17693f2aa4dSMichal Nazarewicz .bDescriptorType = USB_DT_ENDPOINT, 17793f2aa4dSMichal Nazarewicz .bEndpointAddress = 2 | USB_DIR_OUT, 17893f2aa4dSMichal Nazarewicz .bmAttributes = USB_ENDPOINT_XFER_BULK, 17993f2aa4dSMichal Nazarewicz .wMaxPacketSize = cpu_to_le16(512), 18093f2aa4dSMichal Nazarewicz .bInterval = 1, /* NAK every 1 uframe */ 18193f2aa4dSMichal Nazarewicz }, 18293f2aa4dSMichal Nazarewicz }, 183271d2d6dSFelipe Balbi .ss_count = cpu_to_le32(5), 184271d2d6dSFelipe Balbi .ss_descs = { 185271d2d6dSFelipe Balbi .intf = { 186271d2d6dSFelipe Balbi .bLength = sizeof descriptors.fs_descs.intf, 187271d2d6dSFelipe Balbi .bDescriptorType = USB_DT_INTERFACE, 188271d2d6dSFelipe Balbi .bNumEndpoints = 2, 189271d2d6dSFelipe Balbi .bInterfaceClass = USB_CLASS_VENDOR_SPEC, 190271d2d6dSFelipe Balbi .iInterface = 1, 191271d2d6dSFelipe Balbi }, 192271d2d6dSFelipe Balbi .sink = { 193271d2d6dSFelipe Balbi .bLength = sizeof descriptors.hs_descs.sink, 194271d2d6dSFelipe Balbi .bDescriptorType = USB_DT_ENDPOINT, 195271d2d6dSFelipe Balbi .bEndpointAddress = 1 | USB_DIR_IN, 196271d2d6dSFelipe Balbi .bmAttributes = USB_ENDPOINT_XFER_BULK, 197271d2d6dSFelipe Balbi .wMaxPacketSize = cpu_to_le16(1024), 198271d2d6dSFelipe Balbi }, 199271d2d6dSFelipe Balbi .sink_comp = { 200271d2d6dSFelipe Balbi .bLength = USB_DT_SS_EP_COMP_SIZE, 201271d2d6dSFelipe Balbi .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, 202271d2d6dSFelipe Balbi .bMaxBurst = 0, 203271d2d6dSFelipe Balbi .bmAttributes = 0, 204271d2d6dSFelipe Balbi .wBytesPerInterval = 0, 205271d2d6dSFelipe Balbi }, 206271d2d6dSFelipe Balbi .source = { 207271d2d6dSFelipe Balbi .bLength = sizeof descriptors.hs_descs.source, 208271d2d6dSFelipe Balbi .bDescriptorType = USB_DT_ENDPOINT, 209271d2d6dSFelipe Balbi .bEndpointAddress = 2 | USB_DIR_OUT, 210271d2d6dSFelipe Balbi .bmAttributes = USB_ENDPOINT_XFER_BULK, 211271d2d6dSFelipe Balbi .wMaxPacketSize = cpu_to_le16(1024), 212271d2d6dSFelipe Balbi .bInterval = 1, /* NAK every 1 uframe */ 213271d2d6dSFelipe Balbi }, 214271d2d6dSFelipe Balbi .source_comp = { 215271d2d6dSFelipe Balbi .bLength = USB_DT_SS_EP_COMP_SIZE, 216271d2d6dSFelipe Balbi .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, 217271d2d6dSFelipe Balbi .bMaxBurst = 0, 218271d2d6dSFelipe Balbi .bmAttributes = 0, 219271d2d6dSFelipe Balbi .wBytesPerInterval = 0, 220271d2d6dSFelipe Balbi }, 221271d2d6dSFelipe Balbi }, 22293f2aa4dSMichal Nazarewicz }; 22393f2aa4dSMichal Nazarewicz 224b9a42746SMichal Nazarewicz static size_t descs_to_legacy(void **legacy, const void *descriptors_v2) 225b9a42746SMichal Nazarewicz { 226b9a42746SMichal Nazarewicz const unsigned char *descs_end, *descs_start; 227b9a42746SMichal Nazarewicz __u32 length, fs_count = 0, hs_count = 0, count; 228b9a42746SMichal Nazarewicz 229b9a42746SMichal Nazarewicz /* Read v2 header */ 230b9a42746SMichal Nazarewicz { 231b9a42746SMichal Nazarewicz const struct { 232b9a42746SMichal Nazarewicz const struct usb_functionfs_descs_head_v2 header; 233b9a42746SMichal Nazarewicz const __le32 counts[]; 234b9a42746SMichal Nazarewicz } __attribute__((packed)) *const in = descriptors_v2; 235b9a42746SMichal Nazarewicz const __le32 *counts = in->counts; 236b9a42746SMichal Nazarewicz __u32 flags; 237b9a42746SMichal Nazarewicz 238b9a42746SMichal Nazarewicz if (le32_to_cpu(in->header.magic) != 239b9a42746SMichal Nazarewicz FUNCTIONFS_DESCRIPTORS_MAGIC_V2) 240b9a42746SMichal Nazarewicz return 0; 241b9a42746SMichal Nazarewicz length = le32_to_cpu(in->header.length); 242b9a42746SMichal Nazarewicz if (length <= sizeof in->header) 243b9a42746SMichal Nazarewicz return 0; 244b9a42746SMichal Nazarewicz length -= sizeof in->header; 245b9a42746SMichal Nazarewicz flags = le32_to_cpu(in->header.flags); 246b9a42746SMichal Nazarewicz if (flags & ~(FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC | 247b9a42746SMichal Nazarewicz FUNCTIONFS_HAS_SS_DESC)) 248b9a42746SMichal Nazarewicz return 0; 249b9a42746SMichal Nazarewicz 250b9a42746SMichal Nazarewicz #define GET_NEXT_COUNT_IF_FLAG(ret, flg) do { \ 251b9a42746SMichal Nazarewicz if (!(flags & (flg))) \ 252b9a42746SMichal Nazarewicz break; \ 253b9a42746SMichal Nazarewicz if (length < 4) \ 254b9a42746SMichal Nazarewicz return 0; \ 255b9a42746SMichal Nazarewicz ret = le32_to_cpu(*counts); \ 256b9a42746SMichal Nazarewicz length -= 4; \ 257b9a42746SMichal Nazarewicz ++counts; \ 258b9a42746SMichal Nazarewicz } while (0) 259b9a42746SMichal Nazarewicz 260b9a42746SMichal Nazarewicz GET_NEXT_COUNT_IF_FLAG(fs_count, FUNCTIONFS_HAS_FS_DESC); 261b9a42746SMichal Nazarewicz GET_NEXT_COUNT_IF_FLAG(hs_count, FUNCTIONFS_HAS_HS_DESC); 262b9a42746SMichal Nazarewicz GET_NEXT_COUNT_IF_FLAG(count, FUNCTIONFS_HAS_SS_DESC); 263b9a42746SMichal Nazarewicz 264b9a42746SMichal Nazarewicz count = fs_count + hs_count; 265b9a42746SMichal Nazarewicz if (!count) 266b9a42746SMichal Nazarewicz return 0; 267b9a42746SMichal Nazarewicz descs_start = (const void *)counts; 268b9a42746SMichal Nazarewicz 269b9a42746SMichal Nazarewicz #undef GET_NEXT_COUNT_IF_FLAG 270b9a42746SMichal Nazarewicz } 271b9a42746SMichal Nazarewicz 272b9a42746SMichal Nazarewicz /* 273b9a42746SMichal Nazarewicz * Find the end of FS and HS USB descriptors. SS descriptors 274b9a42746SMichal Nazarewicz * are ignored since legacy format does not support them. 275b9a42746SMichal Nazarewicz */ 276b9a42746SMichal Nazarewicz descs_end = descs_start; 277b9a42746SMichal Nazarewicz do { 278b9a42746SMichal Nazarewicz if (length < *descs_end) 279b9a42746SMichal Nazarewicz return 0; 280b9a42746SMichal Nazarewicz length -= *descs_end; 281b9a42746SMichal Nazarewicz descs_end += *descs_end; 282b9a42746SMichal Nazarewicz } while (--count); 283b9a42746SMichal Nazarewicz 284b9a42746SMichal Nazarewicz /* Allocate legacy descriptors and copy the data. */ 285b9a42746SMichal Nazarewicz { 286b9a42746SMichal Nazarewicz #pragma GCC diagnostic push 287b9a42746SMichal Nazarewicz #pragma GCC diagnostic ignored "-Wdeprecated-declarations" 288b9a42746SMichal Nazarewicz struct { 289b9a42746SMichal Nazarewicz struct usb_functionfs_descs_head header; 290b9a42746SMichal Nazarewicz __u8 descriptors[]; 291b9a42746SMichal Nazarewicz } __attribute__((packed)) *out; 292b9a42746SMichal Nazarewicz #pragma GCC diagnostic pop 293b9a42746SMichal Nazarewicz 294b9a42746SMichal Nazarewicz length = sizeof out->header + (descs_end - descs_start); 295b9a42746SMichal Nazarewicz out = malloc(length); 296b9a42746SMichal Nazarewicz out->header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC); 297b9a42746SMichal Nazarewicz out->header.length = cpu_to_le32(length); 298b9a42746SMichal Nazarewicz out->header.fs_count = cpu_to_le32(fs_count); 299b9a42746SMichal Nazarewicz out->header.hs_count = cpu_to_le32(hs_count); 300b9a42746SMichal Nazarewicz memcpy(out->descriptors, descs_start, descs_end - descs_start); 301b9a42746SMichal Nazarewicz *legacy = out; 302b9a42746SMichal Nazarewicz } 303b9a42746SMichal Nazarewicz 304b9a42746SMichal Nazarewicz return length; 305b9a42746SMichal Nazarewicz } 306b9a42746SMichal Nazarewicz 30793f2aa4dSMichal Nazarewicz 30893f2aa4dSMichal Nazarewicz #define STR_INTERFACE_ "Source/Sink" 30993f2aa4dSMichal Nazarewicz 31093f2aa4dSMichal Nazarewicz static const struct { 31193f2aa4dSMichal Nazarewicz struct usb_functionfs_strings_head header; 31293f2aa4dSMichal Nazarewicz struct { 31393f2aa4dSMichal Nazarewicz __le16 code; 31493f2aa4dSMichal Nazarewicz const char str1[sizeof STR_INTERFACE_]; 31593f2aa4dSMichal Nazarewicz } __attribute__((packed)) lang0; 31693f2aa4dSMichal Nazarewicz } __attribute__((packed)) strings = { 31793f2aa4dSMichal Nazarewicz .header = { 31893f2aa4dSMichal Nazarewicz .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC), 31993f2aa4dSMichal Nazarewicz .length = cpu_to_le32(sizeof strings), 32093f2aa4dSMichal Nazarewicz .str_count = cpu_to_le32(1), 32193f2aa4dSMichal Nazarewicz .lang_count = cpu_to_le32(1), 32293f2aa4dSMichal Nazarewicz }, 32393f2aa4dSMichal Nazarewicz .lang0 = { 32493f2aa4dSMichal Nazarewicz cpu_to_le16(0x0409), /* en-us */ 32593f2aa4dSMichal Nazarewicz STR_INTERFACE_, 32693f2aa4dSMichal Nazarewicz }, 32793f2aa4dSMichal Nazarewicz }; 32893f2aa4dSMichal Nazarewicz 32993f2aa4dSMichal Nazarewicz #define STR_INTERFACE strings.lang0.str1 33093f2aa4dSMichal Nazarewicz 33193f2aa4dSMichal Nazarewicz 33293f2aa4dSMichal Nazarewicz /******************** Files and Threads Handling ****************************/ 33393f2aa4dSMichal Nazarewicz 33493f2aa4dSMichal Nazarewicz struct thread; 33593f2aa4dSMichal Nazarewicz 33693f2aa4dSMichal Nazarewicz static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes); 33793f2aa4dSMichal Nazarewicz static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes); 33893f2aa4dSMichal Nazarewicz static ssize_t ep0_consume(struct thread *t, const void *buf, size_t nbytes); 33993f2aa4dSMichal Nazarewicz static ssize_t fill_in_buf(struct thread *t, void *buf, size_t nbytes); 34093f2aa4dSMichal Nazarewicz static ssize_t empty_out_buf(struct thread *t, const void *buf, size_t nbytes); 34193f2aa4dSMichal Nazarewicz 34293f2aa4dSMichal Nazarewicz 34393f2aa4dSMichal Nazarewicz static struct thread { 34493f2aa4dSMichal Nazarewicz const char *const filename; 34593f2aa4dSMichal Nazarewicz size_t buf_size; 34693f2aa4dSMichal Nazarewicz 34793f2aa4dSMichal Nazarewicz ssize_t (*in)(struct thread *, void *, size_t); 34893f2aa4dSMichal Nazarewicz const char *const in_name; 34993f2aa4dSMichal Nazarewicz 35093f2aa4dSMichal Nazarewicz ssize_t (*out)(struct thread *, const void *, size_t); 35193f2aa4dSMichal Nazarewicz const char *const out_name; 35293f2aa4dSMichal Nazarewicz 35393f2aa4dSMichal Nazarewicz int fd; 35493f2aa4dSMichal Nazarewicz pthread_t id; 35593f2aa4dSMichal Nazarewicz void *buf; 35693f2aa4dSMichal Nazarewicz ssize_t status; 35793f2aa4dSMichal Nazarewicz } threads[] = { 35893f2aa4dSMichal Nazarewicz { 35993f2aa4dSMichal Nazarewicz "ep0", 4 * sizeof(struct usb_functionfs_event), 36093f2aa4dSMichal Nazarewicz read_wrap, NULL, 36193f2aa4dSMichal Nazarewicz ep0_consume, "<consume>", 36293f2aa4dSMichal Nazarewicz 0, 0, NULL, 0 36393f2aa4dSMichal Nazarewicz }, 36493f2aa4dSMichal Nazarewicz { 36593f2aa4dSMichal Nazarewicz "ep1", 8 * 1024, 36693f2aa4dSMichal Nazarewicz fill_in_buf, "<in>", 36793f2aa4dSMichal Nazarewicz write_wrap, NULL, 36893f2aa4dSMichal Nazarewicz 0, 0, NULL, 0 36993f2aa4dSMichal Nazarewicz }, 37093f2aa4dSMichal Nazarewicz { 37193f2aa4dSMichal Nazarewicz "ep2", 8 * 1024, 37293f2aa4dSMichal Nazarewicz read_wrap, NULL, 37393f2aa4dSMichal Nazarewicz empty_out_buf, "<out>", 37493f2aa4dSMichal Nazarewicz 0, 0, NULL, 0 37593f2aa4dSMichal Nazarewicz }, 37693f2aa4dSMichal Nazarewicz }; 37793f2aa4dSMichal Nazarewicz 37893f2aa4dSMichal Nazarewicz 37993f2aa4dSMichal Nazarewicz static void init_thread(struct thread *t) 38093f2aa4dSMichal Nazarewicz { 38193f2aa4dSMichal Nazarewicz t->buf = malloc(t->buf_size); 38293f2aa4dSMichal Nazarewicz die_on(!t->buf, "malloc"); 38393f2aa4dSMichal Nazarewicz 38493f2aa4dSMichal Nazarewicz t->fd = open(t->filename, O_RDWR); 38593f2aa4dSMichal Nazarewicz die_on(t->fd < 0, "%s", t->filename); 38693f2aa4dSMichal Nazarewicz } 38793f2aa4dSMichal Nazarewicz 38893f2aa4dSMichal Nazarewicz static void cleanup_thread(void *arg) 38993f2aa4dSMichal Nazarewicz { 39093f2aa4dSMichal Nazarewicz struct thread *t = arg; 39193f2aa4dSMichal Nazarewicz int ret, fd; 39293f2aa4dSMichal Nazarewicz 39393f2aa4dSMichal Nazarewicz fd = t->fd; 39493f2aa4dSMichal Nazarewicz if (t->fd < 0) 39593f2aa4dSMichal Nazarewicz return; 39693f2aa4dSMichal Nazarewicz t->fd = -1; 39793f2aa4dSMichal Nazarewicz 39893f2aa4dSMichal Nazarewicz /* test the FIFO ioctls (non-ep0 code paths) */ 39993f2aa4dSMichal Nazarewicz if (t != threads) { 40093f2aa4dSMichal Nazarewicz ret = ioctl(fd, FUNCTIONFS_FIFO_STATUS); 40193f2aa4dSMichal Nazarewicz if (ret < 0) { 40293f2aa4dSMichal Nazarewicz /* ENODEV reported after disconnect */ 40393f2aa4dSMichal Nazarewicz if (errno != ENODEV) 40493f2aa4dSMichal Nazarewicz err("%s: get fifo status", t->filename); 40593f2aa4dSMichal Nazarewicz } else if (ret) { 40693f2aa4dSMichal Nazarewicz warn("%s: unclaimed = %d\n", t->filename, ret); 40793f2aa4dSMichal Nazarewicz if (ioctl(fd, FUNCTIONFS_FIFO_FLUSH) < 0) 40893f2aa4dSMichal Nazarewicz err("%s: fifo flush", t->filename); 40993f2aa4dSMichal Nazarewicz } 41093f2aa4dSMichal Nazarewicz } 41193f2aa4dSMichal Nazarewicz 41293f2aa4dSMichal Nazarewicz if (close(fd) < 0) 41393f2aa4dSMichal Nazarewicz err("%s: close", t->filename); 41493f2aa4dSMichal Nazarewicz 41593f2aa4dSMichal Nazarewicz free(t->buf); 41693f2aa4dSMichal Nazarewicz t->buf = NULL; 41793f2aa4dSMichal Nazarewicz } 41893f2aa4dSMichal Nazarewicz 41993f2aa4dSMichal Nazarewicz static void *start_thread_helper(void *arg) 42093f2aa4dSMichal Nazarewicz { 42193f2aa4dSMichal Nazarewicz const char *name, *op, *in_name, *out_name; 42293f2aa4dSMichal Nazarewicz struct thread *t = arg; 42393f2aa4dSMichal Nazarewicz ssize_t ret; 42493f2aa4dSMichal Nazarewicz 42593f2aa4dSMichal Nazarewicz info("%s: starts\n", t->filename); 42693f2aa4dSMichal Nazarewicz in_name = t->in_name ? t->in_name : t->filename; 42793f2aa4dSMichal Nazarewicz out_name = t->out_name ? t->out_name : t->filename; 42893f2aa4dSMichal Nazarewicz 42993f2aa4dSMichal Nazarewicz pthread_cleanup_push(cleanup_thread, arg); 43093f2aa4dSMichal Nazarewicz 43193f2aa4dSMichal Nazarewicz for (;;) { 43293f2aa4dSMichal Nazarewicz pthread_testcancel(); 43393f2aa4dSMichal Nazarewicz 43493f2aa4dSMichal Nazarewicz ret = t->in(t, t->buf, t->buf_size); 43593f2aa4dSMichal Nazarewicz if (ret > 0) { 436eb9c5836SMatthias Fend ret = t->out(t, t->buf, ret); 43793f2aa4dSMichal Nazarewicz name = out_name; 43893f2aa4dSMichal Nazarewicz op = "write"; 43993f2aa4dSMichal Nazarewicz } else { 44093f2aa4dSMichal Nazarewicz name = in_name; 44193f2aa4dSMichal Nazarewicz op = "read"; 44293f2aa4dSMichal Nazarewicz } 44393f2aa4dSMichal Nazarewicz 44493f2aa4dSMichal Nazarewicz if (ret > 0) { 44593f2aa4dSMichal Nazarewicz /* nop */ 44693f2aa4dSMichal Nazarewicz } else if (!ret) { 44793f2aa4dSMichal Nazarewicz debug("%s: %s: EOF", name, op); 44893f2aa4dSMichal Nazarewicz break; 44993f2aa4dSMichal Nazarewicz } else if (errno == EINTR || errno == EAGAIN) { 45093f2aa4dSMichal Nazarewicz debug("%s: %s", name, op); 45193f2aa4dSMichal Nazarewicz } else { 45293f2aa4dSMichal Nazarewicz warn("%s: %s", name, op); 45393f2aa4dSMichal Nazarewicz break; 45493f2aa4dSMichal Nazarewicz } 45593f2aa4dSMichal Nazarewicz } 45693f2aa4dSMichal Nazarewicz 45793f2aa4dSMichal Nazarewicz pthread_cleanup_pop(1); 45893f2aa4dSMichal Nazarewicz 45993f2aa4dSMichal Nazarewicz t->status = ret; 46093f2aa4dSMichal Nazarewicz info("%s: ends\n", t->filename); 46193f2aa4dSMichal Nazarewicz return NULL; 46293f2aa4dSMichal Nazarewicz } 46393f2aa4dSMichal Nazarewicz 46493f2aa4dSMichal Nazarewicz static void start_thread(struct thread *t) 46593f2aa4dSMichal Nazarewicz { 46693f2aa4dSMichal Nazarewicz debug("%s: starting\n", t->filename); 46793f2aa4dSMichal Nazarewicz 46893f2aa4dSMichal Nazarewicz die_on(pthread_create(&t->id, NULL, start_thread_helper, t) < 0, 46993f2aa4dSMichal Nazarewicz "pthread_create(%s)", t->filename); 47093f2aa4dSMichal Nazarewicz } 47193f2aa4dSMichal Nazarewicz 47293f2aa4dSMichal Nazarewicz static void join_thread(struct thread *t) 47393f2aa4dSMichal Nazarewicz { 47493f2aa4dSMichal Nazarewicz int ret = pthread_join(t->id, NULL); 47593f2aa4dSMichal Nazarewicz 47693f2aa4dSMichal Nazarewicz if (ret < 0) 47793f2aa4dSMichal Nazarewicz err("%s: joining thread", t->filename); 47893f2aa4dSMichal Nazarewicz else 47993f2aa4dSMichal Nazarewicz debug("%s: joined\n", t->filename); 48093f2aa4dSMichal Nazarewicz } 48193f2aa4dSMichal Nazarewicz 48293f2aa4dSMichal Nazarewicz 48393f2aa4dSMichal Nazarewicz static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes) 48493f2aa4dSMichal Nazarewicz { 48593f2aa4dSMichal Nazarewicz return read(t->fd, buf, nbytes); 48693f2aa4dSMichal Nazarewicz } 48793f2aa4dSMichal Nazarewicz 48893f2aa4dSMichal Nazarewicz static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes) 48993f2aa4dSMichal Nazarewicz { 49093f2aa4dSMichal Nazarewicz return write(t->fd, buf, nbytes); 49193f2aa4dSMichal Nazarewicz } 49293f2aa4dSMichal Nazarewicz 49393f2aa4dSMichal Nazarewicz 49493f2aa4dSMichal Nazarewicz /******************** Empty/Fill buffer routines ****************************/ 49593f2aa4dSMichal Nazarewicz 49693f2aa4dSMichal Nazarewicz /* 0 -- stream of zeros, 1 -- i % 63, 2 -- pipe */ 49793f2aa4dSMichal Nazarewicz enum pattern { PAT_ZERO, PAT_SEQ, PAT_PIPE }; 49893f2aa4dSMichal Nazarewicz static enum pattern pattern; 49993f2aa4dSMichal Nazarewicz 50093f2aa4dSMichal Nazarewicz static ssize_t 50193f2aa4dSMichal Nazarewicz fill_in_buf(struct thread *ignore, void *buf, size_t nbytes) 50293f2aa4dSMichal Nazarewicz { 50393f2aa4dSMichal Nazarewicz size_t i; 50493f2aa4dSMichal Nazarewicz __u8 *p; 50593f2aa4dSMichal Nazarewicz 50693f2aa4dSMichal Nazarewicz (void)ignore; 50793f2aa4dSMichal Nazarewicz 50893f2aa4dSMichal Nazarewicz switch (pattern) { 50993f2aa4dSMichal Nazarewicz case PAT_ZERO: 51093f2aa4dSMichal Nazarewicz memset(buf, 0, nbytes); 51193f2aa4dSMichal Nazarewicz break; 51293f2aa4dSMichal Nazarewicz 51393f2aa4dSMichal Nazarewicz case PAT_SEQ: 51493f2aa4dSMichal Nazarewicz for (p = buf, i = 0; i < nbytes; ++i, ++p) 51593f2aa4dSMichal Nazarewicz *p = i % 63; 51693f2aa4dSMichal Nazarewicz break; 51793f2aa4dSMichal Nazarewicz 51893f2aa4dSMichal Nazarewicz case PAT_PIPE: 51993f2aa4dSMichal Nazarewicz return fread(buf, 1, nbytes, stdin); 52093f2aa4dSMichal Nazarewicz } 52193f2aa4dSMichal Nazarewicz 52293f2aa4dSMichal Nazarewicz return nbytes; 52393f2aa4dSMichal Nazarewicz } 52493f2aa4dSMichal Nazarewicz 52593f2aa4dSMichal Nazarewicz static ssize_t 52693f2aa4dSMichal Nazarewicz empty_out_buf(struct thread *ignore, const void *buf, size_t nbytes) 52793f2aa4dSMichal Nazarewicz { 52893f2aa4dSMichal Nazarewicz const __u8 *p; 52993f2aa4dSMichal Nazarewicz __u8 expected; 53093f2aa4dSMichal Nazarewicz ssize_t ret; 53193f2aa4dSMichal Nazarewicz size_t len; 53293f2aa4dSMichal Nazarewicz 53393f2aa4dSMichal Nazarewicz (void)ignore; 53493f2aa4dSMichal Nazarewicz 53593f2aa4dSMichal Nazarewicz switch (pattern) { 53693f2aa4dSMichal Nazarewicz case PAT_ZERO: 53793f2aa4dSMichal Nazarewicz expected = 0; 53893f2aa4dSMichal Nazarewicz for (p = buf, len = 0; len < nbytes; ++p, ++len) 53993f2aa4dSMichal Nazarewicz if (*p) 54093f2aa4dSMichal Nazarewicz goto invalid; 54193f2aa4dSMichal Nazarewicz break; 54293f2aa4dSMichal Nazarewicz 54393f2aa4dSMichal Nazarewicz case PAT_SEQ: 54493f2aa4dSMichal Nazarewicz for (p = buf, len = 0; len < nbytes; ++p, ++len) 54593f2aa4dSMichal Nazarewicz if (*p != len % 63) { 54693f2aa4dSMichal Nazarewicz expected = len % 63; 54793f2aa4dSMichal Nazarewicz goto invalid; 54893f2aa4dSMichal Nazarewicz } 54993f2aa4dSMichal Nazarewicz break; 55093f2aa4dSMichal Nazarewicz 55193f2aa4dSMichal Nazarewicz case PAT_PIPE: 55293f2aa4dSMichal Nazarewicz ret = fwrite(buf, nbytes, 1, stdout); 55393f2aa4dSMichal Nazarewicz if (ret > 0) 55493f2aa4dSMichal Nazarewicz fflush(stdout); 55593f2aa4dSMichal Nazarewicz break; 55693f2aa4dSMichal Nazarewicz 55793f2aa4dSMichal Nazarewicz invalid: 55893f2aa4dSMichal Nazarewicz err("bad OUT byte %zd, expected %02x got %02x\n", 55993f2aa4dSMichal Nazarewicz len, expected, *p); 56093f2aa4dSMichal Nazarewicz for (p = buf, len = 0; len < nbytes; ++p, ++len) { 56193f2aa4dSMichal Nazarewicz if (0 == (len % 32)) 562d105e74eSDavidlohr Bueso fprintf(stderr, "%4zd:", len); 56393f2aa4dSMichal Nazarewicz fprintf(stderr, " %02x", *p); 56493f2aa4dSMichal Nazarewicz if (31 == (len % 32)) 56593f2aa4dSMichal Nazarewicz fprintf(stderr, "\n"); 56693f2aa4dSMichal Nazarewicz } 56793f2aa4dSMichal Nazarewicz fflush(stderr); 56893f2aa4dSMichal Nazarewicz errno = EILSEQ; 56993f2aa4dSMichal Nazarewicz return -1; 57093f2aa4dSMichal Nazarewicz } 57193f2aa4dSMichal Nazarewicz 57293f2aa4dSMichal Nazarewicz return len; 57393f2aa4dSMichal Nazarewicz } 57493f2aa4dSMichal Nazarewicz 57593f2aa4dSMichal Nazarewicz 57693f2aa4dSMichal Nazarewicz /******************** Endpoints routines ************************************/ 57793f2aa4dSMichal Nazarewicz 57893f2aa4dSMichal Nazarewicz static void handle_setup(const struct usb_ctrlrequest *setup) 57993f2aa4dSMichal Nazarewicz { 58093f2aa4dSMichal Nazarewicz printf("bRequestType = %d\n", setup->bRequestType); 58193f2aa4dSMichal Nazarewicz printf("bRequest = %d\n", setup->bRequest); 58293f2aa4dSMichal Nazarewicz printf("wValue = %d\n", le16_to_cpu(setup->wValue)); 58393f2aa4dSMichal Nazarewicz printf("wIndex = %d\n", le16_to_cpu(setup->wIndex)); 58493f2aa4dSMichal Nazarewicz printf("wLength = %d\n", le16_to_cpu(setup->wLength)); 58593f2aa4dSMichal Nazarewicz } 58693f2aa4dSMichal Nazarewicz 58793f2aa4dSMichal Nazarewicz static ssize_t 58893f2aa4dSMichal Nazarewicz ep0_consume(struct thread *ignore, const void *buf, size_t nbytes) 58993f2aa4dSMichal Nazarewicz { 59093f2aa4dSMichal Nazarewicz static const char *const names[] = { 59193f2aa4dSMichal Nazarewicz [FUNCTIONFS_BIND] = "BIND", 59293f2aa4dSMichal Nazarewicz [FUNCTIONFS_UNBIND] = "UNBIND", 59393f2aa4dSMichal Nazarewicz [FUNCTIONFS_ENABLE] = "ENABLE", 59493f2aa4dSMichal Nazarewicz [FUNCTIONFS_DISABLE] = "DISABLE", 59593f2aa4dSMichal Nazarewicz [FUNCTIONFS_SETUP] = "SETUP", 59693f2aa4dSMichal Nazarewicz [FUNCTIONFS_SUSPEND] = "SUSPEND", 59793f2aa4dSMichal Nazarewicz [FUNCTIONFS_RESUME] = "RESUME", 59893f2aa4dSMichal Nazarewicz }; 59993f2aa4dSMichal Nazarewicz 60093f2aa4dSMichal Nazarewicz const struct usb_functionfs_event *event = buf; 60193f2aa4dSMichal Nazarewicz size_t n; 60293f2aa4dSMichal Nazarewicz 60393f2aa4dSMichal Nazarewicz (void)ignore; 60493f2aa4dSMichal Nazarewicz 60593f2aa4dSMichal Nazarewicz for (n = nbytes / sizeof *event; n; --n, ++event) 60693f2aa4dSMichal Nazarewicz switch (event->type) { 60793f2aa4dSMichal Nazarewicz case FUNCTIONFS_BIND: 60893f2aa4dSMichal Nazarewicz case FUNCTIONFS_UNBIND: 60993f2aa4dSMichal Nazarewicz case FUNCTIONFS_ENABLE: 61093f2aa4dSMichal Nazarewicz case FUNCTIONFS_DISABLE: 61193f2aa4dSMichal Nazarewicz case FUNCTIONFS_SETUP: 61293f2aa4dSMichal Nazarewicz case FUNCTIONFS_SUSPEND: 61393f2aa4dSMichal Nazarewicz case FUNCTIONFS_RESUME: 61493f2aa4dSMichal Nazarewicz printf("Event %s\n", names[event->type]); 61593f2aa4dSMichal Nazarewicz if (event->type == FUNCTIONFS_SETUP) 61693f2aa4dSMichal Nazarewicz handle_setup(&event->u.setup); 61793f2aa4dSMichal Nazarewicz break; 61893f2aa4dSMichal Nazarewicz 61993f2aa4dSMichal Nazarewicz default: 62093f2aa4dSMichal Nazarewicz printf("Event %03u (unknown)\n", event->type); 62193f2aa4dSMichal Nazarewicz } 62293f2aa4dSMichal Nazarewicz 62393f2aa4dSMichal Nazarewicz return nbytes; 62493f2aa4dSMichal Nazarewicz } 62593f2aa4dSMichal Nazarewicz 626b9a42746SMichal Nazarewicz static void ep0_init(struct thread *t, bool legacy_descriptors) 62793f2aa4dSMichal Nazarewicz { 628b9a42746SMichal Nazarewicz void *legacy; 62993f2aa4dSMichal Nazarewicz ssize_t ret; 630b9a42746SMichal Nazarewicz size_t len; 63193f2aa4dSMichal Nazarewicz 632b9a42746SMichal Nazarewicz if (legacy_descriptors) { 63393f2aa4dSMichal Nazarewicz info("%s: writing descriptors\n", t->filename); 634b9a42746SMichal Nazarewicz goto legacy; 635b9a42746SMichal Nazarewicz } 636b9a42746SMichal Nazarewicz 637b9a42746SMichal Nazarewicz info("%s: writing descriptors (in v2 format)\n", t->filename); 63893f2aa4dSMichal Nazarewicz ret = write(t->fd, &descriptors, sizeof descriptors); 639b9a42746SMichal Nazarewicz 640b9a42746SMichal Nazarewicz if (ret < 0 && errno == EINVAL) { 641b9a42746SMichal Nazarewicz warn("%s: new format rejected, trying legacy\n", t->filename); 642b9a42746SMichal Nazarewicz legacy: 643b9a42746SMichal Nazarewicz len = descs_to_legacy(&legacy, &descriptors); 644b9a42746SMichal Nazarewicz if (len) { 645b9a42746SMichal Nazarewicz ret = write(t->fd, legacy, len); 646b9a42746SMichal Nazarewicz free(legacy); 647b9a42746SMichal Nazarewicz } 648b9a42746SMichal Nazarewicz } 64993f2aa4dSMichal Nazarewicz die_on(ret < 0, "%s: write: descriptors", t->filename); 65093f2aa4dSMichal Nazarewicz 65193f2aa4dSMichal Nazarewicz info("%s: writing strings\n", t->filename); 65293f2aa4dSMichal Nazarewicz ret = write(t->fd, &strings, sizeof strings); 65393f2aa4dSMichal Nazarewicz die_on(ret < 0, "%s: write: strings", t->filename); 65493f2aa4dSMichal Nazarewicz } 65593f2aa4dSMichal Nazarewicz 65693f2aa4dSMichal Nazarewicz 65793f2aa4dSMichal Nazarewicz /******************** Main **************************************************/ 65893f2aa4dSMichal Nazarewicz 659b9a42746SMichal Nazarewicz int main(int argc, char **argv) 66093f2aa4dSMichal Nazarewicz { 661b9a42746SMichal Nazarewicz bool legacy_descriptors; 66293f2aa4dSMichal Nazarewicz unsigned i; 66393f2aa4dSMichal Nazarewicz 664b9a42746SMichal Nazarewicz legacy_descriptors = argc > 2 && !strcmp(argv[1], "-l"); 66593f2aa4dSMichal Nazarewicz 66693f2aa4dSMichal Nazarewicz init_thread(threads); 667b9a42746SMichal Nazarewicz ep0_init(threads, legacy_descriptors); 66893f2aa4dSMichal Nazarewicz 66993f2aa4dSMichal Nazarewicz for (i = 1; i < sizeof threads / sizeof *threads; ++i) 67093f2aa4dSMichal Nazarewicz init_thread(threads + i); 67193f2aa4dSMichal Nazarewicz 67293f2aa4dSMichal Nazarewicz for (i = 1; i < sizeof threads / sizeof *threads; ++i) 67393f2aa4dSMichal Nazarewicz start_thread(threads + i); 67493f2aa4dSMichal Nazarewicz 67593f2aa4dSMichal Nazarewicz start_thread_helper(threads); 67693f2aa4dSMichal Nazarewicz 67793f2aa4dSMichal Nazarewicz for (i = 1; i < sizeof threads / sizeof *threads; ++i) 67893f2aa4dSMichal Nazarewicz join_thread(threads + i); 67993f2aa4dSMichal Nazarewicz 68093f2aa4dSMichal Nazarewicz return 0; 68193f2aa4dSMichal Nazarewicz } 682