feat(proto): 添加设备通信协议v1修订版及统一帧格式

- 新增docs/device_communication_protocol_v1.md文档,定义V1修订版协议
- CDC和BLE GATT均改为直接传输业务消息,去掉外层协议封装
- BLE改为使用NUS(Nordic UART Service)替代原有GATT服务
- 统一键盘位图为29字节格式,FunctionKeyEvent改为上报全键盘位图
- 顶层消息增加msg_id和reply_to字段用于请求响应匹配
- Ack和Error合并为统一Response消息类型
- CDC和NUS均增加统一外层帧格式:magic(2) + len(1) + protobuf
- 添加Proto frame常量定义及长度验证逻辑
- 更新proto文件定义,包含DeviceMessage统一信封和ResponseCode枚举

- 重构hid_flowctrl_module.c中的上下文访问方式,统一使用ctx前缀
This commit is contained in:
2026-04-24 10:54:14 +08:00
parent 48968e7880
commit 3971d7c4b2
10 changed files with 703 additions and 321 deletions

View File

@@ -0,0 +1,302 @@
# 下位机通信协议 v1 修订版
本版基于原 `device_communication_protocol_v1.md` 修订,变更点如下:
- `CDC` 去掉外层协议,直接传业务消息
- `BLE GATT` 改为使用 `NUS (Nordic UART Service)`
- `Bitmap` 统一为 29 字节键盘位图格式
- `FunctionKeyEvent` 不再上报单键按下/释放,改为上报全键盘位图,格式与 `Bitmap` 一致
- 顶层消息增加 `msg_id` / `reply_to`
- `Ack``Error` 合并为统一 `Response`
- `CDC``NUS` 均增加统一外层帧:`magic(2) + len(1) + protobuf`
## 1. 总体原则
- 标准键盘输入继续走 `HID / BLE HID`,不变。
- 私有通信继续保留两条通道:
- `USB`:走 `CDC`
- `BLE`:走 `NUS`
- 两条通道统一承载同一套 `protobuf` 业务消息。
- `CDC``NUS` 均使用完全相同的外层帧格式,帧内承载 `protobuf` 业务消息。
- 当前版本不做应用层分片。
说明:
- 顶层 `protobuf` 消息名改为 `DeviceMessage`,表示独立于具体传输通道的统一业务信封。
- 当前版本使用统一外层帧,`CDC``NUS` 都按完整帧进行发送与解析。
## 2. 共享业务消息
统一的 `protobuf` 业务消息为 `DeviceMessage`,内部 `oneof body` 可取以下类型:
| 消息 | 方向 | 说明 |
| --- | --- | --- |
| `HelloReq` | Host -> Device | 握手请求 |
| `HelloRsp` | Device -> Host | 握手响应 |
| `Bitmap` | Host -> Device | 下发功能键位图配置 |
| `FunctionKeyEvent` | Device -> Host | 上报当前全键盘状态位图 |
| `LedState` | Device -> Host | 上报键盘 LED 状态 |
| `TimeSync` | Host -> Device | 下发时间同步 |
| `ThemeRgb` | Host -> Device | 下发主题颜色 |
| `Response` | Device -> Host | 控制命令统一响应 |
顶层公共字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `msg_id` | `uint32` | 当前消息 ID`0` 表示未使用 |
| `reply_to` | `uint32` | 当前消息响应的请求 ID`0` 表示不是响应 |
规则:
- Host 发起的请求消息应填写唯一 `msg_id`
- `HelloRsp``Response` 应填写 `reply_to = 原请求.msg_id`
- `FunctionKeyEvent``LedState` 等异步上报消息应设置 `reply_to = 0`
- 当前版本建议每条链路同一时刻仅保留 `1` 条 in-flight 控制请求。
## 3. CDC 协议
CDC 在线路上的真实字节格式如下:
```text
magic(2B) + len(1B) + protobuf(DeviceMessage)
```
约束:
- `magic` 固定为 `0xAA55`,按小端发送,即线路字节序为 `0x55 0xAA`
- `len` 为后续 `protobuf(DeviceMessage)` 的字节长度,范围 `0..64`
- Host -> Device 与 Device -> Host 都发送完整帧。
- 当前版本要求单条业务消息一次完整发送,不做应用层分片与重组。
## 4. NUS 协议
BLE 私有通信改为使用 `NUS (Nordic UART Service)`
NUS 线路上的真实字节格式如下:
```text
magic(2B) + len(1B) + protobuf(DeviceMessage)
```
方向定义:
- Host -> Device`RX` 特征写入完整帧
- Device -> Host通过 `TX Notify` 上报完整帧
## 5. NUS 服务定义
Service UUID
```text
6E400001-B5A3-F393-E0A9-E50E24DCCA9E
```
Characteristic 定义:
| 名称 | UUID | 属性 | 用途 |
| --- | --- | --- | --- |
| `RX` | `6E400002-B5A3-F393-E0A9-E50E24DCCA9E` | `Write`, `Write Without Response` | Host -> Device |
| `TX` | `6E400003-B5A3-F393-E0A9-E50E24DCCA9E` | `Notify` | Device -> Host |
约束:
- 连接后主机必须先订阅 `TX`
- 当前版本不做应用层分片。
- 因为增加了 `magic + len` 外层帧,建议协商 `ATT_MTU >= 64`,保证完整帧可一次发送。
## 6. 外层帧定义
外层帧字段如下:
| 字段 | 大小 | 说明 |
| --- | --- | --- |
| `magic` | `2` 字节 | 固定 `0xAA55`,线路字节序 `55 AA` |
| `len` | `1` 字节 | `protobuf(DeviceMessage)` 长度,最大 `64` |
| `payload` | `len` 字节 | `protobuf(DeviceMessage)` |
## 7. protobuf 字段定义
以下是业务字段语义。
### HelloReq
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `protocol_version` | `uint32` | 当前固定为 `1` |
### HelloRsp
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `protocol_version` | `uint32` | 当前固定为 `1` |
| `vendor_id` | `uint32` | 当前建议 `0x1209` |
| `product_id` | `uint32` | 当前建议 `0x0001` |
| `firmware_major` | `uint32` | 固件主版本 |
| `firmware_minor` | `uint32` | 固件次版本 |
| `capability_flags` | `uint32` | 能力位 |
### Bitmap
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `usage_bitmap` | `bytes` | 固定 29 字节,表示功能键配置位图 |
语义:
- `Bitmap.usage_bitmap` 长度固定为 `29` 字节。
-`0` 字节表示 `0xE0``0xE7` 这 8 个控制键。
-`1` 到第 `28` 字节表示 `0x00``0xDF`
- 位值为 `1` 表示该 usage 被配置为功能键。
- 位值为 `0` 表示该 usage 不是功能键,继续按普通键处理。
位映射规则:
-`0` 字节:
- `bit0 -> 0xE0`
- `bit1 -> 0xE1`
- ...
- `bit7 -> 0xE7`
-`1` 字节:
- `bit0 -> 0x00`
- `bit1 -> 0x01`
- ...
- `bit7 -> 0x07`
-`2` 字节:
- `bit0 -> 0x08`
- ...
- `bit7 -> 0x0F`
- 依此类推。
-`28` 字节:
- `bit0 -> 0xD8`
- ...
- `bit7 -> 0xDF`
### FunctionKeyEvent
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `usage_bitmap` | `bytes` | 固定 29 字节,表示当前全键盘状态位图 |
语义:
- `FunctionKeyEvent` 不再使用 `usage + action` 的单键事件格式。
- `FunctionKeyEvent.usage_bitmap` 长度固定为 `29` 字节。
- 位图编码方式与 `Bitmap.usage_bitmap` 完全一致。
- 位值为 `1` 表示该 usage 当前处于按下状态。
- 位值为 `0` 表示该 usage 当前处于释放状态。
- 该消息表示一次完整键盘状态快照,而不是单个按键变化。
### LedState
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `led_mask` | `uint32` | NumLock/CapsLock 等位掩码 |
### TimeSync
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `version` | `uint32` | 当前固定 `1` |
| `flags` | `uint32` | 标志位 |
| `timezone_min` | `sint32` | 时区偏移,单位分钟 |
| `utc_ms` | `fixed64` | UTC 毫秒时间戳 |
| `accuracy_ms` | `fixed32` | 精度,毫秒 |
### ThemeRgb
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `red` | `uint32` | `0..255` |
| `green` | `uint32` | `0..255` |
| `blue` | `uint32` | `0..255` |
### Response
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `error_code` | `enum ResponseCode` | 响应结果;`OK` 表示成功,其余表示失败原因 |
## 8. 枚举定义
### ResponseCode
| 值 | 含义 |
| ---: | --- |
| `0` | `OK` |
| `1` | `UNKNOWN_TYPE` |
| `2` | `INVALID_LENGTH` |
| `3` | `INVALID_PARAM` |
| `4` | `NOT_READY` |
说明:
-`KeyAction` 枚举已不再使用,因为 `FunctionKeyEvent` 改为完整位图上报。
## 9. CapabilityFlags 定义
`HelloRsp.capability_flags` 先按以下位定义:
| Bit | 含义 |
| ---: | --- |
| `0` | 支持功能位图配置 |
| `1` | 支持时间同步 |
| `2` | 支持主题切换 |
| `3` | 支持 LED 状态上报 |
| `4` | 支持全键盘位图事件上报 |
当前建议最小返回值:
- 若仅先做握手:可先返回 `0`
-`Bitmap` 可用:打开 `bit0`
-`FunctionKeyEvent` 全键盘位图上报可用:打开 `bit4`
## 10. 握手流程
### CDC 握手
1. Host 打开串口
2. Host 发送 `magic + len + protobuf(DeviceMessage{hello_req})`
3. Device 解析外层帧与 `protobuf(DeviceMessage)`
4. Device 取出 `hello_req`
5. Device 返回 `magic + len + protobuf(DeviceMessage{hello_rsp})`
### NUS 握手
1. Host 连接 BLE
2. Host 发现 `NUS Service`
3. Host 订阅 `TX Notify`
4. Host 向 `RX` 写入完整帧
5. Device 解析外层帧与 `protobuf(DeviceMessage)`
6. Device 通过 `TX Notify` 发回完整帧
说明:
- 文中 `protobuf(HelloReq)` / `protobuf(HelloRsp)` 指业务上发送对应的 `DeviceMessage` 消息,其 `oneof body` 分别为 `hello_req` / `hello_rsp`
## 11. 设备行为规则
- 上电默认所有按键都是普通键。
- 普通键的标准输入行为继续走 `HID / BLE HID`,不变。
- 收到 `Bitmap` 后,位图中标记为功能键的按键按功能键逻辑处理。
- `FunctionKeyEvent` 上报的是当前全键盘状态快照,编码格式与 `Bitmap` 一致。
- `LedState` 在 LED 状态变化时上报。
- `TimeSync` 收到后更新时间管理模块。
- `ThemeRgb` 收到后更新主题模块。
- 控制类请求处理完成后统一返回 `Response`
- 不支持的消息类型也应返回 `Response { error_code = UNKNOWN_TYPE }`,不得静默丢弃。
## 12. Response 规则
- `HelloReq` 不返回 `Response`,而是直接返回 `HelloRsp`,且 `reply_to = HelloReq.msg_id`
- `Bitmap``TimeSync``ThemeRgb` 处理完成后应返回 `Response`
- `Response.error_code = OK` 表示成功。
- 不支持的消息类型返回 `Response { error_code = UNKNOWN_TYPE }`
- 长度错误、参数错误、模块未就绪时分别返回 `INVALID_LENGTH``INVALID_PARAM``NOT_READY`
示例:
- 收到 `Bitmap` 成功处理
返回 `Response { error_code = OK }`
- 收到 `ThemeRgb` 参数非法
返回 `Response { error_code = INVALID_PARAM }`

View File

@@ -16,6 +16,11 @@ enum proto_transport_link_state {
PROTO_TRANSPORT_LINK_READY,
};
#define PROTO_FRAME_MAGIC 0xAA55U
#define PROTO_FRAME_HEADER_SIZE 3U
#define PROTO_MAX_PAYLOAD_LEN 64U
#define PROTO_MAX_FRAME_LEN (PROTO_FRAME_HEADER_SIZE + PROTO_MAX_PAYLOAD_LEN)
#ifdef __cplusplus
}
#endif

View File

@@ -28,7 +28,8 @@ static inline int submit_proto_rx_event(enum proto_transport transport,
struct proto_rx_event *event;
if ((transport >= PROTO_TRANSPORT_COUNT) ||
((data == NULL) && (len > 0U))) {
((data == NULL) && (len > 0U)) ||
(len > PROTO_MAX_FRAME_LEN)) {
return -EINVAL;
}

View File

@@ -28,7 +28,8 @@ static inline int submit_proto_tx_event(enum proto_transport transport,
struct proto_tx_event *event;
if ((transport >= PROTO_TRANSPORT_COUNT) ||
((data == NULL) && (len > 0U))) {
((data == NULL) && (len > 0U)) ||
(len > PROTO_MAX_FRAME_LEN)) {
return -EINVAL;
}

View File

@@ -1,5 +1,13 @@
syntax = "proto3";
enum ResponseCode {
RESPONSE_CODE_OK = 0;
RESPONSE_CODE_UNKNOWN_TYPE = 1;
RESPONSE_CODE_INVALID_LENGTH = 2;
RESPONSE_CODE_INVALID_PARAM = 3;
RESPONSE_CODE_NOT_READY = 4;
}
message HelloReq {
uint32 protocol_version = 1;
}
@@ -39,24 +47,14 @@ message ThemeRgb {
uint32 blue = 3;
}
message Ack {
uint32 acked_type = 1;
message Response {
ResponseCode error_code = 1;
}
enum ErrorCode {
ERROR_CODE_NONE = 0;
ERROR_CODE_UNKNOWN_TYPE = 1;
ERROR_CODE_INVALID_LENGTH = 2;
ERROR_CODE_INVALID_PARAM = 3;
ERROR_CODE_NOT_READY = 4;
}
message DeviceMessage {
uint32 msg_id = 10;
uint32 reply_to = 11;
message Error {
uint32 error_type = 1;
ErrorCode error_code = 2;
}
message CdcPacketBody {
oneof body {
HelloReq hello_req = 1;
HelloRsp hello_rsp = 2;
@@ -65,7 +63,6 @@ message CdcPacketBody {
LedState led_state = 5;
TimeSync time_sync = 6;
ThemeRgb theme_rgb = 7;
Ack ack = 8;
Error error = 9;
Response response = 8;
}
}

View File

@@ -174,6 +174,11 @@ static void received(struct bt_conn *conn, const void *data, uint16_t len,
return;
}
if ((len < PROTO_FRAME_HEADER_SIZE) || (len > PROTO_MAX_FRAME_LEN)) {
LOG_WRN("BLE NUS drop invalid framed RX len:%u", len);
return;
}
(void)submit_proto_rx_event(PROTO_TRANSPORT_BLE_NUS, data, len);
}

View File

@@ -98,19 +98,6 @@ static struct hid_flowctrl_module_ctx ctx = {
},
};
#define lifecycle ctx.lc
#define channel_state ctx.channel_state
#define pending_keys ctx.pending_keys
#define pending_consumer_latest ctx.pending_consumer_latest
#define consumer_fifo ctx.consumer_fifo
#define consumer_fifo_head ctx.consumer_fifo_head
#define consumer_fifo_tail ctx.consumer_fifo_tail
#define consumer_fifo_count ctx.consumer_fifo_count
#define in_flight ctx.in_flight
#define current_transport ctx.current_transport
#define next_sequence ctx.next_sequence
#define running module_lifecycle_is_running(&ctx.lc)
static bool current_transport_to_channel(enum keyboard_report_type report_type,
enum hid_send_channel *channel)
{
@@ -118,7 +105,7 @@ static bool current_transport_to_channel(enum keyboard_report_type report_type,
return false;
}
switch (current_transport) {
switch (ctx.current_transport) {
case HID_TRANSPORT_POLICY_USB:
*channel = (report_type == KEYBOARD_REPORT_TYPE_KEYS) ?
HID_SEND_CH_USB_KEYS :
@@ -136,44 +123,47 @@ static bool current_transport_to_channel(enum keyboard_report_type report_type,
static void clear_pending_reports(void)
{
memset(&pending_keys, 0, sizeof(pending_keys));
memset(&pending_consumer_latest, 0, sizeof(pending_consumer_latest));
consumer_fifo_head = 0U;
consumer_fifo_tail = 0U;
consumer_fifo_count = 0U;
memset(&in_flight, 0, sizeof(in_flight));
memset(&ctx.pending_keys, 0, sizeof(ctx.pending_keys));
memset(&ctx.pending_consumer_latest, 0, sizeof(ctx.pending_consumer_latest));
ctx.consumer_fifo_head = 0U;
ctx.consumer_fifo_tail = 0U;
ctx.consumer_fifo_count = 0U;
memset(&ctx.in_flight, 0, sizeof(ctx.in_flight));
}
static void consumer_fifo_push(enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode,
const uint8_t *data, size_t size)
{
if (consumer_fifo_count == HID_FLOWCTRL_FIFO_DEPTH) {
if (ctx.consumer_fifo_count == HID_FLOWCTRL_FIFO_DEPTH) {
LOG_WRN("Consumer FIFO full, dropping oldest pulse");
consumer_fifo_head = (consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
consumer_fifo_count--;
ctx.consumer_fifo_head =
(ctx.consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
ctx.consumer_fifo_count--;
}
struct queued_report *entry = &consumer_fifo[consumer_fifo_tail];
struct queued_report *entry = &ctx.consumer_fifo[ctx.consumer_fifo_tail];
entry->report_type = report_type;
entry->protocol_mode = protocol_mode;
entry->size = size;
memcpy(entry->data, data, size);
consumer_fifo_tail = (consumer_fifo_tail + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
consumer_fifo_count++;
ctx.consumer_fifo_tail =
(ctx.consumer_fifo_tail + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
ctx.consumer_fifo_count++;
}
static bool consumer_fifo_pop(struct queued_report *entry)
{
if (consumer_fifo_count == 0U) {
if (ctx.consumer_fifo_count == 0U) {
return false;
}
*entry = consumer_fifo[consumer_fifo_head];
consumer_fifo_head = (consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
consumer_fifo_count--;
*entry = ctx.consumer_fifo[ctx.consumer_fifo_head];
ctx.consumer_fifo_head =
(ctx.consumer_fifo_head + 1U) % HID_FLOWCTRL_FIFO_DEPTH;
ctx.consumer_fifo_count--;
return true;
}
@@ -182,9 +172,9 @@ static bool channel_can_send_report(enum hid_send_channel channel,
enum keyboard_report_type report_type,
enum keyboard_protocol_mode protocol_mode)
{
const struct hid_channel_state_data *state = &channel_state[channel];
const struct hid_channel_state_data *state = &ctx.channel_state[channel];
if (in_flight[channel].active) {
if (ctx.in_flight[channel].active) {
return false;
}
@@ -205,7 +195,7 @@ static void try_send_keys(void)
{
enum hid_send_channel channel;
if (!pending_keys.valid) {
if (!ctx.pending_keys.valid) {
return;
}
@@ -213,20 +203,20 @@ static void try_send_keys(void)
return;
}
if (!channel_can_send_report(channel, pending_keys.report_type,
pending_keys.protocol_mode)) {
if (!channel_can_send_report(channel, ctx.pending_keys.report_type,
ctx.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;
ctx.in_flight[channel].active = true;
ctx.in_flight[channel].channel = channel;
ctx.in_flight[channel].report_type = ctx.pending_keys.report_type;
ctx.in_flight[channel].sequence = ctx.next_sequence++;
(void)submit_hid_tx_report_event(channel, ctx.pending_keys.report_type,
ctx.pending_keys.protocol_mode,
ctx.in_flight[channel].sequence,
ctx.pending_keys.data, ctx.pending_keys.size);
ctx.pending_keys.valid = false;
}
static void try_send_consumer_fifo(void)
@@ -234,7 +224,7 @@ static void try_send_consumer_fifo(void)
struct queued_report queued;
enum hid_send_channel channel;
if (consumer_fifo_count == 0U) {
if (ctx.consumer_fifo_count == 0U) {
return;
}
@@ -248,20 +238,20 @@ static void try_send_consumer_fifo(void)
if (!channel_can_send_report(channel, queued.report_type,
queued.protocol_mode)) {
if (queued.protocol_mode == channel_state[channel].protocol_mode) {
if (queued.protocol_mode == ctx.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++;
ctx.in_flight[channel].active = true;
ctx.in_flight[channel].channel = channel;
ctx.in_flight[channel].report_type = queued.report_type;
ctx.in_flight[channel].sequence = ctx.next_sequence++;
(void)submit_hid_tx_report_event(channel, queued.report_type,
queued.protocol_mode,
in_flight[channel].sequence,
ctx.in_flight[channel].sequence,
queued.data, queued.size);
}
@@ -269,7 +259,7 @@ static void try_send_consumer_latest(void)
{
enum hid_send_channel channel;
if (!pending_consumer_latest.valid) {
if (!ctx.pending_consumer_latest.valid) {
return;
}
@@ -278,27 +268,28 @@ static void try_send_consumer_latest(void)
}
if (!channel_can_send_report(channel,
pending_consumer_latest.report_type,
pending_consumer_latest.protocol_mode)) {
ctx.pending_consumer_latest.report_type,
ctx.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++;
ctx.in_flight[channel].active = true;
ctx.in_flight[channel].channel = channel;
ctx.in_flight[channel].report_type =
ctx.pending_consumer_latest.report_type;
ctx.in_flight[channel].sequence = ctx.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;
ctx.pending_consumer_latest.report_type,
ctx.pending_consumer_latest.protocol_mode,
ctx.in_flight[channel].sequence,
ctx.pending_consumer_latest.data,
ctx.pending_consumer_latest.size);
ctx.pending_consumer_latest.valid = false;
}
static void try_send_next(void)
{
if (!running) {
if (!module_lifecycle_is_running(&ctx.lc)) {
return;
}
@@ -310,7 +301,7 @@ static void try_send_next(void)
static bool handle_keyboard_hid_report_event(
const struct keyboard_hid_report_event *event)
{
if (!running ||
if (!module_lifecycle_is_running(&ctx.lc) ||
((event->mode != MODE_SWITCH_USB) && (event->mode != MODE_SWITCH_BLE))) {
return false;
}
@@ -321,17 +312,18 @@ static bool handle_keyboard_hid_report_event(
event->dyndata.data,
event->dyndata.size);
} else if (event->report_type == KEYBOARD_REPORT_TYPE_KEYS) {
pending_keys.valid = true;
pending_keys.report_type = event->report_type;
pending_keys.protocol_mode = event->protocol_mode;
pending_keys.size = event->dyndata.size;
memcpy(pending_keys.data, event->dyndata.data, event->dyndata.size);
ctx.pending_keys.valid = true;
ctx.pending_keys.report_type = event->report_type;
ctx.pending_keys.protocol_mode = event->protocol_mode;
ctx.pending_keys.size = event->dyndata.size;
memcpy(ctx.pending_keys.data, event->dyndata.data,
event->dyndata.size);
} else {
pending_consumer_latest.valid = true;
pending_consumer_latest.report_type = event->report_type;
pending_consumer_latest.protocol_mode = event->protocol_mode;
pending_consumer_latest.size = event->dyndata.size;
memcpy(pending_consumer_latest.data, event->dyndata.data,
ctx.pending_consumer_latest.valid = true;
ctx.pending_consumer_latest.report_type = event->report_type;
ctx.pending_consumer_latest.protocol_mode = event->protocol_mode;
ctx.pending_consumer_latest.size = event->dyndata.size;
memcpy(ctx.pending_consumer_latest.data, event->dyndata.data,
event->dyndata.size);
}
@@ -346,11 +338,12 @@ static bool handle_hid_channel_state_event(
return false;
}
channel_state[event->channel].report_ready_bm = event->report_ready_bm;
channel_state[event->channel].protocol_mode = event->protocol_mode;
ctx.channel_state[event->channel].report_ready_bm =
event->report_ready_bm;
ctx.channel_state[event->channel].protocol_mode = event->protocol_mode;
if (event->report_ready_bm == 0U) {
in_flight[event->channel].active = false;
ctx.in_flight[event->channel].active = false;
}
try_send_next();
@@ -363,17 +356,17 @@ static bool handle_hid_report_sent_event(const struct hid_report_sent_event *eve
return false;
}
if (!in_flight[event->channel].active) {
if (!ctx.in_flight[event->channel].active) {
return false;
}
if (event->sequence != in_flight[event->channel].sequence) {
if (event->sequence != ctx.in_flight[event->channel].sequence) {
LOG_WRN("Unexpected HID sent sequence %u (expected %u)",
event->sequence, in_flight[event->channel].sequence);
event->sequence, ctx.in_flight[event->channel].sequence);
return false;
}
in_flight[event->channel].active = false;
ctx.in_flight[event->channel].active = false;
if (event->error) {
LOG_WRN("HID report send failed for seq %u", event->sequence);
@@ -386,12 +379,13 @@ static bool handle_hid_report_sent_event(const struct hid_report_sent_event *eve
static bool handle_transport_policy_event(
const struct transport_policy_event *event)
{
bool transport_changed = (current_transport != event->hid_transport);
bool transport_changed =
(ctx.current_transport != event->hid_transport);
current_transport = event->hid_transport;
ctx.current_transport = event->hid_transport;
if (transport_changed ||
(current_transport == HID_TRANSPORT_POLICY_NONE)) {
(ctx.current_transport == HID_TRANSPORT_POLICY_NONE)) {
clear_pending_reports();
}
@@ -402,21 +396,21 @@ static bool handle_transport_policy_event(
static int do_init(void)
{
clear_pending_reports();
current_transport = HID_TRANSPORT_POLICY_USB;
memset(channel_state, 0, sizeof(channel_state));
channel_state[HID_SEND_CH_USB_KEYS].protocol_mode =
ctx.current_transport = HID_TRANSPORT_POLICY_USB;
memset(ctx.channel_state, 0, sizeof(ctx.channel_state));
ctx.channel_state[HID_SEND_CH_USB_KEYS].protocol_mode =
KEYBOARD_PROTOCOL_MODE_REPORT;
channel_state[HID_SEND_CH_USB_CONSUMER].protocol_mode =
ctx.channel_state[HID_SEND_CH_USB_CONSUMER].protocol_mode =
KEYBOARD_PROTOCOL_MODE_REPORT;
channel_state[HID_SEND_CH_BLE_SHARED].protocol_mode =
ctx.channel_state[HID_SEND_CH_BLE_SHARED].protocol_mode =
KEYBOARD_PROTOCOL_MODE_REPORT;
next_sequence = 1U;
ctx.next_sequence = 1U;
return 0;
}
static int do_start(void)
{
if (running) {
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
@@ -426,7 +420,7 @@ static int do_start(void)
static int do_stop(void)
{
if (!running) {
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
@@ -462,23 +456,23 @@ static bool app_event_handler(const struct app_event_header *aeh)
cast_module_state_event(aeh);
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
(void)module_set_lifecycle(&lifecycle, LC_RUNNING);
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&lifecycle)) {
(void)module_set_lifecycle(&lifecycle, LC_STOPPED);
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(&lifecycle)) {
(void)module_set_lifecycle(&lifecycle, LC_RUNNING);
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;

View File

@@ -125,16 +125,6 @@ static struct keyboard_core_module_ctx ctx = {
},
};
#define lifecycle ctx.lc
#define keyboard_state ctx.keyboard_state
#define reports_cache ctx.reports_cache
#define function_usage_mask ctx.function_usage_mask
#define transport_protocol_modes ctx.transport_protocol_modes
#define current_transport ctx.current_transport
#define mode_valid ctx.mode_valid
#define settings_active ctx.settings_active
#define running module_lifecycle_is_running(&ctx.lc)
static bool policy_to_transport(enum hid_transport_policy policy,
enum hid_transport *transport)
{
@@ -156,8 +146,9 @@ static enum keyboard_protocol_mode active_protocol_mode_get(void)
{
enum hid_transport transport;
if (mode_valid && policy_to_transport(current_transport, &transport)) {
return transport_protocol_modes[transport];
if (ctx.mode_valid &&
policy_to_transport(ctx.current_transport, &transport)) {
return ctx.transport_protocol_modes[transport];
}
return KEYBOARD_PROTOCOL_MODE_REPORT;
@@ -269,38 +260,39 @@ static bool consumer_key_update(uint16_t consumer_id, bool pressed)
return false;
}
bool was_pressed = (keyboard_state.consumer_bits & BIT(consumer_id)) != 0U;
bool was_pressed =
(ctx.keyboard_state.consumer_bits & BIT(consumer_id)) != 0U;
if (was_pressed == pressed) {
return false;
}
WRITE_BIT(keyboard_state.consumer_bits, consumer_id, pressed);
WRITE_BIT(ctx.keyboard_state.consumer_bits, consumer_id, pressed);
return true;
}
static void keyboard_state_clear(void)
{
memset(&keyboard_state, 0, sizeof(keyboard_state));
memset(&ctx.keyboard_state, 0, sizeof(ctx.keyboard_state));
}
static void function_usage_mask_clear(void)
{
memset(function_usage_mask, 0, sizeof(function_usage_mask));
memset(ctx.function_usage_mask, 0, sizeof(ctx.function_usage_mask));
}
static void reports_cache_invalidate(void)
{
reports_cache.boot_valid = false;
reports_cache.nkro_valid = false;
reports_cache.consumer_valid = false;
ctx.reports_cache.boot_valid = false;
ctx.reports_cache.nkro_valid = false;
ctx.reports_cache.consumer_valid = false;
}
static void build_effective_hid_bitmap(uint8_t bitmap[KEYBOARD_PROTOCOL_BITMAP_BYTES])
{
for (size_t i = 0; i < KEYBOARD_PROTOCOL_BITMAP_BYTES; i++) {
bitmap[i] = keyboard_state.pressed_usage_bitmap[i] &
(uint8_t)~keyboard_state.function_pressed_bitmap[i];
bitmap[i] = ctx.keyboard_state.pressed_usage_bitmap[i] &
(uint8_t)~ctx.keyboard_state.function_pressed_bitmap[i];
}
}
@@ -347,7 +339,7 @@ static void build_nkro_report(uint8_t report[KEYBOARD_NKRO_REPORT_SIZE])
static uint16_t active_consumer_usage_get(void)
{
for (uint8_t consumer_id = 0; consumer_id < KEYBOARD_CONSUMER_CTRL_COUNT; consumer_id++) {
if ((keyboard_state.consumer_bits & BIT(consumer_id)) != 0U) {
if ((ctx.keyboard_state.consumer_bits & BIT(consumer_id)) != 0U) {
return consumer_usage_map[consumer_id];
}
}
@@ -366,9 +358,9 @@ static void submit_consumer_fifo_frame(uint16_t usage_id)
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
if (!running || !mode_valid ||
settings_active ||
!transport_policy_to_mode(current_transport, &mode) ||
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.mode_valid ||
ctx.settings_active ||
!transport_policy_to_mode(ctx.current_transport, &mode) ||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
return;
}
@@ -414,24 +406,25 @@ static void emit_keys_report(bool force)
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
if (!mode_valid || !transport_policy_to_mode(current_transport, &mode)) {
if (!ctx.mode_valid ||
!transport_policy_to_mode(ctx.current_transport, &mode)) {
return;
}
if (settings_active) {
if (ctx.settings_active) {
return;
}
if (protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT) {
build_boot_report(report_buf);
report_size = KEYBOARD_BOOT_REPORT_SIZE;
cache_buf = reports_cache.boot_report;
cache_valid = &reports_cache.boot_valid;
cache_buf = ctx.reports_cache.boot_report;
cache_valid = &ctx.reports_cache.boot_valid;
} else {
build_nkro_report(report_buf);
report_size = KEYBOARD_NKRO_REPORT_SIZE;
cache_buf = reports_cache.nkro_report;
cache_valid = &reports_cache.nkro_valid;
cache_buf = ctx.reports_cache.nkro_report;
cache_valid = &ctx.reports_cache.nkro_valid;
}
if (!force && *cache_valid && (memcmp(cache_buf, report_buf, report_size) == 0)) {
@@ -452,21 +445,23 @@ static void emit_consumer_report(bool force)
enum keyboard_protocol_mode protocol_mode = active_protocol_mode_get();
enum mode_switch_mode mode;
if (!mode_valid || !transport_policy_to_mode(current_transport, &mode) ||
settings_active ||
if (!ctx.mode_valid ||
!transport_policy_to_mode(ctx.current_transport, &mode) ||
ctx.settings_active ||
(protocol_mode == KEYBOARD_PROTOCOL_MODE_BOOT)) {
return;
}
build_consumer_report(report_buf);
if (!force && reports_cache.consumer_valid &&
(memcmp(reports_cache.consumer_report, report_buf,
if (!force && ctx.reports_cache.consumer_valid &&
(memcmp(ctx.reports_cache.consumer_report, report_buf,
KEYBOARD_CONSUMER_REPORT_SIZE) == 0)) {
return;
}
memcpy(reports_cache.consumer_report, report_buf, KEYBOARD_CONSUMER_REPORT_SIZE);
reports_cache.consumer_valid = true;
memcpy(ctx.reports_cache.consumer_report, report_buf,
KEYBOARD_CONSUMER_REPORT_SIZE);
ctx.reports_cache.consumer_valid = true;
(void)submit_keyboard_hid_report_event(
mode, KEYBOARD_REPORT_TYPE_CONSUMER, protocol_mode,
@@ -485,7 +480,8 @@ static void emit_all_reports(bool force)
static void emit_function_state_event(void)
{
(void)submit_function_bitmap_state_event(keyboard_state.pressed_usage_bitmap);
(void)submit_function_bitmap_state_event(
ctx.keyboard_state.pressed_usage_bitmap);
}
static void emit_release_reports(enum hid_transport_policy transport_policy)
@@ -526,11 +522,11 @@ static int do_init(void)
keyboard_state_clear();
reports_cache_invalidate();
function_usage_mask_clear();
mode_valid = false;
settings_active = false;
transport_protocol_modes[HID_TRANSPORT_USB] =
ctx.mode_valid = false;
ctx.settings_active = false;
ctx.transport_protocol_modes[HID_TRANSPORT_USB] =
KEYBOARD_PROTOCOL_MODE_REPORT;
transport_protocol_modes[HID_TRANSPORT_BLE] =
ctx.transport_protocol_modes[HID_TRANSPORT_BLE] =
KEYBOARD_PROTOCOL_MODE_REPORT;
return 0;
@@ -538,7 +534,7 @@ static int do_init(void)
static int do_start(void)
{
if (running) {
if (module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
@@ -547,18 +543,18 @@ static int do_start(void)
static int do_stop(void)
{
if (!running) {
if (!module_lifecycle_is_running(&ctx.lc)) {
return 0;
}
if (mode_valid) {
emit_release_reports(current_transport);
if (ctx.mode_valid) {
emit_release_reports(ctx.current_transport);
}
emit_function_state_event();
keyboard_state_clear();
reports_cache_invalidate();
mode_valid = false;
ctx.mode_valid = false;
return 0;
}
@@ -568,11 +564,11 @@ static bool handle_button_event(const struct button_event *event)
const struct keymap_entry *entry;
bool changed;
if (!running) {
if (!module_lifecycle_is_running(&ctx.lc)) {
return false;
}
if (settings_active) {
if (ctx.settings_active) {
return false;
}
@@ -585,7 +581,7 @@ static bool handle_button_event(const struct button_event *event)
if (entry->usage_type == KEY_USAGE_TYPE_KEYBOARD) {
bool routed_to_function;
changed = usage_bitmap_write(keyboard_state.pressed_usage_bitmap,
changed = usage_bitmap_write(ctx.keyboard_state.pressed_usage_bitmap,
entry->usage_id, event->pressed);
if (!changed) {
return false;
@@ -593,14 +589,14 @@ static bool handle_button_event(const struct button_event *event)
if (event->pressed) {
routed_to_function =
usage_bitmap_test(function_usage_mask, entry->usage_id);
(void)usage_bitmap_write(keyboard_state.function_pressed_bitmap,
usage_bitmap_test(ctx.function_usage_mask, entry->usage_id);
(void)usage_bitmap_write(ctx.keyboard_state.function_pressed_bitmap,
entry->usage_id, routed_to_function);
} else {
routed_to_function =
usage_bitmap_test(keyboard_state.function_pressed_bitmap,
usage_bitmap_test(ctx.keyboard_state.function_pressed_bitmap,
entry->usage_id);
(void)usage_bitmap_write(keyboard_state.function_pressed_bitmap,
(void)usage_bitmap_write(ctx.keyboard_state.function_pressed_bitmap,
entry->usage_id, false);
}
@@ -624,23 +620,24 @@ static bool handle_transport_policy_event(
{
bool transport_changed;
if (!running) {
current_transport = event->hid_transport;
if (!module_lifecycle_is_running(&ctx.lc)) {
ctx.current_transport = event->hid_transport;
return false;
}
transport_changed = mode_valid && (current_transport != event->hid_transport);
transport_changed =
ctx.mode_valid && (ctx.current_transport != event->hid_transport);
if (transport_changed) {
emit_release_reports(current_transport);
emit_release_reports(ctx.current_transport);
emit_function_state_event();
keyboard_state_clear();
reports_cache_invalidate();
}
current_transport = event->hid_transport;
mode_valid = (current_transport != HID_TRANSPORT_POLICY_NONE);
ctx.current_transport = event->hid_transport;
ctx.mode_valid = (ctx.current_transport != HID_TRANSPORT_POLICY_NONE);
if (mode_valid) {
if (ctx.mode_valid) {
emit_all_reports(true);
}
@@ -649,11 +646,11 @@ static bool handle_transport_policy_event(
static bool handle_encoder_event(const struct encoder_event *event)
{
if (!running || !mode_valid) {
if (!module_lifecycle_is_running(&ctx.lc) || !ctx.mode_valid) {
return false;
}
if (settings_active) {
if (ctx.settings_active) {
return false;
}
@@ -671,7 +668,8 @@ static bool handle_encoder_event(const struct encoder_event *event)
static bool handle_function_bitmap_update_event(
const struct function_bitmap_update_event *event)
{
memcpy(function_usage_mask, event->bitmap, sizeof(function_usage_mask));
memcpy(ctx.function_usage_mask, event->bitmap,
sizeof(ctx.function_usage_mask));
return false;
}
@@ -698,11 +696,13 @@ static bool app_event_handler(const struct app_event_header *aeh)
return false;
}
if (transport_protocol_modes[event->transport] != event->protocol_mode) {
transport_protocol_modes[event->transport] = event->protocol_mode;
if (ctx.transport_protocol_modes[event->transport] !=
event->protocol_mode) {
ctx.transport_protocol_modes[event->transport] =
event->protocol_mode;
if (running && mode_valid &&
policy_to_transport(current_transport, &active_transport) &&
if (module_lifecycle_is_running(&ctx.lc) && ctx.mode_valid &&
policy_to_transport(ctx.current_transport, &active_transport) &&
(active_transport == event->transport)) {
reports_cache_invalidate();
emit_keys_report(true);
@@ -725,14 +725,14 @@ static bool app_event_handler(const struct app_event_header *aeh)
const struct settings_mode_event *event =
cast_settings_mode_event(aeh);
if (settings_active == event->active) {
if (ctx.settings_active == event->active) {
return false;
}
settings_active = event->active;
if (settings_active) {
if (mode_valid) {
emit_release_reports(current_transport);
ctx.settings_active = event->active;
if (ctx.settings_active) {
if (ctx.mode_valid) {
emit_release_reports(ctx.current_transport);
}
emit_function_state_event();
keyboard_state_clear();
@@ -746,23 +746,23 @@ static bool app_event_handler(const struct app_event_header *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(&lifecycle, LC_RUNNING);
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;
}
if (is_power_down_event(aeh)) {
if (module_lifecycle_is_initialized(&lifecycle)) {
(void)module_set_lifecycle(&lifecycle, LC_STOPPED);
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(&lifecycle)) {
(void)module_set_lifecycle(&lifecycle, LC_RUNNING);
if (module_lifecycle_is_initialized(&ctx.lc)) {
(void)module_set_lifecycle(&ctx.lc, LC_RUNNING);
}
return false;

View File

@@ -36,7 +36,7 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define PROTOCOL_FIRMWARE_MAJOR 0U
#define PROTOCOL_FIRMWARE_MINOR 0U
#define PROTOCOL_CAPABILITY_FLAGS (BIT(0) | BIT(1) | BIT(2) | BIT(3) | BIT(4))
#define PROTOCOL_MAX_MSG_LEN 128U
#define PROTOCOL_MAX_MSG_LEN PROTO_MAX_FRAME_LEN
enum proto_session_state {
PROTO_SESSION_DOWN = 0,
@@ -72,8 +72,6 @@ static struct protocol_module_ctx ctx = {
},
};
#define session_state ctx.session_state
static const char *proto_session_name(enum proto_session_state state)
{
switch (state) {
@@ -110,7 +108,7 @@ static void proto_session_set(enum proto_transport transport,
return;
}
old_state = session_state[transport];
old_state = ctx.session_state[transport];
if (old_state == new_state) {
LOG_INF("Protocol session keep transport:%s state:%s reason:%s",
proto_transport_name(transport),
@@ -118,14 +116,14 @@ static void proto_session_set(enum proto_transport transport,
return;
}
session_state[transport] = new_state;
ctx.session_state[transport] = new_state;
LOG_INF("Protocol session transport:%s %s -> %s reason:%s",
proto_transport_name(transport), proto_session_name(old_state),
proto_session_name(new_state), reason);
}
static int decode_body(const uint8_t *payload, size_t payload_len,
CdcPacketBody *body)
DeviceMessage *body)
{
pb_istream_t stream;
@@ -133,10 +131,10 @@ static int decode_body(const uint8_t *payload, size_t payload_len,
return -EINVAL;
}
*body = (CdcPacketBody)CdcPacketBody_init_zero;
*body = (DeviceMessage)DeviceMessage_init_zero;
stream = pb_istream_from_buffer(payload, payload_len);
if (!pb_decode(&stream, CdcPacketBody_fields, body)) {
if (!pb_decode(&stream, DeviceMessage_fields, body)) {
LOG_WRN("pb_decode failed: %s", PB_GET_ERROR(&stream));
return -EBADMSG;
}
@@ -144,7 +142,37 @@ static int decode_body(const uint8_t *payload, size_t payload_len,
return 0;
}
static int encode_body(const CdcPacketBody *body, uint8_t *payload,
static int decode_frame(const uint8_t *frame, size_t frame_len,
const uint8_t **payload, size_t *payload_len)
{
uint16_t magic;
uint8_t len;
if ((frame == NULL) || (payload == NULL) || (payload_len == NULL)) {
return -EINVAL;
}
if (frame_len < PROTO_FRAME_HEADER_SIZE) {
return -EBADMSG;
}
magic = (uint16_t)frame[0] | ((uint16_t)frame[1] << 8);
if (magic != PROTO_FRAME_MAGIC) {
return -EBADMSG;
}
len = frame[2];
if ((len > PROTO_MAX_PAYLOAD_LEN) ||
(frame_len != (PROTO_FRAME_HEADER_SIZE + len))) {
return -EBADMSG;
}
*payload = &frame[PROTO_FRAME_HEADER_SIZE];
*payload_len = len;
return 0;
}
static int encode_body(const DeviceMessage *body, uint8_t *payload,
size_t payload_buf_size, size_t *payload_len)
{
pb_ostream_t stream;
@@ -154,7 +182,7 @@ static int encode_body(const CdcPacketBody *body, uint8_t *payload,
}
stream = pb_ostream_from_buffer(payload, payload_buf_size);
if (!pb_encode(&stream, CdcPacketBody_fields, body)) {
if (!pb_encode(&stream, DeviceMessage_fields, body)) {
LOG_WRN("pb_encode failed: %s", PB_GET_ERROR(&stream));
return -EIO;
}
@@ -163,12 +191,45 @@ static int encode_body(const CdcPacketBody *body, uint8_t *payload,
return 0;
}
static int encode_hello_rsp(uint8_t *rsp_payload, size_t rsp_payload_buf_size,
static int encode_frame(const DeviceMessage *body, uint8_t *frame,
size_t frame_buf_size, size_t *frame_len)
{
size_t payload_len;
int err;
if ((frame == NULL) || (frame_len == NULL)) {
return -EINVAL;
}
if (frame_buf_size < PROTO_FRAME_HEADER_SIZE) {
return -ENOSPC;
}
err = encode_body(body, &frame[PROTO_FRAME_HEADER_SIZE],
frame_buf_size - PROTO_FRAME_HEADER_SIZE, &payload_len);
if (err) {
return err;
}
if (payload_len > PROTO_MAX_PAYLOAD_LEN) {
return -EMSGSIZE;
}
frame[0] = (uint8_t)(PROTO_FRAME_MAGIC & 0xFFU);
frame[1] = (uint8_t)(PROTO_FRAME_MAGIC >> 8);
frame[2] = (uint8_t)payload_len;
*frame_len = PROTO_FRAME_HEADER_SIZE + payload_len;
return 0;
}
static int encode_hello_rsp(uint32_t reply_to, uint8_t *rsp_payload,
size_t rsp_payload_buf_size,
size_t *rsp_payload_len)
{
CdcPacketBody body = CdcPacketBody_init_zero;
DeviceMessage body = DeviceMessage_init_zero;
body.which_body = CdcPacketBody_hello_rsp_tag;
body.reply_to = reply_to;
body.which_body = DeviceMessage_hello_rsp_tag;
body.body.hello_rsp.protocol_version = PROTOCOL_VERSION;
body.body.hello_rsp.vendor_id = PROTOCOL_VENDOR_ID;
body.body.hello_rsp.product_id = PROTOCOL_PRODUCT_ID;
@@ -176,66 +237,55 @@ static int encode_hello_rsp(uint8_t *rsp_payload, size_t rsp_payload_buf_size,
body.body.hello_rsp.firmware_minor = PROTOCOL_FIRMWARE_MINOR;
body.body.hello_rsp.capability_flags = PROTOCOL_CAPABILITY_FLAGS;
return encode_body(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
return encode_frame(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
static int encode_ack(uint8_t acked_type, uint8_t *rsp_payload,
size_t rsp_payload_buf_size, size_t *rsp_payload_len)
{
CdcPacketBody body = CdcPacketBody_init_zero;
body.which_body = CdcPacketBody_ack_tag;
body.body.ack.acked_type = acked_type;
return encode_body(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
static int encode_error(uint8_t error_type, ErrorCode error_code,
static int encode_response(uint32_t reply_to, ResponseCode error_code,
uint8_t *rsp_payload, size_t rsp_payload_buf_size,
size_t *rsp_payload_len)
{
CdcPacketBody body = CdcPacketBody_init_zero;
DeviceMessage body = DeviceMessage_init_zero;
body.which_body = CdcPacketBody_error_tag;
body.body.error.error_type = error_type;
body.body.error.error_code = error_code;
body.reply_to = reply_to;
body.which_body = DeviceMessage_response_tag;
body.body.response.error_code = error_code;
return encode_body(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
return encode_frame(&body, rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
static int encode_led_state(uint32_t led_mask, uint8_t *payload,
size_t payload_buf_size, size_t *payload_len)
{
CdcPacketBody body = CdcPacketBody_init_zero;
DeviceMessage body = DeviceMessage_init_zero;
body.which_body = CdcPacketBody_led_state_tag;
body.which_body = DeviceMessage_led_state_tag;
body.body.led_state.led_mask = led_mask;
return encode_body(&body, payload, payload_buf_size, payload_len);
return encode_frame(&body, payload, payload_buf_size, payload_len);
}
static int encode_function_bitmap_state(const uint8_t *bitmap, uint8_t *payload,
size_t payload_buf_size,
size_t *payload_len)
{
CdcPacketBody body = CdcPacketBody_init_zero;
DeviceMessage body = DeviceMessage_init_zero;
if (bitmap == NULL) {
return -EINVAL;
}
body.which_body = CdcPacketBody_function_key_event_tag;
body.which_body = DeviceMessage_function_key_event_tag;
body.body.function_key_event.usage_bitmap.size = KEYBOARD_PROTOCOL_BITMAP_BYTES;
memcpy(body.body.function_key_event.usage_bitmap.bytes, bitmap,
KEYBOARD_PROTOCOL_BITMAP_BYTES);
return encode_body(&body, payload, payload_buf_size, payload_len);
return encode_frame(&body, payload, payload_buf_size, payload_len);
}
static int do_init(void)
{
for (size_t i = 0; i < ARRAY_SIZE(session_state); i++) {
session_state[i] = PROTO_SESSION_DOWN;
for (size_t i = 0; i < ARRAY_SIZE(ctx.session_state); i++) {
ctx.session_state[i] = PROTO_SESSION_DOWN;
}
LOG_INF("Protocol init done");
@@ -258,7 +308,7 @@ static int do_stop(void)
return 0;
}
for (size_t i = 0; i < ARRAY_SIZE(session_state); i++) {
for (size_t i = 0; i < ARRAY_SIZE(ctx.session_state); i++) {
proto_session_set((enum proto_transport)i, PROTO_SESSION_DOWN,
"module_stop");
}
@@ -273,7 +323,9 @@ int protocol_module_process_message(enum proto_transport transport,
size_t rsp_payload_buf_size,
size_t *rsp_payload_len)
{
CdcPacketBody body;
DeviceMessage body;
const uint8_t *payload;
size_t payload_len;
int err;
if ((transport >= PROTO_TRANSPORT_COUNT) ||
@@ -288,17 +340,22 @@ int protocol_module_process_message(enum proto_transport transport,
return -EAGAIN;
}
err = decode_body(req_payload, req_payload_len, &body);
err = decode_frame(req_payload, req_payload_len, &payload, &payload_len);
if (err) {
return err;
}
err = decode_body(payload, payload_len, &body);
if (err) {
return err;
}
switch (body.which_body) {
case CdcPacketBody_hello_req_tag:
if (session_state[transport] == PROTO_SESSION_DOWN) {
case DeviceMessage_hello_req_tag:
if (ctx.session_state[transport] == PROTO_SESSION_DOWN) {
LOG_WRN("Reject HelloReq transport:%s session:%s lc:%s",
proto_transport_name(transport),
proto_session_name(session_state[transport]),
proto_session_name(ctx.session_state[transport]),
module_lifecycle_name(ctx.lc.state));
return -EAGAIN;
}
@@ -312,48 +369,47 @@ int protocol_module_process_message(enum proto_transport transport,
}
proto_session_set(transport, PROTO_SESSION_ACTIVE, "hello_req");
return encode_hello_rsp(rsp_payload, rsp_payload_buf_size, rsp_payload_len);
return encode_hello_rsp(body.msg_id, rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
case CdcPacketBody_bitmap_tag:
if (session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_error(CdcPacketBody_bitmap_tag,
ErrorCode_ERROR_CODE_NOT_READY,
case DeviceMessage_bitmap_tag:
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_NOT_READY,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
if (body.body.bitmap.usage_bitmap.size != KEYBOARD_PROTOCOL_BITMAP_BYTES) {
return encode_error(CdcPacketBody_bitmap_tag,
ErrorCode_ERROR_CODE_INVALID_LENGTH,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_LENGTH,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
err = submit_function_bitmap_update_event(
body.body.bitmap.usage_bitmap.bytes);
if (err) {
return encode_error(CdcPacketBody_bitmap_tag,
ErrorCode_ERROR_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
return encode_ack(CdcPacketBody_bitmap_tag, rsp_payload,
rsp_payload_buf_size, rsp_payload_len);
return encode_response(body.msg_id, ResponseCode_RESPONSE_CODE_OK,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
case CdcPacketBody_time_sync_tag:
if (session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_error(CdcPacketBody_time_sync_tag,
ErrorCode_ERROR_CODE_NOT_READY,
case DeviceMessage_time_sync_tag:
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_NOT_READY,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
if (body.body.time_sync.version != 1U) {
return encode_error(CdcPacketBody_time_sync_tag,
ErrorCode_ERROR_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
submit_time_sync_event(body.body.time_sync.version,
@@ -361,13 +417,14 @@ int protocol_module_process_message(enum proto_transport transport,
body.body.time_sync.timezone_min,
body.body.time_sync.utc_ms,
body.body.time_sync.accuracy_ms);
return encode_ack(CdcPacketBody_time_sync_tag, rsp_payload,
rsp_payload_buf_size, rsp_payload_len);
return encode_response(body.msg_id, ResponseCode_RESPONSE_CODE_OK,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
case CdcPacketBody_theme_rgb_tag:
if (session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_error(CdcPacketBody_theme_rgb_tag,
ErrorCode_ERROR_CODE_NOT_READY,
case DeviceMessage_theme_rgb_tag:
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_NOT_READY,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
@@ -375,10 +432,9 @@ int protocol_module_process_message(enum proto_transport transport,
if ((body.body.theme_rgb.red > 255U) ||
(body.body.theme_rgb.green > 255U) ||
(body.body.theme_rgb.blue > 255U)) {
return encode_error(CdcPacketBody_theme_rgb_tag,
ErrorCode_ERROR_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
return encode_response(
body.msg_id, ResponseCode_RESPONSE_CODE_INVALID_PARAM,
rsp_payload, rsp_payload_buf_size, rsp_payload_len);
}
submit_theme_rgb_update_event((struct theme_rgb) {
@@ -386,12 +442,16 @@ int protocol_module_process_message(enum proto_transport transport,
.g = (uint8_t)body.body.theme_rgb.green,
.b = (uint8_t)body.body.theme_rgb.blue,
});
return encode_ack(CdcPacketBody_theme_rgb_tag, rsp_payload,
rsp_payload_buf_size, rsp_payload_len);
return encode_response(body.msg_id, ResponseCode_RESPONSE_CODE_OK,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
default:
LOG_WRN("Unsupported protobuf body case %d", body.which_body);
return -ENOTSUP;
return encode_response(body.msg_id,
ResponseCode_RESPONSE_CODE_UNKNOWN_TYPE,
rsp_payload, rsp_payload_buf_size,
rsp_payload_len);
}
}
@@ -415,7 +475,7 @@ static bool handle_proto_rx_event(const struct proto_rx_event *event)
if (err != -ENOTSUP) {
LOG_WRN("Protocol processing failed (%d) transport:%s session:%s lc:%s len:%u",
err, proto_transport_name(event->transport),
proto_session_name(session_state[event->transport]),
proto_session_name(ctx.session_state[event->transport]),
module_lifecycle_name(ctx.lc.state),
(uint32_t)event->dyndata.size);
}
@@ -425,7 +485,7 @@ static bool handle_proto_rx_event(const struct proto_rx_event *event)
LOG_INF("Protocol response ready transport:%s rsp_len:%u session:%s",
proto_transport_name(event->transport), (unsigned int)rsp_payload_len,
proto_session_name(session_state[event->transport]));
proto_session_name(ctx.session_state[event->transport]));
err = submit_proto_tx_event(event->transport, rsp_payload, rsp_payload_len);
if (err) {
LOG_WRN("Proto TX submit failed (%d)", err);
@@ -472,10 +532,10 @@ static bool handle_function_bitmap_state_event(
for (enum proto_transport transport = 0; transport < PROTO_TRANSPORT_COUNT;
transport++) {
if (session_state[transport] != PROTO_SESSION_ACTIVE) {
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
LOG_INF("FunctionKeyEvent skip transport:%s session:%s",
proto_transport_name(transport),
proto_session_name(session_state[transport]));
proto_session_name(ctx.session_state[transport]));
continue;
}
@@ -514,10 +574,10 @@ static bool handle_hid_led_event(const struct hid_led_event *event)
PROTO_TRANSPORT_USB_CDC :
PROTO_TRANSPORT_BLE_NUS;
if (session_state[transport] != PROTO_SESSION_ACTIVE) {
if (ctx.session_state[transport] != PROTO_SESSION_ACTIVE) {
LOG_INF("LedState skip transport:%s session:%s led_mask:0x%02x",
proto_transport_name(transport),
proto_session_name(session_state[transport]), event->led_bm);
proto_session_name(ctx.session_state[transport]), event->led_bm);
return false;
}

View File

@@ -28,8 +28,7 @@ LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define USB_CDC_RX_RING_BUF_SIZE 256
#define USB_CDC_TX_RING_BUF_SIZE 256
#define USB_CDC_RX_CHUNK_SIZE 32
#define USB_CDC_PROTO_RX_BUF_SIZE 128
#define USB_CDC_PROTO_RX_FLUSH_DELAY K_MSEC(3)
#define USB_CDC_PROTO_RX_BUF_SIZE PROTO_MAX_FRAME_LEN
#define USB_CDC_EXPECTED_BAUDRATE 115200U
enum usb_cdc_business_state {
@@ -47,7 +46,6 @@ struct usb_cdc_ctx {
struct ring_buf rx_ringbuf;
struct ring_buf tx_ringbuf;
struct k_work rx_work;
struct k_work_delayable rx_flush_work;
uint8_t proto_rx_buf[USB_CDC_PROTO_RX_BUF_SIZE];
bool usb_active;
size_t proto_rx_len;
@@ -79,8 +77,6 @@ static struct usb_cdc_ctx ctx = {
.proto_rx_len = 0U,
};
#define proto_rx_buf ctx.proto_rx_buf
static void validate_line_coding(void);
static const char *usb_cdc_business_state_name(enum usb_cdc_business_state state)
@@ -137,7 +133,6 @@ static void disable_uart_io(void)
uart_irq_rx_disable(ctx.cdc_dev);
uart_irq_tx_disable(ctx.cdc_dev);
ctx.proto_rx_len = 0U;
k_work_cancel_delayable(&ctx.rx_flush_work);
reset_ring_buffers();
}
@@ -280,6 +275,46 @@ static void validate_line_coding(void)
#endif
}
static bool try_extract_frame(void)
{
uint8_t *buf = ctx.proto_rx_buf;
size_t frame_len;
uint16_t magic;
if (ctx.proto_rx_len < PROTO_FRAME_HEADER_SIZE) {
return false;
}
magic = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
if (magic != PROTO_FRAME_MAGIC) {
LOG_WRN("CDC invalid frame magic 0x%04x", magic);
memmove(buf, &buf[1], ctx.proto_rx_len - 1U);
ctx.proto_rx_len--;
return true;
}
if (buf[2] > PROTO_MAX_PAYLOAD_LEN) {
LOG_WRN("CDC invalid frame len:%u", buf[2]);
memmove(buf, &buf[1], ctx.proto_rx_len - 1U);
ctx.proto_rx_len--;
return true;
}
frame_len = PROTO_FRAME_HEADER_SIZE + buf[2];
if (ctx.proto_rx_len < frame_len) {
return false;
}
LOG_INF("CDC submit framed proto_rx len:%u", (unsigned int)frame_len);
(void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, buf, frame_len);
if (ctx.proto_rx_len > frame_len) {
memmove(buf, &buf[frame_len], ctx.proto_rx_len - frame_len);
}
ctx.proto_rx_len -= frame_len;
return true;
}
static void rx_work_handler(struct k_work *work)
{
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
@@ -294,43 +329,26 @@ static void rx_work_handler(struct k_work *work)
irq_unlock(key);
if (len == 0U) {
return;
break;
}
LOG_INF("CDC rx_work pulled %u bytes from RX ring (proto_rx_len:%u)",
(unsigned int)len, (unsigned int)ctx.proto_rx_len);
if ((ctx.proto_rx_len + len) > sizeof(proto_rx_buf)) {
LOG_WRN("Drop oversized CDC protobuf message len:%u",
if ((ctx.proto_rx_len + len) > sizeof(ctx.proto_rx_buf)) {
LOG_WRN("Drop oversized CDC framed data len:%u",
(uint32_t)(ctx.proto_rx_len + len));
ctx.proto_rx_len = 0U;
}
if (len > 0U) {
memcpy(&proto_rx_buf[ctx.proto_rx_len], buffer, len);
memcpy(&ctx.proto_rx_buf[ctx.proto_rx_len], buffer, len);
ctx.proto_rx_len += len;
k_work_reschedule(&ctx.rx_flush_work, USB_CDC_PROTO_RX_FLUSH_DELAY);
}
}
}
static void rx_flush_work_handler(struct k_work *work)
{
ARG_UNUSED(work);
if ((transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) ||
(ctx.proto_rx_len == 0U)) {
LOG_INF("CDC rx_flush skipped: link=%s proto_rx_len:%u business:%s",
proto_link_state_name(transport_link_state_get()),
(unsigned int)ctx.proto_rx_len,
usb_cdc_business_state_name(ctx.business));
return;
while (try_extract_frame()) {
}
LOG_INF("CDC rx_flush submit proto_rx len:%u", (unsigned int)ctx.proto_rx_len);
(void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, proto_rx_buf,
ctx.proto_rx_len);
ctx.proto_rx_len = 0U;
}
static void cdc_interrupt_handler(const struct device *dev, void *user_data)
@@ -408,7 +426,6 @@ static int do_init(void)
reset_ring_buffers();
k_work_init(&ctx.rx_work, rx_work_handler);
k_work_init_delayable(&ctx.rx_flush_work, rx_flush_work_handler);
uart_irq_callback_set(ctx.cdc_dev, cdc_interrupt_handler);
ctx.business = USB_CDC_BUS_OFFLINE;
ctx.usb_active = false;