1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

usbus_cdc_ecm: initial CDC ECM functionality

This commit is contained in:
Koen Zandberg 2019-02-27 22:19:12 +01:00
parent 06d541a117
commit 748cacd54b
No known key found for this signature in database
GPG Key ID: 0895A893E6D2985B
6 changed files with 517 additions and 0 deletions

View File

@ -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

View File

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

View File

@ -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 <koen@bergzand.net>
*/
#ifndef USB_USBUS_CDC_ECM_H
#define USB_USBUS_CDC_ECM_H
#include <stdint.h>
#include <stdlib.h>
#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 */
/** @} */

View File

@ -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

View File

@ -0,0 +1,3 @@
MODULE = usbus_cdc_ecm
include $(RIOTBASE)/Makefile.base

View File

@ -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 <koen@bergzand.net>
* @}
*/
#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 <string.h>
#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;
}
}