use alloc::boxed::Box;
use alloc::vec::Vec;
use core::sync::atomic::{Ordering, fence};
use tock_registers::interfaces::{ReadWriteable, Readable, Writeable};
use super::desc::{
BUF_SIZE, DmaDesc, RDES0_ES, RDES0_FL_MASK, RDES0_FL_SHIFT, RDES0_OWN, RDES1_RBS1_MASK,
RDES1_RER, RX_RING_SIZE, TDES0_OWN, TDES1_FS, TDES1_IC, TDES1_LS, TDES1_TBS1_MASK, TDES1_TER,
TX_RING_SIZE,
};
use super::mdio::{mdio_read, mdio_write};
use super::regs::{
Addr0High, DmaBusMode, DmaOperation, DmaStatus, Dwc3DmaRegs, Dwc3GmacRegs, FrameFilter,
MacControl, dma_regs, gmac_regs,
};
use crate::utils::cache::{dcache_clean_range, dcache_invalidate_range};
pub const ETH_BASE: usize = 0x0407_0000;
const PHY_ADDR: u32 = 0;
const CLKGEN_BASE: usize = 0x0300_2000;
const REG_CLK_EN_0: usize = 0x000;
const CLKEN0_ETH_MASK: u32 = (1 << 25) | (1 << 26);
const SOFT_RSTN3_EPHY_MASK: u32 = (1 << 0) | (1 << 1);
const MIN_ETH_FRAME: usize = 60;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EthError {
Again,
NoMemory,
BadParam,
}
pub type EthResult<T> = Result<T, EthError>;
#[repr(C, align(64))]
struct DmaPktBuf([u8; BUF_SIZE]);
impl DmaPktBuf {
fn new() -> Self {
Self([0u8; BUF_SIZE])
}
}
pub struct CvitekEthNic {
base: usize,
mac_addr: [u8; 6],
tx_descs: Vec<DmaDesc>,
rx_descs: Vec<DmaDesc>,
tx_bufs: Vec<Box<DmaPktBuf>>,
rx_bufs: Vec<Box<DmaPktBuf>>,
tx_head: usize,
tx_tail: usize,
rx_cur: usize,
rx_count: u32,
tx_count: u32,
}
unsafe impl Send for CvitekEthNic {}
unsafe impl Sync for CvitekEthNic {}
impl CvitekEthNic {
pub const TX_QUEUE_SIZE: usize = TX_RING_SIZE;
pub const RX_QUEUE_SIZE: usize = RX_RING_SIZE;
pub const MAX_FRAME_LEN: usize = BUF_SIZE;
#[inline]
fn gmac(&self) -> &Dwc3GmacRegs {
unsafe { gmac_regs(self.base) }
}
#[inline]
fn dma(&self) -> &Dwc3DmaRegs {
unsafe { dma_regs(self.base) }
}
fn dma_reset(&self) {
let dma = self.dma();
dma.bus_mode.modify(DmaBusMode::SWR::SET);
let mut t = 100_000u32;
while dma.bus_mode.is_set(DmaBusMode::SWR) {
t = t.wrapping_sub(1);
if t == 0 {
log::warn!("cvitek-eth: DMA reset timeout");
break;
}
}
}
fn read_mac_from_hw(&self) -> [u8; 6] {
let gmac = self.gmac();
let hi = gmac.addr0_high.get();
let lo = gmac.addr0_low.get();
[
(lo & 0xFF) as u8,
((lo >> 8) & 0xFF) as u8,
((lo >> 16) & 0xFF) as u8,
((lo >> 24) & 0xFF) as u8,
(hi & 0xFF) as u8,
((hi >> 8) & 0xFF) as u8,
]
}
fn set_mac_hw(&self, m: &[u8; 6]) {
let lo = (m[0] as u32)
| ((m[1] as u32) << 8)
| ((m[2] as u32) << 16)
| ((m[3] as u32) << 24);
let hi_field = (m[4] as u32) | ((m[5] as u32) << 8);
self.gmac().addr0_low.set(lo);
self.gmac()
.addr0_high
.write(Addr0High::ADDR_HI.val(hi_field) + Addr0High::ADDR_ENABLE::SET);
}
fn flush_desc(desc: &DmaDesc) {
dcache_clean_range(desc as *const DmaDesc as usize, core::mem::size_of::<DmaDesc>());
}
fn invalidate_desc(desc: &DmaDesc) {
dcache_invalidate_range(desc as *const DmaDesc as usize, core::mem::size_of::<DmaDesc>());
}
fn setup_tx_ring(&mut self) {
for i in 0..TX_RING_SIZE {
let buf_pa = self.tx_bufs[i].0.as_ptr() as u32;
let d = &mut self.tx_descs[i];
d.des0 = 0;
d.des1 = if i == TX_RING_SIZE - 1 { TDES1_TER } else { 0 };
d.des2 = buf_pa;
d.des3 = 0;
Self::flush_desc(d);
}
}
fn setup_rx_ring(&mut self) {
for i in 0..RX_RING_SIZE {
let data_pa = self.rx_bufs[i].0.as_ptr() as u32;
let d = &mut self.rx_descs[i];
d.des2 = data_pa;
d.des1 = {
let mut v = (BUF_SIZE as u32).min(RDES1_RBS1_MASK);
if i == RX_RING_SIZE - 1 {
v |= RDES1_RER;
}
v
};
d.des3 = 0;
fence(Ordering::Release);
d.des0 = RDES0_OWN;
Self::flush_desc(d);
}
}
unsafe fn enable_clocks_and_release_resets() {
unsafe {
let clk_en0 = (CLKGEN_BASE + REG_CLK_EN_0) as *mut u32;
let v = core::ptr::read_volatile(clk_en0);
core::ptr::write_volatile(clk_en0, v | CLKEN0_ETH_MASK);
}
let rstc = unsafe { crate::rstc::Rstc::new() };
let regs = rstc.regs();
let v0 = regs.soft_rstn_0.get();
regs.soft_rstn_0.set(v0 | (1 << 12));
let v3 = regs.soft_rstn_3.get();
regs.soft_rstn_3.set(v3 | SOFT_RSTN3_EPHY_MASK);
for _ in 0..2_000_000u32 {
core::hint::spin_loop();
}
}
pub fn init(base: usize) -> EthResult<Self> {
let mut tx_descs = Vec::with_capacity(TX_RING_SIZE);
let mut rx_descs = Vec::with_capacity(RX_RING_SIZE);
for _ in 0..TX_RING_SIZE {
tx_descs.push(DmaDesc::zero());
}
for _ in 0..RX_RING_SIZE {
rx_descs.push(DmaDesc::zero());
}
let mut tx_bufs: Vec<Box<DmaPktBuf>> = Vec::with_capacity(TX_RING_SIZE);
let mut rx_bufs: Vec<Box<DmaPktBuf>> = Vec::with_capacity(RX_RING_SIZE);
for _ in 0..TX_RING_SIZE {
tx_bufs.push(Box::new(DmaPktBuf::new()));
}
for _ in 0..RX_RING_SIZE {
rx_bufs.push(Box::new(DmaPktBuf::new()));
}
let mut nic = Self {
base,
mac_addr: [0; 6],
tx_descs,
rx_descs,
tx_bufs,
rx_bufs,
tx_head: 0,
tx_tail: 0,
rx_cur: 0,
rx_count: 0,
tx_count: 0,
};
let ver = nic.gmac().version.get();
log::info!("cvitek-eth: DWMAC version {:#x}", ver);
nic.mac_addr = nic.read_mac_from_hw();
if nic.mac_addr == [0; 6] || nic.mac_addr == [0xFF; 6] {
nic.mac_addr = [0x00, 0x50, 0x43, 0x02, 0x02, 0x02];
}
log::info!(
"cvitek-eth: MAC {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
nic.mac_addr[0], nic.mac_addr[1], nic.mac_addr[2],
nic.mac_addr[3], nic.mac_addr[4], nic.mac_addr[5],
);
unsafe {
Self::enable_clocks_and_release_resets();
}
nic.dma_reset();
nic.setup_tx_ring();
nic.setup_rx_ring();
nic.dma().bus_mode.write(
DmaBusMode::PBL.val(8)
+ DmaBusMode::DSL.val(12)
+ DmaBusMode::FB::SET
+ DmaBusMode::AAL::SET,
);
nic.dma().tx_base.set(nic.tx_descs.as_ptr() as u32);
nic.dma().rx_base.set(nic.rx_descs.as_ptr() as u32);
nic.gmac().int_mask.set(0x60F);
nic.dma().intr_ena.set(0);
nic.set_mac_hw(&nic.mac_addr);
nic.gmac()
.frame_filter
.write(FrameFilter::PR::SET + FrameFilter::RA::SET);
nic.gmac().hash_high.set(0xFFFF_FFFF);
nic.gmac().hash_low.set(0xFFFF_FFFF);
mdio_write(nic.gmac(), PHY_ADDR, 0, 0x8000);
for _ in 0..1_000_000u32 {
core::hint::spin_loop();
}
for _ in 0..100u32 {
if mdio_read(nic.gmac(), PHY_ADDR, 0) & 0x8000 == 0 {
break;
}
for _ in 0..100_000u32 {
core::hint::spin_loop();
}
}
mdio_write(nic.gmac(), PHY_ADDR, 0, 0x3300);
let mut link_up = false;
let mut bmcr = 0u16;
#[allow(unused_assignments)]
let mut bmsr = 0u16;
let mut lpa = 0u16;
for i in 0..2500u32 {
let _ = mdio_read(nic.gmac(), PHY_ADDR, 1);
bmsr = mdio_read(nic.gmac(), PHY_ADDR, 1);
if bmsr & 4 != 0 {
bmcr = mdio_read(nic.gmac(), PHY_ADDR, 0);
lpa = mdio_read(nic.gmac(), PHY_ADDR, 5);
log::info!(
"cvitek-eth: link UP after {} polls bmcr=0x{:04x} bmsr=0x{:04x} lpa=0x{:04x}",
i, bmcr, bmsr, lpa
);
link_up = true;
break;
}
for _ in 0..200_000u32 {
core::hint::spin_loop();
}
}
if !link_up {
bmcr = mdio_read(nic.gmac(), PHY_ADDR, 0);
bmsr = mdio_read(nic.gmac(), PHY_ADDR, 1);
lpa = mdio_read(nic.gmac(), PHY_ADDR, 5);
log::warn!(
"cvitek-eth: link still DOWN after autoneg timeout, continuing (bmcr=0x{:04x} bmsr=0x{:04x} lpa=0x{:04x})",
bmcr, bmsr, lpa
);
}
let anar = mdio_read(nic.gmac(), PHY_ADDR, 4);
let common = anar & lpa;
let (speed_100m, full_duplex) = if common & (1 << 8) != 0 {
(true, true)
} else if common & (1 << 7) != 0 {
(true, false)
} else if common & (1 << 6) != 0 {
(false, true)
} else if common & (1 << 5) != 0 {
(false, false)
} else {
(
bmcr & (1 << 13) != 0 || true,
bmcr & (1 << 8) != 0 || true,
)
};
log::info!(
"cvitek-eth: link mode = {} {} (anar=0x{:04x} lpa=0x{:04x})",
if speed_100m { "100M" } else { "10M" },
if full_duplex { "FD" } else { "HD" },
anar, lpa
);
let mut mc = MacControl::PS::SET + MacControl::TE::SET + MacControl::RE::SET;
if speed_100m {
mc += MacControl::FES::SET;
}
if full_duplex {
mc += MacControl::DM::SET;
}
nic.gmac().mac_control.write(mc);
nic.gmac().mmc_cntrl.set(0x01);
nic.dma().operation.write(
DmaOperation::TSF::SET
+ DmaOperation::RSF::SET
+ DmaOperation::OSF::SET
+ DmaOperation::FTF::SET,
);
let mut t = 100_000u32;
while nic.dma().operation.is_set(DmaOperation::FTF) {
t = t.wrapping_sub(1);
if t == 0 {
break;
}
}
nic.dma()
.operation
.modify(DmaOperation::ST::SET + DmaOperation::SR::SET);
nic.dma().rx_poll.set(1);
let g = nic.gmac();
let d = nic.dma();
log::debug!(
"cvitek-eth: DUMP mac_ctl=0x{:08x} frame_filter=0x{:08x} addr0_hi=0x{:08x} addr0_lo=0x{:08x}",
g.mac_control.get(), g.frame_filter.get(), g.addr0_high.get(), g.addr0_low.get()
);
log::debug!(
"cvitek-eth: DUMP dma_bus=0x{:08x} dma_op=0x{:08x} dma_status=0x{:08x} tx_base=0x{:08x} rx_base=0x{:08x}",
d.bus_mode.get(), d.operation.get(), d.status.get(), d.tx_base.get(), d.rx_base.get()
);
log::info!("cvitek-eth: initialized OK");
Ok(nic)
}
pub fn mac_address(&self) -> [u8; 6] {
self.mac_addr
}
pub fn can_transmit(&self) -> bool {
Self::invalidate_desc(&self.tx_descs[self.tx_head]);
let des0 = unsafe { core::ptr::read_volatile(&self.tx_descs[self.tx_head].des0) };
des0 & TDES0_OWN == 0
}
pub fn can_receive(&self) -> bool {
Self::invalidate_desc(&self.rx_descs[self.rx_cur]);
let des0 = unsafe { core::ptr::read_volatile(&self.rx_descs[self.rx_cur].des0) };
des0 & RDES0_OWN == 0
}
pub fn reclaim_tx(&mut self) {
while self.tx_tail != self.tx_head {
Self::invalidate_desc(&self.tx_descs[self.tx_tail]);
let des0 = unsafe { core::ptr::read_volatile(&self.tx_descs[self.tx_tail].des0) };
if des0 & TDES0_OWN != 0 {
break;
}
self.tx_tail = (self.tx_tail + 1) % TX_RING_SIZE;
}
}
pub fn transmit(&mut self, packet: &[u8]) -> EthResult<()> {
if packet.len() > Self::MAX_FRAME_LEN {
return Err(EthError::BadParam);
}
self.reclaim_tx();
let idx = self.tx_head;
Self::invalidate_desc(&self.tx_descs[idx]);
let des0 = unsafe { core::ptr::read_volatile(&self.tx_descs[idx].des0) };
if des0 & TDES0_OWN != 0 {
return Err(EthError::Again);
}
let buf = &mut self.tx_bufs[idx].0;
buf[..packet.len()].copy_from_slice(packet);
let mut len = packet.len();
if len < MIN_ETH_FRAME {
for byte in &mut buf[len..MIN_ETH_FRAME] {
*byte = 0;
}
len = MIN_ETH_FRAME;
}
let data_pa = buf.as_ptr() as usize;
dcache_clean_range(data_pa, len);
let d = &mut self.tx_descs[idx];
let mut tdes1 = TDES1_IC | TDES1_FS | TDES1_LS | ((len as u32) & TDES1_TBS1_MASK);
if idx == TX_RING_SIZE - 1 {
tdes1 |= TDES1_TER;
}
unsafe {
core::ptr::write_volatile(&mut d.des2, data_pa as u32);
core::ptr::write_volatile(&mut d.des1, tdes1);
}
fence(Ordering::Release);
unsafe { core::ptr::write_volatile(&mut d.des0, TDES0_OWN) };
Self::flush_desc(d);
dcache_clean_range(data_pa, len);
self.tx_count = self.tx_count.wrapping_add(1);
if log::log_enabled!(log::Level::Trace) {
let n = packet.len().min(20);
log::trace!(
"cvitek-eth: TX#{} idx={} len={} hdr={:02x?}",
self.tx_count, idx, len, &packet[..n]
);
}
self.tx_head = (self.tx_head + 1) % TX_RING_SIZE;
self.dma().tx_poll.set(1);
Ok(())
}
pub fn receive(&mut self) -> EthResult<RxToken<'_>> {
let idx = self.rx_cur;
if self.dma().status.is_set(DmaStatus::RI) {
self.dma()
.status
.write(DmaStatus::RI::SET + DmaStatus::NIS::SET);
}
Self::invalidate_desc(&self.rx_descs[idx]);
let des0 = unsafe { core::ptr::read_volatile(&self.rx_descs[idx].des0) };
if des0 & RDES0_OWN != 0 {
return Err(EthError::Again);
}
if des0 & RDES0_ES != 0 {
self.requeue_rx(idx);
self.rx_cur = (self.rx_cur + 1) % RX_RING_SIZE;
return Err(EthError::Again);
}
let frame_len = ((des0 & RDES0_FL_MASK) >> RDES0_FL_SHIFT) as usize;
let frame_len = if frame_len >= 4 { frame_len - 4 } else { frame_len }
.min(BUF_SIZE);
let buf_va = self.rx_bufs[idx].0.as_ptr() as usize;
dcache_invalidate_range(buf_va, frame_len);
self.rx_cur = (self.rx_cur + 1) % RX_RING_SIZE;
self.rx_count = self.rx_count.wrapping_add(1);
if log::log_enabled!(log::Level::Trace) {
let raw = &self.rx_bufs[idx].0[..];
let n = frame_len.min(20);
log::trace!(
"cvitek-eth: RX#{} idx={} len={} hdr={:02x?}",
self.rx_count, idx, frame_len, &raw[..n]
);
}
Ok(RxToken {
nic: self,
slot: idx,
len: frame_len,
})
}
fn requeue_rx(&mut self, slot: usize) {
let pa = self.rx_bufs[slot].0.as_ptr() as u32;
let d = &mut self.rx_descs[slot];
unsafe {
core::ptr::write_volatile(&mut d.des2, pa);
let mut rdes1 = (BUF_SIZE as u32).min(RDES1_RBS1_MASK);
if slot == RX_RING_SIZE - 1 {
rdes1 |= RDES1_RER;
}
core::ptr::write_volatile(&mut d.des1, rdes1);
core::ptr::write_volatile(&mut d.des3, 0);
fence(Ordering::Release);
core::ptr::write_volatile(&mut d.des0, RDES0_OWN);
}
Self::flush_desc(d);
self.dma()
.status
.write(DmaStatus::RI::SET + DmaStatus::NIS::SET);
self.dma().rx_poll.set(1);
}
}
pub struct RxToken<'a> {
nic: &'a mut CvitekEthNic,
slot: usize,
len: usize,
}
impl<'a> RxToken<'a> {
#[inline]
pub fn frame(&self) -> &[u8] {
&self.nic.rx_bufs[self.slot].0[..self.len]
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<'a> Drop for RxToken<'a> {
fn drop(&mut self) {
let slot = self.slot;
self.nic.requeue_rx(slot);
}
}