#![allow(missing_docs)]
#![allow(clippy::std_instead_of_core, clippy::std_instead_of_alloc)]
extern crate std;
use core::cell::RefCell;
use std::collections::HashMap;
use std::vec;
use std::vec::Vec;
use crate::driver::error::Result;
use crate::hal::mdio::MdioBus;
use crate::internal::phy_regs::standard::{anlpar, bmsr, phy_reg};
#[derive(Debug, Default)]
pub struct MockMdioBus {
registers: RefCell<HashMap<(u8, u8), u16>>,
write_log: RefCell<Vec<(u8, u8, u16)>>,
busy: RefCell<bool>,
}
impl MockMdioBus {
pub fn new() -> Self {
Self::default()
}
pub fn set_register(&self, phy_addr: u8, reg_addr: u8, value: u16) {
self.registers
.borrow_mut()
.insert((phy_addr, reg_addr), value);
}
pub fn get_register(&self, phy_addr: u8, reg_addr: u8) -> Option<u16> {
self.registers.borrow().get(&(phy_addr, reg_addr)).copied()
}
pub fn get_writes(&self) -> Vec<(u8, u8, u16)> {
self.write_log.borrow().clone()
}
pub fn clear_writes(&self) {
self.write_log.borrow_mut().clear();
}
pub fn set_busy(&self, busy: bool) {
*self.busy.borrow_mut() = busy;
}
pub fn setup_lan8720a(&self, phy_addr: u8) {
self.set_register(phy_addr, phy_reg::PHYIDR1, 0x0007);
self.set_register(phy_addr, phy_reg::PHYIDR2, 0xC0F1);
let bmsr_value = bmsr::TX_FD_CAPABLE
| bmsr::TX_HD_CAPABLE
| bmsr::T10_FD_CAPABLE
| bmsr::T10_HD_CAPABLE
| bmsr::AN_ABILITY
| bmsr::EXT_CAPABLE;
self.set_register(phy_addr, phy_reg::BMSR, bmsr_value);
self.set_register(phy_addr, phy_reg::BMCR, 0x1000);
self.set_register(phy_addr, phy_reg::ANAR, 0x01E1);
self.set_register(phy_addr, phy_reg::ANLPAR, 0x0000);
}
pub fn simulate_link_up_100_fd(&self, phy_addr: u8) {
let mut bmsr_val = self.get_register(phy_addr, phy_reg::BMSR).unwrap_or(0);
bmsr_val |= bmsr::LINK_STATUS | bmsr::AN_COMPLETE;
self.set_register(phy_addr, phy_reg::BMSR, bmsr_val);
let anlpar_val = anlpar::SELECTOR_802_3
| anlpar::CAN_100_FD
| anlpar::CAN_100_HD
| anlpar::CAN_10_FD
| anlpar::CAN_10_HD;
self.set_register(phy_addr, phy_reg::ANLPAR, anlpar_val);
}
pub fn simulate_link_up_10_hd(&self, phy_addr: u8) {
let mut bmsr_val = self.get_register(phy_addr, phy_reg::BMSR).unwrap_or(0);
bmsr_val |= bmsr::LINK_STATUS | bmsr::AN_COMPLETE;
self.set_register(phy_addr, phy_reg::BMSR, bmsr_val);
let anlpar_val = anlpar::SELECTOR_802_3 | anlpar::CAN_10_HD;
self.set_register(phy_addr, phy_reg::ANLPAR, anlpar_val);
}
pub fn simulate_link_down(&self, phy_addr: u8) {
let mut bmsr_val = self.get_register(phy_addr, phy_reg::BMSR).unwrap_or(0);
bmsr_val &= !(bmsr::LINK_STATUS | bmsr::AN_COMPLETE);
self.set_register(phy_addr, phy_reg::BMSR, bmsr_val);
self.set_register(phy_addr, phy_reg::ANLPAR, 0x0000);
}
}
impl MdioBus for MockMdioBus {
fn read(&mut self, phy_addr: u8, reg_addr: u8) -> Result<u16> {
Ok(self
.registers
.borrow()
.get(&(phy_addr, reg_addr))
.copied()
.unwrap_or(0))
}
fn write(&mut self, phy_addr: u8, reg_addr: u8, value: u16) -> Result<()> {
self.write_log
.borrow_mut()
.push((phy_addr, reg_addr, value));
self.registers
.borrow_mut()
.insert((phy_addr, reg_addr), value);
Ok(())
}
fn is_busy(&self) -> bool {
*self.busy.borrow()
}
}
#[derive(Debug, Default)]
pub struct MockDelay {
total_ns: RefCell<u64>,
}
impl MockDelay {
pub fn new() -> Self {
Self::default()
}
pub fn total_ns(&self) -> u64 {
*self.total_ns.borrow()
}
pub fn total_ms(&self) -> u64 {
self.total_ns() / 1_000_000
}
pub fn reset(&self) {
*self.total_ns.borrow_mut() = 0;
}
}
impl embedded_hal::delay::DelayNs for MockDelay {
fn delay_ns(&mut self, ns: u32) {
*self.total_ns.borrow_mut() += ns as u64;
}
}
#[derive(Clone, Copy, Default, Debug)]
pub struct MockDescriptor {
pub owned: bool,
pub first: bool,
pub last: bool,
pub has_error: bool,
pub frame_len: usize,
}
impl MockDescriptor {
pub fn new() -> Self {
Self::default()
}
pub fn is_owned(&self) -> bool {
self.owned
}
pub fn set_owned(&mut self) {
self.owned = true;
}
pub fn clear_owned(&mut self) {
self.owned = false;
}
pub fn is_first(&self) -> bool {
self.first
}
pub fn is_last(&self) -> bool {
self.last
}
pub fn has_error(&self) -> bool {
self.has_error
}
pub fn frame_length(&self) -> usize {
self.frame_len
}
pub fn simulate_receive(&mut self, len: usize) {
self.owned = false; self.first = true;
self.last = true;
self.has_error = false;
self.frame_len = len;
}
pub fn simulate_error(&mut self) {
self.owned = false;
self.first = true;
self.last = true;
self.has_error = true;
self.frame_len = 0;
}
pub fn simulate_fragment(&mut self, first: bool, last: bool, len: usize) {
self.owned = false;
self.first = first;
self.last = last;
self.has_error = false;
self.frame_len = len;
}
pub fn reset(&mut self) {
*self = Self::default();
}
pub fn recycle(&mut self) {
self.first = false;
self.last = false;
self.has_error = false;
self.frame_len = 0;
}
}
#[macro_export]
macro_rules! assert_reg_written {
($mdio:expr, $phy:expr, $reg:expr, $value:expr) => {
let writes = $mdio.get_writes();
assert!(
writes
.iter()
.any(|w| w.0 == $phy && w.1 == $reg && w.2 == $value),
"Expected write to PHY {} reg {} with value 0x{:04X}, but got: {:?}",
$phy,
$reg,
$value,
writes
);
};
}
#[macro_export]
macro_rules! assert_reg_written_any {
($mdio:expr, $phy:expr, $reg:expr) => {
let writes = $mdio.get_writes();
assert!(
writes.iter().any(|w| w.0 == $phy && w.1 == $reg),
"Expected write to PHY {} reg {}, but got: {:?}",
$phy,
$reg,
writes
);
};
}
pub mod phy_regs {
pub const BMCR: u8 = 0x00;
pub const BMSR: u8 = 0x01;
pub const PHYIDR1: u8 = 0x02;
pub const PHYIDR2: u8 = 0x03;
pub const ANAR: u8 = 0x04;
pub const ANLPAR: u8 = 0x05;
}
pub mod bmcr_bits {
pub const RESET: u16 = 1 << 15;
pub const LOOPBACK: u16 = 1 << 14;
pub const SPEED_100: u16 = 1 << 13;
pub const AUTO_NEG_ENABLE: u16 = 1 << 12;
pub const POWER_DOWN: u16 = 1 << 11;
pub const ISOLATE: u16 = 1 << 10;
pub const RESTART_AUTO_NEG: u16 = 1 << 9;
pub const DUPLEX_FULL: u16 = 1 << 8;
}
pub mod bmsr_bits {
pub const CAN_100_T4: u16 = 1 << 15;
pub const CAN_100_FD: u16 = 1 << 14;
pub const CAN_100_HD: u16 = 1 << 13;
pub const CAN_10_FD: u16 = 1 << 12;
pub const CAN_10_HD: u16 = 1 << 11;
pub const AUTO_NEG_COMPLETE: u16 = 1 << 5;
pub const REMOTE_FAULT: u16 = 1 << 4;
pub const CAN_AUTO_NEG: u16 = 1 << 3;
pub const LINK_STATUS: u16 = 1 << 2;
pub const JABBER_DETECT: u16 = 1 << 1;
pub const EXTENDED_CAPABLE: u16 = 1 << 0;
}
pub mod anlpar_bits {
pub const NEXT_PAGE: u16 = 1 << 15;
pub const ACK: u16 = 1 << 14;
pub const REMOTE_FAULT: u16 = 1 << 13;
pub const PAUSE_ASYM: u16 = 1 << 11;
pub const PAUSE: u16 = 1 << 10;
pub const CAN_100_T4: u16 = 1 << 9;
pub const CAN_100_FD: u16 = 1 << 8;
pub const CAN_100_HD: u16 = 1 << 7;
pub const CAN_10_FD: u16 = 1 << 6;
pub const CAN_10_HD: u16 = 1 << 5;
pub const SELECTOR_MASK: u16 = 0x1F;
pub const SELECTOR_802_3: u16 = 0x01;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mock_mdio_read_write() {
let mut mdio = MockMdioBus::new();
assert_eq!(mdio.read(0, 1).unwrap(), 0);
mdio.set_register(0, 1, 0x1234);
assert_eq!(mdio.read(0, 1).unwrap(), 0x1234);
mdio.write(0, 1, 0x5678).unwrap();
assert_eq!(mdio.read(0, 1).unwrap(), 0x5678);
assert_eq!(mdio.get_writes(), vec![(0, 1, 0x5678)]);
}
#[test]
fn mock_mdio_multiple_phys() {
let mut mdio = MockMdioBus::new();
mdio.set_register(0, 1, 0x1111);
mdio.set_register(1, 1, 0x2222);
assert_eq!(mdio.read(0, 1).unwrap(), 0x1111);
assert_eq!(mdio.read(1, 1).unwrap(), 0x2222);
}
#[test]
fn mock_delay_tracking() {
let mut delay = MockDelay::new();
embedded_hal::delay::DelayNs::delay_ns(&mut delay, 1000);
embedded_hal::delay::DelayNs::delay_ns(&mut delay, 2000);
assert_eq!(delay.total_ns(), 3000);
assert_eq!(delay.total_ms(), 0);
embedded_hal::delay::DelayNs::delay_ns(&mut delay, 1_000_000);
assert_eq!(delay.total_ms(), 1);
}
#[test]
fn mock_mdio_lan8720a_setup() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
assert_eq!(mdio.read(0, phy_regs::PHYIDR1).unwrap(), 0x0007);
assert_eq!(mdio.read(0, phy_regs::PHYIDR2).unwrap(), 0xC0F1);
let bmsr = mdio.read(0, phy_regs::BMSR).unwrap();
assert!(bmsr & bmsr_bits::CAN_100_FD != 0);
assert!(bmsr & bmsr_bits::LINK_STATUS == 0);
}
#[test]
fn mock_mdio_link_simulation() {
let mut mdio = MockMdioBus::new();
mdio.setup_lan8720a(0);
let bmsr = mdio.read(0, phy_regs::BMSR).unwrap();
assert!(bmsr & bmsr_bits::LINK_STATUS == 0);
mdio.simulate_link_up_100_fd(0);
let bmsr = mdio.read(0, phy_regs::BMSR).unwrap();
assert!(bmsr & bmsr_bits::LINK_STATUS != 0);
assert!(bmsr & bmsr_bits::AUTO_NEG_COMPLETE != 0);
let anlpar = mdio.read(0, phy_regs::ANLPAR).unwrap();
assert!(anlpar & anlpar_bits::CAN_100_FD != 0);
mdio.simulate_link_down(0);
let bmsr = mdio.read(0, phy_regs::BMSR).unwrap();
assert!(bmsr & bmsr_bits::LINK_STATUS == 0);
}
}