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
This commit is contained in:
Tomasz Moń 2021-06-08 16:46:07 +02:00
parent 663d846cf3
commit f3f9d1fb95
2 changed files with 224 additions and 15 deletions

View File

@ -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 */

View File

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