#include #include #include #include #include #define MODULE usb_cdc_module #include #include #include #include #include #include #include #include #include "proto_rx_event.h" #include "proto_tx_event.h" #include "protocol_module.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_CONTROL_POLL_INTERVAL K_MSEC(100) #define USB_CDC_PROTO_RX_FLUSH_DELAY K_MSEC(3) #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; static struct k_work_delayable rx_flush_work; static bool initialized; static bool running; static bool usb_active; static bool dtr_ready; static bool rx_enabled; static uint8_t proto_rx_buf[USB_CDC_PROTO_RX_BUF_SIZE]; static size_t proto_rx_len; 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; proto_rx_len = 0U; k_work_cancel_delayable(&rx_flush_work); 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; } 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; } (void)submit_proto_rx_event(PROTO_TRANSPORT_USB_CDC, proto_rx_buf, proto_rx_len); proto_rx_len = 0U; } 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); k_work_init_delayable(&rx_flush_work, rx_flush_work_handler); uart_irq_callback_set(cdc_dev, cdc_interrupt_handler); proto_rx_len = 0U; 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); k_work_cancel_delayable(&rx_flush_work); disable_uart_io(); running = false; } 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 == usb_active) { return false; } usb_active = new_usb_active; if (!usb_active) { k_work_cancel_delayable(&control_work); k_work_cancel_delayable(&rx_flush_work); protocol_module_reset_transport_state(PROTO_TRANSPORT_USB_CDC); disable_uart_io(); } else if (running) { k_work_reschedule(&control_work, K_NO_WAIT); } 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 (!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) { 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_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); APP_EVENT_SUBSCRIBE(MODULE, proto_tx_event); APP_EVENT_SUBSCRIBE(MODULE, usb_state_event); APP_EVENT_SUBSCRIBE_EARLY(MODULE, power_down_event); APP_EVENT_SUBSCRIBE(MODULE, wake_up_event);