diff --git a/.gitignore b/.gitignore index 1b8630d..cdd6dae 100644 --- a/.gitignore +++ b/.gitignore @@ -28,10 +28,10 @@ GPATH **/test_apps/**/sdkconfig.old # Example project files -**/examples/**/sdkconfig -**/examples/**/sdkconfig.old -**/examples/**/build -**/examples/**/managed_components +**/**examples/**/sdkconfig +**/**examples/**/sdkconfig.old +**/**examples/**/build +**/**examples/**/managed_components # Doc build artifacts docs/_build/ @@ -80,4 +80,9 @@ dependencies.lock docs/html # component hash file generated by idf -**/.component_hash \ No newline at end of file +**/.component_hash + +# PHY tester build realted files [IDF-11145] +phy_tester/managed_components +phy_tester/sdkconfig +phy_tester/sdkconfig.old \ No newline at end of file diff --git a/phy_tester/README.md b/phy_tester/README.md index abbff71..3d166b1 100644 --- a/phy_tester/README.md +++ b/phy_tester/README.md @@ -24,7 +24,7 @@ or run automatic test to identify Ethernet related issue of your board: 1) Configure PHY and Ethernet component based on your actual board's needs in sdkconfig. 2) Build. 3) Find your network interface (NIC) name the DUT is connected to (e.g. use `ip` command). -4) Run `pytest --target=esp32 --eth_nic=YOUR_NIC_NAME` +4) Run `pytest -s --embedded-services esp,idf --tb=no --add-target-as-marker y -m esp32 --eth-nic YOUR_NIC_NAME` (change target and NIC per your setup) Note: you need `pytest` installed, see [pytest in ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/contribute/esp-idf-tests-with-pytest.html) for more information. diff --git a/phy_tester/conftest.py b/phy_tester/conftest.py new file mode 100644 index 0000000..3795ed8 --- /dev/null +++ b/phy_tester/conftest.py @@ -0,0 +1,13 @@ +import pytest +from _pytest.fixtures import FixtureRequest + +@pytest.fixture(scope='session') +def eth_nic(request: FixtureRequest) -> str: + return request.config.getoption('eth_nic') or '' + +def pytest_addoption(parser: pytest.Parser) -> None: + idf_group = parser.getgroup('idf') + idf_group.addoption( + '--eth-nic', + help='Network interface (NIC) name the DUT is connected to', + ) diff --git a/phy_tester/eth_error_msg.py b/phy_tester/eth_error_msg.py index 1fae7f2..268908e 100644 --- a/phy_tester/eth_error_msg.py +++ b/phy_tester/eth_error_msg.py @@ -18,15 +18,17 @@ def print_eth_init_fail_msg(cls) -> None: cls.print_yellow('=================================================================================') cls.print_yellow(f'Ethernet initialization failed!') cls.print_yellow(f'------------------------------') - cls.print_yellow(f'Possible root causes:') - cls.print_yellow(f'1) RMII REF CLK mode incorrectly configured if emac initialization timeouts. Does ESP32 outputs the RMII CLK? Or is RMII CLK ' - 'provided externally by PHY or oscillator?') - cls.print_yellow(f'2) If external RMII CLK is used, measure the clock signal at ESP32 REF RMII CLK input pin using oscilloscope with sufficient ' + cls.print_yellow(f'Check the above DUT log and investigate possible root causes:') + cls.print_yellow(f'1) If EMAC errors:') + cls.print_yellow(f' a) RMII REF CLK mode incorrectly configured if emac initialization timeouts.') + cls.print_yellow(f' Does ESP32 outputs the RMII CLK? Or is RMII CLK provided externally by PHY or oscillator?') + cls.print_yellow(f' b) If external RMII CLK is used, measure the clock signal at ESP32 REF RMII CLK input pin using oscilloscope with sufficient ' 'bandwidth. There must be 50 MHz square wave signal.') # TODO make it target dependent - cls.print_yellow(f'3) Make sure programmer/monitor device correctly handles “nDTR”/”nRST” and associated transistors which are connected to GPIO0.') - - cls.print_yellow(f'4) If PHY error reported: check if bootstrap (PHY address) is set correctly or set to "auto" in the code.') + cls.print_yellow(f' c) Make sure programmer/monitor device correctly handles “nDTR”/”nRST” and associated transistors which are connected to GPIO0.') + cls.print_yellow(f'2) If PHY errors:') + cls.print_yellow(f' a) Check if bootstrap (PHY address) is set correctly or set to "auto" in the code (menuconfig).') + cls.print_yellow(f' b) Cross-check with schematics if MDIO and MDC GPIOs are correctly configured.') cls.print_yellow('=================================================================================') @classmethod diff --git a/phy_tester/main/eth_common.c b/phy_tester/main/eth_common.c index 2c35bad..7b769c0 100644 --- a/phy_tester/main/eth_common.c +++ b/phy_tester/main/eth_common.c @@ -5,6 +5,7 @@ */ #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" +#include "esp_rom_sys.h" #include "esp_event.h" #include "esp_check.h" #include "esp_log.h" @@ -190,11 +191,18 @@ esp_err_t loopback_far_end_en(esp_eth_handle_t *eth_handle, phy_id_t phy_id, boo return ESP_FAIL; } - esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®); - reg_val_exp = reg_val; - esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®); - if (reg_val_exp != reg_val) { + // it was observed that e.g. IP101 needs to be commanded multiple time to take it effect + uint8_t attempt = 0; + do { + esp_eth_ioctl(eth_handle, ETH_CMD_WRITE_PHY_REG, ®); + reg_val_exp = reg_val; + esp_eth_ioctl(eth_handle, ETH_CMD_READ_PHY_REG, ®); + attempt++; + } while (reg_val_exp != reg_val && attempt < 10); + + if (attempt >= 10) { ESP_LOGE(TAG, "error to configure far-end loopback"); + ESP_LOGE(TAG, "expected reg. val 0x%lx, actual 0x%lx", reg_val_exp, reg_val); return ESP_FAIL; } diff --git a/phy_tester/sdkconfig.defaults b/phy_tester/sdkconfig.defaults new file mode 100644 index 0000000..b196108 --- /dev/null +++ b/phy_tester/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_ETHERNET_RX_TASK_STACK_SIZE=4096 \ No newline at end of file diff --git a/phy_tester/pytest_eth_phy.py b/phy_tester/test_eth_phy.py similarity index 90% rename from phy_tester/pytest_eth_phy.py rename to phy_tester/test_eth_phy.py index d6537d6..1701abf 100644 --- a/phy_tester/pytest_eth_phy.py +++ b/phy_tester/test_eth_phy.py @@ -27,6 +27,7 @@ green = '\033[32m' italics = '\033[3m' yellow = '\033[33m' +cyan = '\033[36m' class EthTestIntf(object): @@ -116,11 +117,16 @@ def send_recv_cmp(self, mac: str, payload_len: int=1010) -> bool: def traffic_gen(self, mac: str, count: int, payload_len: int, data_pattern_hex: str, interval: float, pipe_rcv:connection.Connection) -> None: with self.configure_eth_if() as so: - payload = bytes.fromhex(data_pattern_hex) * int((payload_len + 1) / len(bytes.fromhex(data_pattern_hex))) - eth_frame = Ether(dst=mac, src=so.getsockname()[4], type=self.eth_type) / raw(payload[0:payload_len]) + payload = bytearray.fromhex(data_pattern_hex) * int((payload_len + 1) / len(bytearray.fromhex(data_pattern_hex))) i = 0 + cnt = 0 try: while pipe_rcv.poll() is not True: + payload[0] = cnt + cnt += 1 + if cnt > 255: + cnt = 0 + eth_frame = Ether(dst=mac, src=so.getsockname()[4], type=self.eth_type) / raw(payload[0:payload_len]) so.send(raw(eth_frame)) sleep(interval) if count != -1: @@ -131,7 +137,7 @@ def traffic_gen(self, mac: str, count: int, payload_len: int, data_pattern_hex: raise e -def test_loopback_server(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: +def _test_loopback_server(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: ret = 'PASS' eth_type_hex = hex(eth_if.eth_type) dut.write(f'loop-server -f {eth_type_hex} -t 2000\n') @@ -148,7 +154,7 @@ def test_loopback_server(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: return ret -def test_farend_loopback(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: +def _test_farend_loopback(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: ret = 'PASS' dut.write('farend-loop-en -e\n') try: @@ -174,19 +180,19 @@ def test_farend_loopback(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: return ret -def test_nearend_loopback(dut: Dut, count: int) -> str: +def _test_nearend_loopback(dut: Dut, count: int) -> str: ret = 'PASS' dut.write(f'loop-test -s 640 -c {count}\n') try: dut.expect_exact('Link Up') - dut.expect_exact(f'looped frames: {count}, rx errors: 0', timeout=4) + dut.expect_exact(f'looped frames: {count}, rx errors: 0', timeout=10) except: # noqa logging.error('near-end loop back failed') ret = 'FAIL' return ret -def test_dut_rx(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: +def _test_dut_rx(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: ret = 'PASS' eth_type_hex = hex(eth_if.eth_type) dut.write(f'loop-server -v -f {eth_type_hex} -t 2000\n') @@ -221,7 +227,7 @@ def test_dut_rx(dut: Dut, eth_if: EthTestIntf, mac: str) -> str: return ret -def test_dut_tx(dut: Dut, eth_if: EthTestIntf, count: int) -> str: +def _test_dut_tx(dut: Dut, eth_if: EthTestIntf, count: int) -> str: ret = 'PASS' with eth_if.configure_eth_if() as so: so.settimeout(5) @@ -309,7 +315,7 @@ def ethernet_phy_test(dut: Dut, test_pc_nic: str) -> None: dut.expect_exact('Steps to Test Ethernet PHY') - res_dict = {'loopback server': test_loopback_server(dut, target_if, 'ff:ff:ff:ff:ff:ff')} + res_dict = {'loopback server': _test_loopback_server(dut, target_if, 'ff:ff:ff:ff:ff:ff')} # Loopback server results if res_dict['loopback server'] == 'PASS': tx_path = green @@ -321,15 +327,15 @@ def ethernet_phy_test(dut: Dut, test_pc_nic: str) -> None: print(f'loopback server: {tx_path}{res_dict["loopback server"]}{norm}') draw_result(tx_path, rx_path, tx_path, rx_path, False, True) - if res_dict['loopback server'] == 'FAIL': + if True is True: #res_dict['loopback server'] == 'FAIL': logging.error('loopback server test failed!') logging.info('Running additional tests to try to isolate the problem...') - res_dict.update({'DUT tx': test_dut_tx(dut, target_if, 1)}) - res_dict.update({'DUT rx': test_dut_rx(dut, target_if, 'ff:ff:ff:ff:ff:ff')}) + res_dict.update({'DUT tx': _test_dut_tx(dut, target_if, 1)}) + res_dict.update({'DUT rx': _test_dut_rx(dut, target_if, 'ff:ff:ff:ff:ff:ff')}) - res_dict.update({'far-end loopback': test_farend_loopback(dut, target_if, 'ff:ff:ff:ff:ff:ff')}) - res_dict.update({'near-end loopback': test_nearend_loopback(dut, 2)}) + res_dict.update({'far-end loopback': _test_farend_loopback(dut, target_if, 'ff:ff:ff:ff:ff:ff')}) + res_dict.update({'near-end loopback': _test_nearend_loopback(dut, 5)}) # DUT Tx/Rx results if res_dict['DUT tx'] == 'PASS': @@ -408,16 +414,19 @@ def ethernet_phy_test(dut: Dut, test_pc_nic: str) -> None: if rj45_rx_fail or rj45_tx_fail: EthFailMsg.print_rj45_fail_msg(rj45_rx_fail, rj45_tx_fail) - print('\nThe test finished! `Final problem isolation` should show you the most probable location of issue. However, go over the full log to see ' - 'additional details and to fully understand each tested scenario.') + print(f'\n{cyan}The test finished! `Final problem isolation` should show you the most probable location of issue. However, go over the full log to see ' + f'additional details and to fully understand each tested scenario.{norm}') print('\nScript run:') -@pytest.mark.esp32 -@pytest.mark.esp32p4 +@pytest.mark.parametrize('target', [ + 'esp32', + 'esp32p4', +], indirect=True) def test_esp_ethernet(dut: Dut, eth_nic: str) -> None: + print(eth_nic) ethernet_phy_test(dut, eth_nic) @@ -430,7 +439,7 @@ def test_esp_ethernet(dut: Dut, parser = argparse.ArgumentParser(description='Ethernet PHY tester helper script') parser.add_argument('--eth_nic', default='', help='Name of the test PC Ethernet NIC connected to DUT. If the option ' - 'is omitted, script uses the first identified Ethernet interface.') + 'is omitted, script automatically uses the first identified Ethernet interface.') subparsers = parser.add_subparsers(help='Commands', dest='command') @@ -466,3 +475,4 @@ def test_esp_ethernet(dut: Dut, tx_proc.join() if tx_proc.exitcode is None: tx_proc.terminate() +