Skip to content

Commit dc4e5cb

Browse files
authored
Merge pull request #5481 from dhalbert/hid-boot-protocol
HID Boot device support; HID feature report support
2 parents 2e0be0b + 11c8080 commit dc4e5cb

File tree

10 files changed

+186
-27
lines changed

10 files changed

+186
-27
lines changed

locale/circuitpython.pot

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,10 @@ msgstr ""
551551
msgid "Bit depth must be multiple of 8."
552552
msgstr ""
553553

554+
#: supervisor/shared/safe_mode.c
555+
msgid "Boot device must be first device (interface #0)."
556+
msgstr ""
557+
554558
#: ports/mimxrt10xx/common-hal/busio/UART.c
555559
msgid "Both RX and TX required for flow control"
556560
msgstr ""

shared-bindings/usb_hid/Device.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050
//| Use a size of ``0`` for a report that is not an OUT report.
5151
//| "OUT" is with respect to the host.
5252
//|
53-
//| ``report_ids``, ``in_report_lengths``, and ``out_report_lengths`` must all be the same length.
53+
//| ``report_ids``, ``in_report_lengths``, and ``out_report_lengths`` must all have the
54+
//| same number of elements.
5455
//|
5556
//| Here is an example of a `Device` with a descriptor that specifies two report IDs, 3 and 4.
5657
//| Report ID 3 sends an IN report of length 5, and receives an OUT report of length 6.
@@ -192,7 +193,7 @@ STATIC mp_obj_t usb_hid_device_send_report(size_t n_args, const mp_obj_t *pos_ar
192193
MP_DEFINE_CONST_FUN_OBJ_KW(usb_hid_device_send_report_obj, 1, usb_hid_device_send_report);
193194

194195
//| def get_last_received_report(self, report_id: Optional[int] = None) -> bytes:
195-
//| """Get the last received HID OUT report for the given report ID.
196+
//| """Get the last received HID OUT or feature report for the given report ID.
196197
//| The report ID may be omitted if there is no report ID, or only one report ID.
197198
//| Return `None` if nothing received.
198199
//| """

shared-bindings/usb_hid/__init__.c

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@
4040
//| """Tuple of all active HID device interfaces.
4141
//| The default set of devices is ``Device.KEYBOARD, Device.MOUSE, Device.CONSUMER_CONTROL``,
4242
//| On boards where `usb_hid` is disabled by default, `devices` is an empty tuple.
43+
//|
44+
//| If a boot device is enabled by `usb_hid.enable()`, *and* the host has requested a boot device,
45+
//| the `devices` tuple is **replaced** when ``code.py`` starts with a single-element tuple
46+
//| containing a `Device` that describes the boot device chosen (keyboard or mouse).
47+
//| The request for a boot device overrides any other HID devices.
4348
//| """
4449
//|
4550

@@ -60,23 +65,61 @@ STATIC mp_obj_t usb_hid_disable(void) {
6065
}
6166
MP_DEFINE_CONST_FUN_OBJ_0(usb_hid_disable_obj, usb_hid_disable);
6267

63-
//| def enable(devices: Optional[Sequence[Device]]) -> None:
68+
//| def enable(devices: Optional[Sequence[Device]], boot_device: int = 0) -> None:
6469
//| """Specify which USB HID devices that will be available.
6570
//| Can be called in ``boot.py``, before USB is connected.
6671
//|
6772
//| :param Sequence devices: `Device` objects.
6873
//| If `devices` is empty, HID is disabled. The order of the ``Devices``
6974
//| may matter to the host. For instance, for MacOS, put the mouse device
7075
//| before any Gamepad or Digitizer HID device or else it will not work.
76+
//| :param int boot_device: If non-zero, inform the host that support for a
77+
//| a boot HID device is available.
78+
//| If ``boot_device=1``, a boot keyboard is available.
79+
//| If ``boot_device=2``, a boot mouse is available. No other values are allowed.
80+
//| See below.
7181
//|
7282
//| If you enable too many devices at once, you will run out of USB endpoints.
7383
//| The number of available endpoints varies by microcontroller.
7484
//| CircuitPython will go into safe mode after running ``boot.py`` to inform you if
7585
//| not enough endpoints are available.
86+
//|
87+
//| **Boot Devices**
88+
//|
89+
//| Boot devices implement a fixed, predefined report descriptor, defined in
90+
//| https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host
91+
//| can request to use the boot device if the USB device says it is available.
92+
//| Usually only a BIOS or other kind of limited-functionality
93+
//| host needs boot keyboard support.
94+
//|
95+
//| For example, to make a boot keyboard available, you can use this code::
96+
//|
97+
//| usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard
98+
//|
99+
//| If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD`
100+
//| will be ignored, and the predefined report descriptor will be used.
101+
//| But if the host does not request the boot keyboard,
102+
//| the descriptor provided by `Device.KEYBOARD` will be used.
103+
//|
104+
//| The HID boot device must usually be the first or only device presented by CircuitPython.
105+
//| The HID device will be USB interface number 0.
106+
//| To make sure it is the first device, disable other USB devices, including CDC and MSC (CIRCUITPY).
107+
//| If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython
108+
//| will enter safe mode to report this error.
76109
//| """
77110
//| ...
78111
//|
79-
STATIC mp_obj_t usb_hid_enable(mp_obj_t devices) {
112+
STATIC mp_obj_t usb_hid_enable(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
113+
enum { ARG_devices, ARG_boot_device };
114+
static const mp_arg_t allowed_args[] = {
115+
{ MP_QSTR_devices, MP_ARG_REQUIRED | MP_ARG_OBJ },
116+
{ MP_QSTR_boot_device, MP_ARG_INT, {.u_int = 0} },
117+
};
118+
119+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
120+
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
121+
122+
mp_obj_t devices = args[ARG_devices].u_obj;
80123
const mp_int_t len = mp_obj_get_int(mp_obj_len(devices));
81124
for (mp_int_t i = 0; i < len; i++) {
82125
mp_obj_t item = mp_obj_subscr(devices, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL);
@@ -85,21 +128,44 @@ STATIC mp_obj_t usb_hid_enable(mp_obj_t devices) {
85128
}
86129
}
87130

88-
if (!common_hal_usb_hid_enable(devices)) {
131+
uint8_t boot_device =
132+
(uint8_t)mp_arg_validate_int_range(args[ARG_boot_device].u_int, 0, 2, MP_QSTR_boot_device);
133+
134+
if (!common_hal_usb_hid_enable(devices, boot_device)) {
89135
mp_raise_RuntimeError(translate("Cannot change USB devices now"));
90136
}
91137

92138
return mp_const_none;
93139
}
94-
MP_DEFINE_CONST_FUN_OBJ_1(usb_hid_enable_obj, usb_hid_enable);
140+
MP_DEFINE_CONST_FUN_OBJ_KW(usb_hid_enable_obj, 1, usb_hid_enable);
141+
142+
//| def get_boot_device() -> int:
143+
//| """
144+
//| :return: the boot device requested by the host, if any.
145+
//| Returns 0 if the host did not request a boot device, or if `usb_hid.enable()`
146+
//| was called with ``boot_device=0``, the default, which disables boot device support.
147+
//| If the host did request a boot device,
148+
//| returns the value of ``boot_device`` set in `usb_hid.enable()`:
149+
//| ``1`` for a boot keyboard, or ``2`` for boot mouse.
150+
//| However, the standard devices provided by CircuitPython, `Device.KEYBOARD` and `Device.MOUSE`,
151+
//| describe reports that match the boot device reports, so you don't need to check this
152+
//| if you are using those devices.
153+
//| :rtype int:
154+
//| """
155+
//|
156+
STATIC mp_obj_t usb_hid_get_boot_device(void) {
157+
return MP_OBJ_NEW_SMALL_INT(common_hal_usb_hid_get_boot_device());
158+
}
159+
MP_DEFINE_CONST_FUN_OBJ_0(usb_hid_get_boot_device_obj, usb_hid_get_boot_device);
95160

96161
// usb_hid.devices is set once the usb devices are determined, after boot.py runs.
97162
STATIC mp_map_elem_t usb_hid_module_globals_table[] = {
98-
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb_hid) },
99-
{ MP_ROM_QSTR(MP_QSTR_Device), MP_OBJ_FROM_PTR(&usb_hid_device_type) },
100-
{ MP_ROM_QSTR(MP_QSTR_devices), mp_const_none },
101-
{ MP_ROM_QSTR(MP_QSTR_disable), MP_OBJ_FROM_PTR(&usb_hid_disable_obj) },
102-
{ MP_ROM_QSTR(MP_QSTR_enable), MP_OBJ_FROM_PTR(&usb_hid_enable_obj) },
163+
{ MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_usb_hid) },
164+
{ MP_ROM_QSTR(MP_QSTR_Device), MP_OBJ_FROM_PTR(&usb_hid_device_type) },
165+
{ MP_ROM_QSTR(MP_QSTR_devices), mp_const_none },
166+
{ MP_ROM_QSTR(MP_QSTR_disable), MP_OBJ_FROM_PTR(&usb_hid_disable_obj) },
167+
{ MP_ROM_QSTR(MP_QSTR_enable), MP_OBJ_FROM_PTR(&usb_hid_enable_obj) },
168+
{ MP_ROM_QSTR(MP_QSTR_get_boot_device), MP_OBJ_FROM_PTR(&usb_hid_get_boot_device_obj) },
103169
};
104170

105171
STATIC MP_DEFINE_MUTABLE_DICT(usb_hid_module_globals, usb_hid_module_globals_table);

shared-bindings/usb_hid/__init__.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ extern mp_obj_tuple_t common_hal_usb_hid_devices;
3636
void usb_hid_set_devices(mp_obj_t devices);
3737

3838
bool common_hal_usb_hid_disable(void);
39-
bool common_hal_usb_hid_enable(const mp_obj_t devices_seq);
39+
bool common_hal_usb_hid_enable(const mp_obj_t devices_seq, uint8_t boot_device);
40+
uint8_t common_hal_usb_hid_get_boot_device(void);
4041

4142
#endif // SHARED_BINDINGS_USB_HID_H

shared-module/usb_hid/Device.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ void common_hal_usb_hid_device_send_report(usb_hid_device_obj_t *self, uint8_t *
244244
}
245245

246246
mp_obj_t common_hal_usb_hid_device_get_last_received_report(usb_hid_device_obj_t *self, uint8_t report_id) {
247-
// report_id has already been validated for this deveice.
247+
// report_id has already been validated for this device.
248248
size_t id_idx = get_report_id_idx(self, report_id);
249249
return mp_obj_new_bytes(self->out_report_buffers[id_idx], self->out_report_lengths[id_idx]);
250250
}
@@ -266,11 +266,11 @@ void usb_hid_device_create_report_buffers(usb_hid_device_obj_t *self) {
266266
}
267267

268268

269-
// Callbacks invoked when we received Get_Report request through control endpoint
269+
// Callback invoked when we receive Get_Report request through control endpoint
270270
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
271271
(void)itf;
272-
// only support Input Report
273-
if (report_type != HID_REPORT_TYPE_INPUT) {
272+
// Support Input Report and Feature Report
273+
if (report_type != HID_REPORT_TYPE_INPUT && report_type != HID_REPORT_TYPE_FEATURE) {
274274
return 0;
275275
}
276276

@@ -289,14 +289,14 @@ uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t
289289
return 0;
290290
}
291291

292-
// Callbacks invoked when we received Set_Report request through control endpoint
292+
// Callback invoked when we receive Set_Report request through control endpoint
293293
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
294294
(void)itf;
295295
if (report_type == HID_REPORT_TYPE_INVALID) {
296296
report_id = buffer[0];
297297
buffer++;
298298
bufsize--;
299-
} else if (report_type != HID_REPORT_TYPE_OUTPUT) {
299+
} else if (report_type != HID_REPORT_TYPE_OUTPUT && report_type != HID_REPORT_TYPE_FEATURE) {
300300
return;
301301
}
302302

shared-module/usb_hid/__init__.c

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include "py/gc.h"
3232
#include "py/runtime.h"
3333
#include "shared-bindings/usb_hid/__init__.h"
34-
#include "shared-module/usb_hid/Device.h"
34+
#include "shared-bindings/usb_hid/Device.h"
3535
#include "supervisor/memory.h"
3636
#include "supervisor/usb.h"
3737

@@ -44,7 +44,9 @@ static const uint8_t usb_hid_descriptor_template[] = {
4444
0x02, // 4 bNumEndpoints 2
4545
0x03, // 5 bInterfaceClass: HID
4646
0x00, // 6 bInterfaceSubClass: NOBOOT
47+
#define HID_DESCRIPTOR_SUBCLASS_INDEX (6)
4748
0x00, // 7 bInterfaceProtocol: NONE
49+
#define HID_DESCRIPTOR_INTERFACE_PROTOCOL_INDEX (7)
4850
0xFF, // 8 iInterface (String Index) [SET AT RUNTIME]
4951
#define HID_DESCRIPTOR_INTERFACE_STRING_INDEX (8)
5052

@@ -81,6 +83,14 @@ static usb_hid_device_obj_t hid_devices[MAX_HID_DEVICES];
8183
// If 0, USB HID is disabled.
8284
static mp_int_t num_hid_devices;
8385

86+
// Which boot device is available? 0: no boot devices, 1: boot keyboard, 2: boot mouse.
87+
// This value is set by usb_hid.enable(), and used to build the HID interface descriptor.
88+
// The value is remembered here from boot.py to code.py.
89+
static uint8_t hid_boot_device;
90+
91+
// Whether a boot device was requested by a SET_PROTOCOL request from the host.
92+
static bool hid_boot_device_requested;
93+
8494
// This tuple is store in usb_hid.devices.
8595
static mp_obj_tuple_t *hid_devices_tuple;
8696

@@ -96,13 +106,56 @@ static mp_obj_tuple_t default_hid_devices_tuple = {
96106
},
97107
};
98108

109+
// These describe the standard descriptors used for boot keyboard and mouse, which don't use report IDs.
110+
// When the host requests a boot device, replace whatever HID devices were enabled with a tuple
111+
// containing just one of these, since the host is uninterested in other devices.
112+
// The driver code will then use the proper report length and send_report() will not send a report ID.
113+
static const usb_hid_device_obj_t boot_keyboard_obj = {
114+
.base = {
115+
.type = &usb_hid_device_type,
116+
},
117+
.report_descriptor = NULL,
118+
.report_descriptor_length = 0,
119+
.usage_page = 0x01,
120+
.usage = 0x06,
121+
.num_report_ids = 1,
122+
.report_ids = { 0, },
123+
.in_report_lengths = { 8, },
124+
.out_report_lengths = { 1, },
125+
};
126+
127+
static const usb_hid_device_obj_t boot_mouse_obj = {
128+
.base = {
129+
.type = &usb_hid_device_type,
130+
},
131+
.report_descriptor = NULL,
132+
.report_descriptor_length = 0,
133+
.usage_page = 0x01,
134+
.usage = 0x02,
135+
.num_report_ids = 1,
136+
.report_ids = { 0, },
137+
.in_report_lengths = { 4, },
138+
.out_report_lengths = { 0, },
139+
};
140+
99141
bool usb_hid_enabled(void) {
100142
return num_hid_devices > 0;
101143
}
102144

145+
uint8_t usb_hid_boot_device(void) {
146+
return hid_boot_device;
147+
}
148+
149+
// Returns 1 or 2 if host requested a boot device and boot protocol was enabled in the interface descriptor.
150+
uint8_t common_hal_usb_hid_get_boot_device(void) {
151+
return hid_boot_device_requested ? hid_boot_device : 0;
152+
}
153+
103154
void usb_hid_set_defaults(void) {
155+
hid_boot_device = 0;
156+
hid_boot_device_requested = false;
104157
common_hal_usb_hid_enable(
105-
CIRCUITPY_USB_HID_ENABLED_DEFAULT ? &default_hid_devices_tuple : mp_const_empty_tuple);
158+
CIRCUITPY_USB_HID_ENABLED_DEFAULT ? &default_hid_devices_tuple : mp_const_empty_tuple, 0);
106159
}
107160

108161
// This is the interface descriptor, not the report descriptor.
@@ -113,12 +166,17 @@ size_t usb_hid_descriptor_length(void) {
113166
static const char usb_hid_interface_name[] = USB_INTERFACE_NAME " HID";
114167

115168
// This is the interface descriptor, not the report descriptor.
116-
size_t usb_hid_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string, uint16_t report_descriptor_length) {
169+
size_t usb_hid_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string, uint16_t report_descriptor_length, uint8_t boot_device) {
117170
memcpy(descriptor_buf, usb_hid_descriptor_template, sizeof(usb_hid_descriptor_template));
118171

119172
descriptor_buf[HID_DESCRIPTOR_INTERFACE_INDEX] = descriptor_counts->current_interface;
120173
descriptor_counts->current_interface++;
121174

175+
if (boot_device > 0) {
176+
descriptor_buf[HID_DESCRIPTOR_SUBCLASS_INDEX] = 1; // BOOT protocol (device) available.
177+
descriptor_buf[HID_DESCRIPTOR_INTERFACE_PROTOCOL_INDEX] = boot_device; // 1: keyboard, 2: mouse
178+
}
179+
122180
usb_add_interface_string(*current_interface_string, usb_hid_interface_name);
123181
descriptor_buf[HID_DESCRIPTOR_INTERFACE_STRING_INDEX] = *current_interface_string;
124182
(*current_interface_string)++;
@@ -151,10 +209,10 @@ static void usb_hid_set_devices_from_hid_devices(void) {
151209
}
152210

153211
bool common_hal_usb_hid_disable(void) {
154-
return common_hal_usb_hid_enable(mp_const_empty_tuple);
212+
return common_hal_usb_hid_enable(mp_const_empty_tuple, 0);
155213
}
156214

157-
bool common_hal_usb_hid_enable(const mp_obj_t devices) {
215+
bool common_hal_usb_hid_enable(const mp_obj_t devices, uint8_t boot_device) {
158216
// We can't change the devices once we're connected.
159217
if (tud_connected()) {
160218
return false;
@@ -167,6 +225,8 @@ bool common_hal_usb_hid_enable(const mp_obj_t devices) {
167225

168226
num_hid_devices = num_devices;
169227

228+
hid_boot_device = boot_device;
229+
170230
// Remember the devices in static storage so they live across VMs.
171231
for (mp_int_t i = 0; i < num_hid_devices; i++) {
172232
// devices has already been validated to contain only usb_hid_device_obj_t objects.
@@ -182,6 +242,17 @@ bool common_hal_usb_hid_enable(const mp_obj_t devices) {
182242

183243
// Called when HID devices are ready to be used, when code.py or the REPL starts running.
184244
void usb_hid_setup_devices(void) {
245+
246+
// If the host requested a boot device, replace the current list of devices
247+
// with a single-element tuple containing the proper boot device.
248+
if (hid_boot_device_requested) {
249+
memcpy(&hid_devices[0],
250+
// Will be 1 (keyboard) or 2 (mouse).
251+
hid_boot_device == 1 ? &boot_keyboard_obj : &boot_mouse_obj,
252+
sizeof(usb_hid_device_obj_t));
253+
num_hid_devices = 1;
254+
}
255+
185256
usb_hid_set_devices_from_hid_devices();
186257

187258
// Create report buffers on the heap.
@@ -272,9 +343,15 @@ bool usb_hid_get_device_with_report_id(uint8_t report_id, usb_hid_device_obj_t *
272343
return false;
273344
}
274345

275-
// Invoked when GET HID REPORT DESCRIPTOR is received.
276-
// Application return pointer to descriptor
346+
// Callback invoked when we receive a GET HID REPORT DESCRIPTOR
347+
// Application returns pointer to descriptor
277348
// Descriptor contents must exist long enough for transfer to complete
278349
uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) {
279350
return (uint8_t *)hid_report_descriptor_allocation->ptr;
280351
}
352+
353+
// Callback invoked when we receive a SET_PROTOCOL request.
354+
// Protocol is either HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1)
355+
void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol) {
356+
hid_boot_device_requested = (protocol == HID_PROTOCOL_BOOT);
357+
}

shared-module/usb_hid/__init__.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@
3333
extern usb_hid_device_obj_t usb_hid_devices[];
3434

3535
bool usb_hid_enabled(void);
36+
uint8_t usb_hid_boot_device(void);
3637
void usb_hid_set_defaults(void);
3738

38-
size_t usb_hid_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string, uint16_t report_descriptor_length);
39+
size_t usb_hid_add_descriptor(uint8_t *descriptor_buf, descriptor_counts_t *descriptor_counts, uint8_t *current_interface_string, uint16_t report_descriptor_length, uint8_t boot_device);
3940
size_t usb_hid_descriptor_length(void);
4041
size_t usb_hid_report_descriptor_length(void);
4142

supervisor/shared/safe_mode.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ void print_safe_mode_message(safe_mode_t reason) {
169169
case USB_TOO_MANY_INTERFACE_NAMES:
170170
message = translate("USB devices specify too many interface names.");
171171
break;
172+
case USB_BOOT_DEVICE_NOT_INTERFACE_ZERO:
173+
message = translate("Boot device must be first device (interface #0).");
174+
break;
172175
case WATCHDOG_RESET:
173176
message = translate("Watchdog timer expired.");
174177
break;

0 commit comments

Comments
 (0)