/* * 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-2021 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 . */ #include "prologue.h" #include #include #include #include #include #include #include "log.h" #include "io_usb.h" #include "usb_internal.h" #include "system_darwin.h" typedef struct { UsbEndpoint *endpoint; void *context; void *buffer; size_t length; IOReturn result; ssize_t count; } UsbAsynchronousRequest; struct UsbDeviceExtensionStruct { IOUSBDeviceInterface182 **device; unsigned deviceOpened:1; IOUSBInterfaceInterface190 **interface; unsigned interfaceOpened:1; UInt8 pipeCount; CFRunLoopSourceRef runloopSource; }; struct UsbEndpointExtensionStruct { UsbEndpoint *endpoint; Queue *completedRequests; UInt8 pipeNumber; UInt8 endpointNumber; UInt8 transferDirection; UInt8 transferMode; UInt8 pollInterval; UInt16 packetSize; }; static void setUsbError (long int result, const char *action) { switch (result) { default: setDarwinSystemError(result); break; //MAP_DARWIN_ERROR(kIOUSBUnknownPipeErr, ) //MAP_DARWIN_ERROR(kIOUSBTooManyPipesErr, ) //MAP_DARWIN_ERROR(kIOUSBNoAsyncPortErr, ) //MAP_DARWIN_ERROR(kIOUSBNotEnoughPipesErr, ) //MAP_DARWIN_ERROR(kIOUSBNotEnoughPowerErr, ) //MAP_DARWIN_ERROR(kIOUSBEndpointNotFound, ) //MAP_DARWIN_ERROR(kIOUSBConfigNotFound, ) MAP_DARWIN_ERROR(kIOUSBTransactionTimeout, ETIMEDOUT) //MAP_DARWIN_ERROR(kIOUSBTransactionReturned, ) //MAP_DARWIN_ERROR(kIOUSBPipeStalled, ) //MAP_DARWIN_ERROR(kIOUSBInterfaceNotFound, ) //MAP_DARWIN_ERROR(kIOUSBLowLatencyBufferNotPreviouslyAllocated, ) //MAP_DARWIN_ERROR(kIOUSBLowLatencyFrameListNotPreviouslyAllocated, ) //MAP_DARWIN_ERROR(kIOUSBHighSpeedSplitError, ) //MAP_DARWIN_ERROR(kIOUSBLinkErr, ) //MAP_DARWIN_ERROR(kIOUSBNotSent2Err, ) //MAP_DARWIN_ERROR(kIOUSBNotSent1Err, ) //MAP_DARWIN_ERROR(kIOUSBBufferUnderrunErr, ) //MAP_DARWIN_ERROR(kIOUSBBufferOverrunErr, ) //MAP_DARWIN_ERROR(kIOUSBReserved2Err, ) //MAP_DARWIN_ERROR(kIOUSBReserved1Err, ) //MAP_DARWIN_ERROR(kIOUSBWrongPIDErr, ) //MAP_DARWIN_ERROR(kIOUSBPIDCheckErr, ) //MAP_DARWIN_ERROR(kIOUSBDataToggleErr, ) //MAP_DARWIN_ERROR(kIOUSBBitstufErr, ) //MAP_DARWIN_ERROR(kIOUSBCRCErr, ) } if (action) { logMessage(LOG_WARNING, "Darwin error 0X%lX.", result); logSystemError(action); } } static int openDevice (UsbDevice *device, int seize) { UsbDeviceExtension *devx = device->extension; IOReturn result; if (!devx->deviceOpened) { const char *action = "opened"; int level = LOG_INFO; result = (*devx->device)->USBDeviceOpen(devx->device); if (result != kIOReturnSuccess) { setUsbError(result, "USB device open"); if ((result != kIOReturnExclusiveAccess) || !seize) return 0; result = (*devx->device)->USBDeviceOpenSeize(devx->device); if (result != kIOReturnSuccess) { setUsbError(result, "USB device seize"); return 0; } action = "seized"; level = LOG_NOTICE; } logMessage(level, "USB device %s: vendor=%04X product=%04X", action, device->descriptor.idVendor, device->descriptor.idProduct); devx->deviceOpened = 1; } return 1; } static int unsetInterface (UsbDeviceExtension *devx) { int ok = 1; if (devx->interface) { IOReturn result; if (devx->interfaceOpened) { if (devx->runloopSource) { { int pipe; for (pipe=1; pipe<=devx->pipeCount; ++pipe) { result = (*devx->interface)->AbortPipe(devx->interface, pipe); if (result != kIOReturnSuccess) { setUsbError(result, "USB pipe abort"); } } } removeRunLoopSource(devx->runloopSource); } result = (*devx->interface)->USBInterfaceClose(devx->interface); if (result != kIOReturnSuccess) { setUsbError(result, "USB interface close"); ok = 0; } devx->interfaceOpened = 0; } (*devx->interface)->Release(devx->interface); devx->interface = NULL; } return ok; } static int isInterface (IOUSBInterfaceInterface190 **interface, UInt8 number) { IOReturn result; UInt8 num; result = (*interface)->GetInterfaceNumber(interface, &num); if (result != kIOReturnSuccess) { setUsbError(result, "USB interface number query"); } else if (num == number) { return 1; } return 0; } static int setInterface (UsbDeviceExtension *devx, UInt8 number) { int found = 0; IOReturn result; io_iterator_t iterator = 0; if (devx->interface) if (isInterface(devx->interface, number)) return 1; { IOUSBFindInterfaceRequest request; request.bInterfaceClass = kIOUSBFindInterfaceDontCare; request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; request.bAlternateSetting = kIOUSBFindInterfaceDontCare; result = (*devx->device)->CreateInterfaceIterator(devx->device, &request, &iterator); } if ((result == kIOReturnSuccess) && iterator) { io_service_t service; while ((service = IOIteratorNext(iterator))) { IOCFPlugInInterface **plugin = NULL; SInt32 score; result = IOCreatePlugInInterfaceForService(service, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); IOObjectRelease(service); service = 0; if ((result == kIOReturnSuccess) && plugin) { IOUSBInterfaceInterface190 **interface = NULL; result = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID190), (LPVOID)&interface); (*plugin)->Release(plugin); plugin = NULL; if ((result == kIOReturnSuccess) && interface) { if (isInterface(interface, number)) { unsetInterface(devx); devx->interface = interface; found = 1; break; } (*interface)->Release(interface); interface = NULL; } else { setUsbError(result, "USB interface interface create"); } } else { setUsbError(result, "USB interface service plugin create"); } } if (!found) logMessage(LOG_ERR, "USB interface not found: %d", number); IOObjectRelease(iterator); iterator = 0; } else { setUsbError(result, "USB interface iterator create"); } return found; } static void usbAsynchronousRequestCallback (void *context, IOReturn result, void *arg) { UsbAsynchronousRequest *request = context; UsbEndpoint *endpoint = request->endpoint; UsbEndpointExtension *eptx = endpoint->extension; request->result = result; request->count = (intptr_t)arg; if (!enqueueItem(eptx->completedRequests, request)) { logSystemError("USB completed request enqueue"); free(request); } } int usbDisableAutosuspend (UsbDevice *device) { logUnsupportedFunction(); return 0; } int usbSetConfiguration (UsbDevice *device, unsigned char configuration) { UsbDeviceExtension *devx = device->extension; if (openDevice(device, 1)) { UInt8 arg = configuration; IOReturn result = (*devx->device)->SetConfiguration(devx->device, arg); if (result == kIOReturnSuccess) return 1; setUsbError(result, "USB configuration set"); } return 0; } int usbClaimInterface (UsbDevice *device, unsigned char interface) { UsbDeviceExtension *devx = device->extension; IOReturn result; if (setInterface(devx, interface)) { if (devx->interfaceOpened) return 1; result = (*devx->interface)->USBInterfaceOpen(devx->interface); if (result == kIOReturnSuccess) { result = (*devx->interface)->GetNumEndpoints(devx->interface, &devx->pipeCount); if (result == kIOReturnSuccess) { devx->interfaceOpened = 1; return 1; } else { setUsbError(result, "USB pipe count query"); } (*devx->interface)->USBInterfaceClose(devx->interface); } else { setUsbError(result, "USB interface open"); } } return 0; } int usbReleaseInterface (UsbDevice *device, unsigned char interface) { UsbDeviceExtension *devx = device->extension; return setInterface(devx, interface) && unsetInterface(devx); } int usbSetAlternative ( UsbDevice *device, unsigned char interface, unsigned char alternative ) { UsbDeviceExtension *devx = device->extension; if (setInterface(devx, interface)) { IOReturn result; UInt8 arg; result = (*devx->interface)->GetAlternateSetting(devx->interface, &arg); if (result == kIOReturnSuccess) { if (arg == alternative) return 1; arg = alternative; result = (*devx->interface)->SetAlternateInterface(devx->interface, arg); if (result == kIOReturnSuccess) return 1; setUsbError(result, "USB alternative set"); } else { setUsbError(result, "USB alternative get"); } } return 0; } int usbResetDevice (UsbDevice *device) { logUnsupportedFunction(); return 0; } int usbClearHalt (UsbDevice *device, unsigned char endpointAddress) { UsbDeviceExtension *devx = device->extension; UsbEndpoint *endpoint; if ((endpoint = usbGetEndpoint(device, endpointAddress))) { UsbEndpointExtension *eptx = endpoint->extension; IOReturn result; result = (*devx->interface)->ClearPipeStallBothEnds(devx->interface, eptx->pipeNumber); if (result == kIOReturnSuccess) return 1; setUsbError(result, "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; IOReturn result; IOUSBDevRequestTO arg; arg.bmRequestType = direction | recipient | type; arg.bRequest = request; arg.wValue = value; arg.wIndex = index; arg.wLength = length; arg.pData = buffer; arg.noDataTimeout = timeout; arg.completionTimeout = timeout; result = (*devx->device)->DeviceRequestTO(devx->device, &arg); if (result == kIOReturnSuccess) return arg.wLenDone; setUsbError(result, "USB control transfer"); return -1; } void * usbSubmitRequest ( UsbDevice *device, unsigned char endpointAddress, void *buffer, size_t length, void *context ) { UsbDeviceExtension *devx = device->extension; UsbEndpoint *endpoint; if ((endpoint = usbGetEndpoint(device, endpointAddress))) { UsbEndpointExtension *eptx = endpoint->extension; IOReturn result; UsbAsynchronousRequest *request; if (!devx->runloopSource) { result = (*devx->interface)->CreateInterfaceAsyncEventSource(devx->interface, &devx->runloopSource); if (result != kIOReturnSuccess) { setUsbError(result, "USB interface event source create"); return NULL; } addRunLoopSource(devx->runloopSource); } if ((request = malloc(sizeof(*request) + length))) { request->endpoint = endpoint; request->context = context; request->buffer = (request->length = length)? (request + 1): NULL; switch (eptx->transferDirection) { case kUSBIn: result = (*devx->interface)->ReadPipeAsync(devx->interface, eptx->pipeNumber, request->buffer, request->length, usbAsynchronousRequestCallback, request); if (result == kIOReturnSuccess) return request; setUsbError(result, "USB endpoint asynchronous read"); break; case kUSBOut: if (request->buffer) memcpy(request->buffer, buffer, length); result = (*devx->interface)->WritePipeAsync(devx->interface, eptx->pipeNumber, request->buffer, request->length, usbAsynchronousRequestCallback, request); if (result == kIOReturnSuccess) return request; setUsbError(result, "USB endpoint asynchronous write"); break; default: logMessage(LOG_ERR, "USB endpoint direction not suppported: %d", eptx->transferDirection); errno = ENOSYS; break; } free(request); } else { logSystemError("USB asynchronous request allocate"); } } return NULL; } int usbCancelRequest (UsbDevice *device, void *request) { logUnsupportedFunction(); return 0; } void * usbReapResponse ( UsbDevice *device, unsigned char endpointAddress, UsbResponse *response, int wait ) { UsbEndpoint *endpoint; if ((endpoint = usbGetEndpoint(device, endpointAddress))) { UsbEndpointExtension *eptx = endpoint->extension; UsbAsynchronousRequest *request; while (!(request = dequeueItem(eptx->completedRequests))) { switch (executeRunLoop((wait? 60: 0))) { case kCFRunLoopRunTimedOut: if (wait) continue; case kCFRunLoopRunFinished: errno = EAGAIN; goto none; case kCFRunLoopRunStopped: case kCFRunLoopRunHandledSource: default: continue; } } response->context = request->context; response->buffer = request->buffer; response->size = request->length; if (request->result == kIOReturnSuccess) { response->error = 0; response->count = request->count; if (!usbApplyInputFilters(endpoint, response->buffer, response->size, &response->count)) { response->error = EIO; response->count = -1; } } else { setUsbError(request->result, "USB asynchronous response"); response->error = errno; response->count = -1; } return request; } none: 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; UsbEndpoint *endpoint; if ((endpoint = usbGetInputEndpoint(device, endpointNumber))) { UsbEndpointExtension *eptx = endpoint->extension; IOReturn result; UInt32 count; int stalled = 0; read: count = length; result = (*devx->interface)->ReadPipeTO(devx->interface, eptx->pipeNumber, buffer, &count, timeout, timeout); switch (result) { case kIOReturnSuccess: { ssize_t actual = count; if (usbApplyInputFilters(endpoint, buffer, length, &actual)) return actual; } errno = EIO; break; case kIOUSBTransactionTimeout: errno = EAGAIN; break; case kIOUSBPipeStalled: if (!stalled) { result = (*devx->interface)->ClearPipeStallBothEnds(devx->interface, eptx->pipeNumber); if (result == kIOReturnSuccess) { stalled = 1; goto read; } setUsbError(result, "USB stall clear"); break; } default: setUsbError(result, "USB endpoint read"); break; } } return -1; } ssize_t usbWriteEndpoint ( UsbDevice *device, unsigned char endpointNumber, const void *buffer, size_t length, int timeout ) { UsbDeviceExtension *devx = device->extension; UsbEndpoint *endpoint; if ((endpoint = usbGetOutputEndpoint(device, endpointNumber))) { UsbEndpointExtension *eptx = endpoint->extension; IOReturn result; result = (*devx->interface)->WritePipeTO(devx->interface, eptx->pipeNumber, (void *)buffer, length, timeout, timeout); if (result == kIOReturnSuccess) return length; setUsbError(result, "USB endpoint write"); } return -1; } int usbReadDeviceDescriptor (UsbDevice *device) { UsbDeviceExtension *devx = device->extension; IOReturn result; { uint8_t speed; if ((result = (*devx->device)->GetDeviceSpeed(devx->device, &speed)) != kIOReturnSuccess) goto error; switch (speed) { default : device->descriptor.bcdUSB = 0X0000; break; case kUSBDeviceSpeedLow : device->descriptor.bcdUSB = kUSBRel10; break; case kUSBDeviceSpeedFull: device->descriptor.bcdUSB = kUSBRel11; break; case kUSBDeviceSpeedHigh: device->descriptor.bcdUSB = kUSBRel20; break; } } if ((result = (*devx->device)->GetDeviceClass(devx->device, &device->descriptor.bDeviceClass)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->GetDeviceSubClass(devx->device, &device->descriptor.bDeviceSubClass)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->GetDeviceProtocol(devx->device, &device->descriptor.bDeviceProtocol)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->GetDeviceVendor(devx->device, &device->descriptor.idVendor)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->GetDeviceProduct(devx->device, &device->descriptor.idProduct)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->GetDeviceReleaseNumber(devx->device, &device->descriptor.bcdDevice)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->USBGetManufacturerStringIndex(devx->device, &device->descriptor.iManufacturer)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->USBGetProductStringIndex(devx->device, &device->descriptor.iProduct)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->USBGetSerialNumberStringIndex(devx->device, &device->descriptor.iSerialNumber)) != kIOReturnSuccess) goto error; if ((result = (*devx->device)->GetNumberOfConfigurations(devx->device, &device->descriptor.bNumConfigurations)) != kIOReturnSuccess) goto error; device->descriptor.bMaxPacketSize0 = 0; device->descriptor.bLength = UsbDescriptorSize_Device; device->descriptor.bDescriptorType = UsbDescriptorType_Device; return 1; error: setUsbError(result, "USB device descriptor read"); return 0; } int usbAllocateEndpointExtension (UsbEndpoint *endpoint) { UsbDeviceExtension *devx = endpoint->device->extension; UsbEndpointExtension *eptx; if ((eptx = malloc(sizeof(*eptx)))) { if ((eptx->completedRequests = newQueue(NULL, NULL))) { IOReturn result; unsigned char number = USB_ENDPOINT_NUMBER(endpoint->descriptor); unsigned char direction = USB_ENDPOINT_DIRECTION(endpoint->descriptor); for (eptx->pipeNumber=1; eptx->pipeNumber<=devx->pipeCount; ++eptx->pipeNumber) { result = (*devx->interface)->GetPipeProperties(devx->interface, eptx->pipeNumber, &eptx->transferDirection, &eptx->endpointNumber, &eptx->transferMode, &eptx->packetSize, &eptx->pollInterval); if (result == kIOReturnSuccess) { if ((eptx->endpointNumber == number) && (((eptx->transferDirection == kUSBIn) && (direction == UsbEndpointDirection_Input)) || ((eptx->transferDirection == kUSBOut) && (direction == UsbEndpointDirection_Output)))) { logMessage(LOG_CATEGORY(USB_IO), "ept=%02X -> pip=%d (num=%d dir=%d xfr=%d int=%d pkt=%d)", endpoint->descriptor->bEndpointAddress, eptx->pipeNumber, eptx->endpointNumber, eptx->transferDirection, eptx->transferMode, eptx->pollInterval, eptx->packetSize); eptx->endpoint = endpoint; endpoint->extension = eptx; return 1; } } else { setUsbError(result, "USB pipe properties query"); } } errno = EIO; logMessage(LOG_ERR, "USB pipe not found: ept=%02X", endpoint->descriptor->bEndpointAddress); deallocateQueue(eptx->completedRequests); } else { logSystemError("USB completed request queue allocate"); } free(eptx); } else { logSystemError("USB endpoint extension allocate"); } return 0; } void usbDeallocateEndpointExtension (UsbEndpointExtension *eptx) { if (eptx->completedRequests) { deallocateQueue(eptx->completedRequests); eptx->completedRequests = NULL; } free(eptx); } void usbDeallocateDeviceExtension (UsbDeviceExtension *devx) { IOReturn result; unsetInterface(devx); if (devx->deviceOpened) { result = (*devx->device)->USBDeviceClose(devx->device); if (result != kIOReturnSuccess) setUsbError(result, "USB device close"); devx->deviceOpened = 0; } (*devx->device)->Release(devx->device); devx->device = NULL; free(devx); } UsbDevice * usbFindDevice (UsbDeviceChooser *chooser, UsbChooseChannelData *data) { UsbDevice *device = NULL; kern_return_t kernelResult; IOReturn ioResult; mach_port_t port; kernelResult = IOMasterPort(MACH_PORT_NULL, &port); if (kernelResult == KERN_SUCCESS) { CFMutableDictionaryRef dictionary; if ((dictionary = IOServiceMatching(kIOUSBDeviceClassName))) { io_iterator_t iterator = 0; kernelResult = IOServiceGetMatchingServices(port, dictionary, &iterator); dictionary = NULL; if ((kernelResult == KERN_SUCCESS) && iterator) { io_service_t service; while ((service = IOIteratorNext(iterator))) { IOCFPlugInInterface **plugin = NULL; SInt32 score; ioResult = IOCreatePlugInInterfaceForService(service, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &score); IOObjectRelease(service); service = 0; if ((ioResult == kIOReturnSuccess) && plugin) { IOUSBDeviceInterface182 **interface = NULL; ioResult = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182), (LPVOID)&interface); (*plugin)->Release(plugin); plugin = NULL; if ((ioResult == kIOReturnSuccess) && interface) { UsbDeviceExtension *devx; if ((devx = malloc(sizeof(*devx)))) { devx->device = interface; devx->deviceOpened = 0; devx->interface = NULL; devx->interfaceOpened = 0; devx->runloopSource = NULL; if ((device = usbTestDevice(devx, chooser, data))) break; free(devx); devx = NULL; } else { logSystemError("USB device extension allocate"); } (*interface)->Release(interface); interface = NULL; } else { setUsbError(ioResult, "USB device interface create"); } } else { setUsbError(ioResult, "USB device service plugin create"); } } IOObjectRelease(iterator); iterator = 0; } else { setUsbError(kernelResult, "USB device iterator create"); } } else { logMessage(LOG_ERR, "USB device matching dictionary create error."); } mach_port_deallocate(mach_task_self(), port); } else { setUsbError(kernelResult, "Darwin master port create"); } return device; } void usbForgetDevices (void) { }