2026-04-15 15:47:14 +08:00
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include <app_event_manager.h>
|
|
|
|
|
|
|
|
|
|
#define MODULE usb_hid_consumer_module
|
|
|
|
|
#include <caf/events/module_state_event.h>
|
|
|
|
|
#include <caf/events/power_event.h>
|
|
|
|
|
|
|
|
|
|
#include <zephyr/device.h>
|
|
|
|
|
#include <zephyr/drivers/usb/udc_buf.h>
|
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
|
#include <zephyr/usb/class/usbd_hid.h>
|
|
|
|
|
|
|
|
|
|
#include "hid_channel_state_event.h"
|
|
|
|
|
#include "hid_report_sent_event.h"
|
|
|
|
|
#include "hid_tx_report_event.h"
|
|
|
|
|
#include "keyboard_core.h"
|
2026-04-17 19:12:57 +08:00
|
|
|
#include "module_lifecycle.h"
|
2026-04-15 15:47:14 +08:00
|
|
|
#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
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
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)),
|
|
|
|
|
};
|
2026-04-15 15:47:14 +08:00
|
|
|
|
|
|
|
|
UDC_STATIC_BUF_DEFINE(consumer_tx_buf, KEYBOARD_CONSUMER_REPORT_SIZE);
|
|
|
|
|
|
|
|
|
|
static void publish_consumer_state(void)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
bool ready = module_lifecycle_is_running(&ctx.lc) &&
|
|
|
|
|
ctx.usb_active && ctx.iface_ready;
|
2026-04-15 15:47:14 +08:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.iface_ready = ready;
|
2026-04-15 15:47:14 +08:00
|
|
|
if (!ready) {
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.report_in_flight = false;
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
LOG_INF("%s interface %s", ctx.hid_dev->name,
|
|
|
|
|
ready ? "ready" : "not ready");
|
2026-04-15 15:47:14 +08:00
|
|
|
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);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.report_in_flight = false;
|
2026-04-15 15:47:14 +08:00
|
|
|
submit_hid_report_sent_event(HID_SEND_CH_USB_CONSUMER,
|
|
|
|
|
KEYBOARD_REPORT_TYPE_CONSUMER,
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.in_flight_sequence, false);
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!device_is_ready(ctx.hid_dev)) {
|
|
|
|
|
LOG_ERR("HID device %s not ready", ctx.hid_dev->name);
|
2026-04-15 15:47:14 +08:00
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
return hid_device_register(ctx.hid_dev, consumer_report_desc,
|
2026-04-15 15:47:14 +08:00
|
|
|
sizeof(consumer_report_desc), &consumer_ops);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
USB_FUNCTION_HOOK_DEFINE(usb_hid_consumer_hook, usb_hid_consumer_register_device);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_init(void)
|
2026-04-15 15:47:14 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.usb_active = false;
|
|
|
|
|
ctx.iface_ready = false;
|
|
|
|
|
ctx.report_in_flight = false;
|
2026-04-15 15:47:14 +08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_start(void)
|
2026-04-15 15:47:14 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_running(&ctx.lc)) {
|
2026-04-15 15:47:14 +08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
publish_consumer_state();
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_stop(void)
|
2026-04-15 15:47:14 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!module_lifecycle_is_running(&ctx.lc)) {
|
|
|
|
|
return 0;
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.report_in_flight = false;
|
2026-04-15 15:47:14 +08:00
|
|
|
publish_consumer_state();
|
2026-04-17 19:12:57 +08:00
|
|
|
|
|
|
|
|
return 0;
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool handle_usb_state_event(const struct usb_state_event *event)
|
|
|
|
|
{
|
|
|
|
|
bool new_usb_active = (event->state == USB_STATE_ACTIVE);
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (new_usb_active == ctx.usb_active) {
|
2026-04-15 15:47:14 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.usb_active = new_usb_active;
|
|
|
|
|
if (!ctx.usb_active) {
|
|
|
|
|
ctx.iface_ready = false;
|
|
|
|
|
ctx.report_in_flight = false;
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
publish_consumer_state();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
|
|
|
|
|
{
|
|
|
|
|
int err;
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.usb_active ||
|
2026-04-15 15:47:14 +08:00
|
|
|
(event->channel != HID_SEND_CH_USB_CONSUMER)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event->report_type != KEYBOARD_REPORT_TYPE_CONSUMER) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!ctx.iface_ready) {
|
2026-04-15 15:47:14 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if (ctx.report_in_flight) {
|
2026-04-15 15:47:14 +08:00
|
|
|
LOG_WRN("Drop USB consumer report while previous report is in flight");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memcpy(consumer_tx_buf, event->dyndata.data, event->dyndata.size);
|
2026-04-17 19:12:57 +08:00
|
|
|
err = hid_device_submit_report(ctx.hid_dev, (uint16_t)event->dyndata.size,
|
2026-04-15 15:47:14 +08:00
|
|
|
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 {
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.report_in_flight = true;
|
|
|
|
|
ctx.in_flight_sequence = event->sequence;
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_power_down_event(aeh)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
|
|
|
|
(void)module_set_lifecycle(&ctx.lc, LC_STOPPED);
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_wake_up_event(aeh)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
|
|
|
|
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
|
2026-04-15 15:47:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|