diff --git a/.gitignore b/.gitignore index ab727bd..52402cb 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,7 @@ flake8_output.txt # ESP-IDF default build directory name build +build*/ # lock files for examples and components dependencies.lock diff --git a/README.md b/README.md index cba948a..196f332 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ List of currently supported chips: - [KSZ8863](ksz8863/README.md) - [ADIN1200](adin1200/README.md) +- [Dummy PHY (EMAC to EMAC)](eth_dummy_phy/README.md) ## Resources diff --git a/eth_dummy_phy/CMakeLists.txt b/eth_dummy_phy/CMakeLists.txt new file mode 100644 index 0000000..4bbdabc --- /dev/null +++ b/eth_dummy_phy/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "src/esp_eth_phy_dummy.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES log esp_eth driver) \ No newline at end of file diff --git a/eth_dummy_phy/LICENSE b/eth_dummy_phy/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/eth_dummy_phy/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/eth_dummy_phy/README.md b/eth_dummy_phy/README.md new file mode 100644 index 0000000..1eab268 --- /dev/null +++ b/eth_dummy_phy/README.md @@ -0,0 +1,37 @@ +# Dummy PHY (EMAC to EMAC) + +The `Dummy PHY` component enables you a direct **EMAC to EMAC** communication between two Ethernet capable devices without usage of external PHY transceiver. The design idea is to provide EMAC to EMAC connection between two devices without any modification to ESP-IDF Ethernet driver. + +Note that usage of the `Dummy PHY` component does not conform with the RevMII standard! As such, it does not provide any management interface and so the speed/duplex mode needs to be statically configured to the same mode in both devices. Default configuration is 100Mbps/full duplex mode. If you need to change it, use `esp_eth_ioctl()` function with `ETH_CMD_S_SPEED` or `ETH_CMD_S_DUPLEX_MODE` command. + +## API + +### Steps to use the component in an example code + +1. Add this component to your project using `idf.py add-dependency` command. +2. Include `esp_eth_phy_dummy.h` +3. Create a dummy `phy` driver instance by calling `esp_eth_phy_new_dummy`and initialize Ethernet as you are used to. The only difference is to set SMI GPIOs to `-1` since the management interface is not utilized. + ```c + // Init common MAC and PHY configs to default + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + + // Update PHY config based on board specific configuration + phy_config.reset_gpio_num = -1; // no HW reset + + // Init vendor specific MAC config to default + eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); + // Update vendor specific MAC config based on board configuration + // No SMI, speed/duplex must be statically configured the same in both devices + esp32_emac_config.smi_mdc_gpio_num = -1; + esp32_emac_config.smi_mdio_gpio_num = -1; + + // Create new ESP32 Ethernet MAC instance + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); + // Create dummy PHY instance + esp_eth_phy_t *phy = esp_eth_phy_new_dummy(&phy_config); + ``` + +## Examples + +Please refer to [Simple Example](./examples/simple/README.md) for more information. diff --git a/eth_dummy_phy/examples/simple/CMakeLists.txt b/eth_dummy_phy/examples/simple/CMakeLists.txt new file mode 100644 index 0000000..995ec79 --- /dev/null +++ b/eth_dummy_phy/examples/simple/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(simple_emac2emac) diff --git a/eth_dummy_phy/examples/simple/README.md b/eth_dummy_phy/examples/simple/README.md new file mode 100644 index 0000000..16f35b8 --- /dev/null +++ b/eth_dummy_phy/examples/simple/README.md @@ -0,0 +1,143 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# Simple EMAC to EMAC Example + +## Overview + +This example demonstrates basic usage of the `Dummy PHY` component which enables you a direct **EMAC to EMAC** communication between two Ethernet capable devices without usage of external PHY transceiver. The devices EMACs are connected directly via RMII. + +Output of the example are two applications with the following workflows: + +1. The `RMII CLK Source Device` configured as DHCP Server: + 1. Wait for the `RMII CKL Sink Device` is ready, see [here](#interrupt-pin) for more information + 2. Install Ethernet driver and so enable RMII CLK output + 3. Attach the driver to `esp_netif` + 4. The DHCP Server assigns DHCP lease upon DHCP request from the `RMII CLK Sink Device` + 5. Reset itself when the `RMII CLK Sink Device` is reset + +2. The `RMII CLK Sink Device` configured as DHCP Client: + 1. Notify the `RMII CKL Source Device` that we are ready with basic chip initialization (we've entered the `app_main()`) + 2. Install Ethernet driver (perform multiple attempts since the CLK may not be stable yet) + 3. Attach the driver to `esp_netif` + 4. Send DHCP requests and wait for a DHCP lease + 5. Get IP address and ping the `RMII CLK Source Device` + +## How to use example + +### Hardware Required + +To run this example, you need one devkit which consists of [ESP32-WROOM module](https://www.espressif.com/en/products/modules/esp32) and one devkit which consists of [ESP32-WROVER module](https://www.espressif.com/en/products/modules/esp32) (or two ESP32-WROOM module devkits). The ESP32-WROOM module is used as source of the REF RMII CLK. Note that that **ESP32-WROVER can't be used as REF RMII CLK source** since the GPIO16 and GPIO17 (REF RMII CLK outputs) are utilized by SPIRAM. + +#### Pin Assignment + +Connect devkits' pins as shown in the table below. For demonstration purposes, you can interconnect them just by wires. However, the wires **must be** as short as possible (3-4 cm max!) and with matching length. It is also better to solder the wires to avoid loose contacts. Otherwise, you may get significantly over the [RMII specs.](https://resources.pcb.cadence.com/blog/2019-mii-and-rmii-routing-guidelines-for-ethernet) and face high packet loss or the setup may not work at all. + +| RMII CLK Sink Device
(ESP32 WROVER) | | RMII CLK Source Device
(ESP32 WROOM) | | +| ----- | ----- | ----- | ----- | +| **Func** | **GPIO** | **Func** | **GPIO** | +| RX[0] | GPIO25 | TX[0] | GPIO19 | +| RX[1] | GPIO26 | TX[1] | GPIO22 | +| TX[0] | GPIO19 | RX[0] | GPIO25 | +| TX[1] | GPIO22 | RX[1] | GPIO26 | +| TX_EN | GPIO21 | CRS_DV| GPIO27 | +| CRS_DV| GPIO27 | TX_EN | GPIO21 | +| CLK_IN | GPIO0 | CLK_O | GPIO17 | +| READY_IRQ | GPIO4 | IRQ_IN | GPIO4 | +| GND | GND | GND | GND | + +##### Interrupt Pin + +The Ready interrupt pin (`READY_IRQ`) is used to synchronize initialization of `RMII CLK Source Device` (ESP32-WROOM) with initialization of `RMII CLK Sink Device` (ESP32-WROVER). The `RMII CLK Source Device` needs to wait with its Ethernet initialization for the `RMII CLK Sink Device` since **the RMII CLK input pin (GPIO0) is also used as a boot strap pin for ESP32 chip**. If the `RMII CLK Source Device` didn't wait, the `RMII CLK Sink Device` could boot into incorrect mode. See the code for more information. + +Note that there are more options of how to achieve the intended behavior by hardware means: +* You can keep the `RMII CKL Source Device` in reset state by pulling the EN pin low and enable it only after the `RMII CLK Sink Device` is ready (similarly how it's realized by [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html) with PHY as a CLK source). However, make sure that there is still a way to handle EN pin for reset and flash operation. +* Use external 50 MHz oscillator and enable it only after both ESP32 devices are ready. +* May not be needed at all if `RMII CLK Sink Device` is not ESP32. + + +### Configure the project + +As mentioned above, the output of the example are two applications - the `RMII CLK Source Device` and the `RMII CLK Sink Device`. You can configure each application manually using + +``` +idf.py menuconfig +``` + +or build each application *automatically* based on provided `sdkconfig.defaults` files as follows: + +``` +idf.py -B "build_defaults_clk_source" -DSDKCONFIG="build_defaults_clk_source/sdkconfig" -DSDKCONFIG_DEFAULTS="sdkconfig.defaults.clk_source" build +``` + +and + +``` +idf.py -B "build_defaults_clk_sink" -DSDKCONFIG="build_defaults_clk_sink/sdkconfig" -DSDKCONFIG_DEFAULTS="sdkconfig.defaults.clk_sink" build +``` + + +### Build, Flash, and Run + +If you configured our applications manually using `menuconfig`, build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +If you built the applications based on on provided `sdkconfig.defaults` files, go to each `build` folder and flash each board by running: + +``` +python -m esptool --chip esp32 --port PORT -b 460800 --before default_reset --after hard_reset write_flash "@flash_args" +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +**The RMII CLK Source Device Output** + +```bash +I (360) main_task: Started on CPU0 +I (370) main_task: Calling app_main() +W (370) emac2emac: waiting for RMII CLK sink device interrupt +W (370) emac2emac: if RMII CLK sink device is already running, reset it by `EN` button +I (9300) emac2emac: starting Ethernet initialization +I (9310) esp_eth.netif.netif_glue: 94:e6:86:65:9b:27 +I (9310) esp_eth.netif.netif_glue: ethernet attached to netif +I (9310) emac2emac: Ethernet Started +I (9320) emac2emac: Ethernet Link Up +I (9320) emac2emac: Ethernet HW Addr 94:e6:86:65:9b:27 +I (9820) esp_netif_lwip: DHCP server assigned IP to a client, IP is: 192.168.4.2 +``` + +**The RMII CLK Sink Device Output** + +```bash +I (873) main_task: Returned from app_main() +I (873) emac2emac: Ethernet Started +I (883) emac2emac: Ethernet Link Up +I (893) emac2emac: Ethernet HW Addr e0:e2:e6:6a:7c:ff +I (2383) esp_netif_handlers: eth ip: 192.168.4.2, mask: 255.255.255.0, gw: 192.168.4.1 +I (2383) emac2emac: Ethernet Got IP Address +I (2383) emac2emac: ~~~~~~~~~~~ +I (2383) emac2emac: ETHIP:192.168.4.2 +I (2393) emac2emac: ETHMASK:255.255.255.0 +I (2393) emac2emac: ETHGW:192.168.4.1 +I (2403) emac2emac: ~~~~~~~~~~~ +64 bytes from 192.168.4.1 icmp_seq=1 ttl=255 time=1 ms +64 bytes from 192.168.4.1 icmp_seq=2 ttl=255 time=0 ms +64 bytes from 192.168.4.1 icmp_seq=3 ttl=255 time=0 ms +64 bytes from 192.168.4.1 icmp_seq=4 ttl=255 time=0 ms +64 bytes from 192.168.4.1 icmp_seq=5 ttl=255 time=0 ms +--- 192.168.4.1 ping statistics --- +5 packets transmitted, 5 received, 0% packet loss, time 1ms +``` + +## General Notes + +* There is no speed/duplex auto-negotiation between devices, therefore both devices EMACs must be statically configured to the same speed/duplex. The example default configuration is 100Mbps/full duplex mode. If you need to change it, use `esp_eth_ioctl()` function with `ETH_CMD_S_SPEED` or `ETH_CMD_S_DUPLEX_MODE` command. diff --git a/eth_dummy_phy/examples/simple/main/CMakeLists.txt b/eth_dummy_phy/examples/simple/main/CMakeLists.txt new file mode 100644 index 0000000..a7e9618 --- /dev/null +++ b/eth_dummy_phy/examples/simple/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "emac2emac.c" + PRIV_REQUIRES esp_netif driver esp_eth) diff --git a/eth_dummy_phy/examples/simple/main/Kconfig.projbuild b/eth_dummy_phy/examples/simple/main/Kconfig.projbuild new file mode 100644 index 0000000..1a78ea8 --- /dev/null +++ b/eth_dummy_phy/examples/simple/main/Kconfig.projbuild @@ -0,0 +1,33 @@ +menu "Example Configuration" + + choice EXAMPLE_RMII_CLK_DEVICE_TYPE + prompt "RMII CLK" + default EXAMPLE_RMII_CLK_SOURCE_DEV + help + Configure device type. ESP32 device which is RMII CLK source needs to wait + with its Ethernet initialization for the "RMII CLK Sink Device" since + the RMII CLK input pin (GPIO0) is also used as a boot strap pin. If + the "RMII CLK Source Device" didn't wait, the "RMII CLK Sink Device" + could boot into incorrect mode. + + config EXAMPLE_RMII_CLK_SOURCE_DEV + bool "RMII CLK Source Device" + config EXAMPLE_RMII_CLK_SINK_DEV + bool "RMII CLK Sink Device" + endchoice + + config EXAMPLE_CLK_SINK_READY_GPIO + int "RMII CLK Sink Device is ready GPIO" + default 4 + help + GPIO number at which the "RMII CLK Sink Device" is ready and so the "RMII + CLK Source Device" can continue in its Ethernet initialization. + + config EXAMPLE_DHCP_SERVER_EN + bool "Enable DHCP Server" + default n + help + Enable the DHCP Server on the device. The DHCP server is enabled only at + the first interface if multiple interfaces are available. + +endmenu diff --git a/eth_dummy_phy/examples/simple/main/emac2emac.c b/eth_dummy_phy/examples/simple/main/emac2emac.c new file mode 100644 index 0000000..9d35fd0 --- /dev/null +++ b/eth_dummy_phy/examples/simple/main/emac2emac.c @@ -0,0 +1,302 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_netif.h" +#include "esp_eth.h" +#include "esp_event.h" +#include "esp_log.h" +#include "sdkconfig.h" +#include "driver/gpio.h" +#include "esp_system.h" +#include "esp_eth_driver.h" +#include "esp_eth_phy_dummy.h" + +#include "lwip/inet.h" +#include "ping/ping_sock.h" + +#define STARTUP_DELAY_MS 500 + +static const char *TAG = "emac2emac"; + +#if !CONFIG_EXAMPLE_DHCP_SERVER_EN +static void cmd_ping_on_ping_success(esp_ping_handle_t hdl, void *args) +{ + uint8_t ttl; + uint16_t seqno; + uint32_t elapsed_time, recv_len; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TTL, &ttl, sizeof(ttl)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_SIZE, &recv_len, sizeof(recv_len)); + esp_ping_get_profile(hdl, ESP_PING_PROF_TIMEGAP, &elapsed_time, sizeof(elapsed_time)); + printf("%" PRIu32 " bytes from %s icmp_seq=%" PRIu16 " ttl=%" PRIu16 " time=%" PRIu32 " ms\n", + recv_len, ipaddr_ntoa((ip_addr_t *)&target_addr), seqno, ttl, elapsed_time); +} + +static void cmd_ping_on_ping_timeout(esp_ping_handle_t hdl, void *args) +{ + uint16_t seqno; + ip_addr_t target_addr; + esp_ping_get_profile(hdl, ESP_PING_PROF_SEQNO, &seqno, sizeof(seqno)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + printf("From %s icmp_seq=%d timeout\n", ipaddr_ntoa((ip_addr_t *)&target_addr), seqno); +} + +static void cmd_ping_on_ping_end(esp_ping_handle_t hdl, void *args) +{ + ip_addr_t target_addr; + uint32_t transmitted; + uint32_t received; + uint32_t total_time_ms; + uint32_t loss; + + esp_ping_get_profile(hdl, ESP_PING_PROF_REQUEST, &transmitted, sizeof(transmitted)); + esp_ping_get_profile(hdl, ESP_PING_PROF_REPLY, &received, sizeof(received)); + esp_ping_get_profile(hdl, ESP_PING_PROF_IPADDR, &target_addr, sizeof(target_addr)); + esp_ping_get_profile(hdl, ESP_PING_PROF_DURATION, &total_time_ms, sizeof(total_time_ms)); + + if (transmitted > 0) { + loss = (uint32_t)((1 - ((float)received) / transmitted) * 100); + } else { + loss = 0; + } + if (IP_IS_V4(&target_addr)) { + printf("\n--- %s ping statistics ---\n", inet_ntoa(*ip_2_ip4(&target_addr))); + } else { + printf("\n--- %s ping statistics ---\n", inet6_ntoa(*ip_2_ip6(&target_addr))); + } + printf("%" PRIu32 " packets transmitted, %" PRIu32 " received, %" PRIu32 "%% packet loss, time %" PRIu32 "ms\n", + transmitted, received, loss, total_time_ms); + + esp_ping_delete_session(hdl); +} + +static void ping_start(const esp_ip4_addr_t *ip) +{ + esp_ping_config_t config = ESP_PING_DEFAULT_CONFIG(); + + ip4_addr_set_u32(ip_2_ip4(&config.target_addr), ip->addr); + config.target_addr.type = IPADDR_TYPE_V4; // currently only IPv4 for this example + + /* set callback functions */ + esp_ping_callbacks_t cbs = { + .cb_args = NULL, + .on_ping_success = cmd_ping_on_ping_success, + .on_ping_timeout = cmd_ping_on_ping_timeout, + .on_ping_end = cmd_ping_on_ping_end + }; + esp_ping_handle_t ping; + esp_ping_new_session(&config, &cbs, &ping); + esp_ping_start(ping); +} +#endif // !CONFIG_EXAMPLE_DHCP_SERVER_EN + +/** Event handler for Ethernet events */ +static void eth_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + uint8_t mac_addr[6] = {0}; + /* we can get the ethernet driver handle from event data */ + esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data; + + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: + esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); + ESP_LOGI(TAG, "Ethernet Link Up"); + ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x", + mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); +#if CONFIG_EXAMPLE_DHCP_SERVER_EN + esp_netif_dhcps_start(esp_netif_get_handle_from_ifkey("ETH_DEF")); +#endif + break; + case ETHERNET_EVENT_DISCONNECTED: + ESP_LOGI(TAG, "Ethernet Link Down"); + break; + case ETHERNET_EVENT_START: + ESP_LOGI(TAG, "Ethernet Started"); + break; + case ETHERNET_EVENT_STOP: + ESP_LOGI(TAG, "Ethernet Stopped"); + break; + default: + break; + } +} + +/** Event handler for IP_EVENT_ETH_GOT_IP */ +static void got_ip_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + 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, "~~~~~~~~~~~"); + +#if !CONFIG_EXAMPLE_DHCP_SERVER_EN + ping_start(&ip_info->gw); +#endif +} + +#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV +IRAM_ATTR static void gpio_isr_handler(void *arg) +{ + BaseType_t high_task_wakeup = pdFALSE; + TaskHandle_t task_handle = (TaskHandle_t)arg; + + vTaskNotifyGiveFromISR(task_handle, &high_task_wakeup); + if (high_task_wakeup != pdFALSE) { + portYIELD_FROM_ISR(); + } +} +#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + +void app_main(void) +{ + // ESP32 device which is RMII CLK source needs to wait with its Ethernet initialization for the "RMII CLK Sink Device" + // since the RMII CLK input pin (GPIO0) is also used as a boot strap pin. If the "RMII CLK Source Device" didn't wait, + // the "RMII CLK Sink Device" could boot into incorrect mode. +#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + esp_rom_gpio_pad_select_gpio(EMAC_CLK_OUT_180_GPIO); + gpio_set_pull_mode(EMAC_CLK_OUT_180_GPIO, GPIO_FLOATING); // to not affect GPIO0 (so the Sink Device could be flashed) + gpio_install_isr_service(0); + gpio_config_t gpio_source_cfg = { + .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_CLK_SINK_READY_GPIO), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_ANYEDGE + }; + gpio_config(&gpio_source_cfg); + TaskHandle_t task_handle = xTaskGetHandle(pcTaskGetName(NULL)); + gpio_isr_handler_add(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO, gpio_isr_handler, task_handle); + ESP_LOGW(TAG, "waiting for RMII CLK sink device interrupt"); + ESP_LOGW(TAG, "if RMII CLK sink device is already running, reset it by `EN` button"); + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (gpio_get_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO) == 1) { + break; + } + } + ESP_LOGI(TAG, "starting Ethernet initialization"); +#else + gpio_config_t gpio_sink_cfg = { + .pin_bit_mask = (1ULL << CONFIG_EXAMPLE_CLK_SINK_READY_GPIO), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&gpio_sink_cfg); + gpio_set_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO, 0); + vTaskDelay(pdMS_TO_TICKS(STARTUP_DELAY_MS)); + gpio_set_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO, 1); +#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + + // --- Initialize Ethernet driver --- + // Init common MAC and PHY configs to default + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + + // Update PHY config based on board specific configuration + phy_config.reset_gpio_num = -1; // no HW reset + + // Init vendor specific MAC config to default + eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); + // Update vendor specific MAC config based on board configuration + // No SMI, speed/duplex must be statically configured the same in both devices +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + esp32_emac_config.smi_gpio.mdc_num = -1; + esp32_emac_config.smi_gpio.mdio_num = -1; +#else + esp32_emac_config.smi_mdc_gpio_num = -1; + esp32_emac_config.smi_mdio_gpio_num = -1; +#endif +#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT; + esp32_emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_OUT_180_GPIO; +#else + esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; + esp32_emac_config.clock_config.rmii.clock_gpio = EMAC_CLK_IN_GPIO; +#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + + // Create new ESP32 Ethernet MAC instance + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); + // Create dummy PHY instance + esp_eth_phy_t *phy = esp_eth_phy_new_dummy(&phy_config); + + // Init Ethernet driver to default and install it + esp_eth_handle_t eth_handle = NULL; + esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); +#if CONFIG_EXAMPLE_RMII_CLK_SINK_DEV + // REF RMII CLK sink device performs multiple EMAC init attempts since RMII CLK source device may not be ready yet + int i; + for (i = 1; i <= 5; i++) { + ESP_LOGI(TAG, "Ethernet driver install attempt: %i", i); + if (esp_eth_driver_install(&config, ð_handle) == ESP_OK) { + break; + } + vTaskDelay(pdMS_TO_TICKS(100)); + } + if (i > 5) { + ESP_LOGE(TAG, "Ethernet driver install failed"); + abort(); + return; + } +#else + ESP_ERROR_CHECK(esp_eth_driver_install(&config, ð_handle)); +#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + + // Initialize TCP/IP network interface aka the esp-netif + ESP_ERROR_CHECK(esp_netif_init()); + // Create default event loop that running in background + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + // Create instance of esp-netif for Ethernet + esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH(); +#if CONFIG_EXAMPLE_DHCP_SERVER_EN + esp_netif_config.flags = (esp_netif_flags_t)(ESP_NETIF_IPV4_ONLY_FLAGS(ESP_NETIF_DHCP_SERVER)); + esp_netif_config.ip_info = &_g_esp_netif_soft_ap_ip; // Use the same IP ranges as IDF's soft AP + esp_netif_config.get_ip_event = 0; + esp_netif_config.lost_ip_event = 0; +#endif + esp_netif_config_t cfg = { + .base = &esp_netif_config, + .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH + }; + esp_netif_t *eth_netif = esp_netif_new(&cfg); + // Attach Ethernet driver to TCP/IP stack + ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); + + // Register user defined event handers + ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL)); + + ESP_ERROR_CHECK(esp_eth_start(eth_handle)); + +#if CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV + // Wait indefinitely or reset when "RMII CLK Sink Device" resets + // We reset the "RMII CLK Source Device" to ensure there is no CLK at GPIO0 of the + // "RMII CLK Sink Device" during startup + while (1) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (gpio_get_level(CONFIG_EXAMPLE_CLK_SINK_READY_GPIO) == 0) { + break; + } + } + ESP_LOGW(TAG, "RMII CLK Sink device reset, I'm going to reset too!"); + esp_restart(); +#endif // CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV +} diff --git a/eth_dummy_phy/examples/simple/main/idf_component.yml b/eth_dummy_phy/examples/simple/main/idf_component.yml new file mode 100644 index 0000000..1b85c74 --- /dev/null +++ b/eth_dummy_phy/examples/simple/main/idf_component.yml @@ -0,0 +1,6 @@ +description: Simple EMAC to EMAC example (ESP32) +dependencies: + espressif/eth_dummy_phy: + version: '*' + # For local development use the local copy of the component + override_path: '../../../' \ No newline at end of file diff --git a/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_sink b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_sink new file mode 100644 index 0000000..4e5668d --- /dev/null +++ b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_sink @@ -0,0 +1 @@ +CONFIG_EXAMPLE_RMII_CLK_SINK_DEV=y diff --git a/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_source b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_source new file mode 100644 index 0000000..c00b610 --- /dev/null +++ b/eth_dummy_phy/examples/simple/sdkconfig.defaults.clk_source @@ -0,0 +1,2 @@ +CONFIG_EXAMPLE_RMII_CLK_SOURCE_DEV=y +CONFIG_EXAMPLE_DHCP_SERVER_EN=y diff --git a/eth_dummy_phy/idf_component.yml b/eth_dummy_phy/idf_component.yml new file mode 100644 index 0000000..56fa25e --- /dev/null +++ b/eth_dummy_phy/idf_component.yml @@ -0,0 +1,9 @@ +version: 0.5.0 +license: 'Apache-2.0' +targets: + - esp32 + - esp32p4 +description: This component creates dummy instance of Ethernet PHY and so two devices can be connected directly by EMAC to EMAC. +url: https://github.com/espressif/esp-eth-drivers/tree/master/eth_dummy_phy +dependencies: + idf: '>=5.2.2' diff --git a/eth_dummy_phy/include/esp_eth_phy_dummy.h b/eth_dummy_phy/include/esp_eth_phy_dummy.h new file mode 100644 index 0000000..cea6bf7 --- /dev/null +++ b/eth_dummy_phy/include/esp_eth_phy_dummy.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_eth_phy.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @brief Create a dummy PHY instance +* +* @param[in] config: configuration of PHY +* +* @return +* - instance: create PHY instance successfully +* - NULL: create PHY instance failed because some error occurred +*/ +esp_eth_phy_t *esp_eth_phy_new_dummy(const eth_phy_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/eth_dummy_phy/src/esp_eth_phy_dummy.c b/eth_dummy_phy/src/esp_eth_phy_dummy.c new file mode 100644 index 0000000..90e9ed2 --- /dev/null +++ b/eth_dummy_phy/src/esp_eth_phy_dummy.c @@ -0,0 +1,188 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "esp_eth_driver.h" +#include "driver/gpio.h" + +typedef struct { + esp_eth_phy_t parent; + esp_eth_mediator_t *eth; + int reset_gpio_num; + eth_link_t link; + eth_speed_t speed; + eth_duplex_t duplex; +} phy_dummy_t; + +static const char *TAG = "dummy_phy"; + +static esp_err_t get_link(esp_eth_phy_t *phy) +{ + esp_err_t ret = ESP_OK; + phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent); + esp_eth_mediator_t *eth = dummy_phy->eth; + + // TODO: check if EMAC is working (e.g. External CLK is present) + + if (dummy_phy->link == ETH_LINK_DOWN) { + dummy_phy->link = ETH_LINK_UP; + + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)dummy_phy->speed), err, TAG, "change speed failed"); + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)dummy_phy->duplex), err, TAG, "change duplex failed"); + + bool peer_pause_ability = false; // always false + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_PAUSE, (void *)peer_pause_ability), err, TAG, "change pause ability failed"); + + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)dummy_phy->link), err, TAG, "change link failed"); + } + +err: + return ret; +} + +static esp_err_t set_link(esp_eth_phy_t *phy, eth_link_t link) +{ + esp_err_t ret = ESP_OK; + phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent); + esp_eth_mediator_t *eth = dummy_phy->eth; + + if (dummy_phy->link != link) { + dummy_phy->link = link; + // link status changed, inmiedately report to upper layers + ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)dummy_phy->link), err, TAG, "change link failed"); + } +err: + return ret; +} + +static esp_err_t set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(eth, ESP_ERR_INVALID_ARG, err, TAG, "mediator can't be null"); + phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent); + dummy_phy->eth = eth; +err: + return ret; +} + +static esp_err_t reset_hw(esp_eth_phy_t *phy) +{ + phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent); + + if (dummy_phy->reset_gpio_num >= 0) { + esp_rom_gpio_pad_select_gpio(dummy_phy->reset_gpio_num); + gpio_set_direction(dummy_phy->reset_gpio_num, GPIO_MODE_OUTPUT); + gpio_set_level(dummy_phy->reset_gpio_num, 0); + esp_rom_delay_us(100); + gpio_set_level(dummy_phy->reset_gpio_num, 1); + } + return ESP_OK; +} + +static esp_err_t autonego_ctrl(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *autonego_en_stat) +{ + switch (cmd) { + case ESP_ETH_PHY_AUTONEGO_RESTART: + // Fallthrough + case ESP_ETH_PHY_AUTONEGO_EN: + // Fallthrough + case ESP_ETH_PHY_AUTONEGO_DIS: + return ESP_ERR_NOT_SUPPORTED; // no autonegotiation operations are supported + case ESP_ETH_PHY_AUTONEGO_G_STAT: + // report that auto-negotiation is not supported + *autonego_en_stat = false; + break; + default: + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} + +static esp_err_t set_speed(esp_eth_phy_t *phy, eth_speed_t speed) +{ + phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent); + dummy_phy->link = ETH_LINK_DOWN; + dummy_phy->speed = speed; + get_link(phy); // propagate the change to higher layer + return ESP_OK; +} + +static esp_err_t set_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex) +{ + phy_dummy_t *dummy_phy = __containerof(phy, phy_dummy_t, parent); + dummy_phy->link = ETH_LINK_DOWN; + dummy_phy->duplex = duplex; + get_link(phy); // propagate the change to higher layer + return ESP_OK; +} + +static esp_err_t do_nothing(esp_eth_phy_t *phy) +{ + return ESP_OK; +} + +static esp_err_t do_nothing_arg_bool(esp_eth_phy_t *phy, bool option) +{ + return ESP_OK; +} + +static esp_err_t do_nothing_arg_uint32(esp_eth_phy_t *phy, uint32_t option) +{ + return ESP_OK; +} + +static esp_err_t do_nothing_arg_uint32p(esp_eth_phy_t *phy, uint32_t *option) +{ + return ESP_OK; +} + +static esp_err_t del(esp_eth_phy_t *phy) +{ + free(phy); + return ESP_OK; +} + +esp_eth_phy_t *esp_eth_phy_new_dummy(const eth_phy_config_t *config) +{ + esp_eth_phy_t *ret = NULL; + phy_dummy_t *dummy_phy = calloc(1, sizeof(phy_dummy_t)); + ESP_GOTO_ON_FALSE(dummy_phy, NULL, err, TAG, "calloc dummy_phy failed"); + + dummy_phy->link = ETH_LINK_DOWN; + // default link configuration + dummy_phy->speed = ETH_SPEED_100M; + dummy_phy->duplex = ETH_DUPLEX_FULL; + + dummy_phy->reset_gpio_num = config->reset_gpio_num; + + dummy_phy->parent.reset = do_nothing; + dummy_phy->parent.reset_hw = reset_hw; + dummy_phy->parent.init = do_nothing; + dummy_phy->parent.deinit = do_nothing; + dummy_phy->parent.set_mediator = set_mediator; + dummy_phy->parent.autonego_ctrl = autonego_ctrl; + dummy_phy->parent.pwrctl = do_nothing_arg_bool; + dummy_phy->parent.get_addr = do_nothing_arg_uint32p; + dummy_phy->parent.set_addr = do_nothing_arg_uint32; + dummy_phy->parent.advertise_pause_ability = do_nothing_arg_uint32; + dummy_phy->parent.loopback = do_nothing_arg_bool; + dummy_phy->parent.set_speed = set_speed; + dummy_phy->parent.set_duplex = set_duplex; + dummy_phy->parent.del = del; + dummy_phy->parent.get_link = get_link; + dummy_phy->parent.set_link = set_link; + dummy_phy->parent.custom_ioctl = NULL; + + return &dummy_phy->parent; +err: + if (dummy_phy != NULL) { + free(dummy_phy); + } + return ret; +}