Skip to content

Commit 4f2220d

Browse files
committed
usbd: Major refactor to match refactored low-level API.
- Uses the new API, however a number of implementations have changed substantially. - Should be back in sync now. Signed-off-by: Angus Gratton <[email protected]>
1 parent 7a6b8b2 commit 4f2220d

File tree

9 files changed

+401
-468
lines changed

9 files changed

+401
-468
lines changed

micropython/usbd/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from .device import get_usbdevice, USBInterface
21
from .hid import HIDInterface
32
from .midi import MIDIInterface, MIDIUSB
43
from .cdc import CDC
5-
from . import utils
4+
from . import device, utils

micropython/usbd/cdc.py

+112-125
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import struct
99
from micropython import const
1010

11-
from .device import USBInterface, get_usbdevice
11+
from .device import USBInterface, get
1212
from .utils import (
1313
Buffer,
1414
split_bmRequestType,
@@ -87,25 +87,42 @@
8787
_MP_STREAM_POLL_HUP = const(0x10)
8888

8989

90-
class CDC(io.IOBase):
90+
class CDC(io.IOBase, USBInterface):
9191
# USB CDC serial device class, designed to resemble machine.UART
9292
# with some additional methods.
9393
#
94-
# This is a standalone class, instead of a USBInterface subclass, because
95-
# CDC consists of multiple interfaces (CDC control and CDC data) and also
96-
# so it can derive from io.IOBase.
94+
# Relies on multiple inheritance so it can be an io.IOBase for stream
95+
# functions and also a USBInterface (actually an Interface Association
96+
# Descriptor holding two interfaces.)
9797
def __init__(self, **kwargs):
98+
# io.IOBase has no __init__()
99+
USBInterface.__init__(self)
100+
98101
# For CDC to work, the device class must be set to Interface Association
99-
usb_device = get_usbdevice()
102+
# TODO: Fix this hack by adding a callback from _USBDevice.init() that lets
103+
# all the USBInterface instances tweak the device class if needed
104+
usb_device = get()
100105
usb_device.device_class = _DEV_CLASS_MISC
101106
usb_device.device_subclass = 2
102107
usb_device.device_protocol = 1 # Itf association descriptor
103108

104-
self._ctrl = CDCControlInterface()
105-
self._data = CDCDataInterface()
106-
# The data interface *must* be added immediately after the control interface
107-
usb_device.add_interface(self._ctrl)
108-
usb_device.add_interface(self._data)
109+
# Callbacks for particular control changes initiated by the host
110+
self.break_cb = None # Host sent a "break" condition
111+
self.line_state_cb = None
112+
self.line_coding_cb = None
113+
114+
self._line_state = 0 # DTR & RTS
115+
# Set a default line coding of 115200/8N1
116+
self._line_coding = bytearray(b"\x00\xc2\x01\x00\x00\x00\x08")
117+
118+
self._wb = () # Optional write Buffer (IN endpoint), set by CDC.init()
119+
self._rb = () # Optional read Buffer (OUT endpoint), set by CDC.init()
120+
self._timeout = 1000 # set from CDC.init() as well
121+
122+
# one control interface endpoint, two data interface endpoints
123+
self.ep_c_in = self.ep_d_in = self.ep_d_out = None
124+
125+
self._c_itf = None # Number of control interface, data interface is one more
109126

110127
self.init(**kwargs)
111128

@@ -121,7 +138,7 @@ def init(
121138
# code, the USB host sets them.)
122139
struct.pack_into(
123140
"<LBBB",
124-
self._ctrl._line_coding,
141+
self._line_coding,
125142
0,
126143
baudrate,
127144
_STOP_BITS_REPR.index(str(stop)),
@@ -135,44 +152,41 @@ def init(
135152
if not (txbuf and rxbuf):
136153
raise ValueError # Buffer sizes are required
137154

138-
self._data._timeout = timeout
139-
self._data._wb = Buffer(txbuf)
140-
self._data._rb = Buffer(rxbuf)
141-
142-
def is_open(self):
143-
return self._ctrl.is_open()
155+
self._timeout = timeout
156+
self._wb = Buffer(txbuf)
157+
self._rb = Buffer(rxbuf)
144158

145159
###
146160
### Line State & Line Coding State property getters
147161
###
148162

149163
@property
150164
def rts(self):
151-
return bool(self._ctrl._line_state & _LINE_STATE_RTS)
165+
return bool(self._line_state & _LINE_STATE_RTS)
152166

153167
@property
154168
def dtr(self):
155-
return bool(self._ctrl._line_state & _LINE_STATE_DTR)
169+
return bool(self._line_state & _LINE_STATE_DTR)
156170

157171
# Line Coding Representation
158172
# Byte 0-3 Byte 4 Byte 5 Byte 6
159173
# dwDTERate bCharFormat bParityType bDataBits
160174

161175
@property
162176
def baudrate(self):
163-
return struct.unpack("<LBBB", self._ctrl._line_coding)[0]
177+
return struct.unpack("<LBBB", self._line_coding)[0]
164178

165179
@property
166180
def stop_bits(self):
167-
return _STOP_BITS_REPR[self._ctrl._line_coding[4]]
181+
return _STOP_BITS_REPR[self._line_coding[4]]
168182

169183
@property
170184
def parity(self):
171-
return _PARITY_BITS_REPR[self._ctrl._line_coding[5]]
185+
return _PARITY_BITS_REPR[self._line_coding[5]]
172186

173187
@property
174188
def data_bits(self):
175-
return self._ctrl._line_coding[6]
189+
return self._line_coding[6]
176190

177191
def __repr__(self):
178192
return f"{self.baudrate}/{self.data_bits}{self.parity}{self.stop_bits} rts={self.rts} dtr={self.dtr}"
@@ -182,60 +196,24 @@ def __repr__(self):
182196
###
183197

184198
def set_break_cb(self, cb):
185-
self._ctrl.break_cb = cb
199+
self.break_cb = cb
186200

187201
def set_line_state_cb(self, cb):
188-
self._ctrl.line_state_cb = cb
202+
self.line_state_cb = cb
189203

190204
def set_line_coding_cb(self, cb):
191-
self._ctrl.line_coding_cb = cb
205+
self.line_coding_cb = cb
192206

193207
###
194-
### io.IOBase stream implementation
208+
### USB Interface Implementation
195209
###
196210

197-
def read(self, size=-1):
198-
return self._data.read(size)
199-
200-
def readinto(self, b):
201-
return self._data.readinto(b)
202-
203-
def write(self, buf):
204-
return self._data.write(buf)
205-
206-
def flush(self):
207-
# a C implementation of this exists in stream.c, but it's not in io.IOBase
208-
# and can't immediately be called from here (AFAIK)
209-
r = self.ioctl(_MP_STREAM_FLUSH, 0)
210-
if r:
211-
raise OSError(r)
212-
213-
def ioctl(self, req, arg):
214-
return self._data.ioctl(req, arg)
215-
216-
217-
class CDCControlInterface(USBInterface):
218-
# Implements the CDC Control Interface
219-
220-
def __init__(self):
221-
super().__init__()
222-
223-
# Callbacks for particular changes initiated by the host
224-
self.break_cb = None # Host sent a "break" condition
225-
self.line_state_cb = None
226-
self.line_coding_cb = None
227-
228-
self._line_state = 0 # DTR & RTS
229-
# Set a default line coding of 115200/8N1
230-
self._line_coding = bytearray(b"\x00\xc2\x01\x00\x00\x00\x08")
231-
232-
self.ep_in = None # Set when enumeration happens
233-
234-
def descriptor_config_cb(self, desc, itf_num, ep_num):
235-
# CDC needs a Interface Association Descriptor (IAD) connecting the Control & Data interfaces
211+
def descriptor_config(self, desc, itf_num, ep_num, strs):
212+
# CDC needs a Interface Association Descriptor (IAD) wrapping two interfaces: Control & Data interfaces
236213
desc.interface_assoc(itf_num, 2, _INTERFACE_CLASS_CDC, _INTERFACE_SUBCLASS_CDC)
237214

238215
# Now add the Control interface descriptor
216+
self._c_itf = itf_num
239217
desc.interface(itf_num, _CDC_CONTROL_EP_NUM, _INTERFACE_CLASS_CDC, _INTERFACE_SUBCLASS_CDC)
240218

241219
# Append the CDC class-specific interface descriptor
@@ -280,14 +258,46 @@ def descriptor_config_cb(self, desc, itf_num, ep_num):
280258
itf_num + 1, # bSubordinateInterface0 (data class itf number)
281259
)
282260

283-
# Descriptor for a single endpoint
284-
self.ep_in = ep_num | EP_IN_FLAG
285-
desc.endpoint(self.ep_in, "interrupt", 8, 16)
261+
# Single control IN endpoint (currently unused in this implementation)
262+
self.ep_c_in = ep_num | EP_IN_FLAG
263+
desc.endpoint(self.ep_c_in, "interrupt", 8, 16)
264+
265+
# Now add the data interface
266+
desc.interface(
267+
itf_num + 1,
268+
_CDC_DATA_EP_NUM,
269+
_CDC_ITF_DATA_CLASS,
270+
_CDC_ITF_DATA_SUBCLASS,
271+
_CDC_ITF_DATA_PROT,
272+
)
273+
274+
# Two data endpoints, bulk OUT and IN
275+
self.ep_d_out = ep_num + 1
276+
self.ep_d_in = (ep_num + 1) | EP_IN_FLAG
277+
desc.endpoint(self.ep_d_out, "bulk", _BULK_EP_LEN, 0)
278+
desc.endpoint(self.ep_d_in, "bulk", _BULK_EP_LEN, 0)
279+
280+
def num_itfs(self):
281+
return 2
282+
283+
def num_eps(self):
284+
return 2 # total after masking out EP_IN_FLAG
285+
286+
def handle_open(self):
287+
super().handle_open()
288+
# kick off any transfers that may have queued while the device was not open
289+
self._rd_xfer()
290+
self._wr_xfer()
286291

287292
def handle_interface_control_xfer(self, stage, request):
288293
# Handle class-specific interface control transfers
289-
bmRequestType, bRequest, wValue, _, wLength = struct.unpack("BBHHH", request)
294+
bmRequestType, bRequest, wValue, wIndex, wLength = struct.unpack("BBHHH", request)
290295
recipient, req_type, req_dir = split_bmRequestType(bmRequestType)
296+
297+
# Only for the control interface
298+
if wIndex != self._c_itf:
299+
return False
300+
291301
if stage == STAGE_SETUP:
292302
if req_type == REQ_TYPE_CLASS:
293303
if bRequest == _SET_LINE_CODING_REQ:
@@ -317,34 +327,34 @@ def handle_interface_control_xfer(self, stage, request):
317327

318328
return True
319329

330+
def _wr_xfer(self):
331+
# Submit a new data IN transfer from the _wb buffer, if needed
332+
if self.is_open() and not self.xfer_pending(self.ep_d_in) and self._wb.readable():
333+
self.submit_xfer(self.ep_d_in, self._wb.pend_read(), self._wr_cb)
320334

321-
class CDCDataInterface(USBInterface):
322-
# Implements the CDC Data Interface
323-
324-
def __init__(self):
325-
super().__init__()
326-
327-
self._wb = () # Optional write Buffer (IN endpoint), set by CDC.init()
328-
self._rb = () # Optional read Buffer (OUT endpoint), set by CDC.init()
329-
self._timeout = 1000 # set from CDC.init() as well
335+
def _wr_cb(self, ep, res, num_bytes):
336+
# Whenever a data IN transfer ends
337+
if res == 0:
338+
self._wb.finish_read(num_bytes)
339+
self._wr_xfer()
330340

331-
self.ep_in = self.ep_out = None # Set when enumeration happens
341+
def _rd_xfer(self):
342+
# Keep an active data OUT transfer to read data from the host,
343+
# whenever the receive buffer has room for new data
344+
if self.is_open() and not self.xfer_pending(self.ep_d_out) and self._rb.writable():
345+
# Can only submit up to the endpoint length per transaction, otherwise we won't
346+
# get any transfer callback until the full transaction completes.
347+
self.submit_xfer(self.ep_d_out, self._rb.pend_write(_BULK_EP_LEN), self._rd_cb)
332348

333-
def descriptor_config_cb(self, desc, itf_num, ep_num):
334-
# Add the standard interface descriptor
335-
desc.interface(
336-
itf_num,
337-
_CDC_DATA_EP_NUM,
338-
_CDC_ITF_DATA_CLASS,
339-
_CDC_ITF_DATA_SUBCLASS,
340-
_CDC_ITF_DATA_PROT,
341-
)
349+
def _rd_cb(self, ep, res, num_bytes):
350+
# Whenever a data OUT transfer ends
351+
if res == 0:
352+
self._rb.finish_write(num_bytes)
353+
self._rd_xfer()
342354

343-
# Two endpoints, bulk OUT and IN
344-
self.ep_out = ep_num
345-
self.ep_in = ep_num | EP_IN_FLAG
346-
desc.endpoint(self.ep_out, "bulk", _BULK_EP_LEN, 0)
347-
desc.endpoint(self.ep_in, "bulk", _BULK_EP_LEN, 0)
355+
###
356+
### io.IOBase stream implementation
357+
###
348358

349359
def write(self, buf):
350360
# use a memoryview to track how much of 'buf' we've written so far
@@ -365,36 +375,6 @@ def write(self, buf):
365375
if time.ticks_diff(time.ticks_ms(), start) > self._timeout:
366376
return len(buf) - len(mv)
367377

368-
def _wr_xfer(self):
369-
# Submit a new IN transfer from the _wb buffer, if needed
370-
if self.is_open() and not self.xfer_pending(self.ep_in) and self._wb.readable():
371-
self.submit_xfer(self.ep_in, self._wb.pend_read(), self._wr_cb)
372-
373-
def _wr_cb(self, ep, res, num_bytes):
374-
# Whenever an IN transfer ends
375-
if res == 0:
376-
self._wb.finish_read(num_bytes)
377-
self._wr_xfer()
378-
379-
def _rd_xfer(self):
380-
# Keep an active OUT transfer to read data from the host,
381-
# whenever the receive buffer has room for new data
382-
if self.is_open() and not self.xfer_pending(self.ep_out) and self._rb.writable():
383-
# Can only submit up to the endpoint length per transaction, otherwise we won't
384-
# get any transfer callback until the full transaction completes.
385-
self.submit_xfer(self.ep_out, self._rb.pend_write(_BULK_EP_LEN), self._rd_cb)
386-
387-
def _rd_cb(self, ep, res, num_bytes):
388-
if res == 0:
389-
self._rb.finish_write(num_bytes)
390-
self._rd_xfer()
391-
392-
def handle_open(self):
393-
super().handle_open()
394-
# kick off any transfers that may have queued while the device was not open
395-
self._rd_xfer()
396-
self._wr_xfer()
397-
398378
def read(self, size):
399379
start = time.ticks_ms()
400380

@@ -457,3 +437,10 @@ def ioctl(self, req, arg):
457437
return 0
458438

459439
return _MP_EINVAL
440+
441+
def flush(self):
442+
# a C implementation of this exists in stream.c, but it's not in io.IOBase
443+
# and can't immediately be called from here (AFAIK)
444+
r = self.ioctl(_MP_STREAM_FLUSH, 0)
445+
if r:
446+
raise OSError(r)

0 commit comments

Comments
 (0)