Skip to content

Commit 87cc34e

Browse files
committed
usb-device-{cdc, hid}: Fix handling of zero length OUT control xfers.
This fix relies on a corresponding change in the low-level USBDevice class. Without the low-level change, HID and CDC devices may not work correctly at all. Zero length OUT control requests were being processed on the device but stalling the endpoint so the host saw a failure (or hang followed by failure). Specifically: - CDC set control line state and send break requests - HID set idle or set protocol This commit also simplifies HID set report logic (tested to still work even when buffer size doesn't match report length). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <[email protected]>
1 parent a215fc4 commit 87cc34e

File tree

2 files changed

+35
-53
lines changed
  • micropython/usb
    • usb-device-cdc/usb/device
    • usb-device-hid/usb/device

2 files changed

+35
-53
lines changed

micropython/usb/usb-device-cdc/usb/device/cdc.py

+23-29
Original file line numberDiff line numberDiff line change
@@ -289,38 +289,32 @@ def on_interface_control_xfer(self, stage, request):
289289
bmRequestType, bRequest, wValue, wIndex, wLength = struct.unpack("BBHHH", request)
290290
recipient, req_type, req_dir = split_bmRequestType(bmRequestType)
291291

292-
# Only for the control interface
293292
if wIndex != self._c_itf:
294-
return False
293+
return False # Only for the control interface (may be redundant check?)
294+
295+
if req_type != _REQ_TYPE_CLASS:
296+
return False # Unsupported request type
295297

296298
if stage == _STAGE_SETUP:
297-
if req_type == _REQ_TYPE_CLASS:
298-
if bRequest == _SET_LINE_CODING_REQ:
299-
if wLength == len(self._line_coding):
300-
return self._line_coding
301-
return False # wrong length
302-
elif bRequest == _GET_LINE_CODING_REQ:
303-
return self._line_coding
304-
elif bRequest == _SET_CONTROL_LINE_STATE:
305-
if wLength == 0:
306-
self._line_state = wValue
307-
if self.line_state_cb:
308-
self.line_state_cb(wValue)
309-
return b""
310-
else:
311-
return False # wrong length
312-
elif bRequest == _SEND_BREAK_REQ:
313-
if self.break_cb:
314-
self.break_cb(wValue)
315-
return b""
316-
317-
if stage == _STAGE_DATA:
318-
if req_type == _REQ_TYPE_CLASS:
319-
if bRequest == _SET_LINE_CODING_REQ:
320-
if self.line_coding_cb:
321-
self.line_coding_cb(self._line_coding)
322-
323-
return True
299+
if bRequest in (_SET_LINE_CODING_REQ, _GET_LINE_CODING_REQ):
300+
return self._line_coding # Buffer to read or write
301+
302+
# Continue on other supported requests, stall otherwise
303+
return bRequest in (_SET_CONTROL_LINE_STATE, _SEND_BREAK_REQ)
304+
305+
if stage == _STAGE_ACK:
306+
if bRequest == _SET_LINE_CODING_REQ:
307+
if self.line_coding_cb:
308+
self.line_coding_cb(self._line_coding)
309+
elif bRequest == _SET_CONTROL_LINE_STATE:
310+
self._line_state = wValue
311+
if self.line_state_cb:
312+
self.line_state_cb(wValue)
313+
elif bRequest == _SEND_BREAK_REQ:
314+
if self.break_cb:
315+
self.break_cb(wValue)
316+
317+
return True # allow DATA/ACK stages to complete normally
324318

325319
def _wr_xfer(self):
326320
# Submit a new data IN transfer from the _wb buffer, if needed

micropython/usb/usb-device-hid/usb/device/hid.py

+12-24
Original file line numberDiff line numberDiff line change
@@ -206,37 +206,25 @@ def on_interface_control_xfer(self, stage, request):
206206
return bytes([self.idle_rate])
207207
if bRequest == _REQ_CONTROL_GET_PROTOCOL:
208208
return bytes([self.protocol])
209+
if bRequest in (_REQ_CONTROL_SET_IDLE, _REQ_CONTROL_SET_PROTOCOL):
210+
return True
211+
if bRequest == _REQ_CONTROL_SET_REPORT:
212+
return self._set_report_buf # If None, request will stall
213+
return False # Unsupported request
214+
215+
if stage == _STAGE_ACK:
216+
if req_type == _REQ_TYPE_CLASS:
209217
if bRequest == _REQ_CONTROL_SET_IDLE:
210218
self.idle_rate = wValue >> 8
211-
return b""
212-
if bRequest == _REQ_CONTROL_SET_PROTOCOL:
219+
elif bRequest == _REQ_CONTROL_SET_PROTOCOL:
213220
self.protocol = wValue
214-
return b""
215-
if bRequest == _REQ_CONTROL_SET_REPORT:
216-
# Return the _set_report_buf to be filled with the
217-
# report data
218-
if not self._set_report_buf:
219-
return False
220-
elif wLength >= len(self._set_report_buf):
221-
# Saves an allocation if the size is exactly right (or will be a short read)
222-
return self._set_report_buf
223-
else:
224-
# Otherwise, need to wrap the buffer in a memoryview of the correct length
225-
#
226-
# TODO: check this is correct, maybe TinyUSB won't mind if we ask for more
227-
# bytes than the host has offered us.
228-
return memoryview(self._set_report_buf)[:wLength]
229-
return False # Unsupported
230-
231-
if stage == _STAGE_DATA:
232-
if req_type == _REQ_TYPE_CLASS:
233-
if bRequest == _REQ_CONTROL_SET_REPORT and self._set_report_buf:
221+
elif bRequest == _REQ_CONTROL_SET_REPORT:
234222
report_id = wValue & 0xFF
235223
report_type = wValue >> 8
236224
report_data = self._set_report_buf
237225
if wLength < len(report_data):
238-
# as above, need to truncate the buffer if we read less
239-
# bytes than what was provided
226+
# need to truncate the response in the callback if we got less bytes
227+
# than allowed for in the buffer
240228
report_data = memoryview(self._set_report_buf)[:wLength]
241229
self.on_set_report(report_data, report_id, report_type)
242230

0 commit comments

Comments
 (0)