--- /dev/null
+#define _BSD_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
+#define BUFS_MAX       128
+#define AIO_MAX                (BUFS_MAX*2)
+
+/******************** Descriptors and Strings *******************************/
+
+static const struct {
+       struct usb_functionfs_descs_head header;
+       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 = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+               .length = htole32(sizeof(descriptors)),
+               .fs_count = 3,
+               .hs_count = 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_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 = htole16(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 = htole16(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 = htole32(FUNCTIONFS_STRINGS_MAGIC),
+               .length = htole32(sizeof(strings)),
+               .str_count = htole32(1),
+               .lang_count = htole32(1),
+       },
+       .lang0 = {
+               htole16(0x0409), /* en-us */
+               STR_INTERFACE,
+       },
+};
+
+/********************** Buffer structure *******************************/
+
+struct io_buffer {
+       struct iocb **iocb;
+       unsigned char **buf;
+       unsigned cnt;
+       unsigned len;
+       unsigned requested;
+};
+
+/******************** 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)
+{
+       int ret;
+       struct usb_functionfs_event event;
+
+       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;
+       }
+}
+
+void init_bufs(struct io_buffer *iobuf, unsigned n, unsigned len)
+{
+       unsigned i;
+       iobuf->buf = malloc(n*sizeof(*iobuf->buf));
+       iobuf->iocb = malloc(n*sizeof(*iobuf->iocb));
+       iobuf->cnt = n;
+       iobuf->len = len;
+       iobuf->requested = 0;
+       for (i = 0; i < n; ++i) {
+               iobuf->buf[i] = malloc(len*sizeof(**iobuf->buf));
+               iobuf->iocb[i] = malloc(sizeof(**iobuf->iocb));
+       }
+       iobuf->cnt = n;
+}
+
+void delete_bufs(struct io_buffer *iobuf)
+{
+       unsigned i;
+       for (i = 0; i < iobuf->cnt; ++i) {
+               free(iobuf->buf[i]);
+               free(iobuf->iocb[i]);
+       }
+       free(iobuf->buf);
+       free(iobuf->iocb);
+}
+
+int main(int argc, char *argv[])
+{
+       int ret;
+       unsigned i, j;
+       char *ep_path;
+
+       int ep0, ep1;
+
+       io_context_t ctx;
+
+       int evfd;
+       fd_set rfds;
+
+       struct io_buffer iobuf[2];
+       int actual = 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;
+       }
+       sprintf(ep_path, "%s/ep1", argv[1]);
+       ep1 = open(ep_path, O_RDWR);
+       if (ep1 < 0) {
+               perror("unable to open ep1");
+               return 1;
+       }
+
+       free(ep_path);
+
+       memset(&ctx, 0, sizeof(ctx));
+       /* setup aio context to handle up to AIO_MAX requests */
+       if (io_setup(AIO_MAX, &ctx) < 0) {
+               perror("unable to setup aio");
+               return 1;
+       }
+
+       evfd = eventfd(0, 0);
+       if (evfd < 0) {
+               perror("unable to open eventfd");
+               return 1;
+       }
+
+       for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i)
+               init_bufs(&iobuf[i], BUFS_MAX, BUF_LEN);
+
+       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;
+
+               /*
+                * when we're preparing new data to submit,
+                * second buffer being transmitted
+                */
+               for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i) {
+                       if (iobuf[i].requested)
+                               continue;
+                       /* prepare requests */
+                       for (j = 0; j < iobuf[i].cnt; ++j) {
+                               io_prep_pwrite(iobuf[i].iocb[j], ep1,
+                                              iobuf[i].buf[j],
+                                              iobuf[i].len, 0);
+                               /* enable eventfd notification */
+                               iobuf[i].iocb[j]->u.c.flags |= IOCB_FLAG_RESFD;
+                               iobuf[i].iocb[j]->u.c.resfd = evfd;
+                       }
+                       /* submit table of requests */
+                       ret = io_submit(ctx, iobuf[i].cnt, iobuf[i].iocb);
+                       if (ret >= 0) {
+                               iobuf[i].requested = ret;
+                               printf("submit: %d requests buf: %d\n", ret, i);
+                       } else
+                               perror("unable to submit reqests");
+               }
+
+               /* if event is ready to read */
+               if (!FD_ISSET(evfd, &rfds))
+                       continue;
+
+               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[BUFS_MAX];
+               /* we read aio events */
+               ret = io_getevents(ctx, 1, BUFS_MAX, e, NULL);
+               if (ret > 0) /* if we got events */
+                       iobuf[actual].requested -= ret;
+
+               /* if all req's from iocb completed */
+               if (!iobuf[actual].requested)
+                       actual = (actual + 1)%(sizeof(iobuf)/sizeof(*iobuf));
+       }
+
+       /* free resources */
+
+       for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i)
+               delete_bufs(&iobuf[i]);
+       io_destroy(ctx);
+
+       close(ep1);
+       close(ep0);
+
+       return 0;
+}
 
--- /dev/null
+CC = gcc
+LIBUSB_CFLAGS = $(shell pkg-config --cflags libusb-1.0)
+LIBUSB_LIBS = $(shell pkg-config --libs libusb-1.0)
+WARNINGS = -Wall -Wextra
+CFLAGS = $(LIBUSB_CFLAGS) $(WARNINGS)
+LDFLAGS = $(LIBUSB_LIBS)
+
+all: test
+%: %.c
+       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+clean:
+       $(RM) test
 
--- /dev/null
+#include <libusb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define VENDOR 0x1d6b
+#define PRODUCT        0x0105
+
+/* endpoints indexes */
+
+#define EP_BULK_IN     (1 | LIBUSB_ENDPOINT_IN)
+#define EP_BULK_OUT    (2 | LIBUSB_ENDPOINT_OUT)
+
+#define BUF_LEN                8192
+
+/*
+ * struct test_state - describes test program state
+ * @list: list of devices returned by libusb_get_device_list function
+ * @found: pointer to struct describing tested device
+ * @ctx: context, set to NULL
+ * @handle: handle of tested device
+ * @attached: indicates that device was attached to kernel, and has to be
+ *            reattached at the end of test program
+ */
+
+struct test_state {
+       libusb_device *found;
+       libusb_context *ctx;
+       libusb_device_handle *handle;
+       int attached;
+};
+
+/*
+ * test_init - initialize test program
+ */
+
+int test_init(struct test_state *state)
+{
+       int i, ret;
+       ssize_t cnt;
+       libusb_device **list;
+
+       state->found = NULL;
+       state->ctx = NULL;
+       state->handle = NULL;
+       state->attached = 0;
+
+       ret = libusb_init(&state->ctx);
+       if (ret) {
+               printf("cannot init libusb: %s\n", libusb_error_name(ret));
+               return 1;
+       }
+
+       cnt = libusb_get_device_list(state->ctx, &list);
+       if (cnt <= 0) {
+               printf("no devices found\n");
+               goto error1;
+       }
+
+       for (i = 0; i < cnt; ++i) {
+               libusb_device *dev = list[i];
+               struct libusb_device_descriptor desc;
+               ret = libusb_get_device_descriptor(dev, &desc);
+               if (ret) {
+                       printf("unable to get device descriptor: %s\n",
+                              libusb_error_name(ret));
+                       goto error2;
+               }
+               if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT) {
+                       state->found = dev;
+                       break;
+               }
+       }
+
+       if (!state->found) {
+               printf("no devices found\n");
+               goto error2;
+       }
+
+       ret = libusb_open(state->found, &state->handle);
+       if (ret) {
+               printf("cannot open device: %s\n", libusb_error_name(ret));
+               goto error2;
+       }
+
+       if (libusb_claim_interface(state->handle, 0)) {
+               ret = libusb_detach_kernel_driver(state->handle, 0);
+               if (ret) {
+                       printf("unable to detach kernel driver: %s\n",
+                              libusb_error_name(ret));
+                       goto error3;
+               }
+               state->attached = 1;
+               ret = libusb_claim_interface(state->handle, 0);
+               if (ret) {
+                       printf("cannot claim interface: %s\n",
+                              libusb_error_name(ret));
+                       goto error4;
+               }
+       }
+
+       return 0;
+
+error4:
+       if (state->attached == 1)
+               libusb_attach_kernel_driver(state->handle, 0);
+
+error3:
+       libusb_close(state->handle);
+
+error2:
+       libusb_free_device_list(list, 1);
+
+error1:
+       libusb_exit(state->ctx);
+       return 1;
+}
+
+/*
+ * test_exit - cleanup test program
+ */
+
+void test_exit(struct test_state *state)
+{
+       libusb_release_interface(state->handle, 0);
+       if (state->attached == 1)
+               libusb_attach_kernel_driver(state->handle, 0);
+       libusb_close(state->handle);
+       libusb_exit(state->ctx);
+}
+
+int main(void)
+{
+       struct test_state state;
+
+       if (test_init(&state))
+               return 1;
+
+       while (1) {
+               static unsigned char buffer[BUF_LEN];
+               int bytes;
+               libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN,
+                                    &bytes, 500);
+       }
+       test_exit(&state);
+}
 
--- /dev/null
+#define _BSD_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
+
+/******************** Descriptors and Strings *******************************/
+
+static const struct {
+       struct usb_functionfs_descs_head header;
+       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 = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+               .length = htole32(sizeof(descriptors)),
+               .fs_count = 3,
+               .hs_count = 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_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,
+               },
+               .bulk_source = {
+                       .bLength = sizeof(descriptors.hs_descs.bulk_source),
+                       .bDescriptorType = USB_DT_ENDPOINT,
+                       .bEndpointAddress = 2 | USB_DIR_OUT,
+                       .bmAttributes = USB_ENDPOINT_XFER_BULK,
+               },
+       },
+};
+
+#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 = htole32(FUNCTIONFS_STRINGS_MAGIC),
+               .length = htole32(sizeof(strings)),
+               .str_count = htole32(1),
+               .lang_count = htole32(1),
+       },
+       .lang0 = {
+               htole16(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;
+}
 
--- /dev/null
+CC = gcc
+LIBUSB_CFLAGS = $(shell pkg-config --cflags libusb-1.0)
+LIBUSB_LIBS = $(shell pkg-config --libs libusb-1.0)
+WARNINGS = -Wall -Wextra
+CFLAGS = $(LIBUSB_CFLAGS) $(WARNINGS)
+LDFLAGS = $(LIBUSB_LIBS)
+
+all: test
+%: %.c
+       $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+clean:
+       $(RM) test
 
--- /dev/null
+#include <libusb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define VENDOR 0x1d6b
+#define PRODUCT        0x0105
+
+/* endpoints indexes */
+
+#define EP_BULK_IN     (1 | LIBUSB_ENDPOINT_IN)
+#define EP_BULK_OUT    (2 | LIBUSB_ENDPOINT_OUT)
+
+#define BUF_LEN                8192
+
+/*
+ * struct test_state - describes test program state
+ * @list: list of devices returned by libusb_get_device_list function
+ * @found: pointer to struct describing tested device
+ * @ctx: context, set to NULL
+ * @handle: handle of tested device
+ * @attached: indicates that device was attached to kernel, and has to be
+ *            reattached at the end of test program
+ */
+
+struct test_state {
+       libusb_device *found;
+       libusb_context *ctx;
+       libusb_device_handle *handle;
+       int attached;
+};
+
+/*
+ * test_init - initialize test program
+ */
+
+int test_init(struct test_state *state)
+{
+       int i, ret;
+       ssize_t cnt;
+       libusb_device **list;
+
+       state->found = NULL;
+       state->ctx = NULL;
+       state->handle = NULL;
+       state->attached = 0;
+
+       ret = libusb_init(&state->ctx);
+       if (ret) {
+               printf("cannot init libusb: %s\n", libusb_error_name(ret));
+               return 1;
+       }
+
+       cnt = libusb_get_device_list(state->ctx, &list);
+       if (cnt <= 0) {
+               printf("no devices found\n");
+               goto error1;
+       }
+
+       for (i = 0; i < cnt; ++i) {
+               libusb_device *dev = list[i];
+               struct libusb_device_descriptor desc;
+               ret = libusb_get_device_descriptor(dev, &desc);
+               if (ret) {
+                       printf("unable to get device descriptor: %s\n",
+                              libusb_error_name(ret));
+                       goto error2;
+               }
+               if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT) {
+                       state->found = dev;
+                       break;
+               }
+       }
+
+       if (!state->found) {
+               printf("no devices found\n");
+               goto error2;
+       }
+
+       ret = libusb_open(state->found, &state->handle);
+       if (ret) {
+               printf("cannot open device: %s\n", libusb_error_name(ret));
+               goto error2;
+       }
+
+       if (libusb_claim_interface(state->handle, 0)) {
+               ret = libusb_detach_kernel_driver(state->handle, 0);
+               if (ret) {
+                       printf("unable to detach kernel driver: %s\n",
+                              libusb_error_name(ret));
+                       goto error3;
+               }
+               state->attached = 1;
+               ret = libusb_claim_interface(state->handle, 0);
+               if (ret) {
+                       printf("cannot claim interface: %s\n",
+                              libusb_error_name(ret));
+                       goto error4;
+               }
+       }
+
+       return 0;
+
+error4:
+       if (state->attached == 1)
+               libusb_attach_kernel_driver(state->handle, 0);
+
+error3:
+       libusb_close(state->handle);
+
+error2:
+       libusb_free_device_list(list, 1);
+
+error1:
+       libusb_exit(state->ctx);
+       return 1;
+}
+
+/*
+ * test_exit - cleanup test program
+ */
+
+void test_exit(struct test_state *state)
+{
+       libusb_release_interface(state->handle, 0);
+       if (state->attached == 1)
+               libusb_attach_kernel_driver(state->handle, 0);
+       libusb_close(state->handle);
+       libusb_exit(state->ctx);
+}
+
+int main(void)
+{
+       struct test_state state;
+
+       if (test_init(&state))
+               return 1;
+
+       while (1) {
+               static unsigned char buffer[BUF_LEN];
+               int bytes;
+               libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN,
+                                    &bytes, 500);
+               libusb_bulk_transfer(state.handle, EP_BULK_OUT, buffer, BUF_LEN,
+                                    &bytes, 500);
+       }
+       test_exit(&state);
+}