| /* |
| * 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; |
| } |
| |
| #if defined(CONFIG_NET_TEST) |
| void ppp_driver_feed_data(u8_t *data, int data_len) |
| { |
| struct ppp_driver_context *ppp = &ppp_driver_context_data; |
| size_t recv_off = 0; |
| |
| /* We are expecting that the tests are feeding data in large |
| * chunks so we can reset the uart buffer here. |
| */ |
| memset(ppp->buf, 0, UART_BUF_LEN); |
| |
| ppp_change_state(ppp, STATE_HDLC_FRAME_START); |
| |
| while (data_len > 0) { |
| int data_to_copy = MIN(data_len, UART_BUF_LEN); |
| int remaining; |
| |
| LOG_DBG("Feeding %d bytes", data_to_copy); |
| |
| memcpy(ppp->buf, data, data_to_copy); |
| |
| recv_off = data_to_copy; |
| |
| (void)ppp_recv_cb(ppp->buf, &recv_off); |
| |
| remaining = data_to_copy - recv_off; |
| |
| LOG_DBG("We copied %d bytes", remaining); |
| |
| data_len -= remaining; |
| data += remaining; |
| } |
| } |
| #endif |
| |
| 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; |
| |
| #if defined(CONFIG_NET_TEST) |
| return 0; |
| #endif |
| |
| 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); |