2026-04-11 17:15:11 +08:00
|
|
|
#include <errno.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include <app_event_manager.h>
|
|
|
|
|
|
|
|
|
|
#define MODULE usb_cdc_module
|
|
|
|
|
#include <caf/events/module_state_event.h>
|
|
|
|
|
#include <caf/events/power_event.h>
|
|
|
|
|
|
|
|
|
|
#include <zephyr/device.h>
|
|
|
|
|
#include <zephyr/drivers/uart.h>
|
|
|
|
|
#include <zephyr/kernel.h>
|
|
|
|
|
#include <zephyr/logging/log.h>
|
|
|
|
|
#include <zephyr/sys/ring_buffer.h>
|
|
|
|
|
#include <zephyr/sys/util.h>
|
|
|
|
|
|
2026-04-15 10:23:12 +08:00
|
|
|
#include "proto_rx_event.h"
|
|
|
|
|
#include "proto_tx_event.h"
|
|
|
|
|
#include "protocol_module.h"
|
2026-04-15 09:30:40 +08:00
|
|
|
#include "usb_state_event.h"
|
2026-04-11 17:15:11 +08:00
|
|
|
|
|
|
|
|
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
|
2026-04-15 10:23:12 +08:00
|
|
|
#define USB_CDC_PROTO_RX_BUF_SIZE 128
|
2026-04-11 17:15:11 +08:00
|
|
|
#define USB_CDC_CONTROL_POLL_INTERVAL K_MSEC(100)
|
2026-04-15 10:23:12 +08:00
|
|
|
#define USB_CDC_PROTO_RX_FLUSH_DELAY K_MSEC(3)
|
2026-04-11 17:15:11 +08:00
|
|
|
#define USB_CDC_EXPECTED_BAUDRATE 115200U
|
|
|
|
|
|
|
|
|
|
static const struct device *const cdc_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart);
|
|
|
|
|
|
|
|
|
|
static uint8_t rx_ring_buffer[USB_CDC_RX_RING_BUF_SIZE];
|
|
|
|
|
static uint8_t tx_ring_buffer[USB_CDC_TX_RING_BUF_SIZE];
|
|
|
|
|
static struct ring_buf rx_ringbuf;
|
|
|
|
|
static struct ring_buf tx_ringbuf;
|
|
|
|
|
static struct k_work rx_work;
|
|
|
|
|
static struct k_work_delayable control_work;
|
2026-04-15 10:23:12 +08:00
|
|
|
static struct k_work_delayable rx_flush_work;
|
2026-04-11 17:15:11 +08:00
|
|
|
static bool initialized;
|
|
|
|
|
static bool running;
|
|
|
|
|
static bool usb_active;
|
|
|
|
|
static bool dtr_ready;
|
|
|
|
|
static bool rx_enabled;
|
2026-04-15 10:23:12 +08:00
|
|
|
static uint8_t proto_rx_buf[USB_CDC_PROTO_RX_BUF_SIZE];
|
|
|
|
|
static size_t proto_rx_len;
|
2026-04-11 17:15:11 +08:00
|
|
|
|
|
|
|
|
static void reset_ring_buffers(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
|
|
|
|
|
|
ring_buf_init(&rx_ringbuf, sizeof(rx_ring_buffer), rx_ring_buffer);
|
|
|
|
|
ring_buf_init(&tx_ringbuf, sizeof(tx_ring_buffer), tx_ring_buffer);
|
|
|
|
|
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void disable_uart_io(void)
|
|
|
|
|
{
|
|
|
|
|
uart_irq_rx_disable(cdc_dev);
|
|
|
|
|
uart_irq_tx_disable(cdc_dev);
|
|
|
|
|
rx_enabled = false;
|
|
|
|
|
dtr_ready = false;
|
2026-04-15 10:23:12 +08:00
|
|
|
proto_rx_len = 0U;
|
|
|
|
|
k_work_cancel_delayable(&rx_flush_work);
|
2026-04-11 17:15:11 +08:00
|
|
|
reset_ring_buffers();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void kick_tx(void)
|
|
|
|
|
{
|
|
|
|
|
if (!running || !usb_active || !dtr_ready) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uart_irq_tx_enable(cdc_dev);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void validate_line_coding(void)
|
|
|
|
|
{
|
|
|
|
|
uint32_t baudrate = 0U;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = uart_line_ctrl_get(cdc_dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("Failed to get CDC baudrate (%d)", err);
|
|
|
|
|
} else {
|
|
|
|
|
LOG_INF("CDC baudrate %u", baudrate);
|
|
|
|
|
if (baudrate != USB_CDC_EXPECTED_BAUDRATE) {
|
|
|
|
|
LOG_WRN("Expected CDC baudrate %u, got %u",
|
|
|
|
|
USB_CDC_EXPECTED_BAUDRATE, baudrate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
|
|
|
|
|
{
|
|
|
|
|
struct uart_config cfg;
|
|
|
|
|
|
|
|
|
|
err = uart_config_get(cdc_dev, &cfg);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("uart_config_get failed (%d)", err);
|
|
|
|
|
} else {
|
|
|
|
|
LOG_INF("CDC line coding data:%u stop:%u parity:%u flow:%u",
|
|
|
|
|
cfg.data_bits, cfg.stop_bits, cfg.parity,
|
|
|
|
|
cfg.flow_ctrl);
|
|
|
|
|
if ((cfg.data_bits != UART_CFG_DATA_BITS_8) ||
|
|
|
|
|
(cfg.stop_bits != UART_CFG_STOP_BITS_1) ||
|
|
|
|
|
(cfg.parity != UART_CFG_PARITY_NONE) ||
|
|
|
|
|
(cfg.flow_ctrl != UART_CFG_FLOW_CTRL_NONE)) {
|
|
|
|
|
LOG_WRN("Expected CDC line coding 115200 8N1 no flow control");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void rx_work_handler(struct k_work *work)
|
|
|
|
|
{
|
|
|
|
|
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
|
|
|
|
|
|
|
|
|
|
ARG_UNUSED(work);
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
uint32_t len;
|
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
|
|
|
|
|
|
len = ring_buf_get(&rx_ringbuf, buffer, sizeof(buffer));
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
|
|
|
|
|
if (len == 0U) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 10:23:12 +08:00
|
|
|
if ((proto_rx_len + len) > sizeof(proto_rx_buf)) {
|
|
|
|
|
LOG_WRN("Drop oversized CDC protobuf message len:%u",
|
|
|
|
|
(uint32_t)(proto_rx_len + len));
|
|
|
|
|
proto_rx_len = 0U;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (len > 0U) {
|
|
|
|
|
memcpy(&proto_rx_buf[proto_rx_len], buffer, len);
|
|
|
|
|
proto_rx_len += len;
|
|
|
|
|
k_work_reschedule(&rx_flush_work, USB_CDC_PROTO_RX_FLUSH_DELAY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void rx_flush_work_handler(struct k_work *work)
|
|
|
|
|
{
|
|
|
|
|
ARG_UNUSED(work);
|
|
|
|
|
|
|
|
|
|
if (!running || !usb_active || !dtr_ready || (proto_rx_len == 0U)) {
|
|
|
|
|
return;
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
2026-04-15 10:23:12 +08:00
|
|
|
|
|
|
|
|
(void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, proto_rx_buf, proto_rx_len);
|
|
|
|
|
proto_rx_len = 0U;
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void control_work_handler(struct k_work *work)
|
|
|
|
|
{
|
|
|
|
|
uint32_t dtr = 0U;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
ARG_UNUSED(work);
|
|
|
|
|
|
|
|
|
|
if (!running || !usb_active) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = uart_line_ctrl_get(cdc_dev, UART_LINE_CTRL_DTR, &dtr);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("Failed to get CDC DTR (%d)", err);
|
|
|
|
|
goto reschedule;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (dtr && !dtr_ready) {
|
|
|
|
|
dtr_ready = true;
|
|
|
|
|
LOG_INF("CDC DTR set");
|
|
|
|
|
validate_line_coding();
|
|
|
|
|
|
|
|
|
|
err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DCD, 1);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("Failed to set DCD (%d)", err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = uart_line_ctrl_set(cdc_dev, UART_LINE_CTRL_DSR, 1);
|
|
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("Failed to set DSR (%d)", err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!rx_enabled) {
|
|
|
|
|
uart_irq_rx_enable(cdc_dev);
|
|
|
|
|
rx_enabled = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kick_tx();
|
|
|
|
|
} else if (!dtr && dtr_ready) {
|
|
|
|
|
LOG_INF("CDC DTR cleared");
|
|
|
|
|
disable_uart_io();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reschedule:
|
|
|
|
|
k_work_reschedule(&control_work, USB_CDC_CONTROL_POLL_INTERVAL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cdc_interrupt_handler(const struct device *dev, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
ARG_UNUSED(user_data);
|
|
|
|
|
|
|
|
|
|
while (uart_irq_update(dev) && uart_irq_is_pending(dev)) {
|
|
|
|
|
if (uart_irq_rx_ready(dev)) {
|
|
|
|
|
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
|
|
|
|
|
int recv_len = uart_fifo_read(dev, buffer, sizeof(buffer));
|
|
|
|
|
|
|
|
|
|
if (recv_len < 0) {
|
|
|
|
|
LOG_ERR("Failed to read CDC RX FIFO");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (recv_len > 0) {
|
|
|
|
|
uint32_t written;
|
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
|
|
|
|
|
|
written = ring_buf_put(&rx_ringbuf, buffer,
|
|
|
|
|
(uint32_t)recv_len);
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
|
|
|
|
|
if (written < (uint32_t)recv_len) {
|
|
|
|
|
LOG_WRN("Drop %d CDC RX bytes", recv_len - (int)written);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k_work_submit(&rx_work);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (uart_irq_tx_ready(dev)) {
|
|
|
|
|
uint8_t buffer[USB_CDC_RX_CHUNK_SIZE];
|
|
|
|
|
uint32_t len;
|
|
|
|
|
int sent_len;
|
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
|
|
|
|
|
|
len = ring_buf_get(&tx_ringbuf, buffer, sizeof(buffer));
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
|
|
|
|
|
if (len == 0U) {
|
|
|
|
|
uart_irq_tx_disable(dev);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sent_len = uart_fifo_fill(dev, buffer, len);
|
|
|
|
|
if (sent_len < 0) {
|
|
|
|
|
LOG_ERR("Failed to write CDC TX FIFO");
|
|
|
|
|
uart_irq_tx_disable(dev);
|
|
|
|
|
} else if ((uint32_t)sent_len < len) {
|
|
|
|
|
LOG_WRN("Drop %u CDC TX bytes",
|
|
|
|
|
(unsigned int)(len - (uint32_t)sent_len));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int module_init(void)
|
|
|
|
|
{
|
|
|
|
|
if (!device_is_ready(cdc_dev)) {
|
|
|
|
|
LOG_ERR("CDC ACM device not ready");
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reset_ring_buffers();
|
|
|
|
|
k_work_init(&rx_work, rx_work_handler);
|
|
|
|
|
k_work_init_delayable(&control_work, control_work_handler);
|
2026-04-15 10:23:12 +08:00
|
|
|
k_work_init_delayable(&rx_flush_work, rx_flush_work_handler);
|
2026-04-11 17:15:11 +08:00
|
|
|
uart_irq_callback_set(cdc_dev, cdc_interrupt_handler);
|
2026-04-15 10:23:12 +08:00
|
|
|
proto_rx_len = 0U;
|
2026-04-11 17:15:11 +08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int module_start(void)
|
|
|
|
|
{
|
|
|
|
|
if (running) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
running = true;
|
|
|
|
|
if (usb_active) {
|
|
|
|
|
k_work_reschedule(&control_work, K_NO_WAIT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void module_pause(void)
|
|
|
|
|
{
|
|
|
|
|
if (!running) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
k_work_cancel_delayable(&control_work);
|
2026-04-15 10:23:12 +08:00
|
|
|
k_work_cancel_delayable(&rx_flush_work);
|
2026-04-11 17:15:11 +08:00
|
|
|
disable_uart_io();
|
|
|
|
|
running = false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 09:30:40 +08:00
|
|
|
static bool handle_usb_state_event(const struct usb_state_event *event)
|
2026-04-11 17:15:11 +08:00
|
|
|
{
|
2026-04-15 15:13:44 +08:00
|
|
|
bool new_usb_active = (event->state == USB_STATE_ACTIVE);
|
2026-04-11 17:15:11 +08:00
|
|
|
|
|
|
|
|
if (new_usb_active == usb_active) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
usb_active = new_usb_active;
|
|
|
|
|
|
|
|
|
|
if (!usb_active) {
|
|
|
|
|
k_work_cancel_delayable(&control_work);
|
2026-04-15 10:23:12 +08:00
|
|
|
k_work_cancel_delayable(&rx_flush_work);
|
|
|
|
|
protocol_module_reset_transport_state(PROTO_TRANSPORT_USB_CDC);
|
2026-04-11 17:15:11 +08:00
|
|
|
disable_uart_io();
|
|
|
|
|
} else if (running) {
|
|
|
|
|
k_work_reschedule(&control_work, K_NO_WAIT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 10:23:12 +08:00
|
|
|
static bool handle_proto_tx_event(const struct proto_tx_event *event)
|
2026-04-11 17:15:11 +08:00
|
|
|
{
|
|
|
|
|
uint32_t written;
|
|
|
|
|
unsigned int key;
|
|
|
|
|
|
2026-04-15 10:23:12 +08:00
|
|
|
if (event->transport != PROTO_TRANSPORT_USB_CDC) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 17:15:11 +08:00
|
|
|
if (!running || !usb_active || !dtr_ready) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key = irq_lock();
|
|
|
|
|
written = ring_buf_put(&tx_ringbuf, event->dyndata.data,
|
|
|
|
|
(uint32_t)event->dyndata.size);
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
|
|
|
|
|
if (written < event->dyndata.size) {
|
|
|
|
|
LOG_WRN("Drop %zu CDC TX bytes", event->dyndata.size - written);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (written > 0U) {
|
|
|
|
|
kick_tx();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool app_event_handler(const struct app_event_header *aeh)
|
|
|
|
|
{
|
2026-04-15 09:30:40 +08:00
|
|
|
if (is_usb_state_event(aeh)) {
|
|
|
|
|
return handle_usb_state_event(cast_usb_state_event(aeh));
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-15 10:23:12 +08:00
|
|
|
if (is_proto_tx_event(aeh)) {
|
|
|
|
|
return handle_proto_tx_event(cast_proto_tx_event(aeh));
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2026-04-15 10:23:12 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, proto_tx_event);
|
2026-04-15 09:30:40 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, usb_state_event);
|
2026-04-11 17:15:11 +08:00
|
|
|
APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event);
|
|
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);
|