Compare commits
3 Commits
667db13306
...
c5a734210a
Author | SHA1 | Date | |
---|---|---|---|
c5a734210a | |||
037668e5a9 | |||
138cbc5ea7
|
@@ -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" }
|
||||
|
@@ -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(ð, 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 => (),
|
||||
}
|
||||
|
@@ -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"
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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
3
zynq7000-rt/docs.sh
Executable 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
3
zynq7000/docs.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
export RUSTDOCFLAGS="--cfg docsrs --generate-link-to-definition -Z unstable-options"
|
||||
cargo +nightly doc --open
|
@@ -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)]
|
||||
|
@@ -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 {
|
||||
|
@@ -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};
|
||||
|
||||
|
Reference in New Issue
Block a user