| /* |
| * 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 "strfmt.h" |
| #include "usb_serial.h" |
| #include "usb_cdc_acm.h" |
| #include "usb_internal.h" |
| #include "bitfield.h" |
| |
| struct UsbSerialDataStruct { |
| UsbDevice *device; |
| const UsbInterfaceDescriptor *interface; |
| const UsbEndpointDescriptor *endpoint; |
| |
| USB_CDC_ACM_LineCoding lineCoding; |
| }; |
| |
| static int |
| usbGetParameters_CDC_ACM (UsbDevice *device, uint8_t request, uint16_t value, void *data, uint16_t size) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| ssize_t result = usbControlRead(device, |
| UsbControlRecipient_Interface, UsbControlType_Class, |
| request, value, usd->interface->bInterfaceNumber, |
| data, size, 1000 |
| ); |
| |
| return result != -1; |
| } |
| |
| static int |
| usbGetParameter_CDC_ACM (UsbDevice *device, uint8_t request, void *data, uint16_t size) { |
| return usbGetParameters_CDC_ACM(device, request, 0, data, size); |
| } |
| |
| static int |
| usbSetParameters_CDC_ACM (UsbDevice *device, uint8_t request, uint16_t value, const void *data, uint16_t size) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| ssize_t result = usbControlWrite(device, |
| UsbControlRecipient_Interface, UsbControlType_Class, |
| request, value, usd->interface->bInterfaceNumber, |
| data, size, 1000 |
| ); |
| |
| return result != -1; |
| } |
| |
| static int |
| usbSetParameter_CDC_ACM (UsbDevice *device, uint8_t request, uint16_t value) { |
| return usbSetParameters_CDC_ACM(device, request, value, NULL, 0); |
| } |
| |
| static int |
| usbSetControlLines_CDC_ACM (UsbDevice *device, uint16_t lines) { |
| return usbSetParameter_CDC_ACM(device, USB_CDC_ACM_CTL_SetControlLineState, lines); |
| } |
| |
| static void |
| usbLogLineCoding_CDC_ACM (const USB_CDC_ACM_LineCoding *lineCoding) { |
| char log[0X80]; |
| |
| STR_BEGIN(log, sizeof(log)); |
| STR_PRINTF("CDC ACM line coding:"); |
| |
| { // baud (bits per second) |
| uint32_t baud = getLittleEndian32(lineCoding->dwDTERate); |
| STR_PRINTF(" Baud:%" PRIu32, baud); |
| } |
| |
| { // number of data bits |
| STR_PRINTF(" Data:%u", lineCoding->bDataBits); |
| } |
| |
| { // number of stop bits |
| const char *bits; |
| |
| #define USB_CDC_ACM_STOP(value,name) \ |
| case USB_CDC_ACM_STOP_##value: bits = #name; break; |
| switch (lineCoding->bCharFormat) { |
| USB_CDC_ACM_STOP(1 , 1 ) |
| USB_CDC_ACM_STOP(1_5, 1.5) |
| USB_CDC_ACM_STOP(2 , 2 ) |
| default: bits = "?"; break; |
| } |
| #undef USB_CDC_ACM_STOP |
| |
| STR_PRINTF(" Stop:%s", bits); |
| } |
| |
| { // type of parity |
| const char *parity; |
| |
| #define USB_CDC_ACM_PARITY(value,name) \ |
| case USB_CDC_ACM_PARITY_##value: parity = #name; break; |
| switch (lineCoding->bParityType) { |
| USB_CDC_ACM_PARITY(NONE , none ) |
| USB_CDC_ACM_PARITY(ODD , odd ) |
| USB_CDC_ACM_PARITY(EVEN , even ) |
| USB_CDC_ACM_PARITY(MARK , mark ) |
| USB_CDC_ACM_PARITY(SPACE, space) |
| default: parity = "?"; break; |
| } |
| #undef USB_CDC_ACM_PARITY |
| |
| STR_PRINTF(" Parity:%s", parity); |
| } |
| |
| STR_END; |
| logMessage(LOG_CATEGORY(SERIAL_IO), "%s", log); |
| } |
| |
| static int |
| usbSetLineProperties_CDC_ACM (UsbDevice *device, unsigned int baud, unsigned int dataBits, SerialStopBits stopBits, SerialParity parity) { |
| USB_CDC_ACM_LineCoding lineCoding; |
| memset(&lineCoding, 0, sizeof(lineCoding)); |
| |
| putLittleEndian32(&lineCoding.dwDTERate, baud); |
| |
| switch (dataBits) { |
| case 5: |
| case 6: |
| case 7: |
| case 8: |
| case 16: |
| lineCoding.bDataBits = dataBits; |
| break; |
| |
| default: |
| logUnsupportedDataBits(dataBits); |
| errno = EINVAL; |
| return 0; |
| } |
| |
| switch (stopBits) { |
| case SERIAL_STOP_1: |
| lineCoding.bCharFormat = USB_CDC_ACM_STOP_1; |
| break; |
| |
| case SERIAL_STOP_1_5: |
| lineCoding.bCharFormat = USB_CDC_ACM_STOP_1_5; |
| break; |
| |
| case SERIAL_STOP_2: |
| lineCoding.bCharFormat = USB_CDC_ACM_STOP_2; |
| break; |
| |
| default: |
| logUnsupportedStopBits(stopBits); |
| errno = EINVAL; |
| return 0; |
| } |
| |
| switch (parity) { |
| case SERIAL_PARITY_NONE: |
| lineCoding.bParityType = USB_CDC_ACM_PARITY_NONE; |
| break; |
| |
| case SERIAL_PARITY_ODD: |
| lineCoding.bParityType = USB_CDC_ACM_PARITY_ODD; |
| break; |
| |
| case SERIAL_PARITY_EVEN: |
| lineCoding.bParityType = USB_CDC_ACM_PARITY_EVEN; |
| break; |
| |
| case SERIAL_PARITY_MARK: |
| lineCoding.bParityType = USB_CDC_ACM_PARITY_MARK; |
| break; |
| |
| case SERIAL_PARITY_SPACE: |
| lineCoding.bParityType = USB_CDC_ACM_PARITY_SPACE; |
| break; |
| |
| default: |
| logUnsupportedParity(parity); |
| errno = EINVAL; |
| return 0; |
| } |
| |
| { |
| UsbSerialData *usd = usbGetSerialData(device); |
| USB_CDC_ACM_LineCoding *oldCoding = &usd->lineCoding; |
| |
| if (memcmp(&lineCoding, oldCoding, sizeof(lineCoding)) != 0) { |
| if (!usbSetParameters_CDC_ACM(device, USB_CDC_ACM_CTL_SetLineCoding, 0, |
| &lineCoding, sizeof(lineCoding))) { |
| return 0; |
| } |
| |
| *oldCoding = lineCoding; |
| usbLogLineCoding_CDC_ACM(&lineCoding); |
| } |
| } |
| |
| return 1; |
| } |
| |
| static int |
| usbSetFlowControl_CDC_ACM (UsbDevice *device, SerialFlowControl flow) { |
| if (flow) { |
| logUnsupportedFlowControl(flow); |
| errno = EINVAL; |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static const UsbInterfaceDescriptor * |
| usbFindCommunicationInterface (UsbDevice *device) { |
| const UsbDescriptor *descriptor = NULL; |
| |
| while (usbNextDescriptor(device, &descriptor)) { |
| if (descriptor->header.bDescriptorType == UsbDescriptorType_Interface) { |
| if (descriptor->interface.bInterfaceClass == 0X02) { |
| return &descriptor->interface; |
| } |
| } |
| } |
| |
| logMessage(LOG_WARNING, "USB: communication interface descriptor not found"); |
| errno = ENOENT; |
| return NULL; |
| } |
| |
| static const UsbEndpointDescriptor * |
| usbFindInterruptInputEndpoint (UsbDevice *device, const UsbInterfaceDescriptor *interface) { |
| const UsbDescriptor *descriptor = (const UsbDescriptor *)interface; |
| |
| while (usbNextDescriptor(device, &descriptor)) { |
| if (descriptor->header.bDescriptorType == UsbDescriptorType_Interface) break; |
| |
| if (descriptor->header.bDescriptorType == UsbDescriptorType_Endpoint) { |
| if (USB_ENDPOINT_DIRECTION(&descriptor->endpoint) == UsbEndpointDirection_Input) { |
| if (USB_ENDPOINT_TRANSFER(&descriptor->endpoint) == UsbEndpointTransfer_Interrupt) { |
| return &descriptor->endpoint; |
| } |
| } |
| } |
| } |
| |
| logMessage(LOG_WARNING, "USB: interrupt input endpoint descriptor not found"); |
| errno = ENOENT; |
| return NULL; |
| } |
| |
| static int |
| usbMakeData_CDC_ACM (UsbDevice *device, UsbSerialData **serialData) { |
| UsbSerialData *usd; |
| |
| if ((usd = malloc(sizeof(*usd)))) { |
| memset(usd, 0, sizeof(*usd)); |
| usd->device = device; |
| |
| if ((usd->interface = usbFindCommunicationInterface(device))) { |
| unsigned char interfaceNumber = usd->interface->bInterfaceNumber; |
| |
| if (usbClaimInterface(device, interfaceNumber)) { |
| if (usbSetAlternative(device, usd->interface->bInterfaceNumber, usd->interface->bAlternateSetting)) { |
| if ((usd->endpoint = usbFindInterruptInputEndpoint(device, usd->interface))) { |
| usbBeginInput(device, USB_ENDPOINT_NUMBER(usd->endpoint)); |
| *serialData = usd; |
| return 1; |
| } |
| } |
| |
| usbReleaseInterface(device, interfaceNumber); |
| } |
| } |
| |
| free(usd); |
| } else { |
| logMallocError(); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| usbDestroyData_CDC_ACM (UsbSerialData *usd) { |
| usbReleaseInterface(usd->device, usd->interface->bInterfaceNumber); |
| free(usd); |
| } |
| |
| static int |
| usbEnableAdapter_CDC_ACM (UsbDevice *device) { |
| UsbSerialData *usd = usbGetSerialData(device); |
| |
| if (!usbSetControlLines_CDC_ACM(device, 0)) return 0; |
| if (!usbSetControlLines_CDC_ACM(device, USB_CDC_ACM_LINE_DTR)) return 0; |
| |
| { |
| USB_CDC_ACM_LineCoding *lineCoding = &usd->lineCoding; |
| |
| if (!usbGetParameter_CDC_ACM(device, USB_CDC_ACM_CTL_GetLineCoding, |
| lineCoding, sizeof(*lineCoding))) { |
| return 0; |
| } |
| |
| usbLogLineCoding_CDC_ACM(lineCoding); |
| } |
| |
| return 1; |
| } |
| |
| static void |
| usbDisableAdapter_CDC_ACM (UsbDevice *device) { |
| usbSetControlLines_CDC_ACM(device, 0); |
| } |
| |
| const UsbSerialOperations usbSerialOperations_CDC_ACM = { |
| .name = "CDC_ACM", |
| |
| .makeData = usbMakeData_CDC_ACM, |
| .destroyData = usbDestroyData_CDC_ACM, |
| |
| .setLineProperties = usbSetLineProperties_CDC_ACM, |
| .setFlowControl = usbSetFlowControl_CDC_ACM, |
| |
| .enableAdapter = usbEnableAdapter_CDC_ACM, |
| .disableAdapter = usbDisableAdapter_CDC_ACM |
| }; |