/******************************************************************************* * * Copyright(c) 2015-2019 Intel Corporation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * Neither the name of Intel Corporation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ******************************************************************************/ /** * @file * @brief CDC ACM device class driver * * Driver for USB CDC ACM device class driver */ #include #include #include #include #include #include #include #include #include #include #include #ifndef CONFIG_UART_INTERRUPT_DRIVEN #error "CONFIG_UART_INTERRUPT_DRIVEN must be set for CDC ACM driver" #endif /* definitions */ #define LOG_LEVEL CONFIG_USB_CDC_ACM_LOG_LEVEL #include LOG_MODULE_REGISTER(usb_cdc_acm); #define DEV_DATA(dev) \ ((struct cdc_acm_dev_data_t * const)(dev)->data) /* 115200bps, no parity, 1 stop bit, 8bit char */ #define CDC_ACM_DEFAULT_BAUDRATE {sys_cpu_to_le32(115200), 0, 0, 8} /* Size of the internal buffer used for storing received data */ #define CDC_ACM_BUFFER_SIZE (CONFIG_CDC_ACM_BULK_EP_MPS) /* Serial state notification timeout */ #define CDC_CONTROL_SERIAL_STATE_TIMEOUT_US 100000 #define ACM_INT_EP_IDX 0 #define ACM_OUT_EP_IDX 1 #define ACM_IN_EP_IDX 2 struct usb_cdc_acm_config { #if (CONFIG_USB_COMPOSITE_DEVICE || CONFIG_CDC_ACM_IAD) struct usb_association_descriptor iad_cdc; #endif 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; } __packed; #define INITIALIZER_IAD \ { \ .bLength = sizeof(struct usb_association_descriptor), \ .bDescriptorType = USB_ASSOCIATION_DESC, \ .bFirstInterface = 0, \ .bInterfaceCount = 0x02, \ .bFunctionClass = COMMUNICATION_DEVICE_CLASS, \ .bFunctionSubClass = ACM_SUBCLASS, \ .bFunctionProtocol = 0, \ .iFunction = 0, \ } #define INITIALIZER_IF(iface_num, num_ep, class, subclass) \ { \ .bLength = sizeof(struct usb_if_descriptor), \ .bDescriptorType = USB_INTERFACE_DESC, \ .bInterfaceNumber = iface_num, \ .bAlternateSetting = 0, \ .bNumEndpoints = num_ep, \ .bInterfaceClass = class, \ .bInterfaceSubClass = subclass, \ .bInterfaceProtocol = 0, \ .iInterface = 0, \ } #define INITIALIZER_IF_HDR \ { \ .bFunctionLength = sizeof(struct cdc_header_descriptor),\ .bDescriptorType = USB_CS_INTERFACE_DESC, \ .bDescriptorSubtype = HEADER_FUNC_DESC, \ .bcdCDC = sys_cpu_to_le16(USB_1_1), \ } #define INITIALIZER_IF_CM \ { \ .bFunctionLength = sizeof(struct cdc_cm_descriptor), \ .bDescriptorType = USB_CS_INTERFACE_DESC, \ .bDescriptorSubtype = CALL_MANAGEMENT_FUNC_DESC, \ .bmCapabilities = 0x02, \ .bDataInterface = 1, \ } /* Device supports the request combination of: * Set_Line_Coding, * Set_Control_Line_State, * Get_Line_Coding * and the notification Serial_State */ #define INITIALIZER_IF_ACM \ { \ .bFunctionLength = sizeof(struct cdc_acm_descriptor), \ .bDescriptorType = USB_CS_INTERFACE_DESC, \ .bDescriptorSubtype = ACM_FUNC_DESC, \ .bmCapabilities = 0x02, \ } #define INITIALIZER_IF_UNION \ { \ .bFunctionLength = sizeof(struct cdc_union_descriptor), \ .bDescriptorType = USB_CS_INTERFACE_DESC, \ .bDescriptorSubtype = UNION_FUNC_DESC, \ .bControlInterface = 0, \ .bSubordinateInterface0 = 1, \ } #define INITIALIZER_IF_EP(addr, attr, mps, interval) \ { \ .bLength = sizeof(struct usb_ep_descriptor), \ .bDescriptorType = USB_ENDPOINT_DESC, \ .bEndpointAddress = addr, \ .bmAttributes = attr, \ .wMaxPacketSize = sys_cpu_to_le16(mps), \ .bInterval = interval, \ } static struct k_sem poll_wait_sem; /* Device data structure */ struct cdc_acm_dev_data_t { /* Callback function pointer/arg */ uart_irq_callback_user_data_t cb; void *cb_data; struct k_work cb_work; #if defined(CONFIG_CDC_ACM_DTE_RATE_CALLBACK_SUPPORT) cdc_dte_rate_callback_t rate_cb; #endif struct k_work tx_work; /* Tx ready status. Signals when */ bool tx_ready; bool rx_ready; /* Rx ready status */ bool tx_irq_ena; /* Tx interrupt enable status */ bool rx_irq_ena; /* Rx interrupt enable status */ uint8_t rx_buf[CDC_ACM_BUFFER_SIZE]; /* Internal RX buffer */ struct ring_buf *rx_ringbuf; struct ring_buf *tx_ringbuf; /* Interface data buffer */ /* CDC ACM line coding properties. LE order */ struct cdc_acm_line_coding line_coding; /* CDC ACM line state bitmap, DTE side */ uint8_t line_state; /* CDC ACM serial state bitmap, DCE side */ uint8_t serial_state; /* CDC ACM notification sent status */ uint8_t notification_sent; /* CDC ACM configured flag */ bool configured; /* CDC ACM suspended flag */ bool suspended; struct usb_dev_data common; }; static sys_slist_t cdc_acm_data_devlist; static const struct uart_driver_api cdc_acm_driver_api; /** * @brief Handler called for Class requests not handled by the USB stack. * * @param pSetup Information about the request to execute. * @param len Size of the buffer. * @param data Buffer containing the request result. * * @return 0 on success, negative errno code on fail. */ int cdc_acm_class_handle_req(struct usb_setup_packet *pSetup, int32_t *len, uint8_t **data) { struct cdc_acm_dev_data_t *dev_data; struct usb_dev_data *common; uint32_t rate; uint32_t new_rate; common = usb_get_dev_data_by_iface(&cdc_acm_data_devlist, (uint8_t)pSetup->wIndex); if (common == NULL) { LOG_WRN("Device data not found for interface %u", pSetup->wIndex); return -ENODEV; } dev_data = CONTAINER_OF(common, struct cdc_acm_dev_data_t, common); switch (pSetup->bRequest) { case SET_LINE_CODING: rate = sys_le32_to_cpu(dev_data->line_coding.dwDTERate); memcpy(&dev_data->line_coding, *data, sizeof(dev_data->line_coding)); new_rate = sys_le32_to_cpu(dev_data->line_coding.dwDTERate); LOG_DBG("CDC_SET_LINE_CODING %d %d %d %d", new_rate, dev_data->line_coding.bCharFormat, dev_data->line_coding.bParityType, dev_data->line_coding.bDataBits); #if defined(CONFIG_CDC_ACM_DTE_RATE_CALLBACK_SUPPORT) if (rate != new_rate && dev_data->rate_cb != NULL) { dev_data->rate_cb(common->dev, new_rate); } #endif break; case SET_CONTROL_LINE_STATE: dev_data->line_state = (uint8_t)pSetup->wValue; LOG_DBG("CDC_SET_CONTROL_LINE_STATE 0x%x", dev_data->line_state); break; case GET_LINE_CODING: *data = (uint8_t *)(&dev_data->line_coding); *len = sizeof(dev_data->line_coding); LOG_DBG("CDC_GET_LINE_CODING %d %d %d %d", sys_le32_to_cpu(dev_data->line_coding.dwDTERate), dev_data->line_coding.bCharFormat, dev_data->line_coding.bParityType, dev_data->line_coding.bDataBits); break; default: LOG_DBG("CDC ACM request 0x%x, value 0x%x", pSetup->bRequest, pSetup->wValue); return -EINVAL; } return 0; } static void cdc_acm_write_cb(uint8_t ep, int size, void *priv) { struct cdc_acm_dev_data_t *dev_data = priv; LOG_DBG("ep %x: written %d bytes dev_data %p", ep, size, dev_data); dev_data->tx_ready = true; k_sem_give(&poll_wait_sem); /* Call callback only if tx irq ena */ if (dev_data->cb && dev_data->tx_irq_ena) { k_work_submit(&dev_data->cb_work); } if (ring_buf_is_empty(dev_data->tx_ringbuf)) { LOG_DBG("tx_ringbuf is empty"); return; } k_work_submit(&dev_data->tx_work); } static void tx_work_handler(struct k_work *work) { struct cdc_acm_dev_data_t *dev_data = CONTAINER_OF(work, struct cdc_acm_dev_data_t, tx_work); const struct device *dev = dev_data->common.dev; struct usb_cfg_data *cfg = (void *)dev->config; uint8_t ep = cfg->endpoint[ACM_IN_EP_IDX].ep_addr; uint8_t *data; size_t len; if (usb_transfer_is_busy(ep)) { LOG_DBG("Transfer is ongoing"); return; } len = ring_buf_get_claim(dev_data->tx_ringbuf, &data, CONFIG_USB_CDC_ACM_RINGBUF_SIZE); if (!len) { LOG_DBG("Nothing to send"); return; } /* * Transfer less data to avoid zero-length packet. The application * running on the host may conclude that there is no more data to be * received (i.e. the transaction has completed), hence not triggering * another I/O Request Packet (IRP). */ if (!(len % CONFIG_CDC_ACM_BULK_EP_MPS)) { len -= 1; } LOG_DBG("Got %d bytes from ringbuffer send to ep %x", len, ep); usb_transfer(ep, data, len, USB_TRANS_WRITE, cdc_acm_write_cb, dev_data); ring_buf_get_finish(dev_data->tx_ringbuf, len); } static void cdc_acm_read_cb(uint8_t ep, int size, void *priv) { struct cdc_acm_dev_data_t *dev_data = priv; size_t wrote; LOG_DBG("ep %x size %d dev_data %p rx_ringbuf space %u", ep, size, dev_data, ring_buf_space_get(dev_data->rx_ringbuf)); if (size <= 0) { goto done; } wrote = ring_buf_put(dev_data->rx_ringbuf, dev_data->rx_buf, size); if (wrote < size) { LOG_ERR("Ring buffer full, drop %d bytes", size - wrote); } done: dev_data->rx_ready = true; /* Call callback only if rx irq ena */ if (dev_data->cb && dev_data->rx_irq_ena) { k_work_submit(&dev_data->cb_work); } usb_transfer(ep, dev_data->rx_buf, sizeof(dev_data->rx_buf), USB_TRANS_READ, cdc_acm_read_cb, dev_data); } /** * @brief EP Interrupt handler * * @param ep Endpoint address. * @param ep_status Endpoint status code. * * @return N/A. */ static void cdc_acm_int_in(uint8_t ep, enum usb_dc_ep_cb_status_code ep_status) { struct cdc_acm_dev_data_t *dev_data; struct usb_dev_data *common; ARG_UNUSED(ep_status); common = usb_get_dev_data_by_ep(&cdc_acm_data_devlist, ep); if (common == NULL) { LOG_WRN("Device data not found for endpoint %u", ep); return; } dev_data = CONTAINER_OF(common, struct cdc_acm_dev_data_t, common); dev_data->notification_sent = 1U; LOG_DBG("CDC_IntIN EP[%x]\r", ep); } static void cdc_acm_reset_port(struct cdc_acm_dev_data_t *dev_data) { k_sem_give(&poll_wait_sem); dev_data->configured = false; dev_data->suspended = false; dev_data->rx_ready = false; dev_data->tx_ready = false; dev_data->tx_irq_ena = false; dev_data->rx_irq_ena = false; dev_data->line_coding = (struct cdc_acm_line_coding) CDC_ACM_DEFAULT_BAUDRATE; dev_data->serial_state = 0; dev_data->line_state = 0; memset(&dev_data->rx_buf, 0, CDC_ACM_BUFFER_SIZE); } static void cdc_acm_do_cb(struct cdc_acm_dev_data_t *dev_data, enum usb_dc_status_code status, const uint8_t *param) { const struct device *dev = dev_data->common.dev; struct usb_cfg_data *cfg = (void *)dev->config; /* Check the USB status and do needed action if required */ switch (status) { case USB_DC_ERROR: LOG_DBG("Device error"); break; case USB_DC_RESET: LOG_DBG("Device reset detected"); cdc_acm_reset_port(dev_data); break; case USB_DC_CONNECTED: LOG_DBG("Device connected"); break; case USB_DC_CONFIGURED: LOG_INF("Device configured"); if (!dev_data->configured) { cdc_acm_read_cb(cfg->endpoint[ACM_OUT_EP_IDX].ep_addr, 0, dev_data); } dev_data->configured = true; dev_data->tx_ready = true; dev_data->tx_irq_ena = true; dev_data->rx_irq_ena = true; break; case USB_DC_DISCONNECTED: LOG_INF("Device disconnected"); cdc_acm_reset_port(dev_data); break; case USB_DC_SUSPEND: LOG_INF("Device suspended"); dev_data->suspended = true; break; case USB_DC_RESUME: LOG_INF("Device resumed"); if (dev_data->suspended) { LOG_INF("from suspend"); dev_data->suspended = false; if (dev_data->configured) { cdc_acm_read_cb(cfg->endpoint[ACM_OUT_EP_IDX].ep_addr, 0, dev_data); } } else { LOG_DBG("Spurious resume event"); } break; case USB_DC_SOF: case USB_DC_INTERFACE: break; case USB_DC_UNKNOWN: default: LOG_DBG("Unknown event"); break; } } static void cdc_acm_dev_status_cb(struct usb_cfg_data *cfg, enum usb_dc_status_code status, const uint8_t *param) { struct cdc_acm_dev_data_t *dev_data; struct usb_dev_data *common; LOG_DBG("cfg %p status %d", cfg, status); common = usb_get_dev_data_by_cfg(&cdc_acm_data_devlist, cfg); if (common == NULL) { LOG_WRN("Device data not found for cfg %p", cfg); return; } dev_data = CONTAINER_OF(common, struct cdc_acm_dev_data_t, common); cdc_acm_do_cb(dev_data, status, param); } static void cdc_interface_config(struct usb_desc_header *head, uint8_t bInterfaceNumber) { struct usb_if_descriptor *if_desc = (struct usb_if_descriptor *) head; struct usb_cdc_acm_config *desc = CONTAINER_OF(if_desc, struct usb_cdc_acm_config, if0); desc->if0.bInterfaceNumber = bInterfaceNumber; desc->if0_union.bControlInterface = bInterfaceNumber; desc->if1.bInterfaceNumber = bInterfaceNumber + 1; desc->if0_union.bSubordinateInterface0 = bInterfaceNumber + 1; #ifdef CONFIG_USB_COMPOSITE_DEVICE desc->iad_cdc.bFirstInterface = bInterfaceNumber; #endif } /** * @brief Call the IRQ function callback. * * This routine is called from the system work queue to signal an UART * IRQ. * * @param work Address of work item. * * @return N/A. */ static void cdc_acm_irq_callback_work_handler(struct k_work *work) { struct cdc_acm_dev_data_t *dev_data; dev_data = CONTAINER_OF(work, struct cdc_acm_dev_data_t, cb_work); dev_data->cb(dev_data->common.dev, dev_data->cb_data); } /** * @brief Initialize UART channel * * This routine is called to reset the chip in a quiescent state. * It is assumed that this function is called only once per UART. * * @param dev CDC ACM device struct. * * @return 0 always. */ static int cdc_acm_init(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); int ret = 0; dev_data->common.dev = dev; sys_slist_append(&cdc_acm_data_devlist, &dev_data->common.node); LOG_DBG("Device dev %p dev_data %p cfg %p added to devlist %p", dev, dev_data, dev->config, &cdc_acm_data_devlist); k_sem_init(&poll_wait_sem, 0, UINT_MAX); k_work_init(&dev_data->cb_work, cdc_acm_irq_callback_work_handler); k_work_init(&dev_data->tx_work, tx_work_handler); return ret; } /** * @brief Fill FIFO with data * * @param dev CDC ACM device struct. * @param tx_data Data to transmit. * @param len Number of bytes to send. * * @return Number of bytes sent. */ static int cdc_acm_fifo_fill(const struct device *dev, const uint8_t *tx_data, int len) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); size_t wrote; LOG_DBG("dev_data %p len %d tx_ringbuf space %u", dev_data, len, ring_buf_space_get(dev_data->tx_ringbuf)); if (!dev_data->configured || dev_data->suspended) { LOG_WRN("Device not configured or suspended, drop %d bytes", len); return 0; } dev_data->tx_ready = false; wrote = ring_buf_put(dev_data->tx_ringbuf, tx_data, len); if (wrote < len) { LOG_WRN("Ring buffer full, drop %d bytes", len - wrote); } k_work_submit(&dev_data->tx_work); /* Return written to ringbuf data len */ return wrote; } /** * @brief Read data from FIFO * * @param dev CDC ACM device struct. * @param rx_data Pointer to data container. * @param size Container size. * * @return Number of bytes read. */ static int cdc_acm_fifo_read(const struct device *dev, uint8_t *rx_data, const int size) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); uint32_t len; LOG_DBG("dev %p size %d rx_ringbuf space %u", dev, size, ring_buf_space_get(dev_data->rx_ringbuf)); len = ring_buf_get(dev_data->rx_ringbuf, rx_data, size); if (ring_buf_is_empty(dev_data->rx_ringbuf)) { dev_data->rx_ready = false; } return len; } /** * @brief Enable TX interrupt * * @param dev CDC ACM device struct. * * @return N/A. */ static void cdc_acm_irq_tx_enable(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); dev_data->tx_irq_ena = true; if (dev_data->cb && dev_data->tx_ready) { k_work_submit(&dev_data->cb_work); } } /** * @brief Disable TX interrupt * * @param dev CDC ACM device struct. * * @return N/A. */ static void cdc_acm_irq_tx_disable(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); dev_data->tx_irq_ena = false; } /** * @brief Check if Tx IRQ has been raised * * @param dev CDC ACM device struct. * * @return 1 if a Tx IRQ is pending, 0 otherwise. */ static int cdc_acm_irq_tx_ready(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); if (dev_data->tx_ready) { return 1; } return 0; } /** * @brief Enable RX interrupt * * @param dev CDC ACM device struct. * * @return N/A */ static void cdc_acm_irq_rx_enable(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); dev_data->rx_irq_ena = true; if (dev_data->cb && dev_data->rx_ready) { k_work_submit(&dev_data->cb_work); } } /** * @brief Disable RX interrupt * * @param dev CDC ACM device struct. * * @return N/A. */ static void cdc_acm_irq_rx_disable(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); dev_data->rx_irq_ena = false; } /** * @brief Check if Rx IRQ has been raised * * @param dev CDC ACM device struct. * * @return 1 if an IRQ is ready, 0 otherwise. */ static int cdc_acm_irq_rx_ready(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); if (dev_data->rx_ready) { return 1; } return 0; } /** * @brief Check if Tx or Rx IRQ is pending * * @param dev CDC ACM device struct. * * @return 1 if a Tx or Rx IRQ is pending, 0 otherwise. */ static int cdc_acm_irq_is_pending(const struct device *dev) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); if (dev_data->tx_ready && dev_data->tx_irq_ena) { return 1; } else if (dev_data->rx_ready && dev_data->rx_irq_ena) { return 1; } else { return 0; } } /** * @brief Update IRQ status * * @param dev CDC ACM device struct. * * @return Always 1 */ static int cdc_acm_irq_update(const struct device *dev) { ARG_UNUSED(dev); return 1; } /** * @brief Set the callback function pointer for IRQ. * * @param dev CDC ACM device struct. * @param cb Callback function pointer. * * @return N/A */ static void cdc_acm_irq_callback_set(const struct device *dev, uart_irq_callback_user_data_t cb, void *cb_data) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); dev_data->cb = cb; dev_data->cb_data = cb_data; } #if defined(CONFIG_CDC_ACM_DTE_RATE_CALLBACK_SUPPORT) int cdc_acm_dte_rate_callback_set(const struct device *dev, cdc_dte_rate_callback_t callback) { struct cdc_acm_dev_data_t *const dev_data = DEV_DATA(dev); if (dev->api != &cdc_acm_driver_api) { return -EINVAL; } dev_data->rate_cb = callback; return 0; } #endif #ifdef CONFIG_UART_LINE_CTRL /** * @brief Set the baud rate * * This routine set the given baud rate for the UART. * * @param dev CDC ACM device struct. * @param baudrate Baud rate. * * @return N/A. */ static void cdc_acm_baudrate_set(const struct device *dev, uint32_t baudrate) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); dev_data->line_coding.dwDTERate = sys_cpu_to_le32(baudrate); } /** * @brief Send serial line state notification to the Host * * This routine sends asynchronous notification of UART status * on the interrupt endpoint * * @param dev CDC ACM device struct. * @param ep_status Endpoint status code. * * @return N/A. */ static int cdc_acm_send_notification(const struct device *dev, uint16_t serial_state) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); struct usb_cfg_data * const cfg = (void *)dev->config; struct cdc_acm_notification notification; uint32_t cnt = 0U; notification.bmRequestType = 0xA1; notification.bNotificationType = 0x20; notification.wValue = 0U; notification.wIndex = 0U; notification.wLength = sys_cpu_to_le16(sizeof(serial_state)); notification.data = sys_cpu_to_le16(serial_state); dev_data->notification_sent = 0U; usb_write(cfg->endpoint[ACM_INT_EP_IDX].ep_addr, (const uint8_t *)¬ification, sizeof(notification), NULL); /* Wait for notification to be sent */ while (!((volatile uint8_t)dev_data->notification_sent)) { k_busy_wait(1); if (++cnt > CDC_CONTROL_SERIAL_STATE_TIMEOUT_US) { LOG_DBG("CDC ACM notification timeout!"); return -EIO; } } return 0; } /** * @brief Manipulate line control for UART. * * @param dev CDC ACM device struct * @param ctrl The line control to be manipulated * @param val Value to set the line control * * @return 0 if successful, failed otherwise. */ static int cdc_acm_line_ctrl_set(const struct device *dev, uint32_t ctrl, uint32_t val) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); switch (ctrl) { case USB_CDC_LINE_CTRL_BAUD_RATE: cdc_acm_baudrate_set(dev, val); return 0; case USB_CDC_LINE_CTRL_DCD: dev_data->serial_state &= ~SERIAL_STATE_RX_CARRIER; if (val) { dev_data->serial_state |= SERIAL_STATE_RX_CARRIER; } cdc_acm_send_notification(dev, SERIAL_STATE_RX_CARRIER); return 0; case USB_CDC_LINE_CTRL_DSR: dev_data->serial_state &= ~SERIAL_STATE_TX_CARRIER; if (val) { dev_data->serial_state |= SERIAL_STATE_TX_CARRIER; } cdc_acm_send_notification(dev, dev_data->serial_state); return 0; case USB_CDC_LINE_CTRL_BREAK: dev_data->serial_state &= ~SERIAL_STATE_BREAK; if (val) { dev_data->serial_state |= SERIAL_STATE_BREAK; } cdc_acm_send_notification(dev, dev_data->serial_state); return 0; case USB_CDC_LINE_CTRL_RING_SIGNAL: dev_data->serial_state &= ~SERIAL_STATE_RING_SIGNAL; if (val) { dev_data->serial_state |= SERIAL_STATE_RING_SIGNAL; } cdc_acm_send_notification(dev, dev_data->serial_state); return 0; case USB_CDC_LINE_CTRL_FRAMING: dev_data->serial_state &= ~SERIAL_STATE_FRAMING; if (val) { dev_data->serial_state |= SERIAL_STATE_FRAMING; } cdc_acm_send_notification(dev, dev_data->serial_state); return 0; case USB_CDC_LINE_CTRL_PARITY: dev_data->serial_state &= ~SERIAL_STATE_PARITY; if (val) { dev_data->serial_state |= SERIAL_STATE_PARITY; } cdc_acm_send_notification(dev, dev_data->serial_state); return 0; case USB_CDC_LINE_CTRL_OVER_RUN: dev_data->serial_state &= ~SERIAL_STATE_OVER_RUN; if (val) { dev_data->serial_state |= SERIAL_STATE_OVER_RUN; } cdc_acm_send_notification(dev, dev_data->serial_state); return 0; default: return -ENODEV; } return -ENOTSUP; } /** * @brief Manipulate line control for UART. * * @param dev CDC ACM device struct * @param ctrl The line control to be manipulated * @param val Value to set the line control * * @return 0 if successful, failed otherwise. */ static int cdc_acm_line_ctrl_get(const struct device *dev, uint32_t ctrl, uint32_t *val) { struct cdc_acm_dev_data_t * const dev_data = DEV_DATA(dev); switch (ctrl) { case UART_LINE_CTRL_BAUD_RATE: *val = sys_le32_to_cpu(dev_data->line_coding.dwDTERate); return 0; case UART_LINE_CTRL_RTS: *val = (dev_data->line_state & SET_CONTROL_LINE_STATE_RTS) ? 1 : 0; return 0; case UART_LINE_CTRL_DTR: *val = (dev_data->line_state & SET_CONTROL_LINE_STATE_DTR) ? 1 : 0; return 0; } return -ENOTSUP; } #endif /* CONFIG_UART_LINE_CTRL */ /* * @brief Poll the device for input. * * @return -ENOTSUP Since underlying USB device controller always uses * interrupts, polled mode UART APIs are not implemented for the UART interface * exported by CDC ACM driver. Apps should use fifo_read API instead. */ static int cdc_acm_poll_in(const struct device *dev, unsigned char *c) { ARG_UNUSED(dev); ARG_UNUSED(c); return -ENOTSUP; } /* * @brief Output a character in polled mode. * * The UART poll method for USB UART is simulated by waiting till * we get the next BULK In upcall from the USB device controller or 100 ms. */ static void cdc_acm_poll_out(const struct device *dev, unsigned char c) { cdc_acm_fifo_fill(dev, &c, 1); k_sem_take(&poll_wait_sem, K_MSEC(100)); } static const struct uart_driver_api cdc_acm_driver_api = { .poll_in = cdc_acm_poll_in, .poll_out = cdc_acm_poll_out, .fifo_fill = cdc_acm_fifo_fill, .fifo_read = cdc_acm_fifo_read, .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, #ifdef CONFIG_UART_LINE_CTRL .line_ctrl_set = cdc_acm_line_ctrl_set, .line_ctrl_get = cdc_acm_line_ctrl_get, #endif /* CONFIG_UART_LINE_CTRL */ }; #define INITIALIZER_EP_DATA(cb, addr) \ { \ .ep_cb = cb, \ .ep_addr = addr, \ } #define DEFINE_CDC_ACM_EP(x, int_ep_addr, out_ep_addr, in_ep_addr) \ static struct usb_ep_cfg_data cdc_acm_ep_data_##x[] = { \ INITIALIZER_EP_DATA(cdc_acm_int_in, int_ep_addr), \ INITIALIZER_EP_DATA(usb_transfer_ep_callback, \ out_ep_addr), \ INITIALIZER_EP_DATA(usb_transfer_ep_callback, \ in_ep_addr), \ } #define DEFINE_CDC_ACM_CFG_DATA(x, _) \ USBD_CFG_DATA_DEFINE(primary, cdc_acm) \ struct usb_cfg_data cdc_acm_config_##x = { \ .usb_device_description = NULL, \ .interface_config = cdc_interface_config, \ .interface_descriptor = &cdc_acm_cfg_##x.if0, \ .cb_usb_status = cdc_acm_dev_status_cb, \ .interface = { \ .class_handler = cdc_acm_class_handle_req, \ .custom_handler = NULL, \ }, \ .num_endpoints = ARRAY_SIZE(cdc_acm_ep_data_##x), \ .endpoint = cdc_acm_ep_data_##x, \ }; #if (CONFIG_USB_COMPOSITE_DEVICE || CONFIG_CDC_ACM_IAD) #define DEFINE_CDC_ACM_DESCR(x, int_ep_addr, out_ep_addr, in_ep_addr) \ USBD_CLASS_DESCR_DEFINE(primary, x) \ struct usb_cdc_acm_config cdc_acm_cfg_##x = { \ .iad_cdc = INITIALIZER_IAD, \ .if0 = INITIALIZER_IF(0, 1, COMMUNICATION_DEVICE_CLASS, \ ACM_SUBCLASS), \ .if0_header = INITIALIZER_IF_HDR, \ .if0_cm = INITIALIZER_IF_CM, \ .if0_acm = INITIALIZER_IF_ACM, \ .if0_union = INITIALIZER_IF_UNION, \ .if0_int_ep = INITIALIZER_IF_EP(int_ep_addr, \ USB_DC_EP_INTERRUPT, \ CONFIG_CDC_ACM_INTERRUPT_EP_MPS,\ 0x0A), \ .if1 = INITIALIZER_IF(1, 2, COMMUNICATION_DEVICE_CLASS_DATA, 0),\ .if1_in_ep = INITIALIZER_IF_EP(in_ep_addr, \ USB_DC_EP_BULK, \ CONFIG_CDC_ACM_BULK_EP_MPS, \ 0x00), \ .if1_out_ep = INITIALIZER_IF_EP(out_ep_addr, \ USB_DC_EP_BULK, \ CONFIG_CDC_ACM_BULK_EP_MPS, \ 0x00), \ } #else /* (CONFIG_USB_COMPOSITE_DEVICE || CONFIG_CDC_ACM_IAD) */ #define DEFINE_CDC_ACM_DESCR(x, int_ep_addr, out_ep_addr, in_ep_addr) \ USBD_CLASS_DESCR_DEFINE(primary, x) \ struct usb_cdc_acm_config cdc_acm_cfg_##x = { \ .if0 = INITIALIZER_IF(0, 1, COMMUNICATION_DEVICE_CLASS, \ ACM_SUBCLASS), \ .if0_header = INITIALIZER_IF_HDR, \ .if0_cm = INITIALIZER_IF_CM, \ .if0_acm = INITIALIZER_IF_ACM, \ .if0_union = INITIALIZER_IF_UNION, \ .if0_int_ep = INITIALIZER_IF_EP(int_ep_addr, \ USB_DC_EP_INTERRUPT, \ CONFIG_CDC_ACM_INTERRUPT_EP_MPS,\ 0x0A), \ .if1 = INITIALIZER_IF(1, 2, COMMUNICATION_DEVICE_CLASS_DATA, 0),\ .if1_in_ep = INITIALIZER_IF_EP(in_ep_addr, \ USB_DC_EP_BULK, \ CONFIG_CDC_ACM_BULK_EP_MPS, \ 0x00), \ .if1_out_ep = INITIALIZER_IF_EP(out_ep_addr, \ USB_DC_EP_BULK, \ CONFIG_CDC_ACM_BULK_EP_MPS, \ 0x00), \ } #endif /* (CONFIG_USB_COMPOSITE_DEVICE || CONFIG_CDC_ACM_IAD) */ #define DEFINE_CDC_ACM_DEV_DATA(x, _) \ RING_BUF_DECLARE(rx_ringbuf_##x, \ CONFIG_USB_CDC_ACM_RINGBUF_SIZE); \ RING_BUF_DECLARE(tx_ringbuf_##x, \ CONFIG_USB_CDC_ACM_RINGBUF_SIZE); \ static struct cdc_acm_dev_data_t cdc_acm_dev_data_##x = { \ .line_coding = CDC_ACM_DEFAULT_BAUDRATE, \ .rx_ringbuf = &rx_ringbuf_##x, \ .tx_ringbuf = &tx_ringbuf_##x, \ }; #define DEFINE_CDC_ACM_DEVICE(x, _) \ DEVICE_AND_API_INIT(cdc_acm_##x, \ CONFIG_USB_CDC_ACM_DEVICE_NAME "_" #x, \ &cdc_acm_init, &cdc_acm_dev_data_##x, \ &cdc_acm_config_##x, \ POST_KERNEL, \ CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ &cdc_acm_driver_api); #define DEFINE_CDC_ACM_DESCR_AUTO(x, _) \ DEFINE_CDC_ACM_DESCR(x, AUTO_EP_IN, AUTO_EP_OUT, AUTO_EP_IN); #define DEFINE_CDC_ACM_EP_AUTO(x, _) \ DEFINE_CDC_ACM_EP(x, AUTO_EP_IN, AUTO_EP_OUT, AUTO_EP_IN); UTIL_LISTIFY(CONFIG_USB_CDC_ACM_DEVICE_COUNT, DEFINE_CDC_ACM_DESCR_AUTO, _) UTIL_LISTIFY(CONFIG_USB_CDC_ACM_DEVICE_COUNT, DEFINE_CDC_ACM_EP_AUTO, _) UTIL_LISTIFY(CONFIG_USB_CDC_ACM_DEVICE_COUNT, DEFINE_CDC_ACM_CFG_DATA, _) UTIL_LISTIFY(CONFIG_USB_CDC_ACM_DEVICE_COUNT, DEFINE_CDC_ACM_DEV_DATA, _) UTIL_LISTIFY(CONFIG_USB_CDC_ACM_DEVICE_COUNT, DEFINE_CDC_ACM_DEVICE, _)