From b3195b95e0289ca04d079ab2c686dc122d7690f7 Mon Sep 17 00:00:00 2001 From: sakumisu <1203593632@qq.com> Date: Tue, 30 Jun 2026 21:25:35 +0800 Subject: feat(platform): add zmk support Signed-off-by: sakumisu <1203593632@qq.com> --- docs/zh/quick_start/opensource.rst | 4 + platform/zmk/usb.c | 226 +++++++++++++++++++++++++++++++++ platform/zmk/usb_hid.c | 254 +++++++++++++++++++++++++++++++++++++ 3 files changed, 484 insertions(+) create mode 100644 platform/zmk/usb.c create mode 100644 platform/zmk/usb_hid.c diff --git a/docs/zh/quick_start/opensource.rst b/docs/zh/quick_start/opensource.rst index 24ffc0bb..ed730dbc 100644 --- a/docs/zh/quick_start/opensource.rst +++ b/docs/zh/quick_start/opensource.rst @@ -71,3 +71,7 @@ * - MuseCube - dydcyy-gh - https://github.com/dydcyy-gh/MuseCube + * - zmk + - OpenSiFli + - https://github.com/OpenSiFli/zmk + diff --git a/platform/zmk/usb.c b/platform/zmk/usb.c new file mode 100644 index 00000000..40144d58 --- /dev/null +++ b/platform/zmk/usb.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include "usbd_core.h" +#include "usbd_hid.h" + +#include +#include +#include +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define CHERRYUSB_BUS_ID 0 + +/* USB status tracking, mirroring the CherryUSB event states */ +enum cherryusb_status { + CUSB_STATUS_UNKNOWN, + CUSB_STATUS_CONNECTED, + CUSB_STATUS_DISCONNECTED, + CUSB_STATUS_CONFIGURED, + CUSB_STATUS_SUSPEND, + CUSB_STATUS_RESUME, + CUSB_STATUS_RESET, + CUSB_STATUS_ERROR, +}; + +static enum cherryusb_status usb_status = CUSB_STATUS_UNKNOWN; + +static void raise_usb_status_changed_event(struct k_work *_work) { + raise_zmk_usb_conn_state_changed( + (struct zmk_usb_conn_state_changed){.conn_state = zmk_usb_get_conn_state()}); +} + +K_WORK_DEFINE(usb_status_notifier_work, raise_usb_status_changed_event); + +enum zmk_usb_conn_state zmk_usb_get_conn_state(void) { + LOG_DBG("state: %d", usb_status); + switch (usb_status) { + case CUSB_STATUS_CONFIGURED: + case CUSB_STATUS_SUSPEND: + case CUSB_STATUS_RESUME: + return ZMK_USB_CONN_HID; + + case CUSB_STATUS_DISCONNECTED: + case CUSB_STATUS_UNKNOWN: + return ZMK_USB_CONN_NONE; + + default: + return ZMK_USB_CONN_POWERED; + } +} + +bool zmk_usb_is_hid_ready(void) { + return usb_status == CUSB_STATUS_CONFIGURED || usb_status == CUSB_STATUS_RESUME; +} + +/* ---- USB Descriptors ---- */ + +#define USBD_VID CONFIG_USB_DEVICE_VID +#define USBD_PID CONFIG_USB_DEVICE_PID +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +#define HID_INT_EP 0x81 +#define HID_INT_EP_SIZE 64 +#define HID_INT_EP_INTERVAL 1 + +#define USB_HID_CONFIG_DESC_SIZ (9 + HID_KEYBOARD_DESCRIPTOR_LEN) + +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, + USBD_VID, USBD_PID, 0x0001, 0x01), +}; + +static const uint8_t config_descriptor[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_HID_CONFIG_DESC_SIZ, 0x01, 0x01, + USB_CONFIG_BUS_POWERED | USB_CONFIG_REMOTE_WAKEUP, + USBD_MAX_POWER), + HID_KEYBOARD_DESCRIPTOR_INIT(0x00, +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + 0x01, /* boot interface subclass */ +#else + 0x00, /* no boot */ +#endif + sizeof(zmk_hid_report_desc), + HID_INT_EP, HID_INT_EP_SIZE, HID_INT_EP_INTERVAL), +}; + +static const uint8_t device_quality_descriptor[] = { + 0x0a, + USB_DESCRIPTOR_TYPE_DEVICE_QUALIFIER, + 0x00, 0x02, + 0x00, 0x00, 0x00, + 0x40, + 0x00, 0x00, +}; + +#ifndef CONFIG_USB_DEVICE_MANUFACTURER +#define CONFIG_USB_DEVICE_MANUFACTURER "ZMK Project" +#endif + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + CONFIG_USB_DEVICE_MANUFACTURER, /* Manufacturer */ + CONFIG_ZMK_KEYBOARD_NAME, /* Product */ + "ZMK000000", /* Serial Number */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) { + (void)speed; + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) { + (void)speed; + return config_descriptor; +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) { + (void)speed; + return device_quality_descriptor; +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) { + (void)speed; + if (index >= ARRAY_SIZE(string_descriptors)) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor zmk_cherryusb_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback, +}; + +/* ---- CherryUSB event handler ---- */ + +static void usbd_event_handler(uint8_t busid, uint8_t event) { + LOG_DBG("USB event: bus %d, event %d\n", busid, event); + switch (event) { + case USBD_EVENT_RESET: +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + extern void zmk_usb_hid_set_protocol(uint8_t protocol); + zmk_usb_hid_set_protocol(1); /* HID_PROTOCOL_REPORT */ +#endif + usb_status = CUSB_STATUS_RESET; + break; + case USBD_EVENT_CONNECTED: + usb_status = CUSB_STATUS_CONNECTED; + break; + case USBD_EVENT_DISCONNECTED: + usb_status = CUSB_STATUS_DISCONNECTED; + break; + case USBD_EVENT_CONFIGURED: + usb_status = CUSB_STATUS_CONFIGURED; + break; + case USBD_EVENT_SUSPEND: + usb_status = CUSB_STATUS_SUSPEND; + break; + case USBD_EVENT_RESUME: + usb_status = CUSB_STATUS_RESUME; + break; + case USBD_EVENT_ERROR: + usb_status = CUSB_STATUS_ERROR; + break; + default: + return; + } + k_work_submit(&usb_status_notifier_work); +} + +/* ---- HID interface and endpoint (shared with usb_hid.c) ---- */ + +struct usbd_interface zmk_cherryusb_hid_intf; + +static void hid_in_ep_callback(uint8_t busid, uint8_t ep, uint32_t nbytes); + +struct usbd_endpoint zmk_cherryusb_hid_in_ep = { + .ep_cb = hid_in_ep_callback, + .ep_addr = HID_INT_EP, +}; + +/* Semaphore for EP write completion, given from endpoint callback */ +K_SEM_DEFINE(zmk_cherryusb_hid_sem, 1, 1); + +static void hid_in_ep_callback(uint8_t busid, uint8_t ep, uint32_t nbytes) { + (void)busid; + (void)ep; + (void)nbytes; + k_sem_give(&zmk_cherryusb_hid_sem); +} + +static int zmk_usb_init(void) { + usbd_desc_register(CHERRYUSB_BUS_ID, &zmk_cherryusb_descriptor); + + usbd_add_interface(CHERRYUSB_BUS_ID, + usbd_hid_init_intf(CHERRYUSB_BUS_ID, &zmk_cherryusb_hid_intf, + zmk_hid_report_desc, + sizeof(zmk_hid_report_desc))); + usbd_add_endpoint(CHERRYUSB_BUS_ID, &zmk_cherryusb_hid_in_ep); + + int ret = usbd_initialize(CHERRYUSB_BUS_ID, + (uintptr_t)CONFIG_CHERRYUSB_DEV_BASE_ADDR, + usbd_event_handler); + if (ret != 0) { + LOG_ERR("CherryUSB initialization failed (err %d)", ret); + return -EINVAL; + } + + LOG_INF("CherryUSB initialized"); + return 0; +} + +SYS_INIT(zmk_usb_init, APPLICATION, CONFIG_ZMK_USB_INIT_PRIORITY); \ No newline at end of file diff --git a/platform/zmk/usb_hid.c b/platform/zmk/usb_hid.c new file mode 100644 index 00000000..81bc0d4f --- /dev/null +++ b/platform/zmk/usb_hid.c @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include + +#include "usbd_core.h" +#include "usbd_hid.h" + +#include +#include +#include + +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) +#include +#endif + +#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) +#include +#endif + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#define CHERRYUSB_BUS_ID 0 +#define HID_INT_EP 0x81 + +/* Defined in cherryusb/usb.c */ +extern struct k_sem zmk_cherryusb_hid_sem; + +/* Write buffer must be USB-aligned and in non-cached memory */ +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t hid_write_buffer[128]; + +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) +static uint8_t hid_protocol = 1; /* HID_PROTOCOL_REPORT */ + +void zmk_usb_hid_set_protocol(uint8_t protocol) { hid_protocol = protocol; } +#endif + +static uint8_t *get_keyboard_report(size_t *len) { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol != 1) { /* boot protocol */ + zmk_hid_boot_report_t *boot_report = zmk_hid_get_boot_report(); + *len = sizeof(*boot_report); + return (uint8_t *)boot_report; + } +#endif + struct zmk_hid_keyboard_report *report = zmk_hid_get_keyboard_report(); + *len = sizeof(*report); + return (uint8_t *)report; +} + +/* ---- CherryUSB HID class callbacks (weak overrides) ---- */ + +void usbd_hid_get_report(uint8_t busid, uint8_t intf, uint8_t report_id, + uint8_t report_type, uint8_t **data, uint32_t *len) { + (void)busid; + (void)intf; + + switch (report_type) { + case 3: /* Feature */ + switch (report_id) { +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + case ZMK_HID_REPORT_ID_MOUSE: { + static struct zmk_hid_mouse_resolution_feature_report res_feature_report; + struct zmk_endpoint_instance endpoint = { + .transport = ZMK_TRANSPORT_USB, + }; + *len = sizeof(struct zmk_hid_mouse_resolution_feature_report); + struct zmk_pointing_resolution_multipliers mult = + zmk_pointing_resolution_multipliers_get_profile(endpoint); + res_feature_report.body.wheel_res = mult.wheel; + res_feature_report.body.hwheel_res = mult.hor_wheel; + *data = (uint8_t *)&res_feature_report; + break; + } +#endif + default: + *len = 0; + break; + } + break; + + case 1: /* Input */ + switch (report_id) { + case ZMK_HID_REPORT_ID_KEYBOARD: { + size_t size; + *data = get_keyboard_report(&size); + *len = (uint32_t)size; + 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", report_id); + *len = 0; + break; + } + break; + + default: + LOG_ERR("Unsupported report type %d requested", report_type); + *len = 0; + break; + } +} + +void usbd_hid_set_report(uint8_t busid, uint8_t intf, uint8_t report_id, + uint8_t report_type, uint8_t *report, uint32_t report_len) { + (void)busid; + (void)intf; + + switch (report_type) { + case 3: /* Feature */ + switch (report_id) { +#if IS_ENABLED(CONFIG_ZMK_POINTING_SMOOTH_SCROLLING) + case ZMK_HID_REPORT_ID_MOUSE: + if (report_len != sizeof(struct zmk_hid_mouse_resolution_feature_report)) { + return; + } + + struct zmk_hid_mouse_resolution_feature_report *feat = + (struct zmk_hid_mouse_resolution_feature_report *)report; + struct zmk_endpoint_instance endpoint = { + .transport = ZMK_TRANSPORT_USB, + }; + zmk_pointing_resolution_multipliers_process_report(&feat->body, endpoint); + break; +#endif + default: + break; + } + break; + + case 2: /* Output */ + switch (report_id) { +#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) + case ZMK_HID_REPORT_ID_LEDS: + if (report_len != sizeof(struct zmk_hid_led_report)) { + LOG_ERR("LED set report is malformed: length=%d", report_len); + return; + } + struct zmk_hid_led_report *led_report = (struct zmk_hid_led_report *)report; + struct zmk_endpoint_instance ep = { + .transport = ZMK_TRANSPORT_USB, + }; + zmk_hid_indicators_process_report(&led_report->body, ep); + break; +#endif + default: + LOG_ERR("Invalid report ID %d for output", report_id); + break; + } + break; + + default: + LOG_ERR("Unsupported report type %d", report_type); + break; + } +} + +void usbd_hid_set_idle(uint8_t busid, uint8_t intf, uint8_t report_id, uint8_t duration) { + (void)busid; + (void)intf; + (void)report_id; + (void)duration; +} + +uint8_t usbd_hid_get_idle(uint8_t busid, uint8_t intf, uint8_t report_id) { + (void)busid; + (void)intf; + (void)report_id; + return 0; +} + +uint8_t usbd_hid_get_protocol(uint8_t busid, uint8_t intf) { + (void)busid; + (void)intf; +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + return hid_protocol; +#else + return 1; /* Report protocol */ +#endif +} + +void usbd_hid_set_protocol(uint8_t busid, uint8_t intf, uint8_t protocol) { + (void)busid; + (void)intf; +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + hid_protocol = protocol; +#endif +} + +/* ---- Report sending ---- */ + +static int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { + if (!zmk_usb_is_hid_ready()) { + if (zmk_usb_get_conn_state() == ZMK_USB_CONN_HID) { + /* Suspended - try remote wakeup */ + return usbd_send_remote_wakeup(CHERRYUSB_BUS_ID); + } + return -ENODEV; + } + + if (len > sizeof(hid_write_buffer)) { + return -EINVAL; + } + + k_sem_take(&zmk_cherryusb_hid_sem, K_MSEC(30)); + memcpy(hid_write_buffer, report, len); + int err = usbd_ep_start_write(CHERRYUSB_BUS_ID, HID_INT_EP, hid_write_buffer, len); + if (err) { + k_sem_give(&zmk_cherryusb_hid_sem); + } + return err; +} + +int zmk_usb_hid_send_keyboard_report(void) { + 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(void) { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol != 1) { + return -ENOTSUP; + } +#endif + struct zmk_hid_consumer_report *report = zmk_hid_get_consumer_report(); + return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); +} + +#if IS_ENABLED(CONFIG_ZMK_POINTING) +int zmk_usb_hid_send_mouse_report(void) { +#if IS_ENABLED(CONFIG_ZMK_USB_BOOT) + if (hid_protocol != 1) { + return -ENOTSUP; + } +#endif + struct zmk_hid_mouse_report *report = zmk_hid_get_mouse_report(); + return zmk_usb_hid_send_report((uint8_t *)report, sizeof(*report)); +} +#endif /* CONFIG_ZMK_POINTING */ \ No newline at end of file -- cgit v1.3.1