nuttx/drivers/usbdev/cdcncm.c

3266 lines
91 KiB
C
Raw Normal View History

/****************************************************************************
* drivers/usbdev/cdcncm.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/* References:
* [CDCNCM1.2] Universal Serial Bus - Communications Class - Subclass
* Specification for Ethernet Control Model Devices - Rev 1.2
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/poll.h>
#include <nuttx/net/netdev_lowerhalf.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/cdc.h>
#include <nuttx/usb/cdcncm.h>
#include <nuttx/usb/usbdev_trace.h>
#ifdef CONFIG_BOARD_USBDEV_SERIALSTR
# include <nuttx/board.h>
#endif
#include "cdcecm.h"
#ifdef CONFIG_NET_CDCNCM
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* The low priority work queue is preferred. If it is not enabled, LPWORK
* will be the same as HPWORK. NOTE: Use of the high priority work queue will
* have a negative impact on interrupt handling latency and overall system
* performance. This should be avoided.
*/
#define ETHWORK LPWORK
/* CONFIG_CDCECM_NINTERFACES determines the number of physical interfaces
* that will be supported.
*/
#ifndef CONFIG_CDCECM_NINTERFACES
# define CONFIG_CDCECM_NINTERFACES 1
#endif
/* TX timeout = 1 minute */
#define CDCNCM_TXTIMEOUT (60*CLK_TCK)
#define CDCNCM_DGRAM_COMBINE_PERIOD 1
#define NTB_DEFAULT_IN_SIZE 16384
#define NTB_OUT_SIZE 16384
#define TX_MAX_NUM_DPE 32
/* NCM Transfer Block Parameter Structure */
#define CDC_NCM_NTB16_SUPPORTED (1 << 0)
#define CDC_NCM_NTB32_SUPPORTED (1 << 1)
#define FORMATS_SUPPORTED (CDC_NCM_NTB16_SUPPORTED | \
CDC_NCM_NTB32_SUPPORTED)
#define CDC_NCM_NTH16_SIGN 0x484D434E /* NCMH */
#define CDC_NCM_NTH32_SIGN 0x686D636E /* ncmh */
#define CDC_NCM_NDP16_NOCRC_SIGN 0x304D434E /* NCM0 */
#define CDC_NCM_NDP32_NOCRC_SIGN 0x306D636E /* ncm0 */
#define CDC_MBIM_NDP16_NOCRC_SIGN 0x00535049 /* IPS<sessionID> : IPS0 for now */
#define CDC_MBIM_NDP32_NOCRC_SIGN 0x00737069 /* ips<sessionID> : ips0 for now */
#define INIT_NDP16_OPTS { \
.nthsign = CDC_NCM_NTH16_SIGN, \
.ndpsign = CDC_NCM_NDP16_NOCRC_SIGN, \
.nthsize = sizeof(struct cdc_ncm_nth16_s), \
.ndpsize = sizeof(struct usb_cdc_ncm_ndp16_s), \
.dpesize = sizeof(struct usb_cdc_ncm_dpe16_s), \
.ndpalign = 4, \
.dgramitemlen = 2, \
.blocklen = 2, \
.ndpindex = 2, \
.reserved1 = 0, \
.reserved2 = 0, \
.nextndpindex = 2, \
}
#define INIT_NDP32_OPTS { \
.nthsign = CDC_NCM_NTH32_SIGN, \
.ndpsign = CDC_NCM_NDP32_NOCRC_SIGN, \
.nthsize = sizeof(struct cdc_ncm_nth32_s), \
.ndpsize = sizeof(struct usb_cdc_ncm_ndp32_s), \
.dpesize = sizeof(struct usb_cdc_ncm_dpe32_s), \
.ndpalign = 8, \
.dgramitemlen = 4, \
.blocklen = 4, \
.ndpindex = 4, \
.reserved1 = 2, \
.reserved2 = 4, \
.nextndpindex = 4, \
}
#define CDC_NCM_NCAP_ETH_FILTER (1 << 0)
#define NCAPS (CDC_NCM_NCAP_ETH_FILTER)
#define NCM_ALIGN_MASK(x, mask) (((x) + (mask)) & ~(mask))
#define NCM_ALIGN(x, a) NCM_ALIGN_MASK((x), ((typeof(x))(a) - 1))
#define CDC_MBIM_DEVFORMAT "/dev/cdc-wdm%d"
#define CDC_MBIM_DEVNAMELEN 16
#define CDC_MBIM_NPOLLWAITERS 2
#define CDCMBIM_MAX_CTRL_MESSAGE 0x1000
#ifndef CONFIG_NET_CDCMBIM
# define cdcmbim_mkcfgdesc cdcncm_mkcfgdesc
# define cdcmbim_mkstrdesc cdcncm_mkstrdesc
# define cdcmbim_classobject cdcncm_classobject
# define cdcmbim_uninitialize cdcncm_uninitialize
# define CONFIG_CDCMBIM_PRODUCTSTR CONFIG_CDCNCM_PRODUCTSTR
#endif
/****************************************************************************
* Private Types
****************************************************************************/
enum ncm_notify_state_e
{
NCM_NOTIFY_NONE, /* Don't notify */
NCM_NOTIFY_CONNECT, /* Issue CONNECT next */
NCM_NOTIFY_SPEED, /* Issue SPEED_CHANGE next */
NCM_NOTIFY_RESPONSE_AVAILABLE, /* Issue RESPONSE_AVAILABLE next */
};
struct ndp_parser_opts_s
{
uint32_t nthsign; /* NCM Transfer Header signature */
uint32_t ndpsign; /* NCM Datagram Pointer signature */
uint_fast8_t nthsize; /* The length of NTH */
uint_fast8_t ndpsize; /* The length of NDP */
uint_fast8_t dpesize; /* The length of NDP Entry */
uint_fast8_t ndpalign; /* NDP alignment length */
uint_fast8_t dgramitemlen; /* The length of index or length */
uint_fast8_t blocklen; /* The length of current NTB */
uint_fast8_t ndpindex; /* The offset of first NDP in current NTB */
uint_fast8_t reserved1; /* Reserved1 */
uint_fast8_t reserved2; /* Reserved2 */
uint_fast8_t nextndpindex; /* The offset of next NDP in current NTB */
};
/* NTH: NCM Transfer Header
* NDP: NCM Datagram Pointer
* DPE: NCM Datagram Pointer Entry
* +------------+ or +------------+
* | NTH | | NTH |
* +------------+ +------------+
* | NDP | | Datagrams |
* +------------+ +------------+
* | Datagrams | | NDP |
* +------------+ +------------+
*
* The layout of the NTB(NCM Transfer Block) structure in the NuttX system
* is as follows:
* +--------------------------+
* |NTH : nth sign |
* | nth len |
* | sequence |
* | total len |
* | ndp index |----+
* +--------------------------+ |
* |NDP: ndp sign |<---+
* | ndp len |
* | next ndp index|
* | Datagram index|----+
* | Datagram len | |
* | Datagram index|----|--+
* | Datagram len | | |
* | Datagram index|----|--|--+
* | Datagram len | | | |
* | 0 | Need to end with two zeros
* | 0 | Need to end with two zeros
* | ... [32] | | | |
* +--------------------------+ | | |
* |Datagrams: Datagram1 |<---+ | |
* | pad | | |
* | Datagram2 |<------+ |
* | pad | |
* | Datagram3 |<---------+
* +--------------------------+
*/
begin_packed_struct struct cdc_ncm_nth16_s
{
uint32_t sign;
uint16_t headerlen;
uint16_t seq;
uint16_t blocklen;
uint16_t ndpindex;
} end_packed_struct;
begin_packed_struct struct cdc_ncm_nth32_s
{
uint32_t sign;
uint16_t headerlen;
uint16_t seq;
uint32_t blocklen;
uint32_t ndpindex;
} end_packed_struct;
/* 16-bit NCM Datagram Pointer Entry */
begin_packed_struct struct usb_cdc_ncm_dpe16_s
{
uint16_t index;
uint16_t len;
} end_packed_struct;
/* 16-bit NCM Datagram Pointer Table */
begin_packed_struct struct usb_cdc_ncm_ndp16_s
{
uint32_t sign;
uint16_t len;
uint16_t nextndpindex;
/* struct usb_cdc_ncm_dpe16_s dpe16[]; */
} end_packed_struct;
/* 32-bit NCM Datagram Pointer Entry */
begin_packed_struct struct usb_cdc_ncm_dpe32_s
{
uint32_t index;
uint32_t len;
} end_packed_struct;
/* 32-bit NCM Datagram Pointer Table */
begin_packed_struct struct usb_cdc_ncm_ndp32_s
{
uint32_t sign;
uint16_t len;
uint16_t reserved1;
uint32_t nextndpindex;
uint32_t reserved2;
/* struct usb_cdc_ncm_dpe32_s dpe32[]; */
} end_packed_struct;
begin_packed_struct struct usb_cdc_ncm_ntb_parameters_s
{
uint16_t len;
uint16_t ntbsupported;
uint32_t ntbinmaxsize;
uint16_t ndpindivisor;
uint16_t ndpinpayloadremainder;
uint16_t ndpinalignment;
uint16_t padding;
uint32_t ntboutmaxsize;
uint16_t ndpoutdivisor;
uint16_t ndpoutpayloadremainder;
uint16_t ndpoutalignment;
uint16_t ntboutmaxdatagrams;
} end_packed_struct;
/* The cdcncm_driver_s encapsulates all state information for a single
* hardware interface
*/
struct cdcncm_driver_s
{
/* USB CDC-NCM device */
struct usbdevclass_driver_s usbdev; /* USB device class vtable */
struct usbdev_devinfo_s devinfo;
FAR struct usbdev_req_s *ctrlreq; /* Allocated control request */
FAR struct usbdev_req_s *notifyreq; /* Allocated norify request */
FAR struct usbdev_ep_s *epint; /* Interrupt IN endpoint */
FAR struct usbdev_ep_s *epbulkin; /* Bulk IN endpoint */
FAR struct usbdev_ep_s *epbulkout; /* Bulk OUT endpoint */
uint8_t config; /* Selected configuration number */
FAR struct usbdev_req_s *rdreq; /* Single read request */
bool rxpending; /* Packet available in rdreq */
FAR struct usbdev_req_s *wrreq; /* Single write request */
sem_t wrreq_idle; /* Is the wrreq available? */
bool txdone; /* Did a write request complete? */
enum ncm_notify_state_e notify; /* State of notify */
FAR const struct ndp_parser_opts_s
*parseropts; /* Options currently used to parse NTB */
uint32_t ndpsign; /* NDP signature */
int dgramcount; /* The current tx cache dgram count */
FAR uint8_t *dgramaddr; /* The next tx cache dgram address */
bool isncm; /* true:NCM false:MBIM */
/* Network device */
bool bifup; /* true:ifup false:ifdown */
struct work_s irqwork; /* For deferring interrupt work
* to the work queue */
struct work_s notifywork; /* For deferring notify work
* to the work queue */
struct work_s delaywork; /* For deferring tx work
* to the work queue */
/* This holds the information visible to the NuttX network */
struct netdev_lowerhalf_s dev; /* Interface understood by the
* network */
netpkt_queue_t rx_queue; /* RX packet queue */
};
/* The cdcmbim_driver_s encapsulates all state information for a single
* hardware interface
*/
struct cdcmbim_driver_s
{
struct cdcncm_driver_s ncmdriver; /* CDC/NCM driver structure, must keep first */
mutex_t lock; /* Used to maintain mutual exclusive access */
sem_t read_sem; /* Used to wait for data to be readable */
FAR struct pollfd *fds[CDC_MBIM_NPOLLWAITERS];
struct iob_queue_s rx_queue; /* RX control message queue */
struct iob_queue_s tx_queue; /* TX control message queue */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Control interface driver methods */
#ifdef CONFIG_NET_CDCMBIM
static ssize_t cdcmbim_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t cdcmbim_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int cdcmbim_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
#endif
/* Network Device ***********************************************************/
/* Interrupt handling */
static void cdcncm_receive(FAR struct cdcncm_driver_s *priv);
static void cdcncm_txdone(FAR struct cdcncm_driver_s *priv);
static void cdcncm_interrupt_work(FAR void *arg);
/* NuttX callback functions */
static int cdcncm_ifup(FAR struct netdev_lowerhalf_s *dev);
static int cdcncm_ifdown(FAR struct netdev_lowerhalf_s *dev);
static int cdcncm_send(struct netdev_lowerhalf_s *dev, netpkt_t *pkt);
static FAR netpkt_t *cdcncm_recv(FAR struct netdev_lowerhalf_s *dev);
#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
static int cdcncm_addmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac);
#ifdef CONFIG_NET_MCASTGROUP
static int cdcncm_rmmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac);
#endif
#endif
#ifdef CONFIG_NETDEV_IOCTL
static int cdcncm_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
unsigned long arg);
#endif
static void cdcncm_notify_worker(FAR void *arg);
/* USB Device Class Driver **************************************************/
/* USB Device Class methods */
static int cdcncm_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void cdcncm_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static int cdcncm_setup(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR uint8_t *dataout, size_t outlen);
static void cdcncm_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
/* USB Device Class helpers */
static void cdcncm_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcncm_intcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcncm_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcncm_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static int cdcncm_mkepdesc(int epidx, FAR struct usb_epdesc_s *epdesc,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed);
/****************************************************************************
* Private Data
****************************************************************************/
/* USB Device Class Methods */
static const struct usbdevclass_driverops_s g_usbdevops =
{
cdcncm_bind,
cdcncm_unbind,
cdcncm_setup,
cdcncm_disconnect,
NULL,
NULL
};
/* File operations for control channel */
#ifdef CONFIG_NET_CDCMBIM
static const struct file_operations g_usbdevfops =
{
NULL, /* open */
NULL, /* close */
cdcmbim_read, /* read */
cdcmbim_write, /* write */
NULL, /* seek */
NULL, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
cdcmbim_poll /* poll */
};
#endif
#ifndef CONFIG_CDCNCM_COMPOSITE
static const struct usb_devdesc_s g_ncmdevdesc =
{
USB_SIZEOF_DEVDESC,
USB_DESC_TYPE_DEVICE,
{
LSBYTE(0x0200),
MSBYTE(0x0200)
},
USB_CLASS_CDC,
CDC_SUBCLASS_NCM,
CDC_PROTO_NONE,
CONFIG_CDCNCM_EP0MAXPACKET,
{
LSBYTE(CONFIG_CDCNCM_VENDORID),
MSBYTE(CONFIG_CDCNCM_VENDORID)
},
{
LSBYTE(CONFIG_CDCNCM_PRODUCTID),
MSBYTE(CONFIG_CDCNCM_PRODUCTID)
},
{
LSBYTE(CDCECM_VERSIONNO),
MSBYTE(CDCECM_VERSIONNO)
},
CDCECM_MANUFACTURERSTRID,
CDCECM_PRODUCTSTRID,
CDCECM_SERIALSTRID,
CDCECM_NCONFIGS
};
# ifdef CONFIG_NET_CDCMBIM
static const struct usb_devdesc_s g_mbimdevdesc =
{
USB_SIZEOF_DEVDESC,
USB_DESC_TYPE_DEVICE,
{
LSBYTE(0x0200),
MSBYTE(0x0200)
},
USB_CLASS_CDC,
CDC_SUBCLASS_MBIM,
CDC_PROTO_NONE,
CONFIG_CDCNCM_EP0MAXPACKET,
{
LSBYTE(CONFIG_CDCNCM_VENDORID),
MSBYTE(CONFIG_CDCNCM_VENDORID)
},
{
LSBYTE(CONFIG_CDCNCM_PRODUCTID),
MSBYTE(CONFIG_CDCNCM_PRODUCTID)
},
{
LSBYTE(CDCECM_VERSIONNO),
MSBYTE(CDCECM_VERSIONNO)
},
CDCECM_MANUFACTURERSTRID,
CDCECM_PRODUCTSTRID,
CDCECM_SERIALSTRID,
CDCECM_NCONFIGS
};
# endif /* CONFIG_NET_CDCMBIM */
#endif /* CONFIG_CDCNCM_COMPOSITE */
static const struct ndp_parser_opts_s g_ndp16_opts = INIT_NDP16_OPTS;
static const struct ndp_parser_opts_s g_ndp32_opts = INIT_NDP32_OPTS;
static const struct usb_cdc_ncm_ntb_parameters_s g_ntbparameters =
{
.len = sizeof(g_ntbparameters),
.ntbsupported = FORMATS_SUPPORTED,
.ntbinmaxsize = NTB_DEFAULT_IN_SIZE,
.ndpindivisor = 4,
.ndpinpayloadremainder = 0,
.ndpinalignment = 4,
.ntboutmaxsize = NTB_OUT_SIZE,
.ndpoutdivisor = 4,
.ndpoutpayloadremainder = 0,
.ndpoutalignment = 4,
};
static const struct netdev_ops_s g_netops =
{
cdcncm_ifup, /* ifup */
cdcncm_ifdown, /* ifdown */
cdcncm_send, /* transmit */
cdcncm_recv, /* receive */
#ifdef CONFIG_NET_MCASTGROUP
cdcncm_addmac, /* addmac */
cdcncm_rmmac, /* rmmac */
#endif
#ifdef CONFIG_NETDEV_IOCTL
cdcncm_ioctl, /* ioctl */
#endif
};
/****************************************************************************
* Inline Functions
****************************************************************************/
/****************************************************************************
* Name: cdcncm_get
*
* Description:
* Read size length data from address and increases the address by the
* corresponding size
*
* Input Parameters:
* address - Pointer to address
* size - Size of data
*
* Returned Value:
* The read value
*
****************************************************************************/
static inline uint32_t cdcncm_get(FAR uint8_t **address, size_t size)
{
uint32_t value = 0;
switch (size)
{
case 2:
value = GETUINT16(*address);
break;
case 4:
value = GETUINT32(*address);
break;
default:
nerr("Wrong size cdcncm_get %zu\n", size);
}
*address += size;
return value;
}
/****************************************************************************
* Name: cdcncm_put
*
* Description:
* Write size length data to address and increases the address by the
* corresponding size
*
* Input Parameters:
* address - Pointer to address
* size - Size of data
* value - Value of data
*
* Returned Value:
* None
*
****************************************************************************/
static inline
void cdcncm_put(FAR uint8_t **address, size_t size, uint32_t value)
{
switch (size)
{
case 2:
PUTUINT16(*address, value);
break;
case 4:
PUTUINT32(*address, value);
break;
default:
uerr("Wrong cdcncm_put\n");
}
*address += size;
}
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* File operations for control channel
****************************************************************************/
#ifdef CONFIG_NET_CDCMBIM
static ssize_t cdcmbim_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct cdcmbim_driver_s *self;
FAR struct iob_s *iob;
ssize_t ret;
inode = filep->f_inode;
self = inode->i_private;
for (; ; )
{
nxmutex_lock(&self->lock);
if ((iob = iob_peek_queue(&self->rx_queue)) != NULL)
{
ret = iob_copyout((FAR uint8_t *)buffer, iob, buflen, 0);
if (ret == iob->io_pktlen)
{
iob_remove_queue(&self->rx_queue);
iob_free_chain(iob);
}
else if (ret > 0)
{
iob_trimhead_queue(&self->rx_queue, ret);
}
break;
}
else
{
if (filep->f_oflags & O_NONBLOCK)
{
ret = -EAGAIN;
break;
}
nxmutex_unlock(&self->lock);
nxsem_wait(&self->read_sem);
}
}
nxmutex_unlock(&self->lock);
return ret;
}
static ssize_t cdcmbim_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct cdcmbim_driver_s *self;
FAR struct iob_s *iob;
int ret;
inode = filep->f_inode;
self = inode->i_private;
if (buflen > CDCMBIM_MAX_CTRL_MESSAGE)
{
buflen = CDCMBIM_MAX_CTRL_MESSAGE;
}
nxmutex_lock(&self->lock);
iob = iob_tryalloc(true);
if (iob == NULL)
{
ret = -ENOMEM;
goto errout;
}
ret = iob_copyin(iob, (FAR uint8_t *)buffer, buflen, 0, true);
if (ret < 0)
{
iob_free_chain(iob);
uerr("CDCMBIM copyin failed: %d\n", ret);
goto errout;
}
ret = iob_tryadd_queue(iob, &self->tx_queue);
if (ret < 0)
{
iob_free_chain(iob);
uerr("CDCMBIM add tx queue failed: %d\n", ret);
goto errout;
}
uinfo("wrote %zd bytes\n", buflen);
DEBUGASSERT(self->ncmdriver.notify == NCM_NOTIFY_RESPONSE_AVAILABLE);
work_queue(ETHWORK, &self->ncmdriver.notifywork, cdcncm_notify_worker,
self, 0);
errout:
nxmutex_unlock(&self->lock);
return ret < 0 ? ret : buflen;
}
/****************************************************************************
* Name: usbhost_poll
*
* Description:
* Standard character driver poll method.
*
****************************************************************************/
static int cdcmbim_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode;
FAR struct cdcmbim_driver_s *self;
int ret = OK;
int i;
DEBUGASSERT(fds);
inode = filep->f_inode;
self = inode->i_private;
/* Make sure that we have exclusive access to the private data structure */
DEBUGASSERT(self);
nxmutex_lock(&self->lock);
if (setup)
{
/* This is a request to set up the poll. Find an available slot for
* the poll structure reference
*/
for (i = 0; i < CDC_MBIM_NPOLLWAITERS; i++)
{
/* Find an available slot */
if (!self->fds[i])
{
/* Bind the poll structure and this slot */
self->fds[i] = fds;
fds->priv = &self->fds[i];
break;
}
}
if (i >= CDC_MBIM_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 (iob_get_queue_entry_count(&self->rx_queue))
{
poll_notify(&fds, 1, POLLIN);
}
}
else
{
/* This is a request to tear down the poll. */
FAR struct pollfd **slot = (FAR struct pollfd **)fds->priv;
DEBUGASSERT(slot);
/* Remove all memory of the poll setup */
*slot = NULL;
fds->priv = NULL;
}
errout:
nxmutex_unlock(&self->lock);
return ret;
}
#endif
/****************************************************************************
* Name: cdcncm_transmit_format
*
* Description:
* Format the data to be transmitted to the host in the format specified by
* the NCM protocol (Network Control Model) and the NCM NTB (Network
* Transfer Block) format.
*
* Input Parameters:
* self - Reference to the driver state structure
* pkt - Reference to the packet to be transmitted
*
* Returned Value:
* None
*
****************************************************************************/
static void cdcncm_transmit_format(FAR struct cdcncm_driver_s *self,
FAR netpkt_t *pkt)
{
FAR const struct ndp_parser_opts_s *opts = self->parseropts;
unsigned int dglen = netpkt_getdatalen(&self->dev, pkt);
const int div = g_ntbparameters.ndpindivisor;
const int rem = g_ntbparameters.ndpinpayloadremainder;
const int dgramidxlen = 2 * opts->dgramitemlen;
const int ndpalign = g_ntbparameters.ndpinalignment;
FAR uint8_t *tmp;
int ncblen;
int ndpindex;
ncblen = opts->nthsize;
ndpindex = NCM_ALIGN(ncblen, ndpalign);
if (self->dgramcount == 0)
{
/* Fill NCB */
tmp = self->wrreq->buf;
memset(tmp, 0, ncblen);
cdcncm_put(&tmp, 4, opts->nthsign);
cdcncm_put(&tmp, 2, opts->nthsize);
tmp += 2; /* Skip seq */
tmp += opts->blocklen; /* Skip block len */
cdcncm_put(&tmp, opts->ndpindex, ndpindex);
self->dgramaddr = self->wrreq->buf + ndpindex +
opts->ndpsize + (TX_MAX_NUM_DPE + 1) * dgramidxlen;
self->dgramaddr = (FAR uint8_t *)NCM_ALIGN((uintptr_t)self->dgramaddr,
div) + rem;
/* Fill NDP */
tmp = self->wrreq->buf + ndpindex;
cdcncm_put(&tmp, 4, self->ndpsign);
tmp += 2 + opts->reserved1;
cdcncm_put(&tmp, opts->nextndpindex, 0);
}
tmp = self->wrreq->buf + ndpindex + opts->ndpsize +
self->dgramcount * dgramidxlen;
cdcncm_put(&tmp, opts->dgramitemlen, self->dgramaddr - self->wrreq->buf);
cdcncm_put(&tmp, opts->dgramitemlen, dglen);
/* Fill IP packet */
netpkt_copyout(&self->dev, self->dgramaddr, pkt, dglen, 0);
self->dgramaddr += dglen;
self->dgramaddr = (FAR uint8_t *)NCM_ALIGN((uintptr_t)self->dgramaddr,
div) + rem;
self->dgramcount++;
}
/****************************************************************************
* Name: cdcncm_transmit_work
*
* Description:
* Send NTB to the USB device for ethernet frame transmission
*
* Input Parameters:
* arg - Reference to the driver state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void cdcncm_transmit_work(FAR void *arg)
{
FAR struct cdcncm_driver_s *self = arg;
FAR const struct ndp_parser_opts_s *opts = self->parseropts;
FAR uint8_t *tmp;
const int dgramidxlen = 2 * opts->dgramitemlen;
const int ndpalign = g_ntbparameters.ndpinalignment;
int ncblen;
int ndpindex;
int totallen;
/* Wait until the USB device request for Ethernet frame transmissions
* becomes available.
*/
while (nxsem_wait(&self->wrreq_idle) != OK)
{
}
ncblen = opts->nthsize;
ndpindex = NCM_ALIGN(ncblen, ndpalign);
/* Fill NCB */
tmp = self->wrreq->buf + 8; /* Offset to block length */
totallen = self->dgramaddr - self->wrreq->buf;
cdcncm_put(&tmp, opts->blocklen, totallen);
/* Fill NDP */
tmp = self->wrreq->buf + ndpindex + 4; /* Offset to ndp length */
cdcncm_put(&tmp, 2, opts->ndpsize + (self->dgramcount + 1) * dgramidxlen);
tmp += opts->reserved1 + opts->nextndpindex + opts->reserved2 +
self->dgramcount * dgramidxlen;
self->dgramcount = 0;
cdcncm_put(&tmp, opts->dgramitemlen, 0);
cdcncm_put(&tmp, opts->dgramitemlen, 0);
self->wrreq->len = totallen;
EP_SUBMIT(self->epbulkin, self->wrreq);
}
/****************************************************************************
* Name: cdcncm_packet_handler
*
* Description:
* Sends a single complete packet to the protocol stack
*
* Input Parameters:
* self - Reference to the driver state structure
*
* Returned Value:
* None
*
****************************************************************************/
static int cdcncm_packet_handler(FAR struct cdcncm_driver_s *self,
FAR uint8_t *dgram, uint32_t dglen)
{
FAR netpkt_t *pkt = netpkt_alloc(&self->dev, NETPKT_RX);
int ret = -ENOMEM;
if (pkt == NULL)
{
return ret;
}
ret = netpkt_copyin(&self->dev, pkt, dgram, dglen, 0);
if (ret < 0)
{
netpkt_free(&self->dev, pkt, NETPKT_RX);
return ret;
}
ret = netpkt_tryadd_queue(pkt, &self->rx_queue);
if (ret != 0)
{
netpkt_free(&self->dev, pkt, NETPKT_RX);
}
return ret;
}
/****************************************************************************
* Name: cdcncm_receive
*
* Description:
* An interrupt was received indicating the availability of a new RX packet
*
* Input Parameters:
* self - Reference to the driver state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void cdcncm_receive(FAR struct cdcncm_driver_s *self)
{
FAR const struct ndp_parser_opts_s *opts = self->parseropts;
FAR uint8_t *tmp = self->rdreq->buf;
uint32_t ntbmax = g_ntbparameters.ntboutmaxsize;
uint32_t blocklen;
uint32_t ndplen;
int ndpindex;
int dgramcounter;
/* Get signature */
if (GETUINT32(tmp) != opts->nthsign)
{
uerr("Wrong NTH SIGN, skblen %zu\n", self->rdreq->xfrd);
return;
}
tmp += 4;
/* Get header len */
if (GETUINT16(tmp) != opts->nthsize)
{
uerr("Wrong NTB headersize\n");
return;
}
tmp += 4; /* Skip header len and seq */
blocklen = cdcncm_get(&tmp, opts->blocklen);
/* Get block len */
if (blocklen > ntbmax)
{
uerr("OUT size exceeded\n");
return;
}
ndpindex = cdcncm_get(&tmp, opts->ndpindex);
do
{
uint32_t index;
uint32_t dglen;
if (((ndpindex % 4) != 0) || (ndpindex < opts->nthsize) ||
(ndpindex > (blocklen - opts->ndpsize)))
{
uerr("Bad index: %#X\n", ndpindex);
return;
}
tmp = self->rdreq->buf + ndpindex;
if (GETUINT32(tmp) != self->ndpsign)
{
uerr("Wrong NDP SIGN\n");
return;
}
tmp += 4;
ndplen = cdcncm_get(&tmp, 2);
if ((ndplen < opts->ndpsize + 2 * (opts->dgramitemlen * 2)) ||
(ndplen % opts->ndpalign != 0))
{
uerr("Bad NDP length: %x\n", ndplen);
return;
}
tmp += opts->reserved1;
ndpindex = cdcncm_get(&tmp, opts->nextndpindex);
tmp += opts->reserved2;
ndplen -= opts->ndpsize;
dgramcounter = 0;
do
{
index = cdcncm_get(&tmp, opts->dgramitemlen);
dglen = cdcncm_get(&tmp, opts->dgramitemlen);
/* TODO: support CRC */
/* Check if the packet is a valid size for the network buffer
* configuration.
*/
if (index == 0 || dglen == 0)
{
break;
}
dgramcounter++;
/* Copy the data from the hardware to self->rx_queue. */
cdcncm_packet_handler(self, self->rdreq->buf + index, dglen);
ndplen -= 2 * (opts->dgramitemlen);
}
while (ndplen > 2 * (opts->dgramitemlen));
}
while (ndpindex);
}
/****************************************************************************
* Name: cdcncm_txdone
*
* Description:
* An interrupt was received indicating that the last TX packet(s) is done
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
****************************************************************************/
static void cdcncm_txdone(FAR struct cdcncm_driver_s *priv)
{
/* In any event, poll the network for new TX data */
netdev_lower_txdone(&priv->dev);
}
/****************************************************************************
* Name: cdcncm_interrupt_work
*
* Description:
* Perform interrupt related work from the worker thread
*
* Input Parameters:
* arg - The argument passed when work_queue() was called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* Runs on a worker thread.
*
****************************************************************************/
static void cdcncm_interrupt_work(FAR void *arg)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)arg;
irqstate_t flags;
/* Check if we received an incoming packet, if so, call cdcncm_receive() */
if (self->rxpending)
{
cdcncm_receive(self);
netdev_lower_rxready(&self->dev);
flags = enter_critical_section();
self->rxpending = false;
EP_SUBMIT(self->epbulkout, self->rdreq);
leave_critical_section(flags);
}
/* Check if a packet transmission just completed. If so, call
* cdcncm_txdone. This may disable further Tx interrupts if there
* are no pending transmissions.
*/
flags = enter_critical_section();
if (self->txdone)
{
self->txdone = false;
leave_critical_section(flags);
cdcncm_txdone(self);
}
else
{
leave_critical_section(flags);
}
}
/* NuttX netdev callback functions */
/****************************************************************************
* Name: cdcncm_ifup
*
* Description:
* NuttX Callback: Bring up the Ethernet interface when an IP address is
* provided
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static int cdcncm_ifup(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct cdcncm_driver_s *priv =
container_of(dev, struct cdcncm_driver_s, dev);
#ifdef CONFIG_NET_IPv4
ninfo("Bringing up: %u.%u.%u.%u\n",
ip4_addr1(dev->netdev.d_ipaddr), ip4_addr2(dev->netdev.d_ipaddr),
ip4_addr3(dev->netdev.d_ipaddr), ip4_addr4(dev->netdev.d_ipaddr));
#endif
#ifdef CONFIG_NET_IPv6
ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
dev->netdev.d_ipv6addr[0], dev->netdev.d_ipv6addr[1],
dev->netdev.d_ipv6addr[2], dev->netdev.d_ipv6addr[3],
dev->netdev.d_ipv6addr[4], dev->netdev.d_ipv6addr[5],
dev->netdev.d_ipv6addr[6], dev->netdev.d_ipv6addr[7]);
#endif
priv->bifup = true;
return OK;
}
/****************************************************************************
* Name: cdcncm_ifdown
*
* Description:
* NuttX Callback: Stop the interface.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static int cdcncm_ifdown(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct cdcncm_driver_s *priv =
container_of(dev, struct cdcncm_driver_s, dev);
irqstate_t flags;
/* Disable the Ethernet interrupt */
flags = enter_critical_section();
/* Put the EMAC in its reset, non-operational state. This should be
* a known configuration that will guarantee the cdcncm_ifup() always
* successfully brings the interface back up.
*/
/* Mark the device "down" */
priv->bifup = false;
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: cdcncm_send
*
* Description:
* Transmit a packet through the USB interface
*
* Input Parameters:
* dev - Reference to the NuttX netdev lowerhalf driver structure
* pkt - The packet to be sent
*
* Returned Value:
* OK on success
*
****************************************************************************/
static int cdcncm_send(FAR struct netdev_lowerhalf_s *dev, FAR netpkt_t *pkt)
{
FAR struct cdcncm_driver_s *self;
self = container_of(dev, struct cdcncm_driver_s, dev);
cdcncm_transmit_format(self, pkt);
netpkt_free(dev, pkt, NETPKT_TX);
if ((self->wrreq->buf + NTB_OUT_SIZE - self->dgramaddr <
self->dev.netdev.d_pktsize) || self->dgramcount >= TX_MAX_NUM_DPE)
{
work_cancel(ETHWORK, &self->delaywork);
cdcncm_transmit_work(self);
}
else
{
work_queue(ETHWORK, &self->delaywork, cdcncm_transmit_work, self,
MSEC2TICK(CDCNCM_DGRAM_COMBINE_PERIOD));
}
return OK;
}
/****************************************************************************
* Name: cdcncm_recv
*
* Description:
* Receive a packet from the USB interface
*
* Input Parameters:
* dev - Reference to the NuttX netdev lowerhalf driver structure
*
* Returned Value:
* The received packet, or NULL if no packet is available
*
****************************************************************************/
static FAR netpkt_t *cdcncm_recv(FAR struct netdev_lowerhalf_s *dev)
{
FAR struct cdcncm_driver_s *self;
FAR netpkt_t *pkt;
self = container_of(dev, struct cdcncm_driver_s, dev);
pkt = netpkt_remove_queue(&self->rx_queue);
return pkt;
}
/****************************************************************************
* Name: cdcncm_addmac
*
* Description:
* NuttX Callback: Add the specified MAC address to the hardware multicast
* address filtering
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
* mac - The MAC address to be added
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
static int cdcncm_addmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac)
{
return OK;
}
#endif
/****************************************************************************
* Name: cdcncm_rmmac
*
* Description:
* NuttX Callback: Remove the specified MAC address from the hardware
* multicast address filtering
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
* mac - The MAC address to be removed
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_NET_MCASTGROUP
static int cdcncm_rmmac(FAR struct netdev_lowerhalf_s *dev,
FAR const uint8_t *mac)
{
return OK;
}
#endif
/****************************************************************************
* Name: cdcncm_ioctl
*
* Description:
* Handle network IOCTL commands directed to this device.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
* cmd - The IOCTL command
* arg - The argument for the IOCTL command
*
* Returned Value:
* OK on success; Negated errno on failure.
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
#ifdef CONFIG_NETDEV_IOCTL
static int cdcncm_ioctl(FAR struct netdev_lowerhalf_s *dev, int cmd,
unsigned long arg)
{
return -ENOTTY;
}
#endif
/****************************************************************************
* USB Device Class Helpers
****************************************************************************/
/****************************************************************************
* Name: cdcncm_ep0incomplete
*
* Description:
* Handle completion of EP0 control operations
*
****************************************************************************/
static void cdcncm_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
if (req->result || req->xfrd != req->len)
{
uerr("result: %hd, xfrd: %zu\n", req->result, req->xfrd);
}
}
/****************************************************************************
* Name: cdcncm_intcomplete
*
* Description:
* Handle completion of interrupt write request. This function probably
* executes in the context of an interrupt handler.
*
****************************************************************************/
static void cdcncm_intcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)ep->priv;
if (req->result || req->xfrd != req->len)
{
uerr("result: %hd, xfrd: %zu\n", req->result, req->xfrd);
}
if (self->notify != NCM_NOTIFY_NONE)
{
cdcncm_notify_worker(self);
}
else if (!self->isncm)
{
/* After the NIC information is synchronized, subsequent
* notifications are all related to the mbim control.
*/
self->notify = NCM_NOTIFY_RESPONSE_AVAILABLE;
}
}
/****************************************************************************
* Name: cdcncm_rdcomplete
*
* Description:
* Handle completion of read request on the bulk OUT endpoint.
*
****************************************************************************/
static void cdcncm_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)ep->priv;
uinfo("buf: %p, flags 0x%hhx, len %zu, xfrd %zu, result %hd\n",
req->buf, req->flags, req->len, req->xfrd, req->result);
switch (req->result)
{
case 0: /* Normal completion */
{
DEBUGASSERT(!self->rxpending);
self->rxpending = true;
work_queue(ETHWORK, &self->irqwork,
cdcncm_interrupt_work, self, 0);
}
break;
case -ESHUTDOWN: /* Disconnection */
break;
default: /* Some other error occurred */
{
uerr("req->result: %hd\n", req->result);
EP_SUBMIT(self->epbulkout, self->rdreq);
}
break;
}
}
/****************************************************************************
* Name: cdcncm_wrcomplete
*
* Description:
* Handle completion of write request. This function probably executes
* in the context of an interrupt handler.
*
****************************************************************************/
static void cdcncm_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)ep->priv;
int rc;
uinfo("buf: %p, flags 0x%hhx, len %zu, xfrd %zu, result %hd\n",
req->buf, req->flags, req->len, req->xfrd, req->result);
/* The single USB device write request is available for upcoming
* transmissions again.
*/
rc = nxsem_post(&self->wrreq_idle);
if (rc != OK)
{
nerr("nxsem_post failed! rc: %d\n", rc);
}
/* Inform the network layer that an Ethernet frame was transmitted. */
self->txdone = true;
work_queue(ETHWORK, &self->irqwork, cdcncm_interrupt_work, self, 0);
}
/****************************************************************************
* Name: cdcncm_resetconfig
*
* Description:
* Mark the device as not configured and disable all endpoints.
*
****************************************************************************/
static void cdcncm_resetconfig(FAR struct cdcncm_driver_s *self)
{
/* Are we configured? */
if (self->config != CDCECM_CONFIGID_NONE)
{
/* Yes.. but not anymore */
self->config = CDCECM_CONFIGID_NONE;
/* Inform the networking layer that the link is down */
cdcncm_ifdown(&self->dev);
/* Disable endpoints. This should force completion of all pending
* transfers.
*/
EP_DISABLE(self->epint);
EP_DISABLE(self->epbulkin);
EP_DISABLE(self->epbulkout);
self->notify = NCM_NOTIFY_SPEED;
}
self->parseropts = &g_ndp16_opts;
self->ndpsign = self->isncm ? self->parseropts->ndpsign :
CDC_MBIM_NDP16_NOCRC_SIGN;
}
/****************************************************************************
* Name: cdcncm_setconfig
*
* Set the device configuration by allocating and configuring endpoints and
* by allocating and queue read and write requests.
*
****************************************************************************/
static int cdcncm_setconfig(FAR struct cdcncm_driver_s *self, uint8_t config)
{
struct usb_ss_epdesc_s epdesc;
int ret;
if (config == self->config)
{
return OK;
}
cdcncm_resetconfig(self);
if (config == CDCECM_CONFIGID_NONE)
{
return OK;
}
if (config != CDCECM_CONFIGID)
{
return -EINVAL;
}
cdcncm_mkepdesc(CDCNCM_EP_INTIN_IDX,
&epdesc.epdesc, &self->devinfo, self->usbdev.speed);
ret = EP_CONFIGURE(self->epint, &epdesc.epdesc, false);
if (ret < 0)
{
goto error;
}
self->epint->priv = self;
cdcncm_mkepdesc(CDCNCM_EP_BULKIN_IDX,
&epdesc.epdesc, &self->devinfo, self->usbdev.speed);
ret = EP_CONFIGURE(self->epbulkin, &epdesc.epdesc, false);
if (ret < 0)
{
goto error;
}
self->epbulkin->priv = self;
cdcncm_mkepdesc(CDCNCM_EP_BULKOUT_IDX,
&epdesc.epdesc, &self->devinfo, self->usbdev.speed);
ret = EP_CONFIGURE(self->epbulkout, &epdesc.epdesc, true);
if (ret < 0)
{
goto error;
}
self->epbulkout->priv = self;
/* Queue read requests in the bulk OUT endpoint */
DEBUGASSERT(!self->rxpending);
self->rdreq->callback = cdcncm_rdcomplete,
ret = EP_SUBMIT(self->epbulkout, self->rdreq);
if (ret != OK)
{
uerr("EP_SUBMIT failed. ret %d\n", ret);
goto error;
}
/* We are successfully configured */
self->config = config;
/* Set client's MAC address */
memcpy(self->dev.netdev.d_mac.ether.ether_addr_octet,
"\x00\xe0\xde\xad\xbe\xef", IFHWADDRLEN);
/* Report link up to networking layer */
if (cdcncm_ifup(&self->dev) == OK)
{
self->dev.netdev.d_flags |= IFF_UP;
}
return OK;
error:
cdcncm_resetconfig(self);
return ret;
}
/****************************************************************************
* Name: cdcncm_notify
*
****************************************************************************/
static int cdcncm_notify(FAR struct cdcncm_driver_s *self)
{
FAR struct usb_ctrlreq_s *req =
(FAR struct usb_ctrlreq_s *)self->notifyreq->buf;
int ret = 0;
switch (self->notify)
{
case NCM_NOTIFY_NONE:
return ret;
case NCM_NOTIFY_CONNECT:
/* Notifying the host of the NIC modification status */
req->req = NCM_NETWORK_CONNECTION;
req->value[0] = LSBYTE(IFF_IS_RUNNING(self->dev.netdev.d_flags));
req->value[1] = MSBYTE(IFF_IS_RUNNING(self->dev.netdev.d_flags));
req->len[0] = 0;
req->len[1] = 0;
ret = sizeof(*req);
self->notify = NCM_NOTIFY_NONE;
break;
case NCM_NOTIFY_SPEED:
{
FAR uint32_t *data;
req->req = NCM_SPEED_CHANGE;
req->value[0] = LSBYTE(0);
req->value[1] = MSBYTE(0);
req->len[0] = LSBYTE(8);
req->len[1] = MSBYTE(8);
/* SPEED_CHANGE data is up/down speeds in bits/sec */
data = (FAR uint32_t *)(self->notifyreq->buf + sizeof(*req));
data[0] = self->usbdev.speed == USB_SPEED_HIGH ?
CDCECM_HIGH_BITRATE : CDCECM_LOW_BITRATE;
data[1] = data[0];
ret = sizeof(*req) + 8;
self->notify = NCM_NOTIFY_CONNECT;
break;
}
case NCM_NOTIFY_RESPONSE_AVAILABLE:
{
req->req = MBIM_RESPONSE_AVAILABLE;
req->value[0] = LSBYTE(0);
req->value[1] = MSBYTE(0);
req->len[0] = LSBYTE(0);
req->len[1] = MSBYTE(0);
ret = sizeof(*req);
self->notify = NCM_NOTIFY_NONE;
break;
}
}
req->type = 0xa1;
req->index[0] = LSBYTE(self->devinfo.ifnobase);
req->index[1] = MSBYTE(self->devinfo.ifnobase);
return ret;
}
/****************************************************************************
* Name: cdcncm_notify_worker
*
****************************************************************************/
static void cdcncm_notify_worker(FAR void *arg)
{
FAR struct cdcncm_driver_s *self = arg;
int ret;
ret = cdcncm_notify(self);
if (ret > 0)
{
FAR struct usbdev_req_s *notifyreq = self->notifyreq;
notifyreq->len = ret;
notifyreq->flags = USBDEV_REQFLAGS_NULLPKT;
EP_SUBMIT(self->epint, notifyreq);
}
}
/****************************************************************************
* Name: cdcncm_setinterface
*
****************************************************************************/
static int cdcncm_setinterface(FAR struct cdcncm_driver_s *self,
uint16_t interface, uint16_t altsetting)
{
if (interface == self->devinfo.ifnobase + 1)
{
if (altsetting)
{
self->notify = NCM_NOTIFY_SPEED;
}
netdev_lower_carrier_on(&self->dev);
work_queue(ETHWORK, &self->notifywork, cdcncm_notify_worker, self,
MSEC2TICK(100));
}
else
{
uerr("invailid interface %d\n", interface);
return -EINVAL;
}
return OK;
}
/****************************************************************************
* Name: cdcnm_mkstrdesc
*
* Description:
* Construct a string descriptor
*
****************************************************************************/
static int cdcnm_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc,
bool isncm)
{
FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1);
FAR const char *str;
int len;
int ndata;
int i;
switch (id)
{
#ifndef CONFIG_CDCNCM_COMPOSITE
case 0:
{
/* Descriptor 0 is the language id */
strdesc->len = 4;
strdesc->type = USB_DESC_TYPE_STRING;
data[0] = LSBYTE(CDCECM_STR_LANGUAGE);
data[1] = MSBYTE(CDCECM_STR_LANGUAGE);
return 4;
}
case CDCECM_MANUFACTURERSTRID:
str = CONFIG_CDCNCM_VENDORSTR;
break;
case CDCECM_PRODUCTSTRID:
str = isncm ? CONFIG_CDCNCM_PRODUCTSTR : CONFIG_CDCMBIM_PRODUCTSTR;
break;
case CDCECM_SERIALSTRID:
#ifdef CONFIG_BOARD_USBDEV_SERIALSTR
str = board_usbdev_serialstr();
#else
str = "0";
#endif
break;
case CDCECM_CONFIGSTRID:
str = "Default";
break;
#endif
case CDCECM_MACSTRID:
str = "020000112233";
break;
default:
uerr("Unknown string descriptor index: %d\n", id);
return -EINVAL;
}
/* The string is utf16-le. The poor man's utf-8 to utf16-le
* conversion below will only handle 7-bit en-us ascii
*/
len = strlen(str);
if (len > (CDCECM_MAXSTRLEN / 2))
{
len = (CDCECM_MAXSTRLEN / 2);
}
for (i = 0, ndata = 0; i < len; i++, ndata += 2)
{
data[ndata] = str[i];
data[ndata + 1] = 0;
}
strdesc->len = ndata + 2;
strdesc->type = USB_DESC_TYPE_STRING;
return strdesc->len;
}
/****************************************************************************
* Name: cdcncm_mkstrdesc
*
* Description:
* Construct a string descriptor
*
****************************************************************************/
static int cdcncm_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
return cdcnm_mkstrdesc(id, strdesc, true);
}
/****************************************************************************
* Name: cdcmbim_mkstrdesc
*
* Description:
* Construct a string descriptor
*
****************************************************************************/
#ifdef CONFIG_NET_CDCMBIM
static int cdcmbim_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
return cdcnm_mkstrdesc(id, strdesc, false);
}
#endif
/****************************************************************************
* Name: cdcecm_mkepcompdesc
*
* Description:
* Construct the endpoint companion descriptor
*
****************************************************************************/
#ifdef CONFIG_USBDEV_SUPERSPEED
static void cdcncm_mkepcompdesc(int epidx,
FAR struct usb_ss_epcompdesc_s *epcompdesc)
{
switch (epidx)
{
case CDCNCM_EP_INTIN_IDX: /* Interrupt IN endpoint */
{
epcompdesc->len = USB_SIZEOF_SS_EPCOMPDESC; /* Descriptor length */
epcompdesc->type = USB_DESC_TYPE_ENDPOINT_COMPANION; /* Descriptor type */
if (CONFIG_CDCNCM_EPINTIN_MAXBURST >= USB_SS_INT_EP_MAXBURST)
{
epcompdesc->mxburst = USB_SS_INT_EP_MAXBURST - 1;
}
else
{
epcompdesc->mxburst = CONFIG_CDCNCM_EPINTIN_MAXBURST;
}
epcompdesc->attr = 0;
epcompdesc->wbytes[0] = LSBYTE((epcompdesc->mxburst + 1) *
CONFIG_CDCNCM_EPINTIN_SSSIZE);
epcompdesc->wbytes[1] = MSBYTE((epcompdesc->mxburst + 1) *
CONFIG_CDCNCM_EPINTIN_SSSIZE);
}
break;
case CDCNCM_EP_BULKOUT_IDX:
{
epcompdesc->len = USB_SIZEOF_SS_EPCOMPDESC; /* Descriptor length */
epcompdesc->type = USB_DESC_TYPE_ENDPOINT_COMPANION; /* Descriptor type */
if (CONFIG_CDCNCM_EPBULKOUT_MAXBURST >= USB_SS_BULK_EP_MAXBURST)
{
epcompdesc->mxburst = USB_SS_BULK_EP_MAXBURST - 1;
}
else
{
epcompdesc->mxburst = CONFIG_CDCNCM_EPBULKOUT_MAXBURST;
}
if (CONFIG_CDCNCM_EPBULKOUT_MAXSTREAM > USB_SS_BULK_EP_MAXSTREAM)
{
epcompdesc->attr = USB_SS_BULK_EP_MAXSTREAM;
}
else
{
epcompdesc->attr = CONFIG_CDCNCM_EPBULKOUT_MAXSTREAM;
}
epcompdesc->wbytes[0] = 0;
epcompdesc->wbytes[1] = 0;
}
break;
case CDCNCM_EP_BULKIN_IDX:
{
epcompdesc->len = USB_SIZEOF_SS_EPCOMPDESC; /* Descriptor length */
epcompdesc->type = USB_DESC_TYPE_ENDPOINT_COMPANION; /* Descriptor type */
if (CONFIG_CDCNCM_EPBULKIN_MAXBURST >= USB_SS_BULK_EP_MAXBURST)
{
epcompdesc->mxburst = USB_SS_BULK_EP_MAXBURST - 1;
}
else
{
epcompdesc->mxburst = CONFIG_CDCNCM_EPBULKIN_MAXBURST;
}
if (CONFIG_CDCNCM_EPBULKIN_MAXSTREAM > USB_SS_BULK_EP_MAXSTREAM)
{
epcompdesc->attr = USB_SS_BULK_EP_MAXSTREAM;
}
else
{
epcompdesc->attr = CONFIG_CDCNCM_EPBULKIN_MAXSTREAM;
}
epcompdesc->wbytes[0] = 0;
epcompdesc->wbytes[1] = 0;
}
break;
default:
break;
}
}
#endif
/****************************************************************************
* Name: cdcncm_mkepdesc
*
* Description:
* Construct the endpoint descriptor
*
****************************************************************************/
static int cdcncm_mkepdesc(int epidx, FAR struct usb_epdesc_s *epdesc,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed)
{
uint16_t intin_mxpktsz = CONFIG_CDCNCM_EPINTIN_FSSIZE;
uint16_t bulkout_mxpktsz = CONFIG_CDCNCM_EPBULKOUT_FSSIZE;
uint16_t bulkin_mxpktsz = CONFIG_CDCNCM_EPBULKIN_FSSIZE;
int len = sizeof(struct usb_epdesc_s);
#ifdef CONFIG_USBDEV_SUPERSPEED
if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS)
{
/* Maximum packet size (super speed) */
intin_mxpktsz = CONFIG_CDCNCM_EPINTIN_SSSIZE;
bulkout_mxpktsz = CONFIG_CDCNCM_EPBULKOUT_SSSIZE;
bulkin_mxpktsz = CONFIG_CDCNCM_EPBULKIN_SSSIZE;
len += sizeof(struct usb_ss_epcompdesc_s);
}
else
#endif
#ifdef CONFIG_USBDEV_DUALSPEED
if (speed == USB_SPEED_HIGH)
{
/* Maximum packet size (high speed) */
intin_mxpktsz = CONFIG_CDCNCM_EPINTIN_HSSIZE;
bulkout_mxpktsz = CONFIG_CDCNCM_EPBULKOUT_HSSIZE;
bulkin_mxpktsz = CONFIG_CDCNCM_EPBULKIN_HSSIZE;
}
#else
UNUSED(speed);
#endif
if (epdesc == NULL)
{
return len;
}
epdesc->len = USB_SIZEOF_EPDESC; /* Descriptor length */
epdesc->type = USB_DESC_TYPE_ENDPOINT; /* Descriptor type */
switch (epidx)
{
case CDCNCM_EP_INTIN_IDX: /* Interrupt IN endpoint */
{
epdesc->addr = USB_DIR_IN |
devinfo->epno[CDCNCM_EP_INTIN_IDX];
epdesc->attr = USB_EP_ATTR_XFER_INT;
epdesc->mxpacketsize[0] = LSBYTE(intin_mxpktsz);
epdesc->mxpacketsize[1] = MSBYTE(intin_mxpktsz);
epdesc->interval = 5;
}
break;
case CDCNCM_EP_BULKIN_IDX:
{
epdesc->addr = USB_DIR_IN |
devinfo->epno[CDCNCM_EP_BULKIN_IDX];
epdesc->attr = USB_EP_ATTR_XFER_BULK;
epdesc->mxpacketsize[0] = LSBYTE(bulkin_mxpktsz);
epdesc->mxpacketsize[1] = MSBYTE(bulkin_mxpktsz);
epdesc->interval = 0;
}
break;
case CDCNCM_EP_BULKOUT_IDX:
{
epdesc->addr = USB_DIR_OUT |
devinfo->epno[CDCNCM_EP_BULKOUT_IDX];
epdesc->attr = USB_EP_ATTR_XFER_BULK;
epdesc->mxpacketsize[0] = LSBYTE(bulkout_mxpktsz);
epdesc->mxpacketsize[1] = MSBYTE(bulkout_mxpktsz);
epdesc->interval = 0;
}
break;
default:
DEBUGPANIC();
}
#ifdef CONFIG_USBDEV_SUPERSPEED
if (speed == USB_SPEED_SUPER || speed == USB_SPEED_SUPER_PLUS)
{
epdesc++;
cdcncm_mkepcompdesc(epidx, (FAR struct usb_ss_epcompdesc_s *)epdesc);
}
#endif
return len;
}
/****************************************************************************
* Name: cdcnm_mkcfgdesc
*
* Description:
* Construct the config descriptor
*
****************************************************************************/
static int16_t cdcnm_mkcfgdesc(FAR uint8_t *desc,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed, uint8_t type, bool isncm)
{
FAR struct usb_cfgdesc_s *cfgdesc = NULL;
int16_t len = 0;
int ret;
/* Check for switches between high and full speed */
if (type == USB_DESC_TYPE_OTHERSPEEDCONFIG && speed < USB_SPEED_SUPER)
{
speed = speed == USB_SPEED_HIGH ? USB_SPEED_FULL : USB_SPEED_HIGH;
}
#ifndef CONFIG_CDCNCM_COMPOSITE
if (desc)
{
cfgdesc = (FAR struct usb_cfgdesc_s *)desc;
cfgdesc->len = USB_SIZEOF_CFGDESC;
cfgdesc->type = type;
cfgdesc->ninterfaces = CDCECM_NINTERFACES;
cfgdesc->cfgvalue = CDCECM_CONFIGID;
cfgdesc->icfg = devinfo->strbase + CDCECM_CONFIGSTRID;
cfgdesc->attr = USB_CONFIG_ATTR_ONE | CDCECM_SELFPOWERED |
CDCECM_REMOTEWAKEUP;
cfgdesc->mxpower = (CONFIG_USBDEV_MAXPOWER + 1) / 2;
desc += USB_SIZEOF_CFGDESC;
}
len += USB_SIZEOF_CFGDESC;
#elif defined(CONFIG_COMPOSITE_IAD)
/* Interface association descriptor */
if (desc)
{
FAR struct usb_iaddesc_s *iaddesc;
iaddesc = (FAR struct usb_iaddesc_s *)desc;
iaddesc->len = USB_SIZEOF_IADDESC; /* Descriptor length */
iaddesc->type = USB_DESC_TYPE_INTERFACEASSOCIATION; /* Descriptor type */
iaddesc->firstif = devinfo->ifnobase; /* Number of first interface of the function */
iaddesc->nifs = devinfo->ninterfaces; /* Number of interfaces associated with the function */
iaddesc->classid = USB_CLASS_CDC; /* Class code */
iaddesc->subclass = isncm ? CDC_SUBCLASS_NCM :
CDC_SUBCLASS_MBIM; /* Sub-class code */
iaddesc->protocol = CDC_PROTO_NONE; /* Protocol code */
iaddesc->ifunction = 0; /* Index to string identifying the function */
desc += USB_SIZEOF_IADDESC;
}
len += USB_SIZEOF_IADDESC;
#endif
/* Communications Class Interface */
if (desc)
{
FAR struct usb_ifdesc_s *ifdesc;
ifdesc = (FAR struct usb_ifdesc_s *)desc;
ifdesc->len = USB_SIZEOF_IFDESC;
ifdesc->type = USB_DESC_TYPE_INTERFACE;
ifdesc->ifno = devinfo->ifnobase;
ifdesc->alt = 0;
ifdesc->neps = 1;
ifdesc->classid = USB_CLASS_CDC;
ifdesc->subclass = isncm ? CDC_SUBCLASS_NCM : CDC_SUBCLASS_MBIM;
ifdesc->protocol = CDC_PROTO_NONE;
ifdesc->iif = 0;
desc += USB_SIZEOF_IFDESC;
}
len += USB_SIZEOF_IFDESC;
if (desc)
{
FAR struct cdc_hdr_funcdesc_s *hdrdesc;
hdrdesc = (FAR struct cdc_hdr_funcdesc_s *)desc;
hdrdesc->size = SIZEOF_HDR_FUNCDESC;
hdrdesc->type = USB_DESC_TYPE_CSINTERFACE;
hdrdesc->subtype = CDC_DSUBTYPE_HDR;
hdrdesc->cdc[0] = LSBYTE(0x0110);
hdrdesc->cdc[1] = MSBYTE(0x0110);
desc += SIZEOF_HDR_FUNCDESC;
}
len += SIZEOF_HDR_FUNCDESC;
if (desc)
{
FAR struct cdc_union_funcdesc_s *uniondesc;
uniondesc = (FAR struct cdc_union_funcdesc_s *)desc;
uniondesc->size = SIZEOF_UNION_FUNCDESC(1);
uniondesc->type = USB_DESC_TYPE_CSINTERFACE;
uniondesc->subtype = CDC_DSUBTYPE_UNION;
uniondesc->master = devinfo->ifnobase;
uniondesc->slave[0] = devinfo->ifnobase + 1;
desc += SIZEOF_UNION_FUNCDESC(1);
}
len += SIZEOF_UNION_FUNCDESC(1);
if (desc)
{
FAR struct cdc_ecm_funcdesc_s *ecmdesc;
ecmdesc = (FAR struct cdc_ecm_funcdesc_s *)desc;
ecmdesc->size = SIZEOF_ECM_FUNCDESC;
ecmdesc->type = USB_DESC_TYPE_CSINTERFACE;
ecmdesc->subtype = CDC_DSUBTYPE_ECM;
ecmdesc->mac = devinfo->strbase + CDCECM_MACSTRID;
ecmdesc->stats[0] = 0;
ecmdesc->stats[1] = 0;
ecmdesc->stats[2] = 0;
ecmdesc->stats[3] = 0;
ecmdesc->maxseg[0] = LSBYTE(CONFIG_NET_ETH_PKTSIZE);
ecmdesc->maxseg[1] = MSBYTE(CONFIG_NET_ETH_PKTSIZE);
ecmdesc->nmcflts[0] = LSBYTE(0);
ecmdesc->nmcflts[1] = MSBYTE(0);
ecmdesc->npwrflts = 0;
desc += SIZEOF_ECM_FUNCDESC;
}
len += SIZEOF_ECM_FUNCDESC;
if (isncm)
{
if (desc)
{
FAR struct cdc_ncm_funcdesc_s *ncmdesc;
ncmdesc = (FAR struct cdc_ncm_funcdesc_s *)desc;
ncmdesc->size = SIZEOF_NCM_FUNCDESC;
ncmdesc->type = USB_DESC_TYPE_CSINTERFACE;
ncmdesc->subtype = CDC_DSUBTYPE_NCM;
ncmdesc->version[0] = LSBYTE(CDCECM_VERSIONNO);
ncmdesc->version[1] = MSBYTE(CDCECM_VERSIONNO);
ncmdesc->netcaps = NCAPS;
desc += SIZEOF_NCM_FUNCDESC;
}
len += SIZEOF_NCM_FUNCDESC;
}
else
{
if (desc)
{
FAR struct cdc_mbim_funcdesc_s *mbimdesc;
mbimdesc = (FAR struct cdc_mbim_funcdesc_s *)desc;
mbimdesc->size = SIZEOF_MBIM_FUNCDESC;
mbimdesc->type = USB_DESC_TYPE_CSINTERFACE;
mbimdesc->subtype = CDC_DSUBTYPE_MBIM;
mbimdesc->version[0] = LSBYTE(CDCECM_VERSIONNO);
mbimdesc->version[1] = MSBYTE(CDCECM_VERSIONNO);
mbimdesc->maxctrlmsg[0] = LSBYTE(CDCMBIM_MAX_CTRL_MESSAGE);
mbimdesc->maxctrlmsg[1] = MSBYTE(CDCMBIM_MAX_CTRL_MESSAGE);
mbimdesc->numfilter = 0x20;
mbimdesc->maxfiltersize = 0x80;
mbimdesc->maxsegmentsize[0] = LSBYTE(0x800);
mbimdesc->maxsegmentsize[1] = LSBYTE(0x800);
mbimdesc->netcaps = 0x20;
desc += SIZEOF_MBIM_FUNCDESC;
}
len += SIZEOF_MBIM_FUNCDESC;
}
ret = cdcncm_mkepdesc(CDCNCM_EP_INTIN_IDX,
(FAR struct usb_epdesc_s *)desc,
devinfo, speed);
if (desc)
{
desc += ret;
}
len += ret;
/* Data Class Interface */
if (desc)
{
FAR struct usb_ifdesc_s *ifdesc;
ifdesc = (FAR struct usb_ifdesc_s *)desc;
ifdesc->len = USB_SIZEOF_IFDESC;
ifdesc->type = USB_DESC_TYPE_INTERFACE;
ifdesc->ifno = devinfo->ifnobase + 1;
ifdesc->alt = 0;
ifdesc->neps = 0;
ifdesc->classid = USB_CLASS_CDC_DATA;
ifdesc->subclass = 0;
ifdesc->protocol = isncm ? CDC_DATA_PROTO_NCMNTB :
CDC_DATA_PROTO_MBIMNTB;
ifdesc->iif = 0;
desc += USB_SIZEOF_IFDESC;
}
len += USB_SIZEOF_IFDESC;
if (desc)
{
FAR struct usb_ifdesc_s *ifdesc;
ifdesc = (FAR struct usb_ifdesc_s *)desc;
ifdesc->len = USB_SIZEOF_IFDESC;
ifdesc->type = USB_DESC_TYPE_INTERFACE;
ifdesc->ifno = devinfo->ifnobase + 1;
ifdesc->alt = 1;
ifdesc->neps = 2;
ifdesc->classid = USB_CLASS_CDC_DATA;
ifdesc->subclass = 0;
ifdesc->protocol = isncm ? CDC_DATA_PROTO_NCMNTB :
CDC_DATA_PROTO_MBIMNTB;
ifdesc->iif = 0;
desc += USB_SIZEOF_IFDESC;
}
len += USB_SIZEOF_IFDESC;
ret = cdcncm_mkepdesc(CDCNCM_EP_BULKIN_IDX,
(FAR struct usb_epdesc_s *)desc,
devinfo, speed);
if (desc)
{
desc += ret;
}
len += ret;
ret = cdcncm_mkepdesc(CDCNCM_EP_BULKOUT_IDX,
(FAR struct usb_epdesc_s *)desc,
devinfo, speed);
if (desc)
{
desc += ret;
}
len += ret;
if (cfgdesc)
{
cfgdesc->totallen[0] = LSBYTE(len);
cfgdesc->totallen[1] = MSBYTE(len);
}
DEBUGASSERT(len <= CDCECM_MXDESCLEN);
return len;
}
/****************************************************************************
* Name: cdcncm_mkcfgdesc
*
* Description:
* Construct the config descriptor
*
****************************************************************************/
static int16_t cdcncm_mkcfgdesc(FAR uint8_t *desc,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed, uint8_t type)
{
return cdcnm_mkcfgdesc(desc, devinfo, speed, type, true);
}
# ifdef CONFIG_NET_CDCMBIM
static int16_t cdcmbim_mkcfgdesc(FAR uint8_t *desc,
FAR struct usbdev_devinfo_s *devinfo,
uint8_t speed, uint8_t type)
{
return cdcnm_mkcfgdesc(desc, devinfo, speed, type, false);
}
# endif
/****************************************************************************
* Name: cdcncm_getdescriptor
*
* Description:
* Copy the USB CDC-NCM Device USB Descriptor of a given Type and a given
* Index into the provided Descriptor Buffer.
*
* Input Parameter:
* drvr - The USB Device Fuzzer Driver instance.
* type - The Type of USB Descriptor requested.
* index - The Index of the USB Descriptor requested.
* desc - The USB Descriptor is copied into this buffer, which must be at
* least CDCECM_MXDESCLEN bytes wide.
*
* Returned Value:
* The size in bytes of the requested USB Descriptor or a negated errno in
* case of failure.
*
****************************************************************************/
static int cdcncm_getdescriptor(FAR struct cdcncm_driver_s *self,
uint8_t type, uint8_t index, FAR void *desc)
{
switch (type)
{
#ifndef CONFIG_CDCNCM_COMPOSITE
case USB_DESC_TYPE_DEVICE:
if (self->isncm)
{
return usbdev_copy_devdesc(desc,
&g_ncmdevdesc,
self->usbdev.speed);
}
# ifdef CONFIG_NET_CDCMBIM
else
{
memcpy(desc, &g_mbimdevdesc, sizeof(g_mbimdevdesc));
return sizeof(g_mbimdevdesc);
}
# endif
break;
#endif
#ifdef CONFIG_USBDEV_DUALSPEED
case USB_DESC_TYPE_OTHERSPEEDCONFIG:
#endif /* CONFIG_USBDEV_DUALSPEED */
case USB_DESC_TYPE_CONFIG:
return cdcncm_mkcfgdesc((FAR uint8_t *)desc, &self->devinfo,
self->usbdev.speed, type);
case USB_DESC_TYPE_STRING:
return cdcncm_mkstrdesc(index, (FAR struct usb_strdesc_s *)desc);
default:
uerr("Unsupported descriptor type: 0x%02hhx\n", type);
break;
}
return -ENOTSUP;
}
/****************************************************************************
* USB Device Class Methods
****************************************************************************/
/****************************************************************************
* Name: cdcncm_bind
*
* Description:
* Invoked when the driver is bound to an USB device
*
****************************************************************************/
static int cdcncm_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)driver;
int ret = OK;
uinfo("\n");
#ifndef CONFIG_CDCNCM_COMPOSITE
dev->ep0->priv = self;
#endif
/* Preallocate control request */
self->ctrlreq = usbdev_allocreq(dev->ep0, CDCECM_MXDESCLEN);
if (self->ctrlreq == NULL)
{
ret = -ENOMEM;
goto error;
}
self->ctrlreq->callback = cdcncm_ep0incomplete;
self->epint = DEV_ALLOCEP(dev,
USB_DIR_IN |
self->devinfo.epno[CDCNCM_EP_INTIN_IDX],
true, USB_EP_ATTR_XFER_INT);
self->epbulkin = DEV_ALLOCEP(dev,
USB_DIR_IN |
self->devinfo.epno[CDCNCM_EP_BULKIN_IDX],
true, USB_EP_ATTR_XFER_BULK);
self->epbulkout = DEV_ALLOCEP(dev,
USB_DIR_OUT |
self->devinfo.epno[CDCNCM_EP_BULKOUT_IDX],
false, USB_EP_ATTR_XFER_BULK);
if (!self->epint || !self->epbulkin || !self->epbulkout)
{
uerr("Failed to allocate endpoints!\n");
ret = -ENODEV;
goto error;
}
self->epint->priv = self;
self->epbulkin->priv = self;
self->epbulkout->priv = self;
/* Pre-allocate notify requests. The buffer size is CDCECM_MXDESCLEN. */
self->notifyreq = usbdev_allocreq(self->epint, CDCECM_MXDESCLEN);
if (self->notifyreq == NULL)
{
uerr("Out of memory\n");
ret = -ENOMEM;
goto error;
}
self->notifyreq->callback = cdcncm_intcomplete;
/* Pre-allocate read requests. The buffer size is NTB_DEFAULT_IN_SIZE. */
self->rdreq = usbdev_allocreq(self->epbulkout, NTB_DEFAULT_IN_SIZE);
if (self->rdreq == NULL)
{
uerr("Out of memory\n");
ret = -ENOMEM;
goto error;
}
self->rdreq->callback = cdcncm_rdcomplete;
/* Pre-allocate a single write request. Buffer size is NTB_OUT_SIZE */
self->wrreq = usbdev_allocreq(self->epbulkin, NTB_OUT_SIZE);
if (self->wrreq == NULL)
{
uerr("Out of memory\n");
ret = -ENOMEM;
goto error;
}
self->wrreq->callback = cdcncm_wrcomplete;
/* The single write request just allocated is available now. */
ret = nxsem_init(&self->wrreq_idle, 0, 1);
if (ret != OK)
{
uerr("nxsem_init failed. ret: %d\n", ret);
goto error;
}
self->txdone = false;
#ifndef CONFIG_CDCNCM_COMPOSITE
#ifdef CONFIG_USBDEV_SELFPOWERED
DEV_SETSELFPOWERED(dev);
#endif
/* And pull-up the data line for the soft connect function (unless we are
* part of a composite device)
*/
DEV_CONNECT(dev);
#endif
return OK;
error:
uerr("cdcncm_bind failed! ret: %d\n", ret);
cdcncm_unbind(driver, dev);
return ret;
}
static void cdcncm_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)driver;
#ifdef CONFIG_DEBUG_FEATURES
if (!driver || !dev)
{
usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
return;
}
#endif
/* Make sure that the endpoints have been unconfigured. If
* we were terminated gracefully, then the configuration should
* already have been reset. If not, then calling cdcacm_resetconfig
* should cause the endpoints to immediately terminate all
* transfers and return the requests to us (with result == -ESHUTDOWN)
*/
cdcncm_resetconfig(self);
up_mdelay(50);
/* Free the interrupt IN endpoint */
if (self->epint)
{
DEV_FREEEP(dev, self->epint);
self->epint = NULL;
}
/* Free the pre-allocated control request */
if (self->ctrlreq != NULL)
{
usbdev_freereq(dev->ep0, self->ctrlreq);
self->ctrlreq = NULL;
}
/* Free pre-allocated read requests (which should all have
* been returned to the free list at this time -- we don't check)
*/
if (self->rdreq != NULL)
{
usbdev_freereq(self->epbulkout, self->rdreq);
self->rdreq = NULL;
}
/* Free the bulk OUT endpoint */
if (self->epbulkout)
{
DEV_FREEEP(dev, self->epbulkout);
self->epbulkout = NULL;
}
/* Free write requests that are not in use (which should be all
* of them)
*/
if (self->wrreq != NULL)
{
usbdev_freereq(self->epbulkin, self->wrreq);
self->wrreq = NULL;
}
/* Free the bulk IN endpoint */
if (self->epbulkin)
{
DEV_FREEEP(dev, self->epbulkin);
self->epbulkin = NULL;
}
/* Clear out all data in the rx_queue */
netpkt_free_queue(&self->rx_queue);
}
static int cdcncm_setup(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev,
FAR const struct usb_ctrlreq_s *ctrl,
FAR uint8_t *dataout, size_t outlen)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)driver;
uint16_t value = GETUINT16(ctrl->value);
uint16_t index = GETUINT16(ctrl->index);
uint16_t len = GETUINT16(ctrl->len);
int ret = -EOPNOTSUPP;
if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_STANDARD)
{
switch (ctrl->req)
{
case USB_REQ_GETDESCRIPTOR:
{
uint8_t descindex = ctrl->value[0];
uint8_t desctype = ctrl->value[1];
self->usbdev.speed = dev->speed;
ret = cdcncm_getdescriptor(self, desctype, descindex,
self->ctrlreq->buf);
}
break;
case USB_REQ_SETCONFIGURATION:
ret = cdcncm_setconfig(self, value);
break;
case USB_REQ_SETINTERFACE:
ret = cdcncm_setinterface(self, index, value);
break;
default:
uerr("Unsupported standard req: 0x%02hhx\n", ctrl->req);
break;
}
}
else if ((ctrl->type & USB_REQ_TYPE_MASK) == USB_REQ_TYPE_CLASS)
{
switch (ctrl->req)
{
case ECM_SET_PACKET_FILTER:
/* SetEthernetPacketFilter is the only required CDCNCM subclass
* specific request, but it is still ok to always operate in
* promiscuous mode and rely on the host to do the filtering.
* This is especially true for our case:
* A simulated point-to-point connection.
*/
uinfo("ECM_SET_PACKET_FILTER wValue: 0x%04hx, wIndex: 0x%04hx\n",
GETUINT16(ctrl->value), GETUINT16(ctrl->index));
ret = OK;
break;
case NCM_GET_NTB_PARAMETERS:
if (len >= sizeof(g_ntbparameters))
{
memcpy(self->ctrlreq->buf, &g_ntbparameters,
sizeof(g_ntbparameters));
ret = sizeof(g_ntbparameters);
}
break;
case NCM_SET_NTB_FORMAT:
if (len != 0 || index != self->devinfo.ifnobase)
break;
switch (value)
{
case 0x0000:
self->parseropts = &g_ndp16_opts;
self->ndpsign = self->isncm ? self->parseropts->ndpsign :
CDC_MBIM_NDP16_NOCRC_SIGN;
uinfo("NCM16 selected\n");
ret = 0;
break;
case 0x0001:
self->parseropts = &g_ndp32_opts;
self->ndpsign = self->isncm ? self->parseropts->ndpsign :
CDC_MBIM_NDP32_NOCRC_SIGN;
uinfo("NCM32 selected\n");
ret = 0;
break;
default:
break;
}
break;
case NCM_GET_NTB_INPUT_SIZE:
uinfo("NCM_GET_NTB_INPUT_SIZE len %d\n", len);
ret = 0;
break;
case NCM_SET_NTB_INPUT_SIZE:
if (len == 4 && value == 0)
{
uinfo("NCM_SET_NTB_INPUT_SIZE len %d NTB input size %d\n",
len, *(FAR int *)dataout);
ret = 0;
}
break;
#ifdef CONFIG_NET_CDCMBIM
case MBIM_SEND_COMMAND:
{
FAR struct cdcmbim_driver_s *mbim =
(FAR struct cdcmbim_driver_s *)self;
FAR struct iob_s *iob = iob_tryalloc(true);
if (iob == NULL)
{
return -ENOMEM;
}
ret = iob_copyin(iob, dataout, len, 0, true);
if (ret < 0)
{
iob_free_chain(iob);
uerr("CDCMBIM copyin failed: %d\n", ret);
return ret;
}
ret = iob_tryadd_queue(iob, &mbim->rx_queue);
if (ret < 0)
{
iob_free_chain(iob);
uerr("CDCMBIM add rx queue failed: %d\n", ret);
return ret;
}
nxsem_post(&mbim->read_sem);
poll_notify(mbim->fds, CDC_MBIM_NPOLLWAITERS, POLLIN);
}
break;
case MBIM_GET_RESPONSE:
{
FAR struct cdcmbim_driver_s *mbim =
(FAR struct cdcmbim_driver_s *)self;
FAR struct iob_s *iob;
ret = -ENOSPC;
if ((iob = iob_remove_queue(&mbim->tx_queue)) != NULL)
{
ret = iob_copyout(self->ctrlreq->buf, iob, len, 0);
if (ret >= 0)
{
iob_free_chain(iob);
}
}
}
break;
#endif
default:
uerr("Unsupported class req: 0x%02hhx\n", ctrl->req);
break;
}
}
else
{
uerr("Unsupported type: 0x%02hhx\n", ctrl->type);
}
if (ret >= 0)
{
FAR struct usbdev_req_s *ctrlreq = self->ctrlreq;
ctrlreq->len = MIN(len, ret);
ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;
ret = EP_SUBMIT(dev->ep0, ctrlreq);
uinfo("EP_SUBMIT ret: %d\n", ret);
if (ret < 0)
{
ctrlreq->result = OK;
cdcncm_ep0incomplete(dev->ep0, ctrlreq);
}
}
return ret;
}
static void cdcncm_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
uinfo("\n");
}
/****************************************************************************
* Name: cdcncm_classobject
*
* Description:
* Register USB CDC/NCM and return the class object.
*
* Returned Value:
* A pointer to the allocated class object (NULL on failure).
*
****************************************************************************/
static int cdcncm_classobject(int minor,
FAR struct usbdev_devinfo_s *devinfo,
FAR struct usbdevclass_driver_s **classdev)
{
FAR struct cdcncm_driver_s *self;
int ret;
/* Initialize the driver structure */
self = kmm_zalloc(sizeof(struct cdcncm_driver_s));
if (!self)
{
nerr("Out of memory!\n");
return -ENOMEM;
}
self->isncm = true;
/* Network device initialization */
self->dev.ops = &g_netops;
self->dev.quota[NETPKT_TX] = CONFIG_CDCNCM_QUOTA_TX;
self->dev.quota[NETPKT_RX] = CONFIG_CDCNCM_QUOTA_RX;
/* USB device initialization */
#if defined(CONFIG_USBDEV_SUPERSPEED)
self->usbdev.speed = USB_SPEED_SUPER;
#elif defined(CONFIG_USBDEV_DUALSPEED)
self->usbdev.speed = USB_SPEED_HIGH;
#else
self->usbdev.speed = USB_SPEED_FULL;
#endif
self->usbdev.ops = &g_usbdevops;
memcpy(&self->devinfo, devinfo, sizeof(struct usbdev_devinfo_s));
/* Put the interface in the down state. This usually amounts to resetting
* the device and/or calling cdcncm_ifdown().
*/
cdcncm_ifdown(&self->dev);
/* Read the MAC address from the hardware into
* priv->dev.netdev.d_mac.ether.ether_addr_octet
* Applies only if the Ethernet MAC has its own internal address.
*/
memcpy(self->dev.netdev.d_mac.ether.ether_addr_octet,
"\x00\xe0\xde\xad\xbe\xef", IFHWADDRLEN);
/* Register the device with the OS so that socket IOCTLs can be performed */
ret = netdev_lower_register(&self->dev, NET_LL_ETHERNET);
if (ret < 0)
{
nerr("netdev_lower_register failed. ret: %d\n", ret);
}
*classdev = (FAR struct usbdevclass_driver_s *)self;
return ret;
}
/****************************************************************************
* Name: cdcmbim_classobject
*
* Description:
* Register USB CDC/MBIM and return the class object.
*
* Returned Value:
* A pointer to the allocated class object (NULL on failure).
*
****************************************************************************/
#ifdef CONFIG_NET_CDCMBIM
static int cdcmbim_classobject(int minor,
FAR struct usbdev_devinfo_s *devinfo,
FAR struct usbdevclass_driver_s **classdev)
{
FAR struct cdcmbim_driver_s *self;
FAR struct cdcncm_driver_s *ncm;
int ret;
/* Initialize the driver structure */
self = kmm_zalloc(sizeof(struct cdcmbim_driver_s));
if (!self)
{
nerr("Out of memory!\n");
return -ENOMEM;
}
ncm = &self->ncmdriver;
ncm->isncm = false;
/* Network device initialization */
ncm->dev.ops = &g_netops;
ncm->dev.quota[NETPKT_TX] = CONFIG_CDCNCM_QUOTA_TX;
ncm->dev.quota[NETPKT_RX] = CONFIG_CDCNCM_QUOTA_RX;
/* USB device initialization */
#if defined(CONFIG_USBDEV_SUPERSPEED)
ncm->usbdev.speed = USB_SPEED_SUPER;
#elif defined(CONFIG_USBDEV_DUALSPEED)
ncm->usbdev.speed = USB_SPEED_HIGH;
#else
ncm->usbdev.speed = USB_SPEED_FULL;
#endif
ncm->usbdev.ops = &g_usbdevops;
memcpy(&ncm->devinfo, devinfo, sizeof(struct usbdev_devinfo_s));
nxmutex_init(&self->lock);
nxsem_init(&self->read_sem, 0, 0);
uinfo("Register character driver\n");
/* Put the interface in the down state. This usually amounts to resetting
* the device and/or calling cdcncm_ifdown().
*/
g_netops.ifdown(&self->ncmdriver.dev);
/* Register the device with the OS so that socket IOCTLs can be performed */
ret = netdev_lower_register(&self->ncmdriver.dev, NET_LL_MBIM);
if (ret < 0)
{
nerr("netdev_lower_register failed. ret: %d\n", ret);
}
else
{
char devname[CDC_MBIM_DEVNAMELEN];
uint8_t index = 0;
#ifdef CONFIG_NETDEV_IFINDEX
index = self->ncmdriver.dev.netdev.d_ifindex;
#endif
snprintf(devname, sizeof(devname), CDC_MBIM_DEVFORMAT, index);
ret = register_driver(devname, &g_usbdevfops, 0666, self);
if (ret < 0)
{
nerr("register_driver failed. ret: %d\n", ret);
}
}
*classdev = (FAR struct usbdevclass_driver_s *)self;
return ret;
}
#endif
/****************************************************************************
* Name: cdcncm_uninitialize
*
* Description:
* Un-initialize the USB CDC/NCM class driver. This function is used
* internally by the USB composite driver to uninitialize the CDC/NCM
* driver. This same interface is available (with an untyped input
* parameter) when the CDC/NCM driver is used standalone.
*
* Input Parameters:
* There is one parameter, it differs in typing depending upon whether the
* CDC/NCM driver is an internal part of a composite device, or a
* standalone USB driver:
*
* classdev - The class object returned by cdcncm_classobject()
*
* Returned Value:
* None
*
****************************************************************************/
static void cdcncm_uninitialize(FAR struct usbdevclass_driver_s *classdev)
{
FAR struct cdcncm_driver_s *self = (FAR struct cdcncm_driver_s *)classdev;
int ret;
/* Un-register the CDC/NCM netdev device */
ret = netdev_lower_unregister(&self->dev);
if (ret < 0)
{
nerr("ERROR: netdev_lower_unregister failed. ret: %d\n", ret);
}
#ifndef CONFIG_CDCNCM_COMPOSITE
usbdev_unregister(&self->usbdev);
#endif
/* And free the driver structure */
kmm_free(self);
}
/****************************************************************************
* Name: cdcmbim_uninitialize
*
* Description:
* Un-initialize the USB CDC/MBIM class driver. This function is used
* internally by the USB composite driver to uninitialize the CDC/MBIM
* driver. This same interface is available (with an untyped input
* parameter) when the CDC/MBIM driver is used standalone.
*
* Input Parameters:
* There is one parameter, it differs in typing depending upon whether the
* CDC/MBIM driver is an internal part of a composite device, or a
* standalone USB driver:
*
* classdev - The class object returned by cdcmbim_classobject()
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_NET_CDCMBIM
static void cdcmbim_uninitialize(FAR struct usbdevclass_driver_s *classdev)
{
FAR struct cdcmbim_driver_s *self =
(FAR struct cdcmbim_driver_s *)classdev;
int ret;
/* Un-register the CDC/MBIM netdev device */
ret = netdev_lower_unregister(&self->ncmdriver.dev);
if (ret < 0)
{
nerr("ERROR: netdev_lower_unregister failed. ret: %d\n", ret);
}
# ifndef CONFIG_CDCNCM_COMPOSITE
usbdev_unregister(&self->ncmdriver.usbdev);
# endif
/* And free the driver structure */
nxmutex_destroy(&self->lock);
nxsem_destroy(&self->read_sem);
kmm_free(self);
}
#endif
#ifndef CONFIG_CDCNCM_COMPOSITE
static int cdcnm_initialize(int minor, FAR void **handle, bool isncm)
{
FAR struct usbdevclass_driver_s *drvr = NULL;
struct usbdev_devinfo_s devinfo;
int ret;
memset(&devinfo, 0, sizeof(struct usbdev_devinfo_s));
devinfo.ninterfaces = CDCECM_NINTERFACES;
devinfo.nstrings = CDCECM_NSTRIDS;
devinfo.nendpoints = CDCECM_NUM_EPS;
devinfo.epno[CDCNCM_EP_INTIN_IDX] = CONFIG_CDCNCM_EPINTIN;
devinfo.epno[CDCNCM_EP_BULKIN_IDX] = CONFIG_CDCNCM_EPBULKIN;
devinfo.epno[CDCNCM_EP_BULKOUT_IDX] = CONFIG_CDCNCM_EPBULKOUT;
ret = isncm ? cdcncm_classobject(minor, &devinfo, &drvr) :
cdcmbim_classobject(minor, &devinfo, &drvr);
if (ret == OK)
{
ret = usbdev_register(drvr);
if (ret < 0)
{
uinfo("usbdev_register failed. ret %d\n", ret);
}
}
if (handle)
{
*handle = drvr;
}
return ret;
}
#endif
#ifdef CONFIG_CDCNCM_COMPOSITE
static void cdcnm_get_composite_devdesc(FAR struct composite_devdesc_s *dev,
bool isncm)
{
memset(dev, 0, sizeof(struct composite_devdesc_s));
/* The callback functions for the CDC/NCM class.
*
* classobject() and uninitialize() must be provided by board-specific
* logic
*/
dev->mkconfdesc = isncm ? cdcncm_mkcfgdesc : cdcmbim_mkcfgdesc;
dev->mkstrdesc = isncm ? cdcncm_mkstrdesc : cdcmbim_mkstrdesc;
dev->classobject = isncm ? cdcncm_classobject : cdcmbim_classobject;
dev->uninitialize = isncm ? cdcncm_uninitialize : cdcmbim_uninitialize;
dev->nconfigs = CDCECM_NCONFIGS; /* Number of configurations supported */
dev->configid = CDCECM_CONFIGID; /* The only supported configuration ID */
/* Let the construction function calculate the size of config descriptor */
dev->cfgdescsize = isncm ?
cdcncm_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0) :
cdcmbim_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0);
/* Board-specific logic must provide the device minor */
/* Interfaces.
*
* ifnobase must be provided by board-specific logic
*/
dev->devinfo.ninterfaces = CDCECM_NINTERFACES; /* Number of interfaces in the configuration */
/* Strings.
*
* strbase must be provided by board-specific logic
*/
dev->devinfo.nstrings = CDCECM_NSTRIDS + 1; /* Number of Strings */
/* Endpoints.
*
* Endpoint numbers must be provided by board-specific logic.
*/
dev->devinfo.nendpoints = CDCECM_NUM_EPS;
}
#endif /* CONFIG_CDCNCM_COMPOSITE */
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: cdcncm_initialize / cdcmbim_initialize
*
* Description:
* Register CDC_NCM/MBIM USB device interface. Register the corresponding
* network driver to NuttX and bring up the network.
*
* Input Parameters:
* minor - Device minor number.
* handle - An optional opaque reference to the CDC_NCM/MBIM class object
* that may subsequently be used with cdcncm_uninitialize().
*
* Returned Value:
* Zero (OK) means that the driver was successfully registered. On any
* failure, a negated errno value is returned.
*
****************************************************************************/
#ifndef CONFIG_CDCNCM_COMPOSITE
int cdcncm_initialize(int minor, FAR void **handle)
{
return cdcnm_initialize(minor, handle, true);
}
#ifdef CONFIG_NET_CDCMBIM
int cdcmbim_initialize(int minor, FAR void **handle)
{
return cdcnm_initialize(minor, handle, false);
}
# endif /* CONFIG_NET_CDCMBIM */
#endif /* CONFIG_CDCNCM_COMPOSITE */
/****************************************************************************
* Name: cdcncm_get_composite_devdesc / cdcmbim_get_composite_devdesc
*
* Description:
* Helper function to fill in some constants into the composite
* configuration struct.
*
* Input Parameters:
* dev - Pointer to the configuration struct we should fill
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_CDCNCM_COMPOSITE
void cdcncm_get_composite_devdesc(FAR struct composite_devdesc_s *dev)
{
cdcnm_get_composite_devdesc(dev, true);
}
# ifdef CONFIG_NET_CDCMBIM
void cdcmbim_get_composite_devdesc(FAR struct composite_devdesc_s *dev)
{
cdcnm_get_composite_devdesc(dev, false);
}
# endif /* CONFIG_NET_CDCMBIM */
#endif /* CONFIG_CDCNCM_COMPOSITE */
#endif /* CONFIG_NET_CDCNCM */