realtek-rtl8125 0.2.2

Minimal Realtek RTL8125 PCIe Ethernet driver
Documentation
#![no_std]

extern crate alloc;

use alloc::{boxed::Box, collections::VecDeque, sync::Arc};

use descriptor::{RING_END, RxDesc, TxDesc};
use dma_api::{DeviceDma, DmaOp};
use log::info;
use mmio_api::{Mmio, MmioAddr, MmioOp};
use queue::{QueueStart, QueueStartState, Rtl8125RxQueue, Rtl8125TxQueue};
use rdif_eth::{Event, IRxQueue, ITxQueue, Interface};
use registers::*;
use spin::Mutex;

mod descriptor;
mod hw;
mod queue;
mod registers;

const DRIVER_NAME: &str = "realtek-rtl8125";
const QUEUE_ID0: usize = 0;
const QUEUE_SIZE: usize = 256;
const RX_QUEUE_CONFIG_SIZE: usize = QUEUE_SIZE + 1;
const RX_START_THRESHOLD: usize = QUEUE_SIZE;
const MAX_PACKET: usize = 2048;
const RX_BUF_SIZE: usize = 2048;
const DMA_ALIGN: usize = 256;
const DMA_CACHE_LINE_SIZE: usize = 64;
const RX_DESC_PER_CACHE_LINE: usize = DMA_CACHE_LINE_SIZE / core::mem::size_of::<RxDesc>();
const RX_DEFERRED_REFILL_CAPACITY: usize = QUEUE_SIZE;
const LINK_DOWN_DROP_LOG_INTERVAL: u64 = 64;
const EARLY_PACKET_LOG_COUNT: u64 = 8;
const TX_SUBMIT_LOG_INTERVAL: u64 = 16;
const TX_RECLAIM_LOG_INTERVAL: u64 = 64;
const RX_RECLAIM_LOG_INTERVAL: u64 = 64;
const RX_IDLE_LOG_INTERVAL: u64 = 262_144;
const RX_OVERFLOW_REARM_IDLE_POLLS: u64 = 2048;
const TX_LINK_SAMPLE_INTERVAL: u64 = 64;
const OCP_STD_PHY_BASE: u32 = 0xa400;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChipVersion {
    Rtl8125A,
    Rtl8125B,
    Unknown(u16),
}

#[derive(Debug, Clone, Copy)]
pub struct Rtl8125Status {
    pub phy_status: u8,
    pub chip_cmd: u8,
    pub mcu: u8,
    pub intr_status: u32,
    pub intr_mask: u32,
    pub rx_config: u32,
    pub tx_config: u32,
    pub cplus_cmd: u16,
    pub rx_desc_base: u64,
}

impl Rtl8125Status {
    pub const fn link_up(&self) -> bool {
        phy_status_link_up(self.phy_status)
    }
}

#[derive(thiserror::Error, Debug)]
pub enum Error {
    #[error("unsupported PCI id {vendor:#06x}:{device:#06x}")]
    UnsupportedPciId { vendor: u16, device: u16 },
    #[error("MMIO map failed")]
    MmioMap(#[from] mmio_api::MapError),
    #[error("DMA allocation failed")]
    Dma(#[from] dma_api::DmaError),
    #[error("invalid MAC address")]
    InvalidMacAddress,
    #[error("hardware reset timed out")]
    ResetTimeout,
    #[error("wait for {operation} timed out")]
    HardwareTimeout { operation: &'static str },
    #[error("invalid OCP register address {reg:#x}")]
    InvalidOcpAddress { reg: u32 },
}

pub type Result<T> = core::result::Result<T, Error>;

pub struct Rtl8125 {
    regs: Regs,
    _mmio: Mmio,
    dma: DeviceDma,
    mac: [u8; 6],
    chip: ChipVersion,
    tx_created: bool,
    rx_created: bool,
    phy_ocp_base: u32,
    queue_start: QueueStart,
}

impl Rtl8125 {
    pub fn check_vid_did(vendor: u16, device: u16) -> bool {
        vendor == VENDOR_ID && device == DEVICE_ID_RTL8125
    }

    pub fn new(
        bar_addr: impl Into<MmioAddr>,
        bar_size: usize,
        dma_mask: u64,
        dma_op: &'static dyn DmaOp,
        mmio_op: &'static dyn MmioOp,
    ) -> Result<Self> {
        mmio_api::init(mmio_op);
        let mmio = mmio_api::ioremap(bar_addr.into(), bar_size.max(RTL8125_REGS_SIZE))?;
        let regs = Regs::new(mmio.as_nonnull_ptr());
        let dma = DeviceDma::new(dma_mask, dma_op);
        let xid = rtl8125_xid(regs);
        let chip = chip_version(xid);

        let mut dev = Self {
            regs,
            _mmio: mmio,
            dma,
            mac: [0; 6],
            chip,
            tx_created: false,
            rx_created: false,
            phy_ocp_base: OCP_STD_PHY_BASE,
            queue_start: Arc::new(Mutex::new(QueueStartState::default())),
        };
        dev.init()?;
        info!(
            "RTL8125 device initialized: chip={:?}, xid={:#x}, \
             mac={:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}, status={:?}",
            dev.chip,
            xid,
            dev.mac[0],
            dev.mac[1],
            dev.mac[2],
            dev.mac[3],
            dev.mac[4],
            dev.mac[5],
            dev.status(),
        );
        Ok(dev)
    }

    pub fn init(&mut self) -> Result<()> {
        self.disable_irq();
        self.ack_events(u32::MAX);
        self.reset()?;
        self.hw_init_8125()?;

        self.mac = self.read_mac_address()?;
        self.set_mac_address(self.mac);
        self.regs.configure_cplus(self.dma.dma_mask());
        self.regs.write_default_rx_config();
        self.regs.write_default_tx_config();
        self.regs.write_rx_max_size(RX_BUF_SIZE as u16 + 1);
        self.regs.disable_interrupt_mitigation();
        self.hw_start_8125()?;
        self.hw_phy_config()?;
        Ok(())
    }

    pub fn mac_address(&self) -> [u8; 6] {
        self.mac
    }

    pub fn chip_version(&self) -> ChipVersion {
        self.chip
    }

    pub fn poll_link(&self) -> bool {
        self.status().link_up()
    }

    pub fn status(&self) -> Rtl8125Status {
        read_status(self.regs)
    }

    fn read_mac_address(&self) -> Result<[u8; 6]> {
        let mac = self.regs.read_backup_mac();
        if is_valid_mac(mac) {
            return Ok(mac);
        }

        let mac = self.regs.read_mac();
        if is_valid_mac(mac) {
            return Ok(mac);
        }

        Err(Error::InvalidMacAddress)
    }

    fn set_mac_address(&self, mac: [u8; 6]) {
        self.regs.unlock_config();
        self.regs.write_mac(mac);
        self.regs.lock_config();
    }

    fn reset(&self) -> Result<()> {
        self.regs.request_reset();
        for _ in 0..100_000 {
            if !self.regs.reset_pending() {
                return Ok(());
            }
            core::hint::spin_loop();
        }
        Err(Error::ResetTimeout)
    }
}

impl rdif_eth::DriverGeneric for Rtl8125 {
    fn name(&self) -> &str {
        DRIVER_NAME
    }
}

impl Interface for Rtl8125 {
    fn mac_address(&self) -> [u8; 6] {
        self.mac
    }

    fn create_tx_queue(&mut self) -> Option<Box<dyn ITxQueue>> {
        if self.tx_created {
            return None;
        }

        let mut desc = self
            .dma
            .coherent_array_zero_with_align::<TxDesc>(QUEUE_SIZE, DMA_ALIGN)
            .ok()?;
        desc.set_cpu(
            QUEUE_SIZE - 1,
            TxDesc {
                opts1: RING_END,
                opts2: 0,
                addr: 0,
            },
        );

        {
            let mut start = self.queue_start.lock();
            start.tx_base = Some(desc.dma_addr().as_u64());
        }
        self.tx_created = true;
        self.maybe_start_queues();

        Some(queue::boxed_tx(Rtl8125TxQueue {
            regs: self.regs,
            desc,
            dma_mask: self.dma.dma_mask(),
            bus_addrs: [None; QUEUE_SIZE],
            next_submit: 0,
            next_reclaim: 0,
            link_up: None,
            link_down_drops: 0,
            submitted: 0,
            reclaimed: 0,
        }))
    }

    fn create_rx_queue(&mut self) -> Option<Box<dyn IRxQueue>> {
        if self.rx_created {
            return None;
        }

        let desc = self
            .dma
            .coherent_array_zero_with_align::<RxDesc>(QUEUE_SIZE, DMA_ALIGN)
            .ok()?;

        {
            let mut start = self.queue_start.lock();
            start.rx_base = Some(desc.dma_addr().as_u64());
        }
        self.rx_created = true;
        self.maybe_start_queues();

        Some(queue::boxed_rx(Rtl8125RxQueue {
            regs: self.regs,
            desc,
            dma_mask: self.dma.dma_mask(),
            start: self.queue_start.clone(),
            bus_addrs: [None; QUEUE_SIZE],
            next_submit: 0,
            next_reclaim: 0,
            idle_polls: 0,
            last_rx_rearm_idle: 0,
            submitted: 0,
            reclaimed: 0,
            rx_errors: 0,
            deferred_refill: VecDeque::with_capacity(RX_DEFERRED_REFILL_CAPACITY),
        }))
    }

    fn enable_irq(&mut self) {
        self.ack_events(u32::MAX);
        self.regs.write_interrupt_mask(DEFAULT_IRQ_MASK);
    }

    fn disable_irq(&mut self) {
        self.regs.write_interrupt_mask(0);
    }

    fn is_irq_enabled(&self) -> bool {
        self.regs.read_interrupt_mask() != 0
    }

    fn handle_irq(&mut self) -> Event {
        let status = self.regs.read_interrupt_status();
        if status == 0 || status == u32::MAX {
            return Event::none();
        }

        self.ack_events(status);

        let mut event = Event::none();
        if irq_has_tx_event(status) {
            event.tx_queue.insert(QUEUE_ID0);
        }
        if irq_has_rx_event(status) {
            event.rx_queue.insert(QUEUE_ID0);
        }
        if irq_has_link_change(status) {
            info!("RTL8125 irq link change: status={:?}", self.status());
        }
        event
    }
}

fn rtl8125_xid(regs: Regs) -> u16 {
    ((regs.read_tx_config() >> 20) & 0x0fcf) as u16
}

fn read_status(regs: Regs) -> Rtl8125Status {
    Rtl8125Status {
        phy_status: regs.read_phy_status(),
        chip_cmd: regs.read_chip_cmd(),
        mcu: regs.read_mcu(),
        intr_status: regs.read_interrupt_status(),
        intr_mask: regs.read_interrupt_mask(),
        rx_config: regs.read_rx_config(),
        tx_config: regs.read_tx_config(),
        cplus_cmd: regs.read_cplus_cmd(),
        rx_desc_base: regs.read_rx_desc_base(),
    }
}

pub(crate) fn set_rx_mode(regs: Regs) {
    regs.set_multicast_filter_all();
    regs.set_rx_accept_mode();
}

fn chip_version(xid: u16) -> ChipVersion {
    if xid & 0x07cf == 0x0641 {
        ChipVersion::Rtl8125B
    } else if xid & 0x07cf == 0x0609 {
        ChipVersion::Rtl8125A
    } else {
        ChipVersion::Unknown(xid)
    }
}

fn is_valid_mac(mac: [u8; 6]) -> bool {
    mac != [0; 6] && mac != [0xff; 6] && mac[0] & 1 == 0
}

const _: () = {
    assert!(size_of::<TxDesc>() == 16);
    assert!(size_of::<RxDesc>() == 16);
};