nuttx/drivers/usbdev/cdcncm.c
yangsong8 5a39e83c05 usbdev: extend the usb req len to size_t
Some USB controllers can receive or send multiple data packets then
generate one interrupt. This mechanism can reduce the number of data
copies. Extend req buf to accommodate this.

Signed-off-by: yangsong8 <yangsong8@xiaomi.com>
2024-08-30 01:32:02 +08:00

3266 lines
91 KiB
C

/****************************************************************************
* 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 */