diff --git a/tools/gen_separated_usb_descriptors.py b/tools/gen_separated_usb_descriptors.py new file mode 100644 index 0000000000..1fad2fbeb3 --- /dev/null +++ b/tools/gen_separated_usb_descriptors.py @@ -0,0 +1,1064 @@ +# SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors) +# +# SPDX-License-Identifier: MIT + +import argparse + +import os +import sys + +sys.path.append("../../tools/usb_descriptor") + +from adafruit_usb_descriptor import audio, audio10, cdc, hid, midi, msc, standard, util +import hid_report_descriptors + +DEFAULT_INTERFACE_NAME = "CircuitPython" +ALL_DEVICES = "CDC CDC2 MSC AUDIO HID VENDOR" +ALL_DEVICES_SET = frozenset(ALL_DEVICES.split()) +DEFAULT_DEVICES = "CDC MSC AUDIO HID" + +# This list is in preferred order. MacOS does not like GAMEPAD coming before MOUSE. +ALL_HID_DEVICES = ( + "KEYBOARD MOUSE CONSUMER SYS_CONTROL GAMEPAD DIGITIZER XAC_COMPATIBLE_GAMEPAD RAW" +) +ALL_HID_DEVICES_ORDER = dict((name, idx) for (idx, name) in enumerate(ALL_HID_DEVICES.split())) +ALL_HID_DEVICES_SET = frozenset(ALL_HID_DEVICES.split()) +# Digitizer works on Linux but conflicts with mouse, so omit it. +DEFAULT_HID_DEVICES = "KEYBOARD MOUSE CONSUMER GAMEPAD" + +# In the following URL, don't include the https:// because that prefix gets added automatically +DEFAULT_WEBUSB_URL = "circuitpython.org" # In the future, this may become a specific landing page + +parser = argparse.ArgumentParser(description="Generate USB descriptors.") +parser.add_argument( + "--highspeed", default=False, action="store_true", help="descriptor for highspeed device" +) +parser.add_argument("--manufacturer", type=str, help="manufacturer of the device") +parser.add_argument("--product", type=str, help="product name of the device") +parser.add_argument("--vid", type=lambda x: int(x, 16), help="vendor id") +parser.add_argument("--pid", type=lambda x: int(x, 16), help="product id") +parser.add_argument( + "--serial_number_length", + type=int, + default=32, + help="length needed for the serial number in digits", +) +parser.add_argument( + "--devices", + type=lambda l: tuple(l.split()), + default=DEFAULT_DEVICES, + help="devices to include in descriptor (AUDIO includes MIDI support)", +) +parser.add_argument( + "--hid_devices", + type=lambda l: tuple(l.split()), + default=DEFAULT_HID_DEVICES, + help="HID devices to include in HID report descriptor", +) +parser.add_argument( + "--interface_name", + type=str, + help="The name/prefix to use in the interface descriptions", + default=DEFAULT_INTERFACE_NAME, +) +parser.add_argument( + "--no-renumber_endpoints", + dest="renumber_endpoints", + action="store_false", + help="use to not renumber endpoint", +) +parser.add_argument( + "--cdc_ep_num_notification", type=int, default=0, help="endpoint number of CDC NOTIFICATION" +) +parser.add_argument( + "--cdc2_ep_num_notification", type=int, default=0, help="endpoint number of CDC2 NOTIFICATION" +) +parser.add_argument( + "--cdc_ep_num_data_out", type=int, default=0, help="endpoint number of CDC DATA OUT" +) +parser.add_argument( + "--cdc_ep_num_data_in", type=int, default=0, help="endpoint number of CDC DATA IN" +) +parser.add_argument( + "--cdc2_ep_num_data_out", type=int, default=0, help="endpoint number of CDC2 DATA OUT" +) +parser.add_argument( + "--cdc2_ep_num_data_in", type=int, default=0, help="endpoint number of CDC2 DATA IN" +) +parser.add_argument("--msc_ep_num_out", type=int, default=0, help="endpoint number of MSC OUT") +parser.add_argument("--msc_ep_num_in", type=int, default=0, help="endpoint number of MSC IN") +parser.add_argument("--hid_ep_num_out", type=int, default=0, help="endpoint number of HID OUT") +parser.add_argument("--hid_ep_num_in", type=int, default=0, help="endpoint number of HID IN") +parser.add_argument("--midi_ep_num_out", type=int, default=0, help="endpoint number of MIDI OUT") +parser.add_argument("--midi_ep_num_in", type=int, default=0, help="endpoint number of MIDI IN") +parser.add_argument("--max_ep", type=int, default=0, help="total number of endpoints available") +parser.add_argument( + "--webusb_url", + type=str, + help="The URL to include in the WebUSB URL Descriptor", + default=DEFAULT_WEBUSB_URL, +) +parser.add_argument( + "--vendor_ep_num_out", type=int, default=0, help="endpoint number of VENDOR OUT" +) +parser.add_argument("--vendor_ep_num_in", type=int, default=0, help="endpoint number of VENDOR IN") +parser.add_argument( + "--output_c_file", type=argparse.FileType("w", encoding="UTF-8"), required=True +) +parser.add_argument( + "--output_h_file", type=argparse.FileType("w", encoding="UTF-8"), required=True +) + +args = parser.parse_args() + +unknown_devices = list(frozenset(args.devices) - ALL_DEVICES_SET) +if unknown_devices: + raise ValueError("Unknown device(s)", unknown_devices) + +unknown_hid_devices = list(frozenset(args.hid_devices) - ALL_HID_DEVICES_SET) +if unknown_hid_devices: + raise ValueError("Unknown HID devices(s)", unknown_hid_devices) + +include_cdc = "CDC" in args.devices +include_cdc2 = "CDC2" in args.devices +include_msc = "MSC" in args.devices +include_hid = "HID" in args.devices +include_audio = "AUDIO" in args.devices +include_vendor = "VENDOR" in args.devices + +if not include_cdc and include_cdc2: + raise ValueError("CDC2 requested without CDC") + +if not args.renumber_endpoints: + if include_cdc: + if args.cdc_ep_num_notification == 0: + raise ValueError("CDC notification endpoint number must not be 0") + if args.cdc_ep_num_data_out == 0: + raise ValueError("CDC data OUT endpoint number must not be 0") + if args.cdc_ep_num_data_in == 0: + raise ValueError("CDC data IN endpoint number must not be 0") + + if include_cdc2: + if args.cdc2_ep_num_notification == 0: + raise ValueError("CDC2 notification endpoint number must not be 0") + if args.cdc2_ep_num_data_out == 0: + raise ValueError("CDC2 data OUT endpoint number must not be 0") + if args.cdc2_ep_num_data_in == 0: + raise ValueError("CDC2 data IN endpoint number must not be 0") + + if include_msc: + if args.msc_ep_num_out == 0: + raise ValueError("MSC endpoint OUT number must not be 0") + if args.msc_ep_num_in == 0: + raise ValueError("MSC endpoint IN number must not be 0") + + if include_hid: + if args.args.hid_ep_num_out == 0: + raise ValueError("HID endpoint OUT number must not be 0") + if args.hid_ep_num_in == 0: + raise ValueError("HID endpoint IN number must not be 0") + + if include_audio: + if args.args.midi_ep_num_out == 0: + raise ValueError("MIDI endpoint OUT number must not be 0") + if args.midi_ep_num_in == 0: + raise ValueError("MIDI endpoint IN number must not be 0") + + if include_vendor: + if args.vendor_ep_num_out == 0: + raise ValueError("VENDOR endpoint OUT number must not be 0") + if args.vendor_ep_num_in == 0: + raise ValueError("VENDOR endpoint IN number must not be 0") + + +class StringIndex: + """Assign a monotonically increasing index to each unique string. Start with 0.""" + + string_to_index = {} + index_to_variable = {} + strings = [] + + @classmethod + def index(cls, string, *, variable_name=None): + if string in cls.string_to_index: + idx = cls.string_to_index[string] + if not cls.index_to_variable[idx]: + cls.index_to_variable[idx] = variable_name + return idx + else: + idx = len(cls.strings) + cls.string_to_index[string] = idx + cls.strings.append(string) + cls.index_to_variable[idx] = variable_name + return idx + + @classmethod + def strings_in_order(cls): + return cls.strings + + +# langid must be the 0th string descriptor +LANGID_INDEX = StringIndex.index("\u0409", variable_name="language_id") +assert LANGID_INDEX == 0 +SERIAL_NUMBER_INDEX = StringIndex.index( + "S" * args.serial_number_length, variable_name="usb_serial_number" +) + +device = standard.DeviceDescriptor( + description="top", + idVendor=args.vid, + idProduct=args.pid, + iManufacturer=StringIndex.index(args.manufacturer), + iProduct=StringIndex.index(args.product), + iSerialNumber=SERIAL_NUMBER_INDEX, +) + +# Interface numbers are interface-set local and endpoints are interface local +# until util.join_interfaces renumbers them. + + +def make_cdc_union(name): + return cdc.Union( + description="{} comm".format(name), + # Set bMasterInterface and bSlaveInterface_list to proper values after interfaces are renumbered. + bMasterInterface=0x00, + bSlaveInterface_list=[0x01], + ) + + +def make_cdc_call_management(name): + # Set bDataInterface to proper value after interfaces are renumbered. + return cdc.CallManagement( + description="{} comm".format(name), bmCapabilities=0x01, bDataInterface=0x01 + ) + + +def make_cdc_comm_interface(name, cdc_union, cdc_call_management, cdc_ep_num_notification): + return standard.InterfaceDescriptor( + description="{} comm".format(name), + bInterfaceClass=cdc.CDC_CLASS_COMM, # Communications Device Class + bInterfaceSubClass=cdc.CDC_SUBCLASS_ACM, # Abstract control model + bInterfaceProtocol=cdc.CDC_PROTOCOL_NONE, + iInterface=StringIndex.index("{} {} control".format(args.interface_name, name)), + subdescriptors=[ + cdc.Header(description="{} comm".format(name), bcdCDC=0x0110), + cdc_call_management, + cdc.AbstractControlManagement(description="{} comm".format(name), bmCapabilities=0x02), + cdc_union, + standard.EndpointDescriptor( + description="{} comm in".format(name), + bEndpointAddress=cdc_ep_num_notification + | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT, + wMaxPacketSize=0x0040, + bInterval=0x10, + ), + ], + ) + + +def make_cdc_data_interface(name, cdc_ep_num_data_in, cdc_ep_num_data_out): + return standard.InterfaceDescriptor( + description="{} data".format(name), + bInterfaceClass=cdc.CDC_CLASS_DATA, + iInterface=StringIndex.index("{} {} data".format(args.interface_name, name)), + subdescriptors=[ + standard.EndpointDescriptor( + description="{} data out".format(name), + bEndpointAddress=cdc_ep_num_data_out | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0, + wMaxPacketSize=512 if args.highspeed else 64, + ), + standard.EndpointDescriptor( + description="{} data in".format(name), + bEndpointAddress=cdc_ep_num_data_in | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0, + wMaxPacketSize=512 if args.highspeed else 64, + ), + ], + ) + + +if include_cdc: + cdc_union = make_cdc_union("CDC") + cdc_call_management = make_cdc_call_management("CDC") + cdc_comm_interface = make_cdc_comm_interface( + "CDC", cdc_union, cdc_call_management, args.cdc_ep_num_notification + ) + cdc_data_interface = make_cdc_data_interface( + "CDC", args.cdc_ep_num_data_in, args.cdc_ep_num_data_out + ) + + cdc_interfaces = [cdc_comm_interface, cdc_data_interface] + +if include_cdc2: + cdc2_union = make_cdc_union("CDC2") + cdc2_call_management = make_cdc_call_management("CDC2") + cdc2_comm_interface = make_cdc_comm_interface( + "CDC2", cdc2_union, cdc2_call_management, args.cdc2_ep_num_notification + ) + cdc2_data_interface = make_cdc_data_interface( + "CDC2", args.cdc2_ep_num_data_in, args.cdc2_ep_num_data_out + ) + + cdc2_interfaces = [cdc2_comm_interface, cdc2_data_interface] + +if include_msc: + msc_interfaces = [ + standard.InterfaceDescriptor( + description="MSC", + bInterfaceClass=msc.MSC_CLASS, + bInterfaceSubClass=msc.MSC_SUBCLASS_TRANSPARENT, + bInterfaceProtocol=msc.MSC_PROTOCOL_BULK, + iInterface=StringIndex.index("{} Mass Storage".format(args.interface_name)), + subdescriptors=[ + standard.EndpointDescriptor( + description="MSC in", + bEndpointAddress=args.msc_ep_num_in | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0, + wMaxPacketSize=512 if args.highspeed else 64, + ), + standard.EndpointDescriptor( + description="MSC out", + bEndpointAddress=( + args.msc_ep_num_out | standard.EndpointDescriptor.DIRECTION_OUT + ), + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0, + wMaxPacketSize=512 if args.highspeed else 64, + ), + ], + ) + ] + + +if include_hid: + # When there's only one hid_device, it shouldn't have a report id. + # Otherwise, report ids are assigned sequentially: + # args.hid_devices[0] has report_id 1 + # args.hid_devices[1] has report_id 2 + # etc. + + report_ids = {} + + if len(args.hid_devices) == 1: + name = args.hid_devices[0] + combined_hid_report_descriptor = hid.ReportDescriptor( + description=name, + report_descriptor=bytes(hid_report_descriptors.REPORT_DESCRIPTOR_FUNCTIONS[name](0)), + ) + report_ids[name] = 0 + else: + report_id = 1 + concatenated_descriptors = bytearray() + # Sort HID devices by preferred order. + for name in sorted(args.hid_devices, key=ALL_HID_DEVICES_ORDER.get): + concatenated_descriptors.extend( + bytes(hid_report_descriptors.REPORT_DESCRIPTOR_FUNCTIONS[name](report_id)) + ) + report_ids[name] = report_id + report_id += 1 + combined_hid_report_descriptor = hid.ReportDescriptor( + description="MULTIDEVICE", report_descriptor=bytes(concatenated_descriptors) + ) + + # ASF4 expects keyboard and generic devices to have both in and out endpoints, + # and will fail (possibly silently) if both are not supplied. + hid_endpoint_in_descriptor = standard.EndpointDescriptor( + description="HID in", + bEndpointAddress=args.hid_ep_num_in | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT, + bInterval=8, + ) + + hid_endpoint_out_descriptor = standard.EndpointDescriptor( + description="HID out", + bEndpointAddress=args.hid_ep_num_out | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT, + bInterval=8, + ) + + hid_interfaces = [ + standard.InterfaceDescriptor( + description="HID Multiple Devices", + bInterfaceClass=hid.HID_CLASS, + bInterfaceSubClass=hid.HID_SUBCLASS_NOBOOT, + bInterfaceProtocol=hid.HID_PROTOCOL_NONE, + iInterface=StringIndex.index("{} HID".format(args.interface_name)), + subdescriptors=[ + hid.HIDDescriptor( + description="HID", wDescriptorLength=len(bytes(combined_hid_report_descriptor)) + ), + hid_endpoint_in_descriptor, + hid_endpoint_out_descriptor, + ], + ) + ] + +if include_audio: + # Audio! + # In and out here are relative to CircuitPython + + # USB OUT -> midi_in_jack_emb -> midi_out_jack_ext -> CircuitPython + midi_in_jack_emb = midi.InJackDescriptor( + description="MIDI PC -> {}".format(args.interface_name), + bJackType=midi.JACK_TYPE_EMBEDDED, + iJack=StringIndex.index("{} usb_midi.ports[0]".format(args.interface_name)), + ) + midi_out_jack_ext = midi.OutJackDescriptor( + description="MIDI data out to user code.", + bJackType=midi.JACK_TYPE_EXTERNAL, + input_pins=[(midi_in_jack_emb, 1)], + iJack=0, + ) + + # USB IN <- midi_out_jack_emb <- midi_in_jack_ext <- CircuitPython + midi_in_jack_ext = midi.InJackDescriptor( + description="MIDI data in from user code.", bJackType=midi.JACK_TYPE_EXTERNAL, iJack=0 + ) + midi_out_jack_emb = midi.OutJackDescriptor( + description="MIDI PC <- {}".format(args.interface_name), + bJackType=midi.JACK_TYPE_EMBEDDED, + input_pins=[(midi_in_jack_ext, 1)], + iJack=StringIndex.index("{} usb_midi.ports[1]".format(args.interface_name)), + ) + + audio_midi_interface = standard.InterfaceDescriptor( + description="Midi goodness", + bInterfaceClass=audio.AUDIO_CLASS_DEVICE, + bInterfaceSubClass=audio.AUDIO_SUBCLASS_MIDI_STREAMING, + bInterfaceProtocol=audio.AUDIO_PROTOCOL_V1, + iInterface=StringIndex.index("{} MIDI".format(args.interface_name)), + subdescriptors=[ + midi.Header( + jacks_and_elements=[ + midi_in_jack_emb, + midi_in_jack_ext, + midi_out_jack_emb, + midi_out_jack_ext, + ] + ), + standard.EndpointDescriptor( + description="MIDI data out to {}".format(args.interface_name), + bEndpointAddress=args.midi_ep_num_out | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0, + wMaxPacketSize=512 if args.highspeed else 64, + ), + midi.DataEndpointDescriptor(baAssocJack=[midi_in_jack_emb]), + standard.EndpointDescriptor( + description="MIDI data in from {}".format(args.interface_name), + bEndpointAddress=args.midi_ep_num_in | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0x0, + wMaxPacketSize=512 if args.highspeed else 64, + ), + midi.DataEndpointDescriptor(baAssocJack=[midi_out_jack_emb]), + ], + ) + + cs_ac_interface = audio10.AudioControlInterface( + description="Empty audio control", + audio_streaming_interfaces=[], + midi_streaming_interfaces=[audio_midi_interface], + ) + + audio_control_interface = standard.InterfaceDescriptor( + description="All the audio", + bInterfaceClass=audio.AUDIO_CLASS_DEVICE, + bInterfaceSubClass=audio.AUDIO_SUBCLASS_CONTROL, + bInterfaceProtocol=audio.AUDIO_PROTOCOL_V1, + iInterface=StringIndex.index("{} Audio".format(args.interface_name)), + subdescriptors=[cs_ac_interface], + ) + + # Audio streaming interfaces must occur before MIDI ones. + audio_interfaces = ( + [audio_control_interface] + + cs_ac_interface.audio_streaming_interfaces + + cs_ac_interface.midi_streaming_interfaces + ) + +if include_vendor: + # Vendor-specific interface, for example WebUSB + vendor_endpoint_in_descriptor = standard.EndpointDescriptor( + description="VENDOR in", + bEndpointAddress=args.vendor_ep_num_in | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=16, + ) + + vendor_endpoint_out_descriptor = standard.EndpointDescriptor( + description="VENDOR out", + bEndpointAddress=args.vendor_ep_num_out | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=16, + ) + + vendor_interface = standard.InterfaceDescriptor( + description="VENDOR", + bInterfaceClass=0xFF, # Vendor-specific + bInterfaceSubClass=0x00, + bInterfaceProtocol=0x00, + iInterface=StringIndex.index("{} VENDOR".format(args.interface_name)), + subdescriptors=[vendor_endpoint_in_descriptor, vendor_endpoint_out_descriptor], + ) + + vendor_interfaces = [vendor_interface] + +interfaces_to_join = [] + +if include_cdc: + interfaces_to_join.append(cdc_interfaces) + +if include_cdc2: + interfaces_to_join.append(cdc2_interfaces) + +if include_msc: + interfaces_to_join.append(msc_interfaces) + +if include_hid: + interfaces_to_join.append(hid_interfaces) + +if include_audio: + interfaces_to_join.append(audio_interfaces) + +if include_vendor: + interfaces_to_join.append(vendor_interfaces) + +# util.join_interfaces() will renumber the endpoints to make them unique across descriptors, +# and renumber the interfaces in order. But we still need to fix up certain +# interface cross-references. +interfaces = util.join_interfaces(interfaces_to_join, renumber_endpoints=args.renumber_endpoints) + +if args.max_ep != 0: + for interface in interfaces: + for subdescriptor in interface.subdescriptors: + endpoint_address = getattr(subdescriptor, "bEndpointAddress", 0) & 0x7F + if endpoint_address >= args.max_ep: + raise ValueError( + "Endpoint address %d of '%s' must be less than %d; you have probably run out of endpoints" + % (endpoint_address & 0x7F, interface.description, args.max_ep) + ) +else: + print("Unable to check whether maximum number of endpoints is respected", file=sys.stderr) + +# Now adjust the CDC interface cross-references. + +if include_cdc: + cdc_union.bMasterInterface = cdc_comm_interface.bInterfaceNumber + cdc_union.bSlaveInterface_list = [cdc_data_interface.bInterfaceNumber] + + cdc_call_management.bDataInterface = cdc_data_interface.bInterfaceNumber + +if include_cdc2: + cdc2_union.bMasterInterface = cdc2_comm_interface.bInterfaceNumber + cdc2_union.bSlaveInterface_list = [cdc2_data_interface.bInterfaceNumber] + + cdc2_call_management.bDataInterface = cdc2_data_interface.bInterfaceNumber + + +def make_cdc_iad(cdc_comm_interface, name): + return standard.InterfaceAssociationDescriptor( + description="{} IAD".format(name), + bFirstInterface=cdc_comm_interface.bInterfaceNumber, + bInterfaceCount=len(cdc_interfaces), + bFunctionClass=cdc.CDC_CLASS_COMM, # Communications Device Class + bFunctionSubClass=cdc.CDC_SUBCLASS_ACM, # Abstract control model + bFunctionProtocol=cdc.CDC_PROTOCOL_NONE, + ) + + +if include_cdc: + cdc_iad = make_cdc_iad(cdc_comm_interface, "CDC") +if include_cdc2: + cdc2_iad = make_cdc_iad(cdc2_comm_interface, "CDC2") + +descriptor_list = [] + +if include_cdc: + # Put the CDC IAD just before the CDC interfaces. + # There appears to be a bug in the Windows composite USB driver that requests the + # HID report descriptor with the wrong interface number if the HID interface is not given + # first. However, it still fetches the descriptor anyway. We could reorder the interfaces but + # the Windows 7 Adafruit_usbser.inf file thinks CDC is at Interface 0, so we'll leave it + # there for backwards compatibility. + descriptor_list.append(cdc_iad) + descriptor_list.extend(cdc_interfaces) + +if include_cdc2: + descriptor_list.append(cdc2_iad) + descriptor_list.extend(cdc2_interfaces) + +if include_msc: + descriptor_list.extend(msc_interfaces) + +if include_hid: + descriptor_list.extend(hid_interfaces) + +if include_audio: + # Only add the control interface because other audio interfaces are managed by it to ensure the + # correct ordering. + descriptor_list.append(audio_control_interface) + +if include_vendor: + descriptor_list.extend(vendor_interfaces) + +# Finally, build the composite descriptor. + +configuration = standard.ConfigurationDescriptor( + description="Composite configuration", + wTotalLength=( + standard.ConfigurationDescriptor.bLength + sum([len(bytes(x)) for x in descriptor_list]) + ), + bNumInterfaces=len(interfaces), + # bus powered (bit 6), remote wakeup (bit 5), + # bit 7 is always 1 and 0-4 are always 0 + # Turn off remote wakeup until we handle it in CircuitPython. + bmAttributes=0x80, + +) +descriptor_list.insert(0, configuration) + +string_descriptors = [ + standard.StringDescriptor(string) for string in StringIndex.strings_in_order() +] +serial_number_descriptor = string_descriptors[SERIAL_NUMBER_INDEX] + +c_file = args.output_c_file +h_file = args.output_h_file + + +c_file.write( + """\ +#include + +#include "tusb.h" +#include "py/objtuple.h" +#include "shared-bindings/usb_hid/Device.h" +#include "{H_FILE_NAME}" + +""".format( + H_FILE_NAME=h_file.name + ) +) + +c_file.write( + """\ +// {DESCRIPTION} : {CLASS} +""".format( + DESCRIPTION=device.description, CLASS=device.__class__ + ) +) + +c_file.write( + """\ +const uint8_t usb_desc_dev[] = { +""" +) +for b in bytes(device): + c_file.write("0x{:02x}, ".format(b)) + +c_file.write( + """\ +}; +""" +) + +c_file.write( + """\ +const uint8_t usb_desc_cfg[] = { +""" +) + +# Write out all the regular descriptors as one long array (that's how ASF4 does it). +descriptor_length = 0 +for descriptor in descriptor_list: + c_file.write( + """\ +// {DESCRIPTION} : {CLASS} +""".format( + DESCRIPTION=descriptor.description, CLASS=descriptor.__class__ + ) + ) + + b = bytes(descriptor) + notes = descriptor.notes() + i = 0 + + # This prints each subdescriptor on a separate line. + n = 0 + while i < len(b): + length = b[i] + for j in range(length): + c_file.write("0x{:02x}, ".format(b[i + j])) + c_file.write("// " + notes[n]) + n += 1 + c_file.write("\n") + i += length + descriptor_length += len(b) + +c_file.write( + """\ +}; +""" +) + +pointers_to_strings = [] + +for idx, descriptor in enumerate(string_descriptors): + c_file.write( + """\ +// {DESCRIPTION} : {CLASS} +""".format( + DESCRIPTION=descriptor.description, CLASS=descriptor.__class__ + ) + ) + + b = bytes(descriptor) + notes = descriptor.notes() + i = 0 + + # This prints each subdescriptor on a separate line. + variable_name = StringIndex.index_to_variable[idx] + if not variable_name: + variable_name = "string_descriptor{}".format(idx) + pointers_to_strings.append("{name}".format(name=variable_name)) + + const = "const " + if variable_name == "usb_serial_number": + length = len(b) + c_file.write( + " uint16_t {NAME}[{length}];\n".format(NAME=variable_name, length=length // 2) + ) + else: + c_file.write( + """\ + const uint16_t {NAME}[] = {{ + """.format( + const=const, NAME=variable_name + ) + ) + n = 0 + while i < len(b): + length = b[i] + for j in range(length // 2): + c_file.write("0x{:04x}, ".format(b[i + 2 * j + 1] << 8 | b[i + 2 * j])) + n += 1 + c_file.write("\n") + i += length + c_file.write( + """\ + }; + """ + ) + +c_file.write( + """\ +// array of pointer to string descriptors +uint16_t const * const string_desc_arr [] = +{ +""" +) +c_file.write( + """,\ + +""".join( + pointers_to_strings + ) +) + +c_file.write( + """ +}; +""" +) + +c_file.write("\n") + +if include_hid: + hid_descriptor_length = len(bytes(combined_hid_report_descriptor)) +else: + hid_descriptor_length = 0 + +# Now the values we need for the .h file. +h_file.write( + """\ +#ifndef MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H +#define MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H + +#include + +extern const uint8_t usb_desc_dev[{device_length}]; +extern const uint8_t usb_desc_cfg[{configuration_length}]; +extern uint16_t usb_serial_number[{serial_number_length}]; +extern uint16_t const * const string_desc_arr [{string_descriptor_length}]; + +#define CFG_TUSB_RHPORT0_MODE ({rhport0_mode}) + +// Vendor name included in Inquiry response, max 8 bytes +#define CFG_TUD_MSC_VENDOR "{msc_vendor}" + +// Product name included in Inquiry response, max 16 bytes +#define CFG_TUD_MSC_PRODUCT "{msc_product}" + +""".format( + serial_number_length=len(bytes(serial_number_descriptor)) // 2, + device_length=len(bytes(device)), + configuration_length=descriptor_length, + max_configuration_length=max(hid_descriptor_length, descriptor_length), + string_descriptor_length=len(pointers_to_strings), + rhport0_mode="OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED" + if args.highspeed + else "OPT_MODE_DEVICE", + msc_vendor=args.manufacturer[:8], + msc_product=args.product[:16], + ) +) + +if include_hid: + h_file.write( + """\ +extern const uint8_t hid_report_descriptor[{hid_report_descriptor_length}]; + +#define USB_HID_NUM_DEVICES {hid_num_devices} +""".format( + hid_report_descriptor_length=len(bytes(combined_hid_report_descriptor)), + hid_num_devices=len(args.hid_devices), + ) + ) + +if include_vendor: + h_file.write( + """\ +enum +{ + VENDOR_REQUEST_WEBUSB = 1, + VENDOR_REQUEST_MICROSOFT = 2 +}; + +extern uint8_t const desc_ms_os_20[]; + +// Currently getting compile-time errors in files like tusb_fifo.c +// if we try do define this here (TODO figure this out!) +//extern const tusb_desc_webusb_url_t desc_webusb_url; + +""" + ) + +h_file.write( + """\ +#endif // MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H +""" +) + +if include_hid: + # Write out the report descriptor and info + c_file.write( + """\ +const uint8_t hid_report_descriptor[{HID_DESCRIPTOR_LENGTH}] = {{ +""".format( + HID_DESCRIPTOR_LENGTH=hid_descriptor_length + ) + ) + + for b in bytes(combined_hid_report_descriptor): + c_file.write("0x{:02x}, ".format(b)) + + c_file.write( + """\ +}; + +""" + ) + + # Write out USB HID report buffer definitions. + for name in args.hid_devices: + c_file.write( + """\ +static uint8_t {name}_report_buffer[{report_length}]; +""".format( + name=name.lower(), + report_length=hid_report_descriptors.HID_DEVICE_DATA[name].report_length, + ) + ) + + if hid_report_descriptors.HID_DEVICE_DATA[name].out_report_length > 0: + c_file.write( + """\ +static uint8_t {name}_out_report_buffer[{report_length}]; +""".format( + name=name.lower(), + report_length=hid_report_descriptors.HID_DEVICE_DATA[name].out_report_length, + ) + ) + + # Write out table of device objects. + c_file.write( + """\ +usb_hid_device_obj_t usb_hid_devices[] = { +""" + ) + for name in args.hid_devices: + device_data = hid_report_descriptors.HID_DEVICE_DATA[name] + out_report_buffer = ( + "{}_out_report_buffer".format(name.lower()) + if device_data.out_report_length > 0 + else "NULL" + ) + c_file.write( + """\ + {{ + .base = {{ .type = &usb_hid_device_type }}, + .report_buffer = {name}_report_buffer, + .report_id = {report_id}, + .report_length = {report_length}, + .usage_page = {usage_page:#04x}, + .usage = {usage:#04x}, + .out_report_buffer = {out_report_buffer}, + .out_report_length = {out_report_length}, + }}, +""".format( + name=name.lower(), + report_id=report_ids[name], + report_length=device_data.report_length, + usage_page=device_data.usage_page, + usage=device_data.usage, + out_report_buffer=out_report_buffer, + out_report_length=device_data.out_report_length, + ) + ) + c_file.write( + """\ +}; +""" + ) + + # Write out tuple of device objects. + c_file.write( + """ +mp_obj_tuple_t common_hal_usb_hid_devices = {{ + .base = {{ + .type = &mp_type_tuple, + }}, + .len = {num_devices}, + .items = {{ +""".format( + num_devices=len(args.hid_devices) + ) + ) + for idx in range(len(args.hid_devices)): + c_file.write( + """\ + (mp_obj_t) &usb_hid_devices[{idx}], +""".format( + idx=idx + ) + ) + c_file.write( + """\ + }, +}; +""" + ) + +if include_vendor: + # Mimic what the tinyusb webusb demo does in its main.c file + c_file.write( + """ +#define URL "{webusb_url}" + +const tusb_desc_webusb_url_t desc_webusb_url = +{{ + .bLength = 3 + sizeof(URL) - 1, + .bDescriptorType = 3, // WEBUSB URL type + .bScheme = 1, // 0: http, 1: https, 255: "" + .url = URL +}}; + +// These next two hardcoded descriptors were pulled from the usb_descriptor.c file +// of the tinyusb webusb_serial demo. TODO - this is probably something else to +// integrate into the adafruit_usb_descriptors project... + +//--------------------------------------------------------------------+ +// BOS Descriptor +//--------------------------------------------------------------------+ + +/* Microsoft OS 2.0 registry property descriptor +Per MS requirements https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx +device should create DeviceInterfaceGUIDs. It can be done by driver and +in case of real PnP solution device should expose MS "Microsoft OS 2.0 +registry property descriptor". Such descriptor can insert any record +into Windows registry per device/configuration/interface. In our case it +will insert "DeviceInterfaceGUIDs" multistring property. + +GUID is freshly generated and should be OK to use. + +https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/ +(Section Microsoft OS compatibility descriptors) +*/ + +#define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) + +#define MS_OS_20_DESC_LEN 0xB2 + +// BOS Descriptor is required for webUSB +uint8_t const desc_bos[] = +{{ + // total length, number of device caps + TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), + + // Vendor Code, iLandingPage + TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), + + // Microsoft OS 2.0 descriptor + TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT) +}}; + +uint8_t const * tud_descriptor_bos_cb(void) +{{ + return desc_bos; +}} + + +#define ITF_NUM_VENDOR {webusb_interface} // used in this next descriptor + +uint8_t const desc_ms_os_20[] = +{{ + // Set header: length, type, windows version, total length + U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), + + // Configuration subset header: length, type, configuration index, reserved, configuration total length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION), 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A), + + // Function Subset header: length, type, first interface, reserved, subset length + U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION), ITF_NUM_VENDOR, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08), + + // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID + U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible + + // MS OS 2.0 Registry property descriptor: length, type + U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x08-0x08-0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), + U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, + 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00, + U16_TO_U8S_LE(0x0050), // wPropertyDataLength + //bPropertyData: “{{975F44D9-0D08-43FD-8B3E-127CA8AFFF9D}}”. + '{{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00, 'D', 0x00, '9', 0x00, '-', 0x00, + '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, + '8', 0x00, 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00, 'C', 0x00, 'A', 0x00, + '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00, '9', 0x00, 'D', 0x00, '}}', 0x00, 0x00, 0x00, 0x00, 0x00 +}}; + +TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); + +// End of section about desc_ms_os_20 + +""".format( + webusb_url=args.webusb_url, webusb_interface=vendor_interface.bInterfaceNumber + ) + ) diff --git a/tools/usb_descriptor b/tools/usb_descriptor index 2eaa6114b2..7ca9377687 160000 --- a/tools/usb_descriptor +++ b/tools/usb_descriptor @@ -1 +1 @@ -Subproject commit 2eaa6114b209fe7f0a795eda8d6a7b3b93d76d2e +Subproject commit 7ca9377687cc8bd367bd6401dcc4361a71a9d6cf