Skip to main content

esp_idf_svc/
eth.rs

1use core::fmt::Debug;
2use core::marker::PhantomData;
3use core::time::Duration;
4use core::{ffi, ops, ptr};
5
6extern crate alloc;
7use alloc::boxed::Box;
8use alloc::sync::Arc;
9
10use embedded_svc::eth::*;
11
12#[cfg(any(
13    all(esp32, esp_idf_eth_use_esp32_emac),
14    any(
15        esp_idf_eth_spi_ethernet_dm9051,
16        esp_idf_eth_spi_ethernet_w5500,
17        esp_idf_eth_spi_ethernet_ksz8851snl,
18        // ESP-IDF 6.0+ managed components
19        esp_idf_comp_espressif__dm9051_enabled,
20        esp_idf_comp_espressif__w5500_enabled,
21        esp_idf_comp_espressif__ksz8851snl_enabled,
22    )
23))]
24use crate::hal::gpio;
25#[cfg(any(
26    esp_idf_eth_spi_ethernet_dm9051,
27    esp_idf_comp_espressif__dm9051_enabled,
28    esp_idf_eth_spi_ethernet_w5500,
29    esp_idf_comp_espressif__w5500_enabled,
30    esp_idf_eth_spi_ethernet_ksz8851snl,
31    esp_idf_comp_espressif__ksz8851snl_enabled
32))]
33use crate::hal::{spi, units::Hertz};
34
35use crate::sys::*;
36
37use crate::eventloop::{
38    EspEventDeserializer, EspEventLoop, EspEventSource, EspSubscription, EspSystemEventLoop, System,
39};
40use crate::handle::RawHandle;
41#[cfg(esp_idf_comp_esp_netif_enabled)]
42use crate::netif::*;
43use crate::private::*;
44
45#[cfg(all(esp32, esp_idf_eth_use_esp32_emac))]
46#[derive(Copy, Clone, Debug, Eq, PartialEq)]
47pub enum RmiiEthChipset {
48    /// Use the generic IEEE 802.3-compliant PHY driver.
49    /// Available since ESP-IDF v5.4. On v6.0+, this is the only built-in option
50    /// unless specific PHY components from esp-eth-drivers are included.
51    #[cfg(esp_idf_version_at_least_5_4_0)]
52    Generic,
53    #[cfg(any(
54        not(esp_idf_version_at_least_6_0_0),
55        esp_idf_comp_espressif__ip101_enabled
56    ))]
57    IP101,
58    #[cfg(any(
59        not(esp_idf_version_at_least_6_0_0),
60        esp_idf_comp_espressif__rtl8201_enabled
61    ))]
62    RTL8201,
63    #[cfg(any(
64        not(esp_idf_version_at_least_6_0_0),
65        esp_idf_comp_espressif__lan87xx_enabled
66    ))]
67    LAN87XX,
68    #[cfg(any(
69        not(esp_idf_version_at_least_6_0_0),
70        esp_idf_comp_espressif__dp83848_enabled
71    ))]
72    DP83848,
73    #[cfg(esp_idf_version_major = "4")]
74    KSZ8041,
75    #[cfg(esp_idf_version = "4.4")]
76    KSZ8081,
77    #[cfg(all(
78        not(esp_idf_version_major = "4"),
79        any(
80            not(esp_idf_version_at_least_6_0_0),
81            esp_idf_comp_espressif__ksz80xx_enabled
82        )
83    ))]
84    KSZ80XX,
85}
86
87#[cfg(all(esp32, esp_idf_eth_use_esp32_emac))]
88pub enum RmiiClockConfig<'d> {
89    Input(gpio::Gpio0<'d>),
90    OutputGpio0(gpio::Gpio0<'d>),
91    /// This according to ESP-IDF is for "testing" only    
92    OutputGpio16(gpio::Gpio16<'d>),
93    OutputInvertedGpio17(gpio::Gpio17<'d>),
94}
95
96#[cfg(all(esp32, esp_idf_eth_use_esp32_emac))]
97impl RmiiClockConfig<'_> {
98    fn eth_mac_clock_config(&self) -> eth_mac_clock_config_t {
99        #[cfg(not(esp_idf_version_at_least_6_0_0))]
100        let rmii = match self {
101            Self::Input(_) => eth_mac_clock_config_t__bindgen_ty_2 {
102                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_EXT_IN,
103                clock_gpio: emac_rmii_clock_gpio_t_EMAC_CLK_IN_GPIO,
104            },
105            Self::OutputGpio0(_) => eth_mac_clock_config_t__bindgen_ty_2 {
106                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_OUT,
107                clock_gpio: emac_rmii_clock_gpio_t_EMAC_APPL_CLK_OUT_GPIO,
108            },
109            Self::OutputGpio16(_) => eth_mac_clock_config_t__bindgen_ty_2 {
110                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_OUT,
111                clock_gpio: emac_rmii_clock_gpio_t_EMAC_CLK_OUT_GPIO,
112            },
113            Self::OutputInvertedGpio17(_) => eth_mac_clock_config_t__bindgen_ty_2 {
114                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_OUT,
115                clock_gpio: emac_rmii_clock_gpio_t_EMAC_CLK_OUT_180_GPIO,
116            },
117        };
118
119        // In v6.0, clock_gpio is a plain int (GPIO number) instead of an enum
120        #[cfg(esp_idf_version_at_least_6_0_0)]
121        let rmii = match self {
122            Self::Input(_) => eth_mac_clock_config_t__bindgen_ty_2 {
123                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_EXT_IN,
124                clock_gpio: 0,
125            },
126            Self::OutputGpio0(_) => eth_mac_clock_config_t__bindgen_ty_2 {
127                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_OUT,
128                clock_gpio: 0,
129            },
130            Self::OutputGpio16(_) => eth_mac_clock_config_t__bindgen_ty_2 {
131                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_OUT,
132                clock_gpio: 16,
133            },
134            Self::OutputInvertedGpio17(_) => eth_mac_clock_config_t__bindgen_ty_2 {
135                clock_mode: emac_rmii_clock_mode_t_EMAC_CLK_OUT,
136                clock_gpio: 17,
137            },
138        };
139
140        eth_mac_clock_config_t { rmii }
141    }
142}
143
144#[cfg(any(
145    esp_idf_eth_spi_ethernet_dm9051,
146    esp_idf_comp_espressif__dm9051_enabled,
147    esp_idf_eth_spi_ethernet_w5500,
148    esp_idf_comp_espressif__w5500_enabled,
149    esp_idf_eth_spi_ethernet_ksz8851snl,
150    esp_idf_comp_espressif__ksz8851snl_enabled
151))]
152#[derive(Copy, Clone, Debug, Eq, PartialEq)]
153pub enum SpiEthChipset {
154    #[cfg(any(
155        esp_idf_eth_spi_ethernet_dm9051,
156        esp_idf_comp_espressif__dm9051_enabled
157    ))]
158    DM9051,
159    #[cfg(any(esp_idf_eth_spi_ethernet_w5500, esp_idf_comp_espressif__w5500_enabled))]
160    W5500,
161    #[cfg(any(
162        esp_idf_eth_spi_ethernet_ksz8851snl,
163        esp_idf_comp_espressif__ksz8851snl_enabled
164    ))]
165    KSZ8851SNL,
166}
167
168/// Source/mechanism to use for getting notifications/events from the emac.
169///
170/// # Availability
171///
172/// Pre version `v5.1.4` of esp-idf, only an interrupt pin could be used as source:
173///
174/// - v4.4: <https://github.com/espressif/esp-idf/blob/e499576efdb086551abe309a72899302f82077b7/components/esp_eth/include/esp_eth_mac.h#L461-L464>
175/// - v5.0: <https://github.com/espressif/esp-idf/blob/bcca689866db3dfda47f77670bf8df2a7ec94721/components/esp_eth/include/esp_eth_mac.h#L513-L517>
176/// - v5.1.3: <https://github.com/espressif/esp-idf/blob/e7771c75bd1dbbfb7b3c5381be7e063b197c9734/components/esp_eth/include/esp_eth_mac.h#L612-L617>
177/// - V5.2.0: <https://github.com/espressif/esp-idf/blob/11eaf41b37267ad7709c0899c284e3683d2f0b5e/components/esp_eth/include/esp_eth_mac.h#L612-L617>
178///
179/// Starting with `v5.1.4`, `v5.2.1` and `>= v5.3` the option of `poll_period_ms` became available:
180/// - v5.1.4: <https://github.com/espressif/esp-idf/blob/d7b0a45ddbddbac53afb4fc28168f9f9259dbb79/components/esp_eth/include/esp_eth_mac.h#L614-L620>
181/// - v5.2.1: <https://github.com/espressif/esp-idf/blob/a322e6bdad4b6675d4597fb2722eea2851ba88cb/components/esp_eth/include/esp_eth_mac.h#L614-L620>
182/// - v5.3-dev: <https://github.com/espressif/esp-idf/blob/ea010f84ef878dda07146244e166930738c1c103/components/esp_eth/include/esp_eth_mac.h#L694-L700>
183#[cfg(any(
184    esp_idf_eth_spi_ethernet_dm9051,
185    esp_idf_comp_espressif__dm9051_enabled,
186    esp_idf_eth_spi_ethernet_w5500,
187    esp_idf_comp_espressif__w5500_enabled,
188    esp_idf_eth_spi_ethernet_ksz8851snl,
189    esp_idf_comp_espressif__ksz8851snl_enabled
190))]
191#[derive(Copy, Clone, Debug, Eq, PartialEq)]
192pub struct SpiEventSource<'d> {
193    #[cfg(not(any(
194        esp_idf_version_major = "4",
195        all(
196            esp_idf_version_major = "5",
197            any(
198                esp_idf_version_minor = "0",
199                all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
200                all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
201                all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
202                all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
203                all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
204            )
205        ),
206    )))]
207    pub(crate) poll_interval_ms: u32,
208    pub(crate) interrupt_pin: i32,
209    _p: PhantomData<&'d mut ()>,
210}
211
212#[cfg(any(
213    esp_idf_eth_spi_ethernet_dm9051,
214    esp_idf_comp_espressif__dm9051_enabled,
215    esp_idf_eth_spi_ethernet_w5500,
216    esp_idf_comp_espressif__w5500_enabled,
217    esp_idf_eth_spi_ethernet_ksz8851snl,
218    esp_idf_comp_espressif__ksz8851snl_enabled
219))]
220impl<'d> SpiEventSource<'d> {
221    /// Instead of getting informed by an interrupt pin about updates/changes from the emac, the
222    /// MCU polls the emac periodically for updates.
223    ///
224    /// In most cases, [`Self::interrupt`] should be used as it is more efficient.
225    /// But this source makes e.g. sense if the interrupt pin of the emac is not connected to the MCU.
226    #[cfg(not(any(
227        esp_idf_version_major = "4",
228        all(
229            esp_idf_version_major = "5",
230            any(
231                esp_idf_version_minor = "0",
232                all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
233                all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
234                all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
235                all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
236                all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
237            )
238        ),
239    )))]
240    pub fn polling(interval: Duration) -> Result<Self, core::num::TryFromIntError> {
241        let poll_interval_ms = interval.as_millis().try_into()?;
242
243        Ok(Self {
244            #[cfg(not(any(
245                esp_idf_version_major = "4",
246                all(
247                    esp_idf_version_major = "5",
248                    any(
249                        esp_idf_version_minor = "0",
250                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
251                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
252                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
253                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
254                        all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
255                    )
256                ),
257            )))]
258            poll_interval_ms,
259            interrupt_pin: -1,
260            _p: PhantomData,
261        })
262    }
263
264    /// Get status updates/changes from the emac by way of an interrupt pin.
265    ///
266    /// If the interrupt pin is not connected, see [`Self::polling`] for an alternative.
267    pub fn interrupt(pin: impl gpio::InputPin + 'd) -> Self {
268        Self {
269            #[cfg(not(any(
270                esp_idf_version_major = "4",
271                all(
272                    esp_idf_version_major = "5",
273                    any(
274                        esp_idf_version_minor = "0",
275                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
276                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
277                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
278                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
279                        all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
280                    )
281                ),
282            )))]
283            poll_interval_ms: 0,
284            interrupt_pin: pin.pin() as _,
285            _p: PhantomData,
286        }
287    }
288}
289
290type RawCallback<'a> = Box<dyn FnMut(EthFrame) + Send + 'a>;
291
292struct UnsafeCallback<'a>(*mut RawCallback<'a>);
293
294impl<'a> UnsafeCallback<'a> {
295    #[allow(clippy::type_complexity)]
296    fn from(boxed: &mut Box<RawCallback<'a>>) -> Self {
297        Self(boxed.as_mut())
298    }
299
300    unsafe fn from_ptr(ptr: *mut ffi::c_void) -> Self {
301        Self(ptr.cast())
302    }
303
304    fn as_ptr(&self) -> *mut ffi::c_void {
305        self.0.cast()
306    }
307
308    unsafe fn call(&self, data: EthFrame) {
309        let reference = self.0.as_mut().unwrap();
310
311        (reference)(data);
312    }
313}
314
315#[derive(Copy, Clone, Debug, Eq, PartialEq)]
316enum Status {
317    Stopped,
318    Started,
319    Connected,
320    Disconnected,
321}
322
323pub struct RmiiEth;
324
325pub struct OpenEth;
326
327pub struct SpiEth<T> {
328    _driver: T,
329    device: Option<spi_device_handle_t>,
330}
331
332impl<T> Drop for SpiEth<T> {
333    fn drop(&mut self) {
334        if let Some(device) = self.device {
335            esp!(unsafe { spi_bus_remove_device(device) }).unwrap();
336
337            ::log::info!("SpiEth dropped");
338        }
339    }
340}
341
342/// This struct provides a safe wrapper over the ESP IDF Ethernet C driver.
343///
344/// The driver works on Layer 2 (Data Link) in the OSI model, in that it provides
345/// facilities for sending and receiving ethernet packets over the built-in
346/// RMII interface of `esp32` and/or via a dedicated SPI ethernet peripheral for all
347/// other MCUs.
348///
349/// For most use cases, utilizing `EspEth` - which provides a networking (IP)
350/// layer as well - should be preferred. Using `EthDriver` directly is beneficial
351/// only when one would like to utilize a custom, non-STD network stack like `smoltcp`.
352pub struct EthDriver<'d, T> {
353    _flavor: T,
354    handle: esp_eth_handle_t,
355    status: Arc<mutex::Mutex<Status>>,
356    _subscription: EspSubscription<'static, System>,
357    callback: Option<Box<RawCallback<'d>>>,
358    _p: PhantomData<&'d mut ()>,
359}
360
361#[cfg(all(esp32, esp_idf_eth_use_esp32_emac))]
362impl<'d> EthDriver<'d, RmiiEth> {
363    #[allow(clippy::too_many_arguments)]
364    pub fn new(
365        mac: crate::hal::mac::MAC<'d>,
366        rmii_rdx0: gpio::Gpio25<'d>,
367        rmii_rdx1: gpio::Gpio26<'d>,
368        rmii_crs_dv: gpio::Gpio27<'d>,
369        rmii_mdc: impl gpio::OutputPin + 'd,
370        rmii_txd1: gpio::Gpio22<'d>,
371        rmii_tx_en: gpio::Gpio21<'d>,
372        rmii_txd0: gpio::Gpio19<'d>,
373        rmii_mdio: impl gpio::InputPin + gpio::OutputPin + 'd,
374        rmii_ref_clk_config: RmiiClockConfig<'d>,
375        rst: Option<impl gpio::OutputPin + 'd>,
376        chipset: RmiiEthChipset,
377        phy_addr: Option<u32>,
378        sysloop: EspSystemEventLoop,
379    ) -> Result<Self, EspError> {
380        Self::new_rmii(
381            mac,
382            rmii_rdx0,
383            rmii_rdx1,
384            rmii_crs_dv,
385            rmii_mdc,
386            rmii_txd1,
387            rmii_tx_en,
388            rmii_txd0,
389            rmii_mdio,
390            rmii_ref_clk_config,
391            rst,
392            chipset,
393            phy_addr,
394            sysloop,
395        )
396    }
397
398    #[allow(clippy::too_many_arguments)]
399    pub fn new_rmii(
400        _mac: crate::hal::mac::MAC<'d>,
401        _rmii_rdx0: gpio::Gpio25<'d>,
402        _rmii_rdx1: gpio::Gpio26<'d>,
403        _rmii_crs_dv: gpio::Gpio27<'d>,
404        rmii_mdc: impl gpio::OutputPin + 'd,
405        _rmii_txd1: gpio::Gpio22<'d>,
406        _rmii_tx_en: gpio::Gpio21<'d>,
407        _rmii_txd0: gpio::Gpio19<'d>,
408        rmii_mdio: impl gpio::InputPin + gpio::OutputPin + 'd,
409        rmii_ref_clk_config: RmiiClockConfig<'d>,
410        rst: Option<impl gpio::OutputPin + 'd>,
411        chipset: RmiiEthChipset,
412        phy_addr: Option<u32>,
413        sysloop: EspSystemEventLoop,
414    ) -> Result<Self, EspError> {
415        let rst = rst.map(|rst| rst.pin() as _);
416
417        let eth = Self::init(
418            Self::rmii_mac(
419                rmii_mdc.pin() as _,
420                rmii_mdio.pin() as _,
421                &rmii_ref_clk_config,
422            ),
423            Self::rmii_phy(chipset, rst, phy_addr)?,
424            None,
425            RmiiEth {},
426            sysloop,
427        )?;
428
429        Ok(eth)
430    }
431
432    fn rmii_phy(
433        chipset: RmiiEthChipset,
434        reset: Option<i32>,
435        phy_addr: Option<u32>,
436    ) -> Result<*mut esp_eth_phy_t, EspError> {
437        let phy_cfg = Self::eth_phy_default_config(reset, phy_addr);
438
439        // In ESP-IDF v6.0+, specific PHY functions were moved to the external
440        // esp-eth-drivers component (https://github.com/espressif/esp-eth-drivers).
441        // If the component is included, use the specific function.
442        // A generic driver is always available since v5.4.0, and can be used as fallback.
443        let phy = match chipset {
444            #[cfg(esp_idf_version_at_least_5_4_0)]
445            RmiiEthChipset::Generic => unsafe { esp_eth_phy_new_generic(&phy_cfg) },
446            #[cfg(any(
447                not(esp_idf_version_at_least_6_0_0),
448                esp_idf_comp_espressif__ip101_enabled
449            ))]
450            RmiiEthChipset::IP101 => unsafe { esp_eth_phy_new_ip101(&phy_cfg) },
451            #[cfg(any(
452                not(esp_idf_version_at_least_6_0_0),
453                esp_idf_comp_espressif__rtl8201_enabled
454            ))]
455            RmiiEthChipset::RTL8201 => unsafe { esp_eth_phy_new_rtl8201(&phy_cfg) },
456            #[cfg(any(
457                not(esp_idf_version_at_least_6_0_0),
458                esp_idf_comp_espressif__lan87xx_enabled
459            ))]
460            RmiiEthChipset::LAN87XX => unsafe { esp_eth_phy_new_lan87xx(&phy_cfg) },
461            #[cfg(any(
462                not(esp_idf_version_at_least_6_0_0),
463                esp_idf_comp_espressif__dp83848_enabled
464            ))]
465            RmiiEthChipset::DP83848 => unsafe { esp_eth_phy_new_dp83848(&phy_cfg) },
466            #[cfg(esp_idf_version_major = "4")]
467            RmiiEthChipset::KSZ8041 => unsafe { esp_eth_phy_new_ksz8041(&phy_cfg) },
468            #[cfg(esp_idf_version = "4.4")]
469            RmiiEthChipset::KSZ8081 => unsafe { esp_eth_phy_new_ksz8081(&phy_cfg) },
470            #[cfg(all(
471                not(esp_idf_version_major = "4"),
472                any(
473                    not(esp_idf_version_at_least_6_0_0),
474                    esp_idf_comp_espressif__ksz80xx_enabled
475                )
476            ))]
477            RmiiEthChipset::KSZ80XX => unsafe { esp_eth_phy_new_ksz80xx(&phy_cfg) },
478        };
479
480        Ok(phy)
481    }
482
483    fn rmii_mac(mdc: i32, mdio: i32, clk_config: &RmiiClockConfig<'d>) -> *mut esp_eth_mac_t {
484        #[cfg(esp_idf_version_major = "4")]
485        let mac = {
486            let mut config = Self::eth_mac_default_config(mdc, mdio);
487
488            config.clock_config = clk_config.eth_mac_clock_config();
489
490            unsafe { esp_eth_mac_new_esp32(&config) }
491        };
492
493        #[cfg(not(esp_idf_version_major = "4"))]
494        let mac = {
495            let mut esp32_config = Self::eth_esp32_emac_default_config(mdc, mdio);
496            esp32_config.clock_config = clk_config.eth_mac_clock_config();
497
498            let config = Self::eth_mac_default_config(mdc, mdio);
499
500            unsafe { esp_eth_mac_new_esp32(&esp32_config, &config) }
501        };
502
503        mac
504    }
505
506    #[cfg(any(
507        esp_idf_version = "5.0",
508        esp_idf_version = "5.1",
509        esp_idf_version = "5.2"
510    ))]
511    fn eth_esp32_emac_default_config(mdc: i32, mdio: i32) -> eth_esp32_emac_config_t {
512        eth_esp32_emac_config_t {
513            smi_mdc_gpio_num: mdc,
514            smi_mdio_gpio_num: mdio,
515            interface: eth_data_interface_t_EMAC_DATA_INTERFACE_RMII,
516            ..Default::default()
517        }
518    }
519
520    #[cfg(esp_idf_version = "5.3")]
521    fn eth_esp32_emac_default_config(mdc: i32, mdio: i32) -> eth_esp32_emac_config_t {
522        eth_esp32_emac_config_t {
523            __bindgen_anon_1: eth_esp32_emac_config_t__bindgen_ty_1 {
524                __bindgen_anon_1: eth_esp32_emac_config_t__bindgen_ty_1__bindgen_ty_1 {
525                    smi_mdc_gpio_num: mdc,
526                    smi_mdio_gpio_num: mdio,
527                },
528            },
529            interface: eth_data_interface_t_EMAC_DATA_INTERFACE_RMII,
530            ..Default::default()
531        }
532    }
533
534    #[cfg(all(esp_idf_version_at_least_5_4_0, not(esp_idf_version_at_least_6_0_0)))]
535    fn eth_esp32_emac_default_config(mdc: i32, mdio: i32) -> eth_esp32_emac_config_t {
536        eth_esp32_emac_config_t {
537            __bindgen_anon_1: eth_esp32_emac_config_t__bindgen_ty_1 {
538                smi_gpio: emac_esp_smi_gpio_config_t {
539                    mdc_num: mdc,
540                    mdio_num: mdio,
541                },
542            },
543            interface: eth_data_interface_t_EMAC_DATA_INTERFACE_RMII,
544            ..Default::default()
545        }
546    }
547
548    // In v6.0, __bindgen_anon_1 wrapper was removed; smi_gpio is a direct field
549    #[cfg(esp_idf_version_at_least_6_0_0)]
550    fn eth_esp32_emac_default_config(mdc: i32, mdio: i32) -> eth_esp32_emac_config_t {
551        eth_esp32_emac_config_t {
552            smi_gpio: emac_esp_smi_gpio_config_t {
553                mdc_num: mdc,
554                mdio_num: mdio,
555            },
556            interface: eth_data_interface_t_EMAC_DATA_INTERFACE_RMII,
557            ..Default::default()
558        }
559    }
560}
561
562#[cfg(esp_idf_eth_use_openeth)]
563impl<'d> EthDriver<'d, OpenEth> {
564    pub fn new(
565        mac: crate::hal::mac::MAC<'d>,
566        sysloop: EspSystemEventLoop,
567    ) -> Result<Self, EspError> {
568        Self::new_openeth(mac, sysloop)
569    }
570
571    pub fn new_openeth(
572        _mac: crate::hal::mac::MAC<'d>,
573        sysloop: EspSystemEventLoop,
574    ) -> Result<Self, EspError> {
575        let eth = Self::init(
576            unsafe { esp_eth_mac_new_openeth(&Self::eth_mac_default_config(0, 0)) },
577            unsafe { esp_eth_phy_new_dp83848(&Self::eth_phy_default_config(None, None)) },
578            None,
579            OpenEth {},
580            sysloop,
581        )?;
582
583        Ok(eth)
584    }
585}
586
587#[cfg(any(
588    esp_idf_eth_spi_ethernet_dm9051,
589    esp_idf_comp_espressif__dm9051_enabled,
590    esp_idf_eth_spi_ethernet_w5500,
591    esp_idf_comp_espressif__w5500_enabled,
592    esp_idf_eth_spi_ethernet_ksz8851snl,
593    esp_idf_comp_espressif__ksz8851snl_enabled
594))]
595impl<'d, T> EthDriver<'d, SpiEth<T>>
596where
597    T: core::borrow::Borrow<spi::SpiDriver<'d>>,
598{
599    #[allow(clippy::too_many_arguments)]
600    pub fn new(
601        driver: T,
602        int: impl gpio::InputPin + 'd,
603        cs: Option<impl gpio::OutputPin + 'd>,
604        rst: Option<impl gpio::OutputPin + 'd>,
605        chipset: SpiEthChipset,
606        baudrate: Hertz,
607        mac_addr: Option<&[u8; 6]>,
608        phy_addr: Option<u32>,
609        sysloop: EspSystemEventLoop,
610    ) -> Result<Self, EspError> {
611        Self::new_spi(
612            driver, int, cs, rst, chipset, baudrate, mac_addr, phy_addr, sysloop,
613        )
614    }
615
616    #[allow(clippy::too_many_arguments)]
617    pub fn new_spi(
618        driver: T,
619        int: impl gpio::InputPin + 'd,
620        cs: Option<impl gpio::OutputPin + 'd>,
621        rst: Option<impl gpio::OutputPin + 'd>,
622        chipset: SpiEthChipset,
623        baudrate: Hertz,
624        mac_addr: Option<&[u8; 6]>,
625        phy_addr: Option<u32>,
626        sysloop: EspSystemEventLoop,
627    ) -> Result<Self, EspError> {
628        Self::new_spi_with_event_source(
629            driver,
630            SpiEventSource::interrupt(int),
631            cs,
632            rst,
633            chipset,
634            baudrate,
635            mac_addr,
636            phy_addr,
637            sysloop,
638        )
639    }
640
641    #[allow(clippy::too_many_arguments)]
642    pub fn new_spi_with_event_source(
643        driver: T,
644        event_source: SpiEventSource<'d>,
645        cs: Option<impl gpio::OutputPin + 'd>,
646        rst: Option<impl gpio::OutputPin + 'd>,
647        chipset: SpiEthChipset,
648        baudrate: Hertz,
649        mac_addr: Option<&[u8; 6]>,
650        phy_addr: Option<u32>,
651        sysloop: EspSystemEventLoop,
652    ) -> Result<Self, EspError> {
653        let (mac, phy, device) = Self::init_spi(
654            driver.borrow().host(),
655            chipset,
656            baudrate,
657            event_source.interrupt_pin,
658            #[cfg(not(any(
659                esp_idf_version_major = "4",
660                all(
661                    esp_idf_version_major = "5",
662                    any(
663                        esp_idf_version_minor = "0",
664                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
665                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
666                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
667                        all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
668                        all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
669                    )
670                ),
671            )))]
672            event_source.poll_interval_ms,
673            cs.map(|pin| pin.pin() as _),
674            rst.map(|pin| pin.pin() as _),
675            phy_addr,
676        )?;
677
678        let eth = Self::init(
679            mac,
680            phy,
681            mac_addr,
682            SpiEth {
683                _driver: driver,
684                device,
685            },
686            sysloop,
687        )?;
688
689        Ok(eth)
690    }
691
692    #[allow(clippy::unnecessary_literal_unwrap, clippy::too_many_arguments)]
693    fn init_spi(
694        host: spi_host_device_t,
695        chipset: SpiEthChipset,
696        baudrate: Hertz,
697        int_gpio_num: i32,
698        #[cfg(not(any(
699            esp_idf_version_major = "4",
700            all(
701                esp_idf_version_major = "5",
702                any(
703                    esp_idf_version_minor = "0",
704                    all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
705                    all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
706                    all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
707                    all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
708                    all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
709                )
710            ),
711        )))]
712        poll_period_ms: u32,
713        cs: Option<i32>,
714        rst: Option<i32>,
715        phy_addr: Option<u32>,
716    ) -> Result<
717        (
718            *mut esp_eth_mac_t,
719            *mut esp_eth_phy_t,
720            Option<spi_device_handle_t>,
721        ),
722        EspError,
723    > {
724        crate::hal::gpio::enable_isr_service()?;
725
726        let mac_cfg = Self::eth_mac_default_config(0, 0);
727        let phy_cfg = Self::eth_phy_default_config(rst, phy_addr);
728
729        let (mac, phy, spi_handle) = match chipset {
730            #[cfg(any(
731                esp_idf_eth_spi_ethernet_dm9051,
732                esp_idf_comp_espressif__dm9051_enabled
733            ))]
734            SpiEthChipset::DM9051 => {
735                let spi_devcfg = Self::get_spi_conf(cs, 1, 7, baudrate);
736
737                #[cfg(esp_idf_version_major = "4")]
738                let spi_handle = Some(Self::init_spi_device(host, &spi_devcfg)?);
739
740                #[cfg(not(esp_idf_version_major = "4"))]
741                let spi_handle = None;
742
743                #[cfg(esp_idf_version_major = "4")]
744                let dm9051_cfg = eth_dm9051_config_t {
745                    spi_hdl: spi_handle.unwrap() as *mut _,
746                    int_gpio_num,
747                };
748
749                #[cfg(not(esp_idf_version_major = "4"))]
750                #[allow(clippy::needless_update)]
751                let dm9051_cfg = eth_dm9051_config_t {
752                    spi_host_id: host,
753                    spi_devcfg: &spi_devcfg as *const _ as *mut _,
754                    int_gpio_num,
755                    #[cfg(not(any(
756                        esp_idf_version_major = "4",
757                        all(
758                            esp_idf_version_major = "5",
759                            any(
760                                esp_idf_version_minor = "0",
761                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
762                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
763                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
764                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
765                                all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
766                            )
767                        ),
768                    )))]
769                    poll_period_ms,
770                    #[cfg(not(any(
771                        esp_idf_version_major = "4",
772                        all(
773                            esp_idf_version_major = "5",
774                            any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
775                        ),
776                    )))]
777                    custom_spi_driver: eth_spi_custom_driver_config_t::default(),
778                    ..Default::default()
779                };
780
781                let mac = unsafe { esp_eth_mac_new_dm9051(&dm9051_cfg, &mac_cfg) };
782                let phy = unsafe { esp_eth_phy_new_dm9051(&phy_cfg) };
783
784                (mac, phy, spi_handle)
785            }
786            #[cfg(any(esp_idf_eth_spi_ethernet_w5500, esp_idf_comp_espressif__w5500_enabled))]
787            SpiEthChipset::W5500 => {
788                let spi_devcfg = Self::get_spi_conf(cs, 16, 8, baudrate);
789
790                #[cfg(esp_idf_version_major = "4")]
791                let spi_handle = Some(Self::init_spi_device(host, &spi_devcfg)?);
792
793                #[cfg(not(esp_idf_version_major = "4"))]
794                let spi_handle = None;
795
796                #[cfg(esp_idf_version_major = "4")]
797                let w5500_cfg = eth_w5500_config_t {
798                    spi_hdl: spi_handle.unwrap() as *mut _,
799                    int_gpio_num,
800                };
801
802                #[cfg(not(esp_idf_version_major = "4"))]
803                #[allow(clippy::needless_update)]
804                let w5500_cfg = eth_w5500_config_t {
805                    spi_host_id: host,
806                    spi_devcfg: &spi_devcfg as *const _ as *mut _,
807                    int_gpio_num,
808                    #[cfg(not(any(
809                        esp_idf_version_major = "4",
810                        all(
811                            esp_idf_version_major = "5",
812                            any(
813                                esp_idf_version_minor = "0",
814                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
815                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
816                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
817                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
818                                all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
819                            )
820                        ),
821                    )))]
822                    poll_period_ms,
823                    #[cfg(not(any(
824                        esp_idf_version_major = "4",
825                        all(
826                            esp_idf_version_major = "5",
827                            any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
828                        ),
829                    )))]
830                    custom_spi_driver: eth_spi_custom_driver_config_t::default(),
831                    ..Default::default()
832                };
833
834                let mac = unsafe { esp_eth_mac_new_w5500(&w5500_cfg, &mac_cfg) };
835                let phy = unsafe { esp_eth_phy_new_w5500(&phy_cfg) };
836
837                (mac, phy, spi_handle)
838            }
839            #[cfg(any(
840                esp_idf_eth_spi_ethernet_ksz8851snl,
841                esp_idf_comp_espressif__ksz8851snl_enabled
842            ))]
843            SpiEthChipset::KSZ8851SNL => {
844                let spi_devcfg = Self::get_spi_conf(cs, 0, 0, baudrate);
845
846                #[cfg(esp_idf_version_major = "4")]
847                let spi_handle = Some(Self::init_spi_device(host, &spi_devcfg)?);
848
849                #[cfg(not(esp_idf_version_major = "4"))]
850                let spi_handle = None;
851
852                #[cfg(esp_idf_version_major = "4")]
853                let ksz8851snl_cfg = eth_ksz8851snl_config_t {
854                    spi_hdl: spi_handle.unwrap() as *mut _,
855                    int_gpio_num,
856                };
857
858                #[cfg(not(esp_idf_version_major = "4"))]
859                #[allow(clippy::needless_update)]
860                let ksz8851snl_cfg = eth_ksz8851snl_config_t {
861                    spi_host_id: host,
862                    spi_devcfg: &spi_devcfg as *const _ as *mut _,
863                    int_gpio_num,
864                    #[cfg(not(any(
865                        esp_idf_version_major = "4",
866                        all(
867                            esp_idf_version_major = "5",
868                            any(
869                                esp_idf_version_minor = "0",
870                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "0"),
871                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "1"),
872                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "2"),
873                                all(esp_idf_version_minor = "1", esp_idf_version_patch = "3"),
874                                all(esp_idf_version_minor = "2", esp_idf_version_patch = "0"),
875                            )
876                        ),
877                    )))]
878                    poll_period_ms,
879                    #[cfg(not(any(
880                        esp_idf_version_major = "4",
881                        all(
882                            esp_idf_version_major = "5",
883                            any(esp_idf_version_minor = "0", esp_idf_version_minor = "1")
884                        ),
885                    )))]
886                    custom_spi_driver: eth_spi_custom_driver_config_t::default(),
887                    ..Default::default()
888                };
889
890                let mac = unsafe { esp_eth_mac_new_ksz8851snl(&ksz8851snl_cfg, &mac_cfg) };
891                let phy = unsafe { esp_eth_phy_new_ksz8851snl(&phy_cfg) };
892
893                (mac, phy, spi_handle)
894            }
895        };
896
897        Ok((mac, phy, spi_handle))
898    }
899
900    fn get_spi_conf(
901        cs: Option<i32>,
902        command_bits: u8,
903        address_bits: u8,
904        baudrate: Hertz,
905    ) -> spi_device_interface_config_t {
906        spi_device_interface_config_t {
907            command_bits,
908            address_bits,
909            mode: 0,
910            clock_speed_hz: baudrate.0 as i32,
911            spics_io_num: cs.unwrap_or(-1),
912            queue_size: 20,
913            ..Default::default()
914        }
915    }
916
917    #[cfg(esp_idf_version_major = "4")]
918    fn init_spi_device(
919        host: spi_host_device_t,
920        conf: &spi_device_interface_config_t,
921    ) -> Result<spi_device_handle_t, EspError> {
922        let mut spi_handle: spi_device_handle_t = ptr::null_mut();
923
924        esp!(unsafe { spi_bus_add_device(host, conf, &mut spi_handle) })?;
925
926        Ok(spi_handle)
927    }
928}
929
930impl<'d, T> EthDriver<'d, T> {
931    fn init(
932        mac: *mut esp_eth_mac_t,
933        phy: *mut esp_eth_phy_t,
934        mac_addr: Option<&[u8; 6]>,
935        flavor: T,
936        sysloop: EspSystemEventLoop,
937    ) -> Result<Self, EspError> {
938        let cfg = Self::eth_default_config(mac, phy);
939
940        let mut handle: esp_eth_handle_t = ptr::null_mut();
941        esp!(unsafe { esp_eth_driver_install(&cfg, &mut handle) })?;
942
943        ::log::info!("Driver initialized");
944
945        if let Some(mac_addr) = mac_addr {
946            esp!(unsafe {
947                esp_eth_ioctl(
948                    handle,
949                    esp_eth_io_cmd_t_ETH_CMD_S_MAC_ADDR,
950                    mac_addr.as_ptr() as *mut _,
951                )
952            })?;
953
954            ::log::info!("Attached MAC address: {mac_addr:?}");
955        }
956
957        let (waitable, subscription) = Self::subscribe(handle, &sysloop)?;
958
959        let eth = Self {
960            handle,
961            _flavor: flavor,
962            status: waitable,
963            _subscription: subscription,
964            callback: None,
965            _p: PhantomData,
966        };
967
968        ::log::info!("Initialization complete");
969
970        Ok(eth)
971    }
972
973    fn subscribe(
974        handle: esp_eth_handle_t,
975        sysloop: &EspEventLoop<System>,
976    ) -> Result<(Arc<mutex::Mutex<Status>>, EspSubscription<'static, System>), EspError> {
977        let status = Arc::new(mutex::Mutex::new(Status::Stopped));
978        let s_status = status.clone();
979
980        let handle = handle as usize;
981
982        let subscription = sysloop.subscribe::<EthEvent, _>(move |event| {
983            if event.is_for_handle(handle as _) {
984                let mut guard = s_status.lock();
985
986                match event {
987                    EthEvent::Started(_) => *guard = Status::Started,
988                    EthEvent::Stopped(_) => *guard = Status::Stopped,
989                    EthEvent::Connected(_) => *guard = Status::Connected,
990                    EthEvent::Disconnected(_) => *guard = Status::Disconnected,
991                    EthEvent::Other(_) => (),
992                }
993            }
994        })?;
995
996        Ok((status, subscription))
997    }
998
999    pub fn is_started(&self) -> Result<bool, EspError> {
1000        let guard = self.status.lock();
1001
1002        Ok(*guard == Status::Started
1003            || *guard == Status::Connected
1004            || *guard == Status::Disconnected)
1005    }
1006
1007    pub fn is_connected(&self) -> Result<bool, EspError> {
1008        let guard = self.status.lock();
1009
1010        Ok(*guard == Status::Connected)
1011    }
1012
1013    pub fn start(&mut self) -> Result<(), EspError> {
1014        esp!(unsafe { esp_eth_start(self.handle) })?;
1015
1016        ::log::info!("Start requested");
1017
1018        Ok(())
1019    }
1020
1021    pub fn stop(&mut self) -> Result<(), EspError> {
1022        ::log::info!("Stopping");
1023
1024        let err = unsafe { esp_eth_stop(self.handle) };
1025        if err != ESP_ERR_INVALID_STATE {
1026            esp!(err)?;
1027        }
1028
1029        ::log::info!("Stop requested");
1030
1031        Ok(())
1032    }
1033
1034    pub fn set_rx_callback<F>(&mut self, callback: F) -> Result<(), EspError>
1035    where
1036        F: FnMut(EthFrame) + Send + 'static,
1037    {
1038        self.internal_set_rx_callback(callback)
1039    }
1040
1041    /// # Safety
1042    ///
1043    /// This method - in contrast to method `set_rx_callback` - allows the user to pass
1044    /// a non-static callback/closure. This enables users to borrow
1045    /// - in the closure - variables that live on the stack - or more generally - in the same
1046    ///   scope where the service is created.
1047    ///
1048    /// HOWEVER: care should be taken NOT to call `core::mem::forget()` on the service,
1049    /// as that would immediately lead to an UB (crash).
1050    /// Also note that forgetting the service might happen with `Rc` and `Arc`
1051    /// when circular references are introduced: <https://github.com/rust-lang/rust/issues/24456>
1052    ///
1053    /// The reason is that the closure is actually sent to a hidden ESP IDF thread.
1054    /// This means that if the service is forgotten, Rust is free to e.g. unwind the stack
1055    /// and the closure now owned by this other thread will end up with references to variables that no longer exist.
1056    ///
1057    /// The destructor of the service takes care - prior to the service being dropped and e.g.
1058    /// the stack being unwind - to remove the closure from the hidden thread and destroy it.
1059    /// Unfortunately, when the service is forgotten, the un-subscription does not happen
1060    /// and invalid references are left dangling.
1061    ///
1062    /// This "local borrowing" will only be possible to express in a safe way once/if `!Leak` types
1063    /// are introduced to Rust (i.e. the impossibility to "forget" a type and thus not call its destructor).
1064    pub unsafe fn set_nonstatic_rx_callback<F>(&mut self, callback: F) -> Result<(), EspError>
1065    where
1066        F: FnMut(EthFrame) + Send + 'd,
1067    {
1068        self.internal_set_rx_callback(callback)
1069    }
1070
1071    fn internal_set_rx_callback<F>(&mut self, callback: F) -> Result<(), EspError>
1072    where
1073        F: FnMut(EthFrame) + Send + 'd,
1074    {
1075        let _ = self.stop();
1076
1077        let mut callback: Box<RawCallback> = Box::new(Box::new(callback));
1078
1079        let unsafe_callback = UnsafeCallback::from(&mut callback);
1080
1081        esp!(unsafe {
1082            esp_eth_update_input_path(self.handle(), Some(Self::handle), unsafe_callback.as_ptr())
1083        })?;
1084
1085        self.callback = Some(callback);
1086
1087        Ok(())
1088    }
1089
1090    pub fn send(&mut self, frame: &[u8]) -> Result<(), EspError> {
1091        esp!(unsafe {
1092            esp_eth_transmit(self.handle(), frame.as_ptr() as *mut _, frame.len() as _)
1093        })?;
1094
1095        Ok(())
1096    }
1097
1098    unsafe extern "C" fn handle(
1099        _handle: esp_eth_handle_t,
1100        buf: *mut u8,
1101        len: u32,
1102        event_handler_arg: *mut ffi::c_void,
1103    ) -> esp_err_t {
1104        UnsafeCallback::from_ptr(event_handler_arg as *mut _).call(EthFrame::new(buf, len));
1105
1106        ESP_OK
1107    }
1108
1109    fn clear_all(&mut self) -> Result<(), EspError> {
1110        let _ = self.stop(); // Driver might be stopped already
1111
1112        unsafe {
1113            esp!(esp_eth_driver_uninstall(self.handle))?;
1114        }
1115
1116        ::log::info!("Driver deinitialized");
1117
1118        Ok(())
1119    }
1120
1121    /// Enables or disables promiscuous mode for the [`EthDriver`].
1122    ///
1123    /// When promiscuous mode is enabled, the driver captures all Ethernet frames
1124    /// on the network, regardless of their destination MAC address. This is useful for
1125    /// debugging or monitoring purposes.
1126    pub fn set_promiscuous(&mut self, state: bool) -> Result<(), EspError> {
1127        esp!(unsafe {
1128            esp_eth_ioctl(
1129                self.handle(),
1130                esp_eth_io_cmd_t_ETH_CMD_S_PROMISCUOUS,
1131                &raw const state as *mut _,
1132            )
1133        })?;
1134
1135        if state {
1136            ::log::info!("Driver set in promiscuous mode");
1137        } else {
1138            ::log::info!("Driver set in non-promiscuous mode");
1139        }
1140
1141        Ok(())
1142    }
1143
1144    fn eth_default_config(mac: *mut esp_eth_mac_t, phy: *mut esp_eth_phy_t) -> esp_eth_config_t {
1145        esp_eth_config_t {
1146            mac,
1147            phy,
1148            check_link_period_ms: 2000,
1149            ..Default::default()
1150        }
1151    }
1152
1153    #[allow(clippy::needless_update)]
1154    fn eth_phy_default_config(reset_pin: Option<i32>, phy_addr: Option<u32>) -> eth_phy_config_t {
1155        eth_phy_config_t {
1156            phy_addr: phy_addr.map(|a| a as i32).unwrap_or(ESP_ETH_PHY_ADDR_AUTO),
1157            reset_timeout_ms: 100,
1158            autonego_timeout_ms: 4000,
1159            reset_gpio_num: reset_pin.unwrap_or(-1),
1160            ..Default::default()
1161        }
1162    }
1163
1164    #[cfg(esp_idf_version_major = "4")]
1165    fn eth_mac_default_config(mdc: i32, mdio: i32) -> eth_mac_config_t {
1166        eth_mac_config_t {
1167            sw_reset_timeout_ms: 100,
1168            rx_task_stack_size: 2048,
1169            rx_task_prio: 15,
1170            smi_mdc_gpio_num: mdc,
1171            smi_mdio_gpio_num: mdio,
1172            flags: 0,
1173            #[cfg(esp_idf_version = "4.4")]
1174            interface: eth_data_interface_t_EMAC_DATA_INTERFACE_RMII,
1175            ..Default::default()
1176        }
1177    }
1178
1179    #[cfg(not(esp_idf_version_major = "4"))]
1180    fn eth_mac_default_config(_mdc: i32, _mdio: i32) -> eth_mac_config_t {
1181        eth_mac_config_t {
1182            sw_reset_timeout_ms: 100,
1183            rx_task_stack_size: 4096,
1184            rx_task_prio: 15,
1185            flags: 0,
1186        }
1187    }
1188}
1189
1190impl<T> Eth for EthDriver<'_, T> {
1191    type Error = EspError;
1192
1193    fn start(&mut self) -> Result<(), Self::Error> {
1194        EthDriver::start(self)
1195    }
1196
1197    fn stop(&mut self) -> Result<(), Self::Error> {
1198        EthDriver::stop(self)
1199    }
1200
1201    fn is_started(&self) -> Result<bool, Self::Error> {
1202        EthDriver::is_started(self)
1203    }
1204
1205    fn is_connected(&self) -> Result<bool, Self::Error> {
1206        EthDriver::is_connected(self)
1207    }
1208}
1209
1210unsafe impl<T> Send for EthDriver<'_, T> {}
1211
1212impl<T> Drop for EthDriver<'_, T> {
1213    fn drop(&mut self) {
1214        self.clear_all().unwrap();
1215
1216        ::log::info!("EthDriver dropped");
1217    }
1218}
1219
1220impl<T> RawHandle for EthDriver<'_, T> {
1221    type Handle = esp_eth_handle_t;
1222
1223    fn handle(&self) -> Self::Handle {
1224        self.handle
1225    }
1226}
1227
1228pub struct EthFrame {
1229    buf: *mut u8,
1230    len: u32,
1231}
1232
1233unsafe impl Send for EthFrame {}
1234
1235impl EthFrame {
1236    const unsafe fn new(buf: *mut u8, len: u32) -> Self {
1237        Self { buf, len }
1238    }
1239
1240    pub const fn as_slice(&self) -> &[u8] {
1241        unsafe { core::slice::from_raw_parts(self.buf, self.len as _) }
1242    }
1243
1244    pub fn as_mut_slice(&mut self) -> &mut [u8] {
1245        unsafe { core::slice::from_raw_parts_mut(self.buf, self.len as _) }
1246    }
1247}
1248
1249impl ops::Deref for EthFrame {
1250    type Target = [u8];
1251
1252    fn deref(&self) -> &[u8] {
1253        unsafe { core::slice::from_raw_parts(self.buf, self.len as _) }
1254    }
1255}
1256
1257impl ops::DerefMut for EthFrame {
1258    fn deref_mut(&mut self) -> &mut [u8] {
1259        unsafe { core::slice::from_raw_parts_mut(self.buf, self.len as _) }
1260    }
1261}
1262
1263impl Drop for EthFrame {
1264    fn drop(&mut self) {
1265        unsafe { free(self.buf.cast()) };
1266    }
1267}
1268
1269/// `EspEth` wraps an `EthDriver` Data Link layer instance, and binds the OSI
1270/// Layer 3 (network) facilities of ESP IDF to it.
1271///
1272/// In other words, it connects the ESP IDF ethernet Netif interface to the
1273/// ethernet driver. This allows users to utilize the Rust STD APIs for working with
1274/// TCP and UDP sockets.
1275///
1276/// This struct should be the default option for an ethernet driver in all use cases
1277/// but the niche one where bypassing the ESP IDF Netif and lwIP stacks is
1278/// desirable. E.g., using `smoltcp` or other custom IP stacks on top of the
1279/// ESP IDF ethernet peripheral.
1280#[cfg(esp_idf_comp_esp_netif_enabled)]
1281pub struct EspEth<'d, T> {
1282    glue_handle: *mut esp_eth_netif_glue_t,
1283    netif: EspNetif,
1284    driver: EthDriver<'d, T>,
1285}
1286
1287#[cfg(esp_idf_comp_esp_netif_enabled)]
1288impl<'d, T> EspEth<'d, T> {
1289    pub fn wrap(driver: EthDriver<'d, T>) -> Result<Self, EspError> {
1290        Self::wrap_all(driver, EspNetif::new(NetifStack::Eth)?)
1291    }
1292
1293    pub fn wrap_all(driver: EthDriver<'d, T>, netif: EspNetif) -> Result<Self, EspError> {
1294        let mut this = Self {
1295            driver,
1296            netif,
1297            glue_handle: core::ptr::null_mut(),
1298        };
1299
1300        this.attach_netif()?;
1301
1302        Ok(this)
1303    }
1304
1305    pub fn swap_netif(&mut self, netif: EspNetif) -> Result<EspNetif, EspError> {
1306        self.detach_netif()?;
1307
1308        let old_netif = core::mem::replace(&mut self.netif, netif);
1309
1310        self.attach_netif()?;
1311
1312        Ok(old_netif)
1313    }
1314
1315    pub fn driver(&self) -> &EthDriver<'d, T> {
1316        &self.driver
1317    }
1318
1319    pub fn driver_mut(&mut self) -> &mut EthDriver<'d, T> {
1320        &mut self.driver
1321    }
1322
1323    pub fn netif(&self) -> &EspNetif {
1324        &self.netif
1325    }
1326
1327    pub fn netif_mut(&mut self) -> &mut EspNetif {
1328        &mut self.netif
1329    }
1330
1331    pub fn start(&mut self) -> Result<(), EspError> {
1332        self.driver_mut().start()
1333    }
1334
1335    pub fn stop(&mut self) -> Result<(), EspError> {
1336        self.driver_mut().stop()
1337    }
1338
1339    pub fn is_started(&self) -> Result<bool, EspError> {
1340        self.driver().is_started()
1341    }
1342
1343    pub fn is_connected(&self) -> Result<bool, EspError> {
1344        self.driver().is_connected()
1345    }
1346
1347    pub fn is_up(&self) -> Result<bool, EspError> {
1348        Ok(self.is_connected()? && self.netif().is_up()?)
1349    }
1350
1351    fn attach_netif(&mut self) -> Result<(), EspError> {
1352        let _ = self.driver.stop();
1353
1354        let glue_handle = unsafe { esp_eth_new_netif_glue(self.driver.handle()) };
1355
1356        esp!(unsafe { esp_netif_attach(self.netif.handle(), glue_handle as *mut _) })?;
1357
1358        self.glue_handle = glue_handle;
1359
1360        Ok(())
1361    }
1362
1363    fn detach_netif(&mut self) -> Result<(), EspError> {
1364        let _ = self.driver.stop();
1365
1366        esp!(unsafe { esp_eth_del_netif_glue(self.glue_handle as *mut _) })?;
1367
1368        self.glue_handle = core::ptr::null_mut();
1369
1370        Ok(())
1371    }
1372}
1373
1374#[cfg(esp_idf_comp_esp_netif_enabled)]
1375impl<T> Drop for EspEth<'_, T> {
1376    fn drop(&mut self) {
1377        self.detach_netif().unwrap();
1378
1379        ::log::info!("EspEth dropped");
1380    }
1381}
1382
1383#[cfg(esp_idf_comp_esp_netif_enabled)]
1384unsafe impl<T> Send for EspEth<'_, T> {}
1385
1386#[cfg(esp_idf_comp_esp_netif_enabled)]
1387impl<T> RawHandle for EspEth<'_, T> {
1388    type Handle = *mut esp_eth_netif_glue_t;
1389
1390    fn handle(&self) -> Self::Handle {
1391        self.glue_handle
1392    }
1393}
1394
1395#[cfg(esp_idf_comp_esp_netif_enabled)]
1396impl<T> Eth for EspEth<'_, T> {
1397    type Error = EspError;
1398
1399    fn start(&mut self) -> Result<(), Self::Error> {
1400        EspEth::start(self)
1401    }
1402
1403    fn stop(&mut self) -> Result<(), Self::Error> {
1404        EspEth::stop(self)
1405    }
1406
1407    fn is_started(&self) -> Result<bool, Self::Error> {
1408        EspEth::is_started(self)
1409    }
1410
1411    fn is_connected(&self) -> Result<bool, Self::Error> {
1412        EspEth::is_connected(self)
1413    }
1414}
1415
1416#[cfg(esp_idf_comp_esp_netif_enabled)]
1417impl<T> NetifStatus for EspEth<'_, T> {
1418    fn is_up(&self) -> Result<bool, EspError> {
1419        EspEth::is_up(self)
1420    }
1421}
1422
1423#[derive(Copy, Clone, Debug, Eq, PartialEq)]
1424pub enum EthEvent {
1425    Started(esp_eth_handle_t),
1426    Stopped(esp_eth_handle_t),
1427    Connected(esp_eth_handle_t),
1428    Disconnected(esp_eth_handle_t),
1429
1430    /// An event ID not recognised by this version of the library was received.
1431    ///
1432    /// This variant is produced instead of panicking when an unknown event ID
1433    /// arrives, allowing applications to remain forward-compatible with
1434    /// ESP-IDF versions that introduce new Ethernet events.
1435    Other(i32),
1436}
1437
1438unsafe impl Send for EthEvent {}
1439
1440impl EthEvent {
1441    pub fn is_for(&self, raw_handle: impl RawHandle<Handle = esp_eth_handle_t>) -> bool {
1442        self.is_for_handle(raw_handle.handle())
1443    }
1444
1445    pub fn is_for_handle(&self, handle: esp_eth_handle_t) -> bool {
1446        core::ptr::eq(self.handle(), handle)
1447    }
1448
1449    pub fn handle(&self) -> esp_eth_handle_t {
1450        let handle = match self {
1451            Self::Started(handle) => *handle,
1452            Self::Stopped(handle) => *handle,
1453            Self::Connected(handle) => *handle,
1454            Self::Disconnected(handle) => *handle,
1455            Self::Other(_) => core::ptr::null_mut(),
1456        };
1457
1458        handle as esp_eth_handle_t
1459    }
1460}
1461
1462unsafe impl EspEventSource for EthEvent {
1463    fn source() -> Option<&'static ffi::CStr> {
1464        Some(unsafe { ffi::CStr::from_ptr(ETH_EVENT) })
1465    }
1466}
1467
1468impl EspEventDeserializer for EthEvent {
1469    type Data<'a> = Self;
1470
1471    #[allow(non_upper_case_globals, non_snake_case)]
1472    fn deserialize(data: &crate::eventloop::EspEvent) -> Self {
1473        let eth_handle_ref =
1474            unsafe { (data.payload.unwrap() as *const _ as *const esp_eth_handle_t).as_ref() };
1475
1476        let event_id = data.event_id as u32;
1477
1478        if event_id == eth_event_t_ETHERNET_EVENT_START {
1479            EthEvent::Started(*eth_handle_ref.unwrap() as _)
1480        } else if event_id == eth_event_t_ETHERNET_EVENT_STOP {
1481            EthEvent::Stopped(*eth_handle_ref.unwrap() as _)
1482        } else if event_id == eth_event_t_ETHERNET_EVENT_CONNECTED {
1483            EthEvent::Connected(*eth_handle_ref.unwrap() as _)
1484        } else if event_id == eth_event_t_ETHERNET_EVENT_DISCONNECTED {
1485            EthEvent::Disconnected(*eth_handle_ref.unwrap() as _)
1486        } else {
1487            ::log::warn!("EthEvent: unknown event ID {event_id}, ignoring");
1488            EthEvent::Other(event_id as i32)
1489        }
1490    }
1491}
1492
1493const CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
1494
1495pub struct BlockingEth<T> {
1496    eth: T,
1497    event_loop: crate::eventloop::EspSystemEventLoop,
1498}
1499
1500impl<T> BlockingEth<T>
1501where
1502    T: Eth<Error = EspError>,
1503{
1504    pub fn wrap(eth: T, event_loop: EspSystemEventLoop) -> Result<Self, EspError> {
1505        Ok(Self { eth, event_loop })
1506    }
1507
1508    pub fn eth(&self) -> &T {
1509        &self.eth
1510    }
1511
1512    pub fn eth_mut(&mut self) -> &mut T {
1513        &mut self.eth
1514    }
1515
1516    pub fn is_started(&self) -> Result<bool, EspError> {
1517        self.eth.is_started()
1518    }
1519
1520    pub fn start(&mut self) -> Result<(), EspError> {
1521        self.eth.start()?;
1522        self.eth_wait_while(|| self.eth.is_started().map(|s| !s), None)
1523    }
1524
1525    pub fn stop(&mut self) -> Result<(), EspError> {
1526        self.eth.stop()?;
1527        self.eth_wait_while(|| self.eth.is_started(), None)
1528    }
1529
1530    pub fn is_connected(&self) -> Result<bool, EspError> {
1531        self.eth.is_connected()
1532    }
1533
1534    pub fn wait_connected(&self) -> Result<(), EspError> {
1535        self.eth_wait_while(
1536            || self.eth.is_connected().map(|s| !s),
1537            Some(CONNECT_TIMEOUT),
1538        )
1539    }
1540
1541    pub fn eth_wait_while<F: Fn() -> Result<bool, EspError>>(
1542        &self,
1543        matcher: F,
1544        timeout: Option<Duration>,
1545    ) -> Result<(), EspError> {
1546        let wait = crate::eventloop::Wait::new::<EthEvent>(&self.event_loop)?;
1547
1548        wait.wait_while(matcher, timeout)
1549    }
1550}
1551
1552#[cfg(esp_idf_comp_esp_netif_enabled)]
1553impl<T> BlockingEth<T>
1554where
1555    T: NetifStatus,
1556{
1557    pub fn is_up(&self) -> Result<bool, EspError> {
1558        self.eth.is_up()
1559    }
1560
1561    pub fn wait_netif_up(&self) -> Result<(), EspError> {
1562        self.ip_wait_while(|| self.eth.is_up().map(|s| !s), Some(CONNECT_TIMEOUT))
1563    }
1564
1565    pub fn ip_wait_while<F: Fn() -> Result<bool, EspError>>(
1566        &self,
1567        matcher: F,
1568        timeout: Option<core::time::Duration>,
1569    ) -> Result<(), EspError> {
1570        let wait = crate::eventloop::Wait::new::<IpEvent>(&self.event_loop)?;
1571
1572        wait.wait_while(matcher, timeout)
1573    }
1574}
1575
1576impl<T> Eth for BlockingEth<T>
1577where
1578    T: Eth<Error = EspError>,
1579{
1580    type Error = EspError;
1581
1582    fn is_started(&self) -> Result<bool, Self::Error> {
1583        BlockingEth::is_started(self)
1584    }
1585
1586    fn is_connected(&self) -> Result<bool, Self::Error> {
1587        BlockingEth::is_connected(self)
1588    }
1589
1590    fn start(&mut self) -> Result<(), Self::Error> {
1591        BlockingEth::start(self)
1592    }
1593
1594    fn stop(&mut self) -> Result<(), Self::Error> {
1595        BlockingEth::stop(self)
1596    }
1597}
1598
1599#[cfg(esp_idf_comp_esp_netif_enabled)]
1600impl<T> NetifStatus for BlockingEth<T>
1601where
1602    T: NetifStatus,
1603{
1604    fn is_up(&self) -> Result<bool, EspError> {
1605        BlockingEth::is_up(self)
1606    }
1607}
1608
1609#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
1610pub struct AsyncEth<T> {
1611    pub(crate) eth: T,
1612    pub(crate) event_loop: crate::eventloop::EspSystemEventLoop,
1613    pub(crate) timer_service: crate::timer::EspTaskTimerService,
1614}
1615
1616#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
1617impl<T> AsyncEth<T>
1618where
1619    T: Eth<Error = EspError>,
1620{
1621    pub fn wrap(
1622        eth: T,
1623        event_loop: EspSystemEventLoop,
1624        timer_service: crate::timer::EspTaskTimerService,
1625    ) -> Result<Self, EspError> {
1626        Ok(Self {
1627            eth,
1628            event_loop,
1629            timer_service,
1630        })
1631    }
1632
1633    pub fn eth(&self) -> &T {
1634        &self.eth
1635    }
1636
1637    pub fn eth_mut(&mut self) -> &mut T {
1638        &mut self.eth
1639    }
1640
1641    pub fn is_started(&self) -> Result<bool, EspError> {
1642        self.eth.is_started()
1643    }
1644
1645    pub fn is_connected(&self) -> Result<bool, EspError> {
1646        self.eth.is_connected()
1647    }
1648
1649    pub async fn start(&mut self) -> Result<(), EspError> {
1650        self.eth.start()?;
1651        self.eth_wait_while(|this| this.eth.is_started().map(|s| !s), None)
1652            .await
1653    }
1654
1655    pub async fn stop(&mut self) -> Result<(), EspError> {
1656        self.eth.stop()?;
1657        self.eth_wait_while(|this| this.eth.is_started(), None)
1658            .await
1659    }
1660
1661    pub async fn wait_connected(&mut self) -> Result<(), EspError> {
1662        self.eth_wait_while(
1663            |this| this.eth.is_connected().map(|s| !s),
1664            Some(CONNECT_TIMEOUT),
1665        )
1666        .await
1667    }
1668
1669    pub async fn eth_wait_while<F: FnMut(&mut Self) -> Result<bool, EspError>>(
1670        &mut self,
1671        mut matcher: F,
1672        timeout: Option<Duration>,
1673    ) -> Result<(), EspError> {
1674        let mut wait =
1675            crate::eventloop::AsyncWait::<EthEvent, _>::new(&self.event_loop, &self.timer_service)?;
1676
1677        wait.wait_while(|| matcher(self), timeout).await
1678    }
1679}
1680
1681#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
1682#[cfg(esp_idf_comp_esp_netif_enabled)]
1683impl<T> AsyncEth<T>
1684where
1685    T: NetifStatus,
1686{
1687    pub fn is_up(&self) -> Result<bool, EspError> {
1688        self.eth.is_up()
1689    }
1690
1691    pub async fn wait_netif_up(&mut self) -> Result<(), EspError> {
1692        self.ip_wait_while(|this| this.eth.is_up().map(|s| !s), Some(CONNECT_TIMEOUT))
1693            .await
1694    }
1695
1696    pub async fn ip_wait_while<F: FnMut(&mut Self) -> Result<bool, EspError>>(
1697        &mut self,
1698        mut matcher: F,
1699        timeout: Option<core::time::Duration>,
1700    ) -> Result<(), EspError> {
1701        let mut wait =
1702            crate::eventloop::AsyncWait::<IpEvent, _>::new(&self.event_loop, &self.timer_service)?;
1703
1704        wait.wait_while(|| matcher(self), timeout).await
1705    }
1706}
1707
1708#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
1709impl<T> embedded_svc::eth::asynch::Eth for AsyncEth<T>
1710where
1711    T: Eth<Error = EspError>,
1712{
1713    type Error = T::Error;
1714
1715    async fn start(&mut self) -> Result<(), Self::Error> {
1716        AsyncEth::start(self).await
1717    }
1718
1719    async fn stop(&mut self) -> Result<(), Self::Error> {
1720        AsyncEth::stop(self).await
1721    }
1722
1723    async fn is_started(&self) -> Result<bool, Self::Error> {
1724        AsyncEth::is_started(self)
1725    }
1726
1727    async fn is_connected(&self) -> Result<bool, Self::Error> {
1728        AsyncEth::is_connected(self)
1729    }
1730}
1731
1732#[cfg(esp_idf_comp_esp_netif_enabled)]
1733impl<T> crate::netif::asynch::NetifStatus for EspEth<'_, T> {
1734    async fn is_up(&self) -> Result<bool, EspError> {
1735        EspEth::is_up(self)
1736    }
1737}
1738
1739#[cfg(all(feature = "alloc", esp_idf_comp_esp_timer_enabled))]
1740#[cfg(esp_idf_comp_esp_netif_enabled)]
1741impl<T> crate::netif::asynch::NetifStatus for AsyncEth<T>
1742where
1743    T: NetifStatus,
1744{
1745    async fn is_up(&self) -> Result<bool, EspError> {
1746        AsyncEth::is_up(self)
1747    }
1748}