Compare commits

..

3 Commits

Author SHA1 Message Date
c5a734210a Ethernet and smoltcp/embassy-net support
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2025-07-07 11:38:13 +02:00
037668e5a9 Merge pull request 'small docs improvements' (#6) from small-docs-improvements into main
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
Reviewed-on: #6
2025-06-26 20:32:21 +02:00
138cbc5ea7 small docs improvements
Some checks failed
ci / Check build (push) Has been cancelled
ci / Check formatting (push) Has been cancelled
ci / Check Documentation Build (push) Has been cancelled
ci / Clippy (push) Has been cancelled
ci / Check build (pull_request) Has been cancelled
ci / Check formatting (pull_request) Has been cancelled
ci / Check Documentation Build (pull_request) Has been cancelled
ci / Clippy (pull_request) Has been cancelled
2025-06-26 20:31:53 +02:00
13 changed files with 653 additions and 164 deletions

View File

@@ -30,6 +30,7 @@ embedded-hal = "1"
embedded-hal-async = "1"
fugit = "0.3"
log = "0.4"
rand = { version = "0.9", default-features = false, features = ["small_rng"] }
# embassy-executor = { git = "https://github.com/us-irs/embassy", path = "../embassy/embassy-executor", branch = "local-cortex-ar", features = [
# "arch-cortex-ar",
@@ -41,6 +42,7 @@ embassy-executor = { path = "../../../../Rust/embassy/embassy-executor", feature
]}
# embassy-time = { git = "https://github.com/us-irs/embassy", branch = "local-cortex-ar", version = "0.4", features = ["tick-hz-1_000_000"] }
embassy-time = { path = "../../../../Rust/embassy/embassy-time", version = "0.4", features = ["tick-hz-1_000_000"] }
embassy-net = { path = "../../../../Rust/embassy/embassy-net", version = "0.7", features = ["dhcpv4"] }
heapless = "0.8"
axi-uartlite = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uartlite.git" }
axi-uart16550 = { git = "https://egit.irs.uni-stuttgart.de/rust/axi-uart16550.git" }

View File

@@ -8,19 +8,20 @@ use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use log::{error, info};
use rand::{RngCore, SeedableRng};
use zedboard::PS_CLOCK_FREQUENCY;
use zynq7000_hal::{
BootMode,
clocks::Clocks,
configure_level_shifter,
eth::{EthernetConfig, EthernetLowLevel},
eth::{AlignedBuffer, EthernetConfig, EthernetLowLevel},
gic::{GicConfigurator, GicInterruptHelper, Interrupt},
gpio::{GpioPins, Output, PinState},
gtc::Gtc,
uart::{ClkConfigRaw, Uart, UartConfig},
BootMode,
};
use zynq7000::{slcr::LevelShifterConfig, PsPeripherals};
use zynq7000::{PsPeripherals, slcr::LevelShifterConfig};
use zynq7000_rt::{self as _, mmu::section_attrs::SHAREABLE_DEVICE, mmu_l1_table_mut};
const INIT_STRING: &str = "-- Zynq 7000 Zedboard Ethernet Example --\n\r";
@@ -36,10 +37,6 @@ static TX_DESCRIPTORS: static_cell::ConstStaticCell<
MaybeUninit<[zynq7000_hal::eth::tx_descr::Descriptor; 32]>,
> = static_cell::ConstStaticCell::new(MaybeUninit::uninit());
#[repr(align(32))]
#[derive(Debug, Clone, Copy)]
pub struct AlignedBuffer(pub [u8; zynq7000_hal::eth::MTU]);
const NUM_RX_BUFS: usize = 32;
const NUM_TX_BUFS: usize = 32;
@@ -61,9 +58,16 @@ pub extern "C" fn boot_core(cpu_id: u32) -> ! {
main();
}
#[embassy_executor::task]
async fn net_task(
mut runner: embassy_net::Runner<'static, zynq7000_hal::eth::embassy_net::Driver>,
) -> ! {
runner.run().await
}
#[embassy_executor::main]
#[unsafe(export_name = "main")]
async fn main(_spawner: Spawner) -> ! {
async fn main(spawner: Spawner) -> ! {
// Configure the uncached memory region using the MMU.
mmu_l1_table_mut()
.update(UNCACHED_ADDR, SHAREABLE_DEVICE)
@@ -109,7 +113,10 @@ async fn main(_spawner: Spawner) -> ! {
};
let boot_mode = BootMode::new();
info!("Boot mode: {:?}", boot_mode);
let rx_bufs = ETH_RX_BUFS.take();
let tx_bufs = ETH_TX_BUFS.take();
let rx_descr = RX_DESCRIPTORS.take();
let tx_descr = TX_DESCRIPTORS.take();
@@ -117,15 +124,15 @@ async fn main(_spawner: Spawner) -> ! {
tx_descr.write([const { zynq7000_hal::eth::tx_descr::Descriptor::new() }; 32]);
let rx_descr_init = unsafe { rx_descr.assume_init_mut() };
let tx_descr_init = unsafe { tx_descr.assume_init_mut() };
// Unwraps okay, list length is not 0
let mut rx_descr_ref =
zynq7000_hal::eth::rx_descr::DescriptorListRef::new(rx_descr_init.as_mut_slice());
zynq7000_hal::eth::rx_descr::DescriptorList::new(rx_descr_init.as_mut_slice()).unwrap();
// Create an unsafe copy for error handling.
// let rx_descr_copy_for_error_handling = unsafe { rx_descr_ref.clone_unchecked() };
let mut tx_descr_ref =
zynq7000_hal::eth::tx_descr::DescriptorListRef::new(tx_descr_init.as_mut_slice());
rx_descr_ref.init();
tx_descr_ref.init();
for (index, rx_buf) in rx_bufs.iter().enumerate() {
rx_descr_ref.set_rx_buf_address(index, rx_buf.0.as_ptr() as u32);
}
zynq7000_hal::eth::tx_descr::DescriptorList::new(tx_descr_init.as_mut_slice());
rx_descr_ref.init_with_aligned_bufs(rx_bufs.as_slice());
tx_descr_ref.init_or_reset();
// Unwrap okay, this is a valid peripheral.
let eth_ll = EthernetLowLevel::new(dp.eth_0).unwrap();
@@ -181,8 +188,34 @@ async fn main(_spawner: Spawner) -> ! {
// 1. PHY configuration.
// 2. Interrupt handler for ethernet RX and TX. Need to pass the buffers and descriptors
// to the interrupt handler.
// 3. Create embassy-net driver and schedule it.
//
info!("Boot mode: {:?}", boot_mode);
let bufs = zynq7000_hal::eth::embassy_net::DescriptorsAndBuffers::new(
rx_descr_ref,
rx_bufs,
tx_descr_ref,
tx_bufs,
)
.unwrap();
let driver = zynq7000_hal::eth::embassy_net::Driver::new(&eth, MAC_ADDRESS, bufs);
let config = embassy_net::Config::dhcpv4(Default::default());
static RESOURCES: static_cell::StaticCell<embassy_net::StackResources<3>> =
static_cell::StaticCell::new();
let mut rng = rand::rngs::SmallRng::seed_from_u64(1);
let (stack, runner) = embassy_net::new(
driver,
config,
RESOURCES.init(embassy_net::StackResources::new()),
rng.next_u64(),
);
spawner.spawn(net_task(runner)).unwrap();
stack.wait_config_up().await;
let network_config = stack.config_v4();
info!(
"Network configuration is up: DHCP config: {:?}!",
network_config
);
let mut ticker = Ticker::every(Duration::from_millis(200));
@@ -223,7 +256,14 @@ pub extern "C" fn _irq_handler() {
}
}
}
Interrupt::Spi(_spi_interrupt) => (),
Interrupt::Spi(spi_interrupt) => {
if spi_interrupt == zynq7000_hal::gic::SpiInterrupt::Eth0 {
let result = zynq7000_hal::eth::embassy_net::on_interrupt(
zynq7000_hal::eth::EthernetId::Eth0,
);
// TODO: Send the result structure back to the main thread.
}
}
Interrupt::Invalid(_) => (),
Interrupt::Spurious => (),
}

View File

@@ -36,6 +36,7 @@ libm = "0.2"
log = "0.4"
embassy-sync = "0.6"
embassy-net-driver = { path = "../../../Rust/embassy/embassy-net-driver", version = "0.2" }
vcell = "0.1"
raw-slicee = "0.1"
embedded-io-async = "0.6"

View File

@@ -51,6 +51,9 @@ pub fn invalidate_data_cache_range(addr: u32, len: usize) -> Result<(), Alignmen
///
/// This is commonly also called cache flushing. This function cleans and invalidates both L1
/// and L2 cache. The L2C must be enabled and set up correctly for this function to work correctly.
///
/// Both the address and length to clean and invalidate must be a multiple of the 32 byte cache
/// line.
pub fn clean_and_invalidate_data_cache_range(addr: u32, len: usize) -> Result<(), AlignmentError> {
if addr % 32 != 0 || len % 32 != 0 {
return Err(AlignmentError);

View File

@@ -1,36 +1,229 @@
use core::sync::atomic::AtomicBool;
use crate::cache::{clean_and_invalidate_data_cache_range, invalidate_data_cache_range};
use crate::eth::AlignedBuffer;
use arbitrary_int::u14;
use embassy_sync::waitqueue::AtomicWaker;
use zynq7000::eth::{InterruptStatus, RxStatus, TxStatus};
pub use super::rx_descr;
pub use super::tx_descr;
static TX_WAKER: AtomicWaker = AtomicWaker::new();
static RX_WAKER: AtomicWaker = AtomicWaker::new();
static LINK_WAKER: AtomicWaker = AtomicWaker::new();
static LINK_STATE: AtomicBool = AtomicBool::new(false);
pub struct EthernetEmbassyNet {
pub eth: super::Ethernet,
pub burst_size: usize,
pub mac_addr: [u8; 6],
pub rx_descr: &'static [rx_descr::Descriptor],
//pub rx_index: usize,
//pub tx_descr: &'static [tx_descr::Descriptor],
//pub tx_index: usize,
#[inline]
pub fn link_state() -> embassy_net_driver::LinkState {
match LINK_STATE.load(core::sync::atomic::Ordering::Relaxed) {
true => embassy_net_driver::LinkState::Up,
false => embassy_net_driver::LinkState::Down,
}
}
pub fn update_link_state(new_state: embassy_net_driver::LinkState) {
let new_value = match new_state {
embassy_net_driver::LinkState::Up => true,
embassy_net_driver::LinkState::Down => false,
};
if LINK_STATE.swap(new_value, core::sync::atomic::Ordering::Relaxed) != new_value {
LINK_WAKER.wake();
}
}
/// Anomalies which are not necesarily errors, but might indicate an issue.
#[derive(Debug, Clone, Copy, Default)]
pub struct EthAnomalies {
/// According to the TMR, p.551, this condition implies a packet is dropped because the
/// packet buffer is full. It occurs occasionally when the controller is unable to process
/// the packets if they arrive very fast. No special action for error recovery needs to
/// be taken, but we still report it.
rx_overrun: bool,
/// Possibly indicator for high traffic, not enough descriptors available or not handled
/// quick enough.
rx_descr_read_when_used: bool,
/// This should really never happen because the driver will check whether there is actually
/// space available.
tx_descr_read_when_used: bool,
}
/// Possibly critical errors.
#[derive(Debug, Clone, Copy, Default)]
pub struct EthErrors {
/// The TMR recommends re-initializing the controller and the buffer descriptors for
/// receive and transmit paths.
hresp_error: bool,
/// The TMR recommends disabling the ethernet transmitter, re-initializing the buffer
/// descriptors on the transmit side and re-enabling the transmitter.
tx_frame_corruption_ahb_error: bool,
/// Only set in Gigabit mode, for 10/100 mode, late collision and collisions are treated
/// the same.
tx_late_collision: bool,
/// In 10/100 mode, this is set when the retry limit was reached.
///
/// According to the TMR, p.551, this implies there are a series of collisions for which
/// an Ethernet frame could not be sent out even with a number of retries in half-duplex mode.
/// No drastic measures need to be taken, but this could also be an indicator for a duplex
/// missmatch.
tx_retry_limit_reached: bool,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct InterruptResult {
frame_received: bool,
frame_sent: bool,
/// These are anomalies.
anomalies: EthAnomalies,
/// These are full errors.
errors: EthErrors,
}
impl InterruptResult {
#[inline]
pub fn has_errors(&self) -> bool {
self.errors.hresp_error
|| self.errors.tx_frame_corruption_ahb_error
|| self.errors.tx_late_collision
|| self.errors.tx_retry_limit_reached
}
#[inline]
pub fn has_anomalies(&self) -> bool {
self.anomalies.rx_overrun
|| self.anomalies.rx_descr_read_when_used
|| self.anomalies.tx_descr_read_when_used
}
}
pub fn on_interrupt(eth_id: super::EthernetId) -> InterruptResult {
let mut eth_regs = unsafe { eth_id.steal_regs() };
let status = eth_regs.read_interrupt_status();
let mut clear = InterruptStatus::new_with_raw_value(0);
let mut tx_status_clear = TxStatus::new_with_raw_value(0);
let mut rx_status_clear = RxStatus::new_with_raw_value(0);
let mut result = InterruptResult::default();
if status.frame_received() {
RX_WAKER.wake();
clear.set_frame_received(true);
}
if status.frame_sent() {
TX_WAKER.wake();
tx_status_clear.set_complete(true);
clear.set_frame_sent(true);
}
if status.hresp_not_ok() {
result.errors.hresp_error = true;
clear.set_hresp_not_ok(true);
tx_status_clear.set_hresp_not_ok(true);
rx_status_clear.set_hresp_not_ok(true);
}
if status.tx_retry_limit_reached_or_late_collision() {
let tx_status = eth_regs.read_tx_status();
if tx_status.late_collision() {
result.errors.tx_late_collision = true;
tx_status_clear.set_late_collision(true);
} else {
result.errors.tx_retry_limit_reached = true;
tx_status_clear.set_retry_limit_reached(true);
}
// Clear this in any case.
tx_status_clear.set_collision(true);
clear.set_tx_retry_limit_reached_or_late_collision(true);
}
if status.tx_frame_corruption_ahb_error() {
result.errors.tx_frame_corruption_ahb_error = true;
// The interrupt status bit is cleared on a read.
tx_status_clear.set_frame_corruption_ahb_error(true);
}
if status.rx_descr_read_when_used() {
result.anomalies.rx_descr_read_when_used = true;
// I am guessing that those are related.
rx_status_clear.set_buf_not_available(true);
clear.set_rx_descr_read_when_used(true);
}
if status.rx_overrun() {
result.anomalies.rx_overrun = true;
rx_status_clear.set_overrun(true);
clear.set_rx_overrun(true);
}
eth_regs.write_interrupt_status(clear);
eth_regs.write_tx_status(tx_status_clear);
eth_regs.write_rx_status(rx_status_clear);
result
}
pub struct DescriptorsAndBuffers {
pub rx_descr: rx_descr::DescriptorList<'static>,
pub rx_bufs: &'static mut [super::AlignedBuffer],
pub tx_descr: tx_descr::DescriptorList<'static>,
pub tx_bufs: &'static mut [super::AlignedBuffer],
pub link_state: embassy_net_driver::LinkState,
}
pub struct EmbassyNetRxToken {
rx_descr_ref: rx_descr::DescriptorList<'static>,
rx_index: usize,
impl DescriptorsAndBuffers {
pub fn new(
rx_descr: rx_descr::DescriptorList<'static>,
rx_bufs: &'static mut [super::AlignedBuffer],
tx_descr: tx_descr::DescriptorList<'static>,
tx_bufs: &'static mut [super::AlignedBuffer],
) -> Option<Self> {
if rx_descr.len() != rx_bufs.len() || tx_descr.len() != tx_bufs.len() {
return None;
}
Some(Self {
rx_descr,
rx_bufs,
tx_descr,
tx_bufs,
})
}
#[inline]
pub fn tx_burst_len(&self) -> usize {
self.tx_descr.len()
}
}
impl embassy_net_driver::RxToken for EmbassyNetRxToken {
pub struct Driver {
pub burst_size: usize,
mac_addr: [u8; 6],
bufs: DescriptorsAndBuffers,
}
impl Driver {
pub fn new(_eth: &super::Ethernet, mac_addr: [u8; 6], bufs: DescriptorsAndBuffers) -> Self {
Self {
burst_size: bufs.tx_burst_len(),
mac_addr,
bufs,
}
}
}
pub struct EmbassyNetRxToken<'a> {
descr_list: &'a mut rx_descr::DescriptorList<'static>,
slot_index: usize,
rx_buf: &'a mut super::AlignedBuffer,
rx_size: usize,
}
impl embassy_net_driver::RxToken for EmbassyNetRxToken<'_> {
fn consume<R, F>(self, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
f(&mut [])
// The DMA will write the received frame into DDR. The L1 and L2 cache lines for the
// particular reception address need to be invalidated, to avoid fetching stale data from
// the cache instead of the DDR.
invalidate_data_cache_range(
self.rx_buf.0.as_ptr() as u32,
core::mem::size_of::<AlignedBuffer>(),
)
.expect("RX buffer or buffer size not aligned to cache line size");
let result = f(&mut self.rx_buf.0[0..self.rx_size]);
self.descr_list.clear_slot(self.slot_index);
result
}
}
@@ -40,70 +233,89 @@ pub struct EmbassyNetTxToken<'a> {
}
impl embassy_net_driver::TxToken for EmbassyNetTxToken<'_> {
fn consume<R, F>(mut self, len: usize, f: F) -> R
fn consume<R, F>(self, len: usize, f: F) -> R
where
F: FnOnce(&mut [u8]) -> R,
{
assert!(len <= super::MTU, "packet length exceeds MTU");
// In the transmit call, it was checked that the buffer queue actually is not full.
let tx_idx = self.descr_list.current_tx_idx();
let buffer = self.tx_bufs.get_mut(tx_idx).unwrap();
let result = f(&mut buffer.0);
// TODO: Clean and invalidate cache.
let addr = buffer.0.as_ptr() as u32;
self.descr_list.prepare_transfer_unchecked(addr, u14::new(len as u16), true, false);
// DMA accesses the DDR memory directly, so we need to flush everything that might
// still be in the L1 or L2 cache to the DDR.
clean_and_invalidate_data_cache_range(addr, core::mem::size_of::<AlignedBuffer>())
.expect("TX buffer or buffer size not aligned to cache line size");
self.descr_list
.prepare_transfer_unchecked(addr, u14::new(len as u16), true, false);
result
}
}
impl EthernetEmbassyNet {
#[inline]
pub fn update_link_state(&mut self, link_state: embassy_net_driver::LinkState) {
self.link_state = link_state;
}
#[inline]
pub fn set_link_state_up(&mut self) {
self.update_link_state(embassy_net_driver::LinkState::Up);
}
#[inline]
pub fn set_link_state_down(&mut self) {
self.update_link_state(embassy_net_driver::LinkState::Down);
}
}
impl embassy_net_driver::Driver for EthernetEmbassyNet {
impl embassy_net_driver::Driver for Driver {
type RxToken<'a>
= EmbassyNetRxToken<'a>
where
Self: 'a,
= EmbassyNetRxToken;
Self: 'a;
type TxToken<'a>
= EmbassyNetTxToken<'a>
where
Self: 'a,
= EmbassyNetTxToken<'a>;
Self: 'a;
fn receive(
&mut self,
cx: &mut core::task::Context,
) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> {
RX_WAKER.register(cx.waker());
None
if self.bufs.tx_descr.full() {
TX_WAKER.register(cx.waker());
return None;
}
match self.bufs.rx_descr.scan_and_handle_next_received_frame() {
// Nothing to do.
rx_descr::FrameScanResult::NoFrames => None,
rx_descr::FrameScanResult::SingleFrame { index, size } => Some((
EmbassyNetRxToken {
descr_list: &mut self.bufs.rx_descr,
slot_index: index,
rx_buf: self.bufs.rx_bufs.get_mut(index).unwrap(),
rx_size: size,
},
EmbassyNetTxToken {
descr_list: &mut self.bufs.tx_descr,
tx_bufs: self.bufs.tx_bufs,
},
)),
rx_descr::FrameScanResult::MultiSlotFrame {
first_slot_index: _,
last_slot_index: _,
} => {
// We can not really handle multi-slot frames.. this should never happen.
None
}
rx_descr::FrameScanResult::BrokenFragments {
first_slot_index: _,
last_slot_index: _,
} => None,
}
}
fn transmit(&mut self, cx: &mut core::task::Context) -> Option<Self::TxToken<'_>> {
TX_WAKER.register(cx.waker());
if self.tx_descr.full() {
if self.bufs.tx_descr.full() {
return None;
}
Some(EmbassyNetTxToken {
descr_list: &mut self.tx_descr,
tx_bufs: &mut self.tx_bufs,
descr_list: &mut self.bufs.tx_descr,
tx_bufs: self.bufs.tx_bufs,
})
}
fn link_state(&mut self, cx: &mut core::task::Context) -> embassy_net_driver::LinkState {
self.link_state
LINK_WAKER.register(cx.waker());
link_state()
}
fn capabilities(&self) -> embassy_net_driver::Capabilities {

View File

@@ -1,21 +1,22 @@
use arbitrary_int::{u2, u3};
pub use zynq7000::eth::MdcClkDiv;
use zynq7000::eth::{
BurstLength, DmaRxBufSize, MmioEthernet, SpeedMode, GEM_0_BASE_ADDR, GEM_1_BASE_ADDR,
BurstLength, DmaRxBufSize, GEM_0_BASE_ADDR, GEM_1_BASE_ADDR, InterruptControl, MmioEthernet,
SpeedMode,
};
pub use ll::{ClkConfig, EthernetLowLevel, Speed};
pub mod embassy_net;
pub mod ll;
pub mod mdio;
pub mod rx_descr;
pub mod tx_descr;
pub mod embassy_net;
pub const MTU: usize = 1536;
pub const MAX_MDC_SPEED: Hertz = Hertz::from_raw(2_500_000);
#[repr(align(32))]
#[repr(C, align(32))]
#[derive(Debug, Clone, Copy)]
pub struct AlignedBuffer(pub [u8; MTU]);
@@ -26,11 +27,11 @@ use crate::gpio::mio::{
use crate::{
clocks::ArmClocks,
gpio::{
IoPeriphPin,
mio::{
Mio28, Mio29, Mio30, Mio31, Mio32, Mio33, Mio34, Mio35, Mio36, Mio37, Mio38, Mio39,
Mio52, Mio53, MioPinMarker, MuxConf, Pin,
},
IoPeriphPin,
},
time::Hertz,
};
@@ -251,6 +252,26 @@ pub struct Ethernet {
mdio: mdio::Mdio,
}
const IRQ_CONTROL: InterruptControl = InterruptControl::builder()
.with_tsu_sec_incr(false)
.with_partner_pg_rx(false)
.with_auto_negotiation_complete(false)
.with_external_interrupt(false)
.with_pause_transmitted(false)
.with_pause_time_zero(false)
.with_pause_with_non_zero_quantum(false)
.with_hresp_not_ok(true)
.with_rx_overrun(true)
.with_link_changed(false)
.with_frame_sent(true)
.with_tx_frame_corruption_ahb_error(true)
.with_tx_retry_limit_reached_or_late_collision(true)
.with_tx_descr_read_when_used(true)
.with_rx_descr_read_when_used(true)
.with_frame_received(true)
.with_mgmt_frame_sent(false)
.build();
impl Ethernet {
#[allow(clippy::too_many_arguments)]
pub fn new_with_mio<
@@ -446,6 +467,16 @@ impl Ethernet {
});
}
#[inline]
pub fn enable_interrupts(&mut self) {
self.ll.regs.write_interrupt_enable(IRQ_CONTROL);
}
#[inline]
pub fn disable_interrupts(&mut self) {
self.ll.regs.write_interrupt_disable(IRQ_CONTROL);
}
#[inline]
pub fn ll(&mut self) -> &mut EthernetLowLevel {
&mut self.ll

View File

@@ -1,6 +1,9 @@
//! RX buffer descriptor module.
use crate::eth::AlignedBuffer;
pub use super::shared::Ownership;
use arbitrary_int::{u13, u2, u3, u30};
use arbitrary_int::{Number, u2, u3, u13, u30};
use vcell::VolatileCell;
/// RX buffer descriptor.
///
@@ -8,12 +11,12 @@ use arbitrary_int::{u13, u2, u3, u30};
///
/// These descriptors are shared between software and hardware and contain information
/// related to frame reception.
#[repr(C)]
#[repr(C, align(8))]
pub struct Descriptor {
/// The first word of the descriptor.
pub word0: Word0,
pub word_0: VolatileCell<Word0>,
/// The second word of the descriptor.
pub word1: Word1,
pub status: VolatileCell<StatusWord>,
}
#[bitbybit::bitfield(u32)]
@@ -34,7 +37,7 @@ pub struct Word0 {
/// frame bit.
#[bitbybit::bitfield(u32)]
#[derive(Debug, PartialEq, Eq)]
pub struct Word1 {
pub struct StatusWord {
#[bit(31, r)]
broadcast_detect: bool,
#[bit(30, r)]
@@ -76,29 +79,34 @@ impl Descriptor {
#[inline]
pub const fn new() -> Self {
Self {
word0: Word0::new_with_raw_value(0),
word1: Word1::new_with_raw_value(0),
word_0: VolatileCell::new(Word0::new_with_raw_value(0)),
status: VolatileCell::new(StatusWord::new_with_raw_value(0)),
}
}
#[inline]
pub fn set_ownership(&mut self, ownership: Ownership) {
self.word0.set_ownership(ownership);
}
#[inline]
pub fn ownership(&self) -> Ownership {
self.word0.ownership()
self.word_0.get().ownership()
}
#[inline]
pub fn set_wrap_bit(&mut self) {
self.word0.set_wrap(true);
pub fn set_word_0(&mut self, word: Word0) {
self.word_0.set(word);
}
#[inline]
pub fn write_rx_addr(&mut self, addr: u32) {
self.word0.set_addr_upper_30_bits(u30::new(addr >> 2));
pub fn set_word_1(&mut self, word: StatusWord) {
self.status.set(word);
}
#[inline]
pub fn word_0(&mut self) -> Word0 {
self.word_0.get()
}
#[inline]
pub fn status_word(&mut self) -> StatusWord {
self.status.get()
}
}
@@ -109,25 +117,61 @@ impl Default for Descriptor {
}
}
pub enum FrameScanResult {
NoFrames,
SingleFrame {
index: usize,
size: usize,
},
MultiSlotFrame {
first_slot_index: usize,
last_slot_index: usize,
},
BrokenFragments {
first_slot_index: usize,
last_slot_index: usize,
},
}
pub struct DescriptorList<'a> {
list: &'a mut [Descriptor],
idx: usize,
current_idx: usize,
}
impl<'a> DescriptorList<'a> {
#[inline]
pub fn new(descr_list: &'a mut [Descriptor]) -> Option<Self> {
if descr_list.len() == 0 {
if descr_list.is_empty() {
return None;
}
Some(Self {
list: descr_list,
idx: 0,
current_idx: 0,
})
}
/// Unsafely clone this descriptor list. See safety notes.
//
/// # Safety
//
/// You must not use both the original and the clone at the same time.
pub unsafe fn clone_unchecked(&mut self) -> Self {
Self {
list: unsafe {
core::slice::from_raw_parts_mut(self.list.as_mut().as_mut_ptr(), self.list.len())
},
current_idx: self.current_idx,
}
}
}
impl DescriptorList<'_> {
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
self.list.len()
}
#[inline]
pub fn base_ptr(&self) -> *const Descriptor {
self.list.as_ptr()
@@ -138,16 +182,126 @@ impl DescriptorList<'_> {
self.base_ptr() as u32
}
pub fn init(&mut self) {
self.idx = 0;
for desc in self.list.iter_mut() {
desc.set_ownership(Ownership::Hardware);
/// Resets the descriptor list. This retains the previous configured reception
/// addresses, but sets the ownership for all descriptors to the hardware again.
pub fn reset(&mut self) {
self.current_idx = 0;
let list_len = self.list.len();
let mut word_0;
for desc in self.list.iter_mut().take(list_len - 1) {
word_0 = desc.word_0();
word_0.set_ownership(Ownership::Hardware);
word_0.set_wrap(false);
desc.set_word_0(word_0);
}
self.list.last_mut().unwrap().set_wrap_bit();
let last = self.list.last_mut().unwrap();
word_0 = last.word_0();
word_0.set_ownership(Ownership::Hardware);
word_0.set_wrap(true);
last.set_word_0(word_0);
}
#[inline]
pub fn set_rx_buf_address(&mut self, index: usize, buf: &[u8]) {
self.list[index].write_rx_addr(addr);
pub fn init_with_aligned_bufs(&mut self, aligned_bufs: &[AlignedBuffer]) {
self.current_idx = 0;
let list_len = self.list.len();
for (desc, buf) in self.list.iter_mut().take(list_len - 1).zip(aligned_bufs) {
desc.set_word_0(
Word0::builder()
.with_addr_upper_30_bits(u30::new(buf.0.as_ptr() as u32 >> 2))
.with_wrap(false)
.with_ownership(Ownership::Hardware)
.build(),
);
}
self.list.last_mut().unwrap().set_word_0(
Word0::builder()
.with_addr_upper_30_bits(u30::new(
aligned_bufs.last().unwrap().0.as_ptr() as u32 >> 2,
))
.with_wrap(true)
.with_ownership(Ownership::Hardware)
.build(),
);
}
pub fn scan_and_handle_next_received_frame(&mut self) -> FrameScanResult {
let mut handled_slots = 0;
let mut current_idx = self.current_idx;
let mut start_found = false;
let mut start_idx = 0;
while handled_slots < self.list.len() {
let word_0 = self.list[current_idx].word_0.get();
if word_0.ownership() == Ownership::Hardware {
// The descriptor is not owned by the hardware, so it is not ready for processing.
current_idx = (current_idx + 1) % self.list.len();
handled_slots += 1;
continue;
}
let status = self.list[current_idx].status_word();
match (status.start_of_frame(), status.end_of_frame()) {
(true, true) => {
self.current_idx = (current_idx + 1) % self.list.len();
return FrameScanResult::SingleFrame {
index: current_idx,
size: status.rx_len().as_usize(),
};
}
(true, false) => {
// Consecutive start frame.. Which means something went wrong, and we need
// to discard all the slots until the second start frame slot.
if start_found {
self.clear_slots(start_idx, current_idx);
self.current_idx = (current_idx + 1) % self.list.len();
return FrameScanResult::BrokenFragments {
first_slot_index: start_idx,
last_slot_index: current_idx,
};
} else {
start_found = true;
start_idx = current_idx;
}
}
(false, true) => {
// Detected frame spanning multiple buffers.. which should really not happen
// if we only use frames with a certain MTU, but we will handle it.
if start_found {
self.current_idx = (current_idx + 1) % self.list.len();
return FrameScanResult::MultiSlotFrame {
first_slot_index: start_idx,
last_slot_index: current_idx,
};
}
}
(false, false) => {
// If a slot is neither the start nor the end of frame.
}
}
current_idx = (current_idx + 1) % self.list.len();
handled_slots += 1;
}
FrameScanResult::NoFrames
}
/// Clear a slot range by setting the ownership bit back to [Ownership::Hardware].
pub fn clear_slots(&mut self, start: usize, end: usize) {
if start >= self.list.len() || end >= self.list.len() {
return;
}
let mut current_idx = start;
while current_idx != end {
let mut word_0 = self.list[current_idx].word_0.get();
word_0.set_ownership(Ownership::Hardware);
self.list[current_idx].set_word_0(word_0);
current_idx = (current_idx + 1) % self.list.len();
}
}
pub fn clear_slot(&mut self, index: usize) {
if index >= self.list.len() {
return;
}
let mut word_0 = self.list[index].word_0.get();
word_0.set_ownership(Ownership::Hardware);
self.list[index].set_word_0(word_0);
}
}

View File

@@ -1,6 +1,7 @@
use arbitrary_int::u14;
pub use super::shared::Ownership;
use vcell::VolatileCell;
/// TX buffer descriptor.
///
@@ -8,13 +9,12 @@ pub use super::shared::Ownership;
///
/// These descriptors are shared between software and hardware and contain information
/// related to frame reception.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
#[repr(C, align(8))]
pub struct Descriptor {
/// The first word of the descriptor which is the byte address of the buffer.
pub word0: u32,
pub word0: VolatileCell<u32>,
/// The second word of the descriptor.
pub word1: Word1,
pub word1: VolatileCell<Word1>,
}
#[bitbybit::bitenum(u3, exhaustive = true)]
@@ -60,30 +60,36 @@ impl Descriptor {
#[inline]
pub const fn new() -> Self {
Self {
word0: 0,
word1: Word1::new_with_raw_value(0),
word0: VolatileCell::new(0),
word1: VolatileCell::new(Word1::new_with_raw_value(0)),
}
}
/*
#[inline]
pub fn set_ownership(&mut self, ownership: Ownership) {
self.word1.set_ownership(ownership);
}
*/
#[inline]
pub fn ownership(&self) -> Ownership {
self.word1.ownership()
}
/// Set the wrap bit, which should be done for the last descriptor in the descriptor list.
#[inline]
pub fn set_wrap_bit(&mut self) {
self.word1.set_wrap(true);
self.word1.get().ownership()
}
#[inline]
pub fn set_tx_transfer_addr_unchecked(&mut self, addr: u32) {
self.word0 = addr;
pub fn set_addr(&self, addr: u32) {
self.word0.set(addr)
}
#[inline]
pub fn read_word_1(&self) -> Word1 {
self.word1.get()
}
#[inline]
pub fn set_word_1(&self, word1: Word1) {
self.word1.set(word1);
}
/// Set the information for a transfer.
@@ -94,15 +100,15 @@ impl Descriptor {
last_buffer: bool,
no_crc_generation: bool,
) {
self.set_tx_transfer_addr_unchecked(addr);
self.set_addr(addr);
// Perform the read-modify-write sequence manually to ensure a minimum of reads/writes
// for the uncached memory.
let mut word1 = Word1::new_with_raw_value(self.word1.raw_value());
let mut word1 = self.word1.get();
word1.set_tx_len(tx_len);
word1.set_last_buffer(last_buffer);
word1.set_no_crc_generation(no_crc_generation);
word1.set_ownership(Ownership::Hardware);
self.word1 = word1;
self.word1.set(word1);
}
}
@@ -113,15 +119,6 @@ impl Default for Descriptor {
}
}
#[derive(Debug)]
pub struct DescriptorList<'a> {
list: &'a mut [Descriptor],
/// The head index is used to handle the transmission of new frames.
tx_idx: usize,
/// The tail index is used to track the progress of active transmissions.
busy_idx: usize,
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
#[error("tx error: {self:?}")]
pub struct TxError {
@@ -153,17 +150,6 @@ pub enum IncrementResult {
Ok,
}
impl<'a> DescriptorList<'a> {
#[inline]
pub fn new(descr_list: &'a mut [Descriptor]) -> Self {
Self {
list: descr_list,
tx_idx: 0,
busy_idx: 0,
}
}
}
pub enum BusyHandlingResult {
/// Handled a descriptor slot where a TX error has occured.
TxError(TxError),
@@ -175,7 +161,42 @@ pub enum BusyHandlingResult {
Complete,
}
pub struct DescriptorList<'a> {
list: &'a mut [Descriptor],
/// The head index is used to handle the transmission of new frames.
tx_idx: usize,
/// The tail index is used to track the progress of active transmissions.
busy_idx: usize,
}
impl core::fmt::Debug for DescriptorList<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("DescriptorList")
.field("tx_idx", &self.tx_idx)
.field("busy_idx", &self.busy_idx)
.field("list_len", &self.list.len())
.finish()
}
}
impl<'a> DescriptorList<'a> {
#[inline]
pub fn new(descr_list: &'a mut [Descriptor]) -> Self {
Self {
list: descr_list,
tx_idx: 0,
busy_idx: 0,
}
}
}
impl DescriptorList<'_> {
#[allow(clippy::len_without_is_empty)]
#[inline]
pub fn len(&self) -> usize {
self.list.len()
}
#[inline]
pub fn base_ptr(&self) -> *const Descriptor {
self.list.as_ptr()
@@ -189,10 +210,23 @@ impl DescriptorList<'_> {
pub fn init_or_reset(&mut self) {
self.tx_idx = 0;
self.busy_idx = 0;
for desc in self.list.iter_mut() {
desc.set_ownership(Ownership::Software);
let mut reset_val = Word1::builder()
.with_ownership(Ownership::Software)
.with_wrap(false)
.with_retry_limit_exceeded(false)
.with_transmit_frame_corruption_ahb_error(false)
.with_late_collision(false)
.with_checksum_status(TransmitChecksumGenerationStatus::NoError)
.with_no_crc_generation(false)
.with_last_buffer(false)
.with_tx_len(u14::new(0))
.build();
let list_len = self.list.len();
for desc in self.list.iter_mut().take(list_len - 1) {
desc.set_word_1(reset_val);
}
self.list.last_mut().unwrap().set_wrap_bit();
reset_val.set_wrap(true);
self.list.last_mut().unwrap().set_word_1(reset_val);
}
#[inline]
@@ -217,7 +251,7 @@ impl DescriptorList<'_> {
/// This should be called continuosly when a TX error or a TX completion interrupt has
/// occured until it returns [BusyHandlingResult::Complete].
pub fn check_and_handle_completed_transfer(&mut self) -> BusyHandlingResult {
let word1 = self.list[self.busy_idx].word1;
let word1 = self.list[self.busy_idx].word1.get();
if word1.ownership() == Ownership::Hardware || self.busy_idx == self.tx_idx {
return BusyHandlingResult::Complete;
}
@@ -228,19 +262,21 @@ impl DescriptorList<'_> {
{
return BusyHandlingResult::TxError(TxError::from_word1(&word1));
}
self.list[self.busy_idx].word1 = Word1::builder()
.with_ownership(Ownership::Software)
.with_wrap(word1.wrap())
.with_retry_limit_exceeded(false)
.with_transmit_frame_corruption_ahb_error(false)
.with_late_collision(false)
.with_checksum_status(TransmitChecksumGenerationStatus::NoError)
.with_no_crc_generation(false)
.with_last_buffer(false)
.with_tx_len(u14::new(0))
.build();
self.list[self.busy_idx].word1.set(
Word1::builder()
.with_ownership(Ownership::Software)
.with_wrap(word1.wrap())
.with_retry_limit_exceeded(false)
.with_transmit_frame_corruption_ahb_error(false)
.with_late_collision(false)
.with_checksum_status(TransmitChecksumGenerationStatus::NoError)
.with_no_crc_generation(false)
.with_last_buffer(false)
.with_tx_len(u14::new(0))
.build(),
);
self.increment_busy_idx_unchecked();
return BusyHandlingResult::SlotHandled;
BusyHandlingResult::SlotHandled
}
#[inline]

3
zynq7000-rt/docs.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --open

3
zynq7000/docs.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
cargo +nightly doc --open

View File

@@ -248,14 +248,17 @@ pub struct TxStatus {
hresp_not_ok: bool,
#[bit(7, rw)]
late_collision: bool,
/// This bit should never be se because the DMA is configured for packet buffer mode.
#[bit(6, rw)]
tx_underrun: bool,
underrun: bool,
#[bit(5, rw)]
tx_complete: bool,
complete: bool,
#[bit(4, rw)]
tx_frame_corruption_ahb_error: bool,
frame_corruption_ahb_error: bool,
/// Its called "tx_go" inside the Zynq 7000 documentation, but I think this is just a
/// TX active bit.
#[bit(3, r)]
tx_go: bool,
go: bool,
#[bit(2, rw)]
retry_limit_reached: bool,
#[bit(1, rw)]
@@ -276,7 +279,7 @@ pub struct RxStatus {
#[bit(3, rw)]
hresp_not_ok: bool,
#[bit(2, rw)]
rx_overrun: bool,
overrun: bool,
#[bit(1, rw)]
frame_received: bool,
#[bit(0, rw)]
@@ -315,13 +318,13 @@ pub struct InterruptStatus {
/// Marked N/A in datasheet.
#[bit(9, rw)]
link_changed: bool,
#[bit(7, r)]
tx_complete: bool,
#[bit(7, rw)]
frame_sent: bool,
/// Cleared on read.
#[bit(6, r)]
tx_frame_corruption_ahb_error: bool,
#[bit(5, rw)]
retry_limit_reached: bool,
tx_retry_limit_reached_or_late_collision: bool,
#[bit(3, rw)]
tx_descr_read_when_used: bool,
#[bit(2, rw)]
@@ -332,15 +335,15 @@ pub struct InterruptStatus {
mgmt_frame_sent: bool,
}
#[bitbybit::bitfield(u32)]
#[bitbybit::bitfield(u32, default = 0x00)]
#[derive(Debug)]
pub struct InterruptControl {
#[bit(26, w)]
tsu_sec_incr: bool,
/// Marked N/A in datasheet.
/// Marked N/A in datasheet. Probably because external PHYs are used.
#[bit(17, w)]
partner_pg_rx: bool,
/// Marked N/A in datasheet.
/// Marked N/A in datasheet. Probably because external PHYs are used.
#[bit(16, w)]
auto_negotiation_complete: bool,
#[bit(15, w)]
@@ -355,16 +358,15 @@ pub struct InterruptControl {
hresp_not_ok: bool,
#[bit(10, w)]
rx_overrun: bool,
/// Marked N/A in datasheet.
/// Marked N/A in datasheet. Probably because external PHYs are used.
#[bit(9, w)]
link_changed: bool,
#[bit(7, w)]
tx_complete: bool,
/// Cleared on read.
frame_sent: bool,
#[bit(6, w)]
tx_frame_corruption_ahb_error: bool,
#[bit(5, w)]
retry_limit_reached: bool,
tx_retry_limit_reached_or_late_collision: bool,
#[bit(3, w)]
tx_descr_read_when_used: bool,
#[bit(2, w)]

View File

@@ -6,6 +6,8 @@
//!
//! This crate is purposely kept low-level to allow building higher level abstractions like HALs
//! on top of it.
//! [The Zynq7000 HAL library](https://egit.irs.uni-stuttgart.de/rust/zynq7000-rs/src/branch/main/zynq7000-hal)
//! contains such a HAL which builds on this PAC.
#![no_std]
use core::sync::atomic::{AtomicBool, Ordering};
@@ -49,7 +51,7 @@ pub struct PsPeripherals {
pub ttc_0: ttc::MmioTtc<'static>,
pub ttc_1: ttc::MmioTtc<'static>,
pub eth_0: eth::MmioEthernet<'static>,
pub eth_1: eth::MmioEthernet<'static>
pub eth_1: eth::MmioEthernet<'static>,
}
impl PsPeripherals {

View File

@@ -1,7 +1,7 @@
//! System Level Control Registers (slcr)
//!
//! Writing any of these registers required unlocking the SLCR first.
use arbitrary_int::{u3, u4};
use arbitrary_int::u4;
pub use clocks::{ClockControl, MmioClockControl};
pub use reset::{MmioResetControl, ResetControl};