#include #include #include #include #include #define MODULE usb_hid_consumer_module #include #include #include #include #include #include #include "hid_channel_state_event.h" #include "hid_report_sent_event.h" #include "hid_tx_report_event.h" #include "keyboard_core.h" #include "module_lifecycle.h" #include "usb_function_hook.h" #include "usb_state_event.h" LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); static const uint8_t consumer_report_desc[] = { 0x05, 0x0C, 0x09, 0x01, 0xA1, 0x01, 0x15, 0x00, 0x26, 0xFF, 0x03, 0x19, 0x00, 0x2A, 0xFF, 0x03, 0x75, 0x10, 0x95, 0x01, 0x81, 0x00, 0xC0 }; struct usb_hid_consumer_module_ctx { struct module_lifecycle_ctx lc; const struct device *hid_dev; bool usb_active; bool iface_ready; bool report_in_flight; uint16_t in_flight_sequence; }; static int do_init(void); static int do_start(void); static int do_stop(void); static const struct module_lifecycle_cfg lifecycle_cfg = { .mode = ML_MODE_POWER, .stopped_state = MODULE_STATE_STANDBY, }; static const struct module_lifecycle_ops lifecycle_ops = { .do_init = do_init, .do_start = do_start, .do_stop = do_stop, }; static struct usb_hid_consumer_module_ctx ctx = { .lc = { .state = LC_UNINIT, .cfg = &lifecycle_cfg, .ops = &lifecycle_ops, }, .hid_dev = DEVICE_DT_GET(DT_NODELABEL(hid_consumer)), }; UDC_STATIC_BUF_DEFINE(consumer_tx_buf, KEYBOARD_CONSUMER_REPORT_SIZE); static void publish_consumer_state(void) { bool ready = module_lifecycle_is_running(&ctx.lc) && ctx.usb_active && ctx.iface_ready; submit_hid_channel_state_event(HID_SEND_CH_USB_CONSUMER, ready ? BIT(KEYBOARD_REPORT_TYPE_CONSUMER) : 0U, KEYBOARD_PROTOCOL_MODE_REPORT); } static void consumer_iface_ready(const struct device *dev, const bool ready) { ARG_UNUSED(dev); ctx.iface_ready = ready; if (!ready) { ctx.report_in_flight = false; } LOG_INF("%s interface %s", ctx.hid_dev->name, ready ? "ready" : "not ready"); publish_consumer_state(); } static int consumer_get_report(const struct device *dev, const uint8_t type, const uint8_t id, const uint16_t len, uint8_t *const buf) { ARG_UNUSED(dev); ARG_UNUSED(type); ARG_UNUSED(id); ARG_UNUSED(len); ARG_UNUSED(buf); return -ENOTSUP; } static int consumer_set_report(const struct device *dev, const uint8_t type, const uint8_t id, const uint16_t len, const uint8_t *const buf) { ARG_UNUSED(dev); ARG_UNUSED(type); ARG_UNUSED(id); ARG_UNUSED(len); ARG_UNUSED(buf); return -ENOTSUP; } static void consumer_set_idle(const struct device *dev, const uint8_t id, const uint32_t duration) { ARG_UNUSED(dev); ARG_UNUSED(id); ARG_UNUSED(duration); } static uint32_t consumer_get_idle(const struct device *dev, const uint8_t id) { ARG_UNUSED(dev); ARG_UNUSED(id); return 0U; } static void consumer_set_protocol(const struct device *dev, const uint8_t proto) { ARG_UNUSED(dev); ARG_UNUSED(proto); } static void consumer_input_report_done(const struct device *dev, const uint8_t *const report) { ARG_UNUSED(dev); ARG_UNUSED(report); ctx.report_in_flight = false; submit_hid_report_sent_event(HID_SEND_CH_USB_CONSUMER, KEYBOARD_REPORT_TYPE_CONSUMER, ctx.in_flight_sequence, false); } static void consumer_output_report(const struct device *dev, const uint16_t len, const uint8_t *const buf) { ARG_UNUSED(dev); ARG_UNUSED(len); ARG_UNUSED(buf); } static const struct hid_device_ops consumer_ops = { .iface_ready = consumer_iface_ready, .get_report = consumer_get_report, .set_report = consumer_set_report, .set_idle = consumer_set_idle, .get_idle = consumer_get_idle, .set_protocol = consumer_set_protocol, .input_report_done = consumer_input_report_done, .output_report = consumer_output_report, }; static int usb_hid_consumer_register_device(void) { if (!device_is_ready(ctx.hid_dev)) { LOG_ERR("HID device %s not ready", ctx.hid_dev->name); return -ENODEV; } return hid_device_register(ctx.hid_dev, consumer_report_desc, sizeof(consumer_report_desc), &consumer_ops); } USB_FUNCTION_HOOK_DEFINE(usb_hid_consumer_hook, usb_hid_consumer_register_device); static int do_init(void) { ctx.usb_active = false; ctx.iface_ready = false; ctx.report_in_flight = false; return 0; } static int do_start(void) { if (module_lifecycle_is_running(&ctx.lc)) { return 0; } publish_consumer_state(); return 0; } static int do_stop(void) { if (!module_lifecycle_is_running(&ctx.lc)) { return 0; } ctx.report_in_flight = false; publish_consumer_state(); return 0; } static bool handle_usb_state_event(const struct usb_state_event *event) { bool new_usb_active = (event->state == USB_STATE_ACTIVE); if (new_usb_active == ctx.usb_active) { return false; } ctx.usb_active = new_usb_active; if (!ctx.usb_active) { ctx.iface_ready = false; ctx.report_in_flight = false; } publish_consumer_state(); return false; } static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event) { int err; if (!module_lifecycle_is_running(&ctx.lc) || !ctx.usb_active || (event->channel != HID_SEND_CH_USB_CONSUMER)) { return false; } if (event->report_type != KEYBOARD_REPORT_TYPE_CONSUMER) { return false; } if (!ctx.iface_ready) { return false; } if (ctx.report_in_flight) { LOG_WRN("Drop USB consumer report while previous report is in flight"); return false; } memcpy(consumer_tx_buf, event->dyndata.data, event->dyndata.size); err = hid_device_submit_report(ctx.hid_dev, (uint16_t)event->dyndata.size, consumer_tx_buf); if (err) { LOG_WRN("USB consumer report submit failed (%d)", err); submit_hid_report_sent_event(HID_SEND_CH_USB_CONSUMER, KEYBOARD_REPORT_TYPE_CONSUMER, event->sequence, true); } else { ctx.report_in_flight = true; ctx.in_flight_sequence = event->sequence; } return false; } static bool app_event_handler(const struct app_event_header *aeh) { if (is_hid_tx_report_event(aeh)) { return handle_hid_tx_report_event(cast_hid_tx_report_event(aeh)); } if (is_usb_state_event(aeh)) { return handle_usb_state_event(cast_usb_state_event(aeh)); } if (is_module_state_event(aeh)) { const struct module_state_event *event = cast_module_state_event(aeh); if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } if (is_power_down_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)module_set_lifecycle(&ctx.lc, LC_STOPPED); } return false; } if (is_wake_up_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)module_set_lifecycle(&ctx.lc, LC_RUNNING); } return false; } return false; } APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, hid_tx_report_event); APP_EVENT_SUBSCRIBE(MODULE, usb_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);