From 91aa3378f32f5688329a663fdaba4772e069d548 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Tue, 14 Nov 2023 03:04:04 +0900 Subject: [PATCH] feat(usb): Add boot protocol support * USB boot protocol support * Use a single definition of a boot report, used for regular reports in non-6KRO, and for rollover in all branches. * Handle gaps in the zmk report when producing a boot report in HKRO mode. For .example, if it was 8KRO, it would be possible to have the state 0 0 0 0 0 0 0 17 (by pressing 8 keys, and letting go of the first 7). Copying the first 6 bytes would not show up the single pressed key. * Disable usb status change and callback on SOF events: SOF events were introduced by the boot protocol changes, and required internally by Zephyr's idle support, but are unused within ZMK itself. Ignore them in the usb status callback. --------- Co-authored-by: Andrew Childs --- app/Kconfig | 6 ++- app/include/zmk/hid.h | 28 +++++++++--- app/include/zmk/usb_hid.h | 6 ++- app/src/endpoints.c | 10 ++--- app/src/hid.c | 92 +++++++++++++++++++++++++++++++++++++++ app/src/usb.c | 14 ++++++ app/src/usb_hid.c | 88 ++++++++++++++++++++++++++++++++++++- 7 files changed, 229 insertions(+), 15 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 0dd9316a2a7..798cb7855d5 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -103,6 +103,11 @@ config USB_NUMOF_EP_WRITE_RETRIES config USB_HID_POLL_INTERVAL_MS default 1 +config ZMK_USB_BOOT + bool "USB Boot Protocol Support" + default y + select USB_HID_BOOT_PROTOCOL + select USB_DEVICE_SOF #ZMK_USB endif @@ -575,4 +580,3 @@ osource "$(ZMK_CONFIG)/boards/shields/*/Kconfig.shield" source "Kconfig.zephyr" - diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index da6bfa65a68..afe9210199f 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -116,12 +116,24 @@ static const uint8_t zmk_hid_report_desc[] = { HID_END_COLLECTION, }; -// struct zmk_hid_boot_report -// { -// uint8_t modifiers; -// uint8_t _unused; -// uint8_t keys[6]; -// } __packed; +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + +#define HID_ERROR_ROLLOVER 0x1 +#define HID_BOOT_KEY_LEN 6 + +#if IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_HKRO) && \ + CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE == HID_BOOT_KEY_LEN +typedef struct zmk_hid_keyboard_report_body zmk_hid_boot_report_t; +#else +struct zmk_hid_boot_report { + zmk_mod_flags_t modifiers; + uint8_t _reserved; + uint8_t keys[HID_BOOT_KEY_LEN]; +} __packed; + +typedef struct zmk_hid_boot_report zmk_hid_boot_report_t; +#endif +#endif struct zmk_hid_keyboard_report_body { zmk_mod_flags_t modifiers; @@ -179,3 +191,7 @@ bool zmk_hid_is_pressed(uint32_t usage); struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +zmk_hid_boot_report_t *zmk_hid_get_boot_report(); +#endif diff --git a/app/include/zmk/usb_hid.h b/app/include/zmk/usb_hid.h index 1748835eef2..777f2b48a03 100644 --- a/app/include/zmk/usb_hid.h +++ b/app/include/zmk/usb_hid.h @@ -6,4 +6,8 @@ #pragma once -int zmk_usb_hid_send_report(const uint8_t *report, size_t len); \ No newline at end of file +#include + +int zmk_usb_hid_send_keyboard_report(); +int zmk_usb_hid_send_consumer_report(); +void zmk_usb_hid_set_protocol(uint8_t protocol); diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 138e790fe72..10357d4e02d 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -121,12 +121,10 @@ struct zmk_endpoint_instance zmk_endpoints_selected(void) { } static int send_keyboard_report(void) { - struct zmk_hid_keyboard_report *keyboard_report = zmk_hid_get_keyboard_report(); - switch (current_instance.transport) { #if IS_ENABLED(CONFIG_ZMK_USB) case ZMK_TRANSPORT_USB: { - int err = zmk_usb_hid_send_report((uint8_t *)keyboard_report, sizeof(*keyboard_report)); + int err = zmk_usb_hid_send_keyboard_report(); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); } @@ -136,6 +134,7 @@ static int send_keyboard_report(void) { #if IS_ENABLED(CONFIG_ZMK_BLE) case ZMK_TRANSPORT_BLE: { + struct zmk_hid_keyboard_report *keyboard_report = zmk_hid_get_keyboard_report(); int err = zmk_hog_send_keyboard_report(&keyboard_report->body); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); @@ -150,12 +149,10 @@ static int send_keyboard_report(void) { } static int send_consumer_report(void) { - struct zmk_hid_consumer_report *consumer_report = zmk_hid_get_consumer_report(); - switch (current_instance.transport) { #if IS_ENABLED(CONFIG_ZMK_USB) case ZMK_TRANSPORT_USB: { - int err = zmk_usb_hid_send_report((uint8_t *)consumer_report, sizeof(*consumer_report)); + int err = zmk_usb_hid_send_consumer_report(); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); } @@ -165,6 +162,7 @@ static int send_consumer_report(void) { #if IS_ENABLED(CONFIG_ZMK_BLE) case ZMK_TRANSPORT_BLE: { + struct zmk_hid_consumer_report *consumer_report = zmk_hid_get_consumer_report(); int err = zmk_hog_send_consumer_report(&consumer_report->body); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); diff --git a/app/src/hid.c b/app/src/hid.c index 58e5824d2e4..689a2361e13 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -17,6 +17,13 @@ static struct zmk_hid_keyboard_report keyboard_report = { static struct zmk_hid_consumer_report consumer_report = {.report_id = ZMK_HID_REPORT_ID_CONSUMER, .body = {.keys = {0}}}; +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + +static zmk_hid_boot_report_t boot_report = {.modifiers = 0, ._reserved = 0, .keys = {0}}; +static uint8_t keys_held = 0; + +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. static int explicit_modifier_counts[8] = {0, 0, 0, 0, 0, 0, 0, 0}; @@ -85,15 +92,58 @@ int zmk_hid_unregister_mods(zmk_mod_flags_t modifiers) { return ret; } +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + +static zmk_hid_boot_report_t *boot_report_rollover(uint8_t modifiers) { + boot_report.modifiers = modifiers; + for (int i = 0; i < HID_BOOT_KEY_LEN; i++) { + boot_report.keys[i] = HID_ERROR_ROLLOVER; + } + return &boot_report; +} + +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + #if IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_NKRO) #define TOGGLE_KEYBOARD(code, val) WRITE_BIT(keyboard_report.body.keys[code / 8], code % 8, val) +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +zmk_hid_boot_report_t *zmk_hid_get_boot_report() { + if (keys_held > HID_BOOT_KEY_LEN) { + return boot_report_rollover(keyboard_report.body.modifiers); + } + + boot_report.modifiers = keyboard_report.body.modifiers; + memset(&boot_report.keys, 0, HID_BOOT_KEY_LEN); + int ix = 0; + uint8_t base_code = 0; + for (int i = 0; i < (ZMK_HID_KEYBOARD_NKRO_MAX_USAGE + 1) / 8; ++i) { + if (ix == keys_held) { + break; + } + if (!keyboard_report.body.keys[i]) { + continue; + } + base_code = i * 8; + for (int j = 0; j < 8; ++j) { + if (keyboard_report.body.keys[i] & BIT(j)) { + boot_report.keys[ix++] = base_code + j; + } + } + } + return &boot_report; +} +#endif + static inline int select_keyboard_usage(zmk_key_t usage) { if (usage > ZMK_HID_KEYBOARD_NKRO_MAX_USAGE) { return -EINVAL; } TOGGLE_KEYBOARD(usage, 1); +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + ++keys_held; +#endif return 0; } @@ -102,6 +152,9 @@ static inline int deselect_keyboard_usage(zmk_key_t usage) { return -EINVAL; } TOGGLE_KEYBOARD(usage, 0); +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + --keys_held; +#endif return 0; } @@ -125,13 +178,52 @@ static inline bool check_keyboard_usage(zmk_key_t usage) { } \ } +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +zmk_hid_boot_report_t *zmk_hid_get_boot_report() { + if (keys_held > HID_BOOT_KEY_LEN) { + return boot_report_rollover(keyboard_report.body.modifiers); + } + +#if CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE != HID_BOOT_KEY_LEN + // Form a boot report from a report of different size. + + boot_report.modifiers = keyboard_report.body.modifiers; + + int out = 0; + for (int i = 0; i < CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE; i++) { + uint8_t key = keyboard_report.body.keys[i]; + if (key) { + boot_report.keys[out++] = key; + if (out == keys_held) { + break; + } + } + } + + while (out < HID_BOOT_KEY_LEN) { + boot_report.keys[out++] = 0; + } + + return &boot_report; +#else + return &keyboard_report.body; +#endif /* CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE != HID_BOOT_KEY_LEN */ +} +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + static inline int select_keyboard_usage(zmk_key_t usage) { TOGGLE_KEYBOARD(0U, usage); +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + ++keys_held; +#endif return 0; } static inline int deselect_keyboard_usage(zmk_key_t usage) { TOGGLE_KEYBOARD(usage, 0U); +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + --keys_held; +#endif return 0; } diff --git a/app/src/usb.c b/app/src/usb.c index cf04ef46c8f..9d27900c3c6 100644 --- a/app/src/usb.c +++ b/app/src/usb.c @@ -15,6 +15,8 @@ #include #include +#include + LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static enum usb_dc_status_code usb_status = USB_DC_UNKNOWN; @@ -35,6 +37,7 @@ enum zmk_usb_conn_state zmk_usb_get_conn_state() { case USB_DC_CONFIGURED: case USB_DC_RESUME: case USB_DC_CLEAR_HALT: + case USB_DC_SOF: return ZMK_USB_CONN_HID; case USB_DC_DISCONNECTED: @@ -47,6 +50,17 @@ enum zmk_usb_conn_state zmk_usb_get_conn_state() { } void usb_status_cb(enum usb_dc_status_code status, const uint8_t *params) { + // Start-of-frame events are too frequent and noisy to notify, and they're + // not used within ZMK + if (status == USB_DC_SOF) { + return; + } + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (status == USB_DC_RESET) { + zmk_usb_hid_set_protocol(HID_PROTOCOL_REPORT); + } +#endif usb_status = status; k_work_submit(&usb_status_notifier_work); }; diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index f46c70a0a92..c72bb36c9ef 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -23,11 +23,75 @@ static K_SEM_DEFINE(hid_sem, 1, 1); static void in_ready_cb(const struct device *dev) { k_sem_give(&hid_sem); } +#define HID_GET_REPORT_TYPE_MASK 0xff00 +#define HID_GET_REPORT_ID_MASK 0x00ff + +#define HID_REPORT_TYPE_INPUT 0x100 +#define HID_REPORT_TYPE_OUTPUT 0x200 +#define HID_REPORT_TYPE_FEATURE 0x300 + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +static uint8_t hid_protocol = HID_PROTOCOL_REPORT; + +static void set_proto_cb(const struct device *dev, uint8_t protocol) { hid_protocol = protocol; } + +void zmk_usb_hid_set_protocol(uint8_t protocol) { hid_protocol = protocol; } +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + +static uint8_t *get_keyboard_report(size_t *len) { + if (hid_protocol == HID_PROTOCOL_REPORT) { + struct zmk_hid_keyboard_report *report = zmk_hid_get_keyboard_report(); + *len = sizeof(*report); + return (uint8_t *)report; + } +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + zmk_hid_boot_report_t *boot_report = zmk_hid_get_boot_report(); + *len = sizeof(*boot_report); + return (uint8_t *)boot_report; +#endif +} + +static int get_report_cb(const struct device *dev, struct usb_setup_packet *setup, int32_t *len, + uint8_t **data) { + + /* + * 7.2.1 of the HID v1.11 spec is unclear about handling requests for reports that do not exist + * For requested reports that aren't input reports, return -ENOTSUP like the Zephyr subsys does + */ + if ((setup->wValue & HID_GET_REPORT_TYPE_MASK) != HID_REPORT_TYPE_INPUT) { + LOG_ERR("Unsupported report type %d requested", (setup->wValue & HID_GET_REPORT_TYPE_MASK) + << 8); + return -ENOTSUP; + } + + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { + case ZMK_HID_REPORT_ID_KEYBOARD: { + *data = get_keyboard_report(len); + break; + } + case ZMK_HID_REPORT_ID_CONSUMER: { + struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report(); + *data = (uint8_t *)report; + *len = sizeof(*report); + break; + } + default: + LOG_ERR("Invalid report ID %d requested", setup->wValue & HID_GET_REPORT_ID_MASK); + return -EINVAL; + } + + return 0; +} + static const struct hid_ops ops = { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + .protocol_change = set_proto_cb, +#endif .int_in_ready = in_ready_cb, + .get_report = get_report_cb, }; -int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { +static int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { switch (zmk_usb_get_status()) { case USB_DC_SUSPEND: return usb_wakeup_request(); @@ -48,6 +112,23 @@ int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { } } +int zmk_usb_hid_send_keyboard_report() { + size_t len; + uint8_t *report = get_keyboard_report(&len); + return zmk_usb_hid_send_report(report, len); +} + +int zmk_usb_hid_send_consumer_report() { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol == HID_PROTOCOL_BOOT) { + return -ENOTSUP; + } +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + + struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report(); + return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); +} + static int zmk_usb_hid_init(const struct device *_arg) { hid_dev = device_get_binding("HID_0"); if (hid_dev == NULL) { @@ -56,6 +137,11 @@ static int zmk_usb_hid_init(const struct device *_arg) { } usb_hid_register_device(hid_dev, zmk_hid_report_desc, sizeof(zmk_hid_report_desc), &ops); + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + usb_hid_set_proto_code(hid_dev, HID_BOOT_IFACE_CODE_KEYBOARD); +#endif /* IS_ENABLED(CONFIG_ZMK_USB_BOOT) */ + usb_hid_init(hid_dev); return 0;