blob: 180dcafad1fcaee304e07af3f8470fccdc5ae852 [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 "usb_serial.h"
#include "usb_cp2101.h"
#include "bitfield.h"
static size_t
usbGetProperty_CP2101 (UsbDevice *device, uint8_t request, void *data, size_t length) {
ssize_t result;
logMessage(LOG_CATEGORY(SERIAL_IO), "getting CP2101 property: %02X", request);
result = usbControlRead(device, UsbControlRecipient_Interface, UsbControlType_Vendor,
request, 0, 0, data, length, 1000);
if (result == -1) return 0;
if (result < length) {
unsigned char *bytes = data;
memset(&bytes[result], 0, length-result);
}
logBytes(LOG_CATEGORY(SERIAL_IO), "CP2101 property input", data, result);
return result;
}
static int
usbSetComplexProperty_CP2101 (
UsbDevice *device,
uint8_t request, uint16_t value,
const void *data, size_t length
) {
logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 property: %02X %04X", request, value);
if (length) logBytes(LOG_CATEGORY(SERIAL_IO), "CP2101 property output", data, length);
return usbControlWrite(device, UsbControlRecipient_Interface, UsbControlType_Vendor,
request, value, 0, data, length, 1000) != -1;
}
static int
usbSetSimpleProperty_CP2101 (UsbDevice *device, uint8_t request, uint16_t value) {
return usbSetComplexProperty_CP2101(device, request, value, NULL, 0);
}
static int
usbVerifyBaudRate_CP2101 (UsbDevice *device, USB_CP2101_BaudRate expected) {
USB_CP2101_BaudRate actual;
ssize_t result;
logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 baud rate");
result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetBaudRate,
&actual, sizeof(actual));
if (result == -1) {
logMessage(LOG_WARNING, "unable to get CP2101 baud rate: %s", strerror(errno));
} else if (result != sizeof(actual)) {
logMessage(LOG_WARNING, "unexpected CP2101 baud rate size: %d", (int)result);
} else if (getLittleEndian32(actual) != expected) {
logMessage(LOG_WARNING,
"unexpected CP2101 baud rate value:"
" Expected:%"PRIu32 " Actual:%"PRIu32,
expected, getLittleEndian32(actual));
} else {
return 1;
}
return 0;
}
static int
usbVerifyBaudDivisor_CP2101 (UsbDevice *device, USB_CP2101_BaudDivisor expected) {
USB_CP2101_BaudDivisor actual;
ssize_t result;
logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 baud divisor");
result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetBaudDivisor,
&actual, sizeof(actual));
if (result == -1) {
logMessage(LOG_WARNING, "unable to get CP2101 baud divisor: %s", strerror(errno));
} else if (result != sizeof(actual)) {
logMessage(LOG_WARNING, "unexpected CP2101 baud divisor size: %d", (int)result);
} else if (getLittleEndian16(actual) != expected) {
logMessage(LOG_WARNING,
"unexpected CP2101 baud divisor value: Expected:%u Actual:%u",
expected, getLittleEndian16(actual));
} else {
return 1;
}
return 0;
}
static int
usbVerifyBaud_CP2101 (UsbDevice *device, USB_CP2101_BaudRate rate, USB_CP2101_BaudDivisor divisor) {
if (!usbVerifyBaudRate_CP2101(device, rate)) return 0;
if (!usbVerifyBaudDivisor_CP2101(device, divisor)) return 0;
return 1;
}
static int
usbSetBaud_CP2101 (UsbDevice *device, unsigned int baud) {
USB_CP2101_BaudDivisor divisor = USB_CP2101_BAUD_BASE / baud;
if ((baud * divisor) != USB_CP2101_BAUD_BASE) {
logUnsupportedBaud(baud);
errno = EINVAL;
return 0;
}
{
USB_CP2101_BaudRate rate;
logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 baud rate: %u", baud);
putLittleEndian32(&rate, baud);
if (!usbSetComplexProperty_CP2101(device, USB_CP2101_CTL_SetBaudRate, 0,
&rate, sizeof(rate))) {
logMessage(LOG_WARNING, "unable to set CP2101 baud rate: %s", strerror(errno));
} else if (usbVerifyBaud_CP2101(device, baud, divisor)) {
return 1;
}
}
{
logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 baud divisor: %u", divisor);
if (!usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_SetBaudDivisor, divisor)) {
logMessage(LOG_WARNING, "unable to set CP2101 baud divisor: %s", strerror(errno));
} else if (usbVerifyBaud_CP2101(device, baud, divisor)) {
return 1;
}
}
return 0;
}
static int
usbSetModemState_CP2101 (UsbDevice *device, int state, int shift, const char *name) {
if ((state < 0) || (state > 1)) {
logMessage(LOG_WARNING, "unsupported CP2101 %s state: %d", name, state);
errno = EINVAL;
return 0;
}
logMessage(LOG_CATEGORY(SERIAL_IO),
"setting CP2101 %s state: %s",
name, (state? "high": "low"));
return usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_SetModemHandShaking, ((1 << (shift + 8)) | (state << shift)));
}
static int
usbSetDtrState_CP2101 (UsbDevice *device, int state) {
return usbSetModemState_CP2101(device, state, 0, "DTR");
}
static int
usbSetRtsState_CP2101 (UsbDevice *device, int state) {
return usbSetModemState_CP2101(device, state, 1, "RTS");
}
static int
usbVerifyFlowControl_CP2101 (UsbDevice *device, const USB_CP2101_FlowControl *expected, size_t size) {
USB_CP2101_FlowControl actual;
ssize_t result;
logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 flow control");
result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetFlowControl,
&actual, sizeof(actual));
if (result == -1) {
logMessage(LOG_WARNING, "unable to get CP2101 flow control: %s", strerror(errno));
} else if (result != size) {
logMessage(LOG_WARNING, "unexpected CP2101 flow control size: %d", (int)result);
} else if (memcmp(&actual, expected, size) != 0) {
logMessage(LOG_WARNING, "unexpected CP2101 flow control data");
logBytes(LOG_WARNING, "expected flow control", expected, size);
logBytes(LOG_WARNING, "actual flow control", &actual, size);
} else {
return 1;
}
return 0;
}
static int
usbSetFlowControl_CP2101 (UsbDevice *device, SerialFlowControl flow) {
USB_CP2101_FlowControl oldSettings;
USB_CP2101_FlowControl newSettings;
size_t size;
logMessage(LOG_CATEGORY(SERIAL_IO), "getting CP2101 flow control");
size = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetFlowControl,
&oldSettings, sizeof(oldSettings));
if (!size) {
logMessage(LOG_WARNING, "unable to get CP2101 flow control");
return 0;
}
newSettings = oldSettings;
newSettings.handshakeOptions = getLittleEndian32(newSettings.handshakeOptions);
newSettings.dataFlowOptions = getLittleEndian32(newSettings.dataFlowOptions);
newSettings.handshakeOptions &= ~USB_CP2101_FLOW_HSO_DTR_MASK;
newSettings.handshakeOptions |= USB_CP2101_FLOW_HSO_DTR_ACTIVE;
if (flow & SERIAL_FLOW_OUTPUT_CTS) {
flow &= ~SERIAL_FLOW_OUTPUT_CTS;
newSettings.handshakeOptions |= USB_CP2101_FLOW_HSO_CTS_INTERPRET;
} else {
newSettings.handshakeOptions &= ~USB_CP2101_FLOW_HSO_CTS_INTERPRET;
}
if (flow & SERIAL_FLOW_OUTPUT_RTS) {
flow &= ~SERIAL_FLOW_OUTPUT_RTS;
newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_RTS_MASK;
newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_RTS_XMT_ACTIVE;
} else {
newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_RTS_MASK;
newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_RTS_ACTIVE;
}
if (flow & SERIAL_FLOW_OUTPUT_XON) {
flow &= ~SERIAL_FLOW_OUTPUT_XON;
newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_AUTO_TRANSMIT;
} else {
newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_AUTO_TRANSMIT;
}
if (flow & SERIAL_FLOW_INPUT_XON) {
flow &= ~SERIAL_FLOW_INPUT_XON;
newSettings.dataFlowOptions |= USB_CP2101_FLOW_DFO_AUTO_RECEIVE;
} else {
newSettings.dataFlowOptions &= ~USB_CP2101_FLOW_DFO_AUTO_RECEIVE;
}
putLittleEndian32(&newSettings.handshakeOptions, newSettings.handshakeOptions);
putLittleEndian32(&newSettings.dataFlowOptions, newSettings.dataFlowOptions);
if (flow) {
logUnsupportedFlowControl(flow);
errno = EINVAL;
return 0;
}
if (memcmp(&newSettings, &oldSettings, size) == 0) {
logMessage(LOG_CATEGORY(SERIAL_IO), "CP2101 flow control unchanged");
}
logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 flow control");
if (!usbSetComplexProperty_CP2101(device, USB_CP2101_CTL_SetFlowControl, 0,
&newSettings, size)) {
logMessage(LOG_WARNING, "unable to set CP2101 flow control: %s", strerror(errno));
} else if (usbVerifyFlowControl_CP2101(device, &newSettings, size)) {
return 1;
}
return 0;
}
static int
usbVerifyLineControl_CP2101 (UsbDevice *device, USB_CP2101_LineControl expected) {
USB_CP2101_LineControl actual;
ssize_t result;
logMessage(LOG_CATEGORY(SERIAL_IO), "verifying CP2101 line control");
result = usbGetProperty_CP2101(device, USB_CP2101_CTL_GetLineControl,
&actual, sizeof(actual));
if (result == -1) {
logMessage(LOG_WARNING, "unable to get CP2101 line control: %s", strerror(errno));
} else if (result != sizeof(actual)) {
logMessage(LOG_WARNING, "unexpected CP2101 line control size: %d", (int)result);
} else if (getLittleEndian16(actual) != expected) {
logMessage(LOG_WARNING,
"unexpected CP2101 line control value: Expected:0X%04X Actual:0X%04X",
expected, getLittleEndian16(actual));
} else {
return 1;
}
return 0;
}
static int
usbSetDataFormat_CP2101 (UsbDevice *device, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) {
int ok = 1;
USB_CP2101_LineControl lineControl = 0;
{
USB_CP2101_LineControl value;
if ((dataBits >= USB_CP2101_DATA_MINIMUM) &&
(dataBits <= USB_CP2101_DATA_MAXIMUM)) {
value = dataBits;
} else {
logUnsupportedDataBits(dataBits);
ok = 0;
value = 8;
}
lineControl |= value << USB_CP2101_DATA_SHIFT;
}
{
USB_CP2101_LineControl value;
switch (parity) {
case SERIAL_PARITY_NONE: value = USB_CP2101_PARITY_NONE; break;
case SERIAL_PARITY_ODD: value = USB_CP2101_PARITY_ODD; break;
case SERIAL_PARITY_EVEN: value = USB_CP2101_PARITY_EVEN; break;
case SERIAL_PARITY_MARK: value = USB_CP2101_PARITY_MARK; break;
case SERIAL_PARITY_SPACE: value = USB_CP2101_PARITY_SPACE; break;
default:
logUnsupportedParity(parity);
ok = 0;
value = USB_CP2101_PARITY_NONE;
break;
}
lineControl |= value << USB_CP2101_PARITY_SHIFT;
}
{
USB_CP2101_LineControl value;
switch (stopBits) {
case SERIAL_STOP_1: value = USB_CP2101_STOP_1; break;
case SERIAL_STOP_1_5: value = USB_CP2101_STOP_1_5; break;
case SERIAL_STOP_2: value = USB_CP2101_STOP_2; break;
default:
logUnsupportedStopBits(stopBits);
ok = 0;
value = USB_CP2101_STOP_1;
break;
}
lineControl |= value << USB_CP2101_STOP_SHIFT;
}
if (ok) {
logMessage(LOG_CATEGORY(SERIAL_IO), "setting CP2101 line control: 0X%04X", lineControl);
if (!usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_SetLineControl, lineControl)) {
logMessage(LOG_WARNING, "unable to set CP2101 line control: %0X04X", lineControl);
} else if (usbVerifyLineControl_CP2101(device, lineControl)) {
return 1;
}
}
errno = EINVAL;
return 0;
}
static int
usbSetInterfaceState_CP2101 (UsbDevice *device, int state) {
logMessage(LOG_CATEGORY(SERIAL_IO),
"setting CP2101 interface state: %s",
(state? "enabled": "disabled"));
return usbSetSimpleProperty_CP2101(device, USB_CP2101_CTL_EnableInterface, state);
}
static int
usbEnableAdapter_CP2101 (UsbDevice *device) {
if (!usbSetInterfaceState_CP2101(device, 0)) return 0;
if (!usbSetInterfaceState_CP2101(device, 1)) return 0;
return 1;
}
const UsbSerialOperations usbSerialOperations_CP2101 = {
.name = "CP2101",
.setBaud = usbSetBaud_CP2101,
.setDataFormat = usbSetDataFormat_CP2101,
.setFlowControl = usbSetFlowControl_CP2101,
.setDtrState = usbSetDtrState_CP2101,
.setRtsState = usbSetRtsState_CP2101,
.enableAdapter = usbEnableAdapter_CP2101
};