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 | 41 | ||||
| -rw-r--r-- | tools/usb/hcd-tests.sh | 275 | ||||
| -rw-r--r-- | tools/usb/testusb.c | 80 | 
10 files changed, 1335 insertions, 79 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 index bbe2e3a2ea6..a87e99f37c5 100644 --- a/tools/usb/ffs-test.c +++ b/tools/usb/ffs-test.c @@ -2,7 +2,7 @@   * ffs-test.c.c -- user mode filesystem api for usb composite function   *   * Copyright (C) 2010 Samsung Electronics - *                    Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + *                    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 @@ -36,8 +36,9 @@  #include <sys/stat.h>  #include <sys/types.h>  #include <unistd.h> +#include <tools/le_byteshift.h> -#include <linux/usb/functionfs.h> +#include "../../include/uapi/linux/usb/functionfs.h"  /******************** Little Endian Handling ********************************/ @@ -47,34 +48,6 @@  #define le32_to_cpu(x)  le32toh(x)  #define le16_to_cpu(x)  le16toh(x) -static inline __u16 get_unaligned_le16(const void *_ptr) -{ -	const __u8 *ptr = _ptr; -	return ptr[0] | (ptr[1] << 8); -} - -static inline __u32 get_unaligned_le32(const void *_ptr) -{ -	const __u8 *ptr = _ptr; -	return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24); -} - -static inline void put_unaligned_le16(__u16 val, void *_ptr) -{ -	__u8 *ptr = _ptr; -	*ptr++ = val; -	*ptr++ = val >> 8; -} - -static inline void put_unaligned_le32(__u32 val, void *_ptr) -{ -	__u8 *ptr = _ptr; -	*ptr++ = val; -	*ptr++ = val >>  8; -	*ptr++ = val >> 16; -	*ptr++ = val >> 24; -} -  /******************** Messages and Errors ***********************************/ @@ -143,8 +116,8 @@ static const struct {  	.header = {  		.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC),  		.length = cpu_to_le32(sizeof descriptors), -		.fs_count = 3, -		.hs_count = 3, +		.fs_count = cpu_to_le32(3), +		.hs_count = cpu_to_le32(3),  	},  	.fs_descs = {  		.intf = { @@ -324,7 +297,7 @@ static void *start_thread_helper(void *arg)  		ret = t->in(t, t->buf, t->buf_size);  		if (ret > 0) { -			ret = t->out(t, t->buf, t->buf_size); +			ret = t->out(t, t->buf, ret);  			name = out_name;  			op = "write";  		} else { @@ -450,7 +423,7 @@ invalid:  		    len, expected, *p);  		for (p = buf, len = 0; len < nbytes; ++p, ++len) {  			if (0 == (len % 32)) -				fprintf(stderr, "%4d:", len); +				fprintf(stderr, "%4zd:", len);  			fprintf(stderr, " %02x", *p);  			if (31 == (len % 32))  				fprintf(stderr, "\n"); 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 index f08e8946384..879f9870a6b 100644 --- a/tools/usb/testusb.c +++ b/tools/usb/testusb.c @@ -3,7 +3,7 @@  /*   * Copyright (c) 2002 by David Brownell   * Copyright (c) 2010 by Samsung Electronics - * Author: Michal Nazarewicz <m.nazarewicz@samsung.com> + * 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 @@ -253,9 +253,6 @@ static int find_testdev(const char *name, const struct stat *sb, int flag)  	if (flag != FTW_F)  		return 0; -	/* ignore /proc/bus/usb/{devices,drivers} */ -	if (strrchr(name, '/')[1] == 'd') -		return 0;  	fd = fopen(name, "rb");  	if (!fd) { @@ -282,8 +279,7 @@ nomem:  	entry->ifnum = ifnum; -	/* FIXME ask usbfs what speed; update USBDEVFS_CONNECTINFO so -	 * it tells about high speed etc */ +	/* 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); @@ -354,27 +350,12 @@ restart:  	return arg;  } -static const char *usbfs_dir_find(void) +static const char *usb_dir_find(void)  { -	static char usbfs_path_0[] = "/dev/usb/devices"; -	static char usbfs_path_1[] = "/proc/bus/usb/devices"; - -	static char *const usbfs_paths[] = { -		usbfs_path_0, usbfs_path_1 -	}; - -	static char *const * -		end = usbfs_paths + sizeof usbfs_paths / sizeof *usbfs_paths; - -	char *const *it = usbfs_paths; -	do { -		int fd = open(*it, O_RDONLY); -		close(fd); -		if (fd >= 0) { -			strrchr(*it, '/')[0] = '\0'; -			return *it; -		} -	} while (++it != end); +	static char udev_usb_path[] = "/dev/bus/usb"; + +	if (access(udev_usb_path, F_OK) == 0) +		return udev_usb_path;  	return NULL;  } @@ -398,7 +379,7 @@ int main (int argc, char **argv)  	int			c;  	struct testdev		*entry;  	char			*device; -	const char		*usbfs_dir = NULL; +	const char		*usb_dir = NULL;  	int			all = 0, forever = 0, not = 0;  	int			test = -1 /* all */;  	struct usbtest_param	param; @@ -420,13 +401,13 @@ int main (int argc, char **argv)  	/* for easy use when hotplugging */  	device = getenv ("DEVICE"); -	while ((c = getopt (argc, argv, "D:aA:c:g:hns:t:v:")) != EOF) +	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 usbfs dir */ -		usbfs_dir = optarg; +	case 'A':	/* use all devices with specified USB dir */ +		usb_dir = optarg;  		/* FALL THROUGH */  	case 'a':	/* use all devices */  		device = NULL; @@ -463,32 +444,43 @@ int main (int argc, char **argv)  	case 'h':  	default:  usage: -		fprintf (stderr, "usage: %s [-n] [-D dev | -a | -A usbfs-dir]\n" -			"\t[-c iterations]  [-t testnum]\n" -			"\t[-s packetsize] [-g sglen] [-v vary]\n", -			argv [0]); +		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=/proc/bus/usb/BBB/DDD in env\n"); +			"or DEVICE=/dev/bus/usb/BBB/DDD in env\n");  		goto usage;  	} -	/* Find usbfs mount point */ -	if (!usbfs_dir) { -		usbfs_dir = usbfs_dir_find(); -		if (!usbfs_dir) { -			fputs ("usbfs files are missing\n", stderr); +	/* 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 (usbfs_dir, find_testdev, 3) != 0) { -		fputs ("ftw failed; is usbfs missing?\n", stderr); +	if (ftw (usb_dir, find_testdev, 3) != 0) { +		fputs ("ftw failed; are USB device files missing?\n", stderr);  		return -1;  	} @@ -514,10 +506,8 @@ usage:  			return handle_testdev (entry) != entry;  		}  		status = pthread_create (&entry->thread, 0, handle_testdev, entry); -		if (status) { +		if (status)  			perror ("pthread_create"); -			continue; -		}  	}  	if (device) {  		struct testdev		dev;  | 
