Created EMAC start/stop stress test under heavy traffic

This commit is contained in:
Ondrej Kosta 2022-01-18 13:55:06 +01:00
parent e97fd4b076
commit 57225f99c7
4 changed files with 252 additions and 21 deletions

View File

@ -168,6 +168,7 @@ before_script:
- retry_failed git clone --depth 1 --branch $PYTEST_EMBEDDED_TAG https://gitlab-ci-token:${BOT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/idf/pytest-embedded.git - retry_failed git clone --depth 1 --branch $PYTEST_EMBEDDED_TAG https://gitlab-ci-token:${BOT_TOKEN}@${CI_SERVER_HOST}:${CI_SERVER_PORT}/idf/pytest-embedded.git
- cd pytest-embedded && bash foreach.sh install - cd pytest-embedded && bash foreach.sh install
- pip install pytest-rerunfailures - pip install pytest-rerunfailures
- pip install scapy
- cd $IDF_PATH - cd $IDF_PATH
default: default:

View File

@ -1,7 +1,15 @@
# EMAC Test
| Supported Targets | ESP32 | | Supported Targets | ESP32 |
| ----------------- | ----- | | ----------------- | ----- |
This test app is used to test MAC layer behavior with different PHY chips: This test app is used to test MAC layer behavior with different PHY chips:
- ip101 - IP101
- lan8720 - LAN8720
## Prerequisites
Install third part Python packages:
```bash
pip install scapy
```

View File

@ -17,6 +17,10 @@
#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 POKE_REQ 0xFA
#define POKE_RESP 0xFB
#define DUMMY_TRAFFIC 0xFF
typedef struct { typedef struct {
uint8_t dest[6]; uint8_t dest[6];
uint8_t src[6]; uint8_t src[6];
@ -170,12 +174,14 @@ TEST_CASE("ethernet_broadcast_transmit", "[esp_eth]")
TEST_ASSERT_EQUAL(ESP_OK, esp_eth_start(eth_handle)); // start Ethernet driver state machine TEST_ASSERT_EQUAL(ESP_OK, esp_eth_start(eth_handle)); // start Ethernet driver state machine
TEST_ASSERT(xSemaphoreTake(mutex, pdMS_TO_TICKS(3000))); TEST_ASSERT(xSemaphoreTake(mutex, pdMS_TO_TICKS(3000)));
// even if PHY (IP101) indicates autonegotiation done and link up, it sometimes may miss few packets after atonego reset, hence wait a bit
vTaskDelay(pdMS_TO_TICKS(100));
emac_frame_t *pkt = malloc(1024); emac_frame_t *pkt = malloc(1024);
pkt->proto = 0x2222; pkt->proto = 0x2222;
memset(pkt->dest, 0xff, 6); // broadcast addr memset(pkt->dest, 0xff, 6); // broadcast addr
for (int i = 128; i < 1024; ++i){ for (int i = 0; i < (1024 - ETH_HEADER_LEN); ++i){
((uint8_t*)pkt)[i] = i & 0xff; pkt->data[i] = i & 0xff;
} }
TEST_ASSERT_EQUAL(ESP_OK, esp_eth_transmit(eth_handle, pkt, 1024)); TEST_ASSERT_EQUAL(ESP_OK, esp_eth_transmit(eth_handle, pkt, 1024));
@ -198,8 +204,8 @@ esp_err_t l2_packet_txrx_test_cb(esp_eth_handle_t hdl, uint8_t *buffer, uint32_t
// check header // check header
if (pkt->proto == 0x2222 && length == 1024) { if (pkt->proto == 0x2222 && length == 1024) {
// check content // check content
for (int i = 128; i < 1024; ++i) { for (int i = 0; i < (length - ETH_HEADER_LEN); ++i) {
if (buffer[i] != (i & 0xff)) { if (pkt->data[i] != (i & 0xff)) {
return ESP_OK; return ESP_OK;
} }
} }
@ -259,6 +265,171 @@ TEST_CASE("recv_pkt", "[esp_eth]")
vEventGroupDelete(eth_event_group); vEventGroupDelete(eth_event_group);
} }
typedef struct
{
SemaphoreHandle_t mutex;
int rx_pkt_cnt;
} recv_info_t;
TEST_CASE("start_stop_stress_test", "[esp_eth]")
{
void eth_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:
xEventGroupSetBits(eth_event_group, ETH_CONNECT_BIT);
break;
case ETHERNET_EVENT_DISCONNECTED:
break;
case ETHERNET_EVENT_START:
xEventGroupSetBits(eth_event_group, ETH_START_BIT);
break;
case ETHERNET_EVENT_STOP:
xEventGroupSetBits(eth_event_group, ETH_STOP_BIT);
break;
default:
break;
}
}
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;
}
recv_info_t recv_info;
recv_info.mutex = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(recv_info.mutex);
recv_info.rx_pkt_cnt = 0;
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); // apply default MAC configuration
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); // create MAC instance
TEST_ASSERT_NOT_NULL(mac);
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); // apply default PHY configuration
#if defined(CONFIG_TARGET_ETH_PHY_DEVICE_IP101)
esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config); // create PHY instance
#elif defined(CONFIG_TARGET_ETH_PHY_DEVICE_LAN87XX)
esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
#endif
TEST_ASSERT_NOT_NULL(phy);
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); // apply default driver configuration
esp_eth_handle_t eth_handle = NULL; // after driver installed, we will get the handle of the driver
TEST_ASSERT_EQUAL(ESP_OK, esp_eth_driver_install(&config, &eth_handle)); // install driver
TEST_ASSERT_NOT_NULL(eth_handle);
TEST_ASSERT_EQUAL(ESP_OK, mac->get_addr(mac, 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, eth_recv_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, 6); // broadcast addr
memcpy(ctrl_pkt->src, local_mac_addr, 6);
// create dummy data packet used for traffic generation
emac_frame_t *pkt = calloc(1, 1500);
pkt->proto = 0x2222;
// 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
memset(pkt->dest, 0xBA, 6);
memcpy(pkt->src, local_mac_addr, 6);
printf("EMAC start/stop stress test under heavy Tx traffic\n");
for (int tx_i = 0; tx_i < 10; tx_i++) {
TEST_ASSERT_EQUAL(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);
// even if PHY (IP101) indicates autonegotiation done and link up, it sometimes may miss few packets after atonego reset, hence wait a bit
vTaskDelay(pdMS_TO_TICKS(100));
// at first, check that Tx/Rx path works as expected by poking the test script
// this also serves as main PASS/FAIL criteria
ctrl_pkt->data[0] = POKE_REQ;
ctrl_pkt->data[1] = tx_i;
TEST_ASSERT_EQUAL(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
printf("Note: transmit errors are expected...\n");
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[0] = j & 0xFF;
esp_eth_transmit(eth_handle, pkt, 1500);
}
TEST_ASSERT_EQUAL(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("Ethernet stopped\n");
}
printf("EMAC start/stop stress test under heavy Rx traffic\n");
for (int rx_i = 0; rx_i < 10; rx_i++) {
recv_info.rx_pkt_cnt = 0;
TEST_ASSERT_EQUAL(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);
// even if PHY (IP101) indicates autonegotiation done and link up, it sometimes may miss few packets after atonego reset, hence wait a bit
vTaskDelay(pdMS_TO_TICKS(100));
ctrl_pkt->data[0] = POKE_REQ;
ctrl_pkt->data[1] = rx_i;
TEST_ASSERT_EQUAL(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);
vTaskDelay(pdMS_TO_TICKS(500));
TEST_ASSERT_EQUAL(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", recv_info.rx_pkt_cnt);
TEST_ASSERT_GREATER_THAN_INT32(0, recv_info.rx_pkt_cnt);
printf("Ethernet stopped\n");
}
free(ctrl_pkt);
free(pkt);
TEST_ESP_OK(esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler));
TEST_ASSERT_EQUAL(ESP_OK, esp_event_loop_delete_default());
TEST_ASSERT_EQUAL(ESP_OK, esp_eth_driver_uninstall(eth_handle));
phy->del(phy);
mac->del(mac);
vEventGroupDelete(eth_event_group);
vSemaphoreDelete(recv_info.mutex);
}
void app_main(void) void app_main(void)
{ {
unity_run_menu(); unity_run_menu();

View File

@ -5,10 +5,13 @@ import contextlib
import logging import logging
import os import os
import socket import socket
from collections.abc import Callable
from threading import Thread
from typing import Iterator from typing import Iterator
import pytest import pytest
from pytest_embedded import Dut from pytest_embedded import Dut
from scapy.all import Ether, raw
@contextlib.contextmanager @contextlib.contextmanager
@ -35,18 +38,44 @@ def configure_eth_if() -> Iterator[socket.socket]:
so.close() so.close()
def send_eth_packet(mac: bytes) -> None: def send_eth_packet(mac: str) -> None:
with configure_eth_if() as so: with configure_eth_if() as so:
so.settimeout(10) so.settimeout(10)
pkt = bytearray() payload = bytearray(1010)
pkt += mac # dest for i, _ in enumerate(payload):
pkt += so.getsockname()[4] # src payload[i] = i & 0xff
pkt += bytes.fromhex('2222') # proto eth_frame = Ether(dst=mac, src=so.getsockname()[4], type=0x2222) / raw(payload)
pkt += bytes(1010) # padding to 1024
for i in range(128, 1024):
pkt[i] = i & 0xff
try: try:
so.send(pkt) so.send(raw(eth_frame))
except Exception as e:
raise e
def recv_resp_poke(i: int) -> None:
with configure_eth_if() as so:
so.settimeout(10)
try:
eth_frame = Ether(so.recv(60))
if eth_frame.type == 0x2222 and eth_frame.load[0] == 0xfa:
if eth_frame.load[1] != i:
raise Exception('Missed Poke Packet')
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))
except Exception as e:
raise e
def traffic_gen(mac: str, enabled: Callable) -> None:
with 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=0x2222) / raw(payload)
try:
while enabled() == 1:
so.send(raw(eth_frame))
except Exception as e: except Exception as e:
raise e raise e
@ -67,9 +96,9 @@ def actual_test(dut: Dut) -> None:
with configure_eth_if() as so: with configure_eth_if() as so:
so.settimeout(30) so.settimeout(30)
dut.write('"ethernet_broadcast_transmit"') dut.write('"ethernet_broadcast_transmit"')
pkt = so.recv(1024) eth_frame = Ether(so.recv(1024))
for i in range(128, 1024): for i in range(0, 1010):
if pkt[i] != i & 0xff: if eth_frame.load[i] != i & 0xff:
raise Exception('Packet content mismatch') raise Exception('Packet content mismatch')
dut.expect_unity_test_output() dut.expect_unity_test_output()
@ -79,11 +108,33 @@ def actual_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})'
) )
send_eth_packet(bytes.fromhex('ffffffffffff')) # broadcast frame # pylint: disable=no-value-for-parameter send_eth_packet('ff:ff:ff:ff:ff:ff') # broadcast frame
send_eth_packet(bytes.fromhex('010000000000')) # multicast frame # pylint: disable=no-value-for-parameter send_eth_packet('01:00:00:00:00:00') # multicast frame
send_eth_packet(bytes.fromhex(res.group(2).decode('utf-8').replace(':', ''))) # unicast fram # pylint: disable=no-value-for-parameter, line-too-long # noqa send_eth_packet(res.group(2)) # unicast frame
dut.expect_unity_test_output(extra_before=res.group(1)) dut.expect_unity_test_output(extra_before=res.group(1))
dut.expect_exact("Enter next test, or 'enter' to see menu")
dut.write('"start_stop_stress_test"')
res = dut.expect(
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})'
)
# Start/stop under heavy Tx traffic
for tx_i in range(10):
recv_resp_poke(tx_i)
# Start/stop under heavy Rx traffic
traffic_en = 1
thread = Thread(target=traffic_gen, args=(res.group(2), lambda:traffic_en, ))
thread.start()
try:
for rx_i in range(10):
recv_resp_poke(rx_i)
finally:
traffic_en = 0
thread.join()
dut.expect_unity_test_output()
@pytest.mark.esp32 @pytest.mark.esp32
@pytest.mark.ip101 @pytest.mark.ip101