diff options
Diffstat (limited to 'circuitpython/shared-module/usb_hid')
| -rw-r--r-- | circuitpython/shared-module/usb_hid/Device.c | 314 | ||||
| -rw-r--r-- | circuitpython/shared-module/usb_hid/Device.h | 56 | ||||
| -rw-r--r-- | circuitpython/shared-module/usb_hid/__init__.c | 357 | ||||
| -rw-r--r-- | circuitpython/shared-module/usb_hid/__init__.h | 52 |
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 |
