918ae4cc84
This commit fixes the regression from https://github.com/apache/nuttx/pull/13190 Signed-off-by: yangsong8 <yangsong8@xiaomi.com>
3116 lines
87 KiB
C
3116 lines
87 KiB
C
/****************************************************************************
|
|
* 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
|