| /* |
| * 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 <strings.h> |
| #include <errno.h> |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <sys/asynch.h> |
| #include <sys/usb/clients/ugen/usb_ugen.h> |
| |
| #include "log.h" |
| #include "io_usb.h" |
| #include "usb_internal.h" |
| |
| struct UsbDeviceExtensionStruct { |
| char *path; |
| int data; |
| int status; |
| |
| unsigned char configuration; |
| unsigned char interface; |
| unsigned char alternative; |
| }; |
| |
| struct UsbEndpointExtensionStruct { |
| Queue *requests; |
| |
| char *name; |
| int data; |
| int status; |
| }; |
| |
| typedef struct { |
| aio_result_t result; /* must be first for aiowait() */ |
| |
| UsbEndpoint *endpoint; |
| |
| void *context; |
| void *buffer; |
| size_t length; |
| } UsbAsynchronousRequest; |
| |
| static int |
| usbOpenStatusFile (const char *path, int *status) { |
| if ((*status = open(path, O_RDONLY)) != -1) return 1; |
| logSystemError("USB status file open"); |
| return 0; |
| } |
| |
| static int |
| usbOpenEndpointFiles ( |
| const char *device, |
| const char *endpoint, |
| int *data, |
| int *status, |
| int flags |
| ) { |
| static const char *const suffix = "stat"; |
| char path[strlen(device) + 1 + strlen(endpoint) + strlen(suffix) + 1]; |
| |
| sprintf(path, "%s/%s", device, endpoint); |
| if ((*data = open(path, flags)) != -1) { |
| strcat(path, suffix); |
| if (usbOpenStatusFile(path, status)) { |
| return 1; |
| } else { |
| logSystemError("USB endpoint status open"); |
| } |
| |
| close(*data); |
| *data = -1; |
| } else { |
| logSystemError("USB endpoint data open"); |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbDisableAutosuspend (UsbDevice *device) { |
| logUnsupportedFunction(); |
| return 0; |
| } |
| |
| int |
| usbSetConfiguration (UsbDevice *device, unsigned char configuration) { |
| UsbDeviceExtension *devx = device->extension; |
| devx->configuration = configuration; |
| return 1; |
| } |
| |
| int |
| usbClaimInterface (UsbDevice *device, unsigned char interface) { |
| UsbDeviceExtension *devx = device->extension; |
| devx->interface = interface; |
| devx->alternative = 0; |
| return 1; |
| } |
| |
| int |
| usbReleaseInterface (UsbDevice *device, unsigned char interface) { |
| return 1; |
| } |
| |
| int |
| usbSetAlternative ( |
| UsbDevice *device, |
| unsigned char interface, |
| unsigned char alternative |
| ) { |
| UsbDeviceExtension *devx = device->extension; |
| devx->interface = interface; |
| devx->alternative = alternative; |
| return 1; |
| } |
| |
| int |
| usbResetDevice (UsbDevice *device) { |
| logUnsupportedFunction(); |
| return 0; |
| } |
| |
| int |
| usbClearHalt (UsbDevice *device, unsigned char endpointAddress) { |
| logUnsupportedFunction(); |
| 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; |
| UsbSetupPacket setup; |
| |
| setup.bRequestType = direction | recipient | type; |
| setup.bRequest = request; |
| putLittleEndian16(&setup.wValue, value); |
| putLittleEndian16(&setup.wIndex, index); |
| putLittleEndian16(&setup.wLength, length); |
| |
| switch (direction) { |
| case UsbControlDirection_Input: { |
| size_t size = sizeof(setup); |
| ssize_t count; |
| |
| if ((count = write(devx->data, &setup, size)) == -1) { |
| logSystemError("USB control request"); |
| } else if (count != size) { |
| logMessage(LOG_ERR, "USB truncated control request: %d < %d", count, size); |
| errno = EIO; |
| } else if ((count = read(devx->data, buffer, length)) == -1) { |
| logSystemError("USB control read"); |
| } else { |
| return count; |
| } |
| break; |
| } |
| |
| case UsbControlDirection_Output: { |
| unsigned char packet[sizeof(setup) + length]; |
| size_t size = 0; |
| ssize_t count; |
| |
| memcpy(&packet[size], &setup, sizeof(setup)); |
| size += sizeof(setup); |
| |
| memcpy(&packet[size], buffer, length); |
| size += length; |
| |
| if ((count = write(devx->data, packet, size)) == -1) { |
| logSystemError("USB control write"); |
| } else if (count != size) { |
| logMessage(LOG_ERR, "USB truncated control write: %d < %d", count, size); |
| errno = EIO; |
| } else { |
| return size; |
| } |
| break; |
| } |
| |
| default: |
| logMessage(LOG_ERR, "USB unsupported control direction: %02X", direction); |
| errno = ENOSYS; |
| break; |
| } |
| |
| return -1; |
| } |
| |
| void * |
| usbSubmitRequest ( |
| UsbDevice *device, |
| unsigned char endpointAddress, |
| void *buffer, |
| size_t length, |
| void *context |
| ) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetEndpoint(device, endpointAddress))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| UsbAsynchronousRequest *request; |
| |
| if ((request = malloc(sizeof(*request) + length))) { |
| UsbEndpointDirection direction = USB_ENDPOINT_DIRECTION(endpoint->descriptor); |
| |
| request->endpoint = endpoint; |
| request->context = context; |
| request->buffer = (request->length = length)? (request + 1): NULL; |
| request->result.aio_return = AIO_INPROGRESS; |
| |
| switch (direction) { |
| case UsbEndpointDirection_Input: |
| if (aioread(eptx->data, request->buffer, request->length, 0, SEEK_CUR, &request->result) != -1) return request; |
| logSystemError("USB asynchronous read"); |
| break; |
| |
| case UsbEndpointDirection_Output: |
| if (request->buffer) memcpy(request->buffer, buffer, length); |
| if (aiowrite(eptx->data, request->buffer, request->length, 0, SEEK_CUR, &request->result) != -1) return request; |
| logSystemError("USB asynchronous write"); |
| break; |
| |
| default: |
| logMessage(LOG_ERR, "USB unsupported asynchronous direction: %02X", direction); |
| errno = ENOSYS; |
| break; |
| } |
| |
| free(request); |
| } else { |
| logSystemError("USB asynchronous request allocate"); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| int |
| usbCancelRequest (UsbDevice *device, void *request) { |
| UsbAsynchronousRequest *req = request; |
| UsbEndpoint *endpoint = req->endpoint; |
| UsbEndpointExtension *eptx = endpoint->extension; |
| |
| if (!deleteItem(eptx->requests, req)) { |
| if (aiocancel(&req->result) == -1) { |
| if ((errno != EINVAL) && (errno != EACCES)) { |
| logSystemError("USB asynchronous cancel"); |
| return 0; |
| } |
| } |
| } |
| |
| free(request); |
| return 1; |
| } |
| |
| void * |
| usbReapResponse ( |
| UsbDevice *device, |
| unsigned char endpointAddress, |
| UsbResponse *response, |
| int wait |
| ) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetEndpoint(device, endpointAddress))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| struct timeval timeout; |
| UsbAsynchronousRequest *request; |
| |
| timeout.tv_sec = 0; |
| timeout.tv_usec = 0; |
| |
| while (!(request = dequeueItem(eptx->requests))) { |
| aio_result_t *result; |
| |
| doWait: |
| if ((int)(result = aiowait(wait? NULL: &timeout)) == -1) { |
| if (errno == EINTR) goto doWait; |
| |
| if (errno != EINVAL) { |
| logSystemError("USB asynchronous wait"); |
| return NULL; |
| } |
| |
| result = NULL; |
| } |
| |
| if (!result) { |
| errno = EAGAIN; |
| return NULL; |
| } |
| |
| request = (UsbAsynchronousRequest *)result; |
| { |
| UsbEndpoint *ep = request->endpoint; |
| UsbEndpointExtension *epx = ep->extension; |
| if (!enqueueItem(epx->requests, request)) { |
| logSystemError("USB asynchronous enqueue"); |
| } |
| } |
| } |
| |
| response->context = request->context; |
| response->buffer = request->buffer; |
| response->size = request->length; |
| response->count = request->result.aio_return; |
| response->error = request->result.aio_errno; |
| |
| if (response->count == -1) { |
| errno = response->error; |
| logSystemError("USB asynchronous completion"); |
| } else { |
| 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 request; |
| } |
| |
| 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 |
| ) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| ssize_t count; |
| |
| doRead: |
| if ((count = read(eptx->data, buffer, length)) == -1) { |
| if (errno == EINTR) goto doRead; |
| logSystemError("USB endpoint read"); |
| } else if (!usbApplyInputFilters(endpoint, buffer, length, &count)) { |
| errno = EIO; |
| } else if (!count) { |
| errno = EAGAIN; |
| } else { |
| return count; |
| } |
| } |
| |
| return -1; |
| } |
| |
| ssize_t |
| usbWriteEndpoint ( |
| UsbDevice *device, |
| unsigned char endpointNumber, |
| const void *buffer, |
| size_t length, |
| int timeout |
| ) { |
| UsbEndpoint *endpoint; |
| |
| if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) { |
| UsbEndpointExtension *eptx = endpoint->extension; |
| ssize_t count; |
| |
| doWrite: |
| if ((count = write(eptx->data, buffer, length)) == -1) { |
| if (errno == EINTR) goto doWrite; |
| logSystemError("USB endpoint write"); |
| } else if (count != length) { |
| logMessage(LOG_ERR, "USB truncated endpoint write: %d < %d", count, length); |
| errno = EIO; |
| } else { |
| return count; |
| } |
| } |
| |
| return -1; |
| } |
| |
| int |
| usbReadDeviceDescriptor (UsbDevice *device) { |
| int count = usbGetDeviceDescriptor(device, &device->descriptor); |
| |
| if (count == UsbDescriptorSize_Device) { |
| return 1; |
| } |
| |
| if (count != -1) { |
| logMessage(LOG_ERR, "USB short device descriptor (%d).", count); |
| errno = EIO; |
| } |
| |
| return 0; |
| } |
| |
| int |
| usbAllocateEndpointExtension (UsbEndpoint *endpoint) { |
| UsbDevice *device = endpoint->device; |
| UsbDeviceExtension *devx = device->extension; |
| UsbEndpointExtension *eptx; |
| |
| if ((eptx = malloc(sizeof(*eptx)))) { |
| if ((eptx->requests = newQueue(NULL, NULL))) { |
| int flags; |
| |
| { |
| char name[0X80]; |
| int length = 0; |
| |
| if (devx->configuration != 1) { |
| int count; |
| sprintf(&name[length], "cfg%d%n", devx->configuration, &count); |
| length += count; |
| } |
| |
| { |
| int count; |
| sprintf(&name[length], "if%d%n", devx->interface, &count); |
| length += count; |
| } |
| |
| if (devx->alternative != 0) { |
| int count; |
| sprintf(&name[length], ".%d%n", devx->alternative, &count); |
| length += count; |
| } |
| |
| { |
| const UsbEndpointDescriptor *descriptor = endpoint->descriptor; |
| UsbEndpointDirection direction = USB_ENDPOINT_DIRECTION(descriptor); |
| const char *prefix; |
| |
| switch (direction) { |
| case UsbEndpointDirection_Input: |
| prefix = "in"; |
| flags = O_RDONLY; |
| break; |
| |
| case UsbEndpointDirection_Output: |
| prefix = "out"; |
| flags = O_WRONLY; |
| break; |
| |
| default: |
| logMessage(LOG_ERR, "USB unsupported endpoint direction: %02X", direction); |
| goto nameError; |
| } |
| |
| { |
| int count; |
| sprintf(&name[length], "%s%d%n", prefix, USB_ENDPOINT_NUMBER(descriptor), &count); |
| length += count; |
| } |
| } |
| |
| eptx->name = strdup(name); |
| } |
| |
| if (eptx->name) { |
| if (usbOpenEndpointFiles(devx->path, eptx->name, &eptx->data, &eptx->status, flags)) { |
| endpoint->extension = eptx; |
| return 1; |
| } |
| |
| free(eptx->name); |
| } |
| nameError: |
| |
| deallocateQueue(eptx->requests); |
| } |
| |
| free(eptx); |
| } |
| |
| return 0; |
| } |
| |
| void |
| usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) { |
| if (eptx->status != -1) { |
| close(eptx->status); |
| eptx->status = -1; |
| } |
| |
| if (eptx->data != -1) { |
| close(eptx->data); |
| eptx->data = -1; |
| } |
| |
| if (eptx->name) { |
| free(eptx->name); |
| eptx->name = NULL; |
| } |
| |
| if (eptx->requests) { |
| deallocateQueue(eptx->requests); |
| eptx->requests = NULL; |
| } |
| |
| free(eptx); |
| } |
| |
| void |
| usbDeallocateDeviceExtension (UsbDeviceExtension *devx) { |
| if (devx->status != -1) { |
| close(devx->status); |
| devx->status = -1; |
| } |
| |
| if (devx->data != -1) { |
| close(devx->data); |
| devx->data = -1; |
| } |
| |
| if (devx->path) { |
| free(devx->path); |
| devx->path = NULL; |
| } |
| |
| free(devx); |
| } |
| |
| UsbDevice * |
| usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) { |
| UsbDevice *device = NULL; |
| static const char *const rootPath = "/dev/usb"; |
| int rootLength = strlen(rootPath); |
| DIR *rootDirectory; |
| |
| if ((rootDirectory = opendir(rootPath))) { |
| struct dirent *deviceEntry; |
| |
| while ((deviceEntry = readdir(rootDirectory))) { |
| const char *deviceName = deviceEntry->d_name; |
| int deviceLength = strlen(deviceName); |
| |
| { |
| unsigned int vendor; |
| unsigned int product; |
| int length; |
| if (sscanf(deviceName, "%x.%x%n", &vendor, &product, &length) < 2) continue; |
| if (length != deviceLength) continue; |
| } |
| |
| deviceLength += rootLength + 1; |
| { |
| char devicePath[deviceLength + 1]; |
| DIR *deviceDirectory; |
| |
| sprintf(devicePath, "%s/%s", rootPath, deviceName); |
| if ((deviceDirectory = opendir(devicePath))) { |
| struct dirent *instanceEntry; |
| |
| while ((instanceEntry = readdir(deviceDirectory))) { |
| const char *instanceName = instanceEntry->d_name; |
| int instanceLength = strlen(instanceName); |
| |
| { |
| unsigned int number; |
| int length; |
| if (sscanf(instanceName, "%u%n", &number, &length) < 1) continue; |
| if (length != instanceLength) continue; |
| } |
| |
| instanceLength += deviceLength + 1; |
| { |
| char instancePath[instanceLength + 1]; |
| UsbDeviceExtension *devx; |
| |
| sprintf(instancePath, "%s/%s", devicePath, instanceName); |
| if ((devx = malloc(sizeof(*devx)))) { |
| if ((devx->path = strdup(instancePath))) { |
| if (usbOpenEndpointFiles(devx->path, "cntrl0", &devx->data, &devx->status, O_RDWR)) { |
| if ((device = usbTestDevice(devx, chooser, data))) break; |
| |
| close(devx->status); |
| close(devx->data); |
| } |
| |
| free(devx->path); |
| } |
| |
| free(devx); |
| } |
| } |
| } |
| |
| closedir(deviceDirectory); |
| } |
| } |
| |
| if (device) break; |
| } |
| |
| closedir(rootDirectory); |
| } |
| |
| return device; |
| } |
| |
| void |
| usbForgetDevices (void) { |
| } |