feat(esp_eth): update test_apps to use Ethernet test component

Move test logic into the eth_test_app managed component and slim down
the test_apps main target and pytest suite accordingly.
This commit is contained in:
Ondrej Kosta
2026-06-03 09:45:41 +02:00
parent fa05720f6c
commit 2be2a1370e
10 changed files with 93 additions and 2002 deletions

View File

@@ -0,0 +1,46 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import sys
from collections.abc import Callable
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
if TYPE_CHECKING:
from eth_test_runner import EthTestRunner
def _setup_eth_test_runner_sys_path() -> Path | None:
root = Path(__file__).resolve().parent
for name in ('espressif__eth_test_app', 'eth_test_app'):
candidate = root / 'managed_components' / name
if (candidate / 'eth_test_runner.py').exists():
candidate_str = str(candidate)
if candidate_str not in sys.path:
sys.path.insert(0, candidate_str)
return candidate
return None
# Support local runs when managed_components is already present after build.
_setup_eth_test_runner_sys_path()
@pytest.fixture
def eth_test_runner(
app_path: str,
download_app_extra: Callable[[str], None],
) -> 'EthTestRunner':
runner_dir = _setup_eth_test_runner_sys_path()
if runner_dir is None:
download_app_extra(app_path)
runner_dir = _setup_eth_test_runner_sys_path()
if runner_dir is None:
raise RuntimeError('eth_test_runner.py not found. Build the test app locally or download CI artifacts first.')
from eth_test_runner import EthTestRunner
return EthTestRunner()

View File

@@ -1,12 +1,8 @@
idf_component_register(SRCS "esp_eth_test_main.c"
"esp_eth_test_apps.c"
"esp_eth_test_l2.c"
"esp_eth_test_utils.c"
"esp_eth_test_esp_emac.c"
"esp_eth_test_emac_sleep_retention.c"
"esp_eth_test_emac_init.cpp"
INCLUDE_DIRS "."
PRIV_INCLUDE_DIRS "."
PRIV_REQUIRES unity esp_eth esp_netif esp_http_client esp_driver_gpio esp_driver_uart
EMBED_TXTFILES dl_espressif_com_root_cert.pem
PRIV_REQUIRES unity esp_eth esp_netif esp_event hal esp_hw_support
esp_driver_gpio esp_driver_uart esp_pm
WHOLE_ARCHIVE)

View File

@@ -1,91 +0,0 @@
menu "esp_eth TEST_APPS Configuration"
config ETH_TEST_UNITY_TEST_TASK_PRIO
int "Unity test task priority"
range 0 25
default 5
help
Unity test task priority.
config ETH_TEST_UNITY_TEST_TASK_STACK
int "Unity test task stack size"
default 4096
help
Unity test task stack size in B.
config ETH_TEST_MAC_ADDR_UI
bool "Expect Universal & Individual MAC address"
default y
help
Enable this option to expect Universal & Individual MAC address.
This should be disabled for SPI Ethernet devices.
config ETH_TEST_PHY_ADDRESS_DISABLED
bool "Disable PHY address test"
default n
help
Disable PHY address testing.
This should be disabled for SPI Ethernet devices.
config ETH_TEST_LOOPBACK_DISABLED
bool "Disable loopback test"
default n
help
Disable loopback testing as some chips may not support it.
config ETH_TEST_10MB_LOOPBACK_DISABLED
depends on !ETH_TEST_LOOPBACK_DISABLED
bool "Disable 10MB loopback test"
default n
help
Disable 10MB loopback testing as some chips may not support it.
config ETH_TEST_10MB_LOOPBACK_IGNORE_FAILURES
depends on !ETH_TEST_LOOPBACK_DISABLED
bool "Ignore 10MB loopback test failures"
default y
help
Ignore failures in 10MB loopback tests.
config ETH_TEST_LOOPBACK_WITH_AUTONEGOTIATION_DISABLED
depends on !ETH_TEST_LOOPBACK_DISABLED
bool "Disable loopback with autonegotiation test"
default n
help
Disable loopback testing with autonegotiation.
config ETH_TEST_FILL_RX_BUFFER_ITERATIONS
int "Number of iterations for fill RX buffer test"
range 1 100
default 10
help
Number of iterations to fill RX buffer of module under test.
For internal EMAC, it should be set as ETH_DMA_RX_BUFFER_NUM.
For SPI modules, it should be set as RX_MEM_SIZE / ETH_MAX_PACKET_SIZE, where RX_MEM_SIZE is
module specific RX buffer size in bytes as defined in datasheet.
config ETH_TEST_LAN8720_ERRATA_ENABLED
bool "Enable LAN8720 errata workaround"
default n
help
Enable workaround for LAN8720 errata.
Enable only when LAN8720 is under test!!
config ETH_TEST_W5500_IP6_MCAST_DEVIATION_ENABLED
bool "Enable W5500 IPv6 multicast deviation test"
default n
help
W5500 always receives IPv6 multicast packets, even if the filter is set to block multicast.
Enable only when W5500 is under test!!
config ETH_TEST_STRESS_TEST_TASK_PRIO
int "Start/stop stress test task priority"
range -1 25
default -1
help
The Rx Task may occupy all the resources under heavy Rx traffic and it would not be possible
to access the Ethernet module to stop it. Therefore, the test task priority can be set higher than
the Rx task to be able to preempt the Rx task.
This option should be set for SPI Ethernet modules (>15), otherwise set -1 to use default priority.
endmenu

View File

@@ -1,22 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

View File

@@ -1,554 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "esp_http_client.h"
#include "esp_rom_md5.h"
#include "esp_eth_test_utils.h"
#include "unity.h"
#define LOOPBACK_TEST_PACKET_SIZE 256
static const char *TAG = "esp32_eth_test";
extern const char dl_espressif_com_root_cert_pem_start[] asm("_binary_dl_espressif_com_root_cert_pem_start");
extern const char dl_espressif_com_root_cert_pem_end[] asm("_binary_dl_espressif_com_root_cert_pem_end");
// compute md5 of download file
static md5_context_t md5_context;
static uint8_t digest[16];
// Basic test to verify that the Ethernet driver can be initialized and deinitialized
TEST_CASE("ethernet init/deinit test", "[ethernet],[skip_setup_teardown]")
{
esp_eth_handle_t eth_handle = NULL;
TEST_ESP_OK(esp_eth_test_eth_init(&eth_handle));
TEST_ESP_OK(esp_eth_test_eth_deinit(eth_handle));
}
TEST_CASE("ethernet io test", "[ethernet]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
/* get default MAC address */
uint8_t mac_addr[ETH_ADDR_LEN];
memset(mac_addr, 0, sizeof(mac_addr));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr));
ESP_LOGI(TAG, "Ethernet MAC Address: %02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
TEST_ASSERT(mac_addr[0] != 0);
// *** SPI Ethernet modules deviation ***
// Rationale: The SPI Ethernet modules don't have a burned default factory MAC address hence local MAC is used
#if CONFIG_ETH_TEST_MAC_ADDR_UI
TEST_ASSERT_BITS(0b00000011, 0b00, mac_addr[0]); // Check UL&IG, should be UI
#else
TEST_ASSERT_BITS(0b00000011, 0b10, mac_addr[0]); // Check UL&IG, should be U
#endif
/* set different MAC address */
mac_addr[5] ^= mac_addr[4];
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr));
/* get new MAC address */
uint8_t mac_addr_new[ETH_ADDR_LEN] = { 0 };
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr_new));
ESP_LOGI(TAG, "Ethernet MAC Address: %02x:%02x:%02x:%02x:%02x:%02x",
mac_addr_new[0], mac_addr_new[1], mac_addr_new[2], mac_addr_new[3], mac_addr_new[4], mac_addr_new[5]);
TEST_ASSERT_EQUAL_UINT8_ARRAY(mac_addr_new, mac_addr, ETH_ADDR_LEN);
// *** SPI Ethernet modules deviation ***
// Rationale: SPI Ethernet modules PHYs and MACs are statically configured at one die, hence there is no need for PHY address
// from user's point of view
#if CONFIG_ETH_TEST_PHY_ADDRESS_DISABLED
/* get PHY address */
int phy_addr = -1;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_PHY_ADDR, &phy_addr));
ESP_LOGI(TAG, "Ethernet PHY Address: %d", phy_addr);
TEST_ASSERT(phy_addr >= 0 && phy_addr <= 31);
#endif
}
// This test expects autonegotiation to be enabled on the other node.
TEST_CASE("ethernet io speed/duplex/autonegotiation", "[ethernet]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
EventBits_t bits = 0;
// this test only test layer2, so don't need to register input callback (i.e. esp_eth_update_input_path)
TEST_ESP_OK(esp_eth_start(eth_handle));
// wait for connection start
bits = xEventGroupWaitBits(eth_event_group, ETH_START_BIT, true, true, pdMS_TO_TICKS(ETH_START_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_START_BIT) == ETH_START_BIT);
// wait for connection establish
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
eth_duplex_t exp_duplex;
esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex);
eth_speed_t exp_speed;
esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed);
// verify autonegotiation result (expecting the best link configuration)
TEST_ASSERT_EQUAL(ETH_DUPLEX_FULL, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_100M, exp_speed);
bool exp_autoneg_en;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_AUTONEGO, &exp_autoneg_en));
TEST_ASSERT_EQUAL(true, exp_autoneg_en);
ESP_LOGI(TAG, "try to change autonegotiation when driver is started...");
bool auto_nego_en = false;
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_AUTONEGO, &exp_autoneg_en));
TEST_ASSERT_EQUAL(true, exp_autoneg_en);
ESP_LOGI(TAG, "stop the Ethernet driver and...");
esp_eth_stop(eth_handle);
ESP_LOGI(TAG, "try to change speed/duplex prior disabling the autonegotiation...");
eth_duplex_t duplex = ETH_DUPLEX_HALF;
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &duplex));
eth_speed_t speed = ETH_SPEED_10M;
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &speed));
// Disable autonegotiation and change speed to 10 Mbps and duplex to half
ESP_LOGI(TAG, "disable the autonegotiation and change the speed/duplex...");
auto_nego_en = false;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_AUTONEGO, &exp_autoneg_en));
TEST_ASSERT_EQUAL(false, exp_autoneg_en);
// set new duplex mode
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &duplex));
// set new speed
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &speed));
// *** LAN8720 deviation ***
// Rationale: When the device is in manual 100BASE-TX or 10BASE-T modes with Auto-MDIX enabled, the PHY does not link to a
// link partner that is configured for auto-negotiation. See LAN8720 errata for more details.
#if CONFIG_ETH_TEST_LAN8720_ERRATA_ENABLED
TEST_ESP_OK(eth_test_set_phy_reg_bits(eth_handle, 27, 0x8000, 3));
#endif
// start the driver and wait for connection establish
esp_eth_start(eth_handle);
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed));
TEST_ASSERT_EQUAL(ETH_DUPLEX_HALF, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_10M, exp_speed);
// Change speed back to 100 Mbps
esp_eth_stop(eth_handle);
ESP_LOGI(TAG, "change speed again...");
speed = ETH_SPEED_100M;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &speed));
// start the driver and wait for connection establish
esp_eth_start(eth_handle);
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex));
TEST_ASSERT_EQUAL(ETH_DUPLEX_HALF, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_100M, exp_speed);
// Change duplex back to full
esp_eth_stop(eth_handle);
ESP_LOGI(TAG, "change duplex again...");
duplex = ETH_DUPLEX_FULL;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &duplex));
// start the driver and wait for connection establish
esp_eth_start(eth_handle);
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed));
TEST_ASSERT_EQUAL(ETH_DUPLEX_FULL, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_100M, exp_speed);
ESP_LOGI(TAG, "try to change speed/duplex when driver is started and autonegotiation disabled...");
speed = ETH_SPEED_10M;
duplex = ETH_DUPLEX_HALF;
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &duplex));
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &speed));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed));
TEST_ASSERT_EQUAL(ETH_DUPLEX_FULL, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_100M, exp_speed);
ESP_LOGI(TAG, "change the speed/duplex to 10 Mbps/half and then enable autonegotiation...");
esp_eth_stop(eth_handle);
speed = ETH_SPEED_10M;
duplex = ETH_DUPLEX_HALF;
// set new duplex mode
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &duplex));
// set new speed
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &speed));
// start the driver and wait for connection establish
esp_eth_start(eth_handle);
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed));
TEST_ASSERT_EQUAL(ETH_DUPLEX_HALF, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_10M, exp_speed);
esp_eth_stop(eth_handle);
auto_nego_en = true;
esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en);
// *** LAN8720 deviation ***
// Rationale: See above
#ifdef CONFIG_TARGET_ETH_PHY_DEVICE_LAN8720
TEST_ESP_OK(eth_test_clear_phy_reg_bits(eth_handle, 27, 0x8000, 3));
#endif
esp_eth_start(eth_handle);
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_AUTONEGO, &exp_autoneg_en));
TEST_ASSERT_EQUAL(true, exp_autoneg_en);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &exp_duplex));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &exp_speed));
// verify autonegotiation result (expecting the best link configuration)
TEST_ASSERT_EQUAL(ETH_DUPLEX_FULL, exp_duplex);
TEST_ASSERT_EQUAL(ETH_SPEED_100M, exp_speed);
// stop Ethernet driver
TEST_ESP_OK(esp_eth_stop(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}
// use static semaphore to avoid dynamic allocation and so need for de-allocation in case of test failure
static SemaphoreHandle_t loopback_test_case_data_received;
static StaticSemaphore_t loopback_test_case_data_received_buffer;
static esp_err_t loopback_test_case_incoming_handler(esp_eth_handle_t eth_handle, uint8_t *buffer, uint32_t length, void *priv)
{
TEST_ASSERT(memcmp(priv, buffer, LOOPBACK_TEST_PACKET_SIZE) == 0);
xSemaphoreGive(loopback_test_case_data_received);
free(buffer);
return ESP_OK;
}
TEST_CASE("ethernet io loopback", "[ethernet]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
loopback_test_case_data_received = xSemaphoreCreateBinaryStatic(&loopback_test_case_data_received_buffer);
EventBits_t bits = 0;
// Disable autonegotiation to manually set speed and duplex mode
bool auto_nego_en = false;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
bool loopback_en = true;
// *** PHY loopback not supported deviation ***
// Rationale: Some PHYs do not support loopback at all
#if CONFIG_ETH_TEST_LOOPBACK_DISABLED
TEST_ASSERT(esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en) == ESP_ERR_NOT_SUPPORTED);
return;
#else
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en));
#endif
eth_duplex_t duplex_modes[] = {ETH_DUPLEX_HALF, ETH_DUPLEX_FULL};
eth_speed_t speeds[] = {ETH_SPEED_100M, ETH_SPEED_10M};
emac_frame_t* test_packet = (emac_frame_t*)eth_test_alloc(LOOPBACK_TEST_PACKET_SIZE);
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, test_packet->src);
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, test_packet->dest);
for(size_t i = 0; i < LOOPBACK_TEST_PACKET_SIZE-ETH_HEADER_LEN; i++){
test_packet->data[i] = rand() & 0xff;
}
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, loopback_test_case_incoming_handler, test_packet));
for (int i = 0; i < sizeof(speeds) / sizeof(eth_speed_t); i++) {
eth_speed_t expected_speed = speeds[i];
for (int j = 0; j < sizeof(duplex_modes) / sizeof(eth_duplex_t); j++) {
eth_duplex_t expected_duplex = duplex_modes[j];
ESP_LOGI(TAG, "Test with %s Mbps %s duplex.", expected_speed == ETH_SPEED_10M ? "10" : "100", expected_duplex == ETH_DUPLEX_HALF ? "half" : "full");
// *** 10 Mbps loopback disabled deviation ***
// Rationale: Some PHYs do not support loopback at 10 Mbps
#if CONFIG_ETH_TEST_10MB_LOOPBACK_DISABLED
if ((expected_speed == ETH_SPEED_10M)) {
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &expected_speed));
continue;
} else if (expected_speed == ETH_SPEED_100M) {
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &expected_speed));
}
#else
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_SPEED, &expected_speed));
#endif
if ((expected_duplex == ETH_DUPLEX_HALF)) {
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &expected_duplex));
continue;
} else if (expected_duplex == ETH_DUPLEX_FULL) {
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_DUPLEX_MODE, &expected_duplex));
}
TEST_ESP_OK(esp_eth_start(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
eth_speed_t actual_speed = -1;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &actual_speed));
TEST_ASSERT_EQUAL(expected_speed, actual_speed);
eth_duplex_t actual_duplex = -1;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &actual_duplex));
TEST_ASSERT_EQUAL(expected_duplex, actual_duplex);
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_packet, LOOPBACK_TEST_PACKET_SIZE));
/* 10Mbps RMII loopback may have timing issues due to clock division architecture. RMII CLK stays 50MHz while the data signal must be held still for 10 cycles to
achieve the speed reduction. Everything, including control signals must be perfectly synchronized. This may be a challenge for some PHYs or PCB layouts.*/
if (expected_speed == ETH_SPEED_10M) {
int i;
for (i = 0; i < 3; i++) {
if(xSemaphoreTake(loopback_test_case_data_received, pdMS_TO_TICKS(1000)) != pdTRUE) {
ESP_LOGW(TAG, "Timeout waiting for data received for 10 Mbps mode, trying again...");
TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
TEST_ESP_OK(esp_eth_start(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_packet, LOOPBACK_TEST_PACKET_SIZE));
} else {
break;
}
}
// *** 10 Mbps loopback ignore failures deviation ***
// Rationale: 10 Mbps loopback may be supported by PHY but the test is not reliable.
#if !CONFIG_ETH_TEST_10MB_LOOPBACK_IGNORE_FAILURES
TEST_ASSERT_LESS_THAN(3, i);
#endif
} else {
TEST_ASSERT(xSemaphoreTake(loopback_test_case_data_received, pdMS_TO_TICKS(1000)) == pdTRUE);
}
TEST_ESP_OK(esp_eth_stop(eth_handle));
}
}
// Test enabling autonegotiation when loopback is disabled
ESP_LOGI(TAG, "Test enabling autonegotiation without loopback.");
loopback_en = false;
auto_nego_en = true;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
auto_nego_en = false;
loopback_en = true;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en));
// Test with enabled autonegotiaton
ESP_LOGI(TAG, "Test with enabled autonegotiation.");
auto_nego_en = true;
// *** Loopback with autonegotiation deviation ***
// Rationale: Some PHYs do not support autonegotiation with loopback enabled.
#if CONFIG_ETH_TEST_LOOPBACK_WITH_AUTONEGOTIATION_DISABLED
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
// Test passes - these devices correctly report autonegotiation is not supported with loopback
return;
#endif
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
TEST_ESP_OK(esp_eth_start(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_packet, LOOPBACK_TEST_PACKET_SIZE));
TEST_ASSERT(xSemaphoreTake(loopback_test_case_data_received, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS)) == pdTRUE);
loopback_en = false;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en));
TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}
TEST_CASE("ethernet event test", "[ethernet]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
EventBits_t bits = 0;
// this test only test layer2 event, so don't need to register input callback (i.e. esp_eth_update_input_path)
TEST_ESP_OK(esp_eth_start(eth_handle));
/* wait for connection start */
bits = xEventGroupWaitBits(eth_event_group, ETH_START_BIT, true, true, pdMS_TO_TICKS(ETH_START_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_START_BIT) == ETH_START_BIT);
/* wait for connection establish */
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
// stop Ethernet driver
TEST_ESP_OK(esp_eth_stop(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}
TEST_CASE("ethernet dhcp test", "[ethernet][esp-netif]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
EventBits_t bits = 0;
// start Ethernet driver
TEST_ESP_OK(esp_eth_start(eth_handle));
/* wait for IP lease */
bits = xEventGroupWaitBits(eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_GOT_IP_BIT) == ETH_GOT_IP_BIT);
// stop Ethernet driver
TEST_ESP_OK(esp_eth_stop(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}
TEST_CASE("ethernet start/stop stress test with IP stack", "[ethernet][esp-netif]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
EventBits_t bits = 0;
for(int j = 0; j < 2; j++) {
// run the start/stop test with disabled auto-negotiation
if (j > 0) {
ESP_LOGI(TAG, "Run with auto-negotiation disabled...");
bool auto_nego_en = false;
bool exp_autoneg_en;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_AUTONEGO, &auto_nego_en));
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_AUTONEGO, &exp_autoneg_en));
TEST_ASSERT_EQUAL(false, exp_autoneg_en);
// *** LAN8720 deviation ***
// Rationale: When the device is in manual 100BASE-TX or 10BASE-T modes with Auto-MDIX enabled, the PHY does not link to a
// link partner that is configured for auto-negotiation. See LAN8720 errata for more details.
#if CONFIG_ETH_TEST_LAN8720_ERRATA_ENABLED
TEST_ESP_OK(eth_test_set_phy_reg_bits(eth_handle, 27, 0x8000, 3));
#endif
}
for (int i = 0; i < 10; i++) {
// start Ethernet driver
TEST_ESP_OK(esp_eth_start(eth_handle));
/* wait for IP lease */
bits = xEventGroupWaitBits(eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_GOT_IP_BIT) == ETH_GOT_IP_BIT);
// stop Ethernet driver
TEST_ESP_OK(esp_eth_stop(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}
}
}
esp_err_t http_event_handle(esp_http_client_event_t *evt)
{
switch (evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGE(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGI(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADER");
break;
case HTTP_EVENT_ON_HEADERS_COMPLETE:
ESP_LOGI(TAG, "HTTP_EVENT_ON_HEADERS_COMPLETE");
break;
case HTTP_EVENT_ON_DATA:
esp_rom_md5_update(&md5_context, evt->data, evt->data_len);
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGI(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "HTTP_EVENT_DISCONNECTED");
break;
case HTTP_EVENT_REDIRECT:
ESP_LOGI(TAG, "HTTP_EVENT_REDIRECT");
break;
default:
break;
}
return ESP_OK;
}
static void eth_start_download(void)
{
esp_rom_md5_init(&md5_context);
esp_http_client_config_t config = {
.url = "https://dl.espressif.com/dl/misc/2MB.bin",
.cert_pem = dl_espressif_com_root_cert_pem_start,
.event_handler = http_event_handle,
.buffer_size = 5120
};
esp_http_client_handle_t client = esp_http_client_init(&config);
TEST_ASSERT_NOT_NULL(client);
TEST_ESP_OK(esp_http_client_perform(client));
TEST_ESP_OK(esp_http_client_cleanup(client));
esp_rom_md5_final(digest, &md5_context);
}
TEST_CASE("ethernet download test", "[ethernet][esp-netif]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
EventBits_t bits = 0;
// start Ethernet driver
TEST_ESP_OK(esp_eth_start(eth_handle));
/* wait for IP lease */
bits = xEventGroupWaitBits(eth_event_group, ETH_GOT_IP_BIT, true, true, pdMS_TO_TICKS(ETH_GET_IP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_GOT_IP_BIT) == ETH_GOT_IP_BIT);
eth_start_download();
// check MD5 digest
// MD5: df61db8564d145bbe67112aa8ecdccd8
uint8_t expect_digest[16] = {223, 97, 219, 133, 100, 209, 69, 187, 230, 113, 18, 170, 142, 205, 204, 216};
printf("MD5 Digest: ");
for (int i = 0; i < 16; i++) {
printf("%d ", digest[i]);
}
printf("\r\n");
TEST_ASSERT_EQUAL_UINT8_ARRAY(expect_digest, digest, sizeof(digest));
// stop Ethernet driver
TEST_ESP_OK(esp_eth_stop(eth_handle));
/* wait for connection stop */
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(ETH_STOP_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}

View File

@@ -1,547 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_eth_test_utils.h"
#include "arpa/inet.h" // for ntohs, etc.
#include "esp_log.h"
#define TEST_ETH_TYPE 0x3300
#define TEST_CTRL_ETH_TYPE (TEST_ETH_TYPE + 1)
#define WAIT_FOR_CONN_TMO_MS (5000)
#define WAIT_AFTER_CONN_MS (2500)
#define WAIT_AFTER_CONN_TMO_MS (20000)
#define ETH_BROADCAST_RECV_BIT BIT(0)
#define ETH_MULTICAST_RECV_BIT BIT(1)
#define ETH_UNICAST_RECV_BIT BIT(2)
#define ETH_POKE_RESP_RECV_BIT BIT(3)
#define POKE_REQ (0xFA)
#define POKE_RESP (0xFB)
#define DUMMY_TRAFFIC (0xFF)
static const char *TAG = "esp32_eth_test_l2";
typedef struct
{
EventGroupHandle_t eth_event_group;
uint8_t dst_mac_addr[ETH_ADDR_LEN];
int unicast_rx_cnt;
int multicast_rx_cnt;
int brdcast_rx_cnt;
bool check_rx_data;
} recv_info_t;
static recv_info_t s_recv_info;
static esp_err_t l2_packet_txrx_test_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv) {
recv_info_t *recv_info = (recv_info_t*)priv;
EventGroupHandle_t eth_event_group = recv_info->eth_event_group;
emac_frame_t *pkt = (emac_frame_t *)buffer;
// check header
if (pkt->proto == ntohs(TEST_ETH_TYPE)) { // data packet
uint8_t local_mac_addr[ETH_ADDR_LEN];
esp_eth_ioctl(hdl, ETH_CMD_G_MAC_ADDR, local_mac_addr);
// check data content
if (recv_info->check_rx_data) {
if (length == 1024) {
for (int i = 0; i < (length - ETH_HEADER_LEN); ++i) {
if (pkt->data[i] != (i & 0xff)) {
printf("payload mismatch\n");
free(buffer);
return ESP_OK;
}
}
}
}
if (memcmp(pkt->dest, "\xff\xff\xff\xff\xff\xff", ETH_ADDR_LEN) == 0) {
recv_info->brdcast_rx_cnt++;
xEventGroupSetBits(eth_event_group, ETH_BROADCAST_RECV_BIT);
} else if (pkt->dest[0] & 0x1) {
recv_info->multicast_rx_cnt++;
xEventGroupSetBits(eth_event_group, ETH_MULTICAST_RECV_BIT);
} else if (memcmp(pkt->dest, local_mac_addr, ETH_ADDR_LEN) == 0) {
recv_info->unicast_rx_cnt++;
xEventGroupSetBits(eth_event_group, ETH_UNICAST_RECV_BIT);
}
} else if (ntohs(pkt->proto) == TEST_CTRL_ETH_TYPE) { // control packet
if (pkt->data[0] == POKE_RESP) {
memcpy(recv_info->dst_mac_addr, pkt->src, ETH_ADDR_LEN); // test PC source MAC addr is destination for us
printf("Poke response received\n");
xEventGroupSetBits(eth_event_group, ETH_POKE_RESP_RECV_BIT);
}
}
free(buffer);
return ESP_OK;
}
/**
* @brief The function sends a "POKE" request message over the Ethernet and waits until the test script sends a reply.
* Multiple "POKE" attempts are issued when timeout for the reply expires.
* This function is used to drive the test flow and to ensure that data path between the test points
* has been established. I.e. if DUT is connected in network with a switch, even if link is indicated up,
* it may take some time the switch starts forwarding the associated port (e.g. it runs RSTP at first).
*/
void poke_and_wait(esp_eth_handle_t eth_handle, void *data, uint16_t size, uint8_t *dst_mac_addr, EventGroupHandle_t eth_event_group)
{
// create a control frame to control test flow between the UT and the Python test script
emac_frame_t *ctrl_pkt = calloc(1, 60);
ctrl_pkt->proto = htons(TEST_CTRL_ETH_TYPE);
memset(ctrl_pkt->dest, 0xff, ETH_ADDR_LEN); // broadcast addr
esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, ctrl_pkt->src);
ctrl_pkt->data[0] = POKE_REQ;
if (data != NULL && size > 0) {
memcpy(&ctrl_pkt->data[1], data, size);
}
uint32_t tmo;
uint32_t i;
esp_err_t tx_err = ESP_OK;
for(tmo = 0, i = 1; tmo < WAIT_AFTER_CONN_TMO_MS; tmo += WAIT_AFTER_CONN_MS, i++) {
printf("Poke attempt #%" PRIu32 "\n", i);
tx_err = esp_eth_transmit(eth_handle, ctrl_pkt, 60);
if (tx_err != ESP_OK) {
break;
}
EventBits_t bits = xEventGroupWaitBits(eth_event_group, ETH_POKE_RESP_RECV_BIT,
true, true, pdMS_TO_TICKS(WAIT_AFTER_CONN_MS));
if ((bits & ETH_POKE_RESP_RECV_BIT) == ETH_POKE_RESP_RECV_BIT) {
if (dst_mac_addr != NULL) {
memcpy(dst_mac_addr, s_recv_info.dst_mac_addr, ETH_ADDR_LEN);
}
break;
}
}
free(ctrl_pkt);
// assert only after the allocated memory is freed
TEST_ASSERT(tmo < WAIT_AFTER_CONN_TMO_MS);
TEST_ASSERT_EQUAL_INT_MESSAGE(ESP_OK, tx_err, "esp_eth_transmit failed");
}
TEST_CASE("ethernet broadcast transmit", "[ethernet_l2]")
{
// assign values to variables from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
// use static event group to avoid dynamic memory allocation
StaticEventGroup_t eth_event_rx_group_buffer;
EventGroupHandle_t eth_event_rx_group = xEventGroupCreateStatic(&eth_event_rx_group_buffer);
TEST_ASSERT(eth_event_rx_group != NULL);
s_recv_info.eth_event_group = eth_event_rx_group;
s_recv_info.check_rx_data = false;
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, local_mac_addr));
// test app will parse the DUT MAC from this line of log output
printf("DUT MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", local_mac_addr[0], local_mac_addr[1], local_mac_addr[2],
local_mac_addr[3], local_mac_addr[4], local_mac_addr[5]);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &s_recv_info));
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
EventBits_t bits = 0;
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(WAIT_FOR_CONN_TMO_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
// if DUT is connected in network with switch: even if link is indicated up, it may take some time the switch
// starts switching the associated port (e.g. it runs RSTP at first)
poke_and_wait(eth_handle, NULL, 0, NULL, eth_event_rx_group);
emac_frame_t *pkt = (emac_frame_t *)eth_test_alloc(1024);
pkt->proto = htons(TEST_ETH_TYPE);
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, pkt->src));
memset(pkt->dest, 0xff, ETH_ADDR_LEN); // broadcast addr
for (int i = 0; i < (1024 - ETH_HEADER_LEN); ++i){
pkt->data[i] = i & 0xff;
}
TEST_ESP_OK(esp_eth_transmit(eth_handle, pkt, 1024));
// give it some time to complete transmit
vTaskDelay(pdMS_TO_TICKS(500));
TEST_ESP_OK(esp_eth_stop(eth_handle));
}
TEST_CASE("ethernet recv_pkt", "[ethernet_l2]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
// use static event group to avoid dynamic memory allocation
StaticEventGroup_t eth_event_rx_group_buffer;
EventGroupHandle_t eth_event_rx_group = xEventGroupCreateStatic(&eth_event_rx_group_buffer);
TEST_ASSERT(eth_event_rx_group != NULL);
s_recv_info.eth_event_group = eth_event_rx_group;
s_recv_info.check_rx_data = true;
uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, local_mac_addr));
// test app will parse the DUT MAC from this line of log output
printf("DUT MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", local_mac_addr[0], local_mac_addr[1], local_mac_addr[2],
local_mac_addr[3], local_mac_addr[4], local_mac_addr[5]);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &s_recv_info));
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
EventBits_t bits = 0;
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(WAIT_FOR_CONN_TMO_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
// if DUT is connected in network with switch: even if link is indicated up, it may take some time the switch
// starts switching the associated port (e.g. it runs RSTP at first)
poke_and_wait(eth_handle, NULL, 0, NULL, eth_event_rx_group);
// ---------------------------------------
printf("Enable receive all multicast\n");
// ---------------------------------------
EventBits_t expected_bits = ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
int expected_multicast_rx_cnt = 2;
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
bool all_multicast = true;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_ALL_MULTICAST, &all_multicast));
bits = 0;
xEventGroupClearBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT);
printf("Filter configured\n");
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT,
true, true, pdMS_TO_TICKS(1000));
printf("bits = 0x%" PRIu32 "\n", (uint32_t)bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT));
TEST_ASSERT((bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)) == expected_bits);
TEST_ASSERT_EQUAL(1, s_recv_info.unicast_rx_cnt);
TEST_ASSERT_EQUAL(1, s_recv_info.brdcast_rx_cnt);
TEST_ASSERT_EQUAL(expected_multicast_rx_cnt, s_recv_info.multicast_rx_cnt);
// ---------------------------------------
printf("Disable receive all multicast\n");
// ---------------------------------------
// *** W5500 deviation ***
// Rationale: The W5500 always receives IPv6 multicast packets, even if the filter is set to block multicast.
// It's not documented behavior, but it's observed on the real hardware.
#if CONFIG_ETH_TEST_W5500_IP6_MCAST_DEVIATION_ENABLED
expected_bits = ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
expected_multicast_rx_cnt = 1;
#else
expected_bits = ETH_BROADCAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
expected_multicast_rx_cnt = 0;
#endif
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
all_multicast = false;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_ALL_MULTICAST, &all_multicast));
bits = 0;
xEventGroupClearBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT);
printf("Filter configured\n");
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT,
true, true, pdMS_TO_TICKS(1000));
printf("bits = 0x%" PRIu32 "\n", (uint32_t)bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT));
TEST_ASSERT((bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)) == expected_bits);
TEST_ASSERT_EQUAL(1, s_recv_info.unicast_rx_cnt);
TEST_ASSERT_EQUAL(1, s_recv_info.brdcast_rx_cnt);
TEST_ASSERT_EQUAL(expected_multicast_rx_cnt, s_recv_info.multicast_rx_cnt);
// ---------------------------------------
printf("Add multicast addresses to the filter\n");
// ---------------------------------------
expected_bits = ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
expected_multicast_rx_cnt = 2;
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
uint8_t multicast_addr_ip4[ETH_ADDR_LEN] = {0x01, 0x00, 0x5e, 0x00, 0x00, 0x00};
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_ADD_MAC_FILTER, multicast_addr_ip4));
uint8_t multicast_addr_ip6[ETH_ADDR_LEN] = {0x33, 0x33, 0x00, 0x00, 0x00, 0x00};
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_ADD_MAC_FILTER, multicast_addr_ip6));
bits = 0;
xEventGroupClearBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT);
printf("Filter configured\n");
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT,
true, true, pdMS_TO_TICKS(1000));
printf("bits = 0x%" PRIu32 "\n", (uint32_t)bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT));
TEST_ASSERT((bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)) == expected_bits);
TEST_ASSERT_EQUAL(1, s_recv_info.unicast_rx_cnt);
TEST_ASSERT_EQUAL(1, s_recv_info.brdcast_rx_cnt);
TEST_ASSERT_EQUAL(expected_multicast_rx_cnt, s_recv_info.multicast_rx_cnt);
// --------------------------------------------
printf("Remove one multicast address from the filter\n");
// --------------------------------------------
expected_bits = ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
expected_multicast_rx_cnt = 1;
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_DEL_MAC_FILTER, multicast_addr_ip4));
bits = 0;
xEventGroupClearBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT);
printf("Filter configured\n");
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT,
true, true, pdMS_TO_TICKS(1000));
printf("bits = 0x%" PRIu32 "\n", (uint32_t)bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT));
TEST_ASSERT((bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)) == expected_bits);
TEST_ASSERT_EQUAL(1, s_recv_info.unicast_rx_cnt);
TEST_ASSERT_EQUAL(1, s_recv_info.brdcast_rx_cnt);
TEST_ASSERT_EQUAL(expected_multicast_rx_cnt, s_recv_info.multicast_rx_cnt);
// ----------------------------------------------
printf("Remove all multicast addresses from the filter\n");
// ----------------------------------------------
// *** W5500 deviation ***
// Rationale: The W5500 always receives IPv6 multicast packets, even if the filter is set to block multicast.
// It's not documented behavior, but it's observed on the real hardware.
#if CONFIG_ETH_TEST_W5500_IP6_MCAST_DEVIATION_ENABLED
expected_bits = ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
expected_multicast_rx_cnt = 1;
#else
expected_bits = ETH_BROADCAST_RECV_BIT | ETH_UNICAST_RECV_BIT;
expected_multicast_rx_cnt = 0;
#endif
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
// *** W5500 deviation ***
// Rationale: The W5500 always receives IPv6 multicast packets and hence filter delete fails.
#if CONFIG_ETH_TEST_W5500_IP6_MCAST_DEVIATION_ENABLED
TEST_ESP_ERR(ESP_FAIL, esp_eth_ioctl(eth_handle, ETH_CMD_DEL_MAC_FILTER, multicast_addr_ip6));
#else
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_DEL_MAC_FILTER, multicast_addr_ip6));
#endif
bits = 0;
xEventGroupClearBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT);
printf("Filter configured\n");
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT,
true, true, pdMS_TO_TICKS(1000));
printf("bits = 0x%" PRIu32 "\n", (uint32_t)bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT));
TEST_ASSERT((bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)) == expected_bits);
TEST_ASSERT_EQUAL(1, s_recv_info.unicast_rx_cnt);
TEST_ASSERT_EQUAL(1, s_recv_info.brdcast_rx_cnt);
TEST_ASSERT_EQUAL(expected_multicast_rx_cnt, s_recv_info.multicast_rx_cnt);
TEST_ESP_OK(esp_eth_stop(eth_handle));
}
TEST_CASE("ethernet start/stop stress test under heavy traffic", "[ethernet_l2]")
{
// *** SPI Ethernet modules deviation ***
// Rationale: The SPI bus is bottleneck when reading received frames from the module. The Rx Task would
// occupy all the resources under heavy Rx traffic and it would not be possible to access
// the Ethernet module to stop it. Therefore, the test task priority is set higher than the Rx task
// to be able to preempt the Rx task.
#if CONFIG_ETH_TEST_STRESS_TEST_TASK_PRIO > -1
printf("task priority: %d\n", uxTaskPriorityGet(NULL));
vTaskPrioritySet(NULL, CONFIG_ETH_TEST_STRESS_TEST_TASK_PRIO);
#endif // CONFIG_ETH_TEST_STRESS_TEST_TASK_PRIO > 0
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
// use static event group to avoid dynamic memory allocation
StaticEventGroup_t eth_event_rx_group_buffer;
EventGroupHandle_t eth_event_rx_group = xEventGroupCreateStatic(&eth_event_rx_group_buffer);
TEST_ASSERT(eth_event_rx_group != NULL);
s_recv_info.eth_event_group = eth_event_rx_group;
s_recv_info.check_rx_data = false;
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
uint8_t dest_mac_addr[ETH_ADDR_LEN] = {};
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, local_mac_addr));
// test app will parse the DUT MAC from this line of log output
printf("DUT MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", local_mac_addr[0], local_mac_addr[1], local_mac_addr[2],
local_mac_addr[3], local_mac_addr[4], local_mac_addr[5]);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &s_recv_info));
// create dummy data packet used for traffic generation
emac_frame_t *pkt = (emac_frame_t *)eth_test_alloc(1500);
pkt->proto = htons(TEST_ETH_TYPE);
memcpy(pkt->src, local_mac_addr, ETH_ADDR_LEN);
EventBits_t bits = 0;
printf("EMAC start/stop stress test under heavy Tx traffic\n");
for (int tx_i = 0; tx_i < 10; tx_i++) {
printf("Tx Test iteration %d\n", tx_i);
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(WAIT_FOR_CONN_TMO_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
// at first, check that Tx/Rx path works as expected by poking the test script
// this also serves as main PASS/FAIL criteria
poke_and_wait(eth_handle, &tx_i, sizeof(tx_i), dest_mac_addr, eth_event_rx_group);
memcpy(pkt->dest, dest_mac_addr, ETH_ADDR_LEN);
// generate heavy Tx traffic
for (int j = 0; j < 150; j++) {
// return value is not checked on purpose since it is expected that it may fail time to time because
// we may try to queue more packets than hardware is able to handle
pkt->data[2] = j & 0xFF; // sequence number
esp_eth_transmit(eth_handle, pkt, 1500);
}
TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}
printf("EMAC start/stop stress test under heavy Rx traffic\n");
for (int rx_i = 0; rx_i < 10; rx_i++) {
printf("Rx Test iteration %d\n", rx_i);
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(WAIT_FOR_CONN_TMO_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
poke_and_wait(eth_handle, &rx_i, sizeof(rx_i), NULL, eth_event_rx_group);
// wait for dummy traffic
xEventGroupClearBits(eth_event_rx_group, ETH_UNICAST_RECV_BIT);
s_recv_info.unicast_rx_cnt = 0;
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_UNICAST_RECV_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_UNICAST_RECV_BIT) == ETH_UNICAST_RECV_BIT);
vTaskDelay(pdMS_TO_TICKS(500));
TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
printf("Recv packets: %d\n", s_recv_info.unicast_rx_cnt);
TEST_ASSERT_GREATER_THAN_INT32(0, s_recv_info.unicast_rx_cnt);
}
// Add an extra delay to be sure that there is no traffic generated by the test script during the driver un-installation.
// It was observed unintended behavior of the switch used in test environment when link is set down under heavy load.
vTaskDelay(pdMS_TO_TICKS(500));
}
TEST_CASE("heap utilization", "[ethernet_l2]")
{
// get handles from common module initialized by setUp()
esp_eth_handle_t eth_handle = eth_test_get_eth_handle();
EventGroupHandle_t eth_event_group = eth_test_get_default_event_group();
// use static event group to avoid dynamic memory allocation
StaticEventGroup_t eth_event_rx_group_buffer;
EventGroupHandle_t eth_event_rx_group = xEventGroupCreateStatic(&eth_event_rx_group_buffer);
TEST_ASSERT(eth_event_rx_group != NULL);
s_recv_info.eth_event_group = eth_event_rx_group;
s_recv_info.check_rx_data = false;
s_recv_info.unicast_rx_cnt = 0;
s_recv_info.multicast_rx_cnt = 0;
s_recv_info.brdcast_rx_cnt = 0;
// *** PHY loopback not supported deviation ***
// Rationale: Some Ethernet modules do not support internal loopback
#if !CONFIG_ETH_TEST_LOOPBACK_DISABLED
// ---------------------------------------
// Loopback greatly simplifies the test !!
// ---------------------------------------
bool loopback_en = true;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_S_PHY_LOOPBACK, &loopback_en));
printf("PHY loopback is enabled\n");
#else
printf("PHY loopback is disabled\n");
#endif
uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, local_mac_addr));
// test app will parse the DUT MAC from this line of log output
printf("DUT MAC: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", local_mac_addr[0], local_mac_addr[1], local_mac_addr[2],
local_mac_addr[3], local_mac_addr[4], local_mac_addr[5]);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &s_recv_info));
// start the driver
TEST_ESP_OK(esp_eth_start(eth_handle));
// wait for connection start
EventBits_t bits = 0;
bits = xEventGroupWaitBits(eth_event_group, ETH_START_BIT, true, true, pdMS_TO_TICKS(ETH_START_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_START_BIT) == ETH_START_BIT);
// wait for connection establish
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(ETH_CONNECT_TIMEOUT_MS));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
// create test frame
uint8_t test_pkt_buffer[ETH_MAX_PACKET_SIZE];
emac_frame_t *test_pkt = (emac_frame_t *)test_pkt_buffer;
test_pkt->proto = htons(TEST_ETH_TYPE);
memcpy(test_pkt->dest, local_mac_addr, ETH_ADDR_LEN); // our addr so the frame is not filtered at loopback by MAC
memcpy(test_pkt->src, local_mac_addr, ETH_ADDR_LEN);
// fill with data
for (int i = 0; i < ETH_MAX_PAYLOAD_LEN; i++) {
test_pkt->data[i] = i & 0xFF;
}
// *** PHY loopback not supported deviation ***
// Rationale: Some Ethernet modules do not support internal loopback so we need to loop frames back at test PC side
#if CONFIG_ETH_TEST_LOOPBACK_DISABLED
uint8_t dest_mac_addr[ETH_ADDR_LEN] = {};
poke_and_wait(eth_handle, NULL, 0, dest_mac_addr, eth_event_rx_group);
memcpy(test_pkt->dest, dest_mac_addr, ETH_ADDR_LEN); // overwrite destination address with test PC addr
#endif
uint16_t transmit_size;
size_t free_heap = 0;
uint8_t *p;
ESP_LOGI(TAG, "Allocate all heap");
do {
free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
ESP_LOGD(TAG, "free heap: %i B", free_heap);
p = eth_test_alloc(free_heap);
} while (p != NULL && free_heap > 1024);
ESP_LOGI(TAG, "remaining free heap: %i B", free_heap);
TEST_ASSERT_LESS_OR_EQUAL_INT(1024, free_heap);
transmit_size = ETH_MAX_PAYLOAD_LEN;
ESP_LOGI(TAG, "Verify that the driver is able to recover from `no mem` error");
// define number of iteration to fill device internal buffer (if driver's flush function didn't work as expected)
int32_t max_i = CONFIG_ETH_TEST_FILL_RX_BUFFER_ITERATIONS + 2;
for (int32_t i = 0; i < max_i; i++) { // be sure to fill all the descriptors
ESP_LOGI(TAG, "transmit frame size: %" PRIu16 ", i = %" PRIi32, transmit_size, i);
xEventGroupClearBits(eth_event_rx_group, ETH_UNICAST_RECV_BIT);
s_recv_info.brdcast_rx_cnt = 0;
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
// wait for dummy traffic
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_UNICAST_RECV_BIT, true, true, pdMS_TO_TICKS(200));
TEST_ASSERT(bits == 0); // we don't received the frame due to "no mem"
}
ESP_LOGI(TAG, "Free previously allocated heap");
eth_test_free_all();
free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT);
ESP_LOGI(TAG, "free heap: %i B", free_heap);
for (int32_t i = 0; i < max_i; i++) {
ESP_LOGD(TAG, "transmit frame size: %" PRIu16 ", i = %" PRIi32, transmit_size, i);
xEventGroupClearBits(eth_event_rx_group, ETH_UNICAST_RECV_BIT);
s_recv_info.brdcast_rx_cnt = 0;
TEST_ESP_OK(esp_eth_transmit(eth_handle, test_pkt, transmit_size));
// wait for dummy traffic
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_UNICAST_RECV_BIT, true, true, pdMS_TO_TICKS(200));
TEST_ASSERT((bits & ETH_UNICAST_RECV_BIT) == ETH_UNICAST_RECV_BIT); // now, we should be able to receive frames again
}
TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
}

View File

@@ -1,368 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <inttypes.h>
#include "esp_eth_driver.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "esp_event.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "esp_eth_test_utils.h"
#include "esp_check.h"
#include "ethernet_init.h"
#include "esp_netif.h"
// Local override of TEST_ASSERT and TEST_ESP_OK to fix Unity file name reporting
// when assertions are in a different file than the test. This temporarily sets Unity.
// TestFile to __FILE__ so the correct file name is reported on assertion failure.
#define ETH_TEST_ASSERT(condition) do { \
const char* _unity_test_file_save = Unity.TestFile; \
Unity.TestFile = __FILE__; \
TEST_ASSERT(condition); \
Unity.TestFile = _unity_test_file_save; \
} while(0)
#define ETH_TEST_ESP_OK(rc) do { \
const char* _unity_test_file_save = Unity.TestFile; \
Unity.TestFile = __FILE__; \
TEST_ASSERT_EQUAL_HEX32(ESP_OK, rc); \
Unity.TestFile = _unity_test_file_save; \
} while(0)
static const char *TAG = "esp32_eth_test_common";
static esp_eth_handle_t *s_eth_handles; // only because we use Ethernet Init component
static EventGroupHandle_t s_eth_event_group;
static esp_eth_handle_t s_eth_handle;
static esp_netif_t *s_eth_netif;
static esp_eth_netif_glue_handle_t s_eth_glue;
static void *s_memory_p[MAX_HEAP_ALLOCATION_POINTERS];
esp_err_t eth_test_set_phy_reg_bits(esp_eth_handle_t eth_handle, uint32_t reg_addr, uint32_t bitmask, uint32_t max_attempts)
{
esp_eth_phy_reg_rw_data_t reg = {
.reg_addr = reg_addr,
.reg_value_p = NULL
};
uint32_t reg_value, reg_value_rb;
for (uint32_t i = 0; i < max_attempts; i++) {
reg.reg_value_p = &reg_value;
esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, &reg);
if (ret != ESP_OK) {
return ret;
}
reg_value |= bitmask;
ret = esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, &reg);
if (ret != ESP_OK) {
return ret;
}
reg.reg_value_p = &reg_value_rb;
ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, &reg);
if (ret != ESP_OK) {
return ret;
}
// Check if the write was successful
if ((reg_value_rb & bitmask) == bitmask) {
return ESP_OK;
}
// Add delay only if not the last attempt
if (i < max_attempts - 1) {
ESP_LOGW(TAG, "Setting PHY register %04"PRIx32" failed, retrying... (attempt %"PRIu32" of %"PRIu32")", reg_addr, i + 1, max_attempts);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
return ESP_ERR_TIMEOUT;
}
esp_err_t eth_test_clear_phy_reg_bits(esp_eth_handle_t eth_handle, uint32_t reg_addr, uint32_t bitmask, uint32_t max_attempts)
{
esp_eth_phy_reg_rw_data_t reg = {
.reg_addr = reg_addr,
.reg_value_p = NULL
};
uint32_t reg_value, reg_value_rb;
for (uint32_t i = 0; i < max_attempts; i++) {
reg.reg_value_p = &reg_value;
esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, &reg);
if (ret != ESP_OK) {
return ret;
}
reg_value &= ~bitmask;
ret = esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, &reg);
if (ret != ESP_OK) {
return ret;
}
reg.reg_value_p = &reg_value_rb;
ret = esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, &reg);
if (ret != ESP_OK) {
return ret;
}
// Check if the write was successful
if ((reg_value_rb & bitmask) == 0) {
return ESP_OK;
}
// Add delay only if not the last attempt
if (i < max_attempts - 1) {
ESP_LOGW(TAG, "Clearing PHY register %04"PRIx32" failed, retrying... (attempt %"PRIu32" of %"PRIu32")", reg_addr, i + 1, max_attempts);
vTaskDelay(pdMS_TO_TICKS(10));
}
}
return ESP_ERR_TIMEOUT;
}
__attribute__((weak)) esp_err_t esp_eth_test_eth_init(esp_eth_handle_t *eth_handle)
{
esp_err_t ret = ESP_OK;
uint8_t eth_port_cnt = 0;
ESP_LOGI(TAG, "Default Ethernet Initialization...");
ESP_GOTO_ON_ERROR(ethernet_init_all(&s_eth_handles, &eth_port_cnt), err, TAG, "Failed to initialize Ethernet");
ESP_GOTO_ON_FALSE(eth_port_cnt == 1, ESP_FAIL, err, TAG, "Multiple Ethernet devices detected, the test cannot continue...");
*eth_handle = s_eth_handles[0];
err:
return ret;
}
__attribute__((weak)) esp_err_t esp_eth_test_eth_deinit(esp_eth_handle_t eth_handle)
{
esp_err_t ret = ESP_OK;
ESP_LOGI(TAG, "Default Ethernet Deinitialization...");
ESP_GOTO_ON_FALSE(s_eth_handles[0] == eth_handle, ESP_FAIL, err, TAG, "Ethernet handle does not match");
ESP_GOTO_ON_ERROR(ethernet_deinit_all(s_eth_handles), err, TAG, "Failed to deinitialize Ethernet");
err:
return ret;
}
void setUp(void)
{
if (strstr(Unity.CurrentDetail1, "[skip_setup_teardown]") != NULL) {
return;
}
// Use Unity to check if the initialization is successful, it's intended to fail the test if initialization fails
s_eth_event_group = xEventGroupCreate();
ETH_TEST_ASSERT(s_eth_event_group != NULL);
ETH_TEST_ESP_OK(esp_event_loop_create_default());
ETH_TEST_ESP_OK(esp_eth_test_eth_init(&s_eth_handle));
eth_dev_info_t phy_info = ethernet_init_get_dev_info(s_eth_handle);
ESP_LOGI(TAG, "DUT PHY: %s", phy_info.name);
if (strstr(Unity.CurrentDetail1, "[esp-netif]") != NULL) {
esp_netif_init();
// create TCP/IP netif
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
s_eth_netif = esp_netif_new(&netif_cfg);
// combine driver with netif
s_eth_glue = esp_eth_new_netif_glue(s_eth_handle);
ETH_TEST_ESP_OK(esp_netif_attach(s_eth_netif, s_eth_glue));
// register user defined event handlers
ETH_TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &eth_test_got_ip_event_handler, s_eth_event_group));
}
ETH_TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_test_default_event_handler, s_eth_event_group));
}
void tearDown(void)
{
eth_test_free_all();
if (strstr(Unity.CurrentDetail1, "[skip_setup_teardown]") != NULL) {
return;
}
// Use regular error checking instead of ETH_TEST_ESP_OK to ensure cleanup always completes
// even if errors occur (e.g., if test failed and resources are in invalid state)
// This is critical because TEST_ASSERT failures use longjmp which can skip cleanup
esp_err_t ret;
if (s_eth_glue != NULL) {
ret = esp_eth_del_netif_glue(s_eth_glue);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "esp_eth_del_netif_glue failed: %s", esp_err_to_name(ret));
}
s_eth_glue = NULL;
}
if (s_eth_netif != NULL) {
esp_netif_destroy(s_eth_netif);
s_eth_netif = NULL;
ret = esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, eth_test_got_ip_event_handler);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "esp_event_handler_unregister IP event failed: %s", esp_err_to_name(ret));
}
}
ret = esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_test_default_event_handler);
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
// ESP_ERR_INVALID_STATE is OK - handler might already be unregistered
ESP_LOGW(TAG, "esp_event_handler_unregister failed: %s", esp_err_to_name(ret));
}
if (s_eth_handle != NULL) {
ret = esp_eth_test_eth_deinit(s_eth_handle);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "esp_eth_test_eth_deinit failed: %s", esp_err_to_name(ret));
ESP_LOGI(TAG, "Trying to stop Ethernet driver and deinitialize it again...");
ret = esp_eth_stop(s_eth_handle);
vTaskDelay(pdMS_TO_TICKS(500));
if (ret != ESP_OK) {
ESP_LOGW(TAG, "esp_eth_stop failed: %s", esp_err_to_name(ret));
}
// try to deinitialize Ethernet driver again
ret = esp_eth_test_eth_deinit(s_eth_handle);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "esp_eth_test_eth_deinit failed: %s", esp_err_to_name(ret));
}
}
s_eth_handle = NULL;
}
ret = esp_event_loop_delete_default();
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
// ESP_ERR_INVALID_STATE is OK - loop might already be deleted
ESP_LOGW(TAG, "esp_event_loop_delete_default failed: %s", esp_err_to_name(ret));
}
if (s_eth_event_group != NULL) {
vEventGroupDelete(s_eth_event_group);
s_eth_event_group = NULL;
}
}
esp_eth_handle_t eth_test_get_eth_handle(void)
{
return s_eth_handle;
}
EventGroupHandle_t eth_test_get_default_event_group(void)
{
return s_eth_event_group;
}
esp_netif_t *eth_test_get_netif(void)
{
return s_eth_netif;
}
void* eth_test_alloc(size_t size)
{
for (int i = 0; i < MAX_HEAP_ALLOCATION_POINTERS; i++) {
if (s_memory_p[i] == NULL) {
s_memory_p[i] = malloc(size);
return s_memory_p[i];
}
}
return NULL;
}
void eth_test_free(void *ptr)
{
for (int i = 0; i < MAX_HEAP_ALLOCATION_POINTERS; i++) {
if (s_memory_p[i] == ptr) {
free(s_memory_p[i]);
s_memory_p[i] = NULL;
return;
}
}
return;
}
void eth_test_free_all(void)
{
for (int i = 0; i < MAX_HEAP_ALLOCATION_POINTERS; i++) {
if (s_memory_p[i] != NULL) {
free(s_memory_p[i]);
s_memory_p[i] = NULL;
}
}
}
/** Event handler for Ethernet events */
void eth_test_default_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
EventGroupHandle_t eth_event_group = (EventGroupHandle_t)arg;
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
ESP_LOGI(TAG, "Ethernet Link Up");
xEventGroupSetBits(eth_event_group, ETH_CONNECT_BIT);
break;
case ETHERNET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "Ethernet Link Down");
break;
case ETHERNET_EVENT_START:
ESP_LOGI(TAG, "Ethernet Started");
xEventGroupSetBits(eth_event_group, ETH_START_BIT);
break;
case ETHERNET_EVENT_STOP:
ESP_LOGI(TAG, "Ethernet Stopped");
xEventGroupSetBits(eth_event_group, ETH_STOP_BIT);
break;
default:
break;
}
}
/**
* Set public DNS servers as backup and fallback
*
* Priority used by lwIP:
* DNS_MAIN (0) - assigned by DHCP lease (kept as primary)
* DNS_BACKUP (1) - Google Public DNS 8.8.8.8 (used if main fails)
* DNS_FALLBACK (2) - Google Public DNS 8.8.4.4 (last resort)
*/
static void eth_test_set_dns_fallback(esp_netif_t *netif)
{
esp_netif_dns_info_t dns = {0};
dns.ip.type = ESP_IPADDR_TYPE_V4;
/* Check what DNS the DHCP lease provided in slot 0 */
esp_netif_dns_info_t dhcp_dns = {0};
esp_err_t ret = esp_netif_get_dns_info(netif, ESP_NETIF_DNS_MAIN, &dhcp_dns);
bool has_dhcp_dns = (ret == ESP_OK && dhcp_dns.ip.u_addr.ip4.addr != 0);
if (has_dhcp_dns) {
ESP_LOGI(TAG, "DHCP DNS: " IPSTR " (keeping as primary)", IP2STR(&dhcp_dns.ip.u_addr.ip4));
/* Keep DHCP DNS as primary, add Google DNS as backup and fallback */
dns.ip.u_addr.ip4.addr = ESP_IP4TOADDR(8, 8, 8, 8);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns);
ESP_LOGI(TAG, "Backup DNS set: 8.8.8.8");
dns.ip.u_addr.ip4.addr = ESP_IP4TOADDR(8, 8, 4, 4);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_FALLBACK, &dns);
ESP_LOGI(TAG, "Fallback DNS set: 8.8.4.4");
} else {
/* No DNS from DHCP, set Google DNS as primary so lwIP uses it immediately */
ESP_LOGW(TAG, "No DNS received from DHCP — setting 8.8.8.8 as primary DNS");
dns.ip.u_addr.ip4.addr = ESP_IP4TOADDR(8, 8, 8, 8);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_MAIN, &dns);
ESP_LOGI(TAG, "Primary DNS set: 8.8.8.8");
dns.ip.u_addr.ip4.addr = ESP_IP4TOADDR(8, 8, 4, 4);
esp_netif_set_dns_info(netif, ESP_NETIF_DNS_BACKUP, &dns);
ESP_LOGI(TAG, "Backup DNS set: 8.8.4.4");
}
}
/** Event handler for IP_EVENT_ETH_GOT_IP */
void eth_test_got_ip_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
EventGroupHandle_t eth_event_group = (EventGroupHandle_t)arg;
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
const esp_netif_ip_info_t *ip_info = &event->ip_info;
ESP_LOGI(TAG, "Ethernet Got IP Address");
ESP_LOGI(TAG, "~~~~~~~~~~~");
ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip));
ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask));
ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw));
ESP_LOGI(TAG, "~~~~~~~~~~~");
/* Configure public DNS fallbacks */
eth_test_set_dns_fallback(event->esp_netif);
xEventGroupSetBits(eth_event_group, ETH_GOT_IP_BIT);
}

View File

@@ -1,133 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "esp_err.h"
#include "unity.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_eth.h"
#define ETH_START_BIT BIT(0)
#define ETH_STOP_BIT BIT(1)
#define ETH_CONNECT_BIT BIT(2)
#define ETH_GOT_IP_BIT BIT(3)
#define ETH_START_TIMEOUT_MS (10000)
#define ETH_CONNECT_TIMEOUT_MS (40000)
#define ETH_STOP_TIMEOUT_MS (10000)
#define ETH_GET_IP_TIMEOUT_MS (60000)
#define ETH_DOWNLOAD_END_TIMEOUT_MS (240000)
#define MAX_HEAP_ALLOCATION_POINTERS 20
typedef struct {
uint8_t dest[ETH_ADDR_LEN];
uint8_t src[ETH_ADDR_LEN];
uint16_t proto;
uint8_t data[];
} __attribute__((__packed__)) emac_frame_t;
/** @brief Get the Ethernet handle initialized by setUp()
*
* @return esp_eth_handle_t The Ethernet handle, if initialized successfully, otherwise NULL
*/
esp_eth_handle_t eth_test_get_eth_handle(void);
/** @brief Get the Event Group handle initialized by setUp()
*
* @return EventGroupHandle_t The Event Group handle, if initialized successfully, otherwise NULL
*/
EventGroupHandle_t eth_test_get_default_event_group(void);
/** @brief Get the ESP-NETIF handle initialized by setUp()
*
* @note Valid output only available if the test case is using [esp-netif] identifier
*
* @return esp_netif_t The Network Interface handle if initialized successfully, otherwise NULL
*/
esp_netif_t *eth_test_get_netif(void);
/** @brief Set the PHY register bits
*
* @param eth_handle The Ethernet handle
* @param reg_addr The PHY register address
* @param bitmask The bits to set
* @param max_attempts The maximum number of set attempts
* @return esp_err_t The error code
*/
esp_err_t eth_test_set_phy_reg_bits(esp_eth_handle_t eth_handle, uint32_t reg_addr, uint32_t bitmask, uint32_t max_attempts);
/** @brief Clear the PHY register bits
*
* @param eth_handle The Ethernet handle
* @param reg_addr The PHY register address
* @param bitmask The bits to clear
* @param max_attempts The maximum number of clear attempts
* @return esp_err_t The error code
*/
esp_err_t eth_test_clear_phy_reg_bits(esp_eth_handle_t eth_handle, uint32_t reg_addr, uint32_t bitmask, uint32_t max_attempts);
/** @brief Initialize Ethernet driver (default implementation uses ethernet_init component).
*
* @param[out] eth_handle Initialized Ethernet driver handle
* @return esp_err_t
*/
esp_err_t esp_eth_test_eth_init(esp_eth_handle_t *eth_handle);
/** @brief Deinitialize Ethernet driver (default implementation uses ethernet_init component).
*
* @param eth_handle Ethernet driver handle to deinitialize
* @return esp_err_t
*/
esp_err_t esp_eth_test_eth_deinit(esp_eth_handle_t eth_handle);
/** @brief Default event handler function header for Ethernet events to be registered by `esp_event_handler_register()` in the test
* with event base `ETH_EVENT` and event ID `ESP_EVENT_ANY_ID`
*
* @note This function is intended to be registered as a callback function only when [skip_setup_teardown] identifier is used. Otherwise,
* it's registered automatically by the Common test app during setUp() phase.
*/
void eth_test_default_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data);
/** @brief Default event handler function header for IP_EVENT_ETH_GOT_IP events to be registered by `esp_event_handler_register()` in the test
* with event base `IP_EVENT` and event ID `IP_EVENT_ETH_GOT_IP`
*
* @note This function is intended to be registered as a callback function only when [skip_setup_teardown] identifier is used. Otherwise,
* it's registered automatically by the Common test app during setUp() phase.
*/
void eth_test_got_ip_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data);
/** @brief Allocate memory from the heap
*
* @note The the Common test app keeps track of all allocated memory and frees it automatically during tearDown() phase.
* The maximum number of allocations is limited to `MAX_HEAP_ALLOCATION_POINTERS`.
*
* @param size The size of the memory to allocate
* @return void* The pointer to the allocated memory, NULL if allocation failed
*/
void* eth_test_alloc(size_t size);
/** @brief Free memory from the heap
*
* @note All allocated memory is freed automatically during tearDown() phase. Use this function only when required by the test
* procedure.
*
* @param ptr The pointer to the memory to free
* @return void
*/
void eth_test_free(void *ptr);
/** @brief Free all memory from the heap
*
* @note All allocated memory is freed automatically during tearDown() phase. Use this function only when required by the test
* procedure.
*
* @return void
*/
void eth_test_free_all(void);

View File

@@ -2,3 +2,5 @@
dependencies:
espressif/ethernet_init:
version: "^1.3.0"
espressif/eth_test_app:
version: "*"

View File

@@ -1,254 +1,16 @@
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import contextlib
import logging
import os
import socket
from collections.abc import Iterator
from multiprocessing import Pipe
from multiprocessing import Process
from multiprocessing import connection
from typing import TYPE_CHECKING
import pytest
from pytest_embedded_idf import IdfDut
from pytest_embedded_idf.utils import idf_parametrize
from scapy.all import Ether
from scapy.all import raw
ETH_TYPE = 0x3300
class EthTestIntf:
def __init__(self, eth_type: int, my_if: str = ''):
self.target_if = ''
self.eth_type = eth_type
self.find_target_if(my_if)
def find_target_if(self, my_if: str = '') -> None:
# try to determine which interface to use
netifs = os.listdir('/sys/class/net/')
# order matters - ETH NIC with the highest number is connected to DUT on CI runner
netifs.sort(reverse=True)
logging.info('detected interfaces: %s', str(netifs))
if my_if == '':
if 'dut_p1' in netifs:
self.target_if = 'dut_p1'
else:
for netif in netifs:
# if no interface defined, try to find it automatically
if netif.find('eth') == 0 or netif.find('enp') == 0 or netif.find('eno') == 0:
self.target_if = netif
break
elif my_if in netifs:
self.target_if = my_if
if self.target_if == '':
raise RuntimeError('network interface not found')
logging.info('Use %s for testing', self.target_if)
@contextlib.contextmanager
def configure_eth_if(self, eth_type: int = 0) -> Iterator[socket.socket]:
if eth_type == 0:
eth_type = self.eth_type
so = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(eth_type)) # type: ignore
so.bind((self.target_if, 0))
try:
yield so
finally:
so.close()
def send_eth_packet(self, mac: str) -> None:
with self.configure_eth_if() as so:
so.settimeout(10)
payload = bytearray(1010)
for i, _ in enumerate(payload):
payload[i] = i & 0xFF
eth_frame = Ether(dst=mac, src=so.getsockname()[4], type=self.eth_type) / raw(payload)
try:
so.send(raw(eth_frame))
except Exception as e:
raise e
def recv_resp_poke(self, mac: str, i: int = 0) -> None:
eth_type_ctrl = self.eth_type + 1
with self.configure_eth_if(eth_type_ctrl) as so:
so.settimeout(30)
for _ in range(10):
try:
eth_frame = Ether(so.recv(60))
except Exception as e:
raise e
if mac == eth_frame.src and eth_frame.load[0] == 0xFA:
if eth_frame.load[1] != i:
raise RuntimeError('Missed Poke Packet')
logging.info('Poke Packet received...')
eth_frame.dst = eth_frame.src
eth_frame.src = so.getsockname()[4]
eth_frame.load = bytes.fromhex('fb') # POKE_RESP code
so.send(raw(eth_frame))
break
else:
logging.warning('Unexpected Control packet')
logging.warning('Expected Ctrl command: 0xfa, actual: 0x%x', eth_frame.load[0])
logging.warning('Source MAC %s', eth_frame.src)
else:
raise RuntimeError('No Poke Packet!')
def traffic_gen(self, mac: str, pipe_rcv: connection.Connection) -> None:
with self.configure_eth_if() as so:
payload = bytes.fromhex('ff') # DUMMY_TRAFFIC code
payload += bytes(1485)
eth_frame = Ether(dst=mac, src=so.getsockname()[4], type=self.eth_type) / raw(payload)
try:
while pipe_rcv.poll() is not True:
so.send(raw(eth_frame))
except Exception as e:
raise e
def eth_loopback(self, mac: str, pipe_rcv: connection.Connection) -> None:
with self.configure_eth_if(self.eth_type) as so:
so.settimeout(30)
try:
while pipe_rcv.poll() is not True:
try:
eth_frame = Ether(so.recv(1522))
except Exception as e:
raise e
if mac == eth_frame.src:
eth_frame.dst = eth_frame.src
eth_frame.src = so.getsockname()[4]
so.send(raw(eth_frame))
else:
logging.warning('Received frame from unexpected source')
logging.warning('Source MAC %s', eth_frame.src)
except Exception as e:
raise e
def ethernet_test(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='ethernet', timeout=980)
def ethernet_int_emac_test(dut: IdfDut) -> None:
dut.run_all_single_board_cases(group='esp_emac', timeout=240)
def ethernet_l2_test(dut: IdfDut, test_if: str = '') -> None:
target_if = EthTestIntf(ETH_TYPE, test_if)
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('\n')
dut.expect_exact('Enter test for running.')
with target_if.configure_eth_if() as so:
so.settimeout(30)
dut.write('"ethernet broadcast transmit"')
res = dut.expect(
r'DUT MAC: ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})'
)
# wait for POKE msg to be sure the switch already started forwarding the port's traffic
# (there might be slight delay due to the RSTP execution)
dut_mac = res.group(1).decode('utf-8')
target_if.recv_resp_poke(mac=dut_mac)
for _ in range(10):
eth_frame = Ether(so.recv(1024))
if dut_mac == eth_frame.src:
break
else:
raise RuntimeError('No broadcast received from expected DUT MAC addr')
for i in range(0, 1010):
if eth_frame.load[i] != i & 0xFF:
raise RuntimeError('Packet content mismatch')
dut.expect_unity_test_output()
dut.expect_exact("Enter next test, or 'enter' to see menu")
dut.write('"ethernet recv_pkt"')
res = dut.expect(
r'DUT MAC: ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})'
)
dut_mac = res.group(1).decode('utf-8')
target_if.recv_resp_poke(mac=dut_mac)
for _ in range(5):
# wait to be sure the DUT reconfigured the filter
dut.expect_exact('Filter configured')
target_if.send_eth_packet('ff:ff:ff:ff:ff:ff') # broadcast frame
target_if.send_eth_packet('01:00:5e:00:00:00') # IPv4 multicast frame
target_if.send_eth_packet('33:33:00:00:00:00') # IPv6 multicast frame
target_if.send_eth_packet(mac=dut_mac) # unicast frame
dut.expect_unity_test_output(extra_before=res.group(1))
dut.expect_exact("Enter next test, or 'enter' to see menu")
dut.write('"ethernet start/stop stress test under heavy traffic"')
res = dut.expect(
r'DUT MAC: ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})'
)
dut_mac = res.group(1).decode('utf-8')
# Start/stop under heavy Tx traffic
for tx_i in range(10):
target_if.recv_resp_poke(dut_mac, tx_i)
dut.expect_exact('Ethernet Stopped')
for rx_i in range(10):
target_if.recv_resp_poke(dut_mac, rx_i)
# Start/stop under heavy Rx traffic
pipe_rcv, pipe_send = Pipe(False)
tx_proc = Process(
target=target_if.traffic_gen,
args=(
dut_mac,
pipe_rcv,
),
)
tx_proc.start()
try:
dut.expect_exact('Ethernet Stopped')
finally:
pipe_send.send(0) # just send some dummy data to stop traffic generation
tx_proc.join(5)
if tx_proc.exitcode is None:
tx_proc.terminate()
dut.expect_unity_test_output(extra_before=res.group(1))
def ethernet_heap_alloc_test(dut: IdfDut, test_if: str = '') -> None:
target_if = EthTestIntf(ETH_TYPE, test_if)
dut.expect_exact('Press ENTER to see the list of tests')
dut.write('\n')
dut.expect_exact('Enter test for running.')
dut.write('"heap utilization"')
res = dut.expect(r'PHY loopback is (enabled|disabled)')
phy_loopback = res.group(1).decode('utf-8')
# Some chips do not support internal loopback, we need to loopback at test PC side
if phy_loopback == 'disabled':
logging.info('Starting loopback server...')
res = dut.expect(
r'DUT MAC: ([0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2})'
)
dut_mac = res.group(1).decode('utf-8')
pipe_rcv, pipe_send = Pipe(False)
loopback_proc = Process(
target=target_if.eth_loopback,
args=(
dut_mac,
pipe_rcv,
),
)
loopback_proc.start()
target_if.recv_resp_poke(mac=dut_mac)
dut.expect_exact('Ethernet Stopped')
pipe_send.send(0) # just send some dummy data
loopback_proc.join(5)
if loopback_proc.exitcode is None:
loopback_proc.terminate()
dut.expect_unity_test_output()
# Import for type-checking only — at runtime eth_test_runner pulls in dependencies that
# are not available during pytest collection. The real import is deferred to the fixture body.
if TYPE_CHECKING:
from conftest import EthTestRunner
# ----------- IP101 -----------
@@ -256,8 +18,8 @@ def ethernet_heap_alloc_test(dut: IdfDut, test_if: str = '') -> None:
@pytest.mark.parametrize('config', ['default_generic', 'release_generic', 'single_core_generic'], indirect=True)
@pytest.mark.flaky(reruns=3, reruns_delay=5)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_ethernet(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_ethernet(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
@pytest.mark.eth_ip101
@@ -269,10 +31,10 @@ def test_esp_ethernet(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_emac(dut: IdfDut) -> None:
ethernet_int_emac_test(dut)
def test_esp_emac(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
dut.run_all_single_board_cases(group='esp_emac', timeout=240)
dut.serial.hard_reset()
ethernet_heap_alloc_test(dut)
eth_test_runner.run_ethernet_heap_alloc_test(dut)
@pytest.mark.eth_ip101
@@ -284,8 +46,8 @@ def test_esp_emac(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_ip101(dut: IdfDut) -> None:
ethernet_l2_test(dut)
def test_esp_eth_ip101(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_l2_test(dut)
@pytest.mark.eth_ip101
@@ -310,10 +72,10 @@ def test_esp32_emac_clko(dut: IdfDut) -> None:
],
indirect=['target'],
)
def test_esp32p4_ethernet(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp32p4_ethernet(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
@pytest.mark.parametrize(
@@ -324,10 +86,10 @@ def test_esp32p4_ethernet(dut: IdfDut) -> None:
],
indirect=['target'],
)
def test_esp32p4_emac(dut: IdfDut) -> None:
ethernet_int_emac_test(dut)
def test_esp32p4_emac(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
dut.run_all_single_board_cases(group='esp_emac', timeout=240)
dut.serial.hard_reset()
ethernet_heap_alloc_test(dut)
eth_test_runner.run_ethernet_heap_alloc_test(dut)
@pytest.mark.parametrize(
@@ -352,10 +114,10 @@ def test_esp32p4_emac_clko(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_lan8720(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_lan8720(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
# ----------- RTL8201 -----------
@@ -368,10 +130,10 @@ def test_esp_eth_lan8720(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_rtl8201(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_rtl8201(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
# ----------- KSZ8041 -----------
@@ -384,10 +146,10 @@ def test_esp_eth_rtl8201(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_ksz8041(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_ksz8041(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
# ----------- DP83848 -----------
@@ -400,10 +162,10 @@ def test_esp_eth_ksz8041(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_dp83848(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_dp83848(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
# ----------- W5500 -----------
@@ -416,12 +178,12 @@ def test_esp_eth_dp83848(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_w5500(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_w5500(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
dut.serial.hard_reset()
ethernet_heap_alloc_test(dut)
eth_test_runner.run_ethernet_heap_alloc_test(dut)
# ----------- KSZ8851SNL -----------
@@ -434,12 +196,12 @@ def test_esp_eth_w5500(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_ksz8851snl(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_ksz8851snl(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
dut.serial.hard_reset()
ethernet_heap_alloc_test(dut)
eth_test_runner.run_ethernet_heap_alloc_test(dut)
# ----------- DM9051 -----------
@@ -452,12 +214,12 @@ def test_esp_eth_ksz8851snl(dut: IdfDut) -> None:
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_eth_dm9051(dut: IdfDut) -> None:
ethernet_test(dut)
def test_esp_eth_dm9051(dut: IdfDut, eth_test_runner: 'EthTestRunner') -> None:
eth_test_runner.run_ethernet_test_apps(dut)
dut.serial.hard_reset()
ethernet_l2_test(dut)
eth_test_runner.run_ethernet_l2_test(dut)
dut.serial.hard_reset()
ethernet_heap_alloc_test(dut)
eth_test_runner.run_ethernet_heap_alloc_test(dut)
# ----------- EMAC Sleep Retention -----------