[ethernet] Move received frames to PSRAM on SPI ethernet chips (#19377)

This commit is contained in:
J. Nick Koston
2026-09-29 12:57:35 +13:00
committed by Jesse Hills
parent 43b6b41d16
commit eca4c2d0a5
2 changed files with 61 additions and 0 deletions
@@ -10,6 +10,9 @@
#include <lwip/dns.h>
#include <cinttypes>
#include "esp_event.h"
#ifdef USE_PSRAM
#include <esp_psram.h>
#endif
// IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry;
// they are no longer included via esp_eth.h and need explicit includes.
@@ -71,6 +74,32 @@ static const char *const TAG = "ethernet";
// PHY register size for hex logging
static constexpr size_t PHY_REG_SIZE = 2;
// Dual wifi + ethernet SPI builds: the one place internal RAM is short and lwip's other buffers are
// already in PSRAM. Not with L2 TAP, whose filter lives in the glue's input path this replaces.
#if defined(USE_PSRAM) && defined(USE_ETHERNET_SPI) && defined(USE_WIFI) && !defined(CONFIG_ESP_NETIF_L2_TAP)
#define USE_ETHERNET_RX_PSRAM
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - reported by dump_config()
static bool rx_psram_installed = false;
// ESP-IDF ethernet drivers malloc() every received frame in internal RAM, where it stays until lwIP
// hands it to the application. Move it to PSRAM; if that fails the frame is passed on where it is.
static esp_err_t eth_input_to_psram(esp_eth_handle_t handle, uint8_t *buffer, uint32_t length, void *priv) {
auto *copy = static_cast<uint8_t *>(heap_caps_malloc(length, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
if (copy != nullptr) {
memcpy(copy, buffer, length);
free(buffer); // NOLINT(cppcoreguidelines-no-malloc) - allocated by the driver with malloc()
buffer = copy;
} else {
static bool warned = false; // once, this runs per frame in the driver's task
if (!warned) {
warned = true;
ESP_LOGW(TAG, "PSRAM allocation failed, frame kept in internal RAM (reported once)");
}
}
return esp_netif_receive(static_cast<esp_netif_t *>(priv), buffer, length, nullptr);
}
#endif
void EthernetComponent::log_error_and_mark_failed_(esp_err_t err, const char *message) {
ESP_LOGE(TAG, "%s: (%d) %s", message, err, esp_err_to_name(err));
this->mark_failed();
@@ -453,6 +482,16 @@ void EthernetComponent::ethernet_lazy_init_() {
/* attach Ethernet driver to TCP/IP stack */
err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_));
ESPHL_ERROR_CHECK(err, "ETH netif attach error");
#ifdef USE_ETHERNET_RX_PSRAM
// The glue frees every receive buffer with free(), so the replacement buffer must come from the heap
if (esp_psram_is_initialized()) {
err = esp_eth_update_input_path(this->eth_handle_, eth_input_to_psram, this->eth_netif_);
rx_psram_installed = err == ESP_OK;
if (!rx_psram_installed) {
ESP_LOGW(TAG, "PSRAM RX path not installed: %s", esp_err_to_name(err));
}
}
#endif
// Register user defined event handers
err = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &EthernetComponent::eth_event_handler, nullptr);
@@ -630,6 +669,13 @@ void EthernetComponent::dump_config() {
this->clk_pin_, this->mdc_pin_, this->mdio_pin_, this->phy_addr_);
#endif
ESP_LOGCONFIG(TAG, " Type: %s", eth_type);
#ifdef USE_ETHERNET_RX_PSRAM
// Only known once the driver is up; with enable_on_boot: false that is after this dump
if (this->ethernet_initialized_) {
ESP_LOGCONFIG(TAG, " RX frames: %s",
rx_psram_installed ? LOG_STR_LITERAL("PSRAM") : LOG_STR_LITERAL("internal RAM"));
}
#endif
}
network::IPAddresses EthernetComponent::get_ip_addresses() {
@@ -0,0 +1,15 @@
# W5500 next to wifi with PSRAM: compiles the input path that moves received frames to PSRAM
packages:
ethernet: !include common-w5500.yaml
psram:
mode: quad
wifi:
ssid: MySSID
password: password1
network:
priority:
- ethernet
- wifi