#include #include #include #include #include #define MODULE usb_cdc_module #include #include #include #include #include #include #include #include #include "module_lifecycle.h" #include "proto_rx_event.h" #include "proto_transport_state_event.h" #include "proto_tx_event.h" #include "usb_control_event.h" #include "usb_state_event.h" 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_EXPECTED_BAUDRATE 115200U enum usb_cdc_business_state { USB_CDC_BUS_OFFLINE = 0, USB_CDC_WAIT_DTR, USB_CDC_SESSION_READY, }; struct usb_cdc_ctx { struct module_lifecycle_ctx lc; enum usb_cdc_business_state business; 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]; bool usb_active; size_t proto_rx_len; }; static int do_init(void); static int do_start(void); static int do_stop(void); 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, }; static struct usb_cdc_ctx ctx = { .lc = { .state = LC_UNINIT, .cfg = &lifecycle_cfg, .ops = &lifecycle_ops, }, .business = USB_CDC_BUS_OFFLINE, .cdc_dev = DEVICE_DT_GET_ONE(zephyr_cdc_acm_uart), .usb_active = false, .proto_rx_len = 0U, }; #define proto_rx_buf ctx.proto_rx_buf static void validate_line_coding(void); static bool lifecycle_is_ready(void) { return module_lifecycle_is_running(&ctx.lc); } 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; } static void reset_ring_buffers(void) { unsigned int key = irq_lock(); 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); irq_unlock(key); } 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(); } static void state_reconcile(enum module_lifecycle old_lifecycle, enum usb_cdc_business_state old_business) { enum proto_transport_link_state old_link = ((old_lifecycle == LC_RUNNING) && (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(); if ((old_lifecycle == LC_RUNNING) && (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(); err = uart_line_ctrl_set(ctx.cdc_dev, UART_LINE_CTRL_DCD, 1); if (err) { LOG_WRN("Failed to set DCD (%d)", err); } err = uart_line_ctrl_set(ctx.cdc_dev, UART_LINE_CTRL_DSR, 1); if (err) { LOG_WRN("Failed to set DSR (%d)", err); } uart_irq_rx_enable(ctx.cdc_dev); } 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) { enum module_lifecycle old_lifecycle = ctx.lc.state; 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); } } static void kick_tx(void) { if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) { return; } uart_irq_tx_enable(ctx.cdc_dev); } static void validate_line_coding(void) { uint32_t baudrate = 0U; int err; err = uart_line_ctrl_get(ctx.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(ctx.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(&ctx.rx_ringbuf, buffer, sizeof(buffer)); irq_unlock(key); if (len == 0U) { return; } if ((ctx.proto_rx_len + len) > sizeof(proto_rx_buf)) { LOG_WRN("Drop oversized CDC protobuf message 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); 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)) { return; } (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) { 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(&ctx.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(&ctx.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(&ctx.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 do_init(void) { if (!device_is_ready(ctx.cdc_dev)) { LOG_ERR("CDC ACM device not ready"); return -ENODEV; } 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; ctx.proto_rx_len = 0U; return 0; } static int do_start(void) { return 0; } static int do_stop(void) { 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; } 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 == ctx.usb_active) { return false; } ctx.usb_active = new_usb_active; business_state_sync_from_usb(); return false; } static bool handle_usb_control_event(const struct usb_control_event *event) { if (event->dev != ctx.cdc_dev) { return false; } 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; } } static bool handle_proto_tx_event(const struct proto_tx_event *event) { uint32_t written; unsigned int key; if (event->transport != PROTO_TRANSPORT_USB_CDC) { return false; } if (transport_link_state_get() != PROTO_TRANSPORT_LINK_READY) { return false; } key = irq_lock(); written = ring_buf_put(&ctx.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) { if (is_usb_state_event(aeh)) { return handle_usb_state_event(cast_usb_state_event(aeh)); } if (is_proto_tx_event(aeh)) { return handle_proto_tx_event(cast_proto_tx_event(aeh)); } if (is_usb_control_event(aeh)) { return handle_usb_control_event(cast_usb_control_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)) { (void)apply_lifecycle(LC_RUNNING); } return false; } if (is_power_down_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)apply_lifecycle(LC_STOPPED); } return false; } if (is_wake_up_event(aeh)) { if (module_lifecycle_is_initialized(&ctx.lc)) { (void)apply_lifecycle(LC_RUNNING); } return false; } return false; } APP_EVENT_LISTENER(MODULE, app_event_handler); APP_EVENT_SUBSCRIBE(MODULE, module_state_event); APP_EVENT_SUBSCRIBE(MODULE, proto_tx_event); APP_EVENT_SUBSCRIBE(MODULE, usb_control_event); APP_EVENT_SUBSCRIBE(MODULE, usb_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);