Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / usb_gadget / hid_gadget.py
blob0b632a1ba948f83563315f3699d4037a13a5fd80
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Human Interface Device gadget module.
7 This gadget emulates a USB Human Interface Device. Multiple logical components
8 of a device can be composed together as separate "features" where each has its
9 own Report ID and will be called upon to answer get/set input/output/feature
10 report requests as necessary.
11 """
13 import math
14 import struct
15 import uuid
17 import composite_gadget
18 import hid_constants
19 import usb_constants
20 import usb_descriptors
23 class HidCompositeFeature(composite_gadget.CompositeFeature):
24 """Generic HID feature for a composite device.
25 """
27 def __init__(self, report_desc, features,
28 packet_size=64, interval_ms=10, interface_number=0,
29 interface_string=0,
30 in_endpoint=0x81, out_endpoint=0x01):
31 """Create a composite device feature implementing the HID protocol.
33 Args:
34 report_desc: HID report descriptor.
35 features: Map between Report IDs and HidFeature objects to handle them.
36 packet_size: Maximum interrupt packet size.
37 interval_ms: Interrupt transfer interval in milliseconds.
38 interface_number: Interface number for this feature (default 0).
39 in_endpoint: Endpoint number for the IN endpoint (defualt 0x81).
40 out_endpoint: Endpoint number for the OUT endpoint or None to disable
41 the endpoint (default 0x01).
43 Raises:
44 ValueError: If any of the parameters are out of range.
45 """
46 fs_interface_desc = usb_descriptors.InterfaceDescriptor(
47 bInterfaceNumber=interface_number,
48 bInterfaceClass=usb_constants.DeviceClass.HID,
49 bInterfaceSubClass=0, # Non-bootable.
50 bInterfaceProtocol=0, # None.
51 iInterface=interface_string,
54 hs_interface_desc = usb_descriptors.InterfaceDescriptor(
55 bInterfaceNumber=interface_number,
56 bInterfaceClass=usb_constants.DeviceClass.HID,
57 bInterfaceSubClass=0, # Non-bootable.
58 bInterfaceProtocol=0, # None.
59 iInterface=interface_string,
62 hid_desc = usb_descriptors.HidDescriptor()
63 hid_desc.AddDescriptor(hid_constants.DescriptorType.REPORT,
64 len(report_desc))
65 fs_interface_desc.Add(hid_desc)
66 hs_interface_desc.Add(hid_desc)
68 fs_interval = math.ceil(math.log(interval_ms, 2)) + 1
69 if fs_interval < 1 or fs_interval > 16:
70 raise ValueError('Full speed interval out of range: {} ({} ms)'
71 .format(fs_interval, interval_ms))
73 fs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
74 bEndpointAddress=in_endpoint,
75 bmAttributes=usb_constants.TransferType.INTERRUPT,
76 wMaxPacketSize=packet_size,
77 bInterval=fs_interval
80 hs_interval = math.ceil(math.log(interval_ms, 2)) + 4
81 if hs_interval < 1 or hs_interval > 16:
82 raise ValueError('High speed interval out of range: {} ({} ms)'
83 .format(hs_interval, interval_ms))
85 hs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
86 bEndpointAddress=in_endpoint,
87 bmAttributes=usb_constants.TransferType.INTERRUPT,
88 wMaxPacketSize=packet_size,
89 bInterval=hs_interval
92 if out_endpoint is not None:
93 fs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
94 bEndpointAddress=out_endpoint,
95 bmAttributes=usb_constants.TransferType.INTERRUPT,
96 wMaxPacketSize=packet_size,
97 bInterval=fs_interval
99 hs_interface_desc.AddEndpoint(usb_descriptors.EndpointDescriptor(
100 bEndpointAddress=out_endpoint,
101 bmAttributes=usb_constants.TransferType.INTERRUPT,
102 wMaxPacketSize=packet_size,
103 bInterval=hs_interval
106 super(HidCompositeFeature, self).__init__(
107 [fs_interface_desc], [hs_interface_desc])
108 self._report_desc = report_desc
109 self._features = features
110 self._interface_number = interface_number
111 self._in_endpoint = in_endpoint
112 self._out_endpoint = out_endpoint
114 def Connected(self, gadget):
115 super(HidCompositeFeature, self).Connected(gadget)
116 for report_id, feature in self._features.iteritems():
117 feature.Connected(self, report_id)
119 def Disconnected(self):
120 super(HidCompositeFeature, self).Disconnected()
121 for feature in self._features.itervalues():
122 feature.Disconnected()
124 def StandardControlRead(self, recipient, request, value, index, length):
125 if recipient == usb_constants.Recipient.INTERFACE:
126 if index == self._interface_number:
127 desc_type = value >> 8
128 desc_index = value & 0xff
129 if desc_type == hid_constants.DescriptorType.REPORT:
130 if desc_index == 0:
131 return self._report_desc[:length]
133 return super(HidCompositeFeature, self).StandardControlRead(
134 recipient, request, value, index, length)
136 def ClassControlRead(self, recipient, request, value, index, length):
137 """Handle class-specific control requests.
139 See Device Class Definition for Human Interface Devices (HID) Version 1.11
140 section 7.2.
142 Args:
143 recipient: Request recipient (device, interface, endpoint, etc.)
144 request: bRequest field of the setup packet.
145 value: wValue field of the setup packet.
146 index: wIndex field of the setup packet.
147 length: Maximum amount of data the host expects the device to return.
149 Returns:
150 A buffer to return to the USB host with len <= length on success or
151 None to stall the pipe.
153 if recipient != usb_constants.Recipient.INTERFACE:
154 return None
155 if index != self._interface_number:
156 return None
158 if request == hid_constants.Request.GET_REPORT:
159 report_type, report_id = value >> 8, value & 0xFF
160 print ('GetReport(type={}, id={}, length={})'
161 .format(report_type, report_id, length))
162 return self.GetReport(report_type, report_id, length)
164 def ClassControlWrite(self, recipient, request, value, index, data):
165 """Handle class-specific control requests.
167 See Device Class Definition for Human Interface Devices (HID) Version 1.11
168 section 7.2.
170 Args:
171 recipient: Request recipient (device, interface, endpoint, etc.)
172 request: bRequest field of the setup packet.
173 value: wValue field of the setup packet.
174 index: wIndex field of the setup packet.
175 data: Data stage of the request.
177 Returns:
178 True on success, None to stall the pipe.
180 if recipient != usb_constants.Recipient.INTERFACE:
181 return None
182 if index != self._interface_number:
183 return None
185 if request == hid_constants.Request.SET_REPORT:
186 report_type, report_id = value >> 8, value & 0xFF
187 print('SetReport(type={}, id={}, length={})'
188 .format(report_type, report_id, len(data)))
189 return self.SetReport(report_type, report_id, data)
190 elif request == hid_constants.Request.SET_IDLE:
191 duration, report_id = value >> 8, value & 0xFF
192 print('SetIdle(duration={}, report={})'
193 .format(duration, report_id))
194 return True
196 def GetReport(self, report_type, report_id, length):
197 """Handle GET_REPORT requests.
199 See Device Class Definition for Human Interface Devices (HID) Version 1.11
200 section 7.2.1.
202 Args:
203 report_type: Requested report type.
204 report_id: Requested report ID.
205 length: Maximum amount of data the host expects the device to return.
207 Returns:
208 A buffer to return to the USB host with len <= length on success or
209 None to stall the pipe.
211 feature = self._features.get(report_id, None)
212 if feature is None:
213 return None
215 if report_type == hid_constants.ReportType.INPUT:
216 return feature.GetInputReport()[:length]
217 elif report_type == hid_constants.ReportType.OUTPUT:
218 return feature.GetOutputReport()[:length]
219 elif report_type == hid_constants.ReportType.FEATURE:
220 return feature.GetFeatureReport()[:length]
222 def SetReport(self, report_type, report_id, data):
223 """Handle SET_REPORT requests.
225 See Device Class Definition for Human Interface Devices (HID) Version 1.11
226 section 7.2.2.
228 Args:
229 report_type: Report type.
230 report_id: Report ID.
231 data: Report data.
233 Returns:
234 True on success, None to stall the pipe.
236 feature = self._features.get(report_id, None)
237 if feature is None:
238 return None
240 if report_type == hid_constants.ReportType.INPUT:
241 return feature.SetInputReport(data)
242 elif report_type == hid_constants.ReportType.OUTPUT:
243 return feature.SetOutputReport(data)
244 elif report_type == hid_constants.ReportType.FEATURE:
245 return feature.SetFeatureReport(data)
247 def SendReport(self, report_id, data):
248 """Send a HID report.
250 See Device Class Definition for Human Interface Devices (HID) Version 1.11
251 section 8.
253 Args:
254 report_id: Report ID associated with the data.
255 data: Contents of the report.
257 if report_id == 0:
258 self.SendPacket(self._in_endpoint, data)
259 else:
260 self.SendPacket(self._in_endpoint, struct.pack('B', report_id) + data)
262 def ReceivePacket(self, endpoint, data):
263 """Dispatch a report to the appropriate feature.
265 See Device Class Definition for Human Interface Devices (HID) Version 1.11
266 section 8.
268 Args:
269 endpoint: Incoming endpoint (must be the Interrupt OUT pipe).
270 data: Interrupt packet data.
272 assert endpoint == self._out_endpoint
274 if 0 in self._features:
275 self._features[0].SetOutputReport(data)
276 elif len(data) >= 1:
277 report_id, = struct.unpack('B', data[0])
278 feature = self._features.get(report_id, None)
279 if feature is None or feature.SetOutputReport(data[1:]) is None:
280 self.HaltEndpoint(endpoint)
283 class HidFeature(object):
284 """Represents a component of a HID gadget.
286 A "feature" produces and consumes reports with a particular Report ID. For
287 example a keyboard, mouse or vendor specific functionality.
290 def __init__(self):
291 self._gadget = None
292 self._report_id = None
294 def Connected(self, my_gadget, report_id):
295 self._gadget = my_gadget
296 self._report_id = report_id
298 def Disconnected(self):
299 self._gadget = None
300 self._report_id = None
302 def IsConnected(self):
303 return self._gadget is not None
305 def SendReport(self, data):
306 """Send a report with this feature's Report ID.
308 Args:
309 data: Report to send. If necessary the Report ID will be added.
311 Raises:
312 RuntimeError: If a report cannot be sent at this time.
314 if not self.IsConnected():
315 raise RuntimeError('Device is not connected.')
316 self._gadget.SendReport(self._report_id, data)
318 def SetInputReport(self, data):
319 """Handle an input report sent from the host.
321 This function is called when a SET_REPORT(input) command for this class's
322 Report ID is received. It should be overridden by a subclass.
324 Args:
325 data: Contents of the input report.
327 pass # pragma: no cover
329 def SetOutputReport(self, data):
330 """Handle an feature report sent from the host.
332 This function is called when a SET_REPORT(output) command or interrupt OUT
333 transfer is received with this class's Report ID. It should be overridden
334 by a subclass.
336 Args:
337 data: Contents of the output report.
339 pass # pragma: no cover
341 def SetFeatureReport(self, data):
342 """Handle an feature report sent from the host.
344 This function is called when a SET_REPORT(feature) command for this class's
345 Report ID is received. It should be overridden by a subclass.
347 Args:
348 data: Contents of the feature report.
350 pass # pragma: no cover
352 def GetInputReport(self):
353 """Handle a input report request from the host.
355 This function is called when a GET_REPORT(input) command for this class's
356 Report ID is received. It should be overridden by a subclass.
358 Returns:
359 The input report or None to stall the pipe.
361 pass # pragma: no cover
363 def GetOutputReport(self):
364 """Handle a output report request from the host.
366 This function is called when a GET_REPORT(output) command for this class's
367 Report ID is received. It should be overridden by a subclass.
369 Returns:
370 The output report or None to stall the pipe.
372 pass # pragma: no cover
374 def GetFeatureReport(self):
375 """Handle a feature report request from the host.
377 This function is called when a GET_REPORT(feature) command for this class's
378 Report ID is received. It should be overridden by a subclass.
380 Returns:
381 The feature report or None to stall the pipe.
383 pass # pragma: no cover
385 class HidGadget(composite_gadget.CompositeGadget):
386 """Generic HID gadget.
389 def __init__(self, report_desc, features, vendor_id, product_id,
390 packet_size=64, interval_ms=10, out_endpoint=True,
391 device_version=0x0100):
392 """Create a HID gadget.
394 Args:
395 report_desc: HID report descriptor.
396 features: Map between Report IDs and HidFeature objects to handle them.
397 vendor_id: Device Vendor ID.
398 product_id: Device Product ID.
399 packet_size: Maximum interrupt packet size.
400 interval_ms: Interrupt transfer interval in milliseconds.
401 out_endpoint: Should this device have an interrupt OUT endpoint?
402 device_version: Device version number.
404 Raises:
405 ValueError: If any of the parameters are out of range.
407 device_desc = usb_descriptors.DeviceDescriptor(
408 idVendor=vendor_id,
409 idProduct=product_id,
410 bcdUSB=0x0200,
411 iManufacturer=1,
412 iProduct=2,
413 iSerialNumber=3,
414 bcdDevice=device_version)
416 if out_endpoint:
417 out_endpoint = 0x01
418 else:
419 out_endpoint = None
421 self._hid_feature = HidCompositeFeature(
422 report_desc=report_desc,
423 features=features,
424 packet_size=packet_size,
425 interval_ms=interval_ms,
426 out_endpoint=out_endpoint)
428 super(HidGadget, self).__init__(device_desc, [self._hid_feature])
429 self.AddStringDescriptor(3, '{:06X}'.format(uuid.getnode()))
431 def SendReport(self, report_id, data):
432 self._hid_feature.SendReport(report_id, data)