Skip to main content

eth_mdio_phy/
phy.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//! PHY driver trait and common error type.
5
6use crate::mdio::MdioBus;
7use crate::types::{LinkStatus, PhyCapabilities};
8
9/// Common error type for PHY driver operations.
10#[derive(Debug)]
11#[non_exhaustive]
12#[cfg_attr(feature = "defmt", derive(defmt::Format))]
13pub enum PhyError<E> {
14    /// MDIO bus error (passthrough).
15    Mdio(E),
16    /// PHY soft reset did not complete within allowed attempts.
17    ResetTimeout,
18    /// PHY ID does not match expected chip family.
19    UnsupportedChip {
20        /// The actual PHY ID read from registers.
21        id: u32,
22    },
23    /// PHY ID matched the expected family, but a chip-specific package
24    /// or variant strap did not. Reported by drivers whose family
25    /// covers more than one silicon SKU and which discriminate the
26    /// concrete part at runtime (e.g. LAN867x via `STRAP_CTRL0.PKGTYP`).
27    UnsupportedPackage {
28        /// Raw strap-register value the driver could not decode,
29        /// zero-extended to 32 bits for forward-compatibility with
30        /// chips that expose wider strap windows.
31        strap: u32,
32    },
33}
34
35impl<E> From<E> for PhyError<E> {
36    fn from(e: E) -> Self {
37        PhyError::Mdio(e)
38    }
39}
40
41/// Ethernet PHY driver — one implementation per chip family.
42///
43/// Every method except [`phy_addr`](Self::phy_addr) takes the bus
44/// generically (`<M: MdioBus>`) so a single driver instance can talk
45/// to different concrete buses across a session — useful for unit
46/// tests against a mock bus and for diagnostic passthroughs.
47///
48/// **Object-safety.** Because of those generic methods the trait is
49/// **not** object-safe — `dyn PhyDriver` is a compile error. If you
50/// need polymorphic storage of multiple PHYs (a switch driver, a
51/// link-state watchdog) keep them as concrete types behind an enum,
52/// or write your own object-safe wrapper that fixes a single
53/// `MdioBus` impl.
54pub trait PhyDriver {
55    /// PHY address on the MDIO bus (0-31).
56    fn phy_addr(&self) -> u8;
57
58    /// Initialize PHY: reset, configure, enable auto-negotiation.
59    fn init<M: MdioBus>(&mut self, mdio: &mut M) -> Result<(), PhyError<M::Error>>;
60
61    /// Poll link status. Returns `Some(LinkStatus)` when up, `None` when down.
62    fn poll_link<M: MdioBus>(
63        &mut self,
64        mdio: &mut M,
65    ) -> Result<Option<LinkStatus>, PhyError<M::Error>>;
66
67    /// Query hardware capabilities.
68    fn capabilities<M: MdioBus>(&self, mdio: &mut M)
69        -> Result<PhyCapabilities, PhyError<M::Error>>;
70
71    /// Read the 32-bit PHY identifier composed of the two ID
72    /// registers at Clause 22 addresses 0x02 and 0x03:
73    /// `(reg_0x02 << 16) | reg_0x03`.
74    ///
75    /// The bit-layout of the result is **chip-family-specific** —
76    /// IEEE 802.3 Clause 22.2.4.3.1 defines OUI/model/revision packing
77    /// for 100BASE-TX (e.g. LAN87xx via `PHYIDR1`/`PHYIDR2`), while
78    /// 10BASE-T1S chips (LAN867x) use the same registers as a flat
79    /// `PHY_ID0`/`PHY_ID1` pair with their own field widths. Drivers
80    /// validate the result against a chip-specific mask; downstream
81    /// consumers should treat it as opaque or consult the relevant
82    /// datasheet.
83    fn phy_id<M: MdioBus>(&self, mdio: &mut M) -> Result<u32, PhyError<M::Error>>;
84}
85
86#[cfg(test)]
87mod tests {
88    extern crate alloc;
89
90    use super::*;
91    use alloc::format;
92
93    /// A trivial bus error type for testing.
94    #[derive(Debug, PartialEq)]
95    struct BusError(u8);
96
97    #[test]
98    fn phy_error_from_bus_error() {
99        let bus_err = BusError(42);
100        let phy_err: PhyError<BusError> = PhyError::from(bus_err);
101        match phy_err {
102            PhyError::Mdio(e) => assert_eq!(e, BusError(42)),
103            _ => panic!("expected Mdio variant"),
104        }
105    }
106
107    #[test]
108    fn phy_error_reset_timeout() {
109        let err: PhyError<BusError> = PhyError::ResetTimeout;
110        match err {
111            PhyError::ResetTimeout => {}
112            _ => panic!("expected ResetTimeout variant"),
113        }
114    }
115
116    #[test]
117    fn phy_error_unsupported_chip() {
118        let err: PhyError<BusError> = PhyError::UnsupportedChip { id: 0x0007_C0F1 };
119        match err {
120            PhyError::UnsupportedChip { id } => assert_eq!(id, 0x0007_C0F1),
121            _ => panic!("expected UnsupportedChip variant"),
122        }
123    }
124
125    #[test]
126    fn phy_error_debug() {
127        let err: PhyError<BusError> = PhyError::Mdio(BusError(1));
128        let dbg = format!("{:?}", err);
129        assert!(dbg.contains("Mdio"), "debug missing 'Mdio': {dbg}");
130    }
131
132    #[test]
133    fn phy_error_into_from_bus() {
134        fn fallible() -> Result<(), BusError> {
135            Err(BusError(7))
136        }
137
138        let result: Result<(), PhyError<BusError>> = fallible().map_err(PhyError::from);
139        match result {
140            Err(PhyError::Mdio(e)) => assert_eq!(e, BusError(7)),
141            _ => panic!("expected Mdio error"),
142        }
143    }
144}