| /* |
| * 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 <string.h> |
| #include <errno.h> |
| #include <libusb.h> |
| |
| #include "log.h" |
| #include "io_usb.h" |
| #include "usb_internal.h" |
| #include "bitfield.h" |
| |
| struct UsbDeviceExtensionStruct { |
| libusb_device *device; |
| libusb_device_handle *handle; |
| }; |
| |
| static libusb_context *usbContext = NULL; |
| static libusb_device **usbDeviceList = NULL; |
| static int usbDeviceCount = 0; |
| |
| static int |
| usbToErrno (enum libusb_error error) { |
| switch (error) { |
| case LIBUSB_ERROR_IO: |
| return EIO; |
| |
| case LIBUSB_ERROR_INVALID_PARAM: |
| return EINVAL; |
| |
| case LIBUSB_ERROR_ACCESS: |
| return EACCES; |
| |
| case LIBUSB_ERROR_NO_DEVICE: |
| return ENODEV; |
| |
| case LIBUSB_ERROR_NOT_FOUND: |
| return ENOENT; |
| |
| case LIBUSB_ERROR_BUSY: |
| return EBUSY; |
| |
| case LIBUSB_ERROR_TIMEOUT: |
| return EAGAIN; |
| |
| #ifdef EMSGSIZE |
| case LIBUSB_ERROR_OVERFLOW: |
| return EMSGSIZE; |
| #endif /* EMSGSIZE */ |
| |
| case LIBUSB_ERROR_PIPE: |
| return EPIPE; |
| |
| case LIBUSB_ERROR_INTERRUPTED: |
| return EINTR; |
| |
| case LIBUSB_ERROR_NO_MEM: |
| return ENOMEM; |
| |
| case LIBUSB_ERROR_NOT_SUPPORTED: |
| return ENOSYS; |
| |
| default: |
| logMessage(LOG_CATEGORY(USB_IO), "unsupported libusb1 error code: %d", error); |
| case LIBUSB_ERROR_OTHER: |
| return EIO; |
| } |
| } |
| |
| static void |
| usbSetErrno (enum libusb_error error, const char *action) { |
| errno = usbToErrno(error); |
| if (action) logSystemError(action); |
| } |
| |
| static int |
| usbGetHandle (UsbDeviceExtension *devx) { |
| if (!devx->handle) { |
| int result; |
| |
| if ((result = libusb_open(devx->device, &devx->handle)) != LIBUSB_SUCCESS) { |
| usbSetErrno(result, "libusb_open"); |
| return 0; |
| } |
| } |
| |
| return 1; |
| } |
| |
| int |
| usbDisableAutosuspend (UsbDevice *device) { |
| logUnsupportedFunction(); |
| return 0; |
| } |
| |
| int |
| usbSetConfiguration (UsbDevice *device, unsigned char configuration) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| int result; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "setting configuration: %u", configuration); |
| result = libusb_set_configuration(devx->handle, configuration); |
| if (result == LIBUSB_SUCCESS) return 1; |
| usbSetErrno(result, "libusb_set_configuration"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbClaimInterface (UsbDevice *device, unsigned char interface) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| int detached = 0; |
| int result; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "claiming interface: %u", interface); |
| |
| while (1) { |
| result = libusb_claim_interface(devx->handle, interface); |
| if (result == LIBUSB_SUCCESS) return 1; |
| |
| if (result != LIBUSB_ERROR_BUSY) break; |
| if (detached) break; |
| |
| logMessage(LOG_WARNING, "USB interface in use: %u", interface); |
| result = libusb_detach_kernel_driver(devx->handle, interface); |
| |
| if (result == LIBUSB_SUCCESS) { |
| logMessage(LOG_WARNING, "USB interface detached: %u", interface); |
| detached = 1; |
| continue; |
| } |
| |
| result = LIBUSB_ERROR_BUSY; |
| break; |
| } |
| |
| usbSetErrno(result, "libusb_claim_interface"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbReleaseInterface (UsbDevice *device, unsigned char interface) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| int result; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "releasing interface: %u", interface); |
| result = libusb_release_interface(devx->handle, interface); |
| if (result == LIBUSB_SUCCESS) return 1; |
| usbSetErrno(result, "libusb_release_interface"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbSetAlternative ( |
| UsbDevice *device, |
| unsigned char interface, |
| unsigned char alternative |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| int result; |
| |
| logMessage(LOG_CATEGORY(USB_IO), "setting alternative: %u[%u]", interface, alternative); |
| result = libusb_set_interface_alt_setting(devx->handle, interface, alternative); |
| if (result == LIBUSB_SUCCESS) return 1; |
| usbSetErrno(result, "libusb_set_interface_alt_setting"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbResetDevice (UsbDevice *device) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| logMessage(LOG_CATEGORY(USB_IO), "reset device"); |
| int result = libusb_reset_device(devx->handle); |
| if (result == LIBUSB_SUCCESS) return 1; |
| usbSetErrno(result, "libusb_reset_device"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbClearHalt (UsbDevice *device, unsigned char endpointAddress) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| logMessage(LOG_CATEGORY(USB_IO), "clear halt: %02X", endpointAddress); |
| int result = libusb_clear_halt(devx->handle, endpointAddress); |
| if (result == LIBUSB_SUCCESS) return 1; |
| usbSetErrno(result, "libusb_clear_halt"); |
| } |
| |
| 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 (usbGetHandle(devx)) { |
| UsbSetupPacket setup; |
| int result; |
| |
| usbMakeSetupPacket(&setup, direction, recipient, type, request, value, index, length); |
| |
| if (direction == UsbControlDirection_Output) { |
| if (length) logBytes(LOG_CATEGORY(USB_IO), "control output", buffer, length); |
| } |
| |
| result = libusb_control_transfer(devx->handle, |
| setup.bRequestType, setup.bRequest, |
| getLittleEndian16(setup.wValue), |
| getLittleEndian16(setup.wIndex), buffer, |
| getLittleEndian16(setup.wLength), timeout); |
| |
| if (result >= 0) { |
| if (direction == UsbControlDirection_Input) { |
| logBytes(LOG_CATEGORY(USB_IO), "control input", buffer, result); |
| } |
| |
| return result; |
| } |
| |
| usbSetErrno(result, ""); |
| } |
| |
| return -1; |
| } |
| |
| void * |
| usbSubmitRequest ( |
| UsbDevice *device, |
| unsigned char endpointAddress, |
| void *buffer, |
| size_t length, |
| void *context |
| ) { |
| logUnsupportedFunction(); |
| return NULL; |
| } |
| |
| int |
| usbCancelRequest (UsbDevice *device, void *request) { |
| logUnsupportedFunction(); |
| return 0; |
| } |
| |
| void * |
| usbReapResponse ( |
| UsbDevice *device, |
| unsigned char endpointAddress, |
| UsbResponse *response, |
| int wait |
| ) { |
| logUnsupportedFunction(); |
| return NULL; |
| } |
| |
| int |
| usbMonitorInputEndpoint ( |
| UsbDevice *device, unsigned char endpointNumber, |
| AsyncMonitorCallback *callback, void *data |
| ) { |
| return 0; |
| } |
| |
| ssize_t |
| usbReadEndpoint ( |
| UsbDevice *device, |
| unsigned char endpointNumber, |
| void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) { |
| const UsbEndpointDescriptor *descriptor = endpoint->descriptor; |
| UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(descriptor); |
| int actual_length; |
| int result; |
| |
| switch (transfer) { |
| case UsbEndpointTransfer_Bulk: |
| result = libusb_bulk_transfer(devx->handle, descriptor->bEndpointAddress, |
| buffer, length, &actual_length, timeout); |
| break; |
| |
| case UsbEndpointTransfer_Interrupt: |
| result = libusb_interrupt_transfer(devx->handle, descriptor->bEndpointAddress, |
| buffer, length, &actual_length, timeout); |
| break; |
| |
| default: |
| logMessage(LOG_ERR, "USB endpoint input transfer not supported: 0X%02X", transfer); |
| result = LIBUSB_ERROR_NOT_SUPPORTED; |
| break; |
| } |
| |
| if (result == LIBUSB_SUCCESS) { |
| ssize_t count = actual_length; |
| |
| if (usbApplyInputFilters(endpoint, buffer, length, &count)) return count; |
| result = LIBUSB_ERROR_IO; |
| } |
| |
| usbSetErrno(result, NULL); |
| } |
| } |
| |
| if (errno != EAGAIN) logSystemError("USB endpoint read"); |
| return -1; |
| } |
| |
| ssize_t |
| usbWriteEndpoint ( |
| UsbDevice *device, |
| unsigned char endpointNumber, |
| const void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| |
| if (usbGetHandle(devx)) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) { |
| const UsbEndpointDescriptor *descriptor = endpoint->descriptor; |
| UsbEndpointTransfer transfer = USB_ENDPOINT_TRANSFER(descriptor); |
| int actual_length; |
| int result; |
| |
| switch (transfer) { |
| case UsbEndpointTransfer_Bulk: |
| result = libusb_bulk_transfer(devx->handle, descriptor->bEndpointAddress, |
| (void *)buffer, length, &actual_length, timeout); |
| break; |
| |
| case UsbEndpointTransfer_Interrupt: |
| result = libusb_interrupt_transfer(devx->handle, descriptor->bEndpointAddress, |
| (void *)buffer, length, &actual_length, timeout); |
| break; |
| |
| default: |
| logMessage(LOG_ERR, "USB endpoint output transfer not supported: 0X%02X", transfer); |
| result = LIBUSB_ERROR_NOT_SUPPORTED; |
| break; |
| } |
| |
| if (result == LIBUSB_SUCCESS) return actual_length; |
| usbSetErrno(result, NULL); |
| } |
| } |
| |
| logSystemError("USB endpoint write"); |
| return -1; |
| } |
| |
| int |
| usbReadDeviceDescriptor (UsbDevice *device) { |
| UsbDeviceExtension *devx = device->extension; |
| struct libusb_device_descriptor descriptor; |
| int result; |
| |
| if ((result = libusb_get_device_descriptor(devx->device, &descriptor)) == LIBUSB_SUCCESS) { |
| memcpy(&device->descriptor, &descriptor, UsbDescriptorSize_Device); |
| return 1; |
| } else { |
| usbSetErrno(result, "libusb_get_device_descriptor"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbAllocateEndpointExtension (UsbEndpoint *endpoint) { |
| return 1; |
| } |
| |
| void |
| usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) { |
| } |
| |
| void |
| usbDeallocateDeviceExtension (UsbDeviceExtension *devx) { |
| if (devx->handle) libusb_close(devx->handle); |
| libusb_unref_device(devx->device); |
| free(devx); |
| } |
| |
| UsbDevice * |
| usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) { |
| int result; |
| UsbDeviceExtension *devx; |
| |
| if (!usbContext) { |
| if ((result = libusb_init(&usbContext)) != LIBUSB_SUCCESS) { |
| usbSetErrno(result, "libusb_init"); |
| return NULL; |
| } |
| } |
| |
| if (!usbDeviceList) { |
| ssize_t count; |
| |
| if ((count = libusb_get_device_list(usbContext, &usbDeviceList)) < 0) { |
| usbSetErrno(count, "libusb_get_device_list"); |
| return NULL; |
| } |
| |
| usbDeviceCount = count; |
| } |
| |
| if ((devx = malloc(sizeof(*devx)))) { |
| libusb_device **libusbDevice = usbDeviceList; |
| int deviceCount = usbDeviceCount; |
| |
| while (deviceCount) { |
| deviceCount -= 1; |
| devx->device = *libusbDevice++; |
| libusb_ref_device(devx->device); |
| |
| devx->handle = NULL; |
| |
| { |
| UsbDevice *device = usbTestDevice(devx, chooser, data); |
| if (device) return device; |
| } |
| |
| libusb_unref_device(devx->device); |
| } |
| |
| free(devx); |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| void |
| usbForgetDevices (void) { |
| if (usbDeviceList) { |
| libusb_free_device_list(usbDeviceList, 1); |
| usbDeviceList = NULL; |
| } |
| |
| usbDeviceCount = 0; |
| } |