diff --git a/.github/workflows/eppp__build.yml b/.github/workflows/eppp__build.yml index 2e863d00fd..ed43a9e868 100644 --- a/.github/workflows/eppp__build.yml +++ b/.github/workflows/eppp__build.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: idf_ver: ["latest", "release-v5.3"] - test: [ { app: host, path: "examples/host" }, { app: slave, path: "examples/slave" }, { app: test_app, path: "test/test_app" }] + test: [ { app: host, path: "examples/host" }, { app: slave, path: "examples/slave" }, { app: host, path: "examples/emac2emac" }, { app: test_app, path: "test/test_app" }] runs-on: ubuntu-20.04 container: espressif/idf:${{ matrix.idf_ver }} steps: diff --git a/components/eppp_link/eppp_eth.c b/components/eppp_link/eppp_eth.c index d2a2620d12..769af8cf1e 100644 --- a/components/eppp_link/eppp_eth.c +++ b/components/eppp_link/eppp_eth.c @@ -65,28 +65,39 @@ static esp_err_t receive(esp_eth_handle_t h, uint8_t *buffer, uint32_t len, void return ESP_FAIL; } -esp_err_t eppp_transport_init(eppp_config_t *config, esp_netif_t *esp_netif) +__attribute__((weak)) esp_err_t eppp_transport_ethernet_init(esp_eth_handle_t *handle_array[]) { uint8_t eth_port_cnt = 0; - ESP_ERROR_CHECK(ethernet_init_all(&s_eth_handles, ð_port_cnt)); - if (eth_port_cnt > 1) { - ESP_LOGW(TAG, "multiple Ethernet devices detected, the first initialized is to be used!"); - } - ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handles[0], receive, esp_netif)); + ESP_RETURN_ON_ERROR(ethernet_init_all(handle_array, ð_port_cnt), TAG, "Failed to init common eth drivers"); + ESP_RETURN_ON_FALSE(eth_port_cnt > 1, ESP_ERR_INVALID_ARG, TAG, "multiple Ethernet devices detected, please init only one"); + return ESP_OK; +} + +__attribute__((weak)) void eppp_transport_ethernet_deinit(esp_eth_handle_t *handle_array) +{ + ethernet_deinit_all(s_eth_handles); +} + + +esp_err_t eppp_transport_init(eppp_config_t *config, esp_netif_t *esp_netif) +{ + ESP_RETURN_ON_ERROR(eppp_transport_ethernet_init(&s_eth_handles), TAG, "Failed to initialize Ethernet driver"); + ESP_RETURN_ON_ERROR(esp_eth_update_input_path(s_eth_handles[0], receive, esp_netif), TAG, "Failed to set Ethernet Rx callback"); sscanf(CONFIG_EPPP_LINK_ETHERNET_OUR_ADDRESS, "%2" PRIu8 ":%2" PRIu8 ":%2" PRIi8 ":%2" PRIu8 ":%2" PRIu8 ":%2" PRIu8, &s_our_mac[0], &s_our_mac[1], &s_our_mac[2], &s_our_mac[3], &s_our_mac[4], &s_our_mac[5]); sscanf(CONFIG_EPPP_LINK_ETHERNET_THEIR_ADDRESS, "%2" PRIu8 ":%2" PRIu8 ":%2" PRIi8 ":%2" PRIu8 ":%2" PRIu8 ":%2" PRIu8, &s_their_mac[0], &s_their_mac[1], &s_their_mac[2], &s_their_mac[3], &s_their_mac[4], &s_their_mac[5]); esp_eth_ioctl(s_eth_handles[0], ETH_CMD_S_MAC_ADDR, s_our_mac); - ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL)); - ESP_ERROR_CHECK(esp_eth_start(s_eth_handles[0])); + ESP_RETURN_ON_ERROR(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, event_handler, NULL), TAG, "Failed to register Ethernet handlers"); + ESP_RETURN_ON_ERROR(esp_eth_start(s_eth_handles[0]), TAG, "Failed to start Ethernet driver"); return ESP_OK; } void eppp_transport_deinit(void) { - ethernet_deinit_all(s_eth_handles); + esp_eth_stop(s_eth_handles[0]); + eppp_transport_ethernet_deinit(s_eth_handles); } esp_err_t eppp_transport_tx(void *h, void *buffer, size_t len) diff --git a/components/eppp_link/examples/emac2emac/CMakeLists.txt b/components/eppp_link/examples/emac2emac/CMakeLists.txt new file mode 100644 index 0000000000..ba8427665b --- /dev/null +++ b/components/eppp_link/examples/emac2emac/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) + + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pppos_host) diff --git a/components/eppp_link/examples/emac2emac/README.md b/components/eppp_link/examples/emac2emac/README.md new file mode 100644 index 0000000000..8cd5ff5e20 --- /dev/null +++ b/components/eppp_link/examples/emac2emac/README.md @@ -0,0 +1,12 @@ + +# EPPP link with EMAC to EMAC transport layer + +This example runs a symmetrical server-client eppp application with iperf component using Ethernet transport layer, with a customized Ethernet driver from [eth_dummy_phy](https://components.espressif.com/components/espressif/eth_dummy_phy) component. + +Please refer to the component documentation for more information about the principle of operation and the actual physical connection between nodes. + +## How to use this example + +* Choose `CONFIG_EXAMPLE_NODE_SERVER` for the device connected as **RMII CLK Source Device** +* Choose `EXAMPLE_NODE_CLIENT` for the device connected as **RMII CLK Sink Device** +* Run `iperf` command on both sides to check the network performance (both server and client iperf role could be used on both devices) diff --git a/components/eppp_link/examples/emac2emac/main/CMakeLists.txt b/components/eppp_link/examples/emac2emac/main/CMakeLists.txt new file mode 100644 index 0000000000..0198ddd323 --- /dev/null +++ b/components/eppp_link/examples/emac2emac/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS app_main.c register_iperf.c + INCLUDE_DIRS ".") diff --git a/components/eppp_link/examples/emac2emac/main/Kconfig.projbuild b/components/eppp_link/examples/emac2emac/main/Kconfig.projbuild new file mode 100644 index 0000000000..1f06a124d9 --- /dev/null +++ b/components/eppp_link/examples/emac2emac/main/Kconfig.projbuild @@ -0,0 +1,28 @@ +menu "Example Configuration" + + choice EXAMPLE_NODE_ROLE + prompt "Choose the device role" + default EXAMPLE_NODE_SERVER + help + Select whether this device acts as a server or a client + + config EXAMPLE_NODE_SERVER + bool "Server" + help + Act as an EPPP server, source RMII clock + + config EXAMPLE_NODE_CLIENT + bool "Client" + help + Act as an EPPP client, use server's clock + + endchoice + + config EXAMPLE_RMII_CLK_READY_GPIO + int "RMII CLK Sink Device is ready GPIO" + default 4 + help + GPIO number at which the "RMII CLK Sink Device" is ready and so the "RMII + CLK Source Device" can continue in its Ethernet initialization. + +endmenu diff --git a/components/eppp_link/examples/emac2emac/main/app_main.c b/components/eppp_link/examples/emac2emac/main/app_main.c new file mode 100644 index 0000000000..c3ca058b5e --- /dev/null +++ b/components/eppp_link/examples/emac2emac/main/app_main.c @@ -0,0 +1,201 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include +#include "esp_system.h" +#include "nvs_flash.h" +#include "esp_event.h" +#include "esp_netif.h" +#include "eppp_link.h" +#include "esp_log.h" +#include "esp_check.h" +#include "console_ping.h" +#include "esp_eth_driver.h" +#include "driver/gpio.h" +#include "esp_eth_phy_dummy.h" + +#ifdef CONFIG_EXAMPLE_NODE_SERVER +#define EPPP_CONFIG() EPPP_DEFAULT_SERVER_CONFIG() +#define EPPP_ROLE EPPP_SERVER +#define EPPP_RMII_CLK_SINK +#else +#define EPPP_CONFIG() EPPP_DEFAULT_CLIENT_CONFIG() +#define EPPP_ROLE EPPP_CLIENT +#define EPPP_RMII_CLK_SOURCE +#endif + +void register_iperf(void); + +static const char *TAG = "eppp_emac2emac"; + + +static esp_eth_mac_t *s_mac = NULL; +static esp_eth_phy_t *s_phy = NULL; + +#ifdef EPPP_RMII_CLK_SOURCE +IRAM_ATTR static void gpio_isr_handler(void *arg) +{ + BaseType_t high_task_wakeup = pdFALSE; + TaskHandle_t task_handle = (TaskHandle_t)arg; + + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + if (high_task_wakeup != pdFALSE) { + portYIELD_FROM_ISR(); + } +} +#else +#define STARTUP_DELAY_MS 500 +#endif + +esp_err_t eppp_transport_ethernet_init(esp_eth_handle_t *handle[]) +{ + *handle = malloc(sizeof(esp_eth_handle_t)); + ESP_RETURN_ON_FALSE(handle, ESP_ERR_NO_MEM, TAG, "Our of memory"); + +#ifdef EPPP_RMII_CLK_SOURCE + esp_rom_gpio_pad_select_gpio(EMAC_CLK_OUT_180_GPIO); + gpio_set_pull_mode(EMAC_CLK_OUT_180_GPIO, GPIO_FLOATING); // to not affect GPIO0 (so the Sink Device could be flashed) + gpio_install_isr_service(0); + gpio_config_t gpio_source_cfg = { + .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_RMII_CLK_READY_GPIO), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_ANYEDGE + }; + gpio_config(&gpio_source_cfg); + TaskHandle_t task_handle = xTaskGetHandle(pcTaskGetName(NULL)); + gpio_isr_handler_add(CONFIG_EXAMPLE_RMII_CLK_READY_GPIO, gpio_isr_handler, task_handle); + ESP_LOGW(TAG, "waiting for RMII CLK sink device interrupt"); + ESP_LOGW(TAG, "if RMII CLK sink device is already running, reset it by `EN` button"); + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (gpio_get_level(CONFIG_EXAMPLE_RMII_CLK_READY_GPIO) == 1) { + break; + } + } + ESP_LOGI(TAG, "starting Ethernet initialization"); +#else + gpio_config_t gpio_sink_cfg = { + .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_RMII_CLK_READY_GPIO), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&gpio_sink_cfg); + gpio_set_level(CONFIG_EXAMPLE_RMII_CLK_READY_GPIO, 0); + vTaskDelay(pdMS_TO_TICKS(STARTUP_DELAY_MS)); + gpio_set_level(CONFIG_EXAMPLE_RMII_CLK_READY_GPIO, 1); +#endif // EPPP_RMII_CLK_SOURCE + + // --- Initialize Ethernet driver --- + // Init common MAC and PHY configs to default + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + + // Update PHY config based on board specific configuration + phy_config.reset_gpio_num = -1; // no HW reset + + // Init vendor specific MAC config to default + eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); + // Update vendor specific MAC config based on board configuration + // No SMI, speed/duplex must be statically configured the same in both devices +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + esp32_emac_config.smi_gpio.mdc_num = -1; + esp32_emac_config.smi_gpio.mdio_num = -1; +#else + esp32_emac_config.smi_mdc_gpio_num = -1; + esp32_emac_config.smi_mdio_gpio_num = -1; +#endif +#ifdef EPPP_RMII_CLK_SOURCE + esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT; + esp32_emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_OUT_180_GPIO; +#else + esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; + esp32_emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_IN_GPIO; +#endif // EPPP_RMII_CLK_SOURCE + + // Create new ESP32 Ethernet MAC instance + s_mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); + // Create dummy PHY instance + s_phy = esp_eth_phy_new_dummy(&phy_config); + + // Init Ethernet driver to default and install it + esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy); +#ifdef EPPP_RMII_CLK_SINK + // REF RMII CLK sink device performs multiple EMAC init attempts since RMII CLK source device may not be ready yet + int i; + for (i = 1; i <= 5; i++) { + ESP_LOGI(TAG, "Ethernet driver install attempt: %i", i); + if (esp_eth_driver_install(&config, *handle) == ESP_OK) { + break; + } + vTaskDelay(pdMS_TO_TICKS(100)); + } + ESP_RETURN_ON_FALSE(i <= 5, ESP_FAIL, TAG, "Ethernet driver install failed"); +#else + ESP_RETURN_ON_ERROR(esp_eth_driver_install(&config, *handle), TAG, "Ethernet driver install failed"); +#endif // EPPP_RMII_CLK_SINK + return ESP_OK; + +} + +void app_main(void) +{ + ESP_LOGI(TAG, "[APP] Startup.."); + ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); + ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + /* Sets up the default EPPP-connection + */ + eppp_config_t config = EPPP_CONFIG(); + config.transport = EPPP_TRANSPORT_ETHERNET; + esp_netif_t *eppp_netif = eppp_open(EPPP_ROLE, &config, portMAX_DELAY); + if (eppp_netif == NULL) { + ESP_LOGE(TAG, "Failed to connect"); + return ; + } + // Initialize console REPL + ESP_ERROR_CHECK(console_cmd_init()); + + register_iperf(); + + printf("\n =======================================================\n"); + printf(" | Steps to Test EPPP-emac2emca bandwidth |\n"); + printf(" | |\n"); + printf(" | 1. Wait for the ESP32 to get an IP |\n"); + printf(" | 2. Server: 'iperf -u -s -i 3' (on host) |\n"); + printf(" | 3. Client: 'iperf -u -c SERVER_IP -t 60 -i 3' |\n"); + printf(" | |\n"); + printf(" =======================================================\n\n"); + + // using also ping command to check basic network connectivity + ESP_ERROR_CHECK(console_cmd_ping_register()); + ESP_ERROR_CHECK(console_cmd_start()); + + // handle GPIO0 workaround for ESP32 +#ifdef CONFIG_EXAMPLE_NODE_SERVER + // Wait indefinitely or reset when "RMII CLK Sink Device" resets + // We reset the "RMII CLK Source Device" to ensure there is no CLK at GPIO0 of the + // "RMII CLK Sink Device" during startup + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (gpio_get_level(CONFIG_EXAMPLE_RMII_CLK_READY_GPIO) == 0) { + break; + } + } + ESP_LOGW(TAG, "RMII CLK Sink device reset, I'm going to reset too!"); + esp_restart(); +#endif +} diff --git a/components/eppp_link/examples/emac2emac/main/idf_component.yml b/components/eppp_link/examples/emac2emac/main/idf_component.yml new file mode 100644 index 0000000000..5c4d0630a7 --- /dev/null +++ b/components/eppp_link/examples/emac2emac/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + espressif/iperf-cmd: "^0.1.1" + espressif/eppp_link: + version: "*" + override_path: "../../.." + console_cmd_ping: "*" + espressif/eth_dummy_phy: "*" diff --git a/components/eppp_link/examples/emac2emac/main/register_iperf.c b/components/eppp_link/examples/emac2emac/main/register_iperf.c new file mode 100644 index 0000000000..63fded10c5 --- /dev/null +++ b/components/eppp_link/examples/emac2emac/main/register_iperf.c @@ -0,0 +1,179 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "sys/socket.h" // for INADDR_ANY +#include "esp_netif.h" +#include "esp_log.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ppp.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" + +#include "esp_console.h" +#include "esp_event.h" +#include "esp_bit_defs.h" +#include "argtable3/argtable3.h" +#include "iperf.h" +#include "sdkconfig.h" + +/* "iperf" command */ + +static struct { + struct arg_str *ip; + struct arg_lit *server; + struct arg_lit *udp; + struct arg_lit *version; + struct arg_int *port; + struct arg_int *length; + struct arg_int *interval; + struct arg_int *time; + struct arg_int *bw_limit; + struct arg_lit *abort; + struct arg_end *end; +} iperf_args; + +static int ppp_cmd_iperf(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **)&iperf_args); + // 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; + } + + /* iperf -a */ + if (iperf_args.abort->count != 0) { + iperf_stop(); + return 0; + } + + if (((iperf_args.ip->count == 0) && (iperf_args.server->count == 0)) || + ((iperf_args.ip->count != 0) && (iperf_args.server->count != 0))) { + ESP_LOGE(__func__, "Wrong mode! ESP32 should run in client or server mode"); + return 0; + } + + /* iperf -s */ + if (iperf_args.ip->count == 0) { + cfg.flag |= IPERF_FLAG_SERVER; + } + /* iperf -c SERVER_ADDRESS */ + else { + cfg.destination_ip4 = esp_ip4addr_aton(iperf_args.ip->sval[0]); + cfg.flag |= IPERF_FLAG_CLIENT; + } + + if (iperf_args.length->count == 0) { + cfg.len_send_buf = 0; + } else { + cfg.len_send_buf = iperf_args.length->ival[0]; + } + + cfg.source_ip4 = INADDR_ANY; + + /* iperf -u */ + if (iperf_args.udp->count == 0) { + cfg.flag |= IPERF_FLAG_TCP; + } else { + cfg.flag |= IPERF_FLAG_UDP; + } + + /* iperf -p */ + if (iperf_args.port->count == 0) { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + if (cfg.flag & IPERF_FLAG_SERVER) { + cfg.sport = iperf_args.port->ival[0]; + cfg.dport = IPERF_DEFAULT_PORT; + } else { + cfg.sport = IPERF_DEFAULT_PORT; + cfg.dport = iperf_args.port->ival[0]; + } + } + + /* iperf -i */ + if (iperf_args.interval->count == 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } else { + cfg.interval = iperf_args.interval->ival[0]; + if (cfg.interval <= 0) { + cfg.interval = IPERF_DEFAULT_INTERVAL; + } + } + + /* iperf -t */ + if (iperf_args.time->count == 0) { + cfg.time = IPERF_DEFAULT_TIME; + } else { + cfg.time = iperf_args.time->ival[0]; + if (cfg.time <= cfg.interval) { + cfg.time = cfg.interval; + } + } + + /* iperf -b */ + if (iperf_args.bw_limit->count == 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } else { + cfg.bw_lim = iperf_args.bw_limit->ival[0]; + if (cfg.bw_lim <= 0) { + cfg.bw_lim = IPERF_DEFAULT_NO_BW_LIMIT; + } + } + + printf("mode=%s-%s sip=" IPSTR ":%" PRIu16 ", dip=%" PRIu32 ".%" PRIu32 ".%" PRIu32 ".%" PRIu32 ":%" PRIu16 ", interval=%" PRIu32 ", time=%" PRIu32 "\r\n", + cfg.flag & IPERF_FLAG_TCP ? "tcp" : "udp", + cfg.flag & IPERF_FLAG_SERVER ? "server" : "client", + (uint16_t) cfg.source_ip4 & 0xFF, + (uint16_t) (cfg.source_ip4 >> 8) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 16) & 0xFF, + (uint16_t) (cfg.source_ip4 >> 24) & 0xFF, + cfg.sport, + cfg.destination_ip4 & 0xFF, (cfg.destination_ip4 >> 8) & 0xFF, + (cfg.destination_ip4 >> 16) & 0xFF, (cfg.destination_ip4 >> 24) & 0xFF, cfg.dport, + cfg.interval, cfg.time); + + iperf_start(&cfg); + return 0; +} + +void register_iperf(void) +{ + + iperf_args.ip = arg_str0("c", "client", "", + "run in client mode, connecting to "); + iperf_args.server = arg_lit0("s", "server", "run in server mode"); + iperf_args.udp = arg_lit0("u", "udp", "use UDP rather than TCP"); + iperf_args.version = arg_lit0("V", "ipv6_domain", "use IPV6 address rather than IPV4"); + iperf_args.port = arg_int0("p", "port", "", + "server port to listen on/connect to"); + iperf_args.length = arg_int0("l", "len", "", "set read/write buffer size"); + iperf_args.interval = arg_int0("i", "interval", "", + "seconds between periodic bandwidth reports"); + iperf_args.time = arg_int0("t", "time", "