| Index: tools/usb_gadget/hid_gadget.py
|
| diff --git a/tools/usb_gadget/hid_gadget.py b/tools/usb_gadget/hid_gadget.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..82e4482e01cf147eb711c726753d54e489896d73
|
| --- /dev/null
|
| +++ b/tools/usb_gadget/hid_gadget.py
|
| @@ -0,0 +1,391 @@
|
| +# Copyright 2014 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file.
|
| +
|
| +"""Human Interface Device gadget module.
|
| +
|
| +This gadget emulates a USB Human Interface Device. Multiple logical components
|
| +of a device can be composed together as separate "features" where each has its
|
| +own Report ID and will be called upon to answer get/set input/output/feature
|
| +report requests as necessary.
|
| +"""
|
| +
|
| +import math
|
| +import struct
|
| +import uuid
|
| +
|
| +import gadget
|
| +import hid_constants
|
| +import usb_constants
|
| +import usb_descriptors
|
| +
|
| +
|
| +class HidGadget(gadget.Gadget):
|
| + """Generic HID gadget.
|
| + """
|
| +
|
| + def __init__(self, report_desc, features, vendor_id, product_id,
|
| + packet_size=64, interval_ms=10, out_endpoint=True,
|
| + device_version=0x0100):
|
| + """Create a HID gadget.
|
| +
|
| + Args:
|
| + report_desc: HID report descriptor.
|
| + features: Map between Report IDs and HidFeature objects to handle them.
|
| + vendor_id: Device Vendor ID.
|
| + product_id: Device Product ID.
|
| + packet_size: Maximum interrupt packet size.
|
| + interval_ms: Interrupt transfer interval in milliseconds.
|
| + out_endpoint: Should this device have an interrupt OUT endpoint?
|
| + device_version: Device version number.
|
| +
|
| + Raises:
|
| + ValueError: If any of the parameters are out of range.
|
| + """
|
| + device_desc = usb_descriptors.DeviceDescriptor(
|
| + idVendor=vendor_id,
|
| + idProduct=product_id,
|
| + bcdUSB=0x0200,
|
| + iManufacturer=1,
|
| + iProduct=2,
|
| + iSerialNumber=3,
|
| + bcdDevice=device_version)
|
| +
|
| + fs_config_desc = usb_descriptors.ConfigurationDescriptor(
|
| + bmAttributes=0x80,
|
| + MaxPower=50)
|
| + fs_interface_desc = usb_descriptors.InterfaceDescriptor(
|
| + bInterfaceNumber=0,
|
| + bInterfaceClass=usb_constants.DeviceClass.HID,
|
| + bInterfaceSubClass=0, # Non-bootable.
|
| + bInterfaceProtocol=0, # None.
|
| + )
|
| + fs_config_desc.AddInterface(fs_interface_desc)
|
| +
|
| + hs_config_desc = usb_descriptors.ConfigurationDescriptor(
|
| + bmAttributes=0x80,
|
| + MaxPower=50)
|
| + hs_interface_desc = usb_descriptors.InterfaceDescriptor(
|
| + bInterfaceNumber=0,
|
| + bInterfaceClass=usb_constants.DeviceClass.HID,
|
| + bInterfaceSubClass=0, # Non-bootable.
|
| + bInterfaceProtocol=0, # None.
|
| + )
|
| + hs_config_desc.AddInterface(hs_interface_desc)
|
| +
|
| + hid_desc = usb_descriptors.HidDescriptor()
|
| + hid_desc.AddDescriptor(hid_constants.DescriptorType.REPORT,
|
| + len(report_desc))
|
| + fs_interface_desc.Add(hid_desc)
|
| + hs_interface_desc.Add(hid_desc)
|
| +
|
| + fs_interval = math.ceil(math.log(interval_ms, 2)) + 1
|
| + if fs_interval < 1 or fs_interval > 16:
|
| + raise ValueError('Full speed interval out of range: {} ({} ms)'
|
| + .format(fs_interval, interval_ms))
|
| +
|
| + fs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
|
| + bEndpointAddress=0x81,
|
| + bmAttributes=usb_constants.TransferType.INTERRUPT,
|
| + wMaxPacketSize=packet_size,
|
| + bInterval=fs_interval
|
| + ))
|
| +
|
| + hs_interval = math.ceil(math.log(interval_ms, 2)) + 4
|
| + if hs_interval < 1 or hs_interval > 16:
|
| + raise ValueError('High speed interval out of range: {} ({} ms)'
|
| + .format(hs_interval, interval_ms))
|
| +
|
| + hs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
|
| + bEndpointAddress=0x81,
|
| + bmAttributes=usb_constants.TransferType.INTERRUPT,
|
| + wMaxPacketSize=packet_size,
|
| + bInterval=hs_interval
|
| + ))
|
| +
|
| + if out_endpoint:
|
| + fs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
|
| + bEndpointAddress=0x01,
|
| + bmAttributes=usb_constants.TransferType.INTERRUPT,
|
| + wMaxPacketSize=packet_size,
|
| + bInterval=fs_interval
|
| + ))
|
| + hs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
|
| + bEndpointAddress=0x01,
|
| + bmAttributes=usb_constants.TransferType.INTERRUPT,
|
| + wMaxPacketSize=packet_size,
|
| + bInterval=hs_interval
|
| + ))
|
| +
|
| + super(HidGadget, self).__init__(device_desc, fs_config_desc, hs_config_desc)
|
| + self.AddStringDescriptor(3, '{:06X}'.format(uuid.getnode()))
|
| + self._report_desc = report_desc
|
| + self._features = features
|
| +
|
| + def Connected(self, chip, speed):
|
| + super(HidGadget, self).Connected(chip, speed)
|
| + for report_id, feature in self._features.iteritems():
|
| + feature.Connected(self, report_id)
|
| +
|
| + def Disconnected(self):
|
| + super(HidGadget, self).Disconnected()
|
| + for feature in self._features.itervalues():
|
| + feature.Disconnected()
|
| +
|
| + def GetDescriptor(self, recipient, typ, index, lang, length):
|
| + if recipient == usb_constants.Recipient.INTERFACE:
|
| + if typ == hid_constants.DescriptorType.REPORT:
|
| + if index == 0:
|
| + return self._report_desc[:length]
|
| +
|
| + return super(HidGadget, self).GetDescriptor(recipient, typ, index, lang,
|
| + length)
|
| +
|
| + def ClassControlRead(self, recipient, request, value, index, length):
|
| + """Handle class-specific control requests.
|
| +
|
| + See Device Class Definition for Human Interface Devices (HID) Version 1.11
|
| + section 7.2.
|
| +
|
| + Args:
|
| + recipient: Request recipient (device, interface, endpoint, etc.)
|
| + request: bRequest field of the setup packet.
|
| + value: wValue field of the setup packet.
|
| + index: wIndex field of the setup packet.
|
| + length: Maximum amount of data the host expects the device to return.
|
| +
|
| + Returns:
|
| + A buffer to return to the USB host with len <= length on success or
|
| + None to stall the pipe.
|
| + """
|
| + if recipient != usb_constants.Recipient.INTERFACE:
|
| + return None
|
| + if index != 0:
|
| + return None
|
| +
|
| + if request == hid_constants.Request.GET_REPORT:
|
| + report_type, report_id = value >> 8, value & 0xFF
|
| + print ('GetReport(type={}, id={}, length={})'
|
| + .format(report_type, report_id, length))
|
| + return self.GetReport(report_type, report_id, length)
|
| +
|
| + def ClassControlWrite(self, recipient, request, value, index, data):
|
| + """Handle class-specific control requests.
|
| +
|
| + See Device Class Definition for Human Interface Devices (HID) Version 1.11
|
| + section 7.2.
|
| +
|
| + Args:
|
| + recipient: Request recipient (device, interface, endpoint, etc.)
|
| + request: bRequest field of the setup packet.
|
| + value: wValue field of the setup packet.
|
| + index: wIndex field of the setup packet.
|
| + data: Data stage of the request.
|
| +
|
| + Returns:
|
| + True on success, None to stall the pipe.
|
| + """
|
| + if recipient != usb_constants.Recipient.INTERFACE:
|
| + return None
|
| + if index != 0:
|
| + return None
|
| +
|
| + if request == hid_constants.Request.SET_REPORT:
|
| + report_type, report_id = value >> 8, value & 0xFF
|
| + print('SetReport(type={}, id={}, length={})'
|
| + .format(report_type, report_id, len(data)))
|
| + return self.SetReport(report_type, report_id, data)
|
| + elif request == hid_constants.Request.SET_IDLE:
|
| + duration, report_id = value >> 8, value & 0xFF
|
| + print('SetIdle(duration={}, report={})'
|
| + .format(duration, report_id))
|
| + return True
|
| +
|
| + def GetReport(self, report_type, report_id, length):
|
| + """Handle GET_REPORT requests.
|
| +
|
| + See Device Class Definition for Human Interface Devices (HID) Version 1.11
|
| + section 7.2.1.
|
| +
|
| + Args:
|
| + report_type: Requested report type.
|
| + report_id: Requested report ID.
|
| + length: Maximum amount of data the host expects the device to return.
|
| +
|
| + Returns:
|
| + A buffer to return to the USB host with len <= length on success or
|
| + None to stall the pipe.
|
| + """
|
| + feature = self._features.get(report_id, None)
|
| + if feature is None:
|
| + return None
|
| +
|
| + if report_type == hid_constants.ReportType.INPUT:
|
| + return feature.GetInputReport()[:length]
|
| + elif report_type == hid_constants.ReportType.OUTPUT:
|
| + return feature.GetOutputReport()[:length]
|
| + elif report_type == hid_constants.ReportType.FEATURE:
|
| + return feature.GetFeatureReport()[:length]
|
| +
|
| + def SetReport(self, report_type, report_id, data):
|
| + """Handle SET_REPORT requests.
|
| +
|
| + See Device Class Definition for Human Interface Devices (HID) Version 1.11
|
| + section 7.2.2.
|
| +
|
| + Args:
|
| + report_type: Report type.
|
| + report_id: Report ID.
|
| + data: Report data.
|
| +
|
| + Returns:
|
| + True on success, None to stall the pipe.
|
| + """
|
| + feature = self._features.get(report_id, None)
|
| + if feature is None:
|
| + return None
|
| +
|
| + if report_type == hid_constants.ReportType.INPUT:
|
| + return feature.SetInputReport(data)
|
| + elif report_type == hid_constants.ReportType.OUTPUT:
|
| + return feature.SetOutputReport(data)
|
| + elif report_type == hid_constants.ReportType.FEATURE:
|
| + return feature.SetFeatureReport(data)
|
| +
|
| + def SendReport(self, report_id, data):
|
| + """Send a HID report.
|
| +
|
| + See Device Class Definition for Human Interface Devices (HID) Version 1.11
|
| + section 8.
|
| +
|
| + Args:
|
| + report_id: Report ID associated with the data.
|
| + data: Contents of the report.
|
| + """
|
| + if report_id == 0:
|
| + self.SendPacket(0x81, data)
|
| + else:
|
| + self.SendPacket(0x81, struct.pack('B', report_id) + data)
|
| +
|
| + def ReceivePacket(self, endpoint, data):
|
| + """Dispatch a report to the appropriate feature.
|
| +
|
| + See Device Class Definition for Human Interface Devices (HID) Version 1.11
|
| + section 8.
|
| +
|
| + Args:
|
| + endpoint: Incoming endpoint (must be the Interrupt OUT pipe).
|
| + data: Interrupt packet data.
|
| + """
|
| + assert endpoint == 0x01
|
| +
|
| + if 0 in self._features:
|
| + self._features[0].SetOutputReport(data)
|
| + elif len(data) >= 1:
|
| + report_id, = struct.unpack('B', data[0])
|
| + feature = self._features.get(report_id, None)
|
| + if feature is None or feature.SetOutputReport(data[1:]) is None:
|
| + self.HaltEndpoint(endpoint)
|
| +
|
| +
|
| +class HidFeature(object):
|
| + """Represents a component of a HID gadget.
|
| +
|
| + A "feature" produces and consumes reports with a particular Report ID. For
|
| + example a keyboard, mouse or vendor specific functionality.
|
| + """
|
| +
|
| + def __init__(self):
|
| + self._gadget = None
|
| + self._report_id = None
|
| +
|
| + def Connected(self, my_gadget, report_id):
|
| + self._gadget = my_gadget
|
| + self._report_id = report_id
|
| +
|
| + def Disconnected(self):
|
| + self._gadget = None
|
| + self._report_id = None
|
| +
|
| + def IsConnected(self):
|
| + return self._gadget is not None
|
| +
|
| + def SendReport(self, data):
|
| + """Send a report with this feature's Report ID.
|
| +
|
| + Args:
|
| + data: Report to send. If necessary the Report ID will be added.
|
| +
|
| + Raises:
|
| + RuntimeError: If a report cannot be sent at this time.
|
| + """
|
| + if not self.IsConnected():
|
| + raise RuntimeError('Device is not connected.')
|
| + self._gadget.SendReport(self._report_id, data)
|
| +
|
| + def SetInputReport(self, data):
|
| + """Handle an input report sent from the host.
|
| +
|
| + This function is called when a SET_REPORT(input) command for this class's
|
| + Report ID is received. It should be overridden by a subclass.
|
| +
|
| + Args:
|
| + data: Contents of the input report.
|
| + """
|
| + pass # pragma: no cover
|
| +
|
| + def SetOutputReport(self, data):
|
| + """Handle an feature report sent from the host.
|
| +
|
| + This function is called when a SET_REPORT(output) command or interrupt OUT
|
| + transfer is received with this class's Report ID. It should be overridden
|
| + by a subclass.
|
| +
|
| + Args:
|
| + data: Contents of the output report.
|
| + """
|
| + pass # pragma: no cover
|
| +
|
| + def SetFeatureReport(self, data):
|
| + """Handle an feature report sent from the host.
|
| +
|
| + This function is called when a SET_REPORT(feature) command for this class's
|
| + Report ID is received. It should be overridden by a subclass.
|
| +
|
| + Args:
|
| + data: Contents of the feature report.
|
| + """
|
| + pass # pragma: no cover
|
| +
|
| + def GetInputReport(self):
|
| + """Handle a input report request from the host.
|
| +
|
| + This function is called when a GET_REPORT(input) command for this class's
|
| + Report ID is received. It should be overridden by a subclass.
|
| +
|
| + Returns:
|
| + The input report or None to stall the pipe.
|
| + """
|
| + pass # pragma: no cover
|
| +
|
| + def GetOutputReport(self):
|
| + """Handle a output report request from the host.
|
| +
|
| + This function is called when a GET_REPORT(output) command for this class's
|
| + Report ID is received. It should be overridden by a subclass.
|
| +
|
| + Returns:
|
| + The output report or None to stall the pipe.
|
| + """
|
| + pass # pragma: no cover
|
| +
|
| + def GetFeatureReport(self):
|
| + """Handle a feature report request from the host.
|
| +
|
| + This function is called when a GET_REPORT(feature) command for this class's
|
| + Report ID is received. It should be overridden by a subclass.
|
| +
|
| + Returns:
|
| + The feature report or None to stall the pipe.
|
| + """
|
| + pass # pragma: no cover
|
|
|