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; }