Last active
October 4, 2022 05:48
-
-
Save projectgus/f31f11282b6d914a0d89a1456abf5337 to your computer and use it in GitHub Desktop.
MicroPython report-only HID debug Proof Of Concept
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import machine | |
from micropython import const | |
_CONTROL_STAGE_IDLE = const(0) | |
_CONTROL_STAGE_SETUP = const(1) | |
_CONTROL_STAGE_DATA = const(2) | |
_CONTROL_STAGE_ACK = const(3) | |
_PACKET_SIZE = const(64) | |
_REQ_GET_DESCRIPTOR = const(0x06) | |
_HID_DESC_TYPE_HID = const(0x21) | |
_HID_DESC_TYPE_REPORT = const(0x22) | |
_HID_DESC_TYPE_PHYSICAL = const(0x23) | |
_HID_REQ_CONTROL_GET_REPORT = const(0x01) | |
_HID_REQ_CONTROL_GET_IDLE = const(0x02) | |
_HID_REQ_CONTROL_GET_PROTOCOL = const(0x03) | |
_HID_REQ_CONTROL_SET_REPORT = const(0x09) | |
_HID_REQ_CONTROL_SET_IDLE = const(0x0A) | |
_HID_REQ_CONTROL_SET_PROTOCOL = const(0x0B) | |
_REQ_TYPE_STANDARD = const(0x00) | |
_REQ_TYPE_CLASS = const(0x01) | |
_REQ_TYPE_VENDOR = (const(0x02),) | |
EP_DIR_IN_MASK = const(0x80) | |
class SimpleHIDDebug: | |
def __init__(self): | |
self._epnum = None # set during enumeration | |
self._strings = None | |
usbd = self._usbd = machine.USBD() | |
usbd.init( | |
descriptor_device_cb=None, # TODO: fix usbd.c so this isn't needed | |
descriptor_config_cb=self.descriptor_config_cb, | |
descriptor_string_cb=self.descriptor_string_cb, | |
open_driver_cb=self.open_driver_cb, | |
control_xfer_cb=self.control_xfer_cb, | |
xfer_cb=self.xfer_cb, | |
) | |
self._protocol_mode = None # HID state | |
self._idle_rate = 1 | |
def reenumerate(self): # hacky convenience method | |
self._usbd.reenumerate() | |
def send_hid_report(self, report_data=b'Debug data!'): | |
assert self._epnum is not None # need to have enumerated | |
return self._usbd.submit_xfer(self._epnum, report_data) | |
def descriptor_config_cb(self): | |
static = self._usbd.static | |
self._epnum = static.ep_max | EP_DIR_IN_MASK | |
self._strings = {static.str_max: "Simple HID"} | |
config = bytearray(static.desc_cfg) # start from the static configuration descriptor | |
# append HID interface descriptor | |
# this is obviously way too hacky for long term use, needs rewriting anyhow once there is a layer to handle multiple drivers | |
# and use ustruct instead of adding to bytearrays | |
config += b"\x09\x04" # bLength, bDescriptorType | |
config += bytes([static.itf_max]) # bInterfaceNumber | |
config += b"\x00\x01\x03\x00\x00" # bAlternateSetting, bNumEndpoints, bInterfaceClass, bInterfaceSubClass, bInterfaceProtocol | |
config += bytes([static.str_max]) # iInterface | |
config += self._get_hid_descriptor() | |
# append HID endpoint descriptor | |
config += b"\x07\x05" # bLength, bDescriptorType | |
config += bytes([self._epnum]) # bEndpointAddress | |
config += bytes(b"\x03") # bmAtttributes, interrupt endpoint | |
config += bytes([_PACKET_SIZE, 0]) # wMaxPacketSize | |
config += b"\x01" # bInterval | |
# go back and change the static config descriptor fields as needed | |
config[2:4] = bytes([len(config), len(config) >> 8]) # wTotalLength | |
config[4] += 1 # bNumInterfaces | |
return config | |
def _get_hid_descriptor(self): | |
desc = bytearray() | |
desc += b"\x09\x21\x11\x01\x00" # bLength, bDescriptorType, bcdHID, bCountryCode | |
desc += b"\x01\x22" # 1 descriptor, report type | |
rlen = len(self._get_hid_report_descriptor()) | |
desc += bytes([rlen, rlen >> 8]) # descriptor total len | |
return desc | |
def _get_hid_report_descriptor(self): | |
desc = bytearray() | |
desc += b'\x06\x31\xFF' # Usage Page 0xFF31 (vendor) | |
desc += b'\x09\x74' # Usage 0x74 | |
desc += b'\xa1\x53' # Collection 0x53 | |
desc += b'\x75\x08' # report size 8 bit | |
desc += b'\x15\x00' # logical minimum 0 | |
desc += b'\x26\xff\x00' # logical maximum 255 | |
desc += bytes([0x95, _PACKET_SIZE]) # report count | |
desc += b'\x09\x75' # Usage 0x75 | |
desc += b'\x81\x02' # Input (array) | |
desc += b'\xc0' # end collection | |
return desc | |
def descriptor_string_cb(self, index): | |
return self._strings.get(index, "Bad Index") | |
def open_driver_cb(self, interface_desc_view): | |
print(f"open_driver_cb {interface_desc_view}") | |
def xfer_cb(self, ep_addr, result, xferred_bytes): | |
print(f"xfer_cb EP {ep_addr} result {result} xferred {xferred_bytes}") | |
return True | |
def control_xfer_cb(self, stage, request): | |
bmRequestType, bRequest, wValue, wIndex, wLength = request | |
request_type = (bmRequestType >> 5) & 3 | |
if request_type == _REQ_TYPE_STANDARD: | |
return self._handle_control_standard(stage, request) | |
elif request_type == _REQ_TYPE_CLASS: | |
return self._handle_control_class(stage, request) | |
print(f"unsupported request {request} stage {stage}") | |
return False | |
def _handle_control_standard(self, stage, request): | |
bmRequestType, bRequest, wValue, wIndex, wLength = request | |
if stage != _CONTROL_STAGE_SETUP: | |
return True # allow standard request DATA/ACK stages to complete normally | |
if bRequest == _REQ_GET_DESCRIPTOR: | |
desc_type = wValue >> 8 | |
if desc_type == _HID_DESC_TYPE_HID: | |
return self._usbd.control_xfer( | |
request, self._get_hid_descriptor() | |
) | |
elif desc_type == _HID_DESC_TYPE_REPORT: | |
return self._usbd.control_xfer( | |
request, self._get_hid_report_descriptor() | |
) | |
print(f"unexpected standard SETUP bRequest {bRequest:#x} {request}") | |
return False # STALL | |
def _handle_control_class(self, stage, request): | |
bmRequestType, bRequest, wValue, wIndex, wLength = request | |
if bRequest == _HID_REQ_CONTROL_GET_REPORT: | |
print("HID_REQ_CONTROL_GET_REPORT unsupported") | |
return False | |
elif bRequest == _HID_REQ_CONTROL_GET_IDLE: | |
if stage == _CONTROL_STAGE_SETUP: | |
return self._usbd.control_xfer(request, | |
bytes([ self._idle_rate ])) | |
elif bRequest == _HID_REQ_CONTROL_GET_PROTOCOL: | |
if stage == _CONTROL_STAGE_SETUP: | |
return self._usbd.control_xfer(request, | |
bytes([ self._protocol_mode ])) | |
elif bRequest == _HID_REQ_CONTROL_SET_REPORT: | |
print("HID_REQ_CONTROL_SET_REPORT unsupported") | |
return False | |
elif bRequest == _HID_REQ_CONTROL_SET_IDLE: | |
if stage == _CONTROL_STAGE_SETUP: | |
self._idle_rate = wValue >> 8 | |
return self._usbd.control_xfer(request, None) | |
elif bRequest == _HID_REQ_CONTROL_SET_PROTOCOL: | |
if stage == _CONTROL_STAGE_SETUP: | |
self._protocol_mode = wValue | |
return self._usbd.control_xfer(request, None) | |
else: | |
print(f"unsupported Class bRequest {bRequest:#x} {request}") | |
return False # STALL | |
return True # fallthrough for other supported handlers at different stages |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment