nuttx/drivers/usbdev/cdcecm.c
Xiang Xiao 87cf5c58ae Correct some problems with network timed events when there are multiple network devices in the configuration.
Squashed commit of the following:

Author: Gregory Nutt <gnutt@nuttx.org>

    Ran nxstyle against many of the affected files.  But this job was too big for today.  Many of the network drivers under arch are highly non-compiant and generate many, many faults from nxstyle.  Those will have to be visited again another day.

Author: Xiang Xiao <xiaoxiang@xiaomi.com>

    This effects all network drivers as well as timing related portions of net/: devif_poll_tcp_timer shouldn't be skipped in the multiple card case.  devif_timer will be called multiple time in one period if the multiple card exist, the elapsed time calculated for the first callback is right, but the flowing callback in the same period is wrong(very short) because the global variable g_polltimer is used in the calculation.  So let's pass the delay time to devif_timer and remove g_polltimer.
2019-12-24 10:37:30 -06:00

2378 lines
65 KiB
C

/****************************************************************************
* drivers/net/cdcecm.c
*
* Copyright (C) 2018 Gregory Nutt. All rights reserved.
* Authors: Michael Jung <mijung@gmx.net>
*
* References:
* [CDCECM1.2] Universal Serial Bus - Communications Class - Subclass
* Specification for Ethernet Control Model Devices - Rev 1.2
*
* This driver derives in part from the NuttX CDC/ACM driver:
*
* Copyright (C) 2011-2013, 2016-2017 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* and also from the NuttX RNDIS driver:
*
* Copyright (C) 2011-2017 Gregory Nutt. All rights reserved.
* Authors: Sakari Kapanen <sakari.m.kapanen@gmail.com>,
* Petteri Aimonen <jpa@git.mail.kapsi.fi>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name NuttX nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdint.h>
#include <stdbool.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <debug.h>
#include <queue.h>
#include <arpa/inet.h>
#include <nuttx/arch.h>
#include <nuttx/kmalloc.h>
#include <nuttx/irq.h>
#include <nuttx/wdog.h>
#include <nuttx/wqueue.h>
#include <nuttx/semaphore.h>
#include <nuttx/net/arp.h>
#include <nuttx/net/netdev.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/cdc.h>
#include <nuttx/usb/usbdev_trace.h>
#ifdef CONFIG_NET_PKT
# include <nuttx/net/pkt.h>
#endif
#include "cdcecm.h"
#ifdef CONFIG_NET_CDCECM
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Work queue support is required. */
#if !defined(CONFIG_SCHED_WORKQUEUE)
# error Work queue support is required in this configuration (CONFIG_SCHED_WORKQUEUE)
#endif
/* 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 poll delay = 1 seconds. CLK_TCK is the number of clock ticks per second */
#define CDCECM_WDDELAY (1*CLK_TCK)
/* TX timeout = 1 minute */
#define CDCECM_TXTIMEOUT (60*CLK_TCK)
/* This is a helper pointer for accessing the contents of the Ethernet header */
#define BUF ((struct eth_hdr_s *)self->dev.d_buf)
/****************************************************************************
* Private Types
****************************************************************************/
/* The cdcecm_driver_s encapsulates all state information for a single hardware
* interface
*/
struct cdcecm_driver_s
{
/* USB CDC-ECM 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_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 */
uint8_t pktbuf[CONFIG_NET_ETH_PKTSIZE + CONFIG_NET_GUARDSIZE];
struct usbdev_req_s *rdreq; /* Single read request */
bool rxpending; /* Packet available in rdreq */
struct usbdev_req_s *wrreq; /* Single write request */
sem_t wrreq_idle; /* Is the wrreq available? */
bool txdone; /* Did a write request complete? */
/* Network device */
bool bifup; /* true:ifup false:ifdown */
WDOG_ID txpoll; /* TX poll timer */
struct work_s irqwork; /* For deferring interrupt work
* to the work queue */
struct work_s pollwork; /* For deferring poll work to
* the work queue */
/* This holds the information visible to the NuttX network */
struct net_driver_s dev; /* Interface understood by the
* network */
bool registered; /* netdev is currently registered */
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Network Device ***********************************************************/
/* Common TX logic */
static int cdcecm_transmit(FAR struct cdcecm_driver_s *priv);
static int cdcecm_txpoll(FAR struct net_driver_s *dev);
/* Interrupt handling */
static void cdcecm_reply(struct cdcecm_driver_s *priv);
static void cdcecm_receive(FAR struct cdcecm_driver_s *priv);
static void cdcecm_txdone(FAR struct cdcecm_driver_s *priv);
static void cdcecm_interrupt_work(FAR void *arg);
/* Watchdog timer expirations */
static void cdcecm_poll_work(FAR void *arg);
static void cdcecm_poll_expiry(int argc, wdparm_t arg, ...);
/* NuttX callback functions */
static int cdcecm_ifup(FAR struct net_driver_s *dev);
static int cdcecm_ifdown(FAR struct net_driver_s *dev);
static void cdcecm_txavail_work(FAR void *arg);
static int cdcecm_txavail(FAR struct net_driver_s *dev);
#if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6)
static int cdcecm_addmac(FAR struct net_driver_s *dev,
FAR const uint8_t *mac);
#ifdef CONFIG_NET_MCASTGROUP
static int cdcecm_rmmac(FAR struct net_driver_s *dev,
FAR const uint8_t *mac);
#endif
#ifdef CONFIG_NET_ICMPv6
static void cdcecm_ipv6multicast(FAR struct cdcecm_driver_s *priv);
#endif
#endif
#ifdef CONFIG_NETDEV_IOCTL
static int cdcecm_ioctl(FAR struct net_driver_s *dev, int cmd,
unsigned long arg);
#endif
/* USB Device Class Driver **************************************************/
/* USB Device Class methods */
static int cdcecm_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static void cdcecm_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
static int cdcecm_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 cdcecm_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev);
/* USB Device Class helpers */
static struct usbdev_req_s *cdcecm_allocreq(FAR struct usbdev_ep_s *ep,
uint16_t len);
static void cdcecm_freereq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcecm_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcecm_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcecm_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req);
static void cdcecm_mkepdesc(int epidx,
FAR struct usb_epdesc_s *epdesc,
FAR struct usbdev_devinfo_s *devinfo, bool hispeed);
/****************************************************************************
* Private Data
****************************************************************************/
/* USB Device Class Methods */
static const struct usbdevclass_driverops_s g_usbdevops =
{
cdcecm_bind,
cdcecm_unbind,
cdcecm_setup,
cdcecm_disconnect,
NULL,
NULL
};
#ifndef CONFIG_CDCECM_COMPOSITE
static const struct usb_devdesc_s g_devdesc =
{
USB_SIZEOF_DEVDESC,
USB_DESC_TYPE_DEVICE,
{
LSBYTE(0x0200),
MSBYTE(0x0200)
},
USB_CLASS_CDC,
CDC_SUBCLASS_ECM,
CDC_PROTO_NONE,
CONFIG_CDCECM_EP0MAXPACKET,
{
LSBYTE(CONFIG_CDCECM_VENDORID),
MSBYTE(CONFIG_CDCECM_VENDORID)
},
{
LSBYTE(CONFIG_CDCECM_PRODUCTID),
MSBYTE(CONFIG_CDCECM_PRODUCTID)
},
{
LSBYTE(CDCECM_VERSIONNO),
MSBYTE(CDCECM_VERSIONNO)
},
CDCECM_MANUFACTURERSTRID,
CDCECM_PRODUCTSTRID,
CDCECM_SERIALSTRID,
CDCECM_NCONFIGS
};
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: cdcecm_transmit
*
* Description:
* Start hardware transmission. Called either from the txdone interrupt
* handling or from watchdog based polling.
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* OK on success; a negated errno on failure
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static int cdcecm_transmit(FAR struct cdcecm_driver_s *self)
{
/* Wait until the USB device request for Ethernet frame transmissions becomes
* available.
*/
while (nxsem_wait(&self->wrreq_idle) != OK)
{
}
/* Increment statistics */
NETDEV_TXPACKETS(self->dev);
/* Send the packet: address=priv->dev.d_buf, length=priv->dev.d_len */
memcpy(self->wrreq->buf, self->dev.d_buf, self->dev.d_len);
self->wrreq->len = self->dev.d_len;
return EP_SUBMIT(self->epbulkin, self->wrreq);
}
/****************************************************************************
* Name: cdcecm_txpoll
*
* Description:
* The transmitter is available, check if the network has any outgoing
* packets ready to send. This is a callback from devif_poll().
* devif_poll() may be called:
*
* 1. When the preceding TX packet send is complete,
* 2. When the preceding TX packet send times out and the interface is reset
* 3. During normal TX polling
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* OK on success; a negated errno on failure
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static int cdcecm_txpoll(FAR struct net_driver_s *dev)
{
FAR struct cdcecm_driver_s *priv =
(FAR struct cdcecm_driver_s *)dev->d_private;
/* If the polling resulted in data that should be sent out on the network,
* the field d_len is set to a value > 0.
*/
if (priv->dev.d_len > 0)
{
/* Look up the destination MAC address and add it to the Ethernet
* header.
*/
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
if (IFF_IS_IPv4(priv->dev.d_flags))
#endif
{
arp_out(&priv->dev);
}
#endif /* CONFIG_NET_IPv4 */
#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
else
#endif
{
neighbor_out(&priv->dev);
}
#endif /* CONFIG_NET_IPv6 */
if (!devif_loopback(&priv->dev))
{
/* Send the packet */
cdcecm_transmit(priv);
/* Check if there is room in the device to hold another packet. If
* not, return a non-zero value to terminate the poll.
*/
return 1;
}
}
/* If zero is returned, the polling will continue until all connections
* have been examined.
*/
return 0;
}
/****************************************************************************
* Name: cdcecm_reply
*
* Description:
* After a packet has been received and dispatched to the network, it
* may return return with an outgoing packet. This function checks for
* that case and performs the transmission if necessary.
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static void cdcecm_reply(struct cdcecm_driver_s *priv)
{
/* If the packet dispatch resulted in data that should be sent out on the
* network, the field d_len will set to a value > 0.
*/
if (priv->dev.d_len > 0)
{
/* Update the Ethernet header with the correct MAC address */
#ifdef CONFIG_NET_IPv4
#ifdef CONFIG_NET_IPv6
/* Check for an outgoing IPv4 packet */
if (IFF_IS_IPv4(priv->dev.d_flags))
#endif
{
arp_out(&priv->dev);
}
#endif
#ifdef CONFIG_NET_IPv6
#ifdef CONFIG_NET_IPv4
/* Otherwise, it must be an outgoing IPv6 packet */
else
#endif
{
neighbor_out(&priv->dev);
}
#endif
/* And send the packet */
cdcecm_transmit(priv);
}
}
/****************************************************************************
* Name: cdcecm_receive
*
* Description:
* An interrupt was received indicating the availability of a new RX packet
*
* Input Parameters:
* priv - Reference to the driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static void cdcecm_receive(FAR struct cdcecm_driver_s *self)
{
/* Check for errors and update statistics */
/* Check if the packet is a valid size for the network buffer
* configuration.
*/
/* Copy the data data from the hardware to self->dev.d_buf. Set
* amount of data in self->dev.d_len
*/
memcpy(self->dev.d_buf, self->rdreq->buf, self->rdreq->xfrd);
self->dev.d_len = self->rdreq->xfrd;
#ifdef CONFIG_NET_PKT
/* When packet sockets are enabled, feed the frame into the packet tap */
pkt_input(&self->dev);
#endif
/* We only accept IP packets of the configured type and ARP packets */
#ifdef CONFIG_NET_IPv4
if (BUF->type == HTONS(ETHTYPE_IP))
{
ninfo("IPv4 frame\n");
NETDEV_RXIPV4(&self->dev);
/* Handle ARP on input, then dispatch IPv4 packet to the network
* layer.
*/
arp_ipin(&self->dev);
ipv4_input(&self->dev);
/* Check for a reply to the IPv4 packet */
cdcecm_reply(self);
}
else
#endif
#ifdef CONFIG_NET_IPv6
if (BUF->type == HTONS(ETHTYPE_IP6))
{
ninfo("Iv6 frame\n");
NETDEV_RXIPV6(&self->dev);
/* Dispatch IPv6 packet to the network layer */
ipv6_input(&self->dev);
/* Check for a reply to the IPv6 packet */
cdcecm_reply(self);
}
else
#endif
#ifdef CONFIG_NET_ARP
if (BUF->type == htons(ETHTYPE_ARP))
{
/* Dispatch ARP packet to the network layer */
arp_arpin(&self->dev);
NETDEV_RXARP(&self->dev);
/* If the above function invocation resulted in data that should be
* sent out on the network, the field d_len will set to a value > 0.
*/
if (self->dev.d_len > 0)
{
cdcecm_transmit(self);
}
}
else
#endif
{
NETDEV_RXDROPPED(&self->dev);
}
}
/****************************************************************************
* Name: cdcecm_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
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static void cdcecm_txdone(FAR struct cdcecm_driver_s *priv)
{
/* Check for errors and update statistics */
NETDEV_TXDONE(priv->dev);
/* In any event, poll the network for new TX data */
(void)devif_poll(&priv->dev, cdcecm_txpoll);
}
/****************************************************************************
* Name: cdcecm_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 cdcecm_interrupt_work(FAR void *arg)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)arg;
irqstate_t flags;
/* Lock the network and serialize driver operations if necessary.
* NOTE: Serialization is only required in the case where the driver work
* is performed on an LP worker thread and where more than one LP worker
* thread has been configured.
*/
net_lock();
/* Check if we received an incoming packet, if so, call cdcecm_receive() */
if (self->rxpending)
{
cdcecm_receive(self);
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 cdcecm_txdone.
* This may disable further Tx interrupts if there are no pending
* transmissions.
*/
if (self->txdone)
{
flags = enter_critical_section();
self->txdone = false;
leave_critical_section(flags);
cdcecm_txdone(self);
}
net_unlock();
}
/****************************************************************************
* Name: cdcecm_poll_work
*
* Description:
* Perform periodic polling from the worker thread
*
* Input Parameters:
* arg - The argument passed when work_queue() as called.
*
* Returned Value:
* OK on success
*
* Assumptions:
* Run on a work queue thread.
*
****************************************************************************/
static void cdcecm_poll_work(FAR void *arg)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)arg;
ninfo("rxpending: %d, txdone: %d\n", self->rxpending, self->txdone);
/* Lock the network and serialize driver operations if necessary.
* NOTE: Serialization is only required in the case where the driver work
* is performed on an LP worker thread and where more than one LP worker
* thread has been configured.
*/
net_lock();
/* Perform the poll. We are always able to accept another packet, since
* cdcecm_transmit will just wait until the USB device write request will
* become available.
*/
(void)devif_timer(&self->dev, CDCECM_WDDELAY, cdcecm_txpoll);
/* Setup the watchdog poll timer again */
(void)wd_start(self->txpoll, CDCECM_WDDELAY, cdcecm_poll_expiry, 1,
(wdparm_t)self);
net_unlock();
}
/****************************************************************************
* Name: cdcecm_poll_expiry
*
* Description:
* Periodic timer handler. Called from the timer interrupt handler.
*
* Input Parameters:
* argc - The number of available arguments
* arg - The first argument
*
* Returned Value:
* None
*
* Assumptions:
* Runs in the context of a the timer interrupt handler. Local
* interrupts are disabled by the interrupt logic.
*
****************************************************************************/
static void cdcecm_poll_expiry(int argc, wdparm_t arg, ...)
{
FAR struct cdcecm_driver_s *priv = (FAR struct cdcecm_driver_s *)arg;
/* Schedule to perform the interrupt processing on the worker thread. */
work_queue(ETHWORK, &priv->pollwork, cdcecm_poll_work, priv, 0);
}
/****************************************************************************
* Name: cdcecm_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 cdcecm_ifup(FAR struct net_driver_s *dev)
{
FAR struct cdcecm_driver_s *priv =
(FAR struct cdcecm_driver_s *)dev->d_private;
#ifdef CONFIG_NET_IPv4
ninfo("Bringing up: %d.%d.%d.%d\n",
dev->d_ipaddr & 0xff, (dev->d_ipaddr >> 8) & 0xff,
(dev->d_ipaddr >> 16) & 0xff, dev->d_ipaddr >> 24);
#endif
#ifdef CONFIG_NET_IPv6
ninfo("Bringing up: %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
dev->d_ipv6addr[0], dev->d_ipv6addr[1], dev->d_ipv6addr[2],
dev->d_ipv6addr[3], dev->d_ipv6addr[4], dev->d_ipv6addr[5],
dev->d_ipv6addr[6], dev->d_ipv6addr[7]);
#endif
/* Initialize PHYs, the Ethernet interface, and setup up Ethernet interrupts */
/* Instantiate the MAC address from priv->dev.d_mac.ether.ether_addr_octet */
#ifdef CONFIG_NET_ICMPv6
/* Set up IPv6 multicast address filtering */
cdcecm_ipv6multicast(priv);
#endif
/* Set and activate a timer process */
(void)wd_start(priv->txpoll, CDCECM_WDDELAY, cdcecm_poll_expiry, 1,
(wdparm_t)priv);
priv->bifup = true;
return OK;
}
/****************************************************************************
* Name: cdcecm_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 cdcecm_ifdown(FAR struct net_driver_s *dev)
{
FAR struct cdcecm_driver_s *priv =
(FAR struct cdcecm_driver_s *)dev->d_private;
irqstate_t flags;
/* Disable the Ethernet interrupt */
flags = enter_critical_section();
/* Cancel the TX poll timer and TX timeout timers */
wd_cancel(priv->txpoll);
/* Put the EMAC in its reset, non-operational state. This should be
* a known configuration that will guarantee the cdcecm_ifup() always
* successfully brings the interface back up.
*/
/* Mark the device "down" */
priv->bifup = false;
leave_critical_section(flags);
return OK;
}
/****************************************************************************
* Name: cdcecm_txavail_work
*
* Description:
* Perform an out-of-cycle poll on the worker thread.
*
* Input Parameters:
* arg - Reference to the NuttX driver state structure (cast to void*)
*
* Returned Value:
* None
*
* Assumptions:
* Runs on a work queue thread.
*
****************************************************************************/
static void cdcecm_txavail_work(FAR void *arg)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)arg;
/* Lock the network and serialize driver operations if necessary.
* NOTE: Serialization is only required in the case where the driver work
* is performed on an LP worker thread and where more than one LP worker
* thread has been configured.
*/
net_lock();
/* Ignore the notification if the interface is not yet up */
if (self->bifup)
{
(void)devif_poll(&self->dev, cdcecm_txpoll);
}
net_unlock();
}
/****************************************************************************
* Name: cdcecm_txavail
*
* Description:
* Driver callback invoked when new TX data is available. This is a
* stimulus perform an out-of-cycle poll and, thereby, reduce the TX
* latency.
*
* Input Parameters:
* dev - Reference to the NuttX driver state structure
*
* Returned Value:
* None
*
* Assumptions:
* The network is locked.
*
****************************************************************************/
static int cdcecm_txavail(FAR struct net_driver_s *dev)
{
FAR struct cdcecm_driver_s *priv =
(FAR struct cdcecm_driver_s *)dev->d_private;
/* Is our single work structure available? It may not be if there are
* pending interrupt actions and we will have to ignore the Tx
* availability action.
*/
if (work_available(&priv->pollwork))
{
/* Schedule to serialize the poll on the worker thread. */
work_queue(ETHWORK, &priv->pollwork, cdcecm_txavail_work, priv, 0);
}
return OK;
}
/****************************************************************************
* Name: cdcecm_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 cdcecm_addmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
{
FAR struct cdcecm_driver_s *priv =
(FAR struct cdcecm_driver_s *)dev->d_private;
/* Add the MAC address to the hardware multicast routing table */
UNUSED(priv); /* Not yet implemented */
return OK;
}
#endif
/****************************************************************************
* Name: cdcecm_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 cdcecm_rmmac(FAR struct net_driver_s *dev, FAR const uint8_t *mac)
{
FAR struct cdcecm_driver_s *priv =
(FAR struct cdcecm_driver_s *)dev->d_private;
/* Add the MAC address to the hardware multicast routing table */
UNUSED(priv); /* Not yet implemented */
return OK;
}
#endif
/****************************************************************************
* Name: cdcecm_ipv6multicast
*
* Description:
* Configure the IPv6 multicast MAC address.
*
* Input Parameters:
* priv - A reference to the private driver state structure
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
#ifdef CONFIG_NET_ICMPv6
static void cdcecm_ipv6multicast(FAR struct cdcecm_driver_s *priv)
{
FAR struct net_driver_s *dev;
uint16_t tmp16;
uint8_t mac[6];
/* For ICMPv6, we need to add the IPv6 multicast address
*
* For IPv6 multicast addresses, the Ethernet MAC is derived by
* the four low-order octets OR'ed with the MAC 33:33:00:00:00:00,
* so for example the IPv6 address FF02:DEAD:BEEF::1:3 would map
* to the Ethernet MAC address 33:33:00:01:00:03.
*
* NOTES: This appears correct for the ICMPv6 Router Solicitation
* Message, but the ICMPv6 Neighbor Solicitation message seems to
* use 33:33:ff:01:00:03.
*/
mac[0] = 0x33;
mac[1] = 0x33;
dev = &priv->dev;
tmp16 = dev->d_ipv6addr[6];
mac[2] = 0xff;
mac[3] = tmp16 >> 8;
tmp16 = dev->d_ipv6addr[7];
mac[4] = tmp16 & 0xff;
mac[5] = tmp16 >> 8;
ninfo("IPv6 Multicast: %02x:%02x:%02x:%02x:%02x:%02x\n",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
(void)cdcecm_addmac(dev, mac);
#ifdef CONFIG_NET_ICMPv6_AUTOCONF
/* Add the IPv6 all link-local nodes Ethernet address. This is the
* address that we expect to receive ICMPv6 Router Advertisement
* packets.
*/
(void)cdcecm_addmac(dev, g_ipv6_ethallnodes.ether_addr_octet);
#endif /* CONFIG_NET_ICMPv6_AUTOCONF */
#ifdef CONFIG_NET_ICMPv6_ROUTER
/* Add the IPv6 all link-local routers Ethernet address. This is the
* address that we expect to receive ICMPv6 Router Solicitation
* packets.
*/
(void)cdcecm_addmac(dev, g_ipv6_ethallrouters.ether_addr_octet);
#endif /* CONFIG_NET_ICMPv6_ROUTER */
}
#endif /* CONFIG_NET_ICMPv6 */
/****************************************************************************
* Name: cdcecm_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 cdcecm_ioctl(FAR struct net_driver_s *dev, int cmd,
unsigned long arg)
{
/* Decode and dispatch the driver-specific IOCTL command */
switch (cmd)
{
/* Add cases here to support the IOCTL commands */
default:
nerr("ERROR: Unrecognized IOCTL command: %d\n", command);
return -ENOTTY; /* Special return value for this case */
}
return OK;
}
#endif
/****************************************************************************
* USB Device Class Helpers
****************************************************************************/
/****************************************************************************
* Name: cdcecm_ep0incomplete
*
* Description:
* Handle completion of EP0 control operations
*
****************************************************************************/
static void cdcecm_ep0incomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
if (req->result || req->xfrd != req->len)
{
uerr("result: %hd, xfrd: %hu\n", req->result, req->xfrd);
}
}
/****************************************************************************
* Name: cdcecm_rdcomplete
*
* Description:
* Handle completion of read request on the bulk OUT endpoint.
*
****************************************************************************/
static void cdcecm_rdcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)ep->priv;
uinfo("buf: %p, flags 0x%hhx, len %hu, xfrd %hu, 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, cdcecm_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: cdcecm_wrcomplete
*
* Description:
* Handle completion of write request. This function probably executes
* in the context of an interrupt handler.
*
****************************************************************************/
static void cdcecm_wrcomplete(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)ep->priv;
int rc;
uinfo("buf: %p, flags 0x%hhx, len %hu, xfrd %hu, 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, cdcecm_interrupt_work, self, 0);
}
/****************************************************************************
* Name: cdcecm_allocreq
*
* Description:
* Allocate a request instance along with its buffer
*
****************************************************************************/
static struct usbdev_req_s *cdcecm_allocreq(FAR struct usbdev_ep_s *ep,
uint16_t len)
{
FAR struct usbdev_req_s *req;
req = EP_ALLOCREQ(ep);
if (req != NULL)
{
req->len = len;
req->buf = EP_ALLOCBUFFER(ep, len);
req->flags = USBDEV_REQFLAGS_NULLPKT;
if (req->buf == NULL)
{
EP_FREEREQ(ep, req);
req = NULL;
}
}
return req;
}
/****************************************************************************
* Name: cdcecm_freereq
*
* Description:
* Free a request instance along with its buffer
*
****************************************************************************/
static void cdcecm_freereq(FAR struct usbdev_ep_s *ep,
FAR struct usbdev_req_s *req)
{
if (ep != NULL && req != NULL)
{
if (req->buf != NULL)
{
EP_FREEBUFFER(ep, req->buf);
}
EP_FREEREQ(ep, req);
}
}
/******************************************************************************
* Name: cdcecm_resetconfig
*
* Description:
* Mark the device as not configured and disable all endpoints.
*
******************************************************************************/
static void cdcecm_resetconfig(FAR struct cdcecm_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 */
self->dev.d_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);
}
}
/******************************************************************************
* Name: cdcecm_setconfig
*
* Set the device configuration by allocating and configuring endpoints and
* by allocating and queue read and write requests.
*
****************************************************************************/
static int cdcecm_setconfig(FAR struct cdcecm_driver_s *self, uint8_t config)
{
struct usb_epdesc_s epdesc;
int ret = OK;
if (config == self->config)
{
return OK;
}
cdcecm_resetconfig(self);
if (config == CDCECM_CONFIGID_NONE)
{
return OK;
}
if (config != CDCECM_CONFIGID)
{
return -EINVAL;
}
cdcecm_mkepdesc(CDCECM_EP_INTIN_IDX, &epdesc, &self->devinfo, false);
ret = EP_CONFIGURE(self->epint, &epdesc, false);
if (ret < 0)
{
goto error;
}
self->epint->priv = self;
cdcecm_mkepdesc(CDCECM_EP_BULKIN_IDX, &epdesc, &self->devinfo, false);
ret = EP_CONFIGURE(self->epbulkin, &epdesc, false);
if (ret < 0)
{
goto error;
}
self->epbulkin->priv = self;
cdcecm_mkepdesc(CDCECM_EP_BULKOUT_IDX, &epdesc, &self->devinfo, false);
ret = EP_CONFIGURE(self->epbulkout, &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 = cdcecm_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.d_mac.ether.ether_addr_octet,
"\x00\xe0\xde\xad\xbe\xef", IFHWADDRLEN);
/* Report link up to networking layer */
if (self->dev.d_ifup(&self->dev) == OK)
{
self->dev.d_flags |= IFF_UP;
}
return OK;
error:
cdcecm_resetconfig(self);
return ret;
}
/******************************************************************************
* Name: cdcecm_setinterface
*
****************************************************************************/
static int cdcecm_setinterface(FAR struct cdcecm_driver_s *self,
uint16_t interface, uint16_t altsetting)
{
uinfo("interface: %hu, altsetting: %hu\n", interface, altsetting);
return OK;
}
/****************************************************************************
* Name: cdcecm_mkstrdesc
*
* Description:
* Construct a string descriptor
*
****************************************************************************/
static int cdcecm_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
const char *str;
int len;
int ndata;
int i;
switch (id)
{
#ifndef CONFIG_CDCECM_COMPOSITE
case 0:
{
/* Descriptor 0 is the language id */
strdesc->len = 4;
strdesc->type = USB_DESC_TYPE_STRING;
strdesc->data[0] = LSBYTE(CDCECM_STR_LANGUAGE);
strdesc->data[1] = MSBYTE(CDCECM_STR_LANGUAGE);
return 4;
}
case CDCECM_MANUFACTURERSTRID:
str = CONFIG_CDCECM_VENDORSTR;
break;
case CDCECM_PRODUCTSTRID:
str = CONFIG_CDCECM_PRODUCTSTR;
break;
case CDCECM_SERIALSTRID:
str = "0";
break;
case CDCECM_CONFIGSTRID:
str = "Default";
break;
#endif
case CDCECM_MACSTRID:
str = "020000112233";
break;
default:
uwarn("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)
{
strdesc->data[ndata] = str[i];
strdesc->data[ndata + 1] = 0;
}
strdesc->len = ndata + 2;
strdesc->type = USB_DESC_TYPE_STRING;
return strdesc->len;
}
/****************************************************************************
* Name: cdcecm_mkepdesc
*
* Description:
* Construct the endpoint descriptor
*
****************************************************************************/
static void cdcecm_mkepdesc(int epidx,
FAR struct usb_epdesc_s *epdesc,
FAR struct usbdev_devinfo_s *devinfo,
bool hispeed)
{
uint16_t intin_mxpktsz = CONFIG_CDCECM_EPINTIN_FSSIZE;
uint16_t bulkout_mxpktsz = CONFIG_CDCECM_EPBULKOUT_FSSIZE;
uint16_t bulkin_mxpktsz = CONFIG_CDCECM_EPBULKIN_FSSIZE;
#ifdef CONFIG_USBDEV_DUALSPEED
if (hispeed)
{
intin_mxpktsz = CONFIG_CDCECM_EPINTIN_HSSIZE;
bulkout_mxpktsz = CONFIG_CDCECM_EPBULKOUT_HSSIZE;
bulkin_mxpktsz = CONFIG_CDCECM_EPBULKIN_HSSIZE;
}
#else
UNUSED(hispeed);
#endif
epdesc->len = USB_SIZEOF_EPDESC; /* Descriptor length */
epdesc->type = USB_DESC_TYPE_ENDPOINT; /* Descriptor type */
switch (epidx)
{
case CDCECM_EP_INTIN_IDX: /* Interrupt IN endpoint */
{
epdesc->addr = USB_DIR_IN |
devinfo->epno[CDCECM_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 CDCECM_EP_BULKIN_IDX:
{
epdesc->addr = USB_DIR_IN |
devinfo->epno[CDCECM_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 CDCECM_EP_BULKOUT_IDX:
{
epdesc->addr = USB_DIR_OUT |
devinfo->epno[CDCECM_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:
DEBUGASSERT(false);
}
}
/****************************************************************************
* Name: cdcecm_mkcfgdesc
*
* Description:
* Construct the config descriptor
*
****************************************************************************/
static int16_t cdcecm_mkcfgdesc(FAR uint8_t *desc,
FAR struct usbdev_devinfo_s *devinfo)
{
FAR struct usb_cfgdesc_s *cfgdesc = NULL;
int16_t len = 0;
#ifndef CONFIG_CDCECM_COMPOSITE
if (desc)
{
cfgdesc = (FAR struct usb_cfgdesc_s *)desc;
cfgdesc->len = USB_SIZEOF_CFGDESC;
cfgdesc->type = USB_DESC_TYPE_CONFIG;
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 = (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 = CDC_SUBCLASS_ECM; /* 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 = (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 = CDC_SUBCLASS_ECM;
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 (desc)
{
FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)desc;
cdcecm_mkepdesc(CDCECM_EP_INTIN_IDX, epdesc, devinfo, false);
desc += USB_SIZEOF_EPDESC;
}
len += USB_SIZEOF_EPDESC;
/* Data Class Interface */
if (desc)
{
FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)desc;
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 = CDC_SUBCLASS_ECM;
ifdesc->protocol = CDC_PROTO_NONE;
ifdesc->iif = 0;
desc += USB_SIZEOF_IFDESC;
}
len += USB_SIZEOF_IFDESC;
if (desc)
{
FAR struct usb_ifdesc_s *ifdesc = (FAR struct usb_ifdesc_s *)desc;
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 = CDC_SUBCLASS_ECM;
ifdesc->protocol = CDC_PROTO_NONE;
ifdesc->iif = 0;
desc += USB_SIZEOF_IFDESC;
}
len += USB_SIZEOF_IFDESC;
if (desc)
{
FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)desc;
cdcecm_mkepdesc(CDCECM_EP_BULKIN_IDX, epdesc, devinfo, false);
desc += USB_SIZEOF_EPDESC;
}
len += USB_SIZEOF_EPDESC;
if (desc)
{
FAR struct usb_epdesc_s *epdesc = (FAR struct usb_epdesc_s *)desc;
cdcecm_mkepdesc(CDCECM_EP_BULKOUT_IDX, epdesc, devinfo, false);
desc += USB_SIZEOF_EPDESC;
}
len += USB_SIZEOF_EPDESC;
if (cfgdesc)
{
cfgdesc->totallen[0] = LSBYTE(len);
cfgdesc->totallen[1] = MSBYTE(len);
}
DEBUGASSERT(len <= CDCECM_MXDESCLEN);
return len;
}
/*******************************************************************************
* Name: cdcecm_getdescriptor
*
* Description:
* Copy the USB CDC-ECM 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 cdcecm_getdescriptor(FAR struct cdcecm_driver_s *self, uint8_t type,
uint8_t index, FAR void *desc)
{
uinfo("type: 0x%02hhx, index: 0x%02hhx\n", type, index);
switch (type)
{
#ifndef CONFIG_CDCECM_COMPOSITE
case USB_DESC_TYPE_DEVICE:
{
memcpy(desc, &g_devdesc, sizeof(g_devdesc));
return (int)sizeof(g_devdesc);
}
break;
#endif
case USB_DESC_TYPE_CONFIG:
{
return cdcecm_mkcfgdesc((FAR uint8_t *)desc, &self->devinfo);
}
break;
case USB_DESC_TYPE_STRING:
{
return cdcecm_mkstrdesc(index, (FAR struct usb_strdesc_s *)desc);
}
break;
default:
uwarn("Unsupported descriptor type: 0x%02hhx\n", type);
break;
}
return -ENOTSUP;
}
/****************************************************************************
* USB Device Class Methods
****************************************************************************/
/****************************************************************************
* Name: cdcecm_bind
*
* Description:
* Invoked when the driver is bound to an USB device
*
****************************************************************************/
static int cdcecm_bind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)driver;
int ret = OK;
uinfo("\n");
dev->ep0->priv = self;
/* Preallocate control request */
self->ctrlreq = cdcecm_allocreq(dev->ep0, CDCECM_MXDESCLEN);
if (self->ctrlreq == NULL)
{
ret = -ENOMEM;
goto error;
}
self->ctrlreq->callback = cdcecm_ep0incomplete;
self->epint = DEV_ALLOCEP(dev,
USB_DIR_IN |
self->devinfo.epno[CDCECM_EP_INTIN_IDX],
true, USB_EP_ATTR_XFER_INT);
self->epbulkin = DEV_ALLOCEP(dev,
USB_DIR_IN |
self->devinfo.epno[CDCECM_EP_BULKIN_IDX],
true, USB_EP_ATTR_XFER_BULK);
self->epbulkout = DEV_ALLOCEP(dev,
USB_DIR_OUT |
self->devinfo.epno[CDCECM_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 read requests. The buffer size is one full packet. */
self->rdreq = cdcecm_allocreq(self->epbulkout,
CONFIG_NET_ETH_PKTSIZE + CONFIG_NET_GUARDSIZE);
if (self->rdreq == NULL)
{
uerr("Out of memory\n");
ret = -ENOMEM;
goto error;
}
self->rdreq->callback = cdcecm_rdcomplete;
/* Pre-allocate a single write request. Buffer size is one full packet. */
self->wrreq = cdcecm_allocreq(self->epbulkin,
CONFIG_NET_ETH_PKTSIZE + CONFIG_NET_GUARDSIZE);
if (self->wrreq == NULL)
{
uerr("Out of memory\n");
ret = -ENOMEM;
goto error;
}
self->wrreq->callback = cdcecm_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;
self->dev.d_len = 0;
#ifndef CONFIG_CDCECM_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("cdcecm_bind failed! ret: %d\n", ret);
cdcecm_unbind(driver, dev);
return ret;
}
static void cdcecm_unbind(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_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)
*/
cdcecm_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)
{
cdcecm_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)
{
cdcecm_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)
{
cdcecm_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 buffer */
self->dev.d_len = 0;
}
static int cdcecm_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 cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)driver;
uint16_t value = GETUINT16(ctrl->value);
uint16_t index = GETUINT16(ctrl->index);
uint16_t len = GETUINT16(ctrl->len);
int ret = -EOPNOTSUPP;
uinfo("\n");
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];
ret = cdcecm_getdescriptor(self, desctype, descindex,
self->ctrlreq->buf);
}
break;
case USB_REQ_SETCONFIGURATION:
ret = cdcecm_setconfig(self, value);
break;
case USB_REQ_SETINTERFACE:
ret = cdcecm_setinterface(self, index, value);
break;
default:
uwarn("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 CDCECM 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;
default:
uwarn("Unsupported class req: 0x%02hhx\n", ctrl->req);
break;
}
}
else
{
uwarn("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;
cdcecm_ep0incomplete(dev->ep0, ctrlreq);
}
}
return ret;
}
static void cdcecm_disconnect(FAR struct usbdevclass_driver_s *driver,
FAR struct usbdev_s *dev)
{
uinfo("\n");
}
/****************************************************************************
* Name: cdcecm_classobject
*
* Description:
* Register USB CDC/ECM and return the class object.
*
* Returned Value:
* A pointer to the allocated class object (NULL on failure).
*
****************************************************************************/
static int cdcecm_classobject(int minor, FAR struct usbdev_devinfo_s *devinfo,
FAR struct usbdevclass_driver_s **classdev)
{
FAR struct cdcecm_driver_s *self;
int ret;
/* Initialize the driver structure */
self = kmm_zalloc(sizeof(struct cdcecm_driver_s));
if (!self)
{
nerr("Out of memory!\n");
return -ENOMEM;
}
/* Network device initialization */
self->dev.d_buf = self->pktbuf;
self->dev.d_ifup = cdcecm_ifup; /* I/F up (new IP address) callback */
self->dev.d_ifdown = cdcecm_ifdown; /* I/F down callback */
self->dev.d_txavail = cdcecm_txavail; /* New TX data callback */
#ifdef CONFIG_NET_MCASTGROUP
self->dev.d_addmac = cdcecm_addmac; /* Add multicast MAC address */
self->dev.d_rmmac = cdcecm_rmmac; /* Remove multicast MAC address */
#endif
#ifdef CONFIG_NETDEV_IOCTL
self->dev.d_ioctl = cdcecm_ioctl; /* Handle network IOCTL commands */
#endif
self->dev.d_private = (FAR void *)self; /* Used to recover private state from dev */
/* Create a watchdog for timing polling for and timing of transmissions */
self->txpoll = wd_create(); /* Create periodic poll timer */
DEBUGASSERT(self->txpoll != NULL);
/* USB device initialization */
#ifdef 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 cdcecm_ifdown().
*/
cdcecm_ifdown(&self->dev);
/* Read the MAC address from the hardware into
* priv->dev.d_mac.ether.ether_addr_octet
* Applies only if the Ethernet MAC has its own internal address.
*/
memcpy(self->dev.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_register(&self->dev, NET_LL_ETHERNET);
if (ret < 0)
{
nerr("netdev_register failed. ret: %d\n", ret);
return ret;
}
self->registered = true;
*classdev = (FAR struct usbdevclass_driver_s *)self;
return ret;
}
/****************************************************************************
* Name: cdcecm_uninitialize
*
* Description:
* Un-initialize the USB CDC/ECM class driver. This function is used
* internally by the USB composite driver to uninitialize the CDC/ECM
* driver. This same interface is available (with an untyped input
* parameter) when the CDC/ECM driver is used standalone.
*
* Input Parameters:
* There is one parameter, it differs in typing depending upon whether the
* CDC/ECM driver is an internal part of a composite device, or a standalone
* USB driver:
*
* classdev - The class object returned by cdcacm_classobject()
* handle - The opaque handle representing the class object returned by
* a previous call to cdcacm_initialize().
*
* Returned Value:
* None
*
****************************************************************************/
#ifdef CONFIG_CDCECM_COMPOSITE
void cdcecm_uninitialize(FAR struct usbdevclass_driver_s *classdev)
#else
void cdcecm_uninitialize(FAR void *handle)
#endif
{
#ifdef CONFIG_CDCECM_COMPOSITE
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)classdev;
#else
FAR struct cdcecm_driver_s *self = (FAR struct cdcecm_driver_s *)handle;
#endif
int ret;
#ifdef CONFIG_CDCECM_COMPOSITE
/* Check for pass 2 uninitialization. We did most of the work on the
* first pass uninitialization.
*/
if (!self->registered)
{
/* In this second and final pass, all that remains to be done is to
* free the memory resources.
*/
kmm_free(self);
return;
}
#endif
/* Un-register the CDC/ECM netdev device */
ret = netdev_unregister(&self->dev);
if (ret < 0)
{
nerr("ERROR: netdev_unregister failed. ret: %d\n", ret);
}
/* For the case of the composite driver, there is a two pass
* uninitialization sequence. We cannot yet free the driver structure.
* We will do that on the second pass. We mark the fact that we have
* already uninitialized by setting the registered flag to false.
* If/when we are called again, then we will free the memory resources.
*/
self->registered = false; /* Successfully unregistered netdev */
/* Unregister the driver (unless we are a part of a composite device). The
* device unregister logic will (1) return all of the requests to us then
* (2) call the unbind method.
*
* The same thing will happen in the composite case except that: (1) the
* composite driver will call usbdev_unregister() which will (2) return the
* requests for all members of the composite, and (3) call the unbind
* method in the composite device which will (4) call the unbind method
* for this device.
*/
#ifndef CONFIG_CDCECM_COMPOSITE
usbdev_unregister(&self->usbdev);
/* And free the driver structure */
kmm_free(self);
#endif
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: cdcecm_initialize
*
* Description:
* Register CDC/ECM 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/ECM class object that
* may subsequently be used with cdcecm_uninitialize().
*
* Returned Value:
* Zero (OK) means that the driver was successfully registered. On any
* failure, a negated errno value is returned.
*
****************************************************************************/
#ifndef CONFIG_CDCECM_COMPOSITE
int cdcecm_initialize(int minor, FAR void **handle)
{
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[CDCECM_EP_INTIN_IDX] = CONFIG_CDCECM_EPINTIN;
devinfo.epno[CDCECM_EP_BULKIN_IDX] = CONFIG_CDCECM_EPBULKIN;
devinfo.epno[CDCECM_EP_BULKOUT_IDX] = CONFIG_CDCECM_EPBULKOUT;
ret = cdcecm_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 = (FAR void *)drvr;
}
return ret;
}
#endif
/****************************************************************************
* Name: cdcecm_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_CDCECM_COMPOSITE
void cdcecm_get_composite_devdesc(struct composite_devdesc_s *dev)
{
memset(dev, 0, sizeof(struct composite_devdesc_s));
/* The callback functions for the CDC/ECM class.
*
* classobject() and uninitialize() must be provided by board-specific
* logic
*/
dev->mkconfdesc = cdcecm_mkcfgdesc;
dev->mkstrdesc = cdcecm_mkstrdesc;
dev->classobject = cdcecm_classobject;
dev->uninitialize = cdcecm_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 the config descriptor */
#ifdef CONFIG_USBDEV_DUALSPEED
dev->cfgdescsize = cdcecm_mkcfgdesc(NULL, NULL, USB_SPEED_UNKNOWN, 0);
#else
dev->cfgdescsize = cdcecm_mkcfgdesc(NULL, NULL);
#endif
/* 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_CDCECM_COMPOSITE */
#endif /* CONFIG_NET_CDCECM */