summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomasz Moń <desowin@gmail.com>2021-06-08 16:46:07 +0200
committerTomasz Moń <desowin@gmail.com>2021-06-12 14:47:15 +0000
commitf3f9d1fb9533529776ded4fd3af7fd274ba5f2fe (patch)
tree85bdcf140180d0a32be37dcb617a3663b59f8182
parent663d846cf3867a6aab40b981b4755a1f62a64dcd (diff)
downloadrockbox-f3f9d1fb9533529776ded4fd3af7fd274ba5f2fe.tar.gz
rockbox-f3f9d1fb9533529776ded4fd3af7fd274ba5f2fe.zip
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
-rw-r--r--firmware/export/usb_ch9.h17
-rw-r--r--firmware/usbstack/usb_serial.c222
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
@@ -391,6 +391,23 @@ struct usb_debug_descriptor {
391} __attribute__((packed)); 391} __attribute__((packed));
392 392
393/*-------------------------------------------------------------------------*/ 393/*-------------------------------------------------------------------------*/
394
395/* USB_DT_INTERFACE_ASSOCIATION: groups interfaces */
396struct usb_interface_assoc_descriptor {
397 uint8_t bLength;
398 uint8_t bDescriptorType;
399
400 uint8_t bFirstInterface;
401 uint8_t bInterfaceCount;
402 uint8_t bFunctionClass;
403 uint8_t bFunctionSubClass;
404 uint8_t bFunctionProtocol;
405 uint8_t iFunction;
406} __attribute__ ((packed));
407
408#define USB_DT_INTERFACE_ASSOCIATION_SIZE 8
409
410/*-------------------------------------------------------------------------*/
394/* USB 2.0 defines three speeds, here's how Linux identifies them */ 411/* USB 2.0 defines three speeds, here's how Linux identifies them */
395 412
396enum usb_device_speed { 413enum usb_device_speed {
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 @@
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2007 by Christian Gmeiner 10 * Copyright (C) 2007 by Christian Gmeiner
11 * Copyright (C) 2021 by Tomasz Moń
11 * 12 *
12 * This program is free software; you can redistribute it and/or 13 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 14 * modify it under the terms of the GNU General Public License
@@ -28,9 +29,128 @@
28/*#define LOGF_ENABLE*/ 29/*#define LOGF_ENABLE*/
29#include "logf.h" 30#include "logf.h"
30 31
31/* serial interface */ 32#define CDC_SUBCLASS_ACM 0x02
32static struct usb_interface_descriptor __attribute__((aligned(2))) 33#define CDC_PROTOCOL_NONE 0x00
33 interface_descriptor = 34
35/* Class-Specific Request Codes */
36#define SET_LINE_CODING 0x20
37#define GET_LINE_CODING 0x21
38#define SET_CONTROL_LINE_STATE 0x22
39
40#define SUBTYPE_HEADER 0x00
41#define SUBTYPE_CALL_MANAGEMENT 0x01
42#define SUBTYPE_ACM 0x02
43#define SUBTYPE_UNION 0x06
44
45/* Support SET_LINE_CODING, GET_LINE_CODING, SET_CONTROL_LINE_STATE requests
46 * and SERIAL_STATE notification.
47 */
48#define ACM_CAP_LINE_CODING 0x02
49
50struct cdc_header_descriptor {
51 uint8_t bFunctionLength;
52 uint8_t bDescriptorType;
53 uint8_t bDescriptorSubtype;
54 uint16_t bcdCDC;
55} __attribute__((packed));
56
57struct cdc_call_management_descriptor {
58 uint8_t bFunctionLength;
59 uint8_t bDescriptorType;
60 uint8_t bDescriptorSubtype;
61 uint8_t bmCapabilities;
62 uint8_t bDataInterface;
63} __attribute__((packed));
64
65struct cdc_acm_descriptor {
66 uint8_t bFunctionLength;
67 uint8_t bDescriptorType;
68 uint8_t bDescriptorSubtype;
69 uint8_t bmCapabilities;
70} __attribute__((packed));
71
72struct cdc_union_descriptor {
73 uint8_t bFunctionLength;
74 uint8_t bDescriptorType;
75 uint8_t bDescriptorSubtype;
76 uint8_t bControlInterface;
77 uint8_t bSubordinateInterface0;
78} __attribute__((packed));
79
80struct cdc_line_coding {
81 uint32_t dwDTERate;
82 uint8_t bCharFormat;
83 uint8_t bParityType;
84 uint8_t bDataBits;
85} __attribute__((packed));
86
87static struct usb_interface_assoc_descriptor
88 association_descriptor =
89{
90 .bLength = sizeof(struct usb_interface_assoc_descriptor),
91 .bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
92 .bFirstInterface = 0,
93 .bInterfaceCount = 2,
94 .bFunctionClass = USB_CLASS_COMM,
95 .bFunctionSubClass = CDC_SUBCLASS_ACM,
96 .bFunctionProtocol = CDC_PROTOCOL_NONE,
97 .iFunction = 0
98};
99
100static struct usb_interface_descriptor
101 control_interface_descriptor =
102{
103 .bLength = sizeof(struct usb_interface_descriptor),
104 .bDescriptorType = USB_DT_INTERFACE,
105 .bInterfaceNumber = 0,
106 .bAlternateSetting = 0,
107 .bNumEndpoints = 1,
108 .bInterfaceClass = USB_CLASS_COMM,
109 .bInterfaceSubClass = CDC_SUBCLASS_ACM,
110 .bInterfaceProtocol = CDC_PROTOCOL_NONE,
111 .iInterface = 0
112};
113
114static struct cdc_header_descriptor
115 header_descriptor =
116{
117 .bFunctionLength = sizeof(struct cdc_header_descriptor),
118 .bDescriptorType = USB_DT_CS_INTERFACE,
119 .bDescriptorSubtype = SUBTYPE_HEADER,
120 .bcdCDC = 0x0110
121};
122
123static struct cdc_call_management_descriptor
124 call_management_descriptor =
125{
126 .bFunctionLength = sizeof(struct cdc_call_management_descriptor),
127 .bDescriptorType = USB_DT_CS_INTERFACE,
128 .bDescriptorSubtype = SUBTYPE_CALL_MANAGEMENT,
129 .bmCapabilities = 0,
130 .bDataInterface = 0
131};
132
133static struct cdc_acm_descriptor
134 acm_descriptor =
135{
136 .bFunctionLength = sizeof(struct cdc_acm_descriptor),
137 .bDescriptorType = USB_DT_CS_INTERFACE,
138 .bDescriptorSubtype = SUBTYPE_ACM,
139 .bmCapabilities = ACM_CAP_LINE_CODING
140};
141
142static struct cdc_union_descriptor
143 union_descriptor =
144{
145 .bFunctionLength = sizeof(struct cdc_union_descriptor),
146 .bDescriptorType = USB_DT_CS_INTERFACE,
147 .bDescriptorSubtype = SUBTYPE_UNION,
148 .bControlInterface = 0,
149 .bSubordinateInterface0 = 0
150};
151
152static struct usb_interface_descriptor
153 data_interface_descriptor =
34{ 154{
35 .bLength = sizeof(struct usb_interface_descriptor), 155 .bLength = sizeof(struct usb_interface_descriptor),
36 .bDescriptorType = USB_DT_INTERFACE, 156 .bDescriptorType = USB_DT_INTERFACE,
@@ -43,8 +163,7 @@ static struct usb_interface_descriptor __attribute__((aligned(2)))
43 .iInterface = 0 163 .iInterface = 0
44}; 164};
45 165
46 166static struct usb_endpoint_descriptor
47static struct usb_endpoint_descriptor __attribute__((aligned(2)))
48 endpoint_descriptor = 167 endpoint_descriptor =
49{ 168{
50 .bLength = sizeof(struct usb_endpoint_descriptor), 169 .bLength = sizeof(struct usb_endpoint_descriptor),
@@ -55,6 +174,8 @@ static struct usb_endpoint_descriptor __attribute__((aligned(2)))
55 .bInterval = 0 174 .bInterval = 0
56}; 175};
57 176
177static struct cdc_line_coding line_coding;
178
58/* send_buffer: local ring buffer. 179/* send_buffer: local ring buffer.
59 * transit_buffer: used to store aligned data that will be sent by the USB 180 * transit_buffer: used to store aligned data that will be sent by the USB
60 * driver. PP502x needs boost for high speed USB, but still works up to 181 * driver. PP502x needs boost for high speed USB, but still works up to
@@ -78,8 +199,8 @@ static int buffer_length;
78static int buffer_transitlength; 199static int buffer_transitlength;
79static bool active = false; 200static bool active = false;
80 201
81static int ep_in, ep_out; 202static int ep_in, ep_out, ep_int;
82static int usb_interface; 203static int control_interface, data_interface;
83 204
84int usb_serial_request_endpoints(struct usb_class_driver *drv) 205int usb_serial_request_endpoints(struct usb_class_driver *drv)
85{ 206{
@@ -94,25 +215,59 @@ int usb_serial_request_endpoints(struct usb_class_driver *drv)
94 return -1; 215 return -1;
95 } 216 }
96 217
218 /* Optional interrupt endpoint. While the code does not actively use it,
219 * it is needed to get out-of-the-box serial port experience on Windows
220 * and Linux. If this endpoint is not available, only CDC Data interface
221 * will be exported (can still work on Linux with manual modprobe).
222 */
223 ep_int = usb_core_request_endpoint(USB_ENDPOINT_XFER_INT, USB_DIR_IN, drv);
224
97 return 0; 225 return 0;
98} 226}
99 227
100int usb_serial_set_first_interface(int interface) 228int usb_serial_set_first_interface(int interface)
101{ 229{
102 usb_interface = interface; 230 control_interface = interface;
103 return interface + 1; 231 data_interface = interface + 1;
232 return interface + 2;
104} 233}
105 234
106int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size) 235int usb_serial_get_config_descriptor(unsigned char *dest, int max_packet_size)
107{ 236{
108 unsigned char *orig_dest = dest; 237 unsigned char *orig_dest = dest;
109 238
110 interface_descriptor.bInterfaceNumber = usb_interface; 239 association_descriptor.bFirstInterface = control_interface;
111 PACK_DATA(&dest, interface_descriptor); 240 control_interface_descriptor.bInterfaceNumber = control_interface;
241 call_management_descriptor.bDataInterface = data_interface;
242 union_descriptor.bControlInterface = control_interface;
243 union_descriptor.bSubordinateInterface0 = data_interface;
244 data_interface_descriptor.bInterfaceNumber = data_interface;
112 245
113 endpoint_descriptor.wMaxPacketSize = max_packet_size; 246 if (ep_int > 0)
247 {
248 PACK_DATA(&dest, association_descriptor);
249 PACK_DATA(&dest, control_interface_descriptor);
250 PACK_DATA(&dest, header_descriptor);
251 PACK_DATA(&dest, call_management_descriptor);
252 PACK_DATA(&dest, acm_descriptor);
253 PACK_DATA(&dest, union_descriptor);
254
255 /* Notification endpoint. Set wMaxPacketSize to 64 as it is valid
256 * both on Full and High speed. Note that max_packet_size is for bulk.
257 * Maximum bInterval for High Speed is 16 and for Full Speed is 255.
258 */
259 endpoint_descriptor.bEndpointAddress = ep_int;
260 endpoint_descriptor.bmAttributes = USB_ENDPOINT_XFER_INT;
261 endpoint_descriptor.wMaxPacketSize = 64;
262 endpoint_descriptor.bInterval = 16;
263 PACK_DATA(&dest, endpoint_descriptor);
264 }
114 265
266 PACK_DATA(&dest, data_interface_descriptor);
115 endpoint_descriptor.bEndpointAddress = ep_in; 267 endpoint_descriptor.bEndpointAddress = ep_in;
268 endpoint_descriptor.bmAttributes = USB_ENDPOINT_XFER_BULK;
269 endpoint_descriptor.wMaxPacketSize = max_packet_size;
270 endpoint_descriptor.bInterval = 0;
116 PACK_DATA(&dest, endpoint_descriptor); 271 PACK_DATA(&dest, endpoint_descriptor);
117 272
118 endpoint_descriptor.bEndpointAddress = ep_out; 273 endpoint_descriptor.bEndpointAddress = ep_out;
@@ -127,10 +282,47 @@ bool usb_serial_control_request(struct usb_ctrlrequest* req, unsigned char* dest
127 bool handled = false; 282 bool handled = false;
128 283
129 (void)dest; 284 (void)dest;
130 switch (req->bRequest) { 285 if (req->wIndex != control_interface)
131 default: 286 {
132 logf("serial: unhandeld req %d", req->bRequest); 287 return false;
133 } 288 }
289
290 if (req->bRequestType == (USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_INTERFACE))
291 {
292 if (req->bRequest == SET_LINE_CODING)
293 {
294 if (req->wLength == sizeof(line_coding))
295 {
296 /* Receive line coding into local copy */
297 usb_drv_recv(EP_CONTROL, &line_coding, sizeof(line_coding));
298 usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
299 handled = true;
300 }
301 }
302 else if (req->bRequest == SET_CONTROL_LINE_STATE)
303 {
304 if (req->wLength == 0)
305 {
306 /* wValue holds Control Signal Bitmap that is simply ignored here */
307 usb_drv_send(EP_CONTROL, NULL, 0); /* ack */
308 handled = true;
309 }
310 }
311 }
312 else if (req->bRequestType == (USB_DIR_IN|USB_TYPE_CLASS|USB_RECIP_INTERFACE))
313 {
314 if (req->bRequest == GET_LINE_CODING)
315 {
316 if (req->wLength == sizeof(line_coding))
317 {
318 /* Send back line coding so host is happy */
319 usb_drv_recv(EP_CONTROL, NULL, 0); /* ack */
320 usb_drv_send(EP_CONTROL, &line_coding, sizeof(line_coding));
321 handled = true;
322 }
323 }
324 }
325
134 return handled; 326 return handled;
135} 327}
136 328