diff options
Diffstat (limited to 'tools/usb')
| -rw-r--r-- | tools/usb/Makefile | 14 | ||||
| -rw-r--r-- | tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c | 349 | ||||
| -rw-r--r-- | tools/usb/ffs-aio-example/multibuff/host_app/Makefile | 13 | ||||
| -rw-r--r-- | tools/usb/ffs-aio-example/multibuff/host_app/test.c | 146 | ||||
| -rw-r--r-- | tools/usb/ffs-aio-example/simple/device_app/aio_simple.c | 335 | ||||
| -rw-r--r-- | tools/usb/ffs-aio-example/simple/host_app/Makefile | 13 | ||||
| -rw-r--r-- | tools/usb/ffs-aio-example/simple/host_app/test.c | 148 | ||||
| -rw-r--r-- | tools/usb/ffs-test.c | 527 | ||||
| -rw-r--r-- | tools/usb/hcd-tests.sh | 275 | ||||
| -rw-r--r-- | tools/usb/testusb.c | 537 |
10 files changed, 2357 insertions, 0 deletions
diff --git a/tools/usb/Makefile b/tools/usb/Makefile new file mode 100644 index 00000000000..acf2165c04e --- /dev/null +++ b/tools/usb/Makefile @@ -0,0 +1,14 @@ +# Makefile for USB tools + +CC = $(CROSS_COMPILE)gcc +PTHREAD_LIBS = -lpthread +WARNINGS = -Wall -Wextra +CFLAGS = $(WARNINGS) -g -I../include +LDFLAGS = $(PTHREAD_LIBS) + +all: testusb ffs-test +%: %.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +clean: + $(RM) testusb ffs-test diff --git a/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c b/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c new file mode 100644 index 00000000000..87216a0c4a8 --- /dev/null +++ b/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c @@ -0,0 +1,349 @@ +#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; +} diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/Makefile b/tools/usb/ffs-aio-example/multibuff/host_app/Makefile new file mode 100644 index 00000000000..8c4a6f0aa82 --- /dev/null +++ b/tools/usb/ffs-aio-example/multibuff/host_app/Makefile @@ -0,0 +1,13 @@ +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 diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/test.c b/tools/usb/ffs-aio-example/multibuff/host_app/test.c new file mode 100644 index 00000000000..b0ad8747d03 --- /dev/null +++ b/tools/usb/ffs-aio-example/multibuff/host_app/test.c @@ -0,0 +1,146 @@ +#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); +} diff --git a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c new file mode 100644 index 00000000000..f558664a331 --- /dev/null +++ b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c @@ -0,0 +1,335 @@ +#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; +} diff --git a/tools/usb/ffs-aio-example/simple/host_app/Makefile b/tools/usb/ffs-aio-example/simple/host_app/Makefile new file mode 100644 index 00000000000..8c4a6f0aa82 --- /dev/null +++ b/tools/usb/ffs-aio-example/simple/host_app/Makefile @@ -0,0 +1,13 @@ +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 diff --git a/tools/usb/ffs-aio-example/simple/host_app/test.c b/tools/usb/ffs-aio-example/simple/host_app/test.c new file mode 100644 index 00000000000..64b6a57d8ca --- /dev/null +++ b/tools/usb/ffs-aio-example/simple/host_app/test.c @@ -0,0 +1,148 @@ +#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); +} diff --git a/tools/usb/ffs-test.c b/tools/usb/ffs-test.c new file mode 100644 index 00000000000..a87e99f37c5 --- /dev/null +++ b/tools/usb/ffs-test.c @@ -0,0 +1,527 @@ +/* + * ffs-test.c.c -- user mode filesystem api for usb composite function + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz <mina86@mina86.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* $(CROSS_COMPILE)cc -Wall -Wextra -g -o ffs-test ffs-test.c -lpthread */ + + +#define _BSD_SOURCE /* for endian.h */ + +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.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 <unistd.h> +#include <tools/le_byteshift.h> + +#include "../../include/uapi/linux/usb/functionfs.h" + + +/******************** Little Endian Handling ********************************/ + +#define cpu_to_le16(x) htole16(x) +#define cpu_to_le32(x) htole32(x) +#define le32_to_cpu(x) le32toh(x) +#define le16_to_cpu(x) le16toh(x) + + +/******************** Messages and Errors ***********************************/ + +static const char argv0[] = "ffs-test"; + +static unsigned verbosity = 7; + +static void _msg(unsigned level, const char *fmt, ...) +{ + if (level < 2) + level = 2; + else if (level > 7) + level = 7; + + if (level <= verbosity) { + static const char levels[8][6] = { + [2] = "crit:", + [3] = "err: ", + [4] = "warn:", + [5] = "note:", + [6] = "info:", + [7] = "dbg: " + }; + + int _errno = errno; + va_list ap; + + fprintf(stderr, "%s: %s ", argv0, levels[level]); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[strlen(fmt) - 1] != '\n') { + char buffer[128]; + strerror_r(_errno, buffer, sizeof buffer); + fprintf(stderr, ": (-%d) %s\n", _errno, buffer); + } + + fflush(stderr); + } +} + +#define die(...) (_msg(2, __VA_ARGS__), exit(1)) +#define err(...) _msg(3, __VA_ARGS__) +#define warn(...) _msg(4, __VA_ARGS__) +#define note(...) _msg(5, __VA_ARGS__) +#define info(...) _msg(6, __VA_ARGS__) +#define debug(...) _msg(7, __VA_ARGS__) + +#define die_on(cond, ...) do { \ + if (cond) \ + die(__VA_ARGS__); \ + } while (0) + + +/******************** Descriptors and Strings *******************************/ + +static const struct { + struct usb_functionfs_descs_head header; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio sink; + struct usb_endpoint_descriptor_no_audio source; + } __attribute__((packed)) fs_descs, hs_descs; +} __attribute__((packed)) descriptors = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC), + .length = cpu_to_le32(sizeof descriptors), + .fs_count = cpu_to_le32(3), + .hs_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, + }, + .sink = { + .bLength = sizeof descriptors.fs_descs.sink, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* .wMaxPacketSize = autoconfiguration (kernel) */ + }, + .source = { + .bLength = sizeof descriptors.fs_descs.source, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* .wMaxPacketSize = autoconfiguration (kernel) */ + }, + }, + .hs_descs = { + .intf = { + .bLength = sizeof descriptors.fs_descs.intf, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .sink = { + .bLength = sizeof descriptors.hs_descs.sink, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + }, + .source = { + .bLength = sizeof descriptors.hs_descs.source, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ + }, + }, +}; + + +#define STR_INTERFACE_ "Source/Sink" + +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_, + }, +}; + +#define STR_INTERFACE strings.lang0.str1 + + +/******************** Files and Threads Handling ****************************/ + +struct thread; + +static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes); +static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes); +static ssize_t ep0_consume(struct thread *t, const void *buf, size_t nbytes); +static ssize_t fill_in_buf(struct thread *t, void *buf, size_t nbytes); +static ssize_t empty_out_buf(struct thread *t, const void *buf, size_t nbytes); + + +static struct thread { + const char *const filename; + size_t buf_size; + + ssize_t (*in)(struct thread *, void *, size_t); + const char *const in_name; + + ssize_t (*out)(struct thread *, const void *, size_t); + const char *const out_name; + + int fd; + pthread_t id; + void *buf; + ssize_t status; +} threads[] = { + { + "ep0", 4 * sizeof(struct usb_functionfs_event), + read_wrap, NULL, + ep0_consume, "<consume>", + 0, 0, NULL, 0 + }, + { + "ep1", 8 * 1024, + fill_in_buf, "<in>", + write_wrap, NULL, + 0, 0, NULL, 0 + }, + { + "ep2", 8 * 1024, + read_wrap, NULL, + empty_out_buf, "<out>", + 0, 0, NULL, 0 + }, +}; + + +static void init_thread(struct thread *t) +{ + t->buf = malloc(t->buf_size); + die_on(!t->buf, "malloc"); + + t->fd = open(t->filename, O_RDWR); + die_on(t->fd < 0, "%s", t->filename); +} + +static void cleanup_thread(void *arg) +{ + struct thread *t = arg; + int ret, fd; + + fd = t->fd; + if (t->fd < 0) + return; + t->fd = -1; + + /* test the FIFO ioctls (non-ep0 code paths) */ + if (t != threads) { + ret = ioctl(fd, FUNCTIONFS_FIFO_STATUS); + if (ret < 0) { + /* ENODEV reported after disconnect */ + if (errno != ENODEV) + err("%s: get fifo status", t->filename); + } else if (ret) { + warn("%s: unclaimed = %d\n", t->filename, ret); + if (ioctl(fd, FUNCTIONFS_FIFO_FLUSH) < 0) + err("%s: fifo flush", t->filename); + } + } + + if (close(fd) < 0) + err("%s: close", t->filename); + + free(t->buf); + t->buf = NULL; +} + +static void *start_thread_helper(void *arg) +{ + const char *name, *op, *in_name, *out_name; + struct thread *t = arg; + ssize_t ret; + + info("%s: starts\n", t->filename); + in_name = t->in_name ? t->in_name : t->filename; + out_name = t->out_name ? t->out_name : t->filename; + + pthread_cleanup_push(cleanup_thread, arg); + + for (;;) { + pthread_testcancel(); + + ret = t->in(t, t->buf, t->buf_size); + if (ret > 0) { + ret = t->out(t, t->buf, ret); + name = out_name; + op = "write"; + } else { + name = in_name; + op = "read"; + } + + if (ret > 0) { + /* nop */ + } else if (!ret) { + debug("%s: %s: EOF", name, op); + break; + } else if (errno == EINTR || errno == EAGAIN) { + debug("%s: %s", name, op); + } else { + warn("%s: %s", name, op); + break; + } + } + + pthread_cleanup_pop(1); + + t->status = ret; + info("%s: ends\n", t->filename); + return NULL; +} + +static void start_thread(struct thread *t) +{ + debug("%s: starting\n", t->filename); + + die_on(pthread_create(&t->id, NULL, start_thread_helper, t) < 0, + "pthread_create(%s)", t->filename); +} + +static void join_thread(struct thread *t) +{ + int ret = pthread_join(t->id, NULL); + + if (ret < 0) + err("%s: joining thread", t->filename); + else + debug("%s: joined\n", t->filename); +} + + +static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes) +{ + return read(t->fd, buf, nbytes); +} + +static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes) +{ + return write(t->fd, buf, nbytes); +} + + +/******************** Empty/Fill buffer routines ****************************/ + +/* 0 -- stream of zeros, 1 -- i % 63, 2 -- pipe */ +enum pattern { PAT_ZERO, PAT_SEQ, PAT_PIPE }; +static enum pattern pattern; + +static ssize_t +fill_in_buf(struct thread *ignore, void *buf, size_t nbytes) +{ + size_t i; + __u8 *p; + + (void)ignore; + + switch (pattern) { + case PAT_ZERO: + memset(buf, 0, nbytes); + break; + + case PAT_SEQ: + for (p = buf, i = 0; i < nbytes; ++i, ++p) + *p = i % 63; + break; + + case PAT_PIPE: + return fread(buf, 1, nbytes, stdin); + } + + return nbytes; +} + +static ssize_t +empty_out_buf(struct thread *ignore, const void *buf, size_t nbytes) +{ + const __u8 *p; + __u8 expected; + ssize_t ret; + size_t len; + + (void)ignore; + + switch (pattern) { + case PAT_ZERO: + expected = 0; + for (p = buf, len = 0; len < nbytes; ++p, ++len) + if (*p) + goto invalid; + break; + + case PAT_SEQ: + for (p = buf, len = 0; len < nbytes; ++p, ++len) + if (*p != len % 63) { + expected = len % 63; + goto invalid; + } + break; + + case PAT_PIPE: + ret = fwrite(buf, nbytes, 1, stdout); + if (ret > 0) + fflush(stdout); + break; + +invalid: + err("bad OUT byte %zd, expected %02x got %02x\n", + len, expected, *p); + for (p = buf, len = 0; len < nbytes; ++p, ++len) { + if (0 == (len % 32)) + fprintf(stderr, "%4zd:", len); + fprintf(stderr, " %02x", *p); + if (31 == (len % 32)) + fprintf(stderr, "\n"); + } + fflush(stderr); + errno = EILSEQ; + return -1; + } + + return len; +} + + +/******************** Endpoints routines ************************************/ + +static void handle_setup(const struct usb_ctrlrequest *setup) +{ + printf("bRequestType = %d\n", setup->bRequestType); + printf("bRequest = %d\n", setup->bRequest); + printf("wValue = %d\n", le16_to_cpu(setup->wValue)); + printf("wIndex = %d\n", le16_to_cpu(setup->wIndex)); + printf("wLength = %d\n", le16_to_cpu(setup->wLength)); +} + +static ssize_t +ep0_consume(struct thread *ignore, const void *buf, size_t nbytes) +{ + 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", + }; + + const struct usb_functionfs_event *event = buf; + size_t n; + + (void)ignore; + + for (n = nbytes / sizeof *event; n; --n, ++event) + 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]); + if (event->type == FUNCTIONFS_SETUP) + handle_setup(&event->u.setup); + break; + + default: + printf("Event %03u (unknown)\n", event->type); + } + + return nbytes; +} + +static void ep0_init(struct thread *t) +{ + ssize_t ret; + + info("%s: writing descriptors\n", t->filename); + ret = write(t->fd, &descriptors, sizeof descriptors); + die_on(ret < 0, "%s: write: descriptors", t->filename); + + info("%s: writing strings\n", t->filename); + ret = write(t->fd, &strings, sizeof strings); + die_on(ret < 0, "%s: write: strings", t->filename); +} + + +/******************** Main **************************************************/ + +int main(void) +{ + unsigned i; + + /* XXX TODO: Argument parsing missing */ + + init_thread(threads); + ep0_init(threads); + + for (i = 1; i < sizeof threads / sizeof *threads; ++i) + init_thread(threads + i); + + for (i = 1; i < sizeof threads / sizeof *threads; ++i) + start_thread(threads + i); + + start_thread_helper(threads); + + for (i = 1; i < sizeof threads / sizeof *threads; ++i) + join_thread(threads + i); + + return 0; +} diff --git a/tools/usb/hcd-tests.sh b/tools/usb/hcd-tests.sh new file mode 100644 index 00000000000..b30b3dc4c78 --- /dev/null +++ b/tools/usb/hcd-tests.sh @@ -0,0 +1,275 @@ +#!/bin/sh +# +# test types can be passed on the command line: +# +# - control: any device can do this +# - out, in: out needs 'bulk sink' firmware, in needs 'bulk src' +# - iso-out, iso-in: out needs 'iso sink' firmware, in needs 'iso src' +# - halt: needs bulk sink+src, tests halt set/clear from host +# - unlink: needs bulk sink and/or src, test HCD unlink processing +# - loop: needs firmware that will buffer N transfers +# +# run it for hours, days, weeks. +# + +# +# this default provides a steady test load for a bulk device +# +TYPES='control out in' +#TYPES='control out in halt' + +# +# to test HCD code +# +# - include unlink tests +# - add some ${RANDOM}ness +# - connect several devices concurrently (same HC) +# - keep HC's IRQ lines busy with unrelated traffic (IDE, net, ...) +# - add other concurrent system loads +# + +declare -i COUNT BUFLEN + +COUNT=50000 +BUFLEN=2048 + +# NOTE: the 'in' and 'out' cases are usually bulk, but can be +# set up to use interrupt transfers by 'usbtest' module options + + +if [ "$DEVICE" = "" ]; then + echo "testing ALL recognized usbtest devices" + echo "" + TEST_ARGS="-a" +else + TEST_ARGS="" +fi + +do_test () +{ + if ! ./testusb $TEST_ARGS -s $BUFLEN -c $COUNT $* 2>/dev/null + then + echo "FAIL" + exit 1 + fi +} + +ARGS="$*" + +if [ "$ARGS" = "" ]; +then + ARGS="$TYPES" +fi + +# FIXME use /sys/bus/usb/device/$THIS/bConfigurationValue to +# check and change configs + +CONFIG='' + +check_config () +{ + if [ "$CONFIG" = "" ]; then + CONFIG=$1 + echo "assuming $CONFIG configuration" + return + fi + if [ "$CONFIG" = $1 ]; then + return + fi + + echo "** device must be in $1 config, but it's $CONFIG instead" + exit 1 +} + + +echo "TESTING: $ARGS" + +while : true +do + echo $(date) + + for TYPE in $ARGS + do + # restore defaults + COUNT=5000 + BUFLEN=2048 + + # FIXME automatically multiply COUNT by 10 when + # /sys/bus/usb/device/$THIS/speed == "480" + +# COUNT=50000 + + case $TYPE in + control) + # any device, in any configuration, can use this. + echo '** Control test cases:' + + echo "test 9: ch9 postconfig" + do_test -t 9 -c 5000 + echo "test 10: control queueing" + do_test -t 10 -c 5000 + + # this relies on some vendor-specific commands + echo "test 14: control writes" + do_test -t 14 -c 15000 -s 256 -v 1 + + echo "test 21: control writes, unaligned" + do_test -t 21 -c 100 -s 256 -v 1 + + ;; + + out) + check_config sink-src + echo '** Host Write (OUT) test cases:' + + echo "test 1: $COUNT transfers, same size" + do_test -t 1 + echo "test 3: $COUNT transfers, variable/short size" + do_test -t 3 -v 421 + + COUNT=100 + echo "test 17: $COUNT transfers, unaligned DMA map by core" + do_test -t 17 + + echo "test 19: $COUNT transfers, unaligned DMA map by usb_alloc_coherent" + do_test -t 19 + + COUNT=2000 + echo "test 5: $COUNT scatterlists, same size entries" + do_test -t 5 + + # try to trigger short OUT processing bugs + echo "test 7a: $COUNT scatterlists, variable size/short entries" + do_test -t 7 -v 579 + BUFLEN=4096 + echo "test 7b: $COUNT scatterlists, variable size/bigger entries" + do_test -t 7 -v 41 + BUFLEN=64 + echo "test 7c: $COUNT scatterlists, variable size/micro entries" + do_test -t 7 -v 63 + ;; + + iso-out) + check_config sink-src + echo '** Host ISOCHRONOUS Write (OUT) test cases:' + + # at peak iso transfer rates: + # - usb 2.0 high bandwidth, this is one frame. + # - usb 1.1, it's twenty-four frames. + BUFLEN=24500 + + COUNT=1000 + +# COUNT=10000 + + echo "test 15: $COUNT transfers, same size" + # do_test -t 15 -g 3 -v 0 + BUFLEN=32768 + do_test -t 15 -g 8 -v 0 + + # FIXME it'd make sense to have an iso OUT test issuing + # short writes on more packets than the last one + + COUNT=100 + echo "test 22: $COUNT transfers, non aligned" + do_test -t 22 -g 8 -v 0 + + ;; + + in) + check_config sink-src + echo '** Host Read (IN) test cases:' + + # NOTE: these "variable size" reads are just multiples + # of 512 bytes, no EOVERFLOW testing is done yet + + echo "test 2: $COUNT transfers, same size" + do_test -t 2 + echo "test 4: $COUNT transfers, variable size" + do_test -t 4 + + COUNT=100 + echo "test 18: $COUNT transfers, unaligned DMA map by core" + do_test -t 18 + + echo "test 20: $COUNT transfers, unaligned DMA map by usb_alloc_coherent" + do_test -t 20 + + COUNT=2000 + echo "test 6: $COUNT scatterlists, same size entries" + do_test -t 6 + echo "test 8: $COUNT scatterlists, variable size entries" + do_test -t 8 + ;; + + iso-in) + check_config sink-src + echo '** Host ISOCHRONOUS Read (IN) test cases:' + + # at peak iso transfer rates: + # - usb 2.0 high bandwidth, this is one frame. + # - usb 1.1, it's twenty-four frames. + BUFLEN=24500 + + COUNT=1000 + +# COUNT=10000 + + echo "test 16: $COUNT transfers, same size" + # do_test -t 16 -g 3 -v 0 + BUFLEN=32768 + do_test -t 16 -g 8 -v 0 + + # FIXME since iso expects faults, it'd make sense + # to have an iso IN test issuing short reads ... + + COUNT=100 + echo "test 23: $COUNT transfers, unaligned" + do_test -t 23 -g 8 -v 0 + + ;; + + halt) + # NOTE: sometimes hardware doesn't cooperate well with halting + # endpoints from the host side. so long as mass-storage class + # firmware can halt them from the device, don't worry much if + # you can't make this test work on your device. + COUNT=2000 + echo "test 13: $COUNT halt set/clear" + do_test -t 13 + ;; + + unlink) + COUNT=2000 + echo "test 11: $COUNT read unlinks" + do_test -t 11 + + echo "test 12: $COUNT write unlinks" + do_test -t 12 + ;; + + loop) + # defaults need too much buffering for ez-usb devices + BUFLEN=2048 + COUNT=32 + + # modprobe g_zero qlen=$COUNT buflen=$BUFLEN loopdefault + check_config loopback + + # FIXME someone needs to write and merge a version of this + + echo "write $COUNT buffers of $BUFLEN bytes, read them back" + + echo "write $COUNT variable size buffers, read them back" + + ;; + + *) + echo "Don't understand test type $TYPE" + exit 1; + esac + echo '' + done +done + +# vim: sw=4 diff --git a/tools/usb/testusb.c b/tools/usb/testusb.c new file mode 100644 index 00000000000..879f9870a6b --- /dev/null +++ b/tools/usb/testusb.c @@ -0,0 +1,537 @@ +/* $(CROSS_COMPILE)cc -Wall -Wextra -g -lpthread -o testusb testusb.c */ + +/* + * Copyright (c) 2002 by David Brownell + * Copyright (c) 2010 by Samsung Electronics + * Author: Michal Nazarewicz <mina86@mina86.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* + * This program issues ioctls to perform the tests implemented by the + * kernel driver. It can generate a variety of transfer patterns; you + * should make sure to test both regular streaming and mixes of + * transfer sizes (including short transfers). + * + * For more information on how this can be used and on USB testing + * refer to <URL:http://www.linux-usb.org/usbtest/>. + */ + +#include <stdio.h> +#include <string.h> +#include <ftw.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> + +/*-------------------------------------------------------------------------*/ + +#define TEST_CASES 30 + +// FIXME make these public somewhere; usbdevfs.h? + +struct usbtest_param { + // inputs + unsigned test_num; /* 0..(TEST_CASES-1) */ + unsigned iterations; + unsigned length; + unsigned vary; + unsigned sglen; + + // outputs + struct timeval duration; +}; +#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/*-------------------------------------------------------------------------*/ + +/* #include <linux/usb_ch9.h> */ + +#define USB_DT_DEVICE 0x01 +#define USB_DT_INTERFACE 0x04 + +#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USB_CLASS_VENDOR_SPEC 0xff + + +struct usb_device_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u16 bcdUSB; + __u8 bDeviceClass; + __u8 bDeviceSubClass; + __u8 bDeviceProtocol; + __u8 bMaxPacketSize0; + __u16 idVendor; + __u16 idProduct; + __u16 bcdDevice; + __u8 iManufacturer; + __u8 iProduct; + __u8 iSerialNumber; + __u8 bNumConfigurations; +} __attribute__ ((packed)); + +struct usb_interface_descriptor { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bInterfaceNumber; + __u8 bAlternateSetting; + __u8 bNumEndpoints; + __u8 bInterfaceClass; + __u8 bInterfaceSubClass; + __u8 bInterfaceProtocol; + __u8 iInterface; +} __attribute__ ((packed)); + +enum usb_device_speed { + USB_SPEED_UNKNOWN = 0, /* enumerating */ + USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */ + USB_SPEED_HIGH /* usb 2.0 */ +}; + +/*-------------------------------------------------------------------------*/ + +static char *speed (enum usb_device_speed s) +{ + switch (s) { + case USB_SPEED_UNKNOWN: return "unknown"; + case USB_SPEED_LOW: return "low"; + case USB_SPEED_FULL: return "full"; + case USB_SPEED_HIGH: return "high"; + default: return "??"; + } +} + +struct testdev { + struct testdev *next; + char *name; + pthread_t thread; + enum usb_device_speed speed; + unsigned ifnum : 8; + unsigned forever : 1; + int test; + + struct usbtest_param param; +}; +static struct testdev *testdevs; + +static int testdev_ffs_ifnum(FILE *fd) +{ + union { + char buf[255]; + struct usb_interface_descriptor intf; + } u; + + for (;;) { + if (fread(u.buf, 1, 1, fd) != 1) + return -1; + if (fread(u.buf + 1, (unsigned char)u.buf[0] - 1, 1, fd) != 1) + return -1; + + if (u.intf.bLength == sizeof u.intf + && u.intf.bDescriptorType == USB_DT_INTERFACE + && u.intf.bNumEndpoints == 2 + && u.intf.bInterfaceClass == USB_CLASS_VENDOR_SPEC + && u.intf.bInterfaceSubClass == 0 + && u.intf.bInterfaceProtocol == 0) + return (unsigned char)u.intf.bInterfaceNumber; + } +} + +static int testdev_ifnum(FILE *fd) +{ + struct usb_device_descriptor dev; + + if (fread(&dev, sizeof dev, 1, fd) != 1) + return -1; + + if (dev.bLength != sizeof dev || dev.bDescriptorType != USB_DT_DEVICE) + return -1; + + /* FX2 with (tweaked) bulksrc firmware */ + if (dev.idVendor == 0x0547 && dev.idProduct == 0x1002) + return 0; + + /*----------------------------------------------------*/ + + /* devices that start up using the EZ-USB default device and + * which we can use after loading simple firmware. hotplug + * can fxload it, and then run this test driver. + * + * we return false positives in two cases: + * - the device has a "real" driver (maybe usb-serial) that + * renumerates. the device should vanish quickly. + * - the device doesn't have the test firmware installed. + */ + + /* generic EZ-USB FX controller */ + if (dev.idVendor == 0x0547 && dev.idProduct == 0x2235) + return 0; + + /* generic EZ-USB FX2 controller */ + if (dev.idVendor == 0x04b4 && dev.idProduct == 0x8613) + return 0; + + /* CY3671 development board with EZ-USB FX */ + if (dev.idVendor == 0x0547 && dev.idProduct == 0x0080) + return 0; + + /* Keyspan 19Qi uses an21xx (original EZ-USB) */ + if (dev.idVendor == 0x06cd && dev.idProduct == 0x010b) + return 0; + + /*----------------------------------------------------*/ + + /* "gadget zero", Linux-USB test software */ + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a0) + return 0; + + /* user mode subset of that */ + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a4) + return testdev_ffs_ifnum(fd); + /* return 0; */ + + /* iso version of usermode code */ + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a3) + return 0; + + /* some GPL'd test firmware uses these IDs */ + + if (dev.idVendor == 0xfff0 && dev.idProduct == 0xfff0) + return 0; + + /*----------------------------------------------------*/ + + /* iBOT2 high speed webcam */ + if (dev.idVendor == 0x0b62 && dev.idProduct == 0x0059) + return 0; + + /*----------------------------------------------------*/ + + /* the FunctionFS gadget can have the source/sink interface + * anywhere. We look for an interface descriptor that match + * what we expect. We ignore configuratiens thou. */ + + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4ac + && (dev.bDeviceClass == USB_CLASS_PER_INTERFACE + || dev.bDeviceClass == USB_CLASS_VENDOR_SPEC)) + return testdev_ffs_ifnum(fd); + + return -1; +} + +static int find_testdev(const char *name, const struct stat *sb, int flag) +{ + FILE *fd; + int ifnum; + struct testdev *entry; + + (void)sb; /* unused */ + + if (flag != FTW_F) + return 0; + + fd = fopen(name, "rb"); + if (!fd) { + perror(name); + return 0; + } + + ifnum = testdev_ifnum(fd); + fclose(fd); + if (ifnum < 0) + return 0; + + entry = calloc(1, sizeof *entry); + if (!entry) + goto nomem; + + entry->name = strdup(name); + if (!entry->name) { + free(entry); +nomem: + perror("malloc"); + return 0; + } + + entry->ifnum = ifnum; + + /* FIXME update USBDEVFS_CONNECTINFO so it tells about high speed etc */ + + fprintf(stderr, "%s speed\t%s\t%u\n", + speed(entry->speed), entry->name, entry->ifnum); + + entry->next = testdevs; + testdevs = entry; + return 0; +} + +static int +usbdev_ioctl (int fd, int ifno, unsigned request, void *param) +{ + struct usbdevfs_ioctl wrapper; + + wrapper.ifno = ifno; + wrapper.ioctl_code = request; + wrapper.data = param; + + return ioctl (fd, USBDEVFS_IOCTL, &wrapper); +} + +static void *handle_testdev (void *arg) +{ + struct testdev *dev = arg; + int fd, i; + int status; + + if ((fd = open (dev->name, O_RDWR)) < 0) { + perror ("can't open dev file r/w"); + return 0; + } + +restart: + for (i = 0; i < TEST_CASES; i++) { + if (dev->test != -1 && dev->test != i) + continue; + dev->param.test_num = i; + + status = usbdev_ioctl (fd, dev->ifnum, + USBTEST_REQUEST, &dev->param); + if (status < 0 && errno == EOPNOTSUPP) + continue; + + /* FIXME need a "syslog it" option for background testing */ + + /* NOTE: each thread emits complete lines; no fragments! */ + if (status < 0) { + char buf [80]; + int err = errno; + + if (strerror_r (errno, buf, sizeof buf)) { + snprintf (buf, sizeof buf, "error %d", err); + errno = err; + } + printf ("%s test %d --> %d (%s)\n", + dev->name, i, errno, buf); + } else + printf ("%s test %d, %4d.%.06d secs\n", dev->name, i, + (int) dev->param.duration.tv_sec, + (int) dev->param.duration.tv_usec); + + fflush (stdout); + } + if (dev->forever) + goto restart; + + close (fd); + return arg; +} + +static const char *usb_dir_find(void) +{ + static char udev_usb_path[] = "/dev/bus/usb"; + + if (access(udev_usb_path, F_OK) == 0) + return udev_usb_path; + + return NULL; +} + +static int parse_num(unsigned *num, const char *str) +{ + unsigned long val; + char *end; + + errno = 0; + val = strtoul(str, &end, 0); + if (errno || *end || val > UINT_MAX) + return -1; + *num = val; + return 0; +} + +int main (int argc, char **argv) +{ + + int c; + struct testdev *entry; + char *device; + const char *usb_dir = NULL; + int all = 0, forever = 0, not = 0; + int test = -1 /* all */; + struct usbtest_param param; + + /* pick defaults that works with all speeds, without short packets. + * + * Best per-frame data rates: + * high speed, bulk 512 * 13 * 8 = 53248 + * interrupt 1024 * 3 * 8 = 24576 + * full speed, bulk/intr 64 * 19 = 1216 + * interrupt 64 * 1 = 64 + * low speed, interrupt 8 * 1 = 8 + */ + param.iterations = 1000; + param.length = 512; + param.vary = 512; + param.sglen = 32; + + /* for easy use when hotplugging */ + device = getenv ("DEVICE"); + + while ((c = getopt (argc, argv, "D:aA:c:g:hlns:t:v:")) != EOF) + switch (c) { + case 'D': /* device, if only one */ + device = optarg; + continue; + case 'A': /* use all devices with specified USB dir */ + usb_dir = optarg; + /* FALL THROUGH */ + case 'a': /* use all devices */ + device = NULL; + all = 1; + continue; + case 'c': /* count iterations */ + if (parse_num(¶m.iterations, optarg)) + goto usage; + continue; + case 'g': /* scatter/gather entries */ + if (parse_num(¶m.sglen, optarg)) + goto usage; + continue; + case 'l': /* loop forever */ + forever = 1; + continue; + case 'n': /* no test running! */ + not = 1; + continue; + case 's': /* size of packet */ + if (parse_num(¶m.length, optarg)) + goto usage; + continue; + case 't': /* run just one test */ + test = atoi (optarg); + if (test < 0) + goto usage; + continue; + case 'v': /* vary packet size by ... */ + if (parse_num(¶m.vary, optarg)) + goto usage; + continue; + case '?': + case 'h': + default: +usage: + fprintf (stderr, + "usage: %s [options]\n" + "Options:\n" + "\t-D dev only test specific device\n" + "\t-A usb-dir\n" + "\t-a test all recognized devices\n" + "\t-l loop forever(for stress test)\n" + "\t-t testnum only run specified case\n" + "\t-n no test running, show devices to be tested\n" + "Case arguments:\n" + "\t-c iterations default 1000\n" + "\t-s packetsize default 512\n" + "\t-g sglen default 32\n" + "\t-v vary default 512\n", + argv[0]); + return 1; + } + if (optind != argc) + goto usage; + if (!all && !device) { + fprintf (stderr, "must specify '-a' or '-D dev', " + "or DEVICE=/dev/bus/usb/BBB/DDD in env\n"); + goto usage; + } + + /* Find usb device subdirectory */ + if (!usb_dir) { + usb_dir = usb_dir_find(); + if (!usb_dir) { + fputs ("USB device files are missing\n", stderr); + return -1; + } + } + + /* collect and list the test devices */ + if (ftw (usb_dir, find_testdev, 3) != 0) { + fputs ("ftw failed; are USB device files missing?\n", stderr); + return -1; + } + + /* quit, run single test, or create test threads */ + if (!testdevs && !device) { + fputs ("no test devices recognized\n", stderr); + return -1; + } + if (not) + return 0; + if (testdevs && testdevs->next == 0 && !device) + device = testdevs->name; + for (entry = testdevs; entry; entry = entry->next) { + int status; + + entry->param = param; + entry->forever = forever; + entry->test = test; + + if (device) { + if (strcmp (entry->name, device)) + continue; + return handle_testdev (entry) != entry; + } + status = pthread_create (&entry->thread, 0, handle_testdev, entry); + if (status) + perror ("pthread_create"); + } + if (device) { + struct testdev dev; + + /* kernel can recognize test devices we don't */ + fprintf (stderr, "%s: %s may see only control tests\n", + argv [0], device); + + memset (&dev, 0, sizeof dev); + dev.name = device; + dev.param = param; + dev.forever = forever; + dev.test = test; + return handle_testdev (&dev) != &dev; + } + + /* wait for tests to complete */ + for (entry = testdevs; entry; entry = entry->next) { + void *retval; + + if (pthread_join (entry->thread, &retval)) + perror ("pthread_join"); + /* testing errors discarded! */ + } + + return 0; +} |
