aboutsummaryrefslogtreecommitdiff
path: root/circuitpython/shared-module/usb_hid
diff options
context:
space:
mode:
Diffstat (limited to 'circuitpython/shared-module/usb_hid')
-rw-r--r--circuitpython/shared-module/usb_hid/Device.c314
-rw-r--r--circuitpython/shared-module/usb_hid/Device.h56
-rw-r--r--circuitpython/shared-module/usb_hid/__init__.c357
-rw-r--r--circuitpython/shared-module/usb_hid/__init__.h52
4 files changed, 779 insertions, 0 deletions
diff --git a/circuitpython/shared-module/usb_hid/Device.c b/circuitpython/shared-module/usb_hid/Device.c
new file mode 100644
index 0000000..8a0c429
--- /dev/null
+++ b/circuitpython/shared-module/usb_hid/Device.c
@@ -0,0 +1,314 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 hathach for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "py/gc.h"
+#include "py/runtime.h"
+#include "shared-bindings/usb_hid/Device.h"
+#include "shared-module/usb_hid/__init__.h"
+#include "shared-module/usb_hid/Device.h"
+#include "supervisor/shared/translate.h"
+#include "supervisor/shared/tick.h"
+#include "tusb.h"
+
+static const uint8_t keyboard_report_descriptor[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x06, // Usage (Keyboard)
+ 0xA1, 0x01, // Collection (Application)
+ 0x85, 0x01, // Report ID (1)
+ 0x05, 0x07, // Usage Page (Kbrd/Keypad)
+ 0x19, 0xE0, // Usage Minimum (0xE0)
+ 0x29, 0xE7, // Usage Maximum (0xE7)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x75, 0x01, // Report Size (1)
+ 0x95, 0x08, // Report Count (8)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x08, // Report Size (8)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x03, // Report Count (3)
+ 0x75, 0x01, // Report Size (1)
+ 0x05, 0x08, // Usage Page (LEDs)
+ 0x19, 0x01, // Usage Minimum (Num Lock)
+ 0x29, 0x05, // Usage Maximum (Kana)
+ 0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x05, // Report Size (5)
+ 0x91, 0x01, // Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
+ 0x95, 0x06, // Report Count (6)
+ 0x75, 0x08, // Report Size (8)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x26, 0xFF, 0x00, // Logical Maximum (255)
+ 0x05, 0x07, // Usage Page (Kbrd/Keypad)
+ 0x19, 0x00, // Usage Minimum (0x00)
+ 0x2A, 0xFF, 0x00, // Usage Maximum (0xFF)
+ 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, // End Collection
+};
+
+const usb_hid_device_obj_t usb_hid_device_keyboard_obj = {
+ .base = {
+ .type = &usb_hid_device_type,
+ },
+ .report_descriptor = keyboard_report_descriptor,
+ .report_descriptor_length = sizeof(keyboard_report_descriptor),
+ .usage_page = 0x01,
+ .usage = 0x06,
+ .num_report_ids = 1,
+ .report_ids = { 0x01, },
+ .in_report_lengths = { 8, },
+ .out_report_lengths = { 1, },
+};
+
+static const uint8_t mouse_report_descriptor[] = {
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x02, // Usage (Mouse)
+ 0xA1, 0x01, // Collection (Application)
+ 0x09, 0x01, // Usage (Pointer)
+ 0xA1, 0x00, // Collection (Physical)
+ 0x85, 0x02, // 10, 11 Report ID (2)
+ 0x05, 0x09, // Usage Page (Button)
+ 0x19, 0x01, // Usage Minimum (0x01)
+ 0x29, 0x05, // Usage Maximum (0x05)
+ 0x15, 0x00, // Logical Minimum (0)
+ 0x25, 0x01, // Logical Maximum (1)
+ 0x95, 0x05, // Report Count (5)
+ 0x75, 0x01, // Report Size (1)
+ 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x95, 0x01, // Report Count (1)
+ 0x75, 0x03, // Report Size (3)
+ 0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
+ 0x09, 0x30, // Usage (X)
+ 0x09, 0x31, // Usage (Y)
+ 0x15, 0x81, // Logical Minimum (-127)
+ 0x25, 0x7F, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x02, // Report Count (2)
+ 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+ 0x09, 0x38, // Usage (Wheel)
+ 0x15, 0x81, // Logical Minimum (-127)
+ 0x25, 0x7F, // Logical Maximum (127)
+ 0x75, 0x08, // Report Size (8)
+ 0x95, 0x01, // Report Count (1)
+ 0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, // End Collection
+ 0xC0, // End Collection
+};
+
+const usb_hid_device_obj_t usb_hid_device_mouse_obj = {
+ .base = {
+ .type = &usb_hid_device_type,
+ },
+ .report_descriptor = mouse_report_descriptor,
+ .report_descriptor_length = sizeof(mouse_report_descriptor),
+ .usage_page = 0x01,
+ .usage = 0x02,
+ .num_report_ids = 1,
+ .report_ids = { 0x02, },
+ .in_report_lengths = { 4, },
+ .out_report_lengths = { 0, },
+};
+
+static const uint8_t consumer_control_report_descriptor[] = {
+ 0x05, 0x0C, // Usage Page (Consumer)
+ 0x09, 0x01, // Usage (Consumer Control)
+ 0xA1, 0x01, // Collection (Application)
+ 0x85, 0x03, // Report ID (3)
+ 0x75, 0x10, // Report Size (16)
+ 0x95, 0x01, // Report Count (1)
+ 0x15, 0x01, // Logical Minimum (1)
+ 0x26, 0x8C, 0x02, // Logical Maximum (652)
+ 0x19, 0x01, // Usage Minimum (Consumer Control)
+ 0x2A, 0x8C, 0x02, // Usage Maximum (AC Send)
+ 0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
+ 0xC0, // End Collection
+};
+
+const usb_hid_device_obj_t usb_hid_device_consumer_control_obj = {
+ .base = {
+ .type = &usb_hid_device_type,
+ },
+ .report_descriptor = consumer_control_report_descriptor,
+ .report_descriptor_length = sizeof(consumer_control_report_descriptor),
+ .usage_page = 0x0C,
+ .usage = 0x01,
+ .num_report_ids = 1,
+ .report_ids = { 0x03 },
+ .in_report_lengths = { 2, },
+ .out_report_lengths = { 0, },
+};
+
+STATIC size_t get_report_id_idx(usb_hid_device_obj_t *self, size_t report_id) {
+ for (size_t i = 0; i < self->num_report_ids; i++) {
+ if (report_id == self->report_ids[i]) {
+ return i;
+ }
+ }
+ return CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR;
+}
+
+// See if report_id is used by this device. If it is -1, then return the sole report id used by this device,
+// which might be 0 if no report_id was supplied.
+uint8_t common_hal_usb_hid_device_validate_report_id(usb_hid_device_obj_t *self, mp_int_t report_id_arg) {
+ if (report_id_arg == -1 && self->num_report_ids == 1) {
+ return self->report_ids[0];
+ }
+ if (!(report_id_arg >= 0 &&
+ get_report_id_idx(self, (size_t)report_id_arg) < CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR)) {
+ mp_raise_ValueError_varg(translate("Invalid %q"), MP_QSTR_report_id);
+ }
+ return (uint8_t)report_id_arg;
+}
+
+void common_hal_usb_hid_device_construct(usb_hid_device_obj_t *self, mp_obj_t report_descriptor, uint16_t usage_page, uint16_t usage, size_t num_report_ids, uint8_t *report_ids, uint8_t *in_report_lengths, uint8_t *out_report_lengths) {
+ if (num_report_ids > CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR) {
+ mp_raise_ValueError_varg(translate("More than %d report ids not supported"),
+ CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR);
+ }
+
+ // report buffer pointers are NULL at start, and are created when USB is initialized.
+ mp_buffer_info_t bufinfo;
+ mp_get_buffer_raise(report_descriptor, &bufinfo, MP_BUFFER_READ);
+ self->report_descriptor_length = bufinfo.len;
+
+ // Copy the raw descriptor bytes into a heap obj. We don't keep the Python descriptor object.
+
+ uint8_t *descriptor_bytes = gc_alloc(bufinfo.len, false, false);
+ memcpy(descriptor_bytes, bufinfo.buf, bufinfo.len);
+ self->report_descriptor = descriptor_bytes;
+
+ self->usage_page = usage_page;
+ self->usage = usage;
+ self->num_report_ids = num_report_ids;
+ memcpy(self->report_ids, report_ids, num_report_ids);
+ memcpy(self->in_report_lengths, in_report_lengths, num_report_ids);
+ memcpy(self->out_report_lengths, out_report_lengths, num_report_ids);
+}
+
+uint16_t common_hal_usb_hid_device_get_usage_page(usb_hid_device_obj_t *self) {
+ return self->usage_page;
+}
+
+uint16_t common_hal_usb_hid_device_get_usage(usb_hid_device_obj_t *self) {
+ return self->usage;
+}
+
+void common_hal_usb_hid_device_send_report(usb_hid_device_obj_t *self, uint8_t *report, uint8_t len, uint8_t report_id) {
+ // report_id and len have already been validated for this device.
+ size_t id_idx = get_report_id_idx(self, report_id);
+
+ if (len != self->in_report_lengths[id_idx]) {
+ mp_raise_ValueError_varg(translate("Buffer incorrect size. Should be %d bytes."),
+ self->in_report_lengths[id_idx]);
+ }
+
+ // Wait until interface is ready, timeout = 2 seconds
+ uint64_t end_ticks = supervisor_ticks_ms64() + 2000;
+ while ((supervisor_ticks_ms64() < end_ticks) && !tud_hid_ready()) {
+ RUN_BACKGROUND_TASKS;
+ }
+
+ if (!tud_hid_ready()) {
+ mp_raise_msg(&mp_type_OSError, translate("USB busy"));
+ }
+
+ if (!tud_hid_report(report_id, report, len)) {
+ mp_raise_msg(&mp_type_OSError, translate("USB error"));
+ }
+}
+
+mp_obj_t common_hal_usb_hid_device_get_last_received_report(usb_hid_device_obj_t *self, uint8_t report_id) {
+ // report_id has already been validated for this device.
+ size_t id_idx = get_report_id_idx(self, report_id);
+ return mp_obj_new_bytes(self->out_report_buffers[id_idx], self->out_report_lengths[id_idx]);
+}
+
+void usb_hid_device_create_report_buffers(usb_hid_device_obj_t *self) {
+ for (size_t i = 0; i < self->num_report_ids; i++) {
+ // The IN buffers are used only for tud_hid_get_report_cb(),
+ // which is an unusual case. Normally we can just pass the data directly with tud_hid_report().
+ self->in_report_buffers[i] =
+ self->in_report_lengths[i] > 0
+ ? gc_alloc(self->in_report_lengths[i], false, true /*long-lived*/)
+ : NULL;
+
+ self->out_report_buffers[i] =
+ self->out_report_lengths[i] > 0
+ ? gc_alloc(self->out_report_lengths[i], false, true /*long-lived*/)
+ : NULL;
+ }
+}
+
+
+// Callback invoked when we receive Get_Report request through control endpoint
+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) {
+ (void)itf;
+ // Support Input Report and Feature Report
+ if (report_type != HID_REPORT_TYPE_INPUT && report_type != HID_REPORT_TYPE_FEATURE) {
+ return 0;
+ }
+
+ // fill buffer with current report
+
+ usb_hid_device_obj_t *hid_device;
+ size_t id_idx;
+ // Find device with this report id, and get the report id index.
+ if (usb_hid_get_device_with_report_id(report_id, &hid_device, &id_idx)) {
+ // Make sure buffer exists before trying to copy into it.
+ if (hid_device->in_report_buffers[id_idx]) {
+ memcpy(buffer, hid_device->in_report_buffers[id_idx], reqlen);
+ return reqlen;
+ }
+ }
+ return 0;
+}
+
+// Callback invoked when we receive Set_Report request through control endpoint
+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) {
+ (void)itf;
+ if (report_type == HID_REPORT_TYPE_INVALID) {
+ report_id = buffer[0];
+ buffer++;
+ bufsize--;
+ } else if (report_type != HID_REPORT_TYPE_OUTPUT && report_type != HID_REPORT_TYPE_FEATURE) {
+ return;
+ }
+
+ usb_hid_device_obj_t *hid_device;
+ size_t id_idx;
+ // Find device with this report id, and get the report id index.
+ if (usb_hid_get_device_with_report_id(report_id, &hid_device, &id_idx)) {
+ // If a report of the correct size has been read, save it in the proper OUT report buffer.
+ if (hid_device &&
+ hid_device->out_report_buffers[id_idx] &&
+ hid_device->out_report_lengths[id_idx] >= bufsize) {
+ memcpy(hid_device->out_report_buffers[id_idx], buffer, bufsize);
+ }
+ }
+}
diff --git a/circuitpython/shared-module/usb_hid/Device.h b/circuitpython/shared-module/usb_hid/Device.h
new file mode 100644
index 0000000..f265712
--- /dev/null
+++ b/circuitpython/shared-module/usb_hid/Device.h
@@ -0,0 +1,56 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 hathach for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef SHARED_MODULE_USB_HID_DEVICE_H
+#define SHARED_MODULE_USB_HID_DEVICE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include "py/obj.h"
+
+typedef struct {
+ mp_obj_base_t base;
+ // Python buffer object whose contents are the descriptor.
+ const uint8_t *report_descriptor;
+ uint8_t *in_report_buffers[CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR];
+ uint8_t *out_report_buffers[CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR];
+ uint16_t report_descriptor_length;
+ uint8_t report_ids[CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR];
+ uint8_t in_report_lengths[CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR];
+ uint8_t out_report_lengths[CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR];
+ uint16_t usage_page;
+ uint16_t usage;
+ uint8_t num_report_ids;
+} usb_hid_device_obj_t;
+
+extern const usb_hid_device_obj_t usb_hid_device_keyboard_obj;
+extern const usb_hid_device_obj_t usb_hid_device_mouse_obj;
+extern const usb_hid_device_obj_t usb_hid_device_consumer_control_obj;
+
+void usb_hid_device_create_report_buffers(usb_hid_device_obj_t *self);
+
+#endif /* SHARED_MODULE_USB_HID_DEVICE_H */
diff --git a/circuitpython/shared-module/usb_hid/__init__.c b/circuitpython/shared-module/usb_hid/__init__.c
new file mode 100644
index 0000000..89a05c7
--- /dev/null
+++ b/circuitpython/shared-module/usb_hid/__init__.c
@@ -0,0 +1,357 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018 hathach for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "tusb.h"
+
+#include "py/gc.h"
+#include "py/runtime.h"
+#include "shared-bindings/usb_hid/__init__.h"
+#include "shared-bindings/usb_hid/Device.h"
+#include "supervisor/memory.h"
+#include "supervisor/usb.h"
+
+static const uint8_t usb_hid_descriptor_template[] = {
+ 0x09, // 0 bLength
+ 0x04, // 1 bDescriptorType (Interface)
+ 0xFF, // 2 bInterfaceNumber 3
+#define HID_DESCRIPTOR_INTERFACE_INDEX (2)
+ 0x00, // 3 bAlternateSetting
+ 0x02, // 4 bNumEndpoints 2
+ 0x03, // 5 bInterfaceClass: HID
+ 0x00, // 6 bInterfaceSubClass: NOBOOT
+#define HID_DESCRIPTOR_SUBCLASS_INDEX (6)
+ 0x00, // 7 bInterfaceProtocol: NONE
+#define HID_DESCRIPTOR_INTERFACE_PROTOCOL_INDEX (7)
+ 0xFF, // 8 iInterface (String Index) [SET AT RUNTIME]
+#define HID_DESCRIPTOR_INTERFACE_STRING_INDEX (8)
+
+ 0x09, // 9 bLength
+ 0x21, // 10 bDescriptorType (HID)
+ 0x11, 0x01, // 11,12 bcdHID 1.11
+ 0x00, // 13 bCountryCode
+ 0x01, // 14 bNumDescriptors
+ 0x22, // 15 bDescriptorType[0] (HID)
+ 0xFF, 0xFF, // 16,17 wDescriptorLength[0] [SET AT RUNTIME: lo, hi]
+#define HID_DESCRIPTOR_LENGTH_INDEX (16)
+
+ 0x07, // 18 bLength
+ 0x05, // 19 bDescriptorType (Endpoint)
+ 0xFF, // 20 bEndpointAddress (IN/D2H) [SET AT RUNTIME: 0x80 | endpoint]
+#define HID_IN_ENDPOINT_INDEX (20)
+ 0x03, // 21 bmAttributes (Interrupt)
+ 0x40, 0x00, // 22,23 wMaxPacketSize 64
+ 0x08, // 24 bInterval 8 (unit depends on device speed)
+
+ 0x07, // 25 bLength
+ 0x05, // 26 bDescriptorType (Endpoint)
+ 0xFF, // 27 bEndpointAddress (OUT/H2D) [SET AT RUNTIME]
+#define HID_OUT_ENDPOINT_INDEX (27)
+ 0x03, // 28 bmAttributes (Interrupt)
+ 0x40, 0x00, // 29,30 wMaxPacketSize 64
+ 0x08, // 31 bInterval 8 (unit depends on device speed)
+};
+
+#define MAX_HID_DEVICES 8
+
+static supervisor_allocation *hid_report_descriptor_allocation;
+static usb_hid_device_obj_t hid_devices[MAX_HID_DEVICES];
+// If 0, USB HID is disabled.
+static mp_int_t num_hid_devices;
+
+// Which boot device is available? 0: no boot devices, 1: boot keyboard, 2: boot mouse.
+// This value is set by usb_hid.enable(), and used to build the HID interface descriptor.
+// The value is remembered here from boot.py to code.py.
+static uint8_t hid_boot_device;
+
+// Whether a boot device was requested by a SET_PROTOCOL request from the host.
+static bool hid_boot_device_requested;
+
+// This tuple is store in usb_hid.devices.
+static mp_obj_tuple_t *hid_devices_tuple;
+
+static mp_obj_tuple_t default_hid_devices_tuple = {
+ .base = {
+ .type = &mp_type_tuple,
+ },
+ .len = 3,
+ .items = {
+ MP_OBJ_FROM_PTR(&usb_hid_device_keyboard_obj),
+ MP_OBJ_FROM_PTR(&usb_hid_device_mouse_obj),
+ MP_OBJ_FROM_PTR(&usb_hid_device_consumer_control_obj),
+ },
+};
+
+// These describe the standard descriptors used for boot keyboard and mouse, which don't use report IDs.
+// When the host requests a boot device, replace whatever HID devices were enabled with a tuple
+// containing just one of these, since the host is uninterested in other devices.
+// The driver code will then use the proper report length and send_report() will not send a report ID.
+static const usb_hid_device_obj_t boot_keyboard_obj = {
+ .base = {
+ .type = &usb_hid_device_type,
+ },
+ .report_descriptor = NULL,
+ .report_descriptor_length = 0,
+ .usage_page = 0x01,
+ .usage = 0x06,
+ .num_report_ids = 1,
+ .report_ids = { 0, },
+ .in_report_lengths = { 8, },
+ .out_report_lengths = { 1, },
+};
+
+static const usb_hid_device_obj_t boot_mouse_obj = {
+ .base = {
+ .type = &usb_hid_device_type,
+ },
+ .report_descriptor = NULL,
+ .report_descriptor_length = 0,
+ .usage_page = 0x01,
+ .usage = 0x02,
+ .num_report_ids = 1,
+ .report_ids = { 0, },
+ .in_report_lengths = { 4, },
+ .out_report_lengths = { 0, },
+};
+
+bool usb_hid_enabled(void) {
+ return num_hid_devices > 0;
+}
+
+uint8_t usb_hid_boot_device(void) {
+ return hid_boot_device;
+}
+
+// Returns 1 or 2 if host requested a boot device and boot protocol was enabled in the interface descriptor.
+uint8_t common_hal_usb_hid_get_boot_device(void) {
+ return hid_boot_device_requested ? hid_boot_device : 0;
+}
+
+void usb_hid_set_defaults(void) {
+ hid_boot_device = 0;
+ hid_boot_device_requested = false;
+ common_hal_usb_hid_enable(
+ CIRCUITPY_USB_HID_ENABLED_DEFAULT ? &default_hid_devices_tuple : mp_const_empty_tuple, 0);
+}
+
+// This is the interface descriptor, not the report descriptor.
+size_t usb_hid_descriptor_length(void) {
+ return sizeof(usb_hid_descriptor_template);
+}
+
+static const char usb_hid_interface_name[] = USB_INTERFACE_NAME " HID";
+
+// This is the interface descriptor, not the report descriptor.
+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) {
+ memcpy(descriptor_buf, usb_hid_descriptor_template, sizeof(usb_hid_descriptor_template));
+
+ descriptor_buf[HID_DESCRIPTOR_INTERFACE_INDEX] = descriptor_counts->current_interface;
+ descriptor_counts->current_interface++;
+
+ if (boot_device > 0) {
+ descriptor_buf[HID_DESCRIPTOR_SUBCLASS_INDEX] = 1; // BOOT protocol (device) available.
+ descriptor_buf[HID_DESCRIPTOR_INTERFACE_PROTOCOL_INDEX] = boot_device; // 1: keyboard, 2: mouse
+ }
+
+ usb_add_interface_string(*current_interface_string, usb_hid_interface_name);
+ descriptor_buf[HID_DESCRIPTOR_INTERFACE_STRING_INDEX] = *current_interface_string;
+ (*current_interface_string)++;
+
+ descriptor_buf[HID_DESCRIPTOR_LENGTH_INDEX] = report_descriptor_length & 0xFF;
+ descriptor_buf[HID_DESCRIPTOR_LENGTH_INDEX + 1] = (report_descriptor_length >> 8);
+
+ descriptor_buf[HID_IN_ENDPOINT_INDEX] =
+ 0x80 | (USB_HID_EP_NUM_IN ? USB_HID_EP_NUM_IN : descriptor_counts->current_endpoint);
+ descriptor_counts->num_in_endpoints++;
+ descriptor_buf[HID_OUT_ENDPOINT_INDEX] =
+ USB_HID_EP_NUM_OUT ? USB_HID_EP_NUM_OUT : descriptor_counts->current_endpoint;
+ descriptor_counts->num_out_endpoints++;
+ descriptor_counts->current_endpoint++;
+
+ return sizeof(usb_hid_descriptor_template);
+}
+
+// Make up a fresh tuple containing the device objects saved in the static
+// devices table. Save the tuple in usb_hid.devices.
+static void usb_hid_set_devices_from_hid_devices(void) {
+ mp_obj_t tuple_items[num_hid_devices];
+ for (mp_int_t i = 0; i < num_hid_devices; i++) {
+ tuple_items[i] = &hid_devices[i];
+ }
+
+ // Remember tuple for gc purposes.
+ hid_devices_tuple = mp_obj_new_tuple(num_hid_devices, tuple_items);
+ usb_hid_set_devices(hid_devices_tuple);
+}
+
+bool common_hal_usb_hid_disable(void) {
+ return common_hal_usb_hid_enable(mp_const_empty_tuple, 0);
+}
+
+bool common_hal_usb_hid_enable(const mp_obj_t devices, uint8_t boot_device) {
+ // We can't change the devices once we're connected.
+ if (tud_connected()) {
+ return false;
+ }
+
+ const mp_int_t num_devices = MP_OBJ_SMALL_INT_VALUE(mp_obj_len(devices));
+ if (num_devices > MAX_HID_DEVICES) {
+ mp_raise_ValueError_varg(translate("No more than %d HID devices allowed"), MAX_HID_DEVICES);
+ }
+
+ num_hid_devices = num_devices;
+
+ hid_boot_device = boot_device;
+
+ // Remember the devices in static storage so they live across VMs.
+ for (mp_int_t i = 0; i < num_hid_devices; i++) {
+ // devices has already been validated to contain only usb_hid_device_obj_t objects.
+ usb_hid_device_obj_t *device =
+ MP_OBJ_TO_PTR(mp_obj_subscr(devices, MP_OBJ_NEW_SMALL_INT(i), MP_OBJ_SENTINEL));
+ memcpy(&hid_devices[i], device, sizeof(usb_hid_device_obj_t));
+ }
+
+ usb_hid_set_devices_from_hid_devices();
+
+ return true;
+}
+
+// Called when HID devices are ready to be used, when code.py or the REPL starts running.
+void usb_hid_setup_devices(void) {
+
+ // If the host requested a boot device, replace the current list of devices
+ // with a single-element tuple containing the proper boot device.
+ if (hid_boot_device_requested) {
+ memcpy(&hid_devices[0],
+ // Will be 1 (keyboard) or 2 (mouse).
+ hid_boot_device == 1 ? &boot_keyboard_obj : &boot_mouse_obj,
+ sizeof(usb_hid_device_obj_t));
+ num_hid_devices = 1;
+ }
+
+ usb_hid_set_devices_from_hid_devices();
+
+ // Create report buffers on the heap.
+ for (mp_int_t i = 0; i < num_hid_devices; i++) {
+ usb_hid_device_create_report_buffers(&hid_devices[i]);
+ }
+}
+
+// Total length of the report descriptor, with all configured devices.
+size_t usb_hid_report_descriptor_length(void) {
+ size_t total_hid_report_descriptor_length = 0;
+ for (mp_int_t i = 0; i < num_hid_devices; i++) {
+ total_hid_report_descriptor_length += hid_devices[i].report_descriptor_length;
+ }
+
+ return total_hid_report_descriptor_length;
+}
+
+// Build the combined HID report descriptor in the given space.
+void usb_hid_build_report_descriptor(uint8_t *report_descriptor_space, size_t report_descriptor_length) {
+ if (!usb_hid_enabled()) {
+ return;
+ }
+
+ uint8_t *report_descriptor_start = report_descriptor_space;
+
+ for (mp_int_t i = 0; i < num_hid_devices; i++) {
+ usb_hid_device_obj_t *device = &hid_devices[i];
+ // Copy the report descriptor for this device.
+ memcpy(report_descriptor_start, device->report_descriptor, device->report_descriptor_length);
+
+ // Advance to the next free chunk for the next report descriptor.x
+ report_descriptor_start += device->report_descriptor_length;
+
+ // Clear the heap pointer to the bytes of the descriptor.
+ // We don't need it any more and it will get lost when the heap goes away.
+ device->report_descriptor = NULL;
+ }
+}
+
+// Call this after the heap and VM are finished.
+void usb_hid_save_report_descriptor(uint8_t *report_descriptor_space, size_t report_descriptor_length) {
+ if (!usb_hid_enabled()) {
+ return;
+ }
+
+ // Allocate storage that persists across VMs to hold the combined report descriptor.
+ // and to remember the device details.
+
+ // Copy the descriptor from the temporary area to a supervisor storage allocation that
+ // will leave between VM instantiations.
+ hid_report_descriptor_allocation =
+ allocate_memory(align32_size(report_descriptor_length),
+ /*high_address*/ false, /*movable*/ false);
+ memcpy((uint8_t *)hid_report_descriptor_allocation->ptr, report_descriptor_space, report_descriptor_length);
+}
+
+void usb_hid_gc_collect(void) {
+ gc_collect_ptr(hid_devices_tuple);
+
+ // Mark possible heap pointers in the static device list as in use.
+ for (mp_int_t device_idx = 0; device_idx < num_hid_devices; device_idx++) {
+
+ // Cast away the const for .report_descriptor. It could be in flash or on the heap.
+ // Constant report descriptors must be const so that they are used directly from flash
+ // and not copied into RAM.
+ gc_collect_ptr((void *)hid_devices[device_idx].report_descriptor);
+
+ // Collect all the report buffers for this device.
+ for (size_t id_idx = 0; id_idx < hid_devices[device_idx].num_report_ids; id_idx++) {
+ gc_collect_ptr(hid_devices[device_idx].in_report_buffers[id_idx]);
+ gc_collect_ptr(hid_devices[device_idx].out_report_buffers[id_idx]);
+ }
+ }
+}
+
+bool usb_hid_get_device_with_report_id(uint8_t report_id, usb_hid_device_obj_t **device_out, size_t *id_idx_out) {
+ for (uint8_t device_idx = 0; device_idx < num_hid_devices; device_idx++) {
+ usb_hid_device_obj_t *device = &hid_devices[device_idx];
+ for (size_t id_idx = 0; id_idx < device->num_report_ids; id_idx++) {
+ if (device->report_ids[id_idx] == report_id) {
+ *device_out = device;
+ *id_idx_out = id_idx;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Callback invoked when we receive a GET HID REPORT DESCRIPTOR
+// Application returns pointer to descriptor
+// Descriptor contents must exist long enough for transfer to complete
+uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) {
+ return (uint8_t *)hid_report_descriptor_allocation->ptr;
+}
+
+// Callback invoked when we receive a SET_PROTOCOL request.
+// Protocol is either HID_PROTOCOL_BOOT (0) or HID_PROTOCOL_REPORT (1)
+void tud_hid_set_protocol_cb(uint8_t instance, uint8_t protocol) {
+ hid_boot_device_requested = (protocol == HID_PROTOCOL_BOOT);
+}
diff --git a/circuitpython/shared-module/usb_hid/__init__.h b/circuitpython/shared-module/usb_hid/__init__.h
new file mode 100644
index 0000000..1a33540
--- /dev/null
+++ b/circuitpython/shared-module/usb_hid/__init__.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Dan Halbert for Adafruit Industries
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef SHARED_MODULE_USB_HID___INIT___H
+#define SHARED_MODULE_USB_HID___INIT___H
+
+#include "shared-module/usb_hid/Device.h"
+#include "supervisor/usb.h"
+
+extern usb_hid_device_obj_t usb_hid_devices[];
+
+bool usb_hid_enabled(void);
+uint8_t usb_hid_boot_device(void);
+void usb_hid_set_defaults(void);
+
+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);
+size_t usb_hid_descriptor_length(void);
+size_t usb_hid_report_descriptor_length(void);
+
+void usb_hid_setup_devices(void);
+size_t usb_hid_report_descriptor_length(void);
+void usb_hid_build_report_descriptor(uint8_t *report_descriptor_space, size_t report_descriptor_length);
+void usb_hid_save_report_descriptor(uint8_t *report_descriptor_space, size_t report_descriptor_length);
+
+bool usb_hid_get_device_with_report_id(uint8_t report_id, usb_hid_device_obj_t **device_out, size_t *id_idx_out);
+
+void usb_hid_gc_collect(void);
+
+#endif // SHARED_MODULE_USB_HID___INIT___H