/****************************************************************************
 * drivers/usbdev/rndis.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:
 *   [MS-RNDIS]:
 *     Remote Network Driver Interface Specification (RNDIS) Protocol
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <assert.h>
#include <debug.h>
#include <errno.h>
#include <inttypes.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

#include <sys/param.h>

#include <nuttx/queue.h>
#include <nuttx/net/net.h>
#include <nuttx/net/netdev.h>
#include <nuttx/kmalloc.h>
#include <nuttx/arch.h>
#include <nuttx/usb/usb.h>
#include <nuttx/usb/cdc.h>
#include <nuttx/usb/usbdev.h>
#include <nuttx/usb/usbdev_trace.h>
#include <nuttx/usb/rndis.h>
#include <nuttx/wqueue.h>

#ifdef CONFIG_BOARD_USBDEV_SERIALSTR
#include <nuttx/board.h>
#endif

#include "rndis_std.h"

#ifdef CONFIG_RNDIS_COMPOSITE
#  include <nuttx/usb/composite.h>
#endif

/****************************************************************************
 * Pre-processor definitions
 ****************************************************************************/

#define RNDIS_MKEPINTIN(desc)     (USB_DIR_IN | (desc)->epno[RNDIS_EP_INTIN_IDX])
#define RNDIS_EPINTIN_ATTR        (USB_EP_ATTR_XFER_INT)

#define RNDIS_MKEPBULKIN(desc)    (USB_DIR_IN | (desc)->epno[RNDIS_EP_BULKIN_IDX])
#define RNDIS_EPOUTBULK_ATTR      (USB_EP_ATTR_XFER_BULK)

#define RNDIS_MKEPBULKOUT(desc)   ((desc)->epno[RNDIS_EP_BULKOUT_IDX])
#define RNDIS_EPINBULK_ATTR       (USB_EP_ATTR_XFER_BULK)

#define CONFIG_RNDIS_EP0MAXPACKET 64

#ifndef CONFIG_RNDIS_NWRREQS
#  define CONFIG_RNDIS_NWRREQS  (2)
#endif

#define RNDIS_PACKET_HDR_SIZE   (sizeof(struct rndis_packet_msg))
#define CONFIG_RNDIS_BULKIN_REQLEN \
  (CONFIG_NET_ETH_PKTSIZE + CONFIG_NET_GUARDSIZE + RNDIS_PACKET_HDR_SIZE)
#define CONFIG_RNDIS_BULKOUT_REQLEN CONFIG_RNDIS_BULKIN_REQLEN

static_assert(CONFIG_NET_LL_GUARDSIZE >= RNDIS_PACKET_HDR_SIZE + ETH_HDRLEN,
             "CONFIG_NET_LL_GUARDSIZE cannot be less than ETH_HDRLEN"
             " + RNDIS_PACKET_HDR_SIZE");

static_assert((CONFIG_NET_LL_GUARDSIZE % 4) == 2,
             "CONFIG_NET_LL_GUARDSIZE - ETH_HDRLEN "
             "should be aligned to 4 bytes");

#define RNDIS_NCONFIGS          (1)
#define RNDIS_CONFIGID          (1)
#define RNDIS_CONFIGIDNONE      (0)
#define RNDIS_NINTERFACES       (2)
#define RNDIS_NSTRIDS           (0)

#ifndef CONFIG_RNDIS_COMPOSITE
#  define RNDIS_EPINTIN_ADDR    USB_EPIN(CONFIG_RNDIS_EPINTIN)
#  define RNDIS_EPBULKIN_ADDR   USB_EPIN(CONFIG_RNDIS_EPBULKIN)
#  define RNDIS_EPBULKOUT_ADDR  USB_EPOUT(CONFIG_RNDIS_EPBULKOUT)
#endif
#define RNDIS_NUM_EPS           (3)

#define RNDIS_MANUFACTURERSTRID (1)
#define RNDIS_PRODUCTSTRID      (2)
#define RNDIS_SERIALSTRID       (3)

#define RNDIS_STR_LANGUAGE      (0x0409) /* en-us */

#define RNDIS_MXDESCLEN         (128)
#define RNDIS_MAXSTRLEN         (RNDIS_MXDESCLEN-2)
#define RNDIS_CTRLREQ_LEN       (256)
#define RNDIS_RESP_QUEUE_WORDS  (64)

#define RNDIS_BUFFER_SIZE       CONFIG_NET_ETH_PKTSIZE
#define RNDIS_BUFFER_COUNT      4

/* Work queue to use for network operations. LPWORK should be used here */

#define ETHWORK                 LPWORK

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* Container to support a list of requests */

struct rndis_req_s
{
  FAR struct rndis_req_s  *flink;  /* Implements a singly linked list */
  FAR struct usbdev_req_s *req;    /* The contained request */
  FAR struct iob_s        *iob;    /* IOB offload */
  FAR uint8_t             *buf;    /* Use malloc buffer when config IOB_LEN < CONFIG_RNDIS_BULKIN_REQLEN */
};

/* This structure describes the internal state of the driver */

struct rndis_dev_s
{
  struct net_driver_s      netdev;       /* Network driver structure */
  struct usbdev_devinfo_s  devinfo;

  FAR struct usbdev_s     *usbdev;       /* usbdev driver pointer */
  FAR struct usbdev_ep_s  *epintin;      /* Interrupt IN endpoint structure */
  FAR struct usbdev_ep_s  *epbulkin;     /* Bulk IN endpoint structure */
  FAR struct usbdev_ep_s  *epbulkout;    /* Bulk OUT endpoint structure */
  FAR struct usbdev_req_s *ctrlreq;      /* Pointer to preallocated control request */
  FAR struct usbdev_req_s *epintin_req;  /* Pointer to preallocated interrupt in endpoint request */
  FAR struct usbdev_req_s *rdreq;        /* Pointer to Preallocated control endpoint read request */
  struct sq_queue_s reqlist;             /* List of free write request containers */

  /* Preallocated USB request buffers */

  struct rndis_req_s wrreqs[CONFIG_RNDIS_NWRREQS];

  struct work_s rxwork;                  /* Worker for dispatching RX packets */
  struct work_s pollwork;                /* TX poll worker */

  uint8_t config;                        /* USB Configuration number */
  FAR struct rndis_req_s *net_req;       /* Pointer to request whose buffer is assigned to network */
  FAR struct rndis_req_s *rx_req;        /* Pointer request container that holds RX buffer */
  size_t current_rx_received;            /* Number of bytes of current RX datagram received over USB */
  size_t current_rx_datagram_size;       /* Total number of bytes of the current RX datagram */
  size_t current_rx_datagram_offset;     /* Offset of current RX datagram */
  size_t current_rx_msglen;              /* Length of the entire message to be received */
  bool rdreq_submitted;                  /* Indicates if the read request is submitted */
  bool rx_blocked;                       /* Indicates if we can receive packets on bulk in endpoint */
  bool connected;                        /* Connection status indicator */
  uint32_t rndis_packet_filter;          /* RNDIS packet filter value */
  uint32_t rndis_host_tx_count;          /* TX packet counter */
  uint32_t rndis_host_rx_count;          /* RX packet counter */
  uint8_t host_mac_address[6];           /* Host side MAC address */

  size_t response_queue_words;           /* Count of words waiting in response_queue. */
  uint32_t response_queue[RNDIS_RESP_QUEUE_WORDS];
};

/* The internal version of the class driver */

struct rndis_driver_s
{
  struct usbdevclass_driver_s drvr;
  FAR struct rndis_dev_s      *dev;
};

/* This is what is allocated */

struct rndis_alloc_s
{
  struct rndis_dev_s    dev;
  struct rndis_driver_s drvr;
};

/* RNDIS USB configuration descriptor */

struct rndis_cfgdesc_s
{
#ifndef CONFIG_RNDIS_COMPOSITE
  struct usb_cfgdesc_s cfgdesc;        /* Configuration descriptor */
#elif defined(CONFIG_COMPOSITE_IAD)
  struct usb_iaddesc_s assoc_desc;     /* Interface association descriptor */
#endif
  struct usb_ifdesc_s  comm_ifdesc;    /* Communication interface descriptor */
  struct usb_epdesc_s  epintindesc;    /* Interrupt endpoint descriptor */
  struct usb_ifdesc_s  data_ifdesc;    /* Data interface descriptor */
  struct usb_epdesc_s  epbulkindesc;   /* Bulk in interface descriptor */
  struct usb_epdesc_s  epbulkoutdesc;  /* Bulk out interface descriptor */
};

/* RNDIS object ID - value pair structure */

struct rndis_oid_value_s
{
  uint32_t objid;
  uint32_t length;
  uint32_t value;
  FAR const void *data;                /* Data pointer overrides value if non-NULL. */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Netdev driver callbacks */

static int rndis_ifup(FAR struct net_driver_s *dev);
static int rndis_ifdown(FAR struct net_driver_s *dev);
static int rndis_txavail(FAR struct net_driver_s *dev);
static int rndis_transmit(FAR struct rndis_dev_s *priv);
static int rndis_txpoll(FAR struct net_driver_s *dev);

/* usbclass callbacks */

static int  usbclass_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 int  usbclass_bind(FAR struct usbdevclass_driver_s *driver,
                          FAR struct usbdev_s *dev);
static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver,
                            FAR struct usbdev_s *dev);
static void usbclass_disconnect(FAR struct usbdevclass_driver_s *driver,
                                FAR struct usbdev_s *dev);
static int  usbclass_setconfig(FAR struct rndis_dev_s *priv, uint8_t config);
static void usbclass_resetconfig(FAR struct rndis_dev_s *priv);

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* USB driver operations */

const static struct usbdevclass_driverops_s g_driverops =
{
  &usbclass_bind,
  &usbclass_unbind,
  &usbclass_setup,
  &usbclass_disconnect,
  NULL,
  NULL
};

#ifndef CONFIG_RNDIS_COMPOSITE
static const struct usb_devdesc_s g_devdesc =
{
  USB_SIZEOF_DEVDESC,                           /* len */
  USB_DESC_TYPE_DEVICE,                         /* type */
  {LSBYTE(0x0200), MSBYTE(0x0200)},             /* usb */
  0,                                            /* classid */
  0,                                            /* subclass */
  0,                                            /* protocol */
  CONFIG_RNDIS_EP0MAXPACKET,                    /* maxpacketsize */
  { LSBYTE(CONFIG_RNDIS_VENDORID),              /* vendor */
    MSBYTE(CONFIG_RNDIS_VENDORID) },
  { LSBYTE(CONFIG_RNDIS_PRODUCTID),             /* product */
    MSBYTE(CONFIG_RNDIS_PRODUCTID) },
  { LSBYTE(CONFIG_RNDIS_VERSIONNO),             /* device */
    MSBYTE(CONFIG_RNDIS_VERSIONNO) },
  RNDIS_MANUFACTURERSTRID,                      /* imfgr */
  RNDIS_PRODUCTSTRID,                           /* iproduct */
  RNDIS_SERIALSTRID,                            /* serno */
  RNDIS_NCONFIGS                                /* nconfigs */
};
#endif

#ifndef CONFIG_RNDIS_COMPOSITE

  /* Configuration descriptor */

static const struct usb_cfgdesc_s g_rndis_cfgdesc =
{
  .len          = USB_SIZEOF_CFGDESC,
  .type         = USB_DESC_TYPE_CONFIG,
  .totallen     =
  {
    0, 0
  },
  .ninterfaces  = RNDIS_NINTERFACES,
  .cfgvalue     = RNDIS_CONFIGID,
  .icfg         = 0,
  .attr         = USB_CONFIG_ATTR_ONE | USB_CONFIG_ATTR_SELFPOWER,
  .mxpower      = (CONFIG_USBDEV_MAXPOWER + 1) / 2
};
#elif defined(CONFIG_COMPOSITE_IAD)

  /* Interface association descriptor */

static const struct usb_iaddesc_s g_rndis_assoc_desc =
{
  .len          = USB_SIZEOF_IADDESC,
  .type         = USB_DESC_TYPE_INTERFACEASSOCIATION,
  .firstif      = 0,
  .nifs         = RNDIS_NINTERFACES,
  .classid      = 0xef,
  .subclass     = 0x04,
  .protocol     = 0x01,
  .ifunction    = 0
};
#endif

  /* Communication interface descriptor */

static const struct usb_ifdesc_s g_rndis_comm_ifdesc =
{
  .len          = USB_SIZEOF_IFDESC,
  .type         = USB_DESC_TYPE_INTERFACE,
  .ifno         = 0,
  .alt          = 0,
  .neps         = 1,
  .classid      = USB_CLASS_CDC,
  .subclass     = CDC_SUBCLASS_ACM,
  .protocol     = CDC_PROTO_VENDOR,
  .iif          = 0
};

  /* Interrupt endpoint descriptor */

static const struct usbdev_epinfo_s g_rndis_epintindesc =
{
  .desc =
    {
      .len       = USB_SIZEOF_EPDESC,
      .type      = USB_DESC_TYPE_ENDPOINT,
#ifndef CONFIG_RNDIS_COMPOSITE
      .addr      = RNDIS_EPINTIN_ADDR,
#else
      .addr      = USB_DIR_IN,
#endif
      .attr      = USB_EP_ATTR_XFER_INT,
      .interval  = 10
    },
  .reqnum        = 1,
  .fssize        = CONFIG_RNDIS_EPINTIN_FSSIZE,
#ifdef CONFIG_USBDEV_DUALSPEED
  .hssize        = CONFIG_RNDIS_EPINTIN_HSSIZE,
#endif
#ifdef CONFIG_USBDEV_SUPERSPEED
  .sssize        = CONFIG_RNDIS_EPINTIN_HSSIZE,
  .compdesc      =
    {
      .len       = USB_SIZEOF_SS_EPCOMPDESC,
      .type      = USB_DESC_TYPE_ENDPOINT_COMPANION,
      .mxburst   = CONFIG_RNDIS_EPINTIN_MAXBURST,
      .attr      = 0,
      .wbytes[0] = LSBYTE((CONFIG_RNDIS_EPINTIN_MAXBURST + 1) *
                           CONFIG_RNDIS_EPINTIN_HSSIZE),
      .wbytes[1] = MSBYTE((CONFIG_RNDIS_EPINTIN_MAXBURST + 1) *
                           CONFIG_RNDIS_EPINTIN_HSSIZE),
    },
#endif
};

  /* Data interface descriptor */

static const struct usb_ifdesc_s g_rndis_data_ifdesc =
{
  .len          = USB_SIZEOF_IFDESC,
  .type         = USB_DESC_TYPE_INTERFACE,
  .ifno         = 1,
  .alt          = 0,
  .neps         = 2,
  .classid      = USB_CLASS_CDC_DATA,
  .subclass     = 0,
  .protocol     = 0,
  .iif          = 0
};

  /* Bulk in interface descriptor */

static const struct usbdev_epinfo_s g_rndis_epbulkindesc =
{
  .desc =
    {
      .len       = USB_SIZEOF_EPDESC,
      .type      = USB_DESC_TYPE_ENDPOINT,
#ifndef CONFIG_RNDIS_COMPOSITE
      .addr      = RNDIS_EPBULKIN_ADDR,
#else
      .addr      = USB_DIR_IN,
#endif
      .attr      = USB_EP_ATTR_XFER_BULK,
#ifdef CONFIG_USBDEV_DUALSPEED
      .interval  = 0
#else
      .interval  = 1
#endif
    },
  .reqnum        = 1,
  .fssize        = CONFIG_RNDIS_EPBULKIN_FSSIZE,
#ifdef CONFIG_USBDEV_DUALSPEED
  .hssize        = CONFIG_RNDIS_EPBULKIN_HSSIZE,
#endif
#ifdef CONFIG_USBDEV_SUPERSPEED
  .sssize        = CONFIG_RNDIS_EPBULKIN_SSSIZE,
  .compdesc      =
    {
      .len       = USB_SIZEOF_SS_EPCOMPDESC,
      .type      = USB_DESC_TYPE_ENDPOINT_COMPANION,
      .mxburst   = CONFIG_RNDIS_EPBULKIN_MAXBURST,
      .attr      = CONFIG_RNDIS_EPBULKIN_MAXSTREAM,
      .wbytes[0] = 0,
      .wbytes[1] = 0,
    },
#endif
};

  /* Bulk out interface descriptor */

static const struct usbdev_epinfo_s g_rndis_epbulkoutdesc =
{
  .desc =
    {
      .len       = USB_SIZEOF_EPDESC,
      .type      = USB_DESC_TYPE_ENDPOINT,
#ifndef CONFIG_RNDIS_COMPOSITE
      .addr      = RNDIS_EPBULKOUT_ADDR,
#else
      .addr      = USB_DIR_OUT,
#endif
      .attr      = USB_EP_ATTR_XFER_BULK,
#ifdef CONFIG_USBDEV_DUALSPEED
      .interval  = 0
#else
      .interval  = 1
#endif
    },
  .reqnum        = 1,
  .fssize        = CONFIG_RNDIS_EPBULKOUT_FSSIZE,
#ifdef CONFIG_USBDEV_DUALSPEED
  .hssize        = CONFIG_RNDIS_EPBULKOUT_HSSIZE,
#endif
#ifdef CONFIG_USBDEV_SUPERSPEED
  .sssize        = CONFIG_RNDIS_EPBULKOUT_SSSIZE,
  .compdesc      =
    {
      .len       = USB_SIZEOF_SS_EPCOMPDESC,
      .type      = USB_DESC_TYPE_ENDPOINT_COMPANION,
      .mxburst   = CONFIG_RNDIS_EPBULKOUT_MAXBURST,
      .attr      = CONFIG_RNDIS_EPBULKOUT_MAXSTREAM,
      .wbytes[0] = 0,
      .wbytes[1] = 0,
    },
#endif
};

/* Default MAC address given to the host side of the interface. */

static uint8_t g_rndis_default_mac_addr[6] =
{
  0x02, 0x00, 0x00, 0x11, 0x22, 0x33
};

/* These lists give dummy responses to be returned to PC. The values are
 * chosen so that Windows is happy - other operating systems don't really
 * care much.
 */

static const uint32_t g_rndis_supported_oids[] =
{
  RNDIS_OID_GEN_SUPPORTED_LIST,
  RNDIS_OID_GEN_HARDWARE_STATUS,
  RNDIS_OID_GEN_MEDIA_SUPPORTED,
  RNDIS_OID_GEN_MEDIA_IN_USE,
  RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE,
  RNDIS_OID_GEN_LINK_SPEED,
  RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE,
  RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE,
  RNDIS_OID_GEN_VENDOR_ID,
  RNDIS_OID_GEN_VENDOR_DESCRIPTION,
  RNDIS_OID_GEN_VENDOR_DRIVER_VERSION,
  RNDIS_OID_GEN_CURRENT_PACKET_FILTER,
  RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE,
  RNDIS_OID_GEN_MEDIA_CONNECT_STATUS,
  RNDIS_OID_GEN_PHYSICAL_MEDIUM,
  RNDIS_OID_GEN_XMIT_OK,
  RNDIS_OID_GEN_RCV_OK,
  RNDIS_OID_GEN_XMIT_ERROR,
  RNDIS_OID_GEN_RCV_ERROR,
  RNDIS_OID_GEN_RCV_NO_BUFFER,
  RNDIS_OID_802_3_PERMANENT_ADDRESS,
  RNDIS_OID_802_3_CURRENT_ADDRESS,
  RNDIS_OID_802_3_MULTICAST_LIST,
  RNDIS_OID_802_3_MAC_OPTIONS,
  RNDIS_OID_802_3_MAXIMUM_LIST_SIZE,
  RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT,
  RNDIS_OID_802_3_XMIT_ONE_COLLISION,
  RNDIS_OID_802_3_XMIT_MORE_COLLISION,
};

static const struct rndis_oid_value_s g_rndis_oid_values[] =
{
  {
    RNDIS_OID_GEN_SUPPORTED_LIST,
    sizeof(g_rndis_supported_oids), 0,
    g_rndis_supported_oids
  },
  {RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE,    4, CONFIG_NET_ETH_PKTSIZE,  NULL},
#if defined(CONFIG_USBDEV_DUALSPEED) || defined(CONFIG_USBDEV_SUPERSPEED)
  {RNDIS_OID_GEN_LINK_SPEED,            4, 100000,              NULL},
#else
  {RNDIS_OID_GEN_LINK_SPEED,            4, 2000000,             NULL},
#endif
  {RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE,   4, CONFIG_NET_ETH_PKTSIZE,  NULL},
  {RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE,    4, CONFIG_NET_ETH_PKTSIZE,  NULL},
  {RNDIS_OID_GEN_VENDOR_ID,             4, 0x00ffffff,          NULL},
  {RNDIS_OID_GEN_VENDOR_DESCRIPTION,    6, 0,                   "RNDIS"},
  {RNDIS_OID_GEN_CURRENT_PACKET_FILTER, 4, 0,                   NULL},
  {RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE,    4, 2048,                NULL},
  {RNDIS_OID_GEN_XMIT_OK,               4, 0,                   NULL},
  {RNDIS_OID_GEN_RCV_OK,                4, 0,                   NULL},
  {RNDIS_OID_802_3_PERMANENT_ADDRESS,   6, 0,                   NULL},
  {RNDIS_OID_802_3_CURRENT_ADDRESS,     6, 0,                   NULL},
  {RNDIS_OID_802_3_MULTICAST_LIST,      4, 0xe0000000,          NULL},
  {RNDIS_OID_802_3_MAXIMUM_LIST_SIZE,   4, 1,                   NULL},
  {0x0,                                 4, 0,                   NULL}, /* Default fallback */
};

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Buffering of data is implemented in the following manner:
 *
 * RNDIS driver holds a number of preallocated bulk IN endpoint write
 * requests along with buffers large enough to hold an Ethernet packet and
 * the corresponding RNDIS header.
 *
 * One of these is always reserved for packet reception - when data arrives
 * on the bulk OUT endpoint, it is copied to the reserved request buffer.
 * When the reception of an Ethernet packet is complete, a worker to process
 * the packet is scheduled and bulk OUT endpoint is set to NAK.
 *
 * The processing worker passes the buffer to the network. When the network
 * is done processing the packet, the buffer might contain data to be sent.
 * If so, the corresponding write request is queued on the bulk IN endpoint.
 * The NAK state on bulk OUT endpoint is cleared to allow new packets to
 * arrive. If there's no data to send, the request is returned to the list of
 * free requests.
 *
 * When a bulk IN write operation is complete, the request is added to the
 * list of free requests.
 *
 ****************************************************************************/

/****************************************************************************
 * Name: rndis_submit_rdreq
 *
 * Description:
 *   Submits the bulk OUT read request. Takes care not to submit the request
 *   when the RX packet buffer is already in use.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Returned Value:
 *   The return value of the EP_SUBMIT operation
 *
 ****************************************************************************/

static int rndis_submit_rdreq(FAR struct rndis_dev_s *priv)
{
  irqstate_t flags = enter_critical_section();
  int ret = OK;

  if (!priv->rdreq_submitted && !priv->rx_blocked)
    {
      priv->rdreq->len = priv->epbulkout->maxpacket;
      ret = EP_SUBMIT(priv->epbulkout, priv->rdreq);
      if (ret != OK)
        {
          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT),
                   (uint16_t)-priv->rdreq->result);
        }
      else
        {
          priv->rdreq_submitted = true;
        }
    }

  leave_critical_section(flags);
  return ret;
}

/****************************************************************************
 * Name: rndis_cancel_rdreq
 *
 * Description:
 *   Cancels the bulk OUT endpoint read request.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 ****************************************************************************/

static void rndis_cancel_rdreq(FAR struct rndis_dev_s *priv)
{
  irqstate_t flags = enter_critical_section();
  if (priv->rdreq_submitted)
    {
      EP_CANCEL(priv->epbulkout, priv->rdreq);
      priv->rdreq_submitted = false;
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: rndis_block_rx
 *
 * Description:
 *   Blocks reception of further bulk OUT endpoint data.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 ****************************************************************************/

static void rndis_block_rx(FAR struct rndis_dev_s *priv)
{
  irqstate_t flags = enter_critical_section();

  priv->rx_blocked = true;
  rndis_cancel_rdreq(priv);
  leave_critical_section(flags);
}

/****************************************************************************
 * Name: rndis_unblock_rx
 *
 * Description:
 *   Unblocks reception of bulk OUT endpoint data.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static void rndis_unblock_rx(FAR struct rndis_dev_s *priv)
{
  priv->rx_blocked = false;
}

/****************************************************************************
 * Name: rndis_allocwrreq
 *
 * Description:
 *   Allocates a bulk IN endpoint request from the list of free request
 *   buffers.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Returned Value:
 *   NULL if allocation failed; pointer to allocated request if succeeded
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static FAR struct rndis_req_s *rndis_allocwrreq(FAR struct rndis_dev_s *priv)
{
  return (FAR struct rndis_req_s *)sq_remfirst(&priv->reqlist);
}

/****************************************************************************
 * Name: rndis_hasfreereqs
 *
 * Description:
 *   Checks if there are free requests usable for TX data.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Returned Value:
 *   true if requests available; false if no requests available
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static bool rndis_hasfreereqs(FAR struct rndis_dev_s *priv)
{
  return sq_count(&priv->reqlist) > 1;
}

/****************************************************************************
 * Name: rndis_freewrreq
 *
 * Description:
 *   Returns a bulk IN endpoint write requests to the list of free requests.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *   req: pointer to the request
 *
 * Assumptions:
 *   Called with interrupts disabled.
 *
 ****************************************************************************/

static void rndis_freewrreq(FAR struct rndis_dev_s *priv,
                            FAR struct rndis_req_s *req)
{
  DEBUGASSERT(req != NULL);
  if (req->iob)
    {
      /* In ep submit case, need release iob chain when write complete */

      iob_free_chain(req->iob);
      req->iob = NULL;
    }

  sq_addlast((FAR sq_entry_t *)req, &priv->reqlist);
  rndis_submit_rdreq(priv);
}

/****************************************************************************
 * Name: rndis_allocnetreq
 *
 * Description:
 *   Allocates a request buffer to be used on the network.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Returned Value:
 *   true if succeeded; false if failed
 *
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static bool rndis_allocnetreq(FAR struct rndis_dev_s *priv)
{
  irqstate_t flags = enter_critical_section();
  DEBUGASSERT(priv->net_req == NULL);

  if (!rndis_hasfreereqs(priv))
    {
      leave_critical_section(flags);
      return false;
    }

  priv->net_req = rndis_allocwrreq(priv);

  leave_critical_section(flags);
  return priv->net_req != NULL;
}

/****************************************************************************
 * Name: rndis_sendnetreq
 *
 * Description:
 *   Submits the request buffer held by the network.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static void rndis_sendnetreq(FAR struct rndis_dev_s *priv)
{
  irqstate_t flags = enter_critical_section();

  DEBUGASSERT(priv->net_req != NULL);

  priv->net_req->req->priv = priv->net_req;
  EP_SUBMIT(priv->epbulkin, priv->net_req->req);

  priv->net_req            = NULL;
  leave_critical_section(flags);
}

/****************************************************************************
 * Name: rndis_freenetreq
 *
 * Description:
 *   Frees the request buffer held by the network.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static void rndis_freenetreq(FAR struct rndis_dev_s *priv)
{
  irqstate_t flags = enter_critical_section();

  rndis_freewrreq(priv, priv->net_req);
  priv->net_req = NULL;

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: rndis_iob2buf
 *
 * Description:
 *   Map the appropriate location of req iob to buf.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *   req: the request whose buffer we should fill
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static void rndis_iob2buf(FAR struct rndis_dev_s *priv,
                          FAR struct rndis_req_s *req)
{
  uint16_t llhdrlen = NET_LL_HDRLEN(&priv->netdev);
  uint32_t offset   = CONFIG_NET_LL_GUARDSIZE - llhdrlen -
                      RNDIS_PACKET_HDR_SIZE;

  /*  ----------------------------------------------------------------
   *  |<--- CONFIG_NET_LL_GUARDSIZE ---->|<-- io_len/io_pktlen(0) -->|
   *  ---------------------------------------------------------------|
   *  |unused | rndis hdr size |llhdrlen |<-- io_len/io_pktlen(0) -->|
   *  ---------------------------------------------------------------|
   *  |unused | req->buf(0)                                          |
   *  ---------------------------------------------------------------|
   */

  if (req->iob->io_flink == NULL)
    {
      req->req->buf = &req->iob->io_data[offset];
      req->req->len = CONFIG_RNDIS_BULKIN_REQLEN;
    }
  else
    {
      req->req->buf = req->buf;
      iob_copyout(&req->req->buf[RNDIS_PACKET_HDR_SIZE], req->iob,
                  req->iob->io_pktlen + llhdrlen, -llhdrlen);
      iob_free_chain(req->iob);
      req->iob = NULL;
    }
}

/****************************************************************************
 * Name: rndis_allocrxreq
 *
 * Description:
 *   Allocates a buffer for packet reception if there already isn't one.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Returned Value:
 *   true if succeeded; false if failed
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static bool rndis_allocrxreq(FAR struct rndis_dev_s *priv)
{
  FAR struct iob_s *iob;

  if (priv->rx_req != NULL)
    {
      return true;
    }

  /* Prepare buffer to receivce data from usb driver */

  iob = iob_tryalloc(false);
  if (iob == NULL)
    {
      return false;
    }

  iob_reserve(iob, CONFIG_NET_LL_GUARDSIZE);

  if ((priv->rx_req = rndis_allocwrreq(priv)) == NULL)
    {
      iob_free_chain(iob);
      return false;
    }

  priv->rx_req->iob = iob;
  rndis_iob2buf(priv, priv->rx_req);

  return true;
}

/****************************************************************************
 * Name: rndis_giverxreq
 *
 * Description:
 *   Passes the RX packet buffer to the network
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static void rndis_giverxreq(FAR struct rndis_dev_s *priv)
{
  DEBUGASSERT(priv->rx_req != NULL);
  DEBUGASSERT(priv->net_req == NULL);

  priv->net_req = priv->rx_req;
  priv->rx_req  = NULL;

  /* Move iob from net_req to netdev */

  netdev_iob_release(&priv->netdev);

  priv->netdev.d_iob = priv->net_req->iob;
  priv->netdev.d_len = priv->net_req->iob->io_pktlen;
  priv->net_req->iob = NULL;
}

/****************************************************************************
 * Name: rndis_fillrequest
 *
 * Description:
 *   Fills the RNDIS header to the request buffer
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *   req: the request whose buffer we should fill
 *
 * Returned Value:
 *   The total length of the request data
 *
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static uint16_t rndis_fillrequest(FAR struct rndis_dev_s *priv,
                                  FAR struct rndis_req_s *req)
{
  size_t datalen;

  req->req->len = 0;

  datalen = MIN(priv->netdev.d_len,
                CONFIG_RNDIS_BULKIN_REQLEN - RNDIS_PACKET_HDR_SIZE);
  if (datalen > 0)
    {
      /* Move iob from netdev to net_req and send the required headers */

      req->iob = priv->netdev.d_iob;
      netdev_iob_clear(&priv->netdev);
      rndis_iob2buf(priv, req);
      FAR struct rndis_packet_msg *msg =
        (FAR struct rndis_packet_msg *)req->req->buf;
      memset(msg, 0, RNDIS_PACKET_HDR_SIZE);

      msg->msgtype    = RNDIS_PACKET_MSG;
      msg->msglen     = RNDIS_PACKET_HDR_SIZE + datalen;
      msg->dataoffset = RNDIS_PACKET_HDR_SIZE - 8;
      msg->datalen    = datalen;

      req->req->flags = USBDEV_REQFLAGS_NULLPKT;
      req->req->len   = datalen + RNDIS_PACKET_HDR_SIZE;
    }

  return req->req->len;
}

/****************************************************************************
 * Name: rndis_rxdispatch
 *
 * Description:
 *   Processes the received Ethernet packet. Called from work queue.
 *
 * Input Parameters:
 *   arg: pointer to RNDIS device driver structure
 *
 ****************************************************************************/

static void rndis_rxdispatch(FAR void *arg)
{
  FAR struct rndis_dev_s *priv = (FAR struct rndis_dev_s *)arg;
  FAR struct eth_hdr_s *hdr;
  irqstate_t flags;

  net_lock();
  flags = enter_critical_section();
  rndis_giverxreq(priv);
  priv->netdev.d_len = priv->current_rx_datagram_size;
  leave_critical_section(flags);

  hdr = (FAR struct eth_hdr_s *)
    &priv->netdev.d_iob->io_data[CONFIG_NET_LL_GUARDSIZE -
                                 NET_LL_HDRLEN(&priv->netdev)];

  /* We only accept IP packets of the configured type and ARP packets */

#ifdef CONFIG_NET_IPv4
  if (hdr->type == HTONS(ETHTYPE_IP))
    {
      NETDEV_RXIPV4(&priv->netdev);

      /* Receive an IPv4 packet from the network device */

      ipv4_input(&priv->netdev);

      if (priv->netdev.d_len > 0)
        {
          /* And send the packet */

          rndis_transmit(priv);
        }
    }
  else
#endif
#ifdef CONFIG_NET_IPv6
  if (hdr->type == HTONS(ETHTYPE_IP6))
    {
      NETDEV_RXIPV6(&priv->netdev);

      /* Give the IPv6 packet to the network layer */

      ipv6_input(&priv->netdev);

      if (priv->netdev.d_len > 0)
        {
          /* And send the packet */

          rndis_transmit(priv);
        }
    }
  else
#endif
#ifdef CONFIG_NET_ARP
  if (hdr->type == HTONS(ETHTYPE_ARP))
    {
      NETDEV_RXARP(&priv->netdev);

      arp_input(&priv->netdev);

      if (priv->netdev.d_len > 0)
        {
          rndis_transmit(priv);
        }
    }
  else
#endif
    {
      uerr("ERROR: Unsupported packet type dropped (%02x)\n",
           HTONS(hdr->type));
      NETDEV_RXDROPPED(&priv->netdev);
      priv->netdev.d_len = 0;
    }

  priv->current_rx_datagram_size = 0;
  rndis_unblock_rx(priv);

  if (priv->net_req != NULL)
    {
      rndis_freenetreq(priv);
    }

  net_unlock();
}

/****************************************************************************
 * Name: rndis_txpoll
 *
 * Description:
 *   Sends the packet that is stored in the network packet buffer. Called
 *   from work queue by e.g. txavail and txpoll callbacks.
 *
 * Input Parameters:
 *   dev: pointer to network driver structure
 *
 * Assumptions:
 *   Caller holds the network lock
 *
 ****************************************************************************/

static int rndis_txpoll(FAR struct net_driver_s *dev)
{
  FAR struct rndis_dev_s *priv = (FAR struct rndis_dev_s *)dev->d_private;

  if (!priv->connected)
    {
      return -EBUSY;
    }

  return rndis_transmit(priv);
}

/****************************************************************************
 * Name: rndis_transmit
 *
 * Description:
 *   Start hardware transmission.
 *
 ****************************************************************************/

static int rndis_transmit(FAR struct rndis_dev_s *priv)
{
  int ret = OK;

  /* Queue the packet */

  rndis_fillrequest(priv, priv->net_req);
  rndis_sendnetreq(priv);

  if (!rndis_allocnetreq(priv))
    {
      ret = -EBUSY;
    }

  return ret;
}

/****************************************************************************
 * Name: rndis_ifup
 *
 * Description:
 *   Network ifup callback
 *
 ****************************************************************************/

static int rndis_ifup(FAR struct net_driver_s *dev)
{
  return OK;
}

/****************************************************************************
 * Name: rndis_ifdown
 *
 * Description:
 *   Network ifdown callback
 *
 ****************************************************************************/

static int rndis_ifdown(FAR struct net_driver_s *dev)
{
  return OK;
}

/****************************************************************************
 * Name: rndis_txavail_work
 *
 * Description:
 *   txavail worker function
 *
 ****************************************************************************/

static void rndis_txavail_work(FAR void *arg)
{
  FAR struct rndis_dev_s *priv = (FAR struct rndis_dev_s *)arg;

  net_lock();

  if (rndis_allocnetreq(priv))
    {
      devif_poll(&priv->netdev, rndis_txpoll);
      if (priv->net_req != NULL)
        {
          rndis_freenetreq(priv);
        }
    }

  net_unlock();
}

/****************************************************************************
 * Name: rndis_txavail
 *
 * Description:
 *   Network txavail callback that's called when there are buffers available
 *   for sending data. May be called from an interrupt, so we must queue a
 *   worker to do the actual processing.
 *
 ****************************************************************************/

static int rndis_txavail(FAR struct net_driver_s *dev)
{
  FAR struct rndis_dev_s *priv = (FAR struct rndis_dev_s *)dev->d_private;

  if (work_available(&priv->pollwork))
    {
      work_queue(ETHWORK, &priv->pollwork, rndis_txavail_work, priv, 0);
    }

  return OK;
}

/****************************************************************************
 * Name: rndis_recvpacket
 *
 * Description:
 *   Handles a USB packet arriving on the data bulk out endpoint.
 *
 * Assumptions:
 *   Called from the USB interrupt handler with interrupts disabled.
 *
 ****************************************************************************/

static inline int rndis_recvpacket(FAR struct rndis_dev_s *priv,
                                   FAR uint8_t *reqbuf, uint16_t reqlen)
{
  if (!rndis_allocrxreq(priv))
    {
      return -ENOMEM;
    }

  if (!priv->connected)
    {
      return -EBUSY;
    }

  if (!priv->current_rx_datagram_size)
    {
      if (reqlen < 16)
        {
          /* Packet too small to contain a message header */
        }
      else
        {
          /* The packet contains a RNDIS packet message header */

          FAR struct rndis_packet_msg *msg =
            (FAR struct rndis_packet_msg *)reqbuf;
          if (msg->msgtype == RNDIS_PACKET_MSG)
            {
              priv->current_rx_received = reqlen;
              priv->current_rx_datagram_size = msg->datalen;
              priv->current_rx_msglen = msg->msglen;

              /* According to RNDIS-over-USB send, if the message length is a
               * multiple of endpoint max packet size, the host must send an
               * additional single-byte zero packet. Take that in account
               * here.
               */

              if (!(priv->current_rx_msglen % priv->epbulkout->maxpacket))
                {
                  priv->current_rx_msglen += 1;
                }

              /* Data offset is defined as an offset from the beginning of
               * the offset field itself
               */

              priv->current_rx_datagram_offset = msg->dataoffset + 8;
              if (priv->current_rx_datagram_offset < reqlen)
                {
                  iob_trycopyin(priv->rx_req->iob,
                                &reqbuf[priv->current_rx_datagram_offset],
                                reqlen - priv->current_rx_datagram_offset,
                                -NET_LL_HDRLEN(&priv->netdev), false);
                }
            }
          else
            {
              uerr("Unknown RNDIS message type %" PRIu32 "\n", msg->msgtype);
            }
        }
    }
  else
    {
      if (priv->current_rx_received >= priv->current_rx_datagram_offset &&
          priv->current_rx_received <= priv->current_rx_datagram_size +
          priv->current_rx_datagram_offset)
        {
          size_t index = priv->current_rx_received -
                         priv->current_rx_datagram_offset;
          size_t copysize = MIN(reqlen,
                                priv->current_rx_datagram_size - index);

          /* Check if the received packet exceeds request buffer */

          if ((index + copysize) <= CONFIG_NET_ETH_PKTSIZE)
            {
              iob_trycopyin(priv->rx_req->iob, reqbuf, copysize,
                            priv->rx_req->iob->io_pktlen, false);
            }
          else
            {
              uerr("The packet exceeds request buffer (reqlen=%d)\n",
                   reqlen);
            }
        }
      priv->current_rx_received += reqlen;
    }

  if (priv->current_rx_received >= priv->current_rx_msglen)
    {
      /* Check for a usable packet length (4 added for the CRC) */

      if (priv->current_rx_datagram_size > (CONFIG_NET_ETH_PKTSIZE + 4) ||
          priv->current_rx_datagram_size <= (ETH_HDRLEN + 4))
        {
          uerr("ERROR: Bad packet size dropped (%zu)\n",
               priv->current_rx_datagram_size);
          NETDEV_RXERRORS(&priv->netdev);
          priv->current_rx_datagram_size = 0;
        }
      else
        {
          int ret;

          DEBUGASSERT(work_available(&priv->rxwork));
          ret = work_queue(ETHWORK, &priv->rxwork, rndis_rxdispatch,
                           priv, 0);
          DEBUGASSERT(ret == 0);
          UNUSED(ret);

          rndis_block_rx(priv);
          priv->rndis_host_tx_count++;
          return -EBUSY;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: rndis_prepare_response
 *
 * Description:
 *   Passes the RX packet buffer to the network
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Returns:
 *   pointer to response buffer
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static FAR void *
rndis_prepare_response(FAR struct rndis_dev_s *priv, size_t size,
                       FAR struct rndis_command_header *request_hdr)
{
  size_t size_words = size / sizeof(uint32_t);
  uint32_t *buf = priv->response_queue + priv->response_queue_words;
  FAR struct rndis_response_header *hdr =
    (FAR struct rndis_response_header *)buf;

  if (priv->response_queue_words + size_words > RNDIS_RESP_QUEUE_WORDS)
    {
      uerr("RNDIS response queue full, dropping command %08x",
           (unsigned int)request_hdr->msgtype);
      return NULL;
    }

  hdr->msgtype = request_hdr->msgtype | RNDIS_MSG_COMPLETE;
  hdr->msglen  = size;
  hdr->reqid   = request_hdr->reqid;
  hdr->status  = RNDIS_STATUS_SUCCESS;

  return hdr;
}

/****************************************************************************
 * Name: rndis_send_encapsulated_response
 *
 * Description:
 *   Give a notification to the host that there is an encapsulated response
 *   available.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static int rndis_send_encapsulated_response(FAR struct rndis_dev_s *priv,
                                            size_t size)
{
  size_t size_words = size / sizeof(uint32_t);
  FAR struct rndis_notification *notif =
    (FAR struct rndis_notification *)priv->epintin_req->buf;

  /* RNDIS packets should always be multiple of 4 bytes in size */

  DEBUGASSERT(size_words * sizeof(uint32_t) == size);

  /* Mark the response as available in the queue */

  priv->response_queue_words += size_words;
  DEBUGASSERT(priv->response_queue_words <= RNDIS_RESP_QUEUE_WORDS);

  /* Send notification on IRQ endpoint, to tell host to read the data. */

  notif->notification = RNDIS_NOTIFICATION_RESPONSE_AVAILABLE;
  notif->reserved = 0;
  priv->epintin_req->len = sizeof(struct rndis_notification);

  EP_CANCEL(priv->epintin, priv->epintin_req);
  EP_SUBMIT(priv->epintin, priv->epintin_req);

  return OK;
}

/****************************************************************************
 * Name: rndis_handle_control_message
 *
 * Description:
 *   Handle a RNDIS control message.
 *
 * Input Parameters:
 *   priv: pointer to RNDIS device driver structure
 *
 * Assumptions:
 *   Called from critical section
 *
 ****************************************************************************/

static int rndis_handle_control_message(FAR struct rndis_dev_s *priv,
                                        FAR uint8_t *dataout,
                                        uint16_t outlen)
{
  FAR struct rndis_command_header *cmd_hdr =
    (FAR struct rndis_command_header *)dataout;

  switch (cmd_hdr->msgtype)
    {
      case RNDIS_INITIALIZE_MSG:
        {
          FAR struct rndis_initialize_cmplt *resp;
          size_t respsize = sizeof(struct rndis_initialize_cmplt);

          resp = rndis_prepare_response(priv, respsize, cmd_hdr);
          if (!resp)
            {
              return -ENOMEM;
            }

          resp->major      = RNDIS_MAJOR_VERSION;
          resp->minor      = RNDIS_MINOR_VERSION;
          resp->devflags   = RNDIS_DEVICEFLAGS;
          resp->medium     = RNDIS_MEDIUM_802_3;
          resp->pktperxfer = 1;
          resp->xfrsize    = (4 + 44 + 22) + RNDIS_BUFFER_SIZE;
          resp->pktalign   = 2;

          rndis_send_encapsulated_response(priv, respsize);
        }
        break;

      case RNDIS_HALT_MSG:
        {
          priv->response_queue_words = 0;
          priv->connected = false;
        }
        break;

      case RNDIS_QUERY_MSG:
        {
          int i;
          size_t max_reply_size = sizeof(struct rndis_query_cmplt) +
                                  sizeof(g_rndis_supported_oids);
          FAR struct rndis_query_cmplt *resp;
          FAR struct rndis_query_msg *req =
            (FAR struct rndis_query_msg *)dataout;

          resp = rndis_prepare_response(priv, max_reply_size, cmd_hdr);
          if (!resp)
            {
              return -ENOMEM;
            }

          resp->hdr.msglen = sizeof(struct rndis_query_cmplt);
          resp->bufoffset  = 0;
          resp->buflen     = 0;
          resp->hdr.status = RNDIS_STATUS_NOT_SUPPORTED;

          for (i = 0;
               i < sizeof(g_rndis_oid_values) /
                   sizeof(g_rndis_oid_values[0]);
               i++)
            {
              bool match = (g_rndis_oid_values[i].objid == req->objid);

              if (!match && g_rndis_oid_values[i].objid == 0)
                {
                  int j;

                  /* Check whether to apply the fallback entry */

                  for (j = 0;
                       j < sizeof(g_rndis_supported_oids) / sizeof(uint32_t);
                       j++)
                    {
                      if (g_rndis_supported_oids[j] == req->objid)
                        {
                          match = true;
                          break;
                        }
                    }
                }

              if (match)
                {
                  resp->hdr.status = RNDIS_STATUS_SUCCESS;
                  resp->bufoffset  = 16;
                  resp->buflen     = g_rndis_oid_values[i].length;

                  if (req->objid == RNDIS_OID_GEN_CURRENT_PACKET_FILTER)
                    {
                      resp->buffer[0] = priv->rndis_packet_filter;
                    }
                  else if (req->objid == RNDIS_OID_GEN_XMIT_OK)
                    {
                      resp->buffer[0] = priv->rndis_host_tx_count;
                    }
                  else if (req->objid == RNDIS_OID_GEN_RCV_OK)
                    {
                      resp->buffer[0] = priv->rndis_host_rx_count;
                    }
                  else if (req->objid == RNDIS_OID_802_3_CURRENT_ADDRESS ||
                           req->objid == RNDIS_OID_802_3_PERMANENT_ADDRESS)
                    {
                      memcpy(resp->buffer, priv->host_mac_address, 6);
                    }
                  else if (g_rndis_oid_values[i].data)
                    {
                      memcpy(resp->buffer, g_rndis_oid_values[i].data,
                             resp->buflen);
                    }
                  else
                    {
                      memcpy(resp->buffer, &g_rndis_oid_values[i].value,
                             resp->buflen);
                    }
                  break;
                }
            }

          uinfo("RNDIS Query RID=%08x OID=%08x LEN=%d DAT=%08x",
                (unsigned)req->hdr.reqid, (unsigned)req->objid,
                (int)resp->buflen, (unsigned)resp->buffer[0]);

          resp->hdr.msglen += resp->buflen;

          /* Align to word boundary */

          if ((resp->hdr.msglen & 3) != 0)
            {
              resp->hdr.msglen += 4 - (resp->hdr.msglen & 3);
            }

          rndis_send_encapsulated_response(priv, resp->hdr.msglen);
        }
        break;

      case RNDIS_SET_MSG:
        {
          FAR struct rndis_set_msg *req;
          FAR struct rndis_response_header *resp;
          size_t respsize = sizeof(struct rndis_response_header);

          resp = rndis_prepare_response(priv, respsize, cmd_hdr);
          req  = (FAR struct rndis_set_msg *)dataout;

          if (!resp)
            {
              return -ENOMEM;
            }

          uinfo("RNDIS SET RID=%08x OID=%08x LEN=%d DAT=%08x",
                (unsigned)req->hdr.reqid, (unsigned)req->objid,
                (int)req->buflen, (unsigned)req->buffer[0]);

          if (req->objid == RNDIS_OID_GEN_CURRENT_PACKET_FILTER)
            {
              priv->rndis_packet_filter = req->buffer[0];

              if (req->buffer[0] == 0)
                {
                  priv->connected = false;
                }
              else
                {
                  uinfo("RNDIS is now connected");
                  priv->connected = true;
                }
            }
          else if (req->objid == RNDIS_OID_802_3_MULTICAST_LIST)
            {
              uinfo("RNDIS multicast list ignored");
            }
          else
            {
              uinfo("RNDIS unsupported set %08x", (unsigned)req->objid);
              resp->status = RNDIS_STATUS_NOT_SUPPORTED;
            }

          rndis_send_encapsulated_response(priv, respsize);
        }
        break;

      case RNDIS_RESET_MSG:
        {
          FAR struct rndis_reset_cmplt *resp;
          size_t respsize = sizeof(struct rndis_reset_cmplt);

          priv->response_queue_words = 0;
          resp = rndis_prepare_response(priv, respsize, cmd_hdr);

          if (!resp)
            {
              return -ENOMEM;
            }

          resp->addreset  = 0;
          priv->connected = false;
          rndis_send_encapsulated_response(priv, respsize);
        }
        break;

      case RNDIS_KEEPALIVE_MSG:
        {
          FAR struct rndis_response_header *resp;
          size_t respsize = sizeof(struct rndis_response_header);
          resp = rndis_prepare_response(priv, respsize, cmd_hdr);
          if (!resp)
            {
              return -ENOMEM;
            }

          rndis_send_encapsulated_response(priv, respsize);
        }
        break;

      default:
        uwarn("Unsupported RNDIS control message: %" PRIu32 "\n",
              cmd_hdr->msgtype);
    }

  return OK;
}

/****************************************************************************
 * Name: rndis_rdcomplete
 *
 * Description:
 *   Handle completion of read request on the bulk OUT endpoint.
 *
 ****************************************************************************/

static void rndis_rdcomplete(FAR struct usbdev_ep_s *ep,
                             FAR struct usbdev_req_s *req)
{
  FAR struct rndis_dev_s *priv;
  irqstate_t flags;
  int ret;

  /* Sanity check */

#ifdef CONFIG_DEBUG_FEATURES
  if (!ep || !ep->priv || !req)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract references to private data */

  priv = (FAR struct rndis_dev_s *)ep->priv;

  /* Process the received data unless this is some unusual condition */

  ret = OK;

  flags = enter_critical_section();
  priv->rdreq_submitted = false;

  switch (req->result)
    {
    case 0: /* Normal completion */
      ret = rndis_recvpacket(priv, req->buf, req->xfrd);
      DEBUGASSERT(ret != -ENOMEM);
      break;

    case -ESHUTDOWN: /* Disconnection */
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSHUTDOWN), 0);
      leave_critical_section(flags);
      return;

    default: /* Some other error occurred */
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDUNEXPECTED),
               (uint16_t)-req->result);
      break;
    };

  if (ret == OK)
    {
      rndis_submit_rdreq(priv);
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: rndis_wrcomplete
 *
 * Description:
 *   Handle completion of write request.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static void rndis_wrcomplete(FAR struct usbdev_ep_s *ep,
                             FAR struct usbdev_req_s *req)
{
  FAR struct rndis_dev_s *priv;
  FAR struct rndis_req_s *reqcontainer;
  irqstate_t flags;

  /* Sanity check */

#ifdef CONFIG_DEBUG_FEATURES
  if (!ep || !ep->priv || !req || !req->priv)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract references to our private data */

  priv         = (FAR struct rndis_dev_s *)ep->priv;
  reqcontainer = (FAR struct rndis_req_s *)req->priv;

  /* Return the write request to the free list */

  flags = enter_critical_section();
  rndis_freewrreq(priv, reqcontainer);
  if (rndis_hasfreereqs(priv))
    {
      rndis_txavail(&priv->netdev);
    }

  switch (req->result)
    {
    case OK: /* Normal completion */
      priv->rndis_host_rx_count++;
      break;

    case -ESHUTDOWN: /* Disconnection */
      break;

    default: /* Some other error occurred */
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRUNEXPECTED),
               (uint16_t)-req->result);
      break;
    }

  leave_critical_section(flags);
}

/****************************************************************************
 * Name: usbclass_ep0incomplete
 *
 * Description:
 *   Handle completion of EP0 control operations
 *
 ****************************************************************************/

static void usbclass_ep0incomplete(FAR struct usbdev_ep_s *ep,
                                   FAR struct usbdev_req_s *req)
{
  FAR struct rndis_dev_s *priv;

  if (req->result || req->xfrd != req->len)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT),
               (uint16_t)-req->result);
    }
  else if (req->len > 0 && req->priv)
    {
      /* Get EP0 request private data  */

      priv = (FAR struct rndis_dev_s *)req->priv;

      /* This transfer was from the response queue,
       * subtract remaining byte count.
       */

      size_t len_words = req->len / sizeof(uint32_t);
      DEBUGASSERT(len_words * sizeof(uint32_t) == req->len);
      req->priv = 0;
      if (len_words >= priv->response_queue_words)
      {
        /* Queue now empty */

        priv->response_queue_words = 0;
      }
      else
      {
        /* Copy the remaining responses to beginning of buffer. */

        priv->response_queue_words -= len_words;
        memcpy(priv->response_queue, priv->response_queue + len_words,
               priv->response_queue_words * sizeof(uint32_t));
      }
    }
}

/****************************************************************************
 * Name: usbclass_epintin_complete
 *
 * Description:
 *   Handle completion of interrupt IN endpoint operations
 *
 ****************************************************************************/

static void usbclass_epintin_complete(FAR struct usbdev_ep_s *ep,
                                      FAR struct usbdev_req_s *req)
{
  if (req->result || req->xfrd != req->len)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_REQRESULT),
               (uint16_t)-req->result);
    }
}

/****************************************************************************
 * Name: usbclass_mkstrdesc
 *
 * Description:
 *   Construct a string descriptor
 *
 ****************************************************************************/

static int usbclass_mkstrdesc(uint8_t id, FAR struct usb_strdesc_s *strdesc)
{
  FAR uint8_t *data = (FAR uint8_t *)(strdesc + 1);
  FAR const char *str;
  int len;
  int ndata;
  int i;

  switch (id)
    {
#ifndef CONFIG_RNDIS_COMPOSITE
      case 0:
        {
          /* Descriptor 0 is the language id */

          strdesc->len  = 4;
          strdesc->type = USB_DESC_TYPE_STRING;
          data[0] = LSBYTE(RNDIS_STR_LANGUAGE);
          data[1] = MSBYTE(RNDIS_STR_LANGUAGE);
          return 4;
        }

      case RNDIS_MANUFACTURERSTRID:
        str = CONFIG_RNDIS_VENDORSTR;
        break;

      case RNDIS_PRODUCTSTRID:
        str = CONFIG_RNDIS_PRODUCTSTR;
        break;

      case RNDIS_SERIALSTRID:
#ifdef CONFIG_BOARD_USBDEV_SERIALSTR
        str = board_usbdev_serialstr();
#else
        str = CONFIG_RNDIS_SERIALSTR;
#endif
        break;
#endif

      default:
        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 > (RNDIS_MAXSTRLEN / 2))
    {
      len = (RNDIS_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: usbclass_mkcfgdesc
 *
 * Description:
 *   Construct the configuration descriptor
 *
 ****************************************************************************/

static int16_t usbclass_mkcfgdesc(FAR uint8_t *buf,
                                  FAR struct usbdev_devinfo_s *devinfo,
                                  uint8_t speed, uint8_t type)
{
  uint16_t totallen = 0;
  uint8_t epno;
  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;
    }

  /* This is the total length of the configuration (not necessarily the
   * size that we will be sending now).
   */
#ifndef CONFIG_RNDIS_COMPOSITE
  if (buf != NULL)
    {
      /* Configuration descriptor.
       * If the USB serial device is configured as part of  composite device,
       * then the configuration descriptor will be provided by the
       * composite device logic.
       */

      FAR struct usb_cfgdesc_s *dest = (FAR struct usb_cfgdesc_s *)buf;
      int16_t size = usbclass_mkcfgdesc(NULL, NULL, speed, type);

      memcpy(buf, &g_rndis_cfgdesc, sizeof(struct usb_cfgdesc_s));
      dest->type = type;                                     /* Descriptor type */
      dest->totallen[0] = LSBYTE(size);                      /* LS Total length */
      dest->totallen[1] = MSBYTE(size);                      /* MS Total length */

      buf += sizeof(struct usb_cfgdesc_s);
    }

  totallen += sizeof(struct usb_cfgdesc_s);

#elif defined(CONFIG_COMPOSITE_IAD)
  /* Interface association descriptor */

  if (buf != NULL)
    {
      FAR struct usb_iaddesc_s *dest = (FAR struct usb_iaddesc_s *)buf;

      memcpy(dest, &g_rndis_assoc_desc, sizeof(struct usb_iaddesc_s));
      dest->firstif += devinfo->ifnobase;

      buf += sizeof(struct usb_iaddesc_s);
    }

  totallen += sizeof(struct usb_iaddesc_s);
#endif

  if (buf != NULL)
    {
      FAR struct usb_ifdesc_s *dest = (FAR struct usb_ifdesc_s *)buf;

      memcpy(dest, &g_rndis_comm_ifdesc, sizeof(struct usb_ifdesc_s));
#ifdef CONFIG_RNDIS_COMPOSITE
      dest->ifno += devinfo->ifnobase;
#endif
      buf += sizeof(struct usb_ifdesc_s);
    }

  totallen += sizeof(struct usb_ifdesc_s);

  epno = devinfo ? devinfo->epno[RNDIS_EP_INTIN_IDX] : 0;
  ret = usbdev_copy_epdesc((struct usb_epdesc_s *)buf,
                           epno,
                           speed,
                           &g_rndis_epintindesc);

  if (buf != NULL)
    {
      buf += ret;
    }

  totallen += ret;

  if (buf != NULL)
    {
      FAR struct usb_ifdesc_s *dest = (FAR struct usb_ifdesc_s *)buf;

      memcpy(dest, &g_rndis_data_ifdesc, sizeof(struct usb_ifdesc_s));
#ifdef CONFIG_RNDIS_COMPOSITE
      dest->ifno += devinfo->ifnobase;
#endif
      buf += sizeof(struct usb_ifdesc_s);
    }

  totallen += sizeof(struct usb_ifdesc_s);

  epno = devinfo ? devinfo->epno[RNDIS_EP_BULKIN_IDX] : 0;
  ret = usbdev_copy_epdesc((struct usb_epdesc_s *)buf,
                           epno,
                           speed,
                           &g_rndis_epbulkindesc);
  if (buf != NULL)
    {
      buf += ret;
    }

  totallen += ret;

  epno = devinfo ? devinfo->epno[RNDIS_EP_BULKOUT_IDX] : 0;
  ret = usbdev_copy_epdesc((struct usb_epdesc_s *)buf,
                           epno,
                           speed,
                           &g_rndis_epbulkoutdesc);
  if (buf != NULL)
    {
      buf += ret;
    }

  totallen += ret;

  return totallen;
}

/****************************************************************************
 * Name: usbclass_bind
 *
 * Description:
 *   Invoked when the driver is bound to a USB device driver
 *
 ****************************************************************************/

static int usbclass_bind(FAR struct usbdevclass_driver_s *driver,
                         FAR struct usbdev_s *dev)
{
  FAR struct rndis_dev_s *priv = ((FAR struct rndis_driver_s *)driver)->dev;
  FAR struct rndis_req_s *reqcontainer;
  irqstate_t flags;
  size_t reqlen;
  int ret;
  int i;

  usbtrace(TRACE_CLASSBIND, 0);

  /* Bind the structures */

  priv->usbdev   = dev;

  /* Save the reference to our private data structure in EP0 so that it
   * can be recovered in ep0 completion events (Unless we are part of
   * a composite device and, in that case, the composite device owns
   * EP0).
   */

#ifndef CONFIG_RNDIS_COMPOSITE
  dev->ep0->priv = priv;
#endif

  /* Preallocate control request */

  priv->ctrlreq = usbdev_allocreq(dev->ep0, RNDIS_CTRLREQ_LEN);
  if (priv->ctrlreq == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCCTRLREQ), 0);
      ret = -ENOMEM;
      goto errout;
    }

  priv->ctrlreq->callback = usbclass_ep0incomplete;

  /* Pre-allocate all endpoints... the endpoints will not be functional
   * until the SET CONFIGURATION request is processed in usbclass_setconfig.
   * This is done here because there may be calls to kmm_malloc and the SET
   * CONFIGURATION processing probably occurs within interrupt handling
   * logic where kmm_malloc calls will fail.
   */

  /* Pre-allocate the IN interrupt endpoint */

  priv->epintin = DEV_ALLOCEP(dev,
                    USB_EPIN(priv->devinfo.epno[RNDIS_EP_INTIN_IDX]),
                    true, USB_EP_ATTR_XFER_INT);
  if (!priv->epintin)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINALLOCFAIL), 0);
      ret = -ENODEV;
      goto errout;
    }

  priv->epintin->priv = priv;

  priv->epintin_req =
    usbdev_allocreq(priv->epintin, sizeof(struct rndis_notification));
  if (priv->epintin_req == NULL)
  {
    usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM);
    ret = -ENOMEM;
    goto errout;
  }

  priv->epintin_req->callback = usbclass_epintin_complete;

  /* Pre-allocate the IN bulk endpoint */

  priv->epbulkin = DEV_ALLOCEP(dev,
                      USB_EPIN(priv->devinfo.epno[RNDIS_EP_BULKIN_IDX]),
                      true, USB_EP_ATTR_XFER_BULK);
  if (!priv->epbulkin)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINALLOCFAIL), 0);
      ret = -ENODEV;
      goto errout;
    }

  priv->epbulkin->priv = priv;

  /* Pre-allocate the OUT bulk endpoint */

  priv->epbulkout =
    DEV_ALLOCEP(dev, USB_EPOUT(priv->devinfo.epno[RNDIS_EP_BULKOUT_IDX]),
                false, USB_EP_ATTR_XFER_BULK);
  if (!priv->epbulkout)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTALLOCFAIL), 0);
      ret = -ENODEV;
      goto errout;
    }

  priv->epbulkout->priv = priv;

  /* Pre-allocate read requests.  The buffer size is one full packet. */

#if defined(CONFIG_USBDEV_SUPERSPEED)
  if (dev->speed == USB_SPEED_SUPER ||
      dev->speed == USB_SPEED_SUPER_PLUS)
    {
      if (CONFIG_RNDIS_EPBULKOUT_MAXBURST < USB_SS_BULK_EP_MAXBURST)
        {
          reqlen = CONFIG_RNDIS_EPBULKOUT_SSSIZE *
                   (CONFIG_RNDIS_EPBULKOUT_MAXBURST + 1);
        }
      else
        {
          reqlen = CONFIG_RNDIS_EPBULKOUT_SSSIZE * USB_SS_BULK_EP_MAXBURST;
        }
    }
  else
#endif
#if defined(CONFIG_USBDEV_DUALSPEED)
  if (dev->speed == USB_SPEED_HIGH)
    {
      reqlen = CONFIG_RNDIS_EPBULKOUT_HSSIZE;
    }
  else
#endif
    {
      reqlen = CONFIG_RNDIS_EPBULKOUT_FSSIZE;
    }

  if (CONFIG_RNDIS_BULKOUT_REQLEN > reqlen)
    {
      reqlen = CONFIG_RNDIS_BULKOUT_REQLEN;
    }

  priv->rdreq = usbdev_allocreq(priv->epbulkout, reqlen);
  if (priv->rdreq == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDALLOCREQ), -ENOMEM);
      ret = -ENOMEM;
      goto errout;
    }

  priv->rdreq->callback = rndis_rdcomplete;

  /* Pre-allocate write request containers and put in a free list.
   * The buffer size should be larger than a full packet.  Otherwise,
   * we will send a bogus null packet at the end of each packet.
   *
   * Pick the larger of the max packet size and the configured request
   * size.
   */

  if (CONFIG_IOB_BUFSIZE >= CONFIG_RNDIS_BULKIN_REQLEN)
    {
      reqlen = 0;
    }
  else
    {
#if defined(CONFIG_USBDEV_SUPERSPEED)
      if (dev->speed == USB_SPEED_SUPER ||
          dev->speed == USB_SPEED_SUPER_PLUS)
        {
          if (CONFIG_RNDIS_EPBULKIN_MAXBURST < USB_SS_BULK_EP_MAXBURST)
            {
              reqlen = CONFIG_RNDIS_EPBULKIN_SSSIZE *
                       (CONFIG_RNDIS_EPBULKIN_MAXBURST + 1);
            }
          else
            {
              reqlen = CONFIG_RNDIS_EPBULKIN_SSSIZE *
                       USB_SS_BULK_EP_MAXBURST;
            }
        }
      else
    #endif
    #if defined(CONFIG_USBDEV_DUALSPEED)
      if (dev->speed == USB_SPEED_HIGH)
        {
          reqlen = CONFIG_RNDIS_EPBULKIN_HSSIZE;
        }
      else
    #endif
        {
          reqlen = CONFIG_RNDIS_EPBULKIN_FSSIZE;
        }
    }

  if (CONFIG_RNDIS_BULKIN_REQLEN > reqlen)
    {
      reqlen = CONFIG_RNDIS_BULKIN_REQLEN;
    }

  for (i = 0; i < CONFIG_RNDIS_NWRREQS; i++)
    {
      reqcontainer      = &priv->wrreqs[i];
      reqcontainer->req = usbdev_allocreq(priv->epbulkin, reqlen);

      if (reqcontainer->req == NULL)
        {
          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_WRALLOCREQ), -ENOMEM);
          ret = -ENOMEM;
          goto errout;
        }

      reqcontainer->buf           = reqcontainer->req->buf;
      reqcontainer->req->priv     = reqcontainer;
      reqcontainer->req->callback = rndis_wrcomplete;

      flags = enter_critical_section();
      sq_addlast((FAR sq_entry_t *)reqcontainer, &priv->reqlist);
      leave_critical_section(flags);
    }

  /* Initialize response queue to empty */

  priv->response_queue_words = 0;

  /* Report if we are selfpowered */

#ifndef CONFIG_RNDIS_COMPOSITE
#ifdef CONFIG_USBDEV_SELFPOWERED
  DEV_SETSELFPOWERED(dev);
#endif

  /* And pull-up the data line for the soft connect function */

  DEV_CONNECT(dev);
#endif

  return OK;

errout:
  usbclass_unbind(driver, dev);
  return ret;
}

/****************************************************************************
 * Name: usbclass_unbind
 *
 * Description:
 *    Invoked when the driver is unbound from a USB device driver
 *
 ****************************************************************************/

static void usbclass_unbind(FAR struct usbdevclass_driver_s *driver,
                            FAR struct usbdev_s *dev)
{
  FAR struct rndis_dev_s *priv;
  FAR struct rndis_req_s *reqcontainer;
  irqstate_t flags;

  usbtrace(TRACE_CLASSUNBIND, 0);

#ifdef CONFIG_DEBUG_FEATURES
  if (!driver || !dev || !dev->ep0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract reference to private data */

  priv = ((FAR struct rndis_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG_FEATURES
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* Make sure that we are not already unbound */

  if (priv != NULL)
    {
      /* 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 usbclass_resetconfig
       * should cause the endpoints to immediately terminate all
       * transfers and return the requests to us (with result == -ESHUTDOWN)
       */

      usbclass_resetconfig(priv);
      up_mdelay(50);

      /* Free the pre-allocated control request */

      if (priv->ctrlreq != NULL)
        {
          usbdev_freereq(dev->ep0, priv->ctrlreq);
          priv->ctrlreq = NULL;
        }

      if (priv->epintin_req != NULL)
        {
          usbdev_freereq(priv->epintin, priv->epintin_req);
          priv->epintin_req = NULL;
        }

      /* Free pre-allocated read requests (which should all have
       * been returned to the free list at this time -- we don't check)
       */

      if (priv->rdreq)
        {
          usbdev_freereq(priv->epbulkout, priv->rdreq);
        }

      /* Free write requests that are not in use (which should be all
       * of them
       */

      flags = enter_critical_section();
      while (!sq_empty(&priv->reqlist))
        {
          reqcontainer = (struct rndis_req_s *)sq_remfirst(&priv->reqlist);
          if (reqcontainer->req != NULL)
            {
              reqcontainer->req->buf = reqcontainer->buf;
              usbdev_freereq(priv->epbulkin, reqcontainer->req);
            }
        }

      leave_critical_section(flags);

      /* Free the interrupt IN endpoint */

      if (priv->epintin)
        {
          DEV_FREEEP(dev, priv->epintin);
          priv->epintin = NULL;
        }

      /* Free the bulk IN endpoint */

      if (priv->epbulkin)
        {
          DEV_FREEEP(dev, priv->epbulkin);
          priv->epbulkin = NULL;
        }

      /* Free the bulk OUT endpoint */

      if (priv->epbulkout)
        {
          DEV_FREEEP(dev, priv->epbulkout);
          priv->epbulkout = NULL;
        }
    }
}

/****************************************************************************
 * Name: usbclass_setup
 *
 * Description:
 *   Invoked for ep0 control requests.  This function probably executes
 *   in the context of an interrupt handler.
 *
 ****************************************************************************/

static int usbclass_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 rndis_dev_s *priv;
  FAR struct usbdev_req_s *ctrlreq;
  uint16_t value;
  uint16_t len;
  int ret = -EOPNOTSUPP;

#ifdef CONFIG_DEBUG_FEATURES
  if (!driver || !dev || !dev->ep0 || !ctrl)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return -EIO;
    }
#endif

  /* Extract reference to private data */

  usbtrace(TRACE_CLASSSETUP, ctrl->req);
  priv = ((FAR struct rndis_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG_FEATURES
  if (!priv || !priv->ctrlreq)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
      return -ENODEV;
    }
#endif
  ctrlreq = priv->ctrlreq;
  ctrlreq->priv = 0;

  /* Extract the little-endian 16-bit values to host order */

  value = GETUINT16(ctrl->value);
  len   = GETUINT16(ctrl->len);

  uinfo("type=%02x req=%02x value=%04x len=%04x\n",
        ctrl->type, ctrl->req, value, len);

  switch (ctrl->type & USB_REQ_TYPE_MASK)
    {
     /***********************************************************************
      * Standard Requests
      ***********************************************************************/

    case USB_REQ_TYPE_STANDARD:
      {
        switch (ctrl->req)
          {
          case USB_REQ_GETDESCRIPTOR:
            {
              /* The value field specifies the descriptor type in the MS byte
               * and the descriptor index in the LS byte (order is little
               * endian)
               */

              switch (ctrl->value[1])
                {
#ifndef CONFIG_RNDIS_COMPOSITE
                case USB_DESC_TYPE_DEVICE:
                  {
                    ret = usbdev_copy_devdesc(ctrlreq->buf,
                                              &g_devdesc,
                                              dev->speed);
                  }
                  break;
#endif

                /* If the serial device is used in as part of a composite
                 * device, then the configuration descriptor is provided by
                 * logic in the composite device implementation.
                 */

#ifndef CONFIG_RNDIS_COMPOSITE
#  ifdef CONFIG_USBDEV_DUALSPEED
                case USB_DESC_TYPE_OTHERSPEEDCONFIG:
#  endif /* CONFIG_USBDEV_DUALSPEED */
                case USB_DESC_TYPE_CONFIG:
                  {
                    ret = usbclass_mkcfgdesc(ctrlreq->buf, &priv->devinfo,
                                             dev->speed, ctrl->value[1]);
                  }
                  break;
#endif

#ifndef CONFIG_RNDIS_COMPOSITE
                case USB_DESC_TYPE_STRING:
                  {
                    /* index == language code. */

                    ret = usbclass_mkstrdesc(ctrl->value[0],
                                  (FAR struct usb_strdesc_s *)ctrlreq->buf);
                  }
                  break;
#endif

                default:
                  {
                    usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_GETUNKNOWNDESC),
                             value);
                  }
                  break;
                }
            }
            break;

          case USB_REQ_SETCONFIGURATION:
            {
              if (ctrl->type == 0)
                {
                  ret = usbclass_setconfig(priv, value);
                }
            }
            break;

          /* If the serial device is used in as part of a composite device,
           * then the overall composite class configuration is managed by
           * logic in the composite device implementation.
           */

#ifndef CONFIG_RNDIS_COMPOSITE
          case USB_REQ_GETCONFIGURATION:
            {
              if (ctrl->type == USB_DIR_IN)
                {
                  *(FAR uint8_t *)ctrlreq->buf = priv->config;
                  ret = 1;
                }
            }
            break;
#endif

          default:
            usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDSTDREQ),
                     ctrl->req);
            break;
          }
      }
      break;

    /* Class requests */

    case USB_REQ_TYPE_CLASS:
      {
        if ((ctrl->type & USB_REQ_RECIPIENT_MASK) ==
            USB_REQ_RECIPIENT_INTERFACE)
          {
            if (ctrl->req == RNDIS_SEND_ENCAPSULATED_COMMAND)
              {
                ret = rndis_handle_control_message(priv, dataout, outlen);
              }
            else if (ctrl->req == RNDIS_GET_ENCAPSULATED_RESPONSE)
              {
                if (priv->response_queue_words == 0)
                  {
                    /* No reply available is indicated with a single
                     * 0x00 byte.
                     */

                    ret = 1;
                    ctrlreq->buf[0] = 0;
                  }
                else
                  {
                    /* Retrieve a single reply from the response queue to
                     * control request buffer.
                     */

                    FAR struct rndis_response_header *hdr =
                      (struct rndis_response_header *)priv->response_queue;
                    memcpy(ctrlreq->buf, hdr, hdr->msglen);
                    ctrlreq->priv = priv;
                    ret = hdr->msglen;
                  }
              }
          }
      }
      break;

    default:
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_UNSUPPORTEDTYPE), ctrl->type);
      break;
    }

  /* Respond to the setup command if data was returned.  On an error return
   * value (ret < 0), the USB driver will stall.
   */

  if (ret >= 0)
    {
      /* Configure the response */

      ctrlreq->len   = MIN(len, ret);
      ctrlreq->flags = USBDEV_REQFLAGS_NULLPKT;

      /* Send the response -- either directly to the USB controller or
       * indirectly in the case where this class is a member of a composite
       * device.
       */

#ifndef CONFIG_RNDIS_COMPOSITE
      ret = EP_SUBMIT(dev->ep0, ctrlreq);
#else
      ret = composite_ep0submit(driver, dev, ctrlreq, ctrl);
#endif
      if (ret < 0)
        {
          usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPRESPQ), (uint16_t)-ret);
          ctrlreq->result = OK;
          usbclass_ep0incomplete(dev->ep0, ctrlreq);
        }
    }

  return ret;
}

/****************************************************************************
 * Name: usbclass_disconnect
 *
 * Description:
 *   Invoked after all transfers have been stopped, when the host is
 *   disconnected.  This function is probably called from the context of an
 *   interrupt handler.
 *
 ****************************************************************************/

static void usbclass_disconnect(FAR struct usbdevclass_driver_s *driver,
                                FAR struct usbdev_s *dev)
{
  FAR struct rndis_dev_s *priv;
  irqstate_t flags;

  usbtrace(TRACE_CLASSDISCONNECT, 0);

#ifdef CONFIG_DEBUG_FEATURES
  if (!driver || !dev || !dev->ep0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return;
    }
#endif

  /* Extract reference to private data */

  priv = ((FAR struct rndis_driver_s *)driver)->dev;

#ifdef CONFIG_DEBUG_FEATURES
  if (!priv)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EP0NOTBOUND), 0);
      return;
    }
#endif

  /* Inform the "upper half" network driver that we have lost the USB
   * connection.
   */

  priv->netdev.d_ifdown(&priv->netdev);

  flags = enter_critical_section();

  /* Reset the configuration */

  usbclass_resetconfig(priv);

  leave_critical_section(flags);

  /* Perform the soft connect function so that we will we can be
   * re-enumerated (unless we are part of a composite device)
   */

#ifndef CONFIG_RNDIS_COMPOSITE
  DEV_CONNECT(dev);
#endif
}

/****************************************************************************
 * Name: usbclass_resetconfig
 *
 * Description:
 *   Mark the device as not configured and disable all endpoints.
 *
 ****************************************************************************/

static void usbclass_resetconfig(FAR struct rndis_dev_s *priv)
{
  /* Are we configured? */

  if (priv->config != RNDIS_CONFIGIDNONE)
    {
      /* Yes.. but not anymore */

      priv->config = RNDIS_CONFIGIDNONE;

      priv->netdev.d_ifdown(&priv->netdev);

      /* Disable endpoints.  This should force completion of all pending
       * transfers.
       */

      EP_DISABLE(priv->epintin);
      EP_DISABLE(priv->epbulkin);
      EP_DISABLE(priv->epbulkout);
    }
}

/****************************************************************************
 * Name: usbclass_setconfig
 *
 * Description:
 *   Set the device configuration by allocating and configuring endpoints and
 *   by allocating and queue read and write requests.
 *
 ****************************************************************************/

static int usbclass_setconfig(FAR struct rndis_dev_s *priv, uint8_t config)
{
  struct usb_ss_epdesc_s epdesc;
  int ret = 0;

#ifdef CONFIG_DEBUG_FEATURES
  if (priv == NULL)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_INVALIDARG), 0);
      return -EIO;
    }
#endif

  if (config == priv->config)
    {
      /* Already configured -- Do nothing */

      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALREADYCONFIGURED), 0);
      return 0;
    }

  /* Discard the previous configuration data */

  usbclass_resetconfig(priv);

  /* Was this a request to simply discard the current configuration? */

  if (config == RNDIS_CONFIGIDNONE)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGNONE), 0);
      return 0;
    }

  /* We only accept one configuration */

  if (config != RNDIS_CONFIGID)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_CONFIGIDBAD), 0);
      return -EINVAL;
    }

  /* Configure the IN interrupt endpoint */

  usbdev_copy_epdesc(&epdesc.epdesc,
                     priv->devinfo.epno[RNDIS_EP_INTIN_IDX],
                     priv->usbdev->speed,
                     &g_rndis_epintindesc);
  ret = EP_CONFIGURE(priv->epintin, &epdesc.epdesc, false);
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPINTINCONFIGFAIL), 0);
      goto errout;
    }

  priv->epintin->priv = priv;

  /* Configure the IN bulk endpoint */

  usbdev_copy_epdesc(&epdesc.epdesc,
                     priv->devinfo.epno[RNDIS_EP_BULKIN_IDX],
                     priv->usbdev->speed,
                     &g_rndis_epbulkindesc);
  ret = EP_CONFIGURE(priv->epbulkin, &epdesc.epdesc, false);
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKINCONFIGFAIL), 0);
      goto errout;
    }

  priv->epbulkin->priv = priv;

  /* Configure the OUT bulk endpoint */

  usbdev_copy_epdesc(&epdesc.epdesc,
                     priv->devinfo.epno[RNDIS_EP_BULKOUT_IDX],
                     priv->usbdev->speed,
                     &g_rndis_epbulkoutdesc);
  ret = EP_CONFIGURE(priv->epbulkout, &epdesc.epdesc, true);
  if (ret < 0)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_EPBULKOUTCONFIGFAIL), 0);
      goto errout;
    }

  priv->epbulkout->priv = priv;

  /* Queue read requests in the bulk OUT endpoint */

  priv->rdreq->callback = rndis_rdcomplete;
  ret = rndis_submit_rdreq(priv);
  if (ret != OK)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_RDSUBMIT), (uint16_t)-ret);
      goto errout;
    }

  /* We are successfully configured */

  priv->config = config;
  if (priv->netdev.d_ifup(&priv->netdev) == OK)
    {
      priv->netdev.d_flags |= IFF_UP;
    }

  return OK;

errout:
  usbclass_resetconfig(priv);
  return ret;
}

/****************************************************************************
 * Name: usbclass_classobject
 *
 * Description:
 *   Allocate memory for the RNDIS driver class object
 *
 * Returned Value:
 *   0 on success, negative error code on failure.
 *
 ****************************************************************************/

static int usbclass_classobject(int minor,
                                FAR struct usbdev_devinfo_s *devinfo,
                                FAR struct usbdevclass_driver_s **classdev)
{
  FAR struct rndis_alloc_s *alloc;
  FAR struct rndis_dev_s *priv;
  FAR struct rndis_driver_s *drvr;
  int ret;

  /* Allocate the structures needed */

  alloc = kmm_zalloc(sizeof(struct rndis_alloc_s));
  if (!alloc)
    {
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_ALLOCDEVSTRUCT), 0);
      return -ENOMEM;
    }

  /* Convenience pointers into the allocated blob */

  priv = &alloc->dev;
  drvr = &alloc->drvr;
  *classdev = &drvr->drvr;

  /* Get device info */

  memcpy(&priv->devinfo, devinfo, sizeof(struct usbdev_devinfo_s));

  /* Initialize the USB ethernet driver structure */

  sq_init(&priv->reqlist);
  memcpy(priv->host_mac_address, g_rndis_default_mac_addr, 6);
  priv->netdev.d_private = priv;
  priv->netdev.d_ifup = &rndis_ifup;
  priv->netdev.d_ifdown = &rndis_ifdown;
  priv->netdev.d_txavail = &rndis_txavail;

  /* MAC address filtering is purposefully left out of this driver. Since
   * in the RNDIS USB scenario there are only two devices in the network
   * (host and us), there shouldn't be any packets received that don't
   * belong to us.
   */

  /* Initialize the USB class driver structure */

#if defined(CONFIG_USBDEV_SUPERSPEED)
  drvr->drvr.speed         = USB_SPEED_SUPER;
#elif defined(CONFIG_USBDEV_DUALSPEED)
  drvr->drvr.speed         = USB_SPEED_HIGH;
#else
  drvr->drvr.speed         = USB_SPEED_FULL;
#endif

  drvr->drvr.ops           = &g_driverops;
  drvr->dev                = priv;

  ret = netdev_register(&priv->netdev, NET_LL_ETHERNET);
  if (ret)
    {
      uerr("Failed to register net device");
    }

  return ret;
}

static void usbclass_uninitialize(FAR struct usbdevclass_driver_s *classdev)
{
  FAR struct rndis_driver_s *drvr = (FAR struct rndis_driver_s *)classdev;
  FAR struct rndis_alloc_s *alloc = (FAR struct rndis_alloc_s *)drvr->dev;

  netdev_unregister(&drvr->dev->netdev);
  kmm_free(alloc);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: usbdev_rndis_initialize
 *
 * Description:
 *   Initialize the RNDIS USB device driver.
 *
 * Input Parameters:
 *   mac_address: pointer to an array of six octets which is the MAC address
 *                of the host side of the interface. May be NULL to use the
 *                default MAC address.
 *
 * Returned Value:
 *   0 on success, -errno on failure
 *
 ****************************************************************************/

#ifndef CONFIG_RNDIS_COMPOSITE
int usbdev_rndis_initialize(FAR const uint8_t *mac_address)
{
  int ret;
  FAR struct usbdevclass_driver_s *classdev;
  FAR struct rndis_driver_s *drvr;
  struct usbdev_devinfo_s devinfo;

  memset(&devinfo, 0, sizeof(struct usbdev_devinfo_s));
  devinfo.ninterfaces                = RNDIS_NINTERFACES;
  devinfo.nstrings                   = RNDIS_NSTRIDS;
  devinfo.nendpoints                 = RNDIS_NUM_EPS;
  devinfo.epno[RNDIS_EP_INTIN_IDX]   = CONFIG_RNDIS_EPINTIN;
  devinfo.epno[RNDIS_EP_BULKIN_IDX]  = CONFIG_RNDIS_EPBULKIN;
  devinfo.epno[RNDIS_EP_BULKOUT_IDX] = CONFIG_RNDIS_EPBULKOUT;

  ret = usbclass_classobject(0, &devinfo, &classdev);
  if (ret)
    {
      nerr("usbclass_classobject failed: %d\n", ret);
      return ret;
    }

  drvr = (FAR struct rndis_driver_s *)classdev;

  if (mac_address)
    {
      memcpy(drvr->dev->host_mac_address, mac_address, 6);
    }

  ret = usbdev_register(&drvr->drvr);
  if (ret)
    {
      nerr("usbdev_register failed: %d\n", ret);
      usbtrace(TRACE_CLSERROR(USBSER_TRACEERR_DEVREGISTER), (uint16_t)-ret);
      usbclass_uninitialize(classdev);
      return ret;
    }

  return OK;
}
#endif

/****************************************************************************
 * Name: usbdev_rndis_set_host_mac_addr
 *
 * Description:
 *   Set host MAC address. Mainly for use with composite devices where
 *   the MAC cannot be given directly to usbdev_rndis_initialize().
 *
 * Input Parameters:
 *   netdev:      pointer to the network interface. Can be obtained from
 *                e.g. netdev_findbyname().
 *
 *   mac_address: pointer to an array of six octets which is the MAC address
 *                of the host side of the interface. May be NULL to use the
 *                default MAC address.
 *
 * Returned Value:
 *   0 on success, -errno on failure
 *
 ****************************************************************************/

int usbdev_rndis_set_host_mac_addr(FAR struct net_driver_s *netdev,
                                   FAR const uint8_t *mac_address)
{
  FAR struct rndis_dev_s *dev = (FAR struct rndis_dev_s *)netdev;

  if (mac_address)
    {
      memcpy(dev->host_mac_address, mac_address, 6);
    }
  else
    {
      memcpy(dev->host_mac_address, g_rndis_default_mac_addr, 6);
    }

  return OK;
}

/****************************************************************************
 * Name: usbdev_rndis_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_RNDIS_COMPOSITE
void usbdev_rndis_get_composite_devdesc(struct composite_devdesc_s *dev)
{
  memset(dev, 0, sizeof(struct composite_devdesc_s));

  /* The callback functions for the RNDIS class.
   *
   * classobject() and uninitialize() must be provided by board-specific
   * logic
   */

  dev->mkconfdesc          = usbclass_mkcfgdesc;
  dev->mkstrdesc           = usbclass_mkstrdesc;
  dev->classobject         = usbclass_classobject;
  dev->uninitialize        = usbclass_uninitialize;

  dev->nconfigs            = RNDIS_NCONFIGS; /* Number of configurations supported  */
  dev->configid            = RNDIS_CONFIGID; /* The only supported configuration ID */

  /* Let the construction function calculate the size of config descriptor */

  dev->cfgdescsize         = usbclass_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 = RNDIS_NINTERFACES;

  /* Strings.
   *
   * strbase must be provided by board-specific logic
   */

  dev->devinfo.nstrings    = 0;

  /* Endpoints.
   *
   * Endpoint numbers must be provided by board-specific logic.
   */

  dev->devinfo.nendpoints  = RNDIS_NUM_EPS;
}
#endif