Skip to main content

esp_p4_eth/
phy.rs

1//! Clause 22 MDIO helpers and IP101 PHY integration.
2//!
3//! The current implementation targets the common IP101GRI PHY used in ESP32-P4
4//! RMII designs. Link state is polled over MDIO and then reflected back into the
5//! MAC speed/duplex configuration.
6
7use core::hint::spin_loop;
8
9use crate::{regs, Duplex, Ethernet};
10
11/// Diagnostic UART writer for the cold-boot hang investigation.
12///
13/// Bangs raw bytes onto UART0's TX FIFO with the same MMIO layout the
14/// `mdio_test` example uses, so we can keep the helper self-contained
15/// inside `phy.rs` without taking on a `Write`-trait dependency on the
16/// caller. ROM bootloader has UART0 already configured at the espflash
17/// monitor baud rate by the time `Ip101::init` runs, so no init is needed
18/// here.
19#[cfg(feature = "phy-init-debug")]
20mod debug_uart {
21    use core::fmt;
22    use core::ptr::{read_volatile, write_volatile};
23
24    const UART0_BASE: usize = 0x500C_A000;
25    const UART_FIFO: *mut u32 = UART0_BASE as *mut u32;
26    const UART_STATUS: *const u32 = (UART0_BASE + 0x1C) as *const u32;
27    const UART_TXFIFO_CNT_SHIFT: u32 = 16;
28    const UART_TXFIFO_CNT_MASK: u32 = 0xFF << UART_TXFIFO_CNT_SHIFT;
29    const UART_TXFIFO_CAPACITY: u32 = 128;
30
31    pub struct Writer;
32
33    impl fmt::Write for Writer {
34        fn write_str(&mut self, s: &str) -> fmt::Result {
35            for byte in s.bytes() {
36                if byte == b'\n' {
37                    write_byte(b'\r');
38                }
39                write_byte(byte);
40            }
41            Ok(())
42        }
43    }
44
45    fn write_byte(byte: u8) {
46        while txfifo_count() >= UART_TXFIFO_CAPACITY {
47            core::hint::spin_loop();
48        }
49        unsafe {
50            write_volatile(UART_FIFO, byte as u32);
51        }
52    }
53
54    fn txfifo_count() -> u32 {
55        let status = unsafe { read_volatile(UART_STATUS) };
56        (status & UART_TXFIFO_CNT_MASK) >> UART_TXFIFO_CNT_SHIFT
57    }
58}
59
60/// Step marker emitted to UART0 when the `phy-init-debug` feature is on.
61/// Compiles to a no-op otherwise so the bring-up path stays cost-free in
62/// production builds.
63#[cfg(feature = "phy-init-debug")]
64pub fn diag_log(msg: &str) {
65    use core::fmt::Write;
66    let mut uart = debug_uart::Writer;
67    let _ = writeln!(uart, "{}", msg);
68}
69
70/// No-op fallback when `phy-init-debug` is disabled.
71#[cfg(not(feature = "phy-init-debug"))]
72#[inline(always)]
73pub fn diag_log(_msg: &str) {}
74
75// MDIO transaction at csr_clock_range=0 (= MDC ~250 kHz worst case) takes
76// up to ~200 us. With opt-level >= 1 the polling loop body collapses to a
77// few cycles, so 100 iterations finish in < 1 us and we'd time out before
78// the PHY ever responds. 100_000 covers the worst case at any optimization
79// level we ship (~5 ms upper bound at 360 MHz CPU, well above hardware
80// transaction time).
81const MDIO_TIMEOUT_POLLS: usize = 100_000;
82const PHY_RESET_TIMEOUT_POLLS: usize = 500_000;
83
84/// Basic Mode Control Register address.
85pub const BMCR: u8 = 0x00;
86/// Basic Mode Status Register address.
87pub const BMSR: u8 = 0x01;
88/// PHY ID register 1 address.
89pub const PHYIDR1: u8 = 0x02;
90/// PHY ID register 2 address.
91pub const PHYIDR2: u8 = 0x03;
92/// Auto-negotiation advertisement register address.
93pub const ANAR: u8 = 0x04;
94/// Auto-negotiation link partner ability register address.
95pub const ANLPAR: u8 = 0x05;
96
97/// Bit definitions for [`BMCR`].
98pub mod bmcr {
99    /// Software reset request bit.
100    pub const RESET: u16 = 1 << 15;
101    /// Selects 100 Mbps operation when auto-negotiation is disabled.
102    pub const SPEED_100: u16 = 1 << 13;
103    /// Enables auto-negotiation.
104    pub const ANEN: u16 = 1 << 12;
105    /// Restarts auto-negotiation.
106    pub const RESTART_AN: u16 = 1 << 9;
107    /// Selects full duplex when auto-negotiation is disabled.
108    pub const FULL_DUPLEX: u16 = 1 << 8;
109}
110
111/// Bit definitions for [`BMSR`].
112pub mod bmsr {
113    /// Current link-status bit. On many PHYs this bit is latch-low.
114    pub const LINK_STATUS: u16 = 1 << 2;
115    /// Auto-negotiation complete bit.
116    pub const AN_COMPLETE: u16 = 1 << 5;
117}
118
119/// Common capability bits read from [`ANLPAR`].
120pub mod anlpar {
121    /// Link partner supports 10BASE-T half duplex.
122    pub const BASE_10_HALF: u16 = 1 << 5;
123    /// Link partner supports 10BASE-T full duplex.
124    pub const BASE_10_FULL: u16 = 1 << 6;
125    /// Link partner supports 100BASE-TX half duplex.
126    pub const BASE_100_HALF: u16 = 1 << 7;
127    /// Link partner supports 100BASE-TX full duplex.
128    pub const BASE_100_FULL: u16 = 1 << 8;
129}
130
131/// High-level link state exposed by the PHY layer.
132#[derive(Clone, Copy, Debug, Eq, PartialEq)]
133pub enum LinkState {
134    /// No link is currently active.
135    Down,
136    /// Link is up and includes the negotiated line mode.
137    Up { speed: Speed, duplex: Duplex },
138}
139
140/// Line speed used by both the PHY and MAC layers.
141#[repr(u8)]
142#[derive(Clone, Copy, Debug, Eq, PartialEq)]
143pub enum Speed {
144    /// 10 Mbps RMII operation.
145    Mbps10,
146    /// 100 Mbps RMII operation.
147    Mbps100,
148}
149
150/// Minimal PHY abstraction used by [`crate::Ethernet`].
151pub trait Phy {
152    /// Performs one-time PHY initialization.
153    fn init(&mut self, _eth: &mut Ethernet<'_>) -> Result<(), PhyError>;
154    /// Polls current link state and, when possible, returns negotiated line mode.
155    fn poll_link(&mut self, _eth: &mut Ethernet<'_>) -> LinkState;
156}
157
158/// PHY/MDIO errors.
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub enum PhyError {
161    /// A long PHY-side operation did not complete in time.
162    Timeout,
163    /// Reserved placeholder for MDIO bus-level failures.
164    Bus,
165    /// MDIO busy bit did not clear before timeout.
166    MdioTimeout,
167}
168
169/// IP101 PHY model used by the current driver.
170#[derive(Clone, Copy, Debug, Eq, PartialEq)]
171pub struct Ip101 {
172    /// Clause 22 PHY address on the MDIO bus.
173    pub addr: u8,
174}
175
176impl Ip101 {
177    /// Creates an IP101 handle for the given PHY address.
178    pub const fn new(addr: u8) -> Self {
179        Self { addr }
180    }
181
182    /// Resets the PHY and restarts auto-negotiation.
183    pub fn init(&mut self, _eth: &mut Ethernet<'_>) -> Result<(), PhyError> {
184        #[cfg(feature = "phy-init-debug")]
185        {
186            use core::fmt::Write;
187            let mut uart = debug_uart::Writer;
188            let _ = writeln!(uart, "[phy_init] addr={} starting", self.addr);
189
190            // Pre-probe: snapshot PHY register state BEFORE we touch BMCR.
191            // If MDIO is already broken (PHY not responding, MAC clock not
192            // configured), these will return Err(MdioTimeout) or 0xFFFF.
193            for (label, reg) in [("PHYIDR1", PHYIDR1), ("PHYIDR2", PHYIDR2),
194                                 ("BMCR", BMCR), ("BMSR", BMSR)] {
195                match mdio_read(self.addr, reg) {
196                    Ok(v) => {
197                        let _ = writeln!(uart, "[phy_init] pre {}=0x{:04X}", label, v);
198                    }
199                    Err(e) => {
200                        let _ = writeln!(uart, "[phy_init] pre {} ERR={:?}", label, e);
201                    }
202                }
203            }
204        }
205
206        let write_result = mdio_write(self.addr, BMCR, bmcr::RESET);
207
208        #[cfg(feature = "phy-init-debug")]
209        {
210            use core::fmt::Write;
211            let mut uart = debug_uart::Writer;
212            match &write_result {
213                Ok(()) => {
214                    let _ = writeln!(uart, "[phy_init] BMCR<-RESET write OK, polling clear...");
215                }
216                Err(e) => {
217                    let _ = writeln!(uart, "[phy_init] BMCR<-RESET write ERR={:?}", e);
218                }
219            }
220        }
221        write_result?;
222
223        #[cfg(feature = "phy-init-debug")]
224        let mut last_bmcr: u16 = 0xFFFF;
225
226        for _i in 0..PHY_RESET_TIMEOUT_POLLS {
227            let bmcr_value = match mdio_read(self.addr, BMCR) {
228                Ok(v) => v,
229                Err(e) => {
230                    #[cfg(feature = "phy-init-debug")]
231                    {
232                        use core::fmt::Write;
233                        let mut uart = debug_uart::Writer;
234                        let _ = writeln!(
235                            uart,
236                            "[phy_init] poll iter={} mdio_read ERR={:?}",
237                            _i, e
238                        );
239                    }
240                    return Err(e);
241                }
242            };
243
244            #[cfg(feature = "phy-init-debug")]
245            {
246                if bmcr_value != last_bmcr || _i == 0 || _i % 50_000 == 0 {
247                    use core::fmt::Write;
248                    let mut uart = debug_uart::Writer;
249                    let _ = writeln!(
250                        uart,
251                        "[phy_init] iter={} BMCR=0x{:04X}",
252                        _i, bmcr_value
253                    );
254                    last_bmcr = bmcr_value;
255                }
256            }
257
258            if bmcr_value & bmcr::RESET == 0 {
259                #[cfg(feature = "phy-init-debug")]
260                {
261                    use core::fmt::Write;
262                    let mut uart = debug_uart::Writer;
263                    let _ = writeln!(
264                        uart,
265                        "[phy_init] reset cleared at iter={}, writing ANEN|RESTART_AN",
266                        _i
267                    );
268                }
269
270                mdio_write(self.addr, BMCR, bmcr::ANEN | bmcr::RESTART_AN)?;
271
272                #[cfg(feature = "phy-init-debug")]
273                {
274                    use core::fmt::Write;
275                    let mut uart = debug_uart::Writer;
276                    let _ = writeln!(uart, "[phy_init] DONE OK");
277                }
278
279                return Ok(());
280            }
281
282            spin_loop();
283        }
284
285        #[cfg(feature = "phy-init-debug")]
286        {
287            use core::fmt::Write;
288            let mut uart = debug_uart::Writer;
289            let _ = writeln!(
290                uart,
291                "[phy_init] TIMEOUT after {} polls (RESET bit never cleared)",
292                PHY_RESET_TIMEOUT_POLLS
293            );
294        }
295
296        Err(PhyError::Timeout)
297    }
298
299    /// Returns whether the PHY currently reports link-up.
300    pub fn link_up(&mut self, _eth: &mut Ethernet<'_>) -> Result<bool, PhyError> {
301        let _ = mdio_read(self.addr, BMSR)?;
302        let status = mdio_read(self.addr, BMSR)?;
303        Ok(status & bmsr::LINK_STATUS != 0)
304    }
305
306    /// Resolves negotiated speed/duplex from link partner abilities.
307    pub fn negotiate(&mut self, _eth: &mut Ethernet<'_>) -> Result<(Speed, Duplex), PhyError> {
308        let abilities = mdio_read(self.addr, ANLPAR)?;
309        Ok(resolve_link_from_anlpar(abilities))
310    }
311
312    fn autoneg_complete(&mut self) -> Result<bool, PhyError> {
313        let status = mdio_read(self.addr, BMSR)?;
314        Ok(status & bmsr::AN_COMPLETE != 0)
315    }
316}
317
318/// Writes one Clause 22 PHY register over MDIO.
319pub fn mdio_write(phy_addr: u8, reg: u8, value: u16) -> Result<(), PhyError> {
320    wait_for_mdio_idle(|| regs::read(regs::mac::MII_ADDR))?;
321
322    regs::write(regs::mac::MII_DATA, u32::from(value));
323    regs::write(
324        regs::mac::MII_ADDR,
325        mii_address_value(phy_addr, reg, current_csr_clock_range(), true),
326    );
327    #[cfg(test)]
328    complete_test_mdio_write(reg, value);
329
330    wait_for_mdio_idle(|| regs::read(regs::mac::MII_ADDR))
331}
332
333/// Reads one Clause 22 PHY register over MDIO.
334pub fn mdio_read(phy_addr: u8, reg: u8) -> Result<u16, PhyError> {
335    wait_for_mdio_idle(|| regs::read(regs::mac::MII_ADDR))?;
336
337    regs::write(
338        regs::mac::MII_ADDR,
339        mii_address_value(phy_addr, reg, current_csr_clock_range(), false),
340    );
341    #[cfg(test)]
342    complete_test_mdio_read(reg);
343
344    wait_for_mdio_idle(|| regs::read(regs::mac::MII_ADDR))?;
345    Ok(regs::read(regs::mac::MII_DATA) as u16)
346}
347
348fn current_csr_clock_range() -> u32 {
349    (regs::read(regs::mac::MII_ADDR) & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK)
350        >> regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT
351}
352
353fn mii_address_value(phy_addr: u8, reg: u8, csr_clock_range: u32, write: bool) -> u32 {
354    let mut value = regs::bits::miiaddr::MII_BUSY
355        | ((u32::from(phy_addr) & 0x1f) << regs::bits::miiaddr::PHY_ADDR_SHIFT)
356        | ((u32::from(reg) & 0x1f) << regs::bits::miiaddr::REG_ADDR_SHIFT)
357        | ((csr_clock_range << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT)
358            & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK);
359
360    if write {
361        value |= regs::bits::miiaddr::MII_WRITE;
362    }
363
364    value
365}
366
367fn wait_for_mdio_idle<F>(mut read_miiaddr: F) -> Result<(), PhyError>
368where
369    F: FnMut() -> u32,
370{
371    for _ in 0..MDIO_TIMEOUT_POLLS {
372        if read_miiaddr() & regs::bits::miiaddr::MII_BUSY == 0 {
373            return Ok(());
374        }
375    }
376
377    Err(PhyError::MdioTimeout)
378}
379
380#[cfg(test)]
381fn complete_test_mdio_write(reg: u8, value: u16) {
382    let data = match reg {
383        BMCR if value & bmcr::RESET != 0 => 0,
384        _ => u32::from(value),
385    };
386    regs::write(regs::mac::MII_DATA, data);
387    regs::write(
388        regs::mac::MII_ADDR,
389        current_csr_clock_range() << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
390    );
391}
392
393#[cfg(test)]
394fn complete_test_mdio_read(reg: u8) {
395    let value = match reg {
396        BMCR => 0,
397        BMSR => 0,
398        ANLPAR => 0,
399        PHYIDR1 => 0,
400        PHYIDR2 => 0,
401        _ => regs::read(regs::mac::MII_DATA),
402    };
403
404    regs::write(regs::mac::MII_DATA, value);
405    regs::write(
406        regs::mac::MII_ADDR,
407        current_csr_clock_range() << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
408    );
409}
410
411impl Phy for Ip101 {
412    fn init(&mut self, eth: &mut Ethernet<'_>) -> Result<(), PhyError> {
413        Ip101::init(self, eth)
414    }
415
416    fn poll_link(&mut self, eth: &mut Ethernet<'_>) -> LinkState {
417        match (self.link_up(eth), self.autoneg_complete()) {
418            (Ok(true), Ok(true)) => match self.negotiate(eth) {
419                Ok((speed, duplex)) => LinkState::Up { speed, duplex },
420                Err(_) => LinkState::Down,
421            },
422            _ => LinkState::Down,
423        }
424    }
425}
426
427fn resolve_link_from_anlpar(abilities: u16) -> (Speed, Duplex) {
428    if abilities & anlpar::BASE_100_FULL != 0 {
429        return (Speed::Mbps100, Duplex::Full);
430    }
431
432    if abilities & anlpar::BASE_100_HALF != 0 {
433        return (Speed::Mbps100, Duplex::Half);
434    }
435
436    if abilities & anlpar::BASE_10_FULL != 0 {
437        return (Speed::Mbps10, Duplex::Full);
438    }
439
440    (Speed::Mbps10, Duplex::Half)
441}
442
443#[cfg(test)]
444mod tests {
445    use super::{
446        anlpar, bmcr, bmsr, mdio_read, mdio_write, mii_address_value, resolve_link_from_anlpar,
447        wait_for_mdio_idle, PhyError, ANAR, ANLPAR, BMCR, BMSR, PHYIDR1, PHYIDR2,
448    };
449    use crate::regs;
450
451    #[test]
452    fn phy_id_register_constants_match_standard_register_map() {
453        assert_eq!(BMCR, 0x00);
454        assert_eq!(BMSR, 0x01);
455        assert_eq!(PHYIDR1, 0x02);
456        assert_eq!(PHYIDR2, 0x03);
457        assert_eq!(ANAR, 0x04);
458        assert_eq!(ANLPAR, 0x05);
459    }
460
461    #[test]
462    fn phy_bit_constants_match_clause_22_layout() {
463        assert_eq!(bmcr::RESET, 1 << 15);
464        assert_eq!(bmcr::SPEED_100, 1 << 13);
465        assert_eq!(bmcr::ANEN, 1 << 12);
466        assert_eq!(bmcr::RESTART_AN, 1 << 9);
467        assert_eq!(bmcr::FULL_DUPLEX, 1 << 8);
468        assert_eq!(bmsr::LINK_STATUS, 1 << 2);
469        assert_eq!(bmsr::AN_COMPLETE, 1 << 5);
470    }
471
472    #[test]
473    fn mii_address_value_packs_write_transaction() {
474        let value = mii_address_value(0x1f, 0x12, 0b0001, true);
475
476        assert_ne!(value & regs::bits::miiaddr::MII_BUSY, 0);
477        assert_ne!(value & regs::bits::miiaddr::MII_WRITE, 0);
478        assert_eq!(
479            (value & regs::bits::miiaddr::PHY_ADDR_MASK) >> regs::bits::miiaddr::PHY_ADDR_SHIFT,
480            0x1f
481        );
482        assert_eq!(
483            (value & regs::bits::miiaddr::REG_ADDR_MASK) >> regs::bits::miiaddr::REG_ADDR_SHIFT,
484            0x12
485        );
486        assert_eq!(
487            (value & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK)
488                >> regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
489            0b0001
490        );
491    }
492
493    #[test]
494    fn mii_address_value_packs_read_transaction_without_write_bit() {
495        let value = mii_address_value(0x00, 0x03, 0b0100, false);
496
497        assert_ne!(value & regs::bits::miiaddr::MII_BUSY, 0);
498        assert_eq!(value & regs::bits::miiaddr::MII_WRITE, 0);
499        assert_eq!(
500            (value & regs::bits::miiaddr::REG_ADDR_MASK) >> regs::bits::miiaddr::REG_ADDR_SHIFT,
501            0x03
502        );
503        assert_eq!(
504            (value & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK)
505                >> regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
506            0b0100
507        );
508    }
509
510    #[test]
511    fn mii_address_value_masks_phy_register_and_clock_fields() {
512        let value = mii_address_value(0xff, 0xfe, 0xff, true);
513
514        assert_eq!(
515            (value & regs::bits::miiaddr::PHY_ADDR_MASK) >> regs::bits::miiaddr::PHY_ADDR_SHIFT,
516            0x1f
517        );
518        assert_eq!(
519            (value & regs::bits::miiaddr::REG_ADDR_MASK) >> regs::bits::miiaddr::REG_ADDR_SHIFT,
520            0x1e
521        );
522        assert_eq!(
523            (value & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK)
524                >> regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
525            0x0f
526        );
527    }
528
529    #[test]
530    fn wait_for_mdio_idle_returns_ok_after_busy_clears() {
531        let mut polls = 0usize;
532        let result = wait_for_mdio_idle(|| {
533            polls += 1;
534            if polls < 3 {
535                regs::bits::miiaddr::MII_BUSY
536            } else {
537                0
538            }
539        });
540
541        assert_eq!(result, Ok(()));
542        assert_eq!(polls, 3);
543    }
544
545    #[test]
546    fn wait_for_mdio_idle_times_out_when_busy_stays_set() {
547        let result = wait_for_mdio_idle(|| regs::bits::miiaddr::MII_BUSY);
548
549        assert_eq!(result, Err(PhyError::MdioTimeout));
550    }
551
552    #[test]
553    fn mdio_write_preserves_csr_clock_range_and_writes_data() {
554        regs::reset_test_registers();
555        regs::write(
556            regs::mac::MII_ADDR,
557            0b0101 << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
558        );
559
560        mdio_write(0x03, 0x1f, 0xCAFE).unwrap();
561
562        assert_eq!(regs::read(regs::mac::MII_DATA), 0xCAFE);
563        assert_eq!(
564            regs::read(regs::mac::MII_ADDR) & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK,
565            0b0101 << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT
566        );
567    }
568
569    #[test]
570    fn mdio_read_preserves_csr_clock_range_and_returns_data() {
571        regs::reset_test_registers();
572        regs::write(regs::mac::MII_DATA, 0xBEEF);
573        regs::write(
574            regs::mac::MII_ADDR,
575            0b0011 << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT,
576        );
577
578        let value = mdio_read(0x03, 0x1f).unwrap();
579
580        assert_eq!(value, 0xBEEF);
581        assert_eq!(
582            regs::read(regs::mac::MII_ADDR) & regs::bits::miiaddr::CSR_CLOCK_RANGE_MASK,
583            0b0011 << regs::bits::miiaddr::CSR_CLOCK_RANGE_SHIFT
584        );
585    }
586
587    #[test]
588    fn mdio_transaction_returns_timeout_when_bus_is_busy() {
589        regs::reset_test_registers();
590        regs::write(regs::mac::MII_ADDR, regs::bits::miiaddr::MII_BUSY);
591
592        assert_eq!(mdio_read(0, BMSR), Err(PhyError::MdioTimeout));
593        assert_eq!(mdio_write(0, BMCR, 0), Err(PhyError::MdioTimeout));
594    }
595
596    #[test]
597    fn negotiation_prefers_highest_common_mode() {
598        assert_eq!(
599            resolve_link_from_anlpar(anlpar::BASE_100_FULL),
600            (super::Speed::Mbps100, super::Duplex::Full)
601        );
602        assert_eq!(
603            resolve_link_from_anlpar(anlpar::BASE_100_HALF),
604            (super::Speed::Mbps100, super::Duplex::Half)
605        );
606        assert_eq!(
607            resolve_link_from_anlpar(anlpar::BASE_10_FULL),
608            (super::Speed::Mbps10, super::Duplex::Full)
609        );
610        assert_eq!(
611            resolve_link_from_anlpar(0),
612            (super::Speed::Mbps10, super::Duplex::Half)
613        );
614    }
615
616    #[test]
617    fn negotiation_uses_ethernet_priority_order_for_combined_abilities() {
618        let all_modes = anlpar::BASE_10_HALF
619            | anlpar::BASE_10_FULL
620            | anlpar::BASE_100_HALF
621            | anlpar::BASE_100_FULL;
622
623        assert_eq!(
624            resolve_link_from_anlpar(all_modes),
625            (super::Speed::Mbps100, super::Duplex::Full)
626        );
627        assert_eq!(
628            resolve_link_from_anlpar(anlpar::BASE_10_FULL | anlpar::BASE_100_HALF),
629            (super::Speed::Mbps100, super::Duplex::Half)
630        );
631    }
632}