// Test case for XHCI buffer expansion regression // // 1) Compile this program: gcc -o xhci_bug xhci_bug.c // 2) Plug in a supported USB to serial device // 3) Get the bus and device numbers from lsusb: // Bus 003 Device 015: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC // 4) Run as root: ./xhci_bug ftdi_sio 3 15 4096 #include #include #include #include #include #include #include #include #define URB_SIZE 64 struct Device { const char *driver; int ep; const char *ep_debug; }; static struct Device devices[] = { { "ftdi_sio", 0x81, "ep02" }, { "pl2303", 0x83, "ep06" }, { "cp210x", 0x82, "ep04" }, { }, }; int main (int argc, char *argv[]) { // Parse command line if (argc < 4) { char drivers[255] = { }; for (int i = 0; devices[i].driver; ++i) { strcat(drivers, devices[i].driver); if (devices[i+1].driver) strcat(drivers, "|"); } printf("usage: xhci_bug <%s> BUS DEV NUM_URBS\n", drivers); return 1; } const char *driver = argv[1]; int bus = atoi(argv[2]); int dev = atoi(argv[3]); int num_urbs = atoi(argv[4]); if (num_urbs > 4096) num_urbs = 4096; // Find the driver struct Device *device = NULL; for (int i = 0; devices[i].driver; ++i) { if (strcmp(devices[i].driver, driver) == 0) { device = &devices[i]; break; } } if (device == NULL) { printf("error: driver %s not found\n", driver); return 1; } // Remove the driver char cmd[1024]; sprintf(cmd, "lsmod | grep %s -q && rmmod %s", driver, driver); system(cmd); // Get the debug path char debug[256] = { }; sprintf( cmd, "find /sys/kernel/debug/usb/xhci -name 'name' | " "while IFS= read -r line; do " "dir=$(dirname $line); " "sys=/sys/bus/usb/devices/$(cat $dir/name); " "grep -q '^%d$' $sys/busnum && " "grep -q '^%d$' $sys/devnum && " "echo $dir; " "done", bus, dev ); FILE *fp = popen(cmd, "r"); char *p = fgets(debug, sizeof(debug), fp); pclose(fp); if (p == NULL) { printf("error: debug path not found\n"); return 1; } debug[strlen(debug)-1] = '\0'; // Print the number of TRBs printf("Before URB submission:\n"); sprintf(cmd, "wc -l %s/%s/trbs", debug, device->ep_debug); system(cmd); printf("\n"); // Open the device char path[256]; sprintf(path, "/dev/bus/usb/%03d/%03d", bus, dev); int fd = open(path, O_RDWR); if (fd < 0) return 1; int rc; int cfg = 1; rc = ioctl(fd, USBDEVFS_SETCONFIGURATION, &cfg); if (rc < 0 && errno != EBUSY) { printf("error: unable to set configuration\n"); close(fd); return 1; } int ifce = 0; rc = ioctl(fd, USBDEVFS_CLAIMINTERFACE, &ifce); if (rc < 0) { printf("error: unable to claim interface\n"); close(fd); return 1; } // Submit URBs printf("Submitting %d URBs\n\n", num_urbs); for (int i = 0; i < num_urbs; ++i) { struct usbdevfs_urb *ioctl_urb = calloc(1, sizeof(*ioctl_urb)); ioctl_urb->type = USBDEVFS_URB_TYPE_BULK; ioctl_urb->endpoint = device->ep; ioctl_urb->buffer = calloc(1, URB_SIZE); ioctl_urb->buffer_length = URB_SIZE; rc = ioctl(fd, USBDEVFS_SUBMITURB, ioctl_urb); if (rc < 0) { printf("error: could not submit urb #%d\n", i); return 1; } } // Print the number of TRBs printf("After URB submission:\n"); system(cmd); printf("\n"); // Close the device close(fd); return 0; }