summaryrefslogtreecommitdiff
path: root/tools/gen_usb_descriptor.py
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gen_usb_descriptor.py')
-rw-r--r--tools/gen_usb_descriptor.py474
1 files changed, 474 insertions, 0 deletions
diff --git a/tools/gen_usb_descriptor.py b/tools/gen_usb_descriptor.py
new file mode 100644
index 000000000..b50c0798f
--- /dev/null
+++ b/tools/gen_usb_descriptor.py
@@ -0,0 +1,474 @@
+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
+
+parser = argparse.ArgumentParser(description='Generate USB descriptors.')
+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('--output_c_file', type=argparse.FileType('w'), required=True)
+parser.add_argument('--output_h_file', type=argparse.FileType('w'), required=True)
+
+args = parser.parse_args()
+
+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.
+
+cdc_union = cdc.Union(
+ description="CDC comm",
+ bMasterInterface=0x00, # Adjust this after interfaces are renumbered.
+ bSlaveInterface_list=[0x01]) # Adjust this after interfaces are renumbered.
+
+cdc_call_management = cdc.CallManagement(
+ description="CDC comm",
+ bmCapabilities=0x01,
+ bDataInterface=0x01) # Adjust this after interfaces are renumbered.
+
+cdc_comm_interface = standard.InterfaceDescriptor(
+ description="CDC comm",
+ bInterfaceClass=cdc.CDC_CLASS_COMM, # Communications Device Class
+ bInterfaceSubClass=cdc.CDC_SUBCLASS_ACM, # Abstract control model
+ bInterfaceProtocol=cdc.CDC_PROTOCOL_V25TER,
+ iInterface=StringIndex.index("CircuitPython CDC control"),
+ subdescriptors=[
+ cdc.Header(
+ description="CDC comm",
+ bcdCDC=0x0110),
+ cdc_call_management,
+ cdc.AbstractControlManagement(
+ description="CDC comm",
+ bmCapabilities=0x02),
+ cdc_union,
+ standard.EndpointDescriptor(
+ description="CDC comm in",
+ bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
+ bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT,
+ wMaxPacketSize=0x0040,
+ bInterval=0x10)
+ ])
+
+cdc_data_interface = standard.InterfaceDescriptor(
+ description="CDC data",
+ bInterfaceClass=cdc.CDC_CLASS_DATA,
+ iInterface=StringIndex.index("CircuitPython CDC data"),
+ subdescriptors=[
+ standard.EndpointDescriptor(
+ description="CDC data out",
+ bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_OUT,
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK),
+ standard.EndpointDescriptor(
+ description="CDC data in",
+ bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK),
+ ])
+
+cdc_interfaces = [cdc_comm_interface, cdc_data_interface]
+
+msc_interfaces = [
+ standard.InterfaceDescriptor(
+ description="MSC",
+ bInterfaceClass=msc.MSC_CLASS,
+ bInterfaceSubClass=msc.MSC_SUBCLASS_TRANSPARENT,
+ bInterfaceProtocol=msc.MSC_PROTOCOL_BULK,
+ iInterface=StringIndex.index("CircuitPython Mass Storage"),
+ subdescriptors=[
+ standard.EndpointDescriptor(
+ description="MSC in",
+ bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK,
+ bInterval=0),
+ standard.EndpointDescriptor(
+ description="MSC out",
+ bEndpointAddress=0x1 | standard.EndpointDescriptor.DIRECTION_OUT,
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK,
+ bInterval=0)
+ ]
+ )
+]
+
+# Include only these HID devices.
+# DIGITIZER works on Linux but conflicts with MOUSE, so leave it out for now.
+hid_devices = ("KEYBOARD", "MOUSE", "CONSUMER", "GAMEPAD")
+
+combined_hid_report_descriptor = hid.ReportDescriptor(
+ description="MULTIDEVICE",
+ report_descriptor=b''.join(
+ hid_report_descriptors.REPORT_DESCRIPTORS[name].report_descriptor for name in hid_devices ))
+
+hid_report_ids_dict = { name: hid_report_descriptors.REPORT_IDS[name] for name in hid_devices }
+hid_report_lengths_dict = { name: hid_report_descriptors.REPORT_LENGTHS[name] for name in hid_devices }
+hid_max_report_length = max(hid_report_lengths_dict.values())
+
+# 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=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
+ bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT,
+ bInterval=10)
+
+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("CircuitPython HID"),
+ subdescriptors=[
+ hid.HIDDescriptor(
+ description="HID",
+ wDescriptorLength=len(bytes(combined_hid_report_descriptor))),
+ hid_endpoint_in_descriptor,
+ ]
+ ),
+ ]
+
+# Audio!
+midi_in_jack = midi.InJackDescriptor(
+ description="MIDI PC <- CircuitPython internals",
+ bJackType=midi.JACK_TYPE_EMBEDDED,
+ iJack=0)
+midi_out_jack = midi.OutJackDescriptor(
+ description="MIDI PC -> CircuitPython internals",
+ bJackType=midi.JACK_TYPE_EMBEDDED,
+ iJack=0)
+audio_midi_interface = standard.InterfaceDescriptor(
+ description="All the audio",
+ bInterfaceClass=audio.AUDIO_CLASS_DEVICE,
+ bInterfaceSubClass=audio.AUDIO_SUBCLASS_MIDI_STREAMING,
+ bInterfaceProtocol=audio.AUDIO_PROTOCOL_V1,
+ iInterface=StringIndex.index("CircuitPython MIDI"),
+ subdescriptors=[
+ midi.Header(
+ jacks_and_elements=[
+ midi_in_jack,
+ midi.InJackDescriptor(
+ description="MIDI data in from user code.",
+ bJackType=midi.JACK_TYPE_EXTERNAL, iJack=0),
+ midi_out_jack,
+ midi.OutJackDescriptor(
+ description="MIDI data out to user code.",
+ bJackType=midi.JACK_TYPE_EXTERNAL, iJack=0),
+ ]
+ ),
+ standard.EndpointDescriptor(
+ description="MIDI data out",
+ bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_OUT,
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK),
+ midi.DataEndpointDescriptor(baAssocJack=[midi_out_jack]),
+ standard.EndpointDescriptor(
+ description="MIDI data in",
+ bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN,
+ bmAttributes=standard.EndpointDescriptor.TYPE_BULK),
+ midi.DataEndpointDescriptor(baAssocJack=[midi_in_jack]),
+ ])
+
+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("CircuitPython Audio"),
+ 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
+
+# This 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(cdc_interfaces, msc_interfaces, hid_interfaces)
+
+# Now adjust the CDC interface cross-references.
+
+cdc_union.bMasterInterface = cdc_comm_interface.bInterfaceNumber
+cdc_union.bSlaveInterface_list = [cdc_data_interface.bInterfaceNumber]
+
+cdc_call_management.bDataInterface = cdc_data_interface.bInterfaceNumber
+
+cdc_iad = standard.InterfaceAssociationDescriptor(
+ description="CDC IAD",
+ 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_V25TER) # TODO(tannewt): can this be NONE (aka 0)?
+
+# audio_iad = standard.InterfaceAssociationDescriptor(
+# description="Audio IAD",
+# bFirstInterface=audio_control_interface.bInterfaceNumber,
+# bInterfaceCount=len(audio_interfaces),
+# bFunctionClass=audio.AUDIO_CLASS_DEVICE,
+# bFunctionSubClass=audio.AUDIO_SUBCLASS_UNKNOWN,
+# bFunctionProtocol=audio.AUDIO_PROTOCOL_V1)
+
+
+descriptor_list = []
+descriptor_list.append(cdc_iad)
+# descriptor_list.append(audio_iad)
+descriptor_list.extend(cdc_interfaces)
+descriptor_list.extend(msc_interfaces)
+# descriptor_list.append(audio_control_interface)
+# 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.extend(hid_interfaces)
+
+configuration = standard.ConfigurationDescriptor(
+ description="Composite configuration",
+ wTotalLength=(standard.ConfigurationDescriptor.bLength +
+ sum([len(bytes(x)) for x in descriptor_list])),
+ bNumInterfaces=len(interfaces))
+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 <stdint.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)
+
+ const = "const "
+ if variable_name == "usb_serial_number":
+ const = ""
+ c_file.write("""\
+{const}uint16_t {NAME}[] = {{
+""".format(const=const, NAME=variable_name))
+ pointers_to_strings.append("{name}".format(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");
+
+hid_descriptor_length = len(bytes(combined_hid_report_descriptor))
+
+# Now we 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 <stdint.h>
+
+const uint8_t usb_desc_dev[{device_length}];
+// Make sure the control buffer is big enough to fit the descriptor.
+#define CFG_TUD_ENUM_BUFFER_SIZE {max_configuration_length}
+const uint8_t usb_desc_cfg[{configuration_length}];
+uint16_t usb_serial_number[{serial_number_length}];
+uint16_t const * const string_desc_arr [{string_descriptor_length}];
+
+const uint8_t hid_report_descriptor[{HID_REPORT_DESCRIPTOR_LENGTH}];
+
+// 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),
+ HID_REPORT_DESCRIPTOR_LENGTH=len(bytes(combined_hid_report_descriptor)),
+ msc_vendor=args.manufacturer[:8],
+ msc_product=args.product[:16]))
+
+# #define the report ID's used in the combined HID descriptor
+for name, id in hid_report_ids_dict.items():
+ h_file.write("""\
+#define USB_HID_REPORT_ID_{name} {id}
+""".format(name=name,
+ id=id))
+
+h_file.write("\n")
+
+# #define the report sizes used in the combined HID descriptor
+for name, length in hid_report_lengths_dict.items():
+ h_file.write("""\
+#define USB_HID_REPORT_LENGTH_{name} {length}
+""".format(name=name,
+ length=length))
+
+h_file.write("\n")
+
+h_file.write("""\
+#define USB_HID_NUM_DEVICES {num_devices}
+#define USB_HID_MAX_REPORT_LENGTH {max_length}
+""".format(num_devices=len(hid_report_lengths_dict),
+ max_length=hid_max_report_length))
+
+
+
+# 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("""
+};
+""")
+
+h_file.write("""\
+#endif // MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H
+""")