| /* |
| * BRLTTY - A background process providing access to the console screen (when in |
| * text mode) for a blind person using a refreshable braille display. |
| * |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. |
| * |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. |
| * |
| * This is free software, placed under the terms of the |
| * GNU Lesser General Public License, as published by the Free Software |
| * Foundation; either version 2.1 of the License, or (at your option) any |
| * later version. Please see the file LICENSE-LGPL for details. |
| * |
| * Web Page: http://brltty.app/ |
| * |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. |
| */ |
| |
| #include "prologue.h" |
| |
| #include <stdio.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/vfs.h> |
| #include <sys/ioctl.h> |
| #include <linux/usbdevice_fs.h> |
| |
| #ifndef USBDEVFS_DISCONNECT |
| #define USBDEVFS_DISCONNECT _IO('U', 22) |
| #endif /* USBDEVFS_DISCONNECT */ |
| |
| #ifndef USBDEVFS_CONNECT |
| #define USBDEVFS_CONNECT _IO('U', 23) |
| #endif /* USBDEVFS_CONNECT */ |
| |
| #include "log.h" |
| #include "parameters.h" |
| #include "bitfield.h" |
| #include "strfmt.h" |
| #include "file.h" |
| #include "parse.h" |
| #include "timing.h" |
| #include "async_handle.h" |
| #include "async_wait.h" |
| #include "async_alarm.h" |
| #include "async_io.h" |
| #include "async_signal.h" |
| #include "mntpt.h" |
| #include "io_usb.h" |
| #include "usb_internal.h" |
| |
| typedef struct { |
| char *sysfsPath; |
| char *usbfsPath; |
| UsbDeviceDescriptor usbDescriptor; |
| } UsbHostDevice; |
| |
| static Queue *usbHostDevices = NULL; |
| |
| struct UsbDeviceExtensionStruct { |
| const UsbHostDevice *host; |
| int usbfsFile; |
| AsyncHandle usbfsMonitorHandle; |
| }; |
| |
| struct UsbEndpointExtensionStruct { |
| Queue *completedRequests; |
| |
| struct { |
| struct { |
| AsyncHandle handle; |
| int number; |
| } signal; |
| } monitor; |
| }; |
| |
| static int |
| usbOpenUsbfsFile (UsbDeviceExtension *devx) { |
| if (devx->usbfsFile == -1) { |
| int openFlags = O_RDWR; |
| |
| #ifdef O_CLOEXEC |
| openFlags |= O_CLOEXEC; |
| #endif /* O_CLOEXEC */ |
| |
| if ((devx->usbfsFile = open(devx->host->usbfsPath, openFlags)) == -1) { |
| logMessage(LOG_ERR, "USBFS open error: %s: %s", |
| devx->host->usbfsPath, strerror(errno)); |
| return 0; |
| } |
| |
| logMessage(LOG_CATEGORY(USB_IO), "usbfs file opened: %s fd=%d", |
| devx->host->usbfsPath, devx->usbfsFile); |
| } |
| |
| return 1; |
| } |
| |
| static void |
| usbCloseUsbfsFile (UsbDeviceExtension *devx) { |
| if (devx->usbfsFile != -1) { |
| close(devx->usbfsFile); |
| devx->usbfsFile = -1; |
| } |
| } |
| |
| int |
| usbDisableAutosuspend (UsbDevice *device) { |
| UsbDeviceExtension *devx = device->extension; |
| int ok = 0; |
| |
| if (devx->host->sysfsPath) { |
| char *path = makePath(devx->host->sysfsPath, "power/autosuspend"); |
| |
| if (path) { |
| int openFlags = O_WRONLY; |
| |
| #ifdef O_CLOEXEC |
| openFlags |= O_CLOEXEC; |
| #endif /* O_CLOEXEC */ |
| |
| int file = open(path, openFlags); |
| |
| if (file != -1) { |
| static const char *const values[] = {"-1", "0", NULL}; |
| const char *const *value = values; |
| |
| while (*value) { |
| size_t length = strlen(*value); |
| ssize_t result = write(file, *value, length); |
| |
| if (result != -1) { |
| ok = 1; |
| break; |
| } |
| |
| if (errno != EINVAL) { |
| logMessage(LOG_ERR, "write error: %s: %s", path, strerror(errno)); |
| break; |
| } |
| |
| ++value; |
| } |
| |
| close(file); |
| } else { |
| logMessage((errno == ENOENT)? LOG_CATEGORY(USB_IO): LOG_ERR, |
| "open error: %s: %s", path, strerror(errno)); |
| } |
| |
| free(path); |
| } |
| } |
| |
| return ok; |
| } |
| |
| static char * |
| usbGetDriver (UsbDevice *device, unsigned char interface) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| struct usbdevfs_getdriver arg; |
| |
| memset(&arg, 0, sizeof(arg)); |
| arg.interface = interface; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_GETDRIVER, &arg) != -1) { |
| char *name = strdup(arg.driver); |
| if (name) return name; |
| logMallocError(); |
| } else { |
| logSystemError("USB get driver name"); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| usbControlDriver ( |
| UsbDevice *device, |
| unsigned char interface, |
| int code, |
| void *data |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| struct usbdevfs_ioctl arg; |
| |
| memset(&arg, 0, sizeof(arg)); |
| arg.ifno = interface; |
| arg.ioctl_code = code; |
| arg.data = data; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_IOCTL, &arg) != -1) return 1; |
| logSystemError("USB driver control"); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| usbDisconnectDriver (UsbDevice *device, unsigned char interface) { |
| #ifdef USBDEVFS_DISCONNECT |
| logMessage(LOG_CATEGORY(USB_IO), "disconnecting kernel driver: Int:%u", interface); |
| if (usbControlDriver(device, interface, USBDEVFS_DISCONNECT, NULL)) return 1; |
| #else /* USBDEVFS_DISCONNECT */ |
| errno = ENOSYS; |
| #endif /* USBDEVFS_DISCONNECT */ |
| |
| logSystemError("USAB driver disconnect"); |
| return 0; |
| } |
| |
| static int |
| usbDisconnectInterface (UsbDevice *device, unsigned char interface) { |
| char *driver = usbGetDriver(device, interface); |
| |
| if (driver) { |
| int isUsbfs = strcmp(driver, "usbfs") == 0; |
| |
| logMessage(LOG_WARNING, "USB interface in use: %u (%s)", interface, driver); |
| free(driver); |
| |
| if (isUsbfs) { |
| logPossibleCause("another " PACKAGE_TARNAME " process may be accessing the same device"); |
| logPossibleCause("the device may be attached to a virtual machine running on this host"); |
| errno = EBUSY; |
| } else if (usbDisconnectDriver(device, interface)) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbSetConfiguration (UsbDevice *device, unsigned char configuration) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration); |
| |
| if (usbOpenUsbfsFile(devx)) { |
| unsigned int arg = configuration; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_SETCONFIGURATION, &arg) != -1) return 1; |
| logSystemError("USB configuration set"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbClaimInterface (UsbDevice *device, unsigned char interface) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface); |
| |
| if (usbOpenUsbfsFile(devx)) { |
| int disconnected = 0; |
| |
| while (1) { |
| unsigned int arg = interface; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_CLAIMINTERFACE, &arg) != -1) return 1; |
| if (errno != EBUSY) break; |
| if (disconnected) break; |
| |
| if (!usbDisconnectInterface(device, interface)) { |
| errno = EBUSY; |
| break; |
| } |
| disconnected = 1; |
| } |
| |
| logSystemError("USB interface claim"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbReleaseInterface (UsbDevice *device, unsigned char interface) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface); |
| |
| if (usbOpenUsbfsFile(devx)) { |
| unsigned int arg = interface; |
| if (ioctl(devx->usbfsFile, USBDEVFS_RELEASEINTERFACE, &arg) != -1) return 1; |
| if (errno == ENODEV) return 1; |
| logSystemError("USB interface release"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbSetAlternative ( |
| UsbDevice *device, |
| unsigned char interface, |
| unsigned char alternative |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative); |
| |
| if (usbOpenUsbfsFile(devx)) { |
| struct usbdevfs_setinterface arg; |
| |
| memset(&arg, 0, sizeof(arg)); |
| arg.interface = interface; |
| arg.altsetting = alternative; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_SETINTERFACE, &arg) != -1) return 1; |
| logSystemError("USB alternative set"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbResetDevice (UsbDevice *device) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "reset device"); |
| |
| if (usbOpenUsbfsFile(devx)) { |
| unsigned int arg = 0; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_RESET, &arg) != -1) return 1; |
| logSystemError("USB device reset"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbClearHalt (UsbDevice *device, unsigned char endpointAddress) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress); |
| |
| if (usbOpenUsbfsFile(devx)) { |
| unsigned int arg = endpointAddress; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_CLEAR_HALT, &arg) != -1) return 1; |
| logSystemError("USB endpoint clear"); |
| } |
| |
| return 0; |
| } |
| |
| ssize_t |
| usbControlTransfer ( |
| UsbDevice *device, |
| uint8_t direction, |
| uint8_t recipient, |
| uint8_t type, |
| uint8_t request, |
| uint16_t value, |
| uint16_t index, |
| void *buffer, |
| uint16_t length, |
| int timeout |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| UsbSetupPacket setup; |
| struct usbdevfs_ctrltransfer arg; |
| |
| usbMakeSetupPacket(&setup, direction, recipient, type, |
| request, value, index, length); |
| |
| memset(&arg, 0, sizeof(arg)); |
| arg.bRequestType = setup.bRequestType; |
| arg.bRequest = setup.bRequest; |
| arg.wValue = getLittleEndian16(setup.wValue); |
| arg.wIndex = getLittleEndian16(setup.wIndex); |
| arg.wLength = getLittleEndian16(setup.wLength); |
| arg.data = buffer; |
| arg.timeout = timeout; |
| |
| if (direction == UsbControlDirection_Output) { |
| if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length); |
| } |
| |
| { |
| ssize_t count = ioctl(devx->usbfsFile, USBDEVFS_CONTROL, &arg); |
| |
| if (count != -1) { |
| if (direction == UsbControlDirection_Input) { |
| logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, count); |
| } |
| |
| return count; |
| } |
| |
| logSystemError("USB control transfer"); |
| } |
| } |
| |
| return -1; |
| } |
| |
| static UsbEndpoint * |
| usbReapURB (UsbDevice *device, int wait) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| struct usbdevfs_urb *urb; |
| |
| if (ioctl(devx->usbfsFile, |
| wait? USBDEVFS_REAPURB: USBDEVFS_REAPURBNDELAY, |
| &urb) != -1) { |
| if (urb) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetEndpoint(device, urb->endpoint))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| |
| if (enqueueItem(eptx->completedRequests, urb)) return endpoint; |
| logSystemError("USB completed request enqueue"); |
| free(urb); |
| } |
| } else { |
| errno = EAGAIN; |
| } |
| } else { |
| if (wait || (errno != EAGAIN)) logSystemError("USB URB reap"); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| typedef struct { |
| const struct usbdevfs_urb *urb; |
| const char *action; |
| } UsbFormatUrbData; |
| |
| static size_t |
| usbFormatURB (char *buffer, size_t size, const void *data) { |
| const UsbFormatUrbData *fud = data; |
| const struct usbdevfs_urb *urb = fud->urb; |
| size_t length; |
| |
| STR_BEGIN(buffer, size); |
| STR_PRINTF("%s URB:", fud->action); |
| |
| STR_PRINTF(" Adr:%p", urb); |
| STR_PRINTF(" Ept:%02X", urb->endpoint); |
| |
| STR_PRINTF(" Typ:%u", urb->type); |
| { |
| static const char *const types[] = { |
| [USBDEVFS_URB_TYPE_CONTROL] = "ctl", |
| [USBDEVFS_URB_TYPE_BULK] = "blk", |
| [USBDEVFS_URB_TYPE_INTERRUPT] = "int", |
| [USBDEVFS_URB_TYPE_ISO] = "iso" |
| }; |
| |
| if (urb->type < ARRAY_COUNT(types)) { |
| const char *type = types[urb->type]; |
| if (type) STR_PRINTF("(%s)", type); |
| } |
| } |
| |
| STR_PRINTF(" Flg:%02X", urb->flags); |
| { |
| typedef struct { |
| unsigned char bit; |
| const char *name; |
| } UrbFlagEntry; |
| |
| static const UrbFlagEntry urbFlagTable[] = { |
| #ifdef USBDEVFS_URB_SHORT_NOT_OK |
| { .bit = USBDEVFS_URB_SHORT_NOT_OK, |
| .name = "spd" |
| }, |
| #endif /* USBDEVFS_URB_SHORT_NOT_OK */ |
| |
| #ifdef USBDEVFS_URB_ISO_ASAP |
| { .bit = USBDEVFS_URB_ISO_ASAP, |
| .name = "isa" |
| }, |
| #endif /* USBDEVFS_URB_ISO_ASAP */ |
| |
| #ifdef USBDEVFS_URB_BULK_CONTINUATION |
| { .bit = USBDEVFS_URB_BULK_CONTINUATION, |
| .name = "bkc" |
| }, |
| #endif /* USBDEVFS_URB_BULK_CONTINUATION */ |
| |
| #ifdef USBDEVFS_URB_NO_FSBR |
| { .bit = USBDEVFS_URB_NO_FSBR, |
| .name = "nof" |
| }, |
| #endif /* USBDEVFS_URB_NO_FSBR */ |
| |
| #ifdef USBDEVFS_URB_ZERO_PACKET |
| { .bit = USBDEVFS_URB_ZERO_PACKET, |
| .name = "zpk" |
| }, |
| #endif /* USBDEVFS_URB_ZERO_PACKET */ |
| |
| #ifdef USBDEVFS_URB_NO_INTERRUPT |
| { .bit = USBDEVFS_URB_NO_INTERRUPT, |
| .name = "noi" |
| }, |
| #endif /* USBDEVFS_URB_NO_INTERRUPT */ |
| |
| { .bit=0, .name=NULL } |
| }; |
| |
| int first = 1; |
| const UrbFlagEntry *flag = urbFlagTable; |
| |
| while (flag->bit) { |
| if (urb->flags & flag->bit) { |
| STR_PRINTF("%c%s", (first? '(': ','), flag->name); |
| first = 0; |
| } |
| |
| flag += 1; |
| } |
| |
| if (!first) STR_PRINTF(")"); |
| } |
| |
| STR_PRINTF(" Buf:%p", urb->buffer); |
| STR_PRINTF(" Siz:%d", urb->buffer_length); |
| STR_PRINTF(" Len:%d", urb->actual_length); |
| STR_PRINTF(" Sig:%d", urb->signr); |
| |
| { |
| int error = urb->status; |
| STR_PRINTF(" Err:%d", error); |
| |
| if (error) { |
| if (error < 0) error = -error; |
| STR_PRINTF("(%s)", strerror(error)); |
| } |
| } |
| |
| length = STR_LENGTH; |
| STR_END; |
| return length; |
| } |
| |
| static void |
| usbLogURB (const struct usbdevfs_urb *urb, const char *action) { |
| const UsbFormatUrbData fud = { |
| .urb = urb, |
| .action = action |
| }; |
| |
| logData(LOG_CATEGORY(USB_IO), usbFormatURB, &fud); |
| } |
| |
| static struct usbdevfs_urb * |
| usbMakeURB ( |
| const UsbEndpointDescriptor *endpoint, |
| void *buffer, |
| size_t length, |
| void *context |
| ) { |
| struct usbdevfs_urb *urb; |
| |
| if ((urb = malloc(sizeof(*urb) + length))) { |
| memset(urb, 0, sizeof(*urb)); |
| urb->endpoint = endpoint->bEndpointAddress; |
| urb->flags = 0; |
| urb->signr = 0; |
| urb->usercontext = context; |
| |
| if (!(urb->buffer_length = length)) { |
| urb->buffer = NULL; |
| } else { |
| urb->buffer = urb + 1; |
| if (buffer) memcpy(urb->buffer, buffer, length); |
| } |
| |
| switch (USB_ENDPOINT_TRANSFER(endpoint)) { |
| case UsbEndpointTransfer_Control: |
| urb->type = USBDEVFS_URB_TYPE_CONTROL; |
| break; |
| |
| case UsbEndpointTransfer_Isochronous: |
| urb->type = USBDEVFS_URB_TYPE_ISO; |
| break; |
| |
| case UsbEndpointTransfer_Interrupt: |
| urb->type = USBDEVFS_URB_TYPE_INTERRUPT; |
| break; |
| |
| case UsbEndpointTransfer_Bulk: |
| urb->type = USBDEVFS_URB_TYPE_BULK; |
| break; |
| } |
| |
| return urb; |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| usbSubmitURB (struct usbdevfs_urb *urb, UsbEndpoint *endpoint) { |
| const UsbEndpointDescriptor *descriptor = endpoint->descriptor; |
| UsbDevice *device = endpoint->device; |
| UsbDeviceExtension *devx = device->extension; |
| |
| while (1) { |
| usbLogURB(urb, "submitting"); |
| |
| if ((urb->endpoint & UsbEndpointDirection_Mask) == UsbEndpointDirection_Output) { |
| logBytes(LOG_CATEGORY(USB_IO), "URB output", urb->buffer, urb->buffer_length); |
| } |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_SUBMITURB, urb) != -1) { |
| logMessage(LOG_CATEGORY(USB_IO), "URB submitted"); |
| return 1; |
| } |
| |
| if ((errno == EINVAL) && |
| (USB_ENDPOINT_TRANSFER(descriptor) == UsbEndpointTransfer_Interrupt) && |
| (urb->type == USBDEVFS_URB_TYPE_BULK)) { |
| logMessage(LOG_CATEGORY(USB_IO), "changing URB type from bulk to interrupt"); |
| urb->type = USBDEVFS_URB_TYPE_INTERRUPT; |
| continue; |
| } |
| |
| /* UHCI support returns ENXIO if a URB is already submitted. */ |
| logSystemError("USB URB submit"); |
| return 0; |
| } |
| } |
| |
| void * |
| usbSubmitRequest ( |
| UsbDevice *device, |
| unsigned char endpointAddress, |
| void *buffer, |
| size_t length, |
| void *context |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetEndpoint(device, endpointAddress))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| struct usbdevfs_urb *urb; |
| |
| if ((urb = usbMakeURB(endpoint->descriptor, buffer, length, context))) { |
| urb->actual_length = 0; |
| urb->signr = eptx->monitor.signal.number; |
| |
| if (usbSubmitURB(urb, endpoint)) { |
| return urb; |
| } |
| |
| free(urb); |
| } else { |
| logSystemError("USB URB allocate"); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| int |
| usbCancelRequest (UsbDevice *device, void *request) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| int reap = 1; |
| |
| if (ioctl(devx->usbfsFile, USBDEVFS_DISCARDURB, request) == -1) { |
| if (errno == ENODEV) { |
| reap = 0; |
| } else if (errno != EINVAL) { |
| logSystemError("USB URB discard"); |
| } |
| } |
| |
| { |
| struct usbdevfs_urb *urb = request; |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetEndpoint(device, urb->endpoint))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| int found = 1; |
| |
| while (!deleteItem(eptx->completedRequests, request)) { |
| if (!reap) break; |
| |
| if (!usbReapURB(device, 0)) { |
| found = 0; |
| break; |
| } |
| } |
| |
| if (found) { |
| free(request); |
| return 1; |
| } |
| |
| logMessage(LOG_ERR, "USB request not found: urb=%p ept=%02X", |
| urb, urb->endpoint); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| void * |
| usbReapResponse ( |
| UsbDevice *device, |
| unsigned char endpointAddress, |
| UsbResponse *response, |
| int wait |
| ) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetEndpoint(device, endpointAddress))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| struct usbdevfs_urb *urb; |
| |
| while (!(urb = dequeueItem(eptx->completedRequests))) { |
| if (!usbReapURB(device, wait)) return NULL; |
| } |
| |
| usbLogURB(urb, "reaped"); |
| |
| response->context = urb->usercontext; |
| response->buffer = urb->buffer; |
| response->size = urb->buffer_length; |
| |
| if ((response->error = urb->status)) { |
| if (response->error < 0) response->error = -response->error; |
| errno = response->error; |
| logSystemError("USB URB status"); |
| response->count = -1; |
| } else { |
| response->count = urb->actual_length; |
| |
| switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) { |
| case UsbEndpointDirection_Input: |
| if (!usbApplyInputFilters(endpoint, response->buffer, response->size, &response->count)) { |
| response->error = EIO; |
| response->count = -1; |
| } |
| break; |
| } |
| } |
| |
| return urb; |
| } |
| |
| return NULL; |
| } |
| |
| static ssize_t |
| usbBulkTransfer ( |
| UsbEndpoint *endpoint, |
| void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| UsbDeviceExtension *devx = endpoint->device->extension; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| struct usbdevfs_bulktransfer arg; |
| |
| memset(&arg, 0, sizeof(arg)); |
| arg.ep = endpoint->descriptor->bEndpointAddress; |
| arg.data = buffer; |
| arg.len = length; |
| arg.timeout = timeout; |
| |
| { |
| int count = ioctl(devx->usbfsFile, USBDEVFS_BULK, &arg); |
| if (count != -1) return count; |
| if (USB_ENDPOINT_DIRECTION(endpoint->descriptor) == UsbEndpointDirection_Input) |
| if (errno == ETIMEDOUT) |
| errno = EAGAIN; |
| if (errno != EAGAIN) logSystemError("USB bulk transfer"); |
| } |
| } |
| |
| return -1; |
| } |
| |
| static struct usbdevfs_urb * |
| usbInterruptTransfer ( |
| UsbEndpoint *endpoint, |
| void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| UsbDevice *device = endpoint->device; |
| struct usbdevfs_urb *urb = usbSubmitRequest(device, |
| endpoint->descriptor->bEndpointAddress, |
| buffer, length, NULL); |
| |
| if (urb) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| int retryInterval = endpoint->descriptor->bInterval + 1; |
| TimePeriod period; |
| |
| if (timeout) startTimePeriod(&period, timeout); |
| |
| do { |
| if (usbReapURB(device, 0) && |
| deleteItem(eptx->completedRequests, urb)) { |
| if (!urb->status) return urb; |
| if ((errno = urb->status) < 0) errno = -errno; |
| free(urb); |
| break; |
| } |
| |
| if (!timeout || afterTimePeriod(&period, NULL)) { |
| usbCancelRequest(device, urb); |
| errno = ETIMEDOUT; |
| break; |
| } |
| |
| asyncWait(retryInterval); |
| } while (1); |
| } |
| |
| return NULL; |
| } |
| |
| int |
| usbMonitorInputEndpoint ( |
| UsbDevice *device, unsigned char endpointNumber, |
| AsyncMonitorCallback *callback, void *data |
| ) { |
| return usbMonitorInputPipe(device, endpointNumber, callback, data); |
| } |
| |
| ssize_t |
| usbReadEndpoint ( |
| UsbDevice *device, |
| unsigned char endpointNumber, |
| void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| ssize_t count = -1; |
| UsbEndpoint *endpoint; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "reading endpoint: %u", endpointNumber); |
| |
| if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) { |
| UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(endpoint->descriptor); |
| |
| switch (transfer) { |
| case UsbEndpointTransfer_Interrupt: |
| if (!LINUX_USB_INPUT_TREAT_INTERRUPT_AS_BULK) { |
| struct usbdevfs_urb *urb = usbInterruptTransfer(endpoint, NULL, length, timeout); |
| |
| if (urb) { |
| count = urb->actual_length; |
| if (count > length) count = length; |
| memcpy(buffer, urb->buffer, count); |
| free(urb); |
| } |
| |
| break; |
| } |
| /* fall through */ |
| |
| case UsbEndpointTransfer_Bulk: |
| count = usbBulkTransfer(endpoint, buffer, length, timeout); |
| break; |
| |
| default: |
| logMessage(LOG_ERR, "USB input transfer not supported: %d", transfer); |
| errno = ENOSYS; |
| break; |
| } |
| |
| if (count != -1) { |
| if (!usbApplyInputFilters(endpoint, buffer, length, &count)) { |
| errno = EIO; |
| count = -1; |
| } |
| } |
| } |
| |
| return count; |
| } |
| |
| ssize_t |
| usbWriteEndpoint ( |
| UsbDevice *device, |
| unsigned char endpointNumber, |
| const void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) { |
| UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(endpoint->descriptor); |
| |
| usbLogEndpointData(endpoint, "output", buffer, length); |
| |
| switch (transfer) { |
| case UsbEndpointTransfer_Interrupt: |
| case UsbEndpointTransfer_Bulk: |
| return usbBulkTransfer(endpoint, (void *)buffer, length, timeout); |
| /* |
| case UsbEndpointTransfer_Interrupt: { |
| struct usbdevfs_urb *urb = usbInterruptTransfer(endpoint, (void *)buffer, length, timeout); |
| |
| if (urb) { |
| ssize_t count = urb->actual_length; |
| free(urb); |
| return count; |
| } |
| break; |
| } |
| */ |
| default: |
| logMessage(LOG_ERR, "USB output transfer not supported: %d", transfer); |
| errno = ENOSYS; |
| break; |
| } |
| } |
| return -1; |
| } |
| |
| int |
| usbReadDeviceDescriptor (UsbDevice *device) { |
| device->descriptor = device->extension->host->usbDescriptor; |
| return 1; |
| } |
| |
| static int |
| usbHandleInputURB (UsbEndpoint *endpoint, struct usbdevfs_urb *urb) { |
| deleteItem(endpoint->direction.input.pending.requests, urb); |
| |
| if (urb->actual_length < 0) { |
| usbLogInputProblem(endpoint, "data not available"); |
| return 0; |
| } |
| |
| return usbHandleInputResponse(endpoint, urb->buffer, urb->actual_length); |
| } |
| |
| static void |
| usbInitializeSignalMonitor (UsbEndpointExtension *eptx) { |
| eptx->monitor.signal.handle = NULL; |
| eptx->monitor.signal.number = 0; |
| } |
| |
| static void |
| usbStopSignalMonitor (UsbEndpointExtension *eptx) { |
| if (eptx->monitor.signal.handle) { |
| asyncCancelRequest(eptx->monitor.signal.handle); |
| eptx->monitor.signal.handle = NULL; |
| } |
| |
| if (eptx->monitor.signal.number) { |
| asyncRelinquishSignalNumber(eptx->monitor.signal.number); |
| eptx->monitor.signal.number = 0; |
| } |
| } |
| |
| ASYNC_SIGNAL_CALLBACK(usbHandleInputSignal) { |
| UsbEndpoint *endpoint = parameters->data; |
| UsbEndpointExtension *eptx = endpoint->extension; |
| |
| while (1) { |
| UsbResponse response; |
| struct usbdevfs_urb *urb = usbReapResponse(endpoint->device, |
| endpoint->descriptor->bEndpointAddress, |
| &response, 0); |
| |
| if (!urb) return 1; |
| |
| { |
| int handled = 0; |
| |
| if (!response.error) { |
| urb->actual_length = response.count; |
| if (usbHandleInputURB(endpoint, urb)) handled = 1; |
| } else { |
| errno = response.error; |
| } |
| |
| if (!handled) { |
| usbSetEndpointInputError(endpoint, errno); |
| usbStopSignalMonitor(eptx); |
| } |
| |
| free(urb); |
| if (!handled) return 0; |
| } |
| } |
| } |
| |
| static int |
| usbStartSignalMonitor (UsbEndpoint *endpoint) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| |
| if ((eptx->monitor.signal.number = asyncObtainSignalNumber())) { |
| if (asyncMonitorSignal(&eptx->monitor.signal.handle, |
| eptx->monitor.signal.number, |
| usbHandleInputSignal, endpoint)) { |
| logMessage(LOG_CATEGORY(USB_IO), |
| "signal monitor started: Ept:%02X Sig:%d", |
| endpoint->descriptor->bEndpointAddress, |
| eptx->monitor.signal.number); |
| return 1; |
| } else { |
| usbLogInputProblem(endpoint, "monitor not registered"); |
| } |
| |
| asyncRelinquishSignalNumber(eptx->monitor.signal.number); |
| } else { |
| usbLogInputProblem(endpoint, "signal number not obtained"); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| usbInitializeUsbfsMonitor (UsbDeviceExtension *devx) { |
| devx->usbfsMonitorHandle = NULL; |
| } |
| |
| static void |
| usbStopUsbfsMonitor (UsbDeviceExtension *devx) { |
| if (devx->usbfsMonitorHandle) { |
| asyncCancelRequest(devx->usbfsMonitorHandle); |
| devx->usbfsMonitorHandle = NULL; |
| } |
| } |
| |
| static int |
| usbHandleCompletedInputRequest (UsbEndpoint *endpoint, struct usbdevfs_urb *urb) { |
| ssize_t count = urb->actual_length; |
| int error = urb->status; |
| |
| if (!error) { |
| if (usbApplyInputFilters(endpoint, urb->buffer, urb->buffer_length, &count)) { |
| urb->actual_length = count; |
| |
| if (usbHandleInputURB(endpoint, urb)) { |
| return 1; |
| } |
| } |
| } else { |
| if (error < 0) error = -error; |
| errno = error; |
| logSystemError("USB URB status"); |
| } |
| |
| return 0; |
| } |
| |
| ASYNC_MONITOR_CALLBACK(usbHandleCompletedInputRequests) { |
| UsbDevice *device = parameters->data; |
| UsbEndpoint *endpoint; |
| |
| { |
| int error = parameters->error; |
| |
| if (error) { |
| logActionError(error, "USBFS monitor"); |
| usbSetDeviceInputError(device, error); |
| return 0; |
| } |
| } |
| |
| while ((endpoint = usbReapURB(device, 0))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| |
| while (1) { |
| struct usbdevfs_urb *urb = dequeueItem(eptx->completedRequests); |
| if (!urb) break; |
| usbLogURB(urb, "reaped"); |
| |
| int handled = usbHandleCompletedInputRequest(endpoint, urb); |
| int error = errno; |
| free(urb); |
| |
| if (!handled) { |
| usbSetEndpointInputError(endpoint, error); |
| return 0; |
| } |
| } |
| } |
| |
| if (errno == EAGAIN) return 1; |
| usbSetDeviceInputError(device, errno); |
| return 0; |
| } |
| |
| static int |
| usbStartUsbfsMonitor (UsbDevice *device) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (devx->usbfsMonitorHandle) return 1; |
| |
| if (usbOpenUsbfsFile(devx)) { |
| if (asyncMonitorFileOutput(&devx->usbfsMonitorHandle, |
| devx->usbfsFile, |
| usbHandleCompletedInputRequests, |
| device)) { |
| logMessage(LOG_CATEGORY(USB_IO), "USBFS monitor started"); |
| return 1; |
| } else { |
| logMessage(LOG_ERR, "USBFS monitor error: %s: %s", |
| devx->host->usbfsPath, strerror(errno)); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| usbPrepareInputEndpoint (UsbEndpoint *endpoint) { |
| UsbDevice *device = endpoint->device; |
| |
| if (LINUX_USB_INPUT_PIPE_DISABLE) return 1; |
| |
| switch (USB_ENDPOINT_TRANSFER(endpoint->descriptor)) { |
| case UsbEndpointTransfer_Bulk: |
| case UsbEndpointTransfer_Interrupt: |
| break; |
| |
| default: |
| return 1; |
| } |
| |
| if (usbMakeInputPipe(endpoint)) { |
| int monitorStarted = LINUX_USB_INPUT_USE_SIGNAL_MONITOR? |
| usbStartSignalMonitor(endpoint): |
| usbStartUsbfsMonitor(device); |
| |
| if (monitorStarted) { |
| return 1; |
| } else { |
| usbLogInputProblem(endpoint, "monitor not started"); |
| } |
| |
| usbDestroyInputPipe(endpoint); |
| } else { |
| usbLogInputProblem(endpoint, "pipe not created"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbAllocateEndpointExtension (UsbEndpoint *endpoint) { |
| UsbEndpointExtension *eptx; |
| |
| if ((eptx = malloc(sizeof(*eptx)))) { |
| memset(eptx, 0, sizeof(*eptx)); |
| usbInitializeSignalMonitor(eptx); |
| |
| if ((eptx->completedRequests = newQueue(NULL, NULL))) { |
| switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) { |
| case UsbEndpointDirection_Input: |
| endpoint->prepare = usbPrepareInputEndpoint; |
| break; |
| } |
| |
| endpoint->extension = eptx; |
| return 1; |
| } else { |
| logSystemError("USB endpoint completed request queue allocate"); |
| } |
| |
| free(eptx); |
| } else { |
| logSystemError("USB endpoint extension allocate"); |
| } |
| |
| return 0; |
| } |
| |
| void |
| usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) { |
| usbStopSignalMonitor(eptx); |
| |
| if (eptx->completedRequests) { |
| deallocateQueue(eptx->completedRequests); |
| eptx->completedRequests = NULL; |
| } |
| |
| free(eptx); |
| } |
| |
| void |
| usbDeallocateDeviceExtension (UsbDeviceExtension *devx) { |
| usbStopUsbfsMonitor(devx); |
| usbCloseUsbfsFile(devx); |
| free(devx); |
| } |
| |
| static void |
| usbDeallocateHostDevice (void *item, void *data) { |
| UsbHostDevice *host = item; |
| |
| if (host->sysfsPath) free(host->sysfsPath); |
| if (host->usbfsPath) free(host->usbfsPath); |
| free(host); |
| } |
| |
| typedef struct { |
| UsbDeviceChooser *chooser; |
| UsbChooseChannelData *data; |
| UsbDevice *device; |
| } UsbTestHostDeviceData; |
| |
| static int |
| usbTestHostDevice (void *item, void *data) { |
| const UsbHostDevice *host = item; |
| UsbTestHostDeviceData *test = data; |
| UsbDeviceExtension *devx; |
| |
| if ((devx = malloc(sizeof(*devx)))) { |
| memset(devx, 0, sizeof(*devx)); |
| devx->host = host; |
| devx->usbfsFile = -1; |
| usbInitializeUsbfsMonitor(devx); |
| |
| if ((test->device = usbTestDevice(devx, test->chooser, test->data))) return 1; |
| |
| usbDeallocateDeviceExtension(devx); |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| static char * |
| usbMakeSysfsPath (const char *usbfsPath) { |
| const char *tail = usbfsPath + strlen(usbfsPath); |
| |
| { |
| int count = 0; |
| while (1) { |
| if (tail == usbfsPath) return 0; |
| if (!isPathSeparator(*--tail)) continue; |
| if (++count == 2) break; |
| } |
| } |
| |
| { |
| unsigned int bus; |
| unsigned int device; |
| char extra; |
| int count = sscanf(tail, "/%u/%u%c", &bus, &device, &extra); |
| |
| if (count == 2) { |
| unsigned int minor = ((bus - 1) << 7) | (device - 1); |
| |
| static const char *const formats[] = { |
| "/sys/dev/char/189:%4$u%1$n%2$u%3$u", |
| "/sys/class/usb_device/usbdev%2$u.%3$u/device%1$n", |
| "/sys/class/usb_endpoint/usbdev%2$u.%3$u_ep00/device%1$n", |
| NULL |
| }; |
| const char *const *format = formats; |
| |
| while (*format) { |
| int length; |
| char path[strlen(*format) + (2 * 0X10) + 1]; |
| |
| snprintf(path, sizeof(path), *format, &length, bus, device, minor); |
| path[length] = 0; |
| |
| if (access(path, F_OK) != -1) { |
| char *sysfsPath = strdup(path); |
| if (!sysfsPath) logSystemError("strdup"); |
| return sysfsPath; |
| } |
| |
| format += 1; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| usbReadHostDeviceDescriptor (UsbHostDevice *host) { |
| int ok = 0; |
| int file = -1; |
| int sysfs = 0; |
| |
| if (file == -1) { |
| if (host->sysfsPath) { |
| char *path; |
| |
| if ((path = makePath(host->sysfsPath, "descriptors"))) { |
| int openFlags = O_RDONLY; |
| |
| #ifdef O_CLOEXEC |
| openFlags |= O_CLOEXEC; |
| #endif /* O_CLOEXEC */ |
| |
| if ((file = open(path, openFlags)) != -1) { |
| sysfs = 1; |
| } |
| |
| free(path); |
| } |
| } |
| } |
| |
| if (file == -1) { |
| int openFlags = O_RDONLY; |
| |
| #ifdef O_CLOEXEC |
| openFlags |= O_CLOEXEC; |
| #endif /* O_CLOEXEC */ |
| |
| file = open(host->usbfsPath, openFlags); |
| } |
| |
| if (file != -1) { |
| int count = read(file, &host->usbDescriptor, UsbDescriptorSize_Device); |
| |
| if (count == -1) { |
| logSystemError("USB device descriptor read"); |
| } else if (count != UsbDescriptorSize_Device) { |
| logMessage(LOG_ERR, "USB short device descriptor: %d", count); |
| } else { |
| ok = 1; |
| |
| if (!sysfs) { |
| host->usbDescriptor.bcdUSB = getLittleEndian16(host->usbDescriptor.bcdUSB); |
| host->usbDescriptor.idVendor = getLittleEndian16(host->usbDescriptor.idVendor); |
| host->usbDescriptor.idProduct = getLittleEndian16(host->usbDescriptor.idProduct); |
| host->usbDescriptor.bcdDevice = getLittleEndian16(host->usbDescriptor.bcdDevice); |
| } |
| } |
| |
| close(file); |
| } |
| |
| return ok; |
| } |
| |
| static int |
| usbAddHostDevice (const char *path) { |
| int ok = 0; |
| UsbHostDevice *host; |
| |
| if ((host = malloc(sizeof(*host)))) { |
| if ((host->usbfsPath = strdup(path))) { |
| host->sysfsPath = usbMakeSysfsPath(host->usbfsPath); |
| |
| if (!usbReadHostDeviceDescriptor(host)) { |
| ok = 1; |
| } else if (enqueueItem(usbHostDevices, host)) { |
| return 1; |
| } |
| |
| if (host->sysfsPath) free(host->sysfsPath); |
| free(host->usbfsPath); |
| } else { |
| logSystemError("strdup"); |
| } |
| |
| free(host); |
| } else { |
| logMallocError(); |
| } |
| |
| return ok; |
| } |
| |
| static int |
| usbAddHostDevices (const char *root) { |
| int ok = 0; |
| size_t rootLength = strlen(root); |
| DIR *directory; |
| |
| if ((directory = opendir(root))) { |
| struct dirent *entry; |
| |
| ok = 1; |
| while ((entry = readdir(directory))) { |
| size_t nameLength = strlen(entry->d_name); |
| struct stat status; |
| char path[rootLength + 1 + nameLength + 1]; |
| |
| if (strspn(entry->d_name, "0123456789") != nameLength) continue; |
| snprintf(path, sizeof(path), "%s/%s", root, entry->d_name); |
| if (stat(path, &status) == -1) continue; |
| |
| if (S_ISDIR(status.st_mode)) { |
| if (!usbAddHostDevices(path)) ok = 0; |
| } else if (S_ISREG(status.st_mode) || S_ISCHR(status.st_mode)) { |
| if (!usbAddHostDevice(path)) ok = 0; |
| } |
| |
| if (!ok) break; |
| } |
| |
| closedir(directory); |
| } |
| |
| return ok; |
| } |
| |
| typedef int (*FileSystemVerifier) (const char *path); |
| |
| typedef struct { |
| const char *path; |
| FileSystemVerifier verify; |
| } FileSystemCandidate; |
| |
| static int |
| usbVerifyFileSystem (const char *path, long type) { |
| struct statfs status; |
| if (statfs(path, &status) != -1) { |
| if (status.f_type == type) return 1; |
| } |
| return 0; |
| } |
| |
| static char * |
| usbGetFileSystem (const char *type, const FileSystemCandidate *candidates, MountPointTester test, FileSystemVerifier verify) { |
| if (candidates) { |
| const FileSystemCandidate *candidate = candidates; |
| |
| while (candidate->path) { |
| logMessage(LOG_CATEGORY(USB_IO), |
| "USBFS root candidate: %s: %s", |
| type, candidate->path); |
| |
| if (candidate->verify(candidate->path)) { |
| char *path = strdup(candidate->path); |
| if (path) return path; |
| logMallocError(); |
| } |
| |
| candidate += 1; |
| } |
| } |
| |
| if (test) { |
| char *path = findMountPoint(test); |
| if (path) return path; |
| } |
| |
| if (verify) { |
| char *directory = makeWritablePath(type); |
| |
| if (directory) { |
| if (ensureDirectory(directory, 0)) { |
| if (verify(directory)) return directory; |
| |
| { |
| const char *strings[] = {PACKAGE_TARNAME, "-", type}; |
| char *name = joinStrings(strings, ARRAY_COUNT(strings)); |
| if (makeMountPoint(directory, name, type)) return directory; |
| } |
| } |
| |
| free(directory); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int |
| usbVerifyDirectory (const char *path) { |
| if (access(path, F_OK) != -1) return 1; |
| return 0; |
| } |
| |
| static int |
| usbVerifyUsbfs (const char *path) { |
| return usbVerifyFileSystem(path, USBDEVICE_SUPER_MAGIC); |
| } |
| |
| static int |
| usbTestUsbfs (const char *path, const char *type) { |
| if ((strcmp(type, "usbdevfs") == 0) || |
| (strcmp(type, "usbfs") == 0)) { |
| if (usbVerifyUsbfs(path)) { |
| return 1; |
| } |
| } |
| return 0; |
| } |
| |
| static char * |
| usbGetUsbfs (void) { |
| static const FileSystemCandidate usbfsCandidates[] = { |
| {.path="/dev/bus/usb", .verify=usbVerifyDirectory}, |
| {.path="/proc/bus/usb", .verify=usbVerifyUsbfs}, |
| {.path=NULL, .verify=NULL} |
| }; |
| |
| return usbGetFileSystem("usbfs", usbfsCandidates, usbTestUsbfs, usbVerifyUsbfs); |
| } |
| |
| UsbDevice * |
| usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) { |
| if (!usbHostDevices) { |
| int ok = 0; |
| |
| if ((usbHostDevices = newQueue(usbDeallocateHostDevice, NULL))) { |
| char *root; |
| |
| if ((root = usbGetUsbfs())) { |
| logMessage(LOG_CATEGORY(USB_IO), "USBFS root: %s", root); |
| if (usbAddHostDevices(root)) ok = 1; |
| |
| free(root); |
| } else { |
| logMessage(LOG_CATEGORY(USB_IO), "USBFS not mounted"); |
| } |
| |
| if (!ok) { |
| deallocateQueue(usbHostDevices); |
| usbHostDevices = NULL; |
| } |
| } |
| } |
| |
| if (usbHostDevices) { |
| UsbTestHostDeviceData test = { |
| .chooser = chooser, |
| .data = data, |
| .device = NULL |
| }; |
| |
| if (processQueue(usbHostDevices, usbTestHostDevice, &test)) return test.device; |
| } |
| |
| return NULL; |
| } |
| |
| void |
| usbForgetDevices (void) { |
| if (usbHostDevices) { |
| deallocateQueue(usbHostDevices); |
| usbHostDevices = NULL; |
| } |
| } |