From 9b79afa6d76d25d786e5bd15a055014748005b72 Mon Sep 17 00:00:00 2001 From: Benedek Kupper Date: Mon, 14 Sep 2026 15:43:29 +0200 Subject: [PATCH] UHK60 LED control through HID LampArray interface --- patches/c2usb/0001-Add-diagnostics-logs.patch | 791 +++++++++--------- right/.vscode/launch.json | 13 +- right/prj.conf | 3 + right/src/hid/keyboard_app.cpp | 372 +++++++- right/src/hid/keyboard_app.hpp | 135 ++- right/src/hid/report_ids.h | 27 +- right/src/hid/transport_usb.cpp | 10 +- right/src/ledmap.c | 14 +- right/src/ledmap.h | 5 + right/src/main.c | 6 + submanifests/c2usb.yml | 2 +- 11 files changed, 938 insertions(+), 440 deletions(-) diff --git a/patches/c2usb/0001-Add-diagnostics-logs.patch b/patches/c2usb/0001-Add-diagnostics-logs.patch index 13dc06ff0..55fde5cfc 100644 --- a/patches/c2usb/0001-Add-diagnostics-logs.patch +++ b/patches/c2usb/0001-Add-diagnostics-logs.patch @@ -1,24 +1,24 @@ -From b4de2fb0e2aec895efa6656d37f3b1b89dd60d8b Mon Sep 17 00:00:00 2001 +From c4e86c2b2f1fa83b2bdd11842f37bc3b7707aa21 Mon Sep 17 00:00:00 2001 From: Karel Tucek Date: Sat, 11 Jul 2026 13:05:57 +0200 Subject: [PATCH] Add diagnostics logs. --- - c2usb/CMakeLists.txt | 1 + - c2usb/port/nxp/mcux_mac.cpp | 44 +++++++- - c2usb/port/zephyr/udc_mac.cpp | 62 +++++++++++ - c2usb/usb/df/device.cpp | 3 + - c2usb/usb/df/mac_diag.cpp | 191 ++++++++++++++++++++++++++++++++++ - c2usb/usb/df/mac_diag.hpp | 84 +++++++++++++++ + c2usb/CMakeLists.txt | 1 + + c2usb/usb/df/device.cpp | 3 + + c2usb/usb/df/mac_diag.cpp | 191 +++++++++++++++++++++++++ + c2usb/usb/df/mac_diag.hpp | 84 +++++++++++ + c2usb/usb/df/vendor/nxp/mcux_mac.cpp | 44 +++++- + c2usb/usb/df/vendor/zephyr/udc_mac.cpp | 62 ++++++++ 6 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 c2usb/usb/df/mac_diag.cpp create mode 100644 c2usb/usb/df/mac_diag.hpp diff --git a/c2usb/CMakeLists.txt b/c2usb/CMakeLists.txt -index 123ef42..320c189 100644 +index 62a14da..4538a20 100644 --- a/c2usb/CMakeLists.txt +++ b/c2usb/CMakeLists.txt -@@ -78,6 +78,7 @@ if(CONFIG_C2USB_DF) +@@ -84,6 +84,7 @@ if(CONFIG_C2USB_DF) usb/df/device.cpp usb/df/function.cpp usb/df/mac.cpp @@ -26,405 +26,109 @@ index 123ef42..320c189 100644 usb/df/message.cpp usb/standard/descriptors.cpp ) -diff --git a/c2usb/port/nxp/mcux_mac.cpp b/c2usb/port/nxp/mcux_mac.cpp -index 3d197e7..a806314 100644 ---- a/c2usb/port/nxp/mcux_mac.cpp -+++ b/c2usb/port/nxp/mcux_mac.cpp -@@ -18,6 +18,9 @@ - #include - #endif - +diff --git a/c2usb/usb/df/device.cpp b/c2usb/usb/df/device.cpp +index 23d3926..76dd220 100644 +--- a/c2usb/usb/df/device.cpp ++++ b/c2usb/usb/df/device.cpp +@@ -2,6 +2,8 @@ + #include "usb/df/device.hpp" + #include "usb/df/function.hpp" + #include "usb/standard/descriptors.hpp" +// UHK patch: anomaly diagnostics, see usb/df/mac_diag.hpp. +#include "usb/df/mac_diag.hpp" -+ - namespace usb::df::nxp - { - struct controller_interface : public ::_usb_device_controller_interface_struct -@@ -128,6 +131,8 @@ void mcux_mac::init(const speeds& speeds) - [[maybe_unused]] auto status = driver_.deviceInit(index_, this, &handle_); - assert(status == kStatus_USB_Success); - -+ diag::set_mac(this); -+ - EnableIRQ(usb_irqn(index_)); - } - -@@ -193,6 +198,7 @@ void mcux_mac::control_ep_open() - - void mcux_mac::control_ep_stall() - { -+ diag::record(diag::CTRL_STALL, 0, 0); - auto addr = endpoint::address::control_out(); - [[maybe_unused]] auto status = - driver_.device_control(handle(), kUSB_DeviceControlEndpointStall, &addr); -@@ -223,14 +229,21 @@ usb::result mcux_mac::ep_send(ep_handle eph, const std::span& dat - auto addr = ep_handle_to_address(eph); - if (power_state() != power::state::L0_ON) - { -+ diag::record(diag::EP_NO_POWER, addr, (int)power_state()); - return result::network_down; - } - if (busy_flags_.test_and_set(addr)) - { -+ diag::record(diag::EP_BUSY, addr, 0); - return usb::result::device_or_resource_busy; - } -- return to_result( -- driver_.deviceSend(handle(), addr, const_cast(data.data()), data.size())); -+ auto status = -+ driver_.deviceSend(handle(), addr, const_cast(data.data()), data.size()); -+ if (status != kStatus_USB_Success) -+ { -+ diag::record(diag::ENQUEUE_FAIL, addr, status); -+ } -+ return to_result(status); - } - usb::result mcux_mac::ep_receive(ep_handle eph, const std::span& data) -@@ -238,9 +251,15 @@ usb::result mcux_mac::ep_receive(ep_handle eph, const std::span& data) - auto addr = ep_handle_to_address(eph); - if (busy_flags_.test_and_set(addr)) - { -+ diag::record(diag::EP_BUSY, addr, 0); - return usb::result::device_or_resource_busy; - } -- return to_result(driver_.deviceRecv(handle(), addr, data.data(), data.size())); -+ auto status = driver_.deviceRecv(handle(), addr, data.data(), data.size()); -+ if (status != kStatus_USB_Success) -+ { -+ diag::record(diag::ENQUEUE_FAIL, addr, status); -+ } -+ return to_result(status); - } + using namespace magic_enum::bitwise_operators; - usb::result mcux_mac::ep_cancel(ep_handle eph) -@@ -299,6 +318,7 @@ void mcux_mac::process_ep_notification(const _usb_device_callback_message_struct - { - // control transfer is cancelled at bus reset - // react to the reset signal only, ignore this event -+ diag::record(diag::CTRL_ERROR, message.code, 0); - return; +@@ -163,6 +165,7 @@ void device::set_configuration(message& msg) } - if (message.isSetup) -@@ -361,6 +381,17 @@ void mcux_mac::process_ep_notification(const _usb_device_callback_message_struct - { - busy_flags_.clear(addr); - -+ if (cancelled) -+ { -+ // cancelled transfer: a report that was handed to the controller -+ // never reached the host (dropped) -+ diag::record(diag::EP_ERROR, message.code, 0); -+ c2usb_log("HID report dropped: EP %02x transfer cancelled\n", message.code); -+ } -+ else -+ { -+ diag::success(addr); -+ } - ep_transfer_complete(addr, transfer(message.buffer, cancelled ? 0 : message.length, - not cancelled, ep_address_to_handle(addr))); } -@@ -378,28 +409,35 @@ void mcux_mac::process_notification(const _usb_device_callback_message_struct& m - switch (message.code) - { - case kUSB_DeviceNotifyBusReset: -+ diag::bus(diag::bus_event::RESET); - driver_.device_control(handle(), kUSB_DeviceControlSetDefaultStatus); - bus_reset(); - control_ep_open(); - break; - case kUSB_DeviceNotifySuspend: -+ diag::bus(diag::bus_event::SUSPEND); - set_power_state(power::state::L2_SUSPEND); - break; - case kUSB_DeviceNotifyResume: -+ diag::bus(diag::bus_event::RESUME); - set_power_state(power::state::L0_ON); - break; - case kUSB_DeviceNotifyLPMSleep: -+ diag::bus(diag::bus_event::SLEEP); - set_power_state(power::state::L1_SLEEP); - break; - case kUSB_DeviceNotifyDetach: -+ diag::bus(diag::bus_event::VBUS_OFF); - set_power_state(power::state::L3_OFF); - break; - case kUSB_DeviceNotifyAttach: -+ diag::bus(diag::bus_event::VBUS_ON); - set_power_state(power::state::L2_SUSPEND); - break; - // case kUSB_DeviceNotifyDcdDetectFinished: - case kUSB_DeviceNotifyError: -+ diag::bus(diag::bus_event::ERROR); - break; - default: - process_ep_notification(message); -diff --git a/c2usb/port/zephyr/udc_mac.cpp b/c2usb/port/zephyr/udc_mac.cpp -index 0dddfd1..79a9578 100644 ---- a/c2usb/port/zephyr/udc_mac.cpp -+++ b/c2usb/port/zephyr/udc_mac.cpp -@@ -2,6 +2,7 @@ - #include "port/zephyr/udc_mac.hpp" - #include - #include "compatibility_helper.hpp" -+#include - #include - #include - extern "C" -@@ -9,6 +10,19 @@ extern "C" - #include - } ++ diag::bus(config_index ? diag::bus_event::SET_CONFIG : diag::bus_event::DECONFIG); + set_configuration(config); -+// UHK patch: anomaly diagnostics, see usb/df/mac_diag.hpp. + return msg.confirm(); +diff --git a/c2usb/usb/df/mac_diag.cpp b/c2usb/usb/df/mac_diag.cpp +new file mode 100644 +index 0000000..243d1ed +--- /dev/null ++++ b/c2usb/usb/df/mac_diag.cpp +@@ -0,0 +1,191 @@ ++/// @file ++/// ++/// UHK patch: port-agnostic MAC anomaly diagnostics, see mac_diag.hpp. ++/// +#include "usb/df/mac_diag.hpp" ++#include "usb/df/mac.hpp" + -+extern "C" uint32_t c2usb_diag_time_ms(void) ++extern "C" __attribute__((weak)) void c2usb_log(const char*, ...) {} ++extern "C" __attribute__((weak)) uint32_t c2usb_diag_time_ms(void) +{ -+ return k_uptime_get_32(); ++ return 0; +} ++extern "C" __attribute__((weak)) void c2usb_diag_dump_port(void) {} ++ ++using namespace usb::df; + +namespace +{ -+uint32_t diag_msgq_min_free = UINT32_MAX; -+} // namespace ++const char* const category_names[diag::CATEGORY_COUNT] = { ++ "enqueue fail", "EP error", "ctrl error", "ctrl stall", ++ "evt dropped", "EP busy reject", "no-power reject", ++}; + - #if defined(CONFIG_DEBUG) == defined(NDEBUG) - // for assert() to be active in debug configuration only, this is necessary - #error "Either CONFIG_DEBUG or NDEBUG must be defined" -@@ -104,6 +118,7 @@ static int udc_buf_enqueue(const ::device* dev, ::net_buf* buf) - if (ret != 0) - { - LOG_ERR("Failed to enqueue ep 0x%02x: %d", udc_get_buf_info(buf)->ep, ret); -+ diag::record(diag::ENQUEUE_FAIL, udc_get_buf_info(buf)->ep, ret); - } - return ret; - } -@@ -142,6 +157,7 @@ udc_mac::udc_mac(const ::device* dev, size_t ctrl_ep_buf_size, usb::power::state - } - set_control_buffer(std::span(ctrl_buf_->data, ctrl_buf_->size)); - -+ diag::set_mac(this); - set_driver_ctx(); - } - -@@ -221,9 +237,14 @@ static int udc_mac_event_dispatch(const ::device*, const udc_event* event) - last_full = !message_queue().try_post(*event); - if (last_full) - { -+ diag::record(diag::EVENT_DROPPED, 0, event->type); - __ASSERT_PRINT("udc_mac_msgq full\n"); - __ASSERT_POST_ACTION(); - } -+ if (auto free_space = message_queue().free_space(); free_space < diag_msgq_min_free) ++struct diag_stat ++{ ++ uint32_t total; // since boot / reset ++ uint32_t since_ok; // since last successful data EP transfer ++ uint32_t last_time; // ms ++ int last_err; ++ uint8_t last_ep; ++}; ++ ++struct diag_bus_entry ++{ ++ uint32_t time; ++ uint32_t count; // consecutive events of the same type are collapsed ++ diag::bus_event type; ++}; ++ ++constexpr size_t BUS_RING_SIZE = 16; ++ ++struct ++{ ++ diag_stat stats[diag::CATEGORY_COUNT]; ++ diag_bus_entry bus_ring[BUS_RING_SIZE]; ++ uint8_t bus_ring_next; ++ uint32_t last_ok_time; ++ uint8_t last_ok_ep; ++ uint32_t ok_count; ++ mac* mac_; ++} state; ++ ++const char* bus_name(diag::bus_event type) ++{ ++ switch (type) + { -+ diag_msgq_min_free = free_space; -+ } - #if CONFIG_C2USB_UDC_MAC_LOG_LEVEL >= LOG_LEVEL_DBG - static auto min_free_msgq_space = message_queue().free_space(); - if (auto free_space = message_queue().free_space(); free_space < min_free_msgq_space) -@@ -342,6 +363,7 @@ uint16_t udc_mac::control_ep_max_packet_size(usb::speed speed) const - - void udc_mac::ctrl_stall(net_buf* buf, int err) - { -+ diag::record(diag::CTRL_STALL, 0, err); - auto addr = endpoint::address::control_in(); - if ((request().direction() == usb::direction::OUT) and request().wLength and (err == -ENOMEM)) - { -@@ -512,6 +534,7 @@ void udc_mac::process_ctrl_ep_event(net_buf* buf, const udc_buf_info& info) - net_buf_unref(buf); - LOG_WRN("CTRL EP %x (stage %d) error: %d", info.ep, - info.setup * 0 + info.data * 1 + info.status * 2, info.err); -+ diag::record(diag::CTRL_ERROR, info.ep, info.err); - } - } - -@@ -539,6 +562,29 @@ int udc_mac::event_callback(const udc_event& event) - - int udc_mac::process_event(const udc_event& event) - { -+ switch (event.type) -+ { -+ case UDC_EVT_RESET: -+ diag::bus(diag::bus_event::RESET); -+ break; -+ case UDC_EVT_SUSPEND: -+ diag::bus(diag::bus_event::SUSPEND); -+ break; -+ case UDC_EVT_RESUME: -+ diag::bus(diag::bus_event::RESUME); -+ break; -+ case UDC_EVT_VBUS_READY: -+ diag::bus(diag::bus_event::VBUS_ON); -+ break; -+ case UDC_EVT_VBUS_REMOVED: -+ diag::bus(diag::bus_event::VBUS_OFF); -+ break; -+ case UDC_EVT_ERROR: -+ diag::bus(diag::bus_event::ERROR); -+ break; -+ default: -+ break; -+ } - if ((power_state() == power::state::L3_OFF) and (event.type != UDC_EVT_VBUS_READY)) [[unlikely]] - { - // flush late events after Vbus removal -@@ -617,6 +663,7 @@ void udc_mac::process_ctrl_ep(net_buf* buf, const udc_buf_info& info) - { - LOG_WRN("CTRL EP %x (stage %d) error: %d", info.ep, - info.setup * 0 + info.data * 1 + info.status * 2, info.err); -+ diag::record(diag::CTRL_ERROR, info.ep, info.err); - - if (info.setup or (info.data and (dir == direction::OUT))) - { -@@ -747,6 +794,12 @@ void udc_mac::process_ep_event(net_buf* buf) - if (info.err != 0) - { - LOG_ERR("EP %x error:%d", info.ep, info.err); -+ diag::record(diag::EP_ERROR, info.ep, info.err); -+ c2usb_log("HID report dropped: EP %02x transfer error %d\n", info.ep, info.err); -+ } -+ else -+ { -+ diag::success(info.ep); - } - for (uint8_t i = 0; i < ep_bufs_.size(); ++i) - { -@@ -869,10 +922,12 @@ usb::result udc_mac::ep_transfer(usb::df::ep_handle eph, const transfer& t, usb: - #endif - if ((dir == direction::IN) and (power_state() != power::state::L0_ON)) - { -+ diag::record(diag::EP_NO_POWER, addr, (int)power_state()); - return result::network_down; - } - if (busy_flags_.test_and_set(addr)) - { -+ diag::record(diag::EP_BUSY, addr, 0); - return result::device_or_resource_busy; - } - -@@ -883,6 +938,7 @@ usb::result udc_mac::ep_transfer(usb::df::ep_handle eph, const transfer& t, usb: - auto ret = udc_ep_enqueue(dev_, buf); - if (ret != 0) - { -+ diag::record(diag::ENQUEUE_FAIL, addr, ret); - busy_flags_.clear(addr); - } - return usb::result(ret); -@@ -976,4 +1032,10 @@ bool udc_mac::setup_test_mode(uint8_t mode_selector) - return ret == 0; - } - -+extern "C" void c2usb_diag_dump_port(void) -+{ -+ c2usb_log(" evt queue free=%u min=%u\n", (unsigned)message_queue().free_space(), -+ (unsigned)diag_msgq_min_free); -+} -+ - } // namespace usb::zephyr -diff --git a/c2usb/usb/df/device.cpp b/c2usb/usb/df/device.cpp -index f7797c8..85c81dc 100644 ---- a/c2usb/usb/df/device.cpp -+++ b/c2usb/usb/df/device.cpp -@@ -2,6 +2,8 @@ - #include "usb/df/device.hpp" - #include "usb/df/function.hpp" - #include "usb/standard/descriptors.hpp" -+// UHK patch: anomaly diagnostics, see usb/df/mac_diag.hpp. -+#include "usb/df/mac_diag.hpp" - - using namespace magic_enum::bitwise_operators; - -@@ -163,6 +165,7 @@ void device::set_configuration(message& msg) - } - } - -+ diag::bus(config_index ? diag::bus_event::SET_CONFIG : diag::bus_event::DECONFIG); - set_config(config); - - return msg.confirm(); -diff --git a/c2usb/usb/df/mac_diag.cpp b/c2usb/usb/df/mac_diag.cpp -new file mode 100644 -index 0000000..243d1ed ---- /dev/null -+++ b/c2usb/usb/df/mac_diag.cpp -@@ -0,0 +1,191 @@ -+/// @file -+/// -+/// UHK patch: port-agnostic MAC anomaly diagnostics, see mac_diag.hpp. -+/// -+#include "usb/df/mac_diag.hpp" -+#include "usb/df/mac.hpp" -+ -+extern "C" __attribute__((weak)) void c2usb_log(const char*, ...) {} -+extern "C" __attribute__((weak)) uint32_t c2usb_diag_time_ms(void) -+{ -+ return 0; -+} -+extern "C" __attribute__((weak)) void c2usb_diag_dump_port(void) {} -+ -+using namespace usb::df; -+ -+namespace -+{ -+const char* const category_names[diag::CATEGORY_COUNT] = { -+ "enqueue fail", "EP error", "ctrl error", "ctrl stall", -+ "evt dropped", "EP busy reject", "no-power reject", -+}; -+ -+struct diag_stat -+{ -+ uint32_t total; // since boot / reset -+ uint32_t since_ok; // since last successful data EP transfer -+ uint32_t last_time; // ms -+ int last_err; -+ uint8_t last_ep; -+}; -+ -+struct diag_bus_entry -+{ -+ uint32_t time; -+ uint32_t count; // consecutive events of the same type are collapsed -+ diag::bus_event type; -+}; -+ -+constexpr size_t BUS_RING_SIZE = 16; -+ -+struct -+{ -+ diag_stat stats[diag::CATEGORY_COUNT]; -+ diag_bus_entry bus_ring[BUS_RING_SIZE]; -+ uint8_t bus_ring_next; -+ uint32_t last_ok_time; -+ uint8_t last_ok_ep; -+ uint32_t ok_count; -+ mac* mac_; -+} state; -+ -+const char* bus_name(diag::bus_event type) -+{ -+ switch (type) -+ { -+ case diag::bus_event::RESET: -+ return "reset"; -+ case diag::bus_event::SUSPEND: -+ return "suspend"; -+ case diag::bus_event::RESUME: -+ return "resume"; -+ case diag::bus_event::SLEEP: -+ return "sleep"; -+ case diag::bus_event::VBUS_ON: -+ return "vbus-on"; -+ case diag::bus_event::VBUS_OFF: -+ return "vbus-off"; -+ case diag::bus_event::ERROR: -+ return "error"; -+ case diag::bus_event::SET_CONFIG: -+ return "set-config"; -+ case diag::bus_event::DECONFIG: -+ return "deconfig"; -+ default: -+ return "?"; ++ case diag::bus_event::RESET: ++ return "reset"; ++ case diag::bus_event::SUSPEND: ++ return "suspend"; ++ case diag::bus_event::RESUME: ++ return "resume"; ++ case diag::bus_event::SLEEP: ++ return "sleep"; ++ case diag::bus_event::VBUS_ON: ++ return "vbus-on"; ++ case diag::bus_event::VBUS_OFF: ++ return "vbus-off"; ++ case diag::bus_event::ERROR: ++ return "error"; ++ case diag::bus_event::SET_CONFIG: ++ return "set-config"; ++ case diag::bus_event::DECONFIG: ++ return "deconfig"; ++ default: ++ return "?"; + } +} + @@ -630,6 +334,311 @@ index 0000000..d240521 +#endif + +#endif // __USB_DF_MAC_DIAG_HPP_ +diff --git a/c2usb/usb/df/vendor/nxp/mcux_mac.cpp b/c2usb/usb/df/vendor/nxp/mcux_mac.cpp +index 7027e3e..6d7b586 100644 +--- a/c2usb/usb/df/vendor/nxp/mcux_mac.cpp ++++ b/c2usb/usb/df/vendor/nxp/mcux_mac.cpp +@@ -18,6 +18,9 @@ + #include + #endif + ++// UHK patch: anomaly diagnostics, see usb/df/mac_diag.hpp. ++#include "usb/df/mac_diag.hpp" ++ + namespace usb::df::nxp + { + struct controller_interface : public ::_usb_device_controller_interface_struct +@@ -128,6 +131,8 @@ void mcux_mac::init(const speeds& speeds) + [[maybe_unused]] auto status = driver_.deviceInit(index_, this, &handle_); + assert(status == kStatus_USB_Success); + ++ diag::set_mac(this); ++ + EnableIRQ(usb_irqn(index_)); + } + +@@ -193,6 +198,7 @@ void mcux_mac::control_ep_open() + + void mcux_mac::control_ep_stall() + { ++ diag::record(diag::CTRL_STALL, 0, 0); + auto addr = endpoint::address::control_out(); + [[maybe_unused]] auto status = + driver_.device_control(handle(), kUSB_DeviceControlEndpointStall, &addr); +@@ -223,14 +229,21 @@ usb::result mcux_mac::ep_send(ep_handle eph, const std::span& dat + auto addr = ep_handle_to_address(eph); + if (power_state() != power::state::L0_ON) + { ++ diag::record(diag::EP_NO_POWER, addr, (int)power_state()); + return result::network_down; + } + if (busy_flags_.test_and_set(addr)) + { ++ diag::record(diag::EP_BUSY, addr, 0); + return usb::result::device_or_resource_busy; + } +- return to_result( +- driver_.deviceSend(handle(), addr, const_cast(data.data()), data.size())); ++ auto status = ++ driver_.deviceSend(handle(), addr, const_cast(data.data()), data.size()); ++ if (status != kStatus_USB_Success) ++ { ++ diag::record(diag::ENQUEUE_FAIL, addr, status); ++ } ++ return to_result(status); + } + + usb::result mcux_mac::ep_receive(ep_handle eph, const std::span& data) +@@ -238,9 +251,15 @@ usb::result mcux_mac::ep_receive(ep_handle eph, const std::span& data) + auto addr = ep_handle_to_address(eph); + if (busy_flags_.test_and_set(addr)) + { ++ diag::record(diag::EP_BUSY, addr, 0); + return usb::result::device_or_resource_busy; + } +- return to_result(driver_.deviceRecv(handle(), addr, data.data(), data.size())); ++ auto status = driver_.deviceRecv(handle(), addr, data.data(), data.size()); ++ if (status != kStatus_USB_Success) ++ { ++ diag::record(diag::ENQUEUE_FAIL, addr, status); ++ } ++ return to_result(status); + } + + usb::result mcux_mac::ep_cancel(ep_handle eph) +@@ -299,6 +318,7 @@ void mcux_mac::process_ep_notification(const _usb_device_callback_message_struct + { + // control transfer is cancelled at bus reset + // react to the reset signal only, ignore this event ++ diag::record(diag::CTRL_ERROR, message.code, 0); + return; + } + if (message.isSetup) +@@ -361,6 +381,17 @@ void mcux_mac::process_ep_notification(const _usb_device_callback_message_struct + { + busy_flags_.clear(addr); + ++ if (cancelled) ++ { ++ // cancelled transfer: a report that was handed to the controller ++ // never reached the host (dropped) ++ diag::record(diag::EP_ERROR, message.code, 0); ++ c2usb_log("HID report dropped: EP %02x transfer cancelled\n", message.code); ++ } ++ else ++ { ++ diag::success(addr); ++ } + ep_transfer_complete(addr, transfer(message.buffer, cancelled ? 0 : message.length, + not cancelled, ep_address_to_handle(addr))); + } +@@ -378,6 +409,7 @@ void mcux_mac::process_notification(const _usb_device_callback_message_struct& m + switch (message.code) + { + case kUSB_DeviceNotifyBusReset: ++ diag::bus(diag::bus_event::RESET); + driver_.device_control(handle(), kUSB_DeviceControlSetDefaultStatus); + bus_reset(); + control_ep_open(); +@@ -385,13 +417,16 @@ void mcux_mac::process_notification(const _usb_device_callback_message_struct& m + + #if (defined(USB_DEVICE_CONFIG_LOW_POWER_MODE) && (USB_DEVICE_CONFIG_LOW_POWER_MODE > 0U)) + case kUSB_DeviceNotifySuspend: ++ diag::bus(diag::bus_event::SUSPEND); + set_power_state(power::state::L2_SUSPEND); + break; + case kUSB_DeviceNotifyResume: ++ diag::bus(diag::bus_event::RESUME); + set_power_state(power::state::L0_ON); + break; + #if (defined(USB_DEVICE_CONFIG_LPM_L1) && (USB_DEVICE_CONFIG_LPM_L1 > 0U)) + case kUSB_DeviceNotifyLPMSleep: ++ diag::bus(diag::bus_event::SLEEP); + set_power_state(power::state::L1_SLEEP); + break; + #endif +@@ -405,9 +440,11 @@ void mcux_mac::process_notification(const _usb_device_callback_message_struct& m + + #if USB_DEVICE_CONFIG_DETACH_ENABLE + case kUSB_DeviceNotifyDetach: ++ diag::bus(diag::bus_event::VBUS_OFF); + set_power_state(power::state::L3_OFF); + break; + case kUSB_DeviceNotifyAttach: ++ diag::bus(diag::bus_event::VBUS_ON); + set_power_state(power::state::L2_SUSPEND); + break; + #endif +@@ -417,6 +454,7 @@ void mcux_mac::process_notification(const _usb_device_callback_message_struct& m + #endif + #if (defined(USB_DEVICE_CONFIG_ERROR_HANDLING) && (USB_DEVICE_CONFIG_ERROR_HANDLING > 0U)) + case kUSB_DeviceNotifyError: ++ diag::bus(diag::bus_event::ERROR); + break; + #endif + case kUSB_DeviceNotifyNoop: +diff --git a/c2usb/usb/df/vendor/zephyr/udc_mac.cpp b/c2usb/usb/df/vendor/zephyr/udc_mac.cpp +index 0cb3630..ffc3b7a 100644 +--- a/c2usb/usb/df/vendor/zephyr/udc_mac.cpp ++++ b/c2usb/usb/df/vendor/zephyr/udc_mac.cpp +@@ -2,6 +2,7 @@ + #include "usb/df/vendor/zephyr/udc_mac.hpp" + #include + #include "compatibility_helper.hpp" ++#include + #include + #include + extern "C" +@@ -9,6 +10,19 @@ extern "C" + #include + } + ++// UHK patch: anomaly diagnostics, see usb/df/mac_diag.hpp. ++#include "usb/df/mac_diag.hpp" ++ ++extern "C" uint32_t c2usb_diag_time_ms(void) ++{ ++ return k_uptime_get_32(); ++} ++ ++namespace ++{ ++uint32_t diag_msgq_min_free = UINT32_MAX; ++} // namespace ++ + #if defined(CONFIG_DEBUG) == defined(NDEBUG) + // for assert() to be active in debug configuration only, this is necessary + #error "Either CONFIG_DEBUG or NDEBUG must be defined" +@@ -102,6 +116,7 @@ static int udc_buf_enqueue(const ::device* dev, ::net_buf* buf) + if (ret != 0) + { + LOG_ERR("Failed to enqueue ep 0x%02x: %d", udc_get_buf_info(buf)->ep, ret); ++ diag::record(diag::ENQUEUE_FAIL, udc_get_buf_info(buf)->ep, ret); + } + return ret; + } +@@ -140,6 +155,7 @@ udc_mac::udc_mac(const ::device* dev, size_t ctrl_ep_buf_size, usb::power::state + } + set_control_buffer(std::span(ctrl_buf_->data, ctrl_buf_->size)); + ++ diag::set_mac(this); + set_driver_ctx(); + } + +@@ -219,9 +235,14 @@ static int udc_mac_event_dispatch(const ::device*, const udc_event* event) + last_full = !message_queue().try_post(*event); + if (last_full) + { ++ diag::record(diag::EVENT_DROPPED, 0, event->type); + __ASSERT_PRINT("udc_mac_msgq full\n"); + __ASSERT_POST_ACTION(); + } ++ if (auto free_space = message_queue().free_space(); free_space < diag_msgq_min_free) ++ { ++ diag_msgq_min_free = free_space; ++ } + #if CONFIG_C2USB_UDC_MAC_LOG_LEVEL >= LOG_LEVEL_DBG + static auto min_free_msgq_space = message_queue().free_space(); + if (auto free_space = message_queue().free_space(); free_space < min_free_msgq_space) +@@ -340,6 +361,7 @@ uint16_t udc_mac::control_ep_max_packet_size(usb::speed speed) const + + void udc_mac::ctrl_stall(net_buf* buf, int err) + { ++ diag::record(diag::CTRL_STALL, 0, err); + auto addr = endpoint::address::control_in(); + if ((request().direction() == usb::direction::OUT) and request().wLength and (err == -ENOMEM)) + { +@@ -510,6 +532,7 @@ void udc_mac::process_ctrl_ep_event(net_buf* buf, const udc_buf_info& info) + net_buf_unref(buf); + LOG_WRN("CTRL EP %x (stage %d) error: %d", info.ep, + info.setup * 0 + info.data * 1 + info.status * 2, info.err); ++ diag::record(diag::CTRL_ERROR, info.ep, info.err); + } + } + +@@ -537,6 +560,29 @@ int udc_mac::event_callback(const udc_event& event) + + int udc_mac::process_event(const udc_event& event) + { ++ switch (event.type) ++ { ++ case UDC_EVT_RESET: ++ diag::bus(diag::bus_event::RESET); ++ break; ++ case UDC_EVT_SUSPEND: ++ diag::bus(diag::bus_event::SUSPEND); ++ break; ++ case UDC_EVT_RESUME: ++ diag::bus(diag::bus_event::RESUME); ++ break; ++ case UDC_EVT_VBUS_READY: ++ diag::bus(diag::bus_event::VBUS_ON); ++ break; ++ case UDC_EVT_VBUS_REMOVED: ++ diag::bus(diag::bus_event::VBUS_OFF); ++ break; ++ case UDC_EVT_ERROR: ++ diag::bus(diag::bus_event::ERROR); ++ break; ++ default: ++ break; ++ } + if ((power_state() == power::state::L3_OFF) and (event.type != UDC_EVT_VBUS_READY)) [[unlikely]] + { + // flush late events after Vbus removal +@@ -617,6 +663,7 @@ void udc_mac::process_ctrl_ep(net_buf* buf, const udc_buf_info& info) + { + LOG_WRN("CTRL EP %x (stage %d) error: %d", info.ep, + info.setup * 0 + info.data * 1 + info.status * 2, info.err); ++ diag::record(diag::CTRL_ERROR, info.ep, info.err); + + if (info.setup or (info.data and (dir == direction::OUT))) + { +@@ -747,6 +794,12 @@ void udc_mac::process_ep_event(net_buf* buf) + if (info.err != 0) + { + LOG_ERR("EP %x error:%d", info.ep, info.err); ++ diag::record(diag::EP_ERROR, info.ep, info.err); ++ c2usb_log("HID report dropped: EP %02x transfer error %d\n", info.ep, info.err); ++ } ++ else ++ { ++ diag::success(info.ep); + } + for (uint8_t i = 0; i < ep_bufs_.size(); ++i) + { +@@ -875,10 +928,12 @@ usb::result udc_mac::ep_transfer(usb::df::ep_handle eph, const transfer& t, usb: + #endif + if ((dir == direction::IN) and (power_state() != power::state::L0_ON)) + { ++ diag::record(diag::EP_NO_POWER, addr, (int)power_state()); + return result::network_down; + } + if (busy_flags_.test_and_set(addr)) + { ++ diag::record(diag::EP_BUSY, addr, 0); + return result::device_or_resource_busy; + } + +@@ -889,6 +944,7 @@ usb::result udc_mac::ep_transfer(usb::df::ep_handle eph, const transfer& t, usb: + auto ret = udc_ep_enqueue(dev_, buf); + if (ret != 0) + { ++ diag::record(diag::ENQUEUE_FAIL, addr, ret); + busy_flags_.clear(addr); + } + return usb::result(ret); +@@ -982,4 +1038,10 @@ bool udc_mac::setup_test_mode(uint8_t mode_selector) + return ret == 0; + } + ++extern "C" void c2usb_diag_dump_port(void) ++{ ++ c2usb_log(" evt queue free=%u min=%u\n", (unsigned)message_queue().free_space(), ++ (unsigned)diag_msgq_min_free); ++} ++ + } // namespace usb::df::zephyr -- 2.43.0 diff --git a/right/.vscode/launch.json b/right/.vscode/launch.json index a484518ed..6c274eaec 100644 --- a/right/.vscode/launch.json +++ b/right/.vscode/launch.json @@ -44,7 +44,18 @@ "runToEntryPoint": "main", "armToolchainPath": "${env:ARM_GCC_DIR}/bin", "svdFile": "${workspaceFolder}/../svd/MK22F51212.xml", - "preLaunchTask": "Build + Merge UHK 60 right (v2-release)" + "preLaunchTask": "Build + Merge UHK 60 right (v2-release)", + "rttConfig": { + "enabled": true, + "address": "auto", + "decoders": [ + { + "label": "RTT", + "port": 0, + "type": "console" + } + ] + } } ], "inputs": [ diff --git a/right/prj.conf b/right/prj.conf index e924f5afb..1e545e44f 100644 --- a/right/prj.conf +++ b/right/prj.conf @@ -45,3 +45,6 @@ CONFIG_MCUX_COMPONENT_driver.pit=y CONFIG_MCUX_COMPONENT_driver.rtc=y CONFIG_MCUX_COMPONENT_driver.uart=n CONFIG_MCUX_COMPONENT_driver.lpuart=n + +# Segger J-Link RTT debug console, enable for PRINTF debugging +#CONFIG_MCUX_COMPONENT_utility.debug_console_rtt=y diff --git a/right/src/hid/keyboard_app.cpp b/right/src/hid/keyboard_app.cpp index 6e9451e45..3e6d36b58 100644 --- a/right/src/hid/keyboard_app.cpp +++ b/right/src/hid/keyboard_app.cpp @@ -1,16 +1,76 @@ #include "keyboard_app.hpp" extern "C" { #include "hid/transport.h" +#include "ledmap.h" #include "usb_state.h" #include "utils.h" #if __has_include() #include + #define PRINTF printk +#elif __has_include("fsl_debug_console.h") + #include "fsl_debug_console.h" #endif #ifdef __ZEPHYR__ -#include "connections.h" + #include "connections.h" #endif } +#if DEVICE_IS_UHK60 +// TODO: tune this value to match reality +static constexpr uint8_t LED_UPDATE_DELAY_MS = 20; + +struct position_mm { + uint16_t x; + uint16_t y; +}; + +static constexpr uint16_t lamp_position_z = 15; + +static constexpr auto left_lamp_positions = std::to_array({// first row + {16, 33}, {35, 33}, {54, 33}, {73, 33}, {92, 33}, {112, 33}, {131, 33}, + // second row + {20, 52}, {43, 52}, {63, 52}, {82, 52}, {101, 52}, {120, 52}, + // third row + {23, 71}, {48, 71}, {67, 71}, {86, 71}, {105, 71}, {124, 71}, + // fourth row + {18, 91}, {40, 91}, {59, 91}, {78, 91}, {97, 91}, {116, 91}, {135, 91}, + // fifth row + {18, 110}, {42, 110}, {66, 110}, {89, 110}, {117, 110}, + // key cluster + {154, 91}, {144, 110}, {168, 117}}); +static_assert(left_lamp_positions.size() == keyboard_session::LEFT_LAMP_COUNT); + +static constexpr auto right_lamp_positions = std::to_array({// first row + {21, 33}, {40, 33}, {59, 33}, {78, 33}, {97, 33}, {116, 33}, {140, 33}, + // second row + {10, 52}, {29, 52}, {48, 52}, {77, 52}, {86, 52}, {105, 52}, {124, 52}, {143, 52}, + // third row + {14, 71}, {33, 71}, {52, 71}, {71, 71}, {90, 71}, {110, 71}, {137, 71}, + // fourth row + {24, 91}, {43, 91}, {63, 91}, {82, 91}, {101, 91}, {132, 91}, + // fifth row + {22, 110}, {51, 110}, {75, 110}, {98, 110}, {132, 110}}); +static_assert(right_lamp_positions.size() == keyboard_session::RIGHT_LAMP_COUNT); + +// intensity_level_count is 1, so hosts only distinguish "off" (0) from "on" (non-zero) +static rgb_t lamp_rgbi_to_rgb(const hid::app::lamparray::rgbi_tuple &rgbi) +{ + if (rgbi.intensity == 0) { + return rgb_t{}; + } + return rgb_t{ + .red = static_cast(rgbi.red), + .green = static_cast(rgbi.green), + .blue = static_cast(rgbi.blue), + }; +} +#endif + +keyboard_app::keyboard_app(const hid::report_protocol &rp) + : hid::application(rp), + usb_function_{*this, nullptr, {}, usb::hid::boot_protocol_mode::KEYBOARD} +{} + void keyboard_app::set_rollover(rollover_t mode) { // swap the HID report descriptor, which needs USB re-enumeration @@ -115,16 +175,154 @@ keyboard_session::leds_boot_report keyboard_session::get_leds_report() const void keyboard_session::set_report(hid::report::type type, const std::span &data) { - // only one report is receivable, the LEDs - if (type != hid::report::type::OUTPUT) { - return; - } + if (type == hid::report::type::OUTPUT) { + keyboard_leds_changed_callback(*this); + + // always keep receiving new reports + // if the report data is processed immediately, the same buffer can be used + receive_report(&leds_buffer_); + } else { +#if DEVICE_IS_UHK60 + if (data.empty()) { + return; + } + switch (hid::report::selector(type, data.front())) { + case left_lamp_attrs_req_report::selector(): + if (data.size() >= sizeof(left_lamp_attrs_req_report)) { + auto *req = reinterpret_cast(data.data()); + if (req->lamp_id < keyboard_session::LEFT_LAMP_COUNT) { + req_led_left_ = req->lamp_id; + } + } + break; + case right_lamp_attrs_req_report::selector(): + if (data.size() >= sizeof(right_lamp_attrs_req_report)) { + auto *req = reinterpret_cast(data.data()); + if (req->lamp_id < keyboard_session::RIGHT_LAMP_COUNT) { + req_led_right_ = req->lamp_id; + } + } + break; + case left_lamp_control_report::selector(): + if (data.size() >= sizeof(left_lamp_control_report)) { + auto *report = reinterpret_cast(data.data()); + if (report->autonomous_mode) { + led_remote_mode_flags_ &= ~left_led_remote_mode_flag; + } else { + led_remote_mode_flags_ |= left_led_remote_mode_flag; + } + if (led_remote_mode_flags_ == led_remote_mode) { + Ledmap_SetTemporaryLedBacklightingMode(BacklightingMode_HidLampArray); + } else { + Ledmap_ResetTemporaryLedBacklightingMode(); + Ledmap_TriggerFullUpdate(); + } + } + break; + case right_lamp_control_report::selector(): + if (data.size() >= sizeof(right_lamp_control_report)) { + auto *report = reinterpret_cast(data.data()); + if (report->autonomous_mode) { + led_remote_mode_flags_ &= ~right_led_remote_mode_flag; + } else { + led_remote_mode_flags_ |= right_led_remote_mode_flag; + } + if (led_remote_mode_flags_ == led_remote_mode) { + Ledmap_SetTemporaryLedBacklightingMode(BacklightingMode_HidLampArray); + } else { + Ledmap_ResetTemporaryLedBacklightingMode(); + Ledmap_TriggerFullUpdate(); + } + } + break; + case left_lamp_multi_update_report::selector(): + if (data.size() >= sizeof(left_lamp_multi_update_report)) { + auto *report = reinterpret_cast(data.data()); + if (report->lamp_count > keyboard_session::LEFT_LAMP_COUNT) { + break; + } + for (size_t i = 0; i < report->lamp_count; ++i) { + uint16_t lamp_id = report->lamp_ids[i]; + if (lamp_id >= keyboard_session::LEFT_LAMP_COUNT) { + continue; + } + rgb_t color = lamp_rgbi_to_rgb(report->values[i]); - keyboard_leds_changed_callback(*this); + if (lamp_id < LEFT_HALF_LAMP_COUNT) { + Ledmap_SetKeyColor(&color, SlotId_LeftKeyboardHalf, lamp_id); + } else if (lamp_id < LEFT_LAMP_COUNT) { + Ledmap_SetKeyColor( + &color, SlotId_LeftModule, lamp_id - LEFT_HALF_LAMP_COUNT); + } + } + if (report->update_flags == hid::app::lamparray::update_flags::COMPLETE) { + Ledmap_TriggerFullUpdate(); + } + } + break; + case right_lamp_multi_update_report::selector(): + if (data.size() >= sizeof(right_lamp_multi_update_report)) { + auto *report = + reinterpret_cast(data.data()); + if (report->lamp_count > keyboard_session::RIGHT_LAMP_COUNT) { + break; + } + for (size_t i = 0; i < report->lamp_count; ++i) { + uint16_t lamp_id = report->lamp_ids[i]; + if (lamp_id >= keyboard_session::RIGHT_LAMP_COUNT) { + continue; + } + rgb_t color = lamp_rgbi_to_rgb(report->values[i]); - // always keep receiving new reports - // if the report data is processed immediately, the same buffer can be used - receive_report(&leds_buffer_); + Ledmap_SetKeyColor(&color, SlotId_RightKeyboardHalf, lamp_id); + } + if (report->update_flags == hid::app::lamparray::update_flags::COMPLETE) { + Ledmap_TriggerFullUpdate(); + } + } + break; + case left_lamp_range_update_report::selector(): + if (data.size() >= sizeof(left_lamp_range_update_report)) { + auto *report = reinterpret_cast(data.data()); + if ((report->lamp_id_start > report->lamp_id_end) or + (report->lamp_id_end >= keyboard_session::LEFT_LAMP_COUNT)) { + break; + } + rgb_t color = lamp_rgbi_to_rgb(report->value); + for (size_t i = report->lamp_id_start; i <= report->lamp_id_end; ++i) { + if (i < LEFT_HALF_LAMP_COUNT) { + Ledmap_SetKeyColor(&color, SlotId_LeftKeyboardHalf, i); + } else if (i < LEFT_LAMP_COUNT) { + Ledmap_SetKeyColor(&color, SlotId_LeftModule, i - LEFT_HALF_LAMP_COUNT); + } + } + if (report->update_flags == hid::app::lamparray::update_flags::COMPLETE) { + Ledmap_TriggerFullUpdate(); + } + } + break; + case right_lamp_range_update_report::selector(): + if (data.size() >= sizeof(right_lamp_range_update_report)) { + auto *report = + reinterpret_cast(data.data()); + if ((report->lamp_id_start > report->lamp_id_end) or + (report->lamp_id_end >= keyboard_session::RIGHT_LAMP_COUNT)) { + break; + } + rgb_t color = lamp_rgbi_to_rgb(report->value); + for (size_t i = report->lamp_id_start; i <= report->lamp_id_end; ++i) { + Ledmap_SetKeyColor(&color, SlotId_RightKeyboardHalf, i); + } + if (report->update_flags == hid::app::lamparray::update_flags::COMPLETE) { + Ledmap_TriggerFullUpdate(); + } + } + break; + default: + break; + } +#endif + } } void keyboard_session::report_sent(const std::span &data) @@ -150,36 +348,140 @@ std::span keyboard_session::get_report( return {}; } - if (select == leds_report::selector()) { + switch (select) { + case keyboard_app::keys_6kro_report::selector(): + assert(buffer.size() >= sizeof(keyboard_app::keys_6kro_report)); + std::ignore = new (buffer.data()) keyboard_app::keys_6kro_report{}; + return buffer.subspan(0, sizeof(keyboard_app::keys_6kro_report)); + + case keyboard_app::keys_nkro_report::selector(): + assert(buffer.size() >= sizeof(keyboard_app::keys_nkro_report)); + std::ignore = new (buffer.data()) keyboard_app::keys_nkro_report{}; + return buffer.subspan(0, sizeof(keyboard_app::keys_nkro_report)); + + case leds_report::selector(): { assert(buffer.size() >= sizeof(leds_report)); auto *ptr = new (buffer.data()) leds_report{}; ptr->leds = leds_buffer_.leds; return buffer.subspan(0, sizeof(leds_report)); } - if constexpr (report_ids::IN_KEYBOARD_NKRO == 0) { - // no report ID, use the rollover mode to determine which report to send - if (select.type() == hid::report::type::INPUT) { - if (HID_GetKeyboardRollover() == rollover_t::ROLLOVER_N_KEY) { - assert(buffer.size() >= sizeof(keyboard_app::keys_nkro_report)); - std::ignore = new (buffer.data()) keyboard_app::keys_nkro_report{}; - return buffer.subspan(0, sizeof(keyboard_app::keys_nkro_report)); - } else { - assert(buffer.size() >= sizeof(keyboard_app::keys_6kro_report)); - std::ignore = new (buffer.data()) keyboard_app::keys_6kro_report{}; - return buffer.subspan(0, sizeof(keyboard_app::keys_6kro_report)); - } - } - } else { - if (select == keyboard_app::keys_6kro_report::selector()) { - assert(buffer.size() >= sizeof(keyboard_app::keys_6kro_report)); - std::ignore = new (buffer.data()) keyboard_app::keys_6kro_report{}; - return buffer.subspan(0, sizeof(keyboard_app::keys_6kro_report)); - } - if (select == keyboard_app::keys_nkro_report::selector()) { - assert(buffer.size() >= sizeof(keyboard_app::keys_nkro_report)); - std::ignore = new (buffer.data()) keyboard_app::keys_nkro_report{}; - return buffer.subspan(0, sizeof(keyboard_app::keys_nkro_report)); - } + +#if DEVICE_IS_UHK60 + case left_lamp_array_attrs_report::selector(): { + assert(buffer.size() >= sizeof(left_lamp_array_attrs_report)); + auto *ptr = new (buffer.data()) left_lamp_array_attrs_report{}; + // UHK60 left + module + ptr->lamp_count = LEFT_LAMP_COUNT; + ptr->bounding_box.width = 185 * 1000; // um + ptr->bounding_box.height = 136 * 1000; // um + ptr->bounding_box.depth = 30 * 1000; // um + ptr->min_update_interval = LED_UPDATE_DELAY_MS * 1000; + ptr->kind = hid::app::lamparray::kind::KEYBOARD; + return buffer.subspan(0, sizeof(left_lamp_array_attrs_report)); + } + + case right_lamp_array_attrs_report::selector(): { + assert(buffer.size() >= sizeof(right_lamp_array_attrs_report)); + auto *ptr = new (buffer.data()) right_lamp_array_attrs_report{}; + // UHK60 right + ptr->lamp_count = RIGHT_LAMP_COUNT; + ptr->bounding_box.width = 159 * 1000; // um + ptr->bounding_box.height = 130 * 1000; // um + ptr->bounding_box.depth = 30 * 1000; // um + ptr->min_update_interval = LED_UPDATE_DELAY_MS * 1000; + ptr->kind = hid::app::lamparray::kind::KEYBOARD; + return buffer.subspan(0, sizeof(right_lamp_array_attrs_report)); + } + + case left_lamp_attrs_req_report::selector(): { + assert(buffer.size() >= sizeof(left_lamp_attrs_req_report)); + auto *ptr = new (buffer.data()) left_lamp_attrs_req_report{}; + ptr->lamp_id = req_led_left_; + return buffer.subspan(0, sizeof(left_lamp_attrs_req_report)); + } + + case right_lamp_attrs_req_report::selector(): { + assert(buffer.size() >= sizeof(right_lamp_attrs_req_report)); + auto *ptr = new (buffer.data()) right_lamp_attrs_req_report{}; + ptr->lamp_id = req_led_right_; + return buffer.subspan(0, sizeof(right_lamp_attrs_req_report)); + } + + case left_lamp_attrs_rsp_report::selector(): { + assert(buffer.size() >= sizeof(left_lamp_attrs_rsp_report)); + auto *ptr = new (buffer.data()) left_lamp_attrs_rsp_report{}; + ptr->position.x = left_lamp_positions[req_led_left_].x * 1000; + ptr->position.y = left_lamp_positions[req_led_left_].y * 1000; + ptr->position.z = lamp_position_z * 1000; + ptr->update_latency = LED_UPDATE_DELAY_MS / 2 * 1000; + ptr->red_level_count = std::numeric_limits::max(); + ptr->green_level_count = std::numeric_limits::max(); + ptr->blue_level_count = std::numeric_limits::max(); + ptr->intensity_level_count = 1; + ptr->is_programmable = true; + ptr->purposes = hid::app::lamparray::purposes::CONTROL | + hid::app::lamparray::purposes::ACCENT | + hid::app::lamparray::purposes::STATUS; + // TODO set according to mapped key + ptr->input_binding = 0; + + ptr->lamp_id = req_led_left_; + req_led_left_ = (req_led_left_ + 1) % LEFT_LAMP_COUNT; + return buffer.subspan(0, sizeof(left_lamp_attrs_rsp_report)); + } + + case right_lamp_attrs_rsp_report::selector(): { + assert(buffer.size() >= sizeof(right_lamp_attrs_rsp_report)); + auto *ptr = new (buffer.data()) right_lamp_attrs_rsp_report{}; + ptr->position.x = right_lamp_positions[req_led_right_].x * 1000; + ptr->position.y = right_lamp_positions[req_led_right_].y * 1000; + ptr->position.z = lamp_position_z * 1000; + ptr->update_latency = LED_UPDATE_DELAY_MS / 2 * 1000; + ptr->red_level_count = std::numeric_limits::max(); + ptr->green_level_count = std::numeric_limits::max(); + ptr->blue_level_count = std::numeric_limits::max(); + ptr->intensity_level_count = 1; + ptr->is_programmable = true; + ptr->purposes = hid::app::lamparray::purposes::CONTROL | + hid::app::lamparray::purposes::ACCENT | + hid::app::lamparray::purposes::STATUS; + // TODO set according to mapped key + ptr->input_binding = 0; + + ptr->lamp_id = req_led_right_; + req_led_right_ = (req_led_right_ + 1) % RIGHT_LAMP_COUNT; + return buffer.subspan(0, sizeof(right_lamp_attrs_rsp_report)); + } + + case left_lamp_control_report::selector(): { + assert(buffer.size() >= sizeof(left_lamp_control_report)); + auto *ptr = new (buffer.data()) left_lamp_control_report{}; + ptr->autonomous_mode = (led_remote_mode_flags_ & left_led_remote_mode_flag) == 0; + return buffer.subspan(0, sizeof(left_lamp_control_report)); + } + + case right_lamp_control_report::selector(): { + assert(buffer.size() >= sizeof(right_lamp_control_report)); + auto *ptr = new (buffer.data()) right_lamp_control_report{}; + ptr->autonomous_mode = (led_remote_mode_flags_ & right_led_remote_mode_flag) == 0; + return buffer.subspan(0, sizeof(right_lamp_control_report)); + } +#endif +#if 0 + case attributes_report::selector(): { + assert(buffer.size() >= sizeof(attributes_report)); + auto *ptr = new (buffer.data()) attributes_report{}; + // TODO: either we set the values realistically, + // or we set them to common values that conform to the expected OS layout + ptr->form_factor = hid::app::keyboard::form_factor::FULL_SIZE; + ptr->key_type = hid::app::keyboard::key_type::FULL_TRAVEL; + ptr->layout = hid::app::keyboard::layout::_102; + ptr->ietf_lang_tag_index = keyboard_app::usb_function().string_index(0); + return buffer.subspan(0, sizeof(attributes_report)); + } +#endif + + default: + return {}; } - return {}; } diff --git a/right/src/hid/keyboard_app.hpp b/right/src/hid/keyboard_app.hpp index 622e0ee2d..2e16b33ab 100644 --- a/right/src/hid/keyboard_app.hpp +++ b/right/src/hid/keyboard_app.hpp @@ -7,20 +7,144 @@ extern "C" { } #include "double_buffer.hpp" #include +#include #include +#include +#include using scancode = hid::page::keyboard_keypad; +template +struct keyboard_attributes_report + : public hid::report::base { + hid::app::keyboard::form_factor form_factor{}; + hid::app::keyboard::key_type key_type{}; + hid::app::keyboard::layout layout{}; + usb::istring ietf_lang_tag_index{}; + + [[nodiscard]] static constexpr auto descriptor() + { + using namespace hid::page; + using namespace hid::rdf; + + // clang-format off + return hid::rdf::descriptor( + usage_page(), + collection::logical( + conditional_report_id(), + report_size(8), + report_count(4), + usage(consumer::KEYBOARD_FORM_FACTOR), + usage(consumer::KEYBOARD_KEY_TYPE), + usage(consumer::KEYBOARD_PHYSICAL_LAYOUT), + usage(consumer::KEYBOARD_IETF_LANGUAGE_TAG_INDEX), + logical_limits<1, 2>(0, std::numeric_limits::max()), + feature::absolute_constant() + ) + ); + // clang-format on + } +}; + class keyboard_base_session : public hid::session { public: using hid::session::session; using leds_boot_report = hid::app::keyboard::output_report<0>; using leds_report = hid::app::keyboard::output_report; + using attributes_report = keyboard_attributes_report; + virtual leds_boot_report get_leds_report() const = 0; + +#if DEVICE_IS_UHK60 + static constexpr uint8_t MULTI_UPDATE_LIMIT = 10; + + static constexpr uint8_t LEFT_MAX_LAMP_ID = 33; + static constexpr uint8_t LEFT_LAMP_COUNT = LEFT_MAX_LAMP_ID + 1; + + static constexpr uint8_t LEFT_HALF_LAMP_COUNT = LEFT_LAMP_COUNT - 3; + + static constexpr uint8_t RIGHT_MAX_LAMP_ID = 32; + static constexpr uint8_t RIGHT_LAMP_COUNT = RIGHT_MAX_LAMP_ID + 1; + + using left_lamp_array_attrs_report = hid::app::lamparray::lamp_array_attributes_report< + report_ids::FEATURE_LEFT_LAMP_ARRAY_ATTRS>; + using left_lamp_attrs_req_report = hid::app::lamparray::lamp_attributes_request_report< + report_ids::FEATURE_LEFT_LAMP_ATTRS_REQ>; + using left_lamp_attrs_rsp_report = hid::app::lamparray::lamp_attributes_response_report< + report_ids::FEATURE_LEFT_LAMP_ATTRS_RSP>; + using left_lamp_multi_update_report = + hid::app::lamparray::lamp_multi_update_report; + using left_lamp_range_update_report = + hid::app::lamparray::lamp_range_update_report; + using left_lamp_control_report = + hid::app::lamparray::control_report; + + static constexpr auto left_lamp_descriptor() + { + using namespace hid::rdf; + return descriptor( + // clang-format off + usage_page(), + usage(hid::page::lighting_and_illumination::LAMP_ARRAY), + collection::application( + left_lamp_array_attrs_report::descriptor(), + left_lamp_attrs_req_report::descriptor(), + left_lamp_attrs_rsp_report::descriptor(), + left_lamp_multi_update_report::descriptor(), + left_lamp_range_update_report::descriptor(), + left_lamp_control_report::descriptor() + ) + // clang-format on + ); + } + using right_lamp_array_attrs_report = hid::app::lamparray::lamp_array_attributes_report< + report_ids::FEATURE_RIGHT_LAMP_ARRAY_ATTRS>; + using right_lamp_attrs_req_report = hid::app::lamparray::lamp_attributes_request_report< + report_ids::FEATURE_RIGHT_LAMP_ATTRS_REQ>; + using right_lamp_attrs_rsp_report = hid::app::lamparray::lamp_attributes_response_report< + report_ids::FEATURE_RIGHT_LAMP_ATTRS_RSP>; + using right_lamp_multi_update_report = + hid::app::lamparray::lamp_multi_update_report; + using right_lamp_range_update_report = + hid::app::lamparray::lamp_range_update_report; + using right_lamp_control_report = + hid::app::lamparray::control_report; + + static constexpr auto right_lamp_descriptor() + { + using namespace hid::rdf; + return descriptor( + // clang-format off + usage_page(), + usage(hid::page::lighting_and_illumination::LAMP_ARRAY), + collection::application( + right_lamp_array_attrs_report::descriptor(), + right_lamp_attrs_req_report::descriptor(), + right_lamp_attrs_rsp_report::descriptor(), + right_lamp_multi_update_report::descriptor(), + right_lamp_range_update_report::descriptor(), + right_lamp_control_report::descriptor() + ) + // clang-format on + ); + } +#endif }; + class keyboard_session : public keyboard_base_session { C2USB_USB_TRANSFER_ALIGN(leds_report, leds_buffer_) {}; +#if DEVICE_IS_UHK60 + uint8_t req_led_left_{}; + uint8_t req_led_right_{}; + static constexpr uint8_t left_led_remote_mode_flag = 0x1; + static constexpr uint8_t right_led_remote_mode_flag = 0x2; + static constexpr uint8_t led_remote_mode = 0x3; + uint8_t led_remote_mode_flags_{}; +#endif + protected: void report_sent(const std::span &data) override; std::span get_report( @@ -54,6 +178,7 @@ class keyboard_app : public hid::application { using keys_boot_report = hid::app::keyboard::keys_input_report<0>; using keys_6kro_report = hid::app::keyboard::keys_input_report; + // this includes both 6KRO and NKRO reports, used on BLE static constexpr auto report_desc() { using namespace hid::page; @@ -124,6 +249,11 @@ class keyboard_app : public hid::application { input::absolute_variable(), input::byte_padding() ) +#if DEVICE_IS_UHK60 + , + keyboard_session::left_lamp_descriptor(), + keyboard_session::right_lamp_descriptor() +#endif ); // clang-format on } @@ -180,10 +310,13 @@ class keyboard_app : public hid::application { keyboard_session *session() { return session_.has_value() ? &*session_ : nullptr; } + static usb::df::hid::string_function &usb_function() { return usb_handle().usb_function_; } + private: std::optional session_{}; + usb::df::hid::string_function usb_function_; - keyboard_app(const hid::report_protocol &rp) : hid::application(rp) {} + keyboard_app(const hid::report_protocol &rp); hid::session &start(const hid::session::params ¶ms) override; void stop(hid::session &sess) override; }; diff --git a/right/src/hid/report_ids.h b/right/src/hid/report_ids.h index 938992f21..132df3a0d 100644 --- a/right/src/hid/report_ids.h +++ b/right/src/hid/report_ids.h @@ -4,13 +4,14 @@ enum report_ids { #if DEVICE_IS_UHK60 - IN_KEYBOARD_6KRO = 0, - IN_KEYBOARD_NKRO = 0, - OUT_KEYBOARD_LEDS = 0, + IN_KEYBOARD_6KRO = 1, + IN_KEYBOARD_NKRO = 2, + OUT_KEYBOARD_LEDS = 1, + FEATURE_KEYBOARD_ATTRIBUTES = 1, - IN_MOUSE = 1, + IN_MOUSE = 3, FEATURE_MOUSE = - 1, // mouse needs nonzero report ID as workaround for Linux high-res scrolling bug + 3, // mouse needs nonzero report ID as workaround for Linux high-res scrolling bug // https://bugzilla.kernel.org/show_bug.cgi?id=220144 IN_CONTROLS = 0, @@ -19,6 +20,21 @@ enum report_ids { OUT_COMMAND = 0, IN_GAMEPAD = 0, + + FEATURE_LEFT_LAMP_ARRAY_ATTRS = 8, + FEATURE_LEFT_LAMP_ATTRS_REQ = 9, + FEATURE_LEFT_LAMP_ATTRS_RSP = 10, + FEATURE_LEFT_LAMP_MULTI_UPDATE = 11, + FEATURE_LEFT_LAMP_RANGE_UPDATE = 12, + FEATURE_LEFT_LAMP_CONTROL = 13, + + FEATURE_RIGHT_LAMP_ARRAY_ATTRS = 14, + FEATURE_RIGHT_LAMP_ATTRS_REQ = 15, + FEATURE_RIGHT_LAMP_ATTRS_RSP = 16, + FEATURE_RIGHT_LAMP_MULTI_UPDATE = 17, + FEATURE_RIGHT_LAMP_RANGE_UPDATE = 18, + FEATURE_RIGHT_LAMP_CONTROL = 19, + #else // due to Android HOGP limitation, only one HOGP instance can exist, // so the applications are merged into one instance with multiple TLCs @@ -28,6 +44,7 @@ enum report_ids { IN_KEYBOARD_6KRO = 1, IN_KEYBOARD_NKRO = 2, OUT_KEYBOARD_LEDS = 1, + FEATURE_KEYBOARD_ATTRIBUTES = 1, IN_MOUSE = 3, FEATURE_MOUSE = 3, diff --git a/right/src/hid/transport_usb.cpp b/right/src/hid/transport_usb.cpp index 86aafe2ba..7c2fa91f7 100644 --- a/right/src/hid/transport_usb.cpp +++ b/right/src/hid/transport_usb.cpp @@ -19,15 +19,16 @@ extern "C" { #endif } #ifdef __ZEPHYR__ - #include "port/zephyr/udc_mac.hpp" + #include "usb/df/vendor/zephyr/udc_mac.hpp" #else - #include "port/nxp/mcux_mac.hpp" + #include "usb/df/vendor/nxp/mcux_mac.hpp" #endif #include "command_app.hpp" #include "controls_app.hpp" #include "keyboard_app.hpp" #include "mouse_app.hpp" #include "usb/df/class/hid.hpp" +#include "usb/df/config_factory.hpp" #include "usb/df/device.hpp" #include "usb/df/vendor/microsoft/os_extension.hpp" #include "usb/df/vendor/microsoft/xinput.hpp" @@ -71,8 +72,7 @@ struct usb_manager { using namespace usb::df; static constexpr auto speed = usb::speed::FULL; - static usb::df::hid::function usb_kb{ - keyboard_app::usb_handle(), usb::hid::boot_protocol_mode::KEYBOARD}; + auto &usb_kb = keyboard_app::usb_function(); static usb::df::hid::function usb_mouse{mouse_app::usb_handle()}; static usb::df::hid::function usb_command{command_app::usb_handle()}; static usb::df::hid::function usb_controls{controls_app::usb_handle()}; @@ -134,7 +134,7 @@ struct usb_manager { } #ifdef __ZEPHYR__ - usb::zephyr::udc_mac mac_{DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), 256, + usb::df::zephyr::udc_mac mac_{DEVICE_DT_GET(DT_NODELABEL(zephyr_udc0)), 256, (nrfx_power_usbstatus_get() == NRFX_POWER_USB_STATE_CONNECTED) ? usb::power::state::L2_SUSPEND : usb::power::state::L3_OFF}; diff --git a/right/src/ledmap.c b/right/src/ledmap.c index ad83096c6..f3179dd90 100644 --- a/right/src/ledmap.c +++ b/right/src/ledmap.c @@ -630,6 +630,9 @@ static void setKeyColor(const rgb_t* color, uint8_t slotId, uint8_t keyId) { setPerKeyColor(color, determineMode(slotId), slotId, keyId); } +void Ledmap_SetKeyColor(const rgb_t* color, uint8_t slotId, uint8_t keyId) { + setKeyColor(color, slotId, keyId); +} void Ledmap_SetBlackValues(void) { setEntireMatrix(0); @@ -653,6 +656,11 @@ void handleModeChange(backlighting_mode_t from, backlighting_mode_t to) { } } +void Ledmap_TriggerFullUpdate(void) { + EventVector_Set(EventVector_LedManagerFullUpdateNeeded); + EventVector_WakeMain(); +} + void Ledmap_ActivateTestLedMode(bool active) { if (active) { backlightingLedTestStart = Timer_GetCurrentTime(); @@ -700,6 +708,7 @@ void Ledmap_UpdateBacklightLeds(void) { updateLedsByLightNoneStrategy(); break; case BacklightingMode_Unspecified: + case BacklightingMode_HidLampArray: break; } #if DEVICE_IS_UHK80_RIGHT || DEVICE_IS_UHK80_LEFT @@ -756,7 +765,10 @@ void Ledmap_InitLedLayout(void) { static void updateAlwaysOn() { backlighting_mode_t mode = Ledmap_GetEffectiveBacklightMode(); - Ledmap_AlwaysOn = mode == BacklightingMode_LightAll || mode == BacklightingMode_LedTest; + Ledmap_AlwaysOn = (mode == BacklightingMode_LightAll) + || (mode == BacklightingMode_LedTest) + || (mode == BacklightingMode_HidLampArray) + ; } void Ledmap_SetTemporaryLedBacklightingMode(backlighting_mode_t newMode) { diff --git a/right/src/ledmap.h b/right/src/ledmap.h index d1389fba9..46c03624b 100644 --- a/right/src/ledmap.h +++ b/right/src/ledmap.h @@ -16,6 +16,9 @@ BacklightingMode_LightAll, BacklightingMode_LightNone, BacklightingMode_Unspecified, + // https://www.usb.org/sites/default/files/hutrr84_-_lighting_and_illumination_page.pdf + // https://learn.microsoft.com/en-us/windows-hardware/design/component-guidelines/dynamic-lighting-devices + BacklightingMode_HidLampArray, } backlighting_mode_t; typedef enum { @@ -59,5 +62,7 @@ void Ledmap_SetTemporaryLedBacklightingMode(backlighting_mode_t newMode); void Ledmap_ResetTemporaryLedBacklightingMode(); backlighting_mode_t Ledmap_GetEffectiveBacklightMode(); + void Ledmap_SetKeyColor(const rgb_t* color, uint8_t slotId, uint8_t keyId); + void Ledmap_TriggerFullUpdate(void); #endif diff --git a/right/src/main.c b/right/src/main.c index aa58c4899..156497fc5 100644 --- a/right/src/main.c +++ b/right/src/main.c @@ -42,11 +42,16 @@ #include "trace_reasons.h" #include "config_manager.h" #include "hid/transport.h" +#if __has_include("fsl_debug_console.h") + #include "fsl_debug_console.h" +#endif static volatile bool IsEepromInitialized = false; static volatile bool IsConfigInitialized = false; static volatile bool IsHardwareConfigInitialized = false; +ATTR_WEAK void DbgConsole_Init(void) {} + static void userConfigurationReadFinished(void) { IsEepromInitialized = true; @@ -196,6 +201,7 @@ static void checkSleepMode() { int main(void) { + DbgConsole_Init(); Debug_InitStackCanary(); Trace_Init(); if (StateWormhole_IsOpen()) { diff --git a/submanifests/c2usb.yml b/submanifests/c2usb.yml index 5abe97d55..e09881c55 100644 --- a/submanifests/c2usb.yml +++ b/submanifests/c2usb.yml @@ -5,5 +5,5 @@ manifest: projects: - name: c2usb remote: IntergatedCircuits - revision: 0e11e25e72ecad59847a3de55507ab985a785ee6 + revision: aab54956fbddc0b9ae70deb180aef479d131345d import: true