diff --git a/Makefile.dep b/Makefile.dep index a96437a497..b584e6553b 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -838,6 +838,14 @@ ifneq (,$(filter usbus,$(USEMODULE))) USEMODULE += event endif +ifneq (,$(filter usbus_cdc_ecm,$(USEMODULE))) + USEMODULE += iolist + USEMODULE += fmt + USEMODULE += usbus + USEMODULE += netdev_eth + USEMODULE += luid +endif + ifneq (,$(filter uuid,$(USEMODULE))) USEMODULE += hashes USEMODULE += random diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c index 30a2106e7c..8a75f881c6 100644 --- a/sys/auto_init/auto_init.c +++ b/sys/auto_init/auto_init.c @@ -261,6 +261,11 @@ void auto_init(void) auto_init_kw2xrf(); #endif +#ifdef MODULE_USBUS_CDC_ECM + extern void auto_init_netdev_cdcecm(void); + auto_init_netdev_cdcecm(); +#endif + #ifdef MODULE_NETDEV_TAP extern void auto_init_netdev_tap(void); auto_init_netdev_tap(); diff --git a/sys/auto_init/netif/auto_init_cdcecm.c b/sys/auto_init/netif/auto_init_cdcecm.c new file mode 100644 index 0000000000..5bf9e2229c --- /dev/null +++ b/sys/auto_init/netif/auto_init_cdcecm.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + * + */ + +/** + * @ingroup sys_auto_init_gnrc_netif + * @{ + * + * @file + * @brief Auto initialization for USB CDC ECM module + * + * @author Koen Zandberg + */ + +#ifdef MODULE_USBUS_CDC_ECM + +#include "log.h" +#include "usb/usbus/cdc/ecm.h" +#include "net/gnrc/netif/ethernet.h" + +/** + * @brief global cdc ecm object, declared in the usb auto init file + */ +extern usbus_cdcecm_device_t cdcecm; + +/** + * @brief Define stack parameters for the MAC layer thread + * @{ + */ +#define CDCECM_MAC_STACKSIZE (THREAD_STACKSIZE_DEFAULT) +#ifndef CDCECM_MAC_PRIO +#define CDCECM_MAC_PRIO (GNRC_NETIF_PRIO) +#endif + +/** + * @brief Stacks for the MAC layer threads + */ +static char _netdev_eth_stack[CDCECM_MAC_STACKSIZE]; +extern void cdcecm_netdev_setup(usbus_cdcecm_device_t *cdcecm); + +void auto_init_netdev_cdcecm(void) +{ + LOG_DEBUG("[auto_init_netif] initializing cdc ecm #0\n"); + + cdcecm_netdev_setup(&cdcecm); + /* initialize netdev<->gnrc adapter state */ + gnrc_netif_ethernet_create(_netdev_eth_stack, CDCECM_MAC_STACKSIZE, + CDCECM_MAC_PRIO, "cdcecm", &cdcecm.netdev); +} + +#else +typedef int dont_be_pedantic; +#endif /* MODULE_CDC_ECM */ +/** @} */ diff --git a/sys/auto_init/usb/auto_init_usb.c b/sys/auto_init/usb/auto_init_usb.c index 5eb5d1edad..6d85d3622b 100644 --- a/sys/auto_init/usb/auto_init_usb.c +++ b/sys/auto_init/usb/auto_init_usb.c @@ -24,6 +24,11 @@ #include "usb/usbus.h" +#ifdef MODULE_USBUS_CDC_ECM +#include "usb/usbus/cdc/ecm.h" +usbus_cdcecm_device_t cdcecm; +#endif + static char _stack[USBUS_STACKSIZE]; static usbus_t usbus; @@ -36,6 +41,10 @@ void auto_init_usb(void) /* Initialize basic usbus struct, don't start the thread yet */ usbus_init(&usbus, usbdev); + /* USBUS function handlers initialization */ +#ifdef MODULE_USBUS_CDC_ECM + usbus_cdcecm_init(&usbus, &cdcecm); +#endif /* Finally initialize USBUS thread */ usbus_create(_stack, USBUS_STACKSIZE, USBUS_PRIO, USBUS_TNAME, &usbus); diff --git a/sys/include/usb/cdc.h b/sys/include/usb/cdc.h new file mode 100644 index 0000000000..51d2e71464 --- /dev/null +++ b/sys/include/usb/cdc.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2018 Dylan Laduranty + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @defgroup usb_cdc CDC - USB communications device class + * @ingroup usb + * @brief Generic USB CDC defines and helpers + * + * @{ + * + * @file + * @brief Definition for USB CDC interfaces + * + * @author Dylan Laduranty + * @author Koen Zandberg + */ + +#ifndef USB_CDC_H +#define USB_CDC_H + +#include + +#include "usb.h" +#include "usb/descriptor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define USB_TYPE_DESCRIPTOR_CDC 0x24 /**< USB CDC type descriptor*/ +#define USB_CDC_VERSION_BCD 0x0120 /**< USB CDC version in BCD */ + +/** + * @name USB CDC subclass types + * @anchor usb_cdc_subtype + * @{ + */ +#define USB_CDC_SUBCLASS_NONE 0x00 /**< No subclass */ +#define USB_CDC_SUBCLASS_DLCM 0x01 /**< Direct Line Control Model */ +#define USB_CDC_SUBCLASS_ACM 0x02 /**< Abstract Control Model */ +#define USB_CDC_SUBCLASS_TCM 0x03 /**< Telephone Control Model */ +#define USB_CDC_SUBCLASS_MCCM 0x04 /**< Multi-Channel Control Model */ +#define USB_CDC_SUBCLASS_CCM 0x05 /**< CAPI Control Mode */ +#define USB_CDC_SUBCLASS_ENCM 0x06 /**< Eth Networking Control Model */ +#define USB_CDC_SUBCLASS_ANCM 0x07 /**< ATM Networking Control Model */ +#define USB_CDC_SUBCLASS_WHCM 0x08 /**< Wireless Handset Control Model */ +#define USB_CDC_SUBCLASS_DM 0x09 /**< Device Management */ +#define USB_CDC_SUBCLASS_MDLM 0x0A /**< Mobile Direct Line Model */ +#define USB_CDC_SUBCLASS_OBEX 0x0B /**< OBEX */ +#define USB_CDC_SUBCLASS_EEM 0x0C /**< Ethernet Emulation Model */ +#define USB_CDC_SUBCLASS_NCM 0x0D /**< Network Control Model */ +/** @} */ + +/** + * @name USB CDC protocol types + * @{ + */ +#define USB_CDC_PROTOCOL_NONE 0x00 /**< No protocol required */ +#define USB_CDC_PROTOCOL_ITU 0x01 /**< AT Commands: V.250 etc */ +#define USB_CDC_PROTOCOL_PCCA 0x02 /**< AT Commands defined by PCCA-101 */ +#define USB_CDC_PROTOCOL_PCCA_A 0x03 /**< AT Commands defined by PCCA-101 & Annex O */ +#define USB_CDC_PROTOCOL_GSM 0x04 /**< AT Commands defined by GSM 07.07 */ +#define USB_CDC_PROTOCOL_3GPP 0x05 /**< AT Commands defined by 3GPP 27.007 */ +#define USB_CDC_PROTOCOL_CS 0x06 /**< AT Commands defined by TIA for CDMA */ +#define USB_CDC_PROTOCOL_EEM 0x07 /**< Ethernet Emulation Model */ +#define USB_CDC_PROTOCOL_EXT 0xFE /**< External Protocol */ +#define USB_CDC_PROTOCOL_VENDOR 0xFF /**< Vendor-specific */ +/** @} */ + +/** + * @name USB CDC descriptor subtypes + */ +#define USB_CDC_DESCR_SUBTYPE_FUNCTIONAL 0x00 /**< Header functional + * descriptor */ +#define USB_CDC_DESCR_SUBTYPE_CALL_MGMT 0x01 /**< Call management + descriptor */ +#define USB_CDC_DESCR_SUBTYPE_ACM 0x02 /**< Abstract control + management descriptor */ +#define USB_CDC_DESCR_SUBTYPE_UNION 0x06 /**< Union descriptor */ +#define USB_CDC_DESCR_SUBTYPE_ETH_NET 0x0f /**< Ethernet descriptor */ +/** @} */ + +/** + * @name USB CDC management requests + * @{ + */ + +/** + * @brief Set ethernet multicast filter request + */ +#define USB_CDC_MGNT_REQUEST_SET_ETH_MULTICAST_FILTER 0x40 + +/** + * @brief Set ethernet power management pattern filter + */ +#define USB_CDC_MGNT_REQUEST_SET_ETH_PM_PATTERN_FILTER 0x41 + +/** + * @brief Get ethernet power management pattern filter + */ +#define USB_CDC_MGNT_REQUEST_GET_ETH_PM_PATTERN_FILTER 0x42 + +/** + * @brief Set ethernet packet filter + */ +#define USB_CDC_MGNT_REQUEST_SET_ETH_PACKET_FILTER 0x43 + +/** + * @brief Get ethernet statistics + */ +#define USB_CDC_MGNT_REQUEST_GET_ETH_STATISTICS 0x44 +/** @} */ + +/** + * @name USB CDC management notifications + * @{ + */ + +/** + * @brief Network connection status notification + */ +#define USB_CDC_MGNT_NOTIF_NETWORK_CONNECTION 0x00 + +/** + * @brief Response available notification + */ +#define USB_CDC_MGNT_NOTIF_RESPONSE_AVAILABLE 0x01 + +/** + * @brief Hook on the auxiliary phone changed notification + */ +#define USB_CDC_MGNT_NOTIF_AUX_JACK_HOOK_STATE 0x08 + +/** + * @brief Ring voltage on the POTS line interface notification + */ +#define USB_CDC_MGNT_NOTIF_RING_DETECT 0x09 + +/** + * @brief Asynchronous UART status notification + */ +#define USB_CDC_MGNT_NOTIF_SERIAL_STATE 0x20 + +/** + * @brief Call state change notification + */ +#define USB_CDC_MGNT_NOTIF_CALL_STATE_CHANGE 0x28 + +/** + * @brief Line state change notification + */ +#define USB_CDC_MGNT_NOTIF_LINE_STATE_CHANGE 0x29 + +/** + * @brief Throughput change notification + */ +#define USB_CDC_MGNT_NOTIF_CONN_SPEED_CHANGE 0x2A + +/** @} */ + +/** + * @brief USB CDC ECM descriptor + * + * @see USB CDC 1.2 ECM spec table 3 + */ +typedef struct __attribute__((packed)) { + uint8_t length; /**< Size of this descriptor */ + uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_CDC) */ + uint8_t subtype; /**< Descriptor subtype (@ref USB_CDC_DESCR_SUBTYPE_ETH_NET) */ + uint8_t macaddress; /**< Index of the string containing the ethernet MAC address */ + uint32_t ethernetstatistics; /**< Bitmap indicating the statistics caps */ + uint16_t maxsegmentsize; /**< Maximum segment size of the interface */ + uint16_t numbermcfilters; /**< Number of configurable multicast filters */ + uint8_t numberpowerfilters; /**< Number of pattern filters for host wake-up */ +} usb_desc_ecm_t; + +/** + * @brief USB CDC ACM descriptor + * + * @see USB CDC 1.2 PTSN spec table 4 + */ +typedef struct __attribute__((packed)) { + uint8_t length; /**< Size of this descriptor */ + uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_CDC) */ + uint8_t subtype; /**< Descriptor subtype (@ref USB_CDC_DESCR_SUBTYPE_ACM) */ + uint8_t capabalities; /**< Bitmap indicating the capabilities */ +} usb_desc_acm_t; + +/** + * @brief Generic USB CDC descriptor + * + * @see USB CDC 1.2 spec table 15 + */ +typedef struct __attribute__((packed)) { + uint8_t length; /**< Size of this descriptor */ + uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_CDC) */ + uint8_t subtype; /**< Descriptor subtype (@ref usb_cdc_subtype) */ + uint16_t bcd_cdc; /**< CDC release number in bcd (@ref USB_CDC_VERSION_BCD) */ +} usb_desc_cdc_t; + +/** + * @brief USB union descriptor + */ +typedef struct __attribute__((packed)) { + uint8_t length; /**< Size of this descriptor */ + uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_CDC) */ + uint8_t subtype; /**< Descriptor subtype (@ref USB_CDC_DESCR_SUBTYPE_UNION) */ + uint8_t master_if; /**< Master/controlling interface number */ + uint8_t slave_if; /**< Slave/subordinate interface number */ +} usb_desc_union_t; + +/** + * @brief USB CDC call management functional descriptor + * + * @see USB CDC 1.2 PSTN spec table 13 + */ +typedef struct __attribute__((packed)) { + uint8_t length; /**< Size of this descriptor */ + uint8_t type; /**< Descriptor type (@ref USB_TYPE_DESCRIPTOR_CDC) */ + uint8_t subtype; /**< Descriptor subtype (@ref USB_CDC_DESCR_SUBTYPE_CALL_MGMT) */ + uint8_t capabalities; /**< Supported capabilities */ + uint8_t data_if; /**< Interface number used for the call management */ +} usb_desc_call_mngt_t; + +/** + * @brief USB CDC ECM connection speed change notification + * + * @see USB CDC 1.2 spec section 6.3.3 + */ +typedef struct __attribute__((packed)) { + usb_setup_t setup; /**< Setup request header for the notification */ + uint32_t down; /**< Downlink bit rate */ + uint32_t up; /**< Uplink bit rate */ +} usb_desc_cdcecm_speed_t; + +#ifdef __cplusplus +} +#endif + +#endif /* USB_CDC_H */ +/** @} */ diff --git a/sys/include/usb/descriptor.h b/sys/include/usb/descriptor.h index 627b30d031..886e6a44dd 100644 --- a/sys/include/usb/descriptor.h +++ b/sys/include/usb/descriptor.h @@ -40,6 +40,7 @@ extern "C" { #define USB_SETUP_REQ_SET_CONFIGURATION 0x09 /**< Set configuration */ #define USB_SETUP_REQ_GET_INTERFACE 0x0a /**< Get interface */ #define USB_SETUP_REQ_SET_INTERFACE 0x0b /**< Set interface */ +#define USB_SETUP_REQ_SYNCH_FRAME 0x0c /**< Synch frame */ /** @} */ /** diff --git a/sys/include/usb/usbus/cdc/ecm.h b/sys/include/usb/usbus/cdc/ecm.h new file mode 100644 index 0000000000..c74920f59e --- /dev/null +++ b/sys/include/usb/usbus/cdc/ecm.h @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser General + * Public License v2.1. See the file LICENSE in the top level directory for + * more details. + */ + +/** + * @defgroup usbus_cdc_ecm USBUS CDC ECM - USBUS CDC ethernet control model + * @ingroup usb + * @brief USBUS CDC ECM interface module + * + * @{ + * + * @file + * @brief Interface and definitions for USB CDC ECM type interfaces + * + * @author Koen Zandberg + */ + +#ifndef USB_USBUS_CDC_ECM_H +#define USB_USBUS_CDC_ECM_H + +#include +#include +#include "net/ethernet.h" +#include "net/ethernet/hdr.h" +#include "usb/descriptor.h" +#include "usb/usbus.h" +#include "net/netdev.h" +#include "mutex.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Link throughput as reported by the peripheral + * + * This defines a common up and down link throughput in bits/second. The USB + * peripheral will report this to the host. This doesn't affect the actual + * throughput, only what the peripheral reports to the host. + */ +#ifndef USBUS_CDC_ECM_CONFIG_SPEED +#define USBUS_CDC_ECM_CONFIG_SPEED 1000000 +#endif + +/** + * @brief Link download speed as reported by the peripheral + */ +#ifndef USBUS_CDC_ECM_CONFIG_SPEED_DOWNSTREAM +#define USBUS_CDC_ECM_CONFIG_SPEED_DOWNSTREAM USBUS_CDC_ECM_CONFIG_SPEED +#endif + +/** + * @brief Link upload speed as reported by the peripheral + */ +#ifndef USBUS_CDC_ECM_CONFIG_SPEED_UPSTREAM +#define USBUS_CDC_ECM_CONFIG_SPEED_UPSTREAM USBUS_CDC_ECM_CONFIG_SPEED +#endif + +/** + * @brief CDC ECM interrupt endpoint size. + * + * Used by the device to report events to the host. + * + * @note Must be at least 16B to allow for reporting the link throughput + */ +#define USBUS_CDCECM_EP_CTRL_SIZE 16 + +/** + * @brief CDC ECM bulk data endpoint size. + * + * Used for the transfer of network frames. + */ +#define USBUS_CDCECM_EP_DATA_SIZE 64 + +/** + * @brief notification state, used to track which information must be send to + * the host + */ +typedef enum { + USBUS_CDCECM_NOTIF_NONE, /**< Nothing notified so far */ + USBUS_CDCECM_NOTIF_LINK_UP, /**< Link status is notified */ + USBUS_CDCECM_NOTIF_SPEED, /**< Link speed is notified */ +} usbus_cdcecm_notif_t; + +/** + * @brief USBUS CDC ECM device interface context + */ +typedef struct usbus_cdcecm_device { + usbus_handler_t handler_ctrl; /**< Control interface handler */ + usbus_interface_t iface_data; /**< Data interface */ + usbus_interface_t iface_ctrl; /**< Control interface */ + usbus_interface_alt_t iface_data_alt; /**< Data alternative (active) interface */ + usbus_endpoint_t *ep_in; /**< Data endpoint in */ + usbus_endpoint_t *ep_out; /**< Data endpoint out */ + usbus_endpoint_t *ep_ctrl; /**< Control endpoint */ + usbus_hdr_gen_t ecm_hdr; /**< ECM header generator */ + event_t rx_flush; /**< Receive flush event */ + event_t tx_xmit; /**< Transmit ready event */ + netdev_t netdev; /**< Netdev context struct */ + uint8_t mac_netdev[ETHERNET_ADDR_LEN]; /**< this device's MAC address */ + char mac_host[13]; /**< host side's MAC address as string */ + usbus_string_t mac_str; /**< String context for the host side mac address */ + usbus_t *usbus; /**< Ptr to the USBUS context */ + mutex_t out_lock; /**< mutex used for locking netif/USBUS send */ + size_t tx_len; /**< Length of the current tx frame */ + uint8_t in_buf[ETHERNET_FRAME_LEN]; /**< Buffer for the received frames */ + size_t len; /**< Length of the current rx frame */ + usbus_cdcecm_notif_t notif; /**< Startup message notification tracker */ + unsigned active_iface; /**< Current active data interface */ +} usbus_cdcecm_device_t; + +/** + * @brief CDC ECM initialization function + * + * @param usbus USBUS thread to use + * @param handler CDCECM device struct + */ +void usbus_cdcecm_init(usbus_t *usbus, usbus_cdcecm_device_t *handler); + +#ifdef __cplusplus +} +#endif + +#endif /* USB_USBUS_CDC_ECM_H */ +/** @} */ diff --git a/sys/usb/usbus/Makefile b/sys/usb/usbus/Makefile index b7b193117f..8ee522ad25 100644 --- a/sys/usb/usbus/Makefile +++ b/sys/usb/usbus/Makefile @@ -1,4 +1,7 @@ SRCS := usbus.c SRCS += usbus_hdrs.c +ifneq (,$(filter usbus_cdc_ecm,$(USEMODULE))) + DIRS += cdc/ecm +endif include $(RIOTBASE)/Makefile.base diff --git a/sys/usb/usbus/cdc/ecm/Makefile b/sys/usb/usbus/cdc/ecm/Makefile new file mode 100644 index 0000000000..9d001e77aa --- /dev/null +++ b/sys/usb/usbus/cdc/ecm/Makefile @@ -0,0 +1,3 @@ +MODULE = usbus_cdc_ecm + +include $(RIOTBASE)/Makefile.base diff --git a/sys/usb/usbus/cdc/ecm/cdc_ecm.c b/sys/usb/usbus/cdc/ecm/cdc_ecm.c new file mode 100644 index 0000000000..58b4c1ad1e --- /dev/null +++ b/sys/usb/usbus/cdc/ecm/cdc_ecm.c @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup usbus_cdc_ecm + * @{ + * @file USBUS implementation for ethernet control model + * + * @author Koen Zandberg + * @} + */ + +#include "event.h" +#include "fmt.h" +#include "kernel_defines.h" +#include "luid.h" +#include "net/ethernet.h" +#include "net/eui48.h" +#include "usb/cdc.h" +#include "usb/descriptor.h" +#include "usb/usbus.h" +#include "usb/usbus/control.h" +#include "usb/usbus/cdc/ecm.h" + +#include + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static void _event_handler(usbus_t *usbus, usbus_handler_t *handler, + usbus_event_usb_t event); +static int _setup_handler(usbus_t *usbus, usbus_handler_t *handler, + usbus_setuprq_state_t state, usb_setup_t *setup); +static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, + usbdev_ep_t *ep, usbus_event_transfer_t event); +static void _init(usbus_t *usbus, usbus_handler_t *handler); +static void _handle_rx_flush_ev(event_t *ev); +static void _handle_tx_xmit(event_t *ev); + +static size_t _gen_full_ecm_descriptor(usbus_t *usbus, void *arg); + +static const usbus_hdr_gen_funcs_t _ecm_descriptor = { + .get_header = _gen_full_ecm_descriptor, + .len = { + .fixed_len = sizeof(usb_desc_cdc_t) + + sizeof(usb_desc_union_t) + + sizeof(usb_desc_ecm_t), + }, + .len_type = USBUS_HDR_LEN_FIXED, +}; + +static size_t _gen_union_descriptor(usbus_t *usbus, usbus_cdcecm_device_t *cdcecm) +{ + usb_desc_union_t uni; + + /* functional union descriptor */ + uni.length = sizeof(usb_desc_union_t); + uni.type = USB_TYPE_DESCRIPTOR_CDC; + uni.subtype = USB_CDC_DESCR_SUBTYPE_UNION; + uni.master_if = cdcecm->iface_ctrl.idx; + uni.slave_if = cdcecm->iface_data.idx; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&uni, sizeof(uni)); + return sizeof(usb_desc_union_t); +} + +static size_t _gen_ecm_descriptor(usbus_t *usbus, usbus_cdcecm_device_t *cdcecm) +{ + usb_desc_ecm_t ecm; + + /* functional cdc ecm descriptor */ + ecm.length = sizeof(usb_desc_ecm_t); + ecm.type = USB_TYPE_DESCRIPTOR_CDC; + ecm.subtype = USB_CDC_DESCR_SUBTYPE_ETH_NET; + ecm.macaddress = cdcecm->mac_str.idx; + ecm.ethernetstatistics = 0; + ecm.maxsegmentsize = ETHERNET_FRAME_LEN; + ecm.numbermcfilters = 0x0000; /* No filtering */ + ecm.numberpowerfilters = 0; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&ecm, sizeof(ecm)); + return sizeof(usb_desc_ecm_t); +} + +static size_t _gen_cdc_descriptor(usbus_t *usbus) +{ + usb_desc_cdc_t cdc; + /* functional cdc descriptor */ + cdc.length = sizeof(usb_desc_cdc_t); + cdc.bcd_cdc = USB_CDC_VERSION_BCD; + cdc.type = USB_TYPE_DESCRIPTOR_CDC; + cdc.subtype = 0x00; + usbus_control_slicer_put_bytes(usbus, (uint8_t *)&cdc, sizeof(cdc)); + return sizeof(usb_desc_cdc_t); +} + +static size_t _gen_full_ecm_descriptor(usbus_t *usbus, void *arg) +{ + usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)arg; + size_t total_size = 0; + + total_size += _gen_cdc_descriptor(usbus); + total_size += _gen_union_descriptor(usbus, cdcecm); + total_size += _gen_ecm_descriptor(usbus, cdcecm); + return total_size; +} + +static void _notify_link_speed(usbus_cdcecm_device_t *cdcecm) +{ + DEBUG("CDC ECM: sending link speed indication\n"); + usb_desc_cdcecm_speed_t *notification = + (usb_desc_cdcecm_speed_t *)cdcecm->ep_ctrl->ep->buf; + notification->setup.type = USB_SETUP_REQUEST_DEVICE2HOST | + USB_SETUP_REQUEST_TYPE_CLASS | + USB_SETUP_REQUEST_RECIPIENT_INTERFACE; + notification->setup.request = USB_CDC_MGNT_NOTIF_CONN_SPEED_CHANGE; + notification->setup.value = 0; + notification->setup.index = cdcecm->iface_ctrl.idx; + notification->setup.length = 8; + + notification->down = USBUS_CDC_ECM_CONFIG_SPEED_DOWNSTREAM; + notification->up = USBUS_CDC_ECM_CONFIG_SPEED_UPSTREAM; + usbdev_ep_ready(cdcecm->ep_ctrl->ep, + sizeof(usb_desc_cdcecm_speed_t)); + cdcecm->notif = USBUS_CDCECM_NOTIF_SPEED; +} + +static void _notify_link_up(usbus_cdcecm_device_t *cdcecm) +{ + DEBUG("CDC ECM: sending link up indication\n"); + usb_setup_t *notification = (usb_setup_t *)cdcecm->ep_ctrl->ep->buf; + notification->type = USB_SETUP_REQUEST_DEVICE2HOST | + USB_SETUP_REQUEST_TYPE_CLASS | + USB_SETUP_REQUEST_RECIPIENT_INTERFACE; + notification->request = USB_CDC_MGNT_NOTIF_NETWORK_CONNECTION; + notification->value = 1; + notification->index = cdcecm->iface_ctrl.idx; + notification->length = 0; + usbdev_ep_ready(cdcecm->ep_ctrl->ep, sizeof(usb_setup_t)); + cdcecm->notif = USBUS_CDCECM_NOTIF_LINK_UP; +} + +static const usbus_handler_driver_t cdcecm_driver = { + .init = _init, + .event_handler = _event_handler, + .transfer_handler = _transfer_handler, + .setup_handler = _setup_handler, +}; + +static void _fill_ethernet(usbus_cdcecm_device_t *cdcecm) +{ + uint8_t ethernet[ETHERNET_ADDR_LEN]; + + luid_get(ethernet, ETHERNET_ADDR_LEN); + eui48_set_local((eui48_t*)ethernet); + eui48_clear_group((eui48_t*)ethernet); + fmt_bytes_hex(cdcecm->mac_host, ethernet, sizeof(ethernet)); + +} + +void usbus_cdcecm_init(usbus_t *usbus, usbus_cdcecm_device_t *handler) +{ + assert(usbus); + assert(handler); + memset(handler, 0, sizeof(usbus_cdcecm_device_t)); + mutex_init(&handler->out_lock); + _fill_ethernet(handler); + handler->usbus = usbus; + handler->handler_ctrl.driver = &cdcecm_driver; + usbus_register_event_handler(usbus, (usbus_handler_t *)handler); +} + +static void _init(usbus_t *usbus, usbus_handler_t *handler) +{ + DEBUG("CDC ECM: intialization\n"); + usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)handler; + + /* Add event handlers */ + cdcecm->tx_xmit.handler = _handle_tx_xmit; + cdcecm->rx_flush.handler = _handle_rx_flush_ev; + + /* Set up header generators */ + cdcecm->ecm_hdr.next = NULL; + cdcecm->ecm_hdr.funcs = &_ecm_descriptor; + cdcecm->ecm_hdr.arg = cdcecm; + + /* Configure Interface 0 as control interface */ + cdcecm->iface_ctrl.class = USB_CLASS_CDC_CONTROL; + cdcecm->iface_ctrl.subclass = USB_CDC_SUBCLASS_ENCM; + cdcecm->iface_ctrl.protocol = USB_CDC_PROTOCOL_NONE; + cdcecm->iface_ctrl.hdr_gen = &cdcecm->ecm_hdr; + cdcecm->iface_ctrl.handler = handler; + + /* Configure second interface to handle data endpoint */ + cdcecm->iface_data.class = USB_CLASS_CDC_DATA; + cdcecm->iface_data.subclass = USB_CDC_SUBCLASS_NONE; + cdcecm->iface_data.protocol = USB_CDC_PROTOCOL_NONE; + cdcecm->iface_data.hdr_gen = NULL; + cdcecm->iface_data.handler = handler; + + /* Add string descriptor for the host mac */ + usbus_add_string_descriptor(usbus, &cdcecm->mac_str, cdcecm->mac_host); + + /* Create required endpoints */ + cdcecm->ep_ctrl = usbus_add_endpoint(usbus, &cdcecm->iface_ctrl, + USB_EP_TYPE_INTERRUPT, + USB_EP_DIR_IN, + USBUS_CDCECM_EP_CTRL_SIZE); + cdcecm->ep_ctrl->interval = 0x10; + + cdcecm->ep_out = usbus_add_endpoint(usbus, + (usbus_interface_t *)&cdcecm->iface_data_alt, + USB_EP_TYPE_BULK, + USB_EP_DIR_OUT, + USBUS_CDCECM_EP_DATA_SIZE); + cdcecm->ep_out->interval = 0; /* Must be 0 for bulk endpoints */ + cdcecm->ep_in = usbus_add_endpoint(usbus, + (usbus_interface_t *)&cdcecm->iface_data_alt, + USB_EP_TYPE_BULK, + USB_EP_DIR_IN, + USBUS_CDCECM_EP_DATA_SIZE); + cdcecm->ep_in->interval = 0; /* Must be 0 for bulk endpoints */ + + /* Add interfaces to the stack */ + usbus_add_interface(usbus, &cdcecm->iface_ctrl); + usbus_add_interface(usbus, &cdcecm->iface_data); + + cdcecm->iface_data.alts = &cdcecm->iface_data_alt; + + usbus_enable_endpoint(cdcecm->ep_out); + usbus_enable_endpoint(cdcecm->ep_in); + usbus_enable_endpoint(cdcecm->ep_ctrl); + usbdev_ep_ready(cdcecm->ep_out->ep, 0); + usbus_handler_set_flag(handler, USBUS_HANDLER_FLAG_RESET); +} + +static int _setup_handler(usbus_t *usbus, usbus_handler_t *handler, + usbus_setuprq_state_t state, usb_setup_t *setup) +{ + (void)usbus; + (void)state; + usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)handler; + DEBUG("CDC ECM: Request: 0x%x\n", setup->request); + switch (setup->request) { + case USB_SETUP_REQ_SET_INTERFACE: + DEBUG("CDC ECM: Changing active interface to alt %d\n", + setup->value); + cdcecm->active_iface = (uint8_t)setup->value; + if (cdcecm->active_iface == 1) { + _notify_link_up(cdcecm); + } + break; + + case USB_CDC_MGNT_REQUEST_SET_ETH_PACKET_FILTER: + /* While we do answer the request, CDC ECM filters are not really + * implemented */ + DEBUG("CDC ECM: Not modifying filter to 0x%x\n", setup->value); + break; + + default: + return -1; + } + + return 1; +} + +static int _handle_in_complete(usbus_t *usbus, usbus_handler_t *handler) +{ + (void)usbus; + usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)handler; + mutex_unlock(&cdcecm->out_lock); + return 0; +} + +static void _handle_tx_xmit(event_t *ev) +{ + + usbus_cdcecm_device_t *cdcecm = container_of(ev, usbus_cdcecm_device_t, + tx_xmit); + usbus_t *usbus = cdcecm->usbus; + + DEBUG("CDC_ECM: Handling TX xmit from netdev\n"); + if (usbus->state != USBUS_STATE_CONFIGURED || cdcecm->active_iface == 0) { + DEBUG("CDC ECM: not configured, unlocking\n"); + mutex_unlock(&cdcecm->out_lock); + } + /* Data prepared by netdev_send, signal ready to usbus */ + usbdev_ep_ready(cdcecm->ep_in->ep, cdcecm->tx_len); +} + +static void _handle_rx_flush(usbus_cdcecm_device_t *cdcecm) +{ + cdcecm->len = 0; + usbdev_ep_ready(cdcecm->ep_out->ep, 0); +} + +static void _handle_rx_flush_ev(event_t *ev) +{ + usbus_cdcecm_device_t *cdcecm = container_of(ev, usbus_cdcecm_device_t, + rx_flush); + + _handle_rx_flush(cdcecm); +} + +static void _store_frame_chunk(usbus_cdcecm_device_t *cdcecm) +{ + uint8_t *buf = cdcecm->ep_out->ep->buf; + size_t len = 0; + + usbdev_ep_get(cdcecm->ep_out->ep, USBOPT_EP_AVAILABLE, &len, + sizeof(size_t)); + memcpy(cdcecm->in_buf + cdcecm->len, buf, len); + cdcecm->len += len; + if (len < USBUS_CDCECM_EP_DATA_SIZE && cdcecm->netdev.event_callback) { + cdcecm->netdev.event_callback(&cdcecm->netdev, NETDEV_EVENT_ISR); + } +} + +static void _transfer_handler(usbus_t *usbus, usbus_handler_t *handler, + usbdev_ep_t *ep, usbus_event_transfer_t event) +{ + (void)event; /* Only receives TR_COMPLETE events */ + (void)usbus; + usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)handler; + if (ep == cdcecm->ep_out->ep) { + /* Retrieve incoming data */ + if (cdcecm->notif == USBUS_CDCECM_NOTIF_NONE) { + _notify_link_up(cdcecm); + } + size_t len = 0; + usbdev_ep_get(ep, USBOPT_EP_AVAILABLE, &len, sizeof(size_t)); + _store_frame_chunk(cdcecm); + if (len == USBUS_CDCECM_EP_DATA_SIZE) { + usbdev_ep_ready(ep, 0); + } + } + else if (ep == cdcecm->ep_in->ep) { + _handle_in_complete(usbus, handler); + } + else if (ep == cdcecm->ep_ctrl->ep && + cdcecm->notif == USBUS_CDCECM_NOTIF_LINK_UP) { + _notify_link_speed(cdcecm); + } +} + +static void _handle_reset(usbus_t *usbus, usbus_handler_t *handler) +{ + usbus_cdcecm_device_t *cdcecm = (usbus_cdcecm_device_t *)handler; + + DEBUG("CDC ECM: Reset\n"); + _handle_rx_flush(cdcecm); + _handle_in_complete(usbus, handler); + cdcecm->notif = USBUS_CDCECM_NOTIF_NONE; + cdcecm->active_iface = 0; + mutex_unlock(&cdcecm->out_lock); +} + +static void _event_handler(usbus_t *usbus, usbus_handler_t *handler, + usbus_event_usb_t event) +{ + switch (event) { + case USBUS_EVENT_USB_RESET: + _handle_reset(usbus, handler); + break; + + default: + DEBUG("Unhandled event :0x%x\n", event); + break; + } +} diff --git a/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c b/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c new file mode 100644 index 0000000000..65c575869f --- /dev/null +++ b/sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup usbus_cdc_ecm + * @{ + * @file Netdev implementation for ethernet control model + * + * @author Koen Zandberg + * @} + */ + +#include + +#include "kernel_defines.h" +#include "iolist.h" +#include "luid.h" +#include "mutex.h" +#include "net/ethernet.h" +#include "net/eui48.h" +#include "net/netdev.h" +#include "net/netdev/eth.h" +#include "usb/usbus/cdc/ecm.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static const netdev_driver_t netdev_driver_cdcecm; + +static void _signal_rx_flush(usbus_cdcecm_device_t *cdcecm) +{ + usbus_event_post(cdcecm->usbus, &cdcecm->rx_flush); +} + +static void _signal_tx_xmit(usbus_cdcecm_device_t *cdcecm) +{ + usbus_event_post(cdcecm->usbus, &cdcecm->tx_xmit); +} + +static usbus_cdcecm_device_t *_netdev_to_cdcecm(netdev_t *netdev) +{ + return container_of(netdev, usbus_cdcecm_device_t, netdev); +} + +void cdcecm_netdev_setup(usbus_cdcecm_device_t *cdcecm) +{ + cdcecm->netdev.driver = &netdev_driver_cdcecm; +} + +static int _send(netdev_t *netdev, const iolist_t *iolist) +{ + assert(iolist); + usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev); + uint8_t *buf = cdcecm->ep_in->ep->buf; + const iolist_t *iolist_start = iolist; + size_t len = iolist_size(iolist); + DEBUG("CDC_ECM_netdev: sending %u bytes\n", len); + /* load packet data into FIFO */ + size_t iol_offset = 0; + size_t usb_offset = 0; + size_t usb_remain = cdcecm->ep_in->ep->len; + DEBUG("CDC_ECM_netdev: cur iol: %d\n", iolist->iol_len); + while (len) { + mutex_lock(&cdcecm->out_lock); + if (iolist->iol_len - iol_offset > usb_remain) { + /* Only part of the iolist can be copied, usb_remain bytes */ + memcpy(buf + usb_offset, (uint8_t *)iolist->iol_base + iol_offset, + usb_remain); + + usb_offset = cdcecm->ep_in->maxpacketsize; + len -= usb_remain; + iol_offset += usb_remain; + usb_remain = 0; + } + else { + size_t bytes_copied = iolist->iol_len - iol_offset; + /* Full iolist can be copied */ + memcpy(buf + usb_offset, (uint8_t *)iolist->iol_base + iol_offset, + bytes_copied); + len -= bytes_copied; + usb_offset += bytes_copied; + usb_remain -= bytes_copied; + iol_offset = iolist->iol_len; + } + if (iol_offset == iolist->iol_len) { + /* Current iolist exhausted */ + iolist = iolist->iol_next; + if (iolist) { + DEBUG("CDC_ECM: cur iol: %d\n", iolist->iol_len); + } + iol_offset = 0; + } + if (usb_remain == 0 || !len) { + cdcecm->tx_len = usb_offset; + /* USB frame full or last frame, flush! */ + + DEBUG("CDC_ECM_NETDEV: triggering xmit with len %d\n", + cdcecm->tx_len); + _signal_tx_xmit(cdcecm); + usb_remain = cdcecm->ep_in->maxpacketsize; + usb_offset = 0; + } + else { + mutex_unlock(&cdcecm->out_lock); + } + } + /* Zero length USB packet required */ + if ((iolist_size(iolist_start) % cdcecm->ep_in->maxpacketsize) == 0) { + mutex_lock(&cdcecm->out_lock); + DEBUG("CDC ECM netdev: Zero length USB packet required\n"); + cdcecm->tx_len = 0; + _signal_tx_xmit(cdcecm); + } + return len; +} + +static int _recv(netdev_t *netdev, void *buf, size_t max_len, void *info) +{ + usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev); + + (void)info; + if (max_len == 0 && buf == NULL) { + return cdcecm->len; + } + if (max_len && buf == NULL) { + _signal_rx_flush(cdcecm); + return cdcecm->len; + } + memcpy(buf, cdcecm->in_buf, max_len); + _signal_rx_flush(cdcecm); + return max_len; +} + +static int _init(netdev_t *netdev) +{ + usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev); + + luid_get(cdcecm->mac_netdev, ETHERNET_ADDR_LEN); + eui48_set_local((eui48_t*)cdcecm->mac_netdev); + eui48_clear_group((eui48_t*)cdcecm->mac_netdev); + return 0; +} + +static int _get(netdev_t *netdev, netopt_t opt, void *value, size_t max_len) +{ + usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev); + + (void)max_len; + + switch (opt) { + case NETOPT_ADDRESS: + assert(max_len >= ETHERNET_ADDR_LEN); + memcpy(value, cdcecm->mac_netdev, ETHERNET_ADDR_LEN); + return ETHERNET_ADDR_LEN; + default: + return netdev_eth_get(netdev, opt, value, max_len); + } +} + +static int _set(netdev_t *netdev, netopt_t opt, const void *value, + size_t value_len) +{ + usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev); + + (void)cdcecm; + + switch (opt) { + case NETOPT_ADDRESS: + assert(value_len == ETHERNET_ADDR_LEN); + memcpy(cdcecm->mac_netdev, value, ETHERNET_ADDR_LEN); + return ETHERNET_ADDR_LEN; + default: + return netdev_eth_set(netdev, opt, value, value_len); + } +} + +static void _isr(netdev_t *dev) +{ + usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(dev); + + if (cdcecm->len) { + cdcecm->netdev.event_callback(&cdcecm->netdev, + NETDEV_EVENT_RX_COMPLETE); + } +} + +static const netdev_driver_t netdev_driver_cdcecm = { + .send = _send, + .recv = _recv, + .init = _init, + .isr = _isr, + .get = _get, + .set = _set, +}; diff --git a/tests/usbus_cdc_ecm/Makefile b/tests/usbus_cdc_ecm/Makefile new file mode 100644 index 0000000000..57018444dd --- /dev/null +++ b/tests/usbus_cdc_ecm/Makefile @@ -0,0 +1,28 @@ +BOARD ?= samr21-xpro +include ../Makefile.tests_common + +USEMODULE += auto_init_gnrc_netif +USEMODULE += auto_init_usbus +USEMODULE += gnrc_ipv6_router_default +USEMODULE += gnrc_icmpv6_echo +USEMODULE += usbus_cdc_ecm +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps + +# Increase the number of network interfaces in case the board under test also provides a network interface +CFLAGS += -DGNRC_NETIF_NUMOF=2 + +# USB device vendor and product ID +# pid.codes test VID/PID, not globally unique +USB_VID ?= 1209 +USB_PID ?= 0001 + +CFLAGS += -DUSB_CONFIG_VID=0x$(USB_VID) -DUSB_CONFIG_PID=0x$(USB_PID) + +include $(RIOTBASE)/Makefile.include + +ifeq ($(USB_VID):$(USB_PID), 1209:0001) + $(shell $(COLOR_ECHO) "$(COLOR_RED)Private testing pid.codes USB VID/PID used!, do not use it outside of test environments!$(COLOR_RESET)" 1>&2) + $(shell $(COLOR_ECHO) "$(COLOR_RED)MUST NOT be used on any device redistributed, sold or manufactured, VID/PID is not unique!$(COLOR_RESET)" 1>&2) +endif diff --git a/tests/usbus_cdc_ecm/README.md b/tests/usbus_cdc_ecm/README.md new file mode 100644 index 0000000000..8f0516ae20 --- /dev/null +++ b/tests/usbus_cdc_ecm/README.md @@ -0,0 +1,25 @@ +Expected result +=============== + +Use the network related shell commands to verify the network link between the +board under test and the host computer. Ping to the link local address from and +to the host computer must work. + +On the host computer, using tools such as `ethtool` must show the USB CDC ECM +interface as link dectected: + +``` +# ethtool enp0s20u9u4 +Settings for enp0s20u9u4: + Current message level: 0x00000007 (7) + drv probe link + Link detected: yes +``` + +Background +========== + +This test application can be used to verify the USBUS CDC ECM implementation. +Assuming drivers available, the board under test should show up on the host +computer as an USB network interface. Drivers are available for both Linux and +macOS. diff --git a/tests/usbus_cdc_ecm/main.c b/tests/usbus_cdc_ecm/main.c new file mode 100644 index 0000000000..252f5044f2 --- /dev/null +++ b/tests/usbus_cdc_ecm/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test application for the USBUS CDC ECM interface + * + * @author Koen Zandberg + * + * @} + */ + +#include + +#include "shell.h" +#include "msg.h" + +#define MAIN_QUEUE_SIZE (8U) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +int main(void) +{ + /* we need a message queue for the thread running the shell in order to + * receive potentially fast incoming networking packets */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + puts("Test application for the USBUS CDC ECM interface\n"); + puts("This test pulls in parts of the GNRC network stack, use the\n" + "provided shell commands (i.e. ifconfig, ping6) to interact with\n" + "the CDC ECM based network interface.\n"); + + /* start shell */ + puts("Starting the shell now..."); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +}