/* * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(usbd_cdc_acm, CONFIG_USBD_CDC_ACM_LOG_LEVEL); /* * FIXME: buffer count per device. */ NET_BUF_POOL_FIXED_DEFINE(cdc_acm_ep_pool, 2, 512, sizeof(struct udc_buf_info), NULL); #define CDC_ACM_DEFAULT_LINECODING {sys_cpu_to_le32(115200), 0, 0, 8} #define CDC_ACM_DEFAULT_BULK_EP_MPS 0 #define CDC_ACM_DEFAULT_INT_EP_MPS 16 #define CDC_ACM_DEFAULT_INT_INTERVAL 0x0A #define CDC_ACM_CLASS_ENABLED 0 #define CDC_ACM_CLASS_SUSPENDED 1 #define CDC_ACM_IRQ_RX_ENABLED 2 #define CDC_ACM_IRQ_TX_ENABLED 3 #define CDC_ACM_RX_FIFO_BUSY 4 #define CDC_ACM_LOCK 5 static struct k_work_q cdc_acm_work_q; static K_KERNEL_STACK_DEFINE(cdc_acm_stack, CONFIG_USBD_CDC_ACM_STACK_SIZE); struct cdc_acm_uart_fifo { struct ring_buf *rb; bool irq; bool altered; }; struct cdc_acm_uart_data { /* Pointer to the associated USBD class node */ struct usbd_class_node *c_nd; /* Line Coding Structure */ struct cdc_acm_line_coding line_coding; /* SetControlLineState bitmap */ uint16_t line_state; /* Serial state bitmap */ uint16_t serial_state; /* UART actual configuration */ struct uart_config uart_cfg; /* UART actual RTS state */ bool line_state_rts; /* UART actual DTR state */ bool line_state_dtr; /* UART API IRQ callback */ uart_irq_callback_user_data_t cb; /* UART API user callback data */ void *cb_data; /* UART API IRQ callback work */ struct k_work irq_cb_work; struct cdc_acm_uart_fifo rx_fifo; struct cdc_acm_uart_fifo tx_fifo; /* USBD CDC ACM TX fifo work */ struct k_work tx_fifo_work; /* USBD CDC ACM RX fifo work */ struct k_work rx_fifo_work; atomic_t state; struct k_sem notif_sem; }; struct usbd_cdc_acm_desc { struct usb_association_descriptor iad_cdc; struct usb_if_descriptor if0; struct cdc_header_descriptor if0_header; struct cdc_cm_descriptor if0_cm; struct cdc_acm_descriptor if0_acm; struct cdc_union_descriptor if0_union; struct usb_ep_descriptor if0_int_ep; struct usb_if_descriptor if1; struct usb_ep_descriptor if1_in_ep; struct usb_ep_descriptor if1_out_ep; struct usb_desc_header nil_desc; } __packed; static void cdc_acm_irq_rx_enable(const struct device *dev); struct net_buf *cdc_acm_buf_alloc(const uint8_t ep) { struct net_buf *buf = NULL; struct udc_buf_info *bi; buf = net_buf_alloc(&cdc_acm_ep_pool, K_NO_WAIT); if (!buf) { return NULL; } bi = udc_get_buf_info(buf); memset(bi, 0, sizeof(struct udc_buf_info)); bi->ep = ep; return buf; } static ALWAYS_INLINE int cdc_acm_work_submit(struct k_work *work) { return k_work_submit_to_queue(&cdc_acm_work_q, work); } static ALWAYS_INLINE bool check_wq_ctx(const struct device *dev) { return k_current_get() == k_work_queue_thread_get(&cdc_acm_work_q); } static uint8_t cdc_acm_get_int_in(struct usbd_class_node *const c_nd) { struct usbd_cdc_acm_desc *desc = c_nd->data->desc; return desc->if0_int_ep.bEndpointAddress; } static uint8_t cdc_acm_get_bulk_in(struct usbd_class_node *const c_nd) { struct usbd_cdc_acm_desc *desc = c_nd->data->desc; return desc->if1_in_ep.bEndpointAddress; } static uint8_t cdc_acm_get_bulk_out(struct usbd_class_node *const c_nd) { struct usbd_cdc_acm_desc *desc = c_nd->data->desc; return desc->if1_out_ep.bEndpointAddress; } static size_t cdc_acm_get_bulk_mps(struct usbd_class_node *const c_nd) { struct usbd_cdc_acm_desc *desc = c_nd->data->desc; return desc->if1_out_ep.wMaxPacketSize; } static int usbd_cdc_acm_request(struct usbd_class_node *const c_nd, struct net_buf *buf, int err) { struct usbd_contex *uds_ctx = c_nd->data->uds_ctx; const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; struct udc_buf_info *bi; bi = udc_get_buf_info(buf); if (err) { if (err == -ECONNABORTED) { LOG_WRN("request ep 0x%02x, len %u cancelled", bi->ep, buf->len); } else { LOG_ERR("request ep 0x%02x, len %u failed", bi->ep, buf->len); } if (bi->ep == cdc_acm_get_bulk_out(c_nd)) { atomic_clear_bit(&data->state, CDC_ACM_RX_FIFO_BUSY); } goto ep_request_error; } if (bi->ep == cdc_acm_get_bulk_out(c_nd)) { /* RX transfer completion */ size_t done; LOG_HEXDUMP_INF(buf->data, buf->len, ""); done = ring_buf_put(data->rx_fifo.rb, buf->data, buf->len); if (done && data->cb) { cdc_acm_work_submit(&data->irq_cb_work); } atomic_clear_bit(&data->state, CDC_ACM_RX_FIFO_BUSY); cdc_acm_work_submit(&data->rx_fifo_work); } if (bi->ep == cdc_acm_get_bulk_in(c_nd)) { /* TX transfer completion */ if (data->cb) { cdc_acm_work_submit(&data->irq_cb_work); } } if (bi->ep == cdc_acm_get_int_in(c_nd)) { k_sem_give(&data->notif_sem); } ep_request_error: return usbd_ep_buf_free(uds_ctx, buf); } static void usbd_cdc_acm_update(struct usbd_class_node *const c_nd, uint8_t iface, uint8_t alternate) { LOG_DBG("New configuration, interface %u alternate %u", iface, alternate); } static void usbd_cdc_acm_enable(struct usbd_class_node *const c_nd) { const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; atomic_set_bit(&data->state, CDC_ACM_CLASS_ENABLED); LOG_INF("Configuration enabled"); if (atomic_test_bit(&data->state, CDC_ACM_IRQ_RX_ENABLED)) { cdc_acm_irq_rx_enable(dev); } if (atomic_test_bit(&data->state, CDC_ACM_IRQ_TX_ENABLED)) { /* TODO */ } } static void usbd_cdc_acm_disable(struct usbd_class_node *const c_nd) { const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; atomic_clear_bit(&data->state, CDC_ACM_CLASS_ENABLED); atomic_clear_bit(&data->state, CDC_ACM_CLASS_SUSPENDED); LOG_INF("Configuration disabled"); } static void usbd_cdc_acm_suspended(struct usbd_class_node *const c_nd) { const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; /* FIXME: filter stray suspended events earlier */ atomic_set_bit(&data->state, CDC_ACM_CLASS_SUSPENDED); } static void usbd_cdc_acm_resumed(struct usbd_class_node *const c_nd) { const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; atomic_clear_bit(&data->state, CDC_ACM_CLASS_SUSPENDED); } static void cdc_acm_update_uart_cfg(struct cdc_acm_uart_data *const data) { struct uart_config *const cfg = &data->uart_cfg; cfg->baudrate = sys_le32_to_cpu(data->line_coding.dwDTERate); switch (data->line_coding.bCharFormat) { case USB_CDC_LINE_CODING_STOP_BITS_1: cfg->stop_bits = UART_CFG_STOP_BITS_1; break; case USB_CDC_LINE_CODING_STOP_BITS_1_5: cfg->stop_bits = UART_CFG_STOP_BITS_1_5; break; case USB_CDC_LINE_CODING_STOP_BITS_2: default: cfg->stop_bits = UART_CFG_STOP_BITS_2; break; }; switch (data->line_coding.bParityType) { case USB_CDC_LINE_CODING_PARITY_NO: default: cfg->parity = UART_CFG_PARITY_NONE; break; case USB_CDC_LINE_CODING_PARITY_ODD: cfg->parity = UART_CFG_PARITY_ODD; break; case USB_CDC_LINE_CODING_PARITY_EVEN: cfg->parity = UART_CFG_PARITY_EVEN; break; case USB_CDC_LINE_CODING_PARITY_MARK: cfg->parity = UART_CFG_PARITY_MARK; break; case USB_CDC_LINE_CODING_PARITY_SPACE: cfg->parity = UART_CFG_PARITY_SPACE; break; }; switch (data->line_coding.bDataBits) { case USB_CDC_LINE_CODING_DATA_BITS_5: cfg->data_bits = UART_CFG_DATA_BITS_5; break; case USB_CDC_LINE_CODING_DATA_BITS_6: cfg->data_bits = UART_CFG_DATA_BITS_6; break; case USB_CDC_LINE_CODING_DATA_BITS_7: cfg->data_bits = UART_CFG_DATA_BITS_7; break; case USB_CDC_LINE_CODING_DATA_BITS_8: default: cfg->data_bits = UART_CFG_DATA_BITS_8; break; }; cfg->flow_ctrl = UART_CFG_FLOW_CTRL_NONE; } static void cdc_acm_update_linestate(struct cdc_acm_uart_data *const data) { if (data->line_state & SET_CONTROL_LINE_STATE_RTS) { data->line_state_rts = true; } else { data->line_state_rts = false; } if (data->line_state & SET_CONTROL_LINE_STATE_DTR) { data->line_state_dtr = true; } else { data->line_state_dtr = false; } } static int usbd_cdc_acm_cth(struct usbd_class_node *const c_nd, const struct usb_setup_packet *const setup, struct net_buf *const buf) { const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; size_t min_len; if (setup->bRequest == GET_LINE_CODING) { if (buf == NULL) { errno = -ENOMEM; return 0; } min_len = MIN(sizeof(data->line_coding), setup->wLength); net_buf_add_mem(buf, &data->line_coding, min_len); return 0; } LOG_DBG("bmRequestType 0x%02x bRequest 0x%02x unsupported", setup->bmRequestType, setup->bRequest); errno = -ENOTSUP; return 0; } static int usbd_cdc_acm_ctd(struct usbd_class_node *const c_nd, const struct usb_setup_packet *const setup, const struct net_buf *const buf) { const struct device *dev = c_nd->data->priv; struct cdc_acm_uart_data *data = dev->data; size_t len; switch (setup->bRequest) { case SET_LINE_CODING: len = sizeof(data->line_coding); if (setup->wLength != len) { errno = -ENOTSUP; return 0; } memcpy(&data->line_coding, buf->data, len); cdc_acm_update_uart_cfg(data); return 0; case SET_CONTROL_LINE_STATE: data->line_state = setup->wValue; cdc_acm_update_linestate(data); return 0; default: break; } LOG_DBG("bmRequestType 0x%02x bRequest 0x%02x unsupported", setup->bmRequestType, setup->bRequest); errno = -ENOTSUP; return 0; } static int usbd_cdc_acm_init(struct usbd_class_node *const c_nd) { return 0; } static int cdc_acm_send_notification(const struct device *dev, const uint16_t serial_state) { struct cdc_acm_notification notification = { .bmRequestType = 0xA1, .bNotificationType = USB_CDC_SERIAL_STATE, .wValue = 0, .wIndex = 0, .wLength = sys_cpu_to_le16(sizeof(uint16_t)), .data = sys_cpu_to_le16(serial_state), }; struct cdc_acm_uart_data *data = dev->data; struct usbd_class_node *c_nd = data->c_nd; struct net_buf *buf; uint8_t ep; int ret; if (!atomic_test_bit(&data->state, CDC_ACM_CLASS_ENABLED)) { LOG_INF("USB configuration is not enabled"); return -EACCES; } if (atomic_test_bit(&data->state, CDC_ACM_CLASS_SUSPENDED)) { LOG_INF("USB support is suspended (FIXME)"); return -EACCES; } ep = cdc_acm_get_int_in(c_nd); buf = usbd_ep_buf_alloc(c_nd, ep, sizeof(struct cdc_acm_notification)); if (buf == NULL) { return -ENOMEM; } net_buf_add_mem(buf, ¬ification, sizeof(struct cdc_acm_notification)); ret = usbd_ep_enqueue(c_nd, buf); /* FIXME: support for sync transfers */ k_sem_take(&data->notif_sem, K_FOREVER); return ret; } /* * TX handler is triggered when the state of TX fifo has been altered. */ static void cdc_acm_tx_fifo_handler(struct k_work *work) { struct cdc_acm_uart_data *data; struct usbd_class_node *c_nd; struct net_buf *buf; size_t len; int ret; data = CONTAINER_OF(work, struct cdc_acm_uart_data, tx_fifo_work); c_nd = data->c_nd; if (!atomic_test_bit(&data->state, CDC_ACM_CLASS_ENABLED)) { LOG_DBG("USB configuration is not enabled"); return; } if (atomic_test_bit(&data->state, CDC_ACM_CLASS_SUSPENDED)) { LOG_INF("USB support is suspended (FIXME: submit rwup)"); return; } if (atomic_test_and_set_bit(&data->state, CDC_ACM_LOCK)) { cdc_acm_work_submit(&data->tx_fifo_work); return; } buf = cdc_acm_buf_alloc(cdc_acm_get_bulk_in(c_nd)); if (buf == NULL) { cdc_acm_work_submit(&data->tx_fifo_work); goto tx_fifo_handler_exit; } len = ring_buf_get(data->tx_fifo.rb, buf->data, buf->size); net_buf_add(buf, len); ret = usbd_ep_enqueue(c_nd, buf); if (ret) { LOG_ERR("Failed to enqueue"); net_buf_unref(buf); } tx_fifo_handler_exit: atomic_clear_bit(&data->state, CDC_ACM_LOCK); } /* * RX handler should be conditionally triggered at: * - (x) cdc_acm_irq_rx_enable() * - (x) RX transfer completion * - (x) the end of cdc_acm_irq_cb_handler * - (x) USBD class API enable call * - ( ) USBD class API resumed call (TODO) */ static void cdc_acm_rx_fifo_handler(struct k_work *work) { struct cdc_acm_uart_data *data; struct usbd_class_node *c_nd; struct net_buf *buf; uint8_t ep; int ret; data = CONTAINER_OF(work, struct cdc_acm_uart_data, rx_fifo_work); c_nd = data->c_nd; if (!atomic_test_bit(&data->state, CDC_ACM_CLASS_ENABLED) || atomic_test_bit(&data->state, CDC_ACM_CLASS_SUSPENDED)) { LOG_INF("USB configuration is not enabled or suspended"); return; } if (ring_buf_space_get(data->rx_fifo.rb) < cdc_acm_get_bulk_mps(c_nd)) { LOG_INF("RX buffer to small, throttle"); return; } if (atomic_test_and_set_bit(&data->state, CDC_ACM_RX_FIFO_BUSY)) { LOG_WRN("RX transfer already in progress"); return; } ep = cdc_acm_get_bulk_out(c_nd); buf = cdc_acm_buf_alloc(ep); if (buf == NULL) { return; } ret = usbd_ep_enqueue(c_nd, buf); if (ret) { LOG_ERR("Failed to enqueue net_buf for 0x%02x", ep); net_buf_unref(buf); } } static void cdc_acm_irq_tx_enable(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; atomic_set_bit(&data->state, CDC_ACM_IRQ_TX_ENABLED); if (ring_buf_is_empty(data->tx_fifo.rb)) { LOG_INF("tx_en: trigger irq_cb_work"); cdc_acm_work_submit(&data->irq_cb_work); } } static void cdc_acm_irq_tx_disable(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; atomic_clear_bit(&data->state, CDC_ACM_IRQ_TX_ENABLED); } static void cdc_acm_irq_rx_enable(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; atomic_set_bit(&data->state, CDC_ACM_IRQ_RX_ENABLED); /* Permit buffer to be drained regardless of USB state */ if (!ring_buf_is_empty(data->rx_fifo.rb)) { LOG_INF("rx_en: trigger irq_cb_work"); cdc_acm_work_submit(&data->irq_cb_work); } if (!atomic_test_bit(&data->state, CDC_ACM_RX_FIFO_BUSY)) { LOG_INF("rx_en: trigger rx_fifo_work"); cdc_acm_work_submit(&data->rx_fifo_work); } } static void cdc_acm_irq_rx_disable(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; atomic_clear_bit(&data->state, CDC_ACM_IRQ_RX_ENABLED); } static int cdc_acm_fifo_fill(const struct device *dev, const uint8_t *const tx_data, const int len) { struct cdc_acm_uart_data *const data = dev->data; uint32_t done; if (!check_wq_ctx(dev)) { LOG_WRN("Invoked by inappropriate context"); __ASSERT_NO_MSG(false); return 0; } done = ring_buf_put(data->tx_fifo.rb, tx_data, len); if (done) { data->tx_fifo.altered = true; } LOG_INF("UART dev %p, len %d, remaining space %u", dev, len, ring_buf_space_get(data->tx_fifo.rb)); return done; } static int cdc_acm_fifo_read(const struct device *dev, uint8_t *const rx_data, const int size) { struct cdc_acm_uart_data *const data = dev->data; uint32_t len; LOG_INF("UART dev %p size %d length %u", dev, size, ring_buf_size_get(data->rx_fifo.rb)); if (!check_wq_ctx(dev)) { LOG_WRN("Invoked by inappropriate context"); __ASSERT_NO_MSG(false); return 0; } len = ring_buf_get(data->rx_fifo.rb, rx_data, size); if (len) { data->rx_fifo.altered = true; } return len; } static int cdc_acm_irq_tx_ready(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; if (check_wq_ctx(dev)) { if (ring_buf_space_get(data->tx_fifo.rb)) { return 1; } } else { LOG_WRN("Invoked by inappropriate context"); __ASSERT_NO_MSG(false); } return 0; } static int cdc_acm_irq_rx_ready(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; if (check_wq_ctx(dev)) { if (!ring_buf_is_empty(data->rx_fifo.rb)) { return 1; } } else { LOG_WRN("Invoked by inappropriate context"); __ASSERT_NO_MSG(false); } return 0; } static int cdc_acm_irq_is_pending(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; if (check_wq_ctx(dev)) { if (data->tx_fifo.irq || data->rx_fifo.irq) { return 1; } } else { LOG_WRN("Invoked by inappropriate context"); __ASSERT_NO_MSG(false); } return 0; } static int cdc_acm_irq_update(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; if (!check_wq_ctx(dev)) { LOG_WRN("Invoked by inappropriate context"); __ASSERT_NO_MSG(false); return 0; } if (atomic_test_bit(&data->state, CDC_ACM_IRQ_RX_ENABLED) && !ring_buf_is_empty(data->rx_fifo.rb)) { data->rx_fifo.irq = true; } else { data->rx_fifo.irq = false; } if (atomic_test_bit(&data->state, CDC_ACM_IRQ_TX_ENABLED) && ring_buf_is_empty(data->tx_fifo.rb)) { data->tx_fifo.irq = true; } else { data->tx_fifo.irq = false; } return 1; } /* * IRQ handler should be conditionally triggered for the TX path at: * - cdc_acm_irq_tx_enable() * - TX transfer completion * - TX buffer is empty * - USBD class API enable and resumed calls * * for RX path, if enabled, at: * - cdc_acm_irq_rx_enable() * - RX transfer completion * - RX buffer is not empty */ static void cdc_acm_irq_cb_handler(struct k_work *work) { struct cdc_acm_uart_data *data; struct usbd_class_node *c_nd; data = CONTAINER_OF(work, struct cdc_acm_uart_data, irq_cb_work); c_nd = data->c_nd; if (data->cb == NULL) { LOG_ERR("IRQ callback is not set"); return; } if (atomic_test_and_set_bit(&data->state, CDC_ACM_LOCK)) { LOG_ERR("Polling is in progress"); cdc_acm_work_submit(&data->irq_cb_work); return; } data->tx_fifo.altered = false; data->rx_fifo.altered = false; data->rx_fifo.irq = false; data->tx_fifo.irq = false; if (atomic_test_bit(&data->state, CDC_ACM_IRQ_RX_ENABLED) || atomic_test_bit(&data->state, CDC_ACM_IRQ_TX_ENABLED)) { data->cb(c_nd->data->priv, data->cb_data); } if (data->rx_fifo.altered) { LOG_DBG("rx fifo altered, submit work"); cdc_acm_work_submit(&data->rx_fifo_work); } if (data->tx_fifo.altered) { LOG_DBG("tx fifo altered, submit work"); cdc_acm_work_submit(&data->tx_fifo_work); } if (atomic_test_bit(&data->state, CDC_ACM_IRQ_RX_ENABLED) && !ring_buf_is_empty(data->rx_fifo.rb)) { LOG_DBG("rx irq pending, submit irq_cb_work"); cdc_acm_work_submit(&data->irq_cb_work); } if (atomic_test_bit(&data->state, CDC_ACM_IRQ_TX_ENABLED) && ring_buf_is_empty(data->tx_fifo.rb)) { LOG_DBG("tx irq pending, submit irq_cb_work"); cdc_acm_work_submit(&data->irq_cb_work); } atomic_clear_bit(&data->state, CDC_ACM_LOCK); } static void cdc_acm_irq_callback_set(const struct device *dev, const uart_irq_callback_user_data_t cb, void *const cb_data) { struct cdc_acm_uart_data *const data = dev->data; data->cb = cb; data->cb_data = cb_data; } static int cdc_acm_poll_in(const struct device *dev, unsigned char *const c) { struct cdc_acm_uart_data *const data = dev->data; uint32_t len; int ret = -1; if (atomic_test_and_set_bit(&data->state, CDC_ACM_LOCK)) { LOG_ERR("IRQ callback is used"); return -1; } if (ring_buf_is_empty(data->rx_fifo.rb)) { goto poll_in_exit; } len = ring_buf_get(data->rx_fifo.rb, c, 1); if (len) { cdc_acm_work_submit(&data->rx_fifo_work); ret = 0; } poll_in_exit: atomic_clear_bit(&data->state, CDC_ACM_LOCK); return ret; } static void cdc_acm_poll_out(const struct device *dev, const unsigned char c) { struct cdc_acm_uart_data *const data = dev->data; if (atomic_test_and_set_bit(&data->state, CDC_ACM_LOCK)) { LOG_ERR("IRQ callback is used"); return; } if (ring_buf_put(data->tx_fifo.rb, &c, 1)) { goto poll_out_exit; } LOG_DBG("Ring buffer full, drain buffer"); if (!ring_buf_get(data->tx_fifo.rb, NULL, 1) || !ring_buf_put(data->tx_fifo.rb, &c, 1)) { LOG_ERR("Failed to drain buffer"); __ASSERT_NO_MSG(false); } poll_out_exit: atomic_clear_bit(&data->state, CDC_ACM_LOCK); cdc_acm_work_submit(&data->tx_fifo_work); } #ifdef CONFIG_UART_LINE_CTRL static int cdc_acm_line_ctrl_set(const struct device *dev, const uint32_t ctrl, const uint32_t val) { struct cdc_acm_uart_data *const data = dev->data; uint32_t flag = 0; switch (ctrl) { case USB_CDC_LINE_CTRL_BAUD_RATE: /* Ignore since it can not be used for notification anyway */ return 0; case USB_CDC_LINE_CTRL_DCD: flag = USB_CDC_SERIAL_STATE_RXCARRIER; break; case USB_CDC_LINE_CTRL_DSR: flag = USB_CDC_SERIAL_STATE_TXCARRIER; break; case USB_CDC_LINE_CTRL_BREAK: flag = USB_CDC_SERIAL_STATE_BREAK; break; case USB_CDC_LINE_CTRL_RING_SIGNAL: flag = USB_CDC_SERIAL_STATE_RINGSIGNAL; break; case USB_CDC_LINE_CTRL_FRAMING: flag = USB_CDC_SERIAL_STATE_FRAMING; break; case USB_CDC_LINE_CTRL_PARITY: flag = USB_CDC_SERIAL_STATE_PARITY; break; case USB_CDC_LINE_CTRL_OVER_RUN: flag = USB_CDC_SERIAL_STATE_OVERRUN; break; default: return -EINVAL; } if (val) { data->serial_state |= flag; } else { data->serial_state &= ~flag; } return cdc_acm_send_notification(dev, data->serial_state); } static int cdc_acm_line_ctrl_get(const struct device *dev, const uint32_t ctrl, uint32_t *const val) { struct cdc_acm_uart_data *const data = dev->data; switch (ctrl) { case UART_LINE_CTRL_BAUD_RATE: *val = data->uart_cfg.baudrate; return 0; case UART_LINE_CTRL_RTS: *val = data->line_state_rts; return 0; case UART_LINE_CTRL_DTR: *val = data->line_state_dtr; return 0; } return -ENOTSUP; } #endif #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE static int cdc_acm_configure(const struct device *dev, const struct uart_config *const cfg) { ARG_UNUSED(dev); ARG_UNUSED(cfg); /* * We cannot implement configure API because there is * no notification of configuration changes provided * for the Abstract Control Model and the UART controller * is only emulated. * However, it allows us to use CDC ACM UART together with * subsystems like Modbus which require configure API for * real controllers. */ return 0; } static int cdc_acm_config_get(const struct device *dev, struct uart_config *const cfg) { struct cdc_acm_uart_data *const data = dev->data; memcpy(cfg, &data->uart_cfg, sizeof(struct uart_config)); return 0; } #endif /* CONFIG_UART_USE_RUNTIME_CONFIGURE */ static int usbd_cdc_acm_init_wq(const struct device *dev) { k_work_queue_init(&cdc_acm_work_q); k_work_queue_start(&cdc_acm_work_q, cdc_acm_stack, K_KERNEL_STACK_SIZEOF(cdc_acm_stack), CONFIG_SYSTEM_WORKQUEUE_PRIORITY, NULL); return 0; } static int usbd_cdc_acm_preinit(const struct device *dev) { struct cdc_acm_uart_data *const data = dev->data; ring_buf_reset(data->tx_fifo.rb); ring_buf_reset(data->rx_fifo.rb); k_thread_name_set(&cdc_acm_work_q.thread, "cdc_acm_work_q"); k_work_init(&data->tx_fifo_work, cdc_acm_tx_fifo_handler); k_work_init(&data->rx_fifo_work, cdc_acm_rx_fifo_handler); k_work_init(&data->irq_cb_work, cdc_acm_irq_cb_handler); return 0; } static const struct uart_driver_api cdc_acm_uart_api = { .irq_tx_enable = cdc_acm_irq_tx_enable, .irq_tx_disable = cdc_acm_irq_tx_disable, .irq_tx_ready = cdc_acm_irq_tx_ready, .irq_rx_enable = cdc_acm_irq_rx_enable, .irq_rx_disable = cdc_acm_irq_rx_disable, .irq_rx_ready = cdc_acm_irq_rx_ready, .irq_is_pending = cdc_acm_irq_is_pending, .irq_update = cdc_acm_irq_update, .irq_callback_set = cdc_acm_irq_callback_set, .poll_in = cdc_acm_poll_in, .poll_out = cdc_acm_poll_out, .fifo_fill = cdc_acm_fifo_fill, .fifo_read = cdc_acm_fifo_read, #ifdef CONFIG_UART_LINE_CTRL .line_ctrl_set = cdc_acm_line_ctrl_set, .line_ctrl_get = cdc_acm_line_ctrl_get, #endif #ifdef CONFIG_UART_USE_RUNTIME_CONFIGURE .configure = cdc_acm_configure, .config_get = cdc_acm_config_get, #endif }; struct usbd_class_api usbd_cdc_acm_api = { .request = usbd_cdc_acm_request, .update = usbd_cdc_acm_update, .enable = usbd_cdc_acm_enable, .disable = usbd_cdc_acm_disable, .suspended = usbd_cdc_acm_suspended, .resumed = usbd_cdc_acm_resumed, .control_to_host = usbd_cdc_acm_cth, .control_to_dev = usbd_cdc_acm_ctd, .init = usbd_cdc_acm_init, }; #define CDC_ACM_DEFINE_DESCRIPTOR(n) \ static struct usbd_cdc_acm_desc cdc_acm_desc_##n = { \ .iad_cdc = { \ .bLength = sizeof(struct usb_association_descriptor), \ .bDescriptorType = USB_DESC_INTERFACE_ASSOC, \ .bFirstInterface = 0, \ .bInterfaceCount = 0x02, \ .bFunctionClass = USB_BCC_CDC_CONTROL, \ .bFunctionSubClass = ACM_SUBCLASS, \ .bFunctionProtocol = 0, \ .iFunction = 0, \ }, \ \ .if0 = { \ .bLength = sizeof(struct usb_if_descriptor), \ .bDescriptorType = USB_DESC_INTERFACE, \ .bInterfaceNumber = 0, \ .bAlternateSetting = 0, \ .bNumEndpoints = 1, \ .bInterfaceClass = USB_BCC_CDC_CONTROL, \ .bInterfaceSubClass = ACM_SUBCLASS, \ .bInterfaceProtocol = 0, \ .iInterface = 0, \ }, \ \ .if0_header = { \ .bFunctionLength = sizeof(struct cdc_header_descriptor), \ .bDescriptorType = USB_DESC_CS_INTERFACE, \ .bDescriptorSubtype = HEADER_FUNC_DESC, \ .bcdCDC = sys_cpu_to_le16(USB_SRN_1_1), \ }, \ \ .if0_cm = { \ .bFunctionLength = sizeof(struct cdc_cm_descriptor), \ .bDescriptorType = USB_DESC_CS_INTERFACE, \ .bDescriptorSubtype = CALL_MANAGEMENT_FUNC_DESC, \ .bmCapabilities = 0, \ .bDataInterface = 1, \ }, \ \ .if0_acm = { \ .bFunctionLength = sizeof(struct cdc_acm_descriptor), \ .bDescriptorType = USB_DESC_CS_INTERFACE, \ .bDescriptorSubtype = ACM_FUNC_DESC, \ /* See CDC PSTN Subclass Chapter 5.3.2 */ \ .bmCapabilities = BIT(1), \ }, \ \ .if0_union = { \ .bFunctionLength = sizeof(struct cdc_union_descriptor), \ .bDescriptorType = USB_DESC_CS_INTERFACE, \ .bDescriptorSubtype = UNION_FUNC_DESC, \ .bControlInterface = 0, \ .bSubordinateInterface0 = 1, \ }, \ \ .if0_int_ep = { \ .bLength = sizeof(struct usb_ep_descriptor), \ .bDescriptorType = USB_DESC_ENDPOINT, \ .bEndpointAddress = 0x81, \ .bmAttributes = USB_EP_TYPE_INTERRUPT, \ .wMaxPacketSize = sys_cpu_to_le16(CDC_ACM_DEFAULT_INT_EP_MPS), \ .bInterval = CDC_ACM_DEFAULT_INT_INTERVAL, \ }, \ \ .if1 = { \ .bLength = sizeof(struct usb_if_descriptor), \ .bDescriptorType = USB_DESC_INTERFACE, \ .bInterfaceNumber = 1, \ .bAlternateSetting = 0, \ .bNumEndpoints = 2, \ .bInterfaceClass = USB_BCC_CDC_DATA, \ .bInterfaceSubClass = 0, \ .bInterfaceProtocol = 0, \ .iInterface = 0, \ }, \ \ .if1_in_ep = { \ .bLength = sizeof(struct usb_ep_descriptor), \ .bDescriptorType = USB_DESC_ENDPOINT, \ .bEndpointAddress = 0x82, \ .bmAttributes = USB_EP_TYPE_BULK, \ .wMaxPacketSize = sys_cpu_to_le16(CDC_ACM_DEFAULT_BULK_EP_MPS), \ .bInterval = 0, \ }, \ \ .if1_out_ep = { \ .bLength = sizeof(struct usb_ep_descriptor), \ .bDescriptorType = USB_DESC_ENDPOINT, \ .bEndpointAddress = 0x01, \ .bmAttributes = USB_EP_TYPE_BULK, \ .wMaxPacketSize = sys_cpu_to_le16(CDC_ACM_DEFAULT_BULK_EP_MPS), \ .bInterval = 0, \ }, \ \ .nil_desc = { \ .bLength = 0, \ .bDescriptorType = 0, \ }, \ } #define DT_DRV_COMPAT zephyr_cdc_acm_uart #define USBD_CDC_ACM_DT_DEVICE_DEFINE(n) \ BUILD_ASSERT(DT_INST_ON_BUS(n, usb), \ "node " DT_NODE_PATH(DT_DRV_INST(n)) \ " is not assigned to a USB device controller"); \ \ CDC_ACM_DEFINE_DESCRIPTOR(n); \ \ static struct usbd_class_data usbd_cdc_acm_data_##n; \ \ USBD_DEFINE_CLASS(cdc_acm_##n, \ &usbd_cdc_acm_api, \ &usbd_cdc_acm_data_##n); \ \ RING_BUF_DECLARE(cdc_acm_rb_rx_##n, DT_INST_PROP(n, tx_fifo_size)); \ RING_BUF_DECLARE(cdc_acm_rb_tx_##n, DT_INST_PROP(n, tx_fifo_size)); \ \ static struct cdc_acm_uart_data uart_data_##n = { \ .line_coding = CDC_ACM_DEFAULT_LINECODING, \ .c_nd = &cdc_acm_##n, \ .rx_fifo.rb = &cdc_acm_rb_rx_##n, \ .tx_fifo.rb = &cdc_acm_rb_tx_##n, \ .notif_sem = Z_SEM_INITIALIZER(uart_data_##n.notif_sem, 0, 1), \ }; \ \ static struct usbd_class_data usbd_cdc_acm_data_##n = { \ .desc = (struct usb_desc_header *)&cdc_acm_desc_##n, \ .priv = (void *)DEVICE_DT_GET(DT_DRV_INST(n)), \ }; \ \ DEVICE_DT_INST_DEFINE(n, usbd_cdc_acm_preinit, NULL, \ &uart_data_##n, NULL, \ PRE_KERNEL_1, CONFIG_SERIAL_INIT_PRIORITY, \ &cdc_acm_uart_api); DT_INST_FOREACH_STATUS_OKAY(USBD_CDC_ACM_DT_DEVICE_DEFINE); SYS_INIT(usbd_cdc_acm_init_wq, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);