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-17 19:12:57 +08:00
|
|
|
#include "module_lifecycle.h"
|
2026-04-15 10:23:12 +08:00
|
|
|
#include "proto_rx_event.h"
|
2026-04-17 11:13:11 +08:00
|
|
|
#include "proto_transport_state_event.h"
|
2026-04-15 10:23:12 +08:00
|
|
|
#include "proto_tx_event.h"
|
2026-04-17 11:13:11 +08:00
|
|
|
#include "usb_control_event.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
|
|
|
|
|
#define USB_CDC_PROTO_RX_FLUSH_DELAY K_MSEC(3)
|
2026-04-11 17:15:11 +08:00
|
|
|
#define USB_CDC_EXPECTED_BAUDRATE 115200U
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
enum usb_cdc_business_state {
|
|
|
|
|
USB_CDC_BUS_OFFLINE = 0,
|
|
|
|
|
USB_CDC_WAIT_DTR,
|
|
|
|
|
USB_CDC_SESSION_READY,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct usb_cdc_ctx {
|
2026-04-17 19:12:57 +08:00
|
|
|
struct module_lifecycle_ctx lc;
|
2026-04-17 11:13:11 +08:00
|
|
|
enum usb_cdc_business_state business;
|
2026-04-17 19:12:57 +08:00
|
|
|
const struct device *cdc_dev;
|
|
|
|
|
uint8_t rx_ring_buffer[USB_CDC_RX_RING_BUF_SIZE];
|
|
|
|
|
uint8_t tx_ring_buffer[USB_CDC_TX_RING_BUF_SIZE];
|
|
|
|
|
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];
|
2026-04-17 11:13:11 +08:00
|
|
|
bool usb_active;
|
|
|
|
|
size_t proto_rx_len;
|
|
|
|
|
};
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_init(void);
|
|
|
|
|
static int do_start(void);
|
|
|
|
|
static int do_stop(void);
|
2026-04-17 11:13:11 +08:00
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static const struct module_lifecycle_cfg lifecycle_cfg = {
|
|
|
|
|
.mode = ML_MODE_POWER,
|
|
|
|
|
.stopped_state = MODULE_STATE_STANDBY,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct module_lifecycle_ops lifecycle_ops = {
|
|
|
|
|
.do_init = do_init,
|
|
|
|
|
.do_start = do_start,
|
|
|
|
|
.do_stop = do_stop,
|
|
|
|
|
};
|
2026-04-11 17:15:11 +08:00
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
static struct usb_cdc_ctx ctx = {
|
2026-04-17 19:12:57 +08:00
|
|
|
.lc = {
|
|
|
|
|
.state = LC_UNINIT,
|
|
|
|
|
.cfg = &lifecycle_cfg,
|
|
|
|
|
.ops = &lifecycle_ops,
|
|
|
|
|
},
|
2026-04-17 11:13:11 +08:00
|
|
|
.business = USB_CDC_BUS_OFFLINE,
|
2026-04-17 19:12:57 +08:00
|
|
|
.cdc_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart),
|
2026-04-17 11:13:11 +08:00
|
|
|
.usb_active = false,
|
|
|
|
|
.proto_rx_len = 0U,
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
#define proto_rx_buf ctx.proto_rx_buf
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
static void validate_line_coding(void);
|
|
|
|
|
|
|
|
|
|
static bool lifecycle_is_ready(void)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
return module_lifecycle_is_running(&ctx.lc);
|
2026-04-17 11:13:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static enum proto_transport_link_state transport_link_state_get(void)
|
|
|
|
|
{
|
|
|
|
|
return (lifecycle_is_ready() &&
|
|
|
|
|
(ctx.business == USB_CDC_SESSION_READY)) ?
|
|
|
|
|
PROTO_TRANSPORT_LINK_READY :
|
|
|
|
|
PROTO_TRANSPORT_LINK_DOWN;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 17:15:11 +08:00
|
|
|
static void reset_ring_buffers(void)
|
|
|
|
|
{
|
|
|
|
|
unsigned int key = irq_lock();
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
ring_buf_init(&ctx.rx_ringbuf, sizeof(ctx.rx_ring_buffer), ctx.rx_ring_buffer);
|
|
|
|
|
ring_buf_init(&ctx.tx_ringbuf, sizeof(ctx.tx_ring_buffer), ctx.tx_ring_buffer);
|
2026-04-11 17:15:11 +08:00
|
|
|
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void disable_uart_io(void)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
uart_irq_rx_disable(ctx.cdc_dev);
|
|
|
|
|
uart_irq_tx_disable(ctx.cdc_dev);
|
2026-04-17 11:13:11 +08:00
|
|
|
ctx.proto_rx_len = 0U;
|
2026-04-17 19:12:57 +08:00
|
|
|
k_work_cancel_delayable(&ctx.rx_flush_work);
|
2026-04-11 17:15:11 +08:00
|
|
|
reset_ring_buffers();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static void state_reconcile(enum module_lifecycle old_lifecycle,
|
2026-04-17 11:13:11 +08:00
|
|
|
enum usb_cdc_business_state old_business)
|
|
|
|
|
{
|
|
|
|
|
enum proto_transport_link_state old_link =
|
2026-04-17 19:12:57 +08:00
|
|
|
((old_lifecycle == LC_RUNNING) &&
|
2026-04-17 11:13:11 +08:00
|
|
|
(old_business == USB_CDC_SESSION_READY)) ?
|
|
|
|
|
PROTO_TRANSPORT_LINK_READY :
|
|
|
|
|
PROTO_TRANSPORT_LINK_DOWN;
|
|
|
|
|
enum proto_transport_link_state new_link = transport_link_state_get();
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
if ((old_lifecycle == LC_RUNNING) &&
|
2026-04-17 11:13:11 +08:00
|
|
|
(old_business == USB_CDC_SESSION_READY) &&
|
|
|
|
|
(new_link == PROTO_TRANSPORT_LINK_DOWN)) {
|
|
|
|
|
disable_uart_io();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((old_link == PROTO_TRANSPORT_LINK_DOWN) &&
|
|
|
|
|
(new_link == PROTO_TRANSPORT_LINK_READY)) {
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
validate_line_coding();
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
err = uart_line_ctrl_set(ctx.cdc_dev, UART_LINE_CTRL_DCD, 1);
|
2026-04-17 11:13:11 +08:00
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("Failed to set DCD (%d)", err);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
err = uart_line_ctrl_set(ctx.cdc_dev, UART_LINE_CTRL_DSR, 1);
|
2026-04-17 11:13:11 +08:00
|
|
|
if (err) {
|
|
|
|
|
LOG_WRN("Failed to set DSR (%d)", err);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
uart_irq_rx_enable(ctx.cdc_dev);
|
2026-04-17 11:13:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (old_link != new_link) {
|
|
|
|
|
submit_proto_transport_state_event(PROTO_TRANSPORT_USB_CDC,
|
|
|
|
|
new_link);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void business_state_set(enum usb_cdc_business_state new_state)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
enum module_lifecycle old_lifecycle = ctx.lc.state;
|
2026-04-17 11:13:11 +08:00
|
|
|
enum usb_cdc_business_state old_business = ctx.business;
|
|
|
|
|
|
|
|
|
|
if (ctx.business == new_state) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx.business = new_state;
|
|
|
|
|
state_reconcile(old_lifecycle, old_business);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void business_state_sync_from_usb(void)
|
|
|
|
|
{
|
|
|
|
|
if (!ctx.usb_active) {
|
|
|
|
|
business_state_set(USB_CDC_BUS_OFFLINE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!lifecycle_is_ready()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ctx.business == USB_CDC_BUS_OFFLINE) {
|
|
|
|
|
business_state_set(USB_CDC_WAIT_DTR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 17:15:11 +08:00
|
|
|
static void kick_tx(void)
|
|
|
|
|
{
|
2026-04-17 11:13:11 +08:00
|
|
|
if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) {
|
2026-04-11 17:15:11 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
uart_irq_tx_enable(ctx.cdc_dev);
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void validate_line_coding(void)
|
|
|
|
|
{
|
|
|
|
|
uint32_t baudrate = 0U;
|
|
|
|
|
int err;
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
err = uart_line_ctrl_get(ctx.cdc_dev, UART_LINE_CTRL_BAUD_RATE, &baudrate);
|
2026-04-11 17:15:11 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
err = uart_config_get(ctx.cdc_dev, &cfg);
|
2026-04-11 17:15:11 +08:00
|
|
|
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();
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
len = ring_buf_get(&ctx.rx_ringbuf, buffer, sizeof(buffer));
|
2026-04-11 17:15:11 +08:00
|
|
|
irq_unlock(key);
|
|
|
|
|
|
|
|
|
|
if (len == 0U) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
if ((ctx.proto_rx_len + len) > sizeof(proto_rx_buf)) {
|
2026-04-15 10:23:12 +08:00
|
|
|
LOG_WRN("Drop oversized CDC protobuf message len:%u",
|
2026-04-17 11:13:11 +08:00
|
|
|
(uint32_t)(ctx.proto_rx_len + len));
|
|
|
|
|
ctx.proto_rx_len = 0U;
|
2026-04-15 10:23:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (len > 0U) {
|
2026-04-17 11:13:11 +08:00
|
|
|
memcpy(&proto_rx_buf[ctx.proto_rx_len], buffer, len);
|
|
|
|
|
ctx.proto_rx_len += len;
|
2026-04-17 19:12:57 +08:00
|
|
|
k_work_reschedule(&ctx.rx_flush_work, USB_CDC_PROTO_RX_FLUSH_DELAY);
|
2026-04-15 10:23:12 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void rx_flush_work_handler(struct k_work *work)
|
|
|
|
|
{
|
|
|
|
|
ARG_UNUSED(work);
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
if ((transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) ||
|
|
|
|
|
(ctx.proto_rx_len == 0U)) {
|
2026-04-11 17:15:11 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
(void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, proto_rx_buf,
|
|
|
|
|
ctx.proto_rx_len);
|
|
|
|
|
ctx.proto_rx_len = 0U;
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
written = ring_buf_put(&ctx.rx_ringbuf, buffer,
|
2026-04-11 17:15:11 +08:00
|
|
|
(uint32_t)recv_len);
|
|
|
|
|
irq_unlock(key);
|
|
|
|
|
|
|
|
|
|
if (written < (uint32_t)recv_len) {
|
2026-04-17 11:13:11 +08:00
|
|
|
LOG_WRN("Drop %d CDC RX bytes",
|
|
|
|
|
recv_len - (int)written);
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
k_work_submit(&ctx.rx_work);
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
len = ring_buf_get(&ctx.tx_ringbuf, buffer, sizeof(buffer));
|
2026-04-11 17:15:11 +08:00
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_init(void)
|
2026-04-11 17:15:11 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (!device_is_ready(ctx.cdc_dev)) {
|
2026-04-11 17:15:11 +08:00
|
|
|
LOG_ERR("CDC ACM device not ready");
|
|
|
|
|
return -ENODEV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reset_ring_buffers();
|
2026-04-17 19:12:57 +08:00
|
|
|
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;
|
|
|
|
|
ctx.proto_rx_len = 0U;
|
2026-04-17 11:13:11 +08:00
|
|
|
|
2026-04-11 17:15:11 +08:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_start(void)
|
2026-04-11 17:15:11 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
return 0;
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 19:12:57 +08:00
|
|
|
static int do_stop(void)
|
2026-04-11 17:15:11 +08:00
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
ctx.business = USB_CDC_BUS_OFFLINE;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int apply_lifecycle(enum module_lifecycle target)
|
|
|
|
|
{
|
|
|
|
|
enum module_lifecycle old_lifecycle = ctx.lc.state;
|
|
|
|
|
enum usb_cdc_business_state old_business = ctx.business;
|
|
|
|
|
int err = module_set_lifecycle(&ctx.lc, target);
|
|
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state_reconcile(old_lifecycle, old_business);
|
|
|
|
|
|
|
|
|
|
if (target == LC_RUNNING) {
|
|
|
|
|
business_state_sync_from_usb();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
if (new_usb_active == ctx.usb_active) {
|
2026-04-11 17:15:11 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
ctx.usb_active = new_usb_active;
|
|
|
|
|
business_state_sync_from_usb();
|
2026-04-11 17:15:11 +08:00
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool handle_usb_control_event(const struct usb_control_event *event)
|
|
|
|
|
{
|
2026-04-17 19:12:57 +08:00
|
|
|
if (event->dev != ctx.cdc_dev) {
|
2026-04-17 11:13:11 +08:00
|
|
|
return false;
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
switch (event->type) {
|
|
|
|
|
case USB_CONTROL_EVENT_CDC_LINE_STATE:
|
|
|
|
|
if (!ctx.usb_active || !lifecycle_is_ready()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event->data.cdc_line_state.dtr) {
|
|
|
|
|
LOG_INF("CDC DTR set");
|
|
|
|
|
business_state_set(USB_CDC_SESSION_READY);
|
|
|
|
|
kick_tx();
|
|
|
|
|
} else {
|
|
|
|
|
LOG_INF("CDC DTR cleared");
|
|
|
|
|
business_state_set(USB_CDC_WAIT_DTR);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
case USB_CONTROL_EVENT_CDC_LINE_CODING:
|
|
|
|
|
if (event->data.cdc_line_coding.baudrate != 0U) {
|
|
|
|
|
LOG_INF("CDC baudrate %u",
|
|
|
|
|
event->data.cdc_line_coding.baudrate);
|
|
|
|
|
if (event->data.cdc_line_coding.baudrate !=
|
|
|
|
|
USB_CDC_EXPECTED_BAUDRATE) {
|
|
|
|
|
LOG_WRN("Expected CDC baudrate %u, got %u",
|
|
|
|
|
USB_CDC_EXPECTED_BAUDRATE,
|
|
|
|
|
event->data.cdc_line_coding.baudrate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE
|
|
|
|
|
if ((event->data.cdc_line_coding.data_bits != 0U) ||
|
|
|
|
|
(event->data.cdc_line_coding.stop_bits != 0U) ||
|
|
|
|
|
(event->data.cdc_line_coding.parity != 0U) ||
|
|
|
|
|
(event->data.cdc_line_coding.flow_ctrl != 0U)) {
|
|
|
|
|
LOG_INF("CDC line coding data:%u stop:%u parity:%u flow:%u",
|
|
|
|
|
event->data.cdc_line_coding.data_bits,
|
|
|
|
|
event->data.cdc_line_coding.stop_bits,
|
|
|
|
|
event->data.cdc_line_coding.parity,
|
|
|
|
|
event->data.cdc_line_coding.flow_ctrl);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
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-17 11:13:11 +08:00
|
|
|
if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) {
|
2026-04-11 17:15:11 +08:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key = irq_lock();
|
2026-04-17 19:12:57 +08:00
|
|
|
written = ring_buf_put(&ctx.tx_ringbuf, event->dyndata.data,
|
2026-04-11 17:15:11 +08:00
|
|
|
(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
|
|
|
}
|
|
|
|
|
|
2026-04-17 11:13:11 +08:00
|
|
|
if (is_usb_control_event(aeh)) {
|
|
|
|
|
return handle_usb_control_event(cast_usb_control_event(aeh));
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 17:15:11 +08:00
|
|
|
if (is_module_state_event(aeh)) {
|
2026-04-17 11:13:11 +08:00
|
|
|
const struct module_state_event *event =
|
|
|
|
|
cast_module_state_event(aeh);
|
2026-04-11 17:15:11 +08:00
|
|
|
|
|
|
|
|
if (check_state(event, MODULE_ID(main), MODULE_STATE_READY)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
(void)apply_lifecycle(LC_RUNNING);
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_power_down_event(aeh)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
|
|
|
|
(void)apply_lifecycle(LC_STOPPED);
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (is_wake_up_event(aeh)) {
|
2026-04-17 19:12:57 +08:00
|
|
|
if (module_lifecycle_is_initialized(&ctx.lc)) {
|
|
|
|
|
(void)apply_lifecycle(LC_RUNNING);
|
2026-04-11 17:15:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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-17 11:13:11 +08:00
|
|
|
APP_EVENT_SUBSCRIBE(MODULE, usb_control_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);
|