diff --git a/docs/firmware_proto_transport.md b/docs/firmware_proto_transport.md index 1d94684..b45ff96 100644 --- a/docs/firmware_proto_transport.md +++ b/docs/firmware_proto_transport.md @@ -92,5 +92,30 @@ Implemented behavior: ## Planned next nodes -- CDC transport module - GATT transport module + +### Node 4: CDC transport module + +Files added in this step: + +- `src/modules/usb_cdc_proto_module.c` + +Design notes: + +- use the CDC ACM UART abstraction from Zephyr +- keep CDC transport separate from protocol details by delegating encode/decode + to `keyboard_proto` +- report private outgoing events back to host over CDC + +Implemented behavior: + +- receive raw CDC bytes by UART IRQ +- accumulate bytes into a stream buffer +- extract protobuf `CdcFrame` +- decode host messages and dispatch them +- send: + - `HelloRsp` + - `Ack` + - `Error` + - `FunctionKeyEvent` + - `LedState` diff --git a/src/modules/usb_cdc_proto_module.c b/src/modules/usb_cdc_proto_module.c new file mode 100644 index 0000000..4042511 --- /dev/null +++ b/src/modules/usb_cdc_proto_module.c @@ -0,0 +1,220 @@ +#include + +#include +#include +#include + +#include + +#define MODULE usb_cdc_proto +#include + +#include "function_key_event.h" +#include "keyboard_led_event.h" +#include "keyboard_proto.h" + +#include +LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF); + +#define CDC_RX_BUFFER_SIZE 256 +#define CDC_TX_BUFFER_SIZE KEYBOARD_PROTO_MAX_FRAME_SIZE + +struct usb_cdc_proto_ctx { + const struct device *dev; + struct k_work rx_work; + uint8_t rx_buffer[CDC_RX_BUFFER_SIZE]; + size_t rx_len; + bool ready; +}; + +static struct usb_cdc_proto_ctx g_usb_cdc = { + .dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart), +}; + +static uint32_t keyboard_proto_packet_type_from_body( + const struct keyboard_cdc_CdcPacketBody *body) +{ + switch (body->which_body) { + case 1U: + return 0x01U; + case 2U: + return 0x02U; + case 3U: + return 0x10U; + case 4U: + return 0x20U; + case 5U: + return 0x21U; + case 6U: + return 0x30U; + case 7U: + return 0x31U; + case 8U: + return 0x7EU; + case 9U: + return 0x7FU; + default: + return 0U; + } +} + +static bool usb_cdc_proto_send_body( + const struct keyboard_cdc_CdcPacketBody *body, + void *user_data) +{ + struct usb_cdc_proto_ctx *ctx = user_data; + uint8_t tx_buffer[CDC_TX_BUFFER_SIZE]; + size_t tx_size = 0U; + + if ((ctx == NULL) || !ctx->ready || (ctx->dev == NULL)) { + return false; + } + + if (!keyboard_proto_encode_cdc_frame( + keyboard_proto_packet_type_from_body(body), + body, + tx_buffer, + sizeof(tx_buffer), + &tx_size)) { + return false; + } + + for (size_t i = 0; i < tx_size; ++i) { + uart_poll_out(ctx->dev, tx_buffer[i]); + } + + return true; +} + +static void usb_cdc_proto_process_rx_work(struct k_work *work) +{ + struct keyboard_cdc_CdcFrame frame; + struct keyboard_cdc_CdcPacketBody body; + + ARG_UNUSED(work); + + while (keyboard_proto_try_take_cdc_frame( + g_usb_cdc.rx_buffer, + &g_usb_cdc.rx_len, + &frame)) { + if (!keyboard_proto_decode_body(frame.payload.bytes, + frame.payload.size, + &body)) { + LOG_WRN("Drop invalid CDC body"); + continue; + } + + (void)keyboard_proto_handle_host_body( + &body, + KEYBOARD_PROTO_TRANSPORT_CDC, + usb_cdc_proto_send_body, + &g_usb_cdc); + } +} + +static void usb_cdc_proto_irq_handler(const struct device *dev, void *user_data) +{ + uint8_t buffer[64]; + + ARG_UNUSED(user_data); + + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + if (!uart_irq_rx_ready(dev)) { + continue; + } + + int recv_len = uart_fifo_read(dev, buffer, sizeof(buffer)); + if (recv_len <= 0) { + continue; + } + + if ((g_usb_cdc.rx_len + (size_t)recv_len) > sizeof(g_usb_cdc.rx_buffer)) { + LOG_WRN("CDC RX overflow, drop buffer"); + g_usb_cdc.rx_len = 0U; + continue; + } + + memcpy(&g_usb_cdc.rx_buffer[g_usb_cdc.rx_len], buffer, recv_len); + g_usb_cdc.rx_len += (size_t)recv_len; + k_work_submit(&g_usb_cdc.rx_work); + } +} + +static bool handle_module_state_event(const struct module_state_event *event) +{ + if (!check_state(event, MODULE_ID(main), MODULE_STATE_READY)) { + return false; + } + + if (!device_is_ready(g_usb_cdc.dev)) { + LOG_ERR("CDC ACM device not ready"); + module_set_state(MODULE_STATE_ERROR); + return false; + } + + k_work_init(&g_usb_cdc.rx_work, usb_cdc_proto_process_rx_work); + uart_irq_callback_set(g_usb_cdc.dev, usb_cdc_proto_irq_handler); + uart_irq_rx_enable(g_usb_cdc.dev); + g_usb_cdc.ready = true; + module_set_state(MODULE_STATE_READY); + return false; +} + +static bool handle_function_key_event(const struct function_key_event *event) +{ + struct keyboard_cdc_CdcPacketBody body; + + if (!g_usb_cdc.ready) { + return false; + } + + if (!keyboard_proto_build_function_key_event_body(event->usage, + event->pressed, + &body)) { + return false; + } + + (void)usb_cdc_proto_send_body(&body, &g_usb_cdc); + return false; +} + +static bool handle_keyboard_led_event(const struct keyboard_led_event *event) +{ + struct keyboard_cdc_CdcPacketBody body; + + if (!g_usb_cdc.ready) { + return false; + } + + if (!keyboard_proto_build_led_state_body( + keyboard_led_event_get_mask(event), + &body)) { + return false; + } + + (void)usb_cdc_proto_send_body(&body, &g_usb_cdc); + return false; +} + +static bool app_event_handler(const struct app_event_header *aeh) +{ + if (is_module_state_event(aeh)) { + return handle_module_state_event(cast_module_state_event(aeh)); + } + + if (is_function_key_event(aeh)) { + return handle_function_key_event(cast_function_key_event(aeh)); + } + + if (is_keyboard_led_event(aeh)) { + return handle_keyboard_led_event(cast_keyboard_led_event(aeh)); + } + + __ASSERT_NO_MSG(false); + return false; +} + +APP_EVENT_LISTENER(MODULE, app_event_handler); +APP_EVENT_SUBSCRIBE(MODULE, module_state_event); +APP_EVENT_SUBSCRIBE(MODULE, function_key_event); +APP_EVENT_SUBSCRIBE(MODULE, keyboard_led_event);