blob: 9d54741b08f14082de76de19ec491a6bb82eefb7 [file] [log] [blame] [edit]
/*
* 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(&parameters);
}
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);
}