Files
blinky/src/usb_hid_consumer_module.c

316 lines
7.1 KiB
C
Raw Normal View History

#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"
#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);