Index: tools/usb_gadget/descriptor.py |
diff --git a/tools/usb_gadget/descriptor.py b/tools/usb_gadget/descriptor.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a53a86cd5765b8cc4efd1962e22d4fcf95079c72 |
--- /dev/null |
+++ b/tools/usb_gadget/descriptor.py |
@@ -0,0 +1,359 @@ |
+"""USB descriptor generation utilities. |
not at google - send to devlin
2014/07/23 00:05:44
copyright
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
+ |
+Classes to represent and generate USB descriptors. |
+""" |
Ken Rockot(use gerrit already)
2014/07/23 00:03:48
Maybe this module should be usb_descriptors?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
+ |
+import struct |
+ |
+import usb_const |
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
How about "usb_constants" for the module name?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
+ |
+ |
+class Field(object): |
+ |
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
A bit of documentation?
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
+ def __init__(self, name, str_fmt, struct_fmt, required): |
+ self.name = name |
+ self.str_fmt = str_fmt |
+ self.struct_fmt = struct_fmt |
+ self.required = required |
+ |
+ def Format(self, value): |
+ return self.str_fmt.format(value) |
+ |
+ |
+class Descriptor(object): |
+ """Base class for USB descriptor types. |
+ |
+ This class provides general functionality for creating object types that |
+ represent USB descriptors. The AddField and related methods are used to |
+ define the fields of each structure. Fields can then be set using keyword |
+ arguments to the object constructor or by accessing properties on the object. |
+ """ |
+ |
+ _fields = None |
+ |
+ @classmethod |
+ def AddField(cls, name, struct_fmt, str_fmt='{}', default=None): |
+ """Adds a user-specified field to this descriptor. |
+ |
+ Adds a field to the binary structure representing this descriptor. The field |
+ can be set by passing a keyword argument name=... to the object constructor |
+ will be accessible as foo.name on any instance. |
+ |
+ If no default value is provided then the constructor will through an |
+ exception if this field is not one of the provided keyword arguments. |
+ |
+ Args: |
+ name: String name of the field. |
+ struct_fmt: Python 'struct' module format string for this field. |
+ str_fmt: Python 'string' module format string for this field. |
+ default: Default value. |
+ """ |
+ if cls._fields is None: |
+ cls._fields = [] |
+ cls._fields.append(Field(name, str_fmt, struct_fmt, default is None)) |
+ |
+ member_name = '_{}'.format(name) |
+ def Setter(self, value): |
+ setattr(self, member_name, value) |
+ def Getter(self): |
+ try: |
+ return getattr(self, member_name) |
+ except AttributeError: |
+ assert default is not None |
+ return default |
+ |
+ setattr(cls, name, property(Getter, Setter)) |
+ |
+ @classmethod |
+ def AddFixedField(cls, name, struct_fmt, value, str_fmt='{}'): |
+ """Adds a constant field to this descriptor. |
+ |
+ Adds a constant field to the binary structure representing this descriptor. |
+ The field will be accessible as foo.name on any instance. |
+ |
+ The value of this field may not be given as a constructor parameter or |
+ set on an existing instance. |
+ |
+ Args: |
+ name: String name of the field. |
+ struct_fmt: Python 'struct' module format string for this field. |
+ value: Field value. |
+ str_fmt: Python 'string' module format string for this field. |
+ """ |
+ if cls._fields is None: |
+ cls._fields = [] |
+ cls._fields.append(Field(name, str_fmt, struct_fmt, False)) |
+ |
+ def Setter(unused_self, unused_value): |
+ raise RuntimeError('{} is a fixed field.'.format(name)) |
+ def Getter(unused_self): |
+ return value |
+ |
+ setattr(cls, name, property(Getter, Setter)) |
+ |
+ @classmethod |
+ def AddComputedField(cls, name, struct_fmt, property_name, str_fmt='{}'): |
+ """Adds a constant field to this descriptor. |
+ |
+ Adds a field to the binary structure representing this descriptor whos value |
+ is equal to an object property. The field will be accessible as foo.name on |
+ any instance. |
+ |
+ The value of this field may not be given as a constructor parameter or |
+ set on an existing instance. |
+ |
+ Args: |
+ name: String name of the field. |
+ struct_fmt: Python 'struct' module format string for this field. |
+ property_name: Property to read. |
+ str_fmt: Python 'string' module format string for this field. |
+ """ |
+ if cls._fields is None: |
+ cls._fields = [] |
+ cls._fields.append(Field(name, str_fmt, struct_fmt, False)) |
+ |
+ def Setter(unused_self, unused_value): |
+ raise RuntimeError('{} is a computed field.'.format(name)) |
+ def Getter(self): |
+ return getattr(self, property_name) |
+ |
+ setattr(cls, name, property(Getter, Setter)) |
+ |
+ def __init__(self, **kwargs): |
+ """Constructs a new instance of this descriptor. |
+ |
+ All fields which do not have a default value and are not fixed or computed |
+ from a property must be specified as keyword arguments. |
+ |
+ Args: |
+ **kwargs: Field values. |
+ |
+ Raises: |
+ TypeError: A required field was missing or an unexpected field was given. |
+ """ |
+ fields = {field.name for field in self._fields} |
+ required_fields = {field.name for field in self._fields if field.required} |
+ |
+ for arg, value in kwargs.iteritems(): |
+ if arg not in fields: |
+ raise TypeError('Unexpected field: {}'.format(arg)) |
+ |
+ setattr(self, arg, value) |
+ required_fields.discard(arg) |
+ |
+ if required_fields: |
+ raise TypeError('Missing fields: {}'.format(', '.join(required_fields))) |
+ |
+ @property |
+ def fmt(self): |
+ """Returns the Python 'struct' module format string for this descriptor.""" |
+ return '<{}'.format(''.join([field.struct_fmt for field in self._fields])) |
+ |
+ @property |
+ def struct_size(self): |
+ """Returns the size of the struct defined by fmt.""" |
+ return struct.calcsize(self.fmt) |
+ |
+ @property |
+ def total_size(self): |
+ """Returns the total size of this descriptor.""" |
+ return self.struct_size |
+ |
+ def Encode(self): |
+ """Returns the binary representation of this descriptor.""" |
+ values = [getattr(self, field.name) for field in self._fields] |
+ return struct.pack(self.fmt, *values) |
+ |
+ def __str__(self): |
+ max_length = max(len(field.name) for field in self._fields) |
+ |
+ return '{}:\n {}'.format( |
+ self.__class__.__name__, |
+ '\n '.join('{} {}'.format( |
+ '{}:'.format(field.name).ljust(max_length+1), |
+ field.Format(getattr(self, field.name)) |
+ ) for field in self._fields) |
+ ) |
+ |
+ |
+class DeviceDescriptor(Descriptor): |
+ """Represents a USB device descriptor.""" |
+ |
+ pass |
+ |
+DeviceDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
Ken Rockot(use gerrit already)
2014/07/23 00:03:47
Might as well link to / reference any relevant sec
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
Done.
|
+DeviceDescriptor.AddFixedField('bDescriptorType', 'B', |
+ usb_const.DescriptorType.DEVICE) |
+DeviceDescriptor.AddField('bcdUSB', 'H', default=0x0200, str_fmt='0x{:04X}') |
+DeviceDescriptor.AddField('bDeviceClass', 'B', |
+ default=usb_const.DeviceClass.PER_INTERFACE) |
+DeviceDescriptor.AddField('bDeviceSubClass', 'B', |
+ default=usb_const.DeviceSubClass.PER_INTERFACE) |
+DeviceDescriptor.AddField('bDeviceProtocol', 'B', |
+ default=usb_const.DeviceProtocol.PER_INTERFACE) |
+DeviceDescriptor.AddField('bMaxPacketSize0', 'B', default=64) |
+DeviceDescriptor.AddField('idVendor', 'H', str_fmt='0x{:04X}') |
+DeviceDescriptor.AddField('idProduct', 'H', str_fmt='0x{:04X}') |
+DeviceDescriptor.AddField('bcdDevice', 'H', str_fmt='0x{:04X}') |
+DeviceDescriptor.AddField('iManufacturer', 'B', default=0) |
+DeviceDescriptor.AddField('iProduct', 'B', default=0) |
+DeviceDescriptor.AddField('iSerialNumber', 'B', default=0) |
+DeviceDescriptor.AddField('bNumConfigurations', 'B', default=1) |
+ |
+ |
+class DescriptorContainer(Descriptor): |
Ken Rockot(use gerrit already)
2014/07/23 00:03:48
If this is established nomenclature in USB land, I
Reilly Grant (use Gerrit)
2014/07/23 00:43:54
"CompositeDescriptor" may confusing because "USB C
|
+ """Super-class for descriptors which contain more descriptors. |
+ |
+ This class adds the ability for a descriptor to have an array of additional |
+ descriptors which follow it. |
+ """ |
+ |
+ def __init__(self, **kwargs): |
+ super(DescriptorContainer, self).__init__(**kwargs) |
+ self._descriptors = [] |
+ |
+ @property |
+ def total_size(self): |
+ return self.struct_size + sum([descriptor.total_size |
+ for descriptor in self._descriptors]) |
+ |
+ def Add(self, descriptor): |
+ self._descriptors.append(descriptor) |
+ |
+ def Encode(self): |
+ bufs = [super(DescriptorContainer, self).Encode()] |
+ bufs.extend(descriptor.Encode() for descriptor in self._descriptors) |
+ return ''.join(bufs) |
+ |
+ def __str__(self): |
+ return '{}\n{}'.format(super(DescriptorContainer, self).__str__(), |
+ '\n'.join(str(descriptor) |
+ for descriptor in self._descriptors)) |
+ |
+ |
+class ConfigurationDescriptor(DescriptorContainer): |
+ """Represents a USB device configuration descriptor. |
+ |
+ Configuration descriptors may have a number of interface descriptors. |
+ """ |
+ |
+ def __init__(self, **kwargs): |
+ super(ConfigurationDescriptor, self).__init__(**kwargs) |
+ self._interfaces = {} |
+ |
+ @property |
+ def num_interfaces(self): |
+ interface_numbers = {key[0] for key in self._interfaces.iterkeys()} |
+ return len(interface_numbers) |
+ |
+ def AddInterface(self, interface): |
+ key = (interface.bInterfaceNumber, interface.bAlternateSetting) |
+ if key in self._interfaces: |
+ raise RuntimeError('Interface {} (alternate {}) already defined.' |
+ .format(key[0], key[1])) |
+ self._interfaces[key] = interface |
+ self.Add(interface) |
+ |
+ def GetInterfaces(self): |
+ return self._interfaces.values() |
+ |
+ConfigurationDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
+ConfigurationDescriptor.AddFixedField('bDescriptorType', 'B', |
+ usb_const.DescriptorType.CONFIGURATION) |
+ConfigurationDescriptor.AddComputedField('wTotalLength', 'H', 'total_size') |
+ConfigurationDescriptor.AddComputedField('bNumInterfaces', 'B', |
+ 'num_interfaces') |
+ConfigurationDescriptor.AddField('bConfigurationValue', 'B', default=1) |
+ConfigurationDescriptor.AddField('iConfiguration', 'B', default=0) |
+ConfigurationDescriptor.AddField('bmAttributes', 'B', str_fmt='0x{:02X}') |
+ConfigurationDescriptor.AddField('MaxPower', 'B') |
+ |
+ |
+class InterfaceDescriptor(DescriptorContainer): |
+ """Represents a USB interface descriptor. |
+ |
+ Interface descriptors may have a number of endpoint descriptors. |
+ """ |
+ |
+ def __init__(self, **kwargs): |
+ super(InterfaceDescriptor, self).__init__(**kwargs) |
+ self._endpoints = {} |
+ |
+ @property |
+ def num_endpoints(self): |
+ return len(self._endpoints) |
+ |
+ def AddEndpoint(self, endpoint): |
+ if endpoint.bEndpointAddress in self._endpoints: |
+ raise RuntimeError('Endpoint 0x{:02X} already defined on this interface.' |
+ .format(endpoint.bEndpointAddress)) |
+ self._endpoints[endpoint.bEndpointAddress] = endpoint |
+ self.Add(endpoint) |
+ |
+ def GetEndpoints(self): |
+ return self._endpoints.values() |
+ |
+InterfaceDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
+InterfaceDescriptor.AddFixedField('bDescriptorType', 'B', |
+ usb_const.DescriptorType.INTERFACE) |
+InterfaceDescriptor.AddField('bInterfaceNumber', 'B') |
+InterfaceDescriptor.AddField('bAlternateSetting', 'B', default=0) |
+InterfaceDescriptor.AddComputedField('bNumEndpoints', 'B', 'num_endpoints') |
+InterfaceDescriptor.AddField('bInterfaceClass', 'B', |
+ default=usb_const.InterfaceClass.VENDOR) |
+InterfaceDescriptor.AddField('bInterfaceSubClass', 'B', |
+ default=usb_const.InterfaceSubClass.VENDOR) |
+InterfaceDescriptor.AddField('bInterfaceProtocol', 'B', |
+ default=usb_const.InterfaceProtocol.VENDOR) |
+InterfaceDescriptor.AddField('iInterface', 'B', default=0) |
+ |
+ |
+class EndpointDescriptor(Descriptor): |
+ """Represents a USB endpoint descriptor.""" |
+ |
+ pass |
+ |
+EndpointDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
+EndpointDescriptor.AddFixedField('bDescriptorType', 'B', |
+ usb_const.DescriptorType.ENDPOINT) |
+EndpointDescriptor.AddField('bEndpointAddress', 'B', str_fmt='0x{:02X}') |
+EndpointDescriptor.AddField('bmAttributes', 'B', str_fmt='0x{:02X}') |
+EndpointDescriptor.AddField('wMaxPacketSize', 'H') |
+EndpointDescriptor.AddField('bInterval', 'B') |
+ |
+ |
+class HidDescriptor(Descriptor): |
+ """Represents a USB HID descriptor.""" |
+ |
+ def __init__(self, **kwargs): |
+ super(HidDescriptor, self).__init__(**kwargs) |
+ self._descriptors = [] |
+ |
+ def AddDescriptor(self, typ, length): |
+ self._descriptors.append((typ, length)) |
+ |
+ @property |
+ def struct_size(self): |
+ return super(HidDescriptor, self).struct_size + self.num_descriptors * 3 |
+ |
+ @property |
+ def num_descriptors(self): |
+ return len(self._descriptors) |
+ |
+ def Encode(self): |
+ bufs = [super(HidDescriptor, self).Encode()] |
+ bufs.extend(struct.pack('<BH', typ, length) |
+ for typ, length in self._descriptors) |
+ return ''.join(bufs) |
+ |
+ def __str__(self): |
+ return '{}\n{}'.format( |
+ super(HidDescriptor, self).__str__(), |
+ '\n'.join(' bDescriptorType: 0x{:02X}\n wDescriptorLength: {}' |
+ .format(typ, length) for typ, length in self._descriptors)) |
+ |
+HidDescriptor.AddComputedField('bLength', 'B', 'struct_size') |
+HidDescriptor.AddFixedField('bDescriptorType', 'B', 33) |
+HidDescriptor.AddField('bcdHID', 'H', default=0x0111, str_fmt='0x{:04X}') |
+HidDescriptor.AddField('bCountryCode', 'B', default=0) |
+HidDescriptor.AddComputedField('bNumDescriptors', 'B', 'num_descriptors') |