nuttx/drivers/usbhost/usbhost_cdcacm.c
2015-10-10 10:41:00 -06:00

2805 lines
82 KiB
C

/****************************************************************************
* drivers/usbhost/usbhost_cdcacm.c
*
* Copyright (C) 2015 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* 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 <nuttx/config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <semaphore.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/arch.h>
#include <nuttx/wqueue.h>
#include <nuttx/clock.h>
#include <nuttx/serial/serial.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/usbhost.h>
#include <nuttx/usb/cdc.h>
#include <nuttx/usb/cdcacm.h>
#include <nuttx/usb/usbhost_devaddr.h>
#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
#ifndef CONFIG_SERIAL_REMOVABLE
# warning Removable serial device support is required (CONFIG_SERIAL_REMOVABLE)
#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
/* Supported protocol */
#define HAVE_CLASS_REQUESTS 1
#define HAVE_INTIN_ENDPOINT 1
#define HAVE_CTRL_INTERFACE 1
#if defined(CONFIG_USBHOST_CDCACM_REDUCED)
# undef CONFIG_USBHOST_CDCACM_BULKONLY
# undef CONFIG_USBHOST_CDCACM_COMPLIANT
# undef HAVE_INTIN_ENDPOINT
#elif defined(CONFIG_USBHOST_CDCACM_BULKONLY)
# undef CONFIG_USBHOST_CDCACM_COMPLIANT
# undef HAVE_CLASS_REQUESTS
# undef HAVE_INTIN_ENDPOINT
# undef HAVE_CTRL_INTERFACE
#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_DATAIF_FOUND 0x01 /* Data interface found */
#define USBHOST_BULKIN_FOUND 0x02 /* Bulk IN interface found */
#define USBHOST_BULKOUT_FOUND 0x04 /* Bulk OUT interface found */
#if defined(CONFIG_USBHOST_CDCACM_BULKONLY)
# define USBHOST_MINFOUND 0x07 /* Minimum things needed */
# define USBHOST_ALLFOUND 0x07 /* All configuration things */
#elif defined(CONFIG_USBHOST_CDCACM_REDUCED)
# define USBHOST_CTRLIF_FOUND 0x08 /* Control interface found */
# define USBHOST_MINFOUND 0x07 /* Minimum things needed */
# define USBHOST_HAVE_CTRLIF 0x08 /* Needed for control interface */
# define USBHOST_ALLFOUND 0x0f /* All configuration things */
#else
# define USBHOST_CTRLIF_FOUND 0x08 /* Control interface found */
# define USBHOST_INTIN_FOUND 0x10 /* Interrupt IN interface found */
# define USBHOST_MINFOUND 0x07 /* Minimum things needed */
# define USBHOST_HAVE_CTRLIF 0x18 /* Needed for control interface */
# define USBHOST_ALLFOUND 0x1f /* All configuration things */
#endif
#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 dataif; /* Data interface number */
#ifdef HAVE_CTRL_INTERFACE
uint8_t ctrlif; /* Control interface number */
#endif
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 */
#ifdef HAVE_INTIN_ENDPOINT
usbhost_ep_t intin; /* Interrupt IN endpoint (optional) */
#endif
/* 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 */
#ifdef HAVE_CTRL_INTERFACE
static int usbhost_linecoding_send(FAR struct usbhost_cdcacm_s *priv);
#ifdef HAVE_INTIN_ENDPOINT
static void usbhost_notification_work(FAR void *arg);
static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes);
#endif
#endif
/* 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);
#ifdef HAVE_CTRL_INTERFACE
static void usbhost_putle32(uint8_t *dest, uint32_t val);
#endif
/* 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);
uvdbg("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);
uvdbg("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_kfree() in case we are executing
* from an interrupt handler.
*/
uvdbg("Freeing: %p\n", usbclass);
sched_kfree(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
*
****************************************************************************/
#ifdef HAVE_CTRL_INTERFACE
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->ctrlif);
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;
}
#endif
/****************************************************************************
* 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.
*
****************************************************************************/
#ifdef HAVE_INTIN_ENDPOINT
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->ctrlif) &&
(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);
if (ret < 0)
{
udbg("ERROR: DRVR_ASYNCH failed: %d\n", ret);
}
}
}
#endif
/****************************************************************************
* 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.
*
****************************************************************************/
#ifdef HAVE_INTIN_ENDPOINT
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
{
udbg("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);
}
}
}
#endif
/************************************************************************************
* 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 txndx;
int txtail;
int ret;
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 (or we become disconnected) */
txtail = txbuf->tail;
txndx = 0;
while (txtail != txbuf->head && priv->txena && !priv->disconnected)
{
/* Copy data from the UART TX buffer until either 1) the UART TX
* buffer has been emptied, 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 */
txndx++;
if (++txtail > txbuf->size)
{
txtail = 0;
}
}
/* Save the updated tail pointer so that it cannot be sent again */
txbuf->tail = txtail;
/* Bytes were removed from the TX buffer. Inform any waiters that
* there is space available in the TX buffer.
*/
uart_datasent(uartdev);
/* 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 OR that the device has been disconnected.
*
* Just break out of the loop, rescheduling the work (unless
* the device is disconnected).
*/
udbg("ERROR: DRVR_TRANSFER for packet failed: %d\n", (int)nwritten);
break;
}
}
/* We get here because: 1) the UART TX buffer is empty and there is
* nothing more to send, 2) the CDC/ACM device was not ready to accept our
* data, or the device is no longer available.
*
* If the last packet sent was and even multiple of the packet size, then
* we need to send a zero length packet (ZLP).
*/
if (txndx == priv->pktsize && !priv->disconnected)
{
/* 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);
}
}
/* Check again if TX reception is enabled and that the device is still
* connected. These states could have changed since we started the
* transfer.
*/
if (priv->txena && !priv->disconnected)
{
/* Schedule TX data work to occur after a delay. */
ret = work_queue(LPWORK, &priv->txwork, usbhost_txdata_work, priv,
USBHOST_CDCACM_TXDELAY);
DEBUGASSERT(ret >= 0);
UNUSED(ret);
}
}
/****************************************************************************
* 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;
/* 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
* 3. RX rec
*/
#ifdef CONFIG_SERIAL_IFLOWCONTROL
while (priv->rxena && priv->rts && !priv->disconnected)
#else
while (priv->rxena && !priv->disconnected)
#endif
{
/* 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 OR that the
* transfer was cancelled because the device was disconnected.
*
* Just break out of the loop, rescheduling the work (if the
* device was not disconnected.
*/
udbg("ERROR: DRVR_TRANSFER for packet failed: %d\n", (int)nread);
break;
}
/* Save the number of bytes read. This might be zero if
* a Zero Length Packet (ZLP) is received. The ZLP is
* part of the bulk transfer protocol, but otherwise of
* no interest to us.
*/
DEBUGASSERT(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 head point for for the next pass through the loop
* handling. If nexthead incremented to rxbuf->tail, then the
* RX buffer will and we will exit the loop at the top.
*/
if (++nexthead >= rxbuf->size)
{
nexthead = 0;
}
/* Increment the index in the USB IN packet buffer. If the
* index becomes equal to the number of bytes in the buffer, then
* we have consumed all of the RX data.
*/
if (++rxndx >= priv->nrxbytes)
{
/* In that case set the number of bytes in the buffer to zero.
* This will force re-reading on the next time through the loop.
*/
priv->nrxbytes = 0;
priv->rxndx = 0;
/* Inform any waiters there there is new incoming data available. */
uart_datareceived(uartdev);
nxfrd = 0;
}
}
/* We break out to here: 1) the UART RX buffer is full, 2) the CDC/ACM
* device is not ready to provide us with more serial data, or 3) the
* device has been disconnected.
*
* Check if the device is still available: RX enabled, no RX flow
* control in effect, and that the device is not disconnected. These
* states could have changed since we started the transfer.
*/
#ifdef CONFIG_SERIAL_IFLOWCONTROL
if (priv->rxena && priv->rts && work_available(&priv->rxwork) && !priv->disconnected)
#else
if (priv->rxena && work_available(&priv->rxwork) && !priv->disconnected)
#endif
{
/* Schedule RX data reception work to occur after a delay. This will
* affect our responsive in certain cases. The delayed work, however,
* will be cancelled and replaced with immediate work when the upper
* layer demands more data.
*/
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);
}
#ifdef HAVE_INTIN_ENDPOINT
if (priv->intin)
{
DRVR_EPFREE(hport->drvr, priv->intin);
}
#endif
/* 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;
uint8_t currif = 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. The CDC/ACM device may include two
* interfaces:
*
* 1) A data interface which consists of two endpoints (bulk in +
* bulk out) and
* 2) A control interface which consists of one interrupt in
* endpoint.
*/
case USB_DESC_TYPE_INTERFACE:
{
FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)configdesc;
uvdbg("Interface descriptor: class: %d subclass: %d proto: %d\n",
ifdesc->classid, ifdesc->subclass, ifdesc->protocol);
DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC);
/* Check for the CDC/ACM data interface */
if (ifdesc->classid == USB_CLASS_CDC_DATA &&
(found & USBHOST_DATAIF_FOUND) == 0)
{
/* Save the data interface number and mark that the data
* interface found has been found.
*/
priv->dataif = ifdesc->ifno;
found |= USBHOST_DATAIF_FOUND;
currif = USBHOST_DATAIF_FOUND;
}
#ifdef HAVE_CTRL_INTERFACE
else if (ifdesc->classid == USB_CLASS_CDC &&
(found & USBHOST_CTRLIF_FOUND) == 0)
{
/* Otherwise, tentatively assume that this is the control
* interface.
*/
priv->ctrlif = ifdesc->ifno;
currif = USBHOST_CTRLIF_FOUND;
}
#endif
else
{
/* Its something else */
currif = 0;
}
}
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: currif: %02x attr: %02x\n",
currif, epdesc->attr);
DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC);
/* Check for a bulk endpoint. */
if (currif == USBHOST_DATAIF_FOUND &&
(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_BULKOUT_FOUND) != 0)
{
/* Oops.. more than one endpoint. We don't know
* what to do with this.
*/
return -EINVAL;
}
found |= USBHOST_BULKOUT_FOUND;
/* 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_BULKIN_FOUND) != 0)
{
/* Oops.. more than one endpoint. We don't know
* what to do with this.
*/
return -EINVAL;
}
found |= USBHOST_BULKIN_FOUND;
/* 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);
}
}
#ifdef HAVE_CTRL_INTERFACE
/* Check for an interrupt IN endpoint. */
else if (currif == USBHOST_CTRLIF_FOUND &&
(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))
{
#ifdef HAVE_INTIN_ENDPOINT
/* It is an IN interrupt endpoint. There should be only one
* interrupt IN endpoint.
*/
if ((found & USBHOST_INTIN_FOUND) != 0)
{
/* Oops.. more than one. We don't know what to do
* with this.
*/
return -EINVAL;
}
/* Let's pick this interface as the one and only control
* interface.
*/
found |= (USBHOST_CTRLIF_FOUND | USBHOST_INTIN_FOUND);
/* 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);
#else
found |= USBHOST_CTRLIF_FOUND;
#endif
}
}
#endif
}
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 needed for the
* basic CDC/ACM data itnerface? NOTE: that the Control interface with
* the Interrupt IN endpoint is optional.
*/
if ((found & USBHOST_MINFOUND) != USBHOST_MINFOUND)
{
udbg("ERROR: Found DATA IF:%s BULK IN:%s BULK OUT:%s\n",
(found & USBHOST_DATAIF_FOUND) != 0 ? "YES" : "NO",
(found & USBHOST_BULKIN_FOUND) != 0 ? "YES" : "NO",
(found & USBHOST_BULKOUT_FOUND) != 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;
}
#ifdef HAVE_INTIN_ENDPOINT
/* The control interface with interrupt IN endpoint is optional */
if ((found & USBHOST_HAVE_CTRLIF) == USBHOST_HAVE_CTRLIF)
{
ret = DRVR_EPALLOC(hport->drvr, &iindesc, &priv->intin);
if (ret < 0)
{
udbg("ERROR: Failed to allocate Interrupt IN endpoint\n");
priv->intin = NULL;
}
}
#endif
uvdbg("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
*
****************************************************************************/
#ifdef HAVE_CTRL_INTERFACE
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));
}
#endif
/****************************************************************************
* 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 (%d bytes)\n",
ret, sizeof(struct cdc_linecoding_s));
goto errout;
}
#ifdef HAVE_INTIN_ENDPOINT
/* 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 (%d bytes)\n",
ret, MAX_NOTIFICATION);
goto errout;
}
}
#endif
/* 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 (%d bytes)\n",
ret, priv->pktsize);
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 (%d bytes)\n",
ret, priv->pktsize);
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;
#ifdef HAVE_INTIN_ENDPOINT
FAR struct usbhost_hubport_s *hport;
#endif
char devname[DEV_NAMELEN];
int ret;
DEBUGASSERT(priv != NULL &&
configdesc != NULL &&
desclen >= sizeof(struct usb_cfgdesc_s));
#ifdef HAVE_INTIN_ENDPOINT
hport = priv->usbclass.hport;
DEBUGASSERT(hport);
#endif
/* 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;
}
#ifdef HAVE_CTRL_INTERFACE
/* Send the initial line encoding */
ret = usbhost_linecoding_send(priv);
if (ret < 0)
{
udbg("usbhost_linecoding_send() failed: %d\n", ret);
}
#endif
/* Register the lower half serial instance with the upper half serial
* driver */
usbhost_mkdevname(priv, devname);
uvdbg("Register device: %s\n", devname);
ret = uart_register(devname, &priv->uartdev);
if (ret < 0)
{
udbg("uart_register() failed: %d\n", ret);
goto errout;
}
#ifdef HAVE_INTIN_ENDPOINT
/* Do we have an interrupt IN endpoint? */
if (priv->intin)
{
/* Begin monitoring of port status change events */
uvdbg("Start notification monitoring\n");
ret = DRVR_ASYNCH(hport->drvr, priv->intin,
(FAR uint8_t *)priv->notification,
MAX_NOTIFICATION, usbhost_notification_callback,
priv);
if (ret < 0)
{
udbg("ERROR: DRVR_ASYNCH failed: %d\n", ret);
}
}
#endif
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;
FAR struct usbhost_hubport_s *hport;
irqstate_t flags;
int ret;
DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL);
hport = priv->usbclass.hport;
/* Set an indication to any users of the CDC/ACM device that the device
* is no longer available.
*/
flags = irqsave();
priv->disconnected = true;
/* Let the upper half driver know that serial device is no longer
* connected.
*/
uart_connected(&priv->uartdev, false);
/* Cancel any ongoing Bulk transfers */
ret = DRVR_CANCEL(hport->drvr, priv->bulkin);
if (ret < 0)
{
udbg("ERROR: Bulk IN DRVR_CANCEL failed: %d\n", ret);
}
ret = DRVR_CANCEL(hport->drvr, priv->bulkout);
if (ret < 0)
{
udbg("ERROR: Bulk OUT DRVR_CANCEL failed: %d\n", ret);
}
#ifdef HAVE_INTIN_ENDPOINT
/* Cancel any pending asynchronous I/O */
if (priv->intin)
{
int ret = DRVR_CANCEL(hport->drvr, priv->intin);
if (ret < 0)
{
udbg("ERROR: Interrupt IN DRVR_CANCEL failed: %d\n", ret);
}
}
#endif
/* 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.
*/
uvdbg("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
#ifdef HAVE_CTRL_INTERFACE
/* Effect the changes immediately - note that we do not implement
* TCSADRAIN / TCSAFLUSH
*/
ret = usbhost_linecoding_send(priv);
#endif
}
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)
{
/* Cancel any pending, delayed RX data reception work */
(void)work_cancel(LPWORK, &priv->rxwork);
/* Restart immediate RX data reception work (unless RX flow control
* is in* effect.
*/
#ifdef CONFIG_SERIAL_IFLOWCONTROL
if (priv->rts))
#endif
{
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 && 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)
{
/* Cancel any pending, delayed TX data transmission work */
(void)work_cancel(LPWORK, &priv->txwork);
/* Restart immediate TX data transmission work */
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 */