diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 38d498312ad..8560ea3b29f 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -2244,6 +2244,10 @@ msgstr "" msgid "SocketPool can only be used with wifi.radio or hostnetwork.HostNetwork" msgstr "" +#: ports/zephyr-cp/common-hal/usb_hid/Device.c shared-module/usb_hid/Device.c +msgid "USB error" +msgstr "" + #: ports/zephyr-cp/common-hal/wifi/Radio.c msgid "Failed to set hostname" msgstr "" @@ -3749,10 +3753,6 @@ msgstr "" msgid "bits_per_sample must be 16" msgstr "" -#: shared-bindings/audioi2sin/I2SIn.c -msgid "%q requires %q" -msgstr "" - #: shared-bindings/audioi2sin/I2SIn.c #, c-format msgid "invalid destination buffer, must be an array of type: %c" @@ -4514,10 +4514,6 @@ msgstr "" msgid "USB busy" msgstr "" -#: shared-module/usb_hid/Device.c -msgid "USB error" -msgstr "" - #: shared-module/vectorio/Circle.c shared-module/vectorio/Polygon.c #: shared-module/vectorio/Rectangle.c msgid "can only have one parent" diff --git a/ports/zephyr-cp/app.overlay b/ports/zephyr-cp/app.overlay index 0f34d64a80f..bb2be8924f6 100644 --- a/ports/zephyr-cp/app.overlay +++ b/ports/zephyr-cp/app.overlay @@ -8,3 +8,15 @@ label = "CircuitPython Data"; }; }; + +/ { + hid_dev_0: hid_dev_0 { + compatible = "zephyr,hid-device"; + label = "HID0"; + protocol-code = "none"; + in-polling-period-us = <1000>; + in-report-size = <64>; + out-polling-period-us = <1000>; + out-report-size = <64>; + }; +}; diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml index eef096756e7..459ee6a98c2 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_sense_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml index 187017cb12e..654a6c2e01b 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_nrf52840_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml index 41c4c1dda5e..17022b4d2c7 100644 --- a/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/adafruit/feather_rp2040_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml index 4d4ce0fce98..366d4cd4cb3 100644 --- a/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf5340dk/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml index 6fdd16a7732..b3da39f698c 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54h20dk/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml index 58a5074f733..9da6422265e 100644 --- a/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf54lm20dk/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml index 3f72b515853..9f31476b449 100644 --- a/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nordic/nrf7002dk/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml index 05233a43246..273aff2376d 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_mcxn947/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml index 13d5bdf8971..6efb741df94 100644 --- a/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/frdm_rw612/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml index ba521087358..cf5c0650015 100644 --- a/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/nxp/mimxrt1170_evk/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml index 8f89598c426..e7d3f4502fd 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml index a536de71e7b..95a2873af70 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml index 5a156b1c37b..e2036293823 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml index 1a3912156b9..3c96b7d4f57 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_zephyr/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml index ef56e12139a..4c396485d66 100644 --- a/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/da14695_dk_usb/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml index 390fc8a3925..d943b17bf26 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra6m5/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml index 93ddc67c013..2291d43c4a1 100644 --- a/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/renesas/ek_ra8d1/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml index 92cdc6137f5..9ee6085d189 100644 --- a/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_n657x0_q/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml index 199909fbd6d..59a15de0232 100644 --- a/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/nucleo_u575zi_q/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml index eb808e08111..63fac014f17 100644 --- a/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32h7b3i_dk/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml index 11136930e27..10c7a39b60a 100644 --- a/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml +++ b/ports/zephyr-cp/boards/st/stm32wba65i_dk1/autogen_board_info.toml @@ -109,7 +109,7 @@ uheap = false usb = false usb_audio = false usb_cdc = true -usb_hid = false +usb_hid = true usb_host = false usb_midi = false usb_video = false diff --git a/ports/zephyr-cp/common-hal/usb_hid/Device.c b/ports/zephyr-cp/common-hal/usb_hid/Device.c new file mode 100644 index 00000000000..ba263d44d74 --- /dev/null +++ b/ports/zephyr-cp/common-hal/usb_hid/Device.c @@ -0,0 +1,150 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2018 hathach for Adafruit Industries +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include +#include + +#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 "shared-module/usb_hid/report_descriptors.h" +#include "common-hal/usb_hid/__init__.h" +#include "supervisor/shared/tick.h" + +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, }, +}; + +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, }, +}; + +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, }, +}; + +char *custom_usb_hid_interface_name; + +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; +} + +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_arg_error_invalid(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) { + mp_arg_validate_length_max( + num_report_ids, CIRCUITPY_USB_HID_MAX_REPORT_IDS_PER_DESCRIPTOR, MP_QSTR_report_ids); + + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(report_descriptor, &bufinfo, MP_BUFFER_READ); + self->report_descriptor_length = bufinfo.len; + + uint8_t *descriptor_bytes = gc_alloc(bufinfo.len, 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) { + size_t id_idx = get_report_id_idx(self, report_id); + + mp_arg_validate_length(len, self->in_report_lengths[id_idx], MP_QSTR_report); + + // Wait until interface is ready, timeout = 2 seconds + uint64_t end_ticks = supervisor_ticks_ms64() + 2000; + while ((supervisor_ticks_ms64() < end_ticks) && !usb_hid_ready()) { + RUN_BACKGROUND_TASKS; + } + + if (!usb_hid_submit_report(report_id, report, len)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("USB error")); + } +} + +mp_obj_t common_hal_usb_hid_device_get_last_received_report(usb_hid_device_obj_t *self, uint8_t report_id) { + size_t id_idx = get_report_id_idx(self, report_id); + if (!self->out_report_buffers_updated[id_idx]) { + return mp_const_none; + } + self->out_report_buffers_updated[id_idx] = false; + 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++) { + self->in_report_buffers[i] = + self->in_report_lengths[i] > 0 + ? gc_alloc(self->in_report_lengths[i], false) + : NULL; + + self->out_report_buffers[i] = + self->out_report_lengths[i] > 0 + ? gc_alloc(self->out_report_lengths[i], false) + : NULL; + } + memset(self->out_report_buffers_updated, 0, sizeof(self->out_report_buffers_updated)); +} diff --git a/ports/zephyr-cp/common-hal/usb_hid/__init__.c b/ports/zephyr-cp/common-hal/usb_hid/__init__.c new file mode 100644 index 00000000000..552a91e102b --- /dev/null +++ b/ports/zephyr-cp/common-hal/usb_hid/__init__.c @@ -0,0 +1,377 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#include + +#include "py/gc.h" +#include "py/runtime.h" +#include "shared-bindings/usb_hid/__init__.h" +#include "shared-bindings/usb_hid/Device.h" +#include "shared-module/usb_hid/Device.h" +#include "supervisor/port.h" + +#include +#include +#include +#include +#include + +#define MAX_HID_DEVICES 8 + +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. +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 stored in usb_hid.devices. +static mp_obj_tuple_t *hid_devices_tuple; + +// Whether the HID interface is ready (USB configured). +static bool hid_interface_ready; + +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. +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. +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; + hid_interface_ready = false; + common_hal_usb_hid_enable( + CIRCUITPY_USB_HID_ENABLED_DEFAULT ? &default_hid_devices_tuple : mp_const_empty_tuple, 0); +} + +// 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]; + } + 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 USB is connected. + if (hid_interface_ready) { + return false; + } + + const mp_int_t num_devices = MP_OBJ_SMALL_INT_VALUE(mp_obj_len(devices)); + mp_arg_validate_length_max(num_devices, MAX_HID_DEVICES, MP_QSTR_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++) { + 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], + 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]); + } +} + +// Build the combined HID report descriptor from all devices. +static uint8_t *hid_report_descriptor = NULL; + +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; +} + +void usb_hid_build_report_descriptor(void) { + if (!usb_hid_enabled()) { + return; + } + size_t report_length = usb_hid_report_descriptor_length(); + hid_report_descriptor = port_malloc(report_length, false); + if (hid_report_descriptor == NULL) { + return; + } + + uint8_t *report_descriptor_start = hid_report_descriptor; + for (mp_int_t i = 0; i < num_hid_devices; i++) { + usb_hid_device_obj_t *device = &hid_devices[i]; + memcpy(report_descriptor_start, device->report_descriptor, device->report_descriptor_length); + report_descriptor_start += device->report_descriptor_length; + // Clear the heap pointer to the bytes of the descriptor. + device->report_descriptor = NULL; + } +} + +void usb_hid_gc_collect(void) { + gc_collect_ptr(hid_devices_tuple); + + for (mp_int_t device_idx = 0; device_idx < num_hid_devices; device_idx++) { + gc_collect_ptr((void *)hid_devices[device_idx].report_descriptor); + + 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; +} + +// ===== Zephyr USBD HID Device Callbacks ===== + +static void hid_iface_ready(const struct device *dev, const bool ready) { + hid_interface_ready = ready; +} + +static int hid_get_report(const struct device *dev, + const uint8_t type, const uint8_t id, + const uint16_t len, uint8_t *const buf) { + // Support Input Report and Feature Report + if (type != HID_REPORT_TYPE_INPUT && type != HID_REPORT_TYPE_FEATURE) { + return -ENOTSUP; + } + + usb_hid_device_obj_t *hid_device; + size_t id_idx; + if (!usb_hid_get_device_with_report_id(id, &hid_device, &id_idx)) { + return 0; + } + if (!hid_device->in_report_buffers[id_idx]) { + return 0; + } + + uint8_t in_len = hid_device->in_report_lengths[id_idx]; + const uint8_t *src = hid_device->in_report_buffers[id_idx]; + + // When the descriptor uses Report IDs (id != 0), the on-wire report is + // prefixed with the Report ID byte. Zephyr sends the buffer verbatim, so + // prepend the ID here. id == 0 means no Report ID item -> no prefix. + if (id != 0) { + if (len == 0) { + return 0; + } + buf[0] = id; + uint16_t copy_len = len - 1; + if (copy_len > in_len) { + copy_len = in_len; + } + memcpy(&buf[1], src, copy_len); + return 1 + copy_len; + } + + uint16_t copy_len = len; + if (copy_len > in_len) { + copy_len = in_len; + } + memcpy(buf, src, copy_len); + return copy_len; +} + +static int hid_set_report(const struct device *dev, + const uint8_t type, const uint8_t id, + const uint16_t len, const uint8_t *const buf) { + uint8_t report_id = id; + const uint8_t *data = buf; + uint16_t data_len = len; + + if (report_id != 0 && len > 0) { + // Descriptor uses Report IDs: the on-wire SET_REPORT data stage is + // prefixed with the Report ID byte. Zephyr passes the raw payload, so + // strip the leading ID byte (it should match the id from wValue). + if (buf[0] == report_id) { + data = &buf[1]; + data_len = len - 1; + } + } else if (report_id == 0 && len > 0) { + // id==0 normally means no Report ID item (no prefix). But if no device + // has report ID 0 with a matching out length, treat the first byte as a + // report ID (host sent a prefixed report despite id==0). + usb_hid_device_obj_t *hid0 = NULL; + size_t idx0; + if (!(usb_hid_get_device_with_report_id(0, &hid0, &idx0) && + hid0 && hid0->out_report_buffers[idx0] && + hid0->out_report_lengths[idx0] == len)) { + report_id = buf[0]; + data = &buf[1]; + data_len = len - 1; + } + } + + // Look up the device for the resolved report_id. + usb_hid_device_obj_t *hid_device = NULL; + size_t id_idx; + if (!usb_hid_get_device_with_report_id(report_id, &hid_device, &id_idx)) { + return -EINVAL; + } + + if (hid_device->out_report_buffers[id_idx] && + hid_device->out_report_lengths[id_idx] == data_len) { + memcpy(hid_device->out_report_buffers[id_idx], data, data_len); + hid_device->out_report_buffers_updated[id_idx] = true; + return 0; + } + + return -EINVAL; +} + +static void hid_set_protocol(const struct device *dev, const uint8_t proto) { + hid_boot_device_requested = (proto == HID_PROTOCOL_BOOT); +} + +static struct hid_device_ops hid_ops = { + .iface_ready = hid_iface_ready, + .get_report = hid_get_report, + .set_report = hid_set_report, + .set_protocol = hid_set_protocol, +}; + +// ===== HID Device Registration ===== + +// Get the Zephyr HID device for submitting reports +static const struct device *hid_zephyr_dev; + +const struct device *usb_hid_get_zephyr_device(void) { + return hid_zephyr_dev; +} + +void usb_hid_init_devices(void) { + if (!usb_hid_enabled()) { + return; + } + + hid_zephyr_dev = DEVICE_DT_GET_ONE(zephyr_hid_device); + if (!device_is_ready(hid_zephyr_dev)) { + return; + } + + usb_hid_build_report_descriptor(); + if (hid_report_descriptor == NULL) { + return; + } + + int ret = hid_device_register(hid_zephyr_dev, + hid_report_descriptor, + usb_hid_report_descriptor_length(), + &hid_ops); + if (ret != 0) { + return; + } +} + +bool usb_hid_ready(void) { + return hid_zephyr_dev != NULL && hid_interface_ready; +} + +// Submit an input report through Zephyr's HID device. +// Called from common_hal_usb_hid_device_send_report(). +bool usb_hid_submit_report(uint8_t report_id, const uint8_t *report, uint8_t len) { + if (hid_zephyr_dev == NULL || !hid_interface_ready) { + return false; + } + + // Send report_id zero without a prefix + if (report_id == 0) { + return hid_device_submit_report(hid_zephyr_dev, len, report) == 0; + } + + uint8_t __attribute__((aligned(sizeof(void *)))) report_buffer[1 + len]; + report_buffer[0] = report_id; + memcpy(&report_buffer[1], report, len); + return hid_device_submit_report(hid_zephyr_dev, len + 1, report_buffer) == 0; +} diff --git a/ports/zephyr-cp/common-hal/usb_hid/__init__.h b/ports/zephyr-cp/common-hal/usb_hid/__init__.h new file mode 100644 index 00000000000..1985ea29549 --- /dev/null +++ b/ports/zephyr-cp/common-hal/usb_hid/__init__.h @@ -0,0 +1,28 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2025 Scott Shawcroft for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +#pragma once + +#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); +void usb_hid_setup_devices(void); +size_t usb_hid_report_descriptor_length(void); +void usb_hid_build_report_descriptor(void); +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); + +// For supervisor/usb.c integration +void usb_hid_init_devices(void); + +// For Device.c send_report +bool usb_hid_ready(void); +bool usb_hid_submit_report(uint8_t report_id, const uint8_t *report, uint8_t len); diff --git a/ports/zephyr-cp/cptools/build_circuitpython.py b/ports/zephyr-cp/cptools/build_circuitpython.py index 72c9071bd81..55c069a1ddc 100644 --- a/ports/zephyr-cp/cptools/build_circuitpython.py +++ b/ports/zephyr-cp/cptools/build_circuitpython.py @@ -490,6 +490,11 @@ async def build_circuitpython(): # noqa: C901 if usb_ok: enabled_modules.add("usb_cdc") + enabled_modules.add("usb_hid") + + usb_num_endpoint_pairs = board_info.get("usb_num_endpoint_pairs", 4) + circuitpython_flags.append(f"-DUSB_NUM_ENDPOINT_PAIRS={usb_num_endpoint_pairs}") + circuitpython_flags.append("-DCIRCUITPY_USB_HID_ENABLED_DEFAULT=1") for macro in ("USB_PID", "USB_VID"): print(f"Setting {macro} to {mpconfigboard.get(macro)}") @@ -594,6 +599,10 @@ async def build_circuitpython(): # noqa: C901 # Only include shared-module/*.c if no common-hal/*.c files were found if len(hal_source) == len_before or module.name in SHARED_MODULE_AND_COMMON_HAL: hal_source.extend(top.glob(f"shared-module/{module.name}/**/*.c")) + # The USB HID report descriptors are shared with the TinyUSB ports and + # have no common-hal override, so always compile them. + if module.name == "usb_hid": + hal_source.append(top / "shared-module/usb_hid/report_descriptors.c") hal_source.extend(top.glob(f"shared-bindings/{module.name}/**/*.c")) if module.name in LIBRARY_SOURCE: for library_source in LIBRARY_SOURCE[module.name]: diff --git a/ports/zephyr-cp/prj.conf b/ports/zephyr-cp/prj.conf index a54df43e82f..eb9cd8c52f6 100644 --- a/ports/zephyr-cp/prj.conf +++ b/ports/zephyr-cp/prj.conf @@ -17,6 +17,7 @@ CONFIG_EXCEPTION_STACK_TRACE=n CONFIG_USB_DEVICE_STACK_NEXT=y CONFIG_USBD_CDC_ACM_CLASS=y +CONFIG_USBD_HID_SUPPORT=y CONFIG_USBD_MSC_STACK_SIZE=1536 CONFIG_CDC_ACM_SERIAL_INITIALIZE_AT_BOOT=n diff --git a/ports/zephyr-cp/supervisor/usb.c b/ports/zephyr-cp/supervisor/usb.c index a42a5192f4f..bb7f15f056a 100644 --- a/ports/zephyr-cp/supervisor/usb.c +++ b/ports/zephyr-cp/supervisor/usb.c @@ -3,6 +3,10 @@ #include "shared-bindings/usb_cdc/__init__.h" #include "shared-bindings/usb_cdc/Serial.h" +#if CIRCUITPY_USB_HID +#include "common-hal/usb_hid/__init__.h" +#endif + #include "supervisor/zephyr-cp.h" #include "extmod/vfs.h" @@ -189,22 +193,38 @@ int _zephyr_disk_ioctl(struct disk_info *disk, uint8_t cmd, void *buff) { return 0; } +// Tracks whether the USB host has selected a configuration, i.e. the device has +// enumerated. Written from the USBD message callback (USBD thread or system +// workqueue context) and read from the CircuitPython VM thread, so keep it +// volatile to avoid the compiler caching stale values. +static volatile bool usb_configured = false; + static void _msg_cb(struct usbd_context *const ctx, const struct usbd_msg *msg) { LOG_INF("USBD message: %s", usbd_msg_type_string(msg->type)); if (usbd_can_detect_vbus(ctx)) { if (msg->type == USBD_MSG_VBUS_READY) { + usb_configured = false; if (usbd_enable(ctx)) { LOG_ERR("Failed to enable device support"); } } if (msg->type == USBD_MSG_VBUS_REMOVED) { + usb_configured = false; if (usbd_disable(ctx)) { LOG_ERR("Failed to disable device support"); } } } + + // A non-zero configuration value means the host has enumerated us. + // A value of zero (or a bus reset / VBUS removal) unconfigures us. + if (msg->type == USBD_MSG_CONFIGURATION) { + usb_configured = (msg->status != 0); + } else if (msg->type == USBD_MSG_RESET) { + usb_configured = false; + } } void usb_init(void) { @@ -293,6 +313,17 @@ void usb_init(void) { printk("Registered MSC class for high speed\n"); } + #if CIRCUITPY_USB_HID + if (usb_hid_enabled()) { + printk("Registering High-Speed hid class\n"); + err = usbd_register_class(&main_usbd, "hid_0", USBD_SPEED_HS, 1); + if (err) { + printk("Failed to register HID class (HS) %d\n", err); + return; + } + } + #endif + usbd_device_set_code_triple(&main_usbd, USBD_SPEED_HS, USB_BCC_MISCELLANEOUS, 0x02, 0x01); } @@ -334,6 +365,17 @@ void usb_init(void) { } /* doc functions register end */ + #if CIRCUITPY_USB_HID + if (usb_hid_enabled()) { + printk("Registering Full-Speed hid class\n"); + err = usbd_register_class(&main_usbd, "hid_0", USBD_SPEED_FS, 1); + if (err) { + printk("Failed to register HID class (FS) %d\n", err); + return; + } + } + #endif + usbd_device_set_code_triple(&main_usbd, USBD_SPEED_FS, USB_BCC_MISCELLANEOUS, 0x02, 0x01); printk("Setting self powered\n"); @@ -346,6 +388,11 @@ void usb_init(void) { return; } + #if CIRCUITPY_USB_HID + printk("Initializing HID\n"); + usb_hid_init_devices(); + #endif + printk("usbd_init\n"); err = usbd_init(&main_usbd); if (err) { @@ -366,7 +413,8 @@ void usb_init(void) { } bool usb_connected(void) { - return false; + // Mirrors TinyUSB's tud_ready(): configured (mounted) and not suspended. + return usb_configured && !usbd_is_suspended(&main_usbd); } void usb_disconnect(void) { diff --git a/shared-module/usb_hid/Device.c b/shared-module/usb_hid/Device.c index d6c4697210f..42f5790123e 100644 --- a/shared-module/usb_hid/Device.c +++ b/shared-module/usb_hid/Device.c @@ -12,45 +12,10 @@ #include "shared-bindings/usb_hid/Device.h" #include "shared-module/usb_hid/__init__.h" #include "shared-module/usb_hid/Device.h" +#include "shared-module/usb_hid/report_descriptors.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, 0x05, // Report Count (5) - 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, 0x03, // Report Size (3) - 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, @@ -65,42 +30,6 @@ const usb_hid_device_obj_t usb_hid_device_keyboard_obj = { .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, @@ -115,21 +44,6 @@ const usb_hid_device_obj_t usb_hid_device_mouse_obj = { .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, diff --git a/shared-module/usb_hid/report_descriptors.c b/shared-module/usb_hid/report_descriptors.c new file mode 100644 index 00000000000..fe629153c78 --- /dev/null +++ b/shared-module/usb_hid/report_descriptors.c @@ -0,0 +1,96 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2018 hathach for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// Standard HID report descriptors shared between ports. + +#include "shared-module/usb_hid/report_descriptors.h" + +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, 0x05, // Report Count (5) + 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, 0x03, // Report Size (3) + 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 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 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 +}; diff --git a/shared-module/usb_hid/report_descriptors.h b/shared-module/usb_hid/report_descriptors.h new file mode 100644 index 00000000000..94a9a752f6a --- /dev/null +++ b/shared-module/usb_hid/report_descriptors.h @@ -0,0 +1,19 @@ +// This file is part of the CircuitPython project: https://circuitpython.org +// +// SPDX-FileCopyrightText: Copyright (c) 2018 hathach for Adafruit Industries +// +// SPDX-License-Identifier: MIT + +// Standard HID report descriptors shared between ports. These are plain byte +// arrays with no dependencies, so ports using TinyUSB and ports using other +// USB stacks can both use them. They are defined in report_descriptors.c. +// The explicit sizes let callers use sizeof() and are checked by the compiler +// against the definitions. + +#pragma once + +#include + +extern const uint8_t keyboard_report_descriptor[67]; +extern const uint8_t mouse_report_descriptor[64]; +extern const uint8_t consumer_control_report_descriptor[25]; diff --git a/supervisor/supervisor.mk b/supervisor/supervisor.mk index 95d651c1541..2d881dfe070 100644 --- a/supervisor/supervisor.mk +++ b/supervisor/supervisor.mk @@ -148,6 +148,7 @@ ifeq ($(CIRCUITPY_TINYUSB),1) shared-bindings/usb_hid/Device.c \ shared-module/usb_hid/__init__.c \ shared-module/usb_hid/Device.c \ + shared-module/usb_hid/report_descriptors.c \ endif