From d91926211a83b4df4ccff686e5cc3e5078d769c7 Mon Sep 17 00:00:00 2001 From: "Ryan P. Bitanga" Date: Mon, 21 Feb 2022 14:53:33 +0800 Subject: [PATCH] Boot protocol support --- app/Kconfig | 14 +++++++ app/include/zmk/hid.h | 38 ++++++++++++----- app/include/zmk/hog.h | 4 +- app/include/zmk/usb.h | 2 +- app/src/endpoints.c | 12 ++---- app/src/hid.c | 94 ++++++++++++++++++++++++++++++++++++++++--- app/src/hog.c | 14 ++++--- app/src/usb.c | 74 +++++++++++++++++++++++++++++++++- 8 files changed, 218 insertions(+), 34 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 76035147..823fc538 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -82,12 +82,26 @@ config ZMK_USB select USB select USB_DEVICE_STACK select USB_DEVICE_HID + select USB_DEVICE_SOF + select USB_HID_BOOT_PROTOCOL if ZMK_USB config USB_NUMOF_EP_WRITE_RETRIES default 10 +config ZMK_USB_BOOT + bool "USB Boot Protocol Support" + +if ZMK_USB_BOOT + +# Declare boot keyboard interface +config USB_HID_PROTOCOL_CODE + default 1 + +#ZMK_USB_BOOT +endif + #ZMK_USB endif diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index e23caff9..a0a9d334 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -17,6 +17,9 @@ #define COLLECTION_REPORT 0x03 +#define HID_REPORT_ID_KEYBOARD 0x01 +#define HID_REPORT_ID_CONSUMER 0x02 + static const uint8_t zmk_hid_report_desc[] = { /* USAGE_PAGE (Generic Desktop) */ HID_GI_USAGE_PAGE, @@ -29,7 +32,7 @@ static const uint8_t zmk_hid_report_desc[] = { COLLECTION_APPLICATION, /* REPORT ID (1) */ HID_GI_REPORT_ID, - 0x01, + HID_REPORT_ID_KEYBOARD, /* USAGE_PAGE (Keyboard/Keypad) */ HID_GI_USAGE_PAGE, HID_USAGE_KEY, @@ -134,7 +137,7 @@ static const uint8_t zmk_hid_report_desc[] = { COLLECTION_APPLICATION, /* REPORT ID (1) */ HID_GI_REPORT_ID, - 0x02, + HID_REPORT_ID_CONSUMER, /* USAGE_PAGE (Consumer) */ HID_GI_USAGE_PAGE, HID_USAGE_CONSUMER, @@ -187,12 +190,27 @@ static const uint8_t zmk_hid_report_desc[] = { HID_MI_COLLECTION_END, }; -// struct zmk_hid_boot_report -// { -// uint8_t modifiers; -// uint8_t _unused; -// uint8_t keys[6]; -// } __packed; +typedef enum { + HID_REPORT_FULL, + HID_REPORT_BODY +} zmk_hid_report_request_t; + +#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 +typedef struct +{ + zmk_mod_flags_t modifiers; + uint8_t _reserved; + uint8_t keys[6]; +} __packed zmk_hid_boot_report_t; +#endif +#endif struct zmk_hid_keyboard_report_body { zmk_mod_flags_t modifiers; @@ -237,5 +255,5 @@ int zmk_hid_consumer_press(zmk_key_t key); int zmk_hid_consumer_release(zmk_key_t key); void zmk_hid_consumer_clear(); -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); +uint8_t *zmk_hid_get_keyboard_report(zmk_hid_report_request_t req_t, uint8_t proto); +uint8_t *zmk_hid_get_consumer_report(zmk_hid_report_request_t req_t); diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index 7523fb66..a7909edf 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -11,5 +11,5 @@ int zmk_hog_init(); -int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); -int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); +int zmk_hog_send_keyboard_report(); +int zmk_hog_send_consumer_report(); diff --git a/app/include/zmk/usb.h b/app/include/zmk/usb.h index 62a7e3cb..e54ff861 100644 --- a/app/include/zmk/usb.h +++ b/app/include/zmk/usb.h @@ -25,5 +25,5 @@ static inline bool zmk_usb_is_powered() { return zmk_usb_get_conn_state() != ZMK static inline bool zmk_usb_is_hid_ready() { return zmk_usb_get_conn_state() == ZMK_USB_CONN_HID; } #ifdef CONFIG_ZMK_USB -int zmk_usb_hid_send_report(const uint8_t *report, size_t len); +int zmk_usb_hid_send_report(uint8_t report_id); #endif /* CONFIG_ZMK_USB */ \ No newline at end of file diff --git a/app/src/endpoints.c b/app/src/endpoints.c index c15aad87..c716db31 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -72,12 +72,10 @@ int zmk_endpoints_toggle() { } static int send_keyboard_report() { - struct zmk_hid_keyboard_report *keyboard_report = zmk_hid_get_keyboard_report(); - switch (current_endpoint) { #if IS_ENABLED(CONFIG_ZMK_USB) case ZMK_ENDPOINT_USB: { - int err = zmk_usb_hid_send_report((uint8_t *)keyboard_report, sizeof(*keyboard_report)); + int err = zmk_usb_hid_send_report(HID_REPORT_ID_KEYBOARD); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); } @@ -87,7 +85,7 @@ static int send_keyboard_report() { #if IS_ENABLED(CONFIG_ZMK_BLE) case ZMK_ENDPOINT_BLE: { - int err = zmk_hog_send_keyboard_report(&keyboard_report->body); + int err = zmk_hog_send_keyboard_report(); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); } @@ -102,12 +100,10 @@ static int send_keyboard_report() { } static int send_consumer_report() { - struct zmk_hid_consumer_report *consumer_report = zmk_hid_get_consumer_report(); - switch (current_endpoint) { #if IS_ENABLED(CONFIG_ZMK_USB) case ZMK_ENDPOINT_USB: { - int err = zmk_usb_hid_send_report((uint8_t *)consumer_report, sizeof(*consumer_report)); + int err = zmk_usb_hid_send_report(HID_REPORT_ID_CONSUMER); if (err) { LOG_ERR("FAILED TO SEND OVER USB: %d", err); } @@ -117,7 +113,7 @@ static int send_consumer_report() { #if IS_ENABLED(CONFIG_ZMK_BLE) case ZMK_ENDPOINT_BLE: { - int err = zmk_hog_send_consumer_report(&consumer_report->body); + int err = zmk_hog_send_consumer_report(); if (err) { LOG_ERR("FAILED TO SEND OVER HOG: %d", err); } diff --git a/app/src/hid.c b/app/src/hid.c index d6c63e1d..141c6951 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -11,9 +11,20 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include static struct zmk_hid_keyboard_report keyboard_report = { - .report_id = 1, .body = {.modifiers = 0, ._reserved = 0, .keys = {0}}}; + .report_id = HID_REPORT_ID_KEYBOARD, .body = {.modifiers = 0, ._reserved = 0, .keys = {0}}}; -static struct zmk_hid_consumer_report consumer_report = {.report_id = 2, .body = {.keys = {0}}}; +static struct zmk_hid_consumer_report consumer_report = {.report_id = HID_REPORT_ID_CONSUMER, .body = {.keys = {0}}}; + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +#if !IS_ENABLED(CONFIG_ZMK_HID_REPORT_TYPE_HKRO) || CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE != HID_BOOT_KEY_LEN +static zmk_hid_boot_report_t boot_report = { + .modifiers = 0, + ._reserved = 0, + .keys = {0} +}; +#endif +static uint8_t keys_held = 0; +#endif // Keep track of how often a modifier was pressed. // Only release the modifier if the count is 0. @@ -76,15 +87,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 error_report = { + .modifiers = 0, + ._reserved = 0, + .keys = { + HID_ERROR_ROLLOVER, HID_ERROR_ROLLOVER, HID_ERROR_ROLLOVER, + HID_ERROR_ROLLOVER, HID_ERROR_ROLLOVER, HID_ERROR_ROLLOVER + } +}; + +#define HID_CHECK_ROLLOVER_ERROR() \ + if (keys_held >= HID_BOOT_KEY_LEN) { \ + error_report.modifiers = keyboard_report.body.modifiers; \ + return &error_report; \ + } + +#endif + #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) +static zmk_hid_boot_report_t *get_boot_report() { + HID_CHECK_ROLLOVER_ERROR(); + + 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 (!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; +} + 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; } @@ -93,6 +147,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; } @@ -109,13 +166,31 @@ static inline int deselect_keyboard_usage(zmk_key_t usage) { } \ } +static zmk_hid_boot_report_t *get_boot_report() { + HID_CHECK_ROLLOVER_ERROR(); +#if CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE != HID_BOOT_KEY_LEN + boot_report.modifiers = keyboard_report.body.modifiers; + int len = CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE < HID_BOOT_KEY_LEN ? CONFIG_ZMK_HID_KEYBOARD_REPORT_SIZE : HID_BOOT_KEY_LEN; + memcpy(&boot_report.keys, keyboard_report.body.keys, len); + return &boot_report; +#else + return &keyboard_report.body; +#endif +} + 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; } @@ -178,10 +253,17 @@ int zmk_hid_consumer_release(zmk_key_t code) { void zmk_hid_consumer_clear() { memset(&consumer_report.body, 0, sizeof(consumer_report.body)); } -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { - return &keyboard_report; +uint8_t *zmk_hid_get_keyboard_report(zmk_hid_report_request_t req_t, uint8_t proto) { + if (proto == HID_PROTOCOL_REPORT) { + return req_t == HID_REPORT_FULL ? (uint8_t*)&keyboard_report : (uint8_t*)&keyboard_report.body; + } +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + return (uint8_t*)get_boot_report(); +#else + return NULL; +#endif } -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report() { - return &consumer_report; +uint8_t *zmk_hid_get_consumer_report(zmk_hid_report_request_t req_t) { + return req_t == HID_REPORT_FULL ? (uint8_t*)&consumer_report : (uint8_t*)&consumer_report.body; } diff --git a/app/src/hog.c b/app/src/hog.c index e8aceca3..7970b0dd 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -47,12 +47,12 @@ enum { }; static struct hids_report input = { - .id = 0x01, + .id = HID_REPORT_ID_KEYBOARD, .type = HIDS_INPUT, }; static struct hids_report consumer_input = { - .id = 0x02, + .id = HID_REPORT_ID_CONSUMER, .type = HIDS_INPUT, }; @@ -80,7 +80,7 @@ static ssize_t read_hids_report_map(struct bt_conn *conn, const struct bt_gatt_a static ssize_t read_hids_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { - struct zmk_hid_keyboard_report_body *report_body = &zmk_hid_get_keyboard_report()->body; + uint8_t *report_body = zmk_hid_get_keyboard_report(HID_REPORT_BODY, HID_PROTOCOL_REPORT); return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, sizeof(struct zmk_hid_keyboard_report_body)); } @@ -88,7 +88,7 @@ static ssize_t read_hids_input_report(struct bt_conn *conn, const struct bt_gatt static ssize_t read_hids_consumer_input_report(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) { - struct zmk_hid_consumer_report_body *report_body = &zmk_hid_get_consumer_report()->body; + uint8_t *report_body = zmk_hid_get_consumer_report(HID_REPORT_BODY); return bt_gatt_attr_read(conn, attr, buf, len, offset, report_body, sizeof(struct zmk_hid_consumer_report_body)); } @@ -190,7 +190,8 @@ void send_keyboard_report_callback(struct k_work *work) { K_WORK_DEFINE(hog_keyboard_work, send_keyboard_report_callback); -int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *report) { +int zmk_hog_send_keyboard_report() { + uint8_t *report = zmk_hid_get_keyboard_report(HID_REPORT_BODY, HID_PROTOCOL_REPORT); int err = k_msgq_put(&zmk_hog_keyboard_msgq, report, K_MSEC(100)); if (err) { switch (err) { @@ -240,7 +241,8 @@ void send_consumer_report_callback(struct k_work *work) { K_WORK_DEFINE(hog_consumer_work, send_consumer_report_callback); -int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *report) { +int zmk_hog_send_consumer_report() { + uint8_t *report = zmk_hid_get_consumer_report(HID_REPORT_BODY); int err = k_msgq_put(&zmk_hog_consumer_msgq, report, K_MSEC(100)); if (err) { switch (err) { diff --git a/app/src/usb.c b/app/src/usb.c index 2f0fa439..d2ead98c 100644 --- a/app/src/usb.c +++ b/app/src/usb.c @@ -34,11 +34,57 @@ 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 + +static int zmk_usb_get_report(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) { + return -ENOTSUP; + } + + uint8_t *report = NULL; + switch (setup->wValue & HID_GET_REPORT_ID_MASK) { + case HID_REPORT_ID_KEYBOARD: + report = zmk_hid_get_keyboard_report(HID_REPORT_FULL, HID_PROTOCOL_REPORT); + *len = sizeof(struct zmk_hid_keyboard_report); + break; + case HID_REPORT_ID_CONSUMER: + report = zmk_hid_get_consumer_report(HID_REPORT_FULL); + *len = sizeof(struct zmk_hid_consumer_report); + break; + } + + *data = report; + return 0; +} + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +static uint8_t hid_protocol = HID_PROTOCOL_REPORT; +void zmk_usb_set_proto_cb(const struct device *dev, uint8_t protocol) { + hid_protocol = protocol; +} +#endif + static const struct hid_ops ops = { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + .protocol_change = zmk_usb_set_proto_cb, +#endif .int_in_ready = in_ready_cb, + .get_report = zmk_usb_get_report }; -int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { +int zmk_usb_hid_send_report(uint8_t report_id) { switch (usb_status) { case USB_DC_SUSPEND: return usb_wakeup_request(); @@ -49,6 +95,27 @@ int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { return -ENODEV; default: k_sem_take(&hid_sem, K_MSEC(30)); + uint8_t *report; + size_t len; + + if (report_id == HID_REPORT_ID_KEYBOARD) { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + report = zmk_hid_get_keyboard_report(HID_REPORT_FULL, hid_protocol); + len = hid_protocol == HID_PROTOCOL_BOOT ? sizeof(zmk_hid_boot_report_t) : sizeof(struct zmk_hid_keyboard_report); +#else + report = zmk_hid_get_keyboard_report(HID_REPORT_FULL, hid_protocol); + len = sizeof(struct zmk_hid_keyboard_report); +#endif + } else { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol == HID_PROTOCOL_BOOT) { + return -ENOTSUP; + } +#endif + report = zmk_hid_get_consumer_report(HID_REPORT_FULL); + len = sizeof(struct zmk_hid_consumer_report); + } + int err = hid_int_ep_write(hid_dev, report, len, NULL); if (err) { @@ -80,6 +147,11 @@ enum zmk_usb_conn_state zmk_usb_get_conn_state() { } void usb_status_cb(enum usb_dc_status_code status, const uint8_t *params) { +#ifdef CONFIG_ZMK_USB_BOOT + if (status == USB_DC_RESET) { + hid_protocol = HID_PROTOCOL_REPORT; + } +#endif usb_status = status; k_work_submit(&usb_status_notifier_work); };