Skip to main content

esp_emac/
error.rs

1// SPDX-License-Identifier: GPL-2.0-or-later OR Apache-2.0
2// Copyright (c) Viacheslav Bocharov <v@baodeep.com> and JetHome (r)
3
4//! EMAC error types.
5
6/// EMAC driver error.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9#[cfg_attr(feature = "defmt", derive(defmt::Format))]
10pub enum EmacError {
11    /// MDIO operation timed out (no PHY response within the SMI window).
12    Timeout,
13    /// DMA software reset (`DMABUSMODE.SWR`) did not self-clear within
14    /// the [`crate::reset::ResetController`] timeout. Distinct from
15    /// [`Self::Timeout`] so callers can tell SMI-bus errors apart from
16    /// DMA-controller stuckness.
17    DmaResetTimeout,
18    /// DMA descriptor or buffer error.
19    DmaError,
20    /// Invalid configuration (bad pin, clock, etc.).
21    InvalidConfig,
22    /// PHY address out of range (must be 0-31).
23    InvalidPhyAddress,
24    /// Frame length is zero or otherwise invalid.
25    InvalidLength,
26    /// Frame too large for available descriptors/buffers.
27    FrameTooLarge,
28    /// No TX descriptors available (ring full).
29    NoDescriptorsAvailable,
30    /// Descriptor is still owned by DMA.
31    DescriptorBusy,
32    /// No received frame available.
33    NoFrameAvailable,
34    /// Received frame has errors (CRC, overflow, etc.).
35    FrameError,
36    /// Caller-provided buffer too small for the received frame.
37    BufferTooSmall,
38    /// DMA engine not initialized.
39    NotInitialized,
40    /// `init()` was called twice.
41    AlreadyInitialized,
42    /// `stop()` could not flush the TX FIFO within
43    /// `TX_FIFO_FLUSH_TIMEOUT_US`. Teardown still proceeded — the
44    /// driver state is back to [`crate::EmacState::Initialized`] and
45    /// MAC/DMA are quiesced — but at least one in-flight TX frame may
46    /// have been truncated on the wire. The caller decides whether
47    /// to treat this as a recoverable warning (sample, log,
48    /// `start()` again — the engine is ready) or as terminal: there
49    /// is no in-crate "re-init" path because [`crate::Emac::init`] is
50    /// one-shot, so a hard recovery means a peripheral / SoC reset
51    /// from the application layer.
52    TxFlushTimeout,
53}
54
55impl From<crate::reset::ResetError> for EmacError {
56    fn from(e: crate::reset::ResetError) -> Self {
57        match e {
58            crate::reset::ResetError::Timeout => EmacError::DmaResetTimeout,
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    extern crate alloc;
67
68    #[test]
69    fn emac_error_debug() {
70        let err = EmacError::Timeout;
71        let dbg = alloc::format!("{:?}", err);
72        assert!(dbg.contains("Timeout"));
73    }
74
75    #[test]
76    fn emac_error_equality() {
77        assert_eq!(EmacError::Timeout, EmacError::Timeout);
78        assert_ne!(EmacError::Timeout, EmacError::DmaError);
79        assert_ne!(EmacError::InvalidConfig, EmacError::InvalidPhyAddress);
80    }
81
82    #[test]
83    fn emac_error_clone() {
84        let err = EmacError::DmaError;
85        let cloned = err;
86        assert_eq!(err, cloned);
87    }
88
89    #[test]
90    fn reset_timeout_maps_to_dma_reset_timeout_not_mdio_timeout() {
91        // Regression: previously `From<ResetError>` collapsed DMA reset
92        // timeouts into the MDIO-flavoured `EmacError::Timeout`,
93        // making the two indistinguishable for API consumers. They
94        // are distinct hardware failure modes — keep the conversion
95        // routed at the dedicated variant.
96        let mapped: EmacError = crate::reset::ResetError::Timeout.into();
97        assert_eq!(mapped, EmacError::DmaResetTimeout);
98        assert_ne!(mapped, EmacError::Timeout);
99    }
100}