| /* |
| * 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 "log.h" |
| #include "async_handle.h" |
| #include "async_wait.h" |
| #include "async_alarm.h" |
| #include "io_generic.h" |
| #include "gio_internal.h" |
| #include "io_serial.h" |
| #include "hid_types.h" |
| |
| const GioProperties *const gioProperties[] = { |
| &gioProperties_serial, |
| &gioProperties_usb, |
| &gioProperties_bluetooth, |
| &gioProperties_hid, |
| &gioProperties_null, |
| NULL |
| }; |
| |
| static void |
| gioInitializeOptions (GioOptions *options) { |
| options->applicationData = NULL; |
| options->readyDelay = 0; |
| options->inputTimeout = 0; |
| options->outputTimeout = 0; |
| options->requestTimeout = 0; |
| } |
| |
| void |
| gioInitializeDescriptor (GioDescriptor *descriptor) { |
| descriptor->serial.parameters = NULL; |
| gioInitializeOptions(&descriptor->serial.options); |
| descriptor->serial.options.inputTimeout = 100; |
| |
| descriptor->usb.channelDefinitions = NULL; |
| descriptor->usb.setConnectionProperties = NULL; |
| gioInitializeOptions(&descriptor->usb.options); |
| descriptor->usb.options.inputTimeout = 1000; |
| descriptor->usb.options.outputTimeout = 1000; |
| descriptor->usb.options.requestTimeout = 1000; |
| |
| descriptor->bluetooth.channelNumber = 0; |
| descriptor->bluetooth.discoverChannel = 0; |
| gioInitializeOptions(&descriptor->bluetooth.options); |
| descriptor->bluetooth.options.inputTimeout = 1000; |
| descriptor->bluetooth.options.requestTimeout = 5000; |
| |
| descriptor->hid.modelTable = NULL; |
| gioInitializeOptions(&descriptor->hid.options); |
| |
| gioInitializeOptions(&descriptor->null.options); |
| } |
| |
| void |
| gioInitializeSerialParameters (SerialParameters *parameters) { |
| parameters->baud = SERIAL_DEFAULT_BAUD; |
| parameters->dataBits = SERIAL_DEFAULT_DATA_BITS; |
| parameters->stopBits = SERIAL_DEFAULT_STOP_BITS; |
| parameters->parity = SERIAL_DEFAULT_PARITY; |
| parameters->flowControl = SERIAL_DEFAULT_FLOW_CONTROL; |
| } |
| |
| void |
| gioSetBytesPerSecond (GioEndpoint *endpoint, const SerialParameters *parameters) { |
| endpoint->bytesPerSecond = parameters->baud / serialGetCharacterSize(parameters); |
| } |
| |
| void |
| gioSetApplicationData (GioEndpoint *endpoint, const void *data) { |
| endpoint->options.applicationData = data; |
| } |
| |
| static int |
| gioStartEndpoint (GioEndpoint *endpoint) { |
| { |
| int delay = endpoint->options.readyDelay; |
| if (delay) asyncWait(delay); |
| } |
| |
| return 1; |
| } |
| |
| static const GioProperties * |
| gioGetProperties ( |
| const char **identifier, |
| const GioDescriptor *descriptor |
| ) { |
| for (const GioProperties *const *properties = gioProperties; |
| *properties; properties+=1) { |
| if (descriptor) { |
| GioIsSupportedMethod *isSupported = (*properties)->private->isSupported; |
| if (!isSupported) continue; |
| if (!isSupported(descriptor)) continue; |
| } |
| |
| { |
| GioTestIdentifierMethod *testIdentifier = (*properties)->public->testIdentifier; |
| if (!testIdentifier) continue; |
| if (testIdentifier(identifier)) return *properties; |
| } |
| } |
| |
| errno = ENOSYS; |
| logMessage(LOG_WARNING, "unsupported generic resource identifier: %s", *identifier); |
| return NULL; |
| } |
| |
| const GioPublicProperties * |
| gioGetPublicProperties (const char **identifier) { |
| const GioProperties *properties = gioGetProperties(identifier, NULL); |
| if (properties == NULL) return NULL; |
| return properties->public; |
| } |
| |
| GioEndpoint * |
| gioConnectResource ( |
| const char *identifier, |
| const GioDescriptor *descriptor |
| ) { |
| const GioProperties *properties = gioGetProperties(&identifier, descriptor); |
| |
| if (properties) { |
| GioEndpoint *endpoint; |
| |
| if ((endpoint = malloc(sizeof(*endpoint)))) { |
| memset(endpoint, 0, sizeof(*endpoint)); |
| endpoint->referenceCount = 1; |
| |
| endpoint->resourceType = properties->public->type.identifier; |
| endpoint->bytesPerSecond = 0; |
| |
| endpoint->input.error = 0; |
| endpoint->input.from = 0; |
| endpoint->input.to = 0; |
| |
| if (descriptor && properties->private->getOptions) { |
| endpoint->options = *properties->private->getOptions(descriptor); |
| } else { |
| gioInitializeOptions(&endpoint->options); |
| } |
| |
| if (properties->private->getHandleMethods) { |
| endpoint->handleMethods = properties->private->getHandleMethods(); |
| } else { |
| endpoint->handleMethods = NULL; |
| } |
| |
| if (properties->private->connectResource) { |
| if ((endpoint->handle = properties->private->connectResource(identifier, descriptor))) { |
| { |
| GioGetChainedEndpointMethod *getChainedEndpoint = endpoint->handleMethods->getChainedEndpoint; |
| |
| if (getChainedEndpoint) { |
| GioEndpoint *chainedEndpoint = getChainedEndpoint(endpoint->handle); |
| |
| if (chainedEndpoint) { |
| chainedEndpoint->referenceCount += 1; |
| gioDisconnectResource(endpoint); |
| return chainedEndpoint; |
| } |
| } |
| } |
| |
| if (!properties->private->prepareEndpoint || properties->private->prepareEndpoint(endpoint)) { |
| if (gioStartEndpoint(endpoint)) { |
| return endpoint; |
| } |
| } |
| |
| { |
| int originalErrno = errno; |
| gioDisconnectResource(endpoint); |
| errno = originalErrno; |
| } |
| |
| return NULL; |
| } |
| } else { |
| logUnsupportedOperation("connectResource"); |
| } |
| |
| free(endpoint); |
| } else { |
| logMallocError(); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| const void * |
| gioGetApplicationData (GioEndpoint *endpoint) { |
| return endpoint->options.applicationData; |
| } |
| |
| int |
| gioDisconnectResource (GioEndpoint *endpoint) { |
| if (--endpoint->referenceCount > 0) return 1; |
| |
| int ok = 0; |
| GioDisconnectResourceMethod *method = endpoint->handleMethods->disconnectResource; |
| |
| if (!method) { |
| logUnsupportedOperation("disconnectResource"); |
| errno = ENOSYS; |
| } else if (method(endpoint->handle)) { |
| ok = 1; |
| } |
| |
| free(endpoint); |
| return ok; |
| } |
| |
| const char * |
| gioMakeResourceIdentifier (GioEndpoint *endpoint, char *buffer, size_t size) { |
| const char *identifier = NULL; |
| MakeResourceIdentifierMethod *method = endpoint->handleMethods->makeResourceIdentifier; |
| |
| if (!method) { |
| logUnsupportedOperation("makeResourceIdentifier"); |
| errno = ENOSYS; |
| } else { |
| identifier = method(endpoint->handle, buffer, size); |
| } |
| |
| return identifier; |
| } |
| |
| char * |
| gioGetResourceIdentifier (GioEndpoint *endpoint) { |
| char buffer[0X100]; |
| const char *identifier = gioMakeResourceIdentifier(endpoint, buffer, sizeof(buffer)); |
| if (!identifier) return NULL; |
| |
| char *copy = strdup(identifier); |
| if (!copy) logMallocError(); |
| return copy; |
| } |
| |
| char * |
| gioGetResourceName (GioEndpoint *endpoint) { |
| char *name = NULL; |
| GioGetResourceNameMethod *method = endpoint->handleMethods->getResourceName; |
| |
| if (!method) { |
| logUnsupportedOperation("getResourceName"); |
| errno = ENOSYS; |
| } else { |
| name = method(endpoint->handle, endpoint->options.requestTimeout); |
| } |
| |
| return name; |
| } |
| |
| GioTypeIdentifier |
| gioGetResourceType (GioEndpoint *endpoint) { |
| return endpoint->resourceType; |
| } |
| |
| void * |
| gioGetResourceObject (GioEndpoint *endpoint) { |
| GioGetResourceObjectMethod *method = endpoint->handleMethods->getResourceObject; |
| |
| if (method) return method(endpoint->handle); |
| logUnsupportedOperation("getResourceObject"); |
| return NULL; |
| } |
| |
| ssize_t |
| gioWriteData (GioEndpoint *endpoint, const void *data, size_t size) { |
| GioWriteDataMethod *method = endpoint->handleMethods->writeData; |
| |
| if (!method) { |
| logUnsupportedOperation("writeData"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| logBytes(LOG_CATEGORY(GENERIC_IO), "output", data, size); |
| |
| ssize_t result = method(endpoint->handle, data, size, |
| endpoint->options.outputTimeout); |
| |
| if (endpoint->options.ignoreWriteTimeouts) { |
| if (result == -1) { |
| if ((errno == EAGAIN) |
| #ifdef ETIMEDOUT |
| || (errno == ETIMEDOUT) |
| #endif /* ETIMEDOUT */ |
| ) result = size; |
| } |
| } |
| |
| return result; |
| } |
| |
| int |
| gioAwaitInput (GioEndpoint *endpoint, int timeout) { |
| GioAwaitInputMethod *method = endpoint->handleMethods->awaitInput; |
| |
| if (!method) { |
| logUnsupportedOperation("awaitInput"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| if (endpoint->input.to - endpoint->input.from) return 1; |
| |
| return method(endpoint->handle, timeout); |
| } |
| |
| ssize_t |
| gioReadData (GioEndpoint *endpoint, void *buffer, size_t size, int wait) { |
| GioReadDataMethod *method = endpoint->handleMethods->readData; |
| |
| if (!method) { |
| logUnsupportedOperation("readData"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| { |
| unsigned char *start = buffer; |
| unsigned char *next = start; |
| |
| while (size) { |
| { |
| unsigned int count = endpoint->input.to - endpoint->input.from; |
| |
| if (count) { |
| if (count > size) count = size; |
| memcpy(next, &endpoint->input.buffer[endpoint->input.from], count); |
| |
| endpoint->input.from += count; |
| next += count; |
| size -= count; |
| continue; |
| } |
| |
| endpoint->input.from = endpoint->input.to = 0; |
| } |
| |
| if (endpoint->input.error) { |
| if (next != start) break; |
| errno = endpoint->input.error; |
| endpoint->input.error = 0; |
| return -1; |
| } |
| |
| { |
| ssize_t result = method(endpoint->handle, |
| &endpoint->input.buffer[endpoint->input.to], |
| sizeof(endpoint->input.buffer) - endpoint->input.to, |
| (wait? endpoint->options.inputTimeout: 0), 0); |
| |
| if (result > 0) { |
| logBytes(LOG_CATEGORY(GENERIC_IO), "input", &endpoint->input.buffer[endpoint->input.to], result); |
| endpoint->input.to += result; |
| wait = 1; |
| } else { |
| if (!result) break; |
| if (errno == EAGAIN) break; |
| endpoint->input.error = errno; |
| } |
| } |
| } |
| |
| if (next == start) errno = EAGAIN; |
| return next - start; |
| } |
| } |
| |
| int |
| gioReadByte (GioEndpoint *endpoint, unsigned char *byte, int wait) { |
| ssize_t result = gioReadData(endpoint, byte, 1, wait); |
| if (result > 0) return 1; |
| if (result == 0) errno = EAGAIN; |
| return 0; |
| } |
| |
| int |
| gioDiscardInput (GioEndpoint *endpoint) { |
| unsigned char byte; |
| while (gioReadByte(endpoint, &byte, 0)); |
| return errno == EAGAIN; |
| } |
| |
| int |
| gioMonitorInput (GioEndpoint *endpoint, AsyncMonitorCallback *callback, void *data) { |
| GioMonitorInputMethod *method = endpoint->handleMethods->monitorInput; |
| |
| if (method) { |
| if (method(endpoint->handle, callback, data)) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| gioReconfigureResource ( |
| GioEndpoint *endpoint, |
| const SerialParameters *parameters |
| ) { |
| int ok = 1; |
| GioReconfigureResourceMethod *method = endpoint->handleMethods->reconfigureResource; |
| |
| if (!method) { |
| logUnsupportedOperation("reconfigureResource"); |
| } else if (method(endpoint->handle, parameters)) { |
| gioSetBytesPerSecond(endpoint, parameters); |
| } else { |
| ok = 0; |
| } |
| |
| return ok; |
| } |
| |
| unsigned int |
| gioGetBytesPerSecond (GioEndpoint *endpoint) { |
| return endpoint->bytesPerSecond; |
| } |
| |
| unsigned int |
| gioGetMillisecondsToTransfer (GioEndpoint *endpoint, size_t bytes) { |
| return endpoint->bytesPerSecond? (((bytes * 1000) / endpoint->bytesPerSecond) + 1): 0; |
| } |
| |
| ssize_t |
| gioTellResource ( |
| GioEndpoint *endpoint, |
| uint8_t recipient, uint8_t type, |
| uint8_t request, uint16_t value, uint16_t index, |
| const void *data, uint16_t size |
| ) { |
| GioTellResourceMethod *method = endpoint->handleMethods->tellResource; |
| |
| if (!method) { |
| logUnsupportedOperation("tellResource"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| return method(endpoint->handle, recipient, type, |
| request, value, index, data, size, |
| endpoint->options.requestTimeout); |
| } |
| |
| ssize_t |
| gioAskResource ( |
| GioEndpoint *endpoint, |
| uint8_t recipient, uint8_t type, |
| uint8_t request, uint16_t value, uint16_t index, |
| void *buffer, uint16_t size |
| ) { |
| GioAskResourceMethod *method = endpoint->handleMethods->askResource; |
| |
| if (!method) { |
| logUnsupportedOperation("askResource"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| return method(endpoint->handle, recipient, type, |
| request, value, index, buffer, size, |
| endpoint->options.requestTimeout); |
| } |
| |
| HidItemsDescriptor * |
| gioGetHidDescriptorMethod ( |
| GioEndpoint *endpoint) { |
| GioGetHidDescriptorMethod *method = endpoint->handleMethods->getHidDescriptor; |
| |
| if (!method) { |
| logUnsupportedOperation("getHidDescriptor"); |
| errno = ENOSYS; |
| return NULL; |
| } |
| |
| return method( |
| endpoint->handle |
| ); |
| } |
| |
| int |
| gioGetHidReportSize ( |
| GioEndpoint *endpoint, |
| HidReportIdentifier identifier, |
| HidReportSize *size |
| ) { |
| GioGetHidReportSizeMethod *method = endpoint->handleMethods->getHidReportSize; |
| |
| if (!method) { |
| logUnsupportedOperation("getHidReportSize"); |
| errno = ENOSYS; |
| return 0; |
| } |
| |
| int ok = method( |
| endpoint->handle, identifier, size, |
| endpoint->options.requestTimeout |
| ); |
| |
| if (ok) return 1; |
| logMessage(LOG_WARNING, "HID report not found: %02X", identifier); |
| return 0; |
| } |
| |
| size_t |
| gioGetHidInputSize ( |
| GioEndpoint *endpoint, |
| HidReportIdentifier identifier |
| ) { |
| HidReportSize size; |
| if (!gioGetHidReportSize(endpoint, identifier, &size)) return 0; |
| return size.input; |
| } |
| |
| size_t |
| gioGetHidOutputSize ( |
| GioEndpoint *endpoint, |
| HidReportIdentifier identifier |
| ) { |
| HidReportSize size; |
| if (!gioGetHidReportSize(endpoint, identifier, &size)) return 0; |
| return size.output; |
| } |
| |
| size_t |
| gioGetHidFeatureSize ( |
| GioEndpoint *endpoint, |
| HidReportIdentifier identifier |
| ) { |
| HidReportSize size; |
| if (!gioGetHidReportSize(endpoint, identifier, &size)) return 0; |
| return size.feature; |
| } |
| |
| ssize_t |
| gioGetHidReport ( |
| GioEndpoint *endpoint, HidReportIdentifier identifier, |
| unsigned char *buffer, size_t size |
| ) { |
| GioGetHidReportMethod *method = endpoint->handleMethods->getHidReport; |
| |
| if (!method) { |
| logUnsupportedOperation("getHidReport"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| buffer[0] = identifier; |
| return method(endpoint->handle, identifier, |
| buffer, size, endpoint->options.requestTimeout); |
| } |
| |
| ssize_t |
| gioReadHidReport ( |
| GioEndpoint *endpoint, |
| unsigned char *buffer, size_t size |
| ) { |
| return gioGetHidReport(endpoint, buffer[0], buffer, size); |
| } |
| |
| ssize_t |
| gioSetHidReport ( |
| GioEndpoint *endpoint, HidReportIdentifier identifier, |
| const unsigned char *data, size_t size |
| ) { |
| GioSetHidReportMethod *method = endpoint->handleMethods->setHidReport; |
| |
| if (!method) { |
| logUnsupportedOperation("setHidReport"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| return method(endpoint->handle, identifier, |
| data, size, endpoint->options.requestTimeout); |
| } |
| |
| ssize_t |
| gioWriteHidReport ( |
| GioEndpoint *endpoint, |
| const unsigned char *data, size_t size |
| ) { |
| HidReportIdentifier identifier = data[0]; |
| if (!identifier) data += 1, size -= 1; |
| return gioSetHidReport(endpoint, identifier, data, size); |
| } |
| |
| ssize_t |
| gioGetHidFeature ( |
| GioEndpoint *endpoint, HidReportIdentifier identifier, |
| unsigned char *buffer, size_t size |
| ) { |
| GioGetHidFeatureMethod *method = endpoint->handleMethods->getHidFeature; |
| |
| if (!method) { |
| logUnsupportedOperation("getHidFeature"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| buffer[0] = identifier; |
| return method(endpoint->handle, identifier, |
| buffer, size, endpoint->options.requestTimeout); |
| } |
| |
| ssize_t |
| gioReadHidFeature ( |
| GioEndpoint *endpoint, |
| unsigned char *buffer, size_t size |
| ) { |
| return gioGetHidFeature(endpoint, buffer[0], buffer, size); |
| } |
| |
| ssize_t |
| gioSetHidFeature ( |
| GioEndpoint *endpoint, HidReportIdentifier identifier, |
| const unsigned char *data, size_t size |
| ) { |
| GioSetHidFeatureMethod *method = endpoint->handleMethods->setHidFeature; |
| |
| if (!method) { |
| logUnsupportedOperation("setHidFeature"); |
| errno = ENOSYS; |
| return -1; |
| } |
| |
| return method(endpoint->handle, identifier, |
| data, size, endpoint->options.requestTimeout); |
| } |
| |
| ssize_t |
| gioWriteHidFeature ( |
| GioEndpoint *endpoint, |
| const unsigned char *data, size_t size |
| ) { |
| HidReportIdentifier identifier = data[0]; |
| if (!identifier) data += 1, size -= 1; |
| return gioSetHidFeature(endpoint, identifier, data, size); |
| } |
| |
| struct GioHandleInputObjectStruct { |
| GioEndpoint *endpoint; |
| AsyncHandle pollAlarm; |
| |
| GioInputHandler *handler; |
| void *data; |
| }; |
| |
| static int |
| handleInput (GioHandleInputObject *hio, int error) { |
| GioHandleInputParameters parameters = { |
| .error = error, |
| .data = hio->data |
| }; |
| |
| return hio->handler(¶meters); |
| } |
| |
| ASYNC_MONITOR_CALLBACK(gioInputMonitor) { |
| GioHandleInputObject *hio = parameters->data; |
| |
| handleInput(hio, parameters->error); |
| return 1; |
| } |
| |
| ASYNC_ALARM_CALLBACK(handleInputAlarm) { |
| GioHandleInputObject *hio = parameters->data; |
| |
| if (handleInput(hio, 0)) asyncResetAlarmIn(hio->pollAlarm, 0); |
| } |
| |
| GioHandleInputObject * |
| gioNewHandleInputObject ( |
| GioEndpoint *endpoint, int pollInterval, |
| GioInputHandler *handler, void *data |
| ) { |
| GioHandleInputObject *hio; |
| |
| if ((hio = malloc(sizeof(*hio)))) { |
| memset(hio, 0, sizeof(*hio)); |
| |
| hio->endpoint = endpoint; |
| hio->pollAlarm = NULL; |
| |
| hio->handler = handler; |
| hio->data = data; |
| |
| if (endpoint) { |
| if (gioMonitorInput(endpoint, gioInputMonitor, hio)) { |
| handleInput(hio, 0); |
| return hio; |
| } |
| } |
| |
| if (asyncNewRelativeAlarm(&hio->pollAlarm, 0, handleInputAlarm, hio)) { |
| if (asyncResetAlarmInterval(hio->pollAlarm, pollInterval)) { |
| return hio; |
| } |
| |
| asyncCancelRequest(hio->pollAlarm); |
| } |
| |
| free(hio); |
| } else { |
| logMallocError(); |
| } |
| |
| return NULL; |
| } |
| |
| void |
| gioDestroyHandleInputObject (GioHandleInputObject *hio) { |
| if (hio->pollAlarm) { |
| asyncCancelRequest(hio->pollAlarm); |
| } else { |
| gioMonitorInput(hio->endpoint, NULL, NULL); |
| } |
| |
| free(hio); |
| } |