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}