esp_eth: improved L2 test stability

This commit is contained in:
Ondrej Kosta
2023-01-30 09:29:58 +01:00
parent 256d457969
commit 51ea57f5b8
2 changed files with 209 additions and 142 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@@ -9,20 +9,112 @@
#include "freertos/task.h" #include "freertos/task.h"
#include "freertos/event_groups.h" #include "freertos/event_groups.h"
#include "esp_eth_test_common.h" #include "esp_eth_test_common.h"
#include "arpa/inet.h" // for ntohs, etc.
#include "esp_log.h"
#define TEST_ETH_TYPE 0x2222
#define TEST_CTRL_ETH_TYPE (TEST_ETH_TYPE + 1)
#define WAIT_AFTER_CONN_MS 2500
#define WAIT_AFTER_CONN_TMO_MS 20000
#define ETH_BROADCAST_RECV_BIT BIT(0) #define ETH_BROADCAST_RECV_BIT BIT(0)
#define ETH_MULTICAST_RECV_BIT BIT(1) #define ETH_MULTICAST_RECV_BIT BIT(1)
#define ETH_UNICAST_RECV_BIT BIT(2) #define ETH_UNICAST_RECV_BIT BIT(2)
#define ETH_POKE_RESP_RECV_BIT BIT(3)
#define POKE_REQ 0xFA #define POKE_REQ 0xFA
#define POKE_RESP 0xFB #define POKE_RESP 0xFB
#define DUMMY_TRAFFIC 0xFF #define DUMMY_TRAFFIC 0xFF
typedef struct
{
EventGroupHandle_t eth_event_group;
int unicast_rx_cnt;
int multicast_rx_cnt;
int brdcast_rx_cnt;
bool check_rx_data;
} recv_info_t;
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 == 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) {
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, 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;
for(tmo = 0, i = 1; tmo < WAIT_AFTER_CONN_TMO_MS; tmo += WAIT_AFTER_CONN_MS, i++) {
printf("Poke attempt #%" PRIu32 "\n", i);
TEST_ESP_OK(esp_eth_transmit(eth_handle, ctrl_pkt, 60));
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) {
break;
}
}
TEST_ASSERT(tmo < WAIT_AFTER_CONN_TMO_MS);
free(ctrl_pkt);
}
TEST_CASE("ethernet broadcast transmit", "[ethernet_l2]") TEST_CASE("ethernet broadcast transmit", "[ethernet_l2]")
{ {
EventGroupHandle_t eth_event_group = xEventGroupCreate();
TEST_ASSERT(eth_event_group != NULL);
esp_eth_mac_t *mac = mac_init(NULL, NULL); esp_eth_mac_t *mac = mac_init(NULL, NULL);
TEST_ASSERT_NOT_NULL(mac); TEST_ASSERT_NOT_NULL(mac);
esp_eth_phy_t *phy = phy_init(NULL); esp_eth_phy_t *phy = phy_init(NULL);
@@ -32,19 +124,33 @@ TEST_CASE("ethernet broadcast transmit", "[ethernet_l2]")
TEST_ESP_OK(esp_eth_driver_install(&config, &eth_handle)); // install driver TEST_ESP_OK(esp_eth_driver_install(&config, &eth_handle)); // install driver
TEST_ASSERT_NOT_NULL(eth_handle); TEST_ASSERT_NOT_NULL(eth_handle);
extra_eth_config(eth_handle); extra_eth_config(eth_handle);
TEST_ESP_OK(esp_event_loop_create_default()); TEST_ESP_OK(esp_event_loop_create_default());
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_event_group)); EventGroupHandle_t eth_event_state_group = xEventGroupCreate();
TEST_ASSERT(eth_event_state_group != NULL);
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_event_state_group));
EventGroupHandle_t eth_event_rx_group = xEventGroupCreate();
TEST_ASSERT(eth_event_rx_group != NULL);
recv_info_t recv_info = {
.eth_event_group = eth_event_rx_group,
.check_rx_data = false,
.unicast_rx_cnt = 0,
.multicast_rx_cnt = 0,
.brdcast_rx_cnt = 0
};
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &recv_info));
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
EventBits_t bits = 0; EventBits_t bits = 0;
bits = xEventGroupWaitBits(eth_event_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(3000)); bits = xEventGroupWaitBits(eth_event_state_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT); 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 // 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) // starts switching the associated port (e.g. it runs RSTP at first)
vTaskDelay(pdMS_TO_TICKS(1000)); poke_and_wait(eth_handle, NULL, 0, eth_event_rx_group);
emac_frame_t *pkt = malloc(1024); emac_frame_t *pkt = malloc(1024);
pkt->proto = 0x2222; pkt->proto = TEST_ETH_TYPE;
TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, pkt->src)); TEST_ESP_OK(esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, pkt->src));
memset(pkt->dest, 0xff, ETH_ADDR_LEN); // broadcast addr memset(pkt->dest, 0xff, ETH_ADDR_LEN); // broadcast addr
for (int i = 0; i < (1024 - ETH_HEADER_LEN); ++i){ for (int i = 0; i < (1024 - ETH_HEADER_LEN); ++i){
@@ -61,44 +167,12 @@ TEST_CASE("ethernet broadcast transmit", "[ethernet_l2]")
phy->del(phy); phy->del(phy);
mac->del(mac); mac->del(mac);
extra_cleanup(); extra_cleanup();
vEventGroupDelete(eth_event_group); vEventGroupDelete(eth_event_rx_group);
vEventGroupDelete(eth_event_state_group);
} }
static uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
esp_err_t l2_packet_txrx_test_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv) {
EventGroupHandle_t eth_event_group = (EventGroupHandle_t)priv;
emac_frame_t *pkt = (emac_frame_t *) buffer;
// check header
if (pkt->proto == 0x2222 && length == 1024) {
// check content
for (int i = 0; i < (length - ETH_HEADER_LEN); ++i) {
if (pkt->data[i] != (i & 0xff)) {
printf("payload mismatch\n");
return ESP_OK;
}
}
if (memcmp(pkt->dest, "\xff\xff\xff\xff\xff\xff", ETH_ADDR_LEN) == 0) {
printf("broadcast received...\n");
xEventGroupSetBits(eth_event_group, ETH_BROADCAST_RECV_BIT);
} else if (pkt->dest[0] & 0x1) {
printf("multicast received...\n");
xEventGroupSetBits(eth_event_group, ETH_MULTICAST_RECV_BIT);
} else if (memcmp(pkt->dest, local_mac_addr, ETH_ADDR_LEN) == 0) {
printf("unicast received...\n");
xEventGroupSetBits(eth_event_group, ETH_UNICAST_RECV_BIT);
}
} else {
printf("unexpected frame (protocol: 0x%x, length: %" PRIu32 ")\n", pkt->proto, length);
}
return ESP_OK;
};
TEST_CASE("ethernet recv_pkt", "[ethernet_l2]") TEST_CASE("ethernet recv_pkt", "[ethernet_l2]")
{ {
EventGroupHandle_t eth_event_group = xEventGroupCreate();
TEST_ASSERT(eth_event_group != NULL);
esp_eth_mac_t *mac = mac_init(NULL, NULL); esp_eth_mac_t *mac = mac_init(NULL, NULL);
TEST_ASSERT_NOT_NULL(mac); TEST_ASSERT_NOT_NULL(mac);
esp_eth_phy_t *phy = phy_init(NULL); esp_eth_phy_t *phy = phy_init(NULL);
@@ -108,19 +182,41 @@ TEST_CASE("ethernet recv_pkt", "[ethernet_l2]")
TEST_ESP_OK(esp_eth_driver_install(&config, &eth_handle)); // install driver TEST_ESP_OK(esp_eth_driver_install(&config, &eth_handle)); // install driver
TEST_ASSERT_NOT_NULL(eth_handle); TEST_ASSERT_NOT_NULL(eth_handle);
extra_eth_config(eth_handle); extra_eth_config(eth_handle);
TEST_ESP_OK(esp_event_loop_create_default());
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
TEST_ESP_OK(esp_event_loop_create_default());
EventGroupHandle_t eth_event_state_group = xEventGroupCreate();
TEST_ASSERT(eth_event_state_group != NULL);
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_event_state_group));
EventGroupHandle_t eth_event_rx_group = xEventGroupCreate();
TEST_ASSERT(eth_event_rx_group != NULL);
recv_info_t recv_info = {
.eth_event_group = eth_event_rx_group,
.check_rx_data = true,
.unicast_rx_cnt = 0,
.multicast_rx_cnt = 0,
.brdcast_rx_cnt = 0
};
uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
TEST_ESP_OK(mac->get_addr(mac, local_mac_addr)); TEST_ESP_OK(mac->get_addr(mac, local_mac_addr));
// test app will parse the DUT MAC from this line of log output // 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], 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]); 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, eth_event_group)); TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &recv_info));
TEST_ESP_OK(esp_eth_start(eth_handle)); // start Ethernet driver state machine
EventBits_t bits = 0; EventBits_t bits = 0;
bits = xEventGroupWaitBits(eth_event_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT, bits = xEventGroupWaitBits(eth_event_state_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(3000));
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, eth_event_rx_group);
bits = 0;
bits = xEventGroupWaitBits(eth_event_rx_group, ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT,
true, true, pdMS_TO_TICKS(5000)); true, true, pdMS_TO_TICKS(5000));
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)) == TEST_ASSERT((bits & (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)) ==
(ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT)); (ETH_BROADCAST_RECV_BIT | ETH_MULTICAST_RECV_BIT | ETH_UNICAST_RECV_BIT));
@@ -130,45 +226,13 @@ TEST_CASE("ethernet recv_pkt", "[ethernet_l2]")
phy->del(phy); phy->del(phy);
mac->del(mac); mac->del(mac);
extra_cleanup(); extra_cleanup();
vEventGroupDelete(eth_event_group); vEventGroupDelete(eth_event_state_group);
vEventGroupDelete(eth_event_rx_group);
} }
typedef struct
{
SemaphoreHandle_t mutex;
int rx_pkt_cnt;
} recv_info_t;
static esp_err_t eth_recv_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t length, void *priv)
{
emac_frame_t *pkt = (emac_frame_t *)buffer;
recv_info_t *recv_info = (recv_info_t *)priv;
if (pkt->proto == 0x2222) {
switch (pkt->data[0])
{
case POKE_RESP:
xSemaphoreGive(recv_info->mutex);
break;
case DUMMY_TRAFFIC:
(recv_info->rx_pkt_cnt)++;
break;
default:
break;
}
}
free(buffer);
return ESP_OK;
}
TEST_CASE("ethernet start/stop stress test under heavy traffic", "[ethernet_l2]") TEST_CASE("ethernet start/stop stress test under heavy traffic", "[ethernet_l2]")
{ {
recv_info_t recv_info;
recv_info.mutex = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(recv_info.mutex);
recv_info.rx_pkt_cnt = 0;
esp_eth_mac_t *mac = mac_init(NULL, NULL); esp_eth_mac_t *mac = mac_init(NULL, NULL);
TEST_ASSERT_NOT_NULL(mac); TEST_ASSERT_NOT_NULL(mac);
esp_eth_phy_t *phy = phy_init(NULL); esp_eth_phy_t *phy = phy_init(NULL);
@@ -179,28 +243,32 @@ TEST_CASE("ethernet start/stop stress test under heavy traffic", "[ethernet_l2]"
TEST_ASSERT_NOT_NULL(eth_handle); TEST_ASSERT_NOT_NULL(eth_handle);
extra_eth_config(eth_handle); extra_eth_config(eth_handle);
TEST_ESP_OK(esp_event_loop_create_default());
EventBits_t bits = 0;
EventGroupHandle_t eth_event_state_group = xEventGroupCreate();
TEST_ASSERT(eth_event_state_group != NULL);
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, eth_event_state_group));
EventGroupHandle_t eth_event_rx_group = xEventGroupCreate();
TEST_ASSERT(eth_event_rx_group != NULL);
recv_info_t recv_info = {
.eth_event_group = eth_event_rx_group,
.check_rx_data = false,
.unicast_rx_cnt = 0,
.multicast_rx_cnt = 0,
.brdcast_rx_cnt = 0
};
uint8_t local_mac_addr[ETH_ADDR_LEN] = {};
TEST_ESP_OK(mac->get_addr(mac, local_mac_addr)); TEST_ESP_OK(mac->get_addr(mac, local_mac_addr));
// test app will parse the DUT MAC from this line of log output // 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], 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]); local_mac_addr[3], local_mac_addr[4], local_mac_addr[5]);
TEST_ESP_OK(esp_eth_update_input_path(eth_handle, eth_recv_cb, &recv_info)); TEST_ESP_OK(esp_eth_update_input_path(eth_handle, l2_packet_txrx_test_cb, &recv_info));
EventBits_t bits = 0;
EventGroupHandle_t eth_event_group = xEventGroupCreate();
TEST_ASSERT(eth_event_group != NULL);
TEST_ESP_OK(esp_event_loop_create_default());
TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, 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 = 0x2222;
memset(ctrl_pkt->dest, 0xff, ETH_ADDR_LEN); // broadcast addr
memcpy(ctrl_pkt->src, local_mac_addr, ETH_ADDR_LEN);
// create dummy data packet used for traffic generation // create dummy data packet used for traffic generation
emac_frame_t *pkt = calloc(1, 1500); emac_frame_t *pkt = calloc(1, 1500);
pkt->proto = 0x2222; pkt->proto = TEST_ETH_TYPE;
// we don't care about dest MAC address much, however it is better to not be broadcast or multifcast to not flood // we don't care about dest MAC address much, however it is better to not be broadcast or multifcast to not flood
// other network nodes // other network nodes
memset(pkt->dest, 0xBA, ETH_ADDR_LEN); memset(pkt->dest, 0xBA, ETH_ADDR_LEN);
@@ -208,62 +276,51 @@ TEST_CASE("ethernet start/stop stress test under heavy traffic", "[ethernet_l2]"
printf("EMAC start/stop stress test under heavy Tx traffic\n"); printf("EMAC start/stop stress test under heavy Tx traffic\n");
for (int tx_i = 0; tx_i < 10; tx_i++) { 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 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(3000)); bits = xEventGroupWaitBits(eth_event_state_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT); 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)
vTaskDelay(pdMS_TO_TICKS(1000));
// at first, check that Tx/Rx path works as expected by poking the test script // at first, check that Tx/Rx path works as expected by poking the test script
// this also serves as main PASS/FAIL criteria // this also serves as main PASS/FAIL criteria
ctrl_pkt->data[0] = POKE_REQ; poke_and_wait(eth_handle, &tx_i, sizeof(tx_i), eth_event_rx_group);
ctrl_pkt->data[1] = tx_i;
TEST_ESP_OK(esp_eth_transmit(eth_handle, ctrl_pkt, 60));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(3000)));
printf("Tx Test iteration %d\n", tx_i);
// generate heavy Tx traffic // generate heavy Tx traffic
printf("Note: transmit errors are expected...\n"); printf("Note: transmit errors are expected...\n");
for (int j = 0; j < 150; j++) { 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 // 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 // we may try to queue more packets than hardware is able to handle
pkt->data[0] = j & 0xFF; // sequence number pkt->data[2] = j & 0xFF; // sequence number
esp_eth_transmit(eth_handle, pkt, 1500); esp_eth_transmit(eth_handle, pkt, 1500);
} }
TEST_ESP_OK(esp_eth_stop(eth_handle)); TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000)); bits = xEventGroupWaitBits(eth_event_state_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT); TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
printf("Ethernet stopped\n"); printf("Ethernet stopped\n");
} }
printf("EMAC start/stop stress test under heavy Rx traffic\n"); printf("EMAC start/stop stress test under heavy Rx traffic\n");
for (int rx_i = 0; rx_i < 10; rx_i++) { for (int rx_i = 0; rx_i < 10; rx_i++) {
recv_info.rx_pkt_cnt = 0;
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(3000));
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)
vTaskDelay(pdMS_TO_TICKS(1000));
ctrl_pkt->data[0] = POKE_REQ;
ctrl_pkt->data[1] = rx_i;
TEST_ESP_OK(esp_eth_transmit(eth_handle, ctrl_pkt, 60));
TEST_ASSERT(xSemaphoreTake(recv_info.mutex, pdMS_TO_TICKS(3000)));
printf("Rx Test iteration %d\n", 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_state_group, ETH_CONNECT_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_CONNECT_BIT) == ETH_CONNECT_BIT);
poke_and_wait(eth_handle, &rx_i, sizeof(rx_i), eth_event_rx_group);
// wait for dummy traffic
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)); vTaskDelay(pdMS_TO_TICKS(500));
TEST_ESP_OK(esp_eth_stop(eth_handle)); TEST_ESP_OK(esp_eth_stop(eth_handle));
bits = xEventGroupWaitBits(eth_event_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000)); bits = xEventGroupWaitBits(eth_event_state_group, ETH_STOP_BIT, true, true, pdMS_TO_TICKS(3000));
TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT); TEST_ASSERT((bits & ETH_STOP_BIT) == ETH_STOP_BIT);
printf("Recv packets: %d\n", recv_info.rx_pkt_cnt); printf("Recv packets: %d\n", recv_info.unicast_rx_cnt);
TEST_ASSERT_GREATER_THAN_INT32(0, recv_info.rx_pkt_cnt); TEST_ASSERT_GREATER_THAN_INT32(0, recv_info.unicast_rx_cnt);
printf("Ethernet stopped\n"); printf("Ethernet stopped\n");
} }
free(ctrl_pkt);
free(pkt); free(pkt);
TEST_ESP_OK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler)); TEST_ESP_OK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler));
@@ -272,6 +329,6 @@ TEST_CASE("ethernet start/stop stress test under heavy traffic", "[ethernet_l2]"
phy->del(phy); phy->del(phy);
mac->del(mac); mac->del(mac);
extra_cleanup(); extra_cleanup();
vEventGroupDelete(eth_event_group); vEventGroupDelete(eth_event_rx_group);
vSemaphoreDelete(recv_info.mutex); vEventGroupDelete(eth_event_state_group);
} }

View File

@@ -1,11 +1,10 @@
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD # SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
import contextlib import contextlib
import logging import logging
import os import os
import socket import socket
import time
from multiprocessing import Pipe, Process, connection from multiprocessing import Pipe, Process, connection
from typing import Iterator from typing import Iterator
@@ -42,8 +41,10 @@ class EthTestIntf(object):
logging.info('Use %s for testing', self.target_if) logging.info('Use %s for testing', self.target_if)
@contextlib.contextmanager @contextlib.contextmanager
def configure_eth_if(self) -> Iterator[socket.socket]: def configure_eth_if(self, eth_type:int=0) -> Iterator[socket.socket]:
so = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(self.eth_type)) if eth_type == 0:
eth_type = self.eth_type
so = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(eth_type))
so.bind((self.target_if, 0)) so.bind((self.target_if, 0))
try: try:
yield so yield so
@@ -62,15 +63,16 @@ class EthTestIntf(object):
except Exception as e: except Exception as e:
raise e raise e
def recv_resp_poke(self, i: int) -> None: def recv_resp_poke(self, i:int=0) -> None:
with self.configure_eth_if() as so: eth_type_ctrl = self.eth_type + 1
so.settimeout(10) with self.configure_eth_if(eth_type_ctrl) as so:
so.settimeout(30)
try: try:
eth_frame = Ether(so.recv(60)) eth_frame = Ether(so.recv(60))
if eth_frame.load[0] == 0xfa:
if eth_frame.type == self.eth_type and eth_frame.load[0] == 0xfa:
if eth_frame.load[1] != i: if eth_frame.load[1] != i:
raise Exception('Missed Poke Packet') raise Exception('Missed Poke Packet')
logging.info('Poke Packet received...')
eth_frame.dst = eth_frame.src eth_frame.dst = eth_frame.src
eth_frame.src = so.getsockname()[4] eth_frame.src = so.getsockname()[4]
eth_frame.load = bytes.fromhex('fb') # POKE_RESP code eth_frame.load = bytes.fromhex('fb') # POKE_RESP code
@@ -118,6 +120,11 @@ def ethernet_l2_test(dut: Dut) -> None:
with target_if.configure_eth_if() as so: with target_if.configure_eth_if() as so:
so.settimeout(30) so.settimeout(30)
dut.write('"ethernet broadcast transmit"') dut.write('"ethernet broadcast transmit"')
# 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)
target_if.recv_resp_poke()
eth_frame = Ether(so.recv(1024)) eth_frame = Ether(so.recv(1024))
for i in range(0, 1010): for i in range(0, 1010):
if eth_frame.load[i] != i & 0xff: if eth_frame.load[i] != i & 0xff:
@@ -130,7 +137,9 @@ def ethernet_l2_test(dut: Dut) -> None:
r'([\s\S]*)' r'([\s\S]*)'
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})' 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})'
) )
time.sleep(1) # 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)
target_if.recv_resp_poke()
target_if.send_eth_packet('ff:ff:ff:ff:ff:ff') # broadcast frame target_if.send_eth_packet('ff:ff:ff:ff:ff:ff') # broadcast frame
target_if.send_eth_packet('01:00:00:00:00:00') # multicast frame target_if.send_eth_packet('01:00:00:00:00:00') # multicast frame
target_if.send_eth_packet(res.group(2)) # unicast frame target_if.send_eth_packet(res.group(2)) # unicast frame
@@ -145,19 +154,20 @@ def ethernet_l2_test(dut: Dut) -> None:
# Start/stop under heavy Tx traffic # Start/stop under heavy Tx traffic
for tx_i in range(10): for tx_i in range(10):
target_if.recv_resp_poke(tx_i) target_if.recv_resp_poke(tx_i)
dut.expect_exact('Ethernet stopped')
for rx_i in range(10):
target_if.recv_resp_poke(rx_i)
# Start/stop under heavy Rx traffic # Start/stop under heavy Rx traffic
pipe_rcv, pipe_send = Pipe(False) pipe_rcv, pipe_send = Pipe(False)
tx_proc = Process(target=target_if.traffic_gen, args=(res.group(2), pipe_rcv, )) tx_proc = Process(target=target_if.traffic_gen, args=(res.group(2), pipe_rcv, ))
tx_proc.start() tx_proc.start()
try: dut.expect_exact('Ethernet stopped')
for rx_i in range(10):
target_if.recv_resp_poke(rx_i)
finally:
pipe_send.send(0) # just send some dummy data pipe_send.send(0) # just send some dummy data
tx_proc.join(5) tx_proc.join(5)
if tx_proc.exitcode is None: if tx_proc.exitcode is None:
tx_proc.terminate() tx_proc.terminate()
dut.expect_unity_test_output(extra_before=res.group(1)) dut.expect_unity_test_output(extra_before=res.group(1))