6a3c2aded6
* Simplify EINTR/ECANCEL error handling 1. Add semaphore uninterruptible wait function 2 .Replace semaphore wait loop with a single uninterruptible wait 3. Replace all sem_xxx to nxsem_xxx * Unify the void cast usage 1. Remove void cast for function because many place ignore the returned value witout cast 2. Replace void cast for variable with UNUSED macro
2821 lines
83 KiB
C
2821 lines
83 KiB
C
/****************************************************************************
|
|
* drivers/usbhost/usbhost_cdcacm.c
|
|
*
|
|
* Copyright (C) 2015-2017, 2019 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/irq.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/clock.h>
|
|
#include <nuttx/fs/ioctl.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 in 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(FAR sem_t *sem);
|
|
#define usbhost_givesem(s) nxsem_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(FAR const uint8_t *val);
|
|
static inline uint16_t usbhost_getbe16(FAR const uint8_t *val);
|
|
static inline void usbhost_putle16(FAR uint8_t *dest, uint16_t val);
|
|
#ifdef HAVE_CTRL_INTERFACE
|
|
static void usbhost_putle32(FAR 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 struct usbhost_id_s g_id[2] =
|
|
{
|
|
{
|
|
USB_CLASS_CDC, /* base */
|
|
CDC_SUBCLASS_NONE, /* subclass */
|
|
CDC_PROTO_NONE, /* proto */
|
|
0, /* vid */
|
|
0 /* pid */
|
|
},
|
|
{
|
|
USB_CLASS_CDC, /* base */
|
|
CDC_SUBCLASS_ACM, /* subclass */
|
|
CDC_PROTO_ATM, /* 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 */
|
|
2, /* nids */
|
|
&g_id[0] /* 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(FAR sem_t *sem)
|
|
{
|
|
nxsem_wait_uninterruptible(sem);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* 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 = enter_critical_section();
|
|
entry = g_freelist;
|
|
if (entry)
|
|
{
|
|
g_freelist = entry->flink;
|
|
}
|
|
|
|
leave_critical_section(flags);
|
|
uinfo("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));
|
|
|
|
uinfo("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);
|
|
|
|
uinfo("Freeing: %p\n", entry);
|
|
|
|
/* Just put the pre-allocated class structure back on the freelist */
|
|
|
|
flags = enter_critical_section();
|
|
entry->flink = g_freelist;
|
|
g_freelist = entry;
|
|
leave_critical_section(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.
|
|
*/
|
|
|
|
uinfo("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 = enter_critical_section();
|
|
for (devno = 0; devno < 32; devno++)
|
|
{
|
|
uint32_t bitno = 1 << devno;
|
|
if ((g_devinuse & bitno) == 0)
|
|
{
|
|
g_devinuse |= bitno;
|
|
priv->minor = devno;
|
|
leave_critical_section(flags);
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
leave_critical_section(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 = enter_critical_section();
|
|
g_devinuse &= ~(1 << devno);
|
|
leave_critical_section(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)
|
|
{
|
|
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)
|
|
{
|
|
uerr("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);
|
|
|
|
uinfo("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);
|
|
|
|
uinfo("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)
|
|
{
|
|
uerr("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_INFO)
|
|
if (nbytes != -EAGAIN)
|
|
#endif
|
|
{
|
|
uerr("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))
|
|
{
|
|
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).
|
|
*/
|
|
|
|
uerr("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.
|
|
*/
|
|
|
|
uerr("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.
|
|
*/
|
|
|
|
uerr("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.
|
|
*/
|
|
|
|
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;
|
|
|
|
uinfo("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 */
|
|
|
|
nxsem_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;
|
|
struct usbhost_epdesc_s bindesc;
|
|
struct usbhost_epdesc_s boutdesc;
|
|
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;
|
|
|
|
uinfo("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;
|
|
|
|
uinfo("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);
|
|
|
|
uinfo("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);
|
|
uinfo("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);
|
|
|
|
uinfo("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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("ERROR: Failed to allocate Bulk OUT endpoint\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = DRVR_EPALLOC(hport->drvr, &bindesc, &priv->bulkin);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: Failed to allocate Bulk IN endpoint\n");
|
|
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)
|
|
{
|
|
uerr("ERROR: Failed to allocate Interrupt IN endpoint\n");
|
|
priv->intin = NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
uinfo("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(FAR 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(FAR 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(FAR 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(FAR 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 success, 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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
DRVR_FREE(hport->drvr, priv->ctrlreq);
|
|
}
|
|
|
|
if (priv->linecode)
|
|
{
|
|
DRVR_IOFREE(hport->drvr, priv->linecode);
|
|
}
|
|
|
|
if (priv->notification)
|
|
{
|
|
DRVR_IOFREE(hport->drvr, priv->notification);
|
|
}
|
|
|
|
if (priv->inbuf)
|
|
{
|
|
DRVR_IOFREE(hport->drvr, priv->inbuf);
|
|
}
|
|
|
|
if (priv->outbuf)
|
|
{
|
|
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) */
|
|
|
|
nxsem_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)
|
|
{
|
|
uerr("ERROR: 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)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("ERROR: usbhost_linecoding_send() failed: %d\n", ret);
|
|
}
|
|
#endif
|
|
|
|
/* Register the lower half serial instance with the upper half serial
|
|
* driver.
|
|
*/
|
|
|
|
usbhost_mkdevname(priv, devname);
|
|
uinfo("Register device: %s\n", devname);
|
|
|
|
ret = uart_register(devname, &priv->uartdev);
|
|
if (ret < 0)
|
|
{
|
|
uerr("ERROR: 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 */
|
|
|
|
uinfo("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)
|
|
{
|
|
uerr("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(FAR 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 = enter_critical_section();
|
|
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)
|
|
{
|
|
uerr("ERROR: Bulk IN DRVR_CANCEL failed: %d\n", ret);
|
|
}
|
|
|
|
ret = DRVR_CANCEL(hport->drvr, priv->bulkout);
|
|
if (ret < 0)
|
|
{
|
|
uerr("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)
|
|
{
|
|
uerr("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.
|
|
*/
|
|
|
|
uinfo("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. */
|
|
|
|
uinfo("Queuing destruction: worker %p->%p\n",
|
|
priv->ntwork.worker, usbhost_destroy);
|
|
|
|
DEBUGASSERT(work_available(&priv->ntwork));
|
|
work_queue(HPWORK, &priv->ntwork, usbhost_destroy, priv, 0);
|
|
}
|
|
else
|
|
{
|
|
/* Do the work now */
|
|
|
|
usbhost_destroy(priv);
|
|
}
|
|
}
|
|
|
|
leave_critical_section(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;
|
|
|
|
uinfo("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 = enter_critical_section();
|
|
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;
|
|
}
|
|
|
|
leave_critical_section(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;
|
|
|
|
uinfo("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 = enter_critical_section();
|
|
|
|
/* 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);
|
|
}
|
|
|
|
leave_critical_section(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 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)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct usbhost_cdcacm_s *priv;
|
|
FAR struct uart_dev_s *uartdev;
|
|
int ret = 0;
|
|
|
|
uinfo("Entry\n");
|
|
DEBUGASSERT(filep && filep->f_inode);
|
|
inode = filep->f_inode;
|
|
|
|
DEBUGASSERT(inode && inode->i_private);
|
|
uartdev = (FAR struct uart_dev_s *)inode->i_private;
|
|
|
|
DEBUGASSERT(uartdev && uartdev->priv);
|
|
priv = (FAR struct usbhost_cdcacm_s *)uartdev->priv;
|
|
|
|
/* 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->nbits)
|
|
{
|
|
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->stop2 = (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_cflag & CSIZE)
|
|
{
|
|
case CS5:
|
|
priv->nbits = 5;
|
|
break;
|
|
|
|
case CS6:
|
|
priv->nbits = 6;
|
|
break;
|
|
|
|
case CS7:
|
|
priv->nbits = 7;
|
|
break;
|
|
|
|
default:
|
|
case CS8:
|
|
priv->nbits = 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 */
|
|
|
|
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 */
|
|
|
|
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 */
|
|
|
|
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 */
|
|
|
|
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 */
|
|
|
|
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 */
|