Use libusb to issue a clear halt to an endpoint

Currently, I work on fixing the xhci_endpoint_reset() function so that an endpoint will get reset properly when the usb device driver calls usb_reset_endpoint() through a call to usb_clear_halt().

In order to trigger the bug, I have to issue a clear halt to an endpoint. For that job, I wrote a simple userspace program using libusb. I called the source file ‘xhci_resetep.c’ and its contents are presented below:

/* xhci_resetep.c */

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <libusb-1.0/libusb.h>

#define VENDOR  0x13fe
#define PRODUCT 0x1a00

#define EP_DIR_OUT 0x0
#define EP_DIR_IN 0x80

int is_myusb(libusb_device *dev)
{
	struct libusb_device_descriptor desc;

	libusb_get_device_descriptor(dev, &desc);

	if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT)
		return 0;

	return -1;
}

int main(int argc, char *argv[])
{
	char c;
	char *ep_dir;
	unsigned int ep = 0, iface = 0;
	libusb_device **udev_list;
	libusb_context *ctx = NULL;
	libusb_device *my_udev = NULL;
	size_t udev_cnt;
	libusb_device_handle *handle;
	int i, ret;

	while ((c = getopt (argc, argv, "e:d:i:")) != -1) {
		switch (c) {
		case 'i':
			iface = strtoul(optarg, NULL, 0);
			break;
		case 'e':
			ep = strtoul(optarg, NULL, 0);
			break;

		case 'd':
			ep_dir = strdup(optarg);
			break;
		default:
			fprintf(stderr, "ERR: Invalid options\n");
			return -1;
		}
	}

	if (!ep) {
		fprintf(stderr, "ERR: -e: Specify endpoint number\n");
		return -1;
	}

	if (!strcmp(ep_dir, "in")) {
		ep |= EP_DIR_IN;
	} else if (!strcmp(ep_dir, "out")) {
		ep |= EP_DIR_OUT;
	} else {
		fprintf(stderr, "ERR: -d [in/out]: Invalid direction\n");
		return -1;
	}

	libusb_init(&ctx);
	udev_cnt = libusb_get_device_list(ctx, &udev_list);
	printf("Number of attached usb devices = %zu\n", udev_cnt);

	for (i = 0; i < udev_cnt; i++) {
		if (!is_myusb(udev_list[i])) {
			my_udev = udev_list[i];
			break;
		}
	}

	if (!my_udev) {
		fprintf(stderr, "ERR: udev not found\n");
		ret = -1;
		goto cleanup;
	}

	ret = libusb_open(my_udev, &handle);
	if (ret) {
		fprintf(stderr, "ERR: failed to open udev ");
		switch(ret) {
		case LIBUSB_ERROR_NO_MEM:
			fprintf(stderr, "(memory allocation failed)\n");
			break;
		case LIBUSB_ERROR_ACCESS:
			fprintf(stderr, "(no permission)\n");
			break;
		case LIBUSB_ERROR_NO_DEVICE:
			fprintf(stderr, "(udev not found)\n");
		}
		ret = -1;
		goto cleanup;
	}

	/* detach udev's driver, if present */
	if (libusb_kernel_driver_active(handle, iface))
		libusb_detach_kernel_driver(handle, iface);

	ret = libusb_claim_interface(handle, iface);
	if (ret) {
		switch(ret) {
		case LIBUSB_ERROR_NOT_FOUND:
			fprintf(stderr, "ERR: iface not found\n");
			break;
		case LIBUSB_ERROR_BUSY:
			fprintf(stderr, "ERR: iface claimed by another "
				"driver\n");
			break;
		case LIBUSB_ERROR_NO_DEVICE:
			fprintf(stderr, "ERR: udev not found\n");
		}
		ret = -1;
		goto release;
	}

	ret = libusb_clear_halt(handle, ep);
	if (ret) {
		switch(ret) {
		case LIBUSB_ERROR_NOT_FOUND:
			fprintf(stderr, "ERR: ep %d not found\n", ep);
			break;
		case LIBUSB_ERROR_NO_DEVICE:
			fprintf(stderr, "ERR: udev not found\n");
		}
		ret = -1;
	}

release:
	libusb_attach_kernel_driver(handle, iface);
	libusb_close(handle);

cleanup:
	libusb_free_device_list(udev_list, 1);
	libusb_exit(ctx);

	return ret;
}

To build xhci_resetep.c, do:

$ gcc xhci_resetep.c -lusb-1.0 -o resetep

The executable ‘resetep’ takes the following command line parameters:

-e    : endpoint number
-d    : direction of the endpoint, in or out
-i    : interface number

Also, you need to set appropriately the values for the VENDOR and PRODUCT macros in the source file to correspond to the vendor and product ids of the usb device to which you want to issue the clear halt.
In order to set properly the above parameters retrieve the necessary information using the following commands:

$ usb-devices
AND
$ lsusb -v

For example:

$ sudo ./resetep -e 1 -d in -i 0
$ dmesg | tail -5
[snip]
[   75.253235] xhci_hcd 0000:00:10.0: Endpoint 0x81 not halted, refusing to reset

And voila! the bug was reproduced …

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s