| /**************************************************************************** |
| * drivers/usbdev/cdcacm.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <debug.h> |
| |
| #include <nuttx/irq.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/queue.h> |
| #include <nuttx/wdog.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/serial/serial.h> |
| |
| #include <nuttx/usb/usb.h> |
| #include <nuttx/usb/cdc.h> |
| #include <nuttx/usb/usbdev.h> |
| #include <nuttx/usb/cdcacm.h> |
| #include <nuttx/usb/usbdev_trace.h> |
| |
| #include "cdcacm.h" |
| |
| #ifdef CONFIG_CDCACM_COMPOSITE |
| # include <nuttx/usb/composite.h> |
| # include "composite.h" |
| #endif |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* RX poll delay = 200 milliseconds. CLK_TCK is the number of clock ticks per |
| * second |
| */ |
| |
| #define CDCACM_RXDELAY (CLK_TCK / 5) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* Container to support a list of requests */ |
| |
| struct cdcacm_wrreq_s |
| { |
| FAR struct cdcacm_wrreq_s *flink; /* Implements a singly linked list */ |
| FAR struct usbdev_req_s *req; /* The contained request */ |
| }; |
| |
| struct cdcacm_rdreq_s |
| { |
| FAR struct cdcacm_rdreq_s *flink; /* Implements a singly linked list */ |
| FAR struct usbdev_req_s *req; /* The contained request */ |
| uint16_t offset; /* Offset to valid data in the RX request */ |
| }; |
| |
| /* This structure describes the internal state of the driver */ |
| |
| struct cdcacm_dev_s |
| { |
| FAR struct uart_dev_s serdev; /* Serial device structure */ |
| FAR struct usbdev_s *usbdev; /* usbdev driver pointer */ |
| |
| uint8_t config; /* Configuration number */ |
| uint8_t nwrq; /* Number of queue write requests (in txfree) */ |
| uint8_t nrdq; /* Number of queue read requests (in epbulkout) */ |
| uint8_t minor; /* The device minor number */ |
| uint8_t ctrlline; /* Buffered control line state */ |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| uint8_t serialstate; /* State of the DSR/DCD */ |
| bool iflow; /* True: input flow control is enabled */ |
| bool iactive; /* True: input flow control is active */ |
| bool upper; /* True: RX buffer is (nearly) full */ |
| #endif |
| bool rxenabled; /* true: UART RX "interrupts" enabled */ |
| |
| struct cdc_linecoding_s linecoding; /* Buffered line status */ |
| cdcacm_callback_t callback; /* Serial event callback function */ |
| |
| FAR struct usbdev_ep_s *epintin; /* Interrupt IN endpoint structure */ |
| FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint structure */ |
| FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint structure */ |
| FAR struct usbdev_req_s *ctrlreq; /* Allocated control request */ |
| struct wdog_s rxfailsafe; /* Failsafe timer to prevent RX stalls */ |
| struct sq_queue_s txfree; /* Available write request containers */ |
| struct sq_queue_s rxpending; /* Pending read request containers */ |
| |
| struct usbdev_devinfo_s devinfo; |
| |
| /* Pre-allocated write request containers. The write requests will |
| * be linked in a free list (txfree), and used to send requests to |
| * EPBULKIN; Read requests will be queued in the EBULKOUT. |
| */ |
| |
| struct cdcacm_wrreq_s wrreqs[CONFIG_CDCACM_NWRREQS]; |
| struct cdcacm_rdreq_s rdreqs[CONFIG_CDCACM_NRDREQS]; |
| |
| /* Serial I/O buffers */ |
| |
| char rxbuffer[CONFIG_CDCACM_RXBUFSIZE]; |
| char txbuffer[CONFIG_CDCACM_TXBUFSIZE]; |
| }; |
| |
| /* The internal version of the class driver */ |
| |
| struct cdcacm_driver_s |
| { |
| struct usbdevclass_driver_s drvr; |
| FAR struct cdcacm_dev_s *dev; |
| }; |
| |
| /* This is what is allocated */ |
| |
| struct cdcacm_alloc_s |
| { |
| struct cdcacm_dev_s dev; |
| struct cdcacm_driver_s drvr; |
| }; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Transfer helpers *********************************************************/ |
| |
| static uint16_t cdcacm_fillrequest(FAR struct cdcacm_dev_s *priv, |
| uint8_t *reqbuf, uint16_t reqlen); |
| static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv); |
| static int cdcacm_recvpacket(FAR struct cdcacm_dev_s *priv, |
| FAR struct cdcacm_rdreq_s *rdcontainer); |
| static int cdcacm_requeue_rdrequest(FAR struct cdcacm_dev_s *priv, |
| FAR struct cdcacm_rdreq_s *rdcontainer); |
| static int cdcacm_release_rxpending(FAR struct cdcacm_dev_s *priv); |
| static void cdcacm_rxtimeout(wdparm_t arg); |
| |
| /* Flow Control *************************************************************/ |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| static int cdcacm_serialstate(FAR struct cdcacm_dev_s *priv); |
| #endif |
| |
| /* Configuration ************************************************************/ |
| |
| static void cdcacm_resetconfig(FAR struct cdcacm_dev_s *priv); |
| static int cdcacm_epconfigure(FAR struct usbdev_ep_s *ep, |
| enum cdcacm_epdesc_e epid, bool last, |
| FAR struct usbdev_devinfo_s *devinfo, |
| bool hispeed); |
| static int cdcacm_setconfig(FAR struct cdcacm_dev_s *priv, |
| uint8_t config); |
| |
| /* Completion event handlers ************************************************/ |
| |
| static void cdcacm_ep0incomplete(FAR struct usbdev_ep_s *ep, |
| FAR struct usbdev_req_s *req); |
| static void cdcacm_rdcomplete(FAR struct usbdev_ep_s *ep, |
| FAR struct usbdev_req_s *req); |
| static void cdcacm_wrcomplete(FAR struct usbdev_ep_s *ep, |
| FAR struct usbdev_req_s *req); |
| |
| /* USB class device *********************************************************/ |
| |
| static int cdcacm_bind(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev); |
| static void cdcacm_unbind(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev); |
| static int cdcacm_setup(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev, |
| FAR const struct usb_ctrlreq_s *ctrl, FAR uint8_t *dataout, |
| size_t outlen); |
| static void cdcacm_disconnect(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev); |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| static void cdcacm_suspend(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev); |
| static void cdcacm_resume(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev); |
| #endif |
| |
| /* UART Operations **********************************************************/ |
| |
| static int cdcuart_setup(FAR struct uart_dev_s *dev); |
| static void cdcuart_shutdown(FAR struct uart_dev_s *dev); |
| static int cdcuart_attach(FAR struct uart_dev_s *dev); |
| static void cdcuart_detach(FAR struct uart_dev_s *dev); |
| static int cdcuart_ioctl(FAR struct file *filep, int cmd, |
| unsigned long arg); |
| static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable); |
| #ifdef CONFIG_SERIAL_IFLOWCONTROL |
| static bool cdcuart_rxflowcontrol(FAR struct uart_dev_s *dev, |
| unsigned int nbuffered, bool upper); |
| #endif |
| static void cdcuart_txint(FAR struct uart_dev_s *dev, bool enable); |
| static bool cdcuart_txempty(FAR struct uart_dev_s *dev); |
| static int cdcuart_release(FAR struct uart_dev_s *dev); |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* USB class device *********************************************************/ |
| |
| static const struct usbdevclass_driverops_s g_driverops = |
| { |
| cdcacm_bind, /* bind */ |
| cdcacm_unbind, /* unbind */ |
| cdcacm_setup, /* setup */ |
| cdcacm_disconnect, /* disconnect */ |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| cdcacm_suspend, /* suspend */ |
| cdcacm_resume, /* resume */ |
| #else |
| NULL, /* suspend */ |
| NULL, /* resume */ |
| #endif |
| }; |
| |
| /* Serial port **************************************************************/ |
| |
| static const struct uart_ops_s g_uartops = |
| { |
| cdcuart_setup, /* setup */ |
| cdcuart_shutdown, /* shutdown */ |
| cdcuart_attach, /* attach */ |
| cdcuart_detach, /* detach */ |
| cdcuart_ioctl, /* ioctl */ |
| NULL, /* receive */ |
| cdcuart_rxint, /* rxinit */ |
| NULL, /* rxavailable */ |
| #ifdef CONFIG_SERIAL_IFLOWCONTROL |
| cdcuart_rxflowcontrol, /* rxflowcontrol */ |
| #endif |
| #ifdef CONFIG_SERIAL_TXDMA |
| NULL, /* dmasend */ |
| #endif |
| #ifdef CONFIG_SERIAL_RXDMA |
| NULL, /* dmareceive */ |
| NULL, /* dmarxfree */ |
| #endif |
| #ifdef CONFIG_SERIAL_TXDMA |
| NULL, /* dmatxavail */ |
| #endif |
| NULL, /* send */ |
| cdcuart_txint, /* txinit */ |
| NULL, /* txready */ |
| cdcuart_txempty, /* txempty */ |
| cdcuart_release /* release */ |
| }; |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cdcacm_fillrequest |
| * |
| * Description: |
| * If there is data to send it is copied to the given buffer. Called |
| * either to initiate the first write operation, or from the completion |
| * interrupt handler service consecutive write operations. |
| * |
| * NOTE: The USB serial driver does not use the serial drivers |
| * uart_xmitchars() API. That logic is essentially duplicated here because |
| * unlike UART hardware, we need to be able to handle writes not byte-by- |
| * byte, but packet-by-packet. Unfortunately, that decision also exposes |
| * some internals of the serial driver in the following. |
| * |
| ****************************************************************************/ |
| |
| static uint16_t cdcacm_fillrequest(FAR struct cdcacm_dev_s *priv, |
| FAR uint8_t *reqbuf, |
| uint16_t reqlen) |
| { |
| FAR uart_dev_t *serdev = &priv->serdev; |
| FAR struct uart_buffer_s *xmit = &serdev->xmit; |
| irqstate_t flags; |
| uint16_t nbytes = 0; |
| |
| /* Disable interrupts */ |
| |
| flags = enter_critical_section(); |
| |
| /* Transfer bytes while we have bytes available and there is room in the |
| * request. |
| */ |
| |
| while (xmit->head != xmit->tail && nbytes < reqlen) |
| { |
| *reqbuf++ = xmit->buffer[xmit->tail]; |
| nbytes++; |
| |
| /* Increment the tail pointer */ |
| |
| if (++(xmit->tail) >= xmit->size) |
| { |
| xmit->tail = 0; |
| } |
| } |
| |
| /* When all of the characters have been sent from the buffer disable the |
| * "TX interrupt". |
| */ |
| |
| if (xmit->head == xmit->tail) |
| { |
| uart_disabletxint(serdev); |
| } |
| |
| /* If any bytes were removed from the buffer, inform any waiters that |
| * there is space available. |
| */ |
| |
| if (nbytes) |
| { |
| uart_datasent(serdev); |
| } |
| |
| leave_critical_section(flags); |
| return nbytes; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_sndpacket |
| * |
| * Description: |
| * This function obtains write requests, transfers the TX data into the |
| * request, and submits the requests to the USB controller. This |
| * continues until either (1) there are no further packets available, or |
| * (2) there is no further data to send. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_sndpacket(FAR struct cdcacm_dev_s *priv) |
| { |
| FAR struct usbdev_ep_s *ep; |
| FAR struct usbdev_req_s *req; |
| FAR struct cdcacm_wrreq_s *wrcontainer; |
| uint16_t reqlen; |
| irqstate_t flags; |
| int len; |
| int ret = OK; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (priv == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| flags = enter_critical_section(); |
| |
| /* Use our bulk IN endpoint for the transfer */ |
| |
| ep = priv->epbulkin; |
| |
| /* Loop until either (1) we run out or write requests, or (2) |
| * cdcacm_fillrequest() is unable to fill the request with data (i.e., |
| * until there is no more data to be sent). |
| */ |
| |
| uinfo("head=%d tail=%d nwrq=%d empty=%d\n", |
| priv->serdev.xmit.head, priv->serdev.xmit.tail, |
| priv->nwrq, sq_empty(&priv->txfree)); |
| |
| /* Get the maximum number of bytes that will fit into one bulk IN request */ |
| |
| reqlen = MAX(CONFIG_CDCACM_BULKIN_REQLEN, ep->maxpacket); |
| |
| while (!sq_empty(&priv->txfree)) |
| { |
| /* Peek at the request in the container at the head of the list */ |
| |
| wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_peek(&priv->txfree); |
| req = wrcontainer->req; |
| |
| /* Fill the request with serial TX data */ |
| |
| len = cdcacm_fillrequest(priv, req->buf, reqlen); |
| if (len > 0) |
| { |
| /* Remove the empty container from the request list */ |
| |
| sq_remfirst(&priv->txfree); |
| priv->nwrq--; |
| |
| /* Then submit the request to the endpoint */ |
| |
| req->len = len; |
| req->priv = wrcontainer; |
| req->flags = USBDEV_REQFLAGS_NULLPKT; |
| ret = EP_SUBMIT(ep, req); |
| if (ret != OK) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), |
| (uint16_t)-ret); |
| break; |
| } |
| } |
| else |
| { |
| break; |
| } |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_recvpacket |
| * |
| * Description: |
| * A normal completion event was received by the read completion handler |
| * at the interrupt level (with interrupts disabled). This function handles |
| * the USB packet and provides the received data to the uart RX buffer. |
| * |
| * Assumptions: |
| * Called from the USB interrupt handler with interrupts disabled. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_recvpacket(FAR struct cdcacm_dev_s *priv, |
| FAR struct cdcacm_rdreq_s *rdcontainer) |
| { |
| FAR uart_dev_t *serdev; |
| FAR struct uart_buffer_s *recv; |
| FAR struct usbdev_req_s *req; |
| FAR uint8_t *reqbuf; |
| #ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS |
| unsigned int watermark; |
| #endif |
| uint16_t reqlen; |
| uint16_t nexthead; |
| uint16_t nbytes = 0; |
| |
| DEBUGASSERT(priv != NULL && rdcontainer != NULL); |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| DEBUGASSERT(priv->rxenabled && !priv->iactive); |
| #else |
| DEBUGASSERT(priv->rxenabled); |
| #endif |
| |
| req = rdcontainer->req; |
| DEBUGASSERT(req != NULL); |
| |
| reqbuf = &req->buf[rdcontainer->offset]; |
| reqlen = req->xfrd - rdcontainer->offset; |
| |
| uinfo("head=%d tail=%d nrdq=%d reqlen=%d\n", |
| priv->serdev.recv.head, priv->serdev.recv.tail, priv->nrdq, reqlen); |
| |
| serdev = &priv->serdev; |
| recv = &serdev->recv; |
| |
| /* Pre-calculate the head index and check for wrap around. We need to do |
| * this so that we can determine if the circular buffer will overrun |
| * BEFORE we overrun the buffer! |
| */ |
| |
| nexthead = recv->head + 1; |
| if (nexthead >= recv->size) |
| { |
| nexthead = 0; |
| } |
| |
| #ifdef CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS |
| /* Pre-calculate the watermark level that we will need to test against. |
| * Note that the range of the the upper watermark is from 1 to 99 percent |
| * and that the actual capacity of the RX buffer is (recv->size - 1). |
| */ |
| |
| watermark = CONFIG_SERIAL_IFLOWCONTROL_UPPER_WATERMARK * recv->size / 100; |
| DEBUGASSERT(watermark > 0 && watermark < (recv->size - 1)); |
| #endif |
| |
| /* Then copy data into the RX buffer until either: (1) all of the data has |
| * been copied, or (2) the RX buffer is full. |
| * |
| * NOTE: If the RX buffer becomes full, then we have overrun the serial |
| * driver and data will be lost. This is the correct behavior for a |
| * proper emulation of a serial link. It should not NAK, it should drop |
| * data like a physical serial port. |
| * |
| * If you don't like that behavior. DO NOT change it here. Instead, you |
| * should finish the implementation of RX flow control which is the only |
| * proper way to throttle a serial device. |
| */ |
| |
| while (nexthead != recv->tail && nbytes < reqlen) |
| { |
| #if defined(CONFIG_SERIAL_IFLOWCONTROL) && \ |
| defined(CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS) |
| unsigned int nbuffered; |
| |
| /* How many bytes are buffered */ |
| |
| if (recv->head >= recv->tail) |
| { |
| nbuffered = recv->head - recv->tail; |
| } |
| else |
| { |
| nbuffered = recv->size - recv->tail + recv->head; |
| } |
| |
| /* Is the level now above the watermark level that we need to report? */ |
| |
| if (nbuffered >= watermark) |
| { |
| /* Let the lower level driver know that the watermark level has |
| * been crossed. It will probably activate RX flow control. |
| */ |
| |
| if (cdcuart_rxflowcontrol(&priv->serdev, nbuffered, true)) |
| { |
| /* Low-level driver activated RX flow control, exit loop now. */ |
| |
| break; |
| } |
| } |
| #endif |
| |
| /* Copy one byte to the head of the circular RX buffer */ |
| |
| recv->buffer[recv->head] = *reqbuf++; |
| nbytes++; |
| |
| /* Increment the head index and check for wrap around */ |
| |
| recv->head = nexthead; |
| if (++nexthead >= recv->size) |
| { |
| nexthead = 0; |
| } |
| } |
| |
| #if defined(CONFIG_SERIAL_IFLOWCONTROL) && \ |
| !defined(CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS) |
| /* Check if RX buffer became full and allow serial low-level driver to |
| * pause processing. This allows proper utilization of hardware flow |
| * control when there are no watermarks. |
| */ |
| |
| if (nexthead == recv->tail) |
| { |
| cdcuart_rxflowcontrol(&priv->serdev, recv->size - 1, true); |
| } |
| #endif |
| |
| /* If data was added to the incoming serial buffer, then wake up any |
| * threads is waiting for incoming data. If we are running in an interrupt |
| * handler, then the serial driver will not run until the interrupt |
| * handler returns. |
| */ |
| |
| if (nbytes > 0) |
| { |
| uart_datareceived(serdev); |
| } |
| |
| /* Return an overrun error if the entire packet could not be transferred. */ |
| |
| if (nbytes < reqlen) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RXOVERRUN), 0); |
| rdcontainer->offset += nbytes; |
| return -ENOSPC; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_requeue_rdrequest |
| * |
| * Description: |
| * Add any pending RX packets to the upper half serial drivers RX buffer. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_requeue_rdrequest(FAR struct cdcacm_dev_s *priv, |
| FAR struct cdcacm_rdreq_s *rdcontainer) |
| { |
| FAR struct usbdev_req_s *req; |
| FAR struct usbdev_ep_s *ep; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL && rdcontainer != NULL); |
| rdcontainer->offset = 0; |
| |
| req = rdcontainer->req; |
| DEBUGASSERT(req != NULL); |
| |
| /* Requeue the read request */ |
| |
| ep = priv->epbulkout; |
| req->len = ep->maxpacket; |
| ret = EP_SUBMIT(ep, req); |
| if (ret != OK) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), |
| (uint16_t)-req->result); |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_release_rxpending |
| * |
| * Description: |
| * Add any pending RX packets to the upper half serial drivers RX buffer. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_release_rxpending(FAR struct cdcacm_dev_s *priv) |
| { |
| FAR struct cdcacm_rdreq_s *rdcontainer; |
| irqstate_t flags; |
| int ret = -EBUSY; |
| |
| /* Note that the priv->rxpending queue, priv->rxenabled, priv->iactive |
| * may be modified by interrupt level processing and, hence, interrupts |
| * must be disabled throughout the following. |
| */ |
| |
| flags = enter_critical_section(); |
| |
| /* Cancel any pending failsafe timer */ |
| |
| wd_cancel(&priv->rxfailsafe); |
| |
| /* If RX "interrupts" are enabled and if input flow control is not in |
| * effect, then pass the packet at the head of the pending RX packet list |
| * to the upper serial layer. Otherwise, let the packet continue to pend |
| * the priv->rxpending list until the upper serial layer is able to buffer |
| * it. |
| */ |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| if (priv->rxenabled && !priv->iactive) |
| #else |
| if (priv->rxenabled) |
| #endif |
| { |
| /* Process pending RX packets while the queue is not empty and while |
| * no errors occur. NOTE that the priv->rxpending queue is accessed |
| * from interrupt level processing and, hence, interrupts must be |
| * disabled throughout the following. |
| */ |
| |
| ret = OK; |
| |
| while (!sq_empty(&priv->rxpending)) |
| { |
| /* Process each packet in the priv->rxpending list */ |
| |
| rdcontainer = (FAR struct cdcacm_rdreq_s *) |
| sq_peek(&priv->rxpending); |
| DEBUGASSERT(rdcontainer != NULL); |
| |
| /* cdcacm_recvpacket() will return OK if the entire packet was |
| * successful buffered. In the case of RX buffer overrun, |
| * cdcacm_recvpacket() will return a failure (-ENOSPC) and will |
| * set the req->offset field. |
| */ |
| |
| ret = cdcacm_recvpacket(priv, rdcontainer); |
| if (ret < 0) |
| { |
| uwarn("WARNING: RX buffer full\n"); |
| break; |
| } |
| |
| /* The entire packet was processed and may be removed from the |
| * pending RX list and returned to the DCD. |
| */ |
| |
| sq_remfirst(&priv->rxpending); |
| ret = cdcacm_requeue_rdrequest(priv, rdcontainer); |
| } |
| } |
| |
| /* Restart the RX failsafe timer if there are RX packets in |
| * priv->rxpending. This could happen if either RX "interrupts" are |
| * disable, RX flow control is in effect of if the upper serial drivers |
| * RX buffer is full and cannot accept additional data. |
| * |
| * If/when the timer expires, cdcacm_release_rxpending() will be called |
| * the timer handler (at interrupt level). |
| * |
| * The timer may not be necessary, but it is a failsafe to be certain |
| * that data cannot stall in priv->rxpending. |
| */ |
| |
| if (!sq_empty(&priv->rxpending)) |
| { |
| wd_start(&priv->rxfailsafe, CDCACM_RXDELAY, |
| cdcacm_rxtimeout, (wdparm_t)priv); |
| } |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_rxtimeout |
| * |
| * Description: |
| * Timer expiration handler. Whenever cdcacm_release_rxpending() |
| * terminates with pending RX data in priv->rxpending, it will set a |
| * timer to recheck the queued RX data can be processed later. This |
| * failsafe timer may not be necessary, but this reduces my paranoia |
| * about stalls in the RX pending FIFO . |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_rxtimeout(wdparm_t arg) |
| { |
| FAR struct cdcacm_dev_s *priv = (FAR struct cdcacm_dev_s *)arg; |
| |
| DEBUGASSERT(priv != NULL); |
| cdcacm_release_rxpending(priv); |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_serialstate |
| * |
| * Description: |
| * Send the serial state message. |
| * |
| * 1. Format and send a request header with: |
| * |
| * bmRequestType: |
| * USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | |
| * USB_REQ_RECIPIENT_INTERFACE |
| * bRequest: ACM_SERIAL_STATE |
| * wValue: 0 |
| * wIndex: 0 |
| * wLength: Length of data = 2 |
| * |
| * 2. Followed by the notification data |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| static int cdcacm_serialstate(FAR struct cdcacm_dev_s *priv) |
| { |
| FAR struct usbdev_ep_s *ep; |
| FAR struct usbdev_req_s *req; |
| FAR struct cdcacm_wrreq_s *wrcontainer; |
| FAR struct cdc_notification_s *notify; |
| irqstate_t flags; |
| int ret; |
| |
| DEBUGASSERT(priv != NULL && priv->epintin != NULL); |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (priv == NULL || priv->epintin == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| usbtrace(CDCACM_CLASSAPI_FLOWCONTROL, (uint16_t)priv->serialstate); |
| |
| flags = enter_critical_section(); |
| |
| /* Use our interrupt IN endpoint for the transfer */ |
| |
| ep = priv->epintin; |
| |
| /* Remove the next container from the request list */ |
| |
| wrcontainer = (FAR struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree); |
| if (wrcontainer == NULL) |
| { |
| ret = -ENOMEM; |
| goto errout_with_flags; |
| } |
| |
| /* Decrement the count of write requests */ |
| |
| priv->nwrq--; |
| |
| /* Format the SerialState notification */ |
| |
| DEBUGASSERT(wrcontainer->req != NULL); |
| req = wrcontainer->req; |
| |
| DEBUGASSERT(req->buf != NULL); |
| notify = (FAR struct cdc_notification_s *)req->buf; |
| |
| notify->type = (USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | |
| USB_REQ_RECIPIENT_INTERFACE); |
| notify->notification = ACM_SERIAL_STATE; |
| notify->value[0] = 0; |
| notify->value[1] = 0; |
| notify->index[0] = 0; |
| notify->index[1] = 0; |
| notify->len[0] = 2; |
| notify->len[1] = 0; |
| notify->data[0] = priv->serialstate; |
| notify->data[1] = 0; |
| |
| /* Then submit the request to the endpoint */ |
| |
| req->len = SIZEOF_NOTIFICATION_S(2); |
| req->priv = wrcontainer; |
| req->flags = USBDEV_REQFLAGS_NULLPKT; |
| ret = EP_SUBMIT(ep, req); |
| |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SUBMITFAIL), (uint16_t)-ret); |
| } |
| |
| errout_with_flags: |
| |
| /* Reset all of the "irregular" notification */ |
| |
| priv->serialstate &= CDC_UART_CONSISTENT; |
| |
| leave_critical_section(flags); |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: cdcacm_resetconfig |
| * |
| * Description: |
| * Mark the device as not configured and disable all endpoints. |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_resetconfig(FAR struct cdcacm_dev_s *priv) |
| { |
| /* When the USB is pulled out, if there is an unprocessed buffer, |
| * it needs to be push them to upper half serial drivers RX buffer. |
| */ |
| |
| if (priv->nrdq != 0) |
| { |
| cdcacm_release_rxpending(priv); |
| priv->nrdq = 0; |
| } |
| |
| /* Are we configured? */ |
| |
| if (priv->config != CDCACM_CONFIGIDNONE) |
| { |
| /* Yes.. but not anymore */ |
| |
| priv->config = CDCACM_CONFIGIDNONE; |
| |
| /* Inform the "upper half" driver that there is no (functional) USB |
| * connection. |
| */ |
| |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| uart_connected(&priv->serdev, false); |
| #endif |
| |
| /* Disable endpoints. This should force completion of all pending |
| * transfers. |
| */ |
| |
| EP_DISABLE(priv->epintin); |
| EP_DISABLE(priv->epbulkin); |
| EP_DISABLE(priv->epbulkout); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_epconfigure |
| * |
| * Description: |
| * Configure one endpoint. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_epconfigure(FAR struct usbdev_ep_s *ep, |
| enum cdcacm_epdesc_e epid, bool last, |
| FAR struct usbdev_devinfo_s *devinfo, |
| bool hispeed) |
| { |
| struct usb_epdesc_s epdesc; |
| cdcacm_copy_epdesc(epid, &epdesc, devinfo, hispeed); |
| return EP_CONFIGURE(ep, &epdesc, last); |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_setconfig |
| * |
| * Description: |
| * Set the device configuration by allocating and configuring endpoints and |
| * by allocating and queue read and write requests. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_setconfig(FAR struct cdcacm_dev_s *priv, uint8_t config) |
| { |
| FAR struct usbdev_req_s *req; |
| int i; |
| int ret = 0; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (priv == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| if (config == priv->config) |
| { |
| /* Already configured -- Do nothing */ |
| |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0); |
| return 0; |
| } |
| |
| /* Discard the previous configuration data */ |
| |
| cdcacm_resetconfig(priv); |
| |
| /* Was this a request to simply discard the current configuration? */ |
| |
| if (config == CDCACM_CONFIGIDNONE) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0); |
| return 0; |
| } |
| |
| /* We only accept one configuration */ |
| |
| if (config != CDCACM_CONFIGID) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0); |
| return -EINVAL; |
| } |
| |
| /* Configure the IN interrupt endpoint */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| if (priv->usbdev->speed == USB_SPEED_HIGH) |
| { |
| ret = cdcacm_epconfigure(priv->epintin, CDCACM_EPINTIN, false, |
| &priv->devinfo, true); |
| } |
| else |
| #endif |
| { |
| ret = cdcacm_epconfigure(priv->epintin, CDCACM_EPINTIN, false, |
| &priv->devinfo, false); |
| } |
| |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0); |
| goto errout; |
| } |
| |
| priv->epintin->priv = priv; |
| |
| /* Configure the IN bulk endpoint */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| if (priv->usbdev->speed == USB_SPEED_HIGH) |
| { |
| ret = cdcacm_epconfigure(priv->epbulkin, CDCACM_EPBULKIN, false, |
| &priv->devinfo, true); |
| } |
| else |
| #endif |
| { |
| ret = cdcacm_epconfigure(priv->epbulkin, CDCACM_EPBULKIN, false, |
| &priv->devinfo, false); |
| } |
| |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0); |
| goto errout; |
| } |
| |
| priv->epbulkin->priv = priv; |
| |
| /* Configure the OUT bulk endpoint */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| if (priv->usbdev->speed == USB_SPEED_HIGH) |
| { |
| ret = cdcacm_epconfigure(priv->epbulkout, CDCACM_EPBULKOUT, true, |
| &priv->devinfo, true); |
| } |
| else |
| #endif |
| { |
| ret = cdcacm_epconfigure(priv->epbulkout, CDCACM_EPBULKOUT, true, |
| &priv->devinfo, false); |
| } |
| |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0); |
| goto errout; |
| } |
| |
| priv->epbulkout->priv = priv; |
| |
| /* Queue read requests in the bulk OUT endpoint */ |
| |
| DEBUGASSERT(priv->nrdq == 0); |
| for (i = 0; i < CONFIG_CDCACM_NRDREQS; i++) |
| { |
| req = priv->rdreqs[i].req; |
| req->callback = cdcacm_rdcomplete; |
| ret = EP_SUBMIT(priv->epbulkout, req); |
| if (ret != OK) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), |
| (uint16_t)-ret); |
| goto errout; |
| } |
| |
| priv->nrdq++; |
| } |
| |
| /* We are successfully configured */ |
| |
| priv->config = config; |
| |
| /* Inform the "upper half" driver that we are "open for business" */ |
| |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| uart_connected(&priv->serdev, true); |
| #endif |
| |
| return OK; |
| |
| errout: |
| cdcacm_resetconfig(priv); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_ep0incomplete |
| * |
| * Description: |
| * Handle completion of EP0 control operations |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_ep0incomplete(FAR struct usbdev_ep_s *ep, |
| FAR struct usbdev_req_s *req) |
| { |
| if (req->result || req->xfrd != req->len) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT), |
| (uint16_t)-req->result); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_rdcomplete |
| * |
| * Description: |
| * Handle completion of read request on the bulk OUT endpoint. This |
| * is handled like the receipt of serial data on the "UART" |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_rdcomplete(FAR struct usbdev_ep_s *ep, |
| FAR struct usbdev_req_s *req) |
| { |
| FAR struct cdcacm_rdreq_s *rdcontainer; |
| FAR struct cdcacm_dev_s *priv; |
| irqstate_t flags; |
| |
| /* Sanity check */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!ep || !ep->priv || !req) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract references to private data */ |
| |
| priv = (FAR struct cdcacm_dev_s *)ep->priv; |
| |
| /* Get the container of the read request */ |
| |
| rdcontainer = (FAR struct cdcacm_rdreq_s *)req->priv; |
| DEBUGASSERT(rdcontainer != NULL); |
| |
| /* Process the received data unless this is some unusual condition */ |
| |
| flags = enter_critical_section(); |
| switch (req->result) |
| { |
| case 0: /* Normal completion */ |
| { |
| usbtrace(TRACE_CLASSRDCOMPLETE, priv->nrdq); |
| |
| /* Place the incoming packet at the end of pending RX packet list. */ |
| |
| rdcontainer->offset = 0; |
| sq_addlast((FAR sq_entry_t *)rdcontainer, &priv->rxpending); |
| |
| /* Then process all pending RX packet starting at the head of the |
| * list |
| */ |
| |
| cdcacm_release_rxpending(priv); |
| } |
| break; |
| |
| case -ESHUTDOWN: /* Disconnection */ |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0); |
| priv->nrdq--; |
| } |
| break; |
| |
| default: /* Some other error occurred */ |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED), |
| (uint16_t)-req->result); |
| cdcacm_requeue_rdrequest(priv, rdcontainer); |
| break; |
| } |
| } |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_wrcomplete |
| * |
| * Description: |
| * Handle completion of write request. This function probably executes |
| * in the context of an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_wrcomplete(FAR struct usbdev_ep_s *ep, |
| FAR struct usbdev_req_s *req) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| FAR struct cdcacm_wrreq_s *wrcontainer; |
| irqstate_t flags; |
| |
| /* Sanity check */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!ep || !ep->priv || !req || !req->priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract references to our private data */ |
| |
| priv = (FAR struct cdcacm_dev_s *)ep->priv; |
| wrcontainer = (FAR struct cdcacm_wrreq_s *)req->priv; |
| |
| /* Return the write request to the free list */ |
| |
| flags = enter_critical_section(); |
| sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->txfree); |
| priv->nwrq++; |
| leave_critical_section(flags); |
| |
| /* Send the next packet unless this was some unusual termination |
| * condition |
| */ |
| |
| switch (req->result) |
| { |
| case OK: /* Normal completion */ |
| { |
| usbtrace(TRACE_CLASSWRCOMPLETE, priv->nwrq); |
| cdcacm_sndpacket(priv); |
| } |
| break; |
| |
| case -ESHUTDOWN: /* Disconnection */ |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRSHUTDOWN), priv->nwrq); |
| } |
| break; |
| |
| default: /* Some other error occurred */ |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED), |
| (uint16_t)-req->result); |
| } |
| break; |
| } |
| } |
| |
| /**************************************************************************** |
| * USB Class Driver Methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cdcacm_bind |
| * |
| * Description: |
| * Invoked when the driver is bound to a USB device driver |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_bind(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv = |
| ((FAR struct cdcacm_driver_s *)driver)->dev; |
| FAR struct cdcacm_wrreq_s *wrcontainer; |
| FAR struct cdcacm_rdreq_s *rdcontainer; |
| irqstate_t flags; |
| uint16_t reqlen; |
| int ret; |
| int i; |
| |
| usbtrace(TRACE_CLASSBIND, 0); |
| |
| /* Bind the structures */ |
| |
| priv->usbdev = dev; |
| |
| /* Save the reference to our private data structure in EP0 so that it |
| * can be recovered in ep0 completion events (Unless we are part of |
| * a composite device and, in that case, the composite device owns |
| * EP0). |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| dev->ep0->priv = priv; |
| #endif |
| |
| /* Preallocate control request */ |
| |
| priv->ctrlreq = usbdev_allocreq(dev->ep0, CDCACM_MXDESCLEN); |
| if (priv->ctrlreq == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0); |
| ret = -ENOMEM; |
| goto errout; |
| } |
| |
| priv->ctrlreq->callback = cdcacm_ep0incomplete; |
| |
| /* Pre-allocate all endpoints... the endpoints will not be functional |
| * until the SET CONFIGURATION request is processed in cdcacm_setconfig. |
| * This is done here because there may be calls to kmm_malloc and the SET |
| * CONFIGURATION processing probably occurs within interrupt handling |
| * logic where kmm_malloc calls will fail. |
| */ |
| |
| /* Pre-allocate the IN interrupt endpoint */ |
| |
| priv->epintin = DEV_ALLOCEP(dev, CDCACM_MKEPINTIN(&priv->devinfo), |
| true, USB_EP_ATTR_XFER_INT); |
| if (!priv->epintin) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0); |
| ret = -ENODEV; |
| goto errout; |
| } |
| |
| priv->epintin->priv = priv; |
| |
| /* Pre-allocate the IN bulk endpoint */ |
| |
| priv->epbulkin = DEV_ALLOCEP(dev, CDCACM_MKEPBULKIN(&priv->devinfo), |
| true, USB_EP_ATTR_XFER_BULK); |
| if (!priv->epbulkin) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0); |
| ret = -ENODEV; |
| goto errout; |
| } |
| |
| priv->epbulkin->priv = priv; |
| |
| /* Pre-allocate the OUT bulk endpoint */ |
| |
| priv->epbulkout = DEV_ALLOCEP(dev, CDCACM_MKEPBULKOUT(&priv->devinfo), |
| false, USB_EP_ATTR_XFER_BULK); |
| if (!priv->epbulkout) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0); |
| ret = -ENODEV; |
| goto errout; |
| } |
| |
| priv->epbulkout->priv = priv; |
| |
| /* Pre-allocate read requests. The buffer size is one full packet. */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| reqlen = CONFIG_CDCACM_EPBULKOUT_HSSIZE; |
| #else |
| reqlen = CONFIG_CDCACM_EPBULKOUT_FSSIZE; |
| #endif |
| |
| for (i = 0; i < CONFIG_CDCACM_NRDREQS; i++) |
| { |
| rdcontainer = &priv->rdreqs[i]; |
| rdcontainer->req = usbdev_allocreq(priv->epbulkout, reqlen); |
| if (rdcontainer->req == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM); |
| ret = -ENOMEM; |
| goto errout; |
| } |
| |
| rdcontainer->offset = 0; |
| rdcontainer->req->priv = rdcontainer; |
| rdcontainer->req->callback = cdcacm_rdcomplete; |
| } |
| |
| /* Pre-allocate write request containers and put in a free list. The |
| * buffer size should be larger than a full build IN packet. Otherwise, |
| * we will send a bogus null packet at the end of each packet. |
| * |
| * Pick the larger of the max packet size and the configured request size. |
| * |
| * NOTE: These write requests are sized for the bulk IN endpoint but are |
| * shared with interrupt IN endpoint which does not need a large buffer. |
| */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| reqlen = CONFIG_CDCACM_EPBULKIN_HSSIZE; |
| #else |
| reqlen = CONFIG_CDCACM_EPBULKIN_FSSIZE; |
| #endif |
| |
| if (CONFIG_CDCACM_BULKIN_REQLEN > reqlen) |
| { |
| reqlen = CONFIG_CDCACM_BULKIN_REQLEN; |
| } |
| |
| for (i = 0; i < CONFIG_CDCACM_NWRREQS; i++) |
| { |
| wrcontainer = &priv->wrreqs[i]; |
| wrcontainer->req = usbdev_allocreq(priv->epbulkin, reqlen); |
| if (wrcontainer->req == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM); |
| ret = -ENOMEM; |
| goto errout; |
| } |
| |
| wrcontainer->req->priv = wrcontainer; |
| wrcontainer->req->callback = cdcacm_wrcomplete; |
| |
| flags = enter_critical_section(); |
| sq_addlast((FAR sq_entry_t *)wrcontainer, &priv->txfree); |
| priv->nwrq++; /* Count of write requests available */ |
| leave_critical_section(flags); |
| } |
| |
| /* Report if we are selfpowered (unless we are part of a |
| * composite device) |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| #ifdef CONFIG_USBDEV_SELFPOWERED |
| DEV_SETSELFPOWERED(dev); |
| #endif |
| |
| /* And pull-up the data line for the soft connect function (unless we are |
| * part of a composite device) |
| */ |
| |
| DEV_CONNECT(dev); |
| #endif |
| return OK; |
| |
| errout: |
| cdcacm_unbind(driver, dev); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_unbind |
| * |
| * Description: |
| * Invoked when the driver is unbound from a USB device driver |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_unbind(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| FAR struct cdcacm_wrreq_s *wrcontainer; |
| FAR struct cdcacm_rdreq_s *rdcontainer; |
| irqstate_t flags; |
| int i; |
| |
| usbtrace(TRACE_CLASSUNBIND, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!driver || !dev) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = ((FAR struct cdcacm_driver_s *)driver)->dev; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); |
| return; |
| } |
| #endif |
| |
| /* Make sure that we are not already unbound */ |
| |
| if (priv != NULL) |
| { |
| /* Make sure that the endpoints have been unconfigured. If |
| * we were terminated gracefully, then the configuration should |
| * already have been reset. If not, then calling cdcacm_resetconfig |
| * should cause the endpoints to immediately terminate all |
| * transfers and return the requests to us (with result == -ESHUTDOWN) |
| */ |
| |
| cdcacm_resetconfig(priv); |
| up_mdelay(50); |
| |
| /* Free the pre-allocated control request */ |
| |
| if (priv->ctrlreq != NULL) |
| { |
| usbdev_freereq(dev->ep0, priv->ctrlreq); |
| priv->ctrlreq = NULL; |
| } |
| |
| /* Free pre-allocated read requests (which should all have |
| * been returned to the free list at this time -- we don't check) |
| */ |
| |
| DEBUGASSERT(priv->nrdq == 0); |
| for (i = 0; i < CONFIG_CDCACM_NRDREQS; i++) |
| { |
| rdcontainer = &priv->rdreqs[i]; |
| if (rdcontainer->req) |
| { |
| usbdev_freereq(priv->epbulkout, rdcontainer->req); |
| rdcontainer->req = NULL; |
| } |
| } |
| |
| /* Free write requests that are not in use (which should be all |
| * of them) |
| */ |
| |
| flags = enter_critical_section(); |
| DEBUGASSERT(priv->nwrq == CONFIG_CDCACM_NWRREQS); |
| |
| while (!sq_empty(&priv->txfree)) |
| { |
| wrcontainer = (struct cdcacm_wrreq_s *)sq_remfirst(&priv->txfree); |
| if (wrcontainer->req != NULL) |
| { |
| usbdev_freereq(priv->epbulkin, wrcontainer->req); |
| priv->nwrq--; /* Number of write requests queued */ |
| } |
| } |
| |
| DEBUGASSERT(priv->nwrq == 0); |
| leave_critical_section(flags); |
| |
| /* Free the interrupt IN endpoint */ |
| |
| if (priv->epintin) |
| { |
| DEV_FREEEP(dev, priv->epintin); |
| priv->epintin = NULL; |
| } |
| |
| /* Free the bulk OUT endpoint */ |
| |
| if (priv->epbulkout) |
| { |
| DEV_FREEEP(dev, priv->epbulkout); |
| priv->epbulkout = NULL; |
| } |
| |
| /* Free the bulk IN endpoint */ |
| |
| if (priv->epbulkin) |
| { |
| DEV_FREEEP(dev, priv->epbulkin); |
| priv->epbulkin = NULL; |
| } |
| |
| /* Clear out all data in the circular buffer */ |
| |
| priv->serdev.xmit.head = 0; |
| priv->serdev.xmit.tail = 0; |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_setup |
| * |
| * Description: |
| * Invoked for ep0 control requests. This function probably executes |
| * in the context of an interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static int cdcacm_setup(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev, |
| FAR const struct usb_ctrlreq_s *ctrl, |
| FAR uint8_t *dataout, size_t outlen) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| FAR struct usbdev_req_s *ctrlreq; |
| uint16_t value; |
| uint16_t index; |
| uint16_t len; |
| int ret = -EOPNOTSUPP; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!driver || !dev || !ctrl) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| usbtrace(TRACE_CLASSSETUP, ctrl->req); |
| priv = ((FAR struct cdcacm_driver_s *)driver)->dev; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!priv || !priv->ctrlreq) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); |
| return -ENODEV; |
| } |
| #endif |
| |
| ctrlreq = priv->ctrlreq; |
| |
| /* Extract the little-endian 16-bit values to host order */ |
| |
| value = GETUINT16(ctrl->value); |
| index = GETUINT16(ctrl->index); |
| len = GETUINT16(ctrl->len); |
| |
| uinfo("type=%02x req=%02x value=%04x index=%04x len=%04x\n", |
| ctrl->type, ctrl->req, value, index, len); |
| |
| if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD) |
| { |
| /********************************************************************** |
| * Standard Requests |
| **********************************************************************/ |
| |
| switch (ctrl->req) |
| { |
| case USB_REQ_GETDESCRIPTOR: |
| { |
| /* The value field specifies the descriptor type in the MS byte |
| * and the descriptor index in the LS byte (order is little |
| * endian) |
| */ |
| |
| switch (ctrl->value[1]) |
| { |
| /* If the serial device is used in as part of a composite |
| * device, then the device descriptor is provided by logic in |
| * the composite device implementation. |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| case USB_DESC_TYPE_DEVICE: |
| { |
| ret = usbdev_copy_devdesc(ctrlreq->buf, |
| cdcacm_getdevdesc(), |
| dev->speed); |
| } |
| break; |
| #endif |
| |
| /* If the serial device is used in as part of a composite |
| * device, then the device qualifier descriptor is provided by |
| * logic in the composite device implementation. |
| */ |
| |
| #if !defined(CONFIG_CDCACM_COMPOSITE) && defined(CONFIG_USBDEV_DUALSPEED) |
| case USB_DESC_TYPE_DEVICEQUALIFIER: |
| { |
| ret = USB_SIZEOF_QUALDESC; |
| memcpy(ctrlreq->buf, cdcacm_getqualdesc(), ret); |
| } |
| break; |
| |
| case USB_DESC_TYPE_OTHERSPEEDCONFIG: |
| #endif |
| |
| /* If the serial device is used in as part of a composite |
| * device, then the configuration descriptor is provided by |
| * logic in the composite device implementation. |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| case USB_DESC_TYPE_CONFIG: |
| { |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| ret = cdcacm_mkcfgdesc(ctrlreq->buf, &priv->devinfo, |
| dev->speed, ctrl->value[1]); |
| #else |
| ret = cdcacm_mkcfgdesc(ctrlreq->buf, &priv->devinfo); |
| #endif |
| } |
| break; |
| #endif |
| |
| /* If the serial device is used in as part of a composite |
| * device, then the language string descriptor is provided by |
| * logic in the composite device implementation. |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| case USB_DESC_TYPE_STRING: |
| { |
| /* index == language code. */ |
| |
| ret = |
| cdcacm_mkstrdesc(ctrl->value[0], |
| (FAR struct usb_strdesc_s *) |
| ctrlreq->buf); |
| } |
| break; |
| #endif |
| |
| default: |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC), |
| value); |
| } |
| break; |
| } |
| } |
| break; |
| |
| case USB_REQ_SETCONFIGURATION: |
| { |
| if (ctrl->type == 0) |
| { |
| ret = cdcacm_setconfig(priv, value); |
| } |
| } |
| break; |
| |
| /* If the serial device is used in as part of a composite device, |
| * then the overall composite class configuration is managed by logic |
| * in the composite device implementation. |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| case USB_REQ_GETCONFIGURATION: |
| { |
| if (ctrl->type == USB_DIR_IN) |
| { |
| *(FAR uint8_t *)ctrlreq->buf = priv->config; |
| ret = 1; |
| } |
| } |
| break; |
| #endif |
| |
| case USB_REQ_SETINTERFACE: |
| { |
| if (ctrl->type == USB_REQ_RECIPIENT_INTERFACE && |
| priv->config == CDCACM_CONFIGID) |
| { |
| if ((index == priv->devinfo.ifnobase && |
| value == CDCACM_NOTALTIFID) || |
| (index == (priv->devinfo.ifnobase + 1) && |
| value == CDCACM_DATAALTIFID)) |
| { |
| cdcacm_resetconfig(priv); |
| cdcacm_setconfig(priv, CDCACM_CONFIGID); |
| ret = 0; |
| } |
| } |
| } |
| break; |
| |
| case USB_REQ_GETINTERFACE: |
| { |
| if (ctrl->type == (USB_DIR_IN | USB_REQ_RECIPIENT_INTERFACE) && |
| priv->config == CDCACM_CONFIGIDNONE) |
| { |
| if ((index == priv->devinfo.ifnobase && |
| value == CDCACM_NOTALTIFID) || |
| (index == (priv->devinfo.ifnobase + 1) && |
| value == CDCACM_DATAALTIFID)) |
| { |
| *(FAR uint8_t *) ctrlreq->buf = value; |
| ret = 1; |
| } |
| else |
| { |
| ret = -EDOM; |
| } |
| } |
| } |
| break; |
| |
| default: |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ), |
| ctrl->req); |
| break; |
| } |
| } |
| |
| else if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS) |
| { |
| /********************************************************************** |
| * CDC ACM-Specific Requests |
| **********************************************************************/ |
| |
| switch (ctrl->req) |
| { |
| /* ACM_GET_LINE_CODING requests current DTE rate, stop-bits, parity, |
| * and number-of-character bits. (Optional) |
| */ |
| |
| case ACM_GET_LINE_CODING: |
| { |
| if (ctrl->type == (USB_DIR_IN | USB_REQ_TYPE_CLASS | |
| USB_REQ_RECIPIENT_INTERFACE) && |
| index == priv->devinfo.ifnobase) |
| { |
| /* Return the current line status from the private data |
| * structure. |
| */ |
| |
| memcpy(ctrlreq->buf, &priv->linecoding, |
| SIZEOF_CDC_LINECODING); |
| ret = SIZEOF_CDC_LINECODING; |
| } |
| else |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), |
| ctrl->type); |
| } |
| } |
| break; |
| |
| /* ACM_SET_LINE_CODING configures DTE rate, stop-bits, parity, and |
| * number-of-character bits. (Optional) |
| */ |
| |
| case ACM_SET_LINE_CODING: |
| { |
| if (ctrl->type == (USB_DIR_OUT | USB_REQ_TYPE_CLASS | |
| USB_REQ_RECIPIENT_INTERFACE) && |
| len == SIZEOF_CDC_LINECODING && /* dataout && len == outlen && */ |
| index == priv->devinfo.ifnobase) |
| { |
| /* Save the new line coding in the private data structure. |
| * NOTE: that this is conditional now because not all device |
| * controller drivers supported provision of EP0 OUT data |
| * with the setup command. |
| */ |
| |
| /* REVISIT */ |
| |
| if (dataout && len <= SIZEOF_CDC_LINECODING) |
| { |
| memcpy(&priv->linecoding, |
| dataout, SIZEOF_CDC_LINECODING); |
| } |
| |
| /* Respond with a zero length packet */ |
| |
| ret = 0; |
| |
| /* If there is a registered callback to receive line status |
| * info, then callout now. |
| */ |
| |
| if (priv->callback) |
| { |
| priv->callback(CDCACM_EVENT_LINECODING); |
| } |
| } |
| else |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), |
| ctrl->type); |
| } |
| } |
| break; |
| |
| /* ACM_SET_CTRL_LINE_STATE: RS-232 signal used to tell the DCE |
| * device the DTE device is now present. (Optional) |
| */ |
| |
| case ACM_SET_CTRL_LINE_STATE: |
| { |
| if (ctrl->type == (USB_DIR_OUT | USB_REQ_TYPE_CLASS | |
| USB_REQ_RECIPIENT_INTERFACE) && |
| index == priv->devinfo.ifnobase) |
| { |
| /* Save the control line state in the private data |
| * structure. Only bits 0 and 1 have meaning. Respond with |
| * a zero length packet. |
| */ |
| |
| priv->ctrlline = value & 3; |
| ret = 0; |
| |
| /* If there is a registered callback to receive control line |
| * status info, then call out now. |
| */ |
| |
| if (priv->callback) |
| { |
| priv->callback(CDCACM_EVENT_CTRLLINE); |
| } |
| } |
| else |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), |
| ctrl->type); |
| } |
| } |
| break; |
| |
| /* Sends special carrier */ |
| |
| case ACM_SEND_BREAK: |
| { |
| if (ctrl->type == (USB_DIR_OUT | USB_REQ_TYPE_CLASS | |
| USB_REQ_RECIPIENT_INTERFACE) && |
| index == priv->devinfo.ifnobase) |
| { |
| /* If there is a registered callback to handle the SendBreak |
| * request, then call out now. Respond with a zero length |
| * packet. |
| */ |
| |
| ret = 0; |
| if (priv->callback) |
| { |
| priv->callback(CDCACM_EVENT_SENDBREAK); |
| } |
| } |
| else |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), |
| ctrl->type); |
| } |
| } |
| break; |
| |
| default: |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDCLASSREQ), |
| ctrl->req); |
| break; |
| } |
| } |
| else |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type); |
| } |
| |
| /* Respond to the setup command if data was returned. On an error return |
| * value (ret < 0), the USB driver will stall. |
| */ |
| |
| if (ret >= 0) |
| { |
| /* Configure the response */ |
| |
| ctrlreq->len = MIN(len, ret); |
| ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT; |
| |
| /* Send the response -- either directly to the USB controller or |
| * indirectly in the case where this class is a member of a composite |
| * device. |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| ret = EP_SUBMIT(dev->ep0, ctrlreq); |
| #else |
| ret = composite_ep0submit(driver, dev, ctrlreq, ctrl); |
| #endif |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret); |
| ctrlreq->result = OK; |
| cdcacm_ep0incomplete(dev->ep0, ctrlreq); |
| } |
| } |
| |
| /* Returning a negative value will cause a STALL */ |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_disconnect |
| * |
| * Description: |
| * Invoked after all transfers have been stopped, when the host is |
| * disconnected. This function is probably called from the context of an |
| * interrupt handler. |
| * |
| ****************************************************************************/ |
| |
| static void cdcacm_disconnect(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| irqstate_t flags; |
| |
| usbtrace(TRACE_CLASSDISCONNECT, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!driver || !dev || !dev->ep0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = ((FAR struct cdcacm_driver_s *)driver)->dev; |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0); |
| return; |
| } |
| #endif |
| |
| /* Inform the "upper half serial driver that we have lost the USB serial |
| * connection. |
| */ |
| |
| flags = enter_critical_section(); |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| uart_connected(&priv->serdev, false); |
| #endif |
| |
| /* Reset the configuration */ |
| |
| cdcacm_resetconfig(priv); |
| |
| /* Clear out all outgoing data in the circular buffer */ |
| |
| priv->serdev.xmit.head = 0; |
| priv->serdev.xmit.tail = 0; |
| leave_critical_section(flags); |
| |
| /* Perform the soft connect function so that we will we can be |
| * re-enumerated (unless we are part of a composite device) |
| */ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| DEV_CONNECT(dev); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_suspend |
| * |
| * Description: |
| * Handle the USB suspend event. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| static void cdcacm_suspend(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| |
| usbtrace(TRACE_CLASSSUSPEND, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!driver || !dev) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = ((FAR struct cdcacm_driver_s *)driver)->dev; |
| |
| /* And let the "upper half" driver now that we are suspended */ |
| |
| uart_connected(&priv->serdev, false); |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: cdcacm_resume |
| * |
| * Description: |
| * Handle the USB resume event. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| static void cdcacm_resume(FAR struct usbdevclass_driver_s *driver, |
| FAR struct usbdev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| |
| usbtrace(TRACE_CLASSRESUME, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!driver || !dev) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = ((FAR struct cdcacm_driver_s *)driver)->dev; |
| |
| /* Are we still configured? */ |
| |
| if (priv->config != CDCACM_CONFIGIDNONE) |
| { |
| /* Yes.. let the "upper half" know that have resumed */ |
| |
| uart_connected(&priv->serdev, true); |
| } |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Serial Device Methods |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cdcuart_setup |
| * |
| * Description: |
| * This method is called the first time that the serial port is opened. |
| * |
| ****************************************************************************/ |
| |
| static int cdcuart_setup(FAR struct uart_dev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| |
| usbtrace(CDCACM_CLASSAPI_SETUP, 0); |
| |
| /* Sanity check */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!dev || !dev->priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return -EINVAL; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = (FAR struct cdcacm_dev_s *)dev->priv; |
| |
| /* Check if we have been configured */ |
| |
| if (priv->config == CDCACM_CONFIGIDNONE) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_SETUPNOTCONNECTED), 0); |
| return -ENOTCONN; |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_shutdown |
| * |
| * Description: |
| * This method is called when the serial port is closed. This operation |
| * is very simple for the USB serial back-end because the serial driver |
| * has already assured that the TX data has full drained -- it calls |
| * cdcuart_txempty() until that function returns true before calling this |
| * function. |
| * |
| ****************************************************************************/ |
| |
| static void cdcuart_shutdown(FAR struct uart_dev_s *dev) |
| { |
| usbtrace(CDCACM_CLASSAPI_SHUTDOWN, 0); |
| |
| /* Sanity check */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!dev || !dev->priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| } |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_attach |
| * |
| * Description: |
| * Does not apply to the USB serial class device |
| * |
| ****************************************************************************/ |
| |
| static int cdcuart_attach(FAR struct uart_dev_s *dev) |
| { |
| usbtrace(CDCACM_CLASSAPI_ATTACH, 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_detach |
| * |
| * Description: |
| * Does not apply to the USB serial class device |
| * |
| ****************************************************************************/ |
| |
| static void cdcuart_detach(FAR struct uart_dev_s *dev) |
| { |
| usbtrace(CDCACM_CLASSAPI_DETACH, 0); |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_ioctl |
| * |
| * Description: |
| * All ioctl calls will be routed through this method |
| * |
| ****************************************************************************/ |
| |
| static int cdcuart_ioctl(FAR struct file *filep, int cmd, unsigned long arg) |
| { |
| struct inode *inode = filep->f_inode; |
| struct cdcacm_dev_s *priv = inode->i_private; |
| FAR uart_dev_t *serdev = &priv->serdev; |
| int ret = OK; |
| |
| switch (cmd) |
| { |
| /* CAICO_REGISTERCB |
| * Register a callback for serial event notification. Argument: |
| * cdcacm_callback_t. See cdcacm_callback_t type definition below. |
| * NOTE: The callback will most likely invoked at the interrupt level. |
| * The called back function should, therefore, limit its operations to |
| * invoking some kind of IPC to handle the serial event in some normal |
| * task environment. |
| */ |
| |
| case CAIOC_REGISTERCB: |
| { |
| /* Save the new callback function */ |
| |
| priv->callback = (cdcacm_callback_t)((uintptr_t)arg); |
| } |
| break; |
| |
| /* CAIOC_GETLINECODING |
| * Get current line coding. Argument: struct cdc_linecoding_s*. |
| * See include/nuttx/usb/cdc.h for structure definition. This IOCTL |
| * should be called to get the data associated with the |
| * CDCACM_EVENT_LINECODING event). |
| */ |
| |
| case CAIOC_GETLINECODING: |
| { |
| FAR struct cdc_linecoding_s *ptr = |
| (FAR struct cdc_linecoding_s *)((uintptr_t)arg); |
| if (ptr != NULL) |
| { |
| memcpy(ptr, &priv->linecoding, sizeof(struct cdc_linecoding_s)); |
| } |
| else |
| { |
| ret = -EINVAL; |
| } |
| } |
| break; |
| |
| /* CAIOC_GETCTRLLINE |
| * Get control line status bits. Argument FAR int*. See |
| * include/nuttx/usb/cdc.h for bit definitions. This IOCTL should be |
| * called to get the data associated CDCACM_EVENT_CTRLLINE event. |
| */ |
| |
| case CAIOC_GETCTRLLINE: |
| { |
| FAR int *ptr = (FAR int *)((uintptr_t)arg); |
| if (ptr != NULL) |
| { |
| *ptr = priv->ctrlline; |
| } |
| else |
| { |
| ret = -EINVAL; |
| } |
| } |
| break; |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| /* CAIOC_NOTIFY |
| * Send a serial state to the host via the Interrupt IN endpoint. |
| * Argument: int. This includes the current state of the carrier |
| * detect, DSR, break, and ring signal. See "Table 69: UART State |
| * Bitmap Values" and CDC_UART_definitions in include/nuttx/usb/cdc.h. |
| */ |
| |
| case CAIOC_NOTIFY: |
| { |
| DEBUGASSERT(arg < UINT8_MAX); |
| |
| priv->serialstate = (uint8_t)arg; |
| ret = cdcacm_serialstate(priv); |
| } |
| break; |
| #endif |
| |
| #ifdef CONFIG_SERIAL_TERMIOS |
| case TCGETS: |
| { |
| struct termios *termiosp = (FAR struct termios *)arg; |
| |
| if (!termiosp) |
| { |
| ret = -EINVAL; |
| break; |
| } |
| |
| /* And update with flags from this layer */ |
| |
| termiosp->c_cflag = |
| ((priv->linecoding.parity != CDC_PARITY_NONE) ? PARENB : 0) | |
| ((priv->linecoding.parity == CDC_PARITY_ODD) ? PARODD : 0) | |
| ((priv->linecoding.stop == CDC_CHFMT_STOP2) ? CSTOPB : 0) | |
| CS8; |
| |
| #ifdef CONFIG_CDCACM_OFLOWCONTROL |
| /* Report state of output flow control */ |
| |
| # warning Missing logic |
| #endif |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| /* Report state of input flow control */ |
| |
| termiosp->c_cflag |= (priv->iflow) ? CRTS_IFLOW : 0; |
| #endif |
| cfsetispeed(termiosp, (speed_t)priv->linecoding.baud[3] << 24 | |
| (speed_t)priv->linecoding.baud[2] << 16 | |
| (speed_t)priv->linecoding.baud[1] << 8 | |
| (speed_t)priv->linecoding.baud[0]); |
| } |
| break; |
| |
| case TCSETS: |
| { |
| struct termios *termiosp = (FAR struct termios *)arg; |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| bool iflow; |
| #endif |
| |
| if (!termiosp) |
| { |
| ret = -EINVAL; |
| break; |
| } |
| |
| /* Update the flags we keep at this layer */ |
| |
| #ifdef CONFIG_CDCACM_OFLOWCONTROL |
| /* Handle changes to output flow control */ |
| |
| # warning Missing logic |
| #endif |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| /* Handle changes to input flow control */ |
| |
| iflow = ((termiosp->c_cflag & CRTS_IFLOW) != 0); |
| if (iflow != priv->iflow) |
| { |
| /* Check if flow control has been disabled. */ |
| |
| if (!iflow) |
| { |
| /* Flow control has been disabled. We need to make sure |
| * that DSR is set unconditionally. |
| */ |
| |
| if ((priv->serialstate & CDCACM_UART_DSR) == 0) |
| { |
| priv->serialstate |= (CDCACM_UART_DSR | CDCACM_UART_DCD); |
| ret = cdcacm_serialstate(priv); |
| } |
| |
| /* Save the new flow control setting. */ |
| |
| priv->iflow = false; |
| priv->iactive = false; |
| |
| /* During the time that flow control was disabled, incoming |
| * packets were queued in priv->rxpending. We must now |
| * process all of them (unless RX interrupts are also |
| * disabled) |
| */ |
| |
| cdcacm_release_rxpending(priv); |
| } |
| |
| /* Flow control has been enabled. */ |
| |
| else |
| { |
| /* Save the new flow control setting. */ |
| |
| priv->iflow = true; |
| priv->iactive = false; |
| |
| /* If the RX buffer is already (nearly) full, the we need to |
| * make sure the DSR is clear. |
| * |
| * NOTE: Here we assume that DSR is set so we don't check its |
| * current value nor to we handle the case where we would set |
| * DSR because the RX buffer is (nearly) empty! |
| */ |
| |
| if (priv->upper) |
| { |
| priv->serialstate &= ~CDCACM_UART_DSR; |
| priv->serialstate |= CDCACM_UART_DCD; |
| ret = cdcacm_serialstate(priv); |
| |
| /* Input flow control is now active */ |
| |
| priv->iactive = true; |
| } |
| } |
| |
| /* RX "interrupts are no longer disabled */ |
| |
| priv->rxenabled = true; |
| } |
| #endif |
| } |
| break; |
| #endif |
| |
| /* Get the number of bytes that may be read from the RX buffer (without |
| * waiting) |
| */ |
| |
| case FIONREAD: |
| { |
| int count; |
| irqstate_t flags = enter_critical_section(); |
| |
| /* Determine the number of bytes available in the RX buffer. */ |
| |
| if (serdev->recv.tail <= serdev->recv.head) |
| { |
| count = serdev->recv.head - serdev->recv.tail; |
| } |
| else |
| { |
| count = serdev->recv.size - |
| (serdev->recv.tail - serdev->recv.head); |
| } |
| |
| leave_critical_section(flags); |
| |
| *(FAR int *)arg = count; |
| ret = 0; |
| } |
| break; |
| |
| /* Get the number of bytes that have been written to the TX buffer. */ |
| |
| case FIONWRITE: |
| { |
| int count; |
| irqstate_t flags = enter_critical_section(); |
| |
| /* Determine the number of bytes waiting in the TX buffer. */ |
| |
| if (serdev->xmit.tail <= serdev->xmit.head) |
| { |
| count = serdev->xmit.head - serdev->xmit.tail; |
| } |
| else |
| { |
| count = serdev->xmit.size - |
| (serdev->xmit.tail - serdev->xmit.head); |
| } |
| |
| leave_critical_section(flags); |
| |
| *(FAR int *)arg = count; |
| ret = 0; |
| } |
| break; |
| |
| /* Get the number of free bytes in the TX buffer */ |
| |
| case FIONSPACE: |
| { |
| int count; |
| irqstate_t flags = enter_critical_section(); |
| |
| /* Determine the number of bytes free in the TX buffer */ |
| |
| if (serdev->xmit.head < serdev->xmit.tail) |
| { |
| count = serdev->xmit.tail - serdev->xmit.head - 1; |
| } |
| else |
| { |
| count = serdev->xmit.size - |
| (serdev->xmit.head - serdev->xmit.tail) - 1; |
| } |
| |
| leave_critical_section(flags); |
| |
| *(FAR int *)arg = count; |
| ret = 0; |
| } |
| break; |
| |
| default: |
| ret = -ENOTTY; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_rxint |
| * |
| * Description: |
| * Called by the serial driver to enable or disable RX interrupts. We, of |
| * course, have no RX interrupts but must behave consistently. This method |
| * is called under the conditions: |
| * |
| * 1. With enable==true when the port is opened (just after cdcuart_setup |
| * and cdcuart_attach are called called) |
| * 2. With enable==false while transferring data from the RX buffer |
| * 2. With enable==true while waiting for more incoming data |
| * 3. With enable==false when the port is closed (just before |
| * cdcuart_detach and cdcuart_shutdown are called). |
| * |
| * Assumptions: |
| * Called from the serial upper-half driver running on the thread of |
| * execution of the caller of the driver or, possibly, on from the |
| * USB interrupt handler (at least for the case where the RX interrupt |
| * is disabled) |
| * |
| ****************************************************************************/ |
| |
| static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| irqstate_t flags; |
| |
| usbtrace(CDCACM_CLASSAPI_RXINT, (uint16_t)enable); |
| |
| /* Sanity check */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!dev || !dev->priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = (FAR struct cdcacm_dev_s *)dev->priv; |
| |
| /* We need exclusive access to the RX buffer and private structure |
| * in the following. |
| */ |
| |
| flags = enter_critical_section(); |
| if (enable) |
| { |
| /* RX "interrupts" are enabled. Is this a transition from disabled |
| * to enabled state? |
| */ |
| |
| if (!priv->rxenabled) |
| { |
| /* Yes.. RX "interrupts are no longer disabled */ |
| |
| priv->rxenabled = true; |
| } |
| |
| /* During the time that RX interrupts was disabled, incoming |
| * packets were queued in priv->rxpending. We must now process |
| * all of them (unless flow control is enabled) |
| * |
| * NOTE: This action may cause this function to be re-entered |
| * with enable == false , anyway the pend-list should be flushed |
| */ |
| |
| cdcacm_release_rxpending(priv); |
| } |
| |
| /* RX "interrupts" are disabled. Nothing special needs to be done on a |
| * transition from the enabled to the disabled state. |
| */ |
| |
| else |
| { |
| priv->rxenabled = false; |
| } |
| |
| leave_critical_section(flags); |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_rxflowcontrol |
| * |
| * Description: |
| * Called when Rx buffer is full (or exceeds configured watermark levels |
| * if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is defined). |
| * Return true if UART activated RX flow control to block more incoming |
| * data |
| * |
| * Input Parameters: |
| * dev - UART device instance |
| * nbuffered - the number of characters currently buffered |
| * (if CONFIG_SERIAL_IFLOWCONTROL_WATERMARKS is |
| * not defined the value will be 0 for an empty buffer or the |
| * defined buffer size for a full buffer) |
| * upper - true indicates the upper watermark was crossed where |
| * false indicates the lower watermark has been crossed |
| * |
| * Returned Value: |
| * true if RX flow control activated. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_SERIAL_IFLOWCONTROL |
| static bool cdcuart_rxflowcontrol(FAR struct uart_dev_s *dev, |
| unsigned int nbuffered, bool upper) |
| { |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| FAR struct cdcacm_dev_s *priv; |
| |
| /* Sanity check */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (dev == NULL || dev->priv == NULL) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return false; |
| } |
| #endif |
| |
| /* Extract reference to private data */ |
| |
| priv = (FAR struct cdcacm_dev_s *)dev->priv; |
| |
| /* Is input flow control enabled? */ |
| |
| priv->upper = upper; |
| if (priv->iflow) |
| { |
| /* Yes.. Set DSR (TX carrier) if the lower water mark has been crossed |
| * or clear it if the upper water mark has been crossed. |
| */ |
| |
| if (upper) |
| { |
| /* Don't do anything unless this results in a change in the |
| * setting of DSR. |
| */ |
| |
| if ((priv->serialstate & CDCACM_UART_DSR) != 0) |
| { |
| /* Clear DSR (set DCD in any case). */ |
| |
| priv->serialstate &= ~CDCACM_UART_DSR; |
| priv->serialstate |= CDCACM_UART_DCD; |
| |
| /* And send the SerialState message. |
| * REVISIT: Error return case. Would an error mean DSR is not |
| * set? |
| */ |
| |
| cdcacm_serialstate(priv); |
| } |
| |
| /* Flow control is active */ |
| |
| priv->iactive = true; |
| } |
| |
| /* Lower watermark crossing. Don't do anything unless this results in |
| * a change in the setting of DSR. |
| */ |
| |
| else |
| { |
| /* Flow control is not active (Needed before calling |
| * cdcacm_release_rxpending()) |
| */ |
| |
| priv->iactive = false; |
| |
| /* Set DSR if it is not already set */ |
| |
| if ((priv->serialstate & CDCACM_UART_DSR) == 0) |
| { |
| priv->serialstate |= (CDCACM_UART_DSR | CDCACM_UART_DCD); |
| |
| /* And send the SerialState message. |
| * REVISIT: Error return case. Would an error mean DSR is |
| * still clear? |
| */ |
| |
| cdcacm_serialstate(priv); |
| } |
| |
| /* During the time that flow control ws disabled, incoming packets |
| * were queued in priv->rxpending. We must now process all of |
| * them (unless RX interrupts becomes enabled) |
| * |
| * NOTE: This action may cause this function to be re-entered with |
| * upper == false. |
| */ |
| |
| cdcacm_release_rxpending(priv); |
| } |
| } |
| else |
| { |
| /* Flow control is disabled ... DSR must be set */ |
| |
| if ((priv->serialstate & CDCACM_UART_DSR) == 0) |
| { |
| /* Set DSR and DCD */ |
| |
| priv->serialstate |= (CDCACM_UART_DSR | CDCACM_UART_DCD); |
| |
| /* And send the SerialState message |
| * REVISIT: Error return case. Would an error mean DSR is still |
| * not set? |
| */ |
| |
| cdcacm_serialstate(priv); |
| |
| /* Flow control is not active */ |
| |
| priv->iactive = false; |
| } |
| } |
| |
| /* Return true flow control is active */ |
| |
| return priv->iactive; |
| #else |
| |
| return false; |
| #endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: cdcuart_txint |
| * |
| * Description: |
| * Called by the serial driver to enable or disable TX interrupts. We, of |
| * course, have no TX interrupts but must behave consistently. Initially, |
| * TX interrupts are disabled. This method is called under the conditions: |
| * |
| * 1. With enable==false while transferring data into the TX buffer |
| * 2. With enable==true when data may be taken from the buffer. |
| * 3. With enable==false when the TX buffer is empty |
| * |
| ****************************************************************************/ |
| |
| static void cdcuart_txint(FAR struct uart_dev_s *dev, bool enable) |
| { |
| FAR struct cdcacm_dev_s *priv; |
| |
| usbtrace(CDCACM_CLASSAPI_TXINT, (uint16_t)enable); |
| |
| /* Sanity checks */ |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!dev || !dev->priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return; |
| } |
| #endif |
| |
| /* Extract references to private data */ |
| |
| priv = (FAR struct cdcacm_dev_s *)dev->priv; |
| |
| /* If the new state is enabled and if there is data in the XMIT buffer, |
| * send the next packet now. |
| */ |
| |
| uinfo("enable=%d head=%d tail=%d\n", |
| enable, priv->serdev.xmit.head, priv->serdev.xmit.tail); |
| |
| if (enable && priv->serdev.xmit.head != priv->serdev.xmit.tail) |
| { |
| cdcacm_sndpacket(priv); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_txempty |
| * |
| * Description: |
| * Return true when all data has been sent. This is called from the |
| * serial driver when the driver is closed. It will call this API |
| * periodically until it reports true. NOTE that the serial driver takes |
| * all responsibility for flushing TX data through the hardware so we can |
| * be a bit sloppy about that. |
| * |
| ****************************************************************************/ |
| |
| static bool cdcuart_txempty(FAR struct uart_dev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv = (FAR struct cdcacm_dev_s *)dev->priv; |
| |
| usbtrace(CDCACM_CLASSAPI_TXEMPTY, 0); |
| |
| #ifdef CONFIG_DEBUG_FEATURES |
| if (!priv) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0); |
| return true; |
| } |
| #endif |
| |
| /* When all of the allocated write requests have been returned to the |
| * txfree, then there is no longer any TX data in flight. |
| */ |
| |
| return priv->nwrq >= CONFIG_CDCACM_NWRREQS; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcuart_release |
| * |
| * Description: |
| * This is called to release some resource about the device when device |
| * was close and unregistered. |
| * |
| ****************************************************************************/ |
| |
| static int cdcuart_release(FAR struct uart_dev_s *dev) |
| { |
| FAR struct cdcacm_dev_s *priv = (FAR struct cdcacm_dev_s *)dev->priv; |
| |
| usbtrace(CDCACM_CLASSAPI_RELEASE, 0); |
| |
| /* And free the memory resources. */ |
| |
| wd_cancel(&priv->rxfailsafe); |
| kmm_free(priv); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: cdcacm_classobject |
| * |
| * Description: |
| * Register USB serial port (and USB serial console if so configured) and |
| * return the class object. |
| * |
| * Input Parameters: |
| * minor - Device minor number. E.g., minor 0 would correspond to |
| * /dev/ttyACM0. |
| * classdev - The location to return the CDC serial class' device |
| * instance. |
| * |
| * Returned Value: |
| * A pointer to the allocated class object (NULL on failure). |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| static |
| #endif |
| int cdcacm_classobject(int minor, FAR struct usbdev_devinfo_s *devinfo, |
| FAR struct usbdevclass_driver_s **classdev) |
| { |
| FAR struct cdcacm_alloc_s *alloc; |
| FAR struct cdcacm_dev_s *priv; |
| FAR struct cdcacm_driver_s *drvr; |
| char devname[CDCACM_DEVNAME_SIZE]; |
| int ret; |
| |
| /* Allocate the structures needed */ |
| |
| alloc = (FAR struct cdcacm_alloc_s *) |
| kmm_malloc(sizeof(struct cdcacm_alloc_s)); |
| |
| if (!alloc) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0); |
| return -ENOMEM; |
| } |
| |
| /* Convenience pointers into the allocated blob */ |
| |
| priv = &alloc->dev; |
| drvr = &alloc->drvr; |
| |
| /* Initialize the USB serial driver structure */ |
| |
| memset(priv, 0, sizeof(struct cdcacm_dev_s)); |
| sq_init(&priv->txfree); |
| sq_init(&priv->rxpending); |
| |
| priv->minor = minor; |
| |
| /* Save the caller provided device description (composite only) */ |
| |
| memcpy(&priv->devinfo, devinfo, |
| sizeof(struct usbdev_devinfo_s)); |
| |
| #ifdef CONFIG_CDCACM_IFLOWCONTROL |
| /* SerialState */ |
| |
| priv->serialstate = (CDCACM_UART_DCD | CDCACM_UART_DSR); |
| #endif |
| |
| /* Fake line status */ |
| |
| priv->linecoding.baud[0] = (115200) & 0xff; /* Baud=115200 */ |
| priv->linecoding.baud[1] = (115200 >> 8) & 0xff; |
| priv->linecoding.baud[2] = (115200 >> 16) & 0xff; |
| priv->linecoding.baud[3] = (115200 >> 24) & 0xff; |
| priv->linecoding.stop = CDC_CHFMT_STOP1; /* One stop bit */ |
| priv->linecoding.parity = CDC_PARITY_NONE; /* No parity */ |
| priv->linecoding.nbits = 8; /* 8 data bits */ |
| |
| /* Initialize the serial driver sub-structure */ |
| |
| /* The initial state is disconnected */ |
| |
| #ifdef CONFIG_SERIAL_REMOVABLE |
| priv->serdev.disconnected = true; |
| #endif |
| priv->serdev.recv.size = CONFIG_CDCACM_RXBUFSIZE; |
| priv->serdev.recv.buffer = priv->rxbuffer; |
| priv->serdev.xmit.size = CONFIG_CDCACM_TXBUFSIZE; |
| priv->serdev.xmit.buffer = priv->txbuffer; |
| priv->serdev.ops = &g_uartops; |
| priv->serdev.priv = priv; |
| |
| /* Initialize the USB class driver structure */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| drvr->drvr.speed = USB_SPEED_HIGH; |
| #else |
| drvr->drvr.speed = USB_SPEED_FULL; |
| #endif |
| drvr->drvr.ops = &g_driverops; |
| drvr->dev = priv; |
| |
| /* Register the USB serial console */ |
| |
| #ifdef CONFIG_CDCACM_CONSOLE |
| if (minor == 0) |
| { |
| priv->serdev.isconsole = true; |
| |
| ret = uart_register("/dev/console", &priv->serdev); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONSOLEREGISTER), |
| (uint16_t)-ret); |
| goto errout_with_class; |
| } |
| } |
| #endif |
| |
| /* Register the CDC/ACM TTY device */ |
| |
| snprintf(devname, sizeof(devname), CDCACM_DEVNAME_FORMAT, minor); |
| ret = uart_register(devname, &priv->serdev); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTREGISTER), |
| (uint16_t)-ret); |
| goto errout_with_class; |
| } |
| |
| *classdev = &drvr->drvr; |
| return OK; |
| |
| errout_with_class: |
| kmm_free(alloc); |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_initialize |
| * |
| * Description: |
| * Register USB serial port (and USB serial console if so configured). |
| * |
| * Input Parameters: |
| * minor - Device minor number. E.g., minor 0 would correspond to |
| * /dev/ttyACM0. |
| * handle - An optional opaque reference to the CDC/ACM class object that |
| * may subsequently be used with cdcacm_uninitialize(). |
| * |
| * Returned Value: |
| * Zero (OK) means that the driver was successfully registered. On any |
| * failure, a negated errno value is returned. |
| * |
| ****************************************************************************/ |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| int cdcacm_initialize(int minor, FAR void **handle) |
| { |
| FAR struct usbdevclass_driver_s *drvr = NULL; |
| struct usbdev_devinfo_s devinfo; |
| int ret; |
| |
| memset(&devinfo, 0, sizeof(struct usbdev_devinfo_s)); |
| |
| /* Interfaces. |
| * |
| * ifnobase must be provided by board-specific logic |
| */ |
| |
| devinfo.ninterfaces = CDCACM_NINTERFACES; /* Number of interfaces in the configuration */ |
| |
| /* Strings. |
| * |
| * strbase must be provided by board-specific logic |
| */ |
| |
| devinfo.nstrings = CDCACM_NSTRIDS; /* Number of Strings */ |
| |
| /* Endpoints. |
| * |
| * Endpoint numbers must be provided by board-specific logic when |
| * CDC/ACM is used in a composite device. |
| */ |
| |
| devinfo.nendpoints = CDCACM_NUM_EPS; |
| devinfo.epno[CDCACM_EP_INTIN_IDX] = CONFIG_CDCACM_EPINTIN; |
| devinfo.epno[CDCACM_EP_BULKIN_IDX] = CONFIG_CDCACM_EPBULKIN; |
| devinfo.epno[CDCACM_EP_BULKOUT_IDX] = CONFIG_CDCACM_EPBULKOUT; |
| |
| /* Get an instance of the serial driver class object */ |
| |
| ret = cdcacm_classobject(minor, &devinfo, &drvr); |
| if (ret == OK) |
| { |
| /* Register the USB serial class driver */ |
| |
| ret = usbdev_register(drvr); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), |
| (uint16_t)-ret); |
| } |
| } |
| |
| /* Return the driver instance (if any) if the caller has requested it |
| * by provided a pointer to the location to return it. |
| */ |
| |
| if (handle) |
| { |
| *handle = (FAR void *)drvr; |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Name: cdcacm_uninitialize |
| * |
| * Description: |
| * Un-initialize the USB storage class driver. This function is used |
| * internally by the USB composite driver to uninitialize the CDC/ACM |
| * driver. This same interface is available (with an untyped input |
| * parameter) when the CDC/ACM driver is used standalone. |
| * |
| * Input Parameters: |
| * There is one parameter, it differs in typing depending upon whether the |
| * CDC/ACM driver is an internal part of a composite device, or a |
| * standalone USB driver: |
| * |
| * classdev - The class object returned by cdcacm_classobject() |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| void cdcacm_uninitialize(FAR struct usbdevclass_driver_s *classdev) |
| { |
| FAR struct cdcacm_driver_s *drvr = (FAR struct cdcacm_driver_s *)classdev; |
| FAR struct cdcacm_dev_s *priv = drvr->dev; |
| char devname[CDCACM_DEVNAME_SIZE]; |
| int ret; |
| |
| #ifndef CONFIG_CDCACM_COMPOSITE |
| usbdev_unregister(&drvr->drvr); |
| #endif |
| |
| /* Un-register the CDC/ACM TTY device */ |
| |
| snprintf(devname, sizeof(devname), CDCACM_DEVNAME_FORMAT, priv->minor); |
| ret = unregister_driver(devname); |
| if (ret < 0) |
| { |
| usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UARTUNREGISTER), |
| (uint16_t)-ret); |
| } |
| } |
| |
| /**************************************************************************** |
| * Name: cdcacm_get_composite_devdesc |
| * |
| * Description: |
| * Helper function to fill in some constants into the composite |
| * configuration struct. |
| * |
| * Input Parameters: |
| * dev - Pointer to the configuration struct we should fill |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_USBDEV_COMPOSITE) && defined(CONFIG_CDCACM_COMPOSITE) |
| void cdcacm_get_composite_devdesc(struct composite_devdesc_s *dev) |
| { |
| memset(dev, 0, sizeof(struct composite_devdesc_s)); |
| |
| /* The callback functions for the CDC/ACM class. |
| * |
| * classobject() and uninitialize() must be provided by board-specific |
| * logic |
| */ |
| |
| dev->mkconfdesc = cdcacm_mkcfgdesc; |
| dev->mkstrdesc = cdcacm_mkstrdesc; |
| |
| dev->nconfigs = CDCACM_NCONFIGS; /* Number of configurations supported */ |
| dev->configid = CDCACM_CONFIGID; /* The only supported configuration ID */ |
| |
| /* Let the construction function calculate the size of config descriptor */ |
| |
| #ifdef CONFIG_USBDEV_DUALSPEED |
| dev->cfgdescsize = cdcacm_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0); |
| #else |
| dev->cfgdescsize = cdcacm_mkcfgdesc(NULL, NULL); |
| #endif |
| |
| /* Board-specific logic must provide the device minor */ |
| |
| /* Interfaces. |
| * |
| * ifnobase must be provided by board-specific logic |
| */ |
| |
| dev->devinfo.ninterfaces = CDCACM_NINTERFACES; /* Number of interfaces in the configuration */ |
| |
| /* Strings. |
| * |
| * strbase must be provided by board-specific logic |
| */ |
| |
| dev->devinfo.nstrings = CDCACM_NSTRIDS; /* Number of Strings */ |
| |
| /* Endpoints. |
| * |
| * Endpoint numbers must be provided by board-specific logic. |
| */ |
| |
| dev->devinfo.nendpoints = CDCACM_NUM_EPS; |
| } |
| #endif |