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 <lorne@cons.org.nz>
This commit is contained in:
parent
b80c0be0ce
commit
91aa3378f3
7 changed files with 229 additions and 15 deletions
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -6,4 +6,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
int zmk_usb_hid_send_report(const uint8_t *report, size_t len);
|
||||
#include <stdint.h>
|
||||
|
||||
int zmk_usb_hid_send_keyboard_report();
|
||||
int zmk_usb_hid_send_consumer_report();
|
||||
void zmk_usb_hid_set_protocol(uint8_t protocol);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/usb_conn_state_changed.h>
|
||||
|
||||
#include <zmk/usb_hid.h>
|
||||
|
||||
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);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue