From f3f9d1fb9533529776ded4fd3af7fd274ba5f2fe Mon Sep 17 00:00:00 2001 From: Tomasz Moń Date: Tue, 8 Jun 2021 16:46:07 +0200 Subject: USB Serial: Implement Abstract Control Management On devices that can assign interrupt IN, bulk IN and bulk OUT endpoints this change results in the serial interface working out of the box on Linux and Windows. On Linux it is registered as ttyACM device and on Windows it is assigned a COM port number. On devices that cannot assign the interrupt IN this change won't have any effect. Implement minimum required interface control requests. Respond with whatever line coding was set to make terminal programs happy. Change-Id: Id7d3899d8546e45d7cb4ecc3fe464908cb59e810 --- firmware/export/usb_ch9.h | 17 ++++ firmware/usbstack/usb_serial.c | 222 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 224 insertions(+), 15 deletions(-) diff --git a/firmware/export/usb_ch9.h b/firmware/export/usb_ch9.h index c11775b893..659bcca101 100644 --- a/firmware/export/usb_ch9.h +++ b/firmware/export/usb_ch9.h @@ -390,6 +390,23 @@ struct usb_debug_descriptor { uint8_t bDebugOutEndpoint; } __attribute__((packed)); +/*-------------------------------------------------------------------------*/ + +/* USB_DT_INTERFACE_ASSOCIATION: groups interfaces */ +struct usb_interface_assoc_descriptor { + uint8_t bLength; + uint8_t bDescriptorType; + + uint8_t bFirstInterface; + uint8_t bInterfaceCount; + uint8_t bFunctionClass; + uint8_t bFunctionSubClass; + uint8_t bFunctionProtocol; + uint8_t iFunction; +} __attribute__ ((packed)); + +#define USB_DT_INTERFACE_ASSOCIATION_SIZE 8 + /*-------------------------------------------------------------------------*/ /* USB 2.0 defines three speeds, here's how Linux identifies them */ diff --git a/firmware/usbstack/usb_serial.c b/firmware/usbstack/usb_serial.c index d879dc7c99..a08394c0a8 100644 --- a/firmware/usbstack/usb_serial.c +++ b/firmware/usbstack/usb_serial.c @@ -8,6 +8,7 @@ * $Id$ * * Copyright (C) 2007 by Christian Gmeiner + * Copyright (C) 2021 by Tomasz Moń * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -28,9 +29,128 @@ /*#define LOGF_ENABLE*/ #include "logf.h" -/* serial interface */ -static struct usb_interface_descriptor __attribute__((aligned(2))) - interface_descriptor = +#define CDC_SUBCLASS_ACM 0x02 +#define CDC_PROTOCOL_NONE 0x00 + +/* Class-Specific Request Codes */ +#define SET_LINE_CODING 0x20 +#define GET_LINE_CODING 0x21 +#define SET_CONTROL_LINE_STATE 0x22 + +#define SUBTYPE_HEADER 0x00 +#define SUBTYPE_CALL_MANAGEMENT 0x01 +#define SUBTYPE_ACM 0x02 +#define SUBTYPE_UNION 0x06 + +/* Support SET_LINE_CODING, GET_LINE_CODING, SET_CONTROL_LINE_STATE requests + * and SERIAL_STATE notification. + */ +#define ACM_CAP_LINE_CODING 0x02 + +struct cdc_header_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint16_t bcdCDC; +} __attribute__((packed)); + +struct cdc_call_management_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmCapabilities; + uint8_t bDataInterface; +} __attribute__((packed)); + +struct cdc_acm_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bmCapabilities; +} __attribute__((packed)); + +struct cdc_union_descriptor { + uint8_t bFunctionLength; + uint8_t bDescriptorType; + uint8_t bDescriptorSubtype; + uint8_t bControlInterface; + uint8_t bSubordinateInterface0; +} __attribute__((packed)); + +struct cdc_line_coding { + uint32_t dwDTERate; + uint8_t bCharFormat; + uint8_t bParityType; + uint8_t bDataBits; +} __attribute__((packed)); + +static struct usb_interface_assoc_descriptor + association_descriptor = +{ + .bLength = sizeof(struct usb_interface_assoc_descriptor), + .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION, + .bFirstInterface = 0, + .bInterfaceCount = 2, + .bFunctionClass = USB_CLASS_COMM, + .bFunctionSubClass = CDC_SUBCLASS_ACM, + .bFunctionProtocol = CDC_PROTOCOL_NONE, + .iFunction = 0 +}; + +static struct usb_interface_descriptor + control_interface_descriptor = +{ + .bLength = sizeof(struct usb_interface_descriptor), + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_COMM, + .bInterfaceSubClass = CDC_SUBCLASS_ACM, + .bInterfaceProtocol = CDC_PROTOCOL_NONE, + .iInterface = 0 +}; + +static struct cdc_header_descriptor + header_descriptor = +{ + .bFunctionLength = sizeof(struct cdc_header_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = SUBTYPE_HEADER, + .bcdCDC = 0x0110 +}; + +static struct cdc_call_management_descriptor + call_management_descriptor = +{ + .bFunctionLength = sizeof(struct cdc_call_management_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = SUBTYPE_CALL_MANAGEMENT, + .bmCapabilities = 0, + .bDataInterface = 0 +}; + +static struct cdc_acm_descriptor + acm_descriptor = +{ + .bFunctionLength = sizeof(struct cdc_acm_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = SUBTYPE_ACM, + .bmCapabilities = ACM_CAP_LINE_CODING +}; + +static struct cdc_union_descriptor + union_descriptor = +{ + .bFunctionLength = sizeof(struct cdc_union_descriptor), + .bDescriptorType = USB_DT_CS_INTERFACE, + .bDescriptorSubtype = SUBTYPE_UNION, + .bControlInterface = 0, + .bSubordinateInterface0 = 0 +}; + +static struct usb_interface_descriptor + data_interface_descriptor = { .bLength = sizeof(struct usb_interface_descriptor), .bDescriptorType = USB_DT_INTERFACE, @@ -43,8 +163,7 @@ static struct usb_interface_descriptor __attribute__((aligned(2))) .iInterface = 0 }; - -static struct usb_endpoint_descriptor __attribute__((aligned(2))) +static struct usb_endpoint_descriptor endpoint_descriptor = { .bLength = sizeof(struct usb_endpoint_descriptor), @@ -55,6 +174,8 @@ static struct usb_endpoint_descriptor __attribute__((aligned(2))) .bInterval = 0 }; +static struct cdc_line_coding line_coding; + /* send_buffer: local ring buffer. * transit_buffer: used to store aligned data that will be sent by the USB * driver. PP502x needs boost for high speed USB, but still works up to @@ -78,8 +199,8 @@ static int buffer_length; static int buffer_transitlength; static bool active = false; -static int ep_in, ep_out; -static int usb_interface; +static int ep_in, ep_out, ep_int; +static int control_interface, data_interface; int usb_serial_request_endpoints(struct usb_class_driver *drv) { @@ -94,25 +215,59 @@ int usb_serial_request_endpoints(struct usb_class_driver *drv) return -1; } + /* Optional interrupt endpoint. While the code does not actively use it, + * it is needed to get out-of-the-box serial port experience on Windows + * and Linux. If this endpoint is not available, only CDC Data interface + * will be exported (can still work on Linux with manual modprobe). + */ + ep_int = usb_core_request_endpoint(USB_ENDPOINT_XFER_INT, USB_DIR_IN, drv); + return 0; } int usb_serial_set_first_interface(int interface) { - usb_interface = interface; - return interface + 1; + control_interface = interface; + data_interface = interface + 1; + return interface + 2; } int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size) { unsigned char *orig_dest = dest; - interface_descriptor.bInterfaceNumber = usb_interface; - PACK_DATA(&dest, interface_descriptor); + association_descriptor.bFirstInterface = control_interface; + control_interface_descriptor.bInterfaceNumber = control_interface; + call_management_descriptor.bDataInterface = data_interface; + union_descriptor.bControlInterface = control_interface; + union_descriptor.bSubordinateInterface0 = data_interface; + data_interface_descriptor.bInterfaceNumber = data_interface; - endpoint_descriptor.wMaxPacketSize = max_packet_size; + if (ep_int > 0) + { + PACK_DATA(&dest, association_descriptor); + PACK_DATA(&dest, control_interface_descriptor); + PACK_DATA(&dest, header_descriptor); + PACK_DATA(&dest, call_management_descriptor); + PACK_DATA(&dest, acm_descriptor); + PACK_DATA(&dest, union_descriptor); + + /* Notification endpoint. Set wMaxPacketSize to 64 as it is valid + * both on Full and High speed. Note that max_packet_size is for bulk. + * Maximum bInterval for High Speed is 16 and for Full Speed is 255. + */ + endpoint_descriptor.bEndpointAddress = ep_int; + endpoint_descriptor.bmAttributes = USB_ENDPOINT_XFER_INT; + endpoint_descriptor.wMaxPacketSize = 64; + endpoint_descriptor.bInterval = 16; + PACK_DATA(&dest, endpoint_descriptor); + } + PACK_DATA(&dest, data_interface_descriptor); endpoint_descriptor.bEndpointAddress = ep_in; + endpoint_descriptor.bmAttributes = USB_ENDPOINT_XFER_BULK; + endpoint_descriptor.wMaxPacketSize = max_packet_size; + endpoint_descriptor.bInterval = 0; PACK_DATA(&dest, endpoint_descriptor); endpoint_descriptor.bEndpointAddress = ep_out; @@ -127,10 +282,47 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest bool handled = false; (void)dest; - switch (req->bRequest) { - default: - logf("serial: unhandeld req %d", req->bRequest); + if (req->wIndex != control_interface) + { + return false; } + + if (req->bRequestType == (USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE)) + { + if (req->bRequest == SET_LINE_CODING) + { + if (req->wLength == sizeof(line_coding)) + { + /* Receive line coding into local copy */ + usb_drv_recv(EP_CONTROL, &line_coding, sizeof(line_coding)); + usb_drv_send(EP_CONTROL, NULL, 0); /* ack */ + handled = true; + } + } + else if (req->bRequest == SET_CONTROL_LINE_STATE) + { + if (req->wLength == 0) + { + /* wValue holds Control Signal Bitmap that is simply ignored here */ + usb_drv_send(EP_CONTROL, NULL, 0); /* ack */ + handled = true; + } + } + } + else if (req->bRequestType == (USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE)) + { + if (req->bRequest == GET_LINE_CODING) + { + if (req->wLength == sizeof(line_coding)) + { + /* Send back line coding so host is happy */ + usb_drv_recv(EP_CONTROL, NULL, 0); /* ack */ + usb_drv_send(EP_CONTROL, &line_coding, sizeof(line_coding)); + handled = true; + } + } + } + return handled; } -- cgit v1.2.3