From 9b68dec385e2b413b384fe97cd9f8a5c3ffc5888 Mon Sep 17 00:00:00 2001 From: Koen Zandberg Date: Wed, 27 Feb 2019 22:19:35 +0100 Subject: [PATCH] usbus_cdc_ecm: Provide netdev integration --- sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c | 199 +++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 sys/usb/usbus/cdc/ecm/cdc_ecm_netdev.c 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, +};