Skip to content

Commit 83364c0

Browse files
committed
Merge remote-tracking branch 'upstream/feature/usbd_python' into feature/usbd_python
2 parents 5c51a9e + 2baaf58 commit 83364c0

File tree

5 files changed

+286
-60
lines changed

5 files changed

+286
-60
lines changed

micropython/usbd/device.py

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# MIT license; Copyright (c) 2022 Angus Gratton
33
from micropython import const
44
import machine
5-
import ustruct
5+
import struct
66

77
from .utils import split_bmRequestType, EP_IN_FLAG
88

@@ -78,7 +78,8 @@ def __init__(self):
7878
descriptor_device_cb=self._descriptor_device_cb,
7979
descriptor_config_cb=self._descriptor_config_cb,
8080
descriptor_string_cb=self._descriptor_string_cb,
81-
open_driver_cb=self._open_driver_cb,
81+
open_cb=self._open_cb,
82+
reset_cb=self._reset_cb,
8283
control_xfer_cb=self._control_xfer_cb,
8384
xfer_cb=self._xfer_cb,
8485
)
@@ -118,7 +119,7 @@ def _descriptor_device_cb(self):
118119

119120
FMT = "<BBHBBBBHHHBBBB"
120121
# static descriptor fields
121-
f = ustruct.unpack(FMT, self._usbd.static.desc_device)
122+
f = struct.unpack(FMT, self._usbd.static.desc_device)
122123

123124
def maybe_set(value, idx):
124125
# Override a numeric descriptor value or keep static value f[idx] if 'value' is None
@@ -134,7 +135,7 @@ def maybe_set_str(s, idx):
134135

135136
# Either copy each descriptor field directly from the static device descriptor, or 'maybe'
136137
# override if a custom value has been set on this object
137-
return ustruct.pack(
138+
return struct.pack(
138139
FMT,
139140
f[0], # bLength
140141
f[1], # bDescriptorType
@@ -257,7 +258,7 @@ def _update_configuration_descriptor(self, desc):
257258
bNumInterfaces = self._usbd.static.itf_max if self.include_static else 0
258259
bNumInterfaces += len(self._itfs)
259260

260-
ustruct.pack_into(
261+
struct.pack_into(
261262
"<BBHBBBBB",
262263
desc,
263264
0,
@@ -286,9 +287,37 @@ def _descriptor_string_cb(self, index):
286287
except IndexError:
287288
return None
288289

289-
def _open_driver_cb(self, interface_desc_view):
290-
# Singleton callback from TinyUSB custom class driver
291-
pass
290+
def _open_cb(self, interface_desc_view):
291+
# Singleton callback from TinyUSB custom class driver, when USB host does
292+
# Set Configuration. The "runtime class device" accepts all interfaces that
293+
# it has sent in descriptors, and calls this callback.
294+
295+
# Walk the view of the "claimed" descriptor data provided in the
296+
# callback and call handle_open() on each claimed interface
297+
#
298+
# ... this may be unnecessary at the moment, as only one configuration is supported so we
299+
# can probably assume all the interfaces will be included.
300+
i = 0
301+
while i < len(interface_desc_view):
302+
# descriptor length, type, and index (if it's an interface descriptor)
303+
dl, dt, di = interface_desc_view[i : i + 3]
304+
if dt == _STD_DESC_INTERFACE_TYPE:
305+
if di >= self._usbd.static.itf_max:
306+
di -= self._usbd.static.itf_max
307+
self._itfs[di].handle_open()
308+
i += dl
309+
assert dl
310+
311+
def _reset_cb(self):
312+
# Callback when the USB device is reset by the host
313+
314+
# Cancel outstanding transfer callbacks
315+
for k in self._ep_cbs.keys():
316+
self._ep_cbs[k] = None
317+
318+
# Allow interfaces to respond to the reset
319+
for itf in self._itfs:
320+
itf.handle_reset()
292321

293322
def _submit_xfer(self, ep_addr, data, done_cb=None):
294323
# Singleton function to submit a USB transfer (of any type except control).
@@ -387,6 +416,7 @@ def __init__(
387416
self.bInterfaceSubClass = bInterfaceSubClass
388417
self.bInterfaceProtocol = bInterfaceProtocol
389418
self.interface_str = interface_str
419+
self._open = False
390420

391421
def get_itf_descriptor(self, num_eps, itf_idx, str_idx):
392422
# Return the interface descriptor binary data and associated other
@@ -421,7 +451,7 @@ def get_itf_descriptor(self, num_eps, itf_idx, str_idx):
421451
# (indexes in the descriptor data should start from 'str_idx'.)
422452
#
423453
# See USB 2.0 specification section 9.6.5 p267 for standard interface descriptors.
424-
desc = ustruct.pack(
454+
desc = struct.pack(
425455
"<" + "B" * _STD_DESC_INTERFACE_LEN,
426456
_STD_DESC_INTERFACE_LEN, # bLength
427457
_STD_DESC_INTERFACE_TYPE, # bDescriptorType
@@ -466,6 +496,30 @@ def get_endpoint_descriptors(self, ep_addr, str_idx):
466496
# start from ep_addr, optionally with the utils.EP_IN_FLAG bit set.)
467497
return (b"", [], [])
468498

499+
def handle_open(self):
500+
# Callback called when the USB host accepts the device configuration.
501+
#
502+
# Override this function to initiate any operations that the USB interface
503+
# should do when the USB device is configured to the host.
504+
self._open = True
505+
506+
def handle_reset(self):
507+
# Callback called on every registered interface when the USB device is
508+
# reset by the host. This can happen when the USB device is unplugged,
509+
# or if the host triggers a reset for some other reason.
510+
#
511+
# Override this function to cancel any pending operations specific to
512+
# the interface (outstanding USB transfers are already cancelled).
513+
#
514+
# At this point, no USB functionality is available - handle_open() will
515+
# be called later if/when the USB host re-enumerates and configures the
516+
# interface.
517+
self._open = False
518+
519+
def is_open(self):
520+
# Returns True if the interface is in use
521+
return self._open
522+
469523
def handle_device_control_xfer(self, stage, request):
470524
# Control transfer callback. Override to handle a non-standard device
471525
# control transfer where bmRequestType Recipient is Device, Type is
@@ -486,11 +540,11 @@ def handle_device_control_xfer(self, stage, request):
486540
# The function can call split_bmRequestType() to split bmRequestType into
487541
# (Recipient, Type, Direction).
488542
#
489-
# Result:
543+
# Result, any of:
490544
#
491-
# - True to continue the request False to STALL the endpoint A buffer
492-
# - interface object to provide a buffer to the host as part of the
493-
# - transfer, if possible.
545+
# - True to continue the request, False to STALL the endpoint.
546+
# - Buffer interface object to provide a buffer to the host as part of the
547+
# transfer, if possible.
494548
return False
495549

496550
def handle_interface_control_xfer(self, stage, request):
@@ -512,7 +566,8 @@ def handle_interface_control_xfer(self, stage, request):
512566
def handle_endpoint_control_xfer(self, stage, request):
513567
# Control transfer callback. Override to handle a device
514568
# control transfer where bmRequestType Recipient is Endpoint and
515-
# the lower byte of wIndex indicates an endpoint address associated with this interface.
569+
# the lower byte of wIndex indicates an endpoint address associated
570+
# with this interface.
516571
#
517572
# bmRequestType Type will generally have any value except
518573
# utils.REQ_TYPE_STANDARD, as Standard endpoint requests are handled by
@@ -546,4 +601,25 @@ def submit_xfer(self, ep_addr, data, done_cb=None):
546601
#
547602
# Note that done_cb may be called immediately, possibly before this
548603
# function has returned to the caller.
604+
if not self._open:
605+
raise RuntimeError
549606
return get_usbdevice()._submit_xfer(ep_addr, data, done_cb)
607+
608+
def set_ep_stall(self, ep_addr, stall):
609+
# Set or clear endpoint STALL state, according to the bool "stall" parameter.
610+
#
611+
# Generally endpoint STALL is handled automatically by TinyUSB, but
612+
# there are some device classes that need to explicitly stall or unstall
613+
# an endpoint under certain conditions.
614+
if not self._open or ep_addr not in get_usbdevice()._eps:
615+
raise RuntimeError
616+
get_usbdevice()._usbd.set_ep_stall(ep_addr, stall)
617+
618+
def get_ep_stall(self, ep_addr):
619+
# Get the current endpoint STALL state.
620+
#
621+
# Endpoint can be stalled/unstalled by host, TinyUSB stack, or calls to
622+
# set_ep_stall().
623+
if not self._open or ep_addr not in get_usbdevice()._eps:
624+
raise RuntimeError
625+
return get_usbdevice()._usbd.get_ep_stall(ep_addr)

micropython/usbd/hid.py

Lines changed: 76 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
split_bmRequestType,
99
EP_IN_FLAG,
1010
STAGE_SETUP,
11+
STAGE_DATA,
1112
REQ_TYPE_STANDARD,
1213
REQ_TYPE_CLASS,
1314
)
1415
from micropython import const
15-
import ustruct
16+
import struct
1617

1718
_DESC_HID_TYPE = const(0x21)
1819
_DESC_REPORT_TYPE = const(0x22)
@@ -43,6 +44,7 @@ def __init__(
4344
self,
4445
report_descriptor,
4546
extra_descriptors=[],
47+
set_report_buf=None,
4648
protocol=_INTERFACE_PROTOCOL_NONE,
4749
interface_str=None,
4850
use_out_ep=False,
@@ -58,6 +60,12 @@ def __init__(
5860
# descriptors, to append after the mandatory report descriptor. Most
5961
# HID devices do not use these.
6062
#
63+
# - set_report_buf is an optional writable buffer object (i.e.
64+
# bytearray), where SET_REPORT requests from the host can be
65+
# written. Only necessary if the report_descriptor contains Output
66+
# entries. If set, the size must be at least the size of the largest
67+
# Output entry.
68+
#
6169
# - protocol can be set to a specific value as per HID v1.11 section 4.3 Protocols, p9.
6270
#
6371
# - interface_str is an optional string descriptor to associate with the HID USB interface.
@@ -67,16 +75,23 @@ def __init__(
6775
super().__init__(_INTERFACE_CLASS, _INTERFACE_SUBCLASS_NONE, protocol, interface_str)
6876
self.extra_descriptors = extra_descriptors
6977
self.report_descriptor = report_descriptor
78+
self._set_report_buf = set_report_buf
7079
self._int_ep = None # set during enumeration
7180
self._out_ep = None
7281
self.use_out_ep = use_out_ep
7382

7483
def get_report(self):
7584
return False
7685

77-
def set_report(self):
78-
# Override this if you are expecting reports from the host
79-
return False
86+
def handle_set_report(self, report_data, report_id, report_type):
87+
# Override this function in order to handle SET REPORT requests from the host,
88+
# where it sends data to the HID device.
89+
#
90+
# This function will only be called if the Report descriptor contains at least one Output entry,
91+
# and the set_report_buf argument is provided to the constructor.
92+
#
93+
# Return True to complete the control transfer normally, False to abort it.
94+
return True
8095

8196
def send_report(self, report_data):
8297
# Helper function to send a HID report in the typical USB interrupt
@@ -109,7 +124,7 @@ def get_hid_descriptor(self):
109124
# and optional additional descriptors.
110125
#
111126
# See HID Specification Version 1.1, Section 6.2.1 HID Descriptor p22
112-
result = ustruct.pack(
127+
result = struct.pack(
113128
"<BBHBBBH",
114129
9 + 3 * len(self.extra_descriptors), # bLength
115130
_DESC_HID_TYPE, # bDescriptorType
@@ -125,46 +140,70 @@ def get_hid_descriptor(self):
125140
# support in base class
126141
if self.extra_descriptors:
127142
result += b"".join(
128-
ustruct.pack("<BH", dt, len(dd)) for (dt, dd) in self.extra_descriptors
143+
struct.pack("<BH", dt, len(dd)) for (dt, dd) in self.extra_descriptors
129144
)
130145

131146
return result
132147

133148
def handle_interface_control_xfer(self, stage, request):
134149
# Handle standard and class-specific interface control transfers for HID devices.
135-
bmRequestType, bRequest, wValue, _, _ = request
150+
bmRequestType, bRequest, wValue, _, wLength = request
136151

137152
recipient, req_type, _ = split_bmRequestType(bmRequestType)
138153

139-
if stage != STAGE_SETUP:
140-
return True # allow request DATA/ACK stages to complete normally
141-
142-
if req_type == REQ_TYPE_STANDARD:
143-
# HID Spec p48: 7.1 Standard Requests
144-
if bRequest == _REQ_CONTROL_GET_DESCRIPTOR:
145-
desc_type = wValue >> 8
146-
if desc_type == _DESC_HID_TYPE:
147-
return self.get_hid_descriptor()
148-
if desc_type == _DESC_REPORT_TYPE:
149-
return self.report_descriptor
150-
elif req_type == REQ_TYPE_CLASS:
151-
# HID Spec p50: 7.2 Class-Specific Requests
152-
if bRequest == _REQ_CONTROL_GET_REPORT:
153-
return False # Unsupported for now
154-
if bRequest == _REQ_CONTROL_GET_IDLE:
155-
return bytes([self.idle_rate])
156-
if bRequest == _REQ_CONTROL_GET_PROTOCOL:
157-
return bytes([self.protocol])
158-
if bRequest == _REQ_CONTROL_SET_IDLE:
159-
self.idle_rate = wValue >> 8
160-
return b""
161-
if bRequest == _REQ_CONTROL_SET_PROTOCOL:
162-
self.protocol = wValue
163-
return b""
164-
if bRequest == _REQ_CONTROL_SET_REPORT:
165-
return self.set_report()
166-
167-
return False # Unsupported
154+
if stage == STAGE_SETUP:
155+
if req_type == REQ_TYPE_STANDARD:
156+
# HID Spec p48: 7.1 Standard Requests
157+
if bRequest == _REQ_CONTROL_GET_DESCRIPTOR:
158+
desc_type = wValue >> 8
159+
if desc_type == _DESC_HID_TYPE:
160+
return self.get_hid_descriptor()
161+
if desc_type == _DESC_REPORT_TYPE:
162+
return self.report_descriptor
163+
elif req_type == REQ_TYPE_CLASS:
164+
# HID Spec p50: 7.2 Class-Specific Requests
165+
if bRequest == _REQ_CONTROL_GET_REPORT:
166+
print("GET_REPORT?")
167+
return False # Unsupported for now
168+
if bRequest == _REQ_CONTROL_GET_IDLE:
169+
return bytes([self.idle_rate])
170+
if bRequest == _REQ_CONTROL_GET_PROTOCOL:
171+
return bytes([self.protocol])
172+
if bRequest == _REQ_CONTROL_SET_IDLE:
173+
self.idle_rate = wValue >> 8
174+
return b""
175+
if bRequest == _REQ_CONTROL_SET_PROTOCOL:
176+
self.protocol = wValue
177+
return b""
178+
if bRequest == _REQ_CONTROL_SET_REPORT:
179+
# Return the _set_report_buf to be filled with the
180+
# report data
181+
if not self._set_report_buf:
182+
return False
183+
elif wLength >= len(self._set_report_buf):
184+
# Saves an allocation if the size is exactly right (or will be a short read)
185+
return self._set_report_buf
186+
else:
187+
# Otherwise, need to wrap the buffer in a memoryview of the correct length
188+
#
189+
# TODO: check this is correct, maybe TinyUSB won't mind if we ask for more
190+
# bytes than the host has offered us.
191+
return memoryview(self._set_report_buf)[:wLength]
192+
return False # Unsupported
193+
194+
if stage == STAGE_DATA:
195+
if req_type == REQ_TYPE_CLASS:
196+
if bRequest == _REQ_CONTROL_SET_REPORT and self._set_report_buf:
197+
report_id = wValue & 0xFF
198+
report_type = wValue >> 8
199+
report_data = self._set_report_buf
200+
if wLength < len(report_data):
201+
# as above, need to truncate the buffer if we read less
202+
# bytes than what was provided
203+
report_data = memoryview(self._set_report_buf)[:wLength]
204+
self.handle_set_report(report_data, report_id, report_type)
205+
206+
return True # allow DATA/ACK stages to complete normally
168207

169208

170209
# Basic 3-button mouse HID Report Descriptor.
@@ -254,7 +293,7 @@ def send_report(self, dx=0, dy=0):
254293
# transfer after it's submitted. So reusing a bytearray() creates a risk
255294
# of a race condition if a new report transfer is submitted using the
256295
# same buffer, before the previous one has completed.
257-
report = ustruct.pack("Bbb", b, dx, dy)
296+
report = struct.pack("Bbb", b, dx, dy)
258297

259298
super().send_report(report)
260299

0 commit comments

Comments
 (0)