diff --git a/drivers/usbdev/cdcacm.c b/drivers/usbdev/cdcacm.c index 6440bb2924..6e0b331f40 100644 --- a/drivers/usbdev/cdcacm.c +++ b/drivers/usbdev/cdcacm.c @@ -207,7 +207,6 @@ static void cdcuart_rxint(FAR struct uart_dev_s *dev, bool enable); 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); diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index d21d54bab9..b068020165 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -85,6 +85,103 @@ config USBHOST_MSC Enable support for the keyboard class driver. This also depends on NFILE_DESCRIPTORS > 0 && SCHED_WORKQUEUE=y +config USBHOST_CDCACM + bool "CDC/ACM support" + default n + depends on USBHOST_HAVE_ASYNCH && !USBHOST_BULK_DISABLE && !USBHOST_INT_DISABLE + select USBHOST_ASYNCH + ---help--- + Select this option to build in host support for CDC/ACM serial + devices. + +if USBHOST_CDCACM + +config USBHOST_CDCACM_NTDELAY + int "CDC/ACM notification polling interval (MSec)" + default 400 + ---help--- + On higher end host controllers (OHCI and EHCI), the asynchronous, + interrupt IN transfers will pend until data is available from the + hub. On lower end host controllers (like STM32 and EFM32), the + transfer will fail immediately when the device NAKs the first + attempted interrupt IN transfer (with result == EGAIN) and the hub + class driver will fall back to polling the hub. + + For the case of the higher end controllers, this polling interval + is not critical since it would really only be used in the event of + failures to communicate with the hub. + + But for the lower end host controllers, the asynchronous transfers + are ineffective and this polling interval becomes a critical + parameter that must be tuned to tradeoff CPU usage with + responsiveness to hub-related events (It would, I suppose, be more + efficient to use synchronous transfers with these lower end host + controllers). + +config USBHOST_CDCACM_RXDELAY + int "RX poll delay (MSec) + default 200 + ---help--- + When the CDC/ACM device is inactive, the host must poll it at this + rate in order to discover if it has serial data to send to us. + +config USBHOST_CDCACM_TXDELAY + int "TX poll delay (MSec) + default 200 + ---help--- + When the appellation is inactive, the host must poll it at this + rate in order to discover if it has serial data to send to to the + device. + +config USBHOST_CDCACM_NPREALLOC + int "Preallocated state" + default 0 + ---help--- + If this setting is zero, the CDC/ACM class driver will allocate + memory as needed for CDC/ACM device state. If this value is non- + zero, then it provides a number of preallocated CDC/ACM state + structures. This increases the static size of the code image, but + eliminates all, direct, run-time allocations by the driver. + +config USBHOST_CDCACM_BAUD + int "Initialize CDC/ACM BAUD" + default 115200 + +config USBHOST_CDCACM_PARITY + int "Initialize CDC/ACM parity" + default 0 + range 0 2 + ---help--- + Initialize CDC/ACM parity. 0=None, 1=Odd, 2=Even. Default: None + +config USBHOST_CDCACM_BITS + int "Initialize CDC/ACM number of bits" + default 8 + ---help--- + Initialize CDC/ACM number of bits. Default: 8 + +config USBHOST_CDCACM_2STOP + int "Initialize CDC/ACM two stop bits" + default 0 + ---help--- + 0=1 stop bit, 1=Two stop bits. Default: 1 stop bit + +config USBHOST_CDCACM_RXBUFSIZE + int "Serial RX buffer size" + default 128 + ---help--- + This is the size of the serial buffer that will be used to hold + received data. + +config USBHOST_CDCACM_TXBUFSIZE + int "Serial TX buffer size" + default 128 + ---help--- + This is the size of the serial buffer that will be used to hold + data waiting for tranmission. + +endif # USBHOST_CDCACM + config USBHOST_HIDKBD bool "HID Keyboard Class Support" default n diff --git a/drivers/usbhost/Make.defs b/drivers/usbhost/Make.defs index 859f5ad83f..9058b0eb07 100644 --- a/drivers/usbhost/Make.defs +++ b/drivers/usbhost/Make.defs @@ -50,6 +50,10 @@ ifeq ($(CONFIG_USBHOST_MSC),y) CSRCS += usbhost_storage.c endif +ifeq ($(CONFIG_USBHOST_CDCACM),y) +CSRCS += usbhost_cdcacm.c +endif + ifeq ($(CONFIG_USBHOST_HIDKBD),y) CSRCS += usbhost_hidkbd.c endif diff --git a/drivers/usbhost/usbhost_cdcacm.c b/drivers/usbhost/usbhost_cdcacm.c new file mode 100644 index 0000000000..5cda9a3fda --- /dev/null +++ b/drivers/usbhost/usbhost_cdcacm.c @@ -0,0 +1,2607 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_cdcacm.c + * + * Copyright (C) 2015 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * 2. 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. + * 3. Neither the name NuttX 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef CONFIG_USBHOST_CDCACM + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************/ + +#ifndef CONFIG_USBHOST +# warning USB host support not enabled (CONFIG_USBHOST) +#endif + +#ifdef CONFIG_USBHOST_BULK_DISABLE +# warning USB bulk endpoint support is disabled (CONFIG_USBHOST_BULK_DISABLE) +#endif + +#ifdef CONFIG_USBHOST_INT_DISABLE +# warning USB interrupt endpoint support is disabled (CONFIG_USBHOST_INT_DISABLE) +#endif + +#if !defined(CONFIG_SCHED_WORKQUEUE) +# warning Worker thread support is required (CONFIG_SCHED_WORKQUEUE) +#else +# ifndef CONFIG_SCHED_HPWORK +# warning High priority work thread support is required (CONFIG_SCHED_HPWORK) +# endif +# ifndef CONFIG_SCHED_LPWORK +# warning Low priority work thread support is required (CONFIG_SCHED_LPWORK) +# endif +# if CONFIG_SCHED_LPNTHREADS < 2 +# warning Multiple low priority work threads recommended for performance (CONFIG_SCHED_LPNTHREADS > 1) +# endif +#endif + +#ifndef CONFIG_USBHOST_ASYNCH +# warning Asynchronous transfer support is required (CONFIG_USBHOST_ASYNCH) +#endif + + +#ifdef CONFIG_USBHOST_CDCACM_NTDELAY +# define USBHOST_CDCACM_NTDELAY MSEC2TICK(CONFIG_USBHOST_CDCACM_NTDELAY) +#else +# define USBHOST_CDCACM_NTDELAY MSEC2TICK(200) +#endif + +#ifdef CONFIG_USBHOST_CDCACM_RXDELAY +# define USBHOST_CDCACM_RXDELAY MSEC2TICK(CONFIG_USBHOST_CDCACM_RXDELAY) +#else +# define USBHOST_CDCACM_RXDELAY MSEC2TICK(200) +#endif + +#ifdef CONFIG_USBHOST_CDCACM_TXDELAY +# define USBHOST_CDCACM_TXDELAY MSEC2TICK(CONFIG_USBHOST_CDCACM_TXDELAY) +#else +# define USBHOST_CDCACM_TXDELAY MSEC2TICK(200) +#endif + +/* If the create() method is called by the USB host device driver from an + * interrupt handler, then it will be unable to call kmm_malloc() in order to + * allocate a new class instance. If the create() method is called from the + * interrupt level, then class instances must be pre-allocated. + */ + +#ifndef CONFIG_USBHOST_CDCACM_NPREALLOC +# define CONFIG_USBHOST_CDCACM_NPREALLOC 0 +#endif + +#if CONFIG_USBHOST_CDCACM_NPREALLOC > 32 +# error Currently limited to 32 devices /dev/ttyACM[n] +#endif + +#ifndef CONFIG_USBHOST_CDCACM_RXBUFSIZE +# define CONFIG_USBHOST_CDCACM_RXBUFSIZE 128 +#endif + +#ifndef CONFIG_USBHOST_CDCACM_TXBUFSIZE +# define CONFIG_USBHOST_CDCACM_TXBUFSIZE 128 +#endif + +/* Initial line coding */ + +#ifndef CONFIG_USBHOST_CDCACM_BAUD +# define CONFIG_USBHOST_CDCACM_BAUD 115200 +#endif + +#ifndef CONFIG_USBHOST_CDCACM_PARITY +# define CONFIG_USBHOST_CDCACM_PARITY 0 +#endif + +#ifndef CONFIG_USBHOST_CDCACM_BITS +# define CONFIG_USBHOST_CDCACM_BITS 8 +#endif + +#ifndef CONFIG_USBHOST_CDCACM_2STOP +# define CONFIG_USBHOST_CDCACM_2STOP 0 +#endif + +/* Driver support ***********************************************************/ +/* This format is used to construct the /dev/sd[n] device driver path. It + * defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/ttyACM%d" +#define DEV_NAMELEN 16 + +#define MAX_NOTIFICATION 32 + +/* Used in usbhost_connect() */ + +#define USBHOST_IFFOUND 0x01 /* Interface found */ +#define USBHOST_BINFOUND 0x02 /* Bulk IN interface found */ +#define USBHOST_BOUTFOUND 0x04 /* Bulk OUT interface found */ +#define USBHOST_IINFOUND 0x08 /* Interrupt IN interface found */ +#define USBHOST_MINFOUND 0x07 /* Minimum things needed */ +#define USBHOST_ALLFOUND 0x0f /* All configuration things */ + +#define USBHOST_MAX_CREFS INT16_MAX /* Max cref count before signed overflow */ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This structure contains the internal, private state of the USB host + * CDC/ACM class. + */ + +struct usbhost_cdcacm_s +{ + /* This is the externally visible portion of the state. The usbclass must + * the first element of the structure. It is then cast compatible with + * struct usbhost_cdcacm_s. + */ + + struct usbhost_class_s usbclass; + + /* This is the standard of the lower-half serial interface. It is not + * the first element of the structure, but includes a pointer back to the + * the beginning of this structure. + */ + + struct uart_dev_s uartdev; + + /* The remainder of the fields are provide to the CDC/ACM class */ + + volatile bool disconnected; /* TRUE: Device has been disconnected */ + bool stop2; /* True: 2 stop bits (for line coding) */ + bool dsr; /* State of transmission carrier */ + bool dcd; /* State of receiver carrier detection */ + bool txena; /* True: TX "interrupts" enabled */ + bool rxena; /* True: RX "interrupts" enabled */ +#ifdef CONFIG_SERIAL_IFLOWCONTROL + bool iflow; /* True: Input flow control (RTS) enabled */ + bool rts; /* True: Input flow control is effect */ +#endif +#ifdef CONFIG_SERIAL_OFLOWCONTROL + bool oflow; /* True: Output flow control (CTS) enabled */ +#endif + uint8_t minor; /* Minor number identifying the /dev/ttyACM[n] device */ + uint8_t ifno; /* Interface number */ + uint8_t nbits; /* Number of bits (for line encoding) */ + uint8_t parity; /* Parity (for line encoding) */ + uint16_t pktsize; /* Allocated size of transfer buffers */ + uint16_t nrxbytes; /* Number of bytes in the RX packet buffer */ + uint16_t rxndx; /* Index to the next byte in the RX packet buffer */ + int16_t crefs; /* Reference count on the driver instance */ + int16_t nbytes; /* The number of bytes actually transferred */ + sem_t exclsem; /* Used to maintain mutual exclusive access */ + struct work_s ntwork; /* For asynchronous notification work */ + struct work_s rxwork; /* For RX packet work */ + struct work_s txwork; /* For TX packet work */ + FAR uint8_t *ctrlreq; /* Allocated ctrl request structure */ + FAR uint8_t *linecode; /* The allocated buffer for line encoding */ + FAR uint8_t *notification; /* The allocated buffer for async notifications */ + FAR uint8_t *inbuf; /* Allocated RX buffer for the Bulk IN endpoint */ + FAR uint8_t *outbuf; /* Allocated TX buffer for the Bulk OUT endpoint */ + uint32_t baud; /* Current baud for line coding */ + usbhost_ep_t bulkin; /* Bulk IN endpoint */ + usbhost_ep_t bulkout; /* Bulk OUT endpoint */ + usbhost_ep_t intin; /* Interrupt IN endpoint (optional) */ + + /* This is the serial data buffer */ + + char rxbuffer[CONFIG_USBHOST_CDCACM_RXBUFSIZE]; + char txbuffer[CONFIG_USBHOST_CDCACM_TXBUFSIZE]; +}; + +/* This is how struct usbhost_cdcacm_s looks to the free list logic */ + +struct usbhost_freestate_s +{ + FAR struct usbhost_freestate_s *flink; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Semaphores */ + +static void usbhost_takesem(sem_t *sem); +#define usbhost_givesem(s) sem_post(s); + +/* Memory allocation services */ + +static FAR struct usbhost_cdcacm_s *usbhost_allocclass(void); +static void usbhost_freeclass(FAR struct usbhost_cdcacm_s *usbclass); + +/* Device name management */ + +static int usbhost_devno_alloc(FAR struct usbhost_cdcacm_s *priv); +static void usbhost_devno_free(FAR struct usbhost_cdcacm_s *priv); +static inline void usbhost_mkdevname(FAR struct usbhost_cdcacm_s *priv, + FAR char *devname); + +/* CDC/ACM request helpers */ + +static int usbhost_linecoding_send(FAR struct usbhost_cdcacm_s *priv); +static void usbhost_notification_work(FAR void *arg); +static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes); + +/* UART buffer data transfer */ + +static void usbhost_txdata_work(FAR void *arg); +static void usbhost_rxdata_work(FAR void *arg); + +/* Worker thread actions */ + +static void usbhost_destroy(FAR void *arg); + +/* Helpers for usbhost_connect() */ + +static int usbhost_cfgdesc(FAR struct usbhost_cdcacm_s *priv, + FAR const uint8_t *configdesc, int desclen); + +/* (Little Endian) Data helpers */ + +static inline uint16_t usbhost_getle16(const uint8_t *val); +static inline uint16_t usbhost_getbe16(const uint8_t *val); +static inline void usbhost_putle16(uint8_t *dest, uint16_t val); +static void usbhost_putle32(uint8_t *dest, uint32_t val); + +/* Transfer descriptor memory management */ + +static int usbhost_alloc_buffers(FAR struct usbhost_cdcacm_s *priv); +static void usbhost_free_buffers(FAR struct usbhost_cdcacm_s *priv); + +/* struct usbhost_registry_s methods */ + +static struct usbhost_class_s *usbhost_create( + FAR struct usbhost_hubport_s *hport, + FAR const struct usbhost_id_s *id); + +/* struct usbhost_class_s methods */ + +static int usbhost_connect(FAR struct usbhost_class_s *usbclass, + FAR const uint8_t *configdesc, int desclen); +static int usbhost_disconnected(FAR struct usbhost_class_s *usbclass); + +/* Serial driver lower-half interfaces */ + +static int usbhost_setup(FAR struct uart_dev_s *uartdev); +static void usbhost_shutdown(FAR struct uart_dev_s *uartdev); +static int usbhost_attach(FAR struct uart_dev_s *uartdev); +static void usbhost_detach(FAR struct uart_dev_s *uartdev); +static int usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg); +static void usbhost_rxint(FAR struct uart_dev_s *uartdev, bool enable); +static bool usbhost_rxavailable(FAR struct uart_dev_s *uartdev); +#ifdef CONFIG_SERIAL_IFLOWCONTROL +static bool usbhost_rxflowcontrol(FAR struct uart_dev_s *uartdev, + unsigned int nbuffered, bool upper); +#endif +static void usbhost_txint(FAR struct uart_dev_s *uartdev, bool enable); +static bool usbhost_txready(FAR struct uart_dev_s *uartdev); +static bool usbhost_txempty(FAR struct uart_dev_s *uartdev); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This structure provides the registry entry ID information that will be + * used to associate the USB host CDC/ACM class to a connected USB + * device. + */ + +static const const struct usbhost_id_s g_id = +{ + USB_CLASS_CDC, /* base */ + CDC_SUBCLASS_NONE, /* subclass */ + CDC_PROTO_NONE, /* proto */ + 0, /* vid */ + 0 /* pid */ +}; + +/* This is the USB host CDC/ACM class's registry entry */ + +static struct usbhost_registry_s g_cdcacm = +{ + NULL, /* flink */ + usbhost_create, /* create */ + 1, /* nids */ + &g_id /* id[] */ +}; + +/* Serial driver lower half interface */ + +static const struct uart_ops_s g_uart_ops = +{ + usbhost_setup, /* setup */ + usbhost_shutdown, /* shutdown */ + usbhost_attach, /* attach */ + usbhost_detach, /* detach */ + usbhost_ioctl, /* ioctl */ + NULL , /* receive */ + usbhost_rxint, /* rxinit */ + usbhost_rxavailable, /* rxavailable */ +#ifdef CONFIG_SERIAL_IFLOWCONTROL + usbhost_rxflowcontrol, /* rxflowcontrol */ +#endif + NULL, /* send */ + usbhost_txint, /* txinit */ + usbhost_txready, /* txready */ + usbhost_txempty /* txempty */ +}; + +/* This is an array of pre-allocated USB host CDC/ACM class instances */ + +#if CONFIG_USBHOST_CDCACM_NPREALLOC > 0 +static struct usbhost_cdcacm_s g_prealloc[CONFIG_USBHOST_CDCACM_NPREALLOC]; +#endif + +/* This is a list of free, pre-allocated USB host CDC/ACM class instances */ + +#if CONFIG_USBHOST_CDCACM_NPREALLOC > 0 +static FAR struct usbhost_freestate_s *g_freelist; +#endif + +/* This is a bitmap that is used to allocate device minor numbers /dev/ttyACM[n]. */ + +static uint32_t g_devinuse; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_takesem + * + * Description: + * This is just a wrapper to handle the annoying behavior of semaphore + * waits that return due to the receipt of a signal. + * + ****************************************************************************/ + +static void usbhost_takesem(sem_t *sem) +{ + /* Take the semaphore (perhaps waiting) */ + + while (sem_wait(sem) != 0) + { + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: usbhost_allocclass + * + * Description: + * This is really part of the logic that implements the create() method + * of struct usbhost_registry_s. This function allocates memory for one + * new class instance. + * + * Input Parameters: + * None + * + * Returned Value: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s. NULL is returned on failure; this function will + * will fail only if there are insufficient resources to create another + * USB host class instance. + * + ****************************************************************************/ + +#if CONFIG_USBHOST_CDCACM_NPREALLOC > 0 +static FAR struct usbhost_cdcacm_s *usbhost_allocclass(void) +{ + FAR struct usbhost_freestate_s *entry; + irqstate_t flags; + + /* We may be executing from an interrupt handler so we need to take one of + * our pre-allocated class instances from the free list. + */ + + flags = irqsave(); + entry = g_freelist; + if (entry) + { + g_freelist = entry->flink; + } + + irqrestore(flags); + ullvdbg("Allocated: %p\n", entry);; + return (FAR struct usbhost_cdcacm_s *)entry; +} +#else +static FAR struct usbhost_cdcacm_s *usbhost_allocclass(void) +{ + FAR struct usbhost_cdcacm_s *priv; + + /* We are not executing from an interrupt handler so we can just call + * kmm_malloc() to get memory for the class instance. + */ + + DEBUGASSERT(!up_interrupt_context()); + priv = (FAR struct usbhost_cdcacm_s *)kmm_malloc(sizeof(struct usbhost_cdcacm_s)); + uvdbg("Allocated: %p\n", priv);; + return priv; +} +#endif + +/**************************************************************************** + * Name: usbhost_freeclass + * + * Description: + * Free a class instance previously allocated by usbhost_allocclass(). + * + * Input Parameters: + * usbclass - A reference to the class instance to be freed. + * + * Returned Value: + * None + * + ****************************************************************************/ + +#if CONFIG_USBHOST_CDCACM_NPREALLOC > 0 +static void usbhost_freeclass(FAR struct usbhost_cdcacm_s *usbclass) +{ + FAR struct usbhost_freestate_s *entry = (FAR struct usbhost_freestate_s *)usbclass; + irqstate_t flags; + DEBUGASSERT(entry != NULL); + + ullvdbg("Freeing: %p\n", entry); + + /* Just put the pre-allocated class structure back on the freelist */ + + flags = irqsave(); + entry->flink = g_freelist; + g_freelist = entry; + irqrestore(flags); +} +#else +static void usbhost_freeclass(FAR struct usbhost_cdcacm_s *usbclass) +{ + DEBUGASSERT(usbclass != NULL); + + /* Free the class instance (calling sched_kmm_free() in case we are executing + * from an interrupt handler. + */ + + uvdbg("Freeing: %p\n", usbclass);; + kmm_free(usbclass); +} +#endif + +/**************************************************************************** + * Name: usbhost_devno_alloc + * + * Description: + * Allocate a unique /dev/ttyACM[n] minor number in the range 0-31. + * + ****************************************************************************/ + +static int usbhost_devno_alloc(FAR struct usbhost_cdcacm_s *priv) +{ + irqstate_t flags; + int devno; + + flags = irqsave(); + for (devno = 0; devno < 32; devno++) + { + uint32_t bitno = 1 << devno; + if ((g_devinuse & bitno) == 0) + { + g_devinuse |= bitno; + priv->minor = devno; + irqrestore(flags); + return OK; + } + } + + irqrestore(flags); + return -EMFILE; +} + +/**************************************************************************** + * Name: usbhost_devno_free + * + * Description: + * Free a /dev/ttyACM[n] minor number so that it can be used. + * + ****************************************************************************/ + +static void usbhost_devno_free(FAR struct usbhost_cdcacm_s *priv) +{ + int devno = priv->minor; + + if (devno >= 0 && devno < 32) + { + irqstate_t flags = irqsave(); + g_devinuse &= ~(1 << devno); + irqrestore(flags); + } +} + +/**************************************************************************** + * Name: usbhost_mkdevname + * + * Description: + * Format a /dev/ttyACM[n] device name given a minor number. + * + ****************************************************************************/ + +static inline void usbhost_mkdevname(FAR struct usbhost_cdcacm_s *priv, + FAR char *devname) +{ + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->minor); +} + +/**************************************************************************** + * Name: usbhost_linecoding_send + * + * Description: + * Format and send the on EP0. + * + * Input Parameters: + * arg - A reference to the class instance to be destroyed. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static int usbhost_linecoding_send(FAR struct usbhost_cdcacm_s *priv) +{ + FAR struct usbhost_hubport_s *hport; + FAR struct cdc_linecoding_s *linecode; + FAR struct usb_ctrlreq_s *ctrlreq; + int ret; + + hport = priv->usbclass.hport; + DEBUGASSERT(hport); + + /* Initialize the line coding structure */ + + linecode = (FAR struct cdc_linecoding_s *)priv->linecode; + usbhost_putle32(linecode->baud, priv->baud); + linecode->stop = (priv->stop2) ? 2 : 0; + linecode->parity = priv->parity; + linecode->nbits = priv->nbits; + + /* Initialize the control request */ + + ctrlreq = (FAR struct usb_ctrlreq_s *)priv->ctrlreq; + ctrlreq->type = USB_DIR_OUT | USB_REQ_TYPE_CLASS | USB_REQ_RECIPIENT_INTERFACE; + ctrlreq->req = ACM_SET_LINE_CODING; + + usbhost_putle16(ctrlreq->value, 0); + usbhost_putle16(ctrlreq->index, priv->ifno); + usbhost_putle16(ctrlreq->len, SIZEOF_CDC_LINECODING); + + /* And send the request */ + + ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, priv->linecode); + if (ret < 0) + { + udbg("ERROR: DRVR_CTRLOUT failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_notification_work + * + * Description: + * Handle receipt of an asynchronous notification from the CDC/ACM device + * + * Input Parameters: + * arg - The argument provided with the asynchronous I/O was setup + * + * Returned Value: + * None + * + * Assumptions: + * Probably called from an interrupt handler. + * + ****************************************************************************/ + +static void usbhost_notification_work(FAR void *arg) +{ + FAR struct usbhost_cdcacm_s *priv; + FAR struct usbhost_hubport_s *hport; + FAR struct cdc_notification_s *inmsg; + uint16_t value; + uint16_t index; + uint16_t len; + int ret; + + priv = (FAR struct usbhost_cdcacm_s *)arg; + DEBUGASSERT(priv); + + hport = priv->usbclass.hport; + DEBUGASSERT(hport); + + /* Are we still connected? */ + + if (!priv->disconnected && priv->intin) + { + /* Yes.. Was an interrupt IN message received correctly? */ + + if (priv->nbytes >= 0) + { + /* Yes.. decode the notification */ + + inmsg = (FAR struct cdc_notification_s *)priv->notification; + value = usbhost_getle16(inmsg->value); + index = usbhost_getle16(inmsg->index); + len = usbhost_getle16(inmsg->len); + + uvdbg("type: %02x notification: %02x value: %04x index: %04x len: %04x\n", + inmsg->type, inmsg->notification, value, index, len); + + /* We care only about the SerialState notification */ + + if ((inmsg->type == (USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE)) && + (inmsg->notification == ACM_SERIAL_STATE) && + (value == 0) && + (index == priv->ifno) && + (len == 2)) + { + uint16_t state = usbhost_getle16(inmsg->data); + + /* And we care only about the state of the DCD and DSR in the + * the notification. + */ + + priv->dcd = ((state & CDC_UART_RXCARRIER) != 0); + priv->dsr = ((state & CDC_UART_TXCARRIER) != 0); + + uvdbg("ACM_SERIAL_STATE: DCD=%d DSR=%d\n", + priv->dcd, priv->dsr); + } + } + + /* Setup to receive the next line status change event */ + + ret = DRVR_ASYNCH(hport->drvr, priv->intin, + (FAR uint8_t *)priv->notification, + MAX_NOTIFICATION, usbhost_notification_callback, + &priv->usbclass); + if (ret < 0) + { + udbg("ERROR: DRVR_ASYNCH failed: %d\n", ret); + } + } +} + +/**************************************************************************** + * Name: usbhost_notification_callback + * + * Description: + * Handle receipt of line status from the CDC/ACM device + * + * Input Parameters: + * arg - The argument provided with the asynchronous I/O was setup + * nbytes - The number of bytes actually transferred (or a negated errno + * value; + * + * Returned Value: + * None + * + * Assumptions: + * Probably called from an interrupt handler. + * + ****************************************************************************/ + +static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes) +{ + FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)arg; + uint32_t delay = 0; + + DEBUGASSERT(priv); + + /* Are we still connected? */ + + if (!priv->disconnected) + { + /* Check for a failure. On higher end host controllers, the + * asynchronous transfer will pend until data is available (OHCI and + * EHCI). On lower end host controllers (like STM32 and EFM32), the + * transfer will fail immediately when the device NAKs the first + * attempted interrupt IN transfer (with nbytes == -EAGAIN). In that + * case (or in the case of other errors), we must fall back to + * polling. + */ + + DEBUGASSERT(nbytes >= -INT16_MIN && nbytes <= INT16_MAX); + priv->nbytes = (int16_t)nbytes; + + if (nbytes < 0) + { + /* This debug output is good to know, but really a nuisance for + * those configurations where we have to fall back to polling. + * FIX: Don't output the message is the result is -EAGAIN. + */ + +#if defined(CONFIG_DEBUG_USB) && !defined(CONFIG_DEBUG_VERBOSE) + if (nbytes != -EAGAIN) +#endif + { + ulldbg("ERROR: Transfer failed: %d\n", nbytes); + } + + /* We don't know the nature of the failure, but we need to do all + * that we can do to avoid a CPU hog error loop. + * + * Use the low-priority work queue and delay polling for the next + * event. We want to use as little CPU bandwidth as possible in + * this case. + */ + + delay = USBHOST_CDCACM_NTDELAY; + } + + /* Make sure that the work structure available. There is a remote + * chance that this may collide with a device disconnection event. + */ + + if (work_available(&priv->ntwork)) + { + (void)work_queue(HPWORK, &priv->ntwork, + (worker_t)usbhost_notification_work, + priv, delay); + } + } +} + +/************************************************************************************ + * UART buffer data transfer + ************************************************************************************/ +/**************************************************************************** + * Name: usbhost_txdata_work + * + * Description: + * Send more OUT data to the attached CDC/ACM device. + * + * Input Parameters: + * arg - A reference to the CDC/ACM class private data + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_txdata_work(FAR void *arg) +{ + FAR struct usbhost_cdcacm_s *priv; + FAR struct usbhost_hubport_s *hport; + FAR struct uart_dev_s *uartdev; + FAR struct uart_buffer_s *txbuf; + ssize_t nwritten; + int nxfrd; + int txndx; + int txtail; + + priv = (FAR struct usbhost_cdcacm_s *)arg; + DEBUGASSERT(priv); + + hport = priv->usbclass.hport; + DEBUGASSERT(hport); + + uartdev = &priv->uartdev; + txbuf = &uartdev->xmit; + + /* Do nothing if TX transmission is disabled */ + + if (!priv->txena) + { + /* Terminate the work now *without* rescheduling */ + + return; + } + + /* Loop until The UART TX buffer is empty */ + + txtail = txbuf->tail; + nxfrd = 0; + txndx = 0; + + while (txtail != txbuf->head) + { + /* Copy data from the UART TX buffer until either 1) the UART TX + * buffer has been emptie, or 2) the Bulk OUT buffer is full. + */ + + txndx = 0; + while (txtail != txbuf->head && txndx < priv->pktsize) + { + /* Copy the next byte */ + + priv->outbuf[txndx] = txbuf->buffer[txtail]; + + /* Increment counters and indices */ + + nxfrd++; + txndx++; + if (++txtail > txbuf->size) + { + txtail = 0; + } + } + + /* Send the filled TX buffer to the CDC/ACM device */ + + nwritten = DRVR_TRANSFER(hport->drvr, priv->bulkout, + priv->outbuf, txndx); + if (nwritten < 0) + { + /* The most likely reason for a failure is that CDC/ACM device + * NAK'ed our packet + * + * Just break out of the loop, rescheduling the work. + */ + + udbg("ERROR: DRVR_TRANSFER for packet failed: %d\n", (int)nwritten); + break; + } + + /* Save the updated tail pointer so that it cannot be send again */ + + txbuf->tail = txtail; + } + + /* We get here because either 1) the UART TX buffer is empty and there is + * nothing more to send, or 2) the CDC/ACM device was not ready to accept + * our data. + * + * If the last packet sent was and even multiple of the packet size, then + * we need to send a zero length packet (ZLP). + */ + + if (nxfrd > 0 && txndx == priv->pktsize) + { + /* Send the ZLP to the CDC/ACM device */ + + nwritten = DRVR_TRANSFER(hport->drvr, priv->bulkout, + priv->outbuf, 0); + if (nwritten < 0) + { + /* The most likely reason for a failure is that CDC/ACM device + * NAK'ed our packet. + */ + + udbg("ERROR: DRVR_TRANSFER for ZLP failed: %d\n", (int)nwritten); + } + } + + /* If any bytes were removed from the buffer, inform any waiters there there is + * space available. + */ + + if (nxfrd) + { + uart_datasent(uartdev); + } +} + +/**************************************************************************** + * Name: usbhost_rxdata_work + * + * Description: + * Get more IN data from the attached CDC/ACM device. + * + * Input Parameters: + * arg - A reference to the CDC/ACM class private data + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_rxdata_work(FAR void *arg) +{ + FAR struct usbhost_cdcacm_s *priv; + FAR struct usbhost_hubport_s *hport; + FAR struct uart_dev_s *uartdev; + FAR struct uart_buffer_s *rxbuf; + ssize_t nread; + int nxfrd; + int nexthead; + int rxndx; + int ret; + + priv = (FAR struct usbhost_cdcacm_s *)arg; + DEBUGASSERT(priv); + + hport = priv->usbclass.hport; + DEBUGASSERT(hport); + + uartdev = &priv->uartdev; + rxbuf = &uartdev->recv; + + /* Do nothing if RX reception is disabled or if RX flow control is in + * effect. + */ + +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (!priv->rxena || !priv->rts) +#else + if (!priv->rxena) +#endif + { + /* Terminate the work now *without* rescheduling */ + + return; + } + + /* Get the index in the RX packet buffer where we will take the first + * byte of data. + */ + + rxndx = priv->rxndx; + nxfrd = 0; + + /* Get the index to the value of the UART RX buffer head AFTER the + * first character has been stored. We need to know this in order + * to test if the UART RX buffer is full. + */ + + nexthead = rxbuf->head + 1; + if (nexthead >= rxbuf->size) + { + nexthead = 0; + } + + /* Loop until either: + * + * 1. The UART RX buffer is full + * 2. There is no more data available from the CDC/ACM device + */ + + for (;;) + { + /* Stop now if there is no room for another character in the RX buffer. */ + + if (nexthead == rxbuf->tail) + { + /* Break out of the loop, rescheduling the work */ + + break; + } + + /* Do we have any buffer RX data to transfer? */ + + if (priv->nrxbytes < 1) + { + /* No.. Read more data from the CDC/ACM device */ + + nread = DRVR_TRANSFER(hport->drvr, priv->bulkin, + priv->inbuf, priv->pktsize); + if (nread < 0) + { + /* The most likely reason for a failure is that the + * has no data available now and NAK'ed the IN token. + * + * Just break out of the loop, rescheduling the work. + */ + + break; + } + + DEBUGASSERT(nread > 0 && nread <= priv->pktsize); + priv->nrxbytes = (uint16_t)nread; + rxndx = 0; + + /* Ignore ZLPs */ + + if (nread == 0) + { + continue; + } + } + + /* Transfer one byte from the RX packet buffer into UART RX buffer */ + + rxbuf->buffer[rxbuf->head] = priv->inbuf[rxndx]; + nxfrd++; + + /* Save the updated indices */ + + rxbuf->head = nexthead; + priv->rxndx = rxndx; + + /* Update the indices for the next pass through the loop */ + + rxndx++; + if (++nexthead >= rxbuf->size) + { + nexthead = 0; + } + } + + /* We break out to here if either: 1) the UART RX buffer is full or, + * 2) the CDC/ACM device is not ready to provide us with more serial + * data. + * + * Check again if RX reception is enabled. This state could have + * changed since we started the transfer. + */ + +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->rxena && priv->rts) +#else + if (priv->rxena) +#endif + { + /* Schedule RX data reception work flow to occur after a delay. + * This will effect our responsive in certain cased. The delayed + * work, however, will be cancelled and replaced with immediate + * work when the upper layer demands more data. + */ + + DEBUGASSERT(work_available(&priv->rxwork)); + ret = work_queue(LPWORK, &priv->rxwork, usbhost_rxdata_work, priv, + USBHOST_CDCACM_RXDELAY); + DEBUGASSERT(ret >= 0); + UNUSED(ret); + } + + /* If any bytes were added to the buffer, inform any waiters there there + * is new incoming data available. + */ + + if (nxfrd) + { + uart_datareceived(uartdev); + } +} + +/**************************************************************************** + * Name: usbhost_destroy + * + * Description: + * The USB CDC/ACM device has been disconnected and the reference count + * on the USB host class instance has gone to 1.. Time to destroy the USB + * host class instance. + * + * Input Parameters: + * arg - A reference to the class instance to be destroyed. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_destroy(FAR void *arg) +{ + FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)arg; + FAR struct usbhost_hubport_s *hport; + char devname[DEV_NAMELEN]; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL); + hport = priv->usbclass.hport; + + uvdbg("crefs: %d\n", priv->crefs); + + /* Unregister the serial lower half driver */ + + usbhost_mkdevname(priv, devname); +#warning Missing logic + + /* Release the device name used by this connection */ + + usbhost_devno_free(priv); + + /* Free the allocated endpoints */ + + if (priv->bulkout) + { + DRVR_EPFREE(hport->drvr, priv->bulkout); + } + + if (priv->bulkin) + { + DRVR_EPFREE(hport->drvr, priv->bulkin); + } + + if (priv->intin) + { + DRVR_EPFREE(hport->drvr, priv->intin); + } + + /* Free any transfer buffers */ + + usbhost_free_buffers(priv); + + /* Destroy the semaphores */ + + sem_destroy(&priv->exclsem); + + /* Disconnect the USB host device */ + + DRVR_DISCONNECT(hport->drvr, hport); + + /* Free the function address assigned to this device */ + + usbhost_devaddr_destroy(hport, hport->funcaddr); + hport->funcaddr = 0; + + /* And free the class instance. */ + + usbhost_freeclass(priv); +} + +/**************************************************************************** + * Name: usbhost_cfgdesc + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * priv - The USB host class instance. + * configdesc - A pointer to a uint8_t buffer container the configuration + * descriptor. + * desclen - The length in bytes of the configuration descriptor. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * Assumptions: + * This function will *not* be called from an interrupt handler. + * + ****************************************************************************/ + +static int usbhost_cfgdesc(FAR struct usbhost_cdcacm_s *priv, + FAR const uint8_t *configdesc, int desclen) +{ + FAR struct usbhost_hubport_s *hport; + FAR struct usb_cfgdesc_s *cfgdesc; + FAR struct usb_desc_s *desc; + FAR struct usbhost_epdesc_s bindesc; + FAR struct usbhost_epdesc_s boutdesc; + FAR struct usbhost_epdesc_s iindesc; + int remaining; + uint8_t found = 0; + int ret; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport && + configdesc != NULL && desclen >= sizeof(struct usb_cfgdesc_s)); + hport = priv->usbclass.hport; + + /* Keep the compiler from complaining about uninitialized variables */ + + memset(&bindesc, 0, sizeof(struct usbhost_epdesc_s)); + memset(&boutdesc, 0, sizeof(struct usbhost_epdesc_s)); + memset(&iindesc, 0, sizeof(struct usbhost_epdesc_s)); + + /* Verify that we were passed a configuration descriptor */ + + cfgdesc = (FAR struct usb_cfgdesc_s *)configdesc; + if (cfgdesc->type != USB_DESC_TYPE_CONFIG) + { + return -EINVAL; + } + + /* Get the total length of the configuration descriptor (little endian). + * It might be a good check to get the number of interfaces here too. + */ + + remaining = (int)usbhost_getle16(cfgdesc->totallen); + + /* Skip to the next entry descriptor */ + + configdesc += cfgdesc->len; + remaining -= cfgdesc->len; + + /* Loop where there are more descriptors to examine */ + + while (remaining >= sizeof(struct usb_desc_s)) + { + /* What is the next descriptor? */ + + desc = (FAR struct usb_desc_s *)configdesc; + switch (desc->type) + { + /* Interface descriptor. We really should get the number of endpoints + * from this descriptor too. + */ + + case USB_DESC_TYPE_INTERFACE: + { + FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)configdesc; + + uvdbg("Interface descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); + + /* Save the interface number and mark ONLY the interface found */ + + priv->ifno = ifdesc->ifno; + found = USBHOST_IFFOUND; + } + break; + + /* Endpoint descriptor. We expect two bulk endpoints, an IN and an + * OUT. + */ + + case USB_DESC_TYPE_ENDPOINT: + { + FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)configdesc; + + uvdbg("Endpoint descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); + + /* Check for a bulk endpoint. */ + + if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_BULK) + { + /* Yes.. it is a bulk endpoint. IN or OUT? */ + + if (USB_ISEPOUT(epdesc->addr)) + { + /* It is an OUT bulk endpoint. There should be only one + * bulk OUT endpoint. + */ + + if ((found & USBHOST_BOUTFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + + found |= USBHOST_BOUTFOUND; + + /* Save the bulk OUT endpoint information */ + + boutdesc.hport = hport; + boutdesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + boutdesc.in = false; + boutdesc.xfrtype = USB_EP_ATTR_XFER_BULK; + boutdesc.interval = epdesc->interval; + boutdesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + + uvdbg("Bulk OUT EP addr:%d mxpacketsize:%d\n", + boutdesc.addr, boutdesc.mxpacketsize); + } + else + { + /* It is an IN bulk endpoint. There should be only one + * bulk IN endpoint. + */ + + if ((found & USBHOST_BINFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + + found |= USBHOST_BINFOUND; + + /* Save the bulk IN endpoint information */ + + bindesc.hport = hport; + bindesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + bindesc.in = 1; + bindesc.xfrtype = USB_EP_ATTR_XFER_BULK; + bindesc.interval = epdesc->interval; + bindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + uvdbg("Bulk IN EP addr:%d mxpacketsize:%d\n", + bindesc.addr, bindesc.mxpacketsize); + } + } + + /* Check for an interrupt IN endpoint. */ + + else if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == USB_EP_ATTR_XFER_INT) + { + /* Yes.. it is a interrupt endpoint. IN or OUT? */ + + if (USB_ISEPIN(epdesc->addr)) + { + /* It is an IN interrupt endpoint. There should be only one + * interrupt IN endpoint. + */ + + if ((found & USBHOST_IINFOUND) != 0) + { + /* Oops.. more than one. We don't know what to do + * with this. + */ + + return -EINVAL; + } + + found |= USBHOST_BOUTFOUND; + + /* Save the bulk OUT endpoint information */ + + iindesc.hport = hport; + iindesc.addr = epdesc->addr & USB_EP_ADDR_NUMBER_MASK; + iindesc.in = false; + iindesc.xfrtype = USB_EP_ATTR_XFER_INT; + iindesc.interval = epdesc->interval; + iindesc.mxpacketsize = usbhost_getle16(epdesc->mxpacketsize); + + uvdbg("Interrupt IN EP addr:%d mxpacketsize:%d\n", + boutdesc.addr, boutdesc.mxpacketsize); + } + } + } + break; + + /* Other descriptors are just ignored for now */ + + default: + break; + } + + /* If we found everything we need with this interface, then break out + * of the loop early. + */ + + if (found == USBHOST_ALLFOUND) + { + break; + } + + /* Increment the address of the next descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + } + + /* Sanity checking... did we find all of things that we need? NOTE: that + * the Interrupt IN endpoint is optional. + */ + + if ((found & USBHOST_MINFOUND) != USBHOST_MINFOUND) + { + ulldbg("ERROR: Found IF:%s BIN:%s BOUT:%s INTIN:%s\n", + (found & USBHOST_IFFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BINFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BOUTFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_IINFOUND) != 0 ? "YES" : "NO"); + return -EINVAL; + } + + /* We are good... Allocate the endpoints */ + + ret = DRVR_EPALLOC(hport->drvr, &boutdesc, &priv->bulkout); + if (ret < 0) + { + udbg("ERROR: Failed to allocate Bulk OUT endpoint\n"); + return ret; + } + + ret = DRVR_EPALLOC(hport->drvr, &bindesc, &priv->bulkin); + if (ret < 0) + { + udbg("ERROR: Failed to allocate Bulk IN endpoint\n"); + (void)DRVR_EPFREE(hport->drvr, priv->bulkout); + return ret; + } + + /* The interrupt IN endpoint is optional */ + + if ((found & USBHOST_IINFOUND) != 0) + { + ret = DRVR_EPALLOC(hport->drvr, &iindesc, &priv->intin); + if (ret < 0) + { + udbg("ERROR: Failed to allocate Interrupt IN endpoint\n"); + priv->intin = NULL; + } + } + + ullvdbg("Endpoints allocated\n"); + return OK; +} + +/**************************************************************************** + * Name: usbhost_getle16 + * + * Description: + * Get a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the little endian value. + * + * Returned Value: + * A uint16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getle16(const uint8_t *val) +{ + return (uint16_t)val[1] << 8 | (uint16_t)val[0]; +} + +/**************************************************************************** + * Name: usbhost_getbe16 + * + * Description: + * Get a (possibly unaligned) 16-bit big endian value. + * + * Input Parameters: + * val - A pointer to the first byte of the big endian value. + * + * Returned Value: + * A uint16_t representing the whole 16-bit integer value + * + ****************************************************************************/ + +static inline uint16_t usbhost_getbe16(const uint8_t *val) +{ + return (uint16_t)val[0] << 8 | (uint16_t)val[1]; +} + +/**************************************************************************** + * Name: usbhost_putle16 + * + * Description: + * Put a (possibly unaligned) 16-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 16-bit value to be saved. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_putle16(uint8_t *dest, uint16_t val) +{ + dest[0] = val & 0xff; /* Little endian means LS byte first in byte stream */ + dest[1] = val >> 8; +} + +/**************************************************************************** + * Name: usbhost_putle32 + * + * Description: + * Put a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 32-bit value to be saved. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_putle32(uint8_t *dest, uint32_t val) +{ + /* Little endian means LS halfword first in byte stream */ + + usbhost_putle16(dest, (uint16_t)(val & 0xffff)); + usbhost_putle16(dest+2, (uint16_t)(val >> 16)); +} + +/**************************************************************************** + * Name: usbhost_alloc_buffers + * + * Description: + * Allocate transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Value: + * On sucess, zero (OK) is returned. On failure, an negated errno value + * is returned to indicate the nature of the failure. + * + ****************************************************************************/ + +static int usbhost_alloc_buffers(FAR struct usbhost_cdcacm_s *priv) +{ + FAR struct usbhost_hubport_s *hport; + size_t maxlen; + int ret; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL && + priv->linecode == NULL); + hport = priv->usbclass.hport; + + /* Allocate memory for control requests */ + + ret = DRVR_ALLOC(hport->drvr, (FAR uint8_t **)&priv->ctrlreq, &maxlen); + if (ret < 0) + { + udbg("ERROR: DRVR_ALLOC of ctrlreq failed: %d\n", ret); + goto errout; + } + + DEBUGASSERT(maxlen >= sizeof(struct usb_ctrlreq_s)); + + /* Allocate buffer for sending line coding data. */ + + ret = DRVR_IOALLOC(hport->drvr, &priv->linecode, + sizeof( struct cdc_linecoding_s)); + if (ret < 0) + { + udbg("ERROR: DRVR_IOALLOC of line coding failed: %d\n", ret); + goto errout; + } + + /* Allocate (optional) buffer for receiving line status data. */ + + if (priv->intin) + { + ret = DRVR_IOALLOC(hport->drvr, &priv->notification, MAX_NOTIFICATION); + if (ret < 0) + { + udbg("ERROR: DRVR_IOALLOC of line status failed: %d\n", ret); + goto errout; + } + } + + /* Set the size of Bulk IN and OUT buffers to the max packet size */ + + priv->pktsize = (hport->speed == USB_SPEED_HIGH) ? 512 : 64; + + /* Allocate a RX buffer for Bulk IN transfers */ + + ret = DRVR_IOALLOC(hport->drvr, &priv->inbuf, priv->pktsize); + if (ret < 0) + { + udbg("ERROR: DRVR_IOALLOC of Bulk IN buffer failed: %d\n", ret); + goto errout; + } + + /* Allocate a TX buffer for Bulk IN transfers */ + + ret = DRVR_IOALLOC(hport->drvr, &priv->outbuf, priv->pktsize); + if (ret < 0) + { + udbg("ERROR: DRVR_IOALLOC of Bulk OUT buffer failed: %d\n", ret); + goto errout; + } + + return OK; + +errout: + usbhost_free_buffers(priv); + return ret; +} + +/**************************************************************************** + * Name: usbhost_free_buffers + * + * Description: + * Free transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_free_buffers(FAR struct usbhost_cdcacm_s *priv) +{ + FAR struct usbhost_hubport_s *hport; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL); + hport = priv->usbclass.hport; + + if (priv->ctrlreq) + { + (void)DRVR_FREE(hport->drvr, priv->ctrlreq); + } + + if (priv->linecode) + { + (void)DRVR_IOFREE(hport->drvr, priv->linecode); + } + + if (priv->notification) + { + (void)DRVR_IOFREE(hport->drvr, priv->notification); + } + + if (priv->inbuf) + { + (void)DRVR_IOFREE(hport->drvr, priv->inbuf); + } + + if (priv->outbuf) + { + (void)DRVR_IOFREE(hport->drvr, priv->outbuf); + } + + priv->pktsize = 0; + priv->ctrlreq = NULL; + priv->linecode = NULL; + priv->notification = NULL; + priv->inbuf = NULL; + priv->outbuf = NULL; +} + +/**************************************************************************** + * struct usbhost_registry_s methods + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_create + * + * Description: + * This function implements the create() method of struct usbhost_registry_s. + * The create() method is a callback into the class implementation. It is + * used to (1) create a new instance of the USB host class state and to (2) + * bind a USB host driver "session" to the class instance. Use of this + * create() method will support environments where there may be multiple + * USB ports and multiple USB devices simultaneously connected. + * + * Input Parameters: + * hport - The hub port that manages the new class instance. + * id - In the case where the device supports multiple base classes, + * subclasses, or protocols, this specifies which to configure for. + * + * Returned Value: + * On success, this function will return a non-NULL instance of struct + * usbhost_class_s that can be used by the USB host driver to communicate + * with the USB host class. NULL is returned on failure; this function + * will fail only if the hport input parameter is NULL or if there are + * insufficient resources to create another USB host class instance. + * + ****************************************************************************/ + +static FAR struct usbhost_class_s * +usbhost_create(FAR struct usbhost_hubport_s *hport, + FAR const struct usbhost_id_s *id) +{ + FAR struct usbhost_cdcacm_s *priv; + FAR struct uart_dev_s *uartdev; + + /* Allocate a USB host CDC/ACM class instance */ + + priv = usbhost_allocclass(); + if (priv) + { + /* Initialize the allocated CDC/ACM class instance */ + + memset(priv, 0, sizeof(struct usbhost_cdcacm_s)); + + /* Assign a device number to this class instance */ + + if (usbhost_devno_alloc(priv) == OK) + { + /* Initialize class method function pointers */ + + priv->usbclass.hport = hport; + priv->usbclass.connect = usbhost_connect; + priv->usbclass.disconnected = usbhost_disconnected; + + /* The initial reference count is 1... One reference is held by the driver */ + + priv->crefs = 1; + + /* Initialize semaphores (this works okay in the interrupt context) */ + + sem_init(&priv->exclsem, 0, 1); + + /* Set up the serial lower-half interface */ + + + uartdev = &priv->uartdev; + uartdev->recv.size = CONFIG_USBHOST_CDCACM_RXBUFSIZE; + uartdev->recv.buffer = priv->rxbuffer; + uartdev->xmit.size = CONFIG_USBHOST_CDCACM_TXBUFSIZE; + uartdev->xmit.buffer = priv->txbuffer; + uartdev->ops = &g_uart_ops; + uartdev->priv = priv; + + /* Set up the initial line status */ + + priv->baud = CONFIG_USBHOST_CDCACM_BAUD; + priv->nbits = CONFIG_USBHOST_CDCACM_BITS; + priv->parity = CONFIG_USBHOST_CDCACM_PARITY; + priv->stop2 = CONFIG_USBHOST_CDCACM_2STOP; + +#ifdef CONFIG_SERIAL_IFLOWCONTROL + priv->rts = true; +#endif + + /* Return the instance of the USB CDC/ACM class */ + + return &priv->usbclass; + } + } + + /* An error occurred. Free the allocation and return NULL on all failures */ + + if (priv) + { + usbhost_freeclass(priv); + } + + return NULL; +} + +/**************************************************************************** + * struct usbhost_class_s methods + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_connect + * + * Description: + * This function implements the connect() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to provide the device's configuration + * descriptor to the class so that the class may initialize properly + * + * Input Parameters: + * usbclass - The USB host class entry previously obtained from a call to + * create(). + * configdesc - A pointer to a uint8_t buffer container the configuration + * descriptor. + * desclen - The length in bytes of the configuration descriptor. + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value is + * returned indicating the nature of the failure + * + * NOTE that the class instance remains valid upon return with a failure. It is + * the responsibility of the higher level enumeration logic to call + * CLASS_DISCONNECTED to free up the class driver resources. + * + * Assumptions: + * - This function will *not* be called from an interrupt handler. + * - If this function returns an error, the USB host controller driver + * must call to DISCONNECTED method to recover from the error + * + ****************************************************************************/ + +static int usbhost_connect(FAR struct usbhost_class_s *usbclass, + FAR const uint8_t *configdesc, int desclen) +{ + FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)usbclass; + FAR struct usbhost_hubport_s *hport; + char devname[DEV_NAMELEN]; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + hport = priv->usbclass.hport; + DEBUGASSERT(hport); + + /* Get exclusive access to the device structure */ + + usbhost_takesem(&priv->exclsem); + + /* Increment the reference count. This will prevent usbhost_destroy() from + * being called asynchronously if the device is removed. + */ + + priv->crefs++; + DEBUGASSERT(priv->crefs == 2); + + /* Parse the configuration descriptor to get the bulk I/O endpoints */ + + ret = usbhost_cfgdesc(priv, configdesc, desclen); + if (ret < 0) + { + udbg("usbhost_cfgdesc() failed: %d\n", ret); + goto errout; + } + + /* Set aside a transfer buffer for exclusive use by the CDC/ACM driver */ + + ret = usbhost_alloc_buffers(priv); + if (ret < 0) + { + udbg("ERROR: Failed to allocate transfer buffer\n"); + goto errout; + } + + /* Send the initial line encoding */ + + ret = usbhost_linecoding_send(priv); + if (ret < 0) + { + udbg("usbhost_linecoding_send() failed: %d\n", ret); + } + + /* Register the lower half serial instance with the upper half serial + * driver */ + + usbhost_mkdevname(priv, devname); + ret = uart_register(devname, &priv->uartdev); + if (ret < 0) + { + udbg("uart_register() failed: %d\n", ret); + goto errout; + } + + /* Do we have an interrupt IN endpoint? */ + + if (priv->intin) + { + /* Begin monitoring of port status change events */ + + ret = DRVR_ASYNCH(hport->drvr, priv->intin, + (FAR uint8_t *)priv->notification, + MAX_NOTIFICATION, usbhost_notification_callback, + &priv->usbclass); + if (ret < 0) + { + udbg("ERROR: DRVR_ASYNCH failed: %d\n", ret); + } + } + +errout: + /* Decrement the reference count. We incremented the reference count + * above so that usbhost_destroy() could not be called. We now have to + * be concerned about asynchronous modification of crefs because the + * serial driver has been registered. + */ + + DEBUGASSERT(priv->crefs >= 2); + priv->crefs--; + + /* Release the semaphore... there is a race condition here. + * Decrementing the reference count and releasing the semaphore + * allows usbhost_destroy() to execute (on the worker thread); + * the class driver instance could get destroyed before we are + * ready to handle it! + */ + + usbhost_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: usbhost_disconnected + * + * Description: + * This function implements the disconnected() method of struct + * usbhost_class_s. This method is a callback into the class + * implementation. It is used to inform the class that the USB device has + * been disconnected. + * + * Input Parameters: + * usbclass - The USB host class entry previously obtained from a call to + * create(). + * + * Returned Value: + * On success, zero (OK) is returned. On a failure, a negated errno value + * is returned indicating the nature of the failure + * + * Assumptions: + * This function may be called from an interrupt handler. + * + ****************************************************************************/ + +static int usbhost_disconnected(struct usbhost_class_s *usbclass) +{ + FAR struct usbhost_cdcacm_s *priv = (FAR struct usbhost_cdcacm_s *)usbclass; + irqstate_t flags; + + DEBUGASSERT(priv != NULL); + + /* Set an indication to any users of the CDC/ACM device that the device + * is no longer available. + */ + + flags = irqsave(); + priv->disconnected = true; + + /* Now check the number of references on the class instance. If it is one, + * then we can free the class instance now. Otherwise, we will have to + * wait until the holders of the references free them by closing the + * serial driver. + */ + + ullvdbg("crefs: %d\n", priv->crefs); + if (priv->crefs == 1) + { + /* Destroy the class instance. If we are executing from an interrupt + * handler, then defer the destruction to the worker thread. + * Otherwise, destroy the instance now. + */ + + if (up_interrupt_context()) + { + /* Destroy the instance on the worker thread. */ + + uvdbg("Queuing destruction: worker %p->%p\n", + priv->ntwork.worker, usbhost_destroy); + + DEBUGASSERT(work_available(&priv->ntwork)); + (void)work_queue(HPWORK, &priv->ntwork, usbhost_destroy, priv, 0); + } + else + { + /* Do the work now */ + + usbhost_destroy(priv); + } + } + + irqrestore(flags); + return OK; +} + +/**************************************************************************** + * Serial Lower-Half Interfaces + ****************************************************************************/ +/**************************************************************************** + * Name: usbhost_setup + * + * Description: + * Configure the USART baud, bits, parity, etc. This method is called the + * first time that the serial port is opened. + * + ****************************************************************************/ + +static int usbhost_setup(FAR struct uart_dev_s *uartdev) +{ + FAR struct usbhost_cdcacm_s *priv; + irqstate_t flags; + int ret; + + uvdbg("Entry\n"); + DEBUGASSERT(uartdev && uartdev->priv); + priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv; + + /* Make sure that we have exclusive access to the private data structure */ + + DEBUGASSERT(priv->crefs > 0 && priv->crefs < USBHOST_MAX_CREFS); + usbhost_takesem(&priv->exclsem); + + /* Check if the CDC/ACM device is still connected. We need to disable + * interrupts momentarily to assure that there are no asynchronous + * isconnect events. + */ + + flags = irqsave(); + if (priv->disconnected) + { + /* No... the block driver is no longer bound to the class. That means that + * the USB CDC/ACM device is no longer connected. Refuse any further + * attempts to open the driver. + */ + + ret = -ENODEV; + } + else + { + /* Otherwise, just increment the reference count on the driver */ + + priv->crefs++; + ret = OK; + } + + irqrestore(flags); + usbhost_givesem(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * Name: usbhost_shutdown + * + * Description: + * Disable the USART. This method is called when the serial + * port is closed + * + ****************************************************************************/ + +static void usbhost_shutdown(FAR struct uart_dev_s *uartdev) +{ + FAR struct usbhost_cdcacm_s *priv; + irqstate_t flags; + + uvdbg("Entry\n"); + DEBUGASSERT(uartdev && uartdev->priv); + priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv; + + /* Decrement the reference count on the block driver */ + + DEBUGASSERT(priv->crefs > 1); + usbhost_takesem(&priv->exclsem); + priv->crefs--; + + /* Release the semaphore. The following operations when crefs == 1 are + * safe because we know that there is no outstanding open references to + * the block driver. + */ + + usbhost_givesem(&priv->exclsem); + + /* We need to disable interrupts momentarily to assure that there are + * no asynchronous disconnect events. + */ + + flags = irqsave(); + + /* Check if the USB CDC/ACM device is still connected. If the + * CDC/ACM device is not connected and the reference count just + * decremented to one, then unregister the block driver and free + * the class instance. + */ + + if (priv->crefs <= 1 && priv->disconnected) + { + /* Destroy the class instance */ + + DEBUGASSERT(priv->crefs == 1); + usbhost_destroy(priv); + } + + irqrestore(flags); +} + +/**************************************************************************** + * Name: usbhost_attach + * + * Description: + * Configure the USART to operation in interrupt driven mode. This method is + * called when the serial port is opened. Normally, this is just after the + * the setup() method is called, however, the serial console may operate in + * a non-interrupt driven mode during the boot phase. + * + * RX and TX interrupts are not enabled when by the attach method (unless the + * hardware supports multiple levels of interrupt enabling). The RX and TX + * interrupts are not enabled until the txint() and rxint() methods are called. + * + ****************************************************************************/ + +static int usbhost_attach(FAR struct uart_dev_s *uartdev) +{ + return OK; +} + +/**************************************************************************** + * Name: usbhost_detach + * + * Description: + * Detach USART interrupts. This method is called when the serial port is + * closed normally just before the shutdown method is called. The exception + * is the serial console which is never shutdown. + * + ****************************************************************************/ + +static void usbhost_detach(FAR struct uart_dev_s *uartdev) +{ +} + +/**************************************************************************** + * Name: usbhost_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + ****************************************************************************/ + +static int usbhost_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + struct inode *inode; + struct usbhost_cdcacm_s *priv; + int ret; + + uvdbg("Entry\n"); + DEBUGASSERT(filep && filep->f_inode); + inode = filep->f_inode; + + DEBUGASSERT(inode && inode->i_private); + priv = (FAR struct usbhost_cdcacm_s *)inode->i_private; + + /* Check if the CDC/ACM device is still connected */ + + if (priv->disconnected) + { + /* No... the serial device has been disconnecgted. Refuse to process + * any ioctl commands. + */ + + ret = -ENODEV; + } + else + { + /* Process the IOCTL by command */ + + usbhost_takesem(&priv->exclsem); + switch (cmd) + { +#ifdef CONFIG_SERIAL_TIOCSERGSTRUCT + case TIOCSERGSTRUCT: + { + FAR struct usbhost_cdcacm_s *user = + (FAR struct usbhost_cdcacm_s *)arg; + if (!user) + { + ret = -EINVAL; + } + else + { + memcpy(user, uartdev, sizeof(struct usbhost_cdcacm_s)); + } + } + break; +#endif + +#ifdef CONFIG_SERIAL_TERMIOS + case TCGETS: + { + FAR struct termios *termiosp = (FAR struct termios*)arg; + + if (!termiosp) + { + ret = -EINVAL; + break; + } + + cfsetispeed(termiosp, priv->baud); + + termiosp->c_cflag = + ((priv->parity != 0) ? PARENB : 0) | + ((priv->parity == 1) ? PARODD : 0) | + ((priv->stop2) ? CSTOPB : 0) +#ifdef CONFIG_SERIAL_OFLOWCONTROL + | ((priv->oflow) ? CCTS_OFLOW : 0) +#endif +#ifdef CONFIG_SERIAL_IFLOWCONTROL + | ((priv->iflow) ? CRTS_IFLOW : 0) +#endif + ; + + switch (priv->bits) + { + case 5: + termiosp->c_cflag |= CS5; + break; + + case 6: + termiosp->c_cflag |= CS6; + break; + + case 7: + termiosp->c_cflag |= CS7; + break; + + default: /* 16-bits? */ + case 8: + termiosp->c_cflag |= CS8; + break; + } + } + break; + + case TCSETS: + { + FAR struct termios *termiosp = (FAR struct termios*)arg; +#ifdef CONFIG_SERIAL_IFLOWCONTROL + bool iflow; +#endif + + if (!termiosp) + { + ret = -EINVAL; + break; + } + + if (termiosp->c_cflag & PARENB) + { + priv->parity = (termiosp->c_cflag & PARODD) ? 1 : 2; + } + else + { + priv->parity = 0; + } + + priv->stopbits2 = (termiosp->c_cflag & CSTOPB) != 0; +#ifdef CONFIG_SERIAL_OFLOWCONTROL + priv->oflow = (termiosp->c_cflag & CCTS_OFLOW) != 0; +#endif +#ifdef CONFIG_SERIAL_IFLOWCONTROL + iflow = priv->iflow; + priv->iflow = (termiosp->c_cflag & CRTS_IFLOW) != 0; +#endif + + switch (termiosp->c_flag & CSIZE) + { + case C5: + priv->bits = 5; + break; + + case C6: + priv->bits = 6; + break; + + case C7: + priv->bits = 7; + break; + + default: + case C8: + priv->bits = 8; + break; + } + + /* Note that only cfgetispeed is used because we have knowledge + * that only one speed is supported. + */ + + priv->baud = cfgetispeed(termiosp); + +#ifdef CONFIG_SERIAL_IFLOWCONTROL + /* Set RTS if input flow control changed */ + + if (iflow != !priv->iflow) + { + priv->rts = true; + } +#endif + + /* Effect the changes immediately - note that we do not implement + * TCSADRAIN / TCSAFLUSH + */ + + ret = usbhost_linecoding_send(priv); + } + break; +#endif /* CONFIG_SERIAL_TERMIOS */ + +#ifdef CONFIG_USBHOST_CDCACM_BREAKS + case TIOCSBRK: /* BSD compatibility: Turn break on, unconditionally */ + { +#warning Missing logic + } + break; + + case TIOCCBRK: /* BSD compatibility: Turn break off, unconditionally */ + { +#warning Missing logic + } + break; +#endif + + default: + ret = -ENOTTY; + break; + } + + usbhost_givesem(&priv->exclsem); + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_rxint + * + * Description: + * Call to enable or disable RX interrupts + * + ****************************************************************************/ + +static void usbhost_rxint(FAR struct uart_dev_s *uartdev, bool enable) +{ + FAR struct usbhost_cdcacm_s *priv; + int ret; + + DEBUGASSERT(uartdev && uartdev->priv); + priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv; + + /* Are we enabling or disabling RX reception? */ + + if (enable && !priv->rxena) + { + /* Restart RX data reception work flow unless RX flow control is in + * effect. + */ + +#ifdef CONFIG_SERIAL_IFLOWCONTROL + if (priv->rts) +#endif + { + DEBUGASSERT(work_available(&priv->rxwork)); + ret = work_queue(LPWORK, &priv->rxwork, + usbhost_rxdata_work, priv, 0); + DEBUGASSERT(ret >= 0); + UNUSED(ret); + } + } + else if (!enable && priv->rxena) + { + /* Cancel any pending RX data reception work */ + + (void)work_cancel(LPWORK, &priv->rxwork); + } + + /* Save the new RX enable state */ + + priv->rxena = enable; +} + +/**************************************************************************** + * Name: usbhost_rxavailable + * + * Description: + * Return true if the receive buffer is not empty + * + ****************************************************************************/ + +static bool usbhost_rxavailable(FAR struct uart_dev_s *uartdev) +{ + + FAR struct usbhost_cdcacm_s *priv; + + DEBUGASSERT(uartdev && uartdev->priv); + priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv; + return (priv->nrxbytes > 0); +} + +/**************************************************************************** + * Name: usbhost_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: + * uartdev - 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 usbhost_rxflowcontrol(FAR struct uart_dev_s *uartdev, + unsigned int nbuffered, bool upper) +{ + FAR struct usbhost_cdcacm_s *priv; + bool newrts; + int ret; + + DEBUGASSERT(uartdev && uartdev->priv); + priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv + + /* Is RX flow control enabled? */ + + if (!priv->iflow) + { + /* Now.. make sure that RTS is set */ + + priv->rts = true; + return false; + } + + /* Are we enabling or disabling RX flow control? */ + + if (priv->rts && upper) + { + /* RX flow control is not in effect (RTS is true) but we have just + * crossed the upper threshold, meaning that we should now clear + * RTS. + */ + + priv ->rts = false; + + /* Cancel any pending RX data reception work */ + + (void)work_cancel(LPWORK, &priv->rxwork) + return true; + } + else if (!priv->rts && !upper) + { + /* RX flow control is in effect (RTS is false) and we have just + * crossed the lower threshold, meaning that we should now set + * RTS. + */ + + priv ->rts = true; + + /* Restart RX data reception work flow unless RX reception is + * disabled. + */ + + if (priv->rxena) + { + DEBUGASSERT(work_available(&priv->rxwork)); + ret = work_queue(LPWORK, &priv->rxwork, + usbhost_rxdata_work, priv, 0); + DEBUGASSERT(ret >= 0); + UNUSED(ret); + } + + return false; + } +} +#endif + +/**************************************************************************** + * Name: usbhost_txint + * + * Description: + * Call to enable or disable TX interrupts + * + ****************************************************************************/ + +static void usbhost_txint(FAR struct uart_dev_s *uartdev, bool enable) +{ + FAR struct usbhost_cdcacm_s *priv; + int ret; + + DEBUGASSERT(uartdev && uartdev->priv); + priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv; + + /* Are we enabling or disabling TX transmission? */ + + if (enable && !priv->txena) + { + /* Restart TX data transmission work flow */ + + DEBUGASSERT(work_available(&priv->txwork)); + ret = work_queue(LPWORK, &priv->txwork, + usbhost_txdata_work, priv, 0); + DEBUGASSERT(ret >= 0); + UNUSED(ret); + } + else if (!enable && priv->txena) + { + /* Cancel any pending TX data transmission work */ + + (void)work_cancel(LPWORK, &priv->txwork); + } + + /* Save the new RX enable state */ + + priv->txena = enable; +} + +/**************************************************************************** + * Name: usbhost_txready + * + * Description: + * Return true if the transmit data register is not full. + * + ****************************************************************************/ + +static bool usbhost_txready(FAR struct uart_dev_s *uartdev) +{ + return true; +} + +/**************************************************************************** + * Name: usbhost_txempty + * + * Description: + * Return true if the transmit data buffer is empty + * + ****************************************************************************/ + +static bool usbhost_txempty(FAR struct uart_dev_s *uartdev) +{ + return true; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_cdcacm_initialize + * + * Description: + * Initialize the USB host CDC/ACM class. This function should be called + * be platform-specific code in order to initialize and register support + * for the USB host CDC/ACM class. + * + * Input Parameters: + * None + * + * Returned Value: + * On success this function will return zero (OK); A negated errno value + * will be returned on failure. + * + ****************************************************************************/ + +int usbhost_cdcacm_initialize(void) +{ + /* If we have been configured to use pre-allocated CDC/ACM class instances, + * then place all of the pre-allocated USB host CDC/ACM class instances + * into a free list. + */ + +#if CONFIG_USBHOST_CDCACM_NPREALLOC > 0 + FAR struct usbhost_freestate_s *entry; + int i; + + g_freelist = NULL; + for (i = 0; i < CONFIG_USBHOST_CDCACM_NPREALLOC; i++) + { + entry = (FAR struct usbhost_freestate_s *)&g_prealloc[i]; + entry->flink = g_freelist; + g_freelist = entry; + } +#endif + + /* Advertise our availability to support (certain) CDC/ACM devices */ + + return usbhost_registerclass(&g_cdcacm); +} + +#endif /* CONFIG_USBHOST_CDCACM */