Skip to content
usb.c 42.1 KiB
Newer Older
/*
 * 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-2018 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.com/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

#ifdef HAVE_REGEX_H
#include <regex.h>
#endif /* HAVE_REGEX_H */

#include "log.h"
#include "strfmt.h"
#include "parameters.h"
#include "bitmask.h"
#include "parse.h"
#include "file.h"
#include "charset.h"
#include "device.h"
#include "timing.h"
#include "async_wait.h"
#include "async_alarm.h"
#include "io_misc.h"
#include "io_usb.h"
#include "usb_internal.h"
#include "usb_serial.h"
ssize_t
usbControlRead (
  UsbDevice *device,
  uint8_t recipient,
  uint8_t type,
  uint8_t request,
  uint16_t value,
  uint16_t index,
  void *buffer,
  uint16_t length,
  int timeout
) {
  return usbControlTransfer(device, UsbControlDirection_Input, recipient, type,
                            request, value, index, buffer, length, timeout);
}

ssize_t
usbControlWrite (
  UsbDevice *device,
  uint8_t recipient,
  uint8_t type,
  uint8_t request,
  uint16_t value,
  uint16_t index,
  const void *buffer,
  uint16_t length,
  int timeout
) {
  return usbControlTransfer(device, UsbControlDirection_Output, recipient, type,
                            request, value, index, (void *)buffer, length, timeout);
}

ssize_t
usbGetDescriptor (
  UsbDevice *device,
  unsigned char type,
  unsigned char number,
  unsigned int index,
  UsbDescriptor *descriptor,
  int timeout
) {
  return usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
                        UsbStandardRequest_GetDescriptor, (type << 8) | number, index,
                        descriptor->bytes, sizeof(descriptor->bytes), timeout);
}

int
usbGetDeviceDescriptor (
  UsbDevice *device,
  UsbDeviceDescriptor *descriptor
) {
  UsbDescriptor desc;
  int size = usbGetDescriptor(device, UsbDescriptorType_Device, 0, 0, &desc, 1000);

  if (size != -1) {
    *descriptor = desc.device;
  }

  return size;
}

int
usbGetLanguage (
  UsbDevice *device,
  uint16_t *language,
  int timeout
) {
  UsbDescriptor descriptor;
  ssize_t size = usbGetDescriptor(device, UsbDescriptorType_String,
                              0, 0, &descriptor, timeout);
  if (size != -1) {
    if (size >= 4) {
      *language = getLittleEndian16(descriptor.string.wData[0]);
      logMessage(LOG_CATEGORY(USB_IO), "USB language: %02X", *language);
      return 1;
    } else {
      logMessage(LOG_ERR, "USB language code string too short: %"PRIssize, size);
      errno = EIO;
  } else {
    logMessage(LOG_ERR, "USB language code string read error");
  return 0;
}

char *
usbDecodeString (const UsbStringDescriptor *descriptor) {
  size_t count = (descriptor->bLength - 2) / sizeof(descriptor->wData[0]);
  char buffer[(count * UTF8_LEN_MAX) + 1];

  const uint16_t *source = descriptor->wData;
  const uint16_t *end = source + count;
  char *target = buffer;

  while (source < end) {
    size_t length = convertWcharToUtf8(getLittleEndian16(*source++), target);

    target += length;
  }
  *target = 0;

  {
    char *string = strdup(buffer);

    if (!string) logMallocError();
    return string;
  }
}

char *
usbGetString (
  UsbDevice *device,
  unsigned char number,
  int timeout
) {
  UsbDescriptor descriptor;

  if (!device->language) {
    if (!usbGetLanguage(device, &device->language, timeout)) {
      return NULL;

  if (usbGetDescriptor(device, UsbDescriptorType_String,
                       number, device->language,
                       &descriptor, timeout) == -1) {
    logMessage(LOG_ERR, "USB string read error: %u", number);
    return NULL;

  return usbDecodeString(&descriptor.string);
}

char *
usbGetManufacturer (UsbDevice *device, int timeout) {
  return usbGetString(device, device->descriptor.iManufacturer, timeout);
}

char *
usbGetProduct (UsbDevice *device, int timeout) {
  return usbGetString(device, device->descriptor.iProduct, timeout);
}

char *
usbGetSerialNumber (UsbDevice *device, int timeout) {
  return usbGetString(device, device->descriptor.iSerialNumber, timeout);
}

static size_t
usbFormatLogSetupPacket (char *buffer, size_t size, const void *data) {
  const UsbSetupPacket *setup = data;
  size_t length;
  STR_BEGIN(buffer, size);

  STR_PRINTF("setup packet: Typ:%02X Req:%02X Val:%04X Idx:%04X Len:%04X",
             setup->bRequestType, setup->bRequest,
             getLittleEndian16(setup->wValue),
             getLittleEndian16(setup->wIndex),
             getLittleEndian16(setup->wLength));

  length = STR_LENGTH;
  STR_END;
  return length;
}

void
usbLogSetupPacket (const UsbSetupPacket *setup) {
  logData(LOG_CATEGORY(USB_IO), usbFormatLogSetupPacket, setup);
}

void
usbMakeSetupPacket (
  UsbSetupPacket *setup,
  uint8_t direction,
  uint8_t recipient,
  uint8_t type,
  uint8_t request,
  uint16_t value,
  uint16_t index,
  uint16_t length
) {
  setup->bRequestType = direction | recipient | type;
  setup->bRequest = request;
  putLittleEndian16(&setup->wValue, value);
  putLittleEndian16(&setup->wIndex, index);
  putLittleEndian16(&setup->wLength, length);
  usbLogSetupPacket(setup);
}

void
usbLogEndpointData (
  UsbEndpoint *endpoint, const char *label,
  const void *data, size_t size
) {
  logBytes(LOG_CATEGORY(USB_IO), "endpoint %02X %s", data, size,
           endpoint->descriptor->bEndpointAddress, label);
}

void
usbLogString (
  UsbDevice *device,
  unsigned char number,
  const char *label
) {
  if (number) {
    char *string = usbGetString(device, number, 1000);
    if (string) {
      logMessage(LOG_INFO, "USB: %s: %s", label, string);
      free(string);
    }
  }
}

int
usbStringEquals (const char *reference, const char *value) {
  return strcmp(reference, value) == 0;
}

int
usbStringMatches (const char *reference, const char *value) {
  int ok = 0;

#ifdef HAVE_REGEX_H
  regex_t expression;
  if (regcomp(&expression, value, REG_EXTENDED|REG_NOSUB) == 0) {
    if (regexec(&expression, reference, 0, NULL, 0) == 0) {
      ok = 1;
    }
    regfree(&expression);
  }
#endif /* HAVE_REGEX_H */

  return ok;
}

int
usbVerifyString (
  UsbDevice *device,
  UsbStringVerifier verify,
  unsigned char index,
  const char *value
) {
  int ok = 0;
  if (!(value && *value)) return 1;

  if (index) {
    char *reference = usbGetString(device, index, 1000);
    if (reference) {
      if (verify(reference, value)) ok = 1;
      free(reference);
    }
  }
  return ok;
}

int
usbVerifyManufacturerName (UsbDevice *device, const char *eRegExp) {
  return usbVerifyString(device, usbStringMatches,
                         device->descriptor.iManufacturer, eRegExp);
}

int
usbVerifyProductDescription (UsbDevice *device, const char *eRegExp) {
  return usbVerifyString(device, usbStringMatches,
                         device->descriptor.iProduct, eRegExp);
}

int
usbVerifySerialNumber (UsbDevice *device, const char *string) {
  return usbVerifyString(device, usbStringEquals,
                         device->descriptor.iSerialNumber, string);
}

int
usbParseVendorIdentifier (uint16_t *identifier, const char *string) {
  if (string && *string) {
    unsigned int value;

    if (isUnsignedInteger(&value, string)) {
      if ((value > 0) && (value <= UINT16_MAX)) {
        *identifier = value;
        return 1;
      }
    }

    logMessage(LOG_WARNING, "invalid USB vendor identifier: %s", string);
    return 0;
  }

  *identifier = 0;
  return 1;
}

int
usbVerifyVendorIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
  if (!identifier) return 1;
  return identifier == getLittleEndian16(descriptor->idVendor);
}

int
usbParseProductIdentifier (uint16_t *identifier, const char *string) {
  if (string && *string) {
    unsigned int value;

    if (isUnsignedInteger(&value, string)) {
      if ((value > 0) && (value <= UINT16_MAX)) {
        *identifier = value;
        return 1;
      }
    }

    logMessage(LOG_WARNING, "invalid USB product identifier: %s", string);
    return 0;
  }

  *identifier = 0;
  return 1;
}

int
usbVerifyProductIdentifier (const UsbDeviceDescriptor *descriptor, uint16_t identifier) {
  if (!identifier) return 1;
  return identifier == getLittleEndian16(descriptor->idProduct);
}

static int
usbVerifyStrings (
  UsbDevice *device,
  const char *const *strings,
  unsigned char number
) {
  if (!strings) return 1;
  if (!number) return 0;

  char *string = usbGetString(device, number, 1000);
  int matched = 0;

  if (string) {
    while (*strings) {
      if (strcmp(*strings, string) == 0) {
        matched = 1;
        break;
      }

      strings += 1;
    }

    free(string);
  }

  return matched;
}

const UsbDeviceDescriptor *
usbDeviceDescriptor (UsbDevice *device) {
  return &device->descriptor;
}

int
usbGetConfiguration (
  UsbDevice *device,
  unsigned char *configuration
) {
  ssize_t size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
                                UsbStandardRequest_GetConfiguration, 0, 0,
                                configuration, sizeof(*configuration), 1000);
  if (size != -1) return 1;
  logMessage(LOG_WARNING, "USB standard request not supported: get configuration");
  return 0;
}

static void
usbDeallocateConfigurationDescriptor (UsbDevice *device) {
  if (device->configuration) {
    free(device->configuration);
    device->configuration = NULL;
  }
}

const UsbConfigurationDescriptor *
usbConfigurationDescriptor (
  UsbDevice *device
) {
  if (!device->configuration) {
    unsigned char current;
    if (device->descriptor.bNumConfigurations < 2) {
      current = 1;
    } else if (!usbGetConfiguration(device, &current)) {
      current = 0;
    }

    if (current) {
      UsbDescriptor descriptor;
      unsigned char number;

      for (number=0; number<device->descriptor.bNumConfigurations; number++) {
        int size = usbGetDescriptor(device, UsbDescriptorType_Configuration,
                                    number, 0, &descriptor, 1000);
        if (size == -1) {
          logMessage(LOG_WARNING, "USB configuration descriptor not readable: %d", number);
        } else if (descriptor.configuration.bConfigurationValue == current) {
          break;
        }
      }

      if (number < device->descriptor.bNumConfigurations) {
        int length = getLittleEndian16(descriptor.configuration.wTotalLength);
        UsbDescriptor *descriptors;

        if ((descriptors = malloc(length))) {
          ssize_t size;

          if (length > sizeof(descriptor)) {
            size = usbControlRead(device, UsbControlRecipient_Device, UsbControlType_Standard,
                                  UsbStandardRequest_GetDescriptor,
                                  (UsbDescriptorType_Configuration << 8) | number,
                                  0, descriptors, length, 1000);
          } else {
            memcpy(descriptors, &descriptor, (size = length));
          }

          if (size != -1) {
            device->configuration = &descriptors->configuration;
          } else {
            free(descriptors);
          }
        } else {
          logSystemError("USB configuration descriptor allocate");
        }
      } else {
        logMessage(LOG_ERR, "USB configuration descriptor not found: %d", current);
      }
    }
  }

  return device->configuration;
}

int
usbConfigureDevice (
  UsbDevice *device,
  unsigned char configuration
) {
  usbCloseInterface(device);

  if (usbSetConfiguration(device, configuration)) {
    usbDeallocateConfigurationDescriptor(device);
    return 1;
  }

  {
    const UsbConfigurationDescriptor *descriptor = usbConfigurationDescriptor(device);

    if (descriptor)
      if (descriptor->bConfigurationValue == configuration)
        return 1;
  }

  return 0;
}

int
usbNextDescriptor (
  UsbDevice *device,
  const UsbDescriptor **descriptor
) {
  if (*descriptor) {
    const UsbDescriptor *next = (UsbDescriptor *)&(*descriptor)->bytes[(*descriptor)->header.bLength];
    const UsbDescriptor *first = (UsbDescriptor *)device->configuration;
    unsigned int length = getLittleEndian16(first->configuration.wTotalLength);
    if ((&next->bytes[0] - &first->bytes[0]) >= length) return 0;
    if ((&next->bytes[next->header.bLength] - &first->bytes[0]) > length) return 0;
    *descriptor = next;
  } else if (usbConfigurationDescriptor(device)) {
    *descriptor = (UsbDescriptor *)device->configuration;
  } else {
    return 0;
  }
  return 1;
}

const UsbInterfaceDescriptor *
usbInterfaceDescriptor (
  UsbDevice *device,
  unsigned char interface,
  unsigned char alternative
) {
  const UsbDescriptor *descriptor = NULL;

  while (usbNextDescriptor(device, &descriptor)) {
    if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface) {
      if (descriptor->interface.bInterfaceNumber == interface) {
        if (descriptor->interface.bAlternateSetting == alternative) {
          return &descriptor->interface;
  logMessage(LOG_WARNING, "USB: interface descriptor not found: %d.%d", interface, alternative);
  errno = ENOENT;
  return NULL;
}

unsigned int
usbAlternativeCount (
  UsbDevice *device,
  unsigned char interface
) {
  unsigned int count = 0;
  const UsbDescriptor *descriptor = NULL;

  while (usbNextDescriptor(device, &descriptor)) {
    if (descriptor->interface.bDescriptorType == UsbDescriptorType_Interface) {
      if (descriptor->interface.bInterfaceNumber == interface) {
        count += 1;
const UsbEndpointDescriptor *
usbEndpointDescriptor (
  UsbDevice *device,
  unsigned char endpointAddress
) {
  const UsbDescriptor *descriptor = NULL;

  while (usbNextDescriptor(device, &descriptor)) {
    if (descriptor->endpoint.bDescriptorType == UsbDescriptorType_Endpoint) {
      if (descriptor->endpoint.bEndpointAddress == endpointAddress) {
        return &descriptor->endpoint;
  logMessage(LOG_WARNING, "USB: endpoint descriptor not found: %02X", endpointAddress);
  errno = ENOENT;
  return NULL;
}

static void
usbCancelInputMonitor (UsbEndpoint *endpoint) {
  if (endpoint->direction.input.pipe.monitor) {
    asyncCancelRequest(endpoint->direction.input.pipe.monitor);
    endpoint->direction.input.pipe.monitor = NULL;
  }
}

static inline int
usbHaveInputPipe (UsbEndpoint *endpoint) {
  return endpoint->direction.input.pipe.output != INVALID_FILE_DESCRIPTOR;
}

static inline int
usbHaveInputError (UsbEndpoint *endpoint) {
  return endpoint->direction.input.pipe.input == INVALID_FILE_DESCRIPTOR;
}

void
usbSetEndpointInputError (UsbEndpoint *endpoint, int error) {
  if (!usbHaveInputError(endpoint)) {
    endpoint->direction.input.pipe.error = error;
    closeFile(&endpoint->direction.input.pipe.input);
  }
}

static int
usbSetInputError (void *item, void *data) {
  UsbEndpoint *endpoint = item;
  const int *error = data;

  if (usbHaveInputPipe(endpoint)) {
    usbSetEndpointInputError(endpoint, *error);
  }

  return 0;
}

void
usbSetDeviceInputError (UsbDevice *device, int error) {
  processQueue(device->endpoints, usbSetInputError, &error);
}

int
usbEnqueueInput (UsbEndpoint *endpoint, const void *buffer, size_t length) {
  if (usbHaveInputError(endpoint)) {
    errno = EIO;
    return 0;
  }

  return writeFile(endpoint->direction.input.pipe.input, buffer, length) != -1;
}

void
usbDestroyInputPipe (UsbEndpoint *endpoint) {
  usbCancelInputMonitor(endpoint);
  closeFile(&endpoint->direction.input.pipe.input);
  closeFile(&endpoint->direction.input.pipe.output);
}

int
usbMakeInputPipe (UsbEndpoint *endpoint) {
  if (usbHaveInputPipe(endpoint)) return 1;

  if (createAnonymousPipe(&endpoint->direction.input.pipe.input,
                          &endpoint->direction.input.pipe.output)) {
    if (setBlockingIo(endpoint->direction.input.pipe.output, 0)) {
      return 1;
    }
  }

  usbDestroyInputPipe(endpoint);
  return 0;
}

int
usbMonitorInputPipe (
  UsbDevice *device, unsigned char endpointNumber,
  AsyncMonitorCallback *callback, void *data
) {
  UsbEndpoint *endpoint = usbGetInputEndpoint(device, endpointNumber);

  if (endpoint) {
    if (usbHaveInputPipe(endpoint)) {
      usbCancelInputMonitor(endpoint);
      if (!callback) return 1;

      if (asyncMonitorFileInput(&endpoint->direction.input.pipe.monitor,
                                endpoint->direction.input.pipe.output,
                                callback, data)) {
        return 1;
      }
    }
  }

  return 0;
}

static void
usbDeallocateEndpoint (void *item, void *data) {
  UsbEndpoint *endpoint = item;

  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
    case UsbEndpointDirection_Input:
      if (endpoint->direction.input.pending.alarm) {
        asyncCancelRequest(endpoint->direction.input.pending.alarm);
        endpoint->direction.input.pending.alarm = NULL;
      }

      if (endpoint->direction.input.pending.requests) {
        deallocateQueue(endpoint->direction.input.pending.requests);
        endpoint->direction.input.pending.requests = NULL;
      if (endpoint->direction.input.completed.request) {
        free(endpoint->direction.input.completed.request);
        endpoint->direction.input.completed.request = NULL;

    default:
      break;
  }

  if (endpoint->extension) {
    usbDeallocateEndpointExtension(endpoint->extension);
    endpoint->extension = NULL;
  }

  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
    case UsbEndpointDirection_Input:
      usbDestroyInputPipe(endpoint);
      break;

    default:
      break;
  }

  free(endpoint);
}

static int
usbTestEndpoint (const void *item, void *data) {
  const UsbEndpoint *endpoint = item;
  const unsigned char *endpointAddress = data;
  return endpoint->descriptor->bEndpointAddress == *endpointAddress;
}

UsbEndpoint *
usbGetEndpoint (UsbDevice *device, unsigned char endpointAddress) {
  UsbEndpoint *endpoint;
  const UsbEndpointDescriptor *descriptor;

  if ((endpoint = findItem(device->endpoints, usbTestEndpoint, &endpointAddress))) return endpoint;

  if ((descriptor = usbEndpointDescriptor(device, endpointAddress))) {
    {
      const char *direction;
      const char *transfer;

      switch (USB_ENDPOINT_DIRECTION(descriptor)) {
        default:                          direction = "?";   break;
        case UsbEndpointDirection_Input:  direction = "in";  break;
        case UsbEndpointDirection_Output: direction = "out"; break;
      }

      switch (USB_ENDPOINT_TRANSFER(descriptor)) {
        default:                              transfer = "?";   break;
        case UsbEndpointTransfer_Control:     transfer = "ctl"; break;
        case UsbEndpointTransfer_Isochronous: transfer = "iso"; break;
        case UsbEndpointTransfer_Bulk:        transfer = "blk"; break;
        case UsbEndpointTransfer_Interrupt:   transfer = "int"; break;
      }

      logMessage(LOG_CATEGORY(USB_IO), "ept=%02X dir=%s xfr=%s pkt=%d ivl=%dms",
                 descriptor->bEndpointAddress, direction, transfer,
                 getLittleEndian16(descriptor->wMaxPacketSize),
                 descriptor->bInterval);
    }

    if ((endpoint = malloc(sizeof(*endpoint)))) {
      memset(endpoint, 0, sizeof(*endpoint));
      endpoint->device = device;
      endpoint->descriptor = descriptor;
      endpoint->extension = NULL;
      endpoint->prepare = NULL;

      switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
        case UsbEndpointDirection_Input:
          endpoint->direction.input.pending.requests = NULL;
          endpoint->direction.input.pending.alarm = NULL;
          endpoint->direction.input.pending.delay = 0;

          endpoint->direction.input.completed.request = NULL;
          endpoint->direction.input.completed.buffer = NULL;
          endpoint->direction.input.completed.length = 0;

          endpoint->direction.input.pipe.input = INVALID_FILE_DESCRIPTOR;
          endpoint->direction.input.pipe.output = INVALID_FILE_DESCRIPTOR;
          endpoint->direction.input.pipe.monitor = NULL;
          endpoint->direction.input.pipe.error = 0;

          break;
      }

      if (usbAllocateEndpointExtension(endpoint)) {
        if (enqueueItem(device->endpoints, endpoint)) {
          if (device->disableEndpointReset) {
            logMessage(LOG_CATEGORY(USB_IO), "endpoint reset disabled");
          } else {
            usbClearHalt(device, endpoint->descriptor->bEndpointAddress);
          }

          if (!endpoint->prepare || endpoint->prepare(endpoint)) return endpoint;
          deleteItem(device->endpoints, endpoint);
        }

        usbDeallocateEndpointExtension(endpoint->extension);
        usbDestroyInputPipe(endpoint);
      }

      free(endpoint);
    }
  }

  return NULL;
}

UsbEndpoint *
usbGetInputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
  return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Input);
}

UsbEndpoint *
usbGetOutputEndpoint (UsbDevice *device, unsigned char endpointNumber) {
  return usbGetEndpoint(device, endpointNumber|UsbEndpointDirection_Output);
}

static int
usbFinishEndpoint (void *item, void *data) {
  UsbEndpoint *endpoint = item;

  switch (USB_ENDPOINT_DIRECTION(endpoint->descriptor)) {
    case UsbEndpointDirection_Input:
      if (endpoint->direction.input.pending.requests) {
        deleteElements(endpoint->direction.input.pending.requests);
      }
      break;

    default:
      break;
  }

  return 0;
}

static void
usbRemoveEndpoints (UsbDevice *device, int final) {
  if (device->endpoints) {
    processQueue(device->endpoints, usbFinishEndpoint, NULL);
    deleteElements(device->endpoints);

    if (final) {
      deallocateQueue(device->endpoints);
      device->endpoints = NULL;
    }
  }
}

static void
usbDeallocateInputFilter (void *item, void *data) {
  UsbInputFilterEntry *entry = item;
  free(entry);
}

int
usbAddInputFilter (UsbDevice *device, UsbInputFilter *filter) {
  UsbInputFilterEntry *entry;
  if ((entry = malloc(sizeof(*entry)))) {
    memset(entry, 0, sizeof(*entry));
    entry->filter = filter;
    if (enqueueItem(device->inputFilters, entry)) return 1;
    free(entry);
  }
  return 0;
}

static int
usbApplyInputFilter (void *item, void *data) {
  UsbInputFilterEntry *entry = item;
  return !entry->filter(data);
}

int
usbApplyInputFilters (UsbEndpoint *endpoint, void *buffer, size_t size, ssize_t *length) {
  Queue *filters = endpoint->device->inputFilters;
  if (getQueueSize(filters) == 0) {
    usbLogEndpointData(endpoint, "input", buffer, *length);
  } else {
    usbLogEndpointData(endpoint, "unfiltered input", buffer, *length);
    UsbInputFilterData data = {
      .buffer = buffer,
      .size = size,
      .length = *length
    };

    if (processQueue(filters, usbApplyInputFilter, &data)) {
      errno = EIO;
      return 0;
    }

    *length = data.length;
    usbLogEndpointData(endpoint, "filtered input", buffer, *length);
  return 1;
}

void
usbCloseInterface (
  UsbDevice *device
) {
  usbRemoveEndpoints(device, 0);

  if (device->interface) {
    usbReleaseInterface(device, device->interface->bInterfaceNumber);
    device->interface = NULL;
  }
}

int
usbOpenInterface (
  UsbDevice *device,
  unsigned char interface,
  unsigned char alternative
) {
  const UsbInterfaceDescriptor *descriptor = usbInterfaceDescriptor(device, interface, alternative);
  if (!descriptor) return 0;
  if (descriptor == device->interface) return 1;

  if (device->interface)
    if (device->interface->bInterfaceNumber != interface)
      usbCloseInterface(device);

  if (!device->interface)
    if (!usbClaimInterface(device, interface))
      return 0;

  if (usbAlternativeCount(device, interface) == 1) goto done;

  {
    unsigned char response[1];
    ssize_t size = usbControlRead(device, UsbControlRecipient_Interface, UsbControlType_Standard,
                                  UsbStandardRequest_GetInterface, 0, interface,
                                  response, sizeof(response), 1000);

    if (size != -1) {
      if (response[0] == alternative) goto done;
    } else {
      logMessage(LOG_WARNING, "USB standard request not supported: get interface");
  if (usbSetAlternative(device, interface, alternative)) goto done;
  if (!device->interface) usbReleaseInterface(device, interface);
  return 0;

done:
  device->interface = descriptor;
  return 1;
}

void
usbCloseDevice (UsbDevice *device) {
  if (device->serial.operations) {
    const UsbSerialOperations *uso = device->serial.operations;
    if (uso->disableAdapter) uso->disableAdapter(device);
  }

  usbCloseInterface(device);
  usbRemoveEndpoints(device, 1);

  if (device->inputFilters) {
    deallocateQueue(device->inputFilters);
    device->inputFilters = NULL;
  }

  if (device->serial.data) {
    device->serial.operations->destroyData(device->serial.data);
    device->serial.data = NULL;
  }

  if (device->extension) {
    usbDeallocateDeviceExtension(device->extension);
    device->extension = NULL;
  }