drivers: net: ppp: Driver for point-to-point protocol

The ppp driver uses uart_pipe driver for data transfer.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2019-03-26 17:28:20 +02:00
parent 64bdad20b6
commit aa46bac54c
3 changed files with 709 additions and 0 deletions

View file

@ -2,3 +2,4 @@
zephyr_sources_ifdef(CONFIG_SLIP slip.c)
zephyr_sources_ifdef(CONFIG_NET_LOOPBACK loopback.c)
zephyr_sources_ifdef(CONFIG_NET_PPP ppp.c)

View file

@ -1,5 +1,72 @@
# Kconfig - Misc network drivers configuration options
# SPDX-License-Identifier: Apache-2.0
#
# PPP options
#
menuconfig NET_PPP
bool "Point-to-point (PPP) UART based driver"
depends on NET_L2_PPP
select UART_PIPE
select UART_INTERRUPT_DRIVEN
if NET_PPP
config NET_PPP_DRV_NAME
string "PPP Driver name"
default "ppp"
help
This option sets the driver name
config NET_PPP_UART_PIPE_BUF_LEN
int "Buffer length when reading from UART"
default 8
help
This options sets the size of the UART pipe buffer where data
is being read to.
config NET_PPP_MTU
int "PPP MTU"
default 1500
range 80 1500
help
This option sets the MTU for the PPP connection.
The value is only used when fragmenting the network
data into net_buf's.
config NET_PPP_VERIFY_FCS
bool "Verify that received FCS is valid"
default y
help
If you have a reliable link, then it might make sense
to disable this as it takes some time to verify the received
packet.
config NET_PPP_CALC_FCS
bool "Calculate FCS for the sent packet"
default y
help
If you have a reliable link, then it might make sense
to disable this as it takes some time to calculate the FCS for
the sent packet.
config PPP_MAC_ADDR
string "MAC address for the interface"
help
Specify a MAC address for the PPP interface in the form of
six hex 8-bit chars separated by colons (e.g.:
aa:33:cc:22:e2:c0). The default is an empty string, which
means the code will make 00:00:5E:00:53:XX, where XX will be
random.
module = NET_PPP
module-dep = LOG
module-str = Log level for ppp driver
module-help = Sets log level for ppp driver.
source "subsys/net/Kconfig.template.log_config.net"
endif # NET_PPP
#
# SLIP options
#

641
drivers/net/ppp.c Normal file
View file

@ -0,0 +1,641 @@
/*
* Copyright (c) 2019 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
*
* PPP driver using uart_pipe. This is meant for network connectivity between
* two network end points.
*/
#define LOG_LEVEL CONFIG_NET_PPP_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(net_ppp, LOG_LEVEL);
#include <stdio.h>
#include <kernel.h>
#include <stdbool.h>
#include <errno.h>
#include <stddef.h>
#include <net/ppp.h>
#include <net/buf.h>
#include <net/net_pkt.h>
#include <net/net_if.h>
#include <net/net_core.h>
#include <console/uart_pipe.h>
#include <crc.h>
#include "../../subsys/net/ip/net_stats.h"
#include "../../subsys/net/ip/net_private.h"
#define UART_BUF_LEN CONFIG_NET_PPP_UART_PIPE_BUF_LEN
enum ppp_driver_state {
STATE_HDLC_FRAME_START,
STATE_HDLC_FRAME_ADDRESS,
STATE_HDLC_FRAME_DATA,
};
struct ppp_driver_context {
struct net_if *iface;
/* This net_pkt contains pkt that is being read */
struct net_pkt *pkt;
/* How much free space we have in the net_pkt */
size_t available;
/* ppp data is read into this buf */
u8_t buf[UART_BUF_LEN];
/* ppp buf use when sending data */
u8_t send_buf[UART_BUF_LEN];
u8_t mac_addr[6];
struct net_linkaddr ll_addr;
#if defined(CONFIG_NET_STATISTICS_PPP)
struct net_stats_ppp stats;
#endif
enum ppp_driver_state state;
u8_t init_done : 1;
u8_t next_escaped : 1;
};
static struct ppp_driver_context ppp_driver_context_data;
static int ppp_save_byte(struct ppp_driver_context *ppp, u8_t byte)
{
int ret;
if (!ppp->pkt) {
ppp->pkt = net_pkt_rx_alloc_with_buffer(
ppp->iface,
CONFIG_NET_BUF_DATA_SIZE,
AF_UNSPEC, 0, K_NO_WAIT);
if (!ppp->pkt) {
LOG_ERR("[%p] cannot allocate pkt", ppp);
return -ENOMEM;
}
net_pkt_cursor_init(ppp->pkt);
ppp->available = net_pkt_available_buffer(ppp->pkt);
}
/* Extra debugging can be enabled separately if really
* needed. Normally it would just print too much data.
*/
if (0) {
LOG_DBG("Saving byte %02x", byte);
}
/* This is not very intuitive but we must allocate new buffer
* before we write a byte to last available cursor position.
*/
if (ppp->available == 1) {
ret = net_pkt_alloc_buffer(ppp->pkt,
CONFIG_NET_BUF_DATA_SIZE,
AF_UNSPEC, K_NO_WAIT);
if (ret < 0) {
LOG_ERR("[%p] cannot allocate new data buffer", ppp);
goto out_of_mem;
}
ppp->available = net_pkt_available_buffer(ppp->pkt);
}
if (ppp->available) {
ret = net_pkt_write_u8(ppp->pkt, byte);
if (ret < 0) {
LOG_ERR("[%p] Cannot write to pkt %p (%d)",
ppp, ppp->pkt, ret);
goto out_of_mem;
}
ppp->available--;
}
return 0;
out_of_mem:
net_pkt_unref(ppp->pkt);
ppp->pkt = NULL;
return -ENOMEM;
}
static const char *ppp_driver_state_str(enum ppp_driver_state state)
{
#if (CONFIG_NET_PPP_LOG_LEVEL >= LOG_LEVEL_DBG)
switch (state) {
case STATE_HDLC_FRAME_START:
return "START";
case STATE_HDLC_FRAME_ADDRESS:
return "ADDRESS";
case STATE_HDLC_FRAME_DATA:
return "DATA";
}
#else
ARG_UNUSED(state);
#endif
return "";
}
static void ppp_change_state(struct ppp_driver_context *ctx,
enum ppp_driver_state new_state)
{
NET_ASSERT(ctx);
if (ctx->state == new_state) {
return;
}
NET_ASSERT(new_state >= STATE_HDLC_FRAME_START &&
new_state <= STATE_HDLC_FRAME_DATA);
NET_DBG("[%p] state %s (%d) => %s (%d)",
ctx, ppp_driver_state_str(ctx->state), ctx->state,
ppp_driver_state_str(new_state), new_state);
ctx->state = new_state;
}
static int ppp_input_byte(struct ppp_driver_context *ppp, u8_t byte)
{
int ret = -EAGAIN;
switch (ppp->state) {
case STATE_HDLC_FRAME_START:
/* Synchronizing the flow with HDLC flag field */
if (byte == 0x7e) {
/* Note that we do not save the sync flag */
LOG_DBG("Sync byte (0x%02x) start", byte);
ppp_change_state(ppp, STATE_HDLC_FRAME_ADDRESS);
}
break;
case STATE_HDLC_FRAME_ADDRESS:
if (byte != 0xff) {
/* Check if we need to sync again */
if (byte == 0x7e) {
/* Just skip to the start of the pkt byte */
return -EAGAIN;
}
LOG_DBG("Invalid (0x%02x) byte, expecting Address",
byte);
/* If address is != 0xff, then ignore this
* frame. RFC 1662 ch 3.1
*/
ppp_change_state(ppp, STATE_HDLC_FRAME_START);
} else {
LOG_DBG("Address byte (0x%02x) start", byte);
ppp_change_state(ppp, STATE_HDLC_FRAME_DATA);
/* Save the address field so that we can calculate
* the FCS. The address field will not be passed
* to upper stack.
*/
ret = ppp_save_byte(ppp, byte);
if (ret < 0) {
ppp_change_state(ppp, STATE_HDLC_FRAME_START);
}
ret = -EAGAIN;
}
break;
case STATE_HDLC_FRAME_DATA:
/* If the next frame starts, then send this one
* up in the network stack.
*/
if (byte == 0x7e) {
LOG_DBG("End of pkt (0x%02x)", byte);
ppp_change_state(ppp, STATE_HDLC_FRAME_ADDRESS);
ret = 0;
} else {
if (byte == 0x7d) {
/* RFC 1662, ch. 4.2 */
ppp->next_escaped = true;
break;
}
if (ppp->next_escaped) {
/* RFC 1662, ch. 4.2 */
byte ^= 0x20;
ppp->next_escaped = false;
}
ret = ppp_save_byte(ppp, byte);
if (ret < 0) {
ppp_change_state(ppp, STATE_HDLC_FRAME_START);
}
ret = -EAGAIN;
}
break;
default:
LOG_DBG("[%p] Invalid state %d", ppp, ppp->state);
break;
}
return ret;
}
static bool ppp_check_fcs(struct ppp_driver_context *ppp)
{
struct net_buf *buf;
u16_t crc;
buf = ppp->pkt->buffer;
if (!buf) {
return false;
}
crc = crc16_ccitt(0xffff, buf->data, buf->len);
buf = buf->frags;
while (buf) {
crc = crc16_ccitt(crc, buf->data, buf->len);
buf = buf->frags;
}
if (crc != 0xf0b8) {
LOG_DBG("Invalid FCS (0x%x)", crc);
#if defined(CONFIG_NET_STATISTICS_PPP)
ppp->stats.chkerr++;
#endif
return false;
}
return true;
}
static void ppp_process_msg(struct ppp_driver_context *ppp)
{
if (LOG_LEVEL >= LOG_LEVEL_DBG) {
net_pkt_hexdump(ppp->pkt, "recv ppp");
}
if (IS_ENABLED(CONFIG_NET_PPP_VERIFY_FCS) && !ppp_check_fcs(ppp)) {
#if defined(CONFIG_NET_STATISTICS_PPP)
ppp->stats.drop++;
ppp->stats.pkts.rx++;
#endif
net_pkt_unref(ppp->pkt);
} else {
/* Remove the Address (0xff), Control (0x03) and
* FCS fields (16-bit) as the PPP L2 layer does not need
* those bytes.
*/
u16_t addr_and_ctrl = net_buf_pull_be16(ppp->pkt->buffer);
/* Currently we do not support compressed Address and Control
* fields so they must always be present.
*/
if (addr_and_ctrl != (0xff << 8 | 0x03)) {
#if defined(CONFIG_NET_STATISTICS_PPP)
ppp->stats.drop++;
ppp->stats.pkts.rx++;
#endif
net_pkt_unref(ppp->pkt);
} else {
/* Skip FCS bytes (2) */
net_buf_frag_last(ppp->pkt->buffer)->len -= 2;
/* Make sure we now start reading from PPP header in
* PPP L2 recv()
*/
net_pkt_cursor_init(ppp->pkt);
net_pkt_set_overwrite(ppp->pkt, true);
if (net_recv_data(ppp->iface, ppp->pkt) < 0) {
net_pkt_unref(ppp->pkt);
}
}
}
ppp->pkt = NULL;
}
static u8_t *ppp_recv_cb(u8_t *buf, size_t *off)
{
struct ppp_driver_context *ppp =
CONTAINER_OF(buf, struct ppp_driver_context, buf);
size_t i, len = *off;
for (i = 0; i < *off; i++) {
if (0) {
/* Extra debugging can be enabled separately if really
* needed. Normally it would just print too much data.
*/
LOG_DBG("[%zd] %02x", i, buf[i]);
}
if (ppp_input_byte(ppp, buf[i]) == 0) {
/* Ignore empty or too short frames */
if (ppp->pkt && net_pkt_get_len(ppp->pkt) > 3) {
ppp_process_msg(ppp);
break;
}
}
}
if (i == *off) {
*off = 0;
} else {
*off = len - i - 1;
memmove(&buf[0], &buf[i + 1], *off);
}
return buf;
}
static bool calc_fcs(struct net_pkt *pkt, u16_t *fcs, u16_t protocol)
{
struct net_buf *buf;
u16_t crc;
u16_t c;
buf = pkt->buffer;
if (!buf) {
return false;
}
/* HDLC Address and Control fields */
c = sys_cpu_to_be16(0xff << 8 | 0x03);
crc = crc16_ccitt(0xffff, (const u8_t *)&c, sizeof(c));
if (protocol > 0) {
crc = crc16_ccitt(crc, (const u8_t *)&protocol,
sizeof(protocol));
}
while (buf) {
crc = crc16_ccitt(crc, buf->data, buf->len);
buf = buf->frags;
}
crc ^= 0xffff;
*fcs = crc;
return true;
}
static int ppp_send_flush(struct ppp_driver_context *ppp, int off)
{
if (!IS_ENABLED(CONFIG_NET_TEST)) {
uart_pipe_send(ppp->send_buf, off);
}
return 0;
}
static int ppp_send_bytes(struct ppp_driver_context *ppp,
const u8_t *data, int len, int off)
{
int i;
for (i = 0; i < len; i++) {
ppp->send_buf[off++] = data[i];
if (off >= sizeof(ppp->send_buf)) {
off = ppp_send_flush(ppp, off);
}
}
return off;
}
static u16_t ppp_escape_byte(u8_t byte, int *offset)
{
if (byte == 0x7e || byte == 0x7d || byte < 0x20) {
*offset = 0;
return (0x7d << 8) | (byte ^ 0x20);
}
*offset = 1;
return byte;
}
static int ppp_send(struct device *dev, struct net_pkt *pkt)
{
struct ppp_driver_context *ppp = dev->driver_data;
struct net_buf *buf = pkt->buffer;
u16_t protocol = 0;
int send_off = 0;
u32_t sync_addr_ctrl;
u16_t fcs, escaped;
u8_t byte;
int i, offset;
ARG_UNUSED(dev);
if (!buf) {
/* No data? */
return -ENODATA;
}
/* If the packet is a normal network packet, we must add the protocol
* value here.
*/
if (!net_pkt_is_ppp(pkt)) {
if (net_pkt_family(pkt) == AF_INET) {
protocol = htons(PPP_IP);
} else if (net_pkt_family(pkt) == AF_INET6) {
protocol = htons(PPP_IPV6);
} else {
return -EPROTONOSUPPORT;
}
}
if (!calc_fcs(pkt, &fcs, protocol)) {
return -ENOMEM;
}
/* Sync, Address & Control fields */
sync_addr_ctrl = sys_cpu_to_be32(0x7e << 24 | 0xff << 16 |
0x7d << 8 | 0x23);
send_off = ppp_send_bytes(ppp, (const u8_t *)&sync_addr_ctrl,
sizeof(sync_addr_ctrl), send_off);
if (protocol > 0) {
escaped = htons(ppp_escape_byte(protocol, &offset));
send_off = ppp_send_bytes(ppp, (u8_t *)&escaped + offset,
offset ? 1 : 2,
send_off);
escaped = htons(ppp_escape_byte(protocol >> 8, &offset));
send_off = ppp_send_bytes(ppp, (u8_t *)&escaped + offset,
offset ? 1 : 2,
send_off);
}
/* Note that we do not print the first four bytes and FCS bytes at the
* end so that we do not need to allocate separate net_buf just for
* that purpose.
*/
if (LOG_LEVEL >= LOG_LEVEL_DBG) {
net_pkt_hexdump(pkt, "send ppp");
}
while (buf) {
for (i = 0; i < buf->len; i++) {
/* Escape illegal bytes */
escaped = htons(ppp_escape_byte(buf->data[i], &offset));
send_off = ppp_send_bytes(ppp,
(u8_t *)&escaped + offset,
offset ? 1 : 2,
send_off);
}
buf = buf->frags;
}
escaped = htons(ppp_escape_byte(fcs, &offset));
send_off = ppp_send_bytes(ppp, (u8_t *)&escaped + offset,
offset ? 1 : 2,
send_off);
escaped = htons(ppp_escape_byte(fcs >> 8, &offset));
send_off = ppp_send_bytes(ppp, (u8_t *)&escaped + offset,
offset ? 1 : 2,
send_off);
byte = 0x7e;
send_off = ppp_send_bytes(ppp, &byte, 1, send_off);
(void)ppp_send_flush(ppp, send_off);
return 0;
}
static int ppp_driver_init(struct device *dev)
{
struct ppp_driver_context *ppp = dev->driver_data;
LOG_DBG("[%p] dev %p", ppp, dev);
ppp->pkt = NULL;
ppp_change_state(ppp, STATE_HDLC_FRAME_START);
return 0;
}
static inline struct net_linkaddr *ppp_get_mac(struct ppp_driver_context *ppp)
{
ppp->ll_addr.addr = ppp->mac_addr;
ppp->ll_addr.len = sizeof(ppp->mac_addr);
return &ppp->ll_addr;
}
static void ppp_iface_init(struct net_if *iface)
{
struct ppp_driver_context *ppp = net_if_get_device(iface)->driver_data;
struct net_linkaddr *ll_addr;
LOG_DBG("[%p] iface %p", ppp, iface);
net_ppp_init(iface);
if (ppp->init_done) {
return;
}
ppp->init_done = true;
ppp->iface = iface;
/* The mac address is not really used but network interface expects
* to find one.
*/
ll_addr = ppp_get_mac(ppp);
if (CONFIG_PPP_MAC_ADDR[0] != 0) {
if (net_bytes_from_str(ppp->mac_addr, sizeof(ppp->mac_addr),
CONFIG_PPP_MAC_ADDR) < 0) {
goto use_random_mac;
}
} else {
use_random_mac:
/* 00-00-5E-00-53-xx Documentation RFC 7042 */
ppp->mac_addr[0] = 0x00;
ppp->mac_addr[1] = 0x00;
ppp->mac_addr[2] = 0x5E;
ppp->mac_addr[3] = 0x00;
ppp->mac_addr[4] = 0x53;
ppp->mac_addr[5] = sys_rand32_get();
}
net_if_set_link_addr(iface, ll_addr->addr, ll_addr->len,
NET_LINK_ETHERNET);
memset(ppp->buf, 0, sizeof(ppp->buf));
/* We do not use uart_pipe for unit tests as the unit test has its
* own handling of UART. See tests/net/ppp/driver for details.
*/
if (!IS_ENABLED(CONFIG_NET_TEST)) {
uart_pipe_register(ppp->buf, sizeof(ppp->buf), ppp_recv_cb);
}
}
#if defined(CONFIG_NET_STATISTICS_PPP)
static struct net_stats_ppp *ppp_get_stats(struct device *dev)
{
struct ppp_driver_context *context = dev->driver_data;
return &context->stats;
}
#endif
static int ppp_start(struct device *dev)
{
struct ppp_driver_context *context = dev->driver_data;
net_ppp_carrier_on(context->iface);
return 0;
}
static int ppp_stop(struct device *dev)
{
struct ppp_driver_context *context = dev->driver_data;
net_ppp_carrier_off(context->iface);
return 0;
}
static const struct ppp_api ppp_if_api = {
.iface_api.init = ppp_iface_init,
.send = ppp_send,
.start = ppp_start,
.stop = ppp_stop,
#if defined(CONFIG_NET_STATISTICS_PPP)
.get_stats = ppp_get_stats,
#endif
};
NET_DEVICE_INIT(ppp, CONFIG_NET_PPP_DRV_NAME, ppp_driver_init,
&ppp_driver_context_data, NULL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &ppp_if_api,
PPP_L2, NET_L2_GET_CTX_TYPE(PPP_L2), PPP_MTU);