mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
215 lines
6.0 KiB
C
215 lines
6.0 KiB
C
/*
|
|
* 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 <koen@bergzand.net>
|
|
* @}
|
|
*/
|
|
|
|
#define USB_H_USER_IS_RIOT_INTERNAL
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "kernel_defines.h"
|
|
#include "iolist.h"
|
|
#include "mutex.h"
|
|
#include "net/ethernet.h"
|
|
#include "net/eui_provider.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;
|
|
netdev_register(&cdcecm->netdev, NETDEV_CDC_ECM, 0);
|
|
}
|
|
|
|
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->data_in;
|
|
const iolist_t *iolist_start = iolist;
|
|
size_t len = iolist_size(iolist);
|
|
/* interface with alternative function ID 1 is the interface containing the
|
|
* data endpoints, no sense trying to transmit data if it is not active */
|
|
if (cdcecm->active_iface != 1) {
|
|
return -ENOTCONN;
|
|
}
|
|
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->maxpacketsize;
|
|
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)
|
|
{
|
|
(void)info;
|
|
usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev);
|
|
|
|
size_t pktlen = cdcecm->out_urb.transferred;
|
|
|
|
if (max_len == 0 && buf == NULL) {
|
|
return pktlen;
|
|
}
|
|
if (max_len && buf == NULL) {
|
|
_signal_rx_flush(cdcecm);
|
|
return pktlen;
|
|
}
|
|
if (pktlen <= max_len) {
|
|
/* Copy the received data from the host to the netif buffer */
|
|
memcpy(buf, cdcecm->data_out, pktlen);
|
|
}
|
|
_signal_rx_flush(cdcecm);
|
|
return (pktlen <= max_len) ? (int)pktlen : -ENOBUFS;
|
|
}
|
|
|
|
static int _init(netdev_t *netdev)
|
|
{
|
|
usbus_cdcecm_device_t *cdcecm = _netdev_to_cdcecm(netdev);
|
|
|
|
netdev_eui48_get(netdev, (eui48_t*)&cdcecm->mac_netdev);
|
|
|
|
/* signal link UP */
|
|
netdev->event_callback(netdev, NETDEV_EVENT_LINK_UP);
|
|
|
|
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->out_urb.transferred) {
|
|
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,
|
|
};
|