From 18f845275f4c39643433346e74f11e876ddd0c71 Mon Sep 17 00:00:00 2001 From: David Cermak Date: Fri, 22 Dec 2023 20:03:01 +0100 Subject: [PATCH] feat(eppp): Added support for SPI transport --- README.md | 4 + components/eppp_link/Kconfig | 15 - components/eppp_link/README.md | 35 +- components/eppp_link/eppp_link.c | 832 +++++++++++------- components/eppp_link/eppp_link_types.h | 0 .../examples/host/main/Kconfig.projbuild | 38 +- .../eppp_link/examples/host/main/app_main.c | 92 +- .../examples/host/main/idf_component.yml | 2 + .../examples/host/main/register_iperf.c | 10 +- .../examples/host/sdkconfig.defaults | 2 +- .../examples/slave/main/Kconfig.projbuild | 24 + .../slave/main/station_example_main.c | 17 +- components/eppp_link/include/eppp_link.h | 108 ++- .../eppp_link/test/test_app/CMakeLists.txt | 7 + components/eppp_link/test/test_app/README.md | 73 ++ .../test/test_app/main/CMakeLists.txt | 4 + .../eppp_link/test/test_app/main/app_main.c | 344 ++++++++ .../test/test_app/main/idf_component.yml | 4 + .../test/test_app/sdkconfig.defaults | 12 + 19 files changed, 1192 insertions(+), 431 deletions(-) delete mode 100644 components/eppp_link/eppp_link_types.h create mode 100644 components/eppp_link/test/test_app/CMakeLists.txt create mode 100644 components/eppp_link/test/test_app/README.md create mode 100644 components/eppp_link/test/test_app/main/CMakeLists.txt create mode 100644 components/eppp_link/test/test_app/main/app_main.c create mode 100644 components/eppp_link/test/test_app/main/idf_component.yml create mode 100644 components/eppp_link/test/test_app/sdkconfig.defaults diff --git a/README.md b/README.md index f1e693df5..918eac47a 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,7 @@ Please refer to instructions in [ESP-IDF](https://github.com/espressif/esp-idf) ### console_cmd_wifi * Brief introduction [README](components/console_cmd_wifi/README.md) + +### ESP PPP Link (eppp) + +* Brief introduction [README](components/eppp_link/README.md) diff --git a/components/eppp_link/Kconfig b/components/eppp_link/Kconfig index 44c9e8836..4327cba33 100644 --- a/components/eppp_link/Kconfig +++ b/components/eppp_link/Kconfig @@ -32,19 +32,4 @@ menu "eppp_link" Size of the Tx packet queue. You can decrease the number for slower bit rates. - config EPPP_LINK_SERVER_IP - hex "Server IP address" - range 0 0xFFFFFFFF - default 0xc0a80b01 - help - Preferred IP address of the server side. - - config EPPP_LINK_CLIENT_IP - hex "Client IP address" - range 0 0xFFFFFFFF - default 0xc0a80b02 - help - Preferred IP address of the client side. - - endmenu diff --git a/components/eppp_link/README.md b/components/eppp_link/README.md index c47253140..9b517e7f6 100644 --- a/components/eppp_link/README.md +++ b/components/eppp_link/README.md @@ -1,6 +1,14 @@ # ESP PPP Link component (eppp_link) -The component provides a general purpose connectivity engine between two micro-controllers, one acting as PPP server (slave), the other one as PPP client (host). Typical application is a WiFi connectivity provider for chips that do not have WiFi: +The component provides a general purpose connectivity engine between two microcontrollers, one acting as PPP server (slave), the other one as PPP client (host). +This component could be used for extending network using physical serial connection. Applications could vary from providing PRC engine for multiprocessor solutions to serial connection to POSIX machine. This uses a standard PPP protocol to negotiate IP addresses and networking, so standard PPP toolset could be used, e.g. a `pppd` service on linux. Typical application is a WiFi connectivity provider for chips that do not have WiFi + +## Typical application + +Using this component we can construct a WiFi connectivity gateway on PPP channel. The below diagram depicts an application where +PPP server is running on a WiFi capable chip with NAPT module translating packets between WiFi and PPPoS interface. +We usually call this node a SLAVE microcontroller. The "HOST" microcontroller runs PPP client and connects only to the serial line, +brings in the WiFi connectivity from the "SLAVE" microcontroller. ``` SLAVE micro HOST micro @@ -17,16 +25,17 @@ The component provides a general purpose connectivity engine between two micro-c * `eppp_connect()` -- Simplified API. Provides the initialization, starts the task and blocks until we're connected -* `eppp_client_init()` -- Initialization of the client. Need to run only once. -* `eppp_client_start()` -- Starts the connection, could be called after startup or whenever a connection is lost -* `eppp_client_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) - ### Server * `eppp_listen()` -- Simplified API. Provides the initialization, starts the task and blocks until the client connects -* `eppp_server_init()` -- Initialization of the server. Need to run only once. -* `eppp_server_start()` -- (Re)starts the connection, should be called after startup or whenever a connection is lost -* `eppp_server_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) + +### Manual actions + +* `eppp_init()` -- Initializes one endpoint (client/server). +* `eppp_deinit()` -- Destroys the endpoint +* `eppp_netif_start()` -- Starts the network, could be called after startup or whenever a connection is lost +* `eppp_netif_stop()` -- Stops the network +* `eppp_perform()` -- Perform one iteration of the PPP task (need to be called regularly in task-less configuration) ## Throughput @@ -34,10 +43,10 @@ Tested with WiFi-NAPT example, no IRAM optimizations ### UART @ 3Mbauds -* TCP - 2Mbits -* UDP - 2Mbits +* TCP - 2Mbits/s +* UDP - 2Mbits/s -### SPI @ 40MHz +### SPI @ 20MHz -* TCP - 6Mbits -* UDP - 10Mbits +* TCP - 6Mbits/s +* UDP - 10Mbits/s diff --git a/components/eppp_link/eppp_link.c b/components/eppp_link/eppp_link.c index 1dc0f2dc2..1d3e89f8d 100644 --- a/components/eppp_link/eppp_link.c +++ b/components/eppp_link/eppp_link.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,13 +11,14 @@ #include "esp_check.h" #include "esp_event.h" #include "esp_netif_ppp.h" -#include "eppp_link_types.h" +#include "eppp_link.h" #if CONFIG_EPPP_LINK_DEVICE_SPI #include "driver/spi_master.h" #include "driver/spi_slave.h" #include "driver/gpio.h" #include "esp_timer.h" +#include "esp_rom_crc.h" #elif CONFIG_EPPP_LINK_DEVICE_UART #include "driver/uart.h" #endif @@ -29,70 +30,143 @@ static const int CONNECTION_FAILED = BIT1; static EventGroupHandle_t s_event_group = NULL; static const char *TAG = "eppp_link"; static int s_retry_num = 0; +static int s_eppp_netif_count = 0; // used as a suffix for the netif key -#if CONFIG_EPPP_LINK_DEVICE_SPI -static spi_device_handle_t s_spi_device; -#define SPI_HOST SPI2_HOST -#define GPIO_MOSI 11 -#define GPIO_MISO 13 -#define GPIO_SCLK 12 -#define GPIO_CS 10 -#define GPIO_INTR 2 -#endif // CONFIG_EPPP_LINK_DEVICE_SPI - -enum eppp_type { - EPPP_SERVER, - EPPP_CLIENT, -}; - -struct eppp_handle { - QueueHandle_t out_queue; -#if CONFIG_EPPP_LINK_DEVICE_SPI - QueueHandle_t ready_semaphore; -#elif CONFIG_EPPP_LINK_DEVICE_UART - QueueHandle_t uart_event_queue; -#endif - esp_netif_t *netif; - enum eppp_type role; -}; struct packet { size_t len; uint8_t *data; }; +#if CONFIG_EPPP_LINK_DEVICE_SPI +#define MAX_PAYLOAD 1500 +#define MIN_TRIGGER_US 20 +#define SPI_HEADER_MAGIC 0x1234 + +struct header { + uint16_t magic; + uint16_t size; + uint16_t next_size; + uint16_t check; +} __attribute__((packed)); + +enum blocked_status { + NONE, + MASTER_BLOCKED, + MASTER_WANTS_READ, + SLAVE_BLOCKED, + SLAVE_WANTS_WRITE, +}; + +#endif // CONFIG_EPPP_LINK_DEVICE_SPI + +struct eppp_handle { +#if CONFIG_EPPP_LINK_DEVICE_SPI + QueueHandle_t out_queue; + QueueHandle_t ready_semaphore; + spi_device_handle_t spi_device; + spi_host_device_t spi_host; + int gpio_intr; + uint16_t next_size; + uint16_t transaction_size; + struct packet outbound; + enum blocked_status blocked; + uint32_t slave_last_edge; + esp_timer_handle_t timer; +#elif CONFIG_EPPP_LINK_DEVICE_UART + QueueHandle_t uart_event_queue; + uart_port_t uart_port; +#endif + esp_netif_t *netif; + eppp_type_t role; + bool stop; + bool exited; + bool netif_stop; +}; + + static esp_err_t transmit(void *h, void *buffer, size_t len) { -#if CONFIG_EPPP_LINK_DEVICE_SPI -#define MAX_PAYLOAD 1600 struct eppp_handle *handle = h; +#if CONFIG_EPPP_LINK_DEVICE_SPI struct packet buf = { }; - uint8_t *current_buffer = buffer; size_t remaining = len; - do { + do { // TODO(IDF-9194): Refactor this loop to allocate only once and perform + // fragmentation after receiving from the queue (applicable only if MTU > MAX_PAYLOAD) size_t batch = remaining > MAX_PAYLOAD ? MAX_PAYLOAD : remaining; buf.data = malloc(batch); + if (buf.data == NULL) { + ESP_LOGE(TAG, "Failed to allocate packet"); + return ESP_ERR_NO_MEM; + } buf.len = batch; remaining -= batch; memcpy(buf.data, current_buffer, batch); current_buffer += batch; - BaseType_t ret = xQueueSend(handle->out_queue, &buf, pdMS_TO_TICKS(10)); + BaseType_t ret = xQueueSend(handle->out_queue, &buf, 0); if (ret != pdTRUE) { ESP_LOGE(TAG, "Failed to queue packet to slave!"); + return ESP_ERR_NO_MEM; } } while (remaining > 0); + + if (handle->role == EPPP_SERVER && handle->blocked == SLAVE_BLOCKED) { + uint32_t now = esp_timer_get_time(); + uint32_t diff = now - handle->slave_last_edge; + if (diff < MIN_TRIGGER_US) { + esp_rom_delay_us(MIN_TRIGGER_US - diff); + } + gpio_set_level(handle->gpio_intr, 0); + } + #elif CONFIG_EPPP_LINK_DEVICE_UART - uart_write_bytes(UART_NUM_1, buffer, len); + ESP_LOG_BUFFER_HEXDUMP("ppp_uart_send", buffer, len, ESP_LOG_VERBOSE); + uart_write_bytes(handle->uart_port, buffer, len); #endif return ESP_OK; } -static esp_netif_t *netif_init(enum eppp_type role) +static void IRAM_ATTR timer_callback(void *arg) { - static int s_eppp_netif_count = 0; // used as a suffix for the netif key - if (s_eppp_netif_count > 9) { + struct eppp_handle *h = arg; + if (h->blocked == SLAVE_WANTS_WRITE) { + gpio_set_level(h->gpio_intr, 0); + } +} + +static void netif_deinit(esp_netif_t *netif) +{ + if (netif == NULL) { + return; + } + struct eppp_handle *h = esp_netif_get_io_driver(netif); + if (h == NULL) { + return; + } +#if CONFIG_EPPP_LINK_DEVICE_SPI + struct packet buf = { }; + while (xQueueReceive(h->out_queue, &buf, 0) == pdTRUE) { + if (buf.len > 0) { + free(buf.data); + } + } + vQueueDelete(h->out_queue); + if (h->role == EPPP_CLIENT) { + vSemaphoreDelete(h->ready_semaphore); + } +#endif + free(h); + esp_netif_destroy(netif); + if (s_eppp_netif_count > 0) { + s_eppp_netif_count--; + } +} + +static esp_netif_t *netif_init(eppp_type_t role) +{ + if (s_eppp_netif_count > 9) { // Limit to max 10 netifs, since we use "EPPPx" as the unique key (where x is 0-9) ESP_LOGE(TAG, "Cannot create more than 10 instances"); return NULL; } @@ -103,14 +177,14 @@ static esp_netif_t *netif_init(enum eppp_type role) ESP_LOGE(TAG, "Failed to allocate eppp_handle"); return NULL; } + h->role = role; +#if CONFIG_EPPP_LINK_DEVICE_SPI h->out_queue = xQueueCreate(CONFIG_EPPP_LINK_PACKET_QUEUE_SIZE, sizeof(struct packet)); if (!h->out_queue) { ESP_LOGE(TAG, "Failed to create the packet queue"); free(h); return NULL; } - h->role = role; -#if CONFIG_EPPP_LINK_DEVICE_SPI if (role == EPPP_CLIENT) { h->ready_semaphore = xSemaphoreCreateBinary(); if (!h->ready_semaphore) { @@ -120,6 +194,24 @@ static esp_netif_t *netif_init(enum eppp_type role) return NULL; } } + h->transaction_size = 0; + h->outbound.data = NULL; + h->outbound.len = 0; + if (role == EPPP_SERVER) { + esp_timer_create_args_t args = { + .callback = &timer_callback, + .arg = h, + .name = "timer" + }; + if (esp_timer_create(&args, &h->timer) != ESP_OK) { + ESP_LOGE(TAG, "Failed to create the packet queue"); + vQueueDelete(h->out_queue); + vSemaphoreDelete(h->ready_semaphore); + free(h); + return NULL; + } + } + #endif esp_netif_driver_ifconfig_t driver_cfg = { @@ -145,7 +237,12 @@ static esp_netif_t *netif_init(enum eppp_type role) esp_netif_t *netif = esp_netif_new(&netif_ppp_config); if (!netif) { ESP_LOGE(TAG, "Failed to create esp_netif"); +#if CONFIG_EPPP_LINK_DEVICE_SPI vQueueDelete(h->out_queue); + if (h->ready_semaphore) { + vSemaphoreDelete(h->ready_semaphore); + } +#endif free(h); return NULL; } @@ -153,142 +250,220 @@ static esp_netif_t *netif_init(enum eppp_type role) } -static esp_err_t netif_start(esp_netif_t *netif) +esp_err_t eppp_netif_stop(esp_netif_t *netif, int stop_timeout_ms) +{ + esp_netif_action_disconnected(netif, 0, 0, 0); + esp_netif_action_stop(netif, 0, 0, 0); + struct eppp_handle *h = esp_netif_get_io_driver(netif); + for (int wait = 0; wait < 100; wait++) { + vTaskDelay(pdMS_TO_TICKS(stop_timeout_ms) / 100); + if (h->netif_stop) { + break; + } + } + if (!h->netif_stop) { + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t eppp_netif_start(esp_netif_t *netif) { esp_netif_action_start(netif, 0, 0, 0); esp_netif_action_connected(netif, 0, 0, 0); return ESP_OK; } +static int get_netif_num(esp_netif_t *netif) +{ + if (netif == NULL) { + return -1; + } + const char *ifkey = esp_netif_get_ifkey(netif); + if (strstr(ifkey, "EPPP") == NULL) { + return -1; // not our netif + } + int netif_cnt = ifkey[4] - '0'; + if (netif_cnt < 0 || netif_cnt > 9) { + ESP_LOGE(TAG, "Unexpected netif key %s", ifkey); + return -1; + } + return netif_cnt; +} + +static void on_ppp_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + esp_netif_t **netif = data; + ESP_LOGD(TAG, "PPP status event: %" PRId32, event_id); + if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) { + ESP_LOGI(TAG, "Disconnected %d", get_netif_num(*netif)); + struct eppp_handle *h = esp_netif_get_io_driver(*netif); + h->netif_stop = true; + } +} + static void on_ip_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) { ip_event_got_ip_t *event = (ip_event_got_ip_t *)data; esp_netif_t *netif = event->esp_netif; + int netif_cnt = get_netif_num(netif); + if (netif_cnt < 0) { + return; + } if (event_id == IP_EVENT_PPP_GOT_IP) { - ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s\" address: " IPSTR, esp_netif_get_desc(event->esp_netif), IP2STR(&event->ip_info.ip)); - xEventGroupSetBits(s_event_group, GOT_IPV4); + ESP_LOGI(TAG, "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif), + esp_netif_get_ifkey(netif), IP2STR(&event->ip_info.ip)); + xEventGroupSetBits(s_event_group, GOT_IPV4 << (netif_cnt * 2)); } else if (event_id == IP_EVENT_PPP_LOST_IP) { - ESP_LOGI(TAG, "Disconnect from PPP Server"); + ESP_LOGI(TAG, "Disconnected"); s_retry_num++; if (s_retry_num > CONFIG_EPPP_LINK_CONN_MAX_RETRY) { ESP_LOGE(TAG, "PPP Connection failed %d times, stop reconnecting.", s_retry_num); - xEventGroupSetBits(s_event_group, CONNECTION_FAILED); + xEventGroupSetBits(s_event_group, CONNECTION_FAILED << (netif_cnt * 2)); } else { ESP_LOGI(TAG, "PPP Connection failed %d times, try to reconnect.", s_retry_num); - netif_start(netif); + eppp_netif_start(netif); } } } #if CONFIG_EPPP_LINK_DEVICE_SPI -#define TRANSFER_SIZE (MAX_PAYLOAD + 4) -#define SHORT_PAYLOAD (48) -#define CONTROL_SIZE (SHORT_PAYLOAD + 4) - -#define CONTROL_MASTER 0xA5 -#define CONTROL_MASTER_WITH_DATA 0xA6 -#define CONTROL_SLAVE 0x5A -#define CONTROL_SLAVE_WITH_DATA 0x5B -#define DATA_MASTER 0xAF -#define DATA_SLAVE 0xFA - +#define SPI_ALIGN(size) (((size) + 3U) & ~(3U)) +#define TRANSFER_SIZE SPI_ALIGN((MAX_PAYLOAD + 6)) #define MAX(a,b) (((a)>(b))?(a):(b)) -struct header { - union { - uint16_t size; - struct { - uint8_t short_size; - uint8_t long_size; - } __attribute__((packed)); - }; - uint8_t magic; - uint8_t checksum; -} __attribute__((packed)); - static void IRAM_ATTR gpio_isr_handler(void *arg) { static uint32_t s_last_time; uint32_t now = esp_timer_get_time(); uint32_t diff = now - s_last_time; - if (diff < 5) { // debounce + if (diff < MIN_TRIGGER_US) { // debounce return; } s_last_time = now; - - BaseType_t yield = false; struct eppp_handle *h = arg; - xSemaphoreGiveFromISR(h->ready_semaphore, &yield); - if (yield) { - portYIELD_FROM_ISR(); + BaseType_t yield = false; + + // Positive edge means SPI slave prepared the data + if (gpio_get_level(h->gpio_intr) == 1) { + xSemaphoreGiveFromISR(h->ready_semaphore, &yield); + if (yield) { + portYIELD_FROM_ISR(); + } + return; + } + + // Negative edge (when master blocked) means that slave wants to transmit + if (h->blocked == MASTER_BLOCKED) { + struct packet buf = { .data = NULL, .len = -1 }; + xQueueSendFromISR(h->out_queue, &buf, &yield); + if (yield) { + portYIELD_FROM_ISR(); + } } } -static esp_err_t init_master(spi_device_handle_t *spi, esp_netif_t *netif) +static esp_err_t deinit_master(esp_netif_t *netif) { + struct eppp_handle *h = esp_netif_get_io_driver(netif); + ESP_RETURN_ON_ERROR(spi_bus_remove_device(h->spi_device), TAG, "Failed to remove SPI bus"); + ESP_RETURN_ON_ERROR(spi_bus_free(h->spi_host), TAG, "Failed to free SPI bus"); + return ESP_OK; +} + +static esp_err_t init_master(struct eppp_config_spi_s *config, esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->spi_host = config->host; + h->gpio_intr = config->intr; spi_bus_config_t bus_cfg = {}; - bus_cfg.mosi_io_num = GPIO_MOSI; - bus_cfg.miso_io_num = GPIO_MISO; - bus_cfg.sclk_io_num = GPIO_SCLK; + bus_cfg.mosi_io_num = config->mosi; + bus_cfg.miso_io_num = config->miso; + bus_cfg.sclk_io_num = config->sclk; bus_cfg.quadwp_io_num = -1; bus_cfg.quadhd_io_num = -1; - bus_cfg.max_transfer_sz = 14000; + bus_cfg.max_transfer_sz = TRANSFER_SIZE; bus_cfg.flags = 0; bus_cfg.intr_flags = 0; - if (spi_bus_initialize(SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO) != ESP_OK) { + // TODO: Init and deinit SPI bus separately (per Kconfig?) + if (spi_bus_initialize(config->host, &bus_cfg, SPI_DMA_CH_AUTO) != ESP_OK) { return ESP_FAIL; } spi_device_interface_config_t dev_cfg = {}; - dev_cfg.clock_speed_hz = 40 * 1000 * 1000; + dev_cfg.clock_speed_hz = config->freq; dev_cfg.mode = 0; - dev_cfg.spics_io_num = GPIO_CS; - dev_cfg.cs_ena_pretrans = 0; - dev_cfg.cs_ena_posttrans = 0; + dev_cfg.spics_io_num = config->cs; + dev_cfg.cs_ena_pretrans = config->cs_ena_pretrans; + dev_cfg.cs_ena_posttrans = config->cs_ena_posttrans; dev_cfg.duty_cycle_pos = 128; - dev_cfg.input_delay_ns = 0; + dev_cfg.input_delay_ns = config->input_delay_ns; dev_cfg.pre_cb = NULL; dev_cfg.post_cb = NULL; - dev_cfg.cs_ena_posttrans = 3; dev_cfg.queue_size = 3; - if (spi_bus_add_device(SPI_HOST, &dev_cfg, spi) != ESP_OK) { + if (spi_bus_add_device(config->host, &dev_cfg, &h->spi_device) != ESP_OK) { return ESP_FAIL; } //GPIO config for the handshake line. gpio_config_t io_conf = { - .intr_type = GPIO_INTR_POSEDGE, + .intr_type = GPIO_INTR_ANYEDGE, .mode = GPIO_MODE_INPUT, .pull_up_en = 1, - .pin_bit_mask = BIT64(GPIO_INTR), + .pin_bit_mask = BIT64(config->intr), }; gpio_config(&io_conf); gpio_install_isr_service(0); - gpio_set_intr_type(GPIO_INTR, GPIO_INTR_POSEDGE); - gpio_isr_handler_add(GPIO_INTR, gpio_isr_handler, esp_netif_get_io_driver(netif)); + gpio_set_intr_type(config->intr, GPIO_INTR_ANYEDGE); + gpio_isr_handler_add(config->intr, gpio_isr_handler, esp_netif_get_io_driver(netif)); return ESP_OK; } static void post_setup(spi_slave_transaction_t *trans) { - gpio_set_level(GPIO_INTR, 1); + struct eppp_handle *h = trans->user; + h->slave_last_edge = esp_timer_get_time(); + gpio_set_level(h->gpio_intr, 1); + if (h->transaction_size == 0) { // If no transaction planned: + if (h->outbound.len == 0) { // we're blocked if we don't have any data + h->blocked = SLAVE_BLOCKED; + } else { + h->blocked = SLAVE_WANTS_WRITE; // we notify the master that we want to write + esp_timer_start_once(h->timer, MIN_TRIGGER_US); + } + } } static void post_trans(spi_slave_transaction_t *trans) { - gpio_set_level(GPIO_INTR, 0); + struct eppp_handle *h = trans->user; + h->blocked = NONE; + gpio_set_level(h->gpio_intr, 0); } -static esp_err_t init_slave(spi_device_handle_t *spi, esp_netif_t *netif) +static esp_err_t deinit_slave(esp_netif_t *netif) { + struct eppp_handle *h = esp_netif_get_io_driver(netif); + ESP_RETURN_ON_ERROR(spi_slave_free(h->spi_host), TAG, "Failed to free SPI slave host"); + ESP_RETURN_ON_ERROR(spi_bus_remove_device(h->spi_device), TAG, "Failed to remove SPI device"); + ESP_RETURN_ON_ERROR(spi_bus_free(h->spi_host), TAG, "Failed to free SPI bus"); + return ESP_OK; +} + +static esp_err_t init_slave(struct eppp_config_spi_s *config, esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->spi_host = config->host; + h->gpio_intr = config->intr; spi_bus_config_t bus_cfg = {}; - bus_cfg.mosi_io_num = GPIO_MOSI; - bus_cfg.miso_io_num = GPIO_MISO; - bus_cfg.sclk_io_num = GPIO_SCLK; + bus_cfg.mosi_io_num = config->mosi; + bus_cfg.miso_io_num = config->miso; + bus_cfg.sclk_io_num = config->sclk; bus_cfg.quadwp_io_num = -1; bus_cfg.quadhd_io_num = -1; bus_cfg.flags = 0; @@ -297,293 +472,348 @@ static esp_err_t init_slave(spi_device_handle_t *spi, esp_netif_t *netif) //Configuration for the SPI slave interface spi_slave_interface_config_t slvcfg = { .mode = 0, - .spics_io_num = GPIO_CS, + .spics_io_num = config->cs, .queue_size = 3, .flags = 0, .post_setup_cb = post_setup, - .post_trans_cb = post_trans + .post_trans_cb = post_trans, }; //Configuration for the handshake line gpio_config_t io_conf = { .intr_type = GPIO_INTR_DISABLE, .mode = GPIO_MODE_OUTPUT, - .pin_bit_mask = BIT64(GPIO_INTR), + .pin_bit_mask = BIT64(config->intr), }; gpio_config(&io_conf); - gpio_set_pull_mode(GPIO_MOSI, GPIO_PULLUP_ONLY); - gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY); - gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(config->mosi, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(config->sclk, GPIO_PULLUP_ONLY); + gpio_set_pull_mode(config->cs, GPIO_PULLUP_ONLY); //Initialize SPI slave interface - if (spi_slave_initialize(SPI_HOST, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO) != ESP_OK) { + if (spi_slave_initialize(config->host, &bus_cfg, &slvcfg, SPI_DMA_CH_AUTO) != ESP_OK) { return ESP_FAIL; } return ESP_OK; } -union transaction { - spi_transaction_t master; - spi_slave_transaction_t slave; -}; +typedef esp_err_t (*perform_transaction_t)(struct eppp_handle *h, size_t len, const void *tx_buffer, void *rx_buffer); -typedef void (*set_transaction_t)(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer); -typedef esp_err_t (*perform_transaction_t)(union transaction *t, struct eppp_handle *h); - -static void set_transaction_master(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer) +static esp_err_t perform_transaction_master(struct eppp_handle *h, size_t len, const void *tx_buffer, void *rx_buffer) { - t->master.length = len * 8; - t->master.tx_buffer = tx_buffer; - t->master.rx_buffer = rx_buffer; + spi_transaction_t t = {}; + t.length = len * 8; + t.tx_buffer = tx_buffer; + t.rx_buffer = rx_buffer; + return spi_device_transmit(h->spi_device, &t); } -static void set_transaction_slave(union transaction *t, size_t len, const void *tx_buffer, void *rx_buffer) +static esp_err_t perform_transaction_slave(struct eppp_handle *h, size_t len, const void *tx_buffer, void *rx_buffer) { - t->slave.length = len * 8; - t->slave.tx_buffer = tx_buffer; - t->slave.rx_buffer = rx_buffer; + spi_slave_transaction_t t = {}; + t.user = h; + t.length = len * 8; + t.tx_buffer = tx_buffer; + t.rx_buffer = rx_buffer; + return spi_slave_transmit(h->spi_host, &t, portMAX_DELAY); } -static esp_err_t perform_transaction_master(union transaction *t, struct eppp_handle *h) -{ - xSemaphoreTake(h->ready_semaphore, portMAX_DELAY); // Wait until slave is ready - return spi_device_transmit(s_spi_device, &t->master); -} - -static esp_err_t perform_transaction_slave(union transaction *t, struct eppp_handle *h) -{ - return spi_slave_transmit(SPI_HOST, &t->slave, portMAX_DELAY); -} - -_Noreturn static void ppp_task(void *args) +esp_err_t eppp_perform(esp_netif_t *netif) { static WORD_ALIGNED_ATTR uint8_t out_buf[TRANSFER_SIZE] = {}; static WORD_ALIGNED_ATTR uint8_t in_buf[TRANSFER_SIZE] = {}; - esp_netif_t *netif = args; struct eppp_handle *h = esp_netif_get_io_driver(netif); - union transaction t; - const uint8_t FRAME_OUT_CTRL = h->role == EPPP_CLIENT ? CONTROL_MASTER : CONTROL_SLAVE; - const uint8_t FRAME_OUT_CTRL_EX = h->role == EPPP_CLIENT ? CONTROL_MASTER_WITH_DATA : CONTROL_SLAVE_WITH_DATA; - const uint8_t FRAME_OUT_DATA = h->role == EPPP_CLIENT ? DATA_MASTER : DATA_SLAVE; - const uint8_t FRAME_IN_CTRL = h->role == EPPP_SERVER ? CONTROL_MASTER : CONTROL_SLAVE; - const uint8_t FRAME_IN_CTRL_EX = h->role == EPPP_SERVER ? CONTROL_MASTER_WITH_DATA : CONTROL_SLAVE_WITH_DATA; - const uint8_t FRAME_IN_DATA = h->role == EPPP_SERVER ? DATA_MASTER : DATA_SLAVE; - const set_transaction_t set_transaction = h->role == EPPP_CLIENT ? set_transaction_master : set_transaction_slave; + // Perform transaction for master and slave const perform_transaction_t perform_transaction = h->role == EPPP_CLIENT ? perform_transaction_master : perform_transaction_slave; + if (h->stop) { + return ESP_ERR_TIMEOUT; + } + + BaseType_t tx_queue_stat; + bool allow_test_tx = false; + uint16_t next_tx_size = 0; if (h->role == EPPP_CLIENT) { - // as a client, try to actively connect (not waiting for server's interrupt) - xSemaphoreGive(h->ready_semaphore); - } - while (1) { - struct packet buf = { .len = 0 }; - struct header *head = (void *)out_buf; - bool need_data_frame = false; - size_t out_long_payload = 0; - head->magic = FRAME_OUT_CTRL_EX; - head->size = 0; - head->checksum = 0; - BaseType_t tx_queue_stat = xQueueReceive(h->out_queue, &buf, 0); - if (tx_queue_stat == pdTRUE && buf.data) { - if (buf.len > SHORT_PAYLOAD) { - head->magic = FRAME_OUT_CTRL; - head->size = buf.len; - out_long_payload = buf.len; - need_data_frame = true; -// printf("need_data_frame %d\n", buf.len); - } else { - head->magic = FRAME_OUT_CTRL_EX; - head->long_size = 0; - head->short_size = buf.len; - memcpy(out_buf + sizeof(struct header), buf.data, buf.len); - free(buf.data); + // SPI MASTER only code + if (xSemaphoreTake(h->ready_semaphore, pdMS_TO_TICKS(1000)) != pdTRUE) { + // slave might not be ready, but maybe we just missed an interrupt + allow_test_tx = true; + } + if (h->outbound.len == 0 && h->transaction_size == 0 && h->blocked == NONE) { + h->blocked = MASTER_BLOCKED; + xQueueReceive(h->out_queue, &h->outbound, portMAX_DELAY); + h->blocked = NONE; + if (h->outbound.len == -1) { + h->outbound.len = 0; + h->blocked = MASTER_WANTS_READ; } - } - memset(&t, 0, sizeof(t)); - set_transaction(&t, CONTROL_SIZE, out_buf, in_buf); - for (int i = 0; i < sizeof(struct header) - 1; ++i) { - head->checksum += out_buf[i]; - } - esp_err_t ret = perform_transaction(&t, h); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "spi_device_transmit failed"); - continue; - } - head = (void *)in_buf; - uint8_t checksum = 0; - for (int i = 0; i < sizeof(struct header) - 1; ++i) { - checksum += in_buf[i]; - } - if (checksum != head->checksum) { - ESP_LOGE(TAG, "Wrong checksum"); - continue; - } -// printf("MAGIC: %x\n", head->magic); - if (head->magic != FRAME_IN_CTRL && head->magic != FRAME_IN_CTRL_EX) { - ESP_LOGE(TAG, "Wrong magic"); - continue; - } - if (head->magic == FRAME_IN_CTRL_EX && head->short_size > 0) { - esp_netif_receive(netif, in_buf + sizeof(struct header), head->short_size, NULL); - } - size_t in_long_payload = 0; - if (head->magic == FRAME_IN_CTRL) { - need_data_frame = true; - in_long_payload = head->size; - } - if (!need_data_frame) { - continue; - } - // now, we need data frame -// printf("performing data frame %d %d\n", out_long_payload, buf.len); - head = (void *)out_buf; - head->magic = FRAME_OUT_DATA; - head->size = out_long_payload; - head->checksum = 0; - for (int i = 0; i < sizeof(struct header) - 1; ++i) { - head->checksum += out_buf[i]; - } - if (head->size > 0) { - memcpy(out_buf + sizeof(struct header), buf.data, buf.len); -// ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf + sizeof(struct header), head->size, ESP_LOG_INFO); - free(buf.data); - } - - memset(&t, 0, sizeof(t)); - set_transaction(&t, MAX(in_long_payload, out_long_payload) + sizeof(struct header), out_buf, in_buf); - - ret = perform_transaction(&t, h); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "spi_device_transmit failed"); - continue; - } - head = (void *)in_buf; - checksum = 0; - for (int i = 0; i < sizeof(struct header) - 1; ++i) { - checksum += in_buf[i]; - } - if (checksum != head->checksum) { - ESP_LOGE(TAG, "Wrong checksum"); - continue; - } - if (head->magic != FRAME_IN_DATA) { - ESP_LOGE(TAG, "Wrong magic"); - continue; - } -// printf("got size %d\n", head->size); - - if (head->size > 0) { -// ESP_LOG_BUFFER_HEXDUMP(TAG, in_buf + sizeof(struct header), head->size, ESP_LOG_INFO); - esp_netif_receive(netif, in_buf + sizeof(struct header), head->size, NULL); + } else if (h->blocked == MASTER_WANTS_READ) { + h->blocked = NONE; } } + struct header *head = (void *)out_buf; + if (h->outbound.len <= h->transaction_size && allow_test_tx == false) { + // sending outbound + head->size = h->outbound.len; + if (h->outbound.len > 0) { + memcpy(out_buf + sizeof(struct header), h->outbound.data, h->outbound.len); + free(h->outbound.data); + ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf + sizeof(struct header), head->size, ESP_LOG_VERBOSE); + h->outbound.data = NULL; + h->outbound.len = 0; + } + do { + tx_queue_stat = xQueueReceive(h->out_queue, &h->outbound, 0); + } while (tx_queue_stat == pdTRUE && h->outbound.len == -1); + if (h->outbound.len == -1) { // used as a signal only, no actual data + h->outbound.len = 0; + } + } else { + // outbound is bigger, need to transmit in another transaction (keep this empty) + head->size = 0; + } + next_tx_size = head->next_size = h->outbound.len; + head->magic = SPI_HEADER_MAGIC; + head->check = esp_rom_crc16_le(0, out_buf, sizeof(struct header) - sizeof(uint16_t)); + esp_err_t ret = perform_transaction(h, sizeof(struct header) + h->transaction_size, out_buf, in_buf); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "spi_device_transmit failed"); + h->transaction_size = 0; // need to start with HEADER only transaction + return ESP_FAIL; + } + head = (void *)in_buf; + uint16_t check = esp_rom_crc16_le(0, in_buf, sizeof(struct header) - sizeof(uint16_t)); + if (check != head->check || head->magic != SPI_HEADER_MAGIC) { + h->transaction_size = 0; // need to start with HEADER only transaction + if (allow_test_tx) { + return ESP_OK; + } + ESP_LOGE(TAG, "Wrong checksum or magic"); + return ESP_FAIL; + } + if (head->size > 0) { + ESP_LOG_BUFFER_HEXDUMP(TAG, in_buf + sizeof(struct header), head->size, ESP_LOG_VERBOSE); + esp_netif_receive(netif, in_buf + sizeof(struct header), head->size, NULL); + } + h->transaction_size = MAX(next_tx_size, head->next_size); + return ESP_OK; } + #elif CONFIG_EPPP_LINK_DEVICE_UART #define BUF_SIZE (1024) -#define UART_TX_CLIENT_TO_SERVER 10 -#define UART_TX_SERVER_TO_CLIENT 11 -#define UART_BAUDRATE 4000000 -#define UART_QUEUE_SIZE 16 -static esp_err_t init_uart(struct eppp_handle *h) +static esp_err_t init_uart(struct eppp_handle *h, eppp_config_t *config) { + h->uart_port = config->uart.port; uart_config_t uart_config = {}; - uart_config.baud_rate = UART_BAUDRATE; + uart_config.baud_rate = config->uart.baud; uart_config.data_bits = UART_DATA_8_BITS; uart_config.parity = UART_PARITY_DISABLE; uart_config.stop_bits = UART_STOP_BITS_1; uart_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE; uart_config.source_clk = UART_SCLK_DEFAULT; - ESP_RETURN_ON_ERROR(uart_driver_install(UART_NUM_1, BUF_SIZE, 0, UART_QUEUE_SIZE, &h->uart_event_queue, 0), TAG, "Failed to install UART"); - ESP_RETURN_ON_ERROR(uart_param_config(UART_NUM_1, &uart_config), TAG, "Failed to set params"); - int tx_io_num = h->role == EPPP_CLIENT ? UART_TX_CLIENT_TO_SERVER : UART_TX_SERVER_TO_CLIENT; - int rx_io_num = h->role == EPPP_CLIENT ? UART_TX_SERVER_TO_CLIENT : UART_TX_CLIENT_TO_SERVER; - ESP_RETURN_ON_ERROR(uart_set_pin(UART_NUM_1, tx_io_num, rx_io_num, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "Failed to set UART pins"); - ESP_RETURN_ON_ERROR(uart_set_rx_timeout(UART_NUM_1, 1), TAG, "Failed to set UART Rx timeout"); + ESP_RETURN_ON_ERROR(uart_driver_install(h->uart_port, config->uart.rx_buffer_size, 0, config->uart.queue_size, &h->uart_event_queue, 0), TAG, "Failed to install UART"); + ESP_RETURN_ON_ERROR(uart_param_config(h->uart_port, &uart_config), TAG, "Failed to set params"); + ESP_RETURN_ON_ERROR(uart_set_pin(h->uart_port, config->uart.tx_io, config->uart.rx_io, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG, "Failed to set UART pins"); + ESP_RETURN_ON_ERROR(uart_set_rx_timeout(h->uart_port, 1), TAG, "Failed to set UART Rx timeout"); return ESP_OK; } -_Noreturn static void ppp_task(void *args) +static void deinit_uart(struct eppp_handle *h) +{ + uart_driver_delete(h->uart_port); +} + +esp_err_t eppp_perform(esp_netif_t *netif) { static uint8_t buffer[BUF_SIZE] = {}; - - esp_netif_t *netif = args; struct eppp_handle *h = esp_netif_get_io_driver(netif); - uart_event_t event; - while (1) { - xQueueReceive(h->uart_event_queue, &event, pdMS_TO_TICKS(pdMS_TO_TICKS(100))); - if (event.type == UART_DATA) { - size_t len; - uart_get_buffered_data_len(UART_NUM_1, &len); - if (len) { - len = uart_read_bytes(UART_NUM_1, buffer, BUF_SIZE, 0); - ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE); - esp_netif_receive(netif, buffer, len, NULL); - } - } else { - ESP_LOGW(TAG, "Received UART event: %d", event.type); - } + uart_event_t event = {}; + if (h->stop) { + return ESP_ERR_TIMEOUT; } + if (xQueueReceive(h->uart_event_queue, &event, pdMS_TO_TICKS(100)) != pdTRUE) { + return ESP_OK; + } + if (event.type == UART_DATA) { + size_t len; + uart_get_buffered_data_len(h->uart_port, &len); + if (len) { + len = uart_read_bytes(h->uart_port, buffer, BUF_SIZE, 0); + ESP_LOG_BUFFER_HEXDUMP("ppp_uart_recv", buffer, len, ESP_LOG_VERBOSE); + esp_netif_receive(netif, buffer, len, NULL); + } + } else { + ESP_LOGW(TAG, "Received UART event: %d", event.type); + } + return ESP_OK; } + #endif // CONFIG_EPPP_LINK_DEVICE_SPI / UART - -static esp_netif_t *default_setup(enum eppp_type role) +static void ppp_task(void *args) { - s_event_group = xEventGroupCreate(); - if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) { - ESP_LOGE(TAG, "Failed to register IP event handler"); - return NULL; + esp_netif_t *netif = args; + while (eppp_perform(netif) != ESP_ERR_TIMEOUT) {} + struct eppp_handle *h = esp_netif_get_io_driver(netif); + h->exited = true; + vTaskDelete(NULL); +} + +static bool have_some_eppp_netif(esp_netif_t *netif, void *ctx) +{ + return get_netif_num(netif) > 0; +} + +static void remove_handlers(void) +{ + esp_netif_t *netif = esp_netif_find_if(have_some_eppp_netif, NULL); + if (netif == NULL) { + // if EPPP netif in the system, we cleanup the statics + vEventGroupDelete(s_event_group); + s_event_group = NULL; + esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event); + esp_event_handler_unregister(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event); } +} + +void eppp_deinit(esp_netif_t *netif) +{ + if (netif == NULL) { + return; + } +#if CONFIG_EPPP_LINK_DEVICE_SPI + struct eppp_handle *h = esp_netif_get_io_driver(netif); + if (h->role == EPPP_CLIENT) { + deinit_master(netif); + } else { + deinit_slave(netif); + } +#elif CONFIG_EPPP_LINK_DEVICE_UART + deinit_uart(esp_netif_get_io_driver(netif)); +#endif + netif_deinit(netif); +} + +esp_netif_t *eppp_init(eppp_type_t role, eppp_config_t *config) +{ esp_netif_t *netif = netif_init(role); if (!netif) { ESP_LOGE(TAG, "Failed to initialize PPP netif"); - vEventGroupDelete(s_event_group); + remove_handlers(); return NULL; } - esp_event_handler_register(IP_EVENT, IP_EVENT_PPP_GOT_IP, esp_netif_action_connected, netif); esp_netif_ppp_config_t netif_params; ESP_ERROR_CHECK(esp_netif_ppp_get_params(netif, &netif_params)); - netif_params.ppp_our_ip4_addr = esp_netif_htonl(role == EPPP_SERVER ? CONFIG_EPPP_LINK_SERVER_IP : CONFIG_EPPP_LINK_CLIENT_IP); - netif_params.ppp_their_ip4_addr = esp_netif_htonl(role == EPPP_SERVER ? CONFIG_EPPP_LINK_CLIENT_IP : CONFIG_EPPP_LINK_SERVER_IP); + netif_params.ppp_our_ip4_addr = config->ppp.our_ip4_addr; + netif_params.ppp_their_ip4_addr = config->ppp.their_ip4_addr; + netif_params.ppp_error_event_enabled = true; ESP_ERROR_CHECK(esp_netif_ppp_set_params(netif, &netif_params)); #if CONFIG_EPPP_LINK_DEVICE_SPI if (role == EPPP_CLIENT) { - init_master(&s_spi_device, netif); + init_master(&config->spi, netif); } else { - init_slave(&s_spi_device, netif); + init_slave(&config->spi, netif); + } #elif CONFIG_EPPP_LINK_DEVICE_UART - init_uart(esp_netif_get_io_driver(netif)); + init_uart(esp_netif_get_io_driver(netif), config); #endif - - netif_start(netif); - - if (xTaskCreate(ppp_task, "ppp connect", 4096, netif, 18, NULL) != pdTRUE) { - ESP_LOGE(TAG, "Failed to create a ppp connection task"); - return NULL; - } - ESP_LOGI(TAG, "Waiting for IP address"); - EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS, pdFALSE, pdFALSE, portMAX_DELAY); - if (bits & CONNECTION_FAILED) { - ESP_LOGE(TAG, "Connection failed!"); - return NULL; - } - ESP_LOGI(TAG, "Connected!"); return netif; } -esp_netif_t *eppp_connect(void) +esp_netif_t *eppp_open(eppp_type_t role, eppp_config_t *config, int connect_timeout_ms) { - return default_setup(EPPP_CLIENT); +#if CONFIG_EPPP_LINK_DEVICE_UART + if (config->transport != EPPP_TRANSPORT_UART) { + ESP_LOGE(TAG, "Invalid transport: UART device must be enabled in Kconfig"); + return NULL; + } +#endif +#if CONFIG_EPPP_LINK_DEVICE_SPI + if (config->transport != EPPP_TRANSPORT_SPI) { + ESP_LOGE(TAG, "Invalid transport: SPI device must be enabled in Kconfig"); + return NULL; + } +#endif + + if (config->task.run_task == false) { + ESP_LOGE(TAG, "task.run_task == false is invalid in this API. Please use eppp_init()"); + return NULL; + } + if (s_event_group == NULL) { + s_event_group = xEventGroupCreate(); + if (esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_ip_event, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to register IP event handler"); + remove_handlers(); + return NULL; + } + if (esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_ppp_event, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to register PPP status handler"); + remove_handlers(); + return NULL; + } + } + esp_netif_t *netif = eppp_init(role, config); + if (!netif) { + remove_handlers(); + return NULL; + } + + eppp_netif_start(netif); + + if (xTaskCreate(ppp_task, "ppp connect", config->task.stack_size, netif, config->task.priority, NULL) != pdTRUE) { + ESP_LOGE(TAG, "Failed to create a ppp connection task"); + eppp_deinit(netif); + return NULL; + } + int netif_cnt = get_netif_num(netif); + if (netif_cnt < 0) { + eppp_close(netif); + return NULL; + } + ESP_LOGI(TAG, "Waiting for IP address %d", netif_cnt); + EventBits_t bits = xEventGroupWaitBits(s_event_group, CONNECT_BITS << (netif_cnt * 2), pdFALSE, pdFALSE, pdMS_TO_TICKS(connect_timeout_ms)); + if (bits & (CONNECTION_FAILED << (netif_cnt * 2))) { + ESP_LOGE(TAG, "Connection failed!"); + eppp_close(netif); + return NULL; + } + ESP_LOGI(TAG, "Connected! %d", netif_cnt); + return netif; } -esp_netif_t *eppp_listen(void) +esp_netif_t *eppp_connect(eppp_config_t *config) { - return default_setup(EPPP_SERVER); + return eppp_open(EPPP_CLIENT, config, portMAX_DELAY); +} + +esp_netif_t *eppp_listen(eppp_config_t *config) +{ + return eppp_open(EPPP_SERVER, config, portMAX_DELAY); +} + +void eppp_close(esp_netif_t *netif) +{ + struct eppp_handle *h = esp_netif_get_io_driver(netif); + if (eppp_netif_stop(netif, 60000) != ESP_OK) { + ESP_LOGE(TAG, "Network didn't exit cleanly"); + } + h->stop = true; + for (int wait = 0; wait < 100; wait++) { + vTaskDelay(pdMS_TO_TICKS(10)); + if (h->exited) { + break; + } + } + if (!h->exited) { + ESP_LOGE(TAG, "Cannot stop ppp_task"); + } + eppp_deinit(netif); + remove_handlers(); } diff --git a/components/eppp_link/eppp_link_types.h b/components/eppp_link/eppp_link_types.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/components/eppp_link/examples/host/main/Kconfig.projbuild b/components/eppp_link/examples/host/main/Kconfig.projbuild index 02881e10a..5029beb56 100644 --- a/components/eppp_link/examples/host/main/Kconfig.projbuild +++ b/components/eppp_link/examples/host/main/Kconfig.projbuild @@ -20,24 +20,34 @@ menu "Example Configuration" help URL of the broker to connect to. - config EXAMPLE_ICMP_PING - bool "Run ping example" - default y - help - Ping configured address after startup. - - config EXAMPLE_PING_ADDR - hex "Ping IPv4 address" - depends on EXAMPLE_ICMP_PING - range 0 0xFFFFFFFF - default 0x08080808 - help - Address to send ping requests. - config EXAMPLE_IPERF bool "Run iperf" default y help Init and run iperf console. + config EXAMPLE_UART_TX_PIN + int "TXD Pin Number" + depends on EPPP_LINK_DEVICE_UART + default 10 + range 0 31 + help + Pin number of UART TX. + + config EXAMPLE_UART_RX_PIN + int "RXD Pin Number" + depends on EPPP_LINK_DEVICE_UART + default 11 + range 0 31 + help + Pin number of UART RX. + + config EXAMPLE_UART_BAUDRATE + int "Baudrate" + depends on EPPP_LINK_DEVICE_UART + default 2000000 + range 0 4000000 + help + Baudrate used by the PPP over UART + endmenu diff --git a/components/eppp_link/examples/host/main/app_main.c b/components/eppp_link/examples/host/main/app_main.c index 765589c55..b320cf4ff 100644 --- a/components/eppp_link/examples/host/main/app_main.c +++ b/components/eppp_link/examples/host/main/app_main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -13,11 +13,9 @@ #include "esp_event.h" #include "esp_netif.h" #include "eppp_link.h" -#include "lwip/sockets.h" #include "esp_log.h" #include "mqtt_client.h" -#include "ping/ping_sock.h" -#include "esp_console.h" +#include "console_ping.h" void register_iperf(void); @@ -80,7 +78,7 @@ static void mqtt_event_handler(void *args, esp_event_base_t base, int32_t event_ static void mqtt_app_start(void) { esp_mqtt_client_config_t mqtt_cfg = { - .broker.address.uri = CONFIG_EXAMPLE_BROKER_URL, + .broker.address.uri = "mqtt://mqtt.eclipseprojects.io", }; esp_mqtt_client_handle_t client = esp_mqtt_client_init(&mqtt_cfg); @@ -90,43 +88,6 @@ static void mqtt_app_start(void) } #endif // MQTT -#if CONFIG_EXAMPLE_ICMP_PING -static void test_on_ping_success(esp_ping_handle_t hdl, void *args) -{ - uint8_t ttl; - uint16_t seqno; - uint32_t elapsed_time, recv_len; - ip_addr_t target_addr; - esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); - esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); - esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); - esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); - esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); - printf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n", - recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); -} - -static void test_on_ping_timeout(esp_ping_handle_t hdl, void *args) -{ - uint16_t seqno; - ip_addr_t target_addr; - esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); - esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); - printf("From %s icmp_seq=%d timeout\n", inet_ntoa(target_addr.u_addr.ip4), seqno); -} - -static void test_on_ping_end(esp_ping_handle_t hdl, void *args) -{ - uint32_t transmitted; - uint32_t received; - uint32_t total_time_ms; - esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); - esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); - esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); - printf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms); - -} -#endif // PING void app_main(void) { @@ -140,7 +101,16 @@ void app_main(void) /* Sets up the default EPPP-connection */ - esp_netif_t *eppp_netif = eppp_connect(); + eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG(); +#if CONFIG_EPPP_LINK_DEVICE_SPI + config.transport = EPPP_TRANSPORT_SPI; +#else + config.transport = EPPP_TRANSPORT_UART; + config.uart.tx_io = CONFIG_EXAMPLE_UART_TX_PIN; + config.uart.rx_io = CONFIG_EXAMPLE_UART_RX_PIN; + config.uart.baud = CONFIG_EXAMPLE_UART_BAUDRATE; +#endif + esp_netif_t *eppp_netif = eppp_connect(&config); if (eppp_netif == NULL) { ESP_LOGE(TAG, "Failed to connect"); return ; @@ -152,13 +122,6 @@ void app_main(void) ESP_ERROR_CHECK(esp_netif_set_dns_info(eppp_netif, ESP_NETIF_DNS_MAIN, &dns)); #if CONFIG_EXAMPLE_IPERF - esp_console_repl_t *repl = NULL; - esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT(); - esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); - repl_config.prompt = "iperf>"; - // init console REPL environment - ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl)); - register_iperf(); printf("\n =======================================================\n"); @@ -174,28 +137,15 @@ void app_main(void) printf(" | |\n"); printf(" =======================================================\n\n"); +#endif // CONFIG_EXAMPLE_IPERF + + // Initialize console REPL + ESP_ERROR_CHECK(console_cmd_init()); + + // Register the ping command + ESP_ERROR_CHECK(console_cmd_ping_register()); // start console REPL - ESP_ERROR_CHECK(esp_console_start_repl(repl)); -#endif - -#if CONFIG_EXAMPLE_ICMP_PING - ip_addr_t target_addr = { .type = IPADDR_TYPE_V4, .u_addr.ip4.addr = esp_netif_htonl(CONFIG_EXAMPLE_PING_ADDR) }; - - esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); - ping_config.timeout_ms = 2000; - ping_config.interval_ms = 20, - ping_config.target_addr = target_addr; - ping_config.count = 100; // ping in infinite mode - /* set callback functions */ - esp_ping_callbacks_t cbs; - cbs.on_ping_success = test_on_ping_success; - cbs.on_ping_timeout = test_on_ping_timeout; - cbs.on_ping_end = test_on_ping_end; - esp_ping_handle_t ping; - esp_ping_new_session(&ping_config, &cbs, &ping); - /* start ping */ - esp_ping_start(ping); -#endif // PING + ESP_ERROR_CHECK(console_cmd_start()); #if CONFIG_EXAMPLE_MQTT mqtt_app_start(); diff --git a/components/eppp_link/examples/host/main/idf_component.yml b/components/eppp_link/examples/host/main/idf_component.yml index 7ecb517e8..675c13599 100644 --- a/components/eppp_link/examples/host/main/idf_component.yml +++ b/components/eppp_link/examples/host/main/idf_component.yml @@ -2,3 +2,5 @@ dependencies: espressif/eppp_link: version: "*" override_path: "../../.." + console_cmd_ping: + version: "*" diff --git a/components/eppp_link/examples/host/main/register_iperf.c b/components/eppp_link/examples/host/main/register_iperf.c index d2169c376..63fded10c 100644 --- a/components/eppp_link/examples/host/main/register_iperf.c +++ b/components/eppp_link/examples/host/main/register_iperf.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -46,18 +46,14 @@ static struct { static int ppp_cmd_iperf(int argc, char **argv) { int nerrors = arg_parse(argc, argv, (void **)&iperf_args); - iperf_cfg_t cfg; + // ethernet iperf only support IPV4 address + iperf_cfg_t cfg = {.type = IPERF_IP_TYPE_IPV4}; if (nerrors != 0) { arg_print_errors(stderr, iperf_args.end, argv[0]); return 0; } - memset(&cfg, 0, sizeof(cfg)); - - // ethernet iperf only support IPV4 address - cfg.type = IPERF_IP_TYPE_IPV4; - /* iperf -a */ if (iperf_args.abort->count != 0) { iperf_stop(); diff --git a/components/eppp_link/examples/host/sdkconfig.defaults b/components/eppp_link/examples/host/sdkconfig.defaults index 2857f7ac8..ceb19a67c 100644 --- a/components/eppp_link/examples/host/sdkconfig.defaults +++ b/components/eppp_link/examples/host/sdkconfig.defaults @@ -3,6 +3,6 @@ # CONFIG_UART_ISR_IN_IRAM=y CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_SERVER_SUPPORT=y CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n CONFIG_LWIP_PPP_DEBUG_ON=y -CONFIG_EPPP_LINK_DEVICE_SPI=y diff --git a/components/eppp_link/examples/slave/main/Kconfig.projbuild b/components/eppp_link/examples/slave/main/Kconfig.projbuild index ae4d7fb9a..c1d14e785 100644 --- a/components/eppp_link/examples/slave/main/Kconfig.projbuild +++ b/components/eppp_link/examples/slave/main/Kconfig.projbuild @@ -18,4 +18,28 @@ menu "Example Configuration" help Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent. + config EXAMPLE_UART_TX_PIN + int "TXD Pin Number" + depends on EPPP_LINK_DEVICE_UART + default 11 + range 0 31 + help + Pin number of UART TX. + + config EXAMPLE_UART_RX_PIN + int "RXD Pin Number" + depends on EPPP_LINK_DEVICE_UART + default 10 + range 0 31 + help + Pin number of UART RX. + + config EXAMPLE_UART_BAUDRATE + int "Baudrate" + depends on EPPP_LINK_DEVICE_UART + default 2000000 + range 0 4000000 + help + Baudrate used by the PPP over UART + endmenu diff --git a/components/eppp_link/examples/slave/main/station_example_main.c b/components/eppp_link/examples/slave/main/station_example_main.c index eca2a7b5f..ff39dcc50 100644 --- a/components/eppp_link/examples/slave/main/station_example_main.c +++ b/components/eppp_link/examples/slave/main/station_example_main.c @@ -1,12 +1,11 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ #include #include "freertos/FreeRTOS.h" -#include "freertos/task.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_wifi.h" @@ -15,9 +14,6 @@ #include "nvs_flash.h" #include "eppp_link.h" -#include "lwip/err.h" -#include "lwip/sys.h" - /* FreeRTOS event group to signal when we are connected*/ static EventGroupHandle_t s_wifi_event_group; @@ -123,7 +119,16 @@ void app_main(void) ESP_LOGI(TAG, "ESP_WIFI_MODE_STA"); wifi_init_sta(); - esp_netif_t *eppp_netif = eppp_listen(); + eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG(); +#if CONFIG_EPPP_LINK_DEVICE_SPI + config.transport = EPPP_TRANSPORT_SPI; +#else + config.transport = EPPP_TRANSPORT_UART; + config.uart.tx_io = CONFIG_EXAMPLE_UART_TX_PIN; + config.uart.rx_io = CONFIG_EXAMPLE_UART_RX_PIN; + config.uart.baud = CONFIG_EXAMPLE_UART_BAUDRATE; +#endif + esp_netif_t *eppp_netif = eppp_listen(&config); if (eppp_netif == NULL) { ESP_LOGE(TAG, "Failed to setup connection"); return ; diff --git a/components/eppp_link/include/eppp_link.h b/components/eppp_link/include/eppp_link.h index 4d8e820bf..1324f7cde 100644 --- a/components/eppp_link/include/eppp_link.h +++ b/components/eppp_link/include/eppp_link.h @@ -1,9 +1,111 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -esp_netif_t *eppp_connect(void); +#define EPPP_DEFAULT_SERVER_IP() ESP_IP4TOADDR(192, 168, 11, 1) +#define EPPP_DEFAULT_CLIENT_IP() ESP_IP4TOADDR(192, 168, 11, 2) -esp_netif_t *eppp_listen(void); +#define EPPP_DEFAULT_CONFIG(our_ip, their_ip) { \ + .transport = EPPP_TRANSPORT_UART, \ + .spi = { \ + .host = 1, \ + .mosi = 11, \ + .miso = 13, \ + .sclk = 12, \ + .cs = 10, \ + .intr = 2, \ + .freq = 16*1000*1000, \ + .input_delay_ns = 0, \ + .cs_ena_pretrans = 0, \ + .cs_ena_posttrans = 0, \ + }, \ + .uart = { \ + .port = 1, \ + .baud = 921600, \ + .tx_io = 25, \ + .rx_io = 26, \ + .queue_size = 16, \ + .rx_buffer_size = 1024, \ + }, \ + . task = { \ + .run_task = true, \ + .stack_size = 4096, \ + .priority = 8, \ + }, \ + . ppp = { \ + .our_ip4_addr.addr = our_ip, \ + .their_ip4_addr.addr = their_ip, \ + } \ +} + +#define EPPP_DEFAULT_SERVER_CONFIG() EPPP_DEFAULT_CONFIG(EPPP_DEFAULT_SERVER_IP(), EPPP_DEFAULT_CLIENT_IP()) +#define EPPP_DEFAULT_CLIENT_CONFIG() EPPP_DEFAULT_CONFIG(EPPP_DEFAULT_CLIENT_IP(), EPPP_DEFAULT_SERVER_IP()) + +typedef enum eppp_type { + EPPP_SERVER, + EPPP_CLIENT, +} eppp_type_t; + +typedef enum eppp_transport { + EPPP_TRANSPORT_UART, + EPPP_TRANSPORT_SPI, +} eppp_transport_t; + + +typedef struct eppp_config_t { + eppp_transport_t transport; + + struct eppp_config_spi_s { + int host; + int mosi; + int miso; + int sclk; + int cs; + int intr; + int freq; + int input_delay_ns; + int cs_ena_pretrans; + int cs_ena_posttrans; + } spi; + + struct eppp_config_uart_s { + int port; + int baud; + int tx_io; + int rx_io; + int queue_size; + int rx_buffer_size; + } uart; + + struct eppp_config_task_s { + bool run_task; + int stack_size; + int priority; + } task; + + struct eppp_config_pppos_s { + esp_ip4_addr_t our_ip4_addr; + esp_ip4_addr_t their_ip4_addr; + } ppp; + +} eppp_config_t; + +esp_netif_t *eppp_connect(eppp_config_t *config); + +esp_netif_t *eppp_listen(eppp_config_t *config); + +void eppp_close(esp_netif_t *netif); + +esp_netif_t *eppp_init(eppp_type_t role, eppp_config_t *config); + +void eppp_deinit(esp_netif_t *netif); + +esp_netif_t *eppp_open(eppp_type_t role, eppp_config_t *config, int connect_timeout_ms); + +esp_err_t eppp_netif_stop(esp_netif_t *netif, int stop_timeout_ms); + +esp_err_t eppp_netif_start(esp_netif_t *netif); + +esp_err_t eppp_perform(esp_netif_t *netif); diff --git a/components/eppp_link/test/test_app/CMakeLists.txt b/components/eppp_link/test/test_app/CMakeLists.txt new file mode 100644 index 000000000..cc4589c45 --- /dev/null +++ b/components/eppp_link/test/test_app/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following four lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/tools/unit-test-app/components) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_app) diff --git a/components/eppp_link/test/test_app/README.md b/components/eppp_link/test/test_app/README.md new file mode 100644 index 000000000..cb8add3aa --- /dev/null +++ b/components/eppp_link/test/test_app/README.md @@ -0,0 +1,73 @@ + +# Test application running both server and client on the same device + +Need to connect client's Tx to server's Rx and vice versa: +GPIO25 - GPIO4 +GPIO26 - GPIO5 + +We wait for the connection and then we start pinging the client's address on server's netif. + +## Example of output: + +``` +I (393) eppp_test_app: [APP] Startup.. +I (393) eppp_test_app: [APP] Free memory: 296332 bytes +I (393) eppp_test_app: [APP] IDF version: v5.3-dev-1154-gf14d9e7431-dirty +I (423) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (423) uart: queue free spaces: 16 +I (433) eppp_link: Waiting for IP address +I (433) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (443) uart: queue free spaces: 16 +I (443) eppp_link: Waiting for IP address +I (6473) esp-netif_lwip-ppp: Connected +I (6513) eppp_link: Got IPv4 event: Interface "pppos_client" address: 192.168.11.2 +I (6523) esp-netif_lwip-ppp: Connected +I (6513) eppp_link: Connected! +I (6523) eppp_link: Got IPv4 event: Interface "pppos_server" address: 192.168.11.1 +I (6553) main_task: Returned from app_main() +64bytes from 192.168.11.2 icmp_seq=1 ttl=255 time=18 ms +64bytes from 192.168.11.2 icmp_seq=2 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=3 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=4 ttl=255 time=20 ms +64bytes from 192.168.11.2 icmp_seq=5 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=6 ttl=255 time=19 ms +64bytes from 192.168.11.2 icmp_seq=7 ttl=255 time=19 ms +From 192.168.11.2 icmp_seq=8 timeout // <-- Disconnected Tx-Rx wires +From 192.168.11.2 icmp_seq=9 timeout +``` +## Test cases + +This test app exercises these methods of setting up server-client connection: +* simple blocking API (eppp_listen() <--> eppp_connect()): Uses network events internally and waits for connection +* simplified non-blocking API (eppp_open(EPPP_SERVER, ...) <--> eppp_open(EPPP_SERVER, ...) ): Uses events internally, optionally waits for connecting +* manual API (eppp_init(), eppp_netif_start(), eppp_perform()): User to manually drive Rx task + - Note that the ping test for this test case takes longer, since we call perform for both server and client from one task, for example: + +``` +TEST(eppp_test, open_close_taskless)I (28562) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (28572) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +Note: esp_netif_init() has been called. Until next reset, TCP/IP task will periodicially allocate memory and consume CPU time. +I (28602) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (28612) uart: queue free spaces: 16 +I (28612) uart: ESP_INTR_FLAG_IRAM flag not set while CONFIG_UART_ISR_IN_IRAM is enabled, flag updated +I (28622) uart: queue free spaces: 16 +I (28642) esp-netif_lwip-ppp: Connected +I (28642) esp-netif_lwip-ppp: Connected +I (28642) test: Got IPv4 event: Interface "pppos_server(EPPP0)" address: 192.168.11.1 +I (28642) esp-netif_lwip-ppp: Connected +I (28652) test: Got IPv4 event: Interface "pppos_client(EPPP1)" address: 192.168.11.2 +I (28662) esp-netif_lwip-ppp: Connected +64bytes from 192.168.11.2 icmp_seq=1 ttl=255 time=93 ms +64bytes from 192.168.11.2 icmp_seq=2 ttl=255 time=98 ms +64bytes from 192.168.11.2 icmp_seq=3 ttl=255 time=99 ms +64bytes from 192.168.11.2 icmp_seq=4 ttl=255 time=99 ms +64bytes from 192.168.11.2 icmp_seq=5 ttl=255 time=99 ms +5 packets transmitted, 5 received, time 488ms +I (29162) esp-netif_lwip-ppp: User interrupt +I (29162) test: Disconnected interface "pppos_client(EPPP1)" +I (29172) esp-netif_lwip-ppp: User interrupt +I (29172) test: Disconnected interface "pppos_server(EPPP0)" +MALLOC_CAP_8BIT usage: Free memory delta: 0 Leak threshold: -64 +MALLOC_CAP_32BIT usage: Free memory delta: 0 Leak threshold: -64 + PASS +``` diff --git a/components/eppp_link/test/test_app/main/CMakeLists.txt b/components/eppp_link/test/test_app/main/CMakeLists.txt new file mode 100644 index 000000000..c75a706cc --- /dev/null +++ b/components/eppp_link/test/test_app/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS app_main.c + INCLUDE_DIRS "." + REQUIRES test_utils + PRIV_REQUIRES unity nvs_flash esp_netif driver esp_event) diff --git a/components/eppp_link/test/test_app/main/app_main.c b/components/eppp_link/test/test_app/main/app_main.c new file mode 100644 index 000000000..e4edfa487 --- /dev/null +++ b/components/eppp_link/test/test_app/main/app_main.c @@ -0,0 +1,344 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "esp_system.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "eppp_link.h" +#include "lwip/sockets.h" +#include "esp_log.h" +#include "ping/ping_sock.h" +#include "driver/uart.h" +#include "test_utils.h" +#include "unity.h" +#include "test_utils.h" +#include "unity_fixture.h" +#include "memory_checks.h" +#include "lwip/sys.h" + +#define CLIENT_INFO_CONNECTED BIT0 +#define CLIENT_INFO_DISCONNECT BIT1 +#define CLIENT_INFO_CLOSED BIT2 +#define PING_SUCCEEDED BIT3 +#define PING_FAILED BIT4 +#define STOP_WORKER_TASK BIT5 +#define WORKER_TASK_STOPPED BIT6 + +TEST_GROUP(eppp_test); +TEST_SETUP(eppp_test) +{ + // Perform some open/close operations to disregard lazy init one-time allocations + // LWIP: core protection mutex + sys_arch_protect(); + sys_arch_unprotect(0); + // UART: install and delete both drivers to disregard potential leak in allocated interrupt slot + TEST_ESP_OK(uart_driver_install(UART_NUM_1, 256, 0, 0, NULL, 0)); + TEST_ESP_OK(uart_driver_delete(UART_NUM_1)); + TEST_ESP_OK(uart_driver_install(UART_NUM_2, 256, 0, 0, NULL, 0)); + TEST_ESP_OK(uart_driver_delete(UART_NUM_2)); + // PING: used for timestamps + struct timeval time; + gettimeofday(&time, NULL); + + test_utils_record_free_mem(); + TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL)); +} + +TEST_TEAR_DOWN(eppp_test) +{ + test_utils_finish_and_evaluate_leaks(32, 64); +} + +static void test_on_ping_end(esp_ping_handle_t hdl, void *args) +{ + EventGroupHandle_t event = args; + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + printf("%" PRId32 " packets transmitted, %" PRId32 " received, time %" PRId32 "ms\n", transmitted, received, total_time_ms); + if (transmitted == received) { + xEventGroupSetBits(event, PING_SUCCEEDED); + } else { + xEventGroupSetBits(event, PING_FAILED); + } +} + +static void test_on_ping_success(esp_ping_handle_t hdl, void *args) +{ + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + printf("%" PRId32 "bytes from %s icmp_seq=%d ttl=%d time=%" PRId32 " ms\n", + recv_len, inet_ntoa(target_addr.u_addr.ip4), seqno, ttl, elapsed_time); +} + +struct client_info { + esp_netif_t *netif; + EventGroupHandle_t event; +}; + +static void open_client_task(void *ctx) +{ + struct client_info *info = ctx; + eppp_config_t config = EPPP_DEFAULT_CLIENT_CONFIG(); + config.uart.port = UART_NUM_2; + config.uart.tx_io = 4; + config.uart.rx_io = 5; + + info->netif = eppp_connect(&config); + xEventGroupSetBits(info->event, CLIENT_INFO_CONNECTED); + + // wait for disconnection trigger + EventBits_t bits = xEventGroupWaitBits(info->event, CLIENT_INFO_DISCONNECT, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & CLIENT_INFO_DISCONNECT, CLIENT_INFO_DISCONNECT); + eppp_close(info->netif); + xEventGroupSetBits(info->event, CLIENT_INFO_CLOSED); + vTaskDelete(NULL); +} + +TEST(eppp_test, init_deinit) +{ + // Init and deinit server size + eppp_config_t config = EPPP_DEFAULT_CONFIG(0, 0); + esp_netif_t *netif = eppp_init(EPPP_SERVER, &config); + TEST_ASSERT_NOT_NULL(netif); + eppp_deinit(netif); + netif = NULL; + // Init and deinit client size + netif = eppp_init(EPPP_CLIENT, &config); + TEST_ASSERT_NOT_NULL(netif); + eppp_deinit(netif); +} + +static EventBits_t ping_test(uint32_t addr, esp_netif_t *netif, EventGroupHandle_t event) +{ + ip_addr_t target_addr = { .type = IPADDR_TYPE_V4, .u_addr.ip4.addr = addr }; + esp_ping_config_t ping_config = ESP_PING_DEFAULT_CONFIG(); + ping_config.interval_ms = 100; + ping_config.target_addr = target_addr; + ping_config.interface = esp_netif_get_netif_impl_index(netif); + esp_ping_callbacks_t cbs = { .cb_args = event, .on_ping_end = test_on_ping_end, .on_ping_success = test_on_ping_success }; + esp_ping_handle_t ping; + esp_ping_new_session(&ping_config, &cbs, &ping); + esp_ping_start(ping); + // Wait for the client thread closure and delete locally created objects + EventBits_t bits = xEventGroupWaitBits(event, PING_SUCCEEDED | PING_FAILED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + esp_ping_stop(ping); + esp_ping_delete_session(ping); + return bits; +} + +TEST(eppp_test, open_close) +{ + test_case_uses_tcpip(); + + eppp_config_t config = EPPP_DEFAULT_SERVER_CONFIG(); + struct client_info client = { .netif = NULL, .event = xEventGroupCreate()}; + + TEST_ESP_OK(esp_event_loop_create_default()); + + TEST_ASSERT_NOT_NULL(client.event); + + // Need to connect the client in a separate thread, as the simplified API blocks until connection + xTaskCreate(open_client_task, "client_task", 4096, &client, 5, NULL); + + // Now start the server + esp_netif_t *eppp_server = eppp_listen(&config); + + // Wait for the client to connect + EventBits_t bits = xEventGroupWaitBits(client.event, CLIENT_INFO_CONNECTED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & CLIENT_INFO_CONNECTED, CLIENT_INFO_CONNECTED); + + // Check that both server and client are valid netif pointers + TEST_ASSERT_NOT_NULL(eppp_server); + TEST_ASSERT_NOT_NULL(client.netif); + + // Now that we're connected, let's try to ping clients address + bits = ping_test(config.ppp.their_ip4_addr, eppp_server, client.event); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + + // Trigger client disconnection and close the server + xEventGroupSetBits(client.event, CLIENT_INFO_DISCONNECT); + eppp_close(eppp_server); + + // Wait for the client thread closure and delete locally created objects + bits = xEventGroupWaitBits(client.event, CLIENT_INFO_CLOSED, pdFALSE, pdFALSE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & CLIENT_INFO_CLOSED, CLIENT_INFO_CLOSED); + + TEST_ESP_OK(esp_event_loop_delete_default()); + vEventGroupDelete(client.event); + + // wait for the lwip sockets to close cleanly + vTaskDelay(pdMS_TO_TICKS(1000)); +} + +static void on_event(void *arg, esp_event_base_t base, int32_t event_id, void *data) +{ + EventGroupHandle_t event = arg; + if (base == IP_EVENT && event_id == IP_EVENT_PPP_GOT_IP) { + ip_event_got_ip_t *e = (ip_event_got_ip_t *)data; + esp_netif_t *netif = e->esp_netif; + ESP_LOGI("test", "Got IPv4 event: Interface \"%s(%s)\" address: " IPSTR, esp_netif_get_desc(netif), + esp_netif_get_ifkey(netif), IP2STR(&e->ip_info.ip)); + if (strcmp("pppos_server", esp_netif_get_desc(netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_SERVER); + } else if (strcmp("pppos_client", esp_netif_get_desc(netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_CLIENT); + } + } else if (base == NETIF_PPP_STATUS && event_id == NETIF_PPP_ERRORUSER) { + esp_netif_t **netif = data; + ESP_LOGI("test", "Disconnected interface \"%s(%s)\"", esp_netif_get_desc(*netif), esp_netif_get_ifkey(*netif)); + if (strcmp("pppos_server", esp_netif_get_desc(*netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_SERVER); + } else if (strcmp("pppos_client", esp_netif_get_desc(*netif)) == 0) { + xEventGroupSetBits(event, 1 << EPPP_CLIENT); + } + } +} + +TEST(eppp_test, open_close_nonblocking) +{ + test_case_uses_tcpip(); + EventGroupHandle_t event = xEventGroupCreate(); + + eppp_config_t server_config = EPPP_DEFAULT_SERVER_CONFIG(); + TEST_ESP_OK(esp_event_loop_create_default()); + + // Open the server size + TEST_ESP_OK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, event)); + esp_netif_t *eppp_server = eppp_open(EPPP_SERVER, &server_config, 0); + TEST_ASSERT_NOT_NULL(eppp_server); + // Open the client size + eppp_config_t client_config = EPPP_DEFAULT_SERVER_CONFIG(); + client_config.uart.port = UART_NUM_2; + client_config.uart.tx_io = 4; + client_config.uart.rx_io = 5; + esp_netif_t *eppp_client = eppp_open(EPPP_CLIENT, &client_config, 0); + TEST_ASSERT_NOT_NULL(eppp_client); + const EventBits_t wait_bits = (1 << EPPP_SERVER) | (1 << EPPP_CLIENT); + EventBits_t bits = xEventGroupWaitBits(event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits); + + // Now that we're connected, let's try to ping clients address + bits = ping_test(server_config.ppp.their_ip4_addr, eppp_server, event); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + + // stop network for both client and server + eppp_netif_stop(eppp_client, 0); // ignore result, since we're not waiting for clean close + eppp_close(eppp_server); + eppp_close(eppp_client); // finish client close + TEST_ESP_OK(esp_event_loop_delete_default()); + vEventGroupDelete(event); + + // wait for the lwip sockets to close cleanly + vTaskDelay(pdMS_TO_TICKS(1000)); +} + + +struct worker { + esp_netif_t *eppp_server; + esp_netif_t *eppp_client; + EventGroupHandle_t event; +}; + +static void worker_task(void *ctx) +{ + struct worker *info = ctx; + while (1) { + eppp_perform(info->eppp_server); + eppp_perform(info->eppp_client); + if (xEventGroupGetBits(info->event) & STOP_WORKER_TASK) { + break; + } + } + xEventGroupSetBits(info->event, WORKER_TASK_STOPPED); + vTaskDelete(NULL); +} + +TEST(eppp_test, open_close_taskless) +{ + test_case_uses_tcpip(); + struct worker info = { .event = xEventGroupCreate() }; + + TEST_ESP_OK(esp_event_loop_create_default()); + TEST_ESP_OK(esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, on_event, info.event)); + TEST_ESP_OK(esp_event_handler_register(NETIF_PPP_STATUS, ESP_EVENT_ANY_ID, on_event, info.event)); + + // Create server + eppp_config_t server_config = EPPP_DEFAULT_SERVER_CONFIG(); + info.eppp_server = eppp_init(EPPP_SERVER, &server_config); + TEST_ASSERT_NOT_NULL(info.eppp_server); + // Create client + eppp_config_t client_config = EPPP_DEFAULT_CLIENT_CONFIG(); + client_config.uart.port = UART_NUM_2; + client_config.uart.tx_io = 4; + client_config.uart.rx_io = 5; + info.eppp_client = eppp_init(EPPP_CLIENT, &client_config); + TEST_ASSERT_NOT_NULL(info.eppp_client); + // Start workers + xTaskCreate(worker_task, "worker", 4096, &info, 5, NULL); + // Start network + TEST_ESP_OK(eppp_netif_start(info.eppp_server)); + TEST_ESP_OK(eppp_netif_start(info.eppp_client)); + + const EventBits_t wait_bits = (1 << EPPP_SERVER) | (1 << EPPP_CLIENT); + EventBits_t bits = xEventGroupWaitBits(info.event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits); + xEventGroupClearBits(info.event, wait_bits); + + // Now that we're connected, let's try to ping clients address + bits = ping_test(server_config.ppp.their_ip4_addr, info.eppp_server, info.event); + TEST_ASSERT_EQUAL(bits & (PING_SUCCEEDED | PING_FAILED), PING_SUCCEEDED); + + // stop network for both client and server, we won't wait for completion so expecting ESP_FAIL + TEST_ASSERT_EQUAL(eppp_netif_stop(info.eppp_client, 0), ESP_FAIL); + TEST_ASSERT_EQUAL(eppp_netif_stop(info.eppp_server, 0), ESP_FAIL); + // and wait for completion + bits = xEventGroupWaitBits(info.event, wait_bits, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & wait_bits, wait_bits); + + // now stop the worker + xEventGroupSetBits(info.event, STOP_WORKER_TASK); + bits = xEventGroupWaitBits(info.event, WORKER_TASK_STOPPED, pdTRUE, pdTRUE, pdMS_TO_TICKS(50000)); + TEST_ASSERT_EQUAL(bits & WORKER_TASK_STOPPED, WORKER_TASK_STOPPED); + + // and destroy objects + eppp_deinit(info.eppp_server); + eppp_deinit(info.eppp_client); + TEST_ESP_OK(esp_event_loop_delete_default()); + vEventGroupDelete(info.event); + + // wait for the lwip sockets to close cleanly + vTaskDelay(pdMS_TO_TICKS(1000)); +} + + +TEST_GROUP_RUNNER(eppp_test) +{ + RUN_TEST_CASE(eppp_test, init_deinit) + RUN_TEST_CASE(eppp_test, open_close) + RUN_TEST_CASE(eppp_test, open_close_nonblocking) + RUN_TEST_CASE(eppp_test, open_close_taskless) +} + +void app_main(void) +{ + UNITY_MAIN(eppp_test); +} diff --git a/components/eppp_link/test/test_app/main/idf_component.yml b/components/eppp_link/test/test_app/main/idf_component.yml new file mode 100644 index 000000000..7ecb517e8 --- /dev/null +++ b/components/eppp_link/test/test_app/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + espressif/eppp_link: + version: "*" + override_path: "../../.." diff --git a/components/eppp_link/test/test_app/sdkconfig.defaults b/components/eppp_link/test/test_app/sdkconfig.defaults new file mode 100644 index 000000000..0442a3bc8 --- /dev/null +++ b/components/eppp_link/test/test_app/sdkconfig.defaults @@ -0,0 +1,12 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) 5.3.0 Project Minimal Configuration +# +CONFIG_UART_ISR_IN_IRAM=y +CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=0 +CONFIG_FREERTOS_UNICORE=y +CONFIG_HEAP_TRACING_STANDALONE=y +CONFIG_HEAP_TRACING_STACK_DEPTH=6 +CONFIG_LWIP_PPP_SUPPORT=y +CONFIG_LWIP_PPP_VJ_HEADER_COMPRESSION=n +CONFIG_LWIP_PPP_DEBUG_ON=y +CONFIG_UNITY_ENABLE_FIXTURE=y