slipdev.c 5.86 KB
/*
 * Copyright (C) 2017 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 <errno.h>
#include <string.h>

#include "log.h"
#include "slipdev.h"

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

#define SLIP_END               (0xc0U)
#define SLIP_ESC               (0xdbU)
#define SLIP_END_ESC           (0xdcU)
#define SLIP_ESC_ESC           (0xddU)

static int _send(netdev_t *dev, const struct iovec *vector, unsigned count);
static int _recv(netdev_t *dev, void *buf, size_t len, void *info);
static int _init(netdev_t *dev);
static void _isr(netdev_t *dev);
static int _get(netdev_t *dev, netopt_t opt, void *value, size_t max_len);
static int _set(netdev_t *dev, netopt_t opt, const void *value,
                size_t value_len);

static const netdev_driver_t slip_driver = {
    .send = _send,
    .recv = _recv,
    .init = _init,
    .isr = _isr,
    .get = _get,
    .set = _set,
};

void slipdev_setup(slipdev_t *dev, const slipdev_params_t *params)
{
    /* set device descriptor fields */
    memcpy(&dev->config, params, sizeof(dev->config));
    dev->inbytes = 0U;
    dev->inesc = 0U;
    dev->netdev.driver = &slip_driver;
}

static inline void _add_byte_to_inbuf(slipdev_t *dev, uint8_t byte)
{
    if (ringbuffer_add_one(&dev->inbuf, byte) < 0) {
        dev->inbytes++;
    }
}

static void _slip_rx_cb(void *arg, uint8_t data)
{
    slipdev_t *dev = arg;

    if ((data == SLIP_END) && (dev->netdev.event_callback != NULL)) {
        int idx = cib_put(&dev->pktfifo_idx);
        if (idx >= 0) {
            dev->netdev.event_callback((netdev_t *)dev, NETDEV_EVENT_ISR);
            dev->pktfifo[idx] = dev->inbytes;
        }
        else {
            /* can't handover packet => dropping it */
            ringbuffer_remove(&dev->inbuf, dev->inbytes);
        }
        dev->inbytes = 0;
    }
    else if (dev->inesc) {
        dev->inesc = 0U;
        uint8_t actual = (data == SLIP_END_ESC) ? SLIP_END :
                         ((data == SLIP_ESC_ESC) ? SLIP_ESC : 0);

        switch (data) {
            case SLIP_END_ESC:
            case SLIP_ESC_ESC:
                _add_byte_to_inbuf(dev, actual);
                break;
            default:
                break;
        }
    }
    else if (data == SLIP_ESC) {
        dev->inesc = 1U;
    }
    else {
        _add_byte_to_inbuf(dev, data);
    }
}

static int _init(netdev_t *netdev)
{
    slipdev_t *dev = (slipdev_t *)netdev;

    DEBUG("slipdev: initializing device %p on UART %i with baudrate %" PRIu32 "\n",
          (void *)dev, dev->config.uart, dev->config.baudrate);
    /* initialize buffers */
    ringbuffer_init(&dev->inbuf, dev->rxmem, sizeof(dev->rxmem));
    cib_init(&dev->pktfifo_idx, SLIPDEV_PKTFIFO_SIZE);
    if (uart_init(dev->config.uart, dev->config.baudrate, _slip_rx_cb,
                  dev) != UART_OK) {
        LOG_ERROR("slipdev: error initializing UART %i with baudrate %" PRIu32 "\n",
                  dev->config.uart, dev->config.baudrate);
        return -ENODEV;
    }
    return 0;
}

static inline void _write_byte(slipdev_t *dev, uint8_t byte)
{
    uart_write(dev->config.uart, &byte, 1);
}

static int _send(netdev_t *netdev, const struct iovec *vector, unsigned count)
{
    slipdev_t *dev = (slipdev_t *)netdev;
    int bytes = 0;

    DEBUG("slipdev: sending vector of length %u\n", count);
    for (unsigned i = 0; i < count; i++) {
        uint8_t *data = vector[i].iov_base;

        for (unsigned j = 0; j < vector[i].iov_len; j++, data++) {
            switch(*data) {
                case SLIP_END:
                    /* escaping END byte*/
                    _write_byte(dev, SLIP_ESC);
                    _write_byte(dev, SLIP_END_ESC);
                    break;
                case SLIP_ESC:
                    /* escaping ESC byte*/
                    _write_byte(dev, SLIP_ESC);
                    _write_byte(dev, SLIP_ESC_ESC);
                    break;
                default:
                    _write_byte(dev, *data);
            }
            bytes++;
        }
    }
    _write_byte(dev, SLIP_END);
    return bytes;
}

static int _recv(netdev_t *netdev, void *buf, size_t len, void *info)
{
    slipdev_t *dev = (slipdev_t *)netdev;
    int res, idx = cib_peek(&dev->pktfifo_idx);

    (void)info;
    if (idx < 0) {
        return -EFAULT;
    }
    if (buf == NULL) {
        if (len > 0) {
            /* drop packet */
            cib_get(&dev->pktfifo_idx);
            /* and remove data */
            res = ringbuffer_remove(&dev->inbuf, len);
        }
        else {
            res = dev->pktfifo[idx];
        }
    }
    else if (len < dev->pktfifo[idx]) {
        res = -ENOBUFS;
    }
    else {
        size_t bytes = dev->pktfifo[cib_get(&dev->pktfifo_idx)];
        bytes = ringbuffer_get(&dev->inbuf, buf, bytes);
        res = bytes;
    }
    return res;
}

static void _isr(netdev_t *netdev)
{
    DEBUG("slipdev: handling ISR event\n");
    if (netdev->event_callback != NULL) {
        DEBUG("slipdev: event handler set, issuing RX_COMPLETE event\n");
        netdev->event_callback(netdev, NETDEV_EVENT_RX_COMPLETE);
    }
}

static int _get(netdev_t *netdev, netopt_t opt, void *value, size_t max_len)
{
    (void)netdev;
    (void)value;
    (void)max_len;
    switch (opt) {
        case NETOPT_IS_WIRED:
            return 1;
        case NETOPT_DEVICE_TYPE:
            assert(max_len == sizeof(uint16_t));
            *((uint16_t *)value) = NETDEV_TYPE_SLIP;
            return sizeof(uint16_t);
        default:
            return -ENOTSUP;
    }
}

static int _set(netdev_t *netdev, netopt_t opt, const void *value,
                size_t value_len)
{
    (void)netdev;
    (void)opt;
    (void)value;
    (void)value_len;
    return -ENOTSUP;
}

/** @} */