diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4128040..06fe857 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -76,6 +76,16 @@ jobs: - stm32f777 - stm32f778 - stm32f779 + - stm32h735 + - stm32h742 + - stm32h742v + - stm32h743 + - stm32h743v + - stm32h747cm7 + - stm32h750 + - stm32h750v + - stm32h753 + - stm32h753v steps: - name: Checkout uses: actions/checkout@v3 @@ -103,6 +113,7 @@ jobs: - stm32f107 - stm32f407 - stm32f745 + - stm32h735 steps: - name: Checkout uses: actions/checkout@v3 @@ -138,6 +149,7 @@ jobs: - stm32f107 - stm32f429 - stm32f745 + - stm32h735 toolchain: - stable target: diff --git a/Cargo.toml b/Cargo.toml index e867aed..c530d96 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ stm32f7xx-hal = { version = "0.7.0", optional = true } stm32f4xx-hal = { version = "0.14", optional = true } stm32f4 = { version = "0.15", optional = true } stm32f1xx-hal = { version = "0.10", optional = true } +stm32h7xx-hal = { version = "0.13", optional = true } ieee802_3_miim = "0.8" cortex-m = "0.7" log = { version = "0.4", optional = true } @@ -40,6 +41,12 @@ default = [ "defmt", "ptp" ] device-selected = [] fence = [] ptp = [ ] +f-series = [ ] + +stm32f1xx-hal = [ "dep:stm32f1xx-hal", "f-series" ] +stm32f4xx-hal = [ "dep:stm32f4xx-hal", "f-series" ] +stm32f7xx-hal = [ "dep:stm32f7xx-hal", "f-series" ] +stm32h7xx-hal = [ "dep:stm32h7xx-hal" ] stm32f107 = ["stm32f1xx-hal/stm32f107", "device-selected"] @@ -62,6 +69,17 @@ stm32f777 = ["stm32f7xx-hal/stm32f777", "device-selected", "fence"] stm32f778 = ["stm32f7xx-hal/stm32f778", "device-selected", "fence"] stm32f779 = ["stm32f7xx-hal/stm32f779", "device-selected", "fence"] +stm32h735 = ["device-selected", "fence", "stm32h7xx-hal/stm32h735", ] +stm32h742 = ["device-selected", "fence", "stm32h7xx-hal/stm32h742",] +stm32h742v = ["device-selected", "fence", "stm32h7xx-hal/stm32h742v"] +stm32h743 = ["device-selected", "fence", "stm32h7xx-hal/stm32h743"] +stm32h743v = ["device-selected", "fence", "stm32h7xx-hal/stm32h743v"] +stm32h747cm7 = ["device-selected", "fence", "stm32h7xx-hal/stm32h747cm7"] +stm32h750 = ["device-selected", "fence", "stm32h7xx-hal/stm32h750"] +stm32h750v = ["device-selected", "fence", "stm32h7xx-hal/stm32h750v"] +stm32h753 = ["device-selected", "fence", "stm32h7xx-hal/stm32h753"] +stm32h753v = ["device-selected", "fence", "stm32h7xx-hal/stm32h753v"] + smoltcp-phy = ["smoltcp"] [dev-dependencies] @@ -73,6 +91,7 @@ defmt-rtt = "0.4" panic-probe = { version = "0.3", features = [ "print-defmt" ] } systick-monotonic = "1.0" smoltcp = { version = "0.9", features = [ "medium-ethernet", "proto-ipv4", "socket-udp", "socket-tcp", "defmt" ], default-features = false } +siphasher = "0.3" [[example]] name = "pktgen" diff --git a/README.md b/README.md index bc7bd67..6dbebad 100644 --- a/README.md +++ b/README.md @@ -25,14 +25,14 @@ stm32-eth = { version = "0.3.0", features = ["stm32f107"] } # For stm32f107 In `src/main.rs` add: ```rust,no_run +use fugit::RateExtU32; use stm32_eth::{ + dma::{RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing}, hal::gpio::GpioExt, hal::rcc::RccExt, stm32::Peripherals, - dma::{RxRingEntry, TxRingEntry}, - EthPins, + EthPins, MTU, }; -use fugit::RateExtU32; fn main() { let p = Peripherals::take().unwrap(); @@ -56,8 +56,13 @@ fn main() { rx_d1: gpioc.pc5, }; - let mut rx_ring: [RxRingEntry; 16] = Default::default(); - let mut tx_ring: [TxRingEntry; 8] = Default::default(); + let mut rx_ring: [RxDescriptor; 16] = Default::default(); + let mut rx_buffers: [[u8; MTU + 2]; 16] = [[0u8; MTU + 2]; 16]; + let rx_ring = RxDescriptorRing::new(&mut rx_ring[..], &mut rx_buffers[..]); + + let mut tx_ring: [TxDescriptor; 8] = Default::default(); + let mut tx_buffers: [[u8; MTU + 2]; 8] = [[0u8; MTU + 2]; 8]; + let tx_ring = TxDescriptorRing::new(&mut tx_ring[..], &mut tx_buffers[..]); let parts = stm32_eth::PartsIn { mac: p.ETHERNET_MAC, @@ -66,14 +71,11 @@ fn main() { ptp: p.ETHERNET_PTP, }; - let stm32_eth::Parts { dma: mut eth_dma, mac: _, ptp: _ } = stm32_eth::new( - parts, - &mut rx_ring[..], - &mut tx_ring[..], - clocks, - eth_pins, - ) - .unwrap(); + let stm32_eth::Parts { + dma: mut eth_dma, + mac: _, + ptp: _, + } = stm32_eth::new(parts, rx_ring, tx_ring, clocks, eth_pins).unwrap(); eth_dma.enable_interrupt(); if let Ok(pkt) = eth_dma.recv_next(None) { @@ -81,9 +83,11 @@ fn main() { } let size = 42; - eth_dma.send(size, None, |buf| { - // write up to `size` bytes into buf before it is being sent - }).expect("send"); + eth_dma + .send(size, None, |buf| { + // write up to `size` bytes into buf before it is being sent + }) + .expect("send"); } ``` diff --git a/build.rs b/build.rs index 9700e20..29386fa 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,4 @@ fn main() { - #[cfg(feature = "stm32f1xx-hal")] println!("cargo:rustc-link-search=memory.x"); let hse = std::env::var("STM32_ETH_EXAMPLE_HSE"); diff --git a/examples/arp.rs b/examples/arp.rs index 1406a28..129044a 100644 --- a/examples/arp.rs +++ b/examples/arp.rs @@ -8,6 +8,7 @@ #![no_main] use defmt_rtt as _; +use ieee802_3_miim::{phy::BarePhy, Phy}; use panic_probe as _; use core::cell::RefCell; @@ -16,14 +17,13 @@ use cortex_m_rt::{entry, exception}; use cortex_m::interrupt::Mutex; use stm32_eth::{ - mac::{phy::BarePhy, Phy}, stm32::{interrupt, CorePeripherals, Peripherals, SYST}, Parts, }; pub mod common; -use stm32_eth::dma::{RxRingEntry, TxError, TxRingEntry}; +use stm32_eth::dma::TxError; const PHY_ADDR: u8 = 0; @@ -43,22 +43,14 @@ fn main() -> ! { let (eth_pins, mdio, mdc, _) = common::setup_pins(gpio); - let mut rx_ring: [RxRingEntry; 2] = Default::default(); - let mut tx_ring: [TxRingEntry; 2] = Default::default(); + let (tx_ring, rx_ring) = crate::common::setup_rings(); let Parts { mut dma, mac, #[cfg(feature = "ptp")] ptp: _, - } = stm32_eth::new( - ethernet, - &mut rx_ring[..], - &mut tx_ring[..], - clocks, - eth_pins, - ) - .unwrap(); + } = stm32_eth::new(ethernet, rx_ring, tx_ring, clocks, eth_pins).unwrap(); dma.enable_interrupt(); let mut last_link_up = false; @@ -109,14 +101,32 @@ fn main() -> ! { buf[38..42].copy_from_slice(&TARGET_IP); }); + loop { + use core::hash::{Hash, Hasher}; + + if let Ok(rx_packet) = dma.recv_next(None) { + let mut hasher = siphasher::sip::SipHasher::new(); + rx_packet.hash(&mut hasher); + + defmt::info!( + "Received {} bytes. Hash: {:016X}", + rx_packet.len(), + hasher.finish() + ); + break; + } + } + match r { Ok(()) => { defmt::info!("ARP sent"); } - Err(TxError::WouldBlock) => defmt::info!("ARP failed"), + Err(TxError::WouldBlock) => { + defmt::info!("ARP failed. {}", dma.tx_state()) + } } } else { - defmt::info!("Down"); + // defmt::info!("Down"); } cortex_m::interrupt::free(|cs| { diff --git a/examples/common.rs b/examples/common.rs index c1b095d..af687a9 100644 --- a/examples/common.rs +++ b/examples/common.rs @@ -4,16 +4,52 @@ //! //! Note that this module isn't an example by itself. +use core::mem::MaybeUninit; + use stm32_eth::{ - hal::{gpio::GpioExt, rcc::Clocks}, - PartsIn, + dma::{RxDescriptor, RxDescriptorRing, TxDescriptor, TxDescriptorRing}, + hal::gpio::GpioExt, + PartsIn, MTU, }; +#[cfg(feature = "f-series")] +use stm32_eth::hal::rcc::Clocks; + +#[cfg(feature = "stm32h7xx-hal")] +use stm32_eth::hal::rcc::CoreClocks as Clocks; + pub use pins::{setup_pins, Gpio}; use fugit::RateExtU32; use stm32_eth::hal::rcc::RccExt; +const NUM_DESCRIPTORS: usize = 4; + +/// On H7s, the ethernet DMA does not have access to the normal ram +/// so we must explicitly put them in SRAM. +#[cfg_attr(feature = "stm32h7xx-hal", link_section = ".sram1.eth")] +static mut TX_DESCRIPTORS: MaybeUninit<[TxDescriptor; NUM_DESCRIPTORS]> = MaybeUninit::uninit(); +#[cfg_attr(feature = "stm32h7xx-hal", link_section = ".sram1.eth")] +static mut TX_BUFFERS: MaybeUninit<[[u8; MTU + 2]; NUM_DESCRIPTORS]> = MaybeUninit::uninit(); +#[cfg_attr(feature = "stm32h7xx-hal", link_section = ".sram1.eth")] +static mut RX_DESCRIPTORS: MaybeUninit<[RxDescriptor; NUM_DESCRIPTORS]> = MaybeUninit::uninit(); +#[cfg_attr(feature = "stm32h7xx-hal", link_section = ".sram1.eth")] +static mut RX_BUFFERS: MaybeUninit<[[u8; MTU + 2]; NUM_DESCRIPTORS]> = MaybeUninit::uninit(); + +/// Set up the buffers to be used +pub fn setup_rings() -> (TxDescriptorRing<'static>, RxDescriptorRing<'static>) { + let tx_desc = unsafe { TX_DESCRIPTORS.write([TxDescriptor::new(); NUM_DESCRIPTORS]) }; + let tx_buf = unsafe { TX_BUFFERS.write([[0u8; MTU + 2]; NUM_DESCRIPTORS]) }; + + let rx_desc = unsafe { RX_DESCRIPTORS.write([RxDescriptor::new(); NUM_DESCRIPTORS]) }; + let rx_buf = unsafe { RX_BUFFERS.write([[0u8; MTU + 2]; NUM_DESCRIPTORS]) }; + + ( + TxDescriptorRing::new(tx_desc, tx_buf), + RxDescriptorRing::new(rx_desc, rx_buf), + ) +} + /// Setup the clocks and return clocks and a GPIO struct that /// can be used to set up all of the pins. /// @@ -23,8 +59,11 @@ pub fn setup_peripherals(p: stm32_eth::stm32::Peripherals) -> (Clocks, Gpio, Par let ethernet = PartsIn { dma: p.ETHERNET_DMA, mac: p.ETHERNET_MAC, + #[cfg(feature = "stm32h7xx-hal")] + mtl: p.ETHERNET_MTL, + #[cfg(feature = "f-series")] mmc: p.ETHERNET_MMC, - #[cfg(feature = "ptp")] + #[cfg(all(feature = "ptp", feature = "f-series"))] ptp: p.ETHERNET_PTP, }; @@ -99,6 +138,40 @@ pub fn setup_peripherals(p: stm32_eth::stm32::Peripherals) -> (Clocks, Gpio, Par (clocks, gpio, ethernet) } + + #[cfg(feature = "stm32h7xx-hal")] + { + use stm32_eth::hal::pwr::PwrExt; + + let rcc = p.RCC.constrain(); + let pwr = p.PWR.constrain(); + + let syscfg = p.SYSCFG; + + let pwrcfg = pwr.vos0(&syscfg).freeze(); + + let rcc = rcc.hclk(240.MHz()).sys_ck(240.MHz()); + + let rcc = if cfg!(hse = "bypass") { + rcc.bypass_hse().use_hse(8.MHz()) + } else if cfg!(hse = "oscillator") { + rcc.use_hse(8.MHz()) + } else { + rcc + }; + + let ccdr = rcc.freeze(pwrcfg, &syscfg); + let clocks = ccdr.clocks; + + let gpio = Gpio { + gpioa: p.GPIOA.split(ccdr.peripheral.GPIOA), + gpiob: p.GPIOB.split(ccdr.peripheral.GPIOB), + gpioc: p.GPIOC.split(ccdr.peripheral.GPIOC), + gpiog: p.GPIOG.split(ccdr.peripheral.GPIOG), + }; + + (clocks, gpio, ethernet) + } } pub use pins::*; @@ -280,6 +353,96 @@ mod pins { } } +#[cfg(feature = "stm32h7xx-hal")] +mod pins { + use stm32_eth::{ + hal::gpio::{Input, PushPull, *}, + EthPins, + }; + + pub struct Gpio { + pub gpioa: gpioa::Parts, + pub gpiob: gpiob::Parts, + pub gpioc: gpioc::Parts, + pub gpiog: gpiog::Parts, + } + + pub type RefClk = PA1; + pub type Crs = PA7; + + #[cfg(pins = "nucleo")] + pub type TxEn = PG11; + #[cfg(pins = "nucleo")] + pub type TxD0 = PG13; + + #[cfg(not(pins = "nucleo"))] + pub type TxEn = PB11; + #[cfg(not(pins = "nucleo"))] + pub type TxD0 = PB12; + + pub type TxD1 = PB13; + pub type RxD0 = PC4; + pub type RxD1 = PC5; + + #[cfg(not(pps = "alternate"))] + pub type Pps = PB5>; + #[cfg(pps = "alternate")] + pub type Pps = PG5>; + + pub type Mdio = PA2>; + pub type Mdc = PC1>; + + pub fn setup_pins( + gpio: Gpio, + ) -> ( + EthPins, + Mdio, + Mdc, + Pps, + ) { + #[allow(unused_variables)] + let Gpio { + gpioa, + gpiob, + gpioc, + gpiog, + } = gpio; + + let ref_clk = gpioa.pa1.into_input(); + let crs = gpioa.pa7.into_input(); + let rx_d0 = gpioc.pc4.into_input(); + let rx_d1 = gpioc.pc5.into_input(); + let tx_d1 = gpiob.pb13.into_input(); + + #[cfg(not(pins = "nucleo"))] + let (tx_en, tx_d0) = { (gpiob.pb11.into_input(), gpiob.pb12.into_input()) }; + + #[cfg(pins = "nucleo")] + let (tx_en, tx_d0) = { (gpiog.pg11.into_input(), gpiog.pg13.into_input()) }; + + let mdio = gpioa.pa2.into_alternate(); + let mdc = gpioc.pc1.into_alternate(); + + #[cfg(not(pps = "alternate"))] + let pps = gpiob.pb5.into_push_pull_output(); + + #[cfg(pps = "alternate")] + let pps = gpiog.pg5.into_push_pull_output(); + + let pins = EthPins { + ref_clk, + crs, + tx_en, + tx_d0, + tx_d1, + rx_d0, + rx_d1, + }; + + (pins, mdio, mdc, pps) + } +} + use ieee802_3_miim::{ phy::{ lan87xxa::{LAN8720A, LAN8742A}, diff --git a/examples/ip.rs b/examples/ip.rs index d6e6908..9a5c55c 100644 --- a/examples/ip.rs +++ b/examples/ip.rs @@ -22,10 +22,7 @@ use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Address, Ipv4Cidr}; pub mod common; -use stm32_eth::{ - dma::{RxRingEntry, TxRingEntry}, - Parts, -}; +use stm32_eth::Parts; const IP_ADDRESS: Ipv4Address = Ipv4Address::new(10, 0, 0, 1); const SRC_MAC: [u8; 6] = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; @@ -46,21 +43,14 @@ fn main() -> ! { let (eth_pins, _mdio, _mdc, _) = common::setup_pins(gpio); - let mut rx_ring: [RxRingEntry; 2] = Default::default(); - let mut tx_ring: [TxRingEntry; 2] = Default::default(); + let (tx_ring, rx_ring) = common::setup_rings(); + let Parts { mut dma, mac: _, #[cfg(feature = "ptp")] ptp: _, - } = stm32_eth::new( - ethernet, - &mut rx_ring[..], - &mut tx_ring[..], - clocks, - eth_pins, - ) - .unwrap(); + } = stm32_eth::new(ethernet, rx_ring, tx_ring, clocks, eth_pins).unwrap(); dma.enable_interrupt(); let ethernet_addr = EthernetAddress(SRC_MAC); diff --git a/examples/pktgen.rs b/examples/pktgen.rs index e9278ee..7453a1d 100644 --- a/examples/pktgen.rs +++ b/examples/pktgen.rs @@ -18,7 +18,7 @@ use stm32_eth::{ Parts, }; -use stm32_eth::dma::{RxRingEntry, TxError, TxRingEntry}; +use stm32_eth::dma::TxError; pub mod common; @@ -37,26 +37,19 @@ fn main() -> ! { let (clocks, gpio, ethernet) = common::setup_peripherals(p); - setup_systick(&mut cp.SYST); + setup_systick(&mut cp.SYST, clocks.hclk().to_Hz()); defmt::info!("Enabling ethernet..."); let (eth_pins, mdio, mdc, _) = common::setup_pins(gpio); - let mut rx_ring: [RxRingEntry; 2] = Default::default(); - let mut tx_ring: [TxRingEntry; 2] = Default::default(); + let (tx_ring, rx_ring) = common::setup_rings(); + let Parts { mut dma, mac, #[cfg(feature = "ptp")] ptp: _, - } = stm32_eth::new( - ethernet, - &mut rx_ring[..], - &mut tx_ring[..], - clocks, - eth_pins, - ) - .unwrap(); + } = stm32_eth::new(ethernet, rx_ring, tx_ring, clocks, eth_pins).unwrap(); dma.enable_interrupt(); // Main loop @@ -75,6 +68,7 @@ fn main() -> ! { // print stats every 30 seconds if time >= last_stats_time + 30 { let t = time - last_stats_time; + defmt::info!( "T={}\tRx:\t{} KB/s\t{} pps\tTx:\t{} KB/s\t{} pps", time, @@ -149,8 +143,8 @@ fn main() -> ! { } } -fn setup_systick(syst: &mut SYST) { - syst.set_reload(100 * SYST::get_ticks_per_10ms()); +fn setup_systick(syst: &mut SYST, hclk: u32) { + syst.set_reload(hclk.min(0x00FF_FFFF)); syst.enable_counter(); syst.enable_interrupt(); diff --git a/examples/rtic-echo.rs b/examples/rtic-echo.rs index cdef534..cc636b9 100644 --- a/examples/rtic-echo.rs +++ b/examples/rtic-echo.rs @@ -26,11 +26,7 @@ mod app { use ieee802_3_miim::{phy::PhySpeed, Phy}; use systick_monotonic::Systick; - use stm32_eth::{ - dma::{EthernetDMA, RxRingEntry, TxRingEntry}, - mac::Speed, - Parts, - }; + use stm32_eth::{dma::EthernetDMA, mac::Speed, Parts}; use smoltcp::{ iface::{self, Interface, SocketHandle, SocketSet, SocketStorage}, @@ -58,8 +54,6 @@ mod app { } #[init(local = [ - rx_ring: [RxRingEntry; 2] = [RxRingEntry::new(),RxRingEntry::new()], - tx_ring: [TxRingEntry; 2] = [TxRingEntry::new(),TxRingEntry::new()], rx_storage: [u8; 512] = [0u8; 512], tx_storage: [u8; 512] = [0u8; 512], socket_storage: [SocketStorage<'static>; 1] = [SocketStorage::EMPTY; 1], @@ -69,8 +63,7 @@ mod app { let core = cx.core; let p = cx.device; - let rx_ring = cx.local.rx_ring; - let tx_ring = cx.local.tx_ring; + let (tx_ring, rx_ring) = crate::common::setup_rings(); let (clocks, gpio, ethernet) = crate::common::setup_peripherals(p); let mono = Systick::new(core.SYST, clocks.hclk().raw()); diff --git a/examples/rtic-timestamp.rs b/examples/rtic-timestamp.rs index 4bdfa67..30b86fa 100644 --- a/examples/rtic-timestamp.rs +++ b/examples/rtic-timestamp.rs @@ -43,7 +43,7 @@ mod app { use systick_monotonic::Systick; use stm32_eth::{ - dma::{EthernetDMA, PacketId, RxRingEntry, TxRingEntry}, + dma::{EthernetDMA, PacketId}, mac::Speed, ptp::{EthernetPTP, Timestamp}, Parts, @@ -63,17 +63,13 @@ mod app { #[monotonic(binds = SysTick, default = true)] type Monotonic = Systick<1000>; - #[init(local = [ - rx_ring: [RxRingEntry; 2] = [RxRingEntry::new(),RxRingEntry::new()], - tx_ring: [TxRingEntry; 2] = [TxRingEntry::new(),TxRingEntry::new()], - ])] + #[init] fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { defmt::info!("Pre-init"); let core = cx.core; let p = cx.device; - let rx_ring = cx.local.rx_ring; - let tx_ring = cx.local.tx_ring; + let (tx_ring, rx_ring) = crate::common::setup_rings(); let (clocks, gpio, ethernet) = crate::common::setup_peripherals(p); let mono = Systick::new(core.SYST, clocks.hclk().raw()); @@ -155,6 +151,7 @@ mod app { 0, stm32_eth::ptp::Subseconds::new_from_nanos(500_000_000).unwrap(), ); + ptp.configure_target_time_interrupt(in_half_sec); } *sched_time = Some(now); @@ -177,15 +174,17 @@ mod app { buf[12..14].copy_from_slice(Ð_TYPE); buf[14..22].copy_from_slice(&now.raw().to_be_bytes()); }) - .ok(); + .unwrap(); *tx_id = Some((tx_id_val, now)); *tx_id_ctr += 1; *tx_id_ctr |= 0x8000_0000; }); } - #[task(binds = ETH, shared = [dma, tx_id, ptp, scheduled_time], priority = 2)] + #[task(binds = ETH, shared = [dma, tx_id, ptp, scheduled_time], local = [rx_packet_id: u32 = 0], priority = 2)] fn eth_interrupt(cx: eth_interrupt::Context) { + let packet_id = cx.local.rx_packet_id; + ( cx.shared.dma, cx.shared.tx_id, @@ -193,7 +192,7 @@ mod app { cx.shared.scheduled_time, ) .lock(|dma, tx_id, ptp, _sched_time| { - dma.interrupt_handler(); + let interrupt_summary = dma.interrupt_handler(); #[cfg(not(feature = "stm32f107"))] { @@ -208,26 +207,50 @@ mod app { } } - let mut packet_id = 0; + let mut buffer = [0u8; 22]; + + while let Ok((data, rx_timestamp, used_packet_id)) = { + let used_packet_id = *packet_id; + let result = if let Ok(packet) = dma.recv_next(Some(used_packet_id.into())) { + let data_len = packet.len().min(22); + buffer[..data_len].copy_from_slice(&packet[..data_len]); + let data = &buffer[..data_len]; - while let Ok(packet) = dma.recv_next(Some(packet_id.into())) { - let mut dst_mac = [0u8; 6]; - dst_mac.copy_from_slice(&packet[..6]); + // For RX packets, we can grab the timestamp directly or + // indirectly using [`EthernetDMA::get_timestamp_for_id`]. + // + // Using `timestamp` directly is easier, because you don't + // have to re-borrow the DMA for it. + let timestamp = packet.timestamp(); - // Note that, instead of grabbing the timestamp from the `RxPacket` directly, it - // is also possible to retrieve a cached version of the timestamp using - // `EthernetDMA::get_timestamp_for_id` (in the same way as for TX timestamps). - let ts = if let Some(timestamp) = packet.timestamp() { + *packet_id += 1; + *packet_id &= !0x8000_0000; + + Ok((data, timestamp, used_packet_id)) + } else { + Err(()) + }; + result + } { + let rx_timestamp = if let Some(timestamp) = rx_timestamp { timestamp } else { continue; }; - defmt::debug!("RX timestamp: {}", ts); + // Get the timestamp "the long way" around by asking the DMA to retrieve + // the value cached in the RX packet. + let cached_timestamp = dma.get_timestamp_for_id(used_packet_id); - let timestamp = if dst_mac == [0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56] { + // Assert that they are the same. + defmt::assert_eq!(cached_timestamp, Ok(rx_timestamp)); + + defmt::debug!("RX timestamp: {}", rx_timestamp); + + let dst_mac = &data[..6]; + let tx_timestamp = if dst_mac == [0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56] { let mut timestamp_data = [0u8; 8]; - timestamp_data.copy_from_slice(&packet[14..22]); + timestamp_data.copy_from_slice(&data[14..22]); let raw = i64::from_be_bytes(timestamp_data); let timestamp = Timestamp::new_raw(raw); @@ -236,9 +259,9 @@ mod app { continue; }; - defmt::debug!("Contained TX timestamp: {}", ts); + defmt::debug!("Contained TX timestamp: {}", rx_timestamp); - let diff = timestamp - ts; + let diff = tx_timestamp - rx_timestamp; defmt::info!("Difference between TX and RX time: {}", diff); @@ -259,20 +282,19 @@ mod app { defmt::warn!("Updated time."); ptp.update_time(diff); } - - packet_id += 1; - packet_id &= !0x8000_0000; } - if let Some((tx_id, sent_time)) = tx_id.take() { - if let Ok(ts) = dma.get_timestamp_for_id(PacketId(tx_id)) { - defmt::info!("TX timestamp: {}", ts); - defmt::debug!( + if interrupt_summary.is_tx { + if let Some((tx_id, sent_time)) = tx_id.take() { + if let Ok(ts) = dma.get_timestamp_for_id(PacketId(tx_id)) { + defmt::info!("TX timestamp: {}", ts); + defmt::debug!( "Diff between TX timestamp and the time that was put into the packet: {}", ts - sent_time ); - } else { - defmt::warn!("Failed to retrieve TX timestamp"); + } else { + defmt::warn!("Failed to retrieve TX timestamp"); + } } } }); diff --git a/memory.x b/memory.x index 5858599..e141e7a 100644 --- a/memory.x +++ b/memory.x @@ -1,5 +1,63 @@ MEMORY { - FLASH : ORIGIN = 0x08000000, LENGTH = 128k - RAM : ORIGIN = 0x20000000, LENGTH = 32K + /* FLASH and RAM are mandatory memory regions */ + + /* STM32H742xI/743xI/753xI */ + /* STM32H745xI/747xI/755xI/757xI */ + /* STM32H7A3xI/7B3xI */ + FLASH : ORIGIN = 0x08000000, LENGTH = 1M + + /* STM32H742xG/743xG */ + /* STM32H745xG/STM32H747xG */ + /* STM32H7A3xG */ + /* FLASH : ORIGIN = 0x08000000, LENGTH = 512K */ + /* FLASH1 : ORIGIN = 0x08100000, LENGTH = 512K */ + + /* STM32H750xB */ + /* STM32H7B0 */ + /* FLASH : ORIGIN = 0x08000000, LENGTH = 128K */ + + /* DTCM */ + RAM : ORIGIN = 0x20000000, LENGTH = 128K + + /* AXISRAM */ + AXISRAM : ORIGIN = 0x24000000, LENGTH = 512K + + /* SRAM */ + SRAM1 : ORIGIN = 0x30000000, LENGTH = 128K + SRAM2 : ORIGIN = 0x30020000, LENGTH = 128K + /* SRAM3 : ORIGIN = 0x30040000, LENGTH = 32K */ + SRAM4 : ORIGIN = 0x38000000, LENGTH = 64K + + /* Backup SRAM */ + BSRAM : ORIGIN = 0x38800000, LENGTH = 4K + + /* Instruction TCM */ + ITCM : ORIGIN = 0x00000000, LENGTH = 64K } + +/* The location of the stack can be overridden using the + `_stack_start` symbol. Place the stack at the end of RAM */ +_stack_start = ORIGIN(RAM) + LENGTH(RAM); + +/* The location of the .text section can be overridden using the + `_stext` symbol. By default it will place after .vector_table */ +/* _stext = ORIGIN(FLASH) + 0x40c; */ + +/* These sections are used for some of the examples */ +SECTIONS { + .axisram (NOLOAD) : ALIGN(8) { + *(.axisram .axisram.*); + . = ALIGN(8); + } > AXISRAM + /* The SRAM1 and SRAM2 section are commonly used as the stack and heap for the + CM4 core in dualcore versions and should thus not be used in examples*/ + .sram4 (NOLOAD) : ALIGN(4) { + *(.sram4 .sram4.*); + . = ALIGN(4); + } > SRAM4 + .sram1 (NOLOAD) : ALIGN(4) { + *(.sram1 .sram1.*); + . = ALIGN(4); + } > SRAM1 +}; \ No newline at end of file diff --git a/src/dma/desc.rs b/src/dma/desc.rs deleted file mode 100644 index dbd9aad..0000000 --- a/src/dma/desc.rs +++ /dev/null @@ -1,62 +0,0 @@ -use core::ops::{Deref, DerefMut}; - -use aligned::{Aligned, A8}; -use volatile_register::{RO, RW}; - -#[cfg(not(feature = "stm32f1xx-hal"))] -const DESC_SIZE: usize = 8; - -#[cfg(feature = "stm32f1xx-hal")] -const DESC_SIZE: usize = 4; - -#[repr(C)] -pub struct Descriptor { - pub(crate) desc: Aligned, -} - -impl Clone for Descriptor { - fn clone(&self) -> Self { - Descriptor { - desc: Aligned(*self.desc), - } - } -} - -impl Default for Descriptor { - fn default() -> Self { - Self::new() - } -} - -impl Descriptor { - pub const fn new() -> Self { - Self { - desc: Aligned([0; DESC_SIZE]), - } - } - - fn r(&self, n: usize) -> &RO { - let ro = &self.desc.deref()[n] as *const _ as *const RO; - unsafe { &*ro } - } - - unsafe fn rw(&mut self, n: usize) -> &mut RW { - let rw = &mut self.desc.deref_mut()[n] as *mut _ as *mut RW; - &mut *rw - } - - pub fn read(&self, n: usize) -> u32 { - self.r(n).read() - } - - pub unsafe fn write(&mut self, n: usize, value: u32) { - self.rw(n).write(value) - } - - pub unsafe fn modify(&mut self, n: usize, f: F) - where - F: FnOnce(u32) -> u32, - { - self.rw(n).modify(f) - } -} diff --git a/src/dma/mod.rs b/src/dma/mod.rs index c09d9ec..9d2142b 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -11,17 +11,13 @@ mod smoltcp_phy; #[cfg(feature = "smoltcp-phy")] pub use smoltcp_phy::*; -pub(crate) mod desc; - -pub(crate) mod ring; +pub(crate) mod raw_descriptor; mod rx; -use rx::RxRing; -pub use rx::{RxError, RxPacket, RxRingEntry}; +pub use rx::{RxDescriptor, RxDescriptorRing, RxError, RxPacket}; mod tx; -use tx::TxRing; -pub use tx::{TxError, TxRingEntry}; +pub use tx::{TxDescriptor, TxDescriptorRing, TxError}; #[cfg(feature = "ptp")] use crate::ptp::Timestamp; @@ -29,13 +25,34 @@ use crate::ptp::Timestamp; mod packet_id; pub use packet_id::PacketId; +use rx::RxRing; +use tx::{RunningState, TxRing}; + +use self::raw_descriptor::DESC_SIZE; + +const _RXDESC_SIZE: usize = core::mem::size_of::(); +const _TXDESC_SIZE: usize = core::mem::size_of::(); + +/// Assert that our descriptors have the same size. +/// +/// This is necessary as we only have a single Descriptor Skip Length +/// value which applies to both TX and RX descriptors. +const _ASSERT_DESCRIPTOR_SIZES: () = assert!(_RXDESC_SIZE == _TXDESC_SIZE); + +const DESC_WORD_SKIP: u8 = ((_RXDESC_SIZE / 4) - DESC_SIZE) as u8; + +const _ASSERT_DESC_WORD_SKIP_SIZE: () = assert!(DESC_WORD_SKIP <= 0b111); + +/// The maximum transmission unit of this Ethernet peripheral. +/// /// From the datasheet: *VLAN Frame maxsize = 1522* -pub(crate) const MTU: usize = 1522; +pub const MTU: usize = 1522; /// An error that can occur when retrieving a timestamp from an /// RX or TX descriptor handled by the DMA. #[cfg(feature = "ptp")] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TimestampError { /// The descriptor with the given packet ID has not been /// timestamped yet. @@ -46,12 +63,22 @@ pub enum TimestampError { /// Ethernet DMA. pub struct EthernetDMA<'rx, 'tx> { - pub(crate) eth_dma: ETHERNET_DMA, - pub(crate) rx_ring: RxRing<'rx>, - pub(crate) tx_ring: TxRing<'tx>, + parts: DmaParts, + rx_ring: RxRing<'rx, rx::Running>, + tx_ring: TxRing<'tx, tx::Running>, +} + +pub(crate) struct DmaParts { + pub eth_dma: ETHERNET_DMA, + #[cfg(feature = "stm32h7xx-hal")] + pub eth_mtl: crate::stm32::ETHERNET_MTL, } impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { + fn eth_dma(&self) -> ÐERNET_DMA { + &self.parts.eth_dma + } + /// Create and initialise the ethernet DMA /// /// # Note @@ -59,87 +86,153 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { /// accessible by the peripheral. Core-Coupled Memory (CCM) is /// usually not accessible. pub(crate) fn new( - eth_dma: ETHERNET_DMA, - rx_buffer: &'rx mut [RxRingEntry], - tx_buffer: &'tx mut [TxRingEntry], + parts: DmaParts, + rx_buffer: RxDescriptorRing<'rx>, + tx_buffer: TxDescriptorRing<'tx>, ) -> Self { - // reset DMA bus mode register - eth_dma.dmabmr.modify(|_, w| w.sr().set_bit()); - - // Wait until done - while eth_dma.dmabmr.read().sr().bit_is_set() {} - - // operation mode register - eth_dma.dmaomr.modify(|_, w| { - // Dropping of TCP/IP checksum error frames disable - w.dtcefd() - .set_bit() - // Receive store and forward - .rsf() - .set_bit() - // Disable flushing of received frames - .dfrf() - .set_bit() - // Transmit store and forward - .tsf() - .set_bit() - // Forward error frames - .fef() - .set_bit() - // Operate on second frame - .osf() - .set_bit() - }); - - // bus mode register - eth_dma.dmabmr.modify(|_, w| { - // For any non-f107 chips, we must use enhanced descriptor format to support checksum - // offloading and/or timestamps. - #[cfg(not(feature = "stm32f1xx-hal"))] - let w = w.edfe().set_bit(); - - unsafe { - // Address-aligned beats - w.aab() + let DmaParts { + eth_dma, + #[cfg(feature = "stm32h7xx-hal")] + eth_mtl, + } = &parts; + + #[cfg(feature = "f-series")] + { + // reset DMA bus mode register + eth_dma.dmabmr.modify(|_, w| w.sr().set_bit()); + // Wait until done + while eth_dma.dmabmr.read().sr().bit_is_set() {} + + // operation mode register + eth_dma.dmaomr.modify(|_, w| { + // Dropping of TCP/IP checksum error frames disable + w.dtcefd() .set_bit() - // Fixed burst - .fb() + // Receive store and forward + .rsf() .set_bit() - // Rx DMA PBL - .rdp() - .bits(32) - // Programmable burst length - .pbl() - .bits(32) - // Rx Tx priority ratio 2:1 - .pm() - .bits(0b01) - // Use separate PBL - .usp() + // Disable flushing of received frames + .dfrf() .set_bit() - } - }); + // Transmit store and forward + .tsf() + .set_bit() + // Forward error frames + .fef() + .set_bit() + // Operate on second frame + .osf() + .set_bit() + }); + + // bus mode register + eth_dma.dmabmr.modify(|_, w| { + // For any non-f107 chips, we must use enhanced descriptor format to support checksum + // offloading and/or timestamps. + #[cfg(not(feature = "stm32f1xx-hal"))] + let w = w.edfe().set_bit(); + + unsafe { + // Address-aligned beats + w.aab() + .set_bit() + // Fixed burst + .fb() + .set_bit() + // Rx DMA PBL + .rdp() + .bits(32) + // Programmable burst length + .pbl() + .bits(32) + // Rx Tx priority ratio 2:1 + .pm() + .bits(0b01) + // Use separate PBL + .usp() + .set_bit() + } + }); + + // Configure word skip length. + eth_dma.dmabmr.modify(|_, w| w.dsl().bits(DESC_WORD_SKIP)); + } - let mut dma = EthernetDMA { - eth_dma, - rx_ring: RxRing::new(rx_buffer), - tx_ring: TxRing::new(tx_buffer), - }; + #[cfg(feature = "stm32h7xx-hal")] + { + // reset DMA bus mode register + parts.eth_dma.dmamr.modify(|_, w| w.swr().set_bit()); + // Wait until done + while eth_dma.dmamr.read().swr().bit_is_set() {} + + // Rx Tx priority ratio 2:1 + eth_dma.dmamr.modify(|_, w| w.pr().variant(0b001)); + + eth_dma + .dmaccr + .modify(|_, w| w.dsl().variant(DESC_WORD_SKIP)); + + // Operation mode registers + eth_mtl.mtlrx_qomr.modify(|_, w| { + w + // Dropping of TCP/IP checksum error frames disable + .dis_tcp_ef() + .set_bit() + // Receive store and forward + .rsf() + .set_bit() + // Forward error frames + .fep() + .set_bit() + // Forward undersized but good frames + .fup() + .set_bit() + }); + + // Transmit store and forward + eth_mtl.mtltx_qomr.modify(|_, w| w.tsf().set_bit()); + + eth_dma.dmasbmr.modify(|_, w| w.aal().set_bit()); + + eth_dma.dmacrx_cr.modify(|_, w| { + w + // RX DMA programmable burst length. + .rxpbl() + .variant(32) + // Receive buffer size + .rbsz() + .variant(rx_buffer.first_buffer().len() as u16) + }); + + eth_dma.dmactx_cr.modify(|_, w| { + w + // TX DMA programmable burst length. + .txpbl() + .variant(32) + // Operate on second packet + .osf() + .set_bit() + }); + } - dma.rx_ring.start(&dma.eth_dma); - dma.tx_ring.start(&dma.eth_dma); + let rx_ring = RxRing::new(rx_buffer).start(ð_dma); + let tx_ring = TxRing::new(tx_buffer).start(ð_dma); - dma + EthernetDMA { + parts, + rx_ring, + tx_ring, + } } /// Enable RX and TX interrupts /// /// In your handler you must call - /// [`eth_interrupt_handler()`](fn.eth_interrupt_handler.html) to - /// clear interrupt pending bits. Otherwise the interrupt will - /// reoccur immediately. + /// [`eth_interrupt_handler()`] to clear interrupt pending + /// bits. Otherwise the interrupt will reoccur immediately. pub fn enable_interrupt(&self) { - self.eth_dma.dmaier.modify(|_, w| { + #[cfg(feature = "f-series")] + self.eth_dma().dmaier.modify(|_, w| { w // Normal interrupt summary enable .nise() @@ -152,6 +245,29 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { .set_bit() }); + #[cfg(feature = "stm32h7xx-hal")] + self.eth_dma().dmacier.modify(|_, w| { + w + // Normal interrupt summary enable + .nie() + .set_bit() + // Receive Interrupt Enable + .rie() + .set_bit() + // Transmit Interrupt Enable + .tie() + .set_bit() + // Abnormal Interrupt Summary enable + .aie() + .set_bit() + // Receive Buffer Unavailable + .rbue() + .set_bit() + // Transmit Buffer Unavailable + .tbue() + .set_bit() + }); + // Enable ethernet interrupts unsafe { NVIC::unmask(Interrupt::ETH); @@ -164,8 +280,7 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { doc = " and collects/caches TX timestamps. (See [`EthernetDMA::get_timestamp_for_id`] for retrieval)" )] pub fn interrupt_handler(&mut self) -> InterruptReasonSummary { - let eth_dma = &self.eth_dma; - let status = eth_interrupt_handler_impl(eth_dma); + let status = eth_interrupt_handler_impl(self.eth_dma()); #[cfg(feature = "ptp")] self.collect_timestamps(); status @@ -179,12 +294,25 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { self.rx_ring.running_state().is_running() } + /// Get the current state of Tx DMA + pub fn tx_state(&self) -> RunningState { + self.tx_ring.running_state() + } + /// Receive the next packet (if any is ready), or return [`Err`] /// immediately. pub fn recv_next(&mut self, packet_id: Option) -> Result { self.rx_ring.recv_next(packet_id.map(|p| p.into())) } + /// Check if there is a packet available for reading. + /// + /// If this function returns true, it is guaranteed that the + /// next call to [`EthernetDMA::recv_next`] will return [`Ok`]. + pub fn rx_available(&mut self) -> bool { + self.rx_ring.available() + } + /// Is Tx DMA currently running? pub fn tx_is_running(&self) -> bool { self.tx_ring.is_running() @@ -200,6 +328,14 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { self.tx_ring.send(length, packet_id.map(|p| p.into()), f) } + /// Check if sending a packet now would succeed. + /// + /// If this function returns true, it is guaranteed that + /// the next call to [`EthernetDMA::send`] will return [`Ok`] + pub fn tx_available(&mut self) -> bool { + self.tx_ring.available() + } + #[cfg(feature = "ptp")] /// Get a timestamp for the given ID /// @@ -208,10 +344,7 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { /// 2. Before calling [`interrupt_handler`](EthernetDMA::interrupt_handler) again, retrieve timestamps of sent and received frames using this function. /// /// Retrieving RX timestamps can also be done using [`RxPacket::timestamp`]. - pub fn get_timestamp_for_id<'a, PKT>( - &mut self, - packet_id: PKT, - ) -> Result + pub fn get_timestamp_for_id<'a, PKT>(&self, packet_id: PKT) -> Result where PKT: Into, { @@ -232,21 +365,12 @@ impl<'rx, 'tx> EthernetDMA<'rx, 'tx> { fn collect_timestamps(&mut self) { self.tx_ring.collect_timestamps(); } +} - /// Check if there is a packet available for reading. - /// - /// If this function returns true, it is guaranteed that the - /// next call to [`EthernetDMA::recv_next`] will return [`Ok`]. - pub fn rx_available(&mut self) -> bool { - self.rx_ring.next_entry_available() - } - - /// Check if sending a packet now would succeed. - /// - /// If this function returns true, it is guaranteed that - /// the next call to [`EthernetDMA::send`] will return [`Ok`] - pub fn tx_available(&mut self) -> bool { - self.tx_ring.next_entry_available() +impl<'rx, 'tx> Drop for EthernetDMA<'rx, 'tx> { + fn drop(&mut self) { + self.rx_ring.stop(&self.parts.eth_dma); + self.tx_ring.stop(&self.parts.eth_dma); } } @@ -275,17 +399,69 @@ pub fn eth_interrupt_handler(eth_dma: &crate::hal::pac::ETHERNET_DMA) -> Interru } fn eth_interrupt_handler_impl(eth_dma: ÐERNET_DMA) -> InterruptReasonSummary { - let status = eth_dma.dmasr.read(); + #[cfg(feature = "f-series")] + let (is_rx, is_tx, is_error) = { + // Read register + let status = eth_dma.dmasr.read(); + + // Reset bits + eth_dma.dmasr.write(|w| { + w.nis() + .set_bit() + .ts() + .set_bit() + .rs() + .set_bit() + .ais() + .set_bit() + }); - let status = InterruptReasonSummary { - is_rx: status.rs().bit_is_set(), - is_tx: status.ts().bit_is_set(), - is_error: status.ais().bit_is_set(), + ( + status.rs().bit_is_set(), + status.ts().bit_is_set(), + status.ais().bit_is_set(), + ) + }; + + #[cfg(feature = "stm32h7xx-hal")] + let (is_rx, is_tx, is_error) = { + // Read register + let status = eth_dma.dmacsr.read(); + + // Reset bits + eth_dma.dmacsr.write(|w| { + w.nis() + .set_bit() + .ais() + .set_bit() + .ti() + .set_bit() + .ri() + .set_bit() + .rbu() + .set_bit() + .tbu() + .set_bit() + }); + + if status.fbe().bit_is_set() { + // TODO: add a link to a/the github issue describing this problem, + // and how to solve it. + panic!("Fatal bus error! Is the descriptor and buffer memory accessible by the Ethernet MAC/DMA?"); + } + + ( + status.ri().bit_is_set(), + status.ti().bit_is_set(), + status.ais().bit_is_set(), + ) }; - eth_dma - .dmasr - .write(|w| w.nis().set_bit().ts().set_bit().rs().set_bit()); + let status = InterruptReasonSummary { + is_rx, + is_tx, + is_error, + }; status } diff --git a/src/dma/packet_id.rs b/src/dma/packet_id.rs index a842364..4b54e74 100644 --- a/src/dma/packet_id.rs +++ b/src/dma/packet_id.rs @@ -10,7 +10,7 @@ The main use is obtaining timestamps for frames using [`EthernetDMA::get_timesta " )] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, PartialEq, Clone, Copy)] pub struct PacketId(pub u32); impl PacketId { diff --git a/src/dma/raw_descriptor.rs b/src/dma/raw_descriptor.rs new file mode 100644 index 0000000..724dc43 --- /dev/null +++ b/src/dma/raw_descriptor.rs @@ -0,0 +1,116 @@ +use volatile_register::{RO, RW}; + +use crate::MTU; + +#[cfg(all(not(feature = "stm32f1xx-hal"), feature = "f-series"))] +pub(crate) const DESC_SIZE: usize = 8; + +#[cfg(feature = "stm32f1xx-hal")] +pub(crate) const DESC_SIZE: usize = 4; + +#[cfg(feature = "stm32h7xx-hal")] +pub(crate) const DESC_SIZE: usize = 4; + +#[repr(C)] +#[repr(align(4))] +#[derive(Clone, Copy)] +pub struct RawDescriptor { + pub(crate) desc: [u32; DESC_SIZE], +} + +impl Default for RawDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl RawDescriptor { + pub const fn new() -> Self { + Self { + desc: [0; DESC_SIZE], + } + } + + fn r(&self, n: usize) -> &RO { + let ro = &self.desc[n] as *const _ as *const RO; + unsafe { &*ro } + } + + unsafe fn rw(&mut self, n: usize) -> &mut RW { + let rw = &mut self.desc[n] as *mut _ as *mut RW; + &mut *rw + } + + pub fn read(&self, n: usize) -> u32 { + self.r(n).read() + } + + pub unsafe fn write(&mut self, n: usize, value: u32) { + self.rw(n).write(value) + } + + pub unsafe fn modify(&mut self, n: usize, f: F) + where + F: FnOnce(u32) -> u32, + { + self.rw(n).modify(f) + } +} + +pub struct DescriptorRing<'data, T> { + descriptors: &'data mut [T], + buffers: &'data mut [[u8; MTU + 2]], +} + +impl<'data, T> DescriptorRing<'data, T> { + pub fn new(descriptors: &'data mut [T], buffers: &'data mut [[u8; MTU + 2]]) -> Self { + assert!(descriptors.len() == buffers.len()); + + Self { + descriptors, + buffers, + } + } + + pub fn len(&self) -> usize { + self.descriptors.len() + } + + pub fn get(&mut self, index: usize) -> (&mut T, &mut [u8]) { + (&mut self.descriptors[index], &mut self.buffers[index]) + } + + pub fn descriptors_mut(&mut self) -> impl Iterator { + self.descriptors.iter_mut() + } + + pub fn descriptors(&self) -> impl Iterator { + self.descriptors.iter() + } + + pub fn last_descriptor_mut(&mut self) -> &mut T { + &mut self.descriptors[self.descriptors.len() - 1] + } + + pub fn last_descriptor(&self) -> &T { + &self.descriptors[self.descriptors.len() - 1] + } + + pub fn first_buffer(&self) -> &[u8] { + &self.buffers[0] + } + + pub fn last_buffer(&self) -> &[u8] { + &self.buffers[self.buffers.len() - 1] + } + + pub fn descriptors_and_buffers( + &mut self, + ) -> impl Iterator { + self.descriptors.iter_mut().zip(self.buffers.iter_mut()) + } + + pub fn descriptors_start_address(&self) -> *const T { + self.descriptors.as_ptr() + } +} diff --git a/src/dma/ring.rs b/src/dma/ring.rs deleted file mode 100644 index ca9aae8..0000000 --- a/src/dma/ring.rs +++ /dev/null @@ -1,87 +0,0 @@ -use aligned::{Aligned, A8}; -use core::ops::{Deref, DerefMut}; - -use super::{rx::RxDescriptor, tx::TxDescriptor, MTU}; - -pub trait RingDescriptor { - fn setup(&mut self, buffer: *const u8, len: usize, next: Option<&Self>); -} - -/// An entry in a DMA Descriptor ring -pub struct RingEntry { - desc: Aligned, - buffer: Aligned, -} - -impl Clone for RingEntry { - fn clone(&self) -> Self { - RingEntry { - desc: Aligned((*self.desc).clone()), - buffer: Aligned(*self.buffer), - } - } -} - -impl Default for RingEntry { - fn default() -> Self { - RingEntry { - desc: Aligned([T::default()]), - buffer: Aligned([0; MTU]), - } - } -} - -impl RingEntry { - /// The initial value of a TxRingDescriptor - pub const INIT: Self = Self::new(); - - /// Creates a RingEntry with a TxDescriptor. - pub const fn new() -> Self { - RingEntry { - desc: Aligned([TxDescriptor::new()]), - buffer: Aligned([0; MTU]), - } - } -} - -impl RingEntry { - /// The initial value of an RxRingDescriptor - pub const INIT: Self = Self::new(); - - /// Creates a RingEntry with a RxDescriptor. - pub const fn new() -> Self { - RingEntry { - desc: Aligned([RxDescriptor::new()]), - buffer: Aligned([0; MTU]), - } - } -} - -impl RingEntry { - pub(crate) fn setup(&mut self, next: Option<&Self>) { - let buffer = self.buffer.as_ptr(); - let len = self.buffer.len(); - self.desc_mut() - .setup(buffer, len, next.map(|next| next.desc())); - } - - #[inline] - pub(crate) fn desc(&self) -> &T { - &self.desc.deref()[0] - } - - #[inline] - pub(crate) fn desc_mut(&mut self) -> &mut T { - &mut self.desc.deref_mut()[0] - } - - #[inline] - pub(crate) fn as_slice(&self) -> &[u8] { - &(*self.buffer)[..] - } - - #[inline] - pub(crate) fn as_mut_slice(&mut self) -> &mut [u8] { - &mut (*self.buffer)[..] - } -} diff --git a/src/dma/rx.rs b/src/dma/rx.rs deleted file mode 100644 index dc8fcef..0000000 --- a/src/dma/rx.rs +++ /dev/null @@ -1,457 +0,0 @@ -use super::PacketId; -use crate::peripherals::ETHERNET_DMA; - -#[cfg(feature = "ptp")] -use crate::{dma::TimestampError, ptp::Timestamp}; - -use core::{ - ops::{Deref, DerefMut}, - sync::atomic::{self, Ordering}, -}; - -use super::{ - desc::Descriptor, - ring::{RingDescriptor, RingEntry}, -}; - -/// Errors that can occur during RX -#[derive(Debug, PartialEq)] -pub enum RxError { - /// Receiving would block - WouldBlock, - /// The received packet was truncated - Truncated, - /// An error occured with the DMA - DmaError, -} - -/// Owned by DMA engine -const RXDESC_0_OWN: u32 = 1 << 31; -/// First descriptor -const RXDESC_0_FS: u32 = 1 << 9; -/// Last descriptor -const RXDESC_0_LS: u32 = 1 << 8; -/// Error summary -const RXDESC_0_ES: u32 = 1 << 15; -/// Frame length -const RXDESC_0_FL_MASK: u32 = 0x3FFF; -const RXDESC_0_FL_SHIFT: usize = 16; - -const RXDESC_1_RBS_SHIFT: usize = 0; -const RXDESC_1_RBS_MASK: u32 = 0x0fff << RXDESC_1_RBS_SHIFT; -/// Second address chained -const RXDESC_1_RCH: u32 = 1 << 14; -/// End Of Ring -const RXDESC_1_RER: u32 = 1 << 15; - -#[repr(C)] -#[derive(Clone)] -/// An RX DMA Descriptor -pub struct RxDescriptor { - desc: Descriptor, - buffer_address: Option, - next_descriptor: Option, - #[cfg(feature = "ptp")] - timestamp_info: Option<(PacketId, Timestamp)>, -} - -impl Default for RxDescriptor { - fn default() -> Self { - Self::new() - } -} - -impl RxDescriptor { - /// Creates an zeroed RxDescriptor. - pub const fn new() -> Self { - Self { - desc: Descriptor::new(), - buffer_address: None, - next_descriptor: None, - #[cfg(feature = "ptp")] - timestamp_info: None, - } - } - - /// Is owned by the DMA engine? - fn is_owned(&self) -> bool { - (self.desc.read(0) & RXDESC_0_OWN) == RXDESC_0_OWN - } - - /// Pass ownership to the DMA engine - /// - /// Overrides old timestamp data - fn set_owned(&mut self) { - self.write_buffer1(); - self.write_buffer2(); - - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - atomic::fence(Ordering::Release); - atomic::compiler_fence(Ordering::Release); - - unsafe { - self.desc.modify(0, |w| w | RXDESC_0_OWN); - } - - // Used to flush the store buffer as fast as possible to make the buffer available for the - // DMA. - #[cfg(feature = "fence")] - atomic::fence(Ordering::SeqCst); - } - - fn has_error(&self) -> bool { - (self.desc.read(0) & RXDESC_0_ES) == RXDESC_0_ES - } - - /// Descriptor contains first buffer of frame - fn is_first(&self) -> bool { - (self.desc.read(0) & RXDESC_0_FS) == RXDESC_0_FS - } - - /// Descriptor contains last buffers of frame - fn is_last(&self) -> bool { - (self.desc.read(0) & RXDESC_0_LS) == RXDESC_0_LS - } - - /// Get PTP timestamps if available - #[cfg(feature = "ptp")] - pub fn timestamp(&self) -> Option { - #[cfg(not(feature = "stm32f1xx-hal"))] - let is_valid = { - /// RX timestamp - const RXDESC_0_TIMESTAMP_VALID: u32 = 1 << 7; - self.desc.read(0) & RXDESC_0_TIMESTAMP_VALID == RXDESC_0_TIMESTAMP_VALID - }; - - #[cfg(feature = "stm32f1xx-hal")] - // There is no "timestamp valid" indicator bit - // on STM32F1XX - let is_valid = true; - - let timestamp = Timestamp::from_descriptor(&self.desc); - - if is_valid && self.is_last() { - timestamp - } else { - None - } - } - - /// Rewrite buffer1 to the last value we wrote to it - /// - /// In our case, the address of the data buffer for this descriptor - /// - /// This only has to be done on stm32f107. For f4 and f7, enhanced descriptors - /// must be enabled for timestamping support, which we enable by default. - fn write_buffer1(&mut self) { - let buffer_addr = self - .buffer_address - .expect("Writing buffer1 of an RX descriptor, but `buffer_address` is None"); - - unsafe { - self.desc.write(2, buffer_addr); - } - } - - fn set_buffer1(&mut self, buffer: *const u8, len: usize) { - self.buffer_address = Some(buffer as u32); - self.write_buffer1(); - unsafe { - self.desc.modify(1, |w| { - (w & !RXDESC_1_RBS_MASK) | ((len as u32) << RXDESC_1_RBS_SHIFT) - }); - } - } - - /// Rewrite buffer2 to the last value we wrote it to - /// - /// In our case, the address of the next descriptor (may be zero) - /// - /// This only has to be done on stm32f107. For f4 and f7, enhanced descriptors - /// must be enabled for timestamping support, which we enable by default. - fn write_buffer2(&mut self) { - let addr = self - .next_descriptor - .expect("Writing buffer2 of an RX descriptor, but `next_descriptor` is None"); - - unsafe { - self.desc.write(3, addr); - } - } - - // points to next descriptor (RCH) - fn set_buffer2(&mut self, buffer: *const u8) { - self.next_descriptor = Some(buffer as u32); - self.write_buffer2(); - } - - fn set_end_of_ring(&mut self) { - unsafe { - self.desc.modify(1, |w| w | RXDESC_1_RER); - } - } - - fn get_frame_len(&self) -> usize { - ((self.desc.read(0) >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize - } -} - -/// An RX DMA Ring Descriptor entry -pub type RxRingEntry = RingEntry; - -impl RingDescriptor for RxDescriptor { - fn setup(&mut self, buffer: *const u8, len: usize, next: Option<&Self>) { - // Defer this initialization to this function, so we can have `RingEntry` on bss. - unsafe { - self.desc.write(1, RXDESC_1_RCH); - } - self.set_buffer1(buffer, len); - match next { - Some(next) => self.set_buffer2(&next.desc as *const Descriptor as *const u8), - None => { - #[allow(clippy::zero_ptr)] - self.set_buffer2(0 as *const u8); - self.set_end_of_ring(); - } - }; - self.set_owned(); - } -} - -impl RxRingEntry { - /// The initial value for an Rx Ring Entry - pub const RX_INIT: Self = Self::new(); - - fn take_received(&mut self) -> Result { - if self.desc().is_owned() { - Err(RxError::WouldBlock) - } else if self.desc().has_error() { - self.desc_mut().set_owned(); - Err(RxError::DmaError) - } else if self.desc().is_first() && self.desc().is_last() { - let frame_len = self.desc().get_frame_len(); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - atomic::compiler_fence(Ordering::Acquire); - - #[cfg(feature = "ptp")] - let timestamp = Timestamp::from_descriptor(&self.desc().desc); - - // TODO: obtain ethernet frame type (RDESC_1_FT) - let pkt = RxPacket { - entry: self, - length: frame_len, - #[cfg(feature = "ptp")] - timestamp, - }; - Ok(pkt) - } else { - self.desc_mut().set_owned(); - Err(RxError::Truncated) - } - } -} - -#[cfg(feature = "ptp")] -impl RxRingEntry { - fn attach_timestamp(&mut self, packet_id: Option) { - match (packet_id, self.desc().timestamp()) { - (Some(packet_id), Some(timestamp)) => { - self.desc_mut().timestamp_info = Some((packet_id, timestamp)) - } - _ => {} - } - } -} - -/// A received packet. -/// -/// This packet implements [Deref<\[u8\]>](core::ops::Deref) and should be used -/// as a slice. -pub struct RxPacket<'a> { - entry: &'a mut RxRingEntry, - length: usize, - #[cfg(feature = "ptp")] - timestamp: Option, -} - -impl<'a> Deref for RxPacket<'a> { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.entry.as_slice()[0..self.length] - } -} - -impl<'a> DerefMut for RxPacket<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.entry.as_mut_slice()[0..self.length] - } -} - -impl<'a> Drop for RxPacket<'a> { - fn drop(&mut self) { - self.entry.desc_mut().set_owned(); - } -} - -impl<'a> RxPacket<'a> { - /// Pass the received packet back to the DMA engine. - pub fn free(self) { - drop(self) - } - - /// Get the timestamp associated with this packet - #[cfg(feature = "ptp")] - pub fn timestamp(&self) -> Option { - self.timestamp - } -} - -/// Rx DMA state -pub struct RxRing<'a> { - entries: &'a mut [RxRingEntry], - next_entry: usize, -} - -impl<'a> RxRing<'a> { - /// Allocate - pub fn new(entries: &'a mut [RxRingEntry]) -> Self { - RxRing { - entries, - next_entry: 0, - } - } - - /// Setup the DMA engine (**required**) - pub fn start(&mut self, eth_dma: ÐERNET_DMA) { - // Setup ring - { - let mut previous: Option<&mut RxRingEntry> = None; - for entry in self.entries.iter_mut() { - if let Some(prev_entry) = &mut previous { - prev_entry.setup(Some(entry)); - } - previous = Some(entry); - } - if let Some(entry) = &mut previous { - entry.setup(None); - } - } - self.next_entry = 0; - let ring_ptr = self.entries[0].desc() as *const RxDescriptor; - - // Register RxDescriptor - eth_dma - .dmardlar - .write(|w| unsafe { w.srl().bits(ring_ptr as u32) }); - - // We already have fences in `set_owned`, which is called in `setup` - - // Start receive - eth_dma.dmaomr.modify(|_, w| w.sr().set_bit()); - - self.demand_poll(); - } - - /// Demand that the DMA engine polls the current `RxDescriptor` - /// (when in [`RunningState::Stopped`].) - pub fn demand_poll(&self) { - // SAFETY: we only perform an atomic write to `dmarpdr`. - let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - eth_dma.dmarpdr.write(|w| unsafe { w.rpd().bits(1) }); - } - - /// Get current `RunningState` - pub fn running_state(&self) -> RunningState { - // SAFETY: we only perform an atomic read of `dmasr`. - let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - match eth_dma.dmasr.read().rps().bits() { - // Reset or Stop Receive Command issued - 0b000 => RunningState::Stopped, - // Fetching receive transfer descriptor - 0b001 => RunningState::Running, - // Waiting for receive packet - 0b011 => RunningState::Running, - // Receive descriptor unavailable - 0b100 => RunningState::Stopped, - // Closing receive descriptor - 0b101 => RunningState::Running, - // Transferring the receive packet data from receive buffer to host memory - 0b111 => RunningState::Running, - _ => RunningState::Unknown, - } - } - - /// Check if we can receive a new packet - pub fn next_entry_available(&self) -> bool { - if !self.running_state().is_running() { - self.demand_poll(); - } - - !self.entries[self.next_entry].desc().is_owned() - } - - /// Receive the next packet (if any is ready), or return `None` - /// immediately. - pub fn recv_next( - &mut self, - // NOTE(allow): packet_id is unused if ptp is disabled. - #[allow(unused_variables)] packet_id: Option, - ) -> Result { - if !self.running_state().is_running() { - self.demand_poll(); - } - - let entries_len = self.entries.len(); - let mut result = self.entries[self.next_entry].take_received(); - - if result.as_mut().err() != Some(&mut RxError::WouldBlock) { - self.next_entry += 1; - if self.next_entry >= entries_len { - self.next_entry = 0; - } - - // Cache the PTP timestamps if PTP is enabled. - #[cfg(feature = "ptp")] - if let Ok(entry) = &mut result { - entry.entry.attach_timestamp(packet_id); - } - } - - result - } -} - -#[cfg(feature = "ptp")] -impl<'a> RxRing<'a> { - pub fn get_timestamp_for_id(&mut self, id: PacketId) -> Result { - for entry in self.entries.iter_mut() { - if let Some((packet_id, timestamp)) = &mut entry.desc_mut().timestamp_info { - if packet_id == &id { - let ts = *timestamp; - entry.desc_mut().timestamp_info.take(); - return Ok(ts); - } - } - } - - return Err(TimestampError::IdNotFound); - } -} - -/// Running state of the `RxRing` -#[derive(PartialEq, Eq, Debug)] -pub enum RunningState { - Unknown, - Stopped, - Running, -} - -impl RunningState { - /// whether self equals to `RunningState::Running` - pub fn is_running(&self) -> bool { - *self == RunningState::Running - } -} diff --git a/src/dma/rx/f_series_desc.rs b/src/dma/rx/f_series_desc.rs new file mode 100644 index 0000000..ba141dc --- /dev/null +++ b/src/dma/rx/f_series_desc.rs @@ -0,0 +1,204 @@ +use core::sync::atomic::{self, Ordering}; + +use crate::dma::{raw_descriptor::RawDescriptor, PacketId, RxError}; + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +/// Owned by DMA engine +const RXDESC_0_OWN: u32 = 1 << 31; +/// First descriptor +const RXDESC_0_FS: u32 = 1 << 9; +/// Last descriptor +const RXDESC_0_LS: u32 = 1 << 8; +/// Error summary +const RXDESC_0_ES: u32 = 1 << 15; +/// Frame length +const RXDESC_0_FL_MASK: u32 = 0x3FFF; +const RXDESC_0_FL_SHIFT: usize = 16; + +/// Receive buffer 1 size +const RXDESC_1_RBS1_SHIFT: usize = 0; +/// Receive buffer 1 size mask +const RXDESC_1_RBS1_MASK: u32 = 0x0fff << RXDESC_1_RBS1_SHIFT; + +/// Receive buffer 2 size +const RXDESC_1_RBS2_SHIFT: usize = 16; +/// Receive buffer 2 size mask +const RXDESC_1_RBS2_MASK: u32 = 0x0fff << RXDESC_1_RBS2_SHIFT; + +/// Receive end of ring +const RXDESC_1_RER: u32 = 1 << 15; + +#[repr(C)] +#[repr(align(4))] +#[derive(Clone, Copy)] +/// An RX DMA Descriptor. +pub struct RxDescriptor { + inner_raw: RawDescriptor, + packet_id: Option, + #[cfg(feature = "ptp")] + cached_timestamp: Option, +} + +impl Default for RxDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl RxDescriptor { + /// Creates a new [`RxDescriptor`]. + pub const fn new() -> Self { + Self { + inner_raw: RawDescriptor::new(), + packet_id: None, + #[cfg(feature = "ptp")] + cached_timestamp: None, + } + } + + pub(super) fn setup(&mut self, buffer: &mut [u8]) { + self.set_owned(buffer); + } + + /// Is owned by the DMA engine? + pub(super) fn is_owned(&self) -> bool { + (self.inner_raw.read(0) & RXDESC_0_OWN) == RXDESC_0_OWN + } + + /// Pass ownership to the DMA engine + /// + /// Overrides old timestamp data + pub(super) fn set_owned(&mut self, buffer: &mut [u8]) { + self.set_buffer(buffer); + + // "Preceding reads and writes cannot be moved past subsequent writes." + #[cfg(feature = "fence")] + atomic::fence(Ordering::Release); + atomic::compiler_fence(Ordering::Release); + + unsafe { + self.inner_raw.modify(0, |w| w | RXDESC_0_OWN); + } + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + #[cfg(feature = "fence")] + atomic::fence(Ordering::SeqCst); + } + + fn has_error(&self) -> bool { + (self.inner_raw.read(0) & RXDESC_0_ES) == RXDESC_0_ES + } + + /// Descriptor contains first buffer of frame + fn is_first(&self) -> bool { + (self.inner_raw.read(0) & RXDESC_0_FS) == RXDESC_0_FS + } + + /// Descriptor contains last buffers of frame + fn is_last(&self) -> bool { + (self.inner_raw.read(0) & RXDESC_0_LS) == RXDESC_0_LS + } + + /// Configure the buffer and its length. + fn set_buffer(&mut self, buffer: &[u8]) { + let buffer_ptr = buffer.as_ptr(); + let buffer_len = buffer.len(); + + unsafe { + self.inner_raw.modify(1, |w| { + // If rbs1 == 0, RBS1 will be ignored + let w = w & !(RXDESC_1_RBS1_MASK); + // Mask out any previous value of rbs2 + let w = w & !(RXDESC_1_RBS2_MASK); + // Set the length of RBS2 + let w = w | ((buffer_len << RXDESC_1_RBS2_SHIFT) as u32 & RXDESC_1_RBS2_MASK); + w + }); + + self.inner_raw.write(3, buffer_ptr as u32); + } + } + + pub(super) fn frame_length(&self) -> usize { + ((self.inner_raw.read(0) >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize + } + + pub(super) fn take_received( + &mut self, + packet_id: Option, + buffer: &mut [u8], + ) -> Result<(), RxError> { + if self.is_owned() { + Err(RxError::WouldBlock) + } else if self.has_error() { + self.set_owned(buffer); + Err(RxError::DmaError) + } else if self.is_first() && self.is_last() { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + atomic::compiler_fence(Ordering::Acquire); + + self.packet_id = packet_id; + + // Cache the PTP timestamps if PTP is enabled. + #[cfg(feature = "ptp")] + self.attach_timestamp(); + + Ok(()) + } else { + self.set_owned(buffer); + Err(RxError::Truncated) + } + } + + pub(super) fn set_end_of_ring(&mut self) { + unsafe { self.inner_raw.modify(1, |w| w | RXDESC_1_RER) } + } +} + +#[cfg(feature = "ptp")] +impl RxDescriptor { + pub(super) fn packet_id(&self) -> Option<&PacketId> { + self.packet_id.as_ref() + } + + /// Get PTP timestamps if available + pub(super) fn read_timestamp(&self) -> Option { + #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] + let (high, low) = { (self.inner_raw.read(7), self.inner_raw.read(6)) }; + + #[cfg(feature = "stm32f1xx-hal")] + let (high, low) = { (self.inner_raw.read(3), self.inner_raw.read(2)) }; + + #[cfg(not(feature = "stm32f1xx-hal"))] + let is_valid = { + /// RX timestamp + const RXDESC_0_TIMESTAMP_VALID: u32 = 1 << 7; + self.inner_raw.read(0) & RXDESC_0_TIMESTAMP_VALID == RXDESC_0_TIMESTAMP_VALID + }; + + #[cfg(feature = "stm32f1xx-hal")] + // There is no direct "timestamp valid" indicator bit + // on STM32F1XX, but if it's invalid it will be written + // as all ones. + let is_valid = high != 0xFFFF_FFFF || low != 0xFFFF_FFFF; + + let timestamp = Timestamp::from_parts(high, low); + + if is_valid && self.is_last() { + Some(timestamp) + } else { + None + } + } + + fn attach_timestamp(&mut self) { + self.cached_timestamp = self.read_timestamp(); + } + + pub(super) fn timestamp(&self) -> Option<&Timestamp> { + self.cached_timestamp.as_ref() + } +} diff --git a/src/dma/rx/h_desc.rs b/src/dma/rx/h_desc.rs new file mode 100644 index 0000000..2a0bfd6 --- /dev/null +++ b/src/dma/rx/h_desc.rs @@ -0,0 +1,234 @@ +use core::sync::atomic::{self, Ordering}; + +use crate::dma::{raw_descriptor::RawDescriptor, PacketId, RxError}; + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +mod consts { + #![allow(unused)] + + /// Owned by DMA + pub const RXDESC_3_OWN: u32 = 1 << 31; + + // Read format bits + /// Interrupt On Completion + pub const RXDESC_3_IOC: u32 = 1 << 30; + /// Buffer 2 Address Valid + pub const RXDESC_3_BUF2V: u32 = 1 << 25; + /// Buffer 1 Address valid + pub const RXDESC_3_BUF1V: u32 = 1 << 24; + + // Write-back bits + /// Timestamp Dropped + pub const RXDESC_1_TD: u32 = 1 << 16; + /// Timestamp Avaialble + pub const RXDESC_1_TSA: u32 = 1 << 14; + /// Context Descriptor + pub const RXDESC_3_CTXT: u32 = 1 << 30; + /// First Descriptor + pub const RXDESC_3_FD: u32 = 1 << 29; + /// Last Descriptor + pub const RXDESC_3_LD: u32 = 1 << 28; + /// Receive Status RDES2 valid + pub const RXDESC_3_RS2V: u32 = 1 << 27; + /// Receive status RDES1 valid + pub const RXDESC_3_RS1V: u32 = 1 << 26; + /// Receive status RDES0 valid + pub const RXDESC_3_RS0V: u32 = 1 << 26; + /// CRC error + pub const RXDESC_3_CE: u32 = 1 << 24; + /// Giant Packet + pub const RXDESC_3_GP: u32 = 1 << 23; + /// Receive Watchdog Timeout + pub const RXDESC_3_RWT: u32 = 1 << 22; + /// Overflow Error + pub const RXDESC_3_OE: u32 = 1 << 21; + /// Receive Error + pub const RXDESC_3_RE: u32 = 1 << 20; + /// Dribble Bit Error + pub const RXDESC_3_DE: u32 = 1 << 19; + + /// Length/Type Field shift + pub const RXDESC_3_LT_SHIFT: u32 = 16; + /// Length/Type Field mask + pub const RXDESC_3_LT_MASK: u32 = 0b111 << RXDESC_3_LT_SHIFT; + /// Length/Type Field + #[allow(non_camel_case_types)] + #[repr(u32)] + pub enum RXDESC_3_LT { + Length = 0b000 << RXDESC_3_LT_SHIFT, + Type = 0b001 << RXDESC_3_LT_SHIFT, + Reserved = 0b010 << RXDESC_3_LT_SHIFT, + ArpRequest = 0b011 << RXDESC_3_LT_SHIFT, + TypeWithVlan = 0b100 << RXDESC_3_LT_SHIFT, + TypeWIthDoubleVlan = 0b101 << RXDESC_3_LT_SHIFT, + MacControl = 0b110 << RXDESC_3_LT_SHIFT, + Oam = 0b111 << RXDESC_3_LT_SHIFT, + } + + /// Error Summary + pub const RXDESC_3_ES: u32 = 1 << 15; + + /// Packet Length shift + pub const RXDESC_3_PL_SHIFT: u32 = 0; + /// Packet Length mask + pub const RXDESC_3_PL_MASK: u32 = 0x3FFF; +} +pub use consts::*; + +#[repr(C)] +#[repr(align(4))] +#[derive(Clone, Copy)] +/// An RX DMA Descriptor. +pub struct RxDescriptor { + inner_raw: RawDescriptor, + packet_id: Option, + #[cfg(feature = "ptp")] + cached_timestamp: Option, +} + +impl Default for RxDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl RxDescriptor { + /// Creates a new [`RxDescriptor`]. + pub const fn new() -> Self { + Self { + inner_raw: RawDescriptor::new(), + packet_id: None, + #[cfg(feature = "ptp")] + cached_timestamp: None, + } + } + + /// Is owned by the DMA engine? + pub(super) fn is_owned(&self) -> bool { + (self.inner_raw.read(3) & RXDESC_3_OWN) == RXDESC_3_OWN + } + + fn has_error(&self) -> bool { + self.inner_raw.read(3) & RXDESC_3_ES == RXDESC_3_ES + } + + fn is_first(&self) -> bool { + self.inner_raw.read(3) & RXDESC_3_FD == RXDESC_3_FD + } + + fn is_last(&self) -> bool { + self.inner_raw.read(3) & RXDESC_3_LD == RXDESC_3_LD + } + + fn is_context(&self) -> bool { + self.inner_raw.read(3) & RXDESC_3_CTXT == RXDESC_3_CTXT + } + + pub(super) fn frame_length(&self) -> usize { + if self.is_owned() { + 0 + } else { + ((self.inner_raw.read(3) & RXDESC_3_PL_MASK) >> RXDESC_3_PL_SHIFT) as usize + } + } + + #[allow(unused)] + pub(super) fn packet_id(&self) -> Option<&PacketId> { + self.packet_id.as_ref() + } + + pub(super) fn setup(&mut self, buffer: &[u8]) { + self.set_owned(buffer); + } + + /// Pass ownership to the DMA engine + pub(super) fn set_owned(&mut self, buffer: &[u8]) { + let buffer = buffer.as_ptr(); + self.set_buffer(buffer); + + // "Preceding reads and writes cannot be moved past subsequent writes." + #[cfg(feature = "fence")] + atomic::fence(Ordering::Release); + atomic::compiler_fence(Ordering::Release); + + unsafe { + self.inner_raw + .modify(3, |w| w | RXDESC_3_OWN | RXDESC_3_IOC); + } + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + #[cfg(feature = "fence")] + atomic::fence(Ordering::SeqCst); + } + + /// Configure the buffer and its length. + fn set_buffer(&mut self, buffer_ptr: *const u8) { + unsafe { + // Set buffer 1 address. + self.inner_raw.modify(0, |_| buffer_ptr as u32); + + // RXDESC does not contain buffer length, it is set + // in register INSERT_HERE instead. The size of all + // buffers is verified by [`TxRing`](super::TxRing) + + self.inner_raw.modify(3, |w| { + // BUF2 is not valid + let w = w & !(RXDESC_3_BUF2V); + // BUF1 is valid + let w = w | RXDESC_3_BUF1V; + w + }); + } + } + + pub(super) fn take_received( + &mut self, + packet_id: Option, + buffer: &mut [u8], + ) -> Result<(), RxError> { + if self.is_owned() { + Err(RxError::WouldBlock) + } else + // Only single-frame descriptors and non-context descriptors are supported + // for now. + if self.is_first() && self.is_last() && !self.has_error() && !self.is_context() { + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + atomic::compiler_fence(Ordering::Acquire); + + self.packet_id = packet_id; + + Ok(()) + } else { + self.set_owned(buffer); + Err(RxError::Truncated) + } + } +} + +#[cfg(feature = "ptp")] +impl RxDescriptor { + pub(super) fn has_timestamp(&self) -> bool { + (self.inner_raw.read(1) & RXDESC_1_TSA) == RXDESC_1_TSA && self.is_last() + } + + /// Get PTP timestamps if available + pub(super) fn read_timestamp(&self) -> Option { + if self.is_context() && !self.is_owned() { + let (high, low) = (self.inner_raw.read(1), self.inner_raw.read(0)); + Some(Timestamp::from_parts(high, low)) + } else { + None + } + } + + pub(super) fn attach_timestamp(&mut self, timestamp: Option) { + self.cached_timestamp = timestamp; + } + + pub(super) fn timestamp(&self) -> Option<&Timestamp> { + self.cached_timestamp.as_ref() + } +} diff --git a/src/dma/rx/mod.rs b/src/dma/rx/mod.rs new file mode 100644 index 0000000..68c1d9c --- /dev/null +++ b/src/dma/rx/mod.rs @@ -0,0 +1,359 @@ +use core::marker::PhantomData; + +use super::{raw_descriptor::DescriptorRing, PacketId}; +use crate::peripherals::ETHERNET_DMA; + +#[cfg(feature = "ptp")] +use crate::{dma::TimestampError, ptp::Timestamp}; + +#[cfg(feature = "f-series")] +mod f_series_desc; +#[cfg(feature = "f-series")] +use f_series_desc as descriptor; + +#[cfg(feature = "stm32h7xx-hal")] +mod h_desc; +#[cfg(feature = "stm32h7xx-hal")] +use h_desc as descriptor; + +pub use descriptor::RxDescriptor; + +/// Errors that can occur during RX +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq)] +pub enum RxError { + /// Receiving would block + WouldBlock, + /// The received packet was truncated + Truncated, + /// An error occured with the DMA + DmaError, +} + +/// An RX descriptor ring. +pub type RxDescriptorRing<'rx> = DescriptorRing<'rx, RxDescriptor>; + +pub struct NotRunning; +pub struct Running; + +/// Rx DMA state +pub struct RxRing<'data, STATE> { + ring: RxDescriptorRing<'data>, + next_entry: usize, + state: PhantomData, +} + +impl<'data, STATE> RxRing<'data, STATE> { + /// Get current `RunningState` + pub fn running_state(&self) -> RunningState { + // SAFETY: we only perform an atomic read of `dmasr` + let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; + + #[cfg(feature = "f-series")] + let rps = eth_dma.dmasr.read().rps().bits(); + #[cfg(feature = "stm32h7xx-hal")] + let rps = eth_dma.dmadsr.read().rps0().bits(); + + match rps { + // Reset or Stop Receive Command issued + 0b000 => RunningState::Stopped, + // Fetching receive transfer descriptor + 0b001 => RunningState::Running, + // Waiting for receive packet + 0b011 => RunningState::Running, + // Receive descriptor unavailable + 0b100 => RunningState::Stopped, + // Closing receive descriptor + 0b101 => RunningState::Running, + // Transferring the receive packet data from receive buffer to host memory + 0b111 => RunningState::Running, + #[cfg(feature = "stm32h7xx-hal")] + // Timestamp write state + 0b110 => RunningState::Running, + _ => RunningState::Unknown, + } + } +} + +impl<'data> RxRing<'data, NotRunning> { + /// Allocate + pub fn new(ring: RxDescriptorRing<'data>) -> Self { + RxRing { + ring, + next_entry: 0, + state: Default::default(), + } + } + + /// Start the RX ring + pub fn start(mut self, eth_dma: ÐERNET_DMA) -> RxRing<'data, Running> { + // Setup ring + for (entry, buffer) in self.ring.descriptors_and_buffers() { + entry.setup(buffer); + } + + self.next_entry = 0; + let ring_ptr = self.ring.descriptors_start_address(); + + #[cfg(feature = "f-series")] + { + self.ring.last_descriptor_mut().set_end_of_ring(); + // Set the RxDma ring start address. + eth_dma + .dmardlar + .write(|w| unsafe { w.srl().bits(ring_ptr as u32) }); + + // // Start receive + eth_dma.dmaomr.modify(|_, w| w.sr().set_bit()); + } + + #[cfg(feature = "stm32h7xx-hal")] + { + let rx_ring_descriptors = self.ring.descriptors().count(); + assert!(rx_ring_descriptors >= 4); + + // Assert that the descriptors are properly aligned. + // + // FIXME: these require different alignment if the data is stored + // in AXI SRAM + assert!(ring_ptr as u32 % 4 == 0); + assert!(self.ring.last_descriptor_mut() as *const _ as u32 % 4 == 0); + + // Set the start pointer. + eth_dma + .dmacrx_dlar + .write(|w| unsafe { w.bits(ring_ptr as u32) }); + + // Set the Receive Descriptor Ring Length + eth_dma.dmacrx_rlr.write(|w| { + w.rdrl() + .variant((self.ring.descriptors().count() - 1) as u16) + }); + + // Set the tail pointer + eth_dma + .dmacrx_dtpr + .write(|w| unsafe { w.bits(self.ring.last_descriptor() as *const _ as u32) }); + + // Set receive buffer size + let receive_buffer_size = self.ring.last_buffer().len() as u16; + assert!(receive_buffer_size % 4 == 0); + + eth_dma.dmacrx_cr.modify(|_, w| unsafe { + w + // Start receive + .sr() + .set_bit() + // Set receive buffer size + .rbsz() + .bits(receive_buffer_size >> 1) + // AUtomatically flush on bus error + .rpf() + .set_bit() + }); + } + + let me = RxRing { + ring: self.ring, + next_entry: self.next_entry, + state: Default::default(), + }; + + me.demand_poll(); + + me + } +} + +impl<'data> RxRing<'data, Running> { + /// Demand that the DMA engine polls the current `RxDescriptor` + /// (when in `RunningState::Stopped`.) + pub fn demand_poll(&self) { + // # SAFETY + // + // On F7, we only perform an atomic write to `damrpdr`. + // + // On H7, we only perform a Read-Write to `dmacrx_dtpr`, + // always with the same value. Running `demand_poll` concurrently + // with the other location in which this register is written ([`RxRing::start`]) + // is impossible, which is guaranteed the state transition from NotRunning to + // Running. + let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; + + #[cfg(feature = "f-series")] + eth_dma.dmarpdr.write(|w| unsafe { w.rpd().bits(1) }); + + // On H7, we poll by re-writing the tail pointer register. + #[cfg(feature = "stm32h7xx-hal")] + eth_dma + .dmacrx_dtpr + .modify(|r, w| unsafe { w.bits(r.bits()) }); + } + + /// Stop the DMA engine. + pub fn stop(&mut self, eth_dma: ÐERNET_DMA) { + #[cfg(feature = "f-series")] + let start_reg = ð_dma.dmaomr; + + #[cfg(feature = "stm32h7xx-hal")] + let start_reg = ð_dma.dmacrx_cr; + + start_reg.modify(|_, w| w.sr().clear_bit()); + + while self.running_state() != RunningState::Stopped {} + } + + /// Receive the next packet (if any is ready), or return `None` + /// immediately. + pub fn recv_next( + &mut self, + #[allow(unused_variables)] packet_id: Option, + ) -> Result { + if !self.running_state().is_running() { + self.demand_poll(); + } + + let entry = self.next_entry; + let entries_len = self.ring.len(); + + let (descriptor, buffer) = self.ring.get(entry); + + let mut res = descriptor.take_received(packet_id, buffer); + + if res.as_mut().err() != Some(&mut RxError::WouldBlock) { + self.next_entry = (self.next_entry + 1) % entries_len; + } + + #[cfg(all(feature = "ptp", feature = "stm32h7xx-hal"))] + let (timestamp, descriptor, buffer) = { + if res.is_ok() { + let desc_has_timestamp = descriptor.has_timestamp(); + + drop(descriptor); + drop(buffer); + + // On H7's, the timestamp is stored in the next Context + // descriptor. + let (ctx_descriptor, ctx_des_buffer) = self.ring.get(self.next_entry); + + let timestamp = if desc_has_timestamp { + if let Some(timestamp) = ctx_descriptor.read_timestamp() { + Some(timestamp) + } else { + None + } + } else { + None + }; + + if !ctx_descriptor.is_owned() { + // Advance over this buffer + self.next_entry = (self.next_entry + 1) % entries_len; + ctx_descriptor.set_owned(&ctx_des_buffer); + } + + let (descriptor, buffer) = self.ring.get(entry); + + descriptor.attach_timestamp(timestamp); + + (timestamp, descriptor, buffer) + } else { + let (descriptor, buffer) = self.ring.get(entry); + descriptor.attach_timestamp(None); + (None, descriptor, buffer) + } + }; + + res.map(move |_| { + #[cfg(all(feature = "ptp", feature = "f-series"))] + let timestamp = descriptor.read_timestamp(); + + RxPacket { + entry: descriptor, + buffer, + #[cfg(feature = "ptp")] + timestamp, + } + }) + } + + pub fn available(&mut self) -> bool { + let (desc, _) = self.ring.get(self.next_entry); + !desc.is_owned() + } +} + +#[cfg(feature = "ptp")] +impl<'data, STATE> RxRing<'data, STATE> { + pub fn get_timestamp_for_id(&self, id: PacketId) -> Result { + for descriptor in self.ring.descriptors() { + if let (Some(packet_id), Some(timestamp)) = + (descriptor.packet_id(), descriptor.timestamp()) + { + if packet_id == &id { + return Ok(timestamp.clone()); + } + } + } + return Err(TimestampError::IdNotFound); + } +} + +/// Running state of the `RxRing` +#[derive(PartialEq, Eq, Debug)] +pub enum RunningState { + Unknown, + Stopped, + Running, +} + +impl RunningState { + /// whether self equals to `RunningState::Running` + pub fn is_running(&self) -> bool { + *self == RunningState::Running + } +} + +/// A received packet. +/// +/// This packet implements [Deref<\[u8\]>](core::ops::Deref) and should be used +/// as a slice. +pub struct RxPacket<'a> { + entry: &'a mut RxDescriptor, + buffer: &'a mut [u8], + #[cfg(feature = "ptp")] + timestamp: Option, +} + +impl<'a> core::ops::Deref for RxPacket<'a> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.buffer[0..self.entry.frame_length()] + } +} + +impl<'a> core::ops::DerefMut for RxPacket<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer[0..self.entry.frame_length()] + } +} + +impl<'a> Drop for RxPacket<'a> { + fn drop(&mut self) { + self.entry.set_owned(self.buffer); + } +} + +impl<'a> RxPacket<'a> { + /// Pass the received packet back to the DMA engine. + pub fn free(self) { + drop(self) + } + + /// Get the timestamp associated with this packet + #[cfg(feature = "ptp")] + pub fn timestamp(&self) -> Option { + self.timestamp + } +} diff --git a/src/dma/smoltcp_phy.rs b/src/dma/smoltcp_phy.rs index 4829409..8a13b5b 100644 --- a/src/dma/smoltcp_phy.rs +++ b/src/dma/smoltcp_phy.rs @@ -1,5 +1,5 @@ -use super::rx::RxRing; -use super::tx::TxRing; +use super::rx::{self, RxRing}; +use super::tx::{self, TxRing}; use super::EthernetDMA; use smoltcp::phy::{ChecksumCapabilities, Device, DeviceCapabilities, RxToken, TxToken}; use smoltcp::time::Instant; @@ -45,7 +45,7 @@ impl<'a, 'rx, 'tx> Device for &'a mut EthernetDMA<'rx, 'tx> { /// An Ethernet RX token that can be consumed in order to receive /// an ethernet packet. pub struct EthRxToken<'a, 'rx> { - rx_ring: &'a mut RxRing<'rx>, + rx_ring: &'a mut RxRing<'rx, rx::Running>, } impl<'dma, 'rx> RxToken for EthRxToken<'dma, 'rx> { @@ -64,7 +64,7 @@ impl<'dma, 'rx> RxToken for EthRxToken<'dma, 'rx> { /// Just a reference to [`EthernetDMA`] for sending a /// packet later with [`TxToken::consume()`]. pub struct EthTxToken<'a, 'tx> { - tx_ring: &'a mut TxRing<'tx>, + tx_ring: &'a mut TxRing<'tx, tx::Running>, } impl<'dma, 'tx> TxToken for EthTxToken<'dma, 'tx> { diff --git a/src/dma/tx.rs b/src/dma/tx.rs deleted file mode 100644 index 7dd68ba..0000000 --- a/src/dma/tx.rs +++ /dev/null @@ -1,453 +0,0 @@ -use super::PacketId; -use crate::peripherals::ETHERNET_DMA; - -#[cfg(feature = "ptp")] -use super::{Timestamp, TimestampError}; - -use core::{ - ops::{Deref, DerefMut}, - sync::atomic::{self, Ordering}, -}; - -use super::{ - desc::Descriptor, - ring::{RingDescriptor, RingEntry}, -}; - -/// Owned by DMA engine -const TXDESC_0_OWN: u32 = 1 << 31; -/// Interrupt on completion -const TXDESC_0_IC: u32 = 1 << 30; -/// First segment of frame -const TXDESC_0_FS: u32 = 1 << 28; -/// Last segment of frame -const TXDESC_0_LS: u32 = 1 << 29; -/// Checksum insertion control -const TXDESC_0_CIC0: u32 = 1 << 23; -const TXDESC_0_CIC1: u32 = 1 << 22; -/// Timestamp this packet -const TXDESC_0_TIMESTAMP_ENABLE: u32 = 1 << 25; -/// This descriptor contains a timestamp -// NOTE(allow): packet_id is unused if ptp is disabled. -#[allow(dead_code)] -const TXDESC_0_TIMESTAMP_STATUS: u32 = 1 << 17; -/// Transmit end of ring -const TXDESC_0_TER: u32 = 1 << 21; -/// Second address chained -const TXDESC_0_TCH: u32 = 1 << 20; -/// Error status -const TXDESC_0_ES: u32 = 1 << 15; -/// TX done bit -const TXDESC_1_TBS_SHIFT: usize = 0; -const TXDESC_1_TBS_MASK: u32 = 0x0fff << TXDESC_1_TBS_SHIFT; -/// An empty mask -const TXDESC_0_MASK_NONE: u32 = 0; - -/// Errors that can occur during Ethernet TX -#[derive(Debug, PartialEq)] -pub enum TxError { - /// Ring buffer is full - WouldBlock, -} - -/// A TX DMA Ring Descriptor -#[repr(C)] -#[derive(Clone)] -pub struct TxDescriptor { - pub(crate) desc: Descriptor, - pub(crate) packet_id: Option, - buffer_address: u32, - next_descriptor: u32, - /// The value that we want TDES0 to have when - /// the OWNED bit is set. - /// - /// We use a value tracked by this descriptor because - /// the DMA may reset any of the control bits in TDES0 - /// when writing to or updating a descriptor. - /// - /// Control bits are: `IC`, `LS`, `FS`, `DC`, `DP`, `CIC[0:1]`, `TER`, `TCH` - tdes0: u32, - #[cfg(feature = "ptp")] - cached_timestamp: Option, -} - -impl Default for TxDescriptor { - fn default() -> Self { - Self::new() - } -} - -impl TxDescriptor { - /// Creates an zeroed TxDescriptor. - pub const fn new() -> Self { - Self { - desc: Descriptor::new(), - packet_id: None, - buffer_address: 0, - next_descriptor: 0, - tdes0: TXDESC_0_TCH | TXDESC_0_FS | TXDESC_0_LS | TXDESC_0_CIC0 | TXDESC_0_CIC1, - #[cfg(feature = "ptp")] - cached_timestamp: None, - } - } - - /// Write the cached `tdes0` value to the actual - /// TDES word in memory. - fn write_tdes0(&mut self, extra_bits: u32) { - unsafe { - let tdes0 = self.tdes0 | extra_bits; - self.desc.write(0, tdes0); - } - } - - #[allow(unused)] - fn has_error(&self) -> bool { - (self.desc.read(0) & TXDESC_0_ES) == TXDESC_0_ES - } - - /// Is owned by the DMA engine? - pub fn is_owned(&self) -> bool { - (self.desc.read(0) & TXDESC_0_OWN) == TXDESC_0_OWN - } - - // NOTE(allow): packet_id is unused if ptp is disabled. - #[allow(dead_code)] - fn is_last(tdes0: u32) -> bool { - tdes0 & TXDESC_0_LS == TXDESC_0_LS - } - - /// Pass ownership to the DMA engine - fn set_owned(&mut self, extra_status_flags: u32) { - self.write_buffer1(); - self.write_buffer2(); - - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - atomic::fence(Ordering::Release); - atomic::compiler_fence(Ordering::Release); - - self.write_tdes0(TXDESC_0_OWN | extra_status_flags); - - // Used to flush the store buffer as fast as possible to make the buffer available for the - // DMA. - #[cfg(feature = "fence")] - atomic::fence(Ordering::SeqCst); - } - - /// Rewrite buffer1 to the last value we wrote to it - /// - /// In our case, the address of the data buffer for this descriptor - fn write_buffer1(&mut self) { - let buffer_addr = self.buffer_address; - unsafe { - self.desc.write(2, buffer_addr); - } - } - - fn set_buffer1_len(&mut self, len: usize) { - unsafe { - self.desc.modify(1, |w| { - (w & !TXDESC_1_TBS_MASK) | ((len as u32) << TXDESC_1_TBS_SHIFT) - }); - } - } - - /// Rewrite buffer2 to the last value we wrote it to - /// - /// In our case, the address of the next descriptor (may be zero) - fn write_buffer2(&mut self) { - let value = self.next_descriptor; - - unsafe { - self.desc.write(3, value); - } - } - - #[cfg(feature = "ptp")] - fn timestamp(&mut self) -> Option { - let tdes0 = self.desc.read(0); - - let contains_timestamp = (tdes0 & TXDESC_0_TIMESTAMP_STATUS) == TXDESC_0_TIMESTAMP_STATUS; - - if !self.is_owned() && contains_timestamp && Self::is_last(tdes0) { - Timestamp::from_descriptor(&self.desc) - } else { - None - } - } -} - -/// A TX DMA Ring Descriptor entry -pub type TxRingEntry = RingEntry; - -impl RingDescriptor for TxDescriptor { - fn setup(&mut self, buffer: *const u8, _len: usize, next: Option<&Self>) { - // Defer this initialization to this function, so we can have `RingEntry` on bss. - - self.buffer_address = buffer as u32; - self.write_buffer1(); - - let next_desc_addr = if let Some(next) = next { - &next.desc as *const Descriptor as *const u8 as u32 - } else { - self.tdes0 |= TXDESC_0_TER; - 0 - }; - - self.next_descriptor = next_desc_addr; - self.write_buffer2(); - - self.write_tdes0(TXDESC_0_MASK_NONE); - } -} - -impl TxRingEntry { - /// The initial value for a TxRingEntry - pub const TX_INIT: Self = Self::new(); - - fn prepare_packet(&mut self, length: usize, packet_id: Option) -> Option { - assert!(length <= self.as_slice().len()); - - if !self.desc().is_owned() { - let mut extra_flags = TXDESC_0_MASK_NONE; - - self.desc_mut().set_buffer1_len(length); - - if packet_id.is_some() { - extra_flags |= TXDESC_0_TIMESTAMP_ENABLE | TXDESC_0_LS | TXDESC_0_FS; - } - self.desc_mut().packet_id = packet_id; - - extra_flags |= TXDESC_0_IC; - - Some(TxPacket { - entry: self, - length, - extra_flags, - }) - } else { - None - } - } -} - -pub struct TxPacket<'a> { - entry: &'a mut TxRingEntry, - length: usize, - extra_flags: u32, -} - -impl<'a> Deref for TxPacket<'a> { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.entry.as_slice()[0..self.length] - } -} - -impl<'a> DerefMut for TxPacket<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.entry.as_mut_slice()[0..self.length] - } -} - -impl<'a> TxPacket<'a> { - // Pass to DMA engine - pub fn send(self) { - self.entry.desc_mut().set_owned(self.extra_flags); - } -} - -/// Tx DMA state -pub struct TxRing<'a> { - pub(crate) entries: &'a mut [TxRingEntry], - next_entry: usize, -} - -impl<'a> TxRing<'a> { - #[cfg(feature = "ptp")] - pub(crate) fn collect_timestamps(&mut self) { - for entry in self.entries.iter_mut() { - // Clear all old timestamps - entry.desc_mut().cached_timestamp.take(); - - if entry.desc().packet_id.is_some() { - if let Some(timestamp) = entry.desc_mut().timestamp() { - entry.desc_mut().cached_timestamp = Some(timestamp); - } - } - } - } - - #[cfg(feature = "ptp")] - pub(crate) fn get_timestamp_for_id( - &mut self, - id: PacketId, - ) -> Result { - let mut id_found = false; - for entry in self.entries.iter_mut() { - let TxDescriptor { - cached_timestamp: timestamp, - packet_id, - .. - } = entry.desc_mut(); - - if let Some(packet_id) = packet_id { - if packet_id == &id { - id_found = true; - if let Some(timestamp) = timestamp { - let ts = *timestamp; - entry.desc_mut().cached_timestamp.take(); - return Ok(ts); - } - } - } - } - - if !id_found { - Err(TimestampError::IdNotFound) - } else { - Err(TimestampError::NotYetTimestamped) - } - } - - /// Allocate - /// - /// `start()` will be needed before `send()` - pub fn new(entries: &'a mut [TxRingEntry]) -> Self { - TxRing { - entries, - next_entry: 0, - } - } - - /// Start the Tx DMA engine - pub fn start(&mut self, eth_dma: ÐERNET_DMA) { - // Setup ring - { - let mut previous: Option<&mut TxRingEntry> = None; - for entry in self.entries.iter_mut() { - if let Some(prev_entry) = &mut previous { - prev_entry.setup(Some(entry)); - } - previous = Some(entry); - } - if let Some(entry) = &mut previous { - entry.setup(None); - } - } - - let ring_ptr = self.entries[0].desc() as *const TxDescriptor; - // Register TxDescriptor - eth_dma - .dmatdlar - // Note: unsafe block required for `stm32f107`. - .write(|w| unsafe { w.stl().bits(ring_ptr as u32) }); - - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - atomic::fence(Ordering::Release); - - // We don't need a compiler fence here because all interactions with `Descriptor` are - // volatiles - - // Start transmission - eth_dma.dmaomr.modify(|_, w| w.st().set_bit()); - } - - pub fn next_entry_available(&self) -> bool { - !self.entries[self.next_entry].desc().is_owned() - } - - pub fn send R, R>( - &mut self, - length: usize, - packet_id: Option, - f: F, - ) -> Result { - let entries_len = self.entries.len(); - let entry_num = self.next_entry; - - match self.entries[entry_num].prepare_packet(length, packet_id) { - Some(mut pkt) => { - let r = f(pkt.deref_mut()); - pkt.send(); - - self.next_entry += 1; - if self.next_entry >= entries_len { - self.next_entry = 0; - } - - self.demand_poll(); - - Ok(r) - } - None => Err(TxError::WouldBlock), - } - } - - /// Demand that the DMA engine polls the current `TxDescriptor` - /// (when we just transferred ownership to the hardware). - pub fn demand_poll(&self) { - // SAFETY: we only perform an atomic write to `dmatpdr` - let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - eth_dma.dmatpdr.write(|w| { - #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] - { - w.tpd().poll() - } - #[cfg(feature = "stm32f1xx-hal")] - unsafe { - // TODO: There is no nice `poll` method for `stm32f107`? - w.tpd().bits(0) - } - }); - } - - /// Is the Tx DMA engine running? - pub fn is_running(&self) -> bool { - self.running_state().is_running() - } - - fn running_state(&self) -> RunningState { - // SAFETY: we only perform an atomic read of `dmasr`. - let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; - - match eth_dma.dmasr.read().tps().bits() { - // Reset or Stop Transmit Command issued - 0b000 => RunningState::Stopped, - // Fetching transmit transfer descriptor - 0b001 => RunningState::Running, - // Waiting for status - 0b010 => RunningState::Running, - // Reading Data from host memory buffer and queuing it to transmit buffer - 0b011 => RunningState::Running, - 0b100 | 0b101 => RunningState::Reserved, - // Transmit descriptor unavailable - 0b110 => RunningState::Suspended, - _ => RunningState::Unknown, - } - } -} - -#[derive(Debug, PartialEq)] -enum RunningState { - /// Reset or Stop Transmit Command issued - Stopped, - /// Fetching transmit transfer descriptor; - /// Waiting for status; - /// Reading Data from host memory buffer and queuing it to transmit buffer - Running, - /// Reserved for future use - Reserved, - /// Transmit descriptor unavailable - Suspended, - /// Invalid value - Unknown, -} - -impl RunningState { - pub fn is_running(&self) -> bool { - *self == RunningState::Running - } -} diff --git a/src/dma/tx/f_series_desc.rs b/src/dma/tx/f_series_desc.rs new file mode 100644 index 0000000..152c9f7 --- /dev/null +++ b/src/dma/tx/f_series_desc.rs @@ -0,0 +1,185 @@ +use core::sync::atomic::{self, Ordering}; + +use crate::dma::{raw_descriptor::RawDescriptor, PacketId}; + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +// Transmit end of ring +const TXDESC_0_TER: u32 = 1 << 21; +/// Owned by DMA engine +const TXDESC_0_OWN: u32 = 1 << 31; +/// Interrupt on completion +const TXDESC_0_IC: u32 = 1 << 30; +/// First segment of frame +const TXDESC_0_FS: u32 = 1 << 28; +/// Last segment of frame +const TXDESC_0_LS: u32 = 1 << 29; +/// Checksum insertion control +const TXDESC_0_CIC0: u32 = 1 << 23; +const TXDESC_0_CIC1: u32 = 1 << 22; +/// Timestamp this packet +// NOTE(allow): packet_id is unused if ptp is disabled. +#[allow(dead_code)] +const TXDESC_0_TIMESTAMP_ENABLE: u32 = 1 << 25; +/// This descriptor contains a timestamp +// NOTE(allow): packet_id is unused if ptp is disabled. +#[allow(dead_code)] +const TXDESC_0_TIMESTAMP_STATUS: u32 = 1 << 17; +/// Error status +const TXDESC_0_ES: u32 = 1 << 15; +/// Transmit buffer 1 size +const TXDESC_1_TBS1_SHIFT: usize = 0; +/// Transmit buffer 1 size mask +const TXDESC_1_TBS1_MASK: u32 = 0x0fff << TXDESC_1_TBS1_SHIFT; +/// Transmit buffer 2 size +const TXDESC_1_TBS2_SHIFT: usize = 16; +/// Transmit buffer 2 size mask +const TXDESC_1_TBS2_MASK: u32 = 0x0fff << TXDESC_1_TBS2_SHIFT; + +/// A TX DMA Ring Descriptor +#[repr(C)] +#[repr(align(4))] +#[derive(Clone, Copy)] +pub struct TxDescriptor { + inner_raw: RawDescriptor, + packet_id: Option, + #[cfg(feature = "ptp")] + cached_timestamp: Option, +} + +impl Default for TxDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl TxDescriptor { + /// Creates an zeroed TxDescriptor. + pub const fn new() -> Self { + Self { + inner_raw: RawDescriptor::new(), + packet_id: None, + #[cfg(feature = "ptp")] + cached_timestamp: None, + } + } + + pub(super) fn setup(&mut self) { + (0..crate::dma::raw_descriptor::DESC_SIZE) + .for_each(|i| unsafe { self.inner_raw.write(i, 0) }); + } + + #[allow(unused)] + fn has_error(&self) -> bool { + (self.inner_raw.read(0) & TXDESC_0_ES) == TXDESC_0_ES + } + + /// Is owned by the DMA engine? + pub fn is_owned(&self) -> bool { + (self.inner_raw.read(0) & TXDESC_0_OWN) == TXDESC_0_OWN + } + + // NOTE(allow): packet_id is unused if ptp is disabled. + #[allow(dead_code)] + fn is_last(tdes0: u32) -> bool { + tdes0 & TXDESC_0_LS == TXDESC_0_LS + } + + /// Pass ownership to the DMA engine + pub(super) fn send(&mut self, packet_id: Option, buffer: &[u8]) { + self.set_buffer(buffer); + + let extra_flags = if packet_id.is_some() { + if cfg!(feature = "ptp") { + TXDESC_0_TIMESTAMP_ENABLE + } else { + 0 + } + } else { + 0 + }; + + self.packet_id = packet_id; + + // "Preceding reads and writes cannot be moved past subsequent writes." + #[cfg(feature = "fence")] + atomic::fence(Ordering::Release); + atomic::compiler_fence(Ordering::Release); + + unsafe { + self.inner_raw.modify(0, |w| { + w | extra_flags + | TXDESC_0_OWN + | TXDESC_0_CIC0 + | TXDESC_0_CIC1 + | TXDESC_0_FS + | TXDESC_0_LS + | TXDESC_0_IC + }) + }; + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + #[cfg(feature = "fence")] + atomic::fence(Ordering::SeqCst); + } + + /// Configure the buffer to use for transmitting, + /// setting it to `buffer`. + fn set_buffer(&mut self, buffer: &[u8]) { + unsafe { + let ptr = buffer.as_ptr(); + + // Set buffer pointer 2 to the provided buffer. + self.inner_raw.write(3, ptr as u32); + + self.inner_raw.modify(1, |w| { + // If we set tbs1 to 0, the DMA will + // ignore this buffer. + let w = w & !TXDESC_1_TBS1_MASK; + // Configure RBS2 as the provided buffer. + let w = w & !TXDESC_1_TBS2_MASK; + w | ((buffer.len() as u32) << TXDESC_1_TBS2_SHIFT) & TXDESC_1_TBS2_MASK + }); + } + } + + // Set the end of ring bit. + pub(super) fn set_end_of_ring(&mut self) { + unsafe { self.inner_raw.modify(0, |w| w | TXDESC_0_TER) }; + } +} + +#[cfg(feature = "ptp")] +impl TxDescriptor { + pub(super) fn packet_id(&self) -> Option<&PacketId> { + self.packet_id.as_ref() + } + + fn read_timestamp(&mut self) -> Option { + let tdes0 = self.inner_raw.read(0); + + let contains_timestamp = (tdes0 & TXDESC_0_TIMESTAMP_STATUS) == TXDESC_0_TIMESTAMP_STATUS; + + if !self.is_owned() && contains_timestamp && Self::is_last(tdes0) { + #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] + let (high, low) = { (self.inner_raw.read(7), self.inner_raw.read(6)) }; + + #[cfg(feature = "stm32f1xx-hal")] + let (high, low) = { (self.inner_raw.read(3), self.inner_raw.read(2)) }; + + Some(Timestamp::from_parts(high, low)) + } else { + None + } + } + + pub(super) fn attach_timestamp(&mut self) { + self.cached_timestamp = self.read_timestamp(); + } + + pub(super) fn timestamp(&self) -> Option<&Timestamp> { + self.cached_timestamp.as_ref() + } +} diff --git a/src/dma/tx/h_desc.rs b/src/dma/tx/h_desc.rs new file mode 100644 index 0000000..48270c2 --- /dev/null +++ b/src/dma/tx/h_desc.rs @@ -0,0 +1,249 @@ +use core::sync::atomic::{self, Ordering}; + +use crate::dma::{raw_descriptor::RawDescriptor, PacketId}; + +#[cfg(feature = "ptp")] +use crate::ptp::Timestamp; + +mod consts { + + #![allow(unused)] + + // Both read and write-back formats + /// OWN bit + pub const TXDESC_3_OWN: u32 = 1 << 31; + /// Context Type + pub const TXDESC_3_CTXT: u32 = 1 << 30; + /// First descriptor + pub const TXDESC_3_FD: u32 = 1 << 29; + /// Last descriptor + pub const TXDESC_3_LD: u32 = 1 << 28; + + // Read format + /// Interrupt On Completion + pub const TXDESC_2_IOC: u32 = 1 << 31; + /// Transmit Timestamp Enable + pub const TXDESC_2_TTSE: u32 = 1 << 30; + /// Buffer 2 length shift + pub const TXDESC_2_B2L_SHIFT: u32 = 16; + /// Buffer 2 length mask + pub const TXDESC_2_B2L_MASK: u32 = 0x3FFF << TXDESC_2_B2L_SHIFT; + + /// VLAN Tag Insertion or Replacement shift + pub const TXDESC_2_VTIR_SHIFT: u32 = 14; + /// VLAN Tag Insertion or Replacement + #[repr(u32)] + #[allow(non_camel_case_types)] + pub enum TXDESC_2_VTIR { + DontAdd = 0b00 << 14, + RemoveTransmitVlanTag = 0b01 << TXDESC_2_VTIR_SHIFT, + InsertVlanTag = 0b10 << TXDESC_2_VTIR_SHIFT, + ReplaceVlanTag = 0b11 << TXDESC_2_VTIR_SHIFT, + } + /// VLAN Tag Insertion Or Replacement mask + pub const TXDESC_2_VTIR_MASK: u32 = 0b11 << TXDESC_2_VTIR_SHIFT; + + /// Header or Buffer 1 length shift + pub const TXDESC_2_HEAD_B1L_SHIFT: u32 = 0; + /// Header or Buffer 1 length mask + pub const TXDESC_2_HEAD_B1L_MASK: u32 = 0x3FFF << TXDESC_2_HEAD_B1L_SHIFT; + + // CRC Pad Control shift + pub const TXDESC_3_CPC_SHIFT: u32 = 26; + /// CRC Pad Control + #[repr(u32)] + #[allow(non_camel_case_types)] + pub enum TXDESC_3_CPC { + CRCAndPadInsertion = 0b00 << TXDESC_3_CPC_SHIFT, + CRCInsertionOnly = 0b01 << TXDESC_3_CPC_SHIFT, + Disabled = 0b10 << TXDESC_3_CPC_SHIFT, + CRCReplacement = 0b11 << TXDESC_3_CPC_SHIFT, + } + /// CRC Pad Control mask + pub const TXDESC_3_CPC_MASK: u32 = 0b11 << TXDESC_3_CPC_SHIFT; + + /// Checksum Insertion Control shift + pub const TXDESC_3_CIC_SHIFT: u32 = 16; + /// Checksum Insertion Control + #[repr(u32)] + #[allow(non_camel_case_types)] + pub enum TXDESC_3_CIC { + Disabled = 0b00 << TXDESC_3_CIC_SHIFT, + IpHeaderOnly = 0b01 << TXDESC_3_CIC_SHIFT, + IpHeaderAndPayloadOnly = 0b10 << TXDESC_3_CIC_SHIFT, + IpHeaderAndPayloadAndPseudoHeader = 0b11 << TXDESC_3_CIC_SHIFT, + } + /// Checksum Insertion Control mask + pub const TXDESC_3_CIC_MASK: u32 = 0b11 << TXDESC_3_CIC_SHIFT; + + /// Packet length shift + pub const TXDESC_3_FL_SHIFT: u32 = 0; + /// Packet length mask + pub const TXDESC_3_FL_MASK: u32 = 0x3FFF << TXDESC_3_FL_SHIFT; + + // Write back format + /// Tx Timestamp status + pub const TXDESC_3_TTSS: u32 = 1 << 17; + /// Error Summary + pub const TXDESC_3_ES: u32 = 1 << 15; + /// Jabber timeout + pub const TXDESC_3_JT: u32 = 1 << 14; + /// Packet flushed + pub const TXDESC_3_FF: u32 = 1 << 13; + /// Payload Checksum Error + pub const TXDESC_3_PCE: u32 = 1 << 12; + /// Loss of Carrier + pub const TXDESC_3_LOC: u32 = 1 << 11; + /// No Carrier + pub const TXDESC_3_NC: u32 = 1 << 10; + /// Late Collision + pub const TXDESC_3_LC: u32 = 1 << 9; + /// Excessive Collision + pub const TXDESC_3_EC: u32 = 1 << 8; + + /// Collision count shift + pub const TXDESC_3_CC_SHIFT: u32 = 4; + /// Collision Count mask + pub const TXDESC_3_CC_MASK: u32 = 0b1111 << TXDESC_3_CC_SHIFT; + + /// Excessive Deferral + pub const TXDESC_3_ED: u32 = 1 << 3; + /// Underflow error + pub const TXDESC_3_UF: u32 = 1 << 2; + /// Deferred Bit + pub const TXDESC_3_DB: u32 = 1 << 1; + /// IP Header Error + pub const TXDESC_3_IHE: u32 = 1 << 0; +} +pub use consts::*; + +/// A TX DMA Ring Descriptor +#[repr(C, align(4))] +#[derive(Clone, Copy)] +pub struct TxDescriptor { + inner_raw: RawDescriptor, + packet_id: Option, + #[cfg(feature = "ptp")] + cached_timestamp: Option, +} + +impl Default for TxDescriptor { + fn default() -> Self { + Self::new() + } +} + +impl TxDescriptor { + /// Creates an zeroed TxDescriptor. + pub const fn new() -> Self { + Self { + inner_raw: RawDescriptor::new(), + packet_id: None, + #[cfg(feature = "ptp")] + cached_timestamp: None, + } + } + + #[allow(unused)] + fn is_last(&self) -> bool { + (self.inner_raw.read(3) & TXDESC_3_LD) == TXDESC_3_LD + } + + pub(super) fn setup(&mut self) { + // Zero-out all fields in the descriptor + (0..4).for_each(|n| unsafe { self.inner_raw.write(n, 0) }); + self.packet_id.take(); + } + + pub(super) fn is_owned(&self) -> bool { + (self.inner_raw.read(3) & TXDESC_3_OWN) == TXDESC_3_OWN + } + + #[allow(unused)] + pub(super) fn is_context(&self) -> bool { + (self.inner_raw.read(3) & TXDESC_3_CTXT) == TXDESC_3_CTXT + } + + #[allow(unused)] + pub(super) fn packet_id(&self) -> Option<&PacketId> { + self.packet_id.as_ref() + } + + /// Pass ownership to the DMA engine + pub(super) fn send(&mut self, packet_id: Option, buffer: &[u8]) { + self.set_buffer(buffer); + + if packet_id.is_some() && cfg!(feature = "ptp") { + unsafe { + self.inner_raw.modify(2, |w| w | TXDESC_2_TTSE); + } + } + + self.packet_id = packet_id; + + // "Preceding reads and writes cannot be moved past subsequent writes." + atomic::fence(Ordering::Release); + atomic::compiler_fence(Ordering::Release); + + unsafe { + self.inner_raw.modify(2, |w| w | TXDESC_2_IOC); + + let tx_len = ((buffer.len() as u32) << TXDESC_3_FL_SHIFT) & TXDESC_3_FL_MASK; + + self.inner_raw.modify(3, |w| { + w | TXDESC_3_OWN + | TXDESC_3_CIC::IpHeaderAndPayloadAndPseudoHeader as u32 + | TXDESC_3_FD + | TXDESC_3_LD + | tx_len + }) + }; + + // Used to flush the store buffer as fast as possible to make the buffer available for the + // DMA. + #[cfg(feature = "fence")] + atomic::fence(Ordering::SeqCst); + } + + /// Configure the buffer to use for transmitting, + /// setting it to `buffer`. + fn set_buffer(&mut self, buffer: &[u8]) { + unsafe { + let ptr = buffer.as_ptr(); + + // Set buffer pointer 1 to the provided buffer. + self.inner_raw.write(0, ptr as u32); + + self.inner_raw.modify(2, |w| { + // Clear out B1L + let w = w & !TXDESC_2_HEAD_B1L_MASK; + // Clear out B2L + let w = w & !TXDESC_2_B2L_MASK; + // Set B1L + w | ((buffer.len() as u32) << TXDESC_2_HEAD_B1L_SHIFT) & TXDESC_2_HEAD_B1L_MASK + }); + } + } +} + +#[cfg(feature = "ptp")] +impl TxDescriptor { + fn read_timestamp(&mut self) -> Option { + let contains_timestamp = (self.inner_raw.read(3) & TXDESC_3_TTSS) == TXDESC_3_TTSS; + + if !self.is_owned() && contains_timestamp && self.is_last() { + let (low, high) = (self.inner_raw.read(0), self.inner_raw.read(1)); + Some(Timestamp::from_parts(high, low)) + } else { + None + } + } + + pub(super) fn attach_timestamp(&mut self) { + self.cached_timestamp = self.read_timestamp(); + } + + pub(super) fn timestamp(&self) -> Option<&Timestamp> { + self.cached_timestamp.as_ref() + } +} diff --git a/src/dma/tx/mod.rs b/src/dma/tx/mod.rs new file mode 100644 index 0000000..2fe436e --- /dev/null +++ b/src/dma/tx/mod.rs @@ -0,0 +1,322 @@ +use core::marker::PhantomData; + +use super::{raw_descriptor::DescriptorRing, PacketId}; +use crate::peripherals::ETHERNET_DMA; + +#[cfg(feature = "ptp")] +use super::{Timestamp, TimestampError}; + +#[cfg(feature = "f-series")] +mod f_series_desc; +#[cfg(feature = "f-series")] +use f_series_desc as descriptor; + +#[cfg(feature = "stm32h7xx-hal")] +mod h_desc; +#[cfg(feature = "stm32h7xx-hal")] +use h_desc as descriptor; + +pub use descriptor::TxDescriptor; + +pub struct NotRunning; +pub struct Running; + +/// A TX descriptor ring. +pub type TxDescriptorRing<'rx> = DescriptorRing<'rx, TxDescriptor>; + +/// Errors that can occur during Ethernet TX +#[derive(Debug, PartialEq)] +pub enum TxError { + /// Ring buffer is full + WouldBlock, +} + +/// Tx DMA state +pub(crate) struct TxRing<'data, STATE> { + ring: TxDescriptorRing<'data>, + next_entry: usize, + state: PhantomData, +} + +impl<'data, STATE> TxRing<'data, STATE> { + pub fn running_state(&self) -> RunningState { + // SAFETY: we only perform an atomic read of `dmasr` or + // `dmadsr`. + let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; + + #[cfg(feature = "f-series")] + let tx_status = eth_dma.dmasr.read().tps().bits(); + + #[cfg(feature = "stm32h7xx-hal")] + let tx_status = eth_dma.dmadsr.read().tps0().bits(); + + match tx_status { + // Reset or Stop Transmit Command issued + 0b000 => RunningState::Stopped, + // Fetching transmit transfer descriptor + 0b001 => RunningState::Running, + // Waiting for status + 0b010 => RunningState::Running, + // Reading Data from host memory buffer and queuing it to transmit buffer + 0b011 => RunningState::Running, + + 0b101 => RunningState::Reserved, + + // Transmit descriptor unavailable + 0b110 => RunningState::Suspended, + + #[cfg(feature = "f-series")] + 0b100 => RunningState::Reserved, + + // Timestamp write + #[cfg(feature = "stm32h7xx-hal")] + 0b100 => RunningState::Running, + // Closing Tx descriptor + #[cfg(feature = "stm32h7xx-hal")] + 0b111 => RunningState::Running, + + _ => RunningState::Unknown, + } + } +} + +impl<'data> TxRing<'data, NotRunning> { + /// Allocate + /// + /// `start()` will be needed before `send()` + pub fn new(ring: TxDescriptorRing<'data>) -> Self { + TxRing { + ring, + next_entry: 0, + state: Default::default(), + } + } + + /// Start the Tx DMA engine + pub fn start(mut self, eth_dma: ÐERNET_DMA) -> TxRing<'data, Running> { + // Setup ring + for descriptor in self.ring.descriptors_mut() { + descriptor.setup(); + } + + #[cfg(feature = "f-series")] + // Set end of ring register + self.ring.last_descriptor_mut().set_end_of_ring(); + + let ring_ptr = self.ring.descriptors_start_address(); + + #[cfg(feature = "f-series")] + // Register TxDescriptor + eth_dma + .dmatdlar + // Note: unsafe block required for `stm32f107`. + .write(|w| unsafe { w.stl().bits(ring_ptr as u32) }); + + #[cfg(feature = "stm32h7xx-hal")] + { + let tx_descriptor_count = self.ring.descriptors().count(); + assert!(tx_descriptor_count >= 4); + + // Assert that the descriptors are properly aligned. + // + // FIXME: these require different alignment if the data is stored + // in AXI SRAM + assert!(ring_ptr as u32 % 4 == 0); + assert!(self.ring.last_descriptor() as *const _ as u32 % 4 == 0); + + // Set the start pointer. + eth_dma + .dmactx_dlar + .write(|w| unsafe { w.bits(ring_ptr as u32) }); + + // Set the Transmit Descriptor Ring Length + eth_dma.dmactx_rlr.write(|w| { + w.tdrl() + .variant((self.ring.descriptors().count() - 1) as u16) + }); + + // Set the tail pointer + eth_dma + .dmactx_dtpr + .write(|w| unsafe { w.bits(self.ring.last_descriptor_mut() as *const _ as u32) }); + } + + // "Preceding reads and writes cannot be moved past subsequent writes." + #[cfg(feature = "fence")] + core::sync::atomic::fence(core::sync::atomic::Ordering::Release); + + // We don't need a compiler fence here because all interactions with `Descriptor` are + // volatiles + + #[cfg(feature = "f-series")] + let start_reg = ð_dma.dmaomr; + #[cfg(feature = "stm32h7xx-hal")] + let start_reg = ð_dma.dmactx_cr; + + // Start transmission + start_reg.modify(|_, w| w.st().set_bit()); + + TxRing { + ring: self.ring, + next_entry: self.next_entry, + state: Default::default(), + } + } +} + +impl<'data> TxRing<'data, Running> { + pub fn send R, R>( + &mut self, + length: usize, + packet_id: Option, + f: F, + ) -> Result { + let entries_len = self.ring.len(); + let entry_num = self.next_entry; + + let (descriptor, buffer) = self.ring.get(entry_num); + + assert!(length <= buffer.len()); + + if !descriptor.is_owned() { + let r = f(&mut buffer[0..length]); + + descriptor.send(packet_id, &buffer[0..length]); + + self.next_entry = (self.next_entry + 1) % entries_len; + + self.demand_poll(); + + Ok(r) + } else { + Err(TxError::WouldBlock) + } + } + + /// Check whether a descriptor is available for sending. + /// + /// If this function returns `true`, the next [`TxRing::send`] is + /// guaranteed to succeed. + pub fn available(&mut self) -> bool { + let (desc, _) = self.ring.get(self.next_entry); + !desc.is_owned() + } + + /// Demand that the DMA engine polls the current `TxDescriptor` + /// (when we just transferred ownership to the hardware). + fn demand_poll(&self) { + // # SAFETY + // + // On F7, we only perform an atomic write to `damrpdr`. + // + // On H7, we only perform a Read-Write to `dmacrx_dtpr`, + // always with the same value. Running `demand_poll` concurrently + // with the other location in which this register is written ([`TxRing::start`]) + // is impossible, which is guaranteed the state transition from NotRunning to + // Running. + let eth_dma = unsafe { &*ETHERNET_DMA::ptr() }; + + #[cfg(feature = "stm32h7xx-hal")] + // To issue a poll demand, write a value to + // the tail pointer. We just re-write the + // current value. + eth_dma + .dmactx_dtpr + .modify(|r, w| unsafe { w.bits(r.bits()) }); + + #[cfg(feature = "f-series")] + eth_dma.dmatpdr.write(|w| { + #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] + { + w.tpd().poll() + } + #[cfg(feature = "stm32f1xx-hal")] + unsafe { + // TODO: There is no nice `poll` method for `stm32f107`? + w.tpd().bits(0) + } + }); + } + + /// Is the Tx DMA engine running? + pub fn is_running(&self) -> bool { + self.running_state().is_running() + } + + pub fn stop(&mut self, eth_dma: ÐERNET_DMA) { + #[cfg(feature = "f-series")] + let start_reg = ð_dma.dmaomr; + #[cfg(feature = "stm32h7xx-hal")] + let start_reg = ð_dma.dmactx_cr; + + // Start transmission + start_reg.modify(|_, w| w.st().clear_bit()); + + while self.running_state() != RunningState::Stopped {} + } +} + +#[cfg(feature = "ptp")] +impl<'data> TxRing<'data, Running> { + pub(crate) fn collect_timestamps(&mut self) { + for descriptor in self.ring.descriptors_mut() { + descriptor.attach_timestamp(); + } + } + + pub(crate) fn get_timestamp_for_id(&self, id: PacketId) -> Result { + let descriptor = if let Some(descriptor) = + self.ring.descriptors().find(|d| d.packet_id() == Some(&id)) + { + descriptor + } else { + return Err(TimestampError::IdNotFound); + }; + + descriptor + .timestamp() + .map(|t| *t) + .ok_or(TimestampError::NotYetTimestamped) + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[derive(Debug, PartialEq)] +pub enum RunningState { + /// Reset or Stop Transmit Command issued + Stopped, + /// Fetching transmit transfer descriptor; + /// Waiting for status; + /// Reading Data from host memory buffer and queuing it to transmit buffer + Running, + /// Reserved for future use + Reserved, + /// Transmit descriptor unavailable + Suspended, + /// Invalid value + Unknown, +} + +impl RunningState { + pub fn is_running(&self) -> bool { + self == &RunningState::Running + } +} + +pub struct TxPacket<'a> { + buffer: &'a mut [u8], +} + +impl<'a> core::ops::Deref for TxPacket<'a> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl<'a> core::ops::DerefMut for TxPacket<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer + } +} diff --git a/src/lib.rs b/src/lib.rs index 6596ac3..6e0e00c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,9 @@ #[cfg(not(feature = "device-selected"))] compile_error!("No device was selected! Exactly one stm32fxxx feature must be selected."); +#[cfg(feature = "stm32h7xx-hal")] +pub use stm32h7xx_hal as hal; + /// Re-export #[cfg(feature = "stm32f7xx-hal")] pub use stm32f7xx_hal as hal; @@ -21,14 +24,16 @@ pub use stm32f1xx_hal as hal; #[cfg(feature = "device-selected")] pub use hal::pac as stm32; -#[cfg(feature = "device-selected")] +#[cfg(all(feature = "device-selected", not(feature = "stm32h7xx-hal")))] use hal::rcc::Clocks; +#[cfg(all(feature = "device-selected", feature = "stm32h7xx-hal"))] +use hal::rcc::CoreClocks as Clocks; #[cfg(feature = "device-selected")] pub mod dma; #[doc(inline)] #[cfg(feature = "device-selected")] -pub use dma::eth_interrupt_handler; +pub use dma::{eth_interrupt_handler, MTU}; #[cfg(feature = "device-selected")] pub mod mac; @@ -50,8 +55,8 @@ pub use smoltcp; #[cfg(feature = "device-selected")] use { - dma::{EthernetDMA, RxRingEntry, TxRingEntry}, - mac::{EthernetMAC, EthernetMACWithMii, MdcPin, MdioPin, Speed, WrongClock}, + dma::{DmaParts, EthernetDMA, RxDescriptorRing, TxDescriptorRing}, + mac::{EthernetMAC, EthernetMACWithMii, MacParts, MdcPin, MdioPin, Speed, WrongClock}, setup::*, }; @@ -79,8 +84,8 @@ use ptp::EthernetPTP; #[cfg(feature = "device-selected")] pub fn new<'rx, 'tx, REFCLK, CRS, TXEN, TXD0, TXD1, RXD0, RXD1>( parts: PartsIn, - rx_buffer: &'rx mut [RxRingEntry], - tx_buffer: &'tx mut [TxRingEntry], + rx_buffer: RxDescriptorRing<'rx>, + tx_buffer: TxDescriptorRing<'tx>, clocks: Clocks, pins: EthPins, ) -> Result, WrongClock> @@ -94,22 +99,38 @@ where RXD1: RmiiRxD1 + AlternateVeryHighSpeed, { // Configure all of the pins correctly + pins.setup_pins(); // Set up the clocks and reset the MAC periperhal setup::setup(); - let eth_mac = parts.mac.into(); + let dma_parts = DmaParts { + eth_dma: parts.dma.into(), + #[cfg(feature = "stm32h7xx-hal")] + eth_mtl: parts.mtl, + }; + + let mac_parts = MacParts { + eth_mac: parts.mac.into(), + #[cfg(feature = "f-series")] + eth_mmc: parts.mmc.into(), + }; // Congfigure and start up the ethernet DMA. - let dma = EthernetDMA::new(parts.dma.into(), rx_buffer, tx_buffer); + let dma = EthernetDMA::new(dma_parts, rx_buffer, tx_buffer); // Configure the ethernet PTP #[cfg(feature = "ptp")] - let ptp = EthernetPTP::new(parts.ptp.into(), clocks, &dma); + let ptp = EthernetPTP::new( + #[cfg(feature = "f-series")] + parts.ptp.into(), + clocks, + &dma, + ); // Configure the ethernet MAC - let mac = EthernetMAC::new(eth_mac, parts.mmc, clocks, Speed::FullDuplexBase100Tx, &dma)?; + let mac = EthernetMAC::new(mac_parts, clocks, Speed::FullDuplexBase100Tx, &dma)?; let parts = Parts { mac, @@ -141,11 +162,11 @@ where /// accessible by the peripheral. Core-Coupled Memory (CCM) is /// usually not accessible. /// - HCLK must be at least 25 MHz. -#[cfg(feature = "device-selected")] +#[cfg(all(feature = "device-selected"))] pub fn new_with_mii<'rx, 'tx, REFCLK, CRS, TXEN, TXD0, TXD1, RXD0, RXD1, MDIO, MDC>( parts: PartsIn, - rx_buffer: &'rx mut [RxRingEntry], - tx_buffer: &'tx mut [TxRingEntry], + rx_buffer: RxDescriptorRing<'rx>, + tx_buffer: TxDescriptorRing<'tx>, clocks: Clocks, pins: EthPins, mdio: MDIO, @@ -168,18 +189,33 @@ where // Set up the clocks and reset the MAC periperhal setup::setup(); - let eth_mac = parts.mac.into(); + let dma_parts = DmaParts { + eth_dma: parts.dma.into(), + #[cfg(feature = "stm32h7xx-hal")] + eth_mtl: parts.mtl, + }; + + let mac_parts = MacParts { + eth_mac: parts.mac.into(), + #[cfg(feature = "f-series")] + eth_mmc: parts.mmc.into(), + }; // Congfigure and start up the ethernet DMA. - let dma = EthernetDMA::new(parts.dma.into(), rx_buffer, tx_buffer); + let dma = EthernetDMA::new(dma_parts, rx_buffer, tx_buffer); // Configure the ethernet PTP #[cfg(feature = "ptp")] - let ptp = EthernetPTP::new(parts.ptp.into(), clocks, &dma); + let ptp = EthernetPTP::new( + #[cfg(feature = "f-series")] + parts.ptp.into(), + clocks, + &dma, + ); // Configure the ethernet MAC - let mac = EthernetMAC::new(eth_mac, parts.mmc, clocks, Speed::FullDuplexBase100Tx, &dma)? - .with_mii(mdio, mdc); + let mac = + EthernetMAC::new(mac_parts, clocks, Speed::FullDuplexBase100Tx, &dma)?.with_mii(mdio, mdc); let parts = Parts { mac, diff --git a/src/mac/miim.rs b/src/mac/miim.rs deleted file mode 100644 index 635898f..0000000 --- a/src/mac/miim.rs +++ /dev/null @@ -1,148 +0,0 @@ -pub use ieee802_3_miim::Miim; - -pub use ieee802_3_miim::*; - -use crate::{peripherals::ETHERNET_MAC, stm32::ethernet_mac::MACMIIAR}; - -use super::EthernetMAC; - -/// MDIO pin types. -/// -/// # Safety -/// Only pins specified as ETH_MDIO in a part's reference manual -/// may implement this trait -pub unsafe trait MdioPin {} - -/// MDC pin types. -/// -/// # Safety -/// Only pins specified as ETH_MDC in a part's reference manual -/// may implement this trait -pub unsafe trait MdcPin {} - -#[inline(always)] -fn miim_wait_ready(iar: &MACMIIAR) { - while iar.read().mb().bit_is_set() {} -} - -#[inline(always)] -fn miim_write(eth_mac: &mut ETHERNET_MAC, phy: u8, reg: u8, data: u16) { - miim_wait_ready(ð_mac.macmiiar); - eth_mac.macmiidr.write(|w| w.md().bits(data)); - - miim_wait_ready(ð_mac.macmiiar); - - eth_mac.macmiiar.modify(|_, w| { - w.pa() - .bits(phy) - .mr() - .bits(reg) - /* Write operation MW=1*/ - .mw() - .set_bit() - .mb() - .set_bit() - }); - miim_wait_ready(ð_mac.macmiiar); -} - -#[inline(always)] -fn miim_read(eth_mac: &mut ETHERNET_MAC, phy: u8, reg: u8) -> u16 { - miim_wait_ready(ð_mac.macmiiar); - eth_mac.macmiiar.modify(|_, w| { - w.pa() - .bits(phy) - .mr() - .bits(reg) - /* Read operation MW=0 */ - .mw() - .clear_bit() - .mb() - .set_bit() - }); - miim_wait_ready(ð_mac.macmiiar); - - // Return value: - eth_mac.macmiidr.read().md().bits() -} - -/// Serial Management Interface -/// -/// Borrows an [`EthernetMAC`] and holds a mutable borrow to the SMI pins. -pub struct Stm32Mii<'mac, 'pins, Mdio, Mdc> { - mac: &'mac mut EthernetMAC, - _mdio: &'pins mut Mdio, - _mdc: &'pins mut Mdc, -} - -impl<'mac, 'pins, Mdio, Mdc> Stm32Mii<'mac, 'pins, Mdio, Mdc> -where - Mdio: MdioPin, - Mdc: MdcPin, -{ - /// Read MII register `reg` from the PHY at address `phy` - pub fn read(&mut self, phy: u8, reg: u8) -> u16 { - miim_read(&mut self.mac.eth_mac, phy, reg) - } - - /// Write the value `data` to MII register `reg` to the PHY at address `phy` - pub fn write(&mut self, phy: u8, reg: u8, data: u16) { - miim_write(&mut self.mac.eth_mac, phy, reg, data) - } -} - -impl<'eth, 'pins, Mdio, Mdc> Miim for Stm32Mii<'eth, 'pins, Mdio, Mdc> -where - Mdio: MdioPin, - Mdc: MdcPin, -{ - fn read(&mut self, phy: u8, reg: u8) -> u16 { - self.read(phy, reg) - } - - fn write(&mut self, phy: u8, reg: u8, data: u16) { - self.write(phy, reg, data) - } -} - -impl<'eth, 'pins, Mdio, Mdc> Stm32Mii<'eth, 'pins, Mdio, Mdc> -where - Mdio: MdioPin, - Mdc: MdcPin, -{ - /// Create a temporary [`Stm32Mii`] instance. - /// - /// Temporarily take exclusive access to the MDIO and MDC pins to ensure they are not used - /// elsewhere for the duration of SMI communication. - pub fn new(mac: &'eth mut EthernetMAC, _mdio: &'pins mut Mdio, _mdc: &'pins mut Mdc) -> Self { - Self { mac, _mdio, _mdc } - } -} - -#[cfg(feature = "stm32f4xx-hal")] -mod pin_impls { - use crate::hal::gpio::{gpioa::PA2, gpioc::PC1, Alternate}; - - const AF11: u8 = 11; - - unsafe impl super::MdioPin for PA2> {} - unsafe impl super::MdcPin for PC1> {} -} - -#[cfg(feature = "stm32f7xx-hal")] -mod pin_impls { - use crate::hal::gpio::{gpioa::PA2, gpioc::PC1, Alternate}; - - const AF11: u8 = 11; - - unsafe impl super::MdioPin for PA2> {} - unsafe impl super::MdcPin for PC1> {} -} - -#[cfg(feature = "stm32f1xx-hal")] -mod pin_impls { - use crate::hal::gpio::{gpioa::PA2, gpioc::PC1, Alternate, PushPull}; - - unsafe impl super::MdioPin for PA2> {} - unsafe impl super::MdcPin for PC1> {} -} diff --git a/src/mac/miim/f_series_miim.rs b/src/mac/miim/f_series_miim.rs new file mode 100644 index 0000000..9149da3 --- /dev/null +++ b/src/mac/miim/f_series_miim.rs @@ -0,0 +1,76 @@ +use crate::peripherals::ETHERNET_MAC; +use crate::stm32::ethernet_mac::MACMIIAR; + +#[inline(always)] +fn miim_wait_ready(iar: &MACMIIAR) { + while iar.read().mb().bit_is_set() {} +} + +#[inline(always)] +pub(crate) fn miim_write(eth_mac: &mut ETHERNET_MAC, phy: u8, reg: u8, data: u16) { + miim_wait_ready(ð_mac.macmiiar); + eth_mac.macmiidr.write(|w| w.md().bits(data)); + + miim_wait_ready(ð_mac.macmiiar); + + eth_mac.macmiiar.modify(|_, w| { + w.pa() + .bits(phy) + .mr() + .bits(reg) + /* Write operation MW=1*/ + .mw() + .set_bit() + .mb() + .set_bit() + }); + miim_wait_ready(ð_mac.macmiiar); +} + +#[inline(always)] +pub(crate) fn miim_read(eth_mac: &mut ETHERNET_MAC, phy: u8, reg: u8) -> u16 { + miim_wait_ready(ð_mac.macmiiar); + eth_mac.macmiiar.modify(|_, w| { + w.pa() + .bits(phy) + .mr() + .bits(reg) + /* Read operation MW=0 */ + .mw() + .clear_bit() + .mb() + .set_bit() + }); + miim_wait_ready(ð_mac.macmiiar); + + // Return value: + eth_mac.macmiidr.read().md().bits() +} + +#[cfg(feature = "stm32f4xx-hal")] +mod pin_impls { + use crate::hal::gpio::{gpioa::PA2, gpioc::PC1, Alternate}; + + const AF11: u8 = 11; + + unsafe impl crate::mac::MdioPin for PA2> {} + unsafe impl crate::mac::MdcPin for PC1> {} +} + +#[cfg(feature = "stm32f7xx-hal")] +mod pin_impls { + use crate::hal::gpio::{gpioa::PA2, gpioc::PC1, Alternate}; + + const AF11: u8 = 11; + + unsafe impl crate::mac::MdioPin for PA2> {} + unsafe impl crate::mac::MdcPin for PC1> {} +} + +#[cfg(feature = "stm32f1xx-hal")] +mod pin_impls { + use crate::hal::gpio::{gpioa::PA2, gpioc::PC1, Alternate, PushPull}; + + unsafe impl crate::mac::MdioPin for PA2> {} + unsafe impl crate::mac::MdcPin for PC1> {} +} diff --git a/src/mac/miim/h_miim.rs b/src/mac/miim/h_miim.rs new file mode 100644 index 0000000..7771e75 --- /dev/null +++ b/src/mac/miim/h_miim.rs @@ -0,0 +1,59 @@ +use crate::peripherals::ETHERNET_MAC; +use crate::stm32::ethernet_mac::MACMDIOAR; + +use super::{MdcPin, MdioPin}; + +use crate::hal::gpio::{Alternate, PA2, PC1}; + +#[inline(always)] +fn miim_wait_ready(iar: &MACMDIOAR) { + while iar.read().mb().bit_is_set() {} +} + +#[inline(always)] +pub(crate) fn miim_write(eth_mac: &mut ETHERNET_MAC, phy: u8, reg: u8, data: u16) { + miim_wait_ready(ð_mac.macmdioar); + + eth_mac.macmdiodr.write(|w| unsafe { w.md().bits(data) }); + + miim_wait_ready(ð_mac.macmdioar); + + eth_mac.macmdioar.modify(|_, w| unsafe { + w.pa() + .bits(phy) + .rda() + .bits(reg) + /* Write operation GOC=01*/ + .goc() + .variant(0b01) + .mb() + .set_bit() + }); + + miim_wait_ready(ð_mac.macmdioar); +} + +#[inline(always)] +pub(crate) fn miim_read(eth_mac: &mut ETHERNET_MAC, phy: u8, reg: u8) -> u16 { + miim_wait_ready(ð_mac.macmdioar); + + eth_mac.macmdioar.modify(|_, w| unsafe { + w.pa() + .bits(phy) + .rda() + .bits(reg) + /* Write operation GOC=11*/ + .goc() + .variant(0b11) + .mb() + .set_bit() + }); + + miim_wait_ready(ð_mac.macmdioar); + + // Return value: + eth_mac.macmdiodr.read().md().bits() +} + +unsafe impl MdcPin for PC1> {} +unsafe impl MdioPin for PA2> {} diff --git a/src/mac/miim/mod.rs b/src/mac/miim/mod.rs new file mode 100644 index 0000000..e30ce5c --- /dev/null +++ b/src/mac/miim/mod.rs @@ -0,0 +1,176 @@ +pub use ieee802_3_miim::Miim; +pub use ieee802_3_miim::*; + +use core::ops::{Deref, DerefMut}; + +use super::EthernetMAC; + +#[cfg(feature = "f-series")] +mod f_series_miim; +#[cfg(feature = "f-series")] +use f_series_miim::{miim_read, miim_write}; + +#[cfg(feature = "stm32h7xx-hal")] +mod h_miim; +#[cfg(feature = "stm32h7xx-hal")] +use h_miim::{miim_read, miim_write}; + +/// MDIO pin types. +/// +/// # Safety +/// Only pins specified as ETH_MDIO in a part's reference manual +/// may implement this trait +pub unsafe trait MdioPin {} + +/// MDC pin types. +/// +/// # Safety +/// Only pins specified as ETH_MDC in a part's reference manual +/// may implement this trait +pub unsafe trait MdcPin {} + +/// Serial Management Interface +/// +/// Borrows an [`EthernetMAC`] and holds a mutable borrow to the SMI pins. +pub struct Stm32Mii<'mac, 'pins, Mdio, Mdc> { + mac: &'mac mut EthernetMAC, + _mdio: &'pins mut Mdio, + _mdc: &'pins mut Mdc, +} + +impl<'mac, 'pins, Mdio, Mdc> Stm32Mii<'mac, 'pins, Mdio, Mdc> +where + Mdio: MdioPin, + Mdc: MdcPin, +{ + /// Read MII register `reg` from the PHY at address `phy` + pub fn read(&mut self, phy: u8, reg: u8) -> u16 { + miim_read(&mut self.mac.eth_mac, phy, reg) + } + + /// Write the value `data` to MII register `reg` to the PHY at address `phy` + pub fn write(&mut self, phy: u8, reg: u8, data: u16) { + miim_write(&mut self.mac.eth_mac, phy, reg, data) + } +} + +impl<'eth, 'pins, Mdio, Mdc> Miim for Stm32Mii<'eth, 'pins, Mdio, Mdc> +where + Mdio: MdioPin, + Mdc: MdcPin, +{ + fn read(&mut self, phy: u8, reg: u8) -> u16 { + self.read(phy, reg) + } + + fn write(&mut self, phy: u8, reg: u8, data: u16) { + self.write(phy, reg, data) + } +} + +impl<'eth, 'pins, Mdio, Mdc> Stm32Mii<'eth, 'pins, Mdio, Mdc> +where + Mdio: MdioPin, + Mdc: MdcPin, +{ + /// Create a temporary [`Stm32Mii`] instance. + /// + /// Temporarily take exclusive access to the MDIO and MDC pins to ensure they are not used + /// elsewhere for the duration of SMI communication. + pub fn new(mac: &'eth mut EthernetMAC, _mdio: &'pins mut Mdio, _mdc: &'pins mut Mdc) -> Self { + Self { mac, _mdio, _mdc } + } +} + +/// Ethernet media access control (MAC) with owned MII +/// +/// This version of the struct owns it's MII pins, +/// allowing it to be used directly, instead of requiring +/// that a [`Miim`] is created. +pub struct EthernetMACWithMii +where + MDIO: MdioPin, + MDC: MdcPin, +{ + eth_mac: EthernetMAC, + mdio: MDIO, + mdc: MDC, +} + +impl EthernetMACWithMii +where + MDIO: MdioPin, + MDC: MdcPin, +{ + /// Create a new EthernetMAC with owned MDIO and MDC pins. + /// + /// To interact with a connected Phy, use the `read` and `write` functions. + /// + /// Functionality for interacting with PHYs from the `ieee802_3_miim` crate + /// is available. + pub fn new(eth_mac: EthernetMAC, mdio: MDIO, mdc: MDC) -> Self { + Self { eth_mac, mdio, mdc } + } + + /// Release the owned MDIO and MDC pins, and return an EthernetMAC that + /// has to borrow the MDIO and MDC pins. + pub fn release_pins(self) -> (EthernetMAC, MDIO, MDC) { + (self.eth_mac, self.mdio, self.mdc) + } +} + +impl Deref for EthernetMACWithMii +where + MDIO: MdioPin, + MDC: MdcPin, +{ + type Target = EthernetMAC; + + fn deref(&self) -> &Self::Target { + &self.eth_mac + } +} + +impl DerefMut for EthernetMACWithMii +where + MDIO: MdioPin, + MDC: MdcPin, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.eth_mac + } +} + +impl EthernetMACWithMii +where + MDIO: MdioPin, + MDC: MdcPin, +{ + /// Read MII register `reg` from the PHY at address `phy` + pub fn read(&mut self, phy: u8, reg: u8) -> u16 { + self.eth_mac + .mii(&mut self.mdio, &mut self.mdc) + .read(phy, reg) + } + + /// Write the value `data` to MII register `reg` to the PHY at address `phy` + pub fn write(&mut self, phy: u8, reg: u8, data: u16) { + self.eth_mac + .mii(&mut self.mdio, &mut self.mdc) + .write(phy, reg, data) + } +} + +impl Miim for EthernetMACWithMii +where + MDIO: MdioPin, + MDC: MdcPin, +{ + fn read(&mut self, phy: u8, reg: u8) -> u16 { + self.read(phy, reg) + } + + fn write(&mut self, phy: u8, reg: u8, data: u16) { + self.write(phy, reg, data) + } +} diff --git a/src/mac/mod.rs b/src/mac/mod.rs index 0e7a8b2..0fb78f8 100644 --- a/src/mac/mod.rs +++ b/src/mac/mod.rs @@ -1,12 +1,100 @@ //! Ethernet MAC access and configuration. -use core::ops::{Deref, DerefMut}; - -use crate::{dma::EthernetDMA, hal::rcc::Clocks, peripherals::ETHERNET_MAC, stm32::ETHERNET_MMC}; +use crate::{dma::EthernetDMA, peripherals::ETHERNET_MAC, Clocks}; mod miim; pub use miim::*; +pub(crate) struct MacParts { + pub eth_mac: ETHERNET_MAC, + #[cfg(feature = "f-series")] + pub eth_mmc: crate::stm32::ETHERNET_MMC, +} + +impl MacParts { + fn enable_promicious_mode(&self) { + let Self { eth_mac, .. } = self; + + #[cfg(feature = "f-series")] + let (mac_filter, flow_control) = (ð_mac.macffr, ð_mac.macfcr); + #[cfg(feature = "stm32h7xx-hal")] + let (mac_filter, flow_control) = (ð_mac.macpfr, ð_mac.macqtx_fcr); + + // Frame filter register + mac_filter.modify(|_, w| { + // Receive All + w.ra() + .set_bit() + // Promiscuous mode + .pm() + .set_bit() + }); + // Flow Control Register + flow_control.modify(|_, w| { + // Pause time + #[allow(unused_unsafe)] + unsafe { + w.pt().bits(0x100) + } + }); + } + + fn disable_mmc_interrupts(&self) { + #[cfg(feature = "f-series")] + { + let eth_mmc = &self.eth_mmc; + // Disable all MMC RX interrupts + eth_mmc + .mmcrimr + .write(|w| w.rgufm().set_bit().rfaem().set_bit().rfcem().set_bit()); + + // Disable all MMC TX interrupts + eth_mmc + .mmctimr + .write(|w| w.tgfm().set_bit().tgfmscm().set_bit().tgfscm().set_bit()); + + // Fix incorrect TGFM bit position until https://github.com/stm32-rs/stm32-rs/pull/689 + // is released and used by HALs. + eth_mmc + .mmctimr + .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 21)) }); + } + + #[cfg(feature = "stm32h7xx-hal")] + { + let eth_mac = &self.eth_mac; + + // Disable all MMC RX interrupts + eth_mac.mmc_rx_interrupt_mask.write(|w| { + w.rxlpitrcim() + .set_bit() + .rxlpiuscim() + .set_bit() + .rxucgpim() + .set_bit() + .rxalgnerpim() + .set_bit() + .rxcrcerpim() + .set_bit() + }); + + // Disable all MMC TX interrupts + eth_mac.mmc_tx_interrupt_mask.write(|w| { + w.txlpitrcim() + .set_bit() + .txlpiuscim() + .set_bit() + .txgpktim() + .set_bit() + .txmcolgpim() + .set_bit() + .txscolgpim() + .set_bit() + }); + } + } +} + /// Speeds at which this MAC can be configured #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -21,6 +109,7 @@ pub enum Speed { FullDuplexBase100Tx, } +use self::consts::*; mod consts { /* For HCLK 60-100 MHz */ pub const ETH_MACMIIAR_CR_HCLK_DIV_42: u8 = 0; @@ -30,10 +119,12 @@ mod consts { pub const ETH_MACMIIAR_CR_HCLK_DIV_16: u8 = 2; /* For HCLK 35-60 MHz */ pub const ETH_MACMIIAR_CR_HCLK_DIV_26: u8 = 3; - /* For HCLK over 150 MHz */ + /* For HCLK 150-250 MHz */ pub const ETH_MACMIIAR_CR_HCLK_DIV_102: u8 = 4; + /* For HCLK over 250 MHz */ + #[cfg(feature = "stm32h7xx-hal")] + pub const ETH_MACMIIAR_CR_HCLK_DIV_124: u8 = 5; } -use self::consts::*; /// HCLK must be at least 25MHz to use the ethernet peripheral. /// This (empty) struct is returned to indicate that it is not set @@ -60,15 +151,16 @@ impl EthernetMAC { /// Additionally, an `impl` of the [`ieee802_3_miim::Miim`] trait is available /// for PHY communication. pub(crate) fn new( - eth_mac: ETHERNET_MAC, - eth_mmc: ETHERNET_MMC, - clocks: Clocks, + parts: MacParts, + #[allow(unused)] clocks: Clocks, initial_speed: Speed, // Note(_dma): this field exists to ensure that the MAC is not // initialized before the DMA. If MAC is started before the DMA, // it doesn't work. _dma: &EthernetDMA, ) -> Result { + let eth_mac = &parts.eth_mac; + let clock_frequency = clocks.hclk().to_Hz(); let clock_range = match clock_frequency { @@ -77,26 +169,31 @@ impl EthernetMAC { 35_000_000..=59_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_26, 60_000_000..=99_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_42, 100_000_000..=149_999_999 => ETH_MACMIIAR_CR_HCLK_DIV_62, + #[cfg(feature = "stm32h7xx-hal")] + 150_000_000..=250_000_000 => ETH_MACMIIAR_CR_HCLK_DIV_102, + #[cfg(feature = "stm32h7xx-hal")] + _ => ETH_MACMIIAR_CR_HCLK_DIV_124, + #[cfg(feature = "f-series")] _ => ETH_MACMIIAR_CR_HCLK_DIV_102, }; + #[cfg(feature = "f-series")] // Set clock range in MAC MII address register eth_mac .macmiiar .modify(|_, w| unsafe { w.cr().bits(clock_range) }); + #[cfg(feature = "stm32h7xx-hal")] + eth_mac.macmdioar.modify(|_, w| w.cr().variant(clock_range)); + // Configuration Register eth_mac.maccr.modify(|_, w| { // CRC stripping for Type frames. STM32F1xx do not have this bit. #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] let w = w.cstf().set_bit(); - // Fast Ethernet speed - w.fes() - .set_bit() - // Duplex mode - .dm() - .set_bit() + #[cfg(feature = "f-series")] + let w = w // IPv4 checksum offload .ipco() .set_bit() @@ -105,7 +202,21 @@ impl EthernetMAC { .set_bit() // Retry disable in half-duplex mode .rd() + .set_bit(); + + #[cfg(feature = "stm32h7xx-hal")] + let w = w + // IPv4 checksum offload + .acs() + .set_bit() + // Automatic pad/CRC stripping + .ipc() .set_bit() + // Retry disable in half-duplex mode + .dr() + .set_bit(); + + w // Receiver enable .re() .set_bit() @@ -114,45 +225,50 @@ impl EthernetMAC { .set_bit() }); - // Frame filter register - eth_mac.macffr.modify(|_, w| { - // Receive All - w.ra() - .set_bit() - // Promiscuous mode - .pm() - .set_bit() - }); - - // Flow Control Register - eth_mac.macfcr.modify(|_, w| { - // Pause time - w.pt().bits(0x100) - }); - - // Disable all MMC RX interrupts - eth_mmc - .mmcrimr - .write(|w| w.rgufm().set_bit().rfaem().set_bit().rfcem().set_bit()); - - // Disable all MMC TX interrupts - eth_mmc - .mmctimr - .write(|w| w.tgfm().set_bit().tgfmscm().set_bit().tgfscm().set_bit()); + parts.enable_promicious_mode(); + parts.disable_mmc_interrupts(); - // Fix incorrect TGFM bit position until https://github.com/stm32-rs/stm32-rs/pull/689 - // is released and used by HALs. - eth_mmc - .mmctimr - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << 21)) }); + #[cfg(feature = "stm32h7xx-hal")] + // On H7 parts, the target timestamp interrupt + // is not broken, so we can enable it unconditionally. + { + parts.eth_mac.macier.modify(|_, w| w.tsie().set_bit()); + } - let mut me = Self { eth_mac }; + let mut me = Self { + eth_mac: parts.eth_mac, + }; me.set_speed(initial_speed); Ok(me) } + /// Set the Ethernet Speed at which the MAC communicates + /// + /// Note that this does _not_ affect the PHY in any way. To + /// configure the PHY, use [`EthernetMACWithMii`] (see: [`Self::with_mii`]) + /// or [`Stm32Mii`] (see: [`Self::mii`]) + pub fn set_speed(&mut self, speed: Speed) { + self.eth_mac.maccr.modify(|_, w| match speed { + Speed::HalfDuplexBase10T => w.fes().clear_bit().dm().clear_bit(), + Speed::FullDuplexBase10T => w.fes().clear_bit().dm().set_bit(), + Speed::HalfDuplexBase100Tx => w.fes().set_bit().dm().clear_bit(), + Speed::FullDuplexBase100Tx => w.fes().set_bit().dm().set_bit(), + }); + } + + /// Get the Ethernet Speed at which the MAC communicates + pub fn get_speed(&self) -> Speed { + let cr = self.eth_mac.maccr.read(); + match (cr.fes().bit_is_set(), cr.dm().bit_is_set()) { + (false, false) => Speed::HalfDuplexBase10T, + (false, true) => Speed::FullDuplexBase10T, + (true, false) => Speed::HalfDuplexBase100Tx, + (true, true) => Speed::FullDuplexBase100Tx, + } + } + /// Borrow access to the MAC's SMI. /// /// Allows for controlling and monitoring any PHYs that may be accessible via the MDIO/MDC @@ -178,38 +294,10 @@ impl EthernetMAC { MDIO: MdioPin, MDC: MdcPin, { - EthernetMACWithMii { - eth_mac: self, - mdio, - mdc, - } - } - - /// Set the Ethernet Speed at which the MAC communicates - /// - /// Note that this does _not_ affect the PHY in any way. To - /// configure the PHY, use [`EthernetMACWithMii`] (see: [`Self::with_mii`]) - /// or [`Stm32Mii`] (see: [`Self::mii`]) - pub fn set_speed(&mut self, speed: Speed) { - self.eth_mac.maccr.modify(|_, w| match speed { - Speed::HalfDuplexBase10T => w.fes().clear_bit().dm().clear_bit(), - Speed::FullDuplexBase10T => w.fes().clear_bit().dm().set_bit(), - Speed::HalfDuplexBase100Tx => w.fes().set_bit().dm().clear_bit(), - Speed::FullDuplexBase100Tx => w.fes().set_bit().dm().set_bit(), - }); - } - - /// Get the Ethernet Speed at which the MAC communicates - pub fn get_speed(&self) -> Speed { - let cr = self.eth_mac.maccr.read(); - match (cr.fes().bit_is_set(), cr.dm().bit_is_set()) { - (false, false) => Speed::HalfDuplexBase10T, - (false, true) => Speed::FullDuplexBase10T, - (true, false) => Speed::HalfDuplexBase100Tx, - (true, true) => Speed::FullDuplexBase100Tx, - } + EthernetMACWithMii::new(self, mdio, mdc) } + #[cfg(all(feature = "ptp", feature = "f-series"))] pub(crate) fn mask_timestamp_trigger_interrupt() { // SAFETY: MACIMR only receives atomic writes. let mac = &unsafe { &*ETHERNET_MAC::ptr() }; @@ -217,103 +305,10 @@ impl EthernetMAC { } // NOTE(allow): only used on F4 and F7 - #[allow(dead_code)] + #[cfg(all(feature = "ptp", feature = "f-series"))] pub(crate) fn unmask_timestamp_trigger_interrupt() { // SAFETY: MACIMR only receives atomic writes. - let macimr = &unsafe { &*ETHERNET_MAC::ptr() }.macimr; - macimr.write(|w| w.tstim().clear_bit()); - } -} - -/// Ethernet media access control (MAC) with owned MII -/// -/// This version of the struct owns it's MII pins, -/// allowing it to be used directly, instead of requiring -/// that a [`Miim`] is created. -pub struct EthernetMACWithMii -where - MDIO: MdioPin, - MDC: MdcPin, -{ - pub(crate) eth_mac: EthernetMAC, - mdio: MDIO, - mdc: MDC, -} - -impl EthernetMACWithMii -where - MDIO: MdioPin, - MDC: MdcPin, -{ - /// Create a new EthernetMAC with owned MDIO and MDC pins. - /// - /// To interact with a connected Phy, use the `read` and `write` functions. - /// - /// Functionality for interacting with PHYs from the `ieee802_3_miim` crate - /// is available. - pub fn new(eth_mac: EthernetMAC, mdio: MDIO, mdc: MDC) -> Self { - Self { eth_mac, mdio, mdc } - } - - /// Release the owned MDIO and MDC pins, and return an EthernetMAC that - /// has to borrow the MDIO and MDC pins. - pub fn release_pins(self) -> (EthernetMAC, MDIO, MDC) { - (self.eth_mac, self.mdio, self.mdc) - } -} - -impl Deref for EthernetMACWithMii -where - MDIO: MdioPin, - MDC: MdcPin, -{ - type Target = EthernetMAC; - - fn deref(&self) -> &Self::Target { - &self.eth_mac - } -} - -impl DerefMut for EthernetMACWithMii -where - MDIO: MdioPin, - MDC: MdcPin, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.eth_mac - } -} - -impl EthernetMACWithMii -where - MDIO: MdioPin, - MDC: MdcPin, -{ - /// Read MII register `reg` from the PHY at address `phy` - pub fn read(&mut self, phy: u8, reg: u8) -> u16 { - self.eth_mac - .mii(&mut self.mdio, &mut self.mdc) - .read(phy, reg) - } - - /// Write the value `data` to MII register `reg` to the PHY at address `phy` - pub fn write(&mut self, phy: u8, reg: u8, data: u16) { - self.eth_mac - .mii(&mut self.mdio, &mut self.mdc) - .write(phy, reg, data) - } -} - -impl miim::Miim for EthernetMACWithMii -where - MDIO: MdioPin, - MDC: MdcPin, -{ - fn read(&mut self, phy: u8, reg: u8) -> u16 { - self.read(phy, reg) - } - - fn write(&mut self, phy: u8, reg: u8, data: u16) { - self.write(phy, reg, data) + let mac = &unsafe { &*ETHERNET_MAC::ptr() }; + mac.macimr.write(|w| w.tstim().clear_bit()); } } diff --git a/src/peripherals.rs b/src/peripherals.rs index 3d4bec8..b9fdd9e 100644 --- a/src/peripherals.rs +++ b/src/peripherals.rs @@ -4,6 +4,9 @@ #[cfg(any(feature = "stm32f107", feature = "stm32f7xx-hal"))] pub use crate::hal::pac::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_PTP}; +#[cfg(feature = "stm32h7xx-hal")] +pub use crate::hal::pac::{ETHERNET_DMA, ETHERNET_MAC}; + #[cfg(feature = "stm32f4xx-hal")] pub use pac_override_impl::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_PTP}; @@ -193,6 +196,7 @@ mod pac_override_impl { #[doc = r"Return the pointer to the register block"] #[inline(always)] + #[cfg_attr(not(feature = "ptp"), allow(unused))] pub const fn ptr() -> *const MacRegisterBlock { Self::PTR } diff --git a/src/ptp/mod.rs b/src/ptp/mod.rs index 6551e9f..c4ef7b7 100644 --- a/src/ptp/mod.rs +++ b/src/ptp/mod.rs @@ -2,7 +2,10 @@ //! //! See [`EthernetPTP`] for a more details. -use crate::{dma::EthernetDMA, hal::rcc::Clocks, mac::EthernetMAC, peripherals::ETHERNET_PTP}; +use crate::{dma::EthernetDMA, Clocks}; + +#[cfg(feature = "f-series")] +use crate::{mac::EthernetMAC, peripherals::ETHERNET_PTP}; mod timestamp; pub use timestamp::Timestamp; @@ -44,6 +47,7 @@ pub use pps_pin::PPSPin; /// /// [`NonZeroU8`]: core::num::NonZeroU8 pub struct EthernetPTP { + #[cfg(feature = "f-series")] eth_ptp: ETHERNET_PTP, } @@ -65,14 +69,23 @@ impl EthernetPTP { (stssi, tsa) } + #[cfg(feature = "stm32h7xx-hal")] + /// # Safety + /// The reference to the registerblock obtained using this function + /// must _only_ be used to change strictly PTP related registers. + unsafe fn mac(&self) -> &crate::stm32::ethernet_mac::RegisterBlock { + &*crate::peripherals::ETHERNET_MAC::ptr() + } + pub(crate) fn new( - eth_ptp: ETHERNET_PTP, + #[cfg(feature = "f-series")] eth_ptp: ETHERNET_PTP, clocks: Clocks, // Note(_dma): this field exists to ensure that the PTP is not // initialized before the DMA. If PTP is started before the DMA, // it doesn't work. _dma: &EthernetDMA, ) -> Self { + #[cfg(feature = "f-series")] // Mask timestamp interrupt register EthernetMAC::mask_timestamp_trigger_interrupt(); @@ -80,21 +93,54 @@ impl EthernetPTP { let (stssi, tsa) = Self::calculate_regs(hclk); - // Setup PTP timestamping in fine mode. - eth_ptp.ptptscr.write(|w| { - // Enable snapshots for all frames. - #[cfg(not(feature = "stm32f1xx-hal"))] - let w = w.tssarfe().set_bit(); + #[cfg(feature = "f-series")] + let mut me = { + // Setup PTP timestamping in fine mode. + eth_ptp.ptptscr.write(|w| { + // Enable snapshots for all frames. + #[cfg(not(feature = "stm32f1xx-hal"))] + let w = w.tssarfe().set_bit(); - w.tse().set_bit().tsfcu().set_bit() - }); + w.tse().set_bit().tsfcu().set_bit() + }); - // Set up subsecond increment - eth_ptp - .ptpssir - .write(|w| unsafe { w.stssi().bits(stssi.raw() as u8) }); + // Set up subsecond increment + eth_ptp + .ptpssir + .write(|w| unsafe { w.stssi().bits(stssi.raw() as u8) }); - let mut me = Self { eth_ptp }; + Self { eth_ptp } + }; + + #[cfg(feature = "stm32h7xx-hal")] + let mut me = { + let me = Self {}; + + // SAFETY: we only write to `mactscr` (timestamp control register) + let mac = unsafe { me.mac() }; + + mac.mactscr.modify(|_, w| { + w + // Enable timestamp snapshots for all frames + .tsenall() + .set_bit() + // Enable fine-grain update mode + .tscfupdt() + .set_bit() + // Enable all timestamps + .tsena() + .set_bit() + // Tell MAC to overwrite non-read timestamps + .txtsstsm() + .set_bit() + }); + + // Set up the subsecond increment + mac.macssir + .write(|w| unsafe { w.ssinc().bits(stssi.raw() as u8) }); + + me + }; me.set_addend(tsa); me.set_time(Timestamp::new_unchecked(false, 0, 0)); @@ -104,49 +150,104 @@ impl EthernetPTP { /// Get the configured subsecond increment. pub fn subsecond_increment(&self) -> Subseconds { - Subseconds::new_unchecked(self.eth_ptp.ptpssir.read().stssi().bits() as u32) + #[cfg(feature = "f-series")] + return Subseconds::new_unchecked(self.eth_ptp.ptpssir.read().stssi().bits() as u32); + #[cfg(feature = "stm32h7xx-hal")] + // SAFETY: we only read `macssir` (subsecond register). + return Subseconds::new_unchecked(unsafe { + self.mac().macssir.read().ssinc().bits() as u32 + }); } /// Get the currently configured PTP clock addend. pub fn addend(&self) -> u32 { - self.eth_ptp.ptptsar.read().bits() + #[cfg(feature = "f-series")] + return self.eth_ptp.ptptsar.read().bits(); + #[cfg(feature = "stm32h7xx-hal")] + // SAFETY: we only read `mactsar` (timestamp addend register). + return unsafe { self.mac().mactsar.read().bits() }; } /// Set the PTP clock addend. #[inline(always)] pub fn set_addend(&mut self, rate: u32) { + #[cfg(feature = "f-series")] let ptp = &self.eth_ptp; + #[cfg(feature = "f-series")] ptp.ptptsar.write(|w| unsafe { w.bits(rate) }); - #[cfg(feature = "stm32f1xx-hal")] + #[cfg(feature = "f-series")] { - while ptp.ptptscr.read().tsaru().bit_is_set() {} - ptp.ptptscr.modify(|_, w| w.tsaru().set_bit()); - while ptp.ptptscr.read().tsaru().bit_is_set() {} + #[cfg(feature = "stm32f1xx-hal")] + { + while ptp.ptptscr.read().tsaru().bit_is_set() {} + ptp.ptptscr.modify(|_, w| w.tsaru().set_bit()); + while ptp.ptptscr.read().tsaru().bit_is_set() {} + } + + #[cfg(not(feature = "stm32f1xx-hal"))] + { + while ptp.ptptscr.read().ttsaru().bit_is_set() {} + ptp.ptptscr.modify(|_, w| w.ttsaru().set_bit()); + while ptp.ptptscr.read().ttsaru().bit_is_set() {} + } } - #[cfg(not(feature = "stm32f1xx-hal"))] + #[cfg(feature = "stm32h7xx-hal")] { - while ptp.ptptscr.read().ttsaru().bit_is_set() {} - ptp.ptptscr.modify(|_, w| w.ttsaru().set_bit()); - while ptp.ptptscr.read().ttsaru().bit_is_set() {} + // SAFETY: we only write to `mactsar` (timestamp addend register) + // and `mactscr` (timestamp control register) + let (mactsar, mactscr) = unsafe { + let mac = self.mac(); + (&mac.mactsar, &mac.mactscr) + }; + + mactsar.write(|w| unsafe { w.tsar().bits(rate) }); + + while mactscr.read().tsaddreg().bit_is_set() {} + mactscr.modify(|_, w| w.tsaddreg().set_bit()); + while mactscr.read().tsaddreg().bit_is_set() {} } } /// Set the current time. pub fn set_time(&mut self, time: Timestamp) { - let ptp = &self.eth_ptp; - let seconds = time.seconds(); + // TODO(stm32h7): figure out if the time being signed + // means that we have a two's complement number or not + // (the RM makes it read as though it may be). let subseconds = time.subseconds_signed(); - ptp.ptptshur.write(|w| unsafe { w.bits(seconds) }); - ptp.ptptslur.write(|w| unsafe { w.bits(subseconds) }); + #[cfg(feature = "f-series")] + { + let ptp = &self.eth_ptp; + + ptp.ptptshur.write(|w| unsafe { w.bits(seconds) }); + ptp.ptptslur.write(|w| unsafe { w.bits(subseconds) }); + + // Initialise timestamp + while ptp.ptptscr.read().tssti().bit_is_set() {} + ptp.ptptscr.modify(|_, w| w.tssti().set_bit()); + while ptp.ptptscr.read().tssti().bit_is_set() {} + } - // Initialise timestamp - while ptp.ptptscr.read().tssti().bit_is_set() {} - ptp.ptptscr.modify(|_, w| w.tssti().set_bit()); - while ptp.ptptscr.read().tssti().bit_is_set() {} + #[cfg(feature = "stm32h7xx-hal")] + { + // SAFETY: we only write to `mactscr` (timestamp control register), `macstsur` + // (timestamp update seconds register) and `macstnur` (timestmap update subsecond/nanosecond + // register) + let (mactscr, macstsur, macstnur) = unsafe { + let mac = self.mac(); + (&mac.mactscr, &mac.macstsur, &mac.macstnur) + }; + + macstsur.write(|w| unsafe { w.bits(seconds) }); + macstnur.write(|w| unsafe { w.bits(subseconds) }); + + while mactscr.read().tsinit().bit_is_set() {} + mactscr.modify(|_, w| w.tsinit().set_bit()); + while mactscr.read().tsinit().bit_is_set() {} + } } /// Add the provided time to the current time, atomically. @@ -154,32 +255,70 @@ impl EthernetPTP { /// If `time` is negative, it will instead be subtracted from the /// system time. pub fn update_time(&mut self, time: Timestamp) { - let ptp = &self.eth_ptp; - let seconds = time.seconds(); let subseconds = time.subseconds_signed(); - ptp.ptptshur.write(|w| unsafe { w.bits(seconds) }); - ptp.ptptslur.write(|w| unsafe { w.bits(subseconds) }); + #[cfg(feature = "f-series")] + { + let ptp = &self.eth_ptp; - // Add timestamp to global time + ptp.ptptshur.write(|w| unsafe { w.bits(seconds) }); + ptp.ptptslur.write(|w| unsafe { w.bits(subseconds) }); - let read_status = || { - let scr = ptp.ptptscr.read(); - scr.tsstu().bit_is_set() || scr.tssti().bit_is_set() - }; + // Add timestamp to global time - while read_status() {} - ptp.ptptscr.modify(|_, w| w.tsstu().set_bit()); - while ptp.ptptscr.read().tsstu().bit_is_set() {} + let read_status = || { + let scr = ptp.ptptscr.read(); + scr.tsstu().bit_is_set() || scr.tssti().bit_is_set() + }; + + while read_status() {} + ptp.ptptscr.modify(|_, w| w.tsstu().set_bit()); + while ptp.ptptscr.read().tsstu().bit_is_set() {} + } + + #[cfg(feature = "stm32h7xx-hal")] + { + // SAFETY: we only write to `mactscr` (timestamp control register), `macstsur` + // (timestamp update seconds register) and `macstnur` (timestmap update subsecond/nanosecond + // register) + let (mactscr, macstsur, macstnur) = unsafe { + let mac = self.mac(); + (&mac.mactscr, &mac.macstsur, &mac.macstnur) + }; + + macstsur.write(|w| unsafe { w.bits(seconds) }); + macstnur.write(|w| unsafe { w.bits(subseconds) }); + + while mactscr.read().tsupdt().bit_is_set() {} + mactscr.modify(|_, w| w.tsupdt().set_bit()); + while mactscr.read().tsupdt().bit_is_set() {} + } } /// Get the current time. pub fn get_time(&self) -> Timestamp { let try_read_time = || { - let seconds = self.eth_ptp.ptptshr.read().bits(); - let subseconds = self.eth_ptp.ptptslr.read().bits(); - let seconds_after = self.eth_ptp.ptptshr.read().bits(); + #[cfg(feature = "f-series")] + let (seconds, subseconds, seconds_after) = { + let seconds = self.eth_ptp.ptptshr.read().bits(); + let subseconds = self.eth_ptp.ptptslr.read().bits(); + let seconds2 = self.eth_ptp.ptptshr.read().bits(); + (seconds, subseconds, seconds2) + }; + + #[cfg(feature = "stm32h7xx-hal")] + let (seconds, subseconds, seconds_after) = { + let (macstsr, macstnr) = unsafe { + let mac = self.mac(); + (&mac.macstsr, &mac.macstnr) + }; + + let seconds = macstsr.read().bits(); + let subseconds = macstnr.read().bits(); + let seconds2 = macstsr.read().bits(); + (seconds, subseconds, seconds2) + }; if seconds == seconds_after { Ok(Timestamp::from_parts(seconds, subseconds)) @@ -207,17 +346,34 @@ impl EthernetPTP { /// Setting and configuring target time interrupts on the STM32F107 does not /// make any sense: we can generate the interrupt, but it is impossible to /// clear the flag as the register required to do so does not exist. -#[cfg(not(feature = "stm32f1xx-hal"))] +#[cfg(all(not(feature = "stm32f1xx-hal")))] impl EthernetPTP { /// Configure the target time. fn set_target_time(&mut self, timestamp: Timestamp) { let (high, low) = (timestamp.seconds(), timestamp.subseconds_signed()); - self.eth_ptp - .ptptthr - .write(|w| unsafe { w.ttsh().bits(high) }); - self.eth_ptp - .ptpttlr - .write(|w| unsafe { w.ttsl().bits(low) }); + + #[cfg(feature = "f-series")] + { + self.eth_ptp + .ptptthr + .write(|w| unsafe { w.ttsh().bits(high) }); + self.eth_ptp + .ptpttlr + .write(|w| unsafe { w.ttsl().bits(low) }); + } + + #[cfg(feature = "stm32h7xx-hal")] + { + // SAFETY: we only write to `ppsttsr` (PPS target time seconds register) and + // `ppsttnr` (PPS target time subseconds register) + let (ppsttsr, ppsttnr) = unsafe { + let mac = self.mac(); + (&mac.macppsttsr, &mac.macppsttnr) + }; + + ppsttsr.write(|w| unsafe { w.bits(high) }); + ppsttnr.write(|w| unsafe { w.bits(low) }); + } } /// Configure the target time interrupt. @@ -226,19 +382,36 @@ impl EthernetPTP { /// interrupt to detect (and clear) the correct status bits. pub fn configure_target_time_interrupt(&mut self, timestamp: Timestamp) { self.set_target_time(timestamp); - self.eth_ptp.ptptscr.modify(|_, w| w.tsite().set_bit()); - EthernetMAC::unmask_timestamp_trigger_interrupt(); + #[cfg(feature = "f-series")] + { + self.eth_ptp.ptptscr.modify(|_, w| w.tsite().set_bit()); + EthernetMAC::unmask_timestamp_trigger_interrupt(); + } } /// Returns a boolean indicating whether or not the interrupt /// was caused by a Timestamp trigger and clears the interrupt /// flag. pub fn interrupt_handler(&mut self) -> bool { - let is_tsint = self.eth_ptp.ptptssr.read().tsttr().bit_is_set(); - if is_tsint { - self.eth_ptp.ptptscr.modify(|_, w| w.tsite().clear_bit()); - EthernetMAC::mask_timestamp_trigger_interrupt(); - } + #[cfg(feature = "f-series")] + let is_tsint = { + let is_tsint = self.eth_ptp.ptptssr.read().tsttr().bit_is_set(); + if is_tsint { + self.eth_ptp.ptptscr.modify(|_, w| w.tsite().clear_bit()); + EthernetMAC::mask_timestamp_trigger_interrupt(); + } + is_tsint + }; + + #[cfg(feature = "stm32h7xx-hal")] + let is_tsint = { + // SAFETY: we only write to `mactssr` (Timestamp Status register) + let mactssr = unsafe { &self.mac().mactssr }; + + // Reading the bit clears it, and deasserts the interrupt. + mactssr.read().tstargt0().bit_is_set() + }; + is_tsint } @@ -252,10 +425,19 @@ impl EthernetPTP { // SAFETY: we atomically write to the PTPPPSCR register, which is // not read or written to anywhere else. The SVD files are incorrectly // saying that the bits in this register are read-only. + #[cfg(feature = "f-series")] unsafe { let ptpppscr = self.eth_ptp.ptpppscr.as_ptr() as *mut u32; core::ptr::write_volatile(ptpppscr, pps_freq as u32); } + + #[cfg(feature = "stm32h7xx-hal")] + { + // SAFETY: we only access and modify the `macppscr` (PPS Control register) + let macppscr = unsafe { &self.mac().macppscr }; + + macppscr.modify(|_, w| w.ppsctrl().variant(pps_freq)); + } } } @@ -268,7 +450,7 @@ mod test { // with the provided clock speeds. #[test] fn hclk_to_regs() { - for hclk_hz in (25..180).map(|v| v * 1_000_000) { + for hclk_hz in (25..480).map(|v| v * 1_000_000) { let (stssi, tsa) = EthernetPTP::calculate_regs(hclk_hz); let stssi = stssi.raw() as f64; diff --git a/src/ptp/pps_pin.rs b/src/ptp/pps_pin.rs index 99a05b9..bc68810 100644 --- a/src/ptp/pps_pin.rs +++ b/src/ptp/pps_pin.rs @@ -63,3 +63,10 @@ mod impl_pps_pin { } } } + +#[cfg(feature = "stm32h7xx-hal")] +mod impl_pps_pin { + use crate::hal::gpio::{Alternate, Output, PushPull, PB5, PG8}; + + impl_pps_pin!([PG8>, PG8>], [PB5>, PB5>]); +} diff --git a/src/ptp/timestamp.rs b/src/ptp/timestamp.rs index 3a869a8..c9ec26c 100644 --- a/src/ptp/timestamp.rs +++ b/src/ptp/timestamp.rs @@ -1,5 +1,3 @@ -use crate::dma::desc::Descriptor; - use super::Subseconds; /// A timestamp produced by the PTP periperhal @@ -96,28 +94,6 @@ impl Timestamp { Timestamp::new_unchecked(negative, high, subseconds) } - - /// Create a timestamp from the given descriptor - pub fn from_descriptor(desc: &Descriptor) -> Option { - #[cfg(not(feature = "stm32f1xx-hal"))] - { - let (high, low) = { (desc.read(7), desc.read(6)) }; - Some(Self::from_parts(high, low)) - } - - #[cfg(feature = "stm32f1xx-hal")] - { - let (high, low) = { (desc.read(3), desc.read(2)) }; - - // The timestamp registers are written to all-ones if - // timestamping was no succesfull - if high == 0xFFFF_FFFF && low == 0xFFFF_FFFF { - None - } else { - Some(Self::from_parts(high, low)) - } - } - } } impl core::ops::Add for Timestamp { diff --git a/src/setup.rs b/src/setup.rs index 7397bbd..459abac 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -18,7 +18,7 @@ use stm32f4xx_hal::{ pac::{RCC, SYSCFG}, }; -#[cfg(feature = "stm32f7xx-hal")] +#[cfg(any(feature = "stm32f7xx-hal", feature = "stm32h7xx-hal"))] use cortex_m::interrupt; #[cfg(feature = "stm32f7xx-hal")] @@ -34,16 +34,58 @@ use stm32f7xx_hal::{ pac::{RCC, SYSCFG}, }; +#[cfg(not(feature = "stm32h7xx-hal"))] use crate::{ dma::EthernetDMA, stm32::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_MMC}, }; +#[cfg(feature = "stm32h7xx-hal")] +#[allow(unused_imports)] +use crate::{ + dma::EthernetDMA, + hal::gpio::{ + gpioa::{PA0, PA1, PA2, PA3, PA7}, + gpiob::{PB0, PB1, PB10, PB11, PB12, PB13, PB5, PB8}, + gpioc::{PC1, PC2, PC3, PC4, PC5}, + gpiog::{PG11, PG13}, + Input, + Speed::VeryHigh, + }, + stm32::{ETHERNET_DMA, ETHERNET_MAC, ETHERNET_MTL, RCC, SYSCFG}, +}; + #[cfg(feature = "ptp")] -use crate::{ptp::EthernetPTP, stm32::ETHERNET_PTP}; +use crate::ptp::EthernetPTP; // Enable syscfg and ethernet clocks. Reset the Ethernet MAC. pub(crate) fn setup() { + #[cfg(feature = "stm32h7xx-hal")] + interrupt::free(|_| { + // SAFETY: we only perform interrupt-free modifications of RCC. + let rcc = unsafe { &*RCC::ptr() }; + let syscfg = unsafe { &*SYSCFG::ptr() }; + + rcc.apb4enr.modify(|_, w| w.syscfgen().set_bit()); + rcc.ahb1enr.modify(|_, w| { + w.eth1macen() + .set_bit() + .eth1rxen() + .set_bit() + .eth1txen() + .set_bit() + }); + + // Select RMII mode + // + // SAFETY: this is the correct value for RMII mode. + syscfg.pmcr.modify(|_, w| unsafe { w.epis().bits(0b100) }); + + // Reset pulse to MAC. + rcc.ahb1rstr.modify(|_, w| w.eth1macrst().set_bit()); + rcc.ahb1rstr.modify(|_, w| w.eth1macrst().clear_bit()); + }); + #[cfg(feature = "stm32f4xx-hal")] unsafe { const SYSCFG_BIT: u8 = 14; @@ -76,6 +118,7 @@ pub(crate) fn setup() { bb::set(&rcc.ahb1rstr, ETH_MAC_BIT); bb::clear(&rcc.ahb1rstr, ETH_MAC_BIT); } + #[cfg(feature = "stm32f7xx-hal")] //stm32f7xx-hal does not currently have bitbanding interrupt::free(|_| unsafe { @@ -189,33 +232,16 @@ pub trait AlternateVeryHighSpeed { #[allow(missing_docs)] pub struct PartsIn { pub mac: ETHERNET_MAC, - pub mmc: ETHERNET_MMC, pub dma: ETHERNET_DMA, - #[cfg(feature = "ptp")] - pub ptp: ETHERNET_PTP, -} -#[cfg(feature = "ptp")] -impl From<(ETHERNET_MAC, ETHERNET_MMC, ETHERNET_DMA, ETHERNET_PTP)> for PartsIn { - fn from(value: (ETHERNET_MAC, ETHERNET_MMC, ETHERNET_DMA, ETHERNET_PTP)) -> Self { - Self { - mac: value.0, - mmc: value.1, - dma: value.2, - ptp: value.3, - } - } -} + #[cfg(feature = "f-series")] + pub mmc: ETHERNET_MMC, -#[cfg(not(feature = "ptp"))] -impl From<(ETHERNET_MAC, ETHERNET_MMC, ETHERNET_DMA)> for PartsIn { - fn from(value: (ETHERNET_MAC, ETHERNET_MMC, ETHERNET_DMA)) -> Self { - Self { - mac: value.0, - mmc: value.1, - dma: value.2, - } - } + #[cfg(feature = "stm32h7xx-hal")] + pub mtl: ETHERNET_MTL, + + #[cfg(all(feature = "ptp", feature = "f-series"))] + pub ptp: crate::stm32::ETHERNET_PTP, } /// Access to all configured parts of the ethernet peripheral. @@ -305,6 +331,33 @@ macro_rules! impl_pins { }; } +#[cfg(feature = "stm32h7xx-hal")] +impl_pins!( + RmiiRefClk: [ + PA1, + ], + RmiiCrsDv: [ + PA7, + ], + RmiiTxEN: [ + PB11, + PG11, + ], + RmiiTxD0: [ + PB12, + PG13, + ], + RmiiTxD1: [ + PB13, + ], + RmiiRxD0: [ + PC4, + ], + RmiiRxD1: [ + PC5, + ], +); + #[cfg(any(feature = "stm32f4xx-hal", feature = "stm32f7xx-hal"))] impl_pins!( RmiiRefClk: [