micropython / micropython/micropython
esp32: network.LAN() leaks MAC/PHY/netif and GPIO reservations when esp_eth_driver_install fails — Ethernet unrecoverable until reboot
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 22.1k
- Forks
- 9k
- Avg merge
- 6d 4h
- Merged PRs (30d)
- 16
Description
[!NOTE]
AI generated comments — this issue was investigated, tested and written with AI assistance (Claude, Anthropic). All findings and reproductions were run and verified on real hardware (ESP32-PICO-V3-02 + LAN8710A over RMII) by the reporter.
Port, board and/or hardware
esp32 port — ESP32-PICO-V3-02 + Microchip LAN8710A (RMII, MDC=GPIO8, MDIO=GPIO7). The leak itself is target-independent; the "GPIO reserved" consequence applies to all IDF >= 5.5-based builds.
MicroPython version
v1.27.0 (official ESP32_GENERIC-SPIRAM build, ESP-IDF v5.5.1) and current master — ports/esp32/network_lan.c, error path after esp_eth_driver_install().
Reproduction
Make the first network.LAN() fail for any transient reason (in our case a misconfigured pad; a PHY whose power rail is not up yet fails the same way), then retry:
import network
from machine import Pin, mem32
# put the SMI pads in a state that makes PHY init fail (any transient
# PHY problem reproduces the same sequence):
mdc, mdio = Pin(8, Pin.OPEN_DRAIN), Pin(7, Pin.OPEN_DRAIN)
try:
network.LAN(mdc=mdc, mdio=mdio, phy_type=network.PHY_LAN8720, phy_addr=0)
except OSError as e:
print("attempt 1:", e) # OSError: esp_eth_driver_install failed
# fix the pads completely — clear open-drain:
mem32[0x3FF44088 + 4*8] &= ~4
mem32[0x3FF44088 + 4*7] &= ~4
try:
network.LAN(mdc=Pin(8), mdio=Pin(7), phy_type=network.PHY_LAN8720, phy_addr=0)
except OSError as e:
print("attempt 2:", e) # OSError: esp_eth_driver_install failed with invalid argument
Observed output on real hardware:
attempt 1: esp_eth_driver_install failed
attempt 2: esp_eth_driver_install failed with invalid argument
Attempt 2 fails even though the underlying problem is fixed, and keeps failing until a hard reset.
Expected behaviour
A failed network.LAN() should release everything it allocated, so that a later call can succeed once the transient condition clears (applications need to be able to retry PHY bring-up).
Observed behaviour / analysis
In network_lan.c, when esp_eth_driver_install() returns an error the code raises OSError immediately with no cleanup:
- the MAC object from
esp_eth_mac_new_esp32()is never deleted — on IDF >= 5.5 it holdsesp_gpio_reserve()reservations for all EMAC pins (0, 7, 8, 19, 21, 22, 25, 26, 27 on classic ESP32); - the PHY object leaks;
- the
esp_netifcreated just before leaks; - the ETH/IP event handlers stay registered.
On the next network.LAN() call, esp_eth_mac_new_esp32() logs GPIO x is already reserved, returns NULL, and esp_eth_driver_install() is invoked with a NULL mac -> ESP_ERR_INVALID_ARG. Ethernet is therefore permanently dead until reboot after any single transient init failure.
Why this matters for other users
Any transient PHY bring-up failure (slow power rail, oscillator not ready, marginal pad state) turns into a permanent Ethernet outage that only a reboot clears — and the retry attempts fail with a different, misleading error ("invalid argument"), sending users down the wrong debugging path entirely. Field devices that could self-heal with a simple retry loop instead need watchdog reboots. Cleaning up the error path makes network.LAN() failures recoverable and the error reports truthful, sparing other users the trap we fell into.
Suggested fix
In the error path after esp_eth_driver_install() (and the earlier failure points): call mac->del(mac) and self->phy->del(self->phy), destroy the netif, and unregister the event handlers before raising.
Upstream status (ESP-IDF)
The condition that made our first install fail was reported upstream and root-caused in espressif/esp-idf#18756 — full analysis update: https://github.com/espressif/esp-idf/issues/18756#issuecomment-4934987955 (stale open-drain pad state that IDF <= 5.4 used to clean up inside the EMAC driver and >= 5.5 no longer does; Espressif has been asked to consider hardening emac_esp_gpio_matrix_init()). This MicroPython issue is independent of how ESP-IDF resolves that: whatever makes esp_eth_driver_install() fail once, the missing cleanup turns it into a permanent failure. See also the companion MicroPython issue about Pin(mode=-1): #19456.
Code of Conduct
Yes, I agree
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in ports/esp32/network_lan.c at the error path after esp_eth_driver_install(), then trace the earlier failure paths and ownership of the MAC, PHY, netif, and event handlers. Run the supplied network.LAN() retry reproduction on the stated ESP32 hardware or equivalent setup. Done means a transient install failure releases its resources so a later network.LAN() call can succeed without rebooting.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- embedded-iot, networking
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100