feat(hid): 实现HID通道状态管理和多通道支持
- 将原来的HID传输状态事件替换为新的HID通道状态事件,支持USB键盘、USB消费者和BLE共享通道 - 添加usb_hid_consumer_module.c模块来处理USB消费者HID报告 - 添加usb_hid_keyboard_module.c模块来处理USB键盘HID报告 - 修改CMakeLists.txt以包含新的HID模块源文件 - 更新事件系统中的transport字段为channel字段,并相应修改所有相关处理逻辑 - 在hid_flowctrl_module.c中实现多通道并发处理机制 - 删除过时的hid_transport_state_event相关代码 - 添加新的hid_channel_state_event用于报告各通道就绪状态
This commit is contained in:
@@ -39,15 +39,16 @@ target_sources(app PRIVATE
|
||||
src/protocol_module.c
|
||||
src/usb_cdc_module.c
|
||||
src/usb_device_module.c
|
||||
src/usb_hid_module.c
|
||||
src/usb_hid_consumer_module.c
|
||||
src/usb_hid_keyboard_module.c
|
||||
src/events/bat_state_event.c
|
||||
src/events/datetime_event.c
|
||||
src/events/encoder_event.c
|
||||
src/events/function_bitmap_state_event.c
|
||||
src/events/function_bitmap_update_event.c
|
||||
src/events/hid_channel_state_event.c
|
||||
src/events/hid_led_event.c
|
||||
src/events/hid_report_sent_event.c
|
||||
src/events/hid_transport_state_event.c
|
||||
src/events/hid_tx_report_event.c
|
||||
src/events/led_strip_en_event.c
|
||||
src/mode_switch_module.c
|
||||
|
||||
38
inc/events/hid_channel_state_event.h
Normal file
38
inc/events/hid_channel_state_event.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef BLINKY_HID_CHANNEL_STATE_EVENT_H_
|
||||
#define BLINKY_HID_CHANNEL_STATE_EVENT_H_
|
||||
|
||||
#include <app_event_manager.h>
|
||||
#include <app_event_manager_profiler_tracer.h>
|
||||
|
||||
#include "keyboard_core.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct hid_channel_state_event {
|
||||
struct app_event_header header;
|
||||
enum hid_send_channel channel;
|
||||
uint8_t report_ready_bm;
|
||||
enum keyboard_protocol_mode protocol_mode;
|
||||
};
|
||||
|
||||
APP_EVENT_TYPE_DECLARE(hid_channel_state_event);
|
||||
|
||||
static inline void submit_hid_channel_state_event(
|
||||
enum hid_send_channel channel, uint8_t report_ready_bm,
|
||||
enum keyboard_protocol_mode protocol_mode)
|
||||
{
|
||||
struct hid_channel_state_event *event = new_hid_channel_state_event();
|
||||
|
||||
event->channel = channel;
|
||||
event->report_ready_bm = report_ready_bm;
|
||||
event->protocol_mode = protocol_mode;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_HID_CHANNEL_STATE_EVENT_H_ */
|
||||
@@ -12,7 +12,7 @@ extern "C" {
|
||||
|
||||
struct hid_report_sent_event {
|
||||
struct app_event_header header;
|
||||
enum hid_transport transport;
|
||||
enum hid_send_channel channel;
|
||||
enum keyboard_report_type report_type;
|
||||
uint16_t sequence;
|
||||
bool error;
|
||||
@@ -20,13 +20,13 @@ struct hid_report_sent_event {
|
||||
|
||||
APP_EVENT_TYPE_DECLARE(hid_report_sent_event);
|
||||
|
||||
static inline void submit_hid_report_sent_event(enum hid_transport transport,
|
||||
static inline void submit_hid_report_sent_event(enum hid_send_channel channel,
|
||||
enum keyboard_report_type report_type,
|
||||
uint16_t sequence, bool error)
|
||||
{
|
||||
struct hid_report_sent_event *event = new_hid_report_sent_event();
|
||||
|
||||
event->transport = transport;
|
||||
event->channel = channel;
|
||||
event->report_type = report_type;
|
||||
event->sequence = sequence;
|
||||
event->error = error;
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef BLINKY_HID_TRANSPORT_STATE_EVENT_H_
|
||||
#define BLINKY_HID_TRANSPORT_STATE_EVENT_H_
|
||||
|
||||
#include <app_event_manager.h>
|
||||
#include <app_event_manager_profiler_tracer.h>
|
||||
|
||||
#include "keyboard_core.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct hid_transport_state_event {
|
||||
struct app_event_header header;
|
||||
enum hid_transport transport;
|
||||
bool ready;
|
||||
bool keys_ready;
|
||||
bool consumer_ready;
|
||||
enum keyboard_protocol_mode protocol_mode;
|
||||
};
|
||||
|
||||
APP_EVENT_TYPE_DECLARE(hid_transport_state_event);
|
||||
|
||||
static inline void submit_hid_transport_state_event(
|
||||
enum hid_transport transport, bool ready, bool keys_ready,
|
||||
bool consumer_ready, enum keyboard_protocol_mode protocol_mode)
|
||||
{
|
||||
struct hid_transport_state_event *event = new_hid_transport_state_event();
|
||||
|
||||
event->transport = transport;
|
||||
event->ready = ready;
|
||||
event->keys_ready = keys_ready;
|
||||
event->consumer_ready = consumer_ready;
|
||||
event->protocol_mode = protocol_mode;
|
||||
APP_EVENT_SUBMIT(event);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* BLINKY_HID_TRANSPORT_STATE_EVENT_H_ */
|
||||
@@ -16,7 +16,7 @@ extern "C" {
|
||||
|
||||
struct hid_tx_report_event {
|
||||
struct app_event_header header;
|
||||
enum hid_transport transport;
|
||||
enum hid_send_channel channel;
|
||||
enum keyboard_report_type report_type;
|
||||
enum keyboard_protocol_mode protocol_mode;
|
||||
uint16_t sequence;
|
||||
@@ -25,7 +25,7 @@ struct hid_tx_report_event {
|
||||
|
||||
APP_EVENT_TYPE_DYNDATA_DECLARE(hid_tx_report_event);
|
||||
|
||||
static inline int submit_hid_tx_report_event(enum hid_transport transport,
|
||||
static inline int submit_hid_tx_report_event(enum hid_send_channel channel,
|
||||
enum keyboard_report_type report_type,
|
||||
enum keyboard_protocol_mode protocol_mode,
|
||||
uint16_t sequence,
|
||||
@@ -38,7 +38,7 @@ static inline int submit_hid_tx_report_event(enum hid_transport transport,
|
||||
}
|
||||
|
||||
event = new_hid_tx_report_event(size);
|
||||
event->transport = transport;
|
||||
event->channel = channel;
|
||||
event->report_type = report_type;
|
||||
event->protocol_mode = protocol_mode;
|
||||
event->sequence = sequence;
|
||||
|
||||
@@ -36,6 +36,13 @@ enum hid_transport {
|
||||
HID_TRANSPORT_COUNT,
|
||||
};
|
||||
|
||||
enum hid_send_channel {
|
||||
HID_SEND_CH_USB_KEYS,
|
||||
HID_SEND_CH_USB_CONSUMER,
|
||||
HID_SEND_CH_BLE_SHARED,
|
||||
HID_SEND_CH_COUNT,
|
||||
};
|
||||
|
||||
enum keyboard_consumer_control {
|
||||
KEYBOARD_CONSUMER_CTRL_MUTE,
|
||||
KEYBOARD_CONSUMER_CTRL_VOLUME_UP,
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "hid_led_event.h"
|
||||
#include "hid_channel_state_event.h"
|
||||
#include "hid_report_sent_event.h"
|
||||
#include "hid_transport_state_event.h"
|
||||
#include "hid_tx_report_event.h"
|
||||
#include "keyboard_core.h"
|
||||
#include "set_protocol_event.h"
|
||||
@@ -101,15 +101,22 @@ static const uint8_t hid_report_desc[] = {
|
||||
static void submit_ble_transport_state_event(void)
|
||||
{
|
||||
bool ready = running && secured && (active_conn != NULL);
|
||||
uint8_t report_ready_bm = 0U;
|
||||
|
||||
submit_hid_transport_state_event(
|
||||
HID_TRANSPORT_BLE,
|
||||
ready,
|
||||
ready && ((protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ?
|
||||
if (ready && ((protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ?
|
||||
boot_keyboard_notify_enabled :
|
||||
keyboard_report_notify_enabled),
|
||||
ready && (protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT) &&
|
||||
consumer_report_notify_enabled,
|
||||
keyboard_report_notify_enabled)) {
|
||||
report_ready_bm |= BIT(KEYBOARD_REPORT_TYPE_KEYS);
|
||||
}
|
||||
|
||||
if (ready && (protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT) &&
|
||||
consumer_report_notify_enabled) {
|
||||
report_ready_bm |= BIT(KEYBOARD_REPORT_TYPE_CONSUMER);
|
||||
}
|
||||
|
||||
submit_hid_channel_state_event(
|
||||
HID_SEND_CH_BLE_SHARED,
|
||||
report_ready_bm,
|
||||
protocol_mode);
|
||||
}
|
||||
|
||||
@@ -141,7 +148,8 @@ static void hid_report_complete_cb(struct bt_conn *conn, void *user_data)
|
||||
return;
|
||||
}
|
||||
|
||||
submit_hid_report_sent_event(HID_TRANSPORT_BLE, in_flight.report_type,
|
||||
submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED,
|
||||
in_flight.report_type,
|
||||
in_flight.sequence, false);
|
||||
in_flight.active = false;
|
||||
}
|
||||
@@ -317,7 +325,8 @@ static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!running || (event->transport != HID_TRANSPORT_BLE) || in_flight.active) {
|
||||
if (!running || (event->channel != HID_SEND_CH_BLE_SHARED) ||
|
||||
in_flight.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -353,7 +362,7 @@ static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
|
||||
if (err) {
|
||||
in_flight.active = false;
|
||||
LOG_WRN("BLE keyboard report submit failed (%d)", err);
|
||||
submit_hid_report_sent_event(HID_TRANSPORT_BLE,
|
||||
submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED,
|
||||
KEYBOARD_REPORT_TYPE_KEYS,
|
||||
event->sequence, true);
|
||||
}
|
||||
@@ -380,7 +389,7 @@ static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
|
||||
if (err) {
|
||||
in_flight.active = false;
|
||||
LOG_WRN("BLE consumer report submit failed (%d)", err);
|
||||
submit_hid_report_sent_event(HID_TRANSPORT_BLE,
|
||||
submit_hid_report_sent_event(HID_SEND_CH_BLE_SHARED,
|
||||
KEYBOARD_REPORT_TYPE_CONSUMER,
|
||||
event->sequence, true);
|
||||
}
|
||||
|
||||
51
src/events/hid_channel_state_event.c
Normal file
51
src/events/hid_channel_state_event.c
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "hid_channel_state_event.h"
|
||||
|
||||
static const char *channel_name(enum hid_send_channel channel)
|
||||
{
|
||||
switch (channel) {
|
||||
case HID_SEND_CH_USB_KEYS:
|
||||
return "usb_keys";
|
||||
case HID_SEND_CH_USB_CONSUMER:
|
||||
return "usb_consumer";
|
||||
case HID_SEND_CH_BLE_SHARED:
|
||||
return "ble_shared";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static void log_hid_channel_state_event(const struct app_event_header *aeh)
|
||||
{
|
||||
const struct hid_channel_state_event *event =
|
||||
cast_hid_channel_state_event(aeh);
|
||||
|
||||
APP_EVENT_MANAGER_LOG(aeh,
|
||||
"channel:%s ready_bm:0x%02x protocol:%d",
|
||||
channel_name(event->channel),
|
||||
event->report_ready_bm,
|
||||
event->protocol_mode);
|
||||
}
|
||||
|
||||
static void profile_hid_channel_state_event(struct log_event_buf *buf,
|
||||
const struct app_event_header *aeh)
|
||||
{
|
||||
const struct hid_channel_state_event *event =
|
||||
cast_hid_channel_state_event(aeh);
|
||||
|
||||
nrf_profiler_log_encode_uint8(buf, event->channel);
|
||||
nrf_profiler_log_encode_uint8(buf, event->report_ready_bm);
|
||||
nrf_profiler_log_encode_uint8(buf, event->protocol_mode);
|
||||
}
|
||||
|
||||
APP_EVENT_INFO_DEFINE(hid_channel_state_event,
|
||||
ENCODE(NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U8),
|
||||
ENCODE("channel", "ready_bm", "protocol_mode"),
|
||||
profile_hid_channel_state_event);
|
||||
|
||||
APP_EVENT_TYPE_DEFINE(hid_channel_state_event,
|
||||
log_hid_channel_state_event,
|
||||
&hid_channel_state_event_info,
|
||||
APP_EVENT_FLAGS_CREATE(
|
||||
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||
@@ -1,12 +1,14 @@
|
||||
#include "hid_report_sent_event.h"
|
||||
|
||||
static const char *transport_name(enum hid_transport transport)
|
||||
static const char *channel_name(enum hid_send_channel channel)
|
||||
{
|
||||
switch (transport) {
|
||||
case HID_TRANSPORT_USB:
|
||||
return "USB";
|
||||
case HID_TRANSPORT_BLE:
|
||||
return "BLE";
|
||||
switch (channel) {
|
||||
case HID_SEND_CH_USB_KEYS:
|
||||
return "usb_keys";
|
||||
case HID_SEND_CH_USB_CONSUMER:
|
||||
return "usb_consumer";
|
||||
case HID_SEND_CH_BLE_SHARED:
|
||||
return "ble_shared";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
@@ -28,8 +30,8 @@ static void log_hid_report_sent_event(const struct app_event_header *aeh)
|
||||
{
|
||||
const struct hid_report_sent_event *event = cast_hid_report_sent_event(aeh);
|
||||
|
||||
APP_EVENT_MANAGER_LOG(aeh, "transport:%s type:%s seq:%u error:%u",
|
||||
transport_name(event->transport),
|
||||
APP_EVENT_MANAGER_LOG(aeh, "channel:%s type:%s seq:%u error:%u",
|
||||
channel_name(event->channel),
|
||||
report_type_name(event->report_type),
|
||||
event->sequence,
|
||||
event->error);
|
||||
@@ -40,7 +42,7 @@ static void profile_hid_report_sent_event(struct log_event_buf *buf,
|
||||
{
|
||||
const struct hid_report_sent_event *event = cast_hid_report_sent_event(aeh);
|
||||
|
||||
nrf_profiler_log_encode_uint8(buf, event->transport);
|
||||
nrf_profiler_log_encode_uint8(buf, event->channel);
|
||||
nrf_profiler_log_encode_uint8(buf, event->report_type);
|
||||
nrf_profiler_log_encode_uint16(buf, event->sequence);
|
||||
nrf_profiler_log_encode_uint8(buf, event->error);
|
||||
@@ -51,7 +53,7 @@ APP_EVENT_INFO_DEFINE(hid_report_sent_event,
|
||||
NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U16,
|
||||
NRF_PROFILER_ARG_U8),
|
||||
ENCODE("transport", "report_type", "sequence", "error"),
|
||||
ENCODE("channel", "report_type", "sequence", "error"),
|
||||
profile_hid_report_sent_event);
|
||||
|
||||
APP_EVENT_TYPE_DEFINE(hid_report_sent_event,
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#include "hid_transport_state_event.h"
|
||||
|
||||
static const char *transport_name(enum hid_transport transport)
|
||||
{
|
||||
switch (transport) {
|
||||
case HID_TRANSPORT_USB:
|
||||
return "USB";
|
||||
case HID_TRANSPORT_BLE:
|
||||
return "BLE";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *protocol_mode_name(enum keyboard_protocol_mode protocol_mode)
|
||||
{
|
||||
switch (protocol_mode) {
|
||||
case KEYBOARD_PROTOCOL_MODE_BOOT:
|
||||
return "boot";
|
||||
case KEYBOARD_PROTOCOL_MODE_REPORT:
|
||||
return "report";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static void log_hid_transport_state_event(const struct app_event_header *aeh)
|
||||
{
|
||||
const struct hid_transport_state_event *event =
|
||||
cast_hid_transport_state_event(aeh);
|
||||
|
||||
APP_EVENT_MANAGER_LOG(aeh,
|
||||
"transport:%s ready:%u keys_ready:%u consumer_ready:%u protocol:%s",
|
||||
transport_name(event->transport),
|
||||
event->ready,
|
||||
event->keys_ready,
|
||||
event->consumer_ready,
|
||||
protocol_mode_name(event->protocol_mode));
|
||||
}
|
||||
|
||||
static void profile_hid_transport_state_event(struct log_event_buf *buf,
|
||||
const struct app_event_header *aeh)
|
||||
{
|
||||
const struct hid_transport_state_event *event =
|
||||
cast_hid_transport_state_event(aeh);
|
||||
|
||||
nrf_profiler_log_encode_uint8(buf, event->transport);
|
||||
nrf_profiler_log_encode_uint8(buf, event->ready);
|
||||
nrf_profiler_log_encode_uint8(buf, event->keys_ready);
|
||||
nrf_profiler_log_encode_uint8(buf, event->consumer_ready);
|
||||
nrf_profiler_log_encode_uint8(buf, event->protocol_mode);
|
||||
}
|
||||
|
||||
APP_EVENT_INFO_DEFINE(hid_transport_state_event,
|
||||
ENCODE(NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U8),
|
||||
ENCODE("transport", "ready", "keys_ready", "consumer_ready",
|
||||
"protocol_mode"),
|
||||
profile_hid_transport_state_event);
|
||||
|
||||
APP_EVENT_TYPE_DEFINE(hid_transport_state_event,
|
||||
log_hid_transport_state_event,
|
||||
&hid_transport_state_event_info,
|
||||
APP_EVENT_FLAGS_CREATE(
|
||||
APP_EVENT_TYPE_FLAGS_INIT_LOG_ENABLE));
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
#define HID_TX_REPORT_EVENT_LOG_BUF_LEN 192
|
||||
|
||||
static const char *transport_name(enum hid_transport transport)
|
||||
static const char *channel_name(enum hid_send_channel channel)
|
||||
{
|
||||
switch (transport) {
|
||||
case HID_TRANSPORT_USB:
|
||||
return "USB";
|
||||
case HID_TRANSPORT_BLE:
|
||||
return "BLE";
|
||||
switch (channel) {
|
||||
case HID_SEND_CH_USB_KEYS:
|
||||
return "usb_keys";
|
||||
case HID_SEND_CH_USB_CONSUMER:
|
||||
return "usb_consumer";
|
||||
case HID_SEND_CH_BLE_SHARED:
|
||||
return "ble_shared";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
@@ -47,8 +49,8 @@ static void log_hid_tx_report_event(const struct app_event_header *aeh)
|
||||
int pos;
|
||||
|
||||
pos = snprintf(log_buf, sizeof(log_buf),
|
||||
"transport:%s type:%s protocol:%s seq:%u len:%zu",
|
||||
transport_name(event->transport),
|
||||
"channel:%s type:%s protocol:%s seq:%u len:%zu",
|
||||
channel_name(event->channel),
|
||||
report_type_name(event->report_type),
|
||||
protocol_mode_name(event->protocol_mode),
|
||||
event->sequence,
|
||||
@@ -84,7 +86,7 @@ static void profile_hid_tx_report_event(struct log_event_buf *buf,
|
||||
{
|
||||
const struct hid_tx_report_event *event = cast_hid_tx_report_event(aeh);
|
||||
|
||||
nrf_profiler_log_encode_uint8(buf, event->transport);
|
||||
nrf_profiler_log_encode_uint8(buf, event->channel);
|
||||
nrf_profiler_log_encode_uint8(buf, event->report_type);
|
||||
nrf_profiler_log_encode_uint8(buf, event->protocol_mode);
|
||||
nrf_profiler_log_encode_uint16(buf, event->sequence);
|
||||
@@ -97,7 +99,7 @@ APP_EVENT_INFO_DEFINE(hid_tx_report_event,
|
||||
NRF_PROFILER_ARG_U8,
|
||||
NRF_PROFILER_ARG_U16,
|
||||
NRF_PROFILER_ARG_U8),
|
||||
ENCODE("transport", "report_type", "protocol_mode", "sequence", "len"),
|
||||
ENCODE("channel", "report_type", "protocol_mode", "sequence", "len"),
|
||||
profile_hid_tx_report_event);
|
||||
|
||||
APP_EVENT_TYPE_DEFINE(hid_tx_report_event,
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
#include "hid_channel_state_event.h"
|
||||
#include "hid_report_sent_event.h"
|
||||
#include "hid_transport_state_event.h"
|
||||
#include "hid_tx_report_event.h"
|
||||
#include "keyboard_core.h"
|
||||
#include "keyboard_hid_report_event.h"
|
||||
@@ -37,25 +37,26 @@ struct queued_report {
|
||||
uint8_t data[HID_FLOWCTRL_REPORT_DATA_MAX];
|
||||
};
|
||||
|
||||
struct hid_transport_state_data {
|
||||
bool ready;
|
||||
bool keys_ready;
|
||||
bool consumer_ready;
|
||||
struct hid_channel_state_data {
|
||||
uint8_t report_ready_bm;
|
||||
enum keyboard_protocol_mode protocol_mode;
|
||||
};
|
||||
|
||||
struct in_flight_report {
|
||||
bool active;
|
||||
enum hid_transport transport;
|
||||
enum hid_send_channel channel;
|
||||
enum keyboard_report_type report_type;
|
||||
uint16_t sequence;
|
||||
};
|
||||
|
||||
static struct hid_transport_state_data transport_state[HID_TRANSPORT_COUNT] = {
|
||||
[HID_TRANSPORT_USB] = {
|
||||
static struct hid_channel_state_data channel_state[HID_SEND_CH_COUNT] = {
|
||||
[HID_SEND_CH_USB_KEYS] = {
|
||||
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
|
||||
},
|
||||
[HID_TRANSPORT_BLE] = {
|
||||
[HID_SEND_CH_USB_CONSUMER] = {
|
||||
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
|
||||
},
|
||||
[HID_SEND_CH_BLE_SHARED] = {
|
||||
.protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT,
|
||||
},
|
||||
};
|
||||
@@ -65,21 +66,28 @@ static struct queued_report consumer_fifo[HID_FLOWCTRL_FIFO_DEPTH];
|
||||
static uint8_t consumer_fifo_head;
|
||||
static uint8_t consumer_fifo_tail;
|
||||
static uint8_t consumer_fifo_count;
|
||||
static struct in_flight_report in_flight;
|
||||
static struct in_flight_report in_flight[HID_SEND_CH_COUNT];
|
||||
static enum mode_switch_mode current_mode;
|
||||
static uint16_t next_sequence;
|
||||
static bool initialized;
|
||||
static bool running;
|
||||
|
||||
static bool mode_to_transport(enum mode_switch_mode mode, enum hid_transport *transport)
|
||||
static bool current_mode_to_channel(enum keyboard_report_type report_type,
|
||||
enum hid_send_channel *channel)
|
||||
{
|
||||
switch (mode) {
|
||||
if (channel == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (current_mode) {
|
||||
case MODE_SWITCH_USB:
|
||||
*transport = HID_TRANSPORT_USB;
|
||||
*channel = (report_type == KEYBOARD_REPORT_TYPE_KEYS) ?
|
||||
HID_SEND_CH_USB_KEYS :
|
||||
HID_SEND_CH_USB_CONSUMER;
|
||||
return true;
|
||||
|
||||
case MODE_SWITCH_BLE:
|
||||
*transport = HID_TRANSPORT_BLE;
|
||||
*channel = HID_SEND_CH_BLE_SHARED;
|
||||
return true;
|
||||
|
||||
default:
|
||||
@@ -131,102 +139,137 @@ static bool consumer_fifo_pop(struct queued_report *entry)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool transport_can_send_report(enum keyboard_report_type report_type)
|
||||
static bool channel_can_send_report(enum hid_send_channel channel,
|
||||
enum keyboard_report_type report_type,
|
||||
enum keyboard_protocol_mode protocol_mode)
|
||||
{
|
||||
enum hid_transport transport;
|
||||
struct hid_transport_state_data *state;
|
||||
const struct hid_channel_state_data *state = &channel_state[channel];
|
||||
|
||||
if (!mode_to_transport(current_mode, &transport) || in_flight.active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state = &transport_state[transport];
|
||||
|
||||
if (!state->ready) {
|
||||
if (in_flight[channel].active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (report_type == KEYBOARD_REPORT_TYPE_KEYS) {
|
||||
return state->keys_ready;
|
||||
return (state->report_ready_bm & BIT(KEYBOARD_REPORT_TYPE_KEYS)) &&
|
||||
(state->protocol_mode == protocol_mode);
|
||||
}
|
||||
|
||||
return state->consumer_ready;
|
||||
if (channel == HID_SEND_CH_BLE_SHARED) {
|
||||
return (state->report_ready_bm & BIT(KEYBOARD_REPORT_TYPE_CONSUMER)) &&
|
||||
(state->protocol_mode == KEYBOARD_PROTOCOL_MODE_REPORT);
|
||||
}
|
||||
|
||||
return (state->report_ready_bm & BIT(KEYBOARD_REPORT_TYPE_CONSUMER)) != 0U;
|
||||
}
|
||||
|
||||
static void try_send_keys(void)
|
||||
{
|
||||
enum hid_send_channel channel;
|
||||
|
||||
if (!pending_keys.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_mode_to_channel(KEYBOARD_REPORT_TYPE_KEYS, &channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel_can_send_report(channel, pending_keys.report_type,
|
||||
pending_keys.protocol_mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
in_flight[channel].active = true;
|
||||
in_flight[channel].channel = channel;
|
||||
in_flight[channel].report_type = pending_keys.report_type;
|
||||
in_flight[channel].sequence = next_sequence++;
|
||||
(void)submit_hid_tx_report_event(channel, pending_keys.report_type,
|
||||
pending_keys.protocol_mode,
|
||||
in_flight[channel].sequence,
|
||||
pending_keys.data, pending_keys.size);
|
||||
pending_keys.valid = false;
|
||||
}
|
||||
|
||||
static void try_send_consumer_fifo(void)
|
||||
{
|
||||
struct queued_report queued;
|
||||
enum hid_send_channel channel;
|
||||
|
||||
if (consumer_fifo_count == 0U) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_mode_to_channel(KEYBOARD_REPORT_TYPE_CONSUMER, &channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!consumer_fifo_pop(&queued)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel_can_send_report(channel, queued.report_type,
|
||||
queued.protocol_mode)) {
|
||||
if (queued.protocol_mode == channel_state[channel].protocol_mode) {
|
||||
consumer_fifo_push(queued.report_type, queued.protocol_mode,
|
||||
queued.data, queued.size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
in_flight[channel].active = true;
|
||||
in_flight[channel].channel = channel;
|
||||
in_flight[channel].report_type = queued.report_type;
|
||||
in_flight[channel].sequence = next_sequence++;
|
||||
(void)submit_hid_tx_report_event(channel, queued.report_type,
|
||||
queued.protocol_mode,
|
||||
in_flight[channel].sequence,
|
||||
queued.data, queued.size);
|
||||
}
|
||||
|
||||
static void try_send_consumer_latest(void)
|
||||
{
|
||||
enum hid_send_channel channel;
|
||||
|
||||
if (!pending_consumer_latest.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!current_mode_to_channel(KEYBOARD_REPORT_TYPE_CONSUMER, &channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!channel_can_send_report(channel,
|
||||
pending_consumer_latest.report_type,
|
||||
pending_consumer_latest.protocol_mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
in_flight[channel].active = true;
|
||||
in_flight[channel].channel = channel;
|
||||
in_flight[channel].report_type = pending_consumer_latest.report_type;
|
||||
in_flight[channel].sequence = next_sequence++;
|
||||
(void)submit_hid_tx_report_event(channel,
|
||||
pending_consumer_latest.report_type,
|
||||
pending_consumer_latest.protocol_mode,
|
||||
in_flight[channel].sequence,
|
||||
pending_consumer_latest.data,
|
||||
pending_consumer_latest.size);
|
||||
pending_consumer_latest.valid = false;
|
||||
}
|
||||
|
||||
static void try_send_next(void)
|
||||
{
|
||||
struct queued_report queued;
|
||||
enum hid_transport transport;
|
||||
struct hid_transport_state_data *state;
|
||||
|
||||
if (!running || in_flight.active || !mode_to_transport(current_mode, &transport)) {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = &transport_state[transport];
|
||||
|
||||
if (!state->ready) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pending_keys.valid && transport_can_send_report(KEYBOARD_REPORT_TYPE_KEYS)) {
|
||||
if (pending_keys.protocol_mode != state->protocol_mode) {
|
||||
LOG_WRN("Drop stale keys report after protocol change");
|
||||
pending_keys.valid = false;
|
||||
} else {
|
||||
in_flight.active = true;
|
||||
in_flight.transport = transport;
|
||||
in_flight.report_type = pending_keys.report_type;
|
||||
in_flight.sequence = next_sequence++;
|
||||
(void)submit_hid_tx_report_event(
|
||||
transport, pending_keys.report_type,
|
||||
pending_keys.protocol_mode, in_flight.sequence,
|
||||
pending_keys.data, pending_keys.size);
|
||||
pending_keys.valid = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((consumer_fifo_count > 0U) &&
|
||||
transport_can_send_report(KEYBOARD_REPORT_TYPE_CONSUMER) &&
|
||||
consumer_fifo_pop(&queued)) {
|
||||
if (queued.protocol_mode != state->protocol_mode) {
|
||||
LOG_WRN("Drop stale consumer report after protocol change");
|
||||
} else {
|
||||
in_flight.active = true;
|
||||
in_flight.transport = transport;
|
||||
in_flight.report_type = queued.report_type;
|
||||
in_flight.sequence = next_sequence++;
|
||||
(void)submit_hid_tx_report_event(
|
||||
transport, queued.report_type,
|
||||
queued.protocol_mode, in_flight.sequence,
|
||||
queued.data, queued.size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (pending_consumer_latest.valid &&
|
||||
transport_can_send_report(KEYBOARD_REPORT_TYPE_CONSUMER)) {
|
||||
if (pending_consumer_latest.protocol_mode != state->protocol_mode) {
|
||||
LOG_WRN("Drop stale latest consumer report after protocol change");
|
||||
pending_consumer_latest.valid = false;
|
||||
} else {
|
||||
in_flight.active = true;
|
||||
in_flight.transport = transport;
|
||||
in_flight.report_type = pending_consumer_latest.report_type;
|
||||
in_flight.sequence = next_sequence++;
|
||||
(void)submit_hid_tx_report_event(
|
||||
transport, pending_consumer_latest.report_type,
|
||||
pending_consumer_latest.protocol_mode,
|
||||
in_flight.sequence,
|
||||
pending_consumer_latest.data,
|
||||
pending_consumer_latest.size);
|
||||
pending_consumer_latest.valid = false;
|
||||
}
|
||||
}
|
||||
try_send_keys();
|
||||
try_send_consumer_fifo();
|
||||
try_send_consumer_latest();
|
||||
}
|
||||
|
||||
static bool handle_keyboard_hid_report_event(const struct keyboard_hid_report_event *event)
|
||||
static bool handle_keyboard_hid_report_event(
|
||||
const struct keyboard_hid_report_event *event)
|
||||
{
|
||||
if (!running ||
|
||||
((event->mode != MODE_SWITCH_USB) && (event->mode != MODE_SWITCH_BLE))) {
|
||||
@@ -257,41 +300,18 @@ static bool handle_keyboard_hid_report_event(const struct keyboard_hid_report_ev
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_hid_transport_state_event(const struct hid_transport_state_event *event)
|
||||
static bool handle_hid_channel_state_event(
|
||||
const struct hid_channel_state_event *event)
|
||||
{
|
||||
enum hid_transport active_transport;
|
||||
struct hid_transport_state_data *state;
|
||||
|
||||
if (event->transport >= HID_TRANSPORT_COUNT) {
|
||||
if (event->channel >= HID_SEND_CH_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state = &transport_state[event->transport];
|
||||
channel_state[event->channel].report_ready_bm = event->report_ready_bm;
|
||||
channel_state[event->channel].protocol_mode = event->protocol_mode;
|
||||
|
||||
state->ready = event->ready;
|
||||
state->keys_ready = event->keys_ready;
|
||||
state->consumer_ready = event->consumer_ready;
|
||||
|
||||
if (state->protocol_mode != event->protocol_mode) {
|
||||
state->protocol_mode = event->protocol_mode;
|
||||
|
||||
if (mode_to_transport(current_mode, &active_transport) &&
|
||||
(active_transport == event->transport)) {
|
||||
pending_keys.valid = false;
|
||||
pending_consumer_latest.valid = false;
|
||||
consumer_fifo_head = 0U;
|
||||
consumer_fifo_tail = 0U;
|
||||
consumer_fifo_count = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state->ready &&
|
||||
mode_to_transport(current_mode, &active_transport) &&
|
||||
(active_transport == event->transport)) {
|
||||
consumer_fifo_head = 0U;
|
||||
consumer_fifo_tail = 0U;
|
||||
consumer_fifo_count = 0U;
|
||||
in_flight.active = false;
|
||||
if (event->report_ready_bm == 0U) {
|
||||
in_flight[event->channel].active = false;
|
||||
}
|
||||
|
||||
try_send_next();
|
||||
@@ -300,17 +320,21 @@ static bool handle_hid_transport_state_event(const struct hid_transport_state_ev
|
||||
|
||||
static bool handle_hid_report_sent_event(const struct hid_report_sent_event *event)
|
||||
{
|
||||
if (!in_flight.active || (event->transport != in_flight.transport)) {
|
||||
if (event->channel >= HID_SEND_CH_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->sequence != in_flight.sequence) {
|
||||
if (!in_flight[event->channel].active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->sequence != in_flight[event->channel].sequence) {
|
||||
LOG_WRN("Unexpected HID sent sequence %u (expected %u)",
|
||||
event->sequence, in_flight.sequence);
|
||||
event->sequence, in_flight[event->channel].sequence);
|
||||
return false;
|
||||
}
|
||||
|
||||
in_flight.active = false;
|
||||
in_flight[event->channel].active = false;
|
||||
|
||||
if (event->error) {
|
||||
LOG_WRN("HID report send failed for seq %u", event->sequence);
|
||||
@@ -339,18 +363,14 @@ static int module_init(void)
|
||||
{
|
||||
clear_pending_reports();
|
||||
current_mode = MODE_SWITCH_USB;
|
||||
transport_state[HID_TRANSPORT_USB].ready = false;
|
||||
transport_state[HID_TRANSPORT_USB].keys_ready = false;
|
||||
transport_state[HID_TRANSPORT_USB].consumer_ready = false;
|
||||
transport_state[HID_TRANSPORT_USB].protocol_mode =
|
||||
memset(channel_state, 0, sizeof(channel_state));
|
||||
channel_state[HID_SEND_CH_USB_KEYS].protocol_mode =
|
||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
transport_state[HID_TRANSPORT_BLE].ready = false;
|
||||
transport_state[HID_TRANSPORT_BLE].keys_ready = false;
|
||||
transport_state[HID_TRANSPORT_BLE].consumer_ready = false;
|
||||
transport_state[HID_TRANSPORT_BLE].protocol_mode =
|
||||
channel_state[HID_SEND_CH_USB_CONSUMER].protocol_mode =
|
||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
channel_state[HID_SEND_CH_BLE_SHARED].protocol_mode =
|
||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
next_sequence = 1U;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -362,7 +382,6 @@ static int module_start(void)
|
||||
|
||||
running = true;
|
||||
try_send_next();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -379,15 +398,18 @@ static void module_pause(void)
|
||||
static bool app_event_handler(const struct app_event_header *aeh)
|
||||
{
|
||||
if (is_keyboard_hid_report_event(aeh)) {
|
||||
return handle_keyboard_hid_report_event(cast_keyboard_hid_report_event(aeh));
|
||||
return handle_keyboard_hid_report_event(
|
||||
cast_keyboard_hid_report_event(aeh));
|
||||
}
|
||||
|
||||
if (is_hid_transport_state_event(aeh)) {
|
||||
return handle_hid_transport_state_event(cast_hid_transport_state_event(aeh));
|
||||
if (is_hid_channel_state_event(aeh)) {
|
||||
return handle_hid_channel_state_event(
|
||||
cast_hid_channel_state_event(aeh));
|
||||
}
|
||||
|
||||
if (is_hid_report_sent_event(aeh)) {
|
||||
return handle_hid_report_sent_event(cast_hid_report_sent_event(aeh));
|
||||
return handle_hid_report_sent_event(
|
||||
cast_hid_report_sent_event(aeh));
|
||||
}
|
||||
|
||||
if (is_mode_switch_event(aeh)) {
|
||||
@@ -395,7 +417,8 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
}
|
||||
|
||||
if (is_module_state_event(aeh)) {
|
||||
const struct module_state_event *event = cast_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)) {
|
||||
int err;
|
||||
@@ -450,7 +473,7 @@ static bool app_event_handler(const struct app_event_header *aeh)
|
||||
|
||||
APP_EVENT_LISTENER(MODULE, app_event_handler);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, keyboard_hid_report_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_transport_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_channel_state_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, hid_report_sent_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, mode_switch_event);
|
||||
APP_EVENT_SUBSCRIBE(MODULE, module_state_event);
|
||||
|
||||
@@ -43,7 +43,6 @@ static struct k_work_delayable rx_flush_work;
|
||||
static bool initialized;
|
||||
static bool running;
|
||||
static bool usb_active;
|
||||
static bool usb_function_prepared;
|
||||
static bool dtr_ready;
|
||||
static bool rx_enabled;
|
||||
static uint8_t proto_rx_buf[USB_CDC_PROTO_RX_BUF_SIZE];
|
||||
@@ -275,7 +274,6 @@ static int module_init(void)
|
||||
k_work_init_delayable(&control_work, control_work_handler);
|
||||
k_work_init_delayable(&rx_flush_work, rx_flush_work_handler);
|
||||
uart_irq_callback_set(cdc_dev, cdc_interrupt_handler);
|
||||
usb_function_prepared = false;
|
||||
proto_rx_len = 0U;
|
||||
return 0;
|
||||
}
|
||||
|
||||
312
src/usb_hid_consumer_module.c
Normal file
312
src/usb_hid_consumer_module.c
Normal file
@@ -0,0 +1,312 @@
|
||||
#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 "usb_function_hook.h"
|
||||
#include "usb_state_event.h"
|
||||
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
static const struct device *const hid_dev =
|
||||
DEVICE_DT_GET(DT_NODELABEL(hid_consumer));
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
static bool initialized;
|
||||
static bool running;
|
||||
static bool usb_active;
|
||||
static bool iface_ready;
|
||||
static bool report_in_flight;
|
||||
static uint16_t in_flight_sequence;
|
||||
|
||||
UDC_STATIC_BUF_DEFINE(consumer_tx_buf, KEYBOARD_CONSUMER_REPORT_SIZE);
|
||||
|
||||
static void publish_consumer_state(void)
|
||||
{
|
||||
bool ready = running && usb_active && 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);
|
||||
|
||||
iface_ready = ready;
|
||||
if (!ready) {
|
||||
report_in_flight = false;
|
||||
}
|
||||
|
||||
LOG_INF("%s interface %s", 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);
|
||||
|
||||
report_in_flight = false;
|
||||
submit_hid_report_sent_event(HID_SEND_CH_USB_CONSUMER,
|
||||
KEYBOARD_REPORT_TYPE_CONSUMER,
|
||||
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(hid_dev)) {
|
||||
LOG_ERR("HID device %s not ready", hid_dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return hid_device_register(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 module_init(void)
|
||||
{
|
||||
usb_active = false;
|
||||
iface_ready = false;
|
||||
report_in_flight = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int module_start(void)
|
||||
{
|
||||
if (running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running = true;
|
||||
publish_consumer_state();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void module_pause(void)
|
||||
{
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = false;
|
||||
report_in_flight = false;
|
||||
publish_consumer_state();
|
||||
}
|
||||
|
||||
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 == usb_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usb_active = new_usb_active;
|
||||
if (!usb_active) {
|
||||
iface_ready = false;
|
||||
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 (!running || !usb_active ||
|
||||
(event->channel != HID_SEND_CH_USB_CONSUMER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->report_type != KEYBOARD_REPORT_TYPE_CONSUMER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!iface_ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (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(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 {
|
||||
report_in_flight = true;
|
||||
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);
|
||||
int err;
|
||||
|
||||
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||
if (!initialized) {
|
||||
err = module_init();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
err = module_start();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
if (initialized) {
|
||||
module_pause();
|
||||
module_set_state(MODULE_STATE_STANDBY);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_wake_up_event(aeh)) {
|
||||
if (initialized) {
|
||||
int err = module_start();
|
||||
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
350
src/usb_hid_keyboard_module.c
Normal file
350
src/usb_hid_keyboard_module.c
Normal file
@@ -0,0 +1,350 @@
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <app_event_manager.h>
|
||||
|
||||
#define MODULE usb_hid_keyboard_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_led_event.h"
|
||||
#include "hid_report_sent_event.h"
|
||||
#include "hid_tx_report_event.h"
|
||||
#include "keyboard_core.h"
|
||||
#include "set_protocol_event.h"
|
||||
#include "usb_function_hook.h"
|
||||
#include "usb_state_event.h"
|
||||
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define KBD_LED_REPORT_SIZE 1U
|
||||
|
||||
static const struct device *const hid_dev = DEVICE_DT_GET(DT_NODELABEL(hid_kbd));
|
||||
|
||||
static const uint8_t keyboard_report_desc[] = {
|
||||
0x05, 0x01, 0x09, 0x06, 0xA1, 0x01, 0x05, 0x07,
|
||||
0x19, 0xE0, 0x29, 0xE7, 0x15, 0x00, 0x25, 0x01,
|
||||
0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x05, 0x07,
|
||||
0x19, 0x00, 0x2A, 0xDF, 0x00, 0x15, 0x00, 0x25,
|
||||
0x01, 0x75, 0x01, 0x96, 0xE0, 0x00, 0x81, 0x02,
|
||||
0x05, 0x08, 0x19, 0x01, 0x29, 0x05, 0x15, 0x00,
|
||||
0x25, 0x01, 0x75, 0x01, 0x95, 0x05, 0x91, 0x02,
|
||||
0x75, 0x03, 0x95, 0x01, 0x91, 0x01, 0xC0
|
||||
};
|
||||
|
||||
static enum keyboard_protocol_mode keyboard_protocol_mode =
|
||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
static bool initialized;
|
||||
static bool running;
|
||||
static bool usb_active;
|
||||
static bool iface_ready;
|
||||
static bool report_in_flight;
|
||||
static uint16_t in_flight_sequence;
|
||||
|
||||
UDC_STATIC_BUF_DEFINE(keyboard_tx_buf, KEYBOARD_NKRO_REPORT_SIZE);
|
||||
|
||||
static void publish_keyboard_state(void)
|
||||
{
|
||||
bool ready = running && usb_active && iface_ready;
|
||||
|
||||
submit_hid_channel_state_event(HID_SEND_CH_USB_KEYS,
|
||||
ready ? BIT(KEYBOARD_REPORT_TYPE_KEYS) : 0U,
|
||||
keyboard_protocol_mode);
|
||||
}
|
||||
|
||||
static void keyboard_iface_ready(const struct device *dev, const bool ready)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
iface_ready = ready;
|
||||
if (!ready) {
|
||||
report_in_flight = false;
|
||||
}
|
||||
|
||||
LOG_INF("%s interface %s", hid_dev->name, ready ? "ready" : "not ready");
|
||||
publish_keyboard_state();
|
||||
}
|
||||
|
||||
static int keyboard_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 keyboard_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 keyboard_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 keyboard_get_idle(const struct device *dev, const uint8_t id)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(id);
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
static enum keyboard_protocol_mode usb_proto_to_keyboard_proto(uint8_t proto)
|
||||
{
|
||||
return (proto == HID_PROTOCOL_BOOT) ? KEYBOARD_PROTOCOL_MODE_BOOT
|
||||
: KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
}
|
||||
|
||||
static void keyboard_set_protocol(const struct device *dev, const uint8_t proto)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
enum keyboard_protocol_mode new_mode = usb_proto_to_keyboard_proto(proto);
|
||||
|
||||
if (keyboard_protocol_mode == new_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyboard_protocol_mode = new_mode;
|
||||
LOG_INF("USB keyboard protocol -> %s",
|
||||
(new_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ? "boot" : "report");
|
||||
submit_set_protocol_event(HID_TRANSPORT_USB, new_mode);
|
||||
publish_keyboard_state();
|
||||
}
|
||||
|
||||
static void keyboard_input_report_done(const struct device *dev,
|
||||
const uint8_t *const report)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(report);
|
||||
|
||||
report_in_flight = false;
|
||||
submit_hid_report_sent_event(HID_SEND_CH_USB_KEYS,
|
||||
KEYBOARD_REPORT_TYPE_KEYS,
|
||||
in_flight_sequence, false);
|
||||
}
|
||||
|
||||
static void keyboard_output_report(const struct device *dev,
|
||||
const uint16_t len,
|
||||
const uint8_t *const buf)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
if ((len < KBD_LED_REPORT_SIZE) || (buf == NULL)) {
|
||||
LOG_WRN("Invalid keyboard output report");
|
||||
return;
|
||||
}
|
||||
|
||||
submit_hid_led_event(HID_TRANSPORT_USB, buf[0]);
|
||||
}
|
||||
|
||||
static const struct hid_device_ops keyboard_ops = {
|
||||
.iface_ready = keyboard_iface_ready,
|
||||
.get_report = keyboard_get_report,
|
||||
.set_report = keyboard_set_report,
|
||||
.set_idle = keyboard_set_idle,
|
||||
.get_idle = keyboard_get_idle,
|
||||
.set_protocol = keyboard_set_protocol,
|
||||
.input_report_done = keyboard_input_report_done,
|
||||
.output_report = keyboard_output_report,
|
||||
};
|
||||
|
||||
static int usb_hid_keyboard_register_device(void)
|
||||
{
|
||||
if (!device_is_ready(hid_dev)) {
|
||||
LOG_ERR("HID device %s not ready", hid_dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
return hid_device_register(hid_dev, keyboard_report_desc,
|
||||
sizeof(keyboard_report_desc), &keyboard_ops);
|
||||
}
|
||||
|
||||
USB_FUNCTION_HOOK_DEFINE(usb_hid_keyboard_hook, usb_hid_keyboard_register_device);
|
||||
|
||||
static int module_init(void)
|
||||
{
|
||||
keyboard_protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
usb_active = false;
|
||||
iface_ready = false;
|
||||
report_in_flight = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int module_start(void)
|
||||
{
|
||||
if (running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running = true;
|
||||
publish_keyboard_state();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void module_pause(void)
|
||||
{
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = false;
|
||||
report_in_flight = false;
|
||||
publish_keyboard_state();
|
||||
}
|
||||
|
||||
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 == usb_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
usb_active = new_usb_active;
|
||||
if (!usb_active) {
|
||||
iface_ready = false;
|
||||
report_in_flight = false;
|
||||
}
|
||||
|
||||
publish_keyboard_state();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!running || !usb_active ||
|
||||
(event->channel != HID_SEND_CH_USB_KEYS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->report_type != KEYBOARD_REPORT_TYPE_KEYS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->protocol_mode != keyboard_protocol_mode) {
|
||||
LOG_WRN("Drop USB keys report due to protocol mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!iface_ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (report_in_flight) {
|
||||
LOG_WRN("Drop USB keyboard report while previous report is in flight");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(keyboard_tx_buf, event->dyndata.data, event->dyndata.size);
|
||||
err = hid_device_submit_report(hid_dev, (uint16_t)event->dyndata.size,
|
||||
keyboard_tx_buf);
|
||||
if (err) {
|
||||
LOG_WRN("USB keyboard report submit failed (%d)", err);
|
||||
submit_hid_report_sent_event(HID_SEND_CH_USB_KEYS,
|
||||
KEYBOARD_REPORT_TYPE_KEYS,
|
||||
event->sequence, true);
|
||||
} else {
|
||||
report_in_flight = true;
|
||||
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);
|
||||
int err;
|
||||
|
||||
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
||||
if (!initialized) {
|
||||
err = module_init();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
err = module_start();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
if (initialized) {
|
||||
module_pause();
|
||||
module_set_state(MODULE_STATE_STANDBY);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_wake_up_event(aeh)) {
|
||||
if (initialized) {
|
||||
int err = module_start();
|
||||
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1,586 +0,0 @@
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <app_event_manager.h>
|
||||
|
||||
#define MODULE usb_hid_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/sys/util.h>
|
||||
#include <zephyr/usb/class/usbd_hid.h>
|
||||
|
||||
#include "hid_led_event.h"
|
||||
#include "hid_report_sent_event.h"
|
||||
#include "hid_transport_state_event.h"
|
||||
#include "hid_tx_report_event.h"
|
||||
#include "keyboard_core.h"
|
||||
#include "set_protocol_event.h"
|
||||
#include "usb_function_hook.h"
|
||||
#include "usb_state_event.h"
|
||||
|
||||
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
|
||||
|
||||
#define KBD_LED_REPORT_SIZE 1U
|
||||
|
||||
enum usb_hid_interface {
|
||||
USB_HID_INTERFACE_KEYBOARD,
|
||||
USB_HID_INTERFACE_CONSUMER,
|
||||
USB_HID_INTERFACE_COUNT,
|
||||
};
|
||||
|
||||
struct usb_hid_interface_state {
|
||||
const struct device *dev;
|
||||
bool ready;
|
||||
};
|
||||
|
||||
static const uint8_t keyboard_report_desc[] = {
|
||||
0x05, 0x01, /* Usage Page (Generic Desktop) */
|
||||
0x09, 0x06, /* Usage (Keyboard) */
|
||||
0xA1, 0x01, /* Collection (Application) */
|
||||
0x05, 0x07, /* Usage Page (Keyboard/Keypad) */
|
||||
0x19, 0xE0, /* Usage Minimum (0xE0) */
|
||||
0x29, 0xE7, /* Usage Maximum (0xE7) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x95, 0x08, /* Report Count (8) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x05, 0x07, /* Usage Page (Keyboard/Keypad) */
|
||||
0x19, 0x00, /* Usage Minimum (0x00) */
|
||||
0x2A, 0xDF, 0x00, /* Usage Maximum (0x00DF) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x96, 0xE0, 0x00, /* Report Count (224) */
|
||||
0x81, 0x02, /* Input (Data,Var,Abs) */
|
||||
0x05, 0x08, /* Usage Page (LEDs) */
|
||||
0x19, 0x01, /* Usage Minimum (1) */
|
||||
0x29, 0x05, /* Usage Maximum (5) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x25, 0x01, /* Logical Maximum (1) */
|
||||
0x75, 0x01, /* Report Size (1) */
|
||||
0x95, 0x05, /* Report Count (5) */
|
||||
0x91, 0x02, /* Output (Data,Var,Abs) */
|
||||
0x75, 0x03, /* Report Size (3) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x91, 0x01, /* Output (Const,Array,Abs) */
|
||||
0xC0 /* End Collection */
|
||||
};
|
||||
|
||||
static const uint8_t consumer_report_desc[] = {
|
||||
0x05, 0x0C, /* Usage Page (Consumer) */
|
||||
0x09, 0x01, /* Usage (Consumer Control) */
|
||||
0xA1, 0x01, /* Collection (Application) */
|
||||
0x15, 0x00, /* Logical Minimum (0) */
|
||||
0x26, 0xFF, 0x03, /* Logical Maximum (1023) */
|
||||
0x19, 0x00, /* Usage Minimum (0) */
|
||||
0x2A, 0xFF, 0x03, /* Usage Maximum (1023) */
|
||||
0x75, 0x10, /* Report Size (16) */
|
||||
0x95, 0x01, /* Report Count (1) */
|
||||
0x81, 0x00, /* Input (Data,Array,Abs) */
|
||||
0xC0 /* End Collection */
|
||||
};
|
||||
|
||||
static struct usb_hid_interface_state hid_ifaces[USB_HID_INTERFACE_COUNT] = {
|
||||
[USB_HID_INTERFACE_KEYBOARD] = {
|
||||
.dev = DEVICE_DT_GET(DT_NODELABEL(hid_kbd)),
|
||||
},
|
||||
[USB_HID_INTERFACE_CONSUMER] = {
|
||||
.dev = DEVICE_DT_GET(DT_NODELABEL(hid_consumer)),
|
||||
},
|
||||
};
|
||||
|
||||
static enum keyboard_protocol_mode keyboard_protocol_mode =
|
||||
KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
static bool initialized;
|
||||
static bool running;
|
||||
static bool usb_active;
|
||||
static bool usb_function_prepared;
|
||||
static bool keyboard_report_in_flight;
|
||||
static bool consumer_report_in_flight;
|
||||
static uint16_t keyboard_in_flight_sequence;
|
||||
static uint16_t consumer_in_flight_sequence;
|
||||
|
||||
UDC_STATIC_BUF_DEFINE(keyboard_tx_buf, KEYBOARD_NKRO_REPORT_SIZE);
|
||||
UDC_STATIC_BUF_DEFINE(consumer_tx_buf, KEYBOARD_CONSUMER_REPORT_SIZE);
|
||||
|
||||
static struct usb_hid_interface_state *iface_from_dev(const struct device *dev)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(hid_ifaces); i++) {
|
||||
if (hid_ifaces[i].dev == dev) {
|
||||
return &hid_ifaces[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static enum keyboard_protocol_mode usb_proto_to_keyboard_proto(uint8_t proto)
|
||||
{
|
||||
return (proto == HID_PROTOCOL_BOOT) ? KEYBOARD_PROTOCOL_MODE_BOOT
|
||||
: KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
}
|
||||
|
||||
static void reset_usb_runtime_state(void)
|
||||
{
|
||||
for (size_t i = 0; i < ARRAY_SIZE(hid_ifaces); i++) {
|
||||
hid_ifaces[i].ready = false;
|
||||
}
|
||||
|
||||
usb_active = false;
|
||||
keyboard_report_in_flight = false;
|
||||
consumer_report_in_flight = false;
|
||||
}
|
||||
|
||||
static void submit_usb_transport_state_event(void)
|
||||
{
|
||||
bool ready = running && usb_active;
|
||||
|
||||
submit_hid_transport_state_event(
|
||||
HID_TRANSPORT_USB,
|
||||
ready,
|
||||
ready && hid_ifaces[USB_HID_INTERFACE_KEYBOARD].ready,
|
||||
ready && hid_ifaces[USB_HID_INTERFACE_CONSUMER].ready,
|
||||
keyboard_protocol_mode);
|
||||
}
|
||||
|
||||
static void keyboard_iface_ready(const struct device *dev, const bool ready)
|
||||
{
|
||||
struct usb_hid_interface_state *iface = iface_from_dev(dev);
|
||||
|
||||
if (!iface) {
|
||||
return;
|
||||
}
|
||||
|
||||
iface->ready = ready;
|
||||
if (!ready) {
|
||||
keyboard_report_in_flight = false;
|
||||
}
|
||||
|
||||
LOG_INF("%s interface %s", dev->name, ready ? "ready" : "not ready");
|
||||
submit_usb_transport_state_event();
|
||||
}
|
||||
|
||||
static int keyboard_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 keyboard_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 keyboard_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 keyboard_get_idle(const struct device *dev, const uint8_t id)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(id);
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
static void keyboard_set_protocol(const struct device *dev, const uint8_t proto)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
enum keyboard_protocol_mode new_mode = usb_proto_to_keyboard_proto(proto);
|
||||
|
||||
if (keyboard_protocol_mode == new_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyboard_protocol_mode = new_mode;
|
||||
LOG_INF("USB keyboard protocol -> %s",
|
||||
(new_mode == KEYBOARD_PROTOCOL_MODE_BOOT) ? "boot" : "report");
|
||||
submit_set_protocol_event(HID_TRANSPORT_USB, new_mode);
|
||||
submit_usb_transport_state_event();
|
||||
}
|
||||
|
||||
static void keyboard_input_report_done(const struct device *dev,
|
||||
const uint8_t *const report)
|
||||
{
|
||||
ARG_UNUSED(report);
|
||||
|
||||
keyboard_report_in_flight = false;
|
||||
LOG_DBG("USB keyboard report sent by %s", dev->name);
|
||||
submit_hid_report_sent_event(HID_TRANSPORT_USB,
|
||||
KEYBOARD_REPORT_TYPE_KEYS,
|
||||
keyboard_in_flight_sequence, false);
|
||||
}
|
||||
|
||||
static void keyboard_output_report(const struct device *dev,
|
||||
const uint16_t len,
|
||||
const uint8_t *const buf)
|
||||
{
|
||||
ARG_UNUSED(dev);
|
||||
|
||||
if ((len < KBD_LED_REPORT_SIZE) || (buf == NULL)) {
|
||||
LOG_WRN("Invalid keyboard output report");
|
||||
return;
|
||||
}
|
||||
|
||||
submit_hid_led_event(HID_TRANSPORT_USB, buf[0]);
|
||||
}
|
||||
|
||||
static const struct hid_device_ops keyboard_ops = {
|
||||
.iface_ready = keyboard_iface_ready,
|
||||
.get_report = keyboard_get_report,
|
||||
.set_report = keyboard_set_report,
|
||||
.set_idle = keyboard_set_idle,
|
||||
.get_idle = keyboard_get_idle,
|
||||
.set_protocol = keyboard_set_protocol,
|
||||
.input_report_done = keyboard_input_report_done,
|
||||
.output_report = keyboard_output_report,
|
||||
};
|
||||
|
||||
static void consumer_iface_ready(const struct device *dev, const bool ready)
|
||||
{
|
||||
struct usb_hid_interface_state *iface = iface_from_dev(dev);
|
||||
|
||||
if (!iface) {
|
||||
return;
|
||||
}
|
||||
|
||||
iface->ready = ready;
|
||||
if (!ready) {
|
||||
consumer_report_in_flight = false;
|
||||
}
|
||||
|
||||
LOG_INF("%s interface %s", dev->name, ready ? "ready" : "not ready");
|
||||
submit_usb_transport_state_event();
|
||||
}
|
||||
|
||||
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(report);
|
||||
|
||||
consumer_report_in_flight = false;
|
||||
LOG_DBG("USB consumer report sent by %s", dev->name);
|
||||
submit_hid_report_sent_event(HID_TRANSPORT_USB,
|
||||
KEYBOARD_REPORT_TYPE_CONSUMER,
|
||||
consumer_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_register_devices(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_SIZE(hid_ifaces); i++) {
|
||||
if (!device_is_ready(hid_ifaces[i].dev)) {
|
||||
LOG_ERR("HID device %s not ready", hid_ifaces[i].dev->name);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
err = hid_device_register(hid_ifaces[USB_HID_INTERFACE_KEYBOARD].dev,
|
||||
keyboard_report_desc,
|
||||
sizeof(keyboard_report_desc),
|
||||
&keyboard_ops);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = hid_device_register(hid_ifaces[USB_HID_INTERFACE_CONSUMER].dev,
|
||||
consumer_report_desc,
|
||||
sizeof(consumer_report_desc),
|
||||
&consumer_ops);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int usb_hid_pre_stack_init(void)
|
||||
{
|
||||
return usb_hid_register_devices();
|
||||
}
|
||||
|
||||
USB_FUNCTION_HOOK_DEFINE(usb_hid_hook, usb_hid_pre_stack_init);
|
||||
|
||||
static int module_init(void)
|
||||
{
|
||||
reset_usb_runtime_state();
|
||||
keyboard_protocol_mode = KEYBOARD_PROTOCOL_MODE_REPORT;
|
||||
usb_function_prepared = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int module_start(void)
|
||||
{
|
||||
if (running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
running = true;
|
||||
submit_usb_transport_state_event();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void module_pause(void)
|
||||
{
|
||||
running = false;
|
||||
submit_usb_transport_state_event();
|
||||
}
|
||||
|
||||
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 == usb_active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!new_usb_active) {
|
||||
reset_usb_runtime_state();
|
||||
} else {
|
||||
usb_active = true;
|
||||
}
|
||||
|
||||
submit_usb_transport_state_event();
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool handle_hid_tx_report_event(const struct hid_tx_report_event *event)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (!running || !usb_active || (event->transport != HID_TRANSPORT_USB)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) {
|
||||
if (event->protocol_mode != keyboard_protocol_mode) {
|
||||
LOG_WRN("Drop USB keys report due to protocol mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hid_ifaces[USB_HID_INTERFACE_KEYBOARD].ready) {
|
||||
LOG_DBG("USB keyboard interface not ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyboard_report_in_flight) {
|
||||
LOG_WRN("Drop USB keyboard report while previous report is in flight");
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(keyboard_tx_buf, event->dyndata.data, event->dyndata.size);
|
||||
|
||||
err = hid_device_submit_report(
|
||||
hid_ifaces[USB_HID_INTERFACE_KEYBOARD].dev,
|
||||
(uint16_t)event->dyndata.size,
|
||||
keyboard_tx_buf);
|
||||
if (err) {
|
||||
LOG_WRN("USB keyboard report submit failed (%d)", err);
|
||||
submit_hid_report_sent_event(HID_TRANSPORT_USB,
|
||||
KEYBOARD_REPORT_TYPE_KEYS,
|
||||
event->sequence, true);
|
||||
} else {
|
||||
keyboard_report_in_flight = true;
|
||||
keyboard_in_flight_sequence = event->sequence;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->report_type == KEYBOARD_REPORT_TYPE_CONSUMER) {
|
||||
if (!hid_ifaces[USB_HID_INTERFACE_CONSUMER].ready) {
|
||||
LOG_DBG("USB consumer interface not ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (consumer_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(
|
||||
hid_ifaces[USB_HID_INTERFACE_CONSUMER].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_TRANSPORT_USB,
|
||||
KEYBOARD_REPORT_TYPE_CONSUMER,
|
||||
event->sequence, true);
|
||||
} else {
|
||||
consumer_report_in_flight = true;
|
||||
consumer_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)) {
|
||||
int err;
|
||||
|
||||
if (!initialized) {
|
||||
err = module_init();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
err = module_start();
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_power_down_event(aeh)) {
|
||||
if (initialized) {
|
||||
module_pause();
|
||||
module_set_state(MODULE_STATE_STANDBY);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is_wake_up_event(aeh)) {
|
||||
if (initialized) {
|
||||
int err = module_start();
|
||||
|
||||
if (err) {
|
||||
module_set_state(MODULE_STATE_ERROR);
|
||||
} else {
|
||||
module_set_state(MODULE_STATE_READY);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
Reference in New Issue
Block a user