diff --git a/drivers/usbhost/Kconfig b/drivers/usbhost/Kconfig index 0c389b3b468..d9678d105b3 100644 --- a/drivers/usbhost/Kconfig +++ b/drivers/usbhost/Kconfig @@ -262,6 +262,15 @@ config USBHOST_CDCACM_TXBUFSIZE endif # USBHOST_CDCACM +config USBHOST_CDCMBIM + bool "CDC/MBIM support" + default n + depends on USBHOST_HAVE_ASYNCH && !USBHOST_BULK_DISABLE && !USBHOST_INT_DISABLE + select USBHOST_ASYNCH + ---help--- + Select this option to build in host support for CDC/MBIM network + devices. + config USBHOST_HIDKBD bool "HID Keyboard Class Support" default n diff --git a/drivers/usbhost/Make.defs b/drivers/usbhost/Make.defs index 332e83c10e3..bf9320af45e 100644 --- a/drivers/usbhost/Make.defs +++ b/drivers/usbhost/Make.defs @@ -58,6 +58,10 @@ ifeq ($(CONFIG_USBHOST_CDCACM),y) CSRCS += usbhost_cdcacm.c endif +ifeq ($(CONFIG_USBHOST_CDCMBIM),y) +CSRCS += usbhost_cdcmbim.c +endif + ifeq ($(CONFIG_USBHOST_HIDKBD),y) CSRCS += usbhost_hidkbd.c endif diff --git a/drivers/usbhost/usbhost_cdcmbim.c b/drivers/usbhost/usbhost_cdcmbim.c new file mode 100644 index 00000000000..75e5539e159 --- /dev/null +++ b/drivers/usbhost/usbhost_cdcmbim.c @@ -0,0 +1,2578 @@ +/**************************************************************************** + * drivers/usbhost/usbhost_cdcmbim.c + * + * Copyright (C) 2018 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * Adam Porter + * + * 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 + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define CDCMBIM_NETBUF_SIZE 8192 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* put in cdc.h */ +#define USB_CDC_SEND_ENCAPSULATED_COMMAND 0x00 +#define USB_CDC_GET_ENCAPSULATED_RESPONSE 0x01 +#define USB_CDC_GET_NTB_PARAMETERS 0x80 +#define USB_CDC_SET_NTB_INPUT_SIZE 0x86 +#define USB_CDC_SET_NTB_FORMAT 0x83 +#define USB_CDC_SET_CRC_MODE 0x8a +#define USB_CDC_SET_MAX_DATAGRAM_SIZE 0x88 + +#define USB_CDC_NCM_NTB16_SUPPORTED (1 << 0) +#define USB_CDC_NCM_NTB32_SUPPORTED (1 << 1) +#define USB_CDC_NCM_NTB16_FORMAT 0x00 +#define USB_CDC_NCM_NTB32_FORMAT 0x01 +#define USB_CDC_NCM_DATAGRAM_FORMAT_CRC 0x30 +#define USB_CDC_NCM_DATAGRAM_FORMAT_NOCRC 0x31 +#define USB_CDC_NCM_CRC_NOT_APPENDED 0x00 +#define USB_CDC_NCM_CRC_APPENDED 0x01 +#define USB_CDC_NCM_NTH16_SIGNATURE 0x484D434E + +/* Configuration ************************************************************/ + +#ifndef CONFIG_SCHED_WORKQUEUE +# warning "Worker thread support is required (CONFIG_SCHED_WORKQUEUE)" +#endif + +#ifndef CONFIG_USBHOST_ASYNCH +# warning Asynchronous transfer support is required (CONFIG_USBHOST_ASYNCH) +#endif + +#ifdef CONFIG_USBHOST_CDCMBIM_NTDELAY +# define USBHOST_CDCMBIM_NTDELAY MSEC2TICK(CONFIG_USBHOST_CDCMBIM_NTDELAY) +#else +# define USBHOST_CDCMBIM_NTDELAY MSEC2TICK(200) +#endif + +#ifndef CONFIG_USBHOST_CDCMBIM_NPOLLWAITERS +# define CONFIG_USBHOST_CDCMBIM_NPOLLWAITERS 1 +#endif + +/* Driver support ***********************************************************/ + +/* This format is used to construct the /dev/cdc-wdm[n] device driver path. + * It defined here so that it will be used consistently in all places. + */ + +#define DEV_FORMAT "/dev/cdc-wdm%d" +#define DEV_NAMELEN 16 + +/* Used in usbhost_cfgdesc() */ + +#define USBHOST_CTRLIFFOUND 0x01 +#define USBHOST_DATAIFFOUND 0x02 +#define USBHOST_INTRIFFOUND 0x04 +#define USBHOST_BINFOUND 0x08 +#define USBHOST_BOUTFOUND 0x10 +#define USBHOST_ALLFOUND 0x1f + +#define USBHOST_MAX_CREFS 0x7fff + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct usb_cdc_ncm_nth16_s +{ + uint8_t signature[4]; + uint8_t length[2]; + uint8_t sequence[2]; + uint8_t block_length[2]; + uint8_t ndp_index[2]; +}; + +struct usb_cdc_ncm_dpe16_s +{ + uint8_t index[2]; + uint8_t length[2]; +}; + +struct usb_cdc_ncm_ndp16_s +{ + uint8_t signature[4]; + uint8_t length[2]; + uint8_t next_ndp_index[2]; + struct usb_cdc_ncm_dpe16_s dpe16[0]; +}; + +struct usb_cdc_ncm_ntb_params_s +{ + uint8_t length[2]; + uint8_t ntb_formats_supported[2]; + uint8_t ntb_in_max_size[4]; + uint8_t ndp_in_divisor[2]; + uint8_t ndp_in_payload_remainder[2]; + uint8_t ndp_in_alignment[2]; + uint8_t reserved[2]; + uint8_t ntb_out_max_size[4]; + uint8_t ndp_out_divisor[2]; + uint8_t ndp_out_payload_remainder[2]; + uint8_t ndp_out_alignment[2]; + uint8_t ntb_out_max_datagrams[2]; +}; + +struct usb_csifdesc_s +{ + uint8_t len; + uint8_t type; + uint8_t subtype; +}; + +struct usb_mbim_desc_s +{ + uint8_t len; + uint8_t type; + uint8_t subtype; + uint8_t mbim_version[2]; + uint8_t max_ctrl_message[2]; + uint8_t num_filters; + uint8_t max_filter_size; + uint8_t max_segment_size[2]; + uint8_t network_capabilities; +}; + +/* This structure contains the internal, private state of the USB host class + * driver. + */ + +struct usbhost_cdcmbim_s +{ + /* This is the externally visible portion of the state */ + + struct usbhost_class_s usbclass; + + /* The remainder of the fields are provided to the class driver */ + + uint8_t minor; /* Minor number identifying the /dev/cdc-wdm[n] device */ + volatile bool disconnected; /* TRUE: Device has been disconnected */ + uint16_t ctrlif; /* Control interface number */ + uint16_t dataif; /* Data interface number */ + int16_t crefs; /* Reference count on the driver instance */ + sem_t exclsem; /* Used to maintain mutual exclusive access */ + struct work_s ntwork; /* Notification work */ + struct work_s comm_rxwork; /* Communication interface RX work */ + struct work_s bulk_rxwork; + struct work_s txpollwork; + struct work_s destroywork; + int16_t nnbytes; /* Number of bytes received in notification */ + int16_t bulkinbytes; + uint16_t comm_rxlen; /* Number of bytes in the RX buffer */ + uint16_t comm_rxmsgs; /* Number of messages available to be read */ + uint16_t comm_rxpos; /* Read position for input buffer */ + uint16_t maxctrlsize; /* Maximum size of a ctrl request */ + uint16_t maxintsize; /* Maximum size of interrupt IN packet */ + uint32_t maxntbin; /* Maximum size of NTB IN message */ + uint32_t maxntbout; /* Maximum size of NTB OUT message */ + FAR uint8_t *ctrlreq; /* Allocated ctrl request structure */ + FAR uint8_t *data_txbuf; /* Allocated TX buffer for network datagrams */ + FAR uint8_t *data_rxbuf; /* Allocated RX buffer for network datagrams */ + FAR uint8_t *comm_rxbuf; /* Allocated RX buffer comm IN messages */ + FAR uint8_t *notification; /* Allocated RX buffer for async notifications */ + FAR uint8_t *rxnetbuf; /* Allocated RX buffer for NTB frames */ + FAR uint8_t *txnetbuf; /* Allocated TX buffer for NTB frames */ + usbhost_ep_t intin; /* Interrupt endpoint */ + usbhost_ep_t bulkin; /* Bulk IN endpoint */ + usbhost_ep_t bulkout; /* Bulk OUT endpoint */ + uint16_t bulkmxpacket; /* Max packet size for Bulk OUT endpoint */ + uint16_t ntbseq; /* NTB sequence number */ + + struct pollfd *fds[CONFIG_USBHOST_CDCMBIM_NPOLLWAITERS]; + + /* Network device members */ + + WDOG_ID txpoll; /* TX poll timer */ + bool bifup; /* true:ifup false:ifdown */ + struct net_driver_s netdev; /* Interface understood by the network */ + uint8_t txpktbuf[MAX_NETDEV_PKTSIZE]; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Semaphores */ + +static void usbhost_takesem(sem_t *sem); +#define usbhost_givesem(s) nxsem_post(s); + +/* Polling support */ + +static void usbhost_pollnotify(FAR struct usbhost_cdcmbim_s *priv); + +/* Memory allocation services */ + +static inline FAR struct usbhost_cdcmbim_s *usbhost_allocclass(void); +static inline void usbhost_freeclass(FAR struct usbhost_cdcmbim_s *usbclass); + +/* Device name management */ + +static int usbhost_allocdevno(FAR struct usbhost_cdcmbim_s *priv); +static void usbhost_freedevno(FAR struct usbhost_cdcmbim_s *priv); +static inline void usbhost_mkdevname(FAR struct usbhost_cdcmbim_s *priv, + FAR char *devname); + +/* Worker thread actions */ + +static void usbhost_notification_work(FAR void *arg); +static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes); +static void usbhost_rxdata_work(FAR void *arg); +static void usbhost_bulkin_work(FAR void *arg); + +static void usbhost_destroy(FAR void *arg); + +/* Helpers for usbhost_connect() */ + +static int usbhost_cfgdesc(FAR struct usbhost_cdcmbim_s *priv, + FAR const uint8_t *configdesc, int desclen); +static inline int usbhost_devinit(FAR struct usbhost_cdcmbim_s *priv); + +/* (Little Endian) Data helpers */ + +static inline uint16_t usbhost_getle16(const uint8_t *val); +static inline void usbhost_putle16(uint8_t *dest, uint16_t val); +static inline uint32_t usbhost_getle32(const uint8_t *val); +static void usbhost_putle32(uint8_t *dest, uint32_t val); + +/* Buffer memory management */ + +static int usbhost_alloc_buffers(FAR struct usbhost_cdcmbim_s *priv); +static void usbhost_free_buffers(FAR struct usbhost_cdcmbim_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); + +/* Control interface driver methods */ + +static ssize_t cdcwdm_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t cdcwdm_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen); +static int cdcwdm_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); + +/* NuttX network callback functions */ + +static int cdcmbim_ifup(struct net_driver_s *dev); +static int cdcmbim_ifdown(struct net_driver_s *dev); +static int cdcmbim_txavail(struct net_driver_s *dev); + +/* Network support functions */ + +static void cdcmbim_receive(struct usbhost_cdcmbim_s *priv, uint8_t *buf, + size_t len); + +static int cdcmbim_txpoll(struct net_driver_s *dev); +static void cdcmbim_txpoll_work(void *arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This structure provides the registry entry ID information that will be + * used to associate the USB class driver to a connected USB device. + */ + +static const struct usbhost_id_s g_id = +{ + USB_CLASS_CDC, /* base */ + CDC_SUBCLASS_MBIM, /* subclass */ + 0, /* proto */ + 0, /* vid */ + 0 /* pid */ +}; + +/* This is the USB host storage class's registry entry */ + +static struct usbhost_registry_s g_cdcmbim = +{ + NULL, /* flink */ + usbhost_create, /* create */ + 1, /* nids */ + &g_id /* id[] */ +}; + +/* File operations for control channel */ + +static const struct file_operations cdcwdm_fops = +{ + NULL, /* open */ + NULL, /* close */ + cdcwdm_read, /* read */ + cdcwdm_write, /* write */ + NULL, /* seek */ + NULL, /* ioctl */ + cdcwdm_poll /* poll */ +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , NULL /* unlink */ +#endif +}; + +/* This is a bitmap that is used to allocate device names /dev/cdc-wdm[n]. */ + +static uint32_t g_devinuse; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int usbhost_ctrl_cmd(FAR struct usbhost_cdcmbim_s *priv, + uint8_t type, uint8_t req, uint16_t value, + uint16_t iface, uint8_t *payload, uint16_t len) +{ + FAR struct usbhost_hubport_s *hport; + struct usb_ctrlreq_s *ctrlreq; + int ret; + + hport = priv->usbclass.hport; + + ctrlreq = (struct usb_ctrlreq_s *)priv->ctrlreq; + ctrlreq->type = type; + ctrlreq->req = req; + + usbhost_putle16(ctrlreq->value, value); + usbhost_putle16(ctrlreq->index, iface); + usbhost_putle16(ctrlreq->len, len); + + if (type & USB_REQ_DIR_IN) + { + ret = DRVR_CTRLIN(hport->drvr, hport->ep0, ctrlreq, payload); + } + else + { + ret = DRVR_CTRLOUT(hport->drvr, hport->ep0, ctrlreq, payload); + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_pollnotify + ****************************************************************************/ + +static void usbhost_pollnotify(FAR struct usbhost_cdcmbim_s *priv) +{ + int i; + + for (i = 0; i < CONFIG_USBHOST_CDCMBIM_NPOLLWAITERS; i++) + { + struct pollfd *fds = priv->fds[i]; + if (fds) + { + fds->revents |= (fds->events & POLLIN); + if (fds->revents != 0) + { + uinfo("Report events: %02x\n", fds->revents); + nxsem_post(fds->sem); + } + } + } +} + +static ssize_t usbhost_readmessage(FAR struct usbhost_cdcmbim_s *priv, + FAR char *buffer, size_t buflen) +{ + irqstate_t flags; + ssize_t ret = -EAGAIN; + + flags = enter_critical_section(); + + if (priv->comm_rxlen > 0) + { + if (buflen > priv->comm_rxlen) + { + buflen = priv->comm_rxlen; + } + + ret = buflen; + + memcpy(buffer, priv->comm_rxbuf + priv->comm_rxpos, buflen); + priv->comm_rxlen -= buflen; + priv->comm_rxpos += buflen; + if (priv->comm_rxlen == 0) + { + priv->comm_rxpos = 0; + } + } + + leave_critical_section(flags); + return ret; +} + +static ssize_t cdcwdm_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode; + FAR struct usbhost_cdcmbim_s *priv; + ssize_t ret; + + inode = filep->f_inode; + priv = inode->i_private; + + usbhost_takesem(&priv->exclsem); + + if (priv->disconnected) + { + ret = -ENODEV; + goto errout; + } + + ret = usbhost_readmessage(priv, buffer, buflen); + if (ret < 0) + { + if (filep->f_oflags & O_NONBLOCK) + { + ret = -EAGAIN; + goto errout; + } + + /* wait for a message + * TODO blocking read + */ + + ret = 0; + goto errout; + } + + /* If this message has been completely read and there are more + * messages available, read the next message into the input buffer. + */ + + if (priv->comm_rxlen == 0 && priv->comm_rxmsgs > 0) + { + uinfo("Reading next message\n"); + if (work_available(&priv->comm_rxwork)) + { + (void)work_queue(LPWORK, &priv->comm_rxwork, + (worker_t)usbhost_rxdata_work, + priv, 0); + } + } + +errout: + usbhost_givesem(&priv->exclsem); + return ret; +} + +static ssize_t cdcwdm_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct inode *inode; + FAR struct usbhost_cdcmbim_s *priv; + int ret; + + inode = filep->f_inode; + priv = inode->i_private; + + if (priv->disconnected) + { + return -ENODEV; + } + + if (buflen > priv->maxctrlsize) + { + buflen = priv->maxctrlsize; + } + + /* Make sure that we have exclusive access to the private data + * structure. There may now be other tasks with the character driver + * open and actively trying to interact with the class driver. + */ + + usbhost_takesem(&priv->exclsem); + + ret = usbhost_ctrl_cmd(priv, + USB_REQ_DIR_OUT | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE, + USB_CDC_SEND_ENCAPSULATED_COMMAND, + 0, priv->ctrlif, (uint8_t *)buffer, buflen); + + usbhost_givesem(&priv->exclsem); + + if (ret) + { + return ret; + } + + uinfo("wrote %u bytes\n", buflen); + + return buflen; +} + +/**************************************************************************** + * Name: usbhost_poll + * + * Description: + * Standard character driver poll method. + * + ****************************************************************************/ + +static int cdcwdm_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct usbhost_cdcmbim_s *priv; + int ret = OK; + int i; + + DEBUGASSERT(filep && filep->f_inode && fds); + inode = filep->f_inode; + priv = inode->i_private; + + /* Make sure that we have exclusive access to the private data structure */ + + DEBUGASSERT(priv); + usbhost_takesem(&priv->exclsem); + + if (priv->disconnected) + { + ret = -ENODEV; + } + else if (setup) + { + /* This is a request to set up the poll. Find an available slot for + * the poll structure reference + */ + + for (i = 0; i < CONFIG_USBHOST_CDCMBIM_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!priv->fds[i]) + { + /* Bind the poll structure and this slot */ + + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_USBHOST_CDCMBIM_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto errout; + } + + /* Should we immediately notify on any of the requested events? Notify + * the POLLIN event if there is a buffered message. + */ + + if (priv->comm_rxlen > 0) + { + usbhost_pollnotify(priv); + } + } + else + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + DEBUGASSERT(slot); + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +errout: + nxsem_post(&priv->exclsem); + return ret; +} + +/**************************************************************************** + * 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) +{ + int ret; + + do + { + /* Take the semaphore (perhaps waiting) */ + + ret = nxsem_wait(sem); + + /* The only case that an error should occur here is if the wait was + * awakened by a signal. + */ + + DEBUGASSERT(ret == OK || ret == -EINTR); + } + while (ret == -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. + * + ****************************************************************************/ + +static inline FAR struct usbhost_cdcmbim_s *usbhost_allocclass(void) +{ + FAR struct usbhost_cdcmbim_s *priv; + + DEBUGASSERT(!up_interrupt_context()); + priv = (FAR struct usbhost_cdcmbim_s *)kmm_malloc( + sizeof(struct usbhost_cdcmbim_s)); + uinfo("Allocated: %p\n", priv); + return priv; +} + +/**************************************************************************** + * 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 + * + ****************************************************************************/ + +static inline void usbhost_freeclass(FAR struct usbhost_cdcmbim_s *usbclass) +{ + DEBUGASSERT(usbclass != NULL); + + /* Free the class instance (perhaps calling sched_kmm_free() in case we are + * executing from an interrupt handler. + */ + + uinfo("Freeing: %p\n", usbclass); + kmm_free(usbclass); +} + +/**************************************************************************** + * Name: Device name management + * + * Description: + * Some tiny functions to coordinate management of device names. + * + ****************************************************************************/ + +static int usbhost_allocdevno(FAR struct usbhost_cdcmbim_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; +} + +static void usbhost_freedevno(FAR struct usbhost_cdcmbim_s *priv) +{ + int devno = priv->minor; + + if (devno >= 0 && devno < 26) + { + irqstate_t flags = enter_critical_section(); + g_devinuse &= ~(1 << devno); + leave_critical_section(flags); + } +} + +static inline void usbhost_mkdevname(FAR struct usbhost_cdcmbim_s *priv, + FAR char *devname) +{ + (void)snprintf(devname, DEV_NAMELEN, DEV_FORMAT, priv->minor); +} + +static void usbhost_bulkin_callback(FAR void *arg, ssize_t nbytes) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *)arg; + uint32_t delay = 0; + + DEBUGASSERT(priv); + + if (priv->disconnected) + { + return; + } + + priv->bulkinbytes = (int16_t)nbytes; + + if (nbytes < 0) + { + if (nbytes != -EAGAIN) + { + uerr("ERROR: Transfer failed: %d\n", nbytes); + } + + delay = MSEC2TICK(30); + } + + if (work_available(&priv->bulk_rxwork)) + { + (void)work_queue(LPWORK, &priv->bulk_rxwork, + (worker_t)usbhost_bulkin_work, priv, delay); + } +} + +static void usbhost_bulkin_work(FAR void *arg) +{ + struct usbhost_cdcmbim_s *priv; + struct usbhost_hubport_s *hport; + uint16_t ndpoffset; + uint16_t dgram_len; + uint16_t dgram_off; + + priv = (struct usbhost_cdcmbim_s *)arg; + DEBUGASSERT(priv); + + hport = priv->usbclass.hport; + DEBUGASSERT(hport); + + if (priv->disconnected || !priv->bifup) + { + return; + } + + usbhost_takesem(&priv->exclsem); + + if (priv->bulkinbytes < (int16_t)(sizeof(struct usb_cdc_ncm_nth16_s) + + sizeof(struct usb_cdc_ncm_ndp16_s))) + { + goto out; + } + + /* Parse the NTB header */ + + struct usb_cdc_ncm_nth16_s *nth = + (struct usb_cdc_ncm_nth16_s *)priv->rxnetbuf; + + if (usbhost_getle32(nth->signature) != USB_CDC_NCM_NTH16_SIGNATURE) + { + uerr("Invalid NTH signature\n"); + goto out; + } + + uint16_t block_len = usbhost_getle16(nth->block_length); + + if (block_len > priv->bulkinbytes) + { + uerr("Block length larger than rx buffer\n"); + goto out; + } + + ndpoffset = usbhost_getle16(nth->ndp_index); + if (ndpoffset > priv->bulkinbytes) + { + uerr("NDP offset too far %u > %u\n", ndpoffset, priv->bulkinbytes); + goto out; + } + + /* Parse each NDP */ + + do + { + struct usb_cdc_ncm_ndp16_s *ndp + = (struct usb_cdc_ncm_ndp16_s *)(priv->rxnetbuf + ndpoffset); + + ndpoffset = usbhost_getle16(ndp->next_ndp_index); + + /* Parse each DPE */ + + struct usb_cdc_ncm_dpe16_s *dpe; + + for (dpe = ndp->dpe16; usbhost_getle16(dpe->index); dpe++) + { + dgram_off = usbhost_getle16(dpe->index); + dgram_len = usbhost_getle16(dpe->length); + + if (dgram_off + dgram_len <= priv->bulkinbytes) + { + cdcmbim_receive(priv, priv->rxnetbuf + dgram_off, dgram_len); + } + } + } + while (ndpoffset); + +out: + DRVR_ASYNCH(hport->drvr, priv->bulkin, + (uint8_t *)priv->rxnetbuf, CDCMBIM_NETBUF_SIZE, + usbhost_bulkin_callback, priv); + usbhost_givesem(&priv->exclsem); +} + +/**************************************************************************** + * Name: usbhost_rxdata_work + * + * Description: + * Read an available comm message into the RX buffer + * + * Input Parameters: + * arg - Driver private data + * + * Returned Value: + * None + * + * Assumptions: + * None + * + ****************************************************************************/ + +static void usbhost_rxdata_work(FAR void *arg) +{ + FAR struct usbhost_cdcmbim_s *priv; + int ret; + + priv = (FAR struct usbhost_cdcmbim_s *)arg; + DEBUGASSERT(priv); + + /* Are we still connected? */ + + if (priv->disconnected) + { + return; + } + + usbhost_takesem(&priv->exclsem); + + ret = usbhost_ctrl_cmd(priv, + USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE, + USB_CDC_GET_ENCAPSULATED_RESPONSE, + 0, priv->ctrlif, priv->comm_rxbuf, + priv->maxctrlsize); + if (ret) + { + uerr("Failed to read message: %d\n", ret); + goto errout; + } + + /* Would be nice if CTRLIN would return how many bytes it read... */ + + struct mbim_header_s + { + uint8_t type[4]; + uint8_t len[4]; + }; + + struct mbim_header_s *hdr = (struct mbim_header_s *)priv->comm_rxbuf; + uint32_t len = usbhost_getle32(hdr->len); + + if (len > priv->maxctrlsize) + { + uerr("Read invalid MBIM packet\n"); + goto errout; + } + + uinfo("Read MBIM message %u bytes\n", len); + + priv->comm_rxlen = len; + priv->comm_rxmsgs--; + + /* Notify any poll waiters we have data */ + + usbhost_pollnotify(priv); + +errout: + usbhost_givesem(&priv->exclsem); +} + +/**************************************************************************** + * Name: usbhost_notification_work + * + * Description: + * Handle receipt of an asynchronous notification from the CDC device + * + * Input Parameters: + * arg - The argument provided with the asynchronous I/O was setup + * + * Returned Value: + * None + * + * Assumptions: + * Probably called from an interrupt handler. + * + ****************************************************************************/ + +static void usbhost_notification_work(FAR void *arg) +{ + FAR struct usbhost_cdcmbim_s *priv; + FAR struct usbhost_hubport_s *hport; + FAR struct cdc_notification_s *inmsg; + int ret; + + priv = (FAR struct usbhost_cdcmbim_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->nnbytes >= 0) + { + /* Yes.. decode the notification */ + + inmsg = (FAR struct cdc_notification_s *)priv->notification; + + /* We care only about the ResponseAvailable notification */ + + if ((inmsg->type == (USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE)) && + (inmsg->notification == ACM_RESPONSE_AVAILABLE)) + { + priv->comm_rxmsgs++; + + /* If this is the only message available, read it */ + + if (priv->comm_rxmsgs == 1) + { + if (work_available(&priv->comm_rxwork)) + { + (void)work_queue(LPWORK, &priv->comm_rxwork, + (worker_t)usbhost_rxdata_work, + priv, 0); + } + } + } + } + + /* Setup to receive the next notification */ + + ret = DRVR_ASYNCH(hport->drvr, priv->intin, + (FAR uint8_t *)priv->notification, + SIZEOF_NOTIFICATION_S(0), + usbhost_notification_callback, + priv); + if (ret < 0) + { + uerr("ERROR: DRVR_ASYNCH failed: %d\n", ret); + } + } +} + +/**************************************************************************** + * Name: usbhost_notification_callback + * + * Description: + * Handle receipt of Response Available from the CDC/MBIM device + * + * Input Parameters: + * arg - The argument provided with the asynchronous I/O was setup + * nbytes - The number of bytes actually transferred (or a negated errno + * value; + * + * Returned Value: + * None + * + * Assumptions: + * Probably called from an interrupt handler. + * + ****************************************************************************/ + +static void usbhost_notification_callback(FAR void *arg, ssize_t nbytes) +{ + FAR struct usbhost_cdcmbim_s *priv = (FAR struct usbhost_cdcmbim_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->nnbytes = (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_CDCMBIM_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(LPWORK, &priv->ntwork, + (worker_t)usbhost_notification_work, + priv, delay); + } + } +} + +/**************************************************************************** + * Name: usbhost_destroy + * + * Description: + * The USB 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_cdcmbim_s *priv = (FAR struct usbhost_cdcmbim_s *)arg; + FAR struct usbhost_hubport_s *hport; + FAR struct usbhost_driver_s *drvr; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL); + hport = priv->usbclass.hport; + + DEBUGASSERT(hport->drvr); + drvr = hport->drvr; + + uinfo("crefs: %d\n", priv->crefs); + + /* Unregister the driver */ + + /* Release the device name used by this connection */ + + usbhost_freedevno(priv); + + /* Free the endpoints */ + + if (priv->intin) + { + DRVR_EPFREE(hport->drvr, priv->intin); + } + + if (priv->bulkin) + { + DRVR_EPFREE(hport->drvr, priv->bulkin); + } + + if (priv->bulkout) + { + DRVR_EPFREE(hport->drvr, priv->bulkout); + } + + /* Free any transfer buffers */ + + usbhost_free_buffers(priv); + + /* Free the function address assigned to this device */ + + usbhost_devaddr_destroy(hport, hport->funcaddr); + hport->funcaddr = 0; + + /* Destroy the semaphores */ + + /* Disconnect the USB host device */ + + DRVR_DISCONNECT(drvr, hport); + + /* And free the class instance. Hmmm.. this may execute on the worker + * thread and the work structure is part of what is getting freed. That + * should be okay because once the work contained is removed from the + * queue, it should not longer be accessed by the worker thread. + */ + + 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_cdcmbim_s *priv, + FAR const uint8_t *configdesc, int desclen) +{ + FAR struct usbhost_hubport_s *hport; + FAR struct usb_cfgdesc_s *cfgdesc; + FAR struct usb_desc_s *desc; + FAR struct usbhost_epdesc_s bindesc; + FAR struct usbhost_epdesc_s boutdesc; + FAR struct usbhost_epdesc_s iindesc; + int remaining; + uint8_t found = 0; + int ret; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport && + configdesc != NULL && desclen >= sizeof(struct usb_cfgdesc_s)); + hport = priv->usbclass.hport; + + /* 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) + { + case USB_DESC_TYPE_CSINTERFACE: + { + FAR struct usb_csifdesc_s *csdesc = (FAR struct usb_csifdesc_s *) + desc; + + /* MBIM functional descriptor */ + + if (csdesc->subtype == CDC_DSUBTYPE_MBIM) + { + FAR struct usb_mbim_desc_s *mbim = + (FAR struct usb_mbim_desc_s *)desc; + + priv->maxctrlsize = usbhost_getle16(mbim->max_ctrl_message); + uinfo("MBIM max control size: %u\n", priv->maxctrlsize); + uinfo("MBIM max segment size: %u\n", + usbhost_getle16(mbim->max_segment_size)); + } + } + break; + + /* Interface descriptor. We really should get the number of endpoints + * from this descriptor too. + */ + + case USB_DESC_TYPE_INTERFACE: + { + FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *) + configdesc; + + uinfo("Interface descriptor\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_IFDESC); + + /* Is this the control interface? */ + + if (ifdesc->classid == CDC_CLASS_COMM && + ifdesc->subclass == CDC_SUBCLASS_MBIM && + ifdesc->protocol == CDC_PROTO_NONE) + { + priv->ctrlif = ifdesc->ifno; + found |= USBHOST_CTRLIFFOUND; + } + + /* Is this the data interface? */ + + else if (ifdesc->classid == USB_CLASS_CDC_DATA && + ifdesc->subclass == CDC_SUBCLASS_NONE && + ifdesc->protocol == CDC_DATA_PROTO_NTB) + { + priv->dataif = ifdesc->ifno; + found |= USBHOST_DATAIFFOUND; + } + } + break; + + /* Endpoint descriptor. Here, 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\n"); + DEBUGASSERT(remaining >= USB_SIZEOF_EPDESC); + + /* Check for interrupt endpoint */ + + if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == + USB_EP_ATTR_XFER_INT) + { + if (USB_ISEPIN(epdesc->addr)) + { + found |= USBHOST_INTRIFFOUND; + iindesc.hport = hport; + iindesc.addr = epdesc->addr & + USB_EP_ADDR_NUMBER_MASK; + iindesc.in = true; + 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", + iindesc.addr, iindesc.mxpacketsize); + + priv->maxintsize = iindesc.mxpacketsize; + } + } + + /* Check for a bulk endpoint. */ + + else if ((epdesc->attr & USB_EP_ATTR_XFERTYPE_MASK) == + USB_EP_ATTR_XFER_BULK) + { + /* Yes.. it is a bulk endpoint. IN or OUT? */ + + if (USB_ISEPOUT(epdesc->addr)) + { + /* It is an OUT bulk endpoint. There should be only one + * bulk OUT endpoint. + */ + + if ((found & USBHOST_BOUTFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + + found |= USBHOST_BOUTFOUND; + + /* Save the bulk OUT endpoint information */ + + boutdesc.hport = hport; + boutdesc.addr = epdesc->addr & + USB_EP_ADDR_NUMBER_MASK; + boutdesc.in = false; + boutdesc.xfrtype = USB_EP_ATTR_XFER_BULK; + boutdesc.interval = epdesc->interval; + boutdesc.mxpacketsize = + usbhost_getle16(epdesc->mxpacketsize); + uinfo("Bulk OUT EP addr:%d mxpacketsize:%d\n", + boutdesc.addr, boutdesc.mxpacketsize); + + priv->bulkmxpacket = boutdesc.mxpacketsize; + } + else + { + /* It is an IN bulk endpoint. There should be only one + * bulk IN endpoint. + */ + + if ((found & USBHOST_BINFOUND) != 0) + { + /* Oops.. more than one endpoint. We don't know + * what to do with this. + */ + + return -EINVAL; + } + + found |= USBHOST_BINFOUND; + + /* Save the bulk IN endpoint information */ + + bindesc.hport = hport; + bindesc.addr = epdesc->addr & + USB_EP_ADDR_NUMBER_MASK; + bindesc.in = 1; + bindesc.xfrtype = USB_EP_ATTR_XFER_BULK; + bindesc.interval = epdesc->interval; + bindesc.mxpacketsize = + usbhost_getle16(epdesc->mxpacketsize); + uinfo("Bulk IN EP addr:%d mxpacketsize:%d\n", + bindesc.addr, bindesc.mxpacketsize); + } + } + } + break; + + /* Other descriptors are just ignored for now */ + + default: + break; + } + + /* If we found everything we need with this interface, then break out + * of the loop early. + */ + + if (found == USBHOST_ALLFOUND) + { + break; + } + + /* Increment the address of the next descriptor */ + + configdesc += desc->len; + remaining -= desc->len; + } + + /* Sanity checking... did we find all of things that we need? */ + + if (found != USBHOST_ALLFOUND) + { + uerr("ERROR: Found CTRLIF:%s DATAIF: %s BIN:%s BOUT:%s\n", + (found & USBHOST_CTRLIFFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_DATAIFFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BINFOUND) != 0 ? "YES" : "NO", + (found & USBHOST_BOUTFOUND) != 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"); + (void)DRVR_EPFREE(hport->drvr, priv->bulkout); + return ret; + } + + ret = DRVR_EPALLOC(hport->drvr, &iindesc, &priv->intin); + if (ret < 0) + { + uerr("ERROR: Failed to allocate Interrupt IN endpoint\n"); + (void)DRVR_EPFREE(hport->drvr, priv->bulkout); + (void)DRVR_EPFREE(hport->drvr, priv->bulkin); + return ret; + } + + uinfo("Endpoints allocated\n"); + return OK; +} + +static int usbhost_setinterface(FAR struct usbhost_cdcmbim_s *priv, + uint16_t iface, uint16_t setting) +{ + return usbhost_ctrl_cmd(priv, + USB_REQ_DIR_OUT | USB_REQ_RECIPIENT_INTERFACE, + USB_REQ_SETINTERFACE, setting, iface, NULL, 0); +} + +static int cdc_ncm_set_ntb_input_size(FAR struct usbhost_cdcmbim_s *priv, + uint32_t size) +{ + uint8_t buf[4]; + + usbhost_putle32(buf, size); + + return usbhost_ctrl_cmd(priv, + USB_REQ_DIR_OUT | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE, + USB_CDC_SET_NTB_INPUT_SIZE, + 0, priv->ctrlif, buf, 4); +} + +#if 0 +static int cdc_ncm_set_max_dgram_size(FAR struct usbhost_cdcmbim_s *priv, + uint16_t size) +{ + uint8_t buf[2]; + + usbhost_putle16(buf, size); + + return usbhost_ctrl_cmd(priv, + USB_REQ_DIR_OUT | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE, + USB_CDC_SET_MAX_DATAGRAM_SIZE, + 0, priv->ctrlif, buf, 2); +} +#endif + +static int cdc_ncm_read_parameters(FAR struct usbhost_cdcmbim_s *priv) +{ + struct usb_cdc_ncm_ntb_params_s params; + int ret; + + ret = usbhost_ctrl_cmd(priv, + USB_REQ_DIR_IN | USB_REQ_TYPE_CLASS | + USB_REQ_RECIPIENT_INTERFACE, + USB_CDC_GET_NTB_PARAMETERS, + 0, priv->ctrlif, (uint8_t *)¶ms, + sizeof(params)); + if (ret == OK) + { + priv->maxntbin = usbhost_getle32(params.ntb_in_max_size); + priv->maxntbout = usbhost_getle32(params.ntb_out_max_size); + } + + return ret; +} + +/**************************************************************************** + * Name: usbhost_devinit + * + * Description: + * The USB device has been successfully connected. This completes the + * initialization operations. It is first called after the + * configuration descriptor has been received. + * + * This function is called from the connect() method. This function always + * executes on the thread of the caller of connect(). + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline int usbhost_devinit(FAR struct usbhost_cdcmbim_s *priv) +{ + FAR struct usbhost_hubport_s *hport; + int ret = OK; + + hport = priv->usbclass.hport; + + /* Increment the reference count. This will prevent usbhost_destroy() from + * being called asynchronously if the device is removed. + */ + + priv->crefs++; + DEBUGASSERT(priv->crefs == 2); + + /* Configure the device */ + + /* Set aside transfer buffers for exclusive use by the class driver */ + + ret = usbhost_alloc_buffers(priv); + if (ret) + { + uerr("ERROR: failed to allocate buffers\n"); + return ret; + } + + priv->ntbseq = 0; + + /* Read NCM parameters */ + + ret = cdc_ncm_read_parameters(priv); + if (ret) + { + uerr("ERROR: failed to read NCM parameters: %d\n", ret); + } + + /* Set alternate setting 1 on data interface */ + + usbhost_setinterface(priv, priv->dataif, 1); + + /* Set NTB Input size to length of rx buffer */ + + ret = cdc_ncm_set_ntb_input_size(priv, CDCMBIM_NETBUF_SIZE); + if (ret) + { + printf("set NTB input size failed: %d\n", ret); + } + + #if 0 + /* Set max datagram size to MTU */ + + ret = cdc_ncm_set_max_dgram_size(priv, 2048); + if (ret) + { + printf("Failed to set max dgram size: %d\n", ret); + } + #endif + + /* Register the driver */ + + if (ret >= 0) + { + char devname[DEV_NAMELEN]; + + uinfo("Register character driver\n"); + usbhost_mkdevname(priv, devname); + ret = register_driver(devname, &cdcwdm_fops, 0666, priv); + } + + if (priv->intin) + { + /* Begin monitoring of message available events */ + + uinfo("Start notification monitoring\n"); + ret = DRVR_ASYNCH(hport->drvr, priv->intin, + (FAR uint8_t *)priv->notification, + SIZEOF_NOTIFICATION_S(0), + usbhost_notification_callback, + priv); + if (ret < 0) + { + uerr("ERROR: DRVR_ASYNCH failed on intin: %d\n", ret); + } + } + + /* Setup the network interface */ + + memset(&priv->netdev, 0, sizeof(struct net_driver_s)); + priv->netdev.d_ifup = cdcmbim_ifup; + priv->netdev.d_ifdown = cdcmbim_ifdown; + priv->netdev.d_txavail = cdcmbim_txavail; + priv->netdev.d_private = priv; + priv->txpoll = wd_create(); + + /* Register the network device */ + + (void)netdev_register(&priv->netdev, NET_LL_MBIM); + + /* Check if we successfully initialized. We now have to be concerned + * about asynchronous modification of crefs because the character + * driver has been registerd. + */ + + if (ret >= 0) + { + usbhost_takesem(&priv->exclsem); + DEBUGASSERT(priv->crefs >= 2); + + /* Handle a corner case where (1) open() has been called so the + * reference count is > 2, but the device has been disconnected. + * In this case, the class instance needs to persist until close() + * is called. + */ + + if (priv->crefs <= 2 && priv->disconnected) + { + /* We don't have to give the semaphore because it will be + * destroyed when usb_destroy is called. + */ + + ret = -ENODEV; + } + else + { + /* Ready for normal operation */ + + uinfo("Successfully initialized\n"); + priv->crefs--; + usbhost_givesem(&priv->exclsem); + } + } + + return ret; +} + +/**************************************************************************** + * 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_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_getle32 + * + * Description: + * Get a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the big endian value. + * val - The 32-bit value to be saved. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline uint32_t usbhost_getle32(const uint8_t *val) +{ + /* Little endian means LS halfword first in byte stream */ + + return (uint32_t)usbhost_getle16(&val[2]) << 16 | + (uint32_t)usbhost_getle16(val); +} + +/**************************************************************************** + * Name: usbhost_putle32 + * + * Description: + * Put a (possibly unaligned) 32-bit little endian value. + * + * Input Parameters: + * dest - A pointer to the first byte to save the little endian value. + * val - The 32-bit value to be saved. + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void usbhost_putle32(uint8_t *dest, uint32_t val) +{ + /* Little endian means LS halfword first in byte stream */ + + usbhost_putle16(dest, (uint16_t)(val & 0xffff)); + usbhost_putle16(dest + 2, (uint16_t)(val >> 16)); +} + +/**************************************************************************** + * Name: usbhost_alloc_buffers + * + * Description: + * Allocate transfer buffer memory. + * + * Input Parameters: + * priv - A reference to the class instance. + * + * Returned Value: + * On 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_cdcmbim_s *priv) +{ + FAR struct usbhost_hubport_s *hport; + size_t maxlen; + int ret; + + DEBUGASSERT(priv != NULL && priv->usbclass.hport != NULL && + priv->ctrlreq == 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 receiving encapsulated data */ + + ret = DRVR_IOALLOC(hport->drvr, &priv->comm_rxbuf, priv->maxctrlsize); + if (ret < 0) + { + uerr("ERROR: DRVR_IOALLOC of comm_rxbuf failed: %d (%d bytes)\n", ret, + priv->maxctrlsize); + goto errout; + } + + /* Allocate buffer for interrupt IN notifications */ + + ret = DRVR_IOALLOC(hport->drvr, &priv->notification, priv->maxintsize); + if (ret < 0) + { + uerr("ERROR: DRVR_IOALLOC of notification buf failed: %d (%d bytes)\n", + ret, priv->maxintsize); + goto errout; + } + + ret = DRVR_IOALLOC(hport->drvr, &priv->rxnetbuf, CDCMBIM_NETBUF_SIZE); + if (ret < 0) + { + uerr("ERROR: DRVR_IOALLOC of net rx buf failed: %d (%d bytes)\n", + ret, 2048); + goto errout; + } + + ret = DRVR_IOALLOC(hport->drvr, &priv->txnetbuf, 2048); + if (ret < 0) + { + uerr("ERROR: DRVR_IOALLOC of net tx buf failed: %d (%d bytes)\n", + ret, 2048); + 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_cdcmbim_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->comm_rxbuf) + { + (void)DRVR_IOFREE(hport->drvr, priv->comm_rxbuf); + } + + if (priv->notification) + { + (void)DRVR_IOFREE(hport->drvr, priv->notification); + } + + if (priv->rxnetbuf) + { + (void)DRVR_IOFREE(hport->drvr, priv->rxnetbuf); + } + + if (priv->txnetbuf) + { + (void)DRVR_IOFREE(hport->drvr, priv->txnetbuf); + } + + priv->ctrlreq = NULL; + priv->comm_rxbuf = NULL; + priv->notification = NULL; + priv->rxnetbuf = NULL; + priv->txnetbuf = 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 hat 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_cdcmbim_s *priv; + + /* Allocate a USB host class instance */ + + priv = usbhost_allocclass(); + if (priv) + { + /* Initialize the allocated storage class instance */ + + memset(priv, 0, sizeof(struct usbhost_cdcmbim_s)); + + /* Assign a device number to this class instance */ + + if (usbhost_allocdevno(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 in the interrupt context) */ + + nxsem_init(&priv->exclsem, 0, 1); + + /* Return the instance of the USB class driver */ + + 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_cdcmbim_s *priv = (FAR struct usbhost_cdcmbim_s *) + usbclass; + int ret; + + DEBUGASSERT(priv != NULL && + configdesc != NULL && + desclen >= sizeof(struct usb_cfgdesc_s)); + + /* Parse the configuration descriptor to get the endpoints */ + + ret = usbhost_cfgdesc(priv, configdesc, desclen); + if (ret < 0) + { + uerr("ERROR: usbhost_cfgdesc() failed: %d\n", ret); + } + else + { + /* Now configure the device and register the NuttX driver */ + + ret = usbhost_devinit(priv); + if (ret < 0) + { + uerr("ERROR: usbhost_devinit() failed: %d\n", ret); + } + } + + 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_cdcmbim_s *priv = (FAR struct usbhost_cdcmbim_s *) + usbclass; + irqstate_t flags; + + DEBUGASSERT(priv != NULL); + + /* Set an indication to any users of the device that the device is no + * longer available. + */ + + flags = enter_critical_section(); + priv->disconnected = true; + + /* Now check the number of references on the class instance. If it is one, + * then we can free the class instance now. Otherwise, we will have to + * wait until the holders of the references free them by closing the + * block 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->destroywork.worker, usbhost_destroy); + DEBUGASSERT(priv->destroywork.worker == NULL); + (void)work_queue(LPWORK, &priv->destroywork, + usbhost_destroy, priv, 0); + } + else + { + /* Do the work now */ + + usbhost_destroy(priv); + } + } + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: cdcmbim_transmit + * + * Description: + * Start hardware transmission. Called either from the txdone interrupt + * handling or from watchdog based polling. + * + * Input Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +static int cdcmbim_transmit(struct usbhost_cdcmbim_s *priv) +{ + struct usbhost_hubport_s *hport; + struct usb_cdc_ncm_nth16_s *nth; + struct usb_cdc_ncm_ndp16_s *ndp; + struct usb_cdc_ncm_dpe16_s *dpe; + ssize_t ret; + uint16_t len = 0; + + hport = priv->usbclass.hport; + + uinfo("transmit packet: %d bytes\n", priv->netdev.d_len); + + /* Increment statistics */ + + NETDEV_TXPACKETS(&priv->netdev); + + len = sizeof(struct usb_cdc_ncm_nth16_s); + nth = (struct usb_cdc_ncm_nth16_s *)priv->txnetbuf; + + /* Begin filling NTH */ + + usbhost_putle32(nth->signature, USB_CDC_NCM_NTH16_SIGNATURE); + usbhost_putle16(nth->length, sizeof(struct usb_cdc_ncm_nth16_s)); + usbhost_putle16(nth->sequence, priv->ntbseq++); + + /* Payload directly after NTH */ + + memcpy(priv->txnetbuf + len, priv->netdev.d_buf, priv->netdev.d_len); + len += priv->netdev.d_len; + + /* NDP after payload */ + + ndp = (struct usb_cdc_ncm_ndp16_s *)(priv->txnetbuf + len); + usbhost_putle16(ndp->next_ndp_index, 0); + usbhost_putle32(ndp->signature, 0x00535049); + usbhost_putle16(ndp->length, 16); /* NDP + (2 * DPE) */ + + /* Fill NDP and block info in NTH */ + + usbhost_putle16(nth->ndp_index, len); + len += 16; + usbhost_putle16(nth->block_length, len); + + /* Fill first DPE */ + + dpe = ndp->dpe16; + usbhost_putle16(dpe->index, 0x0c); + usbhost_putle16(dpe->length, priv->netdev.d_len); + + /* NULL DPE */ + + dpe++; + usbhost_putle16(dpe->index, 0); + usbhost_putle16(dpe->length, 0); + + ret = DRVR_TRANSFER(hport->drvr, priv->bulkout, priv->txnetbuf, len); + if (ret < 0) + { + uerr("transfer returned error: %d\n", ret); + return ret; + } + + /* If frame is multiple of wMaxPacketSize we must send a ZLP */ + + if ((len % priv->bulkmxpacket) == 0) + { + ret = DRVR_TRANSFER(hport->drvr, priv->bulkout, + priv->txnetbuf, 0); + if (ret < 0) + { + uerr("ERROR: DRVR_TRANSFER for ZLP failed: %d\n", (int)ret); + } + } + + NETDEV_TXDONE(&priv->netdev); + return OK; +} + +/**************************************************************************** + * Name: cdcmbim_receive + * + * Description: + * Handle a received packet. + * + * Input Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + ****************************************************************************/ + +static void cdcmbim_receive(struct usbhost_cdcmbim_s *priv, + uint8_t *buf, size_t len) +{ + uinfo("received packet: %d len\n", len); + + net_lock(); + + NETDEV_RXPACKETS(&priv->netdev); + + /* Any ACK or other response packet generated by the network stack + * will always be shorter than the received packet, therefore it is + * safe to pass the received frame buffer directly. + */ + + priv->netdev.d_buf = buf; + priv->netdev.d_len = len; + + switch (((struct ipv4_hdr_s *)buf)->vhl & IP_VERSION_MASK) + { +#ifdef CONFIG_NET_IPv4 + case IPv4_VERSION: + NETDEV_RXIPV4(&priv->netdev); + ipv4_input(&priv->netdev); + + if (priv->netdev.d_len > 0) + { + cdcmbim_transmit(priv); + } + break; +#endif +#ifdef CONFIG_NET_IPv6 + case IPv6_VERSION: + NETDEV_RXIPV6(&priv->netdev); + ipv6_input(&priv->netdev); + + if (priv->dev.d_len > 0) + { + cdcmbim_transmit(priv); + } + break; +#endif + default: + NETDEV_RXERRORS(dev); + } + + net_unlock(); +} + +static void cdcmbim_txpoll_expiry(int argc, wdparm_t arg, ...) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *)arg; + + work_queue(LPWORK, &priv->txpollwork, cdcmbim_txpoll_work, priv, 0); +} + +static void cdcmbim_txpoll_work(void *arg) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *)arg; + + net_lock(); + priv->netdev.d_buf = priv->txpktbuf; + + (void)devif_timer(&priv->netdev, cdcmbim_txpoll); + + /* setup the watchdog poll timer again */ + + (void)wd_start(priv->txpoll, (1 * CLK_TCK), + cdcmbim_txpoll_expiry, 1, priv); + net_unlock(); +} + +/**************************************************************************** + * Name: cdcmbim_txpoll + * + * Description: + * The transmitter is available, check if the network has any outgoing + * packets ready to send. This is a callback from devif_poll(). + * devif_poll() may be called: + * + * 1. When the preceding TX packet send is complete, + * 2. When the preceding TX packet send timesout and the interface is reset + * 3. During normal TX polling + * + * Input Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * OK on success; a negated errno on failure + * + * Assumptions: + * The network is locked. + * + ****************************************************************************/ + +static int cdcmbim_txpoll(struct net_driver_s *dev) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *) + dev->d_private; + + /* If the polling resulted in data that should be sent out on the network, + * the field d_len is set to a value > 0. + */ + + DEBUGASSERT(priv->netdev.d_buf == priv->txpktbuf); + + usbhost_takesem(&priv->exclsem); + + if (priv->netdev.d_len > 0) + { + if (!devif_loopback(&priv->netdev)) + { + /* Send the packet */ + + cdcmbim_transmit(priv); + } + } + + usbhost_givesem(&priv->exclsem); + + return 0; +} + +/**************************************************************************** + * Name: cdcmbim_ifup + * + * Description: + * NuttX Callback: Bring up the MBIM interface when an IP address is + * provided + * + * Input Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int cdcmbim_ifup(struct net_driver_s *dev) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *) + dev->d_private; + struct usbhost_hubport_s *hport = priv->usbclass.hport; + int ret; + +#ifdef CONFIG_NET_IPv4 + ninfo("Bringing up: %d.%d.%d.%d\n", + dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff, + (dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24); +#endif +#ifdef CONFIG_NET_IPv6 + ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n", + dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2], + dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5], + dev->d_ipv6addr[6], dev->d_ipv6addr[7]); +#endif + + /* Start RX asynch on bulk in */ + + if (priv->bulkin) + { + ret = DRVR_ASYNCH(hport->drvr, priv->bulkin, + priv->rxnetbuf, CDCMBIM_NETBUF_SIZE, + usbhost_bulkin_callback, priv); + if (ret < 0) + { + uerr("ERROR: DRVR_ASYNCH failed on bulkin: %d\n", ret); + } + } + + /* Start network TX poll */ + + (void)wd_start(priv->txpoll, (1 * CLK_TCK), cdcmbim_txpoll_expiry, 1, + (wdparm_t)priv); + priv->bifup = true; + return OK; +} + +/**************************************************************************** + * Name: cdcmbim_ifdown + * + * Description: + * NuttX Callback: Stop the interface. + * + * Input Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * + ****************************************************************************/ + +static int cdcmbim_ifdown(struct net_driver_s *dev) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *) + dev->d_private; + irqstate_t flags; + + flags = enter_critical_section(); + + wd_cancel(priv->txpoll); + + /* Mark the device "down" */ + + priv->bifup = false; + + leave_critical_section(flags); + return OK; +} + +/**************************************************************************** + * Name: cdcmbim_txavail_work + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Input Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static void cdcmbim_txavail_work(void *arg) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *)arg; + + net_lock(); + + priv->netdev.d_buf = priv->txpktbuf; + + if (priv->bifup) + { + (void)devif_poll(&priv->netdev, cdcmbim_txpoll); + } + + net_unlock(); +} + +/**************************************************************************** + * Name: cdcmbim_txavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Input Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called from the network stack with the network locked. + * + ****************************************************************************/ + +static int cdcmbim_txavail(struct net_driver_s *dev) +{ + struct usbhost_cdcmbim_s *priv = (struct usbhost_cdcmbim_s *) + dev->d_private; + + if (work_available(&priv->txpollwork)) + { + work_queue(LPWORK, &priv->txpollwork, cdcmbim_txavail_work, priv, 0); + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: usbhost_cdcmbim_initialize + * + * Description: + * Initialize the USB class driver. This function should be called + * be platform-specific code in order to initialize and register support + * for the USB host class device. + * + * Input Parameters: + * None + * + * Returned Value: + * On success this function will return zero (OK); A negated errno value + * will be returned on failure. + * + ****************************************************************************/ + +int usbhost_cdcmbim_initialize(void) +{ + /* Perform any one-time initialization of the class implementation */ + + /* Advertise our availability to support CDC MBIM devices */ + + return usbhost_registerclass(&g_cdcmbim); +} diff --git a/include/nuttx/net/net.h b/include/nuttx/net/net.h index 8218c0c7601..3bb48087956 100644 --- a/include/nuttx/net/net.h +++ b/include/nuttx/net/net.h @@ -155,7 +155,8 @@ enum net_lltype_e NET_LL_BLUETOOTH, /* Bluetooth */ NET_LL_IEEE80211, /* IEEE 802.11 */ NET_LL_IEEE802154, /* IEEE 802.15.4 MAC */ - NET_LL_PKTRADIO /* Non-standard packet radio */ + NET_LL_PKTRADIO, /* Non-standard packet radio */ + NET_LL_MBIM }; /* This defines a bitmap big enough for one bit for each socket option */ diff --git a/include/nuttx/usb/cdc.h b/include/nuttx/usb/cdc.h index e2b13a1ea60..9030ecf6b0a 100644 --- a/include/nuttx/usb/cdc.h +++ b/include/nuttx/usb/cdc.h @@ -1,4 +1,4 @@ -/******************************************************************************************** +/***************************************************************************** * include/nuttx/usb/cdc.h * * Copyright (C) 2011, 2017 Gregory Nutt. All rights reserved. @@ -34,30 +34,38 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - ********************************************************************************************/ + *****************************************************************************/ #ifndef __INCLUDE_NUTTX_USB_CDC_H #define __INCLUDE_NUTTX_USB_CDC_H -/******************************************************************************************** +/***************************************************************************** * Included Files - ********************************************************************************************/ + *****************************************************************************/ #include -/******************************************************************************************** +/***************************************************************************** * Pre-processor Definitions - ********************************************************************************************/ -/* Device Class Codes ***********************************************************************/ -/* Table 14: Communication Device Class Code (see definition USB_CLASS_CDC in usb.h) */ -/* Table 18: Data Interface Class Code (see definition USB_CLASS_CDC_DATA in usb.h) */ + *****************************************************************************/ + +/* Device Class Codes ********************************************************/ + +/* Table 14: Communication Device Class Code (see definition USB_CLASS_CDC in + * usb.h) + */ + +/* Table 18: Data Interface Class Code (see definition USB_CLASS_CDC_DATA in + * usb.h) + */ + +/* Communication Interface Class Codes ***************************************/ -/* Communication Interface Class Codes ******************************************************/ /* Table 15: Communication Interface Class Code */ #define CDC_CLASS_COMM 0x02 /* Communication Interface Class */ -/* Communication Interface Sub-Class Codes **************************************************/ +/* Communication Interface Sub-Class Codes ***********************************/ #define CDC_SUBCLASS_NONE 0x00 /* Reserved */ #define CDC_SUBCLASS_DLC 0x01 /* Direct Line Control Model */ @@ -67,26 +75,35 @@ #define CDC_SUBCLASS_CAPI 0x05 /* CAPI Control Model */ #define CDC_SUBCLASS_ECM 0x06 /* Ethernet Networking Control Model */ #define CDC_SUBCLASS_ATM 0x07 /* ATM Networking Control Model */ - /* 0x08-0x7f Reserved (future use) */ + /* 0x08-0x0d Reserved (future use) */ +#define CDC_SUBCLASS_MBIM 0x0e /* MBIM Control Model */ + /* 0x0f-0x7f Reserved (future use) */ /* 0x80-0xfe Reserved (vendor specific) */ -/* Communication Interface Class Protocol Codes ********************************************/ + +/* Communication Interface Class Protocol Codes ******************************/ + /* Table 17: Communication Interface Class Control Protocol Codes */ #define CDC_PROTO_NONE 0x00 /* No class specific protocol required */ -#define CDC_PROTO_ATM 0x01 /* Common AT commands (also known as Hayes compatible) */ +#define CDC_PROTO_ATM 0x01 /* Common AT commands (also known as Hayes + * compatible) + */ /* 0x02-0xfe Reserved (future use) */ #define CDC_PROTO_VENDOR 0xff /* Vendor-specific */ -/* Data Interface Sub-Class Codes ***********************************************************/ +/* Data Interface Sub-Class Codes ********************************************/ + /* None defined, should be zero */ #define CDC_DATA_SUBCLASS_NONE 0x00 -/* Date Interface Class Protocol Codes ******************************************************/ +/* Date Interface Class Protocol Codes ***************************************/ + /* Table 19: Data Interface Class Protocol Codes */ #define CDC_DATA_PROTO_NONE 0x00 /* No class specific protocol required */ /* 0x01-0x2f Reserved (future use) */ +#define CDC_DATA_PROTO_NTB 0x02 /* Network Transfer Block protocol */ #define CDC_DATA_PROTO_ISDN 0x30 /* Physical interface protocol for ISDN BRI */ #define CDC_DATA_PROTO_HDLC 0x31 /* HDLC */ #define CDC_DATA_PROTO_TRANSP 0x32 /* Transparent */ @@ -107,7 +124,8 @@ */ #define CDC_DATA_PROTO_VENDOR 0xff /* Vendor-specific */ -/* Requests and Notifications ***************************************************************/ +/* Requests and Notifications ************************************************/ + /* Table 2: Requests, Direct Line Control Model */ #define DLC_SET_AUX_LINE_STATE 0x10 /* Request to connect or disconnect secondary jack from @@ -128,6 +146,7 @@ #define DLC_RING_AUX_JACK 0x15 /* Request for a ring signal to be generated on secondary * phone jack. (Optional) */ + /* Table 3: Notifications, Direct Line Control Model */ #define DLC_AUX_JACK_HOOK_STATE 0x08 /* Indicates hook state of secondary device plugged @@ -136,6 +155,7 @@ #define DLC_RING_DETECT 0x09 /* Message to notify host that ring voltage was * detected on POTS interface. (Required) */ + /* Table 4: Requests, Abstract Control Model */ #define ACM_SEND_COMMAND 0x00 /* Issues a command in the format of the supported @@ -164,6 +184,7 @@ */ #define ACM_SEND_BREAK 0x23 /* Sends special carrier */ + /* Table 5: Notifications, Abstract Control Model */ #define ACM_NETWORK_CONNECTION 0x00 /* Notification to host of network connection status. @@ -175,6 +196,7 @@ #define ACM_SERIAL_STATE 0x20 /* Returns the current state of the carrier detect, DSR, * break, and ring signal. (Optional) */ + /* Table 6: Requests, Telephone Control Model */ #define TCM_SET_COMM_FEATURE 0x02 /* Used to set a unique communication feature, which is @@ -208,12 +230,14 @@ */ #define TCM_DIAL_DIGITS 0x36 /* Dials digits on the network connection. (Required) */ + /* Table 7: Notifications, Telephone Control Model */ #define TCM_CALL_STATE_CHANGE 0x28 /* DReports a state change on a call. (Required) */ #define TCM_LINE_STATE_CHANGE 0x29 /* DReports a state change on a line. (Optional) */ + /* Table 8: Requests, Multi-Channel Model */ #define MCM_SET_UNIT_PARAM 0x37 /* Used to set a Unit specific parameter (Optional) @@ -223,11 +247,13 @@ #define MCM_CLEAR_UNIT_PARAM 0x39 /* Used to set a Unit specific parameter to its default * state. (Optional) */ + /* Table 9: Request, CAPI Control Model */ #define CAPI_GET_PROFILE 0x3a /* Returns the implemented capabilities of the device * (Required) */ + /* Table 10: Requests, Ethernet Networking Control Model */ #define ECM_SEND_COMMAND 0x00 /* Issues a command in the format of the supported @@ -267,6 +293,7 @@ * transmitted, frames received, and bad frames received. * (Optional) */ + /* Table 11: Notifications, Ethernet Networking Control Model */ #define ECM_NETWORK_CONNECTION 0x00 /* Reports whether or not the physical layer (modem, @@ -277,6 +304,7 @@ */ #define ECM_SPEED_CHANGE 0x2a /* Reports a change in upstream or downstream (Required) */ + /* Table 12: Requests, ATM Networking Control Model */ #define ATM_SEND_COMMAND 0x00 /* Issues a command in the format of the supported control @@ -302,6 +330,7 @@ #define ATM_GET_VC_STATISTICS 0x53 /* Retrieves statistics from the ATM Networking device for * a particular VPI/VCI. (Optional) */ + /* Table 13: Requests, Ethernet and ATM Networking Control Model */ #define ATM_NETWORK_CONNECTION 0x00 /* Reports whether or not the physical layer (modem, @@ -317,7 +346,8 @@ */ #define ECM_SPEED_CHANGE ATM_SPEED_CHANGE -/* Descriptors ******************************************************************************/ +/* Descriptors ***************************************************************/ + /* Table 25: bDescriptor SubType in Functional Descriptors */ #define CDC_DSUBTYPE_HDR 0x00 /* Header Functional Descriptor, which marks the @@ -340,6 +370,7 @@ #define CDC_DSUBTYPE_CAPI 0x0e /* CAPI Control Management Functional Descriptor */ #define CDC_DSUBTYPE_ECM 0x0f /* Ethernet Networking Functional Descriptor */ #define CDC_DSUBTYPE_ATM 0x10 /* ATM Networking Functional Descriptor */ +#define CDC_DSUBTYPE_MBIM 0x1b /* MBIM Functional Descriptor */ /* 0x11-0xff Reserved (future use) */ /* Table 42: Ethernet Statistics Capabilities */ @@ -390,6 +421,7 @@ #define FEATURE_COUNTRY_SETTING 0x02 /* Country code in hexadecimal format as defined in * ISO 3166 */ + /* Table 49: POTS Relay Configuration Values */ #define POTS_ON_HOOK 0x0000 @@ -515,18 +547,22 @@ * been detected with HEC errors in the cell * header and successfully corrected. */ + /* Table 66: ATM VC Selector Codes */ -#define VC_US_CELLS_SENT 0x01 /* The number of cells that have been sent upstream to - * the WAN link for the specified VPI/VCI since the +#define VC_US_CELLS_SENT 0x01 /* The number of cells that have been + * sent upstream to the WAN link for the + * specified VPI/VCI since the device + * has been powered on or reset + */ +#define VC_DS_CELLS_RECEIVED 0x02 /* The number of cells that have been + * received downstream from the WAN link + * for the specified VPI/VCI since the * device has been powered on or reset */ -#define VC_DS_CELLS_RECEIVED 0x02 /* The number of cells that have been received - * downstream from the WAN link for the specified - * VPI/VCI since the device has been - * powered on or reset - */ -/* Notifications ****************************************************************************/ + +/* Notifications *************************************************************/ + /* Table 69: UART State Bitmap Values */ #define CDC_UART_RXCARRIER (1 << 0) /* bRxCarrier State of receiver carrier detection @@ -554,12 +590,14 @@ #define CDCACM_UART_DCD CDC_UART_RXCARRIER #define CDCACM_UART_DSR CDC_UART_TXCARRIER -/* "SerialState is used like a real interrupt status register. Once a notification has been - * sent, the device will reset and reevaluate the different signals. For the consistent - * signals like carrier detect or transmission carrier, this will mean another notification - * will not be generated until there is a state change. For the irregular signals like - * break, the incoming ring signal, or the overrun error state, this will reset their values - * to zero and again will not send another notification until their state changes." +/* "SerialState is used like a real interrupt status register. Once a + * notification has been sent, the device will reset and reevaluate the + * different signals. For the consistent signals like carrier detect or + * transmission carrier, this will mean another notification will not be + * generated until there is a state change. For the irregular signals like + * break, the incoming ring signal, or the overrun error state, this will + * reset their values to zero and again will not send another notification + * until their state changes." */ #define CDC_UART_CONSISTENT (CDC_UART_RXCARRIER | CDC_UART_TXCARRIER) @@ -583,9 +621,9 @@ #define CDC_LINEST_OFFHOOK 0x0002 /* Hook-switch has gone off hook */ #define CDC_LINEST_ONHOOK 0x0003 /* Hook-switch has gone on hook */ -/******************************************************************************************** +/***************************************************************************** * Public Types - ********************************************************************************************/ + *****************************************************************************/ /* Table 1: Data Class Protocol Wrapper */ @@ -594,10 +632,13 @@ struct cdc_protowrapper_s uint8_t size[2]; /* Size of wrapper in bytes */ uint8_t dstproto; /* bDstProtocol, Destination protocol ID */ uint8_t srcproto; /* bSrcProtocol, Source protocol ID */ - uint8_t data[1]; /* Data payload, actual size depends of size of the wrapper */ + uint8_t data[1]; /* Data payload, actual size depends of size of the + * wrapper + */ }; -/* Functional Descriptors *******************************************************************/ +/* Functional Descriptors ****************************************************/ + /* Table 23: Functional Descriptor General Format */ struct cdc_funcdesc_s @@ -758,14 +799,18 @@ struct cdc_usbterm_funcdesc_s struct cdc_netchan_funcdesc_s { uint8_t size; /* bFunctionLength, Size of this descriptor */ - uint8_t type; /* bDescriptorType, USB_DESC_TYPE_CSINTERFACE */ - uint8_t subtype; /* bDescriptorSubType, CDC_DSUBTYPE_NETCHAN as defined in Table 25 */ - uint8_t id; /* bEntityId, Constant uniquely identifying the Terminal */ - uint8_t name; /* iName, Index of string descriptor, describing the name of the Network - * Channel Terminal - */ - uint8_t index; /* bChannelIndex, The channel index of the associated network channel */ - uint8_t phyif; /* bPhysicalInterface, Type of physical interface */ + uint8_t type; /* bDescriptorType, USB_DESC_TYPE_CSINTERFACE */ + uint8_t subtype; /* bDescriptorSubType, CDC_DSUBTYPE_NETCHAN as defined in + * Table 25 */ + uint8_t id; /* bEntityId, Constant uniquely identifying the + * Terminal */ + uint8_t name; /* iName, Index of string descriptor, describing the name + * of the Network Channel Terminal + */ + uint8_t index; /* bChannelIndex, The channel index of the associated + * network channel + */ + uint8_t phyif; /* bPhysicalInterface, Type of physical interface */ }; #define SIZEOF_NETCHAN_FUNCDESC 7 @@ -827,22 +872,28 @@ struct cdc_capi_funcdesc_s struct cdc_ecm_funcdesc_s { - uint8_t size; /* bFunctionLength, Size of this descriptor */ - uint8_t type; /* bDescriptorType, USB_DESC_TYPE_CSINTERFACE */ - uint8_t subtype; /* bDescriptorSubType, CDC_DSUBTYPE_ECM as defined in Table 25 */ - uint8_t mac; /* iMACAddress, Index of the 48bit Ethernet MAC address string descriptor */ - uint8_t stats[4]; /* bmEthernetStatistics, Indicates which Ethernet statistics functions - * the device collects. See Table 42. - */ - uint8_t maxseg[2]; /* wMaxSegmentSize, The maximum segment size that the Ethernet device is - * capable of supporting. - */ - uint8_t nmcflts[2]; /* wNumberMCFilters, Contains the number of multicast filters that can be - * configured by the host. - */ - uint8_t npwrflts; /* bNumberPowerFilters, Contains the number of pattern filters that are - * available for causing wake-up of the host. - */ + uint8_t size; /* bFunctionLength, Size of this descriptor */ + uint8_t type; /* bDescriptorType, USB_DESC_TYPE_CSINTERFACE */ + uint8_t subtype; /* bDescriptorSubType, CDC_DSUBTYPE_ECM as defined in + * Table 25. + */ + uint8_t mac; /* iMACAddress, Index of the 48bit Ethernet MAC address + * string descriptor. + */ + uint8_t stats[4]; /* bmEthernetStatistics, Indicates which Ethernet + * statistics functions the device collects. + * See Table 42. + */ + uint8_t maxseg[2]; /* wMaxSegmentSize, The maximum segment size that the + * Ethernet device is capable of supporting. + */ + uint8_t nmcflts[2]; /* wNumberMCFilters, Contains the number of multicast + * filters that can be configured by the host. + */ + uint8_t npwrflts; /* bNumberPowerFilters, Contains the number of pattern + * filters that are available for causing wake-up of + * the host. + */ }; #define SIZEOF_ECM_FUNCDESC 13 @@ -853,26 +904,33 @@ struct cdc_atm_funcdesc_s { uint8_t size; /* bFunctionLength, Size of this descriptor */ uint8_t type; /* bDescriptorType, USB_DESC_TYPE_CSINTERFACE */ - uint8_t subtype; /* bDescriptorSubType, CDC_DSUBTYPE_ATM as defined in Table 25 */ - uint8_t endid; /* iEndSystemIdentifier, Index of End System Identifier string descriptor */ - uint8_t datacaps; /* bmDataCapabilities, The ATM data types the device supports */ - uint8_t devstats; /* bmATMDeviceStatistics, Indicates which optional statistics functions the - * device collects. + uint8_t subtype; /* bDescriptorSubType, CDC_DSUBTYPE_ATM as defined in + * Table 25. */ - uint8_t mxseg2[2]; /* wType2MaxSegmentSize, The maximum segment size that the Type 2 device is - * capable of supporting. + uint8_t endid; /* iEndSystemIdentifier, Index of End System Identifier + * string descriptor. */ - uint8_t mxseg3[2]; /* wType3MaxSegmentSize, The maximum segment size that the Type 3 device is - * capable of supporting + uint8_t datacaps; /* bmDataCapabilities, The ATM data types the device + * supports. */ - uint8_t mxvc[2]; /* wMaxVC, The maximum number of simultaneous virtual circuits the device is - * capable of supporting + uint8_t devstats; /* bmATMDeviceStatistics, Indicates which optional + * statistics functions the device collects. + */ + uint8_t mxseg2[2]; /* wType2MaxSegmentSize, The maximum segment size that + * the Type 2 device is capable of supporting. + */ + uint8_t mxseg3[2]; /* wType3MaxSegmentSize, The maximum segment size that + * the Type 3 device is capable of supporting + */ + uint8_t mxvc[2]; /* wMaxVC, The maximum number of simultaneous virtual + * circuits the device is capable of supporting */ }; #define SIZEOF_ATM_FUNCDESC 12 -/* Descriptor Data Structures ***************************************************************/ +/* Descriptor Data Structures ************************************************/ + /* Table 50: Line Coding Structure */ struct cdc_linecoding_s @@ -890,15 +948,15 @@ struct cdc_linecoding_s struct cdc_linestatus_s { uint8_t size[2]; /* wLength, Size of this structure, in bytes */ - uint8_t ringer[4]; /* dwRingerBitmap, Ringer Configuration bitmap for this line */ + uint8_t ringer[4]; /* dwRingerBitmap, Ringer Conf bitmap for this line */ uint8_t line[4]; /* dwLineState, Defines current state of the line */ - uint32_t call[1]; /* dwCallStateN, Defines current state of call N on the line */ + uint32_t call[1]; /* dwCallStateN, Current state of call N on the line */ }; -/* Messages are formatted as a standardized 8-byte header, followed by a variable-length - * data field. The header identifies the kind of notification, and the interface associated - * with the notification; it also indicates the length of the variable length portion of - * the message +/* Messages are formatted as a standardized 8-byte header, followed by a + * variable-length data field. The header identifies the kind of notification, + * and the interface associated with the notification; it also indicates the + * length of the variable length portion of the message */ struct cdc_notification_s @@ -918,12 +976,15 @@ struct cdc_notification_s struct cdc_unitparm_s { uint8_t id; /* bEntityId, Unit ID */ - uint8_t index; /* bParameterIndex, A zero based value indicating Unit parameter index */ + uint8_t index; /* bParameterIndex, A zero based value indicating Unit + * parameter index + */ }; /* Table 61: Power Management Pattern Filter Structure */ -/* Notification Data Structures *************************************************************/ +/* Notification Data Structures **********************************************/ + /* Table 72: ConnectionSpeedChange Data Structure */ struct cdc_speedchange_s diff --git a/net/netdev/netdev_register.c b/net/netdev/netdev_register.c index 85483dc230f..d8ef03fe4f0 100644 --- a/net/netdev/netdev_register.c +++ b/net/netdev/netdev_register.c @@ -55,6 +55,7 @@ #define NETDEV_PAN_FORMAT "pan%d" #define NETDEV_WLAN_FORMAT "wlan%d" #define NETDEV_WPAN_FORMAT "wpan%d" +#define NETDEV_MBIM_FORMAT "wwan%d" #if defined(CONFIG_DRIVERS_IEEE80211) /* Usually also has CONFIG_NET_ETHERNET */ # define NETDEV_DEFAULT_FORMAT NETDEV_WLAN_FORMAT @@ -313,6 +314,11 @@ int netdev_register(FAR struct net_driver_s *dev, enum net_lltype_e lltype) devfmt = NETDEV_TUN_FORMAT; break; #endif + case NET_LL_MBIM: + dev->d_llhdrlen = 0; + dev->d_pktsize = 1200; + devfmt = NETDEV_MBIM_FORMAT; + break; default: nerr("ERROR: Unrecognized link type: %d\n", lltype);