lwip_netdev.c 8.56 KB
/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * 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.
 */

/**
 * @{
 *
 * @file
 * @author  Martine Lenders <mlenders@inf.fu-berlin.de>
 */

#include <assert.h>
#include <sys/uio.h>
#include <inttypes.h>

#include "lwip/err.h"
#include "lwip/ethip6.h"
#include "lwip/netif.h"
#include "lwip/netif/netdev.h"
#include "lwip/opt.h"
#include "lwip/pbuf.h"
#include "netif/etharp.h"
#include "netif/lowpan6.h"

#include "net/eui64.h"
#include "net/ieee802154.h"
#include "net/ipv6/addr.h"
#include "net/netdev.h"
#include "net/netopt.h"
#include "utlist.h"
#include "thread.h"

#define ENABLE_DEBUG                (0)
#include "debug.h"

#define LWIP_NETDEV_NAME            "lwip_netdev_mux"
#define LWIP_NETDEV_PRIO            (THREAD_PRIORITY_MAIN - 4)
#define LWIP_NETDEV_STACKSIZE       (THREAD_STACKSIZE_DEFAULT)
#define LWIP_NETDEV_QUEUE_LEN       (8)
#define LWIP_NETDEV_MSG_TYPE_EVENT 0x1235

#define ETHERNET_IFNAME1 'E'
#define ETHERNET_IFNAME2 'T'

static kernel_pid_t _pid = KERNEL_PID_UNDEF;
static char _stack[LWIP_NETDEV_STACKSIZE];
static msg_t _queue[LWIP_NETDEV_QUEUE_LEN];
static char _tmp_buf[LWIP_NETDEV_BUFLEN];

#ifdef MODULE_NETDEV_ETH
static err_t _eth_link_output(struct netif *netif, struct pbuf *p);
#endif
#ifdef MODULE_LWIP_SIXLOWPAN
static err_t _ieee802154_link_output(struct netif *netif, struct pbuf *p);
#endif
static void _event_cb(netdev_t *dev, netdev_event_t event);
static void *_event_loop(void *arg);

err_t lwip_netdev_init(struct netif *netif)
{
    LWIP_ASSERT("netif != NULL", (netif != NULL));
    LWIP_ASSERT("netif->state != NULL", (netif->state != NULL));
    netdev_t *netdev;
    uint16_t dev_type;
    err_t res = ERR_OK;

    /* start multiplexing thread (only one needed) */
    if (_pid <= KERNEL_PID_UNDEF) {
        _pid = thread_create(_stack, LWIP_NETDEV_STACKSIZE, LWIP_NETDEV_PRIO,
                             THREAD_CREATE_STACKTEST, _event_loop, netif,
                             LWIP_NETDEV_NAME);
        if (_pid <= 0) {
            return ERR_IF;
        }
    }

    /* initialize netdev and netif */
    netdev = (netdev_t *)netif->state;
    netdev->driver->init(netdev);
    netdev->event_callback = _event_cb;
    if (netdev->driver->get(netdev, NETOPT_DEVICE_TYPE, &dev_type,
                            sizeof(dev_type)) < 0) {
        return ERR_IF;
    }
#if LWIP_NETIF_HOSTNAME
    netif->hostname = "riot";
#endif /* LWIP_NETIF_HOSTNAME */

    /* XXX: for now assume its Ethernet, since netdev is implemented only by ethernet drivers */
    switch (dev_type) {
#ifdef MODULE_NETDEV_ETH
        case NETDEV_TYPE_ETHERNET:
            netif->name[0] = ETHERNET_IFNAME1;
            netif->name[1] = ETHERNET_IFNAME2;
            netif->hwaddr_len = (u8_t)netdev->driver->get(netdev, NETOPT_ADDRESS, netif->hwaddr,
                                                          sizeof(netif->hwaddr));
            if (netif->hwaddr_len > sizeof(netif->hwaddr)) {
                return ERR_IF;
            }
            /* TODO: get from driver (currently not in netdev_eth) */
            netif->mtu = ETHERNET_DATA_LEN;
            netif->linkoutput = _eth_link_output;
#if LWIP_IPV4
            netif->output = etharp_output;
#endif
#if LWIP_IPV6
            netif->output_ip6 = ethip6_output;
            netif_create_ip6_linklocal_address(netif, 1);   /* 1: hwaddr is 48-bit MAC addr */
#endif
            netif->flags |= NETIF_FLAG_BROADCAST;
            netif->flags |= NETIF_FLAG_ETHARP;
            netif->flags |= NETIF_FLAG_ETHERNET;
            break;
#endif
#ifdef MODULE_LWIP_SIXLOWPAN
        case NETDEV_TYPE_IEEE802154:
        {
            u16_t val;
            ipv6_addr_t *addr;
            if (netdev->driver->get(netdev, NETOPT_NID, &val,
                                    sizeof(val)) < 0) {
                return ERR_IF;
            }
            lowpan6_set_pan_id(val);
            netif->hwaddr_len = (u8_t)netdev->driver->get(netdev, NETOPT_ADDRESS_LONG,
                                                          netif->hwaddr, sizeof(netif->hwaddr));
            if (netif->hwaddr_len > sizeof(netif->hwaddr)) {
                return ERR_IF;
            }
            netif->linkoutput = _ieee802154_link_output;
            res = lowpan6_if_init(netif);
            if (res != ERR_OK) {
                return res;
            }
            /* assure usage of long address as source address */
            val = netif->hwaddr_len;
            if (netdev->driver->set(netdev, NETOPT_SRC_LEN, &val, sizeof(val)) < 0) {
                return ERR_IF;
            }
            /* netif_create_ip6_linklocal_address() does weird byte-swapping
             * with full IIDs, so let's do it ourselves */
            addr = (ipv6_addr_t *)&(netif->ip6_addr[0]);
            if (netdev->driver->get(netdev, NETOPT_IPV6_IID, &addr->u8[8], sizeof(eui64_t)) < 0) {
                return ERR_IF;
            }
            ipv6_addr_set_link_local_prefix(addr);
            /* Set address state. */
#if LWIP_IPV6_DUP_DETECT_ATTEMPTS
            /* Will perform duplicate address detection (DAD). */
            netif->ip6_addr_state[0] = IP6_ADDR_TENTATIVE;
#else
            /* Consider address valid. */
            netif->ip6_addr_state[0] = IP6_ADDR_PREFERRED;
#endif /* LWIP_IPV6_AUTOCONFIG */
            break;
        }
#endif
        default:
            return ERR_IF;  /* device type not supported yet */
    }
    netif->flags |= NETIF_FLAG_UP;
    netif->flags |= NETIF_FLAG_LINK_UP;
    netif->flags |= NETIF_FLAG_IGMP;
    netif->flags |= NETIF_FLAG_MLD6;
    netdev->context = netif;
#if LWIP_IPV6_AUTOCONFIG
    netif->ip6_autoconfig_enabled = 1;
#endif

    return res;
}

#ifdef MODULE_NETDEV_ETH
static err_t _eth_link_output(struct netif *netif, struct pbuf *p)
{
    netdev_t *netdev = (netdev_t *)netif->state;
    struct pbuf *q;
    unsigned int count = 0;

#if ETH_PAD_SIZE
    pbuf_header(p, -ETH_PAD_SIZE); /* drop the padding word */
#endif
    LL_COUNT(p, q, count);
    struct iovec pkt[count];
    for (q = p, count = 0; q != NULL; q = q->next, count++) {
        pkt[count].iov_base = q->payload;
        pkt[count].iov_len = (size_t)q->len;
    }
#if ETH_PAD_SIZE
    pbuf_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endif
    return (netdev->driver->send(netdev, pkt, count) > 0) ? ERR_OK : ERR_BUF;
}
#endif

#ifdef MODULE_LWIP_SIXLOWPAN
static err_t _ieee802154_link_output(struct netif *netif, struct pbuf *p)
{
    LWIP_ASSERT("p->next == NULL", p->next == NULL);
    netdev_t *netdev = (netdev_t *)netif->state;
    struct iovec pkt = {
        .iov_base = p->payload,
        .iov_len = (p->len - IEEE802154_FCS_LEN),   /* FCS is written by driver */
    };

    return (netdev->driver->send(netdev, &pkt, 1) > 0) ? ERR_OK : ERR_BUF;
}
#endif

static struct pbuf *_get_recv_pkt(netdev_t *dev)
{
    int len = dev->driver->recv(dev, _tmp_buf, sizeof(_tmp_buf), NULL);

    if (len < 0) {
        DEBUG("lwip_netdev: an error occurred while reading the packet\n");
        return NULL;
    }
    assert(((unsigned)len) <= UINT16_MAX);
    struct pbuf *p = pbuf_alloc(PBUF_RAW, (u16_t)len, PBUF_POOL);

    if (p == NULL) {
        DEBUG("lwip_netdev: can not allocate in pbuf\n");
        return NULL;
    }
    pbuf_take(p, _tmp_buf, len);
    return p;
}

static void _event_cb(netdev_t *dev, netdev_event_t event)
{
    if (event == NETDEV_EVENT_ISR) {
        assert(_pid != KERNEL_PID_UNDEF);
        msg_t msg;

        msg.type = LWIP_NETDEV_MSG_TYPE_EVENT;
        msg.content.ptr = dev;

        if (msg_send(&msg, _pid) <= 0) {
            DEBUG("lwip_netdev: possibly lost interrupt.\n");
        }
    }
    else {
        struct netif *netif = dev->context;
        switch (event) {
            case NETDEV_EVENT_RX_COMPLETE: {
                struct pbuf *p = _get_recv_pkt(dev);
                if (p == NULL) {
                    DEBUG("lwip_netdev: error receiving packet\n");
                    return;
                }
                if (netif->input(p, netif) != ERR_OK) {
                    DEBUG("lwip_netdev: error inputing packet\n");
                    return;
                }
            }
            break;
            default:
                break;
        }
    }
}

static void *_event_loop(void *arg)
{
    (void)arg;
    msg_init_queue(_queue, LWIP_NETDEV_QUEUE_LEN);
    while (1) {
        msg_t msg;
        msg_receive(&msg);
        if (msg.type == LWIP_NETDEV_MSG_TYPE_EVENT) {
            netdev_t *dev = msg.content.ptr;
            dev->driver->isr(dev);
        }
    }
    return NULL;
}

/** @} */